@hasna/testers 0.0.8 → 0.0.11

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.
@@ -17,4 +17,5 @@ export declare function updateResult(id: string, updates: Partial<{
17
17
  costCents: number;
18
18
  }>): Result;
19
19
  export declare function getResultsByRun(runId: string): Result[];
20
+ export declare function countResultsByRun(runId: string): number;
20
21
  //# sourceMappingURL=results.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"results.d.ts","sourceRoot":"","sources":["../../src/db/results.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,MAAM,EAEX,KAAK,YAAY,EAElB,MAAM,mBAAmB,CAAC;AAG3B,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,MAAM,CAkBT;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAanD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAMnD;AAED,wBAAgB,YAAY,CAC1B,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,YAAY,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC,GACD,MAAM,CA+CR;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAEvD"}
1
+ {"version":3,"file":"results.d.ts","sourceRoot":"","sources":["../../src/db/results.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,MAAM,EAEX,KAAK,YAAY,EAElB,MAAM,mBAAmB,CAAC;AAG3B,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,MAAM,CAkBT;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAanD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAMnD;AAED,wBAAgB,YAAY,CAC1B,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,YAAY,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC,GACD,MAAM,CA+CR;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAEvD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMvD"}
package/dist/db/runs.d.ts CHANGED
@@ -4,6 +4,7 @@ export declare function createRun(input: CreateRunInput & {
4
4
  }): Run;
5
5
  export declare function getRun(id: string): Run | null;
6
6
  export declare function listRuns(filter?: RunFilter): Run[];
7
+ export declare function countRuns(filter?: RunFilter): number;
7
8
  export declare function updateRun(id: string, updates: Partial<RunRow>): Run;
8
9
  export declare function deleteRun(id: string): boolean;
9
10
  //# sourceMappingURL=runs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runs.d.ts","sourceRoot":"","sources":["../../src/db/runs.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,GAAG,EACR,KAAK,MAAM,EACX,KAAK,cAAc,EACnB,KAAK,SAAS,EAEf,MAAM,mBAAmB,CAAC;AAG3B,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,GAAG,CAoBxE;AAED,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI,CAc7C;AAED,wBAAgB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,GAAG,GAAG,EAAE,CAgClD;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,GAAG,CAmEnE;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAO7C"}
1
+ {"version":3,"file":"runs.d.ts","sourceRoot":"","sources":["../../src/db/runs.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,GAAG,EACR,KAAK,MAAM,EACX,KAAK,cAAc,EACnB,KAAK,SAAS,EAEf,MAAM,mBAAmB,CAAC;AAG3B,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,GAAG,CAoBxE;AAED,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI,CAc7C;AAED,wBAAgB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,GAAG,GAAG,EAAE,CAwClD;AAED,wBAAgB,SAAS,CAAC,MAAM,CAAC,EAAE,SAAS,GAAG,MAAM,CAmBpD;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,GAAG,CAmEnE;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAO7C"}
@@ -4,5 +4,10 @@ export declare function getScenario(id: string): Scenario | null;
4
4
  export declare function getScenarioByShortId(shortId: string): Scenario | null;
5
5
  export declare function listScenarios(filter?: ScenarioFilter): Scenario[];
6
6
  export declare function updateScenario(id: string, input: UpdateScenarioInput, version: number): Scenario;
7
+ export declare function countScenarios(filter?: ScenarioFilter): number;
8
+ export interface StaleScenario extends Scenario {
9
+ lastRunAt: string | null;
10
+ }
11
+ export declare function findStaleScenarios(days: number): StaleScenario[];
7
12
  export declare function deleteScenario(id: string): boolean;
8
13
  //# sourceMappingURL=scenarios.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scenarios.d.ts","sourceRoot":"","sources":["../../src/db/scenarios.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EAEb,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EAGpB,MAAM,mBAAmB,CAAC;AAsB3B,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,CA8BnE;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAmBvD;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAIrE;AAED,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,QAAQ,EAAE,CA6CjE;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,CAoFhG;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAOlD"}
1
+ {"version":3,"file":"scenarios.d.ts","sourceRoot":"","sources":["../../src/db/scenarios.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EAEb,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EAGpB,MAAM,mBAAmB,CAAC;AAsB3B,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,CA8BnE;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAmBvD;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAIrE;AAED,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,QAAQ,EAAE,CAqDjE;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,CAoFhG;AAED,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,MAAM,CA8B9D;AAED,MAAM,WAAW,aAAc,SAAQ,QAAQ;IAC7C,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAmBhE;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAOlD"}
@@ -13,4 +13,5 @@ export declare function createScreenshot(input: {
13
13
  export declare function getScreenshot(id: string): Screenshot | null;
14
14
  export declare function listScreenshots(resultId: string): Screenshot[];
15
15
  export declare function getScreenshotsByResult(resultId: string): Screenshot[];
16
+ export declare function countScreenshots(resultId: string): number;
16
17
  //# sourceMappingURL=screenshots.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"screenshots.d.ts","sourceRoot":"","sources":["../../src/db/screenshots.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,UAAU,EAGhB,MAAM,mBAAmB,CAAC;AAG3B,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B,GAAG,UAAU,CAuBb;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAI3D;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,EAAE,CAM9D;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,EAAE,CAErE"}
1
+ {"version":3,"file":"screenshots.d.ts","sourceRoot":"","sources":["../../src/db/screenshots.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,UAAU,EAGhB,MAAM,mBAAmB,CAAC;AAG3B,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B,GAAG,UAAU,CAuBb;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAI3D;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,EAAE,CAM9D;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,EAAE,CAErE;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMzD"}
package/dist/index.js CHANGED
@@ -513,7 +513,8 @@ __export(exports_runs, {
513
513
  listRuns: () => listRuns,
514
514
  getRun: () => getRun,
515
515
  deleteRun: () => deleteRun,
516
- createRun: () => createRun
516
+ createRun: () => createRun,
517
+ countRuns: () => countRuns
517
518
  });
518
519
  function createRun(input) {
519
520
  const db2 = getDatabase();
@@ -554,7 +555,10 @@ function listRuns(filter) {
554
555
  if (conditions.length > 0) {
555
556
  sql += " WHERE " + conditions.join(" AND ");
556
557
  }
557
- sql += " ORDER BY started_at DESC";
558
+ const sortField = filter?.sort ?? "date";
559
+ const sortDir = filter?.desc === false ? "ASC" : "DESC";
560
+ const orderByCol = sortField === "duration" ? "(CASE WHEN finished_at IS NULL THEN NULL ELSE (julianday(finished_at) - julianday(started_at)) * 86400000 END)" : sortField === "cost" ? "(SELECT COALESCE(SUM(cost_cents), 0) FROM results WHERE run_id = runs.id)" : "started_at";
561
+ sql += ` ORDER BY ${orderByCol} ${sortDir}`;
558
562
  if (filter?.limit) {
559
563
  sql += " LIMIT ?";
560
564
  params.push(filter.limit);
@@ -566,6 +570,24 @@ function listRuns(filter) {
566
570
  const rows = db2.query(sql).all(...params);
567
571
  return rows.map(runFromRow);
568
572
  }
573
+ function countRuns(filter) {
574
+ const db2 = getDatabase();
575
+ const conditions = [];
576
+ const params = [];
577
+ if (filter?.projectId) {
578
+ conditions.push("project_id = ?");
579
+ params.push(filter.projectId);
580
+ }
581
+ if (filter?.status) {
582
+ conditions.push("status = ?");
583
+ params.push(filter.status);
584
+ }
585
+ let sql = "SELECT COUNT(*) as count FROM runs";
586
+ if (conditions.length > 0)
587
+ sql += " WHERE " + conditions.join(" AND ");
588
+ const row = db2.query(sql).get(...params);
589
+ return row.count;
590
+ }
569
591
  function updateRun(id, updates) {
570
592
  const db2 = getDatabase();
571
593
  const existing = getRun(id);
@@ -1033,7 +1055,10 @@ function listScenarios(filter) {
1033
1055
  if (conditions.length > 0) {
1034
1056
  sql += " WHERE " + conditions.join(" AND ");
1035
1057
  }
1036
- sql += " ORDER BY created_at DESC";
1058
+ const sortField = filter?.sort ?? "date";
1059
+ const sortDir = filter?.desc === false ? "ASC" : "DESC";
1060
+ const orderByCol = sortField === "name" ? "name" : sortField === "priority" ? "CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 ELSE 4 END" : "created_at";
1061
+ sql += ` ORDER BY ${orderByCol} ${sortDir}`;
1037
1062
  if (filter?.limit) {
1038
1063
  sql += " LIMIT ?";
1039
1064
  params.push(filter.limit);
@@ -2665,6 +2690,38 @@ async function testWebhook(id) {
2665
2690
  }
2666
2691
  }
2667
2692
 
2693
+ // src/lib/logs-integration.ts
2694
+ async function pushFailedRunToLogs(run, failedResults, scenarios) {
2695
+ const logsUrl = process.env.LOGS_URL;
2696
+ if (!logsUrl)
2697
+ return;
2698
+ const scenarioMap = new Map(scenarios.map((s) => [s.id, s]));
2699
+ const entries = failedResults.map((result) => {
2700
+ const scenario = scenarioMap.get(result.scenarioId);
2701
+ return {
2702
+ level: "error",
2703
+ source: "sdk",
2704
+ service: "testers",
2705
+ message: `[testers] Scenario failed: ${scenario?.name ?? result.scenarioId}${result.error ? ` \u2014 ${result.error}` : ""}`,
2706
+ metadata: {
2707
+ run_id: run.id,
2708
+ scenario_id: result.scenarioId,
2709
+ scenario_name: scenario?.name,
2710
+ url: run.url,
2711
+ status: result.status,
2712
+ duration_ms: result.durationMs
2713
+ }
2714
+ };
2715
+ });
2716
+ try {
2717
+ await fetch(`${logsUrl.replace(/\/$/, "")}/api/logs`, {
2718
+ method: "POST",
2719
+ headers: { "Content-Type": "application/json" },
2720
+ body: JSON.stringify(entries)
2721
+ });
2722
+ } catch {}
2723
+ }
2724
+
2668
2725
  // src/lib/runner.ts
2669
2726
  var eventHandler = null;
2670
2727
  function onRunEvent(handler) {
@@ -2676,14 +2733,26 @@ function emit(event) {
2676
2733
  }
2677
2734
  function withTimeout(promise, ms, label) {
2678
2735
  return new Promise((resolve, reject) => {
2736
+ const warningAt = Math.floor(ms * 0.8);
2737
+ const warningTimer = setTimeout(() => {
2738
+ emit({
2739
+ type: "scenario:timeout_warning",
2740
+ scenarioName: label,
2741
+ timeoutMs: ms,
2742
+ elapsedMs: warningAt
2743
+ });
2744
+ }, warningAt);
2679
2745
  const timer = setTimeout(() => {
2680
- reject(new Error(`Scenario timeout after ${ms}ms: ${label}`));
2746
+ clearTimeout(warningTimer);
2747
+ reject(new Error(`Scenario '${label}' timed out after ${ms}ms. Try: testers run --timeout ${ms * 2} or simplify the scenario steps.`));
2681
2748
  }, ms);
2682
2749
  promise.then((val) => {
2683
2750
  clearTimeout(timer);
2751
+ clearTimeout(warningTimer);
2684
2752
  resolve(val);
2685
2753
  }, (err) => {
2686
2754
  clearTimeout(timer);
2755
+ clearTimeout(warningTimer);
2687
2756
  reject(err);
2688
2757
  });
2689
2758
  });
@@ -2712,6 +2781,7 @@ async function runSingleScenario(scenario, runId, options) {
2712
2781
  const targetUrl = scenario.targetPath ? `${options.url.replace(/\/$/, "")}${scenario.targetPath}` : options.url;
2713
2782
  const scenarioTimeout = scenario.timeoutMs ?? options.timeout ?? config.browser.timeout ?? 60000;
2714
2783
  await page.goto(targetUrl, { timeout: Math.min(scenarioTimeout, 30000) });
2784
+ const stepStartTimes = new Map;
2715
2785
  const agentResult = await withTimeout(runAgentLoop({
2716
2786
  client,
2717
2787
  page,
@@ -2721,6 +2791,16 @@ async function runSingleScenario(scenario, runId, options) {
2721
2791
  runId,
2722
2792
  maxTurns: 30,
2723
2793
  onStep: (stepEvent) => {
2794
+ let stepDurationMs;
2795
+ if (stepEvent.type === "tool_call") {
2796
+ stepStartTimes.set(stepEvent.stepNumber, Date.now());
2797
+ } else if (stepEvent.type === "tool_result") {
2798
+ const startTime = stepStartTimes.get(stepEvent.stepNumber);
2799
+ if (startTime !== undefined) {
2800
+ stepDurationMs = Date.now() - startTime;
2801
+ stepStartTimes.delete(stepEvent.stepNumber);
2802
+ }
2803
+ }
2724
2804
  emit({
2725
2805
  type: `step:${stepEvent.type}`,
2726
2806
  scenarioId: scenario.id,
@@ -2730,7 +2810,8 @@ async function runSingleScenario(scenario, runId, options) {
2730
2810
  toolInput: stepEvent.toolInput,
2731
2811
  toolResult: stepEvent.toolResult,
2732
2812
  thinking: stepEvent.thinking,
2733
- stepNumber: stepEvent.stepNumber
2813
+ stepNumber: stepEvent.stepNumber,
2814
+ stepDurationMs
2734
2815
  });
2735
2816
  }
2736
2817
  }), scenarioTimeout, scenario.name);
@@ -2810,6 +2891,7 @@ async function runBatch(scenarios, options) {
2810
2891
  } catch {}
2811
2892
  return true;
2812
2893
  };
2894
+ const maxRetries = options.retry ?? 0;
2813
2895
  if (parallel <= 1) {
2814
2896
  for (const scenario of sortedScenarios) {
2815
2897
  if (!await canRun(scenario)) {
@@ -2820,7 +2902,13 @@ async function runBatch(scenarios, options) {
2820
2902
  emit({ type: "scenario:error", scenarioId: scenario.id, scenarioName: scenario.name, error: "Dependency failed \u2014 skipped", runId: run.id });
2821
2903
  continue;
2822
2904
  }
2823
- const result = await runSingleScenario(scenario, run.id, options);
2905
+ let result = await runSingleScenario(scenario, run.id, options);
2906
+ let attempt = 1;
2907
+ while ((result.status === "failed" || result.status === "error") && attempt <= maxRetries) {
2908
+ emit({ type: "scenario:start", scenarioId: scenario.id, scenarioName: scenario.name, runId: run.id, retryAttempt: attempt + 1, maxRetries: maxRetries + 1 });
2909
+ result = await runSingleScenario(scenario, run.id, options);
2910
+ attempt++;
2911
+ }
2824
2912
  results.push(result);
2825
2913
  if (result.status === "failed" || result.status === "error") {
2826
2914
  failedScenarioIds.add(scenario.id);
@@ -2867,6 +2955,10 @@ async function runBatch(scenarios, options) {
2867
2955
  emit({ type: "run:complete", runId: run.id });
2868
2956
  const eventType = finalRun.status === "failed" ? "failed" : "completed";
2869
2957
  dispatchWebhooks(eventType, finalRun).catch(() => {});
2958
+ if (finalRun.status === "failed") {
2959
+ const failedResults = results.filter((r) => r.status === "failed" || r.status === "error");
2960
+ pushFailedRunToLogs(finalRun, failedResults, scenarios).catch(() => {});
2961
+ }
2870
2962
  return { run: finalRun, results };
2871
2963
  }
2872
2964
  async function runByFilter(options) {
@@ -3468,13 +3560,28 @@ var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
3468
3560
  var source_default = chalk;
3469
3561
 
3470
3562
  // src/lib/reporter.ts
3471
- function formatTerminal(run, results) {
3563
+ init_database();
3564
+ function useEmoji() {
3565
+ return !process.env["NO_COLOR"] && process.argv.indexOf("--no-color") === -1;
3566
+ }
3567
+ function formatTerminal(run, results, options) {
3472
3568
  const lines = [];
3569
+ const failedOnly = options?.failedOnly ?? false;
3473
3570
  lines.push("");
3474
3571
  lines.push(source_default.bold(` Run ${run.id.slice(0, 8)} \u2014 ${run.url}`));
3475
3572
  lines.push(source_default.dim(` Model: ${run.model} | Parallel: ${run.parallel} | Headed: ${run.headed ? "yes" : "no"}`));
3476
3573
  lines.push("");
3574
+ if (failedOnly) {
3575
+ const passedCount = results.filter((r) => r.status === "passed").length;
3576
+ if (passedCount > 0) {
3577
+ lines.push(source_default.dim(` (${passedCount} passed scenario${passedCount !== 1 ? "s" : ""} hidden \u2014 use without --failed-only to see all)`));
3578
+ lines.push("");
3579
+ }
3580
+ }
3477
3581
  for (const result of results) {
3582
+ if (failedOnly && result.status !== "failed" && result.status !== "error") {
3583
+ continue;
3584
+ }
3478
3585
  const scenario = getScenario(result.scenarioId);
3479
3586
  const name = scenario ? `${scenario.shortId}: ${scenario.name}` : result.scenarioId.slice(0, 8);
3480
3587
  const screenshots = listScreenshots(result.id);
@@ -3482,21 +3589,22 @@ function formatTerminal(run, results) {
3482
3589
  const screenshotCount = screenshots.length;
3483
3590
  let statusIcon;
3484
3591
  let statusColor;
3592
+ const emoji = useEmoji();
3485
3593
  switch (result.status) {
3486
3594
  case "passed":
3487
- statusIcon = source_default.green("PASS");
3595
+ statusIcon = emoji ? "\u2705" : source_default.green("PASS");
3488
3596
  statusColor = source_default.green;
3489
3597
  break;
3490
3598
  case "failed":
3491
- statusIcon = source_default.red("FAIL");
3599
+ statusIcon = emoji ? "\u274C" : source_default.red("FAIL");
3492
3600
  statusColor = source_default.red;
3493
3601
  break;
3494
3602
  case "error":
3495
- statusIcon = source_default.yellow("ERR ");
3603
+ statusIcon = emoji ? "\u26A0\uFE0F " : source_default.yellow("ERR ");
3496
3604
  statusColor = source_default.yellow;
3497
3605
  break;
3498
3606
  default:
3499
- statusIcon = source_default.dim("SKIP");
3607
+ statusIcon = emoji ? "\u23ED\uFE0F " : source_default.dim("SKIP");
3500
3608
  statusColor = source_default.dim;
3501
3609
  break;
3502
3610
  }
@@ -3509,7 +3617,7 @@ function formatTerminal(run, results) {
3509
3617
  }
3510
3618
  }
3511
3619
  lines.push("");
3512
- lines.push(formatSummary(run));
3620
+ lines.push(formatActionableSummary(run, results));
3513
3621
  lines.push("");
3514
3622
  return lines.join(`
3515
3623
  `);
@@ -3521,6 +3629,30 @@ function formatSummary(run) {
3521
3629
  const totalStr = source_default.dim(` (${run.total} total)`);
3522
3630
  return ` ${passedStr}${failedStr}${totalStr} in ${duration}`;
3523
3631
  }
3632
+ function formatActionableSummary(run, results) {
3633
+ const emoji = useEmoji();
3634
+ const passedCount = results.filter((r) => r.status === "passed").length;
3635
+ const failedCount = results.filter((r) => r.status === "failed" || r.status === "error").length;
3636
+ const shortId = run.id.slice(0, 8);
3637
+ const passStr = `${emoji ? "\u2705" : "PASS"} ${passedCount} passed`;
3638
+ const failStr = failedCount > 0 ? ` ${emoji ? "\u274C" : "FAIL"} ${failedCount} failed` : "";
3639
+ const lines = [];
3640
+ lines.push(` ${source_default.bold(passStr)}${failedCount > 0 ? source_default.bold(failStr) : ""}`);
3641
+ if (failedCount > 0) {
3642
+ lines.push(source_default.dim(` retry failed: testers retry ${shortId} | view: testers results ${shortId}`));
3643
+ } else {
3644
+ lines.push(source_default.dim(` view: testers results ${shortId}`));
3645
+ }
3646
+ const totalCostCents = results.reduce((sum, r) => sum + (r.costCents ?? 0), 0);
3647
+ const totalTokens = results.reduce((sum, r) => sum + (r.tokensUsed ?? 0), 0);
3648
+ if (totalTokens > 0) {
3649
+ const costStr = `$${(totalCostCents / 100).toFixed(4)}`;
3650
+ const tokensStr = totalTokens.toLocaleString();
3651
+ lines.push(source_default.dim(` ${emoji ? "\uD83D\uDCB0" : "cost:"} Cost: ${costStr} (${tokensStr} tokens)`));
3652
+ }
3653
+ return lines.join(`
3654
+ `);
3655
+ }
3524
3656
  function formatJSON(run, results) {
3525
3657
  const output = {
3526
3658
  run: {
@@ -3599,6 +3731,15 @@ function formatRunList(runs) {
3599
3731
  return lines.join(`
3600
3732
  `);
3601
3733
  }
3734
+ function getScenarioRunStats(scenarioId) {
3735
+ const db2 = getDatabase();
3736
+ const lastRow = db2.query("SELECT status FROM results WHERE scenario_id = ? ORDER BY created_at DESC LIMIT 1").get(scenarioId);
3737
+ const statsRow = db2.query("SELECT COUNT(*) as total, SUM(CASE WHEN status = 'passed' THEN 1 ELSE 0 END) as passed FROM results WHERE scenario_id = ?").get(scenarioId);
3738
+ return {
3739
+ lastStatus: lastRow ? lastRow.status : null,
3740
+ passRate: statsRow && statsRow.total > 0 ? `${statsRow.passed}/${statsRow.total}` : "\u2014"
3741
+ };
3742
+ }
3602
3743
  function formatScenarioList(scenarios) {
3603
3744
  const lines = [];
3604
3745
  lines.push("");
@@ -3613,7 +3754,21 @@ function formatScenarioList(scenarios) {
3613
3754
  for (const s of scenarios) {
3614
3755
  const priorityColor = s.priority === "critical" ? source_default.red : s.priority === "high" ? source_default.yellow : s.priority === "medium" ? source_default.blue : source_default.dim;
3615
3756
  const tags = s.tags.length > 0 ? source_default.dim(` [${s.tags.join(", ")}]`) : "";
3616
- lines.push(` ${source_default.cyan(s.shortId)} ${s.name} ${priorityColor(s.priority)}${tags}`);
3757
+ let lastStatusIcon = source_default.dim("\u2014");
3758
+ let passRateStr = source_default.dim("\u2014");
3759
+ if (s.id) {
3760
+ const stats = getScenarioRunStats(s.id);
3761
+ if (stats.lastStatus === "passed")
3762
+ lastStatusIcon = source_default.green("\u2713");
3763
+ else if (stats.lastStatus === "failed")
3764
+ lastStatusIcon = source_default.red("\u2717");
3765
+ else if (stats.lastStatus === "error")
3766
+ lastStatusIcon = source_default.yellow("!");
3767
+ else if (stats.lastStatus === "skipped")
3768
+ lastStatusIcon = source_default.dim("~");
3769
+ passRateStr = stats.passRate === "\u2014" ? source_default.dim("\u2014") : source_default.dim(stats.passRate);
3770
+ }
3771
+ lines.push(` ${source_default.cyan(s.shortId)} ${s.name} ${priorityColor(s.priority)}${tags} ${lastStatusIcon} ${passRateStr}`);
3617
3772
  }
3618
3773
  lines.push("");
3619
3774
  return lines.join(`
@@ -4029,26 +4184,146 @@ function detectFramework(dir) {
4029
4184
  return null;
4030
4185
  }
4031
4186
  function getStarterScenarios(framework, projectId) {
4187
+ if (framework.name === "Next.js") {
4188
+ const scenarios2 = [
4189
+ {
4190
+ name: "Homepage loads",
4191
+ description: "Navigate to the homepage and verify it loads correctly. Check that the main heading and content are visible, and there are no console errors.",
4192
+ tags: ["smoke"],
4193
+ priority: "high",
4194
+ projectId
4195
+ },
4196
+ {
4197
+ name: "404 page works",
4198
+ description: "Navigate to a non-existent URL (e.g. /this-page-does-not-exist) and verify the Next.js 404 page renders correctly.",
4199
+ tags: ["smoke"],
4200
+ priority: "medium",
4201
+ projectId
4202
+ },
4203
+ {
4204
+ name: "Navigation links work",
4205
+ description: "Click through the main navigation links and verify each page loads without errors. Check that client-side routing is working correctly.",
4206
+ tags: ["smoke"],
4207
+ priority: "medium",
4208
+ projectId
4209
+ }
4210
+ ];
4211
+ if (framework.features.includes("hasAuth")) {
4212
+ scenarios2.push({
4213
+ name: "Login flow",
4214
+ description: "Navigate to the login page, enter valid credentials, and verify successful authentication and redirect.",
4215
+ tags: ["auth"],
4216
+ priority: "critical",
4217
+ projectId
4218
+ }, {
4219
+ name: "Protected route redirect",
4220
+ description: "Try to access a protected route without authentication and verify you are redirected to the login page.",
4221
+ tags: ["auth"],
4222
+ priority: "high",
4223
+ projectId
4224
+ });
4225
+ }
4226
+ if (framework.features.includes("hasForms")) {
4227
+ scenarios2.push({
4228
+ name: "Form validation",
4229
+ description: "Submit forms with empty/invalid data and verify validation errors appear correctly.",
4230
+ tags: ["forms"],
4231
+ priority: "medium",
4232
+ projectId
4233
+ });
4234
+ }
4235
+ return scenarios2;
4236
+ }
4237
+ if (framework.name === "Vite" || framework.name === "SvelteKit") {
4238
+ const scenarios2 = [
4239
+ {
4240
+ name: "Homepage loads",
4241
+ description: "Navigate to the homepage and verify it loads correctly with no console errors.",
4242
+ tags: ["smoke"],
4243
+ priority: "high",
4244
+ projectId
4245
+ },
4246
+ {
4247
+ name: "Mobile viewport check",
4248
+ description: "Set the viewport to 375x812 (iPhone) and verify the homepage renders correctly without horizontal scrolling or layout issues.",
4249
+ tags: ["responsive"],
4250
+ priority: "medium",
4251
+ projectId
4252
+ },
4253
+ {
4254
+ name: "No console errors",
4255
+ description: "Navigate through the app and verify there are no JavaScript errors or warnings in the browser console.",
4256
+ tags: ["smoke"],
4257
+ priority: "high",
4258
+ projectId
4259
+ }
4260
+ ];
4261
+ if (framework.features.includes("hasAuth")) {
4262
+ scenarios2.push({
4263
+ name: "Login flow",
4264
+ description: "Navigate to the login page, enter valid credentials, and verify successful authentication.",
4265
+ tags: ["auth"],
4266
+ priority: "critical",
4267
+ projectId
4268
+ });
4269
+ }
4270
+ return scenarios2;
4271
+ }
4272
+ if (framework.name === "Nuxt") {
4273
+ const scenarios2 = [
4274
+ {
4275
+ name: "Homepage loads",
4276
+ description: "Navigate to the homepage and verify it loads correctly. Check that the main heading and content are visible.",
4277
+ tags: ["smoke"],
4278
+ priority: "high",
4279
+ projectId
4280
+ },
4281
+ {
4282
+ name: "Navigation works",
4283
+ description: "Click through main navigation links and verify each page loads without errors.",
4284
+ tags: ["smoke"],
4285
+ priority: "medium",
4286
+ projectId
4287
+ },
4288
+ {
4289
+ name: "Mobile viewport check",
4290
+ description: "Set the viewport to 375x812 and verify the homepage renders correctly on mobile.",
4291
+ tags: ["responsive"],
4292
+ priority: "medium",
4293
+ projectId
4294
+ }
4295
+ ];
4296
+ if (framework.features.includes("hasAuth")) {
4297
+ scenarios2.push({
4298
+ name: "Login flow",
4299
+ description: "Navigate to the login page, enter valid credentials, and verify successful authentication.",
4300
+ tags: ["auth"],
4301
+ priority: "critical",
4302
+ projectId
4303
+ });
4304
+ }
4305
+ return scenarios2;
4306
+ }
4032
4307
  const scenarios = [
4033
4308
  {
4034
- name: "Landing page loads",
4035
- description: "Navigate to the landing page and verify it loads correctly with no console errors. Check that the main heading, navigation, and primary CTA are visible.",
4309
+ name: "Homepage loads",
4310
+ description: "Navigate to the homepage and verify it loads correctly with no console errors. Check that the main heading, navigation, and primary CTA are visible.",
4036
4311
  tags: ["smoke"],
4037
4312
  priority: "high",
4038
4313
  projectId
4039
4314
  },
4040
4315
  {
4041
- name: "Navigation works",
4042
- description: "Click through main navigation links and verify each page loads without errors.",
4316
+ name: "Form submit works",
4317
+ description: "Find the main form on the page, fill it in with valid test data, submit it, and verify the success state.",
4043
4318
  tags: ["smoke"],
4044
4319
  priority: "medium",
4045
4320
  projectId
4046
4321
  },
4047
4322
  {
4048
- name: "No console errors",
4049
- description: "Navigate through the main pages and check the browser console for any JavaScript errors or warnings.",
4050
- tags: ["smoke"],
4051
- priority: "high",
4323
+ name: "Mobile viewport check",
4324
+ description: "Set the viewport to 375x812 (iPhone) and verify the homepage renders correctly without horizontal scrolling or layout issues.",
4325
+ tags: ["responsive"],
4326
+ priority: "medium",
4052
4327
  projectId
4053
4328
  }
4054
4329
  ];
@@ -4891,12 +5166,13 @@ function formatCostsTerminal(summary) {
4891
5166
  }
4892
5167
  if (summary.byScenario.length > 0) {
4893
5168
  lines.push("");
4894
- lines.push(source_default.bold(" Top Scenarios by Cost"));
4895
- lines.push(` ${"Scenario".padEnd(40)} ${"Cost".padEnd(12)} ${"Tokens".padEnd(12)} Runs`);
4896
- lines.push(` ${"\u2500".repeat(40)} ${"\u2500".repeat(12)} ${"\u2500".repeat(12)} ${"\u2500".repeat(6)}`);
5169
+ lines.push(source_default.bold(" Scenarios by Cost (most expensive first)"));
5170
+ lines.push(` ${"Scenario".padEnd(40)} ${"Total Cost".padEnd(12)} ${"Avg/Run".padEnd(12)} ${"Runs".padEnd(6)} Tokens`);
5171
+ lines.push(` ${"\u2500".repeat(40)} ${"\u2500".repeat(12)} ${"\u2500".repeat(12)} ${"\u2500".repeat(6)} ${"\u2500".repeat(10)}`);
4897
5172
  for (const s of summary.byScenario) {
4898
5173
  const label = s.name.length > 38 ? s.name.slice(0, 35) + "..." : s.name;
4899
- lines.push(` ${label.padEnd(40)} ${formatDollars(s.costCents).padEnd(12)} ${formatTokens(s.tokens).padEnd(12)} ${s.runs}`);
5174
+ const avgPerRun = s.runs > 0 ? s.costCents / s.runs : 0;
5175
+ lines.push(` ${label.padEnd(40)} ${formatDollars(s.costCents).padEnd(12)} ${formatDollars(avgPerRun).padEnd(12)} ${String(s.runs).padEnd(6)} ${formatTokens(s.tokens)}`);
4900
5176
  }
4901
5177
  }
4902
5178
  lines.push("");
@@ -27,10 +27,23 @@ export declare function getCostSummary(options?: {
27
27
  projectId?: string;
28
28
  period?: "day" | "week" | "month" | "all";
29
29
  }): CostSummary;
30
+ export interface ScenarioCostRow {
31
+ scenarioId: string;
32
+ name: string;
33
+ runCount: number;
34
+ totalCostCents: number;
35
+ avgCostPerRunCents: number;
36
+ }
37
+ export declare function getCostsByScenario(options?: {
38
+ projectId?: string;
39
+ period?: "day" | "week" | "month" | "all";
40
+ }): ScenarioCostRow[];
41
+ export declare function formatCostsByScenarioTerminal(rows: ScenarioCostRow[], period: string): string;
30
42
  export declare function checkBudget(estimatedCostCents: number): {
31
43
  allowed: boolean;
32
44
  warning?: string;
33
45
  };
34
46
  export declare function formatCostsTerminal(summary: CostSummary): string;
35
47
  export declare function formatCostsJSON(summary: CostSummary): string;
48
+ export declare function formatCostsCsv(summary: CostSummary): string;
36
49
  //# sourceMappingURL=costs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"costs.d.ts","sourceRoot":"","sources":["../../src/lib/costs.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7E,UAAU,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzG,aAAa,EAAE,MAAM,CAAC;IACtB,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;CACvB;AA0CD,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;CAC3C,GAAG,WAAW,CAyFd;AAED,wBAAgB,WAAW,CAAC,kBAAkB,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CA+B9F;AAcD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAwChE;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAE5D"}
1
+ {"version":3,"file":"costs.d.ts","sourceRoot":"","sources":["../../src/lib/costs.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7E,UAAU,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzG,aAAa,EAAE,MAAM,CAAC;IACtB,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;CACvB;AA0CD,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;CAC3C,GAAG,WAAW,CAyFd;AAID,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;CAC3C,GAAG,eAAe,EAAE,CAoCpB;AAED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,eAAe,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAyB7F;AAED,wBAAgB,WAAW,CAAC,kBAAkB,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CA+B9F;AAcD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAyChE;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAE5D;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAS3D"}
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/lib/init.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAI7D,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAmDjE;AAID,wBAAgB,mBAAmB,CACjC,SAAS,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,EAC/C,SAAS,EAAE,MAAM,GAChB,mBAAmB,EAAE,CA6DvB;AAID,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;IAC1C,SAAS,EAAE,UAAU,CAAC,OAAO,cAAc,CAAC,EAAE,CAAC;IAC/C,SAAS,EAAE,aAAa,GAAG,IAAI,CAAC;IAChC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,CAuC5D"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/lib/init.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAI7D,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAmDjE;AAID,wBAAgB,mBAAmB,CACjC,SAAS,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,EAC/C,SAAS,EAAE,MAAM,GAChB,mBAAmB,EAAE,CAkNvB;AAID,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;IAC1C,SAAS,EAAE,UAAU,CAAC,OAAO,cAAc,CAAC,EAAE,CAAC;IAC/C,SAAS,EAAE,aAAa,GAAG,IAAI,CAAC;IAChC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,CAuC5D"}
@@ -0,0 +1,7 @@
1
+ import type { Run, Result, Scenario } from "../types/index.js";
2
+ /**
3
+ * Push failed test results to open-logs if LOGS_URL env var is set.
4
+ * No-op when LOGS_URL is not configured.
5
+ */
6
+ export declare function pushFailedRunToLogs(run: Run, failedResults: Result[], scenarios: Scenario[]): Promise<void>;
7
+ //# sourceMappingURL=logs-integration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs-integration.d.ts","sourceRoot":"","sources":["../../src/lib/logs-integration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE/D;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,GAAG,EACR,aAAa,EAAE,MAAM,EAAE,EACvB,SAAS,EAAE,QAAQ,EAAE,GACpB,OAAO,CAAC,IAAI,CAAC,CAiCf"}
@@ -2,13 +2,21 @@ import type { Run, Result, Screenshot } from "../types/index.js";
2
2
  export interface ReportOptions {
3
3
  json?: boolean;
4
4
  verbose?: boolean;
5
+ failedOnly?: boolean;
5
6
  }
6
- export declare function formatTerminal(run: Run, results: Result[]): string;
7
+ export declare function formatTerminal(run: Run, results: Result[], options?: ReportOptions): string;
7
8
  export declare function formatSummary(run: Run): string;
9
+ export declare function formatActionableSummary(run: Run, results: Result[]): string;
8
10
  export declare function formatJSON(run: Run, results: Result[]): string;
9
11
  export declare function getExitCode(run: Run): number;
10
12
  export declare function formatRunList(runs: Run[]): string;
13
+ export interface ScenarioRunStats {
14
+ lastStatus: "passed" | "failed" | "error" | "skipped" | null;
15
+ passRate: string;
16
+ }
17
+ export declare function getScenarioRunStats(scenarioId: string): ScenarioRunStats;
11
18
  export declare function formatScenarioList(scenarios: Array<{
19
+ id?: string;
12
20
  shortId: string;
13
21
  name: string;
14
22
  priority: string;
@@ -1 +1 @@
1
- {"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../../src/lib/reporter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAIjE,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAmDlE;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAU9C;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAoD9D;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAI5C;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM,CA6BjD;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,GAAG,MAAM,CA2BhI;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,GAAG,MAAM,CAqCpF"}
1
+ {"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../../src/lib/reporter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAWjE,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,CAmE3F;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAU9C;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CA4B3E;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAoD9D;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAI5C;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM,CA6BjD;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,IAAI,CAAC;IAC7D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB,CAiBxE;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,KAAK,CAAC;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,GAAG,MAAM,CAwC7I;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,GAAG,MAAM,CAqCpF"}