From 9ef71046bac3590c843356135bbb15993604e3c2 Mon Sep 17 00:00:00 2001 From: Josh Ribakoff Date: Fri, 6 Feb 2026 01:15:19 -0800 Subject: [PATCH] chore: add Playwright E2E testing infrastructure (#673) * feat: add Playwright testing infrastructure - Add @playwright/test dependency - Add playwright.config.ts with Chromium setup - Add test scripts (test, test:ui, test:headed) - Add hello world test to verify setup works This is the foundation for adding comprehensive E2E tests. Tests run against the Vite dev server on port 1420. Co-Authored-By: Claude Opus 4.5 * ci: add E2E test workflow Runs Playwright tests on pull requests using Chromium. Uploads test artifacts on failure. Co-Authored-By: Claude Opus 4.5 * fix: rename e2e to playwright, fix CI test - Rename workflow from "E2E Tests" to "Playwright" - Rename scripts from test:e2e to test:playwright - Fix test to not require Tauri APIs (just check server responds) - Run prettier Co-Authored-By: Claude Opus 4.5 * fix --------- Co-authored-by: Claude Opus 4.5 Co-authored-by: CJ Pais --- .github/workflows/playwright.yml | 29 +++++++++++++++++++++++++++++ .gitignore | 5 +++++ bun.lock | 9 +++++++++ package.json | 5 ++++- playwright.config.ts | 26 ++++++++++++++++++++++++++ tests/app.spec.ts | 18 ++++++++++++++++++ 6 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/playwright.yml create mode 100644 playwright.config.ts create mode 100644 tests/app.spec.ts diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000..02714e2 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,29 @@ +name: "Playwright" +on: [pull_request] + +jobs: + playwright: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Install Playwright browsers + run: bunx playwright install chromium + + - name: Run Playwright tests + run: bun run test:playwright + + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v4 + with: + name: playwright-report + path: playwright-report/ + retention-days: 7 diff --git a/.gitignore b/.gitignore index b5d5818..fda8cd4 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,8 @@ dist-ssr /target/ recording_* .crush/ + +# Playwright +playwright-report/ +test-results/ +blob-report/ diff --git a/bun.lock b/bun.lock index acc1a0f..c0f3202 100644 --- a/bun.lock +++ b/bun.lock @@ -31,6 +31,7 @@ "zustand": "^5.0.8", }, "devDependencies": { + "@playwright/test": "^1.58.0", "@tauri-apps/cli": "^2.9.1", "@types/node": "^24.9.1", "@types/react": "^18.3.26", @@ -204,6 +205,8 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@playwright/test": ["@playwright/test@1.58.0", "", { "dependencies": { "playwright": "1.58.0" }, "bin": { "playwright": "cli.js" } }, "sha512-fWza+Lpbj6SkQKCrU6si4iu+fD2dD3gxNHFhUPxsfXBPhnv3rRSQVd0NtBUT9Z/RhF/boCBcuUaMUSTRTopjZg=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.5", "", { "os": "android", "cpu": "arm" }, "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ=="], @@ -608,6 +611,10 @@ "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "playwright": ["playwright@1.58.0", "", { "dependencies": { "playwright-core": "1.58.0" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-2SVA0sbPktiIY/MCOPX8e86ehA/e+tDNq+e5Y8qjKYti2Z/JG7xnronT/TXTIkKbYGWlCbuucZ6dziEgkoEjQQ=="], + + "playwright-core": ["playwright-core@1.58.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-aaoB1RWrdNi3//rOeKuMiS65UCcgOVljU46At6eFcOFPFHWtd2weHRRow6z/n+Lec0Lvu0k9ZPKJSjPugikirw=="], + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], @@ -730,6 +737,8 @@ "eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], } } diff --git a/package.json b/package.json index 1bef221..f039567 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,9 @@ "format": "prettier --write . && cd src-tauri && cargo fmt", "format:check": "prettier --check . && cd src-tauri && cargo fmt -- --check", "format:frontend": "prettier --write .", - "format:backend": "cd src-tauri && cargo fmt" + "format:backend": "cd src-tauri && cargo fmt", + "test:playwright": "playwright test", + "test:playwright:ui": "playwright test --ui" }, "dependencies": { "@tailwindcss/vite": "^4.1.16", @@ -42,6 +44,7 @@ "zustand": "^5.0.8" }, "devDependencies": { + "@playwright/test": "^1.58.0", "@tauri-apps/cli": "^2.9.1", "@types/node": "^24.9.1", "@types/react": "^18.3.26", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..8dc7e24 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,26 @@ +import { defineConfig, devices } from "@playwright/test"; + +export default defineConfig({ + testDir: "./tests", + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: "html", + use: { + baseURL: "http://localhost:1420", + trace: "on-first-retry", + }, + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], + webServer: { + command: "bunx vite dev", + url: "http://localhost:1420", + reuseExistingServer: !process.env.CI, + timeout: 30000, + }, +}); diff --git a/tests/app.spec.ts b/tests/app.spec.ts new file mode 100644 index 0000000..9cdfc39 --- /dev/null +++ b/tests/app.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from "@playwright/test"; + +test.describe("Handy App", () => { + test("dev server responds", async ({ page }) => { + // Just verify the dev server is running and responds + const response = await page.goto("/"); + expect(response?.status()).toBe(200); + }); + + test("page has html structure", async ({ page }) => { + await page.goto("/"); + + // Verify basic HTML structure exists + const html = await page.content(); + expect(html).toContain("