Skip to content

refactor(e2e): verify PerfKit at boundary instead of produce path#3613

Draft
fredericoo wants to merge 3 commits intomainfrom
fb-fix-perfkit-bot-detection
Draft

refactor(e2e): verify PerfKit at boundary instead of produce path#3613
fredericoo wants to merge 3 commits intomainfrom
fb-fix-perfkit-bot-detection

Conversation

@fredericoo
Copy link
Copy Markdown
Contributor

@fredericoo fredericoo commented Mar 23, 2026

Summary

Replaces PerfKit produce request assertions with boundary-level assertions that verify what Hydrogen actually controls, eliminating flaky dependencies on PerfKit's internal publish pipeline.

Problem

PerfKit's CDN build added `isObviousBot()` (Shopify/perf-kit#156, deployed to CDN ~March 18-19) which silently blocks all metric publishing when it detects Playwright automation signals. Even after bypassing bot detection via CDP, tests remained flaky because `web-vitals` PerformanceObserver callbacks don't fire reliably in headless Chromium, leaving PerfKit's internal queue empty.

The previous `verifyPerfKitRequests` depended on 5 independent systems outside our control:

  1. Bot detection bypass (PerfKit CDN script)
  2. web-vitals PerformanceObserver timing (browser internals)
  3. PerfKit flush heuristics (PerfKit internals)
  4. sendBeacon reliability (browser API)
  5. CDP request capture for beacons (Chrome DevTools Protocol)

Approach

Test at Hydrogen's boundary instead of PerfKit's produce path. The new `verifyPerfKitRequests` asserts:

  1. PerfKit script element (`#perfkit`) is present with correct `data-application`, `data-monorail-region`, `data-spa-mode` attributes
  2. `window.PerfKit` is initialized (SPA navigation API available)
  3. Tracking cookies (`_shopify_y`, `_shopify_s`) match the expected server-timing values

This verifies the contract Hydrogen owns — providing correct configuration and tracking data to PerfKit — without depending on PerfKit actually publishing.

Changes

  • `e2e/fixtures/storefront.ts`: Refactored `verifyPerfKitRequests` to boundary assertions. Removed CDP bot-signal-hiding code and CDP produce-request capture (no longer needed).
  • 4 spec files: Removed `finalizePerfKitMetrics()` + `waitForTimeout(500)` dance, added `await` to now-async `verifyPerfKitRequests`.

Net: -61 lines (removed more code than added).

Local test results

  • privacy-banner-accept: Passes reliably (both new-cookies and old-cookies)
  • consent-tracking-accept: PerfKit assertions pass; intermittent failure at `addToCart()` (pre-existing UI flake, unrelated to PerfKit)

Test plan

  • CI E2E tests pass (all 4 PerfKit assertion tests in new-cookies and old-cookies)
  • No regressions in other E2E tests

PerfKit's CDN build (shopify-perf-kit-spa.min.js) contains isObviousBot()
which checks navigator.webdriver, window.__playwright__binding__, and
window.__pwInitScripts. When any of these are truthy, PerfKit silently
suppresses all metric publishing — no sendBeacon, no XHR, no produce
requests at all.

Playwright always sets navigator.webdriver=true in automation mode,
which causes the bot check to trigger. This means verifyPerfKitRequests()
can never find any produce requests, causing consent-tracking-accept and
privacy-banner-accept tests to fail.

The fix uses CDP's Page.addScriptToEvaluateOnNewDocument to override
these automation signals before any page scripts (including PerfKit)
execute. CDP injection avoids the chicken-and-egg problem that
page.addInitScript would cause (it sets __pwInitScripts which PerfKit
also detects).

Additionally, goto() now awaits the CDP setup promise to ensure the
bot signal override is registered before the first navigation.

Note: This addresses bot detection only. There may be a secondary issue
with web-vitals metrics not being collected in headless mode, which
would prevent PerfKit from having data to flush even with bot detection
bypassed.
@shopify
Copy link
Copy Markdown
Contributor

shopify bot commented Mar 23, 2026

Oxygen deployed a preview of your fb-fix-perfkit-bot-detection branch. Details:

Storefront Status Preview link Deployment details Last update (UTC)
Skeleton (skeleton.hydrogen.shop) ✅ Successful (Logs) Preview deployment Inspect deployment March 24, 202612:20 PM

Learn more about Hydrogen's GitHub integration.

@fredericoo fredericoo closed this Mar 23, 2026
@fredericoo fredericoo reopened this Mar 23, 2026
PerfKit uses sendBeacon() which Playwright's page.on('request') doesn't
intercept. Add CDP-level capture for /v1/produce requests so
perfKitProduceRequests is populated even when PerfKit sends via beacon.

This complements the bot signal hiding from the previous commit — with
both changes, PerfKit can publish AND we can observe its requests.
Comment on lines +139 to +154
source: `
Object.defineProperty(navigator, 'webdriver', {
get: () => false,
configurable: true,
});
Object.defineProperty(window, '__playwright__binding__', {
get: () => undefined,
set: () => {},
configurable: true,
});
Object.defineProperty(window, '__pwInitScripts', {
get: () => undefined,
set: () => {},
configurable: true,
});
`,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im sorry this is disgusting

Replace verifyPerfKitRequests assertions that depended on PerfKit's
internal publish pipeline (bot detection + web-vitals PerformanceObserver
+ sendBeacon flush) with boundary-level assertions that verify what
Hydrogen actually controls:

1. PerfKit script element is present with correct data attributes
2. window.PerfKit is initialized (SPA navigation API available)
3. Tracking cookies (_shopify_y, _shopify_s) match expected values

This eliminates 5 independent failure points that were outside our
control: bot detection timing, web-vitals callback scheduling in
headless Chromium, PerfKit flush heuristics, sendBeacon reliability,
and CDP request capture for beacons.

Also removes the finalizePerfKitMetrics + waitForTimeout(500) dance
from test specs since we no longer need to trigger PerfKit's flush.
Reverts the CDP bot-signal-hiding and produce-request-capture code
since the refactored assertions don't depend on PerfKit publishing.
@fredericoo fredericoo changed the title fix(e2e): bypass PerfKit bot detection in Playwright tests refactor(e2e): verify PerfKit at boundary instead of produce path Mar 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant