refactor(e2e): organize fixtures by feature

This commit is contained in:
2026-07-16 11:49:58 +08:00
parent 764bb5a862
commit e730799aa5
28 changed files with 521 additions and 1335 deletions
+25
View File
@@ -0,0 +1,25 @@
import type { Page } from "@playwright/test";
import { apiEnvelope } from "../data/common";
import { emailLoginResponse, guestLoginResponse, refreshedEmailLoginResponse, refreshedGuestLoginResponse } from "../data/auth";
export interface AuthMockState {
chatSendTokenRefreshFlow: boolean;
isChatSendTokenExpired: () => boolean;
}
export async function registerAuthMocks(page: Page, state: AuthMockState) {
await page.route("**/api/auth/guest", async (route) => {
const response = state.chatSendTokenRefreshFlow && state.isChatSendTokenExpired() ? refreshedGuestLoginResponse : guestLoginResponse;
await route.fulfill({ json: apiEnvelope(response) });
});
await page.route("**/api/auth/login", async (route) => {
await route.fulfill({ json: apiEnvelope(emailLoginResponse) });
});
await page.route("**/api/auth/refresh", async (route) => {
await route.fulfill({ json: apiEnvelope(refreshedEmailLoginResponse) });
});
await page.route("**/api/auth/logout", async (route) => {
await route.fulfill({ json: apiEnvelope(null) });
});
}
+71
View File
@@ -0,0 +1,71 @@
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";
export interface ChatMockOptions {
chatLimitTriggerAt?: number;
chatSendTokenRefreshFlow: boolean;
paidImageFlow: boolean;
paidImageInsufficientCreditsFlow: boolean;
paidVoiceInsufficientCreditsFlow: boolean;
}
export interface ChatMockState {
hasExpiredChatSend: boolean;
paidImageRequested: boolean;
paidImageUnlocked: boolean;
}
export async function registerChatMocks(page: Page, options: ChatMockOptions, state: ChatMockState) {
let sendCount = 0;
await page.route("**/api/chat/history**", async (route) => {
const response = options.paidVoiceInsufficientCreditsFlow
? paidVoiceHistoryResponse
: options.paidImageInsufficientCreditsFlow
? createPaidImageHistoryResponse(false)
: options.paidImageFlow && state.paidImageRequested
? createPaidImageHistoryResponse(state.paidImageUnlocked)
: emptyChatHistoryResponse;
await route.fulfill({ json: apiEnvelope(response) });
});
await page.route("**/api/chat/send", async (route) => {
sendCount += 1;
const requestBody = route.request().postDataJSON() as { message?: unknown } | undefined;
const message = typeof requestBody?.message === "string" ? requestBody.message : "";
if (options.chatSendTokenRefreshFlow && !state.hasExpiredChatSend) {
state.hasExpiredChatSend = true;
await route.fulfill({ status: 401, json: { message: "expired" } });
return;
}
const response = options.paidImageFlow && message.includes("给我发图片")
? paidImageChatSendResponse
: options.chatLimitTriggerAt != null && sendCount >= options.chatLimitTriggerAt
? createWeeklyLimitChatSendResponse(sendCount, options.chatLimitTriggerAt)
: options.chatLimitTriggerAt != null
? createChatSendResponse(sendCount)
: chatSendResponse;
if (options.paidImageFlow && message.includes("给我发图片")) {
state.paidImageRequested = true;
state.paidImageUnlocked = false;
}
await route.fulfill({ json: apiEnvelope(response) });
});
await page.route("**/api/chat/unlock-history", async (route) => {
if (options.paidImageFlow && state.paidImageRequested) state.paidImageUnlocked = true;
const response = options.paidImageFlow && state.paidImageRequested ? paidImageUnlockHistoryResponse : { unlocked: true, reason: "no_locked_messages", totalLocked: 0, unlockedCount: 0, privateCount: 0, imageCount: 0, voiceCount: 0, requiredCredits: 0, currentCredits: 0, remainingCredits: 0, shortfallCredits: 0, costsByMessage: {}, messageIds: [] };
await route.fulfill({ json: apiEnvelope(response) });
});
await page.route("**/api/chat/unlock-private", async (route) => {
const response = options.paidVoiceInsufficientCreditsFlow
? insufficientCreditsUnlockVoiceResponse
: options.paidImageInsufficientCreditsFlow || options.paidImageFlow && state.paidImageRequested && !state.paidImageUnlocked
? insufficientCreditsUnlockImageResponse
: { unlocked: true, content: "Unlocked message", reason: "ok", creditBalance: 100, creditsCharged: 0, requiredCredits: 0, shortfallCredits: 0, lockDetail: { locked: false, showContent: true, showUpgrade: false, reason: null, hint: null, detail: null } };
await route.fulfill({ json: apiEnvelope(response) });
});
}
+12
View File
@@ -0,0 +1,12 @@
import type { Page } from "@playwright/test";
import { apiEnvelope } from "../data/common";
import { createPaymentOrderResponse, paidPaymentOrderStatusResponse, paymentPlansResponse, tipPaymentPlansResponse, vipStatusResponse } from "../data/payment";
export async function registerPaymentMocks(page: Page) {
await page.route("**/api/payment/plans**", async (route) => route.fulfill({ json: apiEnvelope(paymentPlansResponse) }));
await page.route("**/api/payment/tip-plans**", async (route) => route.fulfill({ json: apiEnvelope(tipPaymentPlansResponse) }));
await page.route("**/api/payment/create-order", async (route) => route.fulfill({ json: apiEnvelope(createPaymentOrderResponse) }));
await page.route("**/api/payment/order-status**", async (route) => route.fulfill({ json: apiEnvelope(paidPaymentOrderStatusResponse) }));
await page.route("**/api/payment/vip-status", async (route) => route.fulfill({ json: apiEnvelope(vipStatusResponse) }));
}
+14
View File
@@ -0,0 +1,14 @@
import type { Page } from "@playwright/test";
import { apiEnvelope } from "../data/common";
import { e2eEmailUser } from "../data/auth";
import { userEntitlementsResponse } from "../data/user";
export async function registerUserMocks(page: Page) {
await page.route("**/api/user/profile", async (route) => {
await route.fulfill({ json: apiEnvelope(e2eEmailUser) });
});
await page.route("**/api/user/entitlements", async (route) => {
await route.fulfill({ json: apiEnvelope(userEntitlementsResponse) });
});
}