test(e2e): fix auth specs for character routes

This commit is contained in:
2026-07-20 15:38:51 +08:00
parent 7967486af7
commit bc6373f4c0
7 changed files with 62 additions and 5 deletions
+2
View File
@@ -2,6 +2,7 @@ import type { Page } from "@playwright/test";
import { registerAuthMocks } from "./api/auth";
import { registerChatMocks } from "./api/chat";
import { registerCharacterMocks } from "./api/character";
import { registerPaymentMocks } from "./api/payment";
import { registerUserMocks } from "./api/user";
@@ -30,6 +31,7 @@ export async function mockCoreApis(page: Page, options: MockCoreApisOptions = {}
isChatSendTokenExpired: () => chatState.hasExpiredChatSend,
psidLoginFlow: chatOptions.psidLoginFlow,
});
await registerCharacterMocks(page);
await registerUserMocks(page);
await registerChatMocks(page, chatOptions, chatState);
await registerPaymentMocks(page);
+10
View File
@@ -0,0 +1,10 @@
import type { Page } from "@playwright/test";
import { apiEnvelope } from "../data/common";
import { chatCharactersResponse } from "../data/character";
export async function registerCharacterMocks(page: Page) {
await page.route("**/api/characters**", async (route) => {
await route.fulfill({ json: apiEnvelope(chatCharactersResponse) });
});
}
+5 -1
View File
@@ -1,7 +1,7 @@
import type { Page } from "@playwright/test";
import { apiEnvelope } from "../data/common";
import { chatSendResponse, createChatSendResponse, createPaidImageHistoryResponse, createWeeklyLimitChatSendResponse, emptyChatHistoryResponse, insufficientCreditsUnlockImageResponse, insufficientCreditsUnlockVoiceResponse, paidImageChatSendResponse, paidImageUnlockHistoryResponse, paidVoiceHistoryResponse } from "../data/chat";
import { chatSendResponse, createChatSendResponse, createPaidImageHistoryResponse, createWeeklyLimitChatSendResponse, emptyChatHistoryResponse, emptyChatPreviewsResponse, insufficientCreditsUnlockImageResponse, insufficientCreditsUnlockVoiceResponse, paidImageChatSendResponse, paidImageUnlockHistoryResponse, paidVoiceHistoryResponse } from "../data/chat";
export interface ChatMockOptions {
chatLimitTriggerAt?: number;
@@ -31,6 +31,10 @@ export async function registerChatMocks(page: Page, options: ChatMockOptions, st
await route.fulfill({ json: apiEnvelope(response) });
});
await page.route("**/api/chat/previews", async (route) => {
await route.fulfill({ json: apiEnvelope(emptyChatPreviewsResponse) });
});
await page.route("**/api/chat/send", async (route) => {
sendCount += 1;
const requestBody = route.request().postDataJSON() as { message?: unknown } | undefined;
+26
View File
@@ -0,0 +1,26 @@
export const chatCharactersResponse = {
items: [
{
id: "elio",
displayName: "Elio Silvestri",
isActive: true,
capabilities: { chat: true, privateContent: true },
sortOrder: 10,
},
{
id: "maya-tan",
displayName: "Maya Tan",
isActive: true,
capabilities: { chat: true, privateContent: true },
sortOrder: 20,
},
{
id: "nayeli-cervantes",
displayName: "Nayeli Cervantes",
isActive: true,
capabilities: { chat: true, privateContent: true },
sortOrder: 30,
},
],
defaultCharacterId: "elio",
};
+2
View File
@@ -9,6 +9,8 @@ export const emptyChatHistoryResponse = {
privateCanViewFree: false,
};
export const emptyChatPreviewsResponse = { items: [] };
export const chatSendResponse = {
reply: "Hello from the E2E boyfriend.",
audioUrl: "",
+3 -1
View File
@@ -11,7 +11,7 @@ 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 defaultCharacterSidebarPath = `/sidebar?returnTo=%2Fcharacters%2F${defaultCharacterSlug}%2Fchat`;
export const defaultCharacterChatUrl = new RegExp(
`/characters/${defaultCharacterSlug}/chat(?:\\?.*)?$`,
);
@@ -34,6 +34,8 @@ export async function seedEmailSession(page: Page) {
await page.goto(defaultCharacterSplashPath);
await setEmailSessionStorage(page);
await page.reload();
// Let the root auth provider hydrate the seeded session before navigation.
await page.waitForTimeout(750);
}
export async function switchToEmailSignIn(page: Page) {
@@ -5,8 +5,11 @@ import {
clearBrowserState,
defaultCharacterSidebarPath,
defaultCharacterSplashPath,
defaultCharacterChatUrl,
enterChatFromSplash,
getSplashStartChatButton,
signInWithEmailAndOpenChat,
submitEmailLogin,
switchToEmailSignIn,
} from "@e2e/fixtures/test-helpers";
test.beforeEach(async ({ baseURL, context, page }) => {
@@ -17,11 +20,19 @@ test.beforeEach(async ({ baseURL, context, page }) => {
test("user can log out from the sidebar after email login", async ({
page,
}) => {
await signInWithEmailAndOpenChat(page);
await enterChatFromSplash(page);
await page
.getByRole("button", { name: "Sign up to unlock more features" })
.click();
await expect(page).toHaveURL(/\/auth(?:\?.*)?$/);
await switchToEmailSignIn(page);
await submitEmailLogin(page, { expectedUrl: defaultCharacterChatUrl });
await expect(page.getByRole("button", { name: "Menu" })).toBeVisible();
await page.getByRole("button", { name: "Menu" }).click();
await expect(page).toHaveURL(new RegExp(`${defaultCharacterSidebarPath}$`));
await expect(page).toHaveURL(
new RegExp(defaultCharacterSidebarPath.replace("?", "\\?") + "$"),
);
const logoutRequestPromise = page.waitForRequest("**/api/auth/logout");
await page.getByRole("button", { name: /Log out/i }).click();