60 lines
4.7 KiB
TypeScript
60 lines
4.7 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { createHash } from 'node:crypto';
|
|
import { templateFixtures, templateProfile } from './pre-p10-template-fixtures';
|
|
|
|
const api = 'http://localhost:5302/api';
|
|
const baseline = path.resolve('../.artifacts/pre-phase10-ux/templates-legacy');
|
|
const output = path.resolve(process.env.CV_TEMPLATE_OUTPUT || '../.artifacts/pre-phase10-ux/templates-revised-01');
|
|
const hash = (text: string) => createHash('sha256').update(text).digest('hex');
|
|
// The existing avatar endpoint now normalizes image pixels with Skia. Retain font data
|
|
// verbatim; exclude only embedded image payload bytes when comparing document markup.
|
|
const normalizeAvatar = (html: string) => html.replace(/data:image\/(?:png|jpeg|webp);base64,[A-Za-z0-9+/=]+/g, 'data:image/normalized-avatar');
|
|
|
|
test('[PRE-P10 B] existing design-one markup, CSS and source markers survive design-two additions', async ({ request }, info) => {
|
|
test.setTimeout(180_000);
|
|
expect((await request.post(`${api}/auth/login`, { data: { email: 'e2e@example.test', password: 'E2ePassword123!' } })).status()).toBe(200);
|
|
const me = await (await request.get(`${api}/auth/me`)).json();
|
|
const headers = { 'X-CSRF-TOKEN': (await request.storageState()).cookies.find(cookie => cookie.name === 'XSRF-TOKEN')!.value, 'X-Session-Context': me.sessionContext as string };
|
|
const avatar = await fs.readFile(path.join(baseline, 'synthetic-nonhuman-color-sample.png'));
|
|
expect((await request.post(`${api}/auth/avatar`, { headers, multipart: { file: { name: 'synthetic-nonhuman.png', mimeType: 'image/png', buffer: avatar } } })).status()).toBe(200);
|
|
const themes = await (await request.get(`${api}/cv/themes`)).json();
|
|
const results = [];
|
|
await fs.mkdir(path.join(output, 'legacy-current'), { recursive: true });
|
|
for (const sample of templateFixtures) {
|
|
const { profile } = templateProfile(sample);
|
|
const source = await (await request.get(`${api}/career/profile`)).json();
|
|
expect((await request.put(`${api}/career/profile`, { headers, data: { expectedVersion: source.revision, profile } })).status()).toBe(200);
|
|
for (const theme of themes) {
|
|
const fixture = `${sample.key}-${theme.id}`;
|
|
const created = await request.post(`${api}/cv/variants`, { headers, data: { name: fixture, settings: { themeId: theme.id, language: sample.language, showPhoto: true, showIcons: true } } });
|
|
expect(created.status(), fixture).toBe(200);
|
|
const variant = await created.json();
|
|
const response = await request.get(`${api}/cv/variants/${variant.id}/preview`);
|
|
expect(response.status(), fixture).toBe(200);
|
|
const current = (await response.json()).html as string;
|
|
const original = await fs.readFile(path.join(baseline, fixture, 'render.html'), 'utf8');
|
|
const style = (html: string) => html.match(/<style>([\s\S]*?)<\/style>/)![1];
|
|
const body = (html: string) => html.match(/<body>([\s\S]*?)<\/body>/)![1];
|
|
const originalMarkers = body(original).match(/\b[SEBPR]\d{2}\b/g);
|
|
const currentMarkers = body(current).match(/\b[SEBPR]\d{2}\b/g);
|
|
const measured = { fixture, rawOriginalHash: hash(original), rawCurrentHash: hash(current),
|
|
normalizedOriginalHash: hash(normalizeAvatar(original)), normalizedCurrentHash: hash(normalizeAvatar(current)),
|
|
originalCssHash: hash(style(original)), currentCssHash: hash(style(current)),
|
|
originalBodyHash: hash(normalizeAvatar(body(original))), currentBodyHash: hash(normalizeAvatar(body(current))),
|
|
markersEqual: JSON.stringify(currentMarkers) === JSON.stringify(originalMarkers) };
|
|
results.push(measured);
|
|
await fs.writeFile(path.join(output, 'legacy-current', `${fixture}.html`), current);
|
|
await fs.writeFile(path.join(output, 'legacy-preservation.json'), JSON.stringify({
|
|
normalization: 'Only data:image/{png,jpeg,webp};base64 payload bytes excluded because the upload pipeline now reencodes the synthetic avatar with Skia. Font resources, CSS and all other HTML bytes remain exact.', fixtures: results }, null, 2));
|
|
expect.soft(measured.normalizedCurrentHash, `${fixture} normalized full HTML`).toBe(measured.normalizedOriginalHash);
|
|
expect.soft(measured.currentCssHash, `${fixture} exact CSS including embedded licensed fonts`).toBe(measured.originalCssHash);
|
|
expect.soft(measured.currentBodyHash, `${fixture} body excluding avatar bytes`).toBe(measured.originalBodyHash);
|
|
expect.soft(measured.markersEqual, `${fixture} marker order`).toBe(true);
|
|
}
|
|
}
|
|
expect(results).toHaveLength(36);
|
|
await info.attach('legacy-preservation', { path: path.join(output, 'legacy-preservation.json'), contentType: 'application/json' });
|
|
});
|