test(e2e): update specs for multi-character routes

This commit is contained in:
2026-07-17 16:49:20 +08:00
parent 8a53a07e0e
commit 78e97c3612
10 changed files with 60 additions and 28 deletions
+9 -1
View File
@@ -8,6 +8,13 @@ export const e2eEmailCredentials = {
}; };
export const splashStartChatButtonName = "Start Chatting"; export const splashStartChatButtonName = "Start Chatting";
export const defaultCharacterSlug = "elio";
export const defaultCharacterSplashPath = `/characters/${defaultCharacterSlug}/splash`;
export const defaultCharacterChatPath = `/characters/${defaultCharacterSlug}/chat`;
export const defaultCharacterSidebarPath = `/characters/${defaultCharacterSlug}/sidebar`;
export const defaultCharacterChatUrl = new RegExp(
`/characters/${defaultCharacterSlug}/chat(?:\\?.*)?$`,
);
export function getSplashStartChatButton(page: Page) { export function getSplashStartChatButton(page: Page) {
return page.getByRole("button", { name: splashStartChatButtonName }); return page.getByRole("button", { name: splashStartChatButtonName });
@@ -24,8 +31,9 @@ export async function setEmailSessionStorage(page: Page) {
} }
export async function seedEmailSession(page: Page) { export async function seedEmailSession(page: Page) {
await page.goto("/splash"); await page.goto(defaultCharacterSplashPath);
await setEmailSessionStorage(page); await setEmailSessionStorage(page);
await page.reload();
} }
export async function switchToEmailSignIn(page: Page) { export async function switchToEmailSignIn(page: Page) {
+13 -4
View File
@@ -1,6 +1,11 @@
import { expect, type BrowserContext, type Page } from "@playwright/test"; import { expect, type BrowserContext, type Page } from "@playwright/test";
import { seedEmailSession } from "./auth"; import {
defaultCharacterChatPath,
defaultCharacterChatUrl,
defaultCharacterSplashPath,
seedEmailSession,
} from "./auth";
export async function clearBrowserState(context: BrowserContext, page: Page, baseURL?: string) { export async function clearBrowserState(context: BrowserContext, page: Page, baseURL?: string) {
await context.clearCookies(); await context.clearCookies();
@@ -12,8 +17,12 @@ export async function clearBrowserState(context: BrowserContext, page: Page, bas
} }
export async function enterChatFromSplash(page: Page, options: { expectedUrl?: RegExp; timeout?: number; waitUntil?: "commit" | "domcontentloaded" | "load" | "networkidle" } = {}) { export async function enterChatFromSplash(page: Page, options: { expectedUrl?: RegExp; timeout?: number; waitUntil?: "commit" | "domcontentloaded" | "load" | "networkidle" } = {}) {
const { expectedUrl = /\/chat$/, timeout = 10_000, waitUntil } = options; const {
await page.goto("/splash", { waitUntil }); expectedUrl = defaultCharacterChatUrl,
timeout = 10_000,
waitUntil,
} = options;
await page.goto(defaultCharacterSplashPath, { waitUntil });
const startChatButton = page.getByRole("button", { name: "Start Chatting" }); const startChatButton = page.getByRole("button", { name: "Start Chatting" });
await expect(startChatButton).toBeVisible({ timeout }); await expect(startChatButton).toBeVisible({ timeout });
await expect(startChatButton).toBeEnabled(); await expect(startChatButton).toBeEnabled();
@@ -34,7 +43,7 @@ export async function dismissChatInterruptions(page: Page) {
export async function signInWithEmailAndOpenChat(page: Page) { export async function signInWithEmailAndOpenChat(page: Page) {
await seedEmailSession(page); await seedEmailSession(page);
const historyResponsePromise = page.waitForResponse("**/api/chat/history**"); const historyResponsePromise = page.waitForResponse("**/api/chat/history**");
await page.goto("/chat"); await page.goto(defaultCharacterChatPath);
await historyResponsePromise; await historyResponsePromise;
await dismissChatInterruptions(page); await dismissChatInterruptions(page);
await expect(page.getByRole("textbox", { name: "Message" })).toBeEnabled({ timeout: 20_000 }); await expect(page.getByRole("textbox", { name: "Message" })).toBeEnabled({ timeout: 20_000 });
@@ -3,6 +3,7 @@ import { expect, test } from "@playwright/test";
import { mockCoreApis } from "@e2e/fixtures/api-mocks"; import { mockCoreApis } from "@e2e/fixtures/api-mocks";
import { import {
clearBrowserState, clearBrowserState,
defaultCharacterChatUrl,
enterChatFromSplash, enterChatFromSplash,
submitEmailLogin, submitEmailLogin,
switchToEmailSignIn, switchToEmailSignIn,
@@ -24,7 +25,9 @@ test("user can email login from other sign-in options and return to chat", async
await expect(page).toHaveURL(/\/auth(?:\?.*)?$/); await expect(page).toHaveURL(/\/auth(?:\?.*)?$/);
await switchToEmailSignIn(page); await switchToEmailSignIn(page);
const loginRequest = await submitEmailLogin(page, { expectedUrl: /\/chat$/ }); const loginRequest = await submitEmailLogin(page, {
expectedUrl: defaultCharacterChatUrl,
});
expect(["desktop", "android"]).toContain(loginRequest?.postDataJSON().platform); expect(["desktop", "android"]).toContain(loginRequest?.postDataJSON().platform);
await expect(page.getByRole("textbox", { name: "Message" })).toBeVisible(); await expect(page.getByRole("textbox", { name: "Message" })).toBeVisible();
@@ -3,6 +3,8 @@ import { expect, test } from "@playwright/test";
import { mockCoreApis } from "@e2e/fixtures/api-mocks"; import { mockCoreApis } from "@e2e/fixtures/api-mocks";
import { import {
clearBrowserState, clearBrowserState,
defaultCharacterSidebarPath,
defaultCharacterSplashPath,
getSplashStartChatButton, getSplashStartChatButton,
signInWithEmailAndOpenChat, signInWithEmailAndOpenChat,
} from "@e2e/fixtures/test-helpers"; } from "@e2e/fixtures/test-helpers";
@@ -19,7 +21,7 @@ test("user can log out from the sidebar after email login", async ({
await expect(page.getByRole("button", { name: "Menu" })).toBeVisible(); await expect(page.getByRole("button", { name: "Menu" })).toBeVisible();
await page.getByRole("button", { name: "Menu" }).click(); await page.getByRole("button", { name: "Menu" }).click();
await expect(page).toHaveURL(/\/sidebar$/); await expect(page).toHaveURL(new RegExp(`${defaultCharacterSidebarPath}$`));
const logoutRequestPromise = page.waitForRequest("**/api/auth/logout"); const logoutRequestPromise = page.waitForRequest("**/api/auth/logout");
await page.getByRole("button", { name: /Log out/i }).click(); await page.getByRole("button", { name: /Log out/i }).click();
@@ -27,6 +29,6 @@ test("user can log out from the sidebar after email login", async ({
const logoutRequest = await logoutRequestPromise; const logoutRequest = await logoutRequestPromise;
expect(logoutRequest.method()).toBe("POST"); expect(logoutRequest.method()).toBe("POST");
await expect(page).toHaveURL(/\/splash$/); await expect(page).toHaveURL(new RegExp(`${defaultCharacterSplashPath}$`));
await expect(getSplashStartChatButton(page)).toBeVisible(); await expect(getSplashStartChatButton(page)).toBeVisible();
}); });
@@ -1,7 +1,10 @@
import { expect, test } from "@playwright/test"; import { expect, test } from "@playwright/test";
import { mockCoreApis } from "@e2e/fixtures/api-mocks"; import { mockCoreApis } from "@e2e/fixtures/api-mocks";
import { clearBrowserState } from "@e2e/fixtures/test-helpers"; import {
clearBrowserState,
defaultCharacterChatUrl,
} from "@e2e/fixtures/test-helpers";
const psid = "e2e-facebook-psid"; const psid = "e2e-facebook-psid";
@@ -27,7 +30,7 @@ test("user enters from an external PSID link and becomes a guest", async ({
}); });
expect(psidLoginRequest.postDataJSON().deviceId).toEqual(expect.any(String)); expect(psidLoginRequest.postDataJSON().deviceId).toEqual(expect.any(String));
await expect(page).toHaveURL(/\/chat$/); await expect(page).toHaveURL(defaultCharacterChatUrl);
await expect(page.getByRole("textbox", { name: "Message" })).toBeVisible(); await expect(page.getByRole("textbox", { name: "Message" })).toBeVisible();
await expect await expect
@@ -4,6 +4,7 @@ import { mockCoreApis } from "@e2e/fixtures/api-mocks";
import { import {
clearBrowserState, clearBrowserState,
completeVipPayment, completeVipPayment,
defaultCharacterChatUrl,
dismissChatInterruptions, dismissChatInterruptions,
enterChatFromSplash, enterChatFromSplash,
expectAuthRedirectToVipChatSubscription, expectAuthRedirectToVipChatSubscription,
@@ -29,9 +30,9 @@ test("guest uses the shared chat limit top-up flow", async ({ page }) => {
await dismissChatInterruptions(page); await dismissChatInterruptions(page);
const messageInput = page.getByRole("textbox", { name: "Message" }); const messageInput = page.getByRole("textbox", { name: "Message" });
const quantityLimitBanner = page const quantityLimitBanner = page.getByTestId(
.getByRole("status") "chat-insufficient-credits-banner",
.filter({ hasText: "Insufficient credits" }); );
let limitTriggeredAt: number | null = null; let limitTriggeredAt: number | null = null;
let completedSendCount = 0; let completedSendCount = 0;
@@ -79,9 +80,7 @@ test("guest uses the shared chat limit top-up flow", async ({ page }) => {
expect(limitTriggeredAt).toBe(mockedLimitTriggerAt); expect(limitTriggeredAt).toBe(mockedLimitTriggerAt);
await expect(quantityLimitBanner).toBeVisible(); await expect(quantityLimitBanner).toBeVisible();
await expect( await expect(quantityLimitBanner.getByText("Out of Daily Free Chats")).toBeVisible();
quantityLimitBanner.getByText("Free chats refresh every day."),
).toBeVisible();
const topUpButton = quantityLimitBanner.getByRole("button", { const topUpButton = quantityLimitBanner.getByRole("button", {
name: "Top up credits to continue", name: "Top up credits to continue",
}); });
@@ -97,7 +96,7 @@ test("guest uses the shared chat limit top-up flow", async ({ page }) => {
await completeVipPayment(page); await completeVipPayment(page);
await expect(page).toHaveURL(/\/chat$/); await expect(page).toHaveURL(defaultCharacterChatUrl);
await expect(page.getByRole("textbox", { name: "Message" })).toBeVisible(); await expect(page.getByRole("textbox", { name: "Message" })).toBeVisible();
}); });
@@ -4,6 +4,7 @@ import { mockCoreApis } from "@e2e/fixtures/api-mocks";
import { import {
clearBrowserState, clearBrowserState,
completeVipPayment, completeVipPayment,
defaultCharacterChatUrl,
expectInsufficientCreditsDialog, expectInsufficientCreditsDialog,
expectVipChatSubscriptionUrl, expectVipChatSubscriptionUrl,
setEmailSessionStorage, setEmailSessionStorage,
@@ -24,7 +25,7 @@ test("guest unlocks a promoted image through email login and top-up", async ({
await page.goto( await page.goto(
"/external-entry?target=chat&mode=promotion&promotion_type=image", "/external-entry?target=chat&mode=promotion&promotion_type=image",
); );
await expect(page).toHaveURL(/\/chat$/); await expect(page).toHaveURL(defaultCharacterChatUrl);
const unlockButton = page.getByRole("button", { const unlockButton = page.getByRole("button", {
name: "Unlock private image", name: "Unlock private image",
@@ -35,7 +36,7 @@ test("guest unlocks a promoted image through email login and top-up", async ({
await expect(page).toHaveURL(/\/auth\?redirect=/); await expect(page).toHaveURL(/\/auth\?redirect=/);
await switchToEmailSignIn(page); await switchToEmailSignIn(page);
await submitEmailLogin(page, { expectedUrl: /\/chat$/ }); await submitEmailLogin(page, { expectedUrl: defaultCharacterChatUrl });
await setEmailSessionStorage(page); await setEmailSessionStorage(page);
const unlockRequestPromise = page.waitForRequest( const unlockRequestPromise = page.waitForRequest(
@@ -57,6 +58,6 @@ test("guest unlocks a promoted image through email login and top-up", async ({
await expectVipChatSubscriptionUrl(page); await expectVipChatSubscriptionUrl(page);
await completeVipPayment(page); await completeVipPayment(page);
await expect(page).toHaveURL(/\/chat$/); await expect(page).toHaveURL(defaultCharacterChatUrl);
await expect(page.getByRole("textbox", { name: "Message" })).toBeVisible(); await expect(page.getByRole("textbox", { name: "Message" })).toBeVisible();
}); });
+10 -5
View File
@@ -5,6 +5,7 @@ import { paidImageMessageId } from "@e2e/fixtures/test-data";
import { import {
clearBrowserState, clearBrowserState,
completeVipPayment, completeVipPayment,
defaultCharacterChatPath,
dismissChatInterruptions, dismissChatInterruptions,
enterChatFromSplash, enterChatFromSplash,
expectInsufficientCreditsDialog, expectInsufficientCreditsDialog,
@@ -15,7 +16,7 @@ import {
} from "@e2e/fixtures/test-helpers"; } from "@e2e/fixtures/test-helpers";
const paidImageOverlayUrl = new RegExp( const paidImageOverlayUrl = new RegExp(
`/chat\\?image=${paidImageMessageId}$`, `${defaultCharacterChatPath}\\?image=${paidImageMessageId}$`,
); );
test.beforeEach(async ({ baseURL, context, page }) => { test.beforeEach(async ({ baseURL, context, page }) => {
@@ -62,7 +63,7 @@ test("guest can unlock a paid image after email login and subscription payment",
await expect(page).toHaveURL(/\/auth\?redirect=/); await expect(page).toHaveURL(/\/auth\?redirect=/);
expect(new URL(page.url()).searchParams.get("redirect")).toBe( expect(new URL(page.url()).searchParams.get("redirect")).toBe(
`/chat?image=${paidImageMessageId}`, `${defaultCharacterChatPath}?image=${paidImageMessageId}`,
); );
await switchToEmailSignIn(page); await switchToEmailSignIn(page);
@@ -74,7 +75,7 @@ test("guest can unlock a paid image after email login and subscription payment",
const unlockRequestPromise = page.waitForRequest( const unlockRequestPromise = page.waitForRequest(
"**/api/chat/unlock-private", "**/api/chat/unlock-private",
); );
await page.goto(`/chat?image=${paidImageMessageId}`); await page.goto(`${defaultCharacterChatPath}?image=${paidImageMessageId}`);
const unlockRequest = await unlockRequestPromise; const unlockRequest = await unlockRequestPromise;
expect(unlockRequest.postDataJSON()).toMatchObject({ expect(unlockRequest.postDataJSON()).toMatchObject({
@@ -89,7 +90,11 @@ test("guest can unlock a paid image after email login and subscription payment",
await expectVipChatSubscriptionUrl(page); await expectVipChatSubscriptionUrl(page);
await completeVipPayment(page); await completeVipPayment(page);
await expect(page).toHaveURL(/\/chat(?:\?image=msg_photo_paywall_001)?$/); await expect(page).toHaveURL(
expect(new URL(page.url()).pathname).toBe("/chat"); new RegExp(
`${defaultCharacterChatPath}(?:\\?image=msg_photo_paywall_001)?$`,
),
);
expect(new URL(page.url()).pathname).toBe(defaultCharacterChatPath);
await expect(page.getByRole("textbox", { name: "Message" })).toBeVisible(); await expect(page.getByRole("textbox", { name: "Message" })).toBeVisible();
}); });
@@ -4,6 +4,7 @@ import { mockCoreApis } from "@e2e/fixtures/api-mocks";
import { paidImageMessageId } from "@e2e/fixtures/test-data"; import { paidImageMessageId } from "@e2e/fixtures/test-data";
import { import {
clearBrowserState, clearBrowserState,
defaultCharacterChatPath,
dismissChatInterruptions, dismissChatInterruptions,
expectInsufficientCreditsDialog, expectInsufficientCreditsDialog,
expectVipChatSubscriptionUrl, expectVipChatSubscriptionUrl,
@@ -11,7 +12,7 @@ import {
} from "@e2e/fixtures/test-helpers"; } from "@e2e/fixtures/test-helpers";
const paidImageOverlayUrl = new RegExp( const paidImageOverlayUrl = new RegExp(
`/chat\\?image=${paidImageMessageId}$`, `${defaultCharacterChatPath}\\?image=${paidImageMessageId}$`,
); );
test.beforeEach(async ({ baseURL, context, page }) => { test.beforeEach(async ({ baseURL, context, page }) => {
@@ -1,6 +1,7 @@
import { expect, test } from "@playwright/test"; import { expect, test } from "@playwright/test";
import { import {
defaultCharacterChatUrl,
enterChatFromSplash, enterChatFromSplash,
submitEmailLogin, submitEmailLogin,
switchToEmailSignIn, switchToEmailSignIn,
@@ -19,7 +20,7 @@ test.describe("pre-release email login smoke", () => {
page, page,
}) => { }) => {
await enterChatFromSplash(page, { await enterChatFromSplash(page, {
expectedUrl: /\/chat(?:\?.*)?$/, expectedUrl: defaultCharacterChatUrl,
timeout: 20_000, timeout: 20_000,
waitUntil: "domcontentloaded", waitUntil: "domcontentloaded",
}); });
@@ -36,7 +37,7 @@ test.describe("pre-release email login smoke", () => {
await switchToEmailSignIn(page); await switchToEmailSignIn(page);
await submitEmailLogin(page, { await submitEmailLogin(page, {
expectedUrl: /\/chat(?:\?.*)?$/, expectedUrl: defaultCharacterChatUrl,
waitForRequest: false, waitForRequest: false,
urlTimeout: 20_000, urlTimeout: 20_000,
}); });