diff --git a/e2e/fixtures/api-mocks.ts b/e2e/fixtures/api-mocks.ts index 7f3c7e39..dcbc4772 100644 --- a/e2e/fixtures/api-mocks.ts +++ b/e2e/fixtures/api-mocks.ts @@ -138,6 +138,8 @@ export async function mockCoreApis( ? insufficientCreditsUnlockVoiceResponse : paidImageInsufficientCreditsFlow ? insufficientCreditsUnlockImageResponse + : paidImageFlow && paidImageRequested && !paidImageUnlocked + ? insufficientCreditsUnlockImageResponse : { unlocked: true, content: "Unlocked message", diff --git a/e2e/fixtures/test-data.ts b/e2e/fixtures/test-data.ts index 575b2a1e..33fbbe8f 100644 --- a/e2e/fixtures/test-data.ts +++ b/e2e/fixtures/test-data.ts @@ -107,6 +107,12 @@ export function createWeeklyLimitChatSendResponse( audioUrl: "", messageId: "", isGuest: true, + canSendMessage: false, + cannotSendReason: "insufficient_credits", + creditBalance: 0, + creditsCharged: 0, + requiredCredits: limit, + shortfallCredits: Math.max(0, limit - usedMessageCount), timestamp: chatSendResponse.timestamp + usedMessageCount, image: { type: null, @@ -282,37 +288,40 @@ export const insufficientCreditsUnlockImageResponse = { export const paymentPlansResponse = { plans: [ { - plan_id: "vip_monthly", - plan_name: "Monthly", - order_type: "vip_monthly", - amount_cents: 999, - original_amount_cents: 1999, - daily_price_cents: 33, + planId: "vip_monthly", + planName: "Monthly", + orderType: "vip_monthly", + amountCents: 999, + originalAmountCents: 1999, + dailyPriceCents: 33, currency: "USD", - vip_days: 30, - dol_amount: null, + vipDays: 30, + dolAmount: null, + creditBalance: 300, }, { - plan_id: "vip_quarterly", - plan_name: "Quarterly", - order_type: "vip_quarterly", - amount_cents: 2499, - original_amount_cents: 5999, - daily_price_cents: 28, + planId: "vip_quarterly", + planName: "Quarterly", + orderType: "vip_quarterly", + amountCents: 2499, + originalAmountCents: 5999, + dailyPriceCents: 28, currency: "USD", - vip_days: 90, - dol_amount: null, + vipDays: 90, + dolAmount: null, + creditBalance: 1000, }, { - plan_id: "dol_voice_30", - plan_name: "Voice Pack", - order_type: "dol_voice", - amount_cents: 499, - original_amount_cents: null, - daily_price_cents: null, + planId: "dol_voice_30", + planName: "Voice Pack", + orderType: "dol_voice", + amountCents: 499, + originalAmountCents: null, + dailyPriceCents: null, currency: "USD", - vip_days: null, - dol_amount: 30, + vipDays: null, + dolAmount: 30, + creditBalance: 30, }, ], }; diff --git a/e2e/fixtures/test-helpers.ts b/e2e/fixtures/test-helpers.ts new file mode 100644 index 00000000..52525638 --- /dev/null +++ b/e2e/fixtures/test-helpers.ts @@ -0,0 +1,178 @@ +import { expect, type BrowserContext, type Page } from "@playwright/test"; + +export const e2eEmailCredentials = { + email: "chanwillian0@gmail.com", + password: "12345678", +}; + +export async function clearBrowserState( + context: BrowserContext, + page: Page, + baseURL?: string, +) { + await context.clearCookies(); + + const client = await context.newCDPSession(page); + const origins = new Set([ + new URL(baseURL ?? "http://127.0.0.1:3000").origin, + "http://127.0.0.1:3000", + "http://localhost:3000", + ]); + + for (const origin of origins) { + await client + .send("Storage.clearDataForOrigin", { + origin, + storageTypes: "all", + }) + .catch(() => {}); + } +} + +export async function enterChatFromSplash( + page: Page, + options: { + expectedUrl?: RegExp; + timeout?: number; + waitUntil?: "commit" | "domcontentloaded" | "load" | "networkidle"; + } = {}, +) { + const { + expectedUrl = /\/chat$/, + timeout = 10_000, + waitUntil, + } = options; + + await page.goto("/splash", { waitUntil }); + + const skipButton = page.getByRole("button", { name: "Skip" }); + await expect(skipButton).toBeVisible({ timeout }); + await expect(skipButton).toBeEnabled(); + + for (let attempt = 0; attempt < 3; attempt += 1) { + await skipButton.click(); + try { + await expect(page).toHaveURL(expectedUrl, { timeout: 3_000 }); + return; + } catch { + await page.waitForTimeout(500); + } + } + + await expect(page).toHaveURL(expectedUrl, { timeout }); +} + +export async function dismissChatInterruptions(page: Page) { + for (const buttonName of ["Later", "Cancel", "OK"]) { + const button = page.getByRole("button", { name: buttonName }).first(); + if (await button.isVisible().catch(() => false)) { + await button.click(); + } + } +} + +export async function switchToEmailSignIn(page: Page) { + 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( + e2eEmailCredentials.email, + ); + await expect(page.getByPlaceholder("Password")).toHaveValue( + e2eEmailCredentials.password, + ); +} + +export async function submitEmailLogin( + page: Page, + options: { + expectedUrl?: RegExp; + waitForRequest?: boolean; + urlTimeout?: number; + } = {}, +) { + const { + expectedUrl, + waitForRequest = true, + urlTimeout = 10_000, + } = options; + const loginButton = page.getByRole("button", { name: "Login" }); + + if (!waitForRequest) { + await Promise.all([ + expectedUrl + ? page.waitForURL(expectedUrl, { timeout: urlTimeout }) + : Promise.resolve(), + loginButton.click(), + ]); + return null; + } + + const loginRequestPromise = page.waitForRequest("**/api/auth/login"); + await loginButton.click(); + + const loginRequest = await loginRequestPromise; + expect(loginRequest.postDataJSON()).toMatchObject({ + email: e2eEmailCredentials.email, + password: e2eEmailCredentials.password, + isTestAccount: true, + }); + + if (expectedUrl) { + await expect(page).toHaveURL(expectedUrl, { timeout: urlTimeout }); + } + + return loginRequest; +} + +export async function signInWithEmailAndOpenChat(page: Page) { + await page.goto("/auth"); + await switchToEmailSignIn(page); + const historyResponsePromise = page + .waitForResponse("**/api/chat/history**", { timeout: 10_000 }) + .catch(() => null); + await submitEmailLogin(page, { expectedUrl: /\/chat$/ }); + await historyResponsePromise; + await dismissChatInterruptions(page); +} + +export async function completeVipPayment(page: Page) { + const createOrderRequestPromise = page.waitForRequest( + "**/api/payment/create-order", + ); + const orderStatusRequestPromise = page.waitForRequest( + "**/api/payment/order-status**", + ); + + await page.getByRole("button", { name: /Pay and Activ/i }).click(); + + const createOrderRequest = await createOrderRequestPromise; + expect(createOrderRequest.postDataJSON()).toMatchObject({ + planId: "vip_monthly", + payChannel: "stripe", + }); + await orderStatusRequestPromise; + + const paymentSuccessDialog = page.getByRole("alertdialog", { + name: "Payment successful", + }); + await expect(paymentSuccessDialog).toBeVisible(); + await paymentSuccessDialog.getByRole("button", { name: "OK" }).click(); +} + +export async function expectInsufficientCreditsDialog(page: Page) { + const insufficientCreditsDialog = page.getByRole("dialog", { + name: "Not enough credits", + }); + await expect(insufficientCreditsDialog).toBeVisible(); + await expect(insufficientCreditsDialog.getByText("Balance")).toBeVisible(); + await expect(insufficientCreditsDialog.getByText("Required")).toBeVisible(); + await expect( + insufficientCreditsDialog.getByText("Still needed"), + ).toBeVisible(); + + return insufficientCreditsDialog; +} diff --git a/e2e/specs/mock/chat-quantity-limit.spec.ts b/e2e/specs/mock/chat-quantity-limit.spec.ts index 7d8071a2..67e4cdf6 100644 --- a/e2e/specs/mock/chat-quantity-limit.spec.ts +++ b/e2e/specs/mock/chat-quantity-limit.spec.ts @@ -1,12 +1,22 @@ import { expect, test } from "@playwright/test"; import { mockCoreApis } from "../../fixtures/api-mocks"; +import { + clearBrowserState, + completeVipPayment, + dismissChatInterruptions, + enterChatFromSplash, + submitEmailLogin, + switchToEmailSignIn, +} from "../../fixtures/test-helpers"; const mockedLimitTriggerAt = 5; const maxManualTestMessages = 51; -test.beforeEach(async ({ context, page }) => { - await context.clearCookies(); +test.setTimeout(90_000); + +test.beforeEach(async ({ baseURL, context, page }) => { + await clearBrowserState(context, page, baseURL); await mockCoreApis(page, { chatLimitTriggerAt: mockedLimitTriggerAt, }); @@ -15,14 +25,13 @@ test.beforeEach(async ({ context, page }) => { test("guest hits the chat quantity limit and can continue to login and subscription", async ({ page, }) => { - await page.goto("/splash"); - - await page.getByRole("button", { name: "Skip" }).click(); - await expect(page).toHaveURL(/\/chat$/); + await enterChatFromSplash(page); await dismissChatInterruptions(page); const messageInput = page.getByRole("textbox", { name: "Message" }); - const quantityLimitBanner = page.getByTestId("chat-quota-exhausted-banner"); + const quantityLimitBanner = page + .getByRole("status") + .filter({ hasText: "Insufficient credits" }); let limitTriggeredAt: number | null = null; let completedSendCount = 0; @@ -72,51 +81,22 @@ test("guest hits the chat quantity limit and can continue to login and subscript await expect(quantityLimitBanner).toBeVisible(); await expect(messageInput).toHaveCount(0); - const unlockButton = page.getByRole("button", { - name: "Unlock your membership to continue", + const topUpButton = page.getByRole("button", { + name: "Top up credits to continue", }); - await expect(unlockButton).toBeVisible(); + await expect(topUpButton).toBeVisible(); - await unlockButton.click(); + await topUpButton.click(); await expect(page).toHaveURL(/\/auth\?redirect=/); expect(new URL(page.url()).searchParams.get("redirect")).toBe( "/subscription?type=vip&returnTo=chat", ); - 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"); - - await page.getByRole("button", { name: "Login" }).click(); + await switchToEmailSignIn(page); + await submitEmailLogin(page); await expect(page).toHaveURL(/\/subscription\?type=vip&returnTo=chat$/); - const createOrderRequestPromise = page.waitForRequest( - "**/api/payment/create-order", - ); - const orderStatusRequestPromise = page.waitForRequest( - "**/api/payment/order-status**", - ); - await page.getByRole("button", { name: /Pay and Activ/i }).click(); - - const createOrderRequest = await createOrderRequestPromise; - expect(createOrderRequest.postDataJSON()).toMatchObject({ - planId: "vip_monthly", - payChannel: "stripe", - }); - await orderStatusRequestPromise; - - const paymentSuccessDialog = page.getByRole("alertdialog", { - name: "Payment successful", - }); - await expect(paymentSuccessDialog).toBeVisible(); - await paymentSuccessDialog.getByRole("button", { name: "OK" }).click(); + await completeVipPayment(page); await expect(page).toHaveURL(/\/chat$/); await expect(page.getByRole("textbox", { name: "Message" })).toBeVisible(); @@ -132,20 +112,3 @@ async function isQuantityLimitVisible( return false; } } - -async function dismissChatInterruptions(page: import("@playwright/test").Page) { - const laterButton = page.getByRole("button", { name: "Later" }); - if (await laterButton.isVisible().catch(() => false)) { - await laterButton.click(); - } - - const cancelButton = page.getByRole("button", { name: "Cancel" }); - if (await cancelButton.isVisible().catch(() => false)) { - await cancelButton.click(); - } - - const okButton = page.getByRole("button", { name: "OK" }); - if (await okButton.isVisible().catch(() => false)) { - await okButton.click(); - } -} diff --git a/e2e/specs/mock/email-login-from-other-options.spec.ts b/e2e/specs/mock/email-login-from-other-options.spec.ts index bbba1275..d93741e6 100644 --- a/e2e/specs/mock/email-login-from-other-options.spec.ts +++ b/e2e/specs/mock/email-login-from-other-options.spec.ts @@ -1,48 +1,34 @@ import { expect, test } from "@playwright/test"; import { mockCoreApis } from "../../fixtures/api-mocks"; +import { + clearBrowserState, + enterChatFromSplash, + submitEmailLogin, + switchToEmailSignIn, +} from "../../fixtures/test-helpers"; -test.beforeEach(async ({ context, page }) => { - await context.clearCookies(); +test.beforeEach(async ({ baseURL, context, page }) => { + await clearBrowserState(context, page, baseURL); 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 enterChatFromSplash(page); 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", + await switchToEmailSignIn(page); + const loginRequest = await submitEmailLogin(page, { expectedUrl: /\/chat$/ }); + expect(loginRequest?.postDataJSON()).toMatchObject({ platform: "desktop", - 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/image-unlock-insufficient-credits.spec.ts b/e2e/specs/mock/image-unlock-insufficient-credits.spec.ts index 1b108a27..8d3a2db6 100644 --- a/e2e/specs/mock/image-unlock-insufficient-credits.spec.ts +++ b/e2e/specs/mock/image-unlock-insufficient-credits.spec.ts @@ -2,25 +2,15 @@ import { expect, test } from "@playwright/test"; import { mockCoreApis } from "../../fixtures/api-mocks"; import { paidImageMessageId } from "../../fixtures/test-data"; +import { + clearBrowserState, + dismissChatInterruptions, + expectInsufficientCreditsDialog, + signInWithEmailAndOpenChat, +} from "../../fixtures/test-helpers"; test.beforeEach(async ({ baseURL, context, page }) => { - await context.clearCookies(); - - const client = await context.newCDPSession(page); - const origins = new Set([ - new URL(baseURL ?? "http://127.0.0.1:3000").origin, - "http://127.0.0.1:3000", - "http://localhost:3000", - ]); - for (const origin of origins) { - await client - .send("Storage.clearDataForOrigin", { - origin, - storageTypes: "all", - }) - .catch(() => {}); - } - + await clearBrowserState(context, page, baseURL); await mockCoreApis(page, { paidImageInsufficientCreditsFlow: true, }); @@ -30,10 +20,11 @@ test("user sees insufficient credits when unlocking a paid image from fullscreen page, }) => { await signInWithEmailAndOpenChat(page); + await dismissChatInterruptions(page); const paidImageButton = page.getByRole("button", { name: "Open image in fullscreen", - }); + }).last(); await expect(paidImageButton).toBeVisible({ timeout: 10_000 }); await paidImageButton.click(); @@ -57,37 +48,10 @@ test("user sees insufficient credits when unlocking a paid image from fullscreen messageId: paidImageMessageId, }); - const insufficientCreditsDialog = page.getByRole("dialog", { - name: "Not enough credits", - }); - await expect(insufficientCreditsDialog).toBeVisible(); - await expect(insufficientCreditsDialog.getByText("Balance")).toBeVisible(); - await expect(insufficientCreditsDialog.getByText("Required")).toBeVisible(); - await expect(insufficientCreditsDialog.getByText("Still needed")).toBeVisible(); - + const insufficientCreditsDialog = await expectInsufficientCreditsDialog(page); await insufficientCreditsDialog .getByRole("button", { name: "Top up now" }) .click(); await expect(page).toHaveURL(/\/subscription\?type=vip&returnTo=chat$/); }); - -async function signInWithEmailAndOpenChat( - page: import("@playwright/test").Page, -) { - await page.goto("/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"); - - await page.getByRole("button", { name: "Login" }).click(); - await expect(page).toHaveURL(/\/chat$/); -} diff --git a/e2e/specs/mock/logout-from-sidebar.spec.ts b/e2e/specs/mock/logout-from-sidebar.spec.ts index bf49fa95..09c31503 100644 --- a/e2e/specs/mock/logout-from-sidebar.spec.ts +++ b/e2e/specs/mock/logout-from-sidebar.spec.ts @@ -1,30 +1,20 @@ import { expect, test } from "@playwright/test"; import { mockCoreApis } from "../../fixtures/api-mocks"; +import { + clearBrowserState, + signInWithEmailAndOpenChat, +} from "../../fixtures/test-helpers"; -test.beforeEach(async ({ context, page }) => { - await context.clearCookies(); +test.beforeEach(async ({ baseURL, context, page }) => { + await clearBrowserState(context, page, baseURL); await mockCoreApis(page); }); test("user can log out from the sidebar after email login", async ({ page, }) => { - await page.goto("/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"); - - await page.getByRole("button", { name: "Login" }).click(); - await expect(page).toHaveURL(/\/chat$/); + await signInWithEmailAndOpenChat(page); await expect(page.getByRole("button", { name: "Menu" })).toBeVisible(); await page.getByRole("button", { name: "Menu" }).click(); diff --git a/e2e/specs/mock/paid-image.spec.ts b/e2e/specs/mock/paid-image.spec.ts index 6d8eba1e..ffe03d66 100644 --- a/e2e/specs/mock/paid-image.spec.ts +++ b/e2e/specs/mock/paid-image.spec.ts @@ -2,23 +2,18 @@ import { expect, test } from "@playwright/test"; import { mockCoreApis } from "../../fixtures/api-mocks"; import { paidImageMessageId } from "../../fixtures/test-data"; +import { + clearBrowserState, + completeVipPayment, + dismissChatInterruptions, + enterChatFromSplash, + expectInsufficientCreditsDialog, + submitEmailLogin, + switchToEmailSignIn, +} from "../../fixtures/test-helpers"; test.beforeEach(async ({ baseURL, context, page }) => { - await context.clearCookies(); - const client = await context.newCDPSession(page); - const origins = new Set([ - new URL(baseURL ?? "http://127.0.0.1:3000").origin, - "http://127.0.0.1:3000", - "http://localhost:3000", - ]); - for (const origin of origins) { - await client - .send("Storage.clearDataForOrigin", { - origin, - storageTypes: "all", - }) - .catch(() => {}); - } + await clearBrowserState(context, page, baseURL); await mockCoreApis(page, { paidImageFlow: true, }); @@ -61,90 +56,29 @@ test("guest can unlock a paid image after email login and subscription payment", await expect(page).toHaveURL(/\/auth\?redirect=/); expect(new URL(page.url()).searchParams.get("redirect")).toBe( - "/subscription?type=vip&returnTo=chat", + `/chat/image/${paidImageMessageId}`, ); - 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 switchToEmailSignIn(page); + const unlockRequestPromise = page.waitForRequest( + "**/api/chat/unlock-private", ); - 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", - isTestAccount: true, + await submitEmailLogin(page, { + expectedUrl: new RegExp(`/chat/image/${paidImageMessageId}$`), }); + const unlockRequest = await unlockRequestPromise; + expect(unlockRequest.postDataJSON()).toMatchObject({ + messageId: paidImageMessageId, + }); + + const insufficientCreditsDialog = await expectInsufficientCreditsDialog(page); + await insufficientCreditsDialog + .getByRole("button", { name: "Top up now" }) + .click(); + await expect(page).toHaveURL(/\/subscription\?type=vip&returnTo=chat$/); - - const createOrderRequestPromise = page.waitForRequest( - "**/api/payment/create-order", - ); - const orderStatusRequestPromise = page.waitForRequest( - "**/api/payment/order-status**", - ); - - await page.getByRole("button", { name: /Pay and Activ/i }).click(); - - const createOrderRequest = await createOrderRequestPromise; - expect(createOrderRequest.postDataJSON()).toMatchObject({ - planId: "vip_monthly", - payChannel: "stripe", - }); - await orderStatusRequestPromise; - - const paymentSuccessDialog = page.getByRole("alertdialog", { - name: "Payment successful", - }); - await expect(paymentSuccessDialog).toBeVisible(); - await paymentSuccessDialog.getByRole("button", { name: "OK" }).click(); + await completeVipPayment(page); await expect(page).toHaveURL(new RegExp(`/chat/image/${paidImageMessageId}$`)); }); - -async function enterChatFromSplash(page: import("@playwright/test").Page) { - await page.goto("/splash"); - - const skipButton = page.getByRole("button", { name: "Skip" }); - await expect(skipButton).toBeVisible({ timeout: 10_000 }); - await expect(skipButton).toBeEnabled(); - - for (let attempt = 0; attempt < 3; attempt += 1) { - await skipButton.click(); - try { - await expect(page).toHaveURL(/\/chat$/, { timeout: 3_000 }); - return; - } catch { - await page.waitForTimeout(500); - } - } - - await expect(page).toHaveURL(/\/chat$/); -} - -async function dismissChatInterruptions(page: import("@playwright/test").Page) { - const laterButton = page.getByRole("button", { name: "Later" }); - if (await laterButton.isVisible().catch(() => false)) { - await laterButton.click(); - } - - const cancelButton = page.getByRole("button", { name: "Cancel" }); - if (await cancelButton.isVisible().catch(() => false)) { - await cancelButton.click(); - } - - const okButton = page.getByRole("button", { name: "OK" }); - if (await okButton.isVisible().catch(() => false)) { - await okButton.click(); - } -} diff --git a/e2e/specs/mock/voice-unlock-insufficient-credits.spec.ts b/e2e/specs/mock/voice-unlock-insufficient-credits.spec.ts index f0786dfb..bc693b4e 100644 --- a/e2e/specs/mock/voice-unlock-insufficient-credits.spec.ts +++ b/e2e/specs/mock/voice-unlock-insufficient-credits.spec.ts @@ -2,25 +2,14 @@ import { expect, test } from "@playwright/test"; import { mockCoreApis } from "../../fixtures/api-mocks"; import { paidVoiceMessageId } from "../../fixtures/test-data"; +import { + clearBrowserState, + expectInsufficientCreditsDialog, + signInWithEmailAndOpenChat, +} from "../../fixtures/test-helpers"; test.beforeEach(async ({ baseURL, context, page }) => { - await context.clearCookies(); - - const client = await context.newCDPSession(page); - const origins = new Set([ - new URL(baseURL ?? "http://127.0.0.1:3000").origin, - "http://127.0.0.1:3000", - "http://localhost:3000", - ]); - for (const origin of origins) { - await client - .send("Storage.clearDataForOrigin", { - origin, - storageTypes: "all", - }) - .catch(() => {}); - } - + await clearBrowserState(context, page, baseURL); await mockCoreApis(page, { paidVoiceInsufficientCreditsFlow: true, }); @@ -30,10 +19,13 @@ test("user sees insufficient credits when unlocking a paid voice message and can page, }) => { await signInWithEmailAndOpenChat(page); + await expect( + page.getByText("Unlock this voice message with credits."), + ).toBeVisible({ timeout: 10_000 }); const unlockButton = page.getByRole("button", { name: "Unlock voice message", - }); + }).last(); await expect(unlockButton).toBeVisible({ timeout: 10_000 }); const unlockRequestPromise = page.waitForRequest( @@ -47,37 +39,10 @@ test("user sees insufficient credits when unlocking a paid voice message and can messageId: paidVoiceMessageId, }); - const insufficientCreditsDialog = page.getByRole("dialog", { - name: "Not enough credits", - }); - await expect(insufficientCreditsDialog).toBeVisible(); - await expect(insufficientCreditsDialog.getByText("Balance")).toBeVisible(); - await expect(insufficientCreditsDialog.getByText("Required")).toBeVisible(); - await expect(insufficientCreditsDialog.getByText("Still needed")).toBeVisible(); - + const insufficientCreditsDialog = await expectInsufficientCreditsDialog(page); await insufficientCreditsDialog .getByRole("button", { name: "Top up now" }) .click(); await expect(page).toHaveURL(/\/subscription\?type=vip&returnTo=chat$/); }); - -async function signInWithEmailAndOpenChat( - page: import("@playwright/test").Page, -) { - await page.goto("/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"); - - await page.getByRole("button", { name: "Login" }).click(); - await expect(page).toHaveURL(/\/chat$/); -} diff --git a/e2e/specs/real/email-login-from-other-options.spec.ts b/e2e/specs/real/email-login-from-other-options.spec.ts index 61c9e37e..0bfda546 100644 --- a/e2e/specs/real/email-login-from-other-options.spec.ts +++ b/e2e/specs/real/email-login-from-other-options.spec.ts @@ -1,5 +1,11 @@ import { expect, test } from "@playwright/test"; +import { + enterChatFromSplash, + submitEmailLogin, + switchToEmailSignIn, +} from "../../fixtures/test-helpers"; + const preReleaseHost = "frontend-test.banlv-ai.com"; const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? ""; @@ -12,13 +18,11 @@ test.describe("pre-release email login smoke", () => { test("guest can switch to email login from other sign-in options and return to chat", async ({ page, }) => { - await page.goto("/splash", { waitUntil: "domcontentloaded" }); - - await expect(page.getByRole("button", { name: "Skip" })).toBeVisible(); - await Promise.all([ - page.waitForURL(/\/chat(?:\?.*)?$/, { timeout: 20_000 }), - page.getByRole("button", { name: "Skip" }).click(), - ]); + await enterChatFromSplash(page, { + expectedUrl: /\/chat(?:\?.*)?$/, + timeout: 20_000, + waitUntil: "domcontentloaded", + }); await expect( page.getByRole("button", { name: "Sign up to unlock more features" }), @@ -30,21 +34,12 @@ test.describe("pre-release email login smoke", () => { .click(), ]); - await page.getByRole("button", { name: "Other Sign In Options" }).click(); - await expect( - page.getByRole("dialog", { name: "Other sign-in options" }), - ).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"); - - await Promise.all([ - page.waitForURL(/\/chat(?:\?.*)?$/, { timeout: 20_000 }), - page.getByRole("button", { name: "Login" }).click(), - ]); + await switchToEmailSignIn(page); + await submitEmailLogin(page, { + expectedUrl: /\/chat(?:\?.*)?$/, + waitForRequest: false, + urlTimeout: 20_000, + }); await expect(page.getByRole("button", { name: "Menu" })).toBeVisible(); await expect(page.getByRole("textbox", { name: "Message" })).toBeVisible(); diff --git a/playwright.config.ts b/playwright.config.ts index 20fa3849..c6db36e9 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -18,6 +18,7 @@ export default defineConfig({ : [["list"], ["html", { open: "never" }]], use: { baseURL, + serviceWorkers: "block", trace: "on-first-retry", screenshot: "only-on-failure", video: "retain-on-failure",