Skip to content

Commit b705930

Browse files
MyNameIsEdiclaude
andauthored
fix(server): eliminate ~2-min test startup delay on Windows (#122)
* fix(server): eliminate ~2-min test startup delay on Windows Replace `npx playwright` with a direct `node @playwright/test/cli.js` spawn in both the /api/playwright/run and /api/run-dynamic-test handlers. npx + cmd.exe shell resolution was adding 30–90 s of overhead before any test code ran on Windows. Also fire the Render warmup ping concurrently with Playwright startup (was sequential) and cap its timeout at 30 s (was 55 s), removing a second source of blocking delay. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * style: fix prettier formatting in CHANGELOG, README, wiki Home Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 3cccdac commit b705930

4 files changed

Lines changed: 56 additions & 40 deletions

File tree

.github/wiki/Home.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ For the full documentation see the [README](https://github.com/MyNameIsEdi/open-
88

99
## Quick links
1010

11-
| Topic | Description |
12-
|---|---|
13-
| [Quick Start](https://github.com/MyNameIsEdi/open-qa#-quick-start) | Clone, install, configure LLM, run |
14-
| [Playwright Dashboard](https://github.com/MyNameIsEdi/open-qa#-features) | Run tests, stream live output, view KPIs |
15-
| [CLI Agents](https://github.com/MyNameIsEdi/open-qa#-cli-agents) | Self-healing, Auto-POM, Bug Triage, A11y, Visual Regression, Data Gen |
16-
| [Switching LLMs](https://github.com/MyNameIsEdi/open-qa#-switching-llm-providers) | Gemini ↔ Ollama toggle |
17-
| [Contributing](https://github.com/MyNameIsEdi/open-qa/blob/main/CONTRIBUTING.md) | PR guidelines, code style, branch naming |
18-
| [Changelog](https://github.com/MyNameIsEdi/open-qa/blob/main/CHANGELOG.md) | Release notes |
11+
| Topic | Description |
12+
| --------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
13+
| [Quick Start](https://github.com/MyNameIsEdi/open-qa#-quick-start) | Clone, install, configure LLM, run |
14+
| [Playwright Dashboard](https://github.com/MyNameIsEdi/open-qa#-features) | Run tests, stream live output, view KPIs |
15+
| [CLI Agents](https://github.com/MyNameIsEdi/open-qa#-cli-agents) | Self-healing, Auto-POM, Bug Triage, A11y, Visual Regression, Data Gen |
16+
| [Switching LLMs](https://github.com/MyNameIsEdi/open-qa#-switching-llm-providers) | Gemini ↔ Ollama toggle |
17+
| [Contributing](https://github.com/MyNameIsEdi/open-qa/blob/main/CONTRIBUTING.md) | PR guidelines, code style, branch naming |
18+
| [Changelog](https://github.com/MyNameIsEdi/open-qa/blob/main/CHANGELOG.md) | Release notes |
1919

2020
---
2121

@@ -33,4 +33,4 @@ Browser (React 18 + Vite) → Express API (port 3001) → Gemini / Ollama
3333

3434
---
3535

36-
*This page is auto-synced from `.github/wiki/Home.md` on every push to `main`.*
36+
_This page is auto-synced from `.github/wiki/Home.md` on every push to `main`._

CHANGELOG.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ All notable changes to this project will be documented in this file.
77
### Added
88

99
- **Smarter pixel agents** — office canvas agents now react to four live signals instead of just test-run state:
10-
1. *Chat-reactive*: `agentStatuses` ('working'/'waiting') is wired to `setAgentActive` so pixel agents walk to their desk and type whenever they are responding to a chat message, and wander again when idle (`PlaywrightDashboard.tsx`).
11-
2. *Emotional reactions*: when a test run completes, all agents briefly show a green checkmark bubble (pass) or red ✗ bubble (fail) and celebrate by staying at their desks for 2.5 s (`officeState.ts`, `PlaywrightDashboard.tsx`).
12-
3. *Handoff bubbles*: in multi-agent chat, the finishing agent gets a waiting bubble on `turn_done` — a visual "passing the baton" moment before the next agent activates (`PlaywrightDashboard.tsx`).
13-
4. *New bubble types*`react_pass` (light-green bg, bright checkmark) and `react_fail` (pink bg, red ×) pixel-art speech bubbles with auto-fade timer, matching the existing sprite pipeline (`types.ts`, `spriteData.ts`, `renderer.ts`).
10+
1. _Chat-reactive_: `agentStatuses` ('working'/'waiting') is wired to `setAgentActive` so pixel agents walk to their desk and type whenever they are responding to a chat message, and wander again when idle (`PlaywrightDashboard.tsx`).
11+
2. _Emotional reactions_: when a test run completes, all agents briefly show a green checkmark bubble (pass) or red ✗ bubble (fail) and celebrate by staying at their desks for 2.5 s (`officeState.ts`, `PlaywrightDashboard.tsx`).
12+
3. _Handoff bubbles_: in multi-agent chat, the finishing agent gets a waiting bubble on `turn_done` — a visual "passing the baton" moment before the next agent activates (`PlaywrightDashboard.tsx`).
13+
4. _New bubble types_`react_pass` (light-green bg, bright checkmark) and `react_fail` (pink bg, red ×) pixel-art speech bubbles with auto-fade timer, matching the existing sprite pipeline (`types.ts`, `spriteData.ts`, `renderer.ts`).
1414
- **SQLite run history** — replaces per-run JSON files in `test-results/runs/` with a
1515
single `better-sqlite3` database (`test-results/runs.db`). `archiveRun()` INSERTs into
1616
the DB; `/api/playwright/history`, `/api/playwright/results/:runId`, and
@@ -32,6 +32,16 @@ All notable changes to this project will be documented in this file.
3232
(`RUN_HISTORY_KEY`, seed-on-mount effect, write-on-change effect) to prevent stale
3333
pre-migration data surfacing on page load (`ui/src/pages/PlaywrightDashboard.tsx`).
3434

35+
### Performance
36+
37+
- **Fast Playwright startup** — replaced `npx playwright` with a direct
38+
`node node_modules/@playwright/test/cli.js` spawn in both `/api/playwright/run`
39+
and `/api/run-dynamic-test`. Eliminates `npx` + shell resolution overhead that
40+
caused ~2-minute delays before tests began on Windows (`server/index.ts`).
41+
- **Non-blocking remote warmup** — warmup HTTP ping to Render/remote baseUrl now
42+
fires concurrently with Playwright startup instead of blocking it; timeout
43+
reduced from 55 s to 30 s (`server/index.ts`).
44+
3545
### Fixed
3646

3747
- **Stop button race condition** — added `runAbortRef = useRef<AbortController | null>(null)`

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Type a request in the chat. **Edi M**, the Team Manager, analyses your intent an
3131

3232
| | Feature | Description |
3333
| ---- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
34-
| 🏢 | **2D Pixel-Art Office** | 7 specialist agents at animated desks — agents walk to their desk and type live while responding to chat, celebrate with checkmark bubbles on passing runs, show red ✗ bubbles on failures, and display handoff bubbles when passing work between agents in multi-turn conversations |
34+
| 🏢 | **2D Pixel-Art Office** | 7 specialist agents at animated desks — agents walk to their desk and type live while responding to chat, celebrate with checkmark bubbles on passing runs, show red ✗ bubbles on failures, and display handoff bubbles when passing work between agents in multi-turn conversations |
3535
| 🤖 | **Edi M — Team Manager** | Orchestrator agent that analyses your request and routes it to the right specialist |
3636
| 🤝 | **Multi-Agent Collaboration** | Tag two or more specialists (or @Edi M) and they iterate: primary drafts → critic reviews → primary refines → manager delivers the polished synthesis. Each turn streams into its own chat bubble with a role pill (primary / critic / synthesis). Chat history and image attachments are forwarded to the initial primary turn so vision prompts and follow-up context are preserved across collaborative rounds |
3737
| ✨🦙 | **Gemini + Ollama** | Switch between cloud Gemini and fully-local Ollama models with a single click — no restart required |
@@ -70,7 +70,7 @@ Type a request in the chat. **Edi M**, the Team Manager, analyses your intent an
7070
│ streams token chunks back via SSE │
7171
│ │
7272
│ /api/run-dynamic-test │
73-
│ Phase 1 → spawn npx playwright test → stream stdout
73+
│ Phase 1 → spawn node @playwright/test/cli.js → stream stdout │
7474
│ Phase 2 → read pw-results.json → Edi M AI summary │
7575
│ classify failures → stream summary_chunk events │
7676
└──────────────────────────────────────────────────────────────────────┘

server/index.ts

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,7 +1212,7 @@ app.post('/api/playwright/run', async (req, res) => {
12121212
};
12131213
env.PW_RUNTIME_CONFIG = JSON.stringify(runtimeConfig);
12141214

1215-
send(`[INFO] Running: npx playwright ${args.join(' ')}`);
1215+
send(`[INFO] Running: playwright ${args.join(' ')}`);
12161216
if (runtimeConfig.baseUrl) send(`[INFO] baseUrl → ${runtimeConfig.baseUrl}`);
12171217
if (runtimeConfig.timeout) send(`[INFO] timeout → ${runtimeConfig.timeout}ms`);
12181218
if (runtimeConfig.workers) send(`[INFO] workers → ${runtimeConfig.workers}`);
@@ -1223,35 +1223,40 @@ app.post('/api/playwright/run', async (req, res) => {
12231223
// Archive key: bare filenames joined (clean display in Run History)
12241224
const archiveSpec = bareNames.length > 0 ? bareNames.join(',') : spec;
12251225

1226-
// ── Remote server warmup ──────────────────────────────────────────────────────
1227-
// Render free-tier sleeps after ~15 min of inactivity. A single HTTP GET
1228-
// before spawning Playwright wakes the dyno so tests don't cold-start timeout.
1226+
// ── Remote server warmup (non-blocking) ──────────────────────────────────────
1227+
// Render free-tier sleeps after ~15 min of inactivity. Fire the warmup ping
1228+
// concurrently with Playwright startup so it doesn't add to wall-clock time.
12291229
const targetUrl = (runtimeConfig.baseUrl as string | undefined) ?? '';
12301230
if (targetUrl && !/localhost|127\.0\.0\.1/.test(targetUrl)) {
12311231
send(`[INFO] Warming up ${targetUrl} …`);
1232-
try {
1233-
const httpMod = await import(targetUrl.startsWith('https') ? 'https' : 'http');
1234-
await new Promise<void>((resolve, reject) => {
1235-
const req = httpMod.default.get(targetUrl, (r: { resume: () => void }) => {
1236-
r.resume();
1237-
resolve();
1238-
});
1239-
req.setTimeout(55_000, () => {
1240-
req.destroy();
1241-
reject(new Error('warmup timeout'));
1232+
void (async () => {
1233+
try {
1234+
const httpMod = await import(targetUrl.startsWith('https') ? 'https' : 'http');
1235+
await new Promise<void>((resolve, reject) => {
1236+
const req = httpMod.default.get(targetUrl, (r: { resume: () => void }) => {
1237+
r.resume();
1238+
resolve();
1239+
});
1240+
req.setTimeout(30_000, () => {
1241+
req.destroy();
1242+
reject(new Error('warmup timeout'));
1243+
});
1244+
req.on('error', (e: Error) => reject(e));
12421245
});
1243-
req.on('error', (e: Error) => reject(e));
1244-
});
1245-
send(`[INFO] Server is awake ✓`);
1246-
} catch (err: unknown) {
1247-
send(`[WARN] Warmup failed: ${String(err)} — tests will proceed but may be slow`);
1248-
}
1246+
send(`[INFO] Server is awake ✓`);
1247+
} catch (err: unknown) {
1248+
send(`[WARN] Warmup failed: ${String(err)} — tests may be slow`);
1249+
}
1250+
})();
12491251
}
12501252

1251-
const child = spawn('npx', ['playwright', ...args], {
1253+
// Use the local playwright CLI directly — avoids npx resolution overhead
1254+
// (saves 30-90s on Windows where npx+shell startup is expensive).
1255+
const pwCli = path.join(root, 'node_modules', '@playwright', 'test', 'cli.js');
1256+
const child = spawn(process.execPath, [pwCli, ...args], {
12521257
cwd: root,
12531258
env,
1254-
shell: process.platform === 'win32',
1259+
shell: false,
12551260
});
12561261

12571262
// Capture log lines so they're (a) persisted with the archived run and
@@ -1571,7 +1576,7 @@ app.post('/api/run-dynamic-test', async (req, res) => {
15711576
try {
15721577
await fs.writeFile(tmpFile, authorLine + code, 'utf-8');
15731578
sendStr(`[INFO] Dynamic spec written by ${agentName ?? 'agent'}`);
1574-
sendStr('[INFO] Running: npx playwright test _dynamic_agent_test.spec.ts');
1579+
sendStr('[INFO] Running: playwright test _dynamic_agent_test.spec.ts');
15751580
} catch (err: unknown) {
15761581
sendStr(`[ERROR] Failed to write test file: ${String(err)}`);
15771582
sendStr('[DONE] Finished with exit code 1');
@@ -1585,10 +1590,11 @@ app.post('/api/run-dynamic-test', async (req, res) => {
15851590
PW_RUNTIME_CONFIG: JSON.stringify({ testMatch: ['**/_dynamic_agent_test.spec.ts'] }),
15861591
};
15871592

1588-
const child = spawn('npx', ['playwright', 'test'], {
1593+
const pwCli = path.join(root, 'node_modules', '@playwright', 'test', 'cli.js');
1594+
const child = spawn(process.execPath, [pwCli, 'test'], {
15891595
cwd: root,
15901596
env,
1591-
shell: process.platform === 'win32',
1597+
shell: false,
15921598
});
15931599

15941600
// Capture every log line so we can persist them with the archived run.

0 commit comments

Comments
 (0)