Testing the Browser

Using Playwright to test the full application end to end

Concepts

What Playwright does

Installing and configuring Playwright

A first browser test

Testing form submission

When end-to-end tests are worth the cost

Check for Understanding

What does end-to-end testing catch that unit tests of individual functions cannot?

End-to-end tests catch integration failures: the server returns the right data but the JavaScript does not insert it into the correct element; the form submits successfully but the HTMX swap target is wrong; the CSS hides an element that should be visible. These bugs live in the interactions between layers, not in any single function, so unit tests that mock each layer individually will not reveal them.

Why do Playwright tests need explicit waits after clicking HTMX-powered buttons?

When you click an element with an hx-get attribute, HTMX makes an asynchronous HTTP request. The Python test code runs faster than the network round-trip; if you assert immediately after clicking, the response has not yet arrived and the DOM has not been updated. Playwright's expect(locator).to_be_visible() and similar methods wait for the condition to become true (up to a timeout) rather than checking at a single instant.

When would you run end-to-end tests and when would you rely on unit tests instead?

Run unit tests on every save during development: they are fast (milliseconds each) and give immediate feedback. Run end-to-end tests before committing a significant feature, before a release, or after a deployment. End-to-end tests are worth the cost when verifying complete user workflows (submit a form, see the result in the table) or when a bug was caused by an integration failure that unit tests missed.

What is a "locator" in Playwright, and how is it different from finding an element with a CSS selector at a fixed point in time?

A locator is a description of how to find an element; Playwright re-evaluates it against the current DOM every time you perform an action or assertion on it. This means a locator does not go stale when the page updates. In contrast, methods like querySelector return a specific DOM node at the moment they are called; if the page re-renders (e.g., after an HTMX swap), the old reference may point to a node that is no longer in the document.

Exercises

Test the search flow

Write a Playwright test that: (1) navigates to the table page, (2) types a known site name into the search input, (3) submits the search, (4) waits for the results to load, and (5) asserts that every visible row shows that site name and that at least one row is present.

Test navigation

Write a test that: (1) loads the first page of results, (2) clicks the "Next" link, (3) waits for the second page to load, (4) asserts that the URL now contains page=2, and (5) asserts that the rows shown are different from those on page 1.

Debug in headed mode

Take a test that is failing and run it in headed mode (--headed flag) so you can watch the browser. Write a two-paragraph description of what you saw the browser do, what you expected it to do, and what you learned from watching that you could not have learned from the error message alone.

Compare test speeds

Run the full pytest unit-test suite and record the total time. Run the full Playwright suite and record its time. Compute the ratio. Given that ratio, write a brief recommendation for how often each suite should be run during a typical development session and before a release.