Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rspec & Capybara: Setting focus to an element using jQuery doesn't apply `:focus` CSS

I have jump links for blind and keyboard users in my webpage which are moved out of the viewport to hide them visually; when they gain focus, they are moved into the viewport.

I want to test this behaviour using RSpec and Capybara, but it somehow doesn't work.

  it 'moves the focus to the navigation when activating the corresponding link', js: true do
    expect(page).not_to have_css '#main:focus'
    page.evaluate_script "$('#jump_to_content > a').focus()"
    click_link 'Jump to content'
    expect(page).to have_css '#main:focus'
  end

The JavaScript in evaluate_script doesn't seem to be executed properly. I tried the same JS ($('#jump_to_content > a').focus()) in the Chrome console, and I'm a bit unsure about the result, because while in the console, the link doesn't appear, but when clicking into the browser view, it appears for a part of a second and disappears again (it disappears because it doesn't have the focus anymore then). In my opinion it should be displayed directly after executing the JS.

This makes me a bit insecure about all this. Any help is highly appreciated.

Update

I meanwhile have come to the conclusion that the behaviour of Chrome is normal: focus is kept in the console, so the element in fact doesn't really receive focus and doesn't show.

I have also come to the conclusion that Capybara doesn't seem to be able to handle focusing an element (and applying :focus styles) properly.

I have created the following CSS rules:

a#jump_to_navigation color: black

&:focus
  color: red

For testing this, I have set up this very specific test:

visit root_path

selector = 'a#jump_to_navigation'

# Initial style
expect(page.evaluate_script('document.activeElement.id')).to eq ''                  # Make sure the link isn't focused yet
expect(page.evaluate_script("$('#{selector}').css('color')")).to  eq 'rgb(0, 0, 0)' # Color initially is black

# Set focus
page.evaluate_script("$('#{selector}').focus()")

# Focused style
expect(page.evaluate_script('document.activeElement.id')).to eq 'jump_to_navigation'  # Make sure the link is focused now
expect(page.evaluate_script("$('#{selector}').css('color')")).to  eq 'rgb(255, 0, 0)' # Color is now red

The test breaks like this:

1) Navigation jump links visually displays them only on focus
   Failure/Error: expect(page.evaluate_script("$('#{selector}').css('color')")).to  eq 'rgb(255, 0, 0)' # Color is now red

     expected: "rgb(255, 0, 0)"
          got: "rgb(0, 0, 0)"

     (compared using ==)

So the focus is set properly, but the :focus styles aren't applied. I guess this is a problem with PhantomJS?

I'd be very happy to find a solution around this problem.

like image 910
Joshua Muheim Avatar asked Mar 23 '15 13:03

Joshua Muheim


2 Answers

This has worked for me to get an element visible on the page first. I've had to use this as a precursor to some drag and drop tests.

page.execute_script("document.getElementById('foo').scrollIntoView()")
like image 93
Phil Avatar answered Oct 20 '22 20:10

Phil


I found a workaround by using JavaScript to add/remove a class (in my case, .sr-only) instead of relying on CSS' :hover mechanism.

visit root_path

selector = '#jump_to_navigation'

expect(page.evaluate_script('document.activeElement.id')).to eq '' # Make sure the link isn't focused yet

# Initial (hidden) position
expect(page.evaluate_script("$('#{selector}')[0].className")).to eq 'sr-only' # Make sure the link has sr-only class

# Set focus
page.evaluate_script("$('#{selector}').focus()")
expect('#' + page.evaluate_script('document.activeElement.id')).to eq selector # Make sure the link is focused now

# Displayed position
expect(page.evaluate_script("$('#{selector}')[0].className")).to eq '' # Make sure the link doesn't have sr-only class

# Remove focus
page.evaluate_script("$('#{selector}').blur()")
expect(page.evaluate_script('document.activeElement.id')).to eq '' # Make sure the link is focused now

# Hidden position again
expect(page.evaluate_script("$('#{selector}')[0].className")).to eq 'sr-only' # Make sure the link has sr-only class

This seems to work. I didn't check whether this way also CSS changes are recognised by Capybara, but for my case, it's enough.

Important: By the way, you should use execute_script instead of evaluate_script, as the latter one has a huge performance impact!

like image 20
Joshua Muheim Avatar answered Oct 20 '22 18:10

Joshua Muheim