Page, crawl, Playwright and Lighthouse coverage now include both pitch locales, and the contributor docs match the new route table and chrome. Co-authored-by: Cursor <cursoragent@cursor.com>
120 lines
4.8 KiB
TypeScript
120 lines
4.8 KiB
TypeScript
import { expect, test, type Page } from '@playwright/test';
|
|
import { SITE_CONTENT_DATA } from '../src/app/core/content/site-content';
|
|
import { expectHead, jsonLdGraph, pagePath } from './helpers';
|
|
|
|
async function ensurePrimaryNavOpen(page: Page): Promise<void> {
|
|
const toggle = page.locator('.nav-toggle');
|
|
if ((await toggle.isVisible()) && (await toggle.getAttribute('aria-expanded')) === 'false') {
|
|
await toggle.click();
|
|
await expect(toggle).toHaveAttribute('aria-expanded', 'true');
|
|
}
|
|
}
|
|
|
|
test.describe('server-rendered metadata', () => {
|
|
test('German and English Home include the full head contract', async ({ request, page }) => {
|
|
await expectHead(request, 'home', 'de');
|
|
await expectHead(request, 'home', 'en');
|
|
|
|
await page.goto('/');
|
|
const graph = (await jsonLdGraph(page)) as { '@graph': Array<{ '@type': string }> };
|
|
const types = graph['@graph'].map((node) => node['@type']);
|
|
expect(types).toContain('Person');
|
|
expect(types).toContain('ProfessionalService');
|
|
|
|
await page.goto('/en');
|
|
const english = (await jsonLdGraph(page)) as { '@graph': Array<{ '@type': string }> };
|
|
expect(english['@graph'].map((node) => node['@type'])).toEqual(
|
|
expect.arrayContaining(['Person', 'ProfessionalService']),
|
|
);
|
|
});
|
|
|
|
test('projects, AI service, contact, legal and 404 keep locale metadata', async ({ request }) => {
|
|
for (const locale of ['de', 'en'] as const) {
|
|
await expectHead(request, 'projects', locale);
|
|
await expectHead(request, 'servicesAi', locale);
|
|
await expectHead(request, 'contact', locale);
|
|
await expectHead(request, 'imprint', locale);
|
|
await expectHead(request, 'notFound', locale);
|
|
}
|
|
});
|
|
|
|
test('pitch has full head metadata and stays out of the primary nav', async ({
|
|
request,
|
|
page,
|
|
}) => {
|
|
await expectHead(request, 'pitch', 'de');
|
|
await expectHead(request, 'pitch', 'en');
|
|
|
|
const sitemap = await request.get('/sitemap.xml');
|
|
const xml = await sitemap.text();
|
|
expect(xml).toContain('https://antoniolede.de/pitch');
|
|
expect(xml).toContain('https://antoniolede.de/en/pitch');
|
|
|
|
await page.goto('/');
|
|
await ensurePrimaryNavOpen(page);
|
|
const nav = page.locator('.primary-nav');
|
|
await expect(nav.locator(`a[href="${pagePath('pitch', 'de')}"]`)).toHaveCount(0);
|
|
await expect(nav.locator(`a[href="${pagePath('home', 'de')}"]`)).toHaveCount(1);
|
|
});
|
|
|
|
test('unmatched URLs return 404 and a real route stays 200', async ({ request }) => {
|
|
const home = await request.get('/');
|
|
expect(home.status(), 'GET /').toBe(200);
|
|
|
|
await expectHead(request, 'notFound', 'de');
|
|
await expectHead(request, 'notFound', 'en');
|
|
});
|
|
|
|
test('client navigation does not duplicate head tags', async ({ page }) => {
|
|
await page.goto('/');
|
|
await ensurePrimaryNavOpen(page);
|
|
await page.locator('a.contact-cta').first().waitFor();
|
|
|
|
const assertUnique = async (canonical: string, description: string) => {
|
|
await expect(page.locator('link[rel="canonical"]')).toHaveCount(1);
|
|
await expect(page.locator('link[rel="alternate"][hreflang="de-DE"]')).toHaveCount(1);
|
|
await expect(page.locator('link[rel="alternate"][hreflang="en"]')).toHaveCount(1);
|
|
await expect(page.locator('link[rel="alternate"][hreflang="x-default"]')).toHaveCount(1);
|
|
await expect(page.locator('meta[name="description"]')).toHaveCount(1);
|
|
await expect(page.locator('meta[name="robots"]')).toHaveCount(1);
|
|
await expect(page.locator('script[type="application/ld+json"]')).toHaveCount(1);
|
|
await expect(page.locator('link[rel="canonical"]')).toHaveAttribute('href', canonical);
|
|
await expect(page.locator('meta[name="description"]')).toHaveAttribute(
|
|
'content',
|
|
description,
|
|
);
|
|
};
|
|
|
|
await ensurePrimaryNavOpen(page);
|
|
await page
|
|
.locator(`a[href="${pagePath('projects', 'de')}"]`)
|
|
.first()
|
|
.click();
|
|
await page.waitForURL('**/projekte');
|
|
await assertUnique(
|
|
'https://antoniolede.de/projekte',
|
|
SITE_CONTENT_DATA.de.pages.projects.description,
|
|
);
|
|
|
|
await ensurePrimaryNavOpen(page);
|
|
const servicesParent = page.locator(
|
|
`.primary-nav > li > a[href="${pagePath('services', 'de')}"]`,
|
|
);
|
|
const servicesAiLink = page
|
|
.locator(`.primary-nav a[href="${pagePath('servicesAi', 'de')}"]`)
|
|
.first();
|
|
if (!(await servicesAiLink.isVisible())) {
|
|
await servicesParent.hover();
|
|
await expect(servicesAiLink).toBeVisible();
|
|
}
|
|
await servicesAiLink.click();
|
|
await page.waitForURL('**/leistungen/ai-integration');
|
|
await expect(page.locator('link[rel="canonical"]')).toHaveCount(1);
|
|
await expect(page.locator('link[rel="canonical"]')).toHaveAttribute(
|
|
'href',
|
|
'https://antoniolede.de/leistungen/ai-integration',
|
|
);
|
|
await expect(page.locator('script[type="application/ld+json"]')).toHaveCount(1);
|
|
});
|
|
});
|