import { SEEDED_TEST_CONTENT } from "../../fixtures/test-constants" import { createCollectionEntry, deleteCollectionEntry, listCollectionEntries } from "./admin-api" type ContentEntry = { id?: string _id?: string | { $oid?: string } _testdata?: boolean translationKey?: string path?: string title?: string [key: string]: unknown } function getEntryId(entry: ContentEntry): string | undefined { if (typeof entry.id === "string") return entry.id if (typeof entry._id === "string") return entry._id if (entry._id && typeof entry._id === "object" && typeof entry._id.$oid === "string") return entry._id.$oid return undefined } const SEEDED_TRANSLATION_KEYS = new Set(Object.values(SEEDED_TEST_CONTENT).map((entry) => entry.translationKey)) const SEEDED_PATHS = new Set(Object.values(SEEDED_TEST_CONTENT).map((entry) => entry.path)) const SEEDED_MEDIA_DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIHWP4////fwAJ+wP9KobjigAAAABJRU5ErkJggg==" function isSeededContentEntry(entry: ContentEntry): boolean { if (entry._testdata === true) { return true } if (typeof entry.translationKey === "string" && SEEDED_TRANSLATION_KEYS.has(entry.translationKey)) { return true } if (typeof entry.path === "string" && SEEDED_PATHS.has(entry.path)) { return true } return false } function isSeededMedialibEntry(entry: ContentEntry): boolean { return entry._testdata === true } const SEEDED_MEDIALIB_ENTRIES = [ { _testdata: true, title: "Playwright Seed Image", alt: { de: "Playwright Seed Bild", en: "Playwright seed image", }, description: "Deterministisches Medialib-Bild fuer Admin- und Preview-Tests.", tags: ["playwright", "seed", "preview"], file: { src: SEEDED_MEDIA_DATA_URI, }, }, ] as const function getSeededContentEntries(previewImageId: string) { return [ { _testdata: true, active: true, lang: "de", translationKey: SEEDED_TEST_CONTENT.home.translationKey, name: "Playwright Startseite", path: SEEDED_TEST_CONTENT.home.path, meta: { title: "Playwright Startseite", description: "Seeded Startseite fuer stabile Playwright-Tests.", keywords: ["playwright", "seed", "e2e"], }, blocks: [ { type: "hero", headline: "Playwright Seed Startseite", headlineH1: true, tagline: "Deterministische Testdaten", subline: "Diese Seite wird vor dem Testlauf frisch ueber die Admin-API angelegt.", containerWidth: "wide", callToAction: { buttonText: "Zum Kontakt", buttonLink: `/de${SEEDED_TEST_CONTENT.contact.path}`, }, }, { type: "features", anchorId: "seed-features", headline: "Stabile Grundlage fuer Frontend-Tests", tagline: "Seed", padding: { top: "md", bottom: "md" }, featureBoxes: [ { icon: "lightning", title: "Frisch angelegt", text: "Die Inhalte werden in globalSetup erstellt statt aus Demo-Daten uebernommen.", }, { icon: "database", title: "API-nah", text: "Die Seed-Daten kommen ueber dieselben Collection-Endpunkte wie das CMS.", }, { icon: "globe", title: "Mehrsprachig", text: "DE und EN teilen sich denselben translationKey fuer Routing-Checks.", }, ], }, { type: "richtext", anchorId: "seed-richtext", headline: "Mehr Kontext", tagline: "API + UI", padding: { top: "md", bottom: "md" }, imagePosition: "none", text: "

Dieser Richtext-Block prueft, dass formatierter HTML-Inhalt im SPA gerendert wird.

", }, { type: "accordion", anchorId: "seed-faq", headline: "Hauefige Fragen", tagline: "Verhalten", padding: { top: "md", bottom: "md" }, accordionItems: [ { question: "Warum Seed-Daten?", answer: "

Damit Tests nicht blind auf bestehende Inhalte oder Demo-Routen vertrauen.

", open: true, }, { question: "Was wird geprueft?", answer: "

API-Antworten, Routing, Sprachwechsel und Block-Rendering.

", open: false, }, ], }, ], }, { _testdata: true, active: true, lang: "en", translationKey: SEEDED_TEST_CONTENT.home.translationKey, name: "Playwright Home", path: SEEDED_TEST_CONTENT.home.path, meta: { title: "Playwright Home", description: "Seeded home page for stable Playwright tests.", keywords: ["playwright", "seed", "home"], }, blocks: [ { type: "hero", headline: "Playwright Seed Home", headlineH1: true, tagline: "Deterministic fixtures", subline: "This page is recreated before every test run through the admin API.", containerWidth: "wide", callToAction: { buttonText: "Go to contact", buttonLink: `/en${SEEDED_TEST_CONTENT.contact.path}`, }, }, { type: "features", anchorId: "seed-features", headline: "Stable frontend coverage", tagline: "Seed", padding: { top: "md", bottom: "md" }, featureBoxes: [ { icon: "lightning", title: "Freshly created", text: "The content is created during globalSetup instead of relying on demo data.", }, { icon: "database", title: "API-backed", text: "The seed uses the same collection endpoints as the CMS itself.", }, { icon: "globe", title: "Localized", text: "DE and EN share the same translationKey for route switching checks.", }, ], }, { type: "richtext", anchorId: "seed-richtext", headline: "More context", tagline: "API + UI", padding: { top: "md", bottom: "md" }, imagePosition: "none", text: "

This richtext block proves that formatted HTML content renders in the SPA.

", }, { type: "accordion", anchorId: "seed-faq", headline: "Common questions", tagline: "Behavior", padding: { top: "md", bottom: "md" }, accordionItems: [ { question: "Why seeded data?", answer: "

