stableFor developers

Project configuration

A .pixmoat.yaml file keeps a project’s comparison and capture settings in your repository, so a change to them goes through the same review as your code. This page explains how the file is read and when each setting takes effect. .pixmoat.yaml reference lists every key with its type, default and scope.

The file is optional. Without it, every build uses the project’s settings from Settings.

Build-scoped and project-global settings

Every key has one of three scopes. The reference shows the scope of each key.

Build-scoped settings

Build-scoped settings travel with the commit. When startBuild() creates a build, the Playwright client sends them with it, and pixmoat stores the result with that build. A key you leave out keeps the project’s current setting.

So a branch can try a different tolerance or turn on accessibility audits without changing other branches, and each build keeps the settings it was compared with. The build-scoped sections are default_tolerance, blocking_mode, removal_gating, diff, viewports, browsers, flakiness, performance, accessibility, console and themes.

Limit: blocking_mode, removal_gating and the gating key of performance, accessibility and console are stored with the build, but pixmoat enforces the project’s value from Settings. The file’s value doesn’t change the commit status, and doesn’t change what must be decided before Finish review. Set these in Settings.

The client sends the file only when it creates the build:

  • startBuild() in global-setup.ts sends it. If PIXMOAT_BUILD_ID is already set, startBuild() adds to that build and sends nothing.
  • The Playwright reporter (beta) never sends it.

Project-global settings

Project-global settings apply to the project as a whole, for every branch. Builds don’t read them from the file: a feature branch can’t change who must approve its own review. The project-global sections are retention, agent_auto_approve, review_policy, owner_map, default_reviewers, webhook and git_provider.

Change these in Settings. The pixmoat CLI can apply them from the file with pixmoat config apply on the default branch, but the CLI has no published install path yet (see CLI commands). Until then, keep project-global sections in the file only as a record, and make the change in Settings too.

Client-only settings

The capture section is read by the Playwright client before each screenshot. pixmoat ignores it. Flaky snapshots explains what each key does.

Where the file goes

Create .pixmoat.yaml in the directory you run npx playwright test from. The client also accepts .pixmoat.yml if there’s no .pixmoat.yaml.

The client reads the file from its current working directory only. It doesn’t look in the repository root or in parent directories. If your CI job runs cd e2e && npx playwright test, the file must be in the e2e directory; a file at the repository root is ignored without a warning, and the build uses the project’s settings.

Start the file with the schema line and version: 1:

# yaml-language-server: $schema=https://pixmoat.com/schemas/pixmoat-config.schema.json
version: 1
default_tolerance: 0.001
blocking_mode: blocking

The schema line gives you completion and validation in editors that use the YAML language server, such as VS Code with the YAML extension.

If the file can’t be parsed, isn’t a mapping or contains a secret such as webhook.secret, startBuild() stops with an error that starts with pixmoat: and names the file. No build is created. Fix the file and run the tests again. Keep tokens and webhook secrets in CI/CD variables and Settings, never in this file.

This example sets every section:

# yaml-language-server: $schema=https://pixmoat.com/schemas/pixmoat-config.schema.json
#
# A .pixmoat.yaml that sets every section. Keep only the keys you need; a key
# you leave out keeps the project's current setting.
#
# File location: .pixmoat.yaml in the directory you run `npx playwright test`
#                from (usually the repository root).
# Install:       nothing extra. @s4labs/pixmoat-playwright reads this file.
# Variables:     none. Never put tokens or webhook secrets in this file.
# Command:       npx playwright test
# Expected:      the run creates its build without a "pixmoat:" error about
#                this file, and pixmoat stores the build-scoped values with the
#                build. Keys marked "enforced from Settings" are stored but
#                pixmoat applies the project's Settings value. Project-global
#                keys (retention, agent_auto_approve, webhook, review_policy,
#                default_reviewers, owner_map, git_provider) only change when
#                applied from the default branch.
version: 1                         # schema version, must be 1

# ── Build-scoped settings: travel with the commit ─────────────────────
default_tolerance: 0.001           # 0.0–1.0, mismatch ratio threshold
blocking_mode: blocking            # blocking | report_only (enforced from Settings)
removal_gating: advisory           # advisory | blocking (enforced from Settings)

diff:
  threshold: 0.1                   # YIQ color sensitivity (0.0–1.0)
  detect_aa: true                  # anti-aliasing detection
  diff_color: [255, 0, 255, 255]   # RGBA highlight color
  dpr: 1                           # device pixel ratio (1, 2 or 3)

viewports:                         # named viewport presets
  desktop: 1280x720
  mobile: 375x812

browsers:                          # expected browser identifiers
  - chromium

flakiness:
  captures: 2                      # 1–5; the Playwright client still captures once
  flakiness_threshold: 0.002       # inter-capture mismatch ratio
  quarantine_after: 3              # flaky builds before quarantine
  recovery_after: 5                # stable builds before recovery

performance:                       # collection also needs PIXMOAT_PERFORMANCE=true
  enabled: true
  gating: advisory                 # advisory | blocking (enforced from Settings)
  regression_threshold: 0.15       # allowed variance from baseline (0.0–1.0)
  thresholds:
    lcp: { soft_fail: 2500, hard_fail: 4000 }
    cls: { soft_fail: 0.1, hard_fail: 0.25 }

