From 4a63387feb56edd689acdafdd3de149cbfc58640 Mon Sep 17 00:00:00 2001 From: YasserYG8 Date: Mon, 22 Jun 2026 09:42:14 +0100 Subject: [PATCH] feat : configure playwright e2e testing and integrate into CI --- .github/workflows/ci.yml | 24 ++++++++++++++++++ .gitignore | 3 ++- e2e/smoke.spec.ts | 28 ++++++++++++++++++++ package.json | 3 +++ playwright.config.ts | 31 ++++++++++++++++++++++ pnpm-lock.yaml | 55 ++++++++++++++++++++++++++++++++++------ 6 files changed, 135 insertions(+), 9 deletions(-) create mode 100644 e2e/smoke.spec.ts create mode 100644 playwright.config.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 04145f7..296299b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,3 +27,27 @@ jobs: - run: pnpm typecheck - run: pnpm test - run: pnpm build + + e2e: + runs-on: ubuntu-latest + env: + AUTH_SECRET: some-dummy-secret-for-ci + AUTH_TRUST_HOST: "true" + TURSO_DATABASE_URL: file:local.db + steps: + - uses: actions/checkout@v7 + + - uses: pnpm/action-setup@v6 + + - uses: actions/setup-node@v6 + with: + node-version: 24 + cache: pnpm + + - run: pnpm install --frozen-lockfile + + - name: Install Playwright Chromium Browser + run: pnpm exec playwright install --with-deps chromium + + - name: Run E2E Smoke Tests + run: pnpm test:e2e diff --git a/.gitignore b/.gitignore index a522b23..02b7e3d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,8 @@ # testing /coverage - +/playwright-report +/test-results # next.js /.next/ /out/ diff --git a/e2e/smoke.spec.ts b/e2e/smoke.spec.ts new file mode 100644 index 0000000..c434d8d --- /dev/null +++ b/e2e/smoke.spec.ts @@ -0,0 +1,28 @@ +import { test, expect } from "@playwright/test"; + +test("should load app, click analyze on default snippet, and render call graph nodes", async ({ page }) => { + // 1. Visit the app in English locale + await page.goto("/en/app"); + + // 2. Locate the textarea for code input and verify it is visible + const textarea = page.locator("textarea"); + await expect(textarea).toBeVisible(); + + // 3. Click the "Analyze" button (using the default Python sample code) + const analyzeBtn = page.getByRole("button", { name: /^Analyze$/ }); + await expect(analyzeBtn).toBeEnabled(); + await analyzeBtn.click(); + + // 4. Assert that the React Flow workspace container is rendered + const reactFlow = page.locator(".react-flow"); + await expect(reactFlow).toBeVisible(); + + // 5. Assert that we successfully render 6 nodes (1 file container + 5 defined functions) + const nodes = page.locator(".react-flow__node"); + await expect(nodes).toHaveCount(6); + + // 6. Verify key node labels contain our function names and container file + await expect(page.locator(".react-flow__node", { hasText: "snippet.py" })).toBeVisible(); + await expect(page.locator(".react-flow__node", { hasText: "main" })).toBeVisible(); + await expect(page.locator(".react-flow__node", { hasText: "clean" })).toBeVisible(); +}); diff --git a/package.json b/package.json index 4bf5fed..aafdc9e 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,8 @@ "lint": "eslint", "typecheck": "tsc --noEmit", "test": "vitest run", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui", "db:push": "drizzle-kit push", "db:studio": "drizzle-kit studio", "format": "prettier --write .", @@ -43,6 +45,7 @@ "zod": "^4.4.3" }, "devDependencies": { + "@playwright/test": "^1.61.0", "@types/dagre": "^0.7.54", "@types/node": "^26", "@types/react": "^19", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..a89972f --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,31 @@ +import { defineConfig, devices } from "@playwright/test"; + +export default defineConfig({ + testDir: "./e2e", + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: "html", + webServer: { + command: "pnpm dev", + url: "http://localhost:3000", + reuseExistingServer: !process.env.CI, + stdout: "ignore", + stderr: "pipe", + env: { + AUTH_TRUST_HOST: "true", + AUTH_SECRET: "some-dummy-secret-for-e2e", + }, + }, + use: { + baseURL: "http://localhost:3000", + trace: "on", + }, + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43286f6..95c3298 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,7 +28,7 @@ importers: version: 4.3.1 '@vercel/analytics': specifier: ^2.0.1 - version: 2.0.1(next@16.2.9(@babel/core@7.29.7)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: 2.0.1(next@16.2.9(@babel/core@7.29.7)(@playwright/test@1.61.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) drizzle-orm: specifier: ^0.45.2 version: 0.45.2(@libsql/client@0.17.4) @@ -43,10 +43,10 @@ importers: version: 4.2.1 next: specifier: 16.2.9 - version: 16.2.9(@babel/core@7.29.7)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 16.2.9(@babel/core@7.29.7)(@playwright/test@1.61.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) next-auth: specifier: 5.0.0-beta.31 - version: 5.0.0-beta.31(next@16.2.9(@babel/core@7.29.7)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: 5.0.0-beta.31(next@16.2.9(@babel/core@7.29.7)(@playwright/test@1.61.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 19.2.7 version: 19.2.7 @@ -66,6 +66,9 @@ importers: specifier: ^4.4.3 version: 4.4.3 devDependencies: + '@playwright/test': + specifier: ^1.61.0 + version: 1.61.0 '@types/dagre': specifier: ^0.7.54 version: 0.7.54 @@ -1049,6 +1052,11 @@ packages: '@panva/hkdf@1.2.1': resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} + '@playwright/test@1.61.0': + resolution: {integrity: sha512-cKA5B6lpFEMyMGjxF54QihfYpB4FkEGH+qZhtArDEG+wezQAJY8Pq6C7T1SjWz+FFzt3TbyoXBQYk/0292TdJA==} + engines: {node: '>=18'} + hasBin: true + '@reactflow/background@11.3.14': resolution: {integrity: sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==} peerDependencies: @@ -2286,6 +2294,11 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} + 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} @@ -2864,6 +2877,16 @@ packages: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} + playwright-core@1.61.0: + resolution: {integrity: sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.61.0: + resolution: {integrity: sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ==} + engines: {node: '>=18'} + hasBin: true + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -4071,6 +4094,10 @@ snapshots: '@panva/hkdf@1.2.1': {} + '@playwright/test@1.61.0': + dependencies: + playwright: 1.61.0 + '@reactflow/background@11.3.14(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@reactflow/core': 11.11.4(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -4605,9 +4632,9 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.12.2': optional: true - '@vercel/analytics@2.0.1(next@16.2.9(@babel/core@7.29.7)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)': + '@vercel/analytics@2.0.1(next@16.2.9(@babel/core@7.29.7)(@playwright/test@1.61.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)': optionalDependencies: - next: 16.2.9(@babel/core@7.29.7)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + next: 16.2.9(@babel/core@7.29.7)(@playwright/test@1.61.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: 19.2.7 '@vitest/expect@4.1.9': @@ -5427,6 +5454,9 @@ snapshots: dependencies: is-callable: 1.2.7 + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -5853,13 +5883,13 @@ snapshots: natural-compare@1.4.0: {} - next-auth@5.0.0-beta.31(next@16.2.9(@babel/core@7.29.7)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7): + next-auth@5.0.0-beta.31(next@16.2.9(@babel/core@7.29.7)(@playwright/test@1.61.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7): dependencies: '@auth/core': 0.41.2 - next: 16.2.9(@babel/core@7.29.7)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + next: 16.2.9(@babel/core@7.29.7)(@playwright/test@1.61.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: 19.2.7 - next@16.2.9(@babel/core@7.29.7)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + next@16.2.9(@babel/core@7.29.7)(@playwright/test@1.61.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: '@next/env': 16.2.9 '@swc/helpers': 0.5.15 @@ -5878,6 +5908,7 @@ snapshots: '@next/swc-linux-x64-musl': 16.2.9 '@next/swc-win32-arm64-msvc': 16.2.9 '@next/swc-win32-x64-msvc': 16.2.9 + '@playwright/test': 1.61.0 sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' @@ -5984,6 +6015,14 @@ snapshots: picomatch@4.0.4: {} + playwright-core@1.61.0: {} + + playwright@1.61.0: + dependencies: + playwright-core: 1.61.0 + optionalDependencies: + fsevents: 2.3.2 + possible-typed-array-names@1.1.0: {} postcss@8.4.31: