| name | run-website-performance-ab |
|---|---|
| description | Build, publish, and evaluate website performance changes with controlled browser A/B tests, representative-route discovery, rendered parity checks, and evidence-based keep-or-revert decisions. Supports matched Preview or staging deployments with no user traffic and production rollouts with field telemetry. Use for web performance experiments, bundle or module-graph changes, runtime variants, matched deployments, or requests to compare candidate and control websites without assuming a repository, framework, hosting provider, CMS, authentication system, or fixed route list. |
Use an exact control and one independently reversible candidate. Preserve unrelated work and never expose credentials, cookies, tokens, environment values, or protected deployment configuration.
Locate the affected application, local instructions, route inventory or sitemap, performance harness, parity harness, deployment workflow, source or variant marker, and available field monitoring. Record how each item was discovered.
Map the candidate's imports, layouts, middleware, route entries, renderer families, client boundaries, and generated manifests to its public impact surface. Select the smallest route set that covers:
- every changed route family, layout, client boundary, or renderer;
- a simple and a component-heavy page when bundle breadth is the hypothesis;
- relevant LCP forms such as text, image, video, or an interactive hero;
- a high-traffic or monitored page when field data is available;
- a page outside the expected impact surface when shared code can regress;
- affected locales and static or dynamic route shapes when routing changes.
Record the reason and affected mechanism for every selected route. Treat routes named in old reports as evidence from those runs, not defaults. If no trustworthy inventory, source marker, or variant marker exists, document the discovery gap before calling the experiment controlled.
Select the mode from the traffic that will actually reach the candidate. Do not wait for field telemetry on a deployment that has no representative users.
| Situation | Primary evidence | Recommended use |
|---|---|---|
| Matched Preview or staging deployments with no user traffic | Controlled browser lab A/B plus parity | Immediate pre-release comparison of exact builds, bundles, rendering, and resources |
| Preview or staging used by a known test cohort | Lab A/B plus cohort telemetry | Directional validation; report the cohort and do not generalize it to production users |
| Production rollout with stable control/candidate assignment | Variant-aware real-user monitoring plus the pre-release lab and parity gates | Release decision and field Core Web Vitals confirmation |
For an untrafficked Preview comparison:
- drive both exact deployments with the browser harness;
- verify the revision and source or variant on every run;
- measure requests, bytes, resources, long tasks, rendering milestones, and parity immediately;
- report LCP and CLS from scripted visits as lab Core Web Vitals data, even if a real-user monitoring service ingests it;
- report INP only when the harness performs a representative scripted interaction, and label it a lab estimate;
- do not present lab measurements as field Core Web Vitals or representative-user percentiles.
For a production rollout:
- pass the lab and parity gates before exposing users;
- assign users stably and concurrently to control or candidate;
- ensure every field event can be attributed to its resolved variant or exact deployment;
- define the minimum sample count, observation window, segmentation, error guardrails, and rollback threshold before rollout;
- compare p75 LCP, INP, and CLS on equivalent routes, devices, regions, and time windows;
- retain the lab resource evidence because field Core Web Vitals do not explain request, byte, or main-thread changes.
If field telemetry cannot distinguish the variants, use it only as an overall rollout guardrail. Do not present it as an A/B result.
Do not assume that every visit produces every field metric. Some real-user monitoring systems finalize layout, interaction, or paint metrics only after an interaction, visibility change, navigation, or page exit. Query the accepted sample count for each metric and segment, and ensure a scripted cohort completes the lifecycle event required by its telemetry provider. Telemetry generated by scripted visits remains lab evidence even when a real-user monitoring backend stores it.
Record the control revision, candidate hypothesis, selected routes and reasons, expected source or variant, and acceptance criteria before editing.
Choose the isolation method:
- Use one deployment with separate deterministic contexts for runtime-only behavior that does not change emitted assets.
- Use matched deployments for dependency, module-graph, route-entry, or bundling changes. A request-time override cannot recreate another build's assets.
Keep content revision, project, environment, region, viewport, authentication, cache policy, browser, and network settings equal unless the hypothesis intentionally changes one of them.
Inspect the dirty worktree and stage only experiment-owned files. Follow the closest repository instructions. Run the scoped lint, typecheck, tests, unused-code checks, formatting, generated-artifact checks, and build required by the affected application.
For bundle work, capture a current build manifest or analyzer artifact plus browser request and transferred-byte evidence. Do not use stale build output as final evidence.
Commit an immutable control when one does not exist. Implement one reversible candidate at a time. Publish control and candidate through the website's normal test-deployment workflow. Filter deployment output to non-secret identifiers, URLs, states, and errors.
Fail before measurement if a smoke route reaches authentication, an error page, the wrong revision, or the wrong source or variant. Never create, rotate, or reveal a protection secret merely to run the test.
Use separate fresh browser contexts for control and candidate. Apply deterministic runtime overrides before navigation when the experiment uses them. Verify the resolved revision, source, or variant on every accepted run.
Alternate order to reduce warm-cache and temporal bias. Run serially unless concurrency is itself the hypothesis. Use the same viewport and browser profile for both sources. Start with two warm-ups and seven measured samples per source; increase the sample count when noise or the decision threshold requires it.
The skill includes two neutral Playwright engines:
scripts/compare-website-performance.mjsfor controlled lab performance;scripts/compare-rendered-pages.mjsfor rendered parity.
They use only control and candidate arm names. They do not assume a framework, repository, CMS, host, deployment provider, authentication system, route list, or feature-flag product. Run them from a project that provides @playwright/test or playwright:
node /path/to/compare-website-performance.mjs \
--control-url=https://control.example \
--candidate-url=https://candidate.example \
--routes-file=/absolute/path/routes.txt \
--warmups=2 \
--runs=7 \
--output=/tmp/website-performance-ab
node /path/to/compare-rendered-pages.mjs \
--control-url=https://control.example \
--candidate-url=https://candidate.example \
--routes-file=/absolute/path/routes.txt \
--viewports=desktop,mobile \
--output=/tmp/rendered-pages-abTo deliberately send only measured visits to a real-user monitoring collector, enable the optional lifecycle mode and provide a request matcher:
WEBSITE_AB_RUM_REQUEST_PATTERN='(?:/rum/|/vitals(?:[/?#]|$))' \
node /path/to/compare-website-performance.mjs \
--control-url=https://control.example \
--candidate-url=https://candidate.example \
--routes-file=/absolute/path/routes.txt \
--flush-rum=trueThe engine blocks matching telemetry during preflight and warm-up visits. For each measured visit, it captures lab metrics, navigates to about:blank to finalize the page lifecycle, waits for a matching outbound RUM request, and only then closes the context. A missing request fails that sample. The request proves that the browser attempted delivery; verify collector acceptance and ingestion from provider sample counts. This mode creates a scripted cohort; it does not turn the results into representative field evidence.
For an INP-producing workflow, set WEBSITE_AB_INTERACTION_MODULE to an absolute ESM module path. The module must default-export a function or export runInteraction:
export async function runInteraction({ page, arm, route }) {
await page.getByRole('button', { name: 'Open menu' }).click();
await page.getByRole('menu').waitFor();
}The adapter receives the Playwright page, control or candidate arm, and route. Keep site-specific selectors and behavior in this caller-owned module, not in the neutral engine.
Use WEBSITE_AB_CONTROL_CONTEXT_JSON and WEBSITE_AB_CANDIDATE_CONTEXT_JSON to supply Playwright context cookies, headers, or ignoreHTTPSErrors without putting values in command arguments or reports. Use WEBSITE_AB_CONTEXT_ADAPTER_MODULE when context setup needs provider APIs, scoped request interception, or another behavior that plain context JSON cannot express. The absolute ESM module path must default-export a function or export configureContext({ context, arm, baseUrl }). Both portable engines call it before the first navigation.
Do not put a deployment-protection secret in Playwright extraHTTPHeaders when a page can request third-party origins. Those headers apply too broadly. A context adapter must scope the secret to the protected origin.
The portable engines include scripts/vercel-protection-context-adapter.mjs. It can use an explicitly supplied Protection Bypass for Automation secret, or retrieve an existing secret through the authenticated Vercel CLI:
WEBSITE_AB_CONTEXT_ADAPTER_MODULE=/path/to/vercel-protection-context-adapter.mjs \
WEBSITE_AB_VERCEL_BYPASS_FROM_CLI=true \
WEBSITE_AB_VERCEL_PROJECT=example-project \
WEBSITE_AB_VERCEL_SCOPE=example-team \
WEBSITE_AB_VERCEL_PROTECTED_ARMS=candidate \
node /path/to/compare-website-performance.mjs \
--control-url=https://control.example \
--candidate-url=https://candidate.example \
--routes-file=/absolute/path/routes.txtThe optional scope can be omitted. Use WEBSITE_AB_VERCEL_PROTECTED_ARMS=control,candidate when both deployments are protected. WEBSITE_AB_VERCEL_BYPASS_SECRET takes precedence over CLI retrieval when a secret is already available in the environment.
CLI retrieval runs only the read-only project API command. It extracts an existing automation-bypass secret in memory and refuses to create or rotate one. The adapter sends the bypass headers only to requests whose origin matches that arm's configured base URL. Scripts and reports record only that a context adapter was configured; they do not include its module path, project, scope, or secret.
Use --expected-<arm>-header=name:value or the paired --expected-<arm>-meta-selector and --expected-<arm>-meta-value options to reject the wrong revision, source, or variant.
An adapter skill can discover routes, create context JSON for deterministic flags, select a provider context adapter, choose expected markers, and run these engines. Keep provider-specific retrieval and secret handling in context adapters. Do not add provider or product logic to the neutral engines.
Report p50, p75, and p95 for:
- LCP, FCP, CLS, and INP or a documented lab proxy;
- TTFB, load, and resource completion;
- long-task count and duration;
- request count and transferred resources.
For bundle candidates, require a deterministic request, byte, parse, or main-thread reduction in addition to timing. Inspect which resources disappeared; aggregate browser timing can undercount cross-origin transfers.
Call the synthetic LCP and CLS results lab Core Web Vitals data. They measure the same rendering and layout signals in a controlled browser, but describe only the scripted environment and sample. A scripted interaction can produce a lab INP estimate; a navigation-only run cannot. Field Core Web Vitals require real-user measurements across eligible page loads and use the field population's p75. Query sample counts with the percentiles and mark a result inconclusive when a route, device class, or variant has insufficient data.
The portable performance engine measures one viewport per invocation. Run it once for each required device profile so samples and summaries remain independent.
Run the discovered parity harness against the same revisions, routes, contexts, and expected markers. Include desktop and mobile profiles. Compare the candidate with its exact control build.
Require zero unexplained differences in status, redirects, metadata, visible text, headings, links, media, normalized DOM, structured data, and browser errors. Review screenshot differences separately because animation, font rasterization, and antialiasing can change hashes without changing content.
For an untrafficked Preview, keep a candidate only when the lab improvement is substantial, repeatable, mechanism-consistent, and parity-safe, without a material tail regression. Deterministic byte or main-thread reductions can justify retention when timings are noisy, but document that the result is an efficiency improvement pending field confirmation.
For a production rollout, require both the pre-release gates and enough variant-attributed field data for the predefined decision. Roll back when error guardrails or material field regressions trigger, even when the lab result was positive.
Reject and revert a candidate that adds errors, changes content, only reshuffles resources, or trades a small median gain for a material p75 or p95 regression. Use a normal revert after publishing shared history.
Use each retained state as the next control. Repeat the workflow for the next independent candidate.
Document:
- exact control and candidate revisions and test URLs;
- isolation method and verified revision, source, or variant;
- route-selection reasons, warm-ups, samples, ordering, viewport, and concurrency;
- metric percentiles and deterministic resource differences;
- parity result and artifact paths;
- keep or revert decision and its mechanism;
- limitations, including cache state, deployment noise, and synthetic versus field evidence.
State the evidence mode explicitly. For Preview-only results, say that no representative field traffic was available. For production results, report variant sample counts and attribution method with the p75 metrics.
Commit and publish the decision record only with user authorization. Verify the remote revision and preserve unrelated work.