# Playwright Check Suites

- Import the `PlaywrightCheck` construct from `checkly/constructs`.
- Use Playwright Check Suites to run an existing Playwright project. Do not rewrite tests as Browser or Multistep Checks unless the user asks.
- Set `playwrightConfigPath` relative to the `.check.ts` file that declares the construct, not the project root. If the check is in `monitoring/suites.check.ts` and the config is at the repo root, use `"../playwright.config.ts"`.
- Use `pwProjects` only when the user wants specific Playwright projects. Values must match the Playwright project `name`, such as `"Cart & Checkout"`, not a folder or slug. Wrong names can deploy but run zero tests.
- Use `pwTags` only when the user wants tag-based selection.
- Use `include` only for non-code assets that specs read via `fs` at runtime (markdown, JSON fixtures, snapshots) — test files and anything reachable through `import` are already bundled via Playwright's project discovery and the import graph. In Checkly CLI v8.0.0 and later, `include` globs resolve relative to the Playwright config directory, not the project root and not the `.check.ts`. Example: `playwrightConfigPath: "./e2e/playwright.config.ts"` with a root fixture at `fixtures/data.json` needs `include: ["../fixtures/data.json"]`.

## Dependencies

- Use the user's `package.json` and lock file for dependencies. Do not add dependency declarations to check code.
- For private packages or custom registries, `.npmrc` is bundled automatically — the workspace-root `.npmrc` and any `.npmrc` beside a package's `package.json` are included by default. You do not need to add `.npmrc` to `include`.
- Because `.npmrc` is uploaded with the bundle, it must reference a Checkly environment variable such as `${NPM_TOKEN}` rather than a plaintext token — and tell the user that the token must exist in Checkly before `deploy` or `trigger`.
- Use `installCommand` only when the default package-manager install command is not enough.
- For pnpm projects, the workspace-root pnpmfile (`.pnpmfile.cjs` / `.pnpmfile.mjs`) is bundled automatically so the remote install can reproduce the lockfile — but only when the lockfile records a pnpmfile checksum (run `pnpm install` with the pnpmfile in place to record one; without it the pnpmfile is silently not bundled). The pnpmfile must also be self-contained: it may only load side-effect-free Node.js builtins (such as `path`, `crypto`, `util`) via literal `require('...')`/`import` specifiers, and must not reference `process`, `__dirname`, `import.meta`, or dynamically computed module paths. A pnpmfile that is not self-contained is skipped with a warning, and the remote install may re-resolve dependencies instead of using the lockfile. The pnpmfile is uploaded, so avoid embedding secrets in it.
- In a workspace (monorepo) whose code bundle covers only part of the workspace, the bundled lockfile is pruned automatically so the remote install only fetches what the bundle needs, instead of failing on dependencies of workspace members that were left out.
  - Supported for `pnpm-lock.yaml` (v6/9), `package-lock.json` (v2/3), the text `bun.lock`, and Yarn Berry `yarn.lock`. Yarn Classic v1 lockfiles are not supported; for bun's binary `bun.lockb`, regenerate a text lockfile with `bun install --save-text-lockfile`.
  - The workspace's package manager binary must be available on the machine running the CLI. pnpm resolves against the workspace's own store plus cached registry metadata, npm reuses its cached metadata, and both only contact the registry for misses; yarn Berry and bun regenerate offline.
  - When pruning was needed but cannot run or cannot verify its result, the original lockfile ships unchanged and the CLI prints a note explaining why — follow it (a timed-out prune's note names `CHECKLY_LOCKFILE_PRUNE_TIMEOUT=<seconds>`, which raises the default 30 s budget). Skips are silent when there is nothing to prune, or when pruning is disabled via `CHECKLY_LOCKFILE_PRUNE=0`; silent skip reasons, a timed-out prune's partial package manager output, and per-entry embed match details are visible under `DEBUG='checkly:cli:services:check-parser:*'` and `DEBUG='checkly:cli:services:embedded-packages'`.
  - For pnpm projects with `patchedDependencies`, patches belonging to unbundled workspace members are filtered out automatically. A note naming stale patch declarations means the lockfile is out of date with the config — refresh it with a regular install.
- Checkly caches installed dependencies between runs, keyed off the workspace's dependency inputs (the lock file, every workspace member's `package.json` and `.npmrc` — whether or not the member is in the bundle) plus the bundle's own install inputs, so the key can change without a file edit, e.g. when a different set of workspace members lands in the bundle. To force a reinstall for deployed checks, set `runner.cache.install.version` (a string or a safe integer) at the top level of `checkly.config.ts` — not per check — and change its value whenever the cache should be invalidated; deployed, scheduled checks pick up the change on the next `checkly deploy`. If the config still declares the deprecated `caching.dependencyCache.version`, remove it — setting both fails config loading. Unset or empty values leave the cache key unchanged, so a dynamic value such as `version: process.env.DEPENDENCY_CACHE_VERSION` is safe when the variable is not always set. For a one-off reinstall during an ad-hoc run, use the `--refresh-cache` flag on `checkly test`, `checkly pw-test`, `checkly trigger`, or `checkly checks run` instead.
- If dependencies come from a private registry that Checkly's infrastructure cannot reach (for example an intranet-only Nexus mirror), list them in `bundle.packages.embed` in `checkly.config.ts` — a top-level section: `bundle: { packages: { embed: ['@acme/private-utils', 'legacy-private-pkg@2.1.0'] } }`. The CLI resolves the entries against the workspace-root lockfile (Yarn Classic v1 lockfiles are not supported), downloads their tarballs, verifies each tarball's integrity (recorded in the lockfile, or fetched from registry metadata for Yarn Berry), and ships them inside the code bundle so the runner can install them without reaching the registry. Applies to Playwright Check Suites only, not Browser or Multistep Checks.
  - Each entry is a package name (embeds every version found in the lockfile) or an exact `name@version` pin. Names may contain `*` wildcards (`@acme/*`; a single `*` never crosses `/`, while `**` does — `**` alone matches every package, and `**/utils` matches `utils` in any scope or none), and a `!` prefix turns an entry into an exclusion that subtracts from the entries before it — order matters: `['@acme/*', '!@acme/legacy']` embeds the scope except `@acme/legacy`. A selecting entry that matches nothing in the lockfile fails validation and blocks the deploy — do not add speculative names — and so does an entry whose only matches cannot be embedded (git/file/URL or integrity-less lockfile entries, which the runner must fetch itself); a `!` exclusion that matches nothing is a silent no-op, and a list that ends up selecting nothing overall only warns.
  - List every unreachable package by name, including private packages that only appear as transitive dependencies of other private packages — dependencies of listed packages are not embedded automatically.
  - The machine running `checkly deploy`/`test` needs registry access on a cold cache. Yarn Berry projects additionally need registry access on every deploy, even with a warm cache, because Berry lockfiles record no npm tarball integrity.
  - Downloads are cached under the workspace root's `node_modules/.cache/checkly` (override with `CHECKLY_CACHE_DIR`); persist the cache in CI to avoid re-downloading.
  - When the bundled lockfile is pruned, packages it no longer references are not embedded even if an entry matches them. If a package unexpectedly stops being embedded, usually only a workspace member outside the bundle depends on it, so the runner never installs it and nothing is wrong; if the checks genuinely need it, import the depending member from check code.
