To clarify...

set 'wait' to a longer number of seconds:
expect(page).to have_selector('.some-node', wait: 90)
If your page has waits that long of course

Wait is different than sleep. Wait retries until the condition is met. This makes it a little bit confusing for "negative presence" tests where you are testing for the absence of a thing, but it ensures that you are not waiting a full 90 seconds for a thing to happen, when that thing has already happened.

This is the greatest, most noticeable effect of the "shift." No more "until".

This is something like explicit/implicit waiting described by the OP's article, although Capybara is careful now to avoid "wait until" you can still get this behavior using expect statements as I showed above. It's much cleaner in capybara[1] nowadays, and I'm actually surprised people work with Selenium WebDriver directly in Python by comparison.

You can also wait explicitly for the AJAX events to complete, although I never do this[2] because it seems to introduce a new source of error: the AJAX completes before we get around to observing that it had started.

This article seems to provide a bullet-proof way to do it though, upon review, because it's simply counting with JS variables in the DOM. You can even start multiple AJAX requests at once and wait for all of them to complete, or just go ahead and move on if you can see they already completed when you first check. No waiting. This is more reliable than adding and removing the ".ajax-processing" class to a hidden element, which you must be careful to observe so you know your AJAX has started, and then observe again as it is removed so you know it has completed. The method in [2] is a bit less "semaphore-ish" and looks more reliable than ways I've used.

Another nice trick is to remove another source of threaded racing: your animations. If you have a test that triggers an animation and you want to disable animations, run once you get to the page:

page.execute_script("$.fx.off=true") # or jquery.fx.off=true

This runs on the DOM though, so it must be run on every page that loads if it is going to be effective. There are also ways[3][4] you can get this script to run on every page rather than running it manually just wherever needed, I haven't tried this, but I'd recommend it if you don't want to spend time by hand tracing down the source of each delay.

I feel like there were about 5 things I had to learn to make my tests reliable, and I am still applying them daily. But once I had the experience of "tracking down the Heisenbug" enough times, I can avoid writing 98% of the bad tests, and tracking down the remaining 2% when they inevitably show up in my test suites as "failed once, then failure went away with no code changes" it simply hasn't been hard to track down the source of the failure anymore, as I now know really well what I'm looking for, what causes this type of failure.

I personally got a bit out of this article, but I'm not sure anyone is going to read this article and just know better how to do browser testing. I think you have to go through this experience of accidentally writing sometimes-unreliable tests, and then figuring out how to fix them. I did not learn without the help of articles like this, though.

The last great advice I found was to learn to mock API responses if your application uses external APIs, but don't over-rely on mocks. We have lots of tests that do hit our APIs and will fail if they are down. But we also have tests that depend on some difficult to replicate condition being met by the API servers. Those mock tests in some cases are more dangerously brittle than the tests that actually hit the API.

My favorite was added recently, the test that shows what happens if the API goes down in the middle of your overnight job. Hint: You want the job to stop running at the first sign that the API has gone down, and trigger alert mail or otherwise signal an error that you can find later.

With WebMock, this test was easy to write! Just send back a 500 error at a time when you know you weren't expecting it.

You can usually use factories to replicate other less unusual conditions, but when your app depends heavily on external APIs, this trick can help greatly to simplify your tests. This particular test would have been impossible to write without mocks. I would not mock more than 50% of API tests though, because it is equally valuable to find out when an unrelated change that is deployed through your CI environment to one of your APIs, will break some part of your application unexpectedly. This is why I do not mock 100% of API-related tests.

[1]: https://www.varvet.com/blog/why-wait_until-was-removed-from-...

[2]: http://cabeca.github.io/blog/2013/06/16/waiting-for-complete...

[3]: https://makandracards.com/coffeeandcode/7503-disable-jquery-...

[4]: https://gist.github.com/keithtom/8763169