trails/e2e/fixtures/test.ts
Ullrich Schäfer 796dbea804
Silently drop Sentry envelope requests in e2e fixture
Two e2e tests intermittently failed with "unmocked external request"
when the Sentry browser SDK emitted an envelope to the real ingest
endpoint despite `enabled: false`. The BrowserTracing integration
captures the page-load transaction before init's enabled flag fully
propagates, so a single envelope leaks on cold start.

Add a SILENT_DROP list that aborts matching requests without recording
them as blocked, so legitimate missing-mock failures still surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 00:53:41 +02:00

78 lines
2.9 KiB
TypeScript

import { test as base, expect } from "@playwright/test";
/**
* Hosts the E2E environment is allowed to contact for real. Anything
* else that reaches our catch-all route (i.e. not intercepted by a
* test-specific `page.route(...)`) is aborted, and the test fails
* at teardown with the list of blocked URLs.
*
* The trigger for this safety net was #282 — a POI test that silently
* relied on live Overpass data for months because its `page.route`
* was pointing at a URL the browser no longer hit after `/api/overpass`
* was introduced. With this fixture in place, a request to
* `overpass.private.coffee` (or any other unlisted host) would abort
* and the test would surface the missing mock immediately.
*/
const EXTERNAL_ALLOWLIST: RegExp[] = [
// App origins served by Playwright's webServer
/^https?:\/\/localhost(:\d+)?\//,
/^https?:\/\/127\.0\.0\.1(:\d+)?\//,
// Tile CDNs used by the map layers in packages/map-core/src/tiles.ts
/tile\.openstreetmap\.org/,
/tile\.opentopomap\.org/,
/tile-cyclosm\.openstreetmap\.fr/,
/tile\.waymarkedtrails\.org/,
/tiles\.wmflabs\.org/,
];
/**
* External requests we silently drop (no allowlist, no failure). The
* Sentry browser SDK emits an envelope on first navigation even when
* `enabled: false` (the BrowserTracing integration captures the page
* load transaction before `init`'s enabled flag fully propagates). We
* don't want to allow it through to the real ingest endpoint, but we
* also don't want every test to fail with "blocked unmocked request".
*/
const SILENT_DROP: RegExp[] = [
/ingest\.[a-z]+\.sentry\.io/,
];
export const test = base.extend({
page: async ({ page }, use) => {
const blocked: string[] = [];
// Registered first, so a test's later `page.route(pattern, ...)`
// calls take precedence (Playwright runs handlers in reverse
// registration order). When no specific mock matches, this
// catch-all decides between continue (allowlist) and abort.
await page.route("**", async (route) => {
const url = route.request().url();
if (EXTERNAL_ALLOWLIST.some((re) => re.test(url))) {
await route.continue();
return;
}
if (SILENT_DROP.some((re) => re.test(url))) {
await route.abort("failed");
return;
}
blocked.push(url);
await route.abort("failed");
});
await use(page);
if (blocked.length > 0) {
const unique = [...new Set(blocked)];
const preview = unique.slice(0, 10).join("\n ");
const more = unique.length > 10 ? `\n …and ${unique.length - 10} more` : "";
throw new Error(
`${blocked.length} unmocked external request(s) blocked:\n ${preview}${more}\n\n` +
`Either mock them with page.route(...) in the test, or add a\n` +
`pattern to EXTERNAL_ALLOWLIST in e2e/fixtures/test.ts.`,
);
}
},
});
export { expect };
export type { CDPSession, Page } from "@playwright/test";