accessibility:
  enabled: true
  preset: wcag_aa                  # contrast_only | wcag_aa | wcag_aaa
  gating: advisory                 # advisory | blocking (enforced from Settings)
  rules:
    exclude:
      - region

console:
  enabled: true
  gating: advisory                 # advisory | blocking (enforced from Settings)
  capture_levels: [error, warning]
  ignore:
    - "Download the React DevTools"

themes:
  light:
    media: "(prefers-color-scheme: light)"
  dark:
    media: "(prefers-color-scheme: dark)"

capture:                           # read by the capture client only
  animations: end                  # end | pause | allow
  wait_for_fonts: true
  wait_for_images: true
  image_timeout_ms: 3000

# ── Project-global settings: applied only from the default branch ─────
retention:
  keep_builds: 50                  # keep N most recent builds per branch
  delete_after_days: 90            # delete builds older than N days
  branch_rules:
    - branch: "release/*"
      keep_builds: 100

agent_auto_approve: intent_only    # none | intent_only | all

review_policy:
  min_approvals: 1
  require_distinct_from_author: true
  agent_may_finish: false

webhook:
  url: https://ci.example.com/hook
  provider: generic                # generic | gitlab (label only)
  on_approved: true
  on_rejected: true
  # The webhook secret is set in the UI only, never in this file.

Turn on accessibility auditing

The client runs an accessibility audit after each screenshot when the file has an accessibility section:

accessibility:
  enabled: true
  preset: wcag_aa
  • An accessibility section turns the audit on, even an empty one. accessibility: {} makes the client audit every snapshot. Only enabled: false inside the section turns it off.
  • Without an accessibility section, the client never audits. Turning on Accessibility in Settings doesn’t make the client audit. The toggle only decides whether pixmoat evaluates the results the client sends.
  • Write enabled: true. With accessibility: {}, the client audits but pixmoat uses the project’s Accessibility toggle to decide whether to evaluate the results. If the toggle is off, the audits run and the results are dropped.

Accessibility checks covers presets, gating and reviewing the results.

Collect performance metrics

Performance collection has two switches, and you need both:

  1. Set the environment variable PIXMOAT_PERFORMANCE=true in the Playwright process. This is the only thing that makes the client collect metrics. performance.enabled: true in the file doesn’t.
  2. Set performance.enabled: true in the file, or turn on Performance in Settings. Without it, pixmoat drops the metrics the client sends.

Performance and console explains the metrics and thresholds.

Capture viewports, browsers and themes

The client decides what to capture from the test code and the environment, not from the viewports, browsers and diff.dpr keys:

  • viewports doesn’t resize anything. It names presets that pixmoat stores with the build. To capture several sizes, pass viewports to pixmoat.snapshot() or set PIXMOAT_VIEWPORTS.
  • browsers doesn’t open any browser. To capture several browsers, pass browsers to pixmoat.snapshot() or set PIXMOAT_BROWSERS.
  • diff.dpr doesn’t change the pixel ratio. The client takes it from the dpr option, PIXMOAT_DPR or the Playwright context’s deviceScaleFactor.
  • flakiness.captures doesn’t make the client capture more than once. The client takes one screenshot per snapshot.

Connect Playwright shows the options.

themes is different: the client reads it. Once the file defines themes, every snapshot() call captures the page once per theme:

themes:
  light:
    media: "(prefers-color-scheme: light)"
  dark:
    media: "(prefers-color-scheme: dark)"

The client uses dark mode for a theme whose media contains dark, and light mode otherwise. Each theme becomes its own snapshot, with ___<theme> added to the browser label, for example chromium___dark.

Console capture and page settling

Two more sections change what the client does:

  • console records console errors during capture, but only with enabled: true. Performance and console shows the setup.
  • capture controls animations and waiting for fonts and images before each screenshot. Flaky snapshots shows the options.

Precedence

When the same setting is set in several places, the most specific one wins:

  1. an option passed to pixmoat.snapshot() in test code
  2. a build-scoped key in .pixmoat.yaml, except the blocking and gating keys listed under Build-scoped settings
  3. the project’s setting in Settings
  4. pixmoat’s default

Plan limits always apply on top. For example, a retention setting can’t keep builds longer than your plan allows.

UI behavior

When the project’s latest build on the default branch included a .pixmoat.yaml file, Settings shows a banner that says the settings are managed by .pixmoat.yaml and that changes made there will be overwritten on the next pixmoat config apply. The Accessibility, Console, Review Policy, Owner Map and Git Provider sections show the same banner.

The settings stay editable. A build-scoped change you make in Settings is used by every build whose file leaves that key out. Builds whose file sets the key keep using the file’s value, except for the blocking and gating keys, which always come from Settings.

CLI commands

The pixmoat CLI has config validate, config diff and config apply commands. It has no published install path yet, so these commands are for developers who build the CLI from source. CLI reference lists their options.

Fragment. pixmoat config apply writes the project-global settings. It refuses to run unless the current branch ($CI_COMMIT_BRANCH, $GITHUB_REF_NAME or the checked-out branch) is the project’s default branch, so run it only there:

pixmoat-config:
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  script:
    - pixmoat config validate
    - pixmoat config apply