Skip to content

Testing

E2E Tests (Playwright)

128 tests across 16 spec files in tests/e2e/, running sequentially (1 worker, shared database). 9 OPDS tests are currently skipped at the describe level (ported to Vitest integration tests).

Test Files

FileTestsTagsCoverage
auth.spec.ts7@smokeSetup form, login, logout, session persistence
book-progress.spec.ts3@smokeMulti-device progress, empty state, finished badge
command-palette.spec.ts3Global search modal, navigation, book results
errors.spec.ts5@smoke / @slowAuth redirects, invalid keys, network errors (@smoke ×4); duplicate file detection (@slow + @external)
hardcover.spec.ts10@smokeToken CRUD, status, sync button/log, feature toggles, persistence
home.spec.ts5@smokeDashboard stats, currently reading, recently added
inbox.spec.ts16@smokeList, search, pagination, empty state, metadata picker, approve, delete
ingestion.spec.ts1@slow @externalFull EPUB pipeline: detect → parse → review → approve → library
library.spec.ts18@smokeGrid/list view, filters, search, pagination, detail page, covers, downloads
multi-user-auth.spec.ts15Ownership enforcement, admin vs regular key, cross-user 403s, credential rotation
multi-user.spec.ts11Per-user data isolation (reading progress, stats, service credentials)
opds.spec.ts9@smoke (skipped)Feed structure, search, covers, downloads, Basic auth (test.describe.skip)
reading-status.spec.ts4@smokeSidebar links, status tabs, per-status book lists, empty state
settings.spec.ts8@smoke (4)Health & diagnostics (@smoke); jobs browser and queue management untagged
stats.spec.ts7@smokeBooks finished, streaks, daily activity, genre distribution
websocket-events.spec.ts6Realtime event bus over WebSocket — job status, pipeline events, Hardcover sync updates

Note: opds.spec.ts uses test.describe.skip() — the OPDS tests have been ported to Vitest integration tests at services/api-hono/src/routes/opds.test.ts. The E2E versions are retained for optional live-server testing but do not run by default.

Tags

TagCountMeaning
@smoke78Critical paths — runs on PRs via --grep @smoke
@slow2File processing / queue waits
@external2Requires external API access (Hardcover in practice)
untagged39Run on main push (full suite) but skipped on PR smoke runs

Untagged specs (command palette, multi-user flows, WebSocket events) validate behavior that doesn't need to block every PR but must pass on main.

Running Tests

Playwright global setup waits for the API, clears BullMQ queue history from Redis, resets the database, and then seeds fresh API keys so queue/admin diagnostics start from a deterministic state.

Docker mode (recommended):

bash
./scripts/test-e2e.sh                       # all tests
./scripts/test-e2e.sh --grep @smoke         # smoke only
./scripts/test-e2e.sh tests/e2e/auth.spec.ts  # single file

Dev-server mode (faster iteration):

bash
docker compose -f docker-compose.test.yml up -d --wait
cp .env.test.example .env.test
vp run -F @libris/e2e e2e                          # all tests
cd tests/e2e && vp exec playwright test --grep @smoke   # smoke only
cd tests/e2e && vp exec playwright test --ui            # interactive Playwright UI

Infrastructure

FilePurpose
playwright.config.tsConfig: 1 worker, retries in CI, reporters, webServer auto-start
global-setup.tsWaits for API health, resets DB (delete all rows), seeds API key
auth.setup.tsLogs in via API, saves session cookie to .auth/user.json
fixtures.tsCustom fixtures: authedPage (pre-authenticated page)
helpers/index.tsseedOrganizedBook(), deleteAllBooks(), waitForJob(), goPath(), etc.

Database Seeding

Tests seed data via direct PostgreSQL queries (not API calls), using the postgres client from helpers/index.ts. Each spec's beforeEach cleans up its own data.

Debugging

  • Screenshots/videos/traces on failure: tests/e2e/test-results/
  • HTML report: tests/e2e/playwright-report/index.html
  • Debug mode: PWDEBUG=1 ./scripts/test-e2e.sh -g "test name"

API Exploration (Bruno)

The bruno/ directory contains a Bruno API collection auto-generated from the OpenAPI spec. It provides a ready-to-use set of all API endpoints for manual testing and exploration.

Opening the Collection

GUI: Open Bruno → Open Collection → select the bruno/ folder → pick the Local environment from the top-right dropdown.

CLI:

bash
bru run --env Local bruno/            # Run all requests
bru run --env Local bruno/library/    # Run a specific folder
bru run --env Local bruno/health/     # Quick health check

Environments

EnvironmentFileBase URL
Localbruno/environments/Local.bruhttp://localhost:3000

