Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 7d56055f0b367966b51117811ff106441cbc0fb7
parent cf3bd181fc752aad1df56945e7f403d05159951a
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Tue, 28 Apr 2026 00:30:37 -0400

chore(editor): scaffold playwright alongside cypress

Both runners coexist until the cypress removal in a later phase, so
each migration step has a green-on-both verification gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Diffstat:
M.gitignore | 4+++-
Aeditor/e2e/helpers.ts | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aeditor/e2e/smoke.spec.ts | 15+++++++++++++++
Meditor/package.json | 3+++
Aeditor/playwright.config.ts | 32++++++++++++++++++++++++++++++++
Mpnpm-lock.yaml | 47+++++++++++++++++++++++++++++++++++++++++++----
6 files changed, 165 insertions(+), 5 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -54,9 +54,11 @@ yarn-error.log* /export/public/transcripts/ /transcripts/index.mdb/ -# editor cypress fixtures and ephemeral state +# editor e2e fixtures and ephemeral state /editor/test-transcripts/ /editor/test-settings.json /editor/cypress/screenshots/ /editor/cypress/videos/ /editor/cypress/downloads/ +/editor/playwright-report/ +/editor/test-results/ diff --git a/editor/e2e/helpers.ts b/editor/e2e/helpers.ts @@ -0,0 +1,69 @@ +import { dirname, resolve, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { + cp, + mkdir, + readFile, + rm, + stat, + writeFile, +} from "node:fs/promises"; + +const here = dirname(fileURLToPath(import.meta.url)); +const editorRoot = resolve(here, ".."); +const fixturesRoot = resolve(editorRoot, "cypress", "fixtures"); +const testTranscriptsDir = resolve(editorRoot, "test-transcripts"); +const testSettingsFile = resolve(editorRoot, "test-settings.json"); + +const baseUrl = process.env.PLAYWRIGHT_BASE_URL ?? "http://localhost:3001"; + +async function fileExists(p: string): Promise<boolean> { + try { + await stat(p); + return true; + } catch { + return false; + } +} + +export async function resetData(fixtureName: string | null = null) { + await rm(testTranscriptsDir, { recursive: true, force: true }); + await rm(testSettingsFile, { force: true }); + if (fixtureName) { + const src = join(fixturesRoot, "test-transcripts", fixtureName); + if (!(await fileExists(src))) { + throw new Error(`Fixture not found: ${fixtureName}`); + } + await mkdir(testTranscriptsDir, { recursive: true }); + await cp(src, testTranscriptsDir, { recursive: true }); + } else { + await mkdir(join(testTranscriptsDir, "channels"), { recursive: true }); + } + await fetch(`${baseUrl}/api/test/invalidate-cache`).catch(() => {}); +} + +export async function writeSettings(settings: Record<string, unknown>) { + await writeFile(testSettingsFile, JSON.stringify(settings, null, 2)); + await fetch(`${baseUrl}/api/test/invalidate-cache`).catch(() => {}); +} + +export async function readJson<T = unknown>(relPath: string): Promise<T> { + const full = resolve(editorRoot, relPath); + const raw = await readFile(full, "utf8"); + return JSON.parse(raw) as T; +} + +export async function pathExists(relPath: string): Promise<boolean> { + return fileExists(resolve(editorRoot, relPath)); +} + +export function resolvePath(relPath: string): string { + return resolve(editorRoot, relPath); +} + +export async function copyFixture(name: string) { + const dst = join(fixturesRoot, "test-transcripts", name); + await rm(dst, { recursive: true, force: true }); + await mkdir(dst, { recursive: true }); + await cp(testTranscriptsDir, dst, { recursive: true }); +} diff --git a/editor/e2e/smoke.spec.ts b/editor/e2e/smoke.spec.ts @@ -0,0 +1,15 @@ +import { test, expect } from "@playwright/test"; +import { resetData } from "./helpers"; + +test.beforeEach(async () => { + await resetData(); +}); + +test("dashboard renders heading and zero stats on empty fixture", async ({ + page, +}) => { + await page.goto("/"); + await expect( + page.getByRole("heading", { level: 1, name: /dashboard/i }), + ).toBeVisible(); +}); diff --git a/editor/package.json b/editor/package.json @@ -9,6 +9,8 @@ "build": "next build", "start": "next start --port 3001", "lint": "eslint", + "e2e": "playwright test", + "e2e:ui": "playwright test --ui", "e2e-dev": "start-server-and-test dev:test http://localhost:3001 \"cypress open --e2e\"", "e2e-dev:headless": "start-server-and-test dev:test http://localhost:3001 \"cypress run --e2e\"" }, @@ -20,6 +22,7 @@ "yt-dlp-transcript-common": "workspace:*" }, "devDependencies": { + "@playwright/test": "^1.59.1", "@tailwindcss/postcss": "^4.2.2", "@testing-library/cypress": "^10.0.3", "@types/node": "^20.19.39", diff --git a/editor/playwright.config.ts b/editor/playwright.config.ts @@ -0,0 +1,32 @@ +import { defineConfig, devices } from "@playwright/test"; + +const PORT = Number(process.env.PORT ?? 3001); +const baseURL = `http://localhost:${PORT}`; + +export default defineConfig({ + testDir: "./e2e", + timeout: 30_000, + retries: process.env.CI ? 2 : 0, + reporter: process.env.CI ? "github" : "list", + outputDir: "test-results/", + fullyParallel: false, + workers: 1, + webServer: { + command: "pnpm dev:test", + url: baseURL, + timeout: 120_000, + reuseExistingServer: !process.env.CI, + }, + use: { + baseURL, + trace: "on-first-retry", + screenshot: "only-on-failure", + video: "retain-on-failure", + }, + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml @@ -31,7 +31,7 @@ importers: version: 3.5.4 next: specifier: 16.2.3 - version: 16.2.3(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) p-limit: specifier: ^7.3.0 version: 7.3.0 @@ -71,7 +71,7 @@ importers: version: 3.0.0 next: specifier: 16.2.3 - version: 16.2.3(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: specifier: 19.2.4 version: 19.2.4 @@ -82,6 +82,9 @@ importers: specifier: workspace:* version: link:../common devDependencies: + '@playwright/test': + specifier: ^1.59.1 + version: 1.59.1 '@tailwindcss/postcss': specifier: ^4.2.2 version: 4.2.4 @@ -123,7 +126,7 @@ importers: version: 5.100.5(react@19.2.4) next: specifier: 16.2.3 - version: 16.2.3(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: specifier: 19.2.4 version: 19.2.4 @@ -809,6 +812,11 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@playwright/test@1.59.1': + resolution: {integrity: sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==} + engines: {node: '>=18'} + hasBin: true + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -1905,6 +1913,11 @@ packages: resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} engines: {node: '>=10'} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2701,6 +2714,16 @@ packages: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} + playwright-core@1.59.1: + resolution: {integrity: sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.59.1: + resolution: {integrity: sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==} + engines: {node: '>=18'} + hasBin: true + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -3794,6 +3817,10 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@playwright/test@1.59.1': + dependencies: + playwright: 1.59.1 + '@rtsao/scc@1.1.0': {} '@sec-ant/readable-stream@0.4.1': {} @@ -5146,6 +5173,9 @@ snapshots: jsonfile: 6.2.1 universalify: 2.0.1 + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -5727,7 +5757,7 @@ snapshots: negotiator@0.6.4: {} - next@16.2.3(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: '@next/env': 16.2.3 '@swc/helpers': 0.5.15 @@ -5746,6 +5776,7 @@ snapshots: '@next/swc-linux-x64-musl': 16.2.3 '@next/swc-win32-arm64-msvc': 16.2.3 '@next/swc-win32-x64-msvc': 16.2.3 + '@playwright/test': 1.59.1 sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' @@ -5896,6 +5927,14 @@ snapshots: pify@2.3.0: {} + playwright-core@1.59.1: {} + + playwright@1.59.1: + dependencies: + playwright-core: 1.59.1 + optionalDependencies: + fsevents: 2.3.2 + possible-typed-array-names@1.1.0: {} postcss@8.4.31: