# What is a browser test framework?

A browser test framework is an open-source library that drives a real browser from test code, so a test can load pages, act on them, and check results.

Last updated September 29, 2026, 8 min read

## Learning objectives

After reading this article you will be able to:

-   Define a browser test framework
-   Choose a framework for tests an agent runs
-   Compare Playwright, Selenium, and Cypress

## Related content

-   [How to test a command-line application](https://specstory.com/learning/cli-and-web/testing-command-line-applications)
-   [What is a headless browser?](https://specstory.com/learning/cli-and-web/headless-browser)
-   [What is Playwright MCP?](https://specstory.com/learning/cli-and-web/playwright-mcp)
-   [What is a hydration error?](https://specstory.com/learning/cli-and-web/hydration-error)

## What is a browser test framework?

A browser test framework is an open-source library that lets test code start a real browser, act on web pages, and check the results. It is also called a browser testing framework. [Playwright](https://playwright.dev/docs/intro), [Selenium](https://www.selenium.dev/documentation/), and [Cypress](https://github.com/cypress-io/cypress) are three frameworks of this kind.

The three differ in what they include. Playwright and Cypress each ship a test runner, which finds the test files, runs them, and reports the results. Playwright's runner is for JavaScript and TypeScript, and its Python, Java, and .NET versions plug into an existing runner, e.g. pytest. Selenium's WebDriver library controls the browser only, so a team pairs it with a separate runner, e.g. JUnit.

Most browser tests are [end-to-end tests](https://specstory.com/learning/testing/end-to-end-testing) of a web app, and each one usually follows one [user journey](https://specstory.com/learning/testing/user-journey-testing), e.g. checkout. A browser test framework is one part of [test automation](https://specstory.com/learning/testing/test-automation) for the web. A [command-line application](https://specstory.com/learning/cli-and-web/testing-command-line-applications) needs no browser, because its tests run the program and read its text output.

## How does a browser test framework work?

A browser test run follows the same steps in each framework:

1.  The test runner finds the test files, and the framework starts a browser, often a [headless browser](https://specstory.com/learning/cli-and-web/headless-browser) with no window.
2.  A test sends a command to the browser, e.g. "fill in the delivery address."
3.  The framework finds the target element with a locator, a query that matches elements, e.g. by their label.
4.  The framework or the test waits until the element is ready, and then the browser performs the action.
5.  The test's [assertions](https://specstory.com/learning/glossary#assertion) compare what the page shows with the expected values.
6.  The runner reports each pass and failure, and it can save a screenshot or a trace of each failed test.

The frameworks differ in how a command reaches the browser. Playwright controls Chromium over the [Chrome DevTools Protocol](https://specstory.com/learning/glossary#chrome-devtools-protocol), and it uses its own patched builds of Firefox and WebKit. Selenium sends commands in the W3C WebDriver standard to a driver, e.g. ChromeDriver, and most drivers come from the browser vendors. Cypress runs the test code inside the browser, next to the app, with a Node.js process beside it for work outside the page.

Diagram: Where each framework runs the test code

Playwright and Selenium tests run outside the browser and send it commands. Cypress tests run inside the browser, next to the app they test.

## What is an example of a browser test?

Here is an illustrative example. Acme Co. sells furniture online. A developer asks a [coding agent](https://specstory.com/learning/ai-coding/coding-agent) to "Let customers edit their delivery address during checkout."

In this version of the example, Acme's browser tests use Selenium's JavaScript library, with Jest as the test runner. A Jest setup file starts Chrome through ChromeDriver and stores the session as `driver`. The agent changes the checkout page and writes this test:

```js
import { By, until } from "selenium-webdriver";

test("saving the address shows a confirmation", async () => {
  await driver.get("https://shop.example.com/checkout");
  await driver.findElement(By.id("address")).sendKeys("12 Elm Street");
  await driver.findElement(By.id("save-address")).click();
  await driver.sleep(200);
  const saved = await driver.findElement(By.id("address-saved"));
  expect(await saved.isDisplayed()).toBe(true);
});
```

The test passes when the agent runs it. On the build server, the same test passes on some runs and fails on others with this error:

```text
NoSuchElementError: no such element: Unable to locate element:
{"method":"css selector","selector":"*[id="address-saved"]"}
```

The confirmation usually appears within 200 milliseconds and sometimes takes 250. The test waits a fixed 200 milliseconds. Selenium's implicit wait is 0 unless the test sets one, so `findElement` then looks once and fails when the message is late. That makes it a [flaky test](https://specstory.com/learning/debugging/flaky-tests).

The agent replaces the fixed wait and the lookup with explicit waits. Each explicit wait checks again until its condition is true or 5 seconds pass:

```js
const saved = await driver.wait(
  until.elementLocated(By.id("address-saved")), 5000);
await driver.wait(until.elementIsVisible(saved), 5000);
```

In Playwright and Cypress, the assertion itself retries, so the same check needs no separate wait. This example is simplified. A real checkout test would also check the cart, e.g. that it still holds 2 items after the address changes.

## What changes when a coding agent writes the code?

When a browser test fails, a coding agent can loosen it instead of fixing the app. In Playwright and Cypress, `{ force: true }` on a click skips the checks that the button is visible and not covered, e.g. by a banner, so the click goes through.

An agent can also drive a browser without writing a test, e.g. through [Playwright MCP](https://specstory.com/learning/cli-and-web/playwright-mcp). That session checks the page once. A test file keeps the check, so it can run again on the next change without the agent.

For tests an agent runs, a team can compare frameworks on these points:

-   **One command.** The tests run headless from one command, e.g. `npx playwright test`, and the run fails if a test fails.
-   **Built-in waiting.** The framework waits for elements and retries assertions, so the agent does not write its own waits.
-   **Readable failures.** The output names the expected and received values in plain text.
-   **Saved evidence.** The runner keeps a screenshot or a trace of each failure for a reviewer.
-   **A familiar language.** The tests use a language the team already reviews.

A reviewer checks each agent edit to retry, timeout, or `force` settings, because each one can turn a failure into a pass.

## What are the limits of browser test frameworks?

A passing browser test run still leaves gaps:

-   **Only what the test checks.** A test checks only the results its assertions name. Playwright can compare the page with an approved screenshot, a form of [snapshot testing](https://specstory.com/learning/testing/snapshot-testing), but that check can also fail on harmless changes.
-   **Test browsers, not customers' devices.** Tests run in the browsers on the test machine, not on customers' phones. Playwright's [device emulation](https://playwright.dev/docs/emulation) sets a phone's screen size and user agent in a desktop browser engine, which is not a real phone. Its WebKit build is not the Safari app, and its Android support is experimental.
-   **Speed and timing.** Each test needs the running app and a browser, so browser tests run far slower than tests of single functions and fail more often on timing.
-   **Upkeep.** Locators break when the page's markup changes. A [self-healing test](https://specstory.com/learning/glossary#self-healing-test) repairs a broken locator on its own, and that repair can hide a real change.

A passing run shows that the tested journeys worked in the tested browsers, which is not the same as complete coverage.

## How do Playwright, Selenium, and Cypress differ?

Playwright, Selenium, and Cypress do the same job, and they differ in how they reach the browser and what they include:

| Attribute | Playwright | Selenium | Cypress |
| --- | --- | --- | --- |
| License | Apache License 2.0 | Apache License 2.0 | MIT License |
| Browser control | Chrome DevTools Protocol for Chromium. Patched builds for Firefox and WebKit | W3C WebDriver commands to a browser driver | Test code runs inside the browser |
| Test runner | Included for JavaScript and TypeScript. Other languages plug into a runner, e.g. pytest | Separate, e.g. pytest | Included |
| Languages | JavaScript, TypeScript, Python, Java, and C# | Java, Python, C#, Ruby, and JavaScript | JavaScript and TypeScript |
| Browsers | Chromium, Firefox, and WebKit builds that it downloads. It also runs installed Chrome and Edge | Major browsers, including Safari, through their drivers | Chrome-family browsers and Firefox, with experimental WebKit |
| Waiting | Actions wait, and assertions retry for up to 5 seconds by default | No wait by default. Tests add explicit or implicit waits | Queries and assertions retry for up to 4 seconds by default |
| Parallel runs | Test files run in parallel workers by default | Set by the runner, e.g. Jest workers | Spec files run one at a time on each machine |
| Mobile | Phone emulation, with experimental Android support | Real devices through Appium, a separate project | Viewport sizes, without mobile events |

No framework in the table fits every team, and other open-source frameworks exist. Playwright fits teams that want browser builds and built-in waiting from one project. Selenium fits teams that write tests in a language the others lack, e.g. Ruby, or that must drive the Safari app. Cypress fits JavaScript teams that want the test code inside the browser, where it can reach the app's own objects directly.

## FAQs

### Is Playwright better than Selenium?

Playwright and Selenium suit different teams, so neither is better in general. Playwright includes browser builds and a test runner for JavaScript and TypeScript, and it waits for each element to be ready before it acts. Selenium supports Ruby, which Playwright does not, and drives the Safari app through the W3C WebDriver standard.

### Can Playwright test mobile browsers?

Playwright tests mobile browsers mainly through emulation, which gives a desktop browser engine a phone's screen size and user agent. Emulation is not a real phone, and Playwright does not drive the Safari app. Its Android support is experimental.

### Does a coding agent need a browser test framework?

A coding agent can try a change in a browser once without writing test code, e.g. through Playwright MCP. A test file in a browser test framework keeps the check, so the same check can run again on each later change without the agent.

### Is Cypress open source?

Cypress is open source, and its code is published under the MIT License. Its test runner is included, and its tests run inside the browser in JavaScript or TypeScript. Playwright and Selenium are open source too, under the Apache License 2.0.

---

Source: [Browser test framework | Playwright vs. Selenium | SpecStory](https://specstory.com/learning/cli-and-web/browser-test-frameworks)
