@hasna/testers 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli/index.js +99 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +74 -0
- package/dist/lib/runner.d.ts +12 -0
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/mcp/index.js +83 -13
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -228,7 +228,7 @@ Screenshots are saved to `~/.testers/screenshots/` organized by:
|
|
|
228
228
|
Install for Claude Code:
|
|
229
229
|
|
|
230
230
|
```bash
|
|
231
|
-
claude mcp add --transport stdio --scope user testers
|
|
231
|
+
claude mcp add --transport stdio --scope user testers -- testers-mcp
|
|
232
232
|
```
|
|
233
233
|
|
|
234
234
|
Available tools: `create_scenario`, `list_scenarios`, `run_scenarios`, `get_results`, `get_screenshots`, and more.
|
package/dist/cli/index.js
CHANGED
|
@@ -3968,6 +3968,79 @@ async function runByFilter(options) {
|
|
|
3968
3968
|
}
|
|
3969
3969
|
return runBatch(scenarios, options);
|
|
3970
3970
|
}
|
|
3971
|
+
function startRunAsync(options) {
|
|
3972
|
+
const config = loadConfig();
|
|
3973
|
+
const model = resolveModel(options.model ?? config.defaultModel);
|
|
3974
|
+
let scenarios;
|
|
3975
|
+
if (options.scenarioIds && options.scenarioIds.length > 0) {
|
|
3976
|
+
const all = listScenarios({ projectId: options.projectId });
|
|
3977
|
+
scenarios = all.filter((s) => options.scenarioIds.includes(s.id) || options.scenarioIds.includes(s.shortId));
|
|
3978
|
+
} else {
|
|
3979
|
+
scenarios = listScenarios({
|
|
3980
|
+
projectId: options.projectId,
|
|
3981
|
+
tags: options.tags,
|
|
3982
|
+
priority: options.priority
|
|
3983
|
+
});
|
|
3984
|
+
}
|
|
3985
|
+
const parallel = options.parallel ?? 1;
|
|
3986
|
+
const run = createRun({
|
|
3987
|
+
url: options.url,
|
|
3988
|
+
model,
|
|
3989
|
+
headed: options.headed,
|
|
3990
|
+
parallel,
|
|
3991
|
+
projectId: options.projectId
|
|
3992
|
+
});
|
|
3993
|
+
if (scenarios.length === 0) {
|
|
3994
|
+
updateRun(run.id, { status: "passed", total: 0, finished_at: new Date().toISOString() });
|
|
3995
|
+
return { runId: run.id, scenarioCount: 0 };
|
|
3996
|
+
}
|
|
3997
|
+
updateRun(run.id, { status: "running", total: scenarios.length });
|
|
3998
|
+
(async () => {
|
|
3999
|
+
const results = [];
|
|
4000
|
+
try {
|
|
4001
|
+
if (parallel <= 1) {
|
|
4002
|
+
for (const scenario of scenarios) {
|
|
4003
|
+
const result = await runSingleScenario(scenario, run.id, options);
|
|
4004
|
+
results.push(result);
|
|
4005
|
+
}
|
|
4006
|
+
} else {
|
|
4007
|
+
const queue = [...scenarios];
|
|
4008
|
+
const running = [];
|
|
4009
|
+
const processNext = async () => {
|
|
4010
|
+
const scenario = queue.shift();
|
|
4011
|
+
if (!scenario)
|
|
4012
|
+
return;
|
|
4013
|
+
const result = await runSingleScenario(scenario, run.id, options);
|
|
4014
|
+
results.push(result);
|
|
4015
|
+
await processNext();
|
|
4016
|
+
};
|
|
4017
|
+
const workers = Math.min(parallel, scenarios.length);
|
|
4018
|
+
for (let i = 0;i < workers; i++) {
|
|
4019
|
+
running.push(processNext());
|
|
4020
|
+
}
|
|
4021
|
+
await Promise.all(running);
|
|
4022
|
+
}
|
|
4023
|
+
const passed = results.filter((r) => r.status === "passed").length;
|
|
4024
|
+
const failed = results.filter((r) => r.status === "failed" || r.status === "error").length;
|
|
4025
|
+
updateRun(run.id, {
|
|
4026
|
+
status: failed > 0 ? "failed" : "passed",
|
|
4027
|
+
passed,
|
|
4028
|
+
failed,
|
|
4029
|
+
total: scenarios.length,
|
|
4030
|
+
finished_at: new Date().toISOString()
|
|
4031
|
+
});
|
|
4032
|
+
emit({ type: "run:complete", runId: run.id });
|
|
4033
|
+
} catch (error) {
|
|
4034
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4035
|
+
updateRun(run.id, {
|
|
4036
|
+
status: "failed",
|
|
4037
|
+
finished_at: new Date().toISOString()
|
|
4038
|
+
});
|
|
4039
|
+
emit({ type: "run:complete", runId: run.id, error: errorMsg });
|
|
4040
|
+
}
|
|
4041
|
+
})();
|
|
4042
|
+
return { runId: run.id, scenarioCount: scenarios.length };
|
|
4043
|
+
}
|
|
3971
4044
|
function estimateCost(model, tokens) {
|
|
3972
4045
|
const costs = {
|
|
3973
4046
|
"claude-haiku-4-5-20251001": 0.1,
|
|
@@ -5442,9 +5515,34 @@ program2.command("delete <id>").description("Delete a scenario").action((id) =>
|
|
|
5442
5515
|
program2.command("run <url> [description]").description("Run test scenarios against a URL").option("-t, --tag <tag>", "Filter by tag (repeatable)", (val, acc) => {
|
|
5443
5516
|
acc.push(val);
|
|
5444
5517
|
return acc;
|
|
5445
|
-
}, []).option("-s, --scenario <id>", "Run specific scenario ID").option("-p, --priority <level>", "Filter by priority").option("--headed", "Run browser in headed mode", false).option("-m, --model <model>", "AI model to use").option("--parallel <n>", "Number of parallel browsers", "1").option("--json", "Output results as JSON", false).option("-o, --output <filepath>", "Write JSON results to file").option("--timeout <ms>", "Timeout in milliseconds").option("--from-todos", "Import scenarios from todos before running", false).option("--project <id>", "Project ID").action(async (url, description, opts) => {
|
|
5518
|
+
}, []).option("-s, --scenario <id>", "Run specific scenario ID").option("-p, --priority <level>", "Filter by priority").option("--headed", "Run browser in headed mode", false).option("-m, --model <model>", "AI model to use").option("--parallel <n>", "Number of parallel browsers", "1").option("--json", "Output results as JSON", false).option("-o, --output <filepath>", "Write JSON results to file").option("--timeout <ms>", "Timeout in milliseconds").option("--from-todos", "Import scenarios from todos before running", false).option("--project <id>", "Project ID").option("-b, --background", "Start run in background and return immediately", false).action(async (url, description, opts) => {
|
|
5446
5519
|
try {
|
|
5447
5520
|
const projectId = resolveProject(opts.project);
|
|
5521
|
+
if (opts.fromTodos) {
|
|
5522
|
+
const result = importFromTodos({ projectId });
|
|
5523
|
+
console.log(chalk4.blue(`Imported ${result.imported} scenarios from todos (${result.skipped} skipped)`));
|
|
5524
|
+
}
|
|
5525
|
+
if (opts.background) {
|
|
5526
|
+
if (description) {
|
|
5527
|
+
createScenario({ name: description, description, tags: ["ad-hoc"], projectId });
|
|
5528
|
+
}
|
|
5529
|
+
const { runId, scenarioCount } = startRunAsync({
|
|
5530
|
+
url,
|
|
5531
|
+
tags: opts.tag.length > 0 ? opts.tag : undefined,
|
|
5532
|
+
scenarioIds: opts.scenario ? [opts.scenario] : undefined,
|
|
5533
|
+
priority: opts.priority,
|
|
5534
|
+
model: opts.model,
|
|
5535
|
+
headed: opts.headed,
|
|
5536
|
+
parallel: parseInt(opts.parallel, 10),
|
|
5537
|
+
timeout: opts.timeout ? parseInt(opts.timeout, 10) : undefined,
|
|
5538
|
+
projectId
|
|
5539
|
+
});
|
|
5540
|
+
console.log(chalk4.green(`Run started in background: ${chalk4.bold(runId.slice(0, 8))}`));
|
|
5541
|
+
console.log(chalk4.dim(` Scenarios: ${scenarioCount}`));
|
|
5542
|
+
console.log(chalk4.dim(` URL: ${url}`));
|
|
5543
|
+
console.log(chalk4.dim(` Check progress: testers results ${runId.slice(0, 8)}`));
|
|
5544
|
+
process.exit(0);
|
|
5545
|
+
}
|
|
5448
5546
|
if (description) {
|
|
5449
5547
|
const scenario = createScenario({
|
|
5450
5548
|
name: description,
|
|
@@ -5475,10 +5573,6 @@ program2.command("run <url> [description]").description("Run test scenarios agai
|
|
|
5475
5573
|
}
|
|
5476
5574
|
process.exit(getExitCode(run2));
|
|
5477
5575
|
}
|
|
5478
|
-
if (opts.fromTodos) {
|
|
5479
|
-
const result = importFromTodos({ projectId });
|
|
5480
|
-
console.log(chalk4.blue(`Imported ${result.imported} scenarios from todos (${result.skipped} skipped)`));
|
|
5481
|
-
}
|
|
5482
5576
|
const { run, results } = await runByFilter({
|
|
5483
5577
|
url,
|
|
5484
5578
|
tags: opts.tag.length > 0 ? opts.tag : undefined,
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export { loadConfig, resolveModel as resolveModelConfig, getDefaultConfig, } fro
|
|
|
12
12
|
export { launchBrowser, getPage, closeBrowser, BrowserPool, installBrowser, } from "./lib/browser.js";
|
|
13
13
|
export { Screenshotter, slugify, generateFilename, getScreenshotDir, ensureDir, } from "./lib/screenshotter.js";
|
|
14
14
|
export { createClient, resolveModel, runAgentLoop, executeTool, BROWSER_TOOLS, } from "./lib/ai-client.js";
|
|
15
|
-
export { runSingleScenario, runBatch, runByFilter, onRunEvent, } from "./lib/runner.js";
|
|
15
|
+
export { runSingleScenario, runBatch, runByFilter, startRunAsync, onRunEvent, } from "./lib/runner.js";
|
|
16
16
|
export type { RunOptions, RunEvent, RunEventHandler } from "./lib/runner.js";
|
|
17
17
|
export { formatTerminal, formatJSON, formatSummary, getExitCode, formatRunList, formatScenarioList, formatResultDetail, } from "./lib/reporter.js";
|
|
18
18
|
export { connectToTodos, pullTasks, taskToScenarioInput, importFromTodos, markTodoDone, } from "./lib/todos-connector.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -2225,6 +2225,79 @@ async function runByFilter(options) {
|
|
|
2225
2225
|
}
|
|
2226
2226
|
return runBatch(scenarios, options);
|
|
2227
2227
|
}
|
|
2228
|
+
function startRunAsync(options) {
|
|
2229
|
+
const config = loadConfig();
|
|
2230
|
+
const model = resolveModel2(options.model ?? config.defaultModel);
|
|
2231
|
+
let scenarios;
|
|
2232
|
+
if (options.scenarioIds && options.scenarioIds.length > 0) {
|
|
2233
|
+
const all = listScenarios({ projectId: options.projectId });
|
|
2234
|
+
scenarios = all.filter((s) => options.scenarioIds.includes(s.id) || options.scenarioIds.includes(s.shortId));
|
|
2235
|
+
} else {
|
|
2236
|
+
scenarios = listScenarios({
|
|
2237
|
+
projectId: options.projectId,
|
|
2238
|
+
tags: options.tags,
|
|
2239
|
+
priority: options.priority
|
|
2240
|
+
});
|
|
2241
|
+
}
|
|
2242
|
+
const parallel = options.parallel ?? 1;
|
|
2243
|
+
const run = createRun({
|
|
2244
|
+
url: options.url,
|
|
2245
|
+
model,
|
|
2246
|
+
headed: options.headed,
|
|
2247
|
+
parallel,
|
|
2248
|
+
projectId: options.projectId
|
|
2249
|
+
});
|
|
2250
|
+
if (scenarios.length === 0) {
|
|
2251
|
+
updateRun(run.id, { status: "passed", total: 0, finished_at: new Date().toISOString() });
|
|
2252
|
+
return { runId: run.id, scenarioCount: 0 };
|
|
2253
|
+
}
|
|
2254
|
+
updateRun(run.id, { status: "running", total: scenarios.length });
|
|
2255
|
+
(async () => {
|
|
2256
|
+
const results = [];
|
|
2257
|
+
try {
|
|
2258
|
+
if (parallel <= 1) {
|
|
2259
|
+
for (const scenario of scenarios) {
|
|
2260
|
+
const result = await runSingleScenario(scenario, run.id, options);
|
|
2261
|
+
results.push(result);
|
|
2262
|
+
}
|
|
2263
|
+
} else {
|
|
2264
|
+
const queue = [...scenarios];
|
|
2265
|
+
const running = [];
|
|
2266
|
+
const processNext = async () => {
|
|
2267
|
+
const scenario = queue.shift();
|
|
2268
|
+
if (!scenario)
|
|
2269
|
+
return;
|
|
2270
|
+
const result = await runSingleScenario(scenario, run.id, options);
|
|
2271
|
+
results.push(result);
|
|
2272
|
+
await processNext();
|
|
2273
|
+
};
|
|
2274
|
+
const workers = Math.min(parallel, scenarios.length);
|
|
2275
|
+
for (let i = 0;i < workers; i++) {
|
|
2276
|
+
running.push(processNext());
|
|
2277
|
+
}
|
|
2278
|
+
await Promise.all(running);
|
|
2279
|
+
}
|
|
2280
|
+
const passed = results.filter((r) => r.status === "passed").length;
|
|
2281
|
+
const failed = results.filter((r) => r.status === "failed" || r.status === "error").length;
|
|
2282
|
+
updateRun(run.id, {
|
|
2283
|
+
status: failed > 0 ? "failed" : "passed",
|
|
2284
|
+
passed,
|
|
2285
|
+
failed,
|
|
2286
|
+
total: scenarios.length,
|
|
2287
|
+
finished_at: new Date().toISOString()
|
|
2288
|
+
});
|
|
2289
|
+
emit({ type: "run:complete", runId: run.id });
|
|
2290
|
+
} catch (error) {
|
|
2291
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
2292
|
+
updateRun(run.id, {
|
|
2293
|
+
status: "failed",
|
|
2294
|
+
finished_at: new Date().toISOString()
|
|
2295
|
+
});
|
|
2296
|
+
emit({ type: "run:complete", runId: run.id, error: errorMsg });
|
|
2297
|
+
}
|
|
2298
|
+
})();
|
|
2299
|
+
return { runId: run.id, scenarioCount: scenarios.length };
|
|
2300
|
+
}
|
|
2228
2301
|
function estimateCost(model, tokens) {
|
|
2229
2302
|
const costs = {
|
|
2230
2303
|
"claude-haiku-4-5-20251001": 0.1,
|
|
@@ -4418,6 +4491,7 @@ export {
|
|
|
4418
4491
|
testWebhook,
|
|
4419
4492
|
taskToScenarioInput,
|
|
4420
4493
|
startWatcher,
|
|
4494
|
+
startRunAsync,
|
|
4421
4495
|
slugify,
|
|
4422
4496
|
shouldRunAt,
|
|
4423
4497
|
shortUuid,
|
package/dist/lib/runner.d.ts
CHANGED
|
@@ -33,4 +33,16 @@ export declare function runByFilter(options: RunOptions & {
|
|
|
33
33
|
run: Run;
|
|
34
34
|
results: Result[];
|
|
35
35
|
}>;
|
|
36
|
+
/**
|
|
37
|
+
* Start a run asynchronously — creates the run record immediately and returns it,
|
|
38
|
+
* then executes scenarios in the background. Poll getRun(id) to check progress.
|
|
39
|
+
*/
|
|
40
|
+
export declare function startRunAsync(options: RunOptions & {
|
|
41
|
+
tags?: string[];
|
|
42
|
+
priority?: string;
|
|
43
|
+
scenarioIds?: string[];
|
|
44
|
+
}): {
|
|
45
|
+
runId: string;
|
|
46
|
+
scenarioCount: number;
|
|
47
|
+
};
|
|
36
48
|
//# sourceMappingURL=runner.d.ts.map
|
package/dist/lib/runner.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/lib/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAW/D,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,gBAAgB,GAAG,eAAe,GAAG,eAAe,GAAG,gBAAgB,GAAG,qBAAqB,GAAG,cAAc,CAAC;IACvH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;AAIxD,wBAAgB,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAEzD;AAMD,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,MAAM,CAAC,CAoFjB;AAED,wBAAsB,QAAQ,CAC5B,SAAS,EAAE,QAAQ,EAAE,EACrB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CA4D1C;AAED,wBAAsB,WAAW,CAC/B,OAAO,EAAE,UAAU,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GACnF,OAAO,CAAC;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAuB1C"}
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/lib/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAW/D,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,gBAAgB,GAAG,eAAe,GAAG,eAAe,GAAG,gBAAgB,GAAG,qBAAqB,GAAG,cAAc,CAAC;IACvH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;AAIxD,wBAAgB,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAEzD;AAMD,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,MAAM,CAAC,CAoFjB;AAED,wBAAsB,QAAQ,CAC5B,SAAS,EAAE,QAAQ,EAAE,EACrB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CA4D1C;AAED,wBAAsB,WAAW,CAC/B,OAAO,EAAE,UAAU,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GACnF,OAAO,CAAC;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAuB1C;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,UAAU,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GACnF;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CA+E1C"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -5891,6 +5891,79 @@ async function runByFilter(options) {
|
|
|
5891
5891
|
}
|
|
5892
5892
|
return runBatch(scenarios, options);
|
|
5893
5893
|
}
|
|
5894
|
+
function startRunAsync(options) {
|
|
5895
|
+
const config = loadConfig();
|
|
5896
|
+
const model = resolveModel(options.model ?? config.defaultModel);
|
|
5897
|
+
let scenarios;
|
|
5898
|
+
if (options.scenarioIds && options.scenarioIds.length > 0) {
|
|
5899
|
+
const all = listScenarios({ projectId: options.projectId });
|
|
5900
|
+
scenarios = all.filter((s) => options.scenarioIds.includes(s.id) || options.scenarioIds.includes(s.shortId));
|
|
5901
|
+
} else {
|
|
5902
|
+
scenarios = listScenarios({
|
|
5903
|
+
projectId: options.projectId,
|
|
5904
|
+
tags: options.tags,
|
|
5905
|
+
priority: options.priority
|
|
5906
|
+
});
|
|
5907
|
+
}
|
|
5908
|
+
const parallel = options.parallel ?? 1;
|
|
5909
|
+
const run = createRun({
|
|
5910
|
+
url: options.url,
|
|
5911
|
+
model,
|
|
5912
|
+
headed: options.headed,
|
|
5913
|
+
parallel,
|
|
5914
|
+
projectId: options.projectId
|
|
5915
|
+
});
|
|
5916
|
+
if (scenarios.length === 0) {
|
|
5917
|
+
updateRun(run.id, { status: "passed", total: 0, finished_at: new Date().toISOString() });
|
|
5918
|
+
return { runId: run.id, scenarioCount: 0 };
|
|
5919
|
+
}
|
|
5920
|
+
updateRun(run.id, { status: "running", total: scenarios.length });
|
|
5921
|
+
(async () => {
|
|
5922
|
+
const results = [];
|
|
5923
|
+
try {
|
|
5924
|
+
if (parallel <= 1) {
|
|
5925
|
+
for (const scenario of scenarios) {
|
|
5926
|
+
const result = await runSingleScenario(scenario, run.id, options);
|
|
5927
|
+
results.push(result);
|
|
5928
|
+
}
|
|
5929
|
+
} else {
|
|
5930
|
+
const queue = [...scenarios];
|
|
5931
|
+
const running = [];
|
|
5932
|
+
const processNext = async () => {
|
|
5933
|
+
const scenario = queue.shift();
|
|
5934
|
+
if (!scenario)
|
|
5935
|
+
return;
|
|
5936
|
+
const result = await runSingleScenario(scenario, run.id, options);
|
|
5937
|
+
results.push(result);
|
|
5938
|
+
await processNext();
|
|
5939
|
+
};
|
|
5940
|
+
const workers = Math.min(parallel, scenarios.length);
|
|
5941
|
+
for (let i = 0;i < workers; i++) {
|
|
5942
|
+
running.push(processNext());
|
|
5943
|
+
}
|
|
5944
|
+
await Promise.all(running);
|
|
5945
|
+
}
|
|
5946
|
+
const passed = results.filter((r) => r.status === "passed").length;
|
|
5947
|
+
const failed = results.filter((r) => r.status === "failed" || r.status === "error").length;
|
|
5948
|
+
updateRun(run.id, {
|
|
5949
|
+
status: failed > 0 ? "failed" : "passed",
|
|
5950
|
+
passed,
|
|
5951
|
+
failed,
|
|
5952
|
+
total: scenarios.length,
|
|
5953
|
+
finished_at: new Date().toISOString()
|
|
5954
|
+
});
|
|
5955
|
+
emit({ type: "run:complete", runId: run.id });
|
|
5956
|
+
} catch (error) {
|
|
5957
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
5958
|
+
updateRun(run.id, {
|
|
5959
|
+
status: "failed",
|
|
5960
|
+
finished_at: new Date().toISOString()
|
|
5961
|
+
});
|
|
5962
|
+
emit({ type: "run:complete", runId: run.id, error: errorMsg });
|
|
5963
|
+
}
|
|
5964
|
+
})();
|
|
5965
|
+
return { runId: run.id, scenarioCount: scenarios.length };
|
|
5966
|
+
}
|
|
5894
5967
|
function estimateCost(model, tokens) {
|
|
5895
5968
|
const costs = {
|
|
5896
5969
|
"claude-haiku-4-5-20251001": 0.1,
|
|
@@ -6343,7 +6416,7 @@ class Scheduler {
|
|
|
6343
6416
|
|
|
6344
6417
|
// src/mcp/index.ts
|
|
6345
6418
|
var server = new McpServer({
|
|
6346
|
-
name: "testers
|
|
6419
|
+
name: "testers",
|
|
6347
6420
|
version: "0.0.1"
|
|
6348
6421
|
});
|
|
6349
6422
|
server.tool("create_scenario", "Create a new test scenario", {
|
|
@@ -6457,18 +6530,15 @@ server.tool("run_scenarios", "Run test scenarios against a URL", {
|
|
|
6457
6530
|
parallel: exports_external.number().optional().describe("Number of parallel workers")
|
|
6458
6531
|
}, async ({ url, tags, scenarioIds, priority, model, headed, parallel }) => {
|
|
6459
6532
|
try {
|
|
6460
|
-
const {
|
|
6461
|
-
const passed = results.filter((r) => r.status === "passed").length;
|
|
6462
|
-
const failed = results.filter((r) => r.status === "failed" || r.status === "error").length;
|
|
6463
|
-
const skipped = results.filter((r) => r.status === "skipped").length;
|
|
6533
|
+
const { runId, scenarioCount } = startRunAsync({ url, tags, scenarioIds, priority, model, headed, parallel });
|
|
6464
6534
|
const text = [
|
|
6465
|
-
`Run
|
|
6466
|
-
`
|
|
6467
|
-
`
|
|
6468
|
-
`
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
].
|
|
6535
|
+
`Run started: ${runId}`,
|
|
6536
|
+
`Scenarios: ${scenarioCount}`,
|
|
6537
|
+
`URL: ${url}`,
|
|
6538
|
+
`Status: running (async)`,
|
|
6539
|
+
``,
|
|
6540
|
+
`Poll with get_run to check progress.`
|
|
6541
|
+
].join(`
|
|
6472
6542
|
`);
|
|
6473
6543
|
return { content: [{ type: "text", text }] };
|
|
6474
6544
|
} catch (error) {
|
|
@@ -6712,6 +6782,6 @@ async function main() {
|
|
|
6712
6782
|
await server.connect(transport);
|
|
6713
6783
|
}
|
|
6714
6784
|
main().catch((error) => {
|
|
6715
|
-
console.error("Failed to start testers
|
|
6785
|
+
console.error("Failed to start testers:", error);
|
|
6716
6786
|
process.exit(1);
|
|
6717
6787
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/testers",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "AI-powered QA testing CLI — spawns cheap AI agents to test web apps with headless browsers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"build:mcp": "bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external @anthropic-ai/sdk --external playwright",
|
|
29
29
|
"build:server": "bun build src/server/index.ts --outdir dist/server --target bun --external @anthropic-ai/sdk --external playwright",
|
|
30
30
|
"build:lib": "bun build src/index.ts --outdir dist --target bun --external playwright --external @anthropic-ai/sdk --external @modelcontextprotocol/sdk",
|
|
31
|
-
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
|
31
|
+
"build:types": "NODE_OPTIONS='--max-old-space-size=8192' tsc --emitDeclarationOnly --outDir dist --skipLibCheck",
|
|
32
32
|
"build:dashboard": "cd dashboard && bun run build",
|
|
33
33
|
"typecheck": "tsc --noEmit",
|
|
34
34
|
"test": "bun test",
|