## Summary - tighten homepage messaging, hero support copy, trust teaser flow, and CTA routing for the website public-content rollout - align shared website copy, smoke expectations, and spec 404 artifacts with the latest messaging pass - replace the previously closed PR for `404-public-content-messaging` ## Commits - `44d27395` feat(website): tighten homepage messaging and trust flow - `1ddbd28b` feat(website): refine public content messaging rollout ## Validation - `git diff --check` ## Notes - local Playwright MCP output remains untracked and was not included Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #398
292 lines
8.8 KiB
TypeScript
292 lines
8.8 KiB
TypeScript
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);
|
|
});
|