Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ scripts
documentation
dist
hooks
electron

# Client
client/node_modules
client-v3/node_modules
client-v3/e2e

# Server
server/conf
server/public
server/static
server/static
server/test
65 changes: 65 additions & 0 deletions .github/workflows/playwright-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Playwright E2E Tests

on: [pull_request]

permissions:
checks: write
pull-requests: write

jobs:
playwright:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
browser: [chromium, firefox]
name: E2E Tests (${{ matrix.browser }})
steps:
- uses: actions/checkout@v4

- name: Set up Python 3.13
uses: actions/setup-python@v5
with:
python-version: "3.13"

- name: Install Python dependencies
run: pip install -r requirements.txt
working-directory: ./server

- name: Set up Node 24
uses: actions/setup-node@v4
with:
node-version: 24

- name: Install Node dependencies
run: npm ci
working-directory: ./client-v3

- name: Build Vue 3 frontend
run: npm run build
working-directory: ./client-v3

- name: Install Playwright browser
# matrix.browser is a static value from the matrix definition (chromium/firefox)
run: npx playwright install --with-deps ${{ matrix.browser }}
working-directory: ./client-v3

- name: Run Playwright tests
# matrix.browser is a static value from the matrix definition (chromium/firefox)
run: npx playwright test --project=${{ matrix.browser }}
working-directory: ./client-v3

- name: Upload Playwright report
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report-${{ matrix.browser }}
path: client-v3/playwright-report/
retention-days: 7

- name: Publish Test Results
uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
check_name: "Playwright E2E Results (${{ matrix.browser }})"
junit_files: "**/client-v3/junit/playwright-results.xml"
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ CLAUDE.md
.playwright-mcp/
plans/

# Playwright
client-v3/playwright-report/
client-v3/test-results/

# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
Expand Down
59 changes: 59 additions & 0 deletions client-v3/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# DigiScript Client (V3)

**Requirements**: Node 24.x

## Project setup
```
npm ci
```

### Run the development server
```
npm run dev
```

The dev server starts at `http://localhost:5173` and proxies API requests to `http://localhost:8080`.

### Compiles and minifies for production
```
npm run build
```

### Lints and formats files
```
npm run lint
```

### Type checking
```
npm run typecheck
```

### Unit tests
```
npm run test:run
```

### E2E tests
```
npm run test:e2e
```

See [e2e/README.md](./e2e/README.md) for full details on running and writing E2E tests.

## Project Structure

### Source Directory
The [src](./src) directory is where the main Vue 3 project lives.

* [assets](./src/assets): static assets such as CSS and images.
* [components](./src/components): reusable Vue components, organised by feature area.
* [composables](./src/composables): Composition API composables (shared logic, replaces Vue 2 mixins).
* [constants](./src/constants): shared constants used across components.
* [js](./src/js): plain TypeScript utilities — HTTP interceptor, validators, platform abstraction.
* [router](./src/router): Vue Router configuration and navigation guards.
* [stores](./src/stores): Pinia stores for application state (user, show, script, websocket, etc.).
* [types](./src/types): TypeScript interfaces for API response shapes and shared types.
* [views](./src/views): page-level components mapped to router routes.
* [App.vue](./src/App.vue): root component — defines the main layout and houses the router view.
* [main.ts](./src/main.ts): application entry point, configures Vue, Pinia, and the router.
77 changes: 77 additions & 0 deletions client-v3/e2e/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# DigiScript E2E Tests

