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.
On this page
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 playwrightOn CI and on a fresh host, add --with-deps. It installs the system libraries that Chromium needs.
Write the test
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-4b62c131Read 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 "toBeVisibleThe run keeps more than it prints. List what it kept for the file.
$npx testkit artifacts src/web/__testkit__/checkout.ui.testkit.tsFor 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.
| Order | Browser target | API target |
|---|---|---|
| 1 | The frontendBaseUrl option you pass | The backendBaseUrl option you pass |
| 2 | ui.frontendBaseUrl in testkit.config.ts | ui.backendBaseUrl in testkit.config.ts |
| 3 | TESTKIT_FRONTEND_BASE_URL | TESTKIT_BACKEND_BASE_URL |
| 4 | BASE_URL, which Testkit sets for the run | API_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.
| Helper | Returns |
|---|---|
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.