fix(multi-role): enable Maya and Nayeli commercial flows
This commit is contained in:
@@ -31,5 +31,5 @@ test("user can email login from other sign-in options and return to chat", async
|
||||
expect(["desktop", "android"]).toContain(loginRequest?.postDataJSON().platform);
|
||||
|
||||
await expect(page.getByRole("textbox", { name: "Message" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Profile" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Save CozSweet" })).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -27,9 +27,12 @@ test("user can log out from the profile after email login", async ({
|
||||
await expect(page).toHaveURL(/\/auth(?:\?.*)?$/);
|
||||
await switchToEmailSignIn(page);
|
||||
await submitEmailLogin(page, { expectedUrl: defaultCharacterChatUrl });
|
||||
await expect(page.getByRole("button", { name: "Profile" })).toBeVisible();
|
||||
await page.getByRole("link", { name: "Back to home" }).click();
|
||||
await expect(page).toHaveURL(
|
||||
new RegExp(defaultCharacterSplashPath.replace("?", "\\?") + "$"),
|
||||
);
|
||||
|
||||
await page.getByRole("button", { name: "Profile" }).click();
|
||||
await page.getByRole("button", { name: "Menu" }).click();
|
||||
await expect(page).toHaveURL(
|
||||
new RegExp(defaultCharacterProfilePath.replace("?", "\\?") + "$"),
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user