@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 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-mcp -- testers-mcp
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";
@@ -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,
@@ -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
@@ -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-mcp",
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 { run, results } = await runByFilter({ url, tags, scenarioIds, priority, model, headed, parallel });
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 ${run.id} \u2014 ${run.status}`,
6466
- `URL: ${run.url}`,
6467
- `Total: ${results.length} | Passed: ${passed} | Failed: ${failed} | Skipped: ${skipped}`,
6468
- `Model: ${run.model}`,
6469
- `Started: ${run.startedAt}`,
6470
- run.finishedAt ? `Finished: ${run.finishedAt}` : null
6471
- ].filter(Boolean).join(`
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-mcp:", error);
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",
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",