Docs

Write tests

Write a browser test

A browser test opens your running app in Chromium with Playwright. Use it when the browser is part of the assertion.

View as Markdown

On this page
  1. Install the browser once
  2. Write the test
  3. Run it
  4. Read a failure
  5. Point the test at another target
  6. Helpers
  7. Keep each file small

Install the browser once

Testkit runs browser tests in a Chromium that it manages. A test run never downloads a browser, so install it first.

$npx testkit setup playwright

On CI and on a fresh host, add --with-deps. It installs the system libraries that Chromium needs.

Write the test

src/web/__testkit__/checkout.ui.testkit.ts
import { defineFile } from "@gfdelarue/testkit";
import { resolveFrontendBaseUrl } from "@gfdelarue/testkit/playwright";
import { expect, test } from "@playwright/test";

export const testkit = defineFile({ engine: "playwright" });

test("the checkout page shows the order total", async ({ page }) => {
	await page.goto(new URL("/checkout", resolveFrontendBaseUrl()).href);
	await expect(page.getByRole("heading", { name: "Checkout" })).toBeVisible();
	await expect(page.getByText("Total")).toBeVisible();
});

resolveFrontendBaseUrl() returns the URL of the service that Testkit started.

Import test and expect from @playwright/test. You do not install that package. Testkit ships it and pins its version, so a copy of Playwright in your own node_modules does not change what the test runs on.

Run it

$npx testkit run ui
[1/1] PASS api ui src/web/__testkit__/checkout.ui.testkit.ts 4s

Summary
Result: PASSED
Passed: 1
Failed: 0
Skipped: 0
Not run: 0
Files: 1
Duration: 12s
Run ID: 1791890876609-1444310-4b62c131

Read a failure

When the page loses its heading, the run prints Playwright's error and the locator that found nothing.

$npx testkit run ui
[1/1] FAIL api ui src/web/__testkit__/checkout.ui.testkit.ts 9s
    Error: expect(locator).toBeVisible() failed Locator: getByRole('heading', { name: 'Checkout' })
    Expected: visible Timeout: 5000ms Error: element(s) not found Call log:  - Expect "toBeVisible

The run keeps more than it prints. List what it kept for the file.

$npx testkit artifacts src/web/__testkit__/checkout.ui.testkit.ts

For a failed browser test, the list includes playwright-failure-details and Playwright's error-context attachment. Read a run's results shows how to print one.

Point the test at another target

resolveFrontendBaseUrl() takes the first target it finds, in this order.

OrderBrowser targetAPI target
1The frontendBaseUrl option you passThe backendBaseUrl option you pass
2ui.frontendBaseUrl in testkit.config.tsui.backendBaseUrl in testkit.config.ts
3TESTKIT_FRONTEND_BASE_URLTESTKIT_BACKEND_BASE_URL
4BASE_URL, which Testkit sets for the runAPI_BASE_URL

resolveBackendBaseUrl() reads the API column.

Testkit refuses a target that is not on loopback. To allow one, set ui.allowRemote to true.

Helpers

Import these from @gfdelarue/testkit/playwright.

HelperReturns
resolveFrontendBaseUrl(options?)The browser target
resolveBackendBaseUrl(options?)The API target
createUiSandbox(options?){ test, expect }, where test adds a testkitSandbox fixture with id, page, frontendBaseUrl, and backendBaseUrl
createAuthedApiClient(request?, options?)A Playwright APIRequestContext for the API target
waitForUiSettled(page, options?)A promise that resolves when the page has settled
waitForNetworkIdle(page, options?)A promise that resolves when the network is idle
waitForElementStable(page, selector, options?)A promise that resolves when the element stops changing
waitForImagesLoaded(page, containerSelector?)A promise that resolves when the images have loaded

The three waitFor... helpers that take options accept frames and timeout.

Keep each file small

testkit lint checks the shape of browser test files. By default, a file can have at most 220 lines and 8 tests. Change the limits with lint.ui.maxLines and lint.ui.maxTests in testkit.config.ts.

  1. OverviewStartWhat Testkit is, and what you need to run it.
  2. Get startedStartFive steps from install to a merge gate.
  3. Test filesWrite testsWhere a test lives and what its name means.
  4. Contract testsWrite testsCall your running service from Node.
  5. HTTP testsWrite testsDrive routes with k6, signed in as named actors.
  6. Browser testsWrite testsAssert on a real page in Chromium.
  7. Database testsWrite testsRun SQL against a Postgres that Testkit builds.
  8. CIRunOne command decides whether a pull request merges.
  9. ResultsRunWhere a run's evidence lands and how to read it.
  10. CLIRunEvery command and flag.
  11. ConfigurationRunEvery field of testkit.config.ts.
  12. CampaignsHunt bugsGive your agent a target and collect proven bugs.
  13. Bug reportsHunt bugsThe five things every issue states.
  14. TurnstoneTurnstoneWhat the dashboard keeps, and what it never stores.
  15. Connect GitHubTurnstoneConnect a repository, and see the access Turnstone asks for.
  16. Connect GiteaTurnstoneAdd your Gitea host, then connect a repository.
  17. SyncTurnstoneWhen Turnstone reads your issues, and what it reads.
  18. FactsTurnstoneEach fact about a bug, and where Turnstone reads it.