import { expect, test } from '@playwright/test'; import { canonicalSiteUrl, docsRoutes, expectCoreCapabilitiesVisible, expectMetadataForRoute, expectNoForbiddenPublicClaims, expectNoHorizontalOverflow, expectNoPlaceholderLinks, expectPublicLinksAreIntentional, expectSitemapExcludesRoutes, expectSitemapIncludesRoutes, readSitemapUrls, redirectRouteExpectations, redirectRoutes, renderedRoutes, } 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, }, '/platform': { title: /Plattform \| Tenantial/i, description: /Policy Governance|Microsoft 365/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 \| Tenantial/i, description: /Trust-Haltung|konservativen Claims|Policy-Governance/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, }, '/welcome-to-docs/': { title: /Tenantial Docs/i, description: /Policy-Governance-Modell für Microsoft 365/i, }, '/guides/intro/': { title: /Einführung in Tenantial/i, description: /Policy-Governance-Modell für Microsoft 365/i, }, '/guides/getting-started/': { title: /Getting Started/i, description: /Tenant-Governance-Problem|Tenantial Walkthrough/i, }, '/guides/first-project-checklist/': { title: /Evaluierungscheckliste/i, description: /frühe Tenantial-Evaluierung|Checkliste/i, }, '/platform/evidence-review/': { title: /Evidence Review/i, description: /Policy-Governance-Modell|Evidence-Review-Workflows/i, }, '/en/': { title: /Tenantial.*Bring Microsoft 365 policies under control/i, description: /Detect Microsoft 365 policy drift early|versioned configuration backups/i, }, '/en/platform': { title: /Platform \| Tenantial/i, description: /policy governance|Microsoft 365/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 \| Tenantial/i, description: /trust posture|policy governance|review/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/welcome-to-docs/': { title: /Tenantial Docs/i, description: /policy-governance model for Microsoft 365/i, }, '/en/guides/intro/': { title: /Introduction to Tenantial/i, description: /policy-governance model for Microsoft 365/i, }, '/en/guides/getting-started/': { title: /Getting Started/i, description: /Tenantial walkthrough|governance problem/i, }, '/en/guides/first-project-checklist/': { title: /Evaluation Checklist/i, description: /conservative checklist|early Tenantial evaluation/i, }, '/en/platform/evidence-review/': { title: /Evidence Review/i, description: /policy-governance model|evidence-review workflows/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); }); 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 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, 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}/?$`)); await expect( page.getByRole('heading', { name: /Policy-Governance-Modell für Microsoft 365|Policy governance model for Microsoft 365/, }) ).toBeVisible(); }); } 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); });