From e523a9b3f1ce7eaf884e54e7b5c2294711314c61 Mon Sep 17 00:00:00 2001 From: Genghis-codes Date: Sun, 18 Oct 2026 15:48:35 +0100 Subject: [PATCH] test(e2e): fix flaky and ambiguous Playwright locators across the suite - analytics.spec.ts: freeze the clock with page.clock so which mock streams fall inside the 30-day/all-time window no longer depends on the day the suite runs. - visual.spec.ts: replace ambiguous text= locators (which matched both the submit button and the tx-preview dialog, or both the trigger button and the dialog title) with getByRole('heading'/'button', ...) so the filled-form and cancel-dialog screenshot tests get past the locator step without strict-mode violations. - visual.spec.ts / contract-integration.spec.ts: replace locator('text=Dashboard'), which matched 2-4 elements (navbar link, mobile nav link, page heading, back-to-dashboard link), with a role-based heading locator on the dashboard page and a main-landmark scoped link locator on the stream detail page. - contract-integration.spec.ts: connect the mock xBull wallet (same helper pattern as accessibility.spec.ts) before visiting /app/create in the create-form tests, since the form is wrapped in RequireWallet and renders only a connect prompt otherwise. Closes #908 Closes #907 Closes #905 Closes #903 Co-Authored-By: Claude Sonnet 5 --- e2e/analytics.spec.ts | 9 +++++++++ e2e/contract-integration.spec.ts | 22 ++++++++++++++++++++-- e2e/visual.spec.ts | 16 +++++++++++----- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/e2e/analytics.spec.ts b/e2e/analytics.spec.ts index e25a1f6..69723a1 100644 --- a/e2e/analytics.spec.ts +++ b/e2e/analytics.spec.ts @@ -7,7 +7,16 @@ import { test, expect, type Page } from '@playwright/test' // the same convention e2e/visual.spec.ts uses for dashboard screenshots. const DEMO_ADDRESS = 'GBQ2X7KFY3R4VZ6N5LJ7WQH3M2PD8C9SAUTV4EXAMPLE0WALLET00ADDR' +// The mock streams in lib/mock-data.ts are seeded with startTime offsets +// relative to Date.now() at module-evaluation time (e.g. "now - 90 days"). +// Without a frozen clock, which streams fall inside a given range depends on +// exactly when the test happens to run. Freeze the clock with Playwright's +// `page.clock` so the seeded offsets — and therefore the expected counts +// below — are deterministic regardless of the current date. +const FIXED_NOW = new Date('2025-06-15T12:00:00.000Z') + async function withWallet(page: Page, address: string = DEMO_ADDRESS) { + await page.clock.install({ time: FIXED_NOW }) await page.addInitScript((walletAddress) => { localStorage.setItem('walletId', 'xbull') ;(window as any).xBullSDK = { diff --git a/e2e/contract-integration.spec.ts b/e2e/contract-integration.spec.ts index abd6a28..8e63a74 100644 --- a/e2e/contract-integration.spec.ts +++ b/e2e/contract-integration.spec.ts @@ -1,4 +1,19 @@ -import { test, expect } from '@playwright/test' +import { test, expect, type Page } from '@playwright/test' + +// The create form is wrapped in `RequireWallet`, so without a connected +// wallet it only renders a connect prompt instead of the real form fields. +// Mirrors the helper used in e2e/accessibility.spec.ts. +async function withWallet(page: Page) { + await page.addInitScript(() => { + localStorage.setItem('walletId', 'xbull') + ;(window as any).xBullSDK = { + connect: async () => ({ + publicKey: 'GA4M5Y74MXREPXU6LGRR7WOC6VLXJBLKJHZBJGDBKOFIP7GBM3MHF3G5', + }), + signXDR: async () => 'AAAAAgAAAAA...dummy-signature...', + } + }) +} test.describe('Contract Integration Tests', () => { test.describe('Wallet Connection & Dashboard', () => { @@ -34,7 +49,7 @@ test.describe('Contract Integration Tests', () => { test('should display back button on stream detail page', async ({ page }) => { await page.goto('/app/stream/test-id') - const backBtn = page.locator('text=Dashboard') + const backBtn = page.getByRole('main').getByRole('link', { name: 'Dashboard' }) await expect(backBtn).toBeVisible() }) @@ -48,6 +63,7 @@ test.describe('Contract Integration Tests', () => { test.describe('Create Stream Form Validation', () => { test('should show create form with recipient field', async ({ page }) => { + await withWallet(page) await page.goto('/app/create') // Look for input fields typical of a stream creation form const inputs = page.locator('input[type="text"], input[type="number"]') @@ -69,6 +85,7 @@ test.describe('Contract Integration Tests', () => { }) test('should handle token selection', async ({ page }) => { + await withWallet(page) await page.goto('/app/create') const tokenInputs = page.locator('input, button').filter({ hasText: /XLM|USDC|token/ }) // At least one token selector should be visible @@ -209,6 +226,7 @@ test.describe('Contract Integration Tests', () => { }) test('should have accessible form labels', async ({ page }) => { + await withWallet(page) await page.goto('/app/create') const labels = page.locator('label') const count = await labels.count() diff --git a/e2e/visual.spec.ts b/e2e/visual.spec.ts index cdc27ce..10fcdb8 100644 --- a/e2e/visual.spec.ts +++ b/e2e/visual.spec.ts @@ -85,7 +85,9 @@ test.describe("Dashboard visual states", () => { test("dashboard connected with streams", async ({ page }) => { await preparePage(page, { theme: "light", connect: true }); await page.goto("/app"); - await expect(page.locator("text=Dashboard")).toBeVisible(); + await expect( + page.getByRole("heading", { name: "Dashboard", level: 1 }), + ).toBeVisible(); await expect(page.locator("text=New stream")).toBeVisible(); await expect(page).toHaveScreenshot("dashboard-with-streams.png", { fullPage: true, @@ -95,7 +97,9 @@ test.describe("Dashboard visual states", () => { test("dashboard tabs: receiving and sent", async ({ page }) => { await preparePage(page, { theme: "light", connect: true }); await page.goto("/app"); - await expect(page.locator("text=Dashboard")).toBeVisible(); + await expect( + page.getByRole("heading", { name: "Dashboard", level: 1 }), + ).toBeVisible(); await page.locator('button:has-text("Receiving")').first().click(); await expect(page.locator("text=Receiving")).toBeVisible(); @@ -131,8 +135,8 @@ test.describe("Create stream form", () => { await page.click('button:has-text("Create stream")'); await expect( page - .locator("text=Review transaction") - .or(page.locator("text=Create stream")), + .getByRole("heading", { name: "Transaction Preview" }) + .or(page.getByRole("button", { name: "Create stream" })), ).toBeVisible(); await expect(page).toHaveScreenshot("create-stream-filled.png", { fullPage: true, @@ -252,7 +256,9 @@ test.describe("Wallet and modal dialogs", () => { await preparePage(page, { theme: "light", connect: true }); await page.goto("/app/stream/1"); await page.locator('button:has-text("Cancel")').first().click(); - await expect(page.locator("text=Cancel stream")).toBeVisible(); + await expect( + page.getByRole("heading", { name: "Cancel stream" }), + ).toBeVisible(); await expect(page).toHaveScreenshot("cancel-dialog.png", { fullPage: true, });