integration: add Playwright, axe and Lighthouse CI gates
Lock SEO, accessibility and crawlability with headless browser checks so regressions fail before a merge instead of after publication. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
71
e2e/layout.e2e.ts
Normal file
71
e2e/layout.e2e.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { SITE_CONFIG } from '../src/app/core/content/site-config';
|
||||
import { expectNoOverflow, pagePath } from './helpers';
|
||||
|
||||
const CHECKED = [
|
||||
pagePath('home', 'de'),
|
||||
pagePath('home', 'en'),
|
||||
pagePath('projects', 'de'),
|
||||
pagePath('projects', 'en'),
|
||||
pagePath('servicesAi', 'de'),
|
||||
pagePath('contact', 'de'),
|
||||
pagePath('imprint', 'en'),
|
||||
'/missing-route',
|
||||
];
|
||||
|
||||
test.describe('layout and crawlability', () => {
|
||||
test('does not overflow horizontally on checked routes', async ({ page }) => {
|
||||
for (const path of CHECKED) {
|
||||
await page.goto(path);
|
||||
await expectNoOverflow(page);
|
||||
}
|
||||
});
|
||||
|
||||
test('same-origin links and crawl files respond below 400', async ({ page, request }) => {
|
||||
const seen = new Set<string>();
|
||||
const queue = [pagePath('home', 'de'), pagePath('home', 'en')];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const path = queue.shift() as string;
|
||||
if (seen.has(path)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(path);
|
||||
|
||||
await page.goto(path);
|
||||
const urls = await page.evaluate(() => {
|
||||
const values = [
|
||||
...Array.from(document.querySelectorAll('a[href]'), (node) => node.getAttribute('href')),
|
||||
...Array.from(document.querySelectorAll('[src]'), (node) => node.getAttribute('src')),
|
||||
];
|
||||
return values.filter(
|
||||
(value): value is string => typeof value === 'string' && value.length > 0,
|
||||
);
|
||||
});
|
||||
|
||||
for (const raw of urls) {
|
||||
if (raw.startsWith('mailto:') || raw.startsWith('tel:') || raw.startsWith('#')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const resolved = new URL(raw, page.url());
|
||||
if (resolved.origin !== new URL(page.url()).origin) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const next = `${resolved.pathname}${resolved.search}`;
|
||||
const response = await request.get(next);
|
||||
expect(response.status(), `${raw} from ${path}`).toBeLessThan(400);
|
||||
|
||||
if (!seen.has(next) && !next.includes('.')) {
|
||||
queue.push(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const asset of ['/robots.txt', '/sitemap.xml', '/llms.txt', SITE_CONFIG.cvAssetPath]) {
|
||||
const response = await request.get(asset);
|
||||
expect(response.status(), asset).toBe(200);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user