import path from "node:path"; import { expect, test, type Page } from "@playwright/test"; import { mockCoreApis } from "@e2e/fixtures/api-mocks"; import { apiEnvelope } from "@e2e/fixtures/data/common"; import { e2eEmailUser } from "@e2e/fixtures/data/auth"; import { chatCharactersResponse } from "@e2e/fixtures/data/character"; import { emptyChatHistoryResponse, emptyChatPreviewsResponse, } from "@e2e/fixtures/data/chat"; import { paymentPlansResponse } from "@e2e/fixtures/data/payment"; import { userEntitlementsResponse } from "@e2e/fixtures/data/user"; import { defaultCharacterChatPath, defaultCharacterSplashPath, seedEmailSession, } from "@e2e/fixtures/helpers/auth"; const previewDirectory = path.join( process.cwd(), ".codex", "artifacts", "frontend-preview", "2026-07-22", "favorite-menu", ); test.beforeEach(async ({ page }) => { await registerFavoriteMenuMocks(page); }); test("favorite entry and three-tab Menu navigation render on the real pages", async ({ page, }, testInfo) => { const mobile = testInfo.project.name.includes("mobile"); await page.setViewportSize( mobile ? { width: 390, height: 844 } : { width: 1440, height: 900 }, ); const browserErrors = collectBrowserErrors(page); await seedEmailSession(page); await page.goto(defaultCharacterChatPath); await expect(page.getByRole("button", { name: "Save CozSweet" })).toBeVisible(); await expect( page.getByRole("button", { name: /First recharge offer, 50% off/i }), ).toBeVisible(); await expect(page.getByRole("button", { name: "Profile" })).toHaveCount(0); await savePreview(page, `${mobile ? "mobile" : "desktop"}-chat.png`); await page.goto(defaultCharacterSplashPath); await expect(page.getByRole("button", { name: "Save CozSweet" })).toBeVisible(); await expect(page.getByRole("button", { name: "Menu" })).toBeVisible(); const navigation = page.getByRole("navigation", { name: "Primary navigation", }); await expect(navigation).toContainText("Chat"); await expect(navigation).toContainText("Private Zone"); await expect(navigation).toContainText("Menu"); await savePreview(page, `${mobile ? "mobile" : "desktop"}-splash.png`); await page.goto("/characters/elio/private-zone"); await expect(page.getByRole("button", { name: "Save CozSweet" })).toBeVisible(); await expect(page.getByRole("button", { name: "Menu" })).toBeVisible(); await savePreview( page, `${mobile ? "mobile" : "desktop"}-private-zone.png`, ); await page.getByRole("button", { name: "Menu" }).click(); await expect(page.getByRole("heading", { name: "Menu" })).toBeVisible(); await expect(page.getByRole("button", { name: "Save CozSweet" })).toBeVisible(); await expect(page.getByRole("button", { name: "Menu" })).toHaveAttribute( "aria-current", "page", ); await savePreview(page, `${mobile ? "mobile" : "desktop"}-menu.png`); if (mobile) { await page.evaluate(() => { localStorage.setItem("cozsweet:favorite_entry", "1"); }); await page.goto(defaultCharacterChatPath); await expect(page.getByRole("button", { name: "Download" })).toBeVisible(); await savePreview(page, "mobile-android-external-chat-download.png"); await page.addInitScript(() => { Object.defineProperty(navigator, "userAgent", { configurable: true, value: "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", }); }); await page.goto(defaultCharacterChatPath); const iosDownload = page.getByRole("button", { name: "Download" }); await expect(iosDownload).toBeVisible(); await iosDownload.click(); await expect( page.getByRole("heading", { name: "Add CozSweet to Home Screen" }), ).toBeVisible(); await expect(page.getByRole("dialog")).toContainText( "In Safari, tap the Share button, then choose “Add to Home Screen”.", ); await savePreview(page, "mobile-ios-external-chat-download.png"); } expect(browserErrors.filter(isFeatureRuntimeError)).toEqual([]); }); async function registerFavoriteMenuMocks(page: Page): Promise { await mockCoreApis(page); await page.route("**/api/payment/plans**", async (route) => { await route.fulfill({ json: apiEnvelope({ ...paymentPlansResponse, isFirstRecharge: true, }), }); }); await page.route("**/api/private-zone/albums**", async (route) => { await route.fulfill({ json: apiEnvelope({ items: [], creditBalance: 80 }), }); }); await page.context().route("**/api/**", async (route) => { const pathname = new URL(route.request().url()).pathname; if (pathname === "/api/auth/session") { await route.fulfill({ json: { expires: "2099-12-31T23:59:59.000Z", user: { email: null, image: null, name: null }, }, }); return; } if (pathname === "/api/characters") { await route.fulfill({ json: apiEnvelope(chatCharactersResponse) }); return; } if (pathname === "/api/user/profile") { await route.fulfill({ json: apiEnvelope(e2eEmailUser) }); return; } if (pathname === "/api/user/entitlements") { await route.fulfill({ json: apiEnvelope(userEntitlementsResponse) }); return; } if (pathname === "/api/chat/history") { await route.fulfill({ json: apiEnvelope(emptyChatHistoryResponse) }); return; } if (pathname === "/api/chat/previews") { await route.fulfill({ json: apiEnvelope(emptyChatPreviewsResponse) }); return; } if (pathname === "/api/payment/plans") { await route.fulfill({ json: apiEnvelope({ ...paymentPlansResponse, isFirstRecharge: true, }), }); return; } if (pathname === "/api/payment/vip-status") { await route.fulfill({ json: apiEnvelope({ isVip: false, expiresAt: null }), }); return; } if (pathname === "/api/private-zone/albums") { await route.fulfill({ json: apiEnvelope({ items: [], creditBalance: 80 }), }); return; } await route.fallback(); }); } function collectBrowserErrors(page: Page): string[] { const errors: string[] = []; page.on("pageerror", (error) => errors.push(error.stack ?? error.message)); page.on("console", (message) => { if (message.type() === "error") errors.push(message.text()); }); return errors; } function isFeatureRuntimeError(message: string): boolean { const isKnownLocalPreviewDependencyError = message.includes("[ApiLoggingInterceptor]") || message.includes("[next-auth][error][CLIENT_FETCH_ERROR]") || message.includes("[Result] Result.wrap caught exception") || message.includes("Cannot read properties of undefined (reading 'waiting')"); return !isKnownLocalPreviewDependencyError; } async function savePreview(page: Page, fileName: string): Promise { if (process.env.FRONTEND_PREVIEW_SCREENSHOTS !== "1") return; await page.screenshot({ path: path.join(previewDirectory, fileName), animations: "disabled", }); }