3

Let's suppose you want to submit a form to create a post, wait for it to be created, fetch the post from database and check if the image from the post is being displayed on the next page:

visit url
fill_in the_form
click_on 'Create'
post = Post.first
img = page.find '.post .image'
assert_equal post.file.thumb.url, URI(img[:src]).path

But this way, post will be nil more often than not. Since when you fetch it from database, it might not have been created yet. How do I make sure next page is loaded?

x-yuri
  • 16,722
  • 15
  • 114
  • 161

2 Answers2

3

find will wait up to Capybara.default_max_wait_time seconds for a matching element to appear on the screen. Therefore, to do what you're asking, check for content you expect to be on the next page before loading the item from the DB

visit url
fill_in the_form
click_on 'Create'
img = page.find '.post .image'
post = Post.first
assert_equal post.file.thumb.url, URI(img[:src]).path

If done in this order the page.find will wait for the element to appear on the page which guarantees the Post has already been saved so you can then load it.

Thomas Walpole
  • 48,548
  • 5
  • 64
  • 78
  • I don't like how intentions get concealed in this case. Basically, with `img = ...` line we wait for page load (or for image to appear on the page, if you like), **and** get the image. I don't like how order matters when at first glance it shouldn't. That is, it's not clear that `post = ...` line must follow `img = ...` line. Implicit is not always better than explicit. If I were to follow your way, I'd at least add alias for find (`wait_for_selector`, or something), and split the `img = ...` line into two. – x-yuri Jun 01 '17 at 02:05
  • 1
    @x-yuri Of course order matters - it always matters in interacting with a page. I have to click A before I click B, or I have to wait for a new element to be visible before I can interact with it. You click on 'Create' and then you look for the element '.post .image' on the page to know that your submission has succeeded, That's exactly how a user uses your app. The issue you really have is that you're asserting against database objects in your feature tests, which is usually frowned upon, but that's not what your question was. – Thomas Walpole Jun 01 '17 at 05:47
  • On second thought, you're right in part. I don't care about page reload. What I really care about is post appearing in the browser. Now it happens after loading new page. In a while it might happen after manipulating current page. But I still insist, that if you wait for post to appear on the page, you've got to state it clearly. Not like, let's just wait for an image (instead of post) to appear on the page. That way we can do both things in one statement. Nobody cares that our intentions are not clear anymore. But we saved ourselves 10 symbols to type. – x-yuri Jun 01 '17 at 08:32
  • As for asserting on database objects, care to join me [here](https://stackoverflow.com/questions/44304883/how-to-assert-that-image-has-been-uploaded-with-capybara)? – x-yuri Jun 01 '17 at 10:21
  • 2
    @x-yuri If you want to make it clear that you're waiting for a post to appear on the page then use an assertion like `assert_selector('.post')` to initiate the wait for the expected element (`expect(page).to have_selector '.post'` if using RSpec). The downside to that (especially when using the JS capable drivers) is that finding elements is relatively slow, so finding twice when you really only need to do it once is needlessly slowing down tests. – Thomas Walpole Jun 01 '17 at 19:11
-1

Unfortunately, you don't want to wait for page reload. Since, for instance, now post appears after page reload. In a while it might appear without full page reload. If you do, explain your circumstances, please.

I say unfortunately since if we ignore what was said above, if we really need to wait for page reload for some reason, the idea from the old answer is better than any other I've seen in the internets.

So, what you care is that post has appeared on the page. Which brings us to the following code:

test 'create post' do
  visit url
  fill_in the_form
  click_on 'Create'
  assert_selector '.post'
  post = Post.first
  img = page.find '.post .image'
  assert_equal post.file.thumb.url, URI(img[:src]).path
end

old answer

Inspired by these great article, link and comment. According to one of capybara's authors, this is the only legitimate use case for wait_until he's heard of in Capybara. Asserting on model objects, that is.

def wait_until
  Timeout.timeout(Capybara.default_max_wait_time) do
    sleep(0.1) until value = yield
    value
  end
end


def wait_for_page_reload
  id = find('html').native.ref
  yield
  wait_until { find('html').native.ref != id }
end

test 'create post' do
  visit url
  fill_in the_form
  wait_for_page_load do
    click_on 'Create'
  end
  post = Post.first
  img = page.find '.post .image'
  assert_equal post.file.thumb.url, URI(img[:src]).path
end

This is a bit hacky. But it works. It waits until html element's internal id changes.

x-yuri
  • 16,722
  • 15
  • 114
  • 161
  • This is not the legitimate case for `wait_until`. If you read the details of the "legitimate" case in the article you linked to, it specifically mentions the case of an asynchronous UI where the UI is updated before the data is actually saved to the database, so there is no change in the UI you can wait for that indicates the data has been saved. This questions case has UI changes that can be auto-waited for. Also by using `.native` you have just tied your tests to a specific driver. – Thomas Walpole Jun 01 '17 at 00:19
  • The article says: "In some cases, especially if your interface is asynchronous, you might still want to do it though." What follows is just example when you want to do it. Maybe the one where there are no other options. But it says, "especially." Which means you might prefer it even if the interface is not asynchronous and you can make assertions on UI. And yes, this method comes with its drawbacks. No doubt about it. – x-yuri Jun 01 '17 at 01:44
  • 2
    You skipped the part that says "asserting on model objects: I am firmly convinced that asserting on the state of the interface is in every way superior to asserting on the state of your model objects" - ie if you can assert on the UI it's the preferred solution to `wait_until` - basically `wait_until` is generally only useful on bad UIs that don't provide a visual change to indicate when data is successfully/unsuccessfully changed – Thomas Walpole Jun 01 '17 at 05:44