This commit is contained in:
+48
-11
@@ -1,9 +1,10 @@
|
||||
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 { mkdirSync, writeFileSync } from 'node:fs';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { join } from 'node:path';
|
||||
import { createOwnedRunDir } from '../scripts/e2e-run-dir.mjs';
|
||||
|
||||
// __dirname under ESM.
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
@@ -11,7 +12,8 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = resolve(__dirname, '..');
|
||||
|
||||
// Dedicated E2E port — avoids clashing with a dev server on 9876.
|
||||
const PORT = Number(process.env.E2E_PORT ?? 8788);
|
||||
if (!process.env.E2E_PORT) process.env.E2E_PORT = execFileSync(process.execPath, ['-e', "const s=require('net').createServer();s.listen(0,'127.0.0.1',()=>{console.log(s.address().port);s.close()})"], { encoding: 'utf8' }).trim();
|
||||
const PORT = Number(process.env.E2E_PORT);
|
||||
const BASE_URL = `http://127.0.0.1:${PORT}`;
|
||||
|
||||
// Isolated, throwaway data + workspace dirs so the E2E never touches real data
|
||||
@@ -19,23 +21,43 @@ const BASE_URL = `http://127.0.0.1:${PORT}`;
|
||||
// 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
|
||||
// dir per evaluation and specs could open a different DB from the webServer
|
||||
// (→ "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.
|
||||
// E2E_RUN_OWNED が立っているのに実行用ディレクトリが無い状態は、手で
|
||||
// 環境変数を export したときにしか作れない。ここで先に落とす: 一時
|
||||
// ディレクトリを 1 つでも作った後に投げると、config が完走せず
|
||||
// globalTeardown も走らないため、作った分が掃除されず残る。
|
||||
if (process.env.E2E_RUN_OWNED === '1' && !process.env.E2E_RUN_TMP) {
|
||||
throw new Error('E2E_RUN_OWNED=1 but E2E_RUN_TMP is not set');
|
||||
}
|
||||
const cache = join(repoRoot, 'node_modules', '.cache');
|
||||
mkdirSync(cache, { recursive: true });
|
||||
if (!process.env.E2E_TMP) {
|
||||
process.env.E2E_TMP = mkdtempSync(join(tmpdir(), 'maestro-e2e-'));
|
||||
const ownedData = createOwnedRunDir(cache, 'maestro-e2e-data-');
|
||||
process.env.E2E_TMP = ownedData.directory;
|
||||
process.env.E2E_DATA_TOKEN = ownedData.token;
|
||||
}
|
||||
const e2eTmp = process.env.E2E_TMP;
|
||||
if (process.env.E2E_RUN_OWNED !== '1') {
|
||||
const ownedRun = createOwnedRunDir(cache, 'maestro-e2e-run-');
|
||||
process.env.E2E_RUN_TMP = ownedRun.directory;
|
||||
process.env.E2E_RUN_TOKEN = ownedRun.token;
|
||||
process.env.E2E_RUN_OWNED = '1';
|
||||
}
|
||||
process.env.E2E_RUN_PREFIX = 'maestro-e2e-run-';
|
||||
// 冒頭のガードが「OWNED=1 なのに未設定」を弾き、上の分岐が「未 OWNED」を
|
||||
// 必ず設定する。したがってここでは必ず値がある(TS はこの 2 段を追えない)。
|
||||
const e2eRunTmp = process.env.E2E_RUN_TMP!;
|
||||
process.env.E2E_CACHE_ROOT = cache;
|
||||
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:
|
||||
@@ -51,8 +73,17 @@ writeFileSync(join(__dirname, '.e2e-db-path'), DB_PATH);
|
||||
// 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.
|
||||
// 3) Bash outbound network allowed — WITHOUT this the webServer refuses to boot
|
||||
// on any host where bwrap cannot create a user namespace. safety.bash_sandbox
|
||||
// defaults to `auto`, and worker-bootstrap.ts (#1235) treats "network blocking
|
||||
// is configured but cannot take effect" as a startup error rather than a
|
||||
// silent downgrade. The dev container hits exactly that (the bwrap binary is
|
||||
// present, `--unshare-all` is denied), and whether a CI runner does depends on
|
||||
// its container settings — pinning this makes the boot outcome the same
|
||||
// everywhere instead of a property of the host. Nothing is actually opened up:
|
||||
// these specs never reach an LLM, so no agent Bash command ever runs.
|
||||
const E2E_CONFIG = join(e2eTmp, 'e2e-config.yaml');
|
||||
writeFileSync(E2E_CONFIG, 'ssh:\n enabled: true\n');
|
||||
writeFileSync(E2E_CONFIG, 'ssh:\n enabled: true\nsafety:\n bash_allow_network: true\n');
|
||||
|
||||
export default defineConfig({
|
||||
testDir: resolve(__dirname, 'e2e'),
|
||||
@@ -63,6 +94,7 @@ export default defineConfig({
|
||||
workers: 1,
|
||||
retries: 0,
|
||||
reporter: [['list']],
|
||||
globalTeardown: resolve(__dirname, 'e2e-global-teardown.ts'),
|
||||
|
||||
use: {
|
||||
baseURL: BASE_URL,
|
||||
@@ -83,11 +115,15 @@ export default defineConfig({
|
||||
},
|
||||
],
|
||||
|
||||
// Build before booting so a plain `npm run test:e2e` never serves a stale or
|
||||
// half-cleaned ui/dist tree. Previously the server could observe ui/dist/
|
||||
// after Vite emptied it but before index.html was emitted, causing sendFile
|
||||
// ENOENT failures when E2E and a build ran near each other.
|
||||
// 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).
|
||||
// The webServer command builds both dist trees before starting Node.
|
||||
webServer: {
|
||||
command: 'node dist/main.js',
|
||||
command: 'node scripts/e2e-web-server.mjs',
|
||||
cwd: repoRoot,
|
||||
url: `${BASE_URL}/ui`,
|
||||
reuseExistingServer: false,
|
||||
@@ -97,6 +133,7 @@ export default defineConfig({
|
||||
env: {
|
||||
...process.env,
|
||||
PORT: String(PORT),
|
||||
E2E_RUN_TMP: e2eRunTmp,
|
||||
DB_PATH,
|
||||
WORKTREE_DIR,
|
||||
// Pin the config to the throwaway E2E file so the run never inherits a
|
||||
|
||||
Reference in New Issue
Block a user