Make capybara wait for an element to disappear - ruby-on-rails

After a click on some element I expect a placeholder to disappear and another element to appear after an AJAX call is finished.
find(someLocator).click
expect(page).not_to have_css(disappearingPlaceholderLocator)
expect(page).to have_css(appearingElementLocator)
The first expectation fails though and the AJAX call is not executed.
I think this is somehow related to this SO question but I just can't figure out how.

This works when the expectations are flipped.
find(someLocator).click
expect(page).to have_css(appearingElementLocator)
expect(page).not_to have_css(disappearingPlaceholderLocator)

Related

Problem with feature spec to check form button is disabled after submit

I am writing an RSpec / capybara feature spec to test that, after a form submit button is pressed, the button is disabled and its text is changed to 'ADDING...'.
In the spec, the button is clicked (and if valid, the form is submitted) with:
find(".Booknow-modal-bookingButton button.primaryCTA").click
And the corresponding javascript that is executed is as follows:
$('form#booknow-room').on('submit', function(e) {
// If form is submitting then html5 validation has passed,
// now disable the button to prevent duplicate submissions.
$('form#booknow-room button.primaryCTA').attr('disabled', true).text('Adding...')
})
When the spec runs I can see that this is working. The next line of the spec is:
expect(page).to have_button('ADDING...', disabled: true)
But I think that, by this time, the form submit has redirected to the basket page so it isn't finding the button, so the test fails.
I have also tried the following variants:
expect(find('.Booknow-modal-bookingButton button.primaryCTA').value).to eq 'ADDING...'
expect(".Booknow-modal-bookingButton button.primaryCTA").to have_content("ADDING...")
expect(page).to eq(".Booknow-modal-bookingButton button.primaryCTA", disabled: true)
But none of them work.
The error message returned by rspec is:
Failure/Error: expect(page).to have_button('ADDING...', disabled: true)
expected to find button "ADDING..." but there were no matches
Assuming clicking the button triggers a full form submission rather than an XHR based submission what you're trying to do may not be possible. This is because of the defined behavior for clicking an element in the WebDriver spec - https://w3c.github.io/webdriver/#element-click. 12.4.1 steps 10, 11, and 12 are the relevant parts where driver implementors may attempt to detect if a navigation is occurring and wait for it to complete. This can mean your check for disabled won't happen until after the page has already changed.
The question to ask next is whether this is something that needs to be tested via end-to-end Capybara tests, or whether it could instead be tested in Javascript only tests.
If you do consider it necessary to be tested in your Capybara tests you could try registering a driver with the page load strategy capability set to "none" which should tell the driver not to wait for any navigations and MAY allow the button state to be checked (you'd probably only want to use that driver for this specific test). Another potential option would be to use execute_script to install an event handler that prevents the actual submit from occurring - although every time you modify the running page code in your test you're potentially reducing the usefulness of the test.
I implemented the other "potential solution" which #Thomas Walpole mentioned -- modify the form, so that when you click the submit button it doesn't actually submit. This allows you to validate that the button is disabled without capybara blocking until the form submits.
page.execute_script(
"$('form#booknow-room').attr('action', 'javascript: void(0);');"
)
page.click_button('Submit')
expect(page).to have_button('Adding...', disabled: true)

How to get poltergeist/PhantomJS to delay returning the page to Capybara until AJAX is loaded?

How do I keep PhantomJS from returning the page to Capybara for several seconds to allow AJAX to finish loading? The HTML body I am getting back is:
<!--div class='loading'>PLEASE WAIT WHILE PAGE LOADS</div-->
Which is telling me that PhantomJS returned the page before the page finished loading its AJAX assets. Is there any way I can slow this down? I've tried using the rasterize.js solution but it looks as though any script I add to poltergeist's "phantomjs_options" hash is overlooked.
Using a Capybara method that waits for you, assert that content is on the page that should exist on the page only after AJAX has completed. For example, if the page should say "Page loaded!" only after AJAX has completed, and assuming you're using RSpec, do this:
expect(page).to have_content("Page loaded!")
You can then safely test other things on the page that depend on AJAX having completed.
Capybara methods that wait for specified content to appear include find and find_* methods, within, has_content?/has_no_content?, has_css?/has_no_css?, has_selector?/has_no_selector?, click_*, fill_in, check, uncheck, select and choose. Some Capybara methods don't wait, including current_path, visit, execute_script, evaluate_script and node accessors like text and value. all and first wait if you give them options that give them something specific to wait for (:count, :minimum, :maximum, :between), otherwise they don't. It's quite a minefield. (I took the lists mostly from this blog post.)
There are other ways to wait (sleep (bad), polling the database until a change caused by AJAX appears, etc.) but using Capybara methods that wait for you is the usual and recommended way.
Not sure what you are actually waiting for css wise on the loaded page, but the way I would do it is:
Capybara.using_wait_time(x) { page.find('css_here') }
where x is the number of seconds you want capybara to allow before erroring if it cannot find the css.
expect(page).to have_content "Page loaded!", wait: x

how to make sure a link is clicked with watir-rails

I am using watir to perform some functions
I need to show that a link is actually clicked by the watir-driver, so I'm trying to put somthing to the console if a link is clicked as follow:
puts "profile clicked" if click_my_profile_link(browser)
and:
def self.click_my_profile_link(browser)
browser.div(class: "topbar").when_present
.link(class: 'my-profile').click
end
but nothing gets printed to the console when the link gets clicked. this behaviour is the same for everywhere I have a click action. Another example is:
puts "Next button clicked" if browser.input(id: 'next', value: 'Next').fire_event :click
How can I get some kind of log when there is a click action fired like this? thanks
Procedural methods are called for their action/side-effect and shouldn't be expected to have a response.
If you didn't get an error, then Watir found the element and sent a click command to it.
So maybe this is sufficient expect {click_my_profile_link(browser)}.to_not raise_error
Typically you write tests to verify the result of the link. In this case that would be something like expect(browser.title == "My Profile Title").to be true
You could create an AfterHook that will take an additional action after every navigation and click, but it wouldn't distinguish between the two, and I really don't think that is what you actually want.

How to test in Capybara if an element is clickable?

I need to test in Capybara if on a page an input text box is visible and clickable. I know how to test about visibility, but I couldn't find a method like .clickable or something in that spirit. How does one do this in Capybara?
It sounds like the OP's needs have been met, but for future explorers, here's some tools to tell if something is clickable.
Test whether the thing can be clicked (Rspec && Capybara):
it "is clickable" do
expect{ find('.your_selector').click }
.not_to raise_error(Capybara::Poltergeist::MouseEventFailed)
end
it "isn't clickable" do
expect{ find('.your_selector').click }
.to raise_error(Capybara::Poltergeist::MouseEventFailed)
end
Click it if possible and do something else if not, per Joe Susnick's answer:
clickable = expect{ find('.your_selector').click }.not_to raise_error(Capybara::Poltergeist::MouseEventFailed)
if clickable
plan_a
else
plan_b
end
Putting this as an answer, from comments on the original question.
You don't need to test clickability (i.e getting the input field to blink when clicked on), because I think that's browser dependent. You don't need to do anything to get that functionality. So if it's visible, and an input field, clicking in it will get that result. Otherwise, if it's not visible, it can't be clicked on anyways, so you're fine. I don't think you need to test anything about clickability, just visibility.
An element can be "visible" without being clickable. I'm actually working on finding a workaround now for selecting a button that's behind an opaque layover. I want to be able to say something like:
if find_button("Change Location").visible?
click it
else
something else
end
I haven't found an answer for this that isn't a hacky workaround so any advice would help. Instead of dismissing the question

How can I detect that a dom element has been deleted

I'm just getting started learning rspec and capybara; I see many examples of testing new content being loaded into a dom, but not so much of verifying that an element has been removed.
I'd like to find an element, click the link to make it disappear, and verify that it is gone.
Something like:
item = find(".list-delete")
item.click
page.driver.browser.switch_to.alert.accept
page.should not_have(item)
I would try the following:
item = find(".list-delete")
item.click
page.driver.browser.switch_to.alert.accept
item.reload.should be_nil

Resources