End-to-end tests for the Vue 3 frontend, written with [Playwright](https://playwright.dev/).
Tests run against a real backend instance with a clean SQLite database, covering every major user-facing workflow.

## Prerequisites

* Node 24.x with dependencies installed (`npm ci` in `client-v3/`)
* Python 3.13.x with dependencies installed (`pip install -r requirements.txt` in `server/`)
* A production build of the frontend (`npm run build` in `client-v3/`)

The global setup script starts and stops the backend automatically — no manual server management required.

## Running the tests

```bash
# Chromium only (default, fastest)
npm run test:e2e

# Firefox only
npm run test:e2e:firefox

# Both browsers
npm run test:e2e:all

# Open the HTML report from the last run
npm run test:e2e:report
```

## How it works

Before the suite runs, `e2e/global-setup.ts` launches the Python backend on port 8888 with a
temporary SQLite database. After the suite finishes, `e2e/global-teardown.ts` stops the server
and cleans up the database file. All tests share this single server instance and database.

## Spec files

Tests are numbered to enforce a specific run order. Each spec depends on the database state
left by earlier specs.

| File | Area |
|------|------|
| `01-first-run.spec.ts` | Initial server setup wizard |
| `02-auth.spec.ts` | Login, logout, session handling |
| `03-system-config.spec.ts` | System Config: show creation, users, settings, system info, logs, backups |
| `04-show-config-show.spec.ts` | Show Config: show details |
| `05-show-config-acts-scenes.spec.ts` | Show Config: acts and scenes CRUD |
| `06-show-config-characters.spec.ts` | Show Config: characters CRUD |
| `07-show-config-stage.spec.ts` | Show Config: props, scenery, crew, stage manager allocations |
| `08-show-config-cues.spec.ts` | Show Config: cue types CRUD |
| `09-show-config-mics.spec.ts` | Show Config: microphones, allocations, timeline |
| `10-show-config-script.spec.ts` | Show Config: script editing, saving, stage direction styles, cue add/edit/delete |
| `11-show-config-revisions.spec.ts` | Show Config: script revisions |
| `12-show-config-sessions.spec.ts` | Show Config: live show sessions |
| `13-live-show.spec.ts` | Live show display and cue triggering |
| `14-user-settings.spec.ts` | User settings and profile |

> **Important**: specs must always be run as a full suite. Running individual spec files in
> isolation will fail because each spec relies on database state created by earlier specs.
> Never use `--grep` or file-specific invocations in CI or local development.

## Debugging

Failed test runs produce an HTML report and screenshots/videos under `playwright-report/`.
Open the report with:

```bash
npm run test:e2e:report
```

Trace files (recorded on failure) can be inspected with the Playwright Trace Viewer.

## CI

The GitHub Actions workflow (`.github/workflows/playwright-test.yml`) runs the full suite against
both Chromium and Firefox on every pull request. The workflow builds the frontend, installs
Playwright browsers, runs the suite, and uploads the HTML report as an artifact.
88 changes: 88 additions & 0 deletions client-v3/e2e/db-snapshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import fs from 'fs';

Check warning on line 1 in client-v3/e2e/db-snapshot.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `node:fs` over `fs`.

See more on https://sonarcloud.io/project/issues?id=dreamteamprod_DigiScript&issues=AZ5hVIZVxRHZv9iNZ-zz&open=AZ5hVIZVxRHZv9iNZ-zz&pullRequest=1085
import path from 'path';

Check warning on line 2 in client-v3/e2e/db-snapshot.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `node:path` over `path`.

See more on https://sonarcloud.io/project/issues?id=dreamteamprod_DigiScript&issues=AZ5hVIZVxRHZv9iNZ-z0&open=AZ5hVIZVxRHZv9iNZ-z0&pullRequest=1085
import { spawn } from 'child_process';

Check warning on line 3 in client-v3/e2e/db-snapshot.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `node:child_process` over `child_process`.

See more on https://sonarcloud.io/project/issues?id=dreamteamprod_DigiScript&issues=AZ5hVIZVxRHZv9iNZ-z1&open=AZ5hVIZVxRHZv9iNZ-z1&pullRequest=1085
import { test } from '@playwright/test';
import { PID_FILE, TMPDIR_FILE, SERVER_PORT, waitForServer } from './global-setup.js';

function getPaths() {
const tempDir = fs.readFileSync(TMPDIR_FILE, 'utf-8').trim();
const db = path.join(tempDir, 'digiscript.sqlite');
const config = path.join(tempDir, 'digiscript.json');
return {
db,
dbSpecStart: `${db}.specstart`,
config,
configSpecStart: `${config}.specstart`,
serverDir: path.resolve(process.cwd(), '..', 'server'),
};
}

export function specStartExists(): boolean {
return fs.existsSync(getPaths().dbSpecStart);
}

/** Copy the current DB and config to the spec-start snapshot files. */
export function snapshotSpecStart(): void {
const { db, dbSpecStart, config, configSpecStart } = getPaths();
fs.copyFileSync(db, dbSpecStart);
fs.copyFileSync(config, configSpecStart);
}

/**
* Restore DB and config from the spec-start snapshot, then restart the server.
* This returns the backend to the state it was in before this spec's first test ran,
* so every test in the spec can re-run cleanly without hitting its own prior side effects.
*/
export async function restoreSpecStartAndRestartServer(): Promise<void> {
const { db, dbSpecStart, config, configSpecStart, serverDir } = getPaths();

if (fs.existsSync(PID_FILE)) {
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim(), 10);

Check warning on line 40 in client-v3/e2e/db-snapshot.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `Number.parseInt` over `parseInt`.

See more on https://sonarcloud.io/project/issues?id=dreamteamprod_DigiScript&issues=AZ5hVIZVxRHZv9iNZ-z2&open=AZ5hVIZVxRHZv9iNZ-z2&pullRequest=1085
try {
process.kill(pid, 'SIGKILL');
} catch {
// process already gone
}
await new Promise((r) => setTimeout(r, 500));
}

// Remove any stale journal file (server uses DELETE journal mode, not WAL)
try {
fs.rmSync(`${db}-journal`);
} catch {
// no journal file present
}

fs.copyFileSync(dbSpecStart, db);
fs.copyFileSync(configSpecStart, config);

const server = spawn(
'python3',
['main.py', `--port=${SERVER_PORT}`, `--settings_path=${config}`, '--debug=false'],
{ cwd: serverDir, detached: true, stdio: 'ignore' }
);
fs.writeFileSync(PID_FILE, String(server.pid!));

Check warning on line 64 in client-v3/e2e/db-snapshot.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This assertion is unnecessary since the receiver accepts the original type of the expression.

See more on https://sonarcloud.io/project/issues?id=dreamteamprod_DigiScript&issues=AZ5hVIZVxRHZv9iNZ-z4&open=AZ5hVIZVxRHZv9iNZ-z4&pullRequest=1085
server.unref();

await waitForServer();
}

/**
* Register beforeAll hooks for retry support. Call once at the top of each spec file.
*
* On first run (retry=0): captures the DB state at spec start so retries have a clean baseline.
* On retry: restores from that spec-start snapshot before tests run again, ensuring every test
* in the spec sees the same starting conditions regardless of side effects from prior attempts.
*/
export function registerRetryHooks(): void {
test.beforeAll(async () => {
if (test.info().retry > 0 && specStartExists()) {
await restoreSpecStartAndRestartServer();
}
});
test.beforeAll(async () => {
if (test.info().retry === 0) {
snapshotSpecStart();
}
});
}
Loading
Loading