From 1e788b2e0c7a8ee23a9ef55eb4b3836609f15cf3 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 13 Jan 2026 00:46:13 +0000 Subject: [PATCH] test: add playwright smoke suite --- .gitignore | 3 ++ bun.lock | 10 +++++++ docs/manual-testing.md | 14 ++++++++++ e2e/menu-smoke.pw.test.ts | 49 +++++++++++++++++++++++++++++++++ package.json | 3 ++ playwright.config.ts | 33 ++++++++++++++++++++++ scripts/run-playwright-local.sh | 14 ++++++++++ 7 files changed, 126 insertions(+) create mode 100644 e2e/menu-smoke.pw.test.ts create mode 100644 playwright.config.ts create mode 100755 scripts/run-playwright-local.sh diff --git a/.gitignore b/.gitignore index 9422cb42..3798bd7b 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ todos.json .vscode .env*.local coverage +playwright-report +test-results +.playwright diff --git a/bun.lock b/bun.lock index 83572d5b..4067a2ca 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "clawdhub", @@ -40,6 +41,7 @@ }, "devDependencies": { "@biomejs/biome": "^2.3.11", + "@playwright/test": "^1.57.0", "@tanstack/devtools-vite": "^0.4.0", "@testing-library/dom": "^10.4.1", "@testing-library/react": "^16.3.1", @@ -384,6 +386,8 @@ "@panva/hkdf": ["@panva/hkdf@1.2.1", "", {}, "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw=="], + "@playwright/test": ["@playwright/test@1.57.0", "", { "dependencies": { "playwright": "1.57.0" }, "bin": { "playwright": "cli.js" } }, "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA=="], + "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="], @@ -1110,6 +1114,10 @@ "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "playwright": ["playwright@1.57.0", "", { "dependencies": { "playwright-core": "1.57.0" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw=="], + + "playwright-core": ["playwright-core@1.57.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ=="], + "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=="], "preact": ["preact@10.24.3", "", {}, "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA=="], @@ -1374,6 +1382,8 @@ "parse5-parser-stream/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], + "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], + "readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "recast/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], diff --git a/docs/manual-testing.md b/docs/manual-testing.md index 3262e43a..29e96d58 100644 --- a/docs/manual-testing.md +++ b/docs/manual-testing.md @@ -48,3 +48,17 @@ read_when: ## Sync - `bun clawdhub sync --dry-run --all` + +## Playwright (menu smoke) + +Run against prod: + +``` +PLAYWRIGHT_BASE_URL=https://clawdhub.com bun run test:pw +``` + +Run against a local preview server: + +``` +bun run test:e2e:local +``` diff --git a/e2e/menu-smoke.pw.test.ts b/e2e/menu-smoke.pw.test.ts new file mode 100644 index 00000000..42ab004f --- /dev/null +++ b/e2e/menu-smoke.pw.test.ts @@ -0,0 +1,49 @@ +import { expect, test } from '@playwright/test' + +const navLabels = ['Skills', 'Upload', 'Import', 'Search'] + +test('skills loads without error', async ({ page }) => { + await page.goto('/skills', { waitUntil: 'domcontentloaded' }) + await expect(page.locator('text=Something went wrong!')).toHaveCount(0) + await expect(page.locator('h1', { hasText: 'Skills' })).toBeVisible() +}) + +test('souls loads without error', async ({ page }) => { + await page.goto('/souls', { waitUntil: 'domcontentloaded' }) + await expect(page.locator('text=Something went wrong!')).toHaveCount(0) + await expect(page.locator('h1', { hasText: 'Souls' })).toBeVisible() +}) + +test('header menu routes render', async ({ page }) => { + await page.goto('/', { waitUntil: 'domcontentloaded' }) + + for (const label of navLabels) { + const link = page.getByRole('link', { name: label }).first() + await expect(link).toBeVisible() + await link.click() + + if (label === 'Skills') { + await expect(page).toHaveURL(/\/skills/) + await expect(page.locator('h1', { hasText: 'Skills' })).toBeVisible() + } + + if (label === 'Upload') { + await expect(page).toHaveURL(/\/upload/) + const heading = page.locator('h1.section-title', { hasText: /^Publish a /i }) + const signInCard = page.locator('text=Sign in to upload') + await expect(heading.or(signInCard)).toBeVisible() + } + + if (label === 'Import') { + await expect(page).toHaveURL(/\/import/) + const heading = page.getByRole('heading', { name: 'Import from GitHub' }) + const signInCard = page.locator('text=Sign in to import and publish skills.') + await expect(heading.or(signInCard)).toBeVisible() + } + + if (label === 'Search') { + await expect(page).toHaveURL(/\/?(\?|$)/) + await expect(page.locator('h1', { hasText: 'ClawdHub' })).toBeVisible() + } + } +}) diff --git a/package.json b/package.json index 2b90a21d..6e070275 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "test": "vitest run", "test:watch": "vitest", "test:e2e": "vitest run -c vitest.e2e.config.ts", + "test:e2e:local": "bash scripts/run-playwright-local.sh", + "test:pw": "playwright test", "coverage": "vitest run --coverage", "convex:deploy": "bunx convex deploy --typecheck=disable --yes", "lint": "bun run lint:biome && bun run lint:oxlint", @@ -57,6 +59,7 @@ }, "devDependencies": { "@biomejs/biome": "^2.3.11", + "@playwright/test": "^1.57.0", "@tanstack/devtools-vite": "^0.4.0", "@testing-library/dom": "^10.4.1", "@testing-library/react": "^16.3.1", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..af479bec --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,33 @@ +import { defineConfig, devices } from '@playwright/test' + +const port = Number(process.env.PLAYWRIGHT_PORT || 4173) +const baseURL = process.env.PLAYWRIGHT_BASE_URL || `http://127.0.0.1:${port}` + +export default defineConfig({ + testDir: './e2e', + testMatch: /.*\.pw\.test\.ts/, + timeout: 60_000, + expect: { timeout: 10_000 }, + fullyParallel: true, + retries: process.env.CI ? 2 : 0, + reporter: process.env.CI ? [['list'], ['html', { open: 'never' }]] : [['list']], + use: { + baseURL, + trace: 'retain-on-failure', + }, + webServer: process.env.PLAYWRIGHT_BASE_URL + ? undefined + : { + command: 'bun run preview -- --host 127.0.0.1 --port 4173', + url: baseURL, + reuseExistingServer: !process.env.CI, + stdout: 'ignore', + stderr: 'pipe', + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], +}) diff --git a/scripts/run-playwright-local.sh b/scripts/run-playwright-local.sh new file mode 100755 index 00000000..0c1b98e5 --- /dev/null +++ b/scripts/run-playwright-local.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail + +PORT="${PLAYWRIGHT_PORT:-4173}" + +if [[ -n "${PLAYWRIGHT_BASE_URL:-}" ]]; then + echo "Running against $PLAYWRIGHT_BASE_URL" + bun run test:pw + exit 0 +fi + +echo "Running against local preview server on http://127.0.0.1:$PORT" +bun run build +PLAYWRIGHT_PORT="$PORT" bun run test:pw