From f3a2a7100f940a14cea9235d80e1f692d1f18540 Mon Sep 17 00:00:00 2001 From: johba Date: Mon, 23 Mar 2026 10:55:01 +0000 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20feat:=20E2E=20quality=20gate=20?= =?UTF-8?q?=E2=80=94=20mobile=20viewports=20+=20cross-browser=20matrix=20(?= =?UTF-8?q?#1099)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Playwright projects for Chromium, Firefox, WebKit, iPhone 14, and Pixel 7 viewports. Chromium runs all specs (01-07); other projects run read-only specs (03, 06, 07) after Chromium finishes, using project dependencies to ensure chain state exists. Coverage audit: - Tests 01/02 already cover /app/get-krk, /app/cheats as part of flows - Test 03 verifies GraphQL endpoints - Test 06 covers wallet + position dashboards - New test 07 adds landing page and docs smoke coverage Changes: - playwright.config.ts: 5 projects (3 desktop browsers + 2 mobile) - wallet-provider.ts: accept optional viewport/screen for mobile contexts - 03, 06 specs: pass project viewport to wallet context - 07-landing-pages.spec.ts: new spec for landing homepage + docs - e2e.yml: timeout 600→900s for cross-browser matrix, updated comments Co-Authored-By: Claude Opus 4.6 (1M context) --- .woodpecker/e2e.yml | 8 ++- playwright.config.ts | 69 ++++++++++++++++++-- tests/e2e/03-verify-graphql-url.spec.ts | 3 + tests/e2e/06-dashboard-pages.spec.ts | 15 +++++ tests/e2e/07-landing-pages.spec.ts | 83 +++++++++++++++++++++++++ tests/setup/wallet-provider.ts | 6 +- 6 files changed, 173 insertions(+), 11 deletions(-) create mode 100644 tests/e2e/07-landing-pages.spec.ts diff --git a/.woodpecker/e2e.yml b/.woodpecker/e2e.yml index 9e1489f..3af6edd 100644 --- a/.woodpecker/e2e.yml +++ b/.woodpecker/e2e.yml @@ -398,12 +398,14 @@ steps: bash scripts/wait-for-service.sh http://caddy:8081/app/ 420 caddy echo "=== Stack is healthy ===" - # Step 3: Run E2E tests + # Step 3: Run E2E tests — cross-browser matrix + # Chromium runs all specs (01-07), then Firefox/WebKit/mobile run read-only specs (03,06,07). + # The matrix is defined in playwright.config.ts via `projects`. - name: run-e2e-tests image: mcr.microsoft.com/playwright:v1.55.1-jammy depends_on: - wait-for-stack - timeout: 600 + timeout: 900 environment: STACK_BASE_URL: http://caddy:8081 STACK_RPC_URL: http://caddy:8081/api/rpc @@ -427,7 +429,7 @@ steps: npm config set audit false npm ci --no-audit --no-fund - echo "=== Running E2E tests (workers=1 to limit memory) ===" + echo "=== Running E2E tests — cross-browser matrix (workers=1 to limit memory) ===" npx playwright test --reporter=list --workers=1 # Step 4: Collect artifacts diff --git a/playwright.config.ts b/playwright.config.ts index 8f3a3b1..c283fb5 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,10 +1,22 @@ -import { defineConfig } from '@playwright/test'; +import { defineConfig, devices } from '@playwright/test'; + +/** + * Cross-browser + mobile viewport matrix for E2E quality gate. + * + * - `chromium` runs ALL numbered specs (01-07) including transactional tests + * that mutate on-chain state. + * - Other projects depend on `chromium` finishing first (chain state must exist) + * and only run read-only / UI-rendering specs (03, 06, 07). + */ + +// Read-only specs safe to run in every browser/viewport after chain state exists. +const CROSS_BROWSER_SPECS = '0[367]-*.spec.ts'; export default defineConfig({ testDir: './tests/e2e', testMatch: process.env.CI ? '[0-9]*.spec.ts' : '**/*.spec.ts', fullyParallel: false, - timeout: 10 * 60 * 1000, // Increased from 5 to 10 minutes for persona journeys with multiple buys + timeout: 10 * 60 * 1000, expect: { timeout: 30_000, }, @@ -12,13 +24,58 @@ export default defineConfig({ workers: process.env.CI ? 1 : undefined, use: { headless: true, - viewport: { width: 1280, height: 720 }, - // Set screen dimensions to match viewport - required for proper isMobile detection - // The webapp uses screen.width (not window.innerWidth) to detect mobile - screen: { width: 1280, height: 720 }, actionTimeout: 0, launchOptions: { args: ['--disable-dev-shm-usage', '--no-sandbox'], }, }, + projects: [ + /* ── Desktop browsers ─────────────────────────────────── */ + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + viewport: { width: 1280, height: 720 }, + screen: { width: 1280, height: 720 }, + }, + }, + { + name: 'firefox', + use: { + ...devices['Desktop Firefox'], + viewport: { width: 1280, height: 720 }, + screen: { width: 1280, height: 720 }, + }, + dependencies: ['chromium'], + testMatch: CROSS_BROWSER_SPECS, + }, + { + name: 'webkit', + use: { + ...devices['Desktop Safari'], + viewport: { width: 1280, height: 720 }, + screen: { width: 1280, height: 720 }, + }, + dependencies: ['chromium'], + testMatch: CROSS_BROWSER_SPECS, + }, + + /* ── Mobile viewports ─────────────────────────────────── */ + { + name: 'iphone', + use: { + ...devices['iPhone 14'], + }, + dependencies: ['chromium'], + testMatch: CROSS_BROWSER_SPECS, + }, + { + name: 'android', + use: { + ...devices['Pixel 7'], + }, + dependencies: ['chromium'], + testMatch: CROSS_BROWSER_SPECS, + }, + ], }); diff --git a/tests/e2e/03-verify-graphql-url.spec.ts b/tests/e2e/03-verify-graphql-url.spec.ts index 90ab18a..73e2859 100644 --- a/tests/e2e/03-verify-graphql-url.spec.ts +++ b/tests/e2e/03-verify-graphql-url.spec.ts @@ -16,9 +16,12 @@ test.describe('GraphQL URL Verification', () => { test('should load staking page without errors and use correct GraphQL endpoint', async ({ browser }) => { console.log('[TEST] Creating wallet context...'); + const projectUse = test.info().project.use; const context = await createWalletContext(browser, { privateKey: ACCOUNT_PRIVATE_KEY, rpcUrl: STACK_RPC_URL, + viewport: projectUse.viewport ?? undefined, + screen: projectUse.screen ?? undefined, }); const page = await context.newPage(); diff --git a/tests/e2e/06-dashboard-pages.spec.ts b/tests/e2e/06-dashboard-pages.spec.ts index 7f5033d..5297f46 100644 --- a/tests/e2e/06-dashboard-pages.spec.ts +++ b/tests/e2e/06-dashboard-pages.spec.ts @@ -3,6 +3,15 @@ import { Wallet } from 'ethers'; import { createWalletContext } from '../setup/wallet-provider'; import { getStackConfig, validateStackHealthy } from '../setup/stack'; +/** Read viewport/screen from the active project so wallet contexts honour the cross-browser matrix. */ +function projectViewport() { + const u = test.info().project.use as Record; + return { + viewport: (u.viewport as { width: number; height: number } | undefined) ?? undefined, + screen: (u.screen as { width: number; height: number } | undefined) ?? undefined, + }; +} + const ACCOUNT_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'; const ACCOUNT_ADDRESS = new Wallet(ACCOUNT_PRIVATE_KEY).address; @@ -73,6 +82,7 @@ test.describe('Dashboard Pages', () => { const context = await createWalletContext(browser, { privateKey: ACCOUNT_PRIVATE_KEY, rpcUrl: STACK_RPC_URL, + ...projectViewport(), }); const page = await context.newPage(); const errors: string[] = []; @@ -141,6 +151,7 @@ test.describe('Dashboard Pages', () => { const context = await createWalletContext(browser, { privateKey: ACCOUNT_PRIVATE_KEY, rpcUrl: STACK_RPC_URL, + ...projectViewport(), }); const page = await context.newPage(); @@ -167,6 +178,7 @@ test.describe('Dashboard Pages', () => { const context = await createWalletContext(browser, { privateKey: ACCOUNT_PRIVATE_KEY, rpcUrl: STACK_RPC_URL, + ...projectViewport(), }); const page = await context.newPage(); const errors: string[] = []; @@ -220,6 +232,7 @@ test.describe('Dashboard Pages', () => { const context = await createWalletContext(browser, { privateKey: ACCOUNT_PRIVATE_KEY, rpcUrl: STACK_RPC_URL, + ...projectViewport(), }); const page = await context.newPage(); const errors: string[] = []; @@ -294,6 +307,7 @@ test.describe('Dashboard Pages', () => { const context = await createWalletContext(browser, { privateKey: ACCOUNT_PRIVATE_KEY, rpcUrl: STACK_RPC_URL, + ...projectViewport(), }); const page = await context.newPage(); const errors: string[] = []; @@ -342,6 +356,7 @@ test.describe('Dashboard Pages', () => { const context = await createWalletContext(browser, { privateKey: ACCOUNT_PRIVATE_KEY, rpcUrl: STACK_RPC_URL, + ...projectViewport(), }); const page = await context.newPage(); diff --git a/tests/e2e/07-landing-pages.spec.ts b/tests/e2e/07-landing-pages.spec.ts new file mode 100644 index 0000000..4764526 --- /dev/null +++ b/tests/e2e/07-landing-pages.spec.ts @@ -0,0 +1,83 @@ +import { test, expect } from '@playwright/test'; +import { getStackConfig, validateStackHealthy } from '../setup/stack'; + +const STACK_CONFIG = getStackConfig(); +const STACK_BASE_URL = process.env.STACK_BASE_URL ?? 'http://localhost:8081'; + +test.describe('Landing Pages', () => { + test.beforeAll(async () => { + await validateStackHealthy(STACK_CONFIG); + }); + + test('landing homepage loads without errors', async ({ page }) => { + const errors: string[] = []; + page.on('console', msg => { + if (msg.type() === 'error') errors.push(msg.text()); + }); + + await page.goto(`${STACK_BASE_URL}/`, { waitUntil: 'domcontentloaded' }); + await page.waitForLoadState('networkidle'); + + // Page should contain recognisable KRAIKEN branding + const body = await page.textContent('body'); + expect(body).toBeTruthy(); + // Landing page always has a call-to-action button + const cta = page.getByRole('link', { name: /app|stake|launch|get started/i }).first(); + await expect(cta).toBeVisible({ timeout: 15_000 }); + + await page.screenshot({ path: 'test-results/landing-homepage.png', fullPage: true }); + + const realErrors = errors.filter( + e => !e.includes('favicon') && !e.includes('DevTools'), + ); + expect(realErrors).toHaveLength(0); + + console.log('[TEST] ✅ Landing homepage renders correctly'); + }); + + test('docs introduction page loads', async ({ page }) => { + const errors: string[] = []; + page.on('console', msg => { + if (msg.type() === 'error') errors.push(msg.text()); + }); + + await page.goto(`${STACK_BASE_URL}/docs/introduction`, { waitUntil: 'domcontentloaded' }); + await page.waitForLoadState('networkidle'); + + const body = await page.textContent('body'); + expect(body).toBeTruthy(); + + // Docs page should have heading or content indicating documentation + const heading = page.locator('h1, h2').first(); + await expect(heading).toBeVisible({ timeout: 15_000 }); + + const realErrors = errors.filter( + e => !e.includes('favicon') && !e.includes('DevTools'), + ); + expect(realErrors).toHaveLength(0); + + console.log('[TEST] ✅ Docs introduction page renders correctly'); + }); + + test('docs navigation works across pages', async ({ page }) => { + await page.goto(`${STACK_BASE_URL}/docs/introduction`, { waitUntil: 'domcontentloaded' }); + await page.waitForLoadState('networkidle'); + + // Find a docs nav link to another page + const navLink = page.locator('a[href*="/docs/"]').filter({ hasNotText: /introduction/i }).first(); + if (await navLink.isVisible({ timeout: 5_000 })) { + const href = await navLink.getAttribute('href'); + console.log(`[TEST] Clicking docs nav link: ${href}`); + await navLink.click(); + // eslint-disable-next-line no-restricted-syntax -- waitForTimeout: SPA navigation has no reliable event source for Vue Router view mount completion across browsers. See AGENTS.md #Engineering Principles. + await page.waitForTimeout(2_000); + + // Should navigate without crashing + const body = await page.textContent('body'); + expect(body).toBeTruthy(); + console.log('[TEST] ✅ Docs navigation works'); + } else { + console.log('[TEST] ⚠️ No docs nav links found — skipping navigation test'); + } + }); +}); diff --git a/tests/setup/wallet-provider.ts b/tests/setup/wallet-provider.ts index cc05116..d359515 100644 --- a/tests/setup/wallet-provider.ts +++ b/tests/setup/wallet-provider.ts @@ -8,6 +8,8 @@ export interface WalletProviderOptions { chainName?: string; walletName?: string; walletUuid?: string; + viewport?: { width: number; height: number }; + screen?: { width: number; height: number }; } const DEFAULT_CHAIN_ID = 31337; @@ -30,8 +32,8 @@ export async function createWalletContext( const chainIdHex = `0x${chainId.toString(16)}`; const context = await browser.newContext({ - viewport: { width: 1280, height: 720 }, - screen: { width: 1280, height: 720 }, + viewport: options.viewport ?? { width: 1280, height: 720 }, + screen: options.screen ?? { width: 1280, height: 720 }, }); await context.addInitScript(() => { From c66b55369255f64956234afb067e6d1da58e67e0 Mon Sep 17 00:00:00 2001 From: johba Date: Mon, 23 Mar 2026 11:18:53 +0000 Subject: [PATCH 2/5] fix: move Chromium-specific launch args out of root use block, fix CTA text match - launchOptions with --disable-dev-shm-usage and --no-sandbox are Chromium-specific; passing them to Firefox/WebKit causes errors. Move to chromium and android project use blocks only. - Fix landing page CTA assertion to match actual button text ("Get $KRK", "Get Your Edge") instead of generic patterns. Co-Authored-By: Claude Opus 4.6 (1M context) --- playwright.config.ts | 8 +++++--- tests/e2e/07-landing-pages.spec.ts | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index c283fb5..5d0da08 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -12,6 +12,9 @@ import { defineConfig, devices } from '@playwright/test'; // Read-only specs safe to run in every browser/viewport after chain state exists. const CROSS_BROWSER_SPECS = '0[367]-*.spec.ts'; +// Chromium-specific launch flags (not valid for Firefox/WebKit). +const CHROMIUM_ARGS = ['--disable-dev-shm-usage', '--no-sandbox']; + export default defineConfig({ testDir: './tests/e2e', testMatch: process.env.CI ? '[0-9]*.spec.ts' : '**/*.spec.ts', @@ -25,9 +28,6 @@ export default defineConfig({ use: { headless: true, actionTimeout: 0, - launchOptions: { - args: ['--disable-dev-shm-usage', '--no-sandbox'], - }, }, projects: [ /* ── Desktop browsers ─────────────────────────────────── */ @@ -37,6 +37,7 @@ export default defineConfig({ ...devices['Desktop Chrome'], viewport: { width: 1280, height: 720 }, screen: { width: 1280, height: 720 }, + launchOptions: { args: CHROMIUM_ARGS }, }, }, { @@ -73,6 +74,7 @@ export default defineConfig({ name: 'android', use: { ...devices['Pixel 7'], + launchOptions: { args: CHROMIUM_ARGS }, }, dependencies: ['chromium'], testMatch: CROSS_BROWSER_SPECS, diff --git a/tests/e2e/07-landing-pages.spec.ts b/tests/e2e/07-landing-pages.spec.ts index 4764526..2e3e5d8 100644 --- a/tests/e2e/07-landing-pages.spec.ts +++ b/tests/e2e/07-landing-pages.spec.ts @@ -21,8 +21,8 @@ test.describe('Landing Pages', () => { // Page should contain recognisable KRAIKEN branding const body = await page.textContent('body'); expect(body).toBeTruthy(); - // Landing page always has a call-to-action button - const cta = page.getByRole('link', { name: /app|stake|launch|get started/i }).first(); + // Landing page always has a call-to-action link ("Get $KRK", "Get Your Edge", etc.) + const cta = page.getByRole('link', { name: /get.*krk|get.*edge|stake|launch/i }).first(); await expect(cta).toBeVisible({ timeout: 15_000 }); await page.screenshot({ path: 'test-results/landing-homepage.png', fullPage: true }); From 932c527b97966561dcfb945624cb937547077256 Mon Sep 17 00:00:00 2001 From: johba Date: Mon, 23 Mar 2026 11:41:33 +0000 Subject: [PATCH 3/5] fix: increase CI step timeout to 1800s, trim cross-browser test set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Step timeout 900→1800s to accommodate 34 tests across 5 projects - Remove test 06 (dashboard pages) from cross-browser specs — each subtest creates a wallet context, making 4× browser runs too slow - Cross-browser now runs 03 (GraphQL verification) + 07 (landing pages) Co-Authored-By: Claude Opus 4.6 (1M context) --- .woodpecker/e2e.yml | 2 +- playwright.config.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.woodpecker/e2e.yml b/.woodpecker/e2e.yml index 3af6edd..786b582 100644 --- a/.woodpecker/e2e.yml +++ b/.woodpecker/e2e.yml @@ -405,7 +405,7 @@ steps: image: mcr.microsoft.com/playwright:v1.55.1-jammy depends_on: - wait-for-stack - timeout: 900 + timeout: 1800 environment: STACK_BASE_URL: http://caddy:8081 STACK_RPC_URL: http://caddy:8081/api/rpc diff --git a/playwright.config.ts b/playwright.config.ts index 5d0da08..c594145 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -9,8 +9,10 @@ import { defineConfig, devices } from '@playwright/test'; * and only run read-only / UI-rendering specs (03, 06, 07). */ -// Read-only specs safe to run in every browser/viewport after chain state exists. -const CROSS_BROWSER_SPECS = '0[367]-*.spec.ts'; +// Lightweight read-only specs for cross-browser/viewport validation. +// Test 06 (dashboard pages) is excluded because each subtest creates a wallet +// context, making it too slow for 4× additional browser runs in CI. +const CROSS_BROWSER_SPECS = '0[37]-*.spec.ts'; // Chromium-specific launch flags (not valid for Firefox/WebKit). const CHROMIUM_ARGS = ['--disable-dev-shm-usage', '--no-sandbox']; From a87eb7ed56c3aa6bc6bc9917f038ec7074692b77 Mon Sep 17 00:00:00 2001 From: johba Date: Mon, 23 Mar 2026 12:03:25 +0000 Subject: [PATCH 4/5] fix: use button role for landing CTA, revert risky test changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: landing page CTA uses (renders