From e951d56076c3b6968452042322f00faf18ec5f2e Mon Sep 17 00:00:00 2001 From: chenhang Date: Mon, 29 Jun 2026 17:43:27 +0800 Subject: [PATCH] feat(e2e): add email login flow tests and mock responses for email user --- e2e/fixtures/api-mocks.ts | 15 +++++ e2e/fixtures/test-data.ts | 23 ++++++++ .../email-login-from-other-options.spec.ts | 48 ++++++++++++++++ e2e/specs/mock/splash-chat.spec.ts | 39 ------------- e2e/specs/real/production-smoke.spec.ts | 57 ------------------- 5 files changed, 86 insertions(+), 96 deletions(-) create mode 100644 e2e/specs/mock/email-login-from-other-options.spec.ts delete mode 100644 e2e/specs/mock/splash-chat.spec.ts delete mode 100644 e2e/specs/real/production-smoke.spec.ts diff --git a/e2e/fixtures/api-mocks.ts b/e2e/fixtures/api-mocks.ts index eb5a0d4e..0b7d2184 100644 --- a/e2e/fixtures/api-mocks.ts +++ b/e2e/fixtures/api-mocks.ts @@ -3,9 +3,12 @@ import type { Page } from "@playwright/test"; import { apiEnvelope, chatSendResponse, + e2eEmailUser, emptyChatHistoryResponse, + emailLoginResponse, guestLoginResponse, paymentPlansResponse, + userEntitlementsResponse, vipStatusResponse, } from "./test-data"; @@ -14,6 +17,18 @@ export async function mockCoreApis(page: Page): Promise { await route.fulfill({ json: apiEnvelope(guestLoginResponse) }); }); + await page.route("**/api/auth/login", async (route) => { + await route.fulfill({ json: apiEnvelope(emailLoginResponse) }); + }); + + 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) }); + }); + await page.route("**/api/chat/history**", async (route) => { await route.fulfill({ json: apiEnvelope(emptyChatHistoryResponse) }); }); diff --git a/e2e/fixtures/test-data.ts b/e2e/fixtures/test-data.ts index 9017c3cc..9645ecb6 100644 --- a/e2e/fixtures/test-data.ts +++ b/e2e/fixtures/test-data.ts @@ -26,6 +26,14 @@ export const e2eUser = { voiceMinutesRemaining: 0, }; +export const e2eEmailUser = { + ...e2eUser, + id: "user_e2e_email", + username: "E2E Email User", + email: "chanwillian0@gmail.com", + isGuest: false, +}; + export const guestLoginResponse = { token: "e2e-guest-token", deviceId: "e2e-device-id", @@ -35,6 +43,21 @@ export const guestLoginResponse = { user: e2eUser, }; +export const emailLoginResponse = { + token: "e2e-email-token", + refreshToken: "e2e-email-refresh-token", + user: e2eEmailUser, +}; + +export const userEntitlementsResponse = { + userId: e2eEmailUser.id, + isGuest: false, + isVip: false, + vipExpiresAt: null, + creditBalance: 0, + dolBalance: 0, +}; + export const emptyChatHistoryResponse = { messages: [], total: 0, diff --git a/e2e/specs/mock/email-login-from-other-options.spec.ts b/e2e/specs/mock/email-login-from-other-options.spec.ts new file mode 100644 index 00000000..59bb3004 --- /dev/null +++ b/e2e/specs/mock/email-login-from-other-options.spec.ts @@ -0,0 +1,48 @@ +import { expect, test } from "@playwright/test"; + +import { mockCoreApis } from "../../fixtures/api-mocks"; + +test.beforeEach(async ({ context, page }) => { + await context.clearCookies(); + await mockCoreApis(page); +}); + +test("user can email login from other sign-in options and return to chat", async ({ + page, +}) => { + await page.goto("/splash"); + + await page.getByRole("button", { name: "Skip" }).click(); + await expect(page).toHaveURL(/\/chat$/); + + await page + .getByRole("button", { name: "Sign up to unlock more features" }) + .click(); + await expect(page).toHaveURL(/\/auth$/); + + await page.getByRole("button", { name: /Other Sign In Options/i }).click(); + await expect( + page.getByRole("dialog", { name: /Other sign-in options/i }), + ).toBeVisible(); + + await page.getByRole("button", { name: "Email Sign In" }).click(); + await expect(page.getByPlaceholder("Email")).toHaveValue( + "chanwillian0@gmail.com", + ); + await expect(page.getByPlaceholder("Password")).toHaveValue("12345678"); + + const loginRequestPromise = page.waitForRequest("**/api/auth/login"); + await page.getByRole("button", { name: "Login" }).click(); + + const loginRequest = await loginRequestPromise; + expect(loginRequest.postDataJSON()).toMatchObject({ + email: "chanwillian0@gmail.com", + password: "12345678", + platform: "web", + isTestAccount: true, + }); + + await expect(page).toHaveURL(/\/chat$/); + await expect(page.getByRole("textbox", { name: "Message" })).toBeVisible(); + await expect(page.getByRole("button", { name: "Menu" })).toBeVisible(); +}); diff --git a/e2e/specs/mock/splash-chat.spec.ts b/e2e/specs/mock/splash-chat.spec.ts deleted file mode 100644 index a8f581d0..00000000 --- a/e2e/specs/mock/splash-chat.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { expect, test } from "@playwright/test"; - -import { mockCoreApis } from "../../fixtures/api-mocks"; - -test.beforeEach(async ({ context, page }) => { - await context.clearCookies(); - await mockCoreApis(page); -}); - -test("guest can skip splash, enter chat, and send a message", async ({ page }) => { - await page.goto("/splash"); - - await page.getByRole("button", { name: "Skip" }).click(); - await expect(page).toHaveURL(/\/chat$/); - - const messageInput = page.getByRole("textbox", { name: "Message" }); - await expect(messageInput).toBeVisible(); - - await messageInput.fill("hello e2e"); - await page.getByRole("button", { name: "Send message" }).click(); - - await expect(page.getByText("hello e2e")).toBeVisible(); - await expect(page.getByText("Hello from the E2E boyfriend.")).toBeVisible(); -}); - -test("subscription page renders mocked plans without touching real payment", async ({ - page, -}) => { - await page.goto("/splash"); - await page.getByRole("button", { name: "Skip" }).click(); - await expect(page).toHaveURL(/\/chat$/); - - await page.goto("/subscription?type=vip"); - - await expect(page.getByText("Subscribe to become the VIP Member")).toBeVisible(); - await expect(page.getByText("Monthly")).toBeVisible(); - await expect(page.getByText("Quarterly")).toBeVisible(); - await expect(page.getByRole("button", { name: /Pay and Activiate/i })).toBeVisible(); -}); diff --git a/e2e/specs/real/production-smoke.spec.ts b/e2e/specs/real/production-smoke.spec.ts deleted file mode 100644 index 88a90cb0..00000000 --- a/e2e/specs/real/production-smoke.spec.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { expect, test } from "@playwright/test"; - -const apiBaseURL = process.env.E2E_API_BASE_URL; - -test.describe("real backend smoke", () => { - test("splash page can be opened", async ({ page }) => { - await page.goto("/splash", { waitUntil: "domcontentloaded" }); - - await expect(page.getByRole("button", { name: "Skip" })).toBeVisible(); - await expect( - page.getByRole("button", { name: "Login with Facebook" }), - ).toBeVisible(); - }); - - test("protected subscription page redirects anonymous visitors safely", async ({ - page, - }) => { - await page.goto("/subscription?type=vip", { - waitUntil: "domcontentloaded", - }); - - await expect(page.getByRole("button", { name: "Skip" })).toBeVisible(); - await expect(page).toHaveURL(/\/splash(?:\?.*)?$/); - }); - - test("real payment plans API returns a usable plan list", async ({ - request, - }) => { - test.skip(!apiBaseURL, "Set E2E_API_BASE_URL to test real backend APIs."); - - const response = await request.get(`${apiBaseURL}/api/payment/plans`); - expect(response.ok()).toBe(true); - const body = await response.json(); - - expect(body.success).toBe(true); - expect(Array.isArray(body.data?.plans)).toBe(true); - expect(body.data.plans.length).toBeGreaterThan(0); - expect(body.data.plans[0]).toEqual( - expect.objectContaining({ - plan_id: expect.any(String), - plan_name: expect.any(String), - amount_cents: expect.any(Number), - currency: expect.any(String), - }), - ); - }); - - test("auth page exposes the social login entry without completing OAuth", async ({ - page, - }) => { - await page.goto("/auth", { waitUntil: "domcontentloaded" }); - - await expect( - page.getByRole("button", { name: /facebook/i }), - ).toBeVisible(); - }); -});