Search code examples
pythontestingpython-unittestassertplaywright-python

Python testing assertions (unittest, playwright)


so im new to testing and have been learning python/playwright testing for a couple of weeks now.

I've got to assertions and have found multiple ways to write them, so this might sound like a stupid question but which of these 3 types of assertions would be best to use, or is there one that i don't know about that is better.

Playwright Assertions:
https://playwright.dev/python/docs/test-assertions
Unittest Assertions:
https://docs.python.org/3/library/unittest.html
Python Assertions:
https://realpython.com/python-assert-statement/

Here is a little example:

class ExperimentTest(StaticLiveServerTestCase):
    def test_experimental(self):
        page.goto(f"{self.live_server_url}")

        expect(page).to_have_url(f"{self.live_server_url}")

        assert page.url == f"{self.live_server_url}")

        self.assertEqual(page.url, f"{self.live_server_url}")

Solution

  • When using Playwright, almost always use the Playwright assertion, which waits for the predicate to be true.

    Consider the following simple example:

    from playwright.sync_api import expect, sync_playwright
    
    
    html = r"""<!DOCTYPE html><html><body>
    <h1>NO!</h1>
    <script>
    setTimeout(() => {
      document.querySelector("h1").textContent = "YES!"
    }, 3000);
    </script>
    </body></html>
    """
    
    
    def main():
        with sync_playwright() as p:
            browser = p.chromium.launch()
            page = browser.new_page()
            page.set_content(html)
            expect(page.locator("h1")).to_have_text("YES!")
            browser.close()
    
    
    if __name__ == "__main__":
        main()
    

    This passes, even though the site takes 3 seconds after load to change its header text from NO! to YES!. If you use any other assertion library, the auto-wait will not occur.

    Now change the above code to fail: expect(page.locator("h1")).to_have_text("NEVER!"). The output will be clear:

    AssertionError: Locator expected to have text 'NEVER!'
    Actual value: YES! 
    Call log:
    LocatorAssertions.to_have_text with timeout 5000ms
    waiting for locator("h1")
      locator resolved to <h1>NO!</h1>
      unexpected value "NO!"
      locator resolved to <h1>NO!</h1>
      unexpected value "NO!"
      locator resolved to <h1>NO!</h1>
      unexpected value "NO!"
      locator resolved to <h1>NO!</h1>
      unexpected value "NO!"
      locator resolved to <h1>NO!</h1>
      unexpected value "NO!"
      locator resolved to <h1>NO!</h1>
      unexpected value "NO!"
      locator resolved to <h1>NO!</h1>
      unexpected value "NO!"
      locator resolved to <h1>YES!</h1>
      unexpected value "YES!"
      locator resolved to <h1>YES!</h1>
      unexpected value "YES!"
    

    Notice it's retried and tracked changes over time, eventually emitting a clear error which makes debugging easier.

    As an aside, there's no need to use f-strings if you're not concatenating anything: f"{self.live_server_url}" should be self.live_server_url.