Make screenshot tests repeatable by providing own fonts

This commit is contained in:
Przemek Więch
2026-05-12 23:35:10 +02:00
parent 328008f8e9
commit 41d7ed8208
10 changed files with 52 additions and 40 deletions

View File

@@ -1,11 +1,10 @@
import {expect, test} from '@playwright/test'; import {expect, test} from '@playwright/test';
import {setupGedcomRoute, waitForFonts} from './helpers'; import {setupGedcomRoute, setupHermeticEnvironment} from './helpers';
test.describe('Core SVG Canvas Layouts @visual', () => { test.describe('Core SVG Canvas Layouts @visual', () => {
test.beforeEach(async ({page, context}) => { test.beforeEach(async ({context}) => {
await setupHermeticEnvironment(context);
await setupGedcomRoute(context); await setupGedcomRoute(context);
await page.goto('/');
await waitForFonts(page);
}); });
const layouts = [ const layouts = [

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -1,11 +1,10 @@
import {expect, test} from '@playwright/test'; import {expect, test} from '@playwright/test';
import {setupGedcomRoute, waitForFonts} from './helpers'; import {setupGedcomRoute, setupHermeticEnvironment} from './helpers';
test.describe('Configurations Integration @visual', () => { test.describe('Configurations Integration @visual', () => {
test.beforeEach(async ({page, context}) => { test.beforeEach(async ({page, context}) => {
await setupHermeticEnvironment(context);
await setupGedcomRoute(context); await setupGedcomRoute(context);
await page.goto('/');
await waitForFonts(page);
await page.goto('/#/view?url=https://example.org/family.ged'); await page.goto('/#/view?url=https://example.org/family.ged');

View File

@@ -1,13 +1,11 @@
import {expect, test} from '@playwright/test'; import {expect, test} from '@playwright/test';
import dedent from 'dedent'; import dedent from 'dedent';
import * as fs from 'fs'; import * as fs from 'fs';
import {mockGedcomResponse, setupHermeticEnvironment, waitForFonts} from './helpers'; import {mockGedcomResponse, setupHermeticEnvironment} from './helpers';
test.describe('Details panel visual validation @visual', () => { test.describe('Details panel visual validation @visual', () => {
test.beforeEach(async ({page, context}) => { test.beforeEach(async ({context}) => {
await setupHermeticEnvironment(context); await setupHermeticEnvironment(context);
await page.goto('/');
await waitForFonts(page);
}); });
test('Complex Names Test', async ({page, context}) => { test('Complex Names Test', async ({page, context}) => {

BIN
tests/fixtures/Montserrat-Bold.woff2 vendored Normal file

Binary file not shown.

BIN
tests/fixtures/Montserrat-Regular.woff2 vendored Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -1,43 +1,68 @@
import {BrowserContext, Page} from '@playwright/test'; import {BrowserContext} from '@playwright/test';
import * as fs from 'fs'; import * as fs from 'fs';
/** /**
* Sets up a hermetic test environment by blocking external tracking services * Sets up a hermetic test environment by blocking external tracking services
* and intercepting Google Fonts to serve static embedded fonts locally. * and intercepting index.html to serve static embedded fonts locally.
*/ */
export async function setupHermeticEnvironment(context: BrowserContext): Promise<void> { export async function setupHermeticEnvironment(
context: BrowserContext,
): Promise<void> {
await context.route('**/*google-analytics.com/**', (route) => route.abort()); await context.route('**/*google-analytics.com/**', (route) => route.abort());
await context.route('**/*googletagmanager.com/**', (route) => route.abort()); await context.route('**/*googletagmanager.com/**', (route) => route.abort());
// Intercept Google Fonts stylesheet requests to serve a deterministically embedded local font. // Intercept index.html to serve preloaded local fonts deterministically.
await context.route('**/fonts.googleapis.com/css2**', async (route) => { await context.route(
const cssContent = ` (url) => url.pathname === '/',
async (route) => {
const response = await route.fetch();
let body = await response.text();
body = body.replace(
/^.*Montserrat.*$/m,
` <link rel="preload" href="Montserrat-Regular.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="Montserrat-Bold.woff2" as="font" type="font/woff2" crossorigin>
<style>
@font-face { @font-face {
font-family: 'Montserrat'; font-family: 'Montserrat';
font-style: normal; font-style: normal;
font-weight: 100 900; font-weight: 400;
font-display: block; src: url('Montserrat-Regular.woff2') format('woff2');
src: url('http://localhost:3000/test-fonts/montserrat.woff2') format('woff2');
} }
@font-face { @font-face {
font-family: 'Montserrat'; font-family: 'Montserrat';
font-style: italic; font-style: normal;
font-weight: 100 900; font-weight: 700;
font-display: block; src: url('Montserrat-Bold.woff2') format('woff2');
src: url('http://localhost:3000/test-fonts/montserrat.woff2') format('woff2');
} }
`; text {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: geometricPrecision;
}
</style>`,
);
await route.fulfill({
response,
body,
});
},
);
// Intercept requests for the locally embedded test fonts and serve the static binaries.
await context.route('**/Montserrat-Regular.woff2', async (route) => {
const fontBuffer = fs.readFileSync(
'tests/fixtures/Montserrat-Regular.woff2',
);
await route.fulfill({ await route.fulfill({
status: 200, status: 200,
contentType: 'text/css', contentType: 'font/woff2',
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
body: cssContent, body: fontBuffer,
}); });
}); });
// Intercept requests for the locally embedded test font and serve the static binary. await context.route('**/Montserrat-Bold.woff2', async (route) => {
await context.route('**/test-fonts/montserrat.woff2', async (route) => { const fontBuffer = fs.readFileSync('tests/fixtures/Montserrat-Bold.woff2');
const fontBuffer = fs.readFileSync('tests/fixtures/montserrat.woff2');
await route.fulfill({ await route.fulfill({
status: 200, status: 200,
contentType: 'font/woff2', contentType: 'font/woff2',
@@ -75,12 +100,4 @@ export async function setupGedcomRoute(context: BrowserContext): Promise<void> {
); );
await mockGedcomResponse(context, gedcomContent); await mockGedcomResponse(context, gedcomContent);
await setupHermeticEnvironment(context);
}
/**
* Ensures all custom web fonts are fully loaded and rendered.
*/
export async function waitForFonts(page: Page): Promise<void> {
await page.evaluate(() => document.fonts.ready);
} }

View File

@@ -1,11 +1,10 @@
import {expect, test} from '@playwright/test'; import {expect, test} from '@playwright/test';
import {setupHermeticEnvironment, waitForFonts} from './helpers'; import {setupHermeticEnvironment} from './helpers';
test.describe('Intro page visual validation @visual', () => { test.describe('Intro page visual validation @visual', () => {
test.beforeEach(async ({page, context}) => { test.beforeEach(async ({page, context}) => {
await setupHermeticEnvironment(context); await setupHermeticEnvironment(context);
await page.goto('/'); await page.goto('/');
await waitForFonts(page);
}); });
test('intro-page', async ({page}) => { test('intro-page', async ({page}) => {