- Credentials for a private registry must be scoped to the registry — the `//registry.example.com/path/:_authToken` form, not a bare `_authToken` at the top of the file, which npm itself rejects. Credentials are read from the project, workspace-root, and user `.npmrc` files, `npm_config_*` environment variables, and pnpm's global `auth.ini` (where `pnpm login` stores tokens). bun or yarn users whose registry credentials live solely in `bunfig.toml` or `.yarnrc.yml` must duplicate them into `.npmrc` or `npm_config_*` — and bun projects must keep registry URLs in `.npmrc` too, since `bunfig.toml` is never read and a wrong registry during lockfile pruning can disclose private package names. The environment variables referenced by `.npmrc` (such as `${NPM_TOKEN}`) must also be set locally wherever `checkly deploy`/`test` runs — embed downloads happen on that machine, not just on Checkly. A download that fails to authenticate names the credential source in the error — tell the user to read the error rather than guessing which source to edit.
- If the registry configuration shipped with the bundle (`.npmrc` etc.) resolves on the machine running the CLI but not from Checkly's runners — for example a mirror URL that only works on a VPN — set `runner.registries` in `checkly.config.ts` to tell the runner which registries to install from: named `upstreams` (a base `url` plus optional `auth: { type: 'bearer', token: '${NPM_TOKEN}' }`) and `packages` routing rules matched top-down, first match wins, each listing upstream names tried in order, ending with a required `{ pattern: '**', ... }` fallback as the last rule. Applies to Playwright Check Suites only, and only on the runner — embed downloads and lockfile pruning on the CLI machine still read `.npmrc`.
  - Auth tokens must be exactly one `${VAR}` environment variable reference, resolved from the check's Checkly environment variables on the runner — the variable must exist in Checkly before `deploy` or `trigger`; any literal token content is rejected at config load.
  - Pattern syntax matches `bundle.packages.embed`'s (a single `*` never crosses `/`, `**` does), but `!` exclusions and `name@version` pins are not accepted. Embedded packages always take priority over the routing rules.
