From 8bad081e794c153ad8de1e0a9a2864c119b8b2d2 Mon Sep 17 00:00:00 2001 From: Marco Pasqualetti <24919330+marcalexiei@users.noreply.github.com> Date: Sun, 8 Feb 2026 10:29:07 +0100 Subject: [PATCH] test(e2e): add playwright (#47) - closes #46 --- .github/workflows/ci.yml | 39 +++++ .gitignore | 7 + apps/frontend/e2e/app.spec.ts | 71 +++++++++ apps/frontend/package.json | 1 + apps/frontend/playwright.config.ts | 81 ++++++++++ apps/frontend/src/pages/Home/Home.tsx | 4 +- apps/frontend/src/pages/Home/stages/Theme.tsx | 63 ++++---- apps/frontend/tsconfig.json | 8 +- package.json | 2 + pnpm-lock.yaml | 140 +++++++++++------- 10 files changed, 330 insertions(+), 86 deletions(-) create mode 100644 apps/frontend/e2e/app.spec.ts create mode 100644 apps/frontend/playwright.config.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a47ed97..ab8c8272 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,45 @@ jobs: - name: Build frontend run: pnpm --filter frontend run build + frontend-test-e2e: + name: Frontend E2E test + + permissions: + contents: read + + timeout-minutes: 60 + + runs-on: ubuntu-latest + + # Use official playwright docker image, which already includes browsers and related system dependencies. + # By doing this ci time we can skip the following step: + # + # ```yml + # - name: Install Playwright Browsers + # run: pnpm exec playwright install --with-deps + # ``` + # + # By doing so job execution time is reduced by ~20s + # + # @see https://playwright.dev/docs/docker + container: + image: mcr.microsoft.com/playwright:v1.58.0-noble + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Install Dependencies + uses: ./.github/actions/install-dependencies + + - name: Run vercel-preparation.sh + run: | + chmod +x ./vercel-preparation.sh + ./vercel-preparation.sh + + - name: Run Playwright tests + run: pnpm --filter frontend run test:e2e + code-checks: name: Code checks diff --git a/.gitignore b/.gitignore index a3ec671d..bd17695b 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,10 @@ tsconfig.tsbuildinfo !.vscode/extensions.json !.vscode/settings.json *.code-workspace + +# Playwright +test-results/ +playwright-report/ +blob-report/ +playwright/.cache/ +playwright/.auth/ diff --git a/apps/frontend/e2e/app.spec.ts b/apps/frontend/e2e/app.spec.ts new file mode 100644 index 00000000..6a2ed8e1 --- /dev/null +++ b/apps/frontend/e2e/app.spec.ts @@ -0,0 +1,71 @@ +import { test, expect } from "@playwright/test"; + +test("load initial page correctly", async ({ page }) => { + await page.goto(""); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/GitHub Stats Extended/); + + // Page title / header branding + await expect(page.getByText("GitHub Stats Extended")).toBeVisible(); + + // Logo exists + await expect(page.locator('img[alt="logo"]')).toBeVisible(); + + // Star on GitHub button + const starButton = page.getByRole("button", { name: /star on/i }); + await expect(starButton).toBeVisible(); + + const githubLink = page.locator( + 'a[href*="github.com/stats-organization/github-stats-extended"]', + ); + await expect(githubLink).toHaveAttribute("target", "_blank"); + + // Login buttons + const publicAccessBtn = page.getByRole("button", { + name: /github public access/i, + }); + await expect(publicAccessBtn).toBeVisible(); + await expect(publicAccessBtn).toBeEnabled(); + + const privateAccessBtn = page.getByRole("button", { + name: /github private access/i, + }); + await expect(privateAccessBtn).toBeVisible(); + await expect(privateAccessBtn).toBeEnabled(); + + const guestBtn = page.getByRole("button", { name: /continue as guest/i }); + await expect(guestBtn).toBeVisible(); + await expect(guestBtn).toBeEnabled(); +}); + +test("navigates between steps", async ({ page }) => { + await page.goto(""); + + // We are at stage 1 + await expect(page.getByRole("heading", { level: 1 })).toContainText("Login"); + + // Go to to stage 2 + await page.getByRole("button", { name: "Select card" }).click(); + await expect(page.getByRole("heading", { level: 1 })).toContainText( + "Select a Card", + ); + + // Go to to stage 3 + await page.getByRole("button", { name: "Modify parameters" }).click(); + await expect(page.getByRole("heading", { level: 1 })).toContainText( + "Modify Card Parameters", + ); + + // Go to to stage 4 + await page.getByRole("button", { name: "Select theme" }).click(); + await expect(page.getByRole("heading", { level: 1 })).toContainText( + "Choose a Theme", + ); + + // Go to to stage 5 + await page.getByRole("button", { name: "Display card" }).click(); + await expect(page.getByRole("heading", { level: 1 })).toContainText( + "Display your Card", + ); +}); diff --git a/apps/frontend/package.json b/apps/frontend/package.json index ac58457e..4094387b 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -39,6 +39,7 @@ "dev": "vite", "build": "vite build", "preview": "vite preview", + "test:e2e": "playwright test", "typecheck": "tsc --noEmit" }, "homepage": "/frontend", diff --git a/apps/frontend/playwright.config.ts b/apps/frontend/playwright.config.ts new file mode 100644 index 00000000..0e38be08 --- /dev/null +++ b/apps/frontend/playwright.config.ts @@ -0,0 +1,81 @@ +import { defineConfig, devices } from "@playwright/test"; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +const baseURL = "http://localhost:5173/frontend/"; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./e2e", + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env["CI"], + /* Retry on CI only */ + retries: process.env["CI"] ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: 1, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('')`. */ + baseURL, + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + + // { + // name: "firefox", + // use: { ...devices["Desktop Firefox"] }, + // }, + + // { + // name: "webkit", + // use: { ...devices["Desktop Safari"] }, + // }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: "pnpm run dev", + url: baseURL, + reuseExistingServer: !process.env["CI"], + }, +}); diff --git a/apps/frontend/src/pages/Home/Home.tsx b/apps/frontend/src/pages/Home/Home.tsx index 754e9582..22997762 100644 --- a/apps/frontend/src/pages/Home/Home.tsx +++ b/apps/frontend/src/pages/Home/Home.tsx @@ -314,9 +314,9 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element {
-
+

{STAGE_LABELS[stage].title} -

+
{stage === 0 && isAuthenticated ? (
diff --git a/apps/frontend/src/pages/Home/stages/Theme.tsx b/apps/frontend/src/pages/Home/stages/Theme.tsx index a457ccbd..91278361 100644 --- a/apps/frontend/src/pages/Home/stages/Theme.tsx +++ b/apps/frontend/src/pages/Home/stages/Theme.tsx @@ -16,6 +16,20 @@ type ThemeData = Record< } >; +const excludedThemes = [ + "merko", + "blue-green", + "gotham", + "blueberry", + "outrun", + "holi", +]; + +const themeList = Object.keys( + /* Needed until themes is typed correctly and retrieved from npm package */ + themes as ThemeData, +).filter((myTheme) => !excludedThemes.includes(myTheme)); + interface ThemeStageProps { fullSuffix: string; theme: string; @@ -30,37 +44,24 @@ export function ThemeStage({ return ( <>
- {/* Needed until themes is typed correctly and retrieved from npm package */} - {Object.keys(themes as ThemeData) - .filter( - (myTheme) => - ![ - "merko", - "blue-green", - "gotham", - "blueberry", - "outrun", - "holi", - ].includes(myTheme), - ) - .map((myTheme) => ( - - ))} + {themeList.map((myTheme) => ( + + ))}
For more theme options check the{" "} diff --git a/apps/frontend/tsconfig.json b/apps/frontend/tsconfig.json index fa153b64..2df80af9 100644 --- a/apps/frontend/tsconfig.json +++ b/apps/frontend/tsconfig.json @@ -1,6 +1,12 @@ { "extends": ["../../tsconfig.base.json"], - "include": ["src", "src/**/*.json", "vite.config.ts"], + "include": [ + "src", + "src/**/*.json", + "e2e", + "vite.config.ts", + "playwright.config.ts" + ], "compilerOptions": { "lib": ["DOM"], "composite": true, diff --git a/package.json b/package.json index d473c0c5..b9a5131a 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "devDependencies": { "@eslint/compat": "2.0.2", "@eslint/js": "9.39.2", + "@playwright/test": "1.58.1", + "@types/node": "24.10.9", "eslint": "9.39.2", "eslint-plugin-jsdoc": "61.7.1", "eslint-plugin-react": "7.37.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3244a072..42dbcdc8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,12 @@ importers: '@eslint/js': specifier: 9.39.2 version: 9.39.2 + '@playwright/test': + specifier: 1.58.1 + version: 1.58.1 + '@types/node': + specifier: 24.10.9 + version: 24.10.9 eslint: specifier: 9.39.2 version: 9.39.2(jiti@2.6.1) @@ -34,7 +40,7 @@ importers: version: 9.1.7 knip: specifier: 5.81.0 - version: 5.81.0(@types/node@25.0.3)(typescript@5.9.3) + version: 5.81.0(@types/node@24.10.9)(typescript@5.9.3) lint-staged: specifier: 16.2.7 version: 16.2.7 @@ -101,7 +107,7 @@ importers: version: 3.2.2 jest: specifier: ^30.2.0 - version: 30.2.0(@types/node@25.0.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.8)(@types/node@25.0.3)(typescript@5.9.3)) + version: 30.2.0(@types/node@24.10.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.8)(@types/node@24.10.9)(typescript@5.9.3)) jest-environment-jsdom: specifier: ^30.2.0 version: 30.2.0 @@ -383,10 +389,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime@7.28.4': - resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} - engines: {node: '>=6.9.0'} - '@babel/runtime@7.28.6': resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} engines: {node: '>=6.9.0'} @@ -990,6 +992,11 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@playwright/test@1.58.1': + resolution: {integrity: sha512-6LdVIUERWxQMmUSSQi0I53GgCBYgM2RpGngCPY7hSeju+VrKjq3lvs7HpJoPbDiY5QM5EYRtRX5fvrinnMAz3w==} + engines: {node: '>=18'} + hasBin: true + '@reduxjs/toolkit@2.11.2': resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==} peerDependencies: @@ -1318,6 +1325,9 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/node@24.10.9': + resolution: {integrity: sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==} + '@types/node@25.0.3': resolution: {integrity: sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==} @@ -1391,10 +1401,6 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.52.0': - resolution: {integrity: sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.54.0': resolution: {integrity: sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2415,6 +2421,11 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + 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} @@ -3464,6 +3475,16 @@ packages: resolution: {integrity: sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==} engines: {node: '>=10'} + playwright-core@1.58.1: + resolution: {integrity: sha512-bcWzOaTxcW+VOOGBCQgnaKToLJ65d6AqfLVKEWvexyS3AS6rbXl+xdpYRMGSRBClPvyj44njOWoxjNdL/H9UNg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.58.1: + resolution: {integrity: sha512-+2uTZHxSCcxjvGc5C891LrS1/NlxglGxzrC4seZiVjcYVQfUa87wBL6rTDqzGjuoWNjnBzRqKmF6zRYGMvQUaQ==} + engines: {node: '>=18'} + hasBin: true + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -4624,10 +4645,7 @@ snapshots: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/runtime@7.28.4': {} - - '@babel/runtime@7.28.6': - optional: true + '@babel/runtime@7.28.6': {} '@babel/template@7.27.2': dependencies: @@ -4698,7 +4716,7 @@ snapshots: '@es-joy/jsdoccomment@0.78.0': dependencies: '@types/estree': 1.0.8 - '@typescript-eslint/types': 8.52.0 + '@typescript-eslint/types': 8.54.0 comment-parser: 1.4.1 esquery: 1.7.0 jsdoc-type-pratt-parser: 7.0.0 @@ -4874,13 +4892,13 @@ snapshots: '@jest/console@30.2.0': dependencies: '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 24.10.9 chalk: 4.1.2 jest-message-util: 30.2.0 jest-util: 30.2.0 slash: 3.0.0 - '@jest/core@30.2.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.8)(@types/node@25.0.3)(typescript@5.9.3))': + '@jest/core@30.2.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.8)(@types/node@24.10.9)(typescript@5.9.3))': dependencies: '@jest/console': 30.2.0 '@jest/pattern': 30.0.1 @@ -4888,14 +4906,14 @@ snapshots: '@jest/test-result': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 24.10.9 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 4.3.1 exit-x: 0.2.2 graceful-fs: 4.2.11 jest-changed-files: 30.2.0 - jest-config: 30.2.0(@types/node@25.0.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.8)(@types/node@25.0.3)(typescript@5.9.3)) + jest-config: 30.2.0(@types/node@24.10.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.8)(@types/node@24.10.9)(typescript@5.9.3)) jest-haste-map: 30.2.0 jest-message-util: 30.2.0 jest-regex-util: 30.0.1 @@ -4924,7 +4942,7 @@ snapshots: '@jest/fake-timers': 30.2.0 '@jest/types': 30.2.0 '@types/jsdom': 21.1.7 - '@types/node': 25.0.3 + '@types/node': 24.10.9 jest-mock: 30.2.0 jest-util: 30.2.0 jsdom: 26.1.0 @@ -4933,7 +4951,7 @@ snapshots: dependencies: '@jest/fake-timers': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 24.10.9 jest-mock: 30.2.0 '@jest/expect-utils@30.2.0': @@ -4951,7 +4969,7 @@ snapshots: dependencies: '@jest/types': 30.2.0 '@sinonjs/fake-timers': 13.0.5 - '@types/node': 25.0.3 + '@types/node': 24.10.9 jest-message-util: 30.2.0 jest-mock: 30.2.0 jest-util: 30.2.0 @@ -4969,7 +4987,7 @@ snapshots: '@jest/pattern@30.0.1': dependencies: - '@types/node': 25.0.3 + '@types/node': 24.10.9 jest-regex-util: 30.0.1 '@jest/reporters@30.2.0': @@ -4980,7 +4998,7 @@ snapshots: '@jest/transform': 30.2.0 '@jest/types': 30.2.0 '@jridgewell/trace-mapping': 0.3.31 - '@types/node': 25.0.3 + '@types/node': 24.10.9 chalk: 4.1.2 collect-v8-coverage: 1.0.3 exit-x: 0.2.2 @@ -5057,7 +5075,7 @@ snapshots: '@jest/schemas': 30.0.5 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 25.0.3 + '@types/node': 24.10.9 '@types/yargs': 17.0.35 chalk: 4.1.2 @@ -5243,6 +5261,10 @@ snapshots: '@pkgr/core@0.2.9': {} + '@playwright/test@1.58.1': + dependencies: + playwright: 1.58.1 + '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@18.3.27)(react@18.3.1)(redux@5.0.1))(react@18.3.1)': dependencies: '@standard-schema/spec': 1.1.0 @@ -5421,7 +5443,7 @@ snapshots: '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.27.1 - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 '@types/aria-query': 5.0.4 aria-query: 5.3.0 dom-accessibility-api: 0.5.16 @@ -5492,15 +5514,20 @@ snapshots: '@types/jsdom@21.1.7': dependencies: - '@types/node': 25.0.3 + '@types/node': 24.10.9 '@types/tough-cookie': 4.0.5 parse5: 7.3.0 '@types/json-schema@7.0.15': {} + '@types/node@24.10.9': + dependencies: + undici-types: 7.16.0 + '@types/node@25.0.3': dependencies: undici-types: 7.16.0 + optional: true '@types/parse-json@4.0.2': optional: true @@ -5588,8 +5615,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.52.0': {} - '@typescript-eslint/types@8.54.0': {} '@typescript-eslint/typescript-estree@8.54.0(typescript@5.9.3)': @@ -6837,6 +6862,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -7283,7 +7311,7 @@ snapshots: '@jest/expect': 30.2.0 '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 24.10.9 chalk: 4.1.2 co: 4.6.0 dedent: 1.7.1(babel-plugin-macros@3.1.0) @@ -7303,15 +7331,15 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@30.2.0(@types/node@25.0.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.8)(@types/node@25.0.3)(typescript@5.9.3)): + jest-cli@30.2.0(@types/node@24.10.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.8)(@types/node@24.10.9)(typescript@5.9.3)): dependencies: - '@jest/core': 30.2.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.8)(@types/node@25.0.3)(typescript@5.9.3)) + '@jest/core': 30.2.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.8)(@types/node@24.10.9)(typescript@5.9.3)) '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 chalk: 4.1.2 exit-x: 0.2.2 import-local: 3.2.0 - jest-config: 30.2.0(@types/node@25.0.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.8)(@types/node@25.0.3)(typescript@5.9.3)) + jest-config: 30.2.0(@types/node@24.10.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.8)(@types/node@24.10.9)(typescript@5.9.3)) jest-util: 30.2.0 jest-validate: 30.2.0 yargs: 17.7.2 @@ -7322,7 +7350,7 @@ snapshots: - supports-color - ts-node - jest-config@30.2.0(@types/node@25.0.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.8)(@types/node@25.0.3)(typescript@5.9.3)): + jest-config@30.2.0(@types/node@24.10.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.8)(@types/node@24.10.9)(typescript@5.9.3)): dependencies: '@babel/core': 7.28.5 '@jest/get-type': 30.1.0 @@ -7349,8 +7377,8 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 25.0.3 - ts-node: 10.9.2(@swc/core@1.15.8)(@types/node@25.0.3)(typescript@5.9.3) + '@types/node': 24.10.9 + ts-node: 10.9.2(@swc/core@1.15.8)(@types/node@24.10.9)(typescript@5.9.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -7379,7 +7407,7 @@ snapshots: '@jest/environment': 30.2.0 '@jest/environment-jsdom-abstract': 30.2.0(jsdom@26.1.0) '@types/jsdom': 21.1.7 - '@types/node': 25.0.3 + '@types/node': 24.10.9 jsdom: 26.1.0 transitivePeerDependencies: - bufferutil @@ -7391,7 +7419,7 @@ snapshots: '@jest/environment': 30.2.0 '@jest/fake-timers': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 24.10.9 jest-mock: 30.2.0 jest-util: 30.2.0 jest-validate: 30.2.0 @@ -7399,7 +7427,7 @@ snapshots: jest-haste-map@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 24.10.9 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -7438,7 +7466,7 @@ snapshots: jest-mock@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 24.10.9 jest-util: 30.2.0 jest-pnp-resolver@1.2.3(jest-resolve@30.2.0): @@ -7472,7 +7500,7 @@ snapshots: '@jest/test-result': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 24.10.9 chalk: 4.1.2 emittery: 0.13.1 exit-x: 0.2.2 @@ -7501,7 +7529,7 @@ snapshots: '@jest/test-result': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 24.10.9 chalk: 4.1.2 cjs-module-lexer: 2.2.0 collect-v8-coverage: 1.0.3 @@ -7548,7 +7576,7 @@ snapshots: jest-util@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 24.10.9 chalk: 4.1.2 ci-info: 4.3.1 graceful-fs: 4.2.11 @@ -7567,7 +7595,7 @@ snapshots: dependencies: '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 24.10.9 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -7576,18 +7604,18 @@ snapshots: jest-worker@30.2.0: dependencies: - '@types/node': 25.0.3 + '@types/node': 24.10.9 '@ungap/structured-clone': 1.3.0 jest-util: 30.2.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@30.2.0(@types/node@25.0.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.8)(@types/node@25.0.3)(typescript@5.9.3)): + jest@30.2.0(@types/node@24.10.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.8)(@types/node@24.10.9)(typescript@5.9.3)): dependencies: - '@jest/core': 30.2.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.8)(@types/node@25.0.3)(typescript@5.9.3)) + '@jest/core': 30.2.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.8)(@types/node@24.10.9)(typescript@5.9.3)) '@jest/types': 30.2.0 import-local: 3.2.0 - jest-cli: 30.2.0(@types/node@25.0.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.8)(@types/node@25.0.3)(typescript@5.9.3)) + jest-cli: 30.2.0(@types/node@24.10.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.8)(@types/node@24.10.9)(typescript@5.9.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -7662,10 +7690,10 @@ snapshots: dependencies: json-buffer: 3.0.1 - knip@5.81.0(@types/node@25.0.3)(typescript@5.9.3): + knip@5.81.0(@types/node@24.10.9)(typescript@5.9.3): dependencies: '@nodelib/fs.walk': 1.2.8 - '@types/node': 25.0.3 + '@types/node': 24.10.9 fast-glob: 3.3.3 formatly: 0.3.0 jiti: 2.6.1 @@ -8120,6 +8148,14 @@ snapshots: dependencies: find-up: 5.0.0 + playwright-core@1.58.1: {} + + playwright@1.58.1: + dependencies: + playwright-core: 1.58.1 + optionalDependencies: + fsevents: 2.3.2 + possible-typed-array-names@1.1.0: {} postcss-import@15.1.0(postcss@8.5.6): @@ -8858,14 +8894,14 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-node@10.9.2(@swc/core@1.15.8)(@types/node@25.0.3)(typescript@5.9.3): + ts-node@10.9.2(@swc/core@1.15.8)(@types/node@24.10.9)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.12 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 25.0.3 + '@types/node': 24.10.9 acorn: 8.15.0 acorn-walk: 8.3.4 arg: 4.1.3