From 328008f8e90ddc6d7d342daa58deb108eade12d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemek=20Wi=C4=99ch?= Date: Tue, 12 May 2026 18:08:32 +0200 Subject: [PATCH] Embed font hoping that this will make the visual tests reproducible --- docs/PLAYWRIGHT_DESIGN.md | 6 ++--- docs/SCREENSHOT_TESTS_DESIGN.md | 6 ++--- tests/README.md | 2 +- tests/details_visual.spec.ts | 4 +-- tests/fixtures/montserrat.woff2 | Bin 0 -> 37956 bytes tests/helpers.ts | 45 +++++++++++++++++++++++++++++--- tests/intro.spec.ts | 4 +-- tests/intro_visual.spec.ts | 4 +-- 8 files changed, 54 insertions(+), 17 deletions(-) create mode 100644 tests/fixtures/montserrat.woff2 diff --git a/docs/PLAYWRIGHT_DESIGN.md b/docs/PLAYWRIGHT_DESIGN.md index 7216803..8f2761a 100644 --- a/docs/PLAYWRIGHT_DESIGN.md +++ b/docs/PLAYWRIGHT_DESIGN.md @@ -131,7 +131,7 @@ This section defines the granular step-by-step instructions and enumerates **eve * **`tests/global.d.ts`** * *Rationale:* Custom global type declaration file for E2E tests to safely declare `__registeredTools` on the `Window` interface without TypeScript compiler warnings. Redundant overrides for `Navigator` are omitted because the test suite inherits it from the application's core WebMCP declarations. * **`tests/helpers.ts`** - * *Rationale:* Shared test utilities to encapsulate wildcard route mocking for family tree fetching (`setupGedcomRoute`) and tracking interception (`blockTracking`). This avoids code duplication across spec files. + * *Rationale:* Shared test utilities to encapsulate wildcard route mocking for family tree fetching (`setupGedcomRoute`) and hermetic context routing (`setupHermeticEnvironment`). This avoids code duplication across spec files. * **`tests/fixtures/embedded_frame.html`** * *Rationale:* Physical template wrapper file defining the iframe and message-passing structure for embedded view E2E verification. * **`src/datasource/testdata/test.ged`** @@ -190,7 +190,7 @@ This section defines the granular step-by-step instructions and enumerates **eve 3. Author `tests/global.d.ts` to provide TypeScript type definitions for mocked window objects: * **Type Extension**: Declares `__registeredTools?` on the global `Window` interface to prevent TypeScript compilation errors during WebMCP mocks. 4. Author `tests/helpers.ts` to provide reusable mock setups and routing interceptions: - * **Tracking Blockers**: Implements a `blockTracking(context)` helper that intercepts and aborts requests targeting Google Analytics and Tag Manager (`**/*google-analytics.com/**`, `**/*googletagmanager.com/**`) to guarantee hermetic and fast test execution. + * **Hermetic Routing**: Implements a `setupHermeticEnvironment(context)` helper that intercepts tracking services (`**/*google-analytics.com/**`, `**/*googletagmanager.com/**`) and embeds baseline web fonts to guarantee deterministic and fast test execution. * **GEDCOM Mocks**: Implements a `setupGedcomRoute(context)` helper that reads the version-controlled local dataset (`src/datasource/testdata/test.ged`) and routes all requests matching `**/family.ged` to be fulfilled with it, serving a `200 OK` response with CORS enablement headers (`Access-Control-Allow-Origin: *`). 5. Author the physical template wrapper file `tests/fixtures/embedded_frame.html` for testing embedded iframe communications: * **Structure**: Defines a standard wrapper document housing an iframe that points to the app's embedded route: `/#/view?embedded=true&handleCors=false`. @@ -200,7 +200,7 @@ This section defines the granular step-by-step instructions and enumerates **eve ##### 1. Intro Test (`tests/intro.spec.ts`) Checks the landing page layout, menu items, and basic static DOM presence: -* **Setup**: Leverages `beforeEach` to block analytics and tracking servers using the `blockTracking` helper, then loads the index page (`/`). +* **Setup**: Leverages `beforeEach` to configure hermetic routes using the `setupHermeticEnvironment` helper, then loads the index page (`/`). * **Assertions**: * Verifies that the main intro landing text content (specifically checking for the presence of `'Examples'`) is visible on the page. * Asserts that core action buttons in the menu (exact text `'Open file'` and `'Load from URL'`) are properly rendered and visible to the user. diff --git a/docs/SCREENSHOT_TESTS_DESIGN.md b/docs/SCREENSHOT_TESTS_DESIGN.md index b828c26..f7213de 100644 --- a/docs/SCREENSHOT_TESTS_DESIGN.md +++ b/docs/SCREENSHOT_TESTS_DESIGN.md @@ -51,7 +51,7 @@ This section defines the granular, step-by-step implementation steps and enumera #### 2. Files to [NEW] * **`tests/helpers.ts`** - * *Rationale:* Provide shared E2E/visual testing helper utilities. Features `blockTracking()` to abort external Google Analytics and Google Tag Manager network requests (ensuring offline hermetic execution) and `setupGedcomRoute()` to serve a standard mock `.ged` dataset. + * *Rationale:* Provide shared E2E/visual testing helper utilities. Features `setupHermeticEnvironment()` to abort external tracking requests and embed local fonts (ensuring offline hermetic execution) and `setupGedcomRoute()` to serve a standard mock `.ged` dataset. * **`tests/intro_visual.spec.ts`** * *Rationale:* Verify the landing page layout, copy block positions, and logo alignments. Employs an in-browser DOM script to overwrite dynamic footer versioning and dynamic changelog blocks prior to capture, ensuring baseline immunity. * **`tests/charts_visual.spec.ts`** @@ -74,7 +74,7 @@ This section defines the granular, step-by-step implementation steps and enumera * `"test:visual:update": "playwright test --project=visual --update-snapshots"` to automatically regenerate baseline reference files. #### Step 2: Landing Page Visual Validation Spec (`tests/intro_visual.spec.ts`) -1. Define a test block marked with the `@visual` tag, utilizing `blockTracking` helper in `beforeEach`. +1. Define a test block marked with the `@visual` tag, utilizing `setupHermeticEnvironment` helper in `beforeEach`. 2. Instruct the browser to navigate to the root path `/`. 3. Right before assertion, trigger `page.evaluate` to clean dynamic elements: * Target the `.version` class element and set `.innerText = "version: 2026-01-01 00:00 (testcommit)"`. @@ -91,7 +91,7 @@ This section defines the granular, step-by-step implementation steps and enumera * Capture the isolated canvas screenshot: `expect(container).toHaveScreenshot('chart-[type].png')`. #### Step 4: Details Panel Layouts Spec (`tests/details_visual.spec.ts`) -1. Set up a `beforeEach` block to block analytics via `blockTracking(context)`. +1. Set up a `beforeEach` block to establish hermetic routes via `setupHermeticEnvironment(context)`. 2. Define isolated test blocks with the `@visual` tag, each loading its own dedicated inline micro-GEDCOM dataset: * **Complex Names Test:** * Mock `**/family.ged` with a GEDCOM string containing prefix/suffix/rufname tags. diff --git a/tests/README.md b/tests/README.md index afbfbbb..36018ef 100644 --- a/tests/README.md +++ b/tests/README.md @@ -38,7 +38,7 @@ To enable lightweight, reproducible, and offline executions, tests rely on the f * **[fixtures/embedded_frame.html](fixtures/embedded_frame.html)**: HTML template for serving cross-origin iframe wrapper mockups virtually to the browser container. * **[global.d.ts](global.d.ts)**: TypeScript declarations defining window overrides (like AI registration pointers `window.__registeredTools`) to bypass compiler warnings. * **[helpers.ts](helpers.ts)**: Unified routing utilities: - * `blockTracking()`: Intercepts and halts metrics and analytical HTTP queries during spec executions. + * `setupHermeticEnvironment()`: Intercepts and halts metrics/analytical queries and embeds local baseline fonts during spec executions. * `setupGedcomRoute()`: Re-routes standard genealogy payload network paths directly to load standard local datasets on-the-fly (`src/datasource/testdata/test.ged`). * **[tsconfig.json](tsconfig.json)**: Typecheck preferences custom to the Playwright runner environment to avoid compilation type collisions. * **`[spec_name]-snapshots/`**: Directory structure containing expected baseline references and image comparison files. diff --git a/tests/details_visual.spec.ts b/tests/details_visual.spec.ts index b1b8f06..deee425 100644 --- a/tests/details_visual.spec.ts +++ b/tests/details_visual.spec.ts @@ -1,11 +1,11 @@ import {expect, test} from '@playwright/test'; import dedent from 'dedent'; import * as fs from 'fs'; -import {blockTracking, mockGedcomResponse, waitForFonts} from './helpers'; +import {mockGedcomResponse, setupHermeticEnvironment, waitForFonts} from './helpers'; test.describe('Details panel visual validation @visual', () => { test.beforeEach(async ({page, context}) => { - await blockTracking(context); + await setupHermeticEnvironment(context); await page.goto('/'); await waitForFonts(page); }); diff --git a/tests/fixtures/montserrat.woff2 b/tests/fixtures/montserrat.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..3b2eaa95e30d5f6c73336d32576b705d4cc6c118 GIT binary patch literal 37956 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 Q_E!toNJ=CTA}<5i2^w)qP5=M^ literal 0 HcmV?d00001 diff --git a/tests/helpers.ts b/tests/helpers.ts index 39e0176..9e75fcf 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -2,12 +2,49 @@ import {BrowserContext, Page} from '@playwright/test'; import * as fs from 'fs'; /** - * Blocks external tracking services (like Google Analytics and Google Tag Manager) - * to guarantee a hermetic and fast test environment. + * Sets up a hermetic test environment by blocking external tracking services + * and intercepting Google Fonts to serve static embedded fonts locally. */ -export async function blockTracking(context: BrowserContext): Promise { +export async function setupHermeticEnvironment(context: BrowserContext): Promise { await context.route('**/*google-analytics.com/**', (route) => route.abort()); await context.route('**/*googletagmanager.com/**', (route) => route.abort()); + + // Intercept Google Fonts stylesheet requests to serve a deterministically embedded local font. + await context.route('**/fonts.googleapis.com/css2**', async (route) => { + const cssContent = ` + @font-face { + font-family: 'Montserrat'; + font-style: normal; + font-weight: 100 900; + font-display: block; + src: url('http://localhost:3000/test-fonts/montserrat.woff2') format('woff2'); + } + @font-face { + font-family: 'Montserrat'; + font-style: italic; + font-weight: 100 900; + font-display: block; + src: url('http://localhost:3000/test-fonts/montserrat.woff2') format('woff2'); + } + `; + await route.fulfill({ + status: 200, + contentType: 'text/css', + headers: {'Access-Control-Allow-Origin': '*'}, + body: cssContent, + }); + }); + + // Intercept requests for the locally embedded test font and serve the static binary. + await context.route('**/test-fonts/montserrat.woff2', async (route) => { + const fontBuffer = fs.readFileSync('tests/fixtures/montserrat.woff2'); + await route.fulfill({ + status: 200, + contentType: 'font/woff2', + headers: {'Access-Control-Allow-Origin': '*'}, + body: fontBuffer, + }); + }); } /** @@ -38,7 +75,7 @@ export async function setupGedcomRoute(context: BrowserContext): Promise { ); await mockGedcomResponse(context, gedcomContent); - await blockTracking(context); + await setupHermeticEnvironment(context); } /** diff --git a/tests/intro.spec.ts b/tests/intro.spec.ts index 39a5f24..a5204bd 100644 --- a/tests/intro.spec.ts +++ b/tests/intro.spec.ts @@ -1,9 +1,9 @@ import {expect, test} from '@playwright/test'; -import {blockTracking} from './helpers'; +import {setupHermeticEnvironment} from './helpers'; test.describe('Intro page', () => { test.beforeEach(async ({page, context}) => { - await blockTracking(context); + await setupHermeticEnvironment(context); await page.goto('/'); }); diff --git a/tests/intro_visual.spec.ts b/tests/intro_visual.spec.ts index 492da18..1bb622f 100644 --- a/tests/intro_visual.spec.ts +++ b/tests/intro_visual.spec.ts @@ -1,9 +1,9 @@ import {expect, test} from '@playwright/test'; -import {blockTracking, waitForFonts} from './helpers'; +import {setupHermeticEnvironment, waitForFonts} from './helpers'; test.describe('Intro page visual validation @visual', () => { test.beforeEach(async ({page, context}) => { - await blockTracking(context); + await setupHermeticEnvironment(context); await page.goto('/'); await waitForFonts(page); });