- To remove dependencies from the `package.json` files shipped in the code bundle (files on disk are never modified), set `bundle.packages.prune` in `checkly.config.ts`: either an array of name patterns removed from every dependency class (`dependencies`, `devDependencies`, `peerDependencies`, `optionalDependencies`), or an object keyed by dependency class whose values are `true` (remove the whole class) or a pattern array — e.g. `bundle: { packages: { prune: { peerDependencies: true } } }`. Applies to Playwright Check Suites only.
  - This is the escape hatch for keeping unreachable private `peerDependencies` out of the remote install: with pnpm's `auto-install-peers` (default since pnpm 8), a bundled member's unused mandatory peers land in the lockfile even though the bundled code never imports them.
  - Wildcards and `!` exclusions work like `bundle.packages.embed`'s, but `name@version` pins are not accepted. Each `*` matches any run of characters except `/`, so a bare `*` matches only unscoped names, while `**` also crosses `/` (and `**/` may match zero segments, glob-style) — remove a whole class with `true`, or with `['**', ...]` when combining with exclusions (`true` cannot be combined with `!` entries). A pattern that matches nothing is silently ignored, not an error.
  - To target specific workspace members instead of every bundled manifest, array entries can be member-scoped objects with `member` (a pattern list matched against manifest `name`s with the same wildcard/`!` rules; `'.'` addresses the workspace root) and exactly one of `remove` or `keep`.
    - `{ member: 'my-app', remove: [...] }` removes only from matching members, composing with the other entries in listed order. In a member-scoped class-keyed map, `true` selects the whole class (accepted as shorthand for `['**']`, so a later `!` exclusion in the same removal order can still spare entries).
    - `{ member: 'my-app', keep: [...] }` declares the member's *entire* remaining dependency set: entries it does not select are removed, classes a class-keyed `keep` (e.g. `keep: { dependencies: ['@acme/utils'] }`) does not mention are emptied, and a keep overrides every removal for that member. Exclusions that trim a keep must live in the same list (`keep: { dependencies: ['**', '!ms'] }`); separate keep entries for the same member union rather than subtract.
    - Prefer exact member names with `keep` — a wildcard can sweep a future member into another member's keep set. As a net, a `member` selector that matches no workspace member at all, or a `keep` pattern that keeps nothing in a matched member, warns at bundling time; if a shared keep list keeps warning for members that legitimately do not declare some of the kept packages, split it into per-member entries.
  - When a lockfile is bundled, requires lockfile pruning to succeed in the same run; otherwise the original manifests ship unchanged with a warning. A bundle that ships no lockfile keeps the pruned manifests.
  - Pruning is not validated against the code — do not remove anything the bundled code needs at runtime.

## Install troubleshooting

- The execution environment is Linux and non-root, and it does not include a C/C++ compiler. Browsers are pre-installed, so never add `playwright install`, `npx playwright install`, Puppeteer browser downloads, or similar browser installation commands.
- If `scripts.postinstall`, `scripts.prepare`, or `scripts.install` references missing files, downloads browsers, sets up git hooks, or compiles native code, set `installCommand` to skip project lifecycle scripts and rebuild dependency scripts:
  - npm: `npm install --ignore-scripts && npm rebuild`
  - pnpm: `pnpm install --ignore-scripts && pnpm rebuild`
- Flag likely dependency problems before suggesting a deploy:
  - `puppeteer` is usually unnecessary because browsers are pre-installed.
  - Native dependencies that require a compiler, such as `sharp` or `better-sqlite3`, may fail.
  - `fsevents` must be optional; a locked non-optional `fsevents` dependency can fail on Linux.
  - `workspace:*` dependencies must be included in the uploaded bundle.
  - Private registries must have auth configured through Checkly environment variables.

## Runtime model

- Do not use `runtimeId` for Playwright Check Suites. Browser and Multistep Checks use `runtimeId`; Playwright Check Suites use project dependencies plus `engine`.
- Omit `engine` unless the user explicitly asks to override the JavaScript engine or version.
- If an engine override is needed, import `Engine` from `checkly/constructs` and use `Engine.node("24")` or `Engine.bun("1.3")`.
- If no engine is configured or detected, Checkly defaults to Node.js 22. Node.js engine majors map to pinned patch versions: `22` -> `22.14.0`, `24` -> `24.13.1`, and `26` -> `26.2.0`.
- For env vars Checkly exposes at runtime (e.g. `CHECKLY=1` for "am I running on Checkly?"), see `npx checkly skills configure environment`.

**Reference:** https://www.checklyhq.com/docs/constructs/playwright-check/

```typescript
import { PlaywrightCheck } from "checkly/constructs"

const playwrightChecks = new PlaywrightCheck("multi-browser-check", {
  name: "Multi-browser check suite",
  playwrightConfigPath: "./playwright.config.ts",
  // Playwright Check Suites support all browsers
  // defined in your `playwright.config`
  pwProjects: ["chromium", "firefox", "webkit"],
  // Bundle non-code assets that specs read via `fs` at runtime.
  // Test files and anything reachable through `import` are already
  // bundled — `include` is only for files outside the import graph.
  // Globs resolve relative to the playwright config's directory.
  include: ["fixtures/**/*.json", "docs/**/*.md"],
});
```
