import { expect, test } from '@playwright/test'; import { canonicalSiteUrl, docsRoutes, expectCoreCapabilitiesVisible, expectMetadataForRoute, expectNoForbiddenPublicClaims, expectNoFakeTrustDownloads, expectNoHorizontalOverflow, expectNoPlaceholderLinks, expectNoProviderOrDataOverclaims, expectPublicLinksAreIntentional, expectSitemapExcludesRoutes, expectSitemapIncludesRoutes, readSitemapUrls, redirectRouteExpectations, redirectRoutes, renderedRoutes, expectTrustHandoffsAreReal, expectTrustSurfaceVisible, } from './smoke-helpers'; import { readFile } from 'node:fs/promises'; import { globby } from 'globby'; const routeMetadata = { '/': { title: /Tenantial.*Microsoft 365 Policies unter Kontrolle bringen/i, description: /Policy-Drift früh erkennen|Konfigurationsstände versioniert sichern/i, }, '/use-cases/msp': { title: /MSP.*Tenantial|Tenantial.*MSP/i, description: /Microsoft 365 Governance|Reviews|Evidence|Backups|Accepted Risks/i, }, '/use-cases/mittelstand': { title: /Mittelstand|Enterprise IT|Tenantial/i, description: /Microsoft 365 Governance|Policy-Drift|Evidence|Backups|Recovery/i, }, '/platform': { title: /Plattform \| Tenantial/i, description: /Policy Governance|Microsoft 365/i, }, '/platform/review-packs': { title: /Review Packs \| Tenantial/i, description: /(?=.*Review Packs)(?=.*Evidence)(?=.*Accepted Risks)(?=.*Decision Summaries)(?=.*Service-Reviews)(?=.*Audit-Vorbereitung)/i, }, '/evaluierung': { title: /Evaluierung & Rollout \| Tenantial/i, description: /Microsoft-365-Governance sichtbarer|Reviews beschleunigt|fokussierten Pilot/i, }, '/pricing': { title: /Preise \| Tenantial/i, description: /Policy-Governance-Evaluierung|Rollout-Planung/i, }, '/contact': { title: /Kontakt \| Tenantial/i, description: /Walkthrough|Provider-Grenzen|Policy-Governance/i, }, '/trust': { title: /Vertrauen, Datenschutz & Security \| Tenantial/i, description: /DACH-Evaluierungen|Dokumentenstatus|Provider-Berechtigungen/i, }, '/legal': { title: /Rechtliches \| Tenantial/i, description: /rechtliche Website-Informationen/i, }, '/privacy': { title: /Datenschutz \| Tenantial/i, description: /Website-Datenschutzhinweis/i, }, '/terms': { title: /Nutzungsbedingungen \| Tenantial/i, description: /Website-Nutzungsbedingungen/i, }, '/imprint': { title: /Impressum \| Tenantial/i, description: /Veröffentlichungsinformationen/i, }, '/docs/': { title: /Dokumentation|Tenantial Dokumentation/i, description: /Evaluation|Betrieb|Governance|Provider|Evidence|Drift/i, }, '/docs/getting-started/': { title: /Erste Schritte|Getting Started/i, description: /Evaluierung|Pilot|Ablauf|Microsoft 365/i, }, '/docs/microsoft-365-provider/': { title: /Microsoft 365 Provider/i, description: /Microsoft 365|Provider|Grenzen|Zugriff/i, }, '/docs/permissions-data-access/': { title: /Berechtigungen|Datenzugriff/i, description: /Berechtigungen|Datenzugriff|Rollen|Lesender Zugriff/i, }, '/docs/data-processing-trust/': { title: /Datenverarbeitung|Trust/i, description: /Datenschutz|Trust|Dokumente|Grenzen/i, }, '/docs/policy-evidence/': { title: /Policy Evidence|Evidence/i, description: /Evidence|Konfigurationsstand|Review|Nachweise/i, }, '/docs/drift-detection/': { title: /Drift Detection|Drift/i, description: /Drift|Aenderungen|Review|Priorisierung/i, }, '/docs/backups-versioning-recovery/': { title: /Backups|Versionierung|Recovery/i, description: /Backups|Versionierung|Recovery|Wiederherstellung/i, }, '/docs/findings-exceptions-accepted-risk/': { title: /Findings|Ausnahmen|Accepted Risk/i, description: /Findings|Ausnahmen|Accepted Risks|Entscheidungen/i, }, '/docs/review-packs-decisions/': { title: /Review Packs|Entscheidungen/i, description: /Review Packs|Entscheidungen|Evidence|Follow-up/i, }, '/docs/evaluation-pilot/': { title: /Evaluierung|Pilot/i, description: /Evaluierung|Pilot|Readiness|Walkthrough/i, }, '/docs/known-limitations/': { title: /Bekannte Grenzen|Limitations/i, description: /Grenzen|Fokus|keine Zusagen|heute nicht/i, }, '/docs/faq/': { title: /FAQ/i, description: /FAQ|Fragen|Antworten|Einordnung/i, }, '/en/': { title: /Tenantial.*Bring Microsoft 365 policies under control/i, description: /Detect Microsoft 365 policy drift early|versioned configuration backups/i, }, '/en/use-cases/msp': { title: /MSP.*Tenantial|Tenantial.*MSP/i, description: /Microsoft 365 governance|reviews|evidence|backups|accepted risks/i, }, '/en/use-cases/mittelstand': { title: /Enterprise IT|Mittelstand|Tenantial/i, description: /Microsoft 365 governance|policy drift|evidence|backups|recovery/i, }, '/en/platform': { title: /Platform \| Tenantial/i, description: /policy governance|Microsoft 365/i, }, '/en/platform/review-packs': { title: /Review Packs \| Tenantial/i, description: /(?=.*Review Packs)(?=.*evidence)(?=.*accepted risks)(?=.*decision summaries)(?=.*service reviews)(?=.*audit preparation)/i, }, '/en/evaluation': { title: /Evaluation & Rollout \| Tenantial/i, description: /Microsoft 365 governance clearer|speeds up reviews|focused pilot/i, }, '/en/pricing': { title: /Pricing \| Tenantial/i, description: /policy-governance evaluation|rollout planning/i, }, '/en/contact': { title: /Contact \| Tenantial/i, description: /walkthrough|provider boundaries|policy governance/i, }, '/en/trust': { title: /Trust, Privacy & Security \| Tenantial/i, description: /DACH evaluation|document status|provider permissions/i, }, '/en/legal': { title: /Legal \| Tenantial/i, description: /public website legal information/i, }, '/en/privacy': { title: /Privacy \| Tenantial/i, description: /public website privacy notice/i, }, '/en/terms': { title: /Terms \| Tenantial/i, description: /public website terms/i, }, '/en/imprint': { title: /Imprint \| Tenantial/i, description: /public website publication information/i, }, '/en/docs/': { title: /Documentation|Tenantial documentation/i, description: /evaluation|operations|governance|provider|evidence|drift/i, }, '/en/docs/getting-started/': { title: /Getting Started/i, description: /evaluation|pilot|workflow|Microsoft 365/i, }, '/en/docs/microsoft-365-provider/': { title: /Microsoft 365 Provider/i, description: /Microsoft 365|provider|boundaries|access/i, }, '/en/docs/permissions-data-access/': { title: /Permissions|Data Access/i, description: /permissions|data access|roles|read-oriented/i, }, '/en/docs/data-processing-trust/': { title: /Data Processing|Trust/i, description: /privacy|trust|documents|boundaries/i, }, '/en/docs/policy-evidence/': { title: /Policy Evidence|Evidence/i, description: /evidence|configuration state|review|proof points/i, }, '/en/docs/drift-detection/': { title: /Drift Detection|Drift/i, description: /drift|changes|review|prioritization/i, }, '/en/docs/backups-versioning-recovery/': { title: /Backups|Versioning|Recovery/i, description: /backups|versioning|recovery|restore context/i, }, '/en/docs/findings-exceptions-accepted-risk/': { title: /Findings|Exceptions|Accepted Risk/i, description: /findings|exceptions|accepted risks|decisions/i, }, '/en/docs/review-packs-decisions/': { title: /Review Packs|Decisions/i, description: /review packs|decisions|evidence|follow-up/i, }, '/en/docs/evaluation-pilot/': { title: /Evaluation|Pilot/i, description: /evaluation|pilot|readiness|walkthrough/i, }, '/en/docs/known-limitations/': { title: /Known Limitations/i, description: /limitations|focus|no promises|not today/i, }, '/en/docs/faq/': { title: /FAQ/i, description: /FAQ|questions|answers|framing/i, }, } as const; for (const route of renderedRoutes) { test(`renders intentional route ${route}`, async ({ page }) => { await page.goto(route); await expect(page.locator('body')).toContainText('Tenantial'); await expectNoForbiddenPublicClaims(page); await expectNoHorizontalOverflow(page); }); } test('homepage first viewport explains core Tenantial capabilities', async ({ page, }) => { await page.goto('/'); const heading = page.getByRole('heading', { name: /Microsoft 365 Policies unter Kontrolle bringen/i, }); await expect(heading).toBeVisible(); const box = await heading.boundingBox(); expect(box?.y ?? Number.POSITIVE_INFINITY).toBeLessThan(420); await expect( page.getByRole('link', { name: /Demo buchen/i }).first() ).toHaveAttribute('href', '/contact'); await expect( page.getByRole('link', { name: /Plattform ansehen/i }).first() ).toHaveAttribute('href', '/platform'); await expect( page.getByRole('link', { name: /Plattform ansehen/i }).last() ).toHaveAttribute('href', '/platform'); await expectCoreCapabilitiesVisible(page); }); for (const { route, links } of [ { route: '/', links: [ { name: 'MSP-Use-Case ansehen', href: '/use-cases/msp' }, { name: 'Interne IT ansehen', href: '/use-cases/mittelstand' }, ], }, { route: '/en/', links: [ { name: 'Explore MSP use case', href: '/en/use-cases/msp' }, { name: 'Explore internal IT use case', href: '/en/use-cases/mittelstand', }, ], }, ] as const) { test(`${route} exposes localized use-case teaser links`, async ({ page }) => { await page.goto(route); const main = page.locator('main'); for (const link of links) { await expect( main.getByRole('link', { name: link.name, exact: true }) ).toHaveAttribute('href', link.href); } await expectNoPlaceholderLinks(page); }); } test('/platform explains the public product model without internal runtime terms', async ({ page, }) => { await page.goto('/platform'); await expect( page.getByRole('heading', { name: /Policy-Governance-Modell für Microsoft 365/i, }) ).toBeVisible(); await expectCoreCapabilitiesVisible(page); await expect(page.locator('body')).toContainText( /Aktueller Fokus: Microsoft 365/i ); await expect(page.locator('body')).toContainText( /weitere Policy-Domänen|Weitere Domänen/i ); await expect(page.locator('body')).toContainText( /authentifiziert keine Besucher, liest keinen Microsoft Tenant, führt keine Operationen aus und speichert keine Tenant-Exporte/i ); const text = await page.locator('body').innerText(); expect(text).not.toMatch(/\b(Laravel|Filament|Livewire)\b/i); expect(text).not.toMatch(/\bGraph runtime\b/i); }); for (const { route, links } of [ { route: '/platform', links: [ { name: 'MSP-Pfad ansehen', href: '/use-cases/msp' }, { name: 'Interne IT ansehen', href: '/use-cases/mittelstand' }, ], }, { route: '/en/platform', links: [ { name: 'See MSP path', href: '/en/use-cases/msp' }, { name: 'See internal IT path', href: '/en/use-cases/mittelstand' }, ], }, ] as const) { test(`${route} exposes compact buyer-path links`, async ({ page }) => { await page.goto(route); const main = page.locator('main'); for (const link of links) { await expect( main.getByRole('link', { name: link.name, exact: true }) ).toHaveAttribute('href', link.href); } await expectNoPlaceholderLinks(page); }); } for (const route of renderedRoutes) { test(`public links on ${route} resolve intentionally`, async ({ page, request, }) => { await page.goto(route); await expectNoPlaceholderLinks(page); await expectPublicLinksAreIntentional(page, request, route); }); } test('built HTML emits no placeholder href values', async () => { const htmlFiles = await globby('dist/**/*.html', { absolute: true, cwd: process.cwd(), }); expect(htmlFiles.length).toBeGreaterThan(0); for (const file of htmlFiles) { const html = await readFile(file, 'utf8'); expect(html, file).not.toMatch(/href=(["'])#\1/); expect(html, file).not.toMatch(/href=\{["']#["']\}/); } }); for (const route of [ '/trust', '/pricing', '/legal', '/privacy', '/terms', '/imprint', ...docsRoutes, ] as const) { test(`claim-sensitive route ${route} stays conservative`, async ({ page, }) => { await page.goto(route); await expectNoForbiddenPublicClaims(page); await expectNoHorizontalOverflow(page); }); } for (const route of [ '/use-cases/msp', '/use-cases/mittelstand', '/platform/review-packs', '/en/use-cases/msp', '/en/use-cases/mittelstand', '/en/platform/review-packs', ] as const) { test(`${route} keeps buyer-story claims conservative`, async ({ page }) => { await page.goto(route); await expectNoForbiddenPublicClaims(page); await expectNoProviderOrDataOverclaims(page); await expectNoHorizontalOverflow(page); }); } for (const { route, linkName, href, footerHref } of [ { route: '/', linkName: 'Review-Pack-Story ansehen', href: '/platform/review-packs', footerHref: '/platform/review-packs', }, { route: '/platform', linkName: 'Review Packs verstehen', href: '/platform/review-packs', footerHref: '/platform/review-packs', }, { route: '/use-cases/msp', linkName: 'Review-Pack-Story für Kunden zeigen', href: '/platform/review-packs', footerHref: '/platform/review-packs', }, { route: '/use-cases/mittelstand', linkName: 'Review-Pack-Story für Reviews', href: '/platform/review-packs', footerHref: '/platform/review-packs', }, { route: '/en/', linkName: 'Explore review-pack story', href: '/en/platform/review-packs', footerHref: '/en/platform/review-packs', }, { route: '/en/platform', linkName: 'See review-pack story', href: '/en/platform/review-packs', footerHref: '/en/platform/review-packs', }, { route: '/en/use-cases/msp', linkName: 'Show the review-pack story', href: '/en/platform/review-packs', footerHref: '/en/platform/review-packs', }, { route: '/en/use-cases/mittelstand', linkName: 'Review-pack story for reviews', href: '/en/platform/review-packs', footerHref: '/en/platform/review-packs', }, ] as const) { test(`${route} exposes review-pack discovery through contextual and footer links`, async ({ page, }) => { await page.goto(route); await expect( page.getByRole('link', { name: linkName, exact: true }).first() ).toHaveAttribute('href', href); await expect( page.getByRole('link', { name: 'Review Packs', exact: true }).last() ).toHaveAttribute('href', footerHref); await expect(page.locator(`footer a[href="${footerHref}"]`)).toBeVisible(); await expectNoPlaceholderLinks(page); }); } for (const { route, linkName, href, footerName, footerHref } of [ { route: '/', linkName: 'Evaluierung & Pilot ansehen', href: '/evaluierung', footerName: 'Evaluierung', footerHref: '/evaluierung', }, { route: '/platform', linkName: 'Evaluierung & Rollout ansehen', href: '/evaluierung', footerName: 'Evaluierung', footerHref: '/evaluierung', }, { route: '/platform/review-packs', linkName: 'Evaluierungspfad ansehen', href: '/evaluierung', footerName: 'Evaluierung', footerHref: '/evaluierung', }, { route: '/trust', linkName: 'Evaluierung & Procurement ansehen', href: '/evaluierung', footerName: 'Evaluierung', footerHref: '/evaluierung', }, { route: '/use-cases/msp', linkName: 'Evaluierung & Pilot planen', href: '/evaluierung', footerName: 'Evaluierung', footerHref: '/evaluierung', }, { route: '/use-cases/mittelstand', linkName: 'Evaluierung & Pilot planen', href: '/evaluierung', footerName: 'Evaluierung', footerHref: '/evaluierung', }, { route: '/en/', linkName: 'See evaluation and pilot', href: '/en/evaluation', footerName: 'Evaluation', footerHref: '/en/evaluation', }, { route: '/en/platform', linkName: 'See evaluation and rollout', href: '/en/evaluation', footerName: 'Evaluation', footerHref: '/en/evaluation', }, { route: '/en/platform/review-packs', linkName: 'See the evaluation path', href: '/en/evaluation', footerName: 'Evaluation', footerHref: '/en/evaluation', }, { route: '/en/trust', linkName: 'See evaluation and procurement', href: '/en/evaluation', footerName: 'Evaluation', footerHref: '/en/evaluation', }, { route: '/en/use-cases/msp', linkName: 'Plan evaluation and pilot', href: '/en/evaluation', footerName: 'Evaluation', footerHref: '/en/evaluation', }, { route: '/en/use-cases/mittelstand', linkName: 'Plan evaluation and pilot', href: '/en/evaluation', footerName: 'Evaluation', footerHref: '/en/evaluation', }, ] as const) { test(`${route} exposes evaluation discovery through contextual and footer links`, async ({ page, }) => { await page.goto(route); await expect( page.getByRole('link', { name: linkName, exact: true }).first() ).toHaveAttribute('href', href); await expect( page.getByRole('link', { name: footerName, exact: true }).last() ).toHaveAttribute('href', footerHref); await expect(page.locator(`footer a[href="${footerHref}"]`)).toBeVisible(); await expectNoPlaceholderLinks(page); }); } for (const { route, locale } of [ { route: '/evaluierung', locale: 'de' }, { route: '/en/evaluation', locale: 'en' }, ] as const) { test(`${route} renders the evaluation path conservatively`, async ({ page, }) => { await page.goto(route); const headingPattern = locale === 'de' ? /(Evaluierungspfad|evaluieren|pilotieren)/i : /(evaluation path|Evaluate Tenantial|controlled demo)/i; await expect(page.locator('main')).toContainText(headingPattern); const securityPattern = locale === 'de' ? /(Security & Procurement|Procurement|Checkliste)/i : /(Security & procurement|procurement|checklist)/i; await expect(page.locator('main')).toContainText(securityPattern); const accessPattern = locale === 'de' ? /(Microsoft-365-Zugriff|Bereitschafts|Berechtigung)/i : /(Microsoft 365 access|readiness|permission)/i; await expect(page.locator('main')).toContainText(accessPattern); const faqPattern = locale === 'de' ? /FAQ|Fragen/i : /FAQ|questions/i; await expect(page.locator('main')).toContainText(faqPattern); const contactPath = locale === 'de' ? '/contact' : '/en/contact'; await expect( page.locator(`main a[href="${contactPath}"]`).first() ).toBeVisible(); await expectNoForbiddenPublicClaims(page); await expectNoProviderOrDataOverclaims(page); await expectNoFakeTrustDownloads(page); await expectNoPlaceholderLinks(page); await expectNoHorizontalOverflow(page); }); } for (const { route, locale } of [ { route: '/trust', locale: 'de' }, { route: '/en/trust', locale: 'en' }, ] as const) { test(`${route} exposes the core trust posture conservatively`, async ({ page, }) => { await page.goto(route); await expectTrustSurfaceVisible(page, locale); await expectTrustHandoffsAreReal(page, locale); await expectNoProviderOrDataOverclaims(page); await expectNoHorizontalOverflow(page); }); } for (const { route, locale, contactPath } of [ { route: '/trust', locale: 'de', contactPath: '/contact' }, { route: '/en/trust', locale: 'en', contactPath: '/en/contact' }, ] as const) { test(`${route} keeps document readiness request-safe`, async ({ page }) => { await page.goto(route); await expectTrustHandoffsAreReal(page, locale); await expectNoFakeTrustDownloads(page); await expect( page.locator(`main a[href="${contactPath}"]`).first() ).toBeVisible(); await expect(page.locator('body')).toContainText(/DPA|AVV/); await expect(page.locator('body')).toContainText(/TOM/); await expect(page.locator('body')).toContainText( /Subprozessoren|Subprocessors/ ); await expect(page.locator('body')).toContainText( /Support-Zugriff|Support access/ ); await expect(page.locator('body')).toContainText( /Security- und Procurement-Fragen|Security and procurement questions/ ); }); } for (const route of ['/trust', '/en/trust'] as const) { test(`${route} explains data and permission boundaries without overclaims`, async ({ page, }) => { await page.goto(route); await expect(page.locator('body')).toContainText( /Data categories|Datenkategorien/ ); await expect(page.locator('body')).toContainText( /Provider permissions|Provider-Berechtigungen/ ); await expect(page.locator('body')).toContainText( /Read-oriented|Lesender Zugriff/ ); await expect(page.locator('body')).toContainText( /Write-oriented|Schreibender Zugriff/ ); await expect(page.locator('body')).toContainText( /Least privilege|Least Privilege/ ); await expect(page.locator('body')).toContainText( /Secrets and encryption|Secrets und Verschlüsselung/ ); await expectNoProviderOrDataOverclaims(page); }); } for (const { route, trustHref, label } of [ { route: '/', trustHref: '/trust', label: /Trust-Haltung ansehen|Vertrauen/ }, { route: '/en/', trustHref: '/en/trust', label: /Review trust posture|Trust/, }, ] as const) { test(`${route} exposes localized trust discoverability`, async ({ page }) => { await page.goto(route); await expect( page.getByRole('link', { name: label }).first() ).toHaveAttribute('href', trustHref); await expect(page.locator(`footer a[href="${trustHref}"]`)).toBeVisible(); await expectNoPlaceholderLinks(page); }); } for (const { route, docsHref } of [ { route: '/', docsHref: '/docs/' }, { route: '/en/', docsHref: '/en/docs/' }, ] as const) { test(`${route} exposes docs through global navigation and footer`, async ({ page, isMobile, }) => { await page.goto(route); if (isMobile) { await page.getByLabel('Toggle navigation').click(); } await expect( page.locator('header').getByRole('link', { name: 'Docs', exact: true }) ).toHaveAttribute('href', docsHref); await expect( page.locator('footer').getByRole('link', { name: 'Docs', exact: true }) ).toHaveAttribute('href', docsHref); await expect(page.locator(`footer a[href="${docsHref}"]`)).toBeVisible(); await expectNoPlaceholderLinks(page); }); } for (const { route, links } of [ { route: '/', links: [{ name: 'Dokumentation öffnen', href: '/docs/' }], }, { route: '/platform', links: [{ name: 'Dokumentation öffnen', href: '/docs/' }], }, { route: '/trust', links: [ { name: 'Docs zu Datenschutz & Trust', href: '/docs/data-processing-trust/', }, ], }, { route: '/evaluierung', links: [ { name: 'Docs zur Pilot-Vorbereitung', href: '/docs/evaluation-pilot/', }, ], }, { route: '/platform/review-packs', links: [ { name: 'Docs zu Review Packs', href: '/docs/review-packs-decisions/' }, ], }, { route: '/use-cases/msp', links: [ { name: 'Docs für Evaluierung & Einstieg', href: '/docs/getting-started/', }, ], }, { route: '/use-cases/mittelstand', links: [ { name: 'Docs für Evaluierung & Einstieg', href: '/docs/getting-started/', }, ], }, { route: '/en/', links: [{ name: 'Open documentation', href: '/en/docs/' }], }, { route: '/en/platform', links: [{ name: 'Open documentation', href: '/en/docs/' }], }, { route: '/en/trust', links: [ { name: 'Docs for privacy and trust', href: '/en/docs/data-processing-trust/', }, ], }, { route: '/en/evaluation', links: [ { name: 'Docs for pilot readiness', href: '/en/docs/evaluation-pilot/', }, ], }, { route: '/en/platform/review-packs', links: [ { name: 'Docs for review packs', href: '/en/docs/review-packs-decisions/', }, ], }, { route: '/en/use-cases/msp', links: [ { name: 'Docs for onboarding and evaluation', href: '/en/docs/getting-started/', }, ], }, { route: '/en/use-cases/mittelstand', links: [ { name: 'Docs for onboarding and evaluation', href: '/en/docs/getting-started/', }, ], }, ] as const) { test(`${route} exposes contextual docs discovery`, async ({ page }) => { await page.goto(route); for (const link of links) { await expect( page.getByRole('link', { name: link.name, exact: true }).first() ).toHaveAttribute('href', link.href); } await expectNoPlaceholderLinks(page); }); } for (const { route, navLinks } of [ { route: '/', navLinks: [ { name: 'MSPs', href: '/use-cases/msp' }, { name: 'Interne IT', href: '/use-cases/mittelstand' }, ], }, { route: '/en/', navLinks: [ { name: 'MSPs', href: '/en/use-cases/msp' }, { name: 'Internal IT', href: '/en/use-cases/mittelstand' }, ], }, ] as const) { test(`${route} exposes use-case links in navigation and footer`, async ({ page, isMobile, }) => { await page.goto(route); const header = page.locator('header'); const footer = page.locator('footer'); if (isMobile) { await page.getByLabel('Toggle navigation').click(); } for (const link of navLinks) { await expect( header.getByRole('link', { name: link.name, exact: true }) ).toHaveAttribute('href', link.href); await expect( footer.getByRole('link', { name: link.name, exact: true }) ).toHaveAttribute('href', link.href); } await expectNoPlaceholderLinks(page); }); } for (const [route, expected] of Object.entries(routeMetadata)) { test(`metadata is route-specific for ${route}`, async ({ page }) => { await page.goto(route); await expectMetadataForRoute(page, route, expected); }); } for (const route of redirectRoutes) { test(`redirects ${route} intentionally`, async ({ page, request }) => { const response = await request.get(route, { maxRedirects: 0 }); const expected = redirectRouteExpectations[route]; if (response.status() >= 300 && response.status() < 400) { expect(response.status()).toBe(expected.status); expect(response.headers().location).toBe(expected.target); } else { expect(response.status()).toBe(200); const html = await response.text(); expect(html).toContain('name="robots" content="noindex"'); expect(html).toContain( `rel="canonical" href="${canonicalSiteUrl}${expected.target}"` ); expect(html).toContain(`href="${expected.target}"`); } await page.goto(route); await expect(page).toHaveURL(new RegExp(`${expected.target}/?$`)); const targetMetadata = routeMetadata[expected.target as keyof typeof routeMetadata]; if (targetMetadata) { await expectMetadataForRoute(page, expected.target, targetMetadata); } }); } test('robots and sitemap are available', async ({ page }) => { await page.goto('/robots.txt'); await expect(page.locator('body')).toContainText( `Sitemap: ${canonicalSiteUrl}/sitemap-index.xml` ); await page.goto('/sitemap-index.xml'); await expect(page.locator('body')).toContainText('sitemap-0.xml'); }); test('sitemap exposes canonical routes and excludes redirect aliases', async ({ page, }) => { const sitemapUrls = await readSitemapUrls(page); expectSitemapIncludesRoutes(sitemapUrls, renderedRoutes); expectSitemapExcludesRoutes(sitemapUrls, redirectRoutes); });