Requests use which resolves from the selected environment.

Regenerating the Collection

When API routes change, regenerate the collection from the OpenAPI spec:

bash
vp run bruno:import

This runs scripts/bruno-import.sh which:

  1. Starts the Hono dev server if not already running (DB/Redis not required)
  2. Fetches the OpenAPI spec from /_docs/openapi.json
  3. Imports into bruno/ grouped by OpenAPI tags
  4. Removes internal/test endpoints
  5. Preserves existing environment files

The collection is committed to git so it's available immediately after cloning.

Structure

Requests are organized by OpenAPI tag into folders:

FolderEndpoints
auth/Setup, API key CRUD
books/Approve metadata, delete, get candidates
credentials/Service credential management
dashboard/Dashboard data
hardcover/Sync status, trigger sync, sync log
health/Health check
inbox/List, get, upload, rescan, cover images, status
jobs/Queue status, failed jobs, retry
library/List, get, update, covers, downloads, refetch, reorg
Reading_Status/Status counts, filtered lists
search/Command palette suggestions
settings/Get/update settings
stats/Reading statistics

Unit Tests (Vitest)

  • API service config: services/api-hono/vite.config.ts (test block — PGlite in-memory DB, mocked env)
  • Web app config: apps/web/vite.config.ts (vue()-only plugin set under process.env.VITEST so shallowMount sees raw .u-badge HTML instead of stubbed components)
  • Run: vp run test (root aggregator, recursive across workspaces) or vp run -F @libris/api-hono test (API only)

Test config lives in each workspace's vite.config.ts test block, per Vite+ guidance — there are no vitest.config.ts files.

API Unit/Integration Test Files

Paths relative to services/api-hono/. Test DB uses in-memory PGlite with mocked BullMQ queues.

FileCoverage
src/db/db.test.tsDatabase schema, migrations, and query helpers
src/env.test.tsEnvironment variable parsing (Redis URL, required vars, defaults)
src/lib/epub/embed-metadata.test.tsEPUB metadata embedding (OPF rewriting)
src/lib/hardcover/matching.test.tsHardcover ISBN / title matching for sync linkage
src/lib/metadata/clients/metadata-clients.test.tsExternal metadata API clients (MSW mocked)
src/lib/metadata/extractors/epub.test.tsEPUB metadata extraction (OPF parsing, cover detection)
src/lib/metadata/sanitize.test.tsHTML stripping and metadata field sanitization
src/lib/reading-status.test.tsReading status derivation from KoSync progress
src/middleware/rate-limit.test.tsPer-IP tiered rate limiting (auth / keyCreation / general)
src/routes/api/books.test.ts/api/books/* approve, delete, candidates (integration)
src/routes/api/inbox.test.ts/api/inbox/* list, detail, approve, delete (integration)
src/routes/api/library.test.ts/api/library/* list, detail, covers, downloads (integration)
src/routes/api/settings.test.ts/api/settings/* get/update including combined status endpoint
src/routes/opds.test.tsOPDS feed endpoints (integration, Hono test client + PGlite)
src/services/queue-diagnostics.test.tsBullMQ aggregation for home/settings diagnostics
src/services/settings.test.tsApp settings service CRUD
src/shared/checksum.test.tsFile checksum helpers used by the ingestion pipeline
src/shared/kosync-auth.test.tsKoSync header-based auth (x-auth-user / x-auth-key)
src/shared/request-ip.test.tsClient IP extraction with and without TRUST_PROXY_HEADERS
src/shared/route-policy.test.tsRoute auth policy lookup table (public/api-key/admin/opds/kosync)
src/workers/book-detected.test.tsBOOK_DETECTED worker: checksum, format detect, dedup
src/workers/book-parse-file.test.tsBOOK_PARSE_FILE worker: metadata extraction orchestration
src/workers/cleanup-orphaned-files.test.tsScheduled orphan-file cleanup worker

Web Unit Test Files

Paths relative to apps/web/. Config in apps/web/vitest.config.ts uses the Vue plugin with Nuxt meta stubs.

FileCoverage
app/components/MetadataFieldPicker.test.tsField-by-field candidate picker logic and validation
app/composables/useServerEvents.test.tsWebSocket event subscriber lifecycle and handler registration

When reproducing queue/admin diagnostics issues against local dev servers, clear Redis queue history as well as Postgres state:

bash
vp run -F @libris/api-hono reset:bullmq

That command deletes only Libris BullMQ keys, which keeps Home and Settings queue diagnostics aligned with a fresh local reset.

CI Pipeline

See ci-cd.md for the full CI workflow. Summary:

  • PRs: @smoke E2E tests only
  • main push: Full E2E suite
  • Test results posted as PR comments