---
description: Load the canonical always-remote IPZ E2E contract
---

Audience: AI coding agents first. Load `.claude/skills/ipz-e2e.md` and obey it.

## Always-remote invariant

ALWAYS execute remotely. No workstation fallback. No local build/browser/server half.

Offload the entire request—dependency installation, snapshot/admin build, WordPress stack,
readiness, Playwright, Chromium, Firefox, retry, focused/full test, smoke, screenshot/visual capture,
and diagnostic browser run—to the same authorized buildbox through `~/.claude/bin/e2e-remote`.

Use only the registered E2E hosts `debian1`/`debian2`/`debian3`
(`debian1/debian2/debian3`) from `~/.claude/buildbox-hosts.json`. Let `e2e-remote` resolve and spill
through the registry order. Fail closed if the registry, wrapper, host, or remote prerequisite is
unavailable. Never fall back to the workstation; never test `dev1`.

The workstation may invoke the wrapper and receive artifacts only. Reject local `npm ci`, `npm run
build`, `pnpm`, `npx playwright`, `playwright install`, `podman`, `docker compose`, `wp`, `php -S`,
browser, smoke, screenshot, diagnostic, retry, direct `ssh`/`scp`/`rsync`, proxy, tunnel, or a
hardcoded host selection such as `--hosts debian2`. Pin a host only for the documented snapshot
repair, through `e2e-remote` and a registry-emitted name.

Use `tests/e2e/run-remote.sh` where its complete remote stack/capture sequence applies; otherwise
use the direct wrapper seam from the skill. Keep server and browser on one buildbox, use
`WP_BASE_URL=http://127.0.0.1:8080`, and keep the Playwright/global-setup default on that same
`127.0.0.1` host so WordPress login cookies cannot cross from `localhost`. Preserve every readiness, fixture, artifact, cleanup,
Chromium, and Firefox requirement in the skill. Do not run E2E builds or tests on the workstation.

```bash
~/.claude/bin/e2e-remote \
  --server "tests/e2e/remote-stack.sh" \
  --wait-port 8080 \
  --env WP_BASE_URL=http://127.0.0.1:8080 \
  -- /home/user/.local/share/mise/installs/pnpm/11.5.2/pnpm \
    --dir tests/e2e \
    exec playwright test --config=playwright.config.js <focused-spec> \
    --project=chromium --project=firefox
```

Use the exact client seam above. Never use `sh -lc`, Bun, `cd`, `npx`, `npm exec`, or a local
server/browser pair. Preserve the remote stack's locked `npm ci` and remote `npm run build`, snapshot
identity, readiness, exact-label cleanup, fixture graph, and artifact contract.

## Yoast compatibility gate

Mandatory for any change touching `includes/Compatibility/` or any `wpseo_*`/third-party-SEO
filter handler. `tests/e2e/compatibility/with-yoast.sh` takes exactly one browser project per
invocation, so chromium and firefox each need a separate, sequential run (never parallel — a
concurrent second run collides on port `8080`), each with its own `IPZ_E2E_RUN_ID`.
`IPZ_E2E_RUN_ID` is required; the client hard-fails without it. Yoast version is pinned by
`IPZ_COMPAT_YOAST_VERSION` (default `25.6`).

```bash
~/.claude/bin/e2e-remote \
  --server "tests/e2e/remote-stack.sh" \
  --wait-port 8080 \
  --env WP_BASE_URL=http://127.0.0.1:8080 \
  --env IPZ_E2E_RUN_ID=<unique-run-id> \
  --key <dedupe-key> \
  -- tests/e2e/compatibility/with-yoast.sh <chromium|firefox>
```

See `.claude/skills/ipz-e2e.md` for the full contract, including the lesson that made this gate
mandatory: Yoast passes generator OBJECTS to `wpseo_schema_graph_pieces` and associative ARRAYS to
`wpseo_schema_graph`; an array-shaped callback on the pieces filter is a guaranteed site-wide fatal.
