# 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.

## 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.

```sh
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

```ts title=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

```console $ 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.

```console $ 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.

```sh
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](https://turnstone.cloud/docs/results.md) 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`](https://turnstone.cloud/docs/configuration.md#lint).
