目录

Testing

Write end-to-end tests that drive your application with Playwright.

Overview 

MōBrowser projects come with end-to-end tests written with Playwright. A test drives the built application the way a user does: it clicks, types, and checks what the windows show. Every test starts its own instance of the application with a new profile and a hidden window, so tests don’t depend on each other or on your data.

Creating a project with tests 

Projects created with npm create mobrowser-app include the testing setup:

.agents/
├── skills/
    ├── mobrowser-testing/
.claude/
├── skills/
    ├── mobrowser-testing/
.github/
├── workflows/
    ├── test.yml
test/
├── e2e/
    ├── app.test.ts
playwright.config.ts
  • test/e2e/ contains the tests, starting with an example.
  • playwright.config.ts configures Playwright. See Configuration.
  • .github/workflows/test.yml runs the tests for every pull request. See Continuous integration.
  • .agents/skills/mobrowser-testing/ tells AI agents how to write and run the tests. See Testing with AI agents.

package.json includes the test and test:e2e scripts and the @playwright/test dev dependency.

Adding tests to an existing project 

To add the testing setup to an existing project, run:

npm run add testing

Note: Projects created with an earlier version of create-mobrowser-app may not have the add script in package.json. In such projects, run npm run mobrowser add testing instead.

The command:

  • Installs @playwright/test.
  • Adds the test and test:e2e scripts, keeping any existing ones.
  • Creates playwright.config.ts, the testing skill, and, in projects with a renderer, an example test.
  • Creates .github/workflows/test.yml if the project has GitHub Actions workflows.

If the project already has a Playwright configuration file, the command changes nothing.

Running tests 

To run the tests, use the following command:

npm test

npm test builds the application and runs the tests in test/e2e/.

Pass Playwright options after --. For example, to run only the tests whose names match a pattern, or the tests in one file:

npm run test:e2e -- -g "greets the user"
npm run test:e2e -- test/e2e/app.test.ts

To watch the tests, show the application window:

npm run test:e2e -- --show-window

Linux 

On Linux without a display, such as a CI agent, the CLI runs the tests with xvfb-run. Install Xvfb if it’s missing:

sudo apt install xvfb

On Ubuntu 24.04 and later, the CLI may offer to create an AppArmor profile, as npm run dev does. See AppArmor Restrictions.

Writing tests 

Put the tests in test/e2e/ as .test.ts or .test.js files. Import test and expect from @mobrowser/cli/e2e, not @playwright/test:

import { expect, test } from "@mobrowser/cli/e2e";

test("greets the user by name", async ({ page }) => {
  await page.getByPlaceholder("Your name").fill("Ada");
  await page.getByRole("button", { name: "Greet" }).click();
  await expect(page.getByText("Hello, Ada!", { exact: true })).toBeVisible();
});

A test fails if the application doesn’t open, crashes, or exits with an error.

A test can use the following fixtures:

FixtureDescription
pageThe application’s first window. If the application opens several windows at startup, pick one from context.pages() by its URL or title.
contextThe browser context of the application. context.pages() lists all its windows.
appAnswers native dialogs. See Native dialogs.

The rest is regular Playwright. Locators, actions, and assertions work as described in the Playwright documentation.

Native dialogs 

Playwright can’t reach native message and file dialogs. MōBrowser intercepts them during tests, and the app fixture answers them.

Queue an answer with app.answerDialog() before the action that opens the dialog:

test("deletes the conversation after confirmation", async ({ page, app }) => {
  await app.answerDialog({ button: "Delete" });
  await page.getByRole("button", { name: "Delete conversation" }).click();
  await expect(page.getByRole("listitem")).toHaveCount(0);

  const { dialogs } = await app.dialogs();
  expect(dialogs[0].dialog.message).toBe("Delete this conversation?");
});

Queued answers are used in order. An answer can have the following properties:

PropertyDescription
buttonThe label of the button to press, or its index as a string, such as "0". If no button matches, the default button is pressed.
textValuesThe values of the message dialog’s text fields.
checkboxThe state of the message dialog’s checkbox.
pathsThe paths an open dialog returns. A save dialog returns the first one.
canceledCancels a file dialog. Message dialogs ignore it.

Without a queued answer, a message dialog presses its default button and a file dialog is canceled.

app.dialogs() returns the dialogs the application has shown and how they were answered.

Limitations 

  • Playwright’s context options under use, such as viewport and locale, don’t apply: the tests use the application’s own browser context. The browser fixture is a separate Chromium, not your application.
  • Every test starts the application with a new profile, so a test can’t restart the application and check that data survives the restart. In tests, app.restart() quits the application instead of restarting it.

Test results 

Playwright prints the results to the console. For each failed test, it keeps the following files in build/test-results/e2e/<test>/:

FileDescription
test-failed-<n>.pngA screenshot of the window at the end of the test.
trace.zipA Playwright trace with the actions, console messages, network requests, and a screencast.
error-context.mdThe error and an accessibility snapshot of the page.
app.logThe output of the application. Start here if the application crashed or exited.

Each run replaces the results of the previous one.

To open a trace, run:

npx playwright show-trace build/test-results/e2e/<test>/trace.zip

To add Chromium logs to app.log, run the tests with --verbose:

npm run test:e2e -- --verbose

Configuration 

playwright.config.ts configures Playwright:

import { defineConfig } from "@playwright/test"

export default defineConfig({
  testDir: "test/e2e",
  outputDir: "build/test-results/e2e",
  preserveOutput: "failures-only",
  reporter: "list",
  use: { trace: "retain-on-failure", screenshot: "only-on-failure" },
})

You can change it as described in the Playwright documentation. Keep the results in build/test-results/: the test workflow uploads this directory.

Continuous integration 

.github/workflows/test.yml runs the tests on macOS, Windows, and Linux for every pull request. Failed tests’ results are uploaded as test-results-<OS> artifacts.

The workflow installs dependencies with npm ci, so commit package-lock.json.

To add the workflow to a project that has the test:e2e script, run npm run add github-actions. See GitHub CI/CD.

Testing with AI agents 

Projects include the mobrowser-testing skill in .agents/skills/ and, for Claude Code, in .claude/skills/. The skill makes an agent keep the checks it makes with the automation commands as regression tests, run the tests, and investigate failures.