mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-07-06 05:23:56 -07:00
Playwright E2E suite (not working yet)
This commit is contained in:
parent
f534cd79b7
commit
68050b56bc
13 changed files with 425 additions and 16 deletions
1
webclient/.gitignore
vendored
1
webclient/.gitignore
vendored
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
/coverage
|
/coverage
|
||||||
|
/test-results
|
||||||
|
|
||||||
# production
|
# production
|
||||||
/build
|
/build
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ React 19 + TypeScript, built with [Vite](https://vite.dev/) 8. State via Redux T
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Node.js and npm
|
- Node.js and npm
|
||||||
|
- Docker Desktop — only required for `npm run test:e2e` (spins up servatrice + MySQL via docker compose)
|
||||||
- Run every command below from the `webclient/` directory
|
- Run every command below from the `webclient/` directory
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
@ -40,11 +41,18 @@ npm start
|
||||||
- `npm run test:watch` — Vitest in watch mode
|
- `npm run test:watch` — Vitest in watch mode
|
||||||
- `npm run test:integration` — integration specs via `vitest.integration.config.ts`
|
- `npm run test:integration` — integration specs via `vitest.integration.config.ts`
|
||||||
- `npm run test:coverage` / `npm run test:integration:coverage` — the above with v8 coverage
|
- `npm run test:coverage` / `npm run test:integration:coverage` — the above with v8 coverage
|
||||||
|
- `npm run test:e2e` — full Playwright e2e pipeline (requires Docker Desktop): `test:e2e:up` → `test:e2e:run` → `test:e2e:down`, preserving the run's exit code
|
||||||
|
- `npm run test:e2e:run` — run specs against an already-running stack (use this in dev loops)
|
||||||
|
- `npm run test:e2e:open` — Playwright UI mode for debugging specs interactively
|
||||||
|
- `npm run test:e2e:up` / `npm run test:e2e:down` — bring the dockerized servatrice + MySQL stack up / down (volumes are wiped on `down`)
|
||||||
|
|
||||||
|
The `pretest:e2e` hook runs `playwright install --with-deps chromium` automatically on first invocation, so no manual browser install is needed.
|
||||||
|
|
||||||
### Quality
|
### Quality
|
||||||
|
|
||||||
- `npm run lint` / `npm run lint:fix` — ESLint over `src/`
|
- `npm run lint` / `npm run lint:fix` — ESLint over `src/`
|
||||||
- `npm run golden` — `lint` + `test` + `test:integration`; the CI-equivalent gate to run before declaring work done
|
- `npm run golden` — `lint` + `test` + `test:integration`; the fast CI-equivalent gate to run before declaring work done
|
||||||
|
- `npm run golden:full` — `golden` + `test:e2e`; the full pre-merge gate (requires Docker Desktop)
|
||||||
|
|
||||||
### Codegen & i18n
|
### Codegen & i18n
|
||||||
|
|
||||||
|
|
|
||||||
41
webclient/e2e/docker/docker-compose.e2e.yml
Normal file
41
webclient/e2e/docker/docker-compose.e2e.yml
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Standalone compose used by the webclient e2e harness.
|
||||||
|
# Paths are relative to THIS file's directory (webclient/e2e/docker/), so the
|
||||||
|
# file must be invoked with `-f webclient/e2e/docker/docker-compose.e2e.yml`
|
||||||
|
# from anywhere — Docker Compose resolves relative mounts / build contexts
|
||||||
|
# against the compose file's own directory.
|
||||||
|
services:
|
||||||
|
mysql:
|
||||||
|
image: mysql:8
|
||||||
|
command: --sql_mode=
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: root-password
|
||||||
|
MYSQL_DATABASE: servatrice
|
||||||
|
MYSQL_USER: servatrice
|
||||||
|
MYSQL_PASSWORD: password
|
||||||
|
volumes:
|
||||||
|
- ../../../servatrice/servatrice.sql:/docker-entrypoint-initdb.d/servatrice.sql:ro
|
||||||
|
- cockatrice_e2e_mysql:/var/lib/mysql
|
||||||
|
|
||||||
|
servatrice:
|
||||||
|
build:
|
||||||
|
# Build context is the repo root so the Dockerfile can see the full
|
||||||
|
# C++ source tree (three levels up from this file).
|
||||||
|
context: ../../..
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: cockatrice/servatrice:e2e
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
ports:
|
||||||
|
- "4748:4748"
|
||||||
|
# Wait for MySQL's TCP port to accept connections before launching
|
||||||
|
# servatrice. The first-time `up --build` has to load servatrice.sql, which
|
||||||
|
# takes longer than any fixed sleep — without this wait, servatrice exits
|
||||||
|
# silently with code 0 when it can't reach the DB. The trailing `sleep 2`
|
||||||
|
# gives mysqld a moment after the port opens to finish accepting auth.
|
||||||
|
# global-setup.ts additionally polls the WebSocket port to avoid races.
|
||||||
|
entrypoint: "/bin/bash -c 'until (echo > /dev/tcp/mysql/3306) 2>/dev/null; do sleep 1; done; sleep 2; servatrice --config /tmp/servatrice.ini --log-to-console'"
|
||||||
|
volumes:
|
||||||
|
- ./servatrice-e2e.ini:/tmp/servatrice.ini:ro
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
cockatrice_e2e_mysql:
|
||||||
54
webclient/e2e/docker/servatrice-e2e.ini
Normal file
54
webclient/e2e/docker/servatrice-e2e.ini
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
; Servatrice config used only by the webclient e2e harness.
|
||||||
|
; Mounted into the servatrice container at /tmp/servatrice.ini by
|
||||||
|
; docker-compose.e2e.yml. Differs from servatrice-docker.ini by enabling
|
||||||
|
; registration, skipping email activation, and relaxing per-host rate limits
|
||||||
|
; so parallel Playwright workers from one machine don't trip them.
|
||||||
|
|
||||||
|
[server]
|
||||||
|
name="Cockatrice E2E"
|
||||||
|
logfile=server.log
|
||||||
|
writelog=0
|
||||||
|
|
||||||
|
[authentication]
|
||||||
|
; sql → registration + login hits the real accounts table, so the full
|
||||||
|
; register-then-login flow is exercised end-to-end.
|
||||||
|
method=sql
|
||||||
|
regonly=false
|
||||||
|
|
||||||
|
[users]
|
||||||
|
; Our test usernames are "e2e_" + 6 hex chars = 10 chars. Lower the min so
|
||||||
|
; that fits with room to spare if we ever shorten the prefix.
|
||||||
|
minnamelength=4
|
||||||
|
maxnamelength=12
|
||||||
|
allowlowercase=true
|
||||||
|
allowuppercase=true
|
||||||
|
allownumerics=true
|
||||||
|
allowedpunctuation=_.-
|
||||||
|
allowpunctuationprefix=false
|
||||||
|
disallowedwords="admin"
|
||||||
|
minpasswordlength=6
|
||||||
|
|
||||||
|
[registration]
|
||||||
|
enabled=true
|
||||||
|
requireemail=false
|
||||||
|
requireemailactivation=false
|
||||||
|
maxaccountsperemail=0
|
||||||
|
|
||||||
|
[database]
|
||||||
|
type=mysql
|
||||||
|
prefix=cockatrice
|
||||||
|
hostname=mysql
|
||||||
|
database=servatrice
|
||||||
|
user=servatrice
|
||||||
|
password=password
|
||||||
|
|
||||||
|
[security]
|
||||||
|
trusted_sources="127.0.0.1,::1"
|
||||||
|
enable_max_user_limit=false
|
||||||
|
max_users_per_address=0
|
||||||
|
max_tcp_users_per_address=0
|
||||||
|
max_websocket_users_per_address=0
|
||||||
|
|
||||||
|
[game]
|
||||||
|
store_replays=false
|
||||||
|
max_game_inactivity_time=120
|
||||||
8
webclient/e2e/global-setup.ts
Normal file
8
webclient/e2e/global-setup.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { waitForServatriceReady } from './helpers/servatrice';
|
||||||
|
|
||||||
|
export default async function globalSetup(): Promise<void> {
|
||||||
|
// The compose entrypoint sleeps 10s waiting for MySQL. On cold CI runs
|
||||||
|
// building the image on top of that can push the first WS frame well past
|
||||||
|
// 30s, so poll for up to 60s before giving up.
|
||||||
|
await waitForServatriceReady(60_000);
|
||||||
|
}
|
||||||
48
webclient/e2e/helpers/seedLocalHost.ts
Normal file
48
webclient/e2e/helpers/seedLocalHost.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import type { Page } from '@playwright/test';
|
||||||
|
import { E2E_WS_URL } from './servatrice';
|
||||||
|
|
||||||
|
// Points the running webclient at the local servatrice without touching any
|
||||||
|
// webclient source. Strategy: navigate to the app once (which lets Dexie open
|
||||||
|
// the Webatrice IndexedDB at schema v1 and seed DefaultHosts), then wipe the
|
||||||
|
// `hosts` store and insert a single "Local E2E" row with lastSelected=true,
|
||||||
|
// then reload so useKnownHosts re-reads. On a fresh Playwright context the
|
||||||
|
// IDB is empty, so our seed is the only known host after reload — the Login
|
||||||
|
// form auto-selects it and fires its test-connection against :4748.
|
||||||
|
export async function seedLocalHostAndReload(page: Page, wsUrl: string = E2E_WS_URL): Promise<void> {
|
||||||
|
const parsed = new URL(wsUrl);
|
||||||
|
const host = parsed.hostname;
|
||||||
|
const port = parsed.port || '4748';
|
||||||
|
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
await page.evaluate(async ({ host, port }) => {
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
const open = indexedDB.open('Webatrice');
|
||||||
|
open.onerror = () => reject(open.error ?? new Error('failed to open Webatrice DB'));
|
||||||
|
open.onsuccess = () => {
|
||||||
|
const db = open.result;
|
||||||
|
if (!db.objectStoreNames.contains('hosts')) {
|
||||||
|
db.close();
|
||||||
|
reject(new Error('hosts object store missing — did Dexie finish opening?'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tx = db.transaction('hosts', 'readwrite');
|
||||||
|
const store = tx.objectStore('hosts');
|
||||||
|
store.clear();
|
||||||
|
store.add({
|
||||||
|
name: 'Local E2E',
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
localHost: host,
|
||||||
|
localPort: port,
|
||||||
|
editable: false,
|
||||||
|
lastSelected: true,
|
||||||
|
});
|
||||||
|
tx.oncomplete = () => { db.close(); resolve(); };
|
||||||
|
tx.onerror = () => { db.close(); reject(tx.error ?? new Error('hosts write failed')); };
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, { host, port });
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
}
|
||||||
37
webclient/e2e/helpers/servatrice.ts
Normal file
37
webclient/e2e/helpers/servatrice.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { WebSocket } from 'ws';
|
||||||
|
|
||||||
|
// The e2e docker-compose.yml publishes servatrice on localhost:4748.
|
||||||
|
export const E2E_WS_URL = 'ws://localhost:4748';
|
||||||
|
|
||||||
|
// Opens a WS to servatrice and resolves once the server sends *anything* back
|
||||||
|
// (ServerIdentification is the first frame). Used by global-setup to wait out
|
||||||
|
// the `sleep 10` in the docker-compose entrypoint before any spec runs.
|
||||||
|
export async function waitForServatriceReady(timeoutMs = 60_000): Promise<void> {
|
||||||
|
const deadline = Date.now() + timeoutMs;
|
||||||
|
|
||||||
|
while (Date.now() < deadline) {
|
||||||
|
const ok = await tryConnect(E2E_WS_URL).catch(() => false);
|
||||||
|
if (ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await sleep(1000);
|
||||||
|
}
|
||||||
|
throw new Error(`servatrice did not become ready at ${E2E_WS_URL} within ${timeoutMs}ms`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryConnect(url: string): Promise<boolean> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const ws = new WebSocket(url);
|
||||||
|
const done = (value: boolean, err?: Error) => {
|
||||||
|
try { ws.removeAllListeners(); ws.close(); } catch { /* ignore */ }
|
||||||
|
err ? reject(err) : resolve(value);
|
||||||
|
};
|
||||||
|
ws.once('message', () => done(true));
|
||||||
|
ws.once('error', (err) => done(false, err));
|
||||||
|
ws.once('close', () => done(false));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(ms: number): Promise<void> {
|
||||||
|
return new Promise((r) => setTimeout(r, ms));
|
||||||
|
}
|
||||||
47
webclient/e2e/helpers/testUser.ts
Normal file
47
webclient/e2e/helpers/testUser.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { randomBytes } from 'node:crypto';
|
||||||
|
import { expect, type Page } from '@playwright/test';
|
||||||
|
|
||||||
|
export interface TestUser {
|
||||||
|
userName: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Servatrice [users] rules (see webclient/e2e/docker/servatrice-e2e.ini):
|
||||||
|
// - 4-12 chars, lowercase/uppercase/numerics + "_.-", no leading punctuation
|
||||||
|
// - "admin" is blacklisted
|
||||||
|
// "e2e_" + 6 hex chars = 10 chars — fits comfortably and is unique per call.
|
||||||
|
export function generateUniqueUser(): TestUser {
|
||||||
|
return {
|
||||||
|
userName: 'e2e_' + randomBytes(3).toString('hex'),
|
||||||
|
password: 'TestPw123!',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function registerViaUi(page: Page, user: TestUser): Promise<void> {
|
||||||
|
await page.getByRole('button', { name: /register/i }).first().click();
|
||||||
|
|
||||||
|
const dialog = page.getByRole('dialog');
|
||||||
|
await expect(dialog).toBeVisible();
|
||||||
|
|
||||||
|
await dialog.locator('input[name="userName"]').fill(user.userName);
|
||||||
|
await dialog.locator('input[name="password"]').fill(user.password);
|
||||||
|
await dialog.locator('input[name="passwordConfirm"]').fill(user.password);
|
||||||
|
|
||||||
|
await dialog.getByRole('button', { name: /register/i }).click();
|
||||||
|
|
||||||
|
// With requireemailactivation=false the server returns RegistrationAccepted
|
||||||
|
// and the dialog closes. If activation were required an AccountActivation
|
||||||
|
// dialog would replace it — that's out of scope for this spec.
|
||||||
|
await expect(dialog).toBeHidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loginViaUi(page: Page, user: TestUser): Promise<void> {
|
||||||
|
await page.locator('input[name="userName"]').fill(user.userName);
|
||||||
|
await page.locator('input[name="password"]').fill(user.password);
|
||||||
|
|
||||||
|
const loginButton = page.locator('button.loginForm-submit');
|
||||||
|
// The Login button is gated on a successful test-connection (fired
|
||||||
|
// automatically when the host loads). Wait it out rather than racing it.
|
||||||
|
await expect(loginButton).toBeEnabled();
|
||||||
|
await loginButton.click();
|
||||||
|
}
|
||||||
28
webclient/e2e/playwright.config.ts
Normal file
28
webclient/e2e/playwright.config.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { defineConfig, devices } from '@playwright/test';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: './specs',
|
||||||
|
fullyParallel: true,
|
||||||
|
workers: 2,
|
||||||
|
timeout: 60_000,
|
||||||
|
expect: { timeout: 15_000 },
|
||||||
|
reporter: [['list'], ['html', { outputFolder: '../coverage/e2e', open: 'never' }]],
|
||||||
|
globalSetup: './global-setup.ts',
|
||||||
|
use: {
|
||||||
|
baseURL: 'http://localhost:5173',
|
||||||
|
headless: true,
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
screenshot: 'only-on-failure',
|
||||||
|
},
|
||||||
|
webServer: {
|
||||||
|
// Run from the webclient root so Vite picks up its own vite.config.ts and
|
||||||
|
// `prestart` proto generation. Specs point the app at local servatrice by
|
||||||
|
// seeding IndexedDB via helpers/seedLocalHost.ts — no source changes needed.
|
||||||
|
command: 'npm run start',
|
||||||
|
cwd: '..',
|
||||||
|
url: 'http://localhost:5173',
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
timeout: 120_000,
|
||||||
|
},
|
||||||
|
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
|
||||||
|
});
|
||||||
19
webclient/e2e/specs/register-and-login.spec.ts
Normal file
19
webclient/e2e/specs/register-and-login.spec.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { seedLocalHostAndReload } from '../helpers/seedLocalHost';
|
||||||
|
import { generateUniqueUser, loginViaUi, registerViaUi } from '../helpers/testUser';
|
||||||
|
|
||||||
|
test('a fresh user can register, then log in against local servatrice', async ({ page }) => {
|
||||||
|
const user = generateUniqueUser();
|
||||||
|
|
||||||
|
// Seed IndexedDB so the Login form auto-selects our Local E2E host instead
|
||||||
|
// of one of the production defaults. After this returns the app has been
|
||||||
|
// reloaded; AppShell uses MemoryRouter, so the address bar never reflects
|
||||||
|
// the active route — assert on DOM markers unique to each screen instead.
|
||||||
|
await seedLocalHostAndReload(page);
|
||||||
|
await expect(page.locator('.login-content')).toBeVisible();
|
||||||
|
|
||||||
|
await registerViaUi(page, user);
|
||||||
|
await loginViaUi(page, user);
|
||||||
|
|
||||||
|
await expect(page.locator('.serverRoomWrapper')).toBeVisible();
|
||||||
|
});
|
||||||
9
webclient/e2e/tsconfig.json
Normal file
9
webclient/e2e/tsconfig.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["node"],
|
||||||
|
"rootDir": ".",
|
||||||
|
"noEmit": true
|
||||||
|
},
|
||||||
|
"include": ["**/*.ts"]
|
||||||
|
}
|
||||||
100
webclient/package-lock.json
generated
100
webclient/package-lock.json
generated
|
|
@ -42,6 +42,7 @@
|
||||||
"@bufbuild/protoc-gen-es": "^2.11.0",
|
"@bufbuild/protoc-gen-es": "^2.11.0",
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@mui/types": "^9.0.0",
|
"@mui/types": "^9.0.0",
|
||||||
|
"@playwright/test": "^1.51.0",
|
||||||
"@testing-library/dom": "^10.4.1",
|
"@testing-library/dom": "^10.4.1",
|
||||||
"@testing-library/jest-dom": "^6.4.0",
|
"@testing-library/jest-dom": "^6.4.0",
|
||||||
"@testing-library/react": "^16.3.2",
|
"@testing-library/react": "^16.3.2",
|
||||||
|
|
@ -52,6 +53,7 @@
|
||||||
"@types/react-dom": "^19.0.0",
|
"@types/react-dom": "^19.0.0",
|
||||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||||
"@types/react-window": "^1.8.5",
|
"@types/react-window": "^1.8.5",
|
||||||
|
"@types/ws": "^8.5.12",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.58.2",
|
"@typescript-eslint/eslint-plugin": "^8.58.2",
|
||||||
"@typescript-eslint/parser": "^8.58.2",
|
"@typescript-eslint/parser": "^8.58.2",
|
||||||
"@vitejs/plugin-react": "^6.0.1",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
|
|
@ -68,7 +70,8 @@
|
||||||
"typescript": "~6.0",
|
"typescript": "~6.0",
|
||||||
"typescript-eslint": "^8.58.2",
|
"typescript-eslint": "^8.58.2",
|
||||||
"vite": "^8.0.8",
|
"vite": "^8.0.8",
|
||||||
"vitest": "^4.1.4"
|
"vitest": "^4.1.4",
|
||||||
|
"ws": "^8.18.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@adobe/css-tools": {
|
"node_modules/@adobe/css-tools": {
|
||||||
|
|
@ -1359,6 +1362,22 @@
|
||||||
"url": "https://github.com/sponsors/Boshen"
|
"url": "https://github.com/sponsors/Boshen"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@playwright/test": {
|
||||||
|
"version": "1.59.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz",
|
||||||
|
"integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"playwright": "1.59.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@popperjs/core": {
|
"node_modules/@popperjs/core": {
|
||||||
"version": "2.11.8",
|
"version": "2.11.8",
|
||||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||||
|
|
@ -2159,6 +2178,16 @@
|
||||||
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
|
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/ws": {
|
||||||
|
"version": "8.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||||
|
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.58.2",
|
"version": "8.58.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.2.tgz",
|
||||||
|
|
@ -5044,6 +5073,53 @@
|
||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/playwright": {
|
||||||
|
"version": "1.59.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz",
|
||||||
|
"integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"playwright-core": "1.59.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "2.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright-core": {
|
||||||
|
"version": "1.59.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz",
|
||||||
|
"integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"playwright-core": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright/node_modules/fsevents": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.10",
|
"version": "8.5.10",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
|
||||||
|
|
@ -6154,6 +6230,28 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.20.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
|
||||||
|
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/xml-name-validator": {
|
"node_modules/xml-name-validator": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -3,27 +3,35 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prebuild": "npm run proto:generate && node prebuild.js",
|
|
||||||
"prestart": "npm run proto:generate && node prebuild.js",
|
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"start": "vite",
|
"diagram": "npm run diagram:simple && npm run diagram:detailed && npm run diagram:flow",
|
||||||
"preview": "vite preview",
|
"diagram:simple": "npx -y -p @mermaid-js/mermaid-cli -p puppeteer mmdc -i architecture/simple.mmd -o architecture/simple.png -b white -s 2",
|
||||||
|
"diagram:detailed": "npx -y -p @mermaid-js/mermaid-cli -p puppeteer mmdc -i architecture/detailed.mmd -o architecture/detailed.png -b white -s 2",
|
||||||
|
"diagram:flow": "npx -y -p @mermaid-js/mermaid-cli -p puppeteer mmdc -i architecture/flow.mmd -o architecture/flow.png -b white -s 2",
|
||||||
|
"golden": "npm run lint && npm run test && npm run test:integration",
|
||||||
|
"golden:coverage": "npm run lint && npm run test:coverage && npm run test:integration:coverage",
|
||||||
|
"golden:full": "npm run golden && npm run test:e2e",
|
||||||
|
"lint": "eslint src",
|
||||||
|
"lint:fix": "eslint src --fix",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"test:coverage": "npm run test -- --coverage",
|
"test:coverage": "npm run test -- --coverage",
|
||||||
"test:watch": "vitest",
|
"test:watch": "vitest",
|
||||||
"test:integration": "vitest run --config vitest.integration.config.ts",
|
"test:integration": "vitest run --config vitest.integration.config.ts",
|
||||||
"test:integration:coverage": "npm run test:integration -- --coverage",
|
"test:integration:coverage": "npm run test:integration -- --coverage",
|
||||||
"lint": "eslint src",
|
"test:e2e": "bash -c \"npm run test:e2e:up && (npm run test:e2e:run; EC=$?; npm run test:e2e:down; exit $EC)\"",
|
||||||
"lint:fix": "eslint src --fix",
|
"test:e2e:open": "playwright test --config e2e/playwright.config.ts --ui",
|
||||||
"golden": "npm run lint && npm run test && npm run test:integration",
|
"test:e2e:run": "playwright test --config e2e/playwright.config.ts",
|
||||||
"golden:coverage": "npm run lint && npm run test:coverage && npm run test:integration:coverage",
|
"test:e2e:up": "docker compose -f e2e/docker/docker-compose.e2e.yml up -d --build",
|
||||||
|
"test:e2e:down": "docker compose -f e2e/docker/docker-compose.e2e.yml down -v",
|
||||||
|
"test:e2e:install-browsers": "npx playwright install --with-deps chromium",
|
||||||
|
"pretest:e2e": "npm run test:e2e:install-browsers",
|
||||||
|
"prebuild": "npm run proto:generate && node prebuild.js",
|
||||||
"prepare": "cd .. && husky",
|
"prepare": "cd .. && husky",
|
||||||
"translate": "node prebuild.js -i18nOnly",
|
"prestart": "npm run proto:generate && node prebuild.js",
|
||||||
|
"preview": "vite preview",
|
||||||
"proto:generate": "npx buf generate",
|
"proto:generate": "npx buf generate",
|
||||||
"diagram": "npm run diagram:simple && npm run diagram:detailed && npm run diagram:flow",
|
"start": "vite",
|
||||||
"diagram:simple": "npx -y -p @mermaid-js/mermaid-cli -p puppeteer mmdc -i architecture/simple.mmd -o architecture/simple.png -b white -s 2",
|
"translate": "node prebuild.js -i18nOnly"
|
||||||
"diagram:detailed": "npx -y -p @mermaid-js/mermaid-cli -p puppeteer mmdc -i architecture/detailed.mmd -o architecture/detailed.png -b white -s 2",
|
|
||||||
"diagram:flow": "npx -y -p @mermaid-js/mermaid-cli -p puppeteer mmdc -i architecture/flow.mmd -o architecture/flow.png -b white -s 2"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bufbuild/protobuf": "^2.11.0",
|
"@bufbuild/protobuf": "^2.11.0",
|
||||||
|
|
@ -59,6 +67,8 @@
|
||||||
"@bufbuild/buf": "^1.68.1",
|
"@bufbuild/buf": "^1.68.1",
|
||||||
"@bufbuild/protoc-gen-es": "^2.11.0",
|
"@bufbuild/protoc-gen-es": "^2.11.0",
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
|
"@playwright/test": "^1.51.0",
|
||||||
|
"@types/ws": "^8.5.12",
|
||||||
"@mui/types": "^9.0.0",
|
"@mui/types": "^9.0.0",
|
||||||
"@testing-library/dom": "^10.4.1",
|
"@testing-library/dom": "^10.4.1",
|
||||||
"@testing-library/jest-dom": "^6.4.0",
|
"@testing-library/jest-dom": "^6.4.0",
|
||||||
|
|
@ -86,7 +96,8 @@
|
||||||
"typescript": "~6.0",
|
"typescript": "~6.0",
|
||||||
"typescript-eslint": "^8.58.2",
|
"typescript-eslint": "^8.58.2",
|
||||||
"vite": "^8.0.8",
|
"vite": "^8.0.8",
|
||||||
"vitest": "^4.1.4"
|
"vitest": "^4.1.4",
|
||||||
|
"ws": "^8.18.0"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue