121 lines
5.3 KiB
TypeScript
121 lines
5.3 KiB
TypeScript
import { defineConfig, devices } from '@playwright/test';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { dirname, resolve } from 'node:path';
|
|
import { tmpdir } from 'node:os';
|
|
import { mkdtempSync, writeFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
|
|
// __dirname under ESM.
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
// Repo root is one level up from ui/.
|
|
const repoRoot = resolve(__dirname, '..');
|
|
|
|
// Dedicated E2E port — avoids clashing with a dev server on 9876.
|
|
const PORT = Number(process.env.E2E_PORT ?? 8788);
|
|
const BASE_URL = `http://127.0.0.1:${PORT}`;
|
|
|
|
// Isolated, throwaway data + workspace dirs so the E2E never touches real data
|
|
// (real DB lives at ./data/maestro.db). A fresh temp DB also means the personal
|
|
// space is auto-created clean on first /api/local/spaces hit.
|
|
// The temp dir MUST be stable across the multiple times Playwright evaluates
|
|
// this config (main process + worker), otherwise mkdtempSync would mint a new
|
|
// dir per evaluation and the .e2e-db-path handoff file could point at a
|
|
// different DB than the one the webServer actually booted with (→ specs open an
|
|
// empty DB and fail with "no such table: jobs"). Self-seed it into the
|
|
// environment on the FIRST evaluation: subsequent evals in this process reuse
|
|
// it, and the webServer subprocess + worker processes inherit it via env. No
|
|
// caller setup required (plain `npm run test:e2e` works); an explicitly-set
|
|
// E2E_TMP is still honored.
|
|
if (!process.env.E2E_TMP) {
|
|
process.env.E2E_TMP = mkdtempSync(join(tmpdir(), 'maestro-e2e-'));
|
|
}
|
|
const e2eTmp = process.env.E2E_TMP;
|
|
const DB_PATH = join(e2eTmp, 'e2e.db');
|
|
const WORKTREE_DIR = join(e2eTmp, 'workspaces');
|
|
// Expose the throwaway DB path to specs that need to seed engine state which
|
|
// can't be produced without a live LLM (e.g. a job parked for tool approval).
|
|
// Written to a gitignored file next to this config so specs can read it.
|
|
writeFileSync(join(__dirname, '.e2e-db-path'), DB_PATH);
|
|
|
|
// Dedicated, deterministic config for the E2E webServer, handed over via
|
|
// AAO_CONFIG. Two reasons:
|
|
// 1) Isolation — without this, dist/main.js loads the repo-root ./config.yaml
|
|
// if a developer happens to have one (it's gitignored, so present locally
|
|
// but absent in CI). That silently changes E2E behavior between machines
|
|
// (e.g. pointing the worker at a real LLM). Pinning AAO_CONFIG makes the
|
|
// run identical everywhere; everything unset here falls back to defaults.
|
|
// 2) SSH enabled — the settings sub-tab specs assert the space-scoped SSH
|
|
// panel (space-ssh-panel) renders. That testid only mounts once
|
|
// /api/ssh/connections answers 200; with SSH off the route 404s and the
|
|
// panel shows its "SSH disabled" view (no testid). In isolation the spec
|
|
// scraped the brief loading-state render and passed by luck; under full-
|
|
// suite load that window closed and both SSH specs failed. Enabling SSH
|
|
// (needs the MCP_ENCRYPTION_KEY set below) makes the panel deterministic.
|
|
const E2E_CONFIG = join(e2eTmp, 'e2e-config.yaml');
|
|
writeFileSync(E2E_CONFIG, 'ssh:\n enabled: true\n');
|
|
|
|
export default defineConfig({
|
|
testDir: resolve(__dirname, 'e2e'),
|
|
// The build+boot can be slow; keep a roomy per-test budget.
|
|
timeout: 60_000,
|
|
expect: { timeout: 15_000 },
|
|
fullyParallel: false,
|
|
workers: 1,
|
|
retries: 0,
|
|
reporter: [['list']],
|
|
|
|
use: {
|
|
baseURL: BASE_URL,
|
|
headless: true,
|
|
locale: 'ja-JP',
|
|
// This sandbox disables the Chromium sandbox (bwrap/userns off). Launch
|
|
// with --no-sandbox or every browser launch fails to spawn.
|
|
launchOptions: {
|
|
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'],
|
|
},
|
|
trace: 'retain-on-failure',
|
|
},
|
|
|
|
projects: [
|
|
{
|
|
name: 'chromium',
|
|
use: { ...devices['Desktop Chrome'] },
|
|
},
|
|
],
|
|
|
|
// Boot the real orchestrator (worker mode) with auth OFF (the pinned
|
|
// E2E_CONFIG has no auth section => defaults => synthetic 'local' user).
|
|
// dist/ must exist (npm run build:all).
|
|
webServer: {
|
|
command: 'node dist/main.js',
|
|
cwd: repoRoot,
|
|
url: `${BASE_URL}/ui`,
|
|
reuseExistingServer: false,
|
|
timeout: 120_000,
|
|
stdout: 'pipe',
|
|
stderr: 'pipe',
|
|
env: {
|
|
...process.env,
|
|
PORT: String(PORT),
|
|
DB_PATH,
|
|
WORKTREE_DIR,
|
|
// Pin the config to the throwaway E2E file so the run never inherits a
|
|
// developer's repo-root ./config.yaml (see E2E_CONFIG above).
|
|
AAO_CONFIG: E2E_CONFIG,
|
|
AAO_MODE: 'worker',
|
|
LOG_LEVEL: 'warn',
|
|
// Mount the MCP subsystem (gated on a 64-hex MCP_ENCRYPTION_KEY) so the
|
|
// per-space MCP isolation E2E can register/list servers via /api/mcp/*.
|
|
// A fixed throwaway key keeps the run deterministic; the temp DB it
|
|
// encrypts is discarded after the run.
|
|
MCP_ENCRYPTION_KEY: '00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff',
|
|
// Configure a STUB LLM so the first-run SetupWizard never gates the UI
|
|
// (it shows when no model is configured). The endpoint is never actually
|
|
// reached by these specs — they assert UI/DB state, not model output — so
|
|
// a fake host is fine and keeps the E2E self-contained (no caller env).
|
|
OLLAMA_BASE_URL: process.env.OLLAMA_BASE_URL ?? 'http://127.0.0.1:11434/v1',
|
|
OLLAMA_MODEL: process.env.OLLAMA_MODEL ?? 'e2e-stub-model',
|
|
},
|
|
},
|
|
});
|