So the tests do not depend on existing demo pages or editorial content.

", open: true, }, { question: "What is covered?", answer: "

API responses, routing, locale switching and block rendering.

", open: false, }, ], }, ], }, { _testdata: true, active: true, lang: "de", translationKey: SEEDED_TEST_CONTENT.pagebuilderPreview.translationKey, name: "Playwright Pagebuilder Preview", path: SEEDED_TEST_CONTENT.pagebuilderPreview.path, meta: { title: "Playwright Pagebuilder Preview", description: "Seeded Seite fuer Pagebuilder- und Medialib-Vorschau-Tests.", keywords: ["playwright", "pagebuilder", "preview"], }, blocks: [ { type: "hero", headline: "Playwright Registry Hero", headlineH1: true, tagline: "Admin Preview", subline: "Dieses Hero-Preview prueft den Block-Registry-Pfad inklusive Bild.", containerWidth: "wide", heroImage: { image: previewImageId, }, }, { type: "richtext", anchorId: "pagebuilder-preview-richtext", headline: "Richtext mit Bild", tagline: "Shared Media Widget", padding: { top: "md", bottom: "md" }, imagePosition: "right", image: previewImageId, text: "

Dieser Block prueft, dass ein image-gestuetzter Preview-Block im Admin ueber dieselbe Registry und dasselbe Bild-Widget rendert.

", }, ], }, { _testdata: true, active: true, lang: "de", translationKey: SEEDED_TEST_CONTENT.contact.translationKey, name: "Playwright Kontakt", path: SEEDED_TEST_CONTENT.contact.path, meta: { title: "Playwright Kontakt", description: "Seeded Kontaktseite fuer Playwright.", keywords: ["playwright", "kontakt"], }, blocks: [ { type: "hero", headline: "Kontakt fuer Testlauf", headlineH1: true, tagline: "Seed", subline: "Diese Seite prueft das aktuelle ContactForm-Rendering.", }, { type: "contact-form", anchorId: "kontaktformular", headline: "Schreibe uns", padding: { top: "md", bottom: "md" }, }, ], }, { _testdata: true, active: true, lang: "en", translationKey: SEEDED_TEST_CONTENT.contact.translationKey, name: "Playwright Contact", path: SEEDED_TEST_CONTENT.contact.path, meta: { title: "Playwright Contact", description: "Seeded contact page for Playwright.", keywords: ["playwright", "contact"], }, blocks: [ { type: "hero", headline: "Contact for the test run", headlineH1: true, tagline: "Seed", subline: "This page verifies the current contact form rendering.", }, { type: "contact-form", anchorId: "contact-form", headline: "Write to us", padding: { top: "md", bottom: "md" }, }, ], }, { _testdata: true, active: false, lang: "de", translationKey: SEEDED_TEST_CONTENT.inactive.translationKey, name: "Playwright Inaktiv", path: SEEDED_TEST_CONTENT.inactive.path, meta: { title: "Playwright Inaktiv", description: "Nicht aktive Seed-Seite fuer Routing-Tests.", keywords: ["playwright", "inactive"], }, blocks: [ { type: "richtext", anchorId: "inactive", headline: "Sollte nicht sichtbar sein", tagline: "Seed", padding: { top: "md", bottom: "md" }, imagePosition: "none", text: "

Diese Seite ist absichtlich inaktiv und darf im Frontend nicht erscheinen.

", }, ], }, ] as const } async function cleanupSeededMedialibEntries(baseURL: string): Promise { const medialibEntries = await listCollectionEntries(baseURL, "medialib") const seededEntries = medialibEntries.filter((entry) => isSeededMedialibEntry(entry)) let deleted = 0 for (const entry of seededEntries) { const entryId = getEntryId(entry) if (!entryId) continue const ok = await deleteCollectionEntry(baseURL, "medialib", entryId) if (ok) deleted++ } return deleted } async function seedMedialibEntries(baseURL: string): Promise<{ created: number; previewImageId: string }> { let previewImageId = "" let created = 0 for (const entry of SEEDED_MEDIALIB_ENTRIES) { const createdEntry = await createCollectionEntry(baseURL, "medialib", { ...(entry as Record), }) const entryId = getEntryId(createdEntry) if (!entryId) { throw new Error("Seeded medialib entry was created without an id") } previewImageId = entryId created++ } return { created, previewImageId } } export async function cleanupSeededTestContent(baseURL: string): Promise { const contentEntries = await listCollectionEntries(baseURL, "content") // Cleanup runs before every seed pass so leftovers from aborted test runs // are removed on the next successful global setup. const seededEntries = contentEntries.filter((entry) => isSeededContentEntry(entry)) let deleted = 0 for (const entry of seededEntries) { const entryId = getEntryId(entry) if (!entryId) continue const ok = await deleteCollectionEntry(baseURL, "content", entryId) if (ok) deleted++ } const deletedMediaEntries = await cleanupSeededMedialibEntries(baseURL) return deleted + deletedMediaEntries } export async function seedTestContent(baseURL: string): Promise { const mediaSeed = await seedMedialibEntries(baseURL) let created = mediaSeed.created for (const entry of getSeededContentEntries(mediaSeed.previewImageId)) { await createCollectionEntry(baseURL, "content", entry as unknown as Record) created++ } return created } export async function ensureSeededTestContent(baseURL: string): Promise<{ deleted: number; created: number }> { const deleted = await cleanupSeededTestContent(baseURL) const created = await seedTestContent(baseURL) return { deleted, created } }