fix(multi-role): enable Maya and Nayeli commercial flows

This commit is contained in:
Codex
2026-07-22 20:49:28 +08:00
parent bdd53a6ea1
commit 4639acf232
8 changed files with 219 additions and 5 deletions
@@ -0,0 +1,211 @@
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 { clearBrowserState } from "@e2e/fixtures/test-helpers";
const characters = [
{
id: "elio",
slug: "elio",
displayName: "Elio Silvestri",
shortName: "Elio",
cover: "elio.png",
},
{
id: "maya-tan",
slug: "maya",
displayName: "Maya Tan",
shortName: "Maya",
cover: "maya.webp",
},
{
id: "nayeli-cervantes",
slug: "nayeli",
displayName: "Nayeli Cervantes",
shortName: "Nayeli",
cover: "nayeli.webp",
},
] as const;
const previewDirectory = path.join(
process.cwd(),
".codex",
"artifacts",
"frontend-preview",
"2026-07-22",
"multi-role-commercial",
);
test.beforeEach(async ({ baseURL, context, page }) => {
await clearBrowserState(context, page, baseURL);
await mockCoreApis(page);
await registerMultiRoleCommercialMocks(page);
});
for (const character of characters) {
test(`${character.displayName} homepage renders its assigned cover`, async ({
page,
}, testInfo) => {
const mobile = testInfo.project.name.includes("mobile");
await page.setViewportSize(
mobile ? { width: 390, height: 844 } : { width: 1440, height: 900 },
);
await page.goto(`/characters/${character.slug}/splash`);
const cover = page.locator(`img[src*="${character.cover}"]`);
await expect(cover).toBeVisible();
await expect(page.getByText(character.displayName).last()).toBeVisible();
await expect
.poll(() =>
cover.evaluate((image: HTMLImageElement) =>
image.complete ? image.naturalWidth : 0,
),
)
.toBeGreaterThan(0);
if (
process.env.FRONTEND_PREVIEW_SCREENSHOTS === "1" &&
character.id !== "elio"
) {
await page.screenshot({
path: path.join(
previewDirectory,
`${character.slug}-${mobile ? "mobile-390x844" : "desktop-1440x900"}.png`,
),
animations: "disabled",
});
}
});
test(`${character.displayName} can open a character-scoped Private Zoom`, async ({
page,
}) => {
const albumRequest = page.waitForRequest((request) => {
const url = new URL(request.url());
return (
url.pathname === "/api/private-zoom/albums" &&
url.searchParams.get("characterId") === character.id
);
});
await page.goto(`/characters/${character.slug}/private-zoom`);
await albumRequest;
await expect(
page.getByRole("heading", { name: "Private albums" }),
).toBeVisible();
await expect(page.getByText(character.displayName).last()).toBeVisible();
await expect(
page.getByRole("button", {
name: `View locked collection with 8 images from ${character.displayName}`,
}),
).toBeVisible();
});
test(`${character.displayName} receives a character-scoped Tip catalog`, async ({
page,
}) => {
const catalogRequest = page.waitForRequest((request) => {
const url = new URL(request.url());
return (
url.pathname === "/api/payment/gift-products" &&
url.searchParams.get("characterId") === character.id
);
});
await page.goto(`/characters/${character.slug}/tip`);
await catalogRequest;
await expect(
page.getByRole("heading", { name: `Buy ${character.shortName} a coffee` }),
).toBeVisible();
await expect(page.getByRole("radio", { name: /Small Coffee/ })).toBeChecked();
await expect(
page.getByRole("button", { name: "Order and Buy" }),
).toBeEnabled();
await expect(
page.getByText("This character does not have any gifts available yet."),
).toHaveCount(0);
});
}
async function registerMultiRoleCommercialMocks(page: Page): Promise<void> {
await page.route("**/api/private-zoom/albums**", async (route) => {
const url = new URL(route.request().url());
const characterId = url.searchParams.get("characterId") ?? "elio";
const character =
characters.find((candidate) => candidate.id === characterId) ?? characters[0];
await route.fulfill({
json: apiEnvelope({
creditBalance: 1000,
items: [
{
albumId: `album-${character.id}`,
title: `${character.shortName} private photos`,
content: null,
previewText: "Unlock this private album",
imageCount: 8,
images: [
{
url: `/images/private-zoom/banner/${character.slug}.png`,
locked: true,
index: 0,
},
],
locked: true,
unlocked: false,
unlockCost: 320,
publishedAt: "2026-07-22T10:00:00+08:00",
lockDetail: { locked: true },
},
],
}),
});
});
await page.route("**/api/payment/gift-products**", async (route) => {
const url = new URL(route.request().url());
const characterId = url.searchParams.get("characterId") ?? "elio";
const planPrefix =
characterId === "elio"
? "tip"
: `tip_${characterId.replaceAll("-", "_")}`;
await route.fulfill({
json: apiEnvelope({
characterId,
categories: [
{
category: "coffee",
name: "Coffee",
productCount: 1,
imageUrl: null,
},
],
plans: [
{
planId: `${planPrefix}_coffee_usd_4_99`,
planName: "Small Coffee",
orderType: "tip",
tipType: "coffee_small",
category: "coffee",
characterId,
description: "A character-scoped coffee gift",
imageUrl: null,
amountCents: 499,
currency: "USD",
autoRenew: false,
isFirstRechargeOffer: false,
firstRechargeDiscountPercent: 0,
promotionType: null,
},
],
}),
});
});
}