From 438a6e845f0065a841638ae95d187a2a931d2045 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 19 Jul 2026 21:08:21 +0000 Subject: [PATCH 1/2] Fix and wire up the Playwright-Electron setup-wizard e2e suite The e2e harness had two bugs that made it fail 2/8 whenever run, and no CI ever ran it - so it silently rotted: - fixtures.ts set EDISON_TEST_MODE=1, an env var nothing reads. The switch the app actually checks is EDISON_DRY_RUN (stdiod/detectord/installRefresh short-circuits). Without it the app tried to spawn native daemons under test. - setup-wizard.spec.ts asserted `text=Sign In` for the step-1 label (it's "Welcome") and expected the email input to be immediately visible (it's behind the "Continue with Email" provider-select click). Changes: - fixtures.ts: EDISON_TEST_MODE -> EDISON_DRY_RUN; launch Electron with --no-sandbox --disable-gpu so it runs headless under Xvfb. - setup-wizard.spec.ts: assert the "Welcome" step label; click "Continue with Email" before asserting the email form. - New .github/workflows/e2e.yml: build the app and run the suite under xvfb-run so the harness is a green gate instead of dead code. Verified locally: 8/8 passing under `xvfb-run -a npm run test:e2e`. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_015grmqjFQGz2cRbY7ghHMCX --- .github/workflows/e2e.yml | 48 +++++++++++++++++++++++++++++++++++++++ e2e/fixtures.ts | 9 +++++--- e2e/setup-wizard.spec.ts | 10 ++++---- 3 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/e2e.yml diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 0000000..82221f4 --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,48 @@ +name: e2e + +# Runs the Playwright-Electron setup-wizard suite against a real build of the app. +# Electron is a GUI process, so it needs an X server - we give it a virtual one +# via xvfb-run and launch with --no-sandbox/--disable-gpu (set in e2e/fixtures.ts). +# EDISON_DRY_RUN=1 (also in fixtures.ts) short-circuits the stdiod/detectord +# daemons so the app boots straight to the wizard with no native subprocesses. + +on: + workflow_dispatch: + pull_request: + branches: [main] + push: + branches: [main] + +concurrency: + group: e2e-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + setup-wizard: + name: Setup wizard (Playwright-Electron) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - run: npm ci + + # System libraries the Electron/Chromium runtime needs (libnss3, libgbm, + # libasound, ...). Playwright ships the exact set for its Chromium, which is + # a superset of what Electron needs, so reuse it instead of a manual apt list. + - name: Install headless runtime deps + run: npx playwright install-deps chromium + + - name: Build the app (main + preload + renderer) + run: npm run build + + # xvfb-run -a provides the virtual X server on an auto-selected display. + - name: Run e2e under Xvfb + run: xvfb-run -a npm run test:e2e diff --git a/e2e/fixtures.ts b/e2e/fixtures.ts index d320818..33c3051 100644 --- a/e2e/fixtures.ts +++ b/e2e/fixtures.ts @@ -6,7 +6,10 @@ import { join } from "path"; * the ElectronApplication and first window Page. * * Expects the app to be built first via `npm run build` (electron-vite build). - * In CI, set EDISON_TEST_MODE=1 to skip real auth and backend calls. + * EDISON_DRY_RUN=1 short-circuits stdiod/detectord subprocess spawning so the + * app boots to the wizard without native daemons (see src/main/stdiod/controller.ts, + * src/main/detectord/controller.ts). --no-sandbox/--disable-gpu let Electron run + * headless under Xvfb (e.g. CI, or as root in a sandbox). */ export const test = base.extend<{ electronApp: ElectronApplication; @@ -17,11 +20,11 @@ export const test = base.extend<{ const mainPath = join(__dirname, "../out/main/index.js"); const app = await electron.launch({ - args: [mainPath], + args: [mainPath, "--no-sandbox", "--disable-gpu"], env: { ...process.env, NODE_ENV: "test", - EDISON_TEST_MODE: "1", + EDISON_DRY_RUN: "1", }, }); diff --git a/e2e/setup-wizard.spec.ts b/e2e/setup-wizard.spec.ts index 1b517d2..3039367 100644 --- a/e2e/setup-wizard.spec.ts +++ b/e2e/setup-wizard.spec.ts @@ -5,17 +5,17 @@ test.describe("Setup Wizard", () => { // The wizard should show the welcome step with Edison branding await expect(firstWindow.locator("text=Edison Watch")).toBeVisible({ timeout: 10000 }); - // Step indicator should show step 1 as active - await expect(firstWindow.locator("text=Sign In")).toBeVisible(); - - // Sign in form should be present - await expect(firstWindow.locator('input[type="email"], input[placeholder*="email" i]')).toBeVisible(); + // Step indicator should show step 1 ("Welcome") as active (see StepIndicator.tsx) + await expect(firstWindow.locator("text=Welcome")).toBeVisible(); }); test("Step 1 shows sign-in options", async ({ firstWindow }) => { // Wait for the welcome step to render await expect(firstWindow.locator("text=Edison Watch")).toBeVisible({ timeout: 10000 }); + // The email form is behind the provider-select view - open it first (WelcomeStep.tsx) + await firstWindow.getByText("Continue with Email", { exact: false }).click(); + // Should show email input const emailInput = firstWindow.locator('input[type="email"], input[placeholder*="email" i]'); await expect(emailInput).toBeVisible(); From d52e0f6c96099483cecfc1a98ebeabbbc45efe2c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 19 Jul 2026 21:14:35 +0000 Subject: [PATCH 2/2] e2e: upload Playwright report + traces on CI failure Addresses cubic review on #39: a failed e2e run only emitted the raw log, with no trace/screenshot/report to inspect. - playwright.config.ts: add the html reporter and retain trace + screenshot on failure (the previous "on-first-retry" trace never fired because retries is 0). - e2e.yml: upload playwright-report/ and test-results/ as an artifact when the job fails. Re-ran locally: still 8/8 under xvfb-run. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_015grmqjFQGz2cRbY7ghHMCX --- .github/workflows/e2e.yml | 14 ++++++++++++++ playwright.config.ts | 7 ++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 82221f4..52c8ca8 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -46,3 +46,17 @@ jobs: # xvfb-run -a provides the virtual X server on an auto-selected display. - name: Run e2e under Xvfb run: xvfb-run -a npm run test:e2e + + # On failure, upload the HTML report + per-test traces/screenshots so the + # failure is debuggable beyond the raw log (playwright.config.ts retains + # trace + screenshot on failure). + - name: Upload Playwright artifacts + if: ${{ failure() }} + uses: actions/upload-artifact@v4 + with: + name: playwright-report + path: | + playwright-report/ + test-results/ + retention-days: 7 + if-no-files-found: ignore diff --git a/playwright.config.ts b/playwright.config.ts index 4a0ffe1..a5a2caa 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -5,7 +5,12 @@ export default defineConfig({ timeout: 30000, retries: 0, workers: 1, // Electron tests must run serially + // HTML report + list output; the report is uploaded as a CI artifact on failure. + reporter: [["list"], ["html", { open: "never" }]], use: { - trace: "on-first-retry", + // retries: 0, so "on-first-retry" would never capture anything - retain on + // failure instead so CI has a trace + screenshot to inspect (see e2e.yml). + trace: "retain-on-failure", + screenshot: "only-on-failure", }, });