Testing
VentureKit apps test at two layers:
- Unit — invoke handlers in-process with no AWS, using the helpers from
@venturekit/runtime/testing(mockEvent,mockContext,createTestHandler). Fast, no Docker. See the runtime package. - Integration & end-to-end — boot a real local stack (
vk migrate+vk dev→ Postgres / MinIO / cognito-local) and drive it through the API or a real browser. This is what@venturekit/testingexists for, and what this guide covers.
Scaffold the setup
Section titled “Scaffold the setup”The fastest way to start is the scaffolder, which writes a Playwright config,
a global setup, a reference spec, and patches package.json + .gitignore:
vk generate e2eIt creates:
playwright.config.ts # boots `vk dev`, gates on /_dev/healthe2e/ global-setup.ts # waitForReady + (commented) auth seeding / DB reset health.spec.ts # a passing health check + skipped examples…and adds test:e2e / test:e2e:ui scripts plus @playwright/test and
@venturekit/testing to devDependencies.
| Option | Description | Default |
|---|---|---|
-p, --port <port> | Port the test API runs on (also honoured via E2E_API_PORT) | 4001 |
-f, --force | Overwrite existing files | false |
Then install and run:
pnpm installnpx playwright install # browsers — only needed for UI (page) testspnpm test:e2eThe generated Playwright config
Section titled “The generated Playwright config”Playwright owns the vk dev process via its webServer block, and waits on
the /_dev/health readiness probe before any spec runs:
import { defineConfig } from '@playwright/test';
// The API runs on an isolated test stage/port so E2E never clobbers// your `vk dev` data. Override with E2E_API_PORT.const API_PORT = Number(process.env.E2E_API_PORT ?? 4001);const API_URL = `http://localhost:${API_PORT}`;
export default defineConfig({ testDir: './e2e', globalSetup: './e2e/global-setup.ts', timeout: 30_000, fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, reporter: process.env.CI ? [['github'], ['html', { open: 'never' }]] : 'list', use: { baseURL: API_URL, trace: 'on-first-retry', }, webServer: { // Migrate the isolated `test` DB, then boot the API. `vk dev` does // not auto-migrate, so the migrate step keeps local + CI consistent. command: `vk migrate --env test && vk dev --stage test --port ${API_PORT} --no-watch`, url: `${API_URL}/_dev/health`, reuseExistingServer: !process.env.CI, timeout: 120_000, },});Global setup: readiness, auth, and DB reset
Section titled “Global setup: readiness, auth, and DB reset”global-setup.ts runs once before the suite. It confirms the server is really
a vk dev server, and is where you seed a login or reset the database:
import { createTestUser, truncateAllTables, waitForReady } from '@venturekit/testing';
const API_URL = `http://localhost:${Number(process.env.E2E_API_PORT ?? 4001)}`;
export default async function globalSetup() { await waitForReady({ baseUrl: API_URL });
// Reset the DB between runs (needs @venturekit/data + DATABASE_URL / DB_*): await truncateAllTables();
// Seed a known login if your API declares an `auth` intent: await createTestUser({ baseUrl: API_URL, email: 'admin@example.com', password: 'Passw0rd!', attributes: { tenantId: 'global' }, });}waitForReady()polls/_dev/health— the same probevktooling uses.createTestUser()provisions a cognito-local user with a permanent password over thevk devadmin API. No AWS SDK required.truncateAllTables()clears app tables but preserves the__vk_migrations/__vk_seedsbookkeeping, so migrations don’t re-run.
Write a UI spec
Section titled “Write a UI spec”With the login seeded, specs drive the real UI — the most faithful E2E:
import { test, expect } from '@playwright/test';
test('logs in and lands on the dashboard', async ({ page }) => { await page.goto('/login'); await page.getByLabel('Email').fill('admin@example.com'); await page.getByLabel('Password').fill('Passw0rd!'); await page.getByRole('button', { name: 'Sign in' }).click(); await expect(page).toHaveURL(/\/(inbox|dashboard)/);});The scaffolded health.spec.ts uses Playwright’s request fixture (no
browser), so it runs without npx playwright install:
import { test, expect } from '@playwright/test';
const API_URL = `http://localhost:${Number(process.env.E2E_API_PORT ?? 4001)}`;
test('the API dev server is up', async ({ request }) => { const res = await request.get(`${API_URL}/_dev/health`); expect(res.ok()).toBeTruthy(); expect((await res.json()).vk).toBe('dev-server');});API-level integration tests (vitest)
Section titled “API-level integration tests (vitest)”When you don’t need a browser, own the stack yourself, mint a token, and hit
the API directly. createApiClient() unwraps the { data } envelope and
throws a typed ApiError on non-2xx:
import { afterAll, beforeAll, expect, test } from 'vitest';import { startTestStack, loginAs, createApiClient, type TestStack } from '@venturekit/testing';
let stack: TestStack;
beforeAll(async () => { stack = await startTestStack({ port: 4100, seed: true });}, 180_000);
afterAll(async () => { await stack?.stop();});
test('GET /tenant returns the current tenant', async () => { const { idToken } = await loginAs({ baseUrl: stack.baseUrl, email: 'admin@example.com', password: 'Passw0rd!', }); const api = createApiClient({ baseUrl: stack.baseUrl, token: idToken }); const res = await api.get<{ slug: string }>('/tenant'); expect(res.status).toBe(200); expect(res.data.slug).toBeDefined();});loginAs() needs the optional @aws-sdk/client-cognito-identity-provider
peer. See the @venturekit/testing reference for the full
API.
Run end-to-end tests in CI
Section titled “Run end-to-end tests in CI”Generate a GitHub Actions workflow that runs the suite on every pull request:
vk gha e2eThis writes .github/workflows/e2e.yml. It needs no AWS credentials — the
GitHub-hosted ubuntu-latest runner ships with Docker, which vk dev uses to
start Postgres / MinIO / cognito-local.
| Option | Description | Default |
|---|---|---|
-p, --path <file> | Output path | .github/workflows/e2e.yml |
--branch <branch...> | Branches that also trigger E2E on push | main |
--force | Overwrite an existing workflow file | false |
The generated workflow installs dependencies and Playwright browsers, then
runs pnpm test:e2e — the Playwright webServer block migrates the test DB,
boots vk dev, and waits on /_dev/health before the specs run:
name: e2e
on: pull_request: push: branches: ["main"] workflow_dispatch:
concurrency: group: e2e-${{ github.ref }} cancel-in-progress: true
jobs: e2e: name: end-to-end runs-on: ubuntu-latest timeout-minutes: 20 steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 with: version: 9 - uses: actions/setup-node@v4 with: node-version: '20' cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm run build --if-present - name: Install Playwright browsers run: pnpm exec playwright install --with-deps chromium - name: Run end-to-end tests run: pnpm run test:e2e env: CI: 'true' - name: Upload Playwright report if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: playwright-report path: playwright-report/ retention-days: 7Related
Section titled “Related”- @venturekit/testing — full harness API reference
- CLI: generate —
vk generate e2e - CLI: dev — the local stack these tests drive
- CLI: migrate — how the test DB is prepared
- Authentication — the Cognito model the auth helpers seed
- Deployment — the deploy workflow
vk gha e2ecomplements