Writing checks
A check is a small Playwright project: one or more *.spec.ts files and an optional playwright.config.ts. Nothing proprietary. The runner wraps the config to force one worker, tracing on, screenshot and video on failure, and a per-plan wall clock.
Read the target from the environment
Never hard-code the URL. Read process.env.BASE_URL and set it per run (--base-url on the CLI, env on run_check) or as the check's default env for schedules. The same check then runs against local, preview and production.
import { test, expect } from '@playwright/test';
const base = process.env.BASE_URL!;
test('sign-up creates an account', async ({ page }) => {
await page.goto(base + '/signup');
await test.step('fill the form', async () => {
await page.getByLabel('Email').fill(`check+${Date.now()}@example.com`);
await page.getByLabel('Password').fill('correct horse battery');
});
await page.getByRole('button', { name: 'Create account' }).click();
await expect(page.getByRole('status')).toHaveText('Account created.');
});Conventions that make failures repairable
- One flow per check, named for the outcome: "Checkout completes".
- Role and label locators over CSS. They survive restyles and read well in a failure message.
- Wrap phases in
test.step. The failure context names the step that broke. - Assert on visible outcomes: text, visibility, URL. Not on implementation details.
- Keep a check under a minute. Free runs get 90 seconds of wall clock; paid get five minutes.
Limits
| Limit | Value |
|---|---|
| Files per bundle | 64 |
| Bundle size | 512 KB, 128 KB per file |
| File types | .ts .js .mjs .cjs .json .md .txt |
| Env vars per run | 32, UPPER_SNAKE keys |
| Per-test timeout | 120 s ceiling |
| Dependencies | @playwright/test only, for now |
Versions
Every push or edit creates an immutable version with an author (you or an agent) and a message. The history is on the check page and over MCP with get_check_history, which is the first thing to read when a check that used to pass starts failing.