chore(release): promote favorite and Menu navigation to pre
Docker Image / Build and Push Docker Image (push) Successful in 1m55s

This commit is contained in:
Codex
2026-07-22 19:38:11 +08:00
23 changed files with 794 additions and 118 deletions
@@ -0,0 +1,198 @@
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("Elio Private Zoom");
await expect(navigation).toContainText("Menu");
await savePreview(page, `${mobile ? "mobile" : "desktop"}-splash.png`);
await page.goto("/characters/elio/private-zoom");
await expect(page.getByRole("button", { name: "Save CozSweet" })).toBeVisible();
await expect(page.getByRole("button", { name: "Menu" })).toBeVisible();
await savePreview(
page,
`${mobile ? "mobile" : "desktop"}-private-zoom.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);
await expect(page.getByRole("button", { name: "Saved" })).toBeVisible();
await savePreview(page, "mobile-ios-external-chat-saved.png");
}
expect(browserErrors.filter(isFeatureRuntimeError)).toEqual([]);
});
async function registerFavoriteMenuMocks(page: Page): Promise<void> {
await mockCoreApis(page);
await page.route("**/api/payment/plans**", async (route) => {
await route.fulfill({
json: apiEnvelope({
...paymentPlansResponse,
isFirstRecharge: true,
}),
});
});
await page.route("**/api/private-zoom/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-zoom/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.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");
return !isKnownLocalPreviewDependencyError;
}
async function savePreview(page: Page, fileName: string): Promise<void> {
if (process.env.FRONTEND_PREVIEW_SCREENSHOTS !== "1") return;
await page.screenshot({
path: path.join(previewDirectory, fileName),
animations: "disabled",
});
}