In the previous chapter, we ran our test using both Chrome and Headless Chrome.
Let's try Firefox — again, making the change in our config.json
file.
{
"browser": "Firefox",
"implicit_wait": 10
}
With the terminal open, let's run our pytest command. Let's see what happens.
We should see Firefox open up shortly and the test begins. Cool.
Oh no, for some reason, it failed!
Wow. What happened?
If we look at the assertion introspection, we can see that for some reason, the page title wasn't as expected. We wanted the page title to contain our search phrase, but instead, it had the DuckDuckGo title.
This failure is due to a race condition.
A race condition happens when actors access the same resource at the same time without following an order of operations.
Race conditions can cause flakiness in any type of black box test.
In web UI test automation, race conditions can happen whenever automation attempts to interact with the page before it's fully loaded.
For example, a test can't click on a button that doesn't exist yet.
To mitigate this type of race condition, always wait for the target element or page property to be ready. There are two types of waits.
Implicit waits are specified once and applied to all interactions.
They will wait for the given amount of time for an element to appear before timing out. We used an implicit wait in our browser
fixture. Implicit waits are helpful for small test automation solutions because they are simple and don't require much extra code.
Explicit waits, on the other hand, must be specified for each interaction.
Explicit waits are more powerful because they can have custom conditions and timeouts. However, they require much more code. Explicit waits are typically better for large test solutions with many interactions for which a one size fits all timeout won't work well.
Mixing Wait Types
As a warning, don't mix implicit and explicit waits. They can interfere with each other and cause unintended consequences.
If you want to learn more about waits, refer to the Python WebDriver docs.
Here's an example for explicit waits.
In Python, explicit waits use the WebDriverWait
class with expected_conditions
methods.
There are many readily available conditions to use, and you can also implement your own.
This page also shows how to use implicit waits — implicitly_wait
— which we already used in our browser
fixture.
Let's take a second look at our test case failure.
The failing assertion is “Then the search result title contains panda”. From the assertion introspection, we can see that the assertion failed because the title clearly didn't include the word “panda”.
It looks like the title for the result page still has the title value from the old search page.
Our test tried to check the value of the new title before it had changed.
This is clearly a race condition. Our test needs to wait before doing the assertion. Also, it looks like Chrome changes the title faster than Firefox does.
There are a few ways we could fix our test.
Let's take a look at the code.
For one, we could add a hard sleep for a few seconds before checking the title to give the time page to load.
However, this is a terrible idea.
NEVER, ever add hard sleeps to your tests. Hard sleeps will make the test take much longer to run than necessary, and they are still susceptible to race conditions. Always use a smart wait instead.
A second solution would be to use an explicit wait.
We can wait for the title to change value.
That would arguably be the most correct solution, but it would also require us to convert the solution from implicit waits to explicit waits, thereby forcing us to add explicit waits to all other interactions.
So, what should we do?
For our purposes, there's actually a shortcut we could use as a third solution — we could simply reorder the assertions.
Implicit waits apply to elements, not to page titles.
The other assertions check elements, meaning they would inherently force the page to be ready, without needing other code changes.
If the title assertion happens last, then the proceeding assertions would have already done the required waiting.
Let's make that code change and rerun the test.
from pages.result import DuckDuckGoResultPage
from pages.search import DuckDuckGoSearchPage
def test_basic_duckduckgo_search(browser):
search_page = DuckDuckGoSearchPage(browser)
result_page = DuckDuckGoResultPage(browser)
PHRASE = "panda"
# Given the DuckDuckGo home page is displayed
search_page.load()
# When the user searches for "panda"
search_page.search(PHRASE)
# Then the search result query is "panda"
assert PHRASE == result_page.search_input_value()
# And the search result links pertain to "panda"
titles = result_page.result_link_titles()
matches = [t for t in titles if PHRASE.lower() in t.lower()]
assert len(matches) > 0
# And the search result title contains "panda"
# (Putting this assertion last guarantees that the page title will be ready)
assert PHRASE in result_page.title()
There goes pytest.
We should see Firefox pop up soon. As we expect DuckDuckGo, does the search, boom. Nice.
Our test passed!
As you're doing web UI test automation, watch out for those race conditions. Also, this is a perfect example for why multi-browser testing is important.