134 lines
4.1 KiB
TypeScript
134 lines
4.1 KiB
TypeScript
import { expect, test } from '../helpers/serve-dist';
|
|
|
|
interface DocumentSnapshot {
|
|
text: string;
|
|
sectionIds: string[];
|
|
sectionHeadings: string[];
|
|
h1: string;
|
|
role: string;
|
|
landmarks: {
|
|
headers: number;
|
|
mains: number;
|
|
navigations: number;
|
|
footers: number;
|
|
};
|
|
}
|
|
|
|
const captureDocument = async (
|
|
page: import('@playwright/test').Page,
|
|
): Promise<DocumentSnapshot> =>
|
|
page.evaluate(() => ({
|
|
text: (document.querySelector('.portfolio-shell')?.textContent ?? '')
|
|
.replace(/\s+/gu, ' ')
|
|
.trim(),
|
|
sectionIds: [
|
|
...document.querySelectorAll<HTMLElement>('main > section'),
|
|
].map((section) => section.id),
|
|
sectionHeadings: [
|
|
...document.querySelectorAll<HTMLElement>('main > section > h2'),
|
|
].map((heading) => heading.innerText.trim()),
|
|
h1: document.querySelector('h1')?.textContent?.trim() ?? '',
|
|
role: document.querySelector('.site-header > p')?.textContent?.trim() ?? '',
|
|
landmarks: {
|
|
headers: document.querySelectorAll('header').length,
|
|
mains: document.querySelectorAll('main').length,
|
|
navigations: document.querySelectorAll('nav').length,
|
|
footers: document.querySelectorAll('footer').length,
|
|
},
|
|
}));
|
|
|
|
test('hydration preserves the built subject matter and landmark structure without errors or external fetches', async ({
|
|
browser,
|
|
page,
|
|
distUrl,
|
|
}, testInfo) => {
|
|
test.skip(
|
|
testInfo.project.name === 'mobile-chromium',
|
|
'Hydration identity is covered once; semantic and no-script behavior run on mobile.',
|
|
);
|
|
|
|
const beforePage = await browser.newPage({
|
|
javaScriptEnabled: false,
|
|
viewport: { width: 1440, height: 900 },
|
|
});
|
|
await beforePage.goto(distUrl, { waitUntil: 'load' });
|
|
const before = await captureDocument(beforePage);
|
|
await beforePage.close();
|
|
|
|
const pageErrors: string[] = [];
|
|
const consoleErrors: string[] = [];
|
|
const failedRequests: string[] = [];
|
|
const requests: string[] = [];
|
|
page.on('pageerror', (error) => pageErrors.push(error.message));
|
|
page.on('console', (message) => {
|
|
if (message.type() === 'error') consoleErrors.push(message.text());
|
|
});
|
|
page.on('request', (request) => requests.push(request.url()));
|
|
page.on('requestfailed', (request) =>
|
|
failedRequests.push(
|
|
`${request.url()}: ${request.failure()?.errorText ?? 'failed'}`,
|
|
),
|
|
);
|
|
await page.addInitScript(() => {
|
|
const unhandled: string[] = [];
|
|
Reflect.set(globalThis, '__portfolioUnhandledRejections', unhandled);
|
|
addEventListener('unhandledrejection', (event) =>
|
|
unhandled.push(String(event.reason)),
|
|
);
|
|
});
|
|
|
|
await page.goto(distUrl, { waitUntil: 'domcontentloaded' });
|
|
await page.waitForFunction(() => {
|
|
const app = document.querySelector('#app');
|
|
return app !== null && Reflect.has(app, '__vue_app__');
|
|
});
|
|
await page.waitForFunction(() => document.readyState === 'complete');
|
|
await page.evaluate(
|
|
() =>
|
|
new Promise<void>((resolve) =>
|
|
requestAnimationFrame(() => requestAnimationFrame(() => resolve())),
|
|
),
|
|
);
|
|
|
|
const after = await captureDocument(page);
|
|
const unhandledRejections = await page.evaluate(
|
|
() =>
|
|
(Reflect.get(globalThis, '__portfolioUnhandledRejections') as
|
|
string[] | undefined) ?? [],
|
|
);
|
|
|
|
expect(after).toEqual(before);
|
|
expect(after.h1).toBe('Luca Pîslaru');
|
|
expect(after.role).toBe('System Administrator & Full-Stack Developer');
|
|
expect(after.sectionIds).toEqual([
|
|
'start',
|
|
'profile',
|
|
'experience',
|
|
'infrastructure',
|
|
'selected-builds',
|
|
'additional-work',
|
|
'toolkit',
|
|
'contact',
|
|
]);
|
|
expect(after.landmarks).toEqual({
|
|
headers: 1,
|
|
mains: 1,
|
|
navigations: 1,
|
|
footers: 1,
|
|
});
|
|
expect(pageErrors).toEqual([]);
|
|
expect(consoleErrors).toEqual([]);
|
|
expect(unhandledRejections).toEqual([]);
|
|
expect(failedRequests).toEqual([]);
|
|
expect(requests.length).toBeGreaterThan(1);
|
|
expect(
|
|
requests.every((requestUrl) => {
|
|
const url = new URL(requestUrl);
|
|
return (
|
|
url.origin === distUrl &&
|
|
(url.pathname === '/' || url.pathname.startsWith('/assets/'))
|
|
);
|
|
}),
|
|
).toBe(true);
|
|
});
|