@hasna/testers 0.0.7 → 0.0.10

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.
@@ -0,0 +1,43 @@
1
+ import { type Browser, type Page } from "playwright";
2
+ import { type ChildProcess } from "child_process";
3
+ /**
4
+ * Check if Lightpanda browser is installed (optional dependency).
5
+ */
6
+ export declare function isLightpandaAvailable(): boolean;
7
+ /**
8
+ * Start Lightpanda as a CDP server on a random available port.
9
+ * Returns the WebSocket endpoint URL.
10
+ */
11
+ export declare function startLightpandaServer(port?: number): Promise<{
12
+ process: ChildProcess;
13
+ wsEndpoint: string;
14
+ }>;
15
+ /**
16
+ * Launch a Lightpanda browser via CDP and return a Playwright Browser instance.
17
+ */
18
+ export declare function launchLightpanda(_options?: {
19
+ viewport?: {
20
+ width: number;
21
+ height: number;
22
+ };
23
+ }): Promise<Browser>;
24
+ /**
25
+ * Create a page from a Lightpanda-connected browser.
26
+ */
27
+ export declare function getLightpandaPage(browser: Browser, options?: {
28
+ viewport?: {
29
+ width: number;
30
+ height: number;
31
+ };
32
+ userAgent?: string;
33
+ locale?: string;
34
+ }): Promise<Page>;
35
+ /**
36
+ * Close a Lightpanda browser and kill the server process.
37
+ */
38
+ export declare function closeLightpanda(browser: Browser): Promise<void>;
39
+ /**
40
+ * Install Lightpanda browser.
41
+ */
42
+ export declare function installLightpanda(): Promise<void>;
43
+ //# sourceMappingURL=browser-lightpanda.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-lightpanda.d.ts","sourceRoot":"","sources":["../../src/lib/browser-lightpanda.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAKzD;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CA4B/C;AAuBD;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC;IAAE,OAAO,EAAE,YAAY,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAwExD;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAC1D,OAAO,CAAC,OAAO,CAAC,CAYlB;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9F,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAerE;AAED;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAcvD"}
@@ -1,4 +1,5 @@
1
1
  import { type Browser, type Page } from "playwright";
2
+ export type BrowserEngine = "playwright" | "lightpanda";
2
3
  interface ViewportSize {
3
4
  width: number;
4
5
  height: number;
@@ -6,6 +7,7 @@ interface ViewportSize {
6
7
  interface LaunchOptions {
7
8
  headless?: boolean;
8
9
  viewport?: ViewportSize;
10
+ engine?: BrowserEngine;
9
11
  }
10
12
  interface PageOptions {
11
13
  viewport?: ViewportSize;
@@ -20,11 +22,13 @@ export declare function launchBrowser(options?: LaunchOptions): Promise<Browser>
20
22
  * Creates a new page in the given browser with optional viewport,
21
23
  * user agent, and locale settings.
22
24
  */
23
- export declare function getPage(browser: Browser, options?: PageOptions): Promise<Page>;
25
+ export declare function getPage(browser: Browser, options?: PageOptions & {
26
+ engine?: BrowserEngine;
27
+ }): Promise<Page>;
24
28
  /**
25
29
  * Closes a browser instance gracefully.
26
30
  */
27
- export declare function closeBrowser(browser: Browser): Promise<void>;
31
+ export declare function closeBrowser(browser: Browser, engine?: BrowserEngine): Promise<void>;
28
32
  /**
29
33
  * A pool of reusable browser instances to avoid the overhead of
30
34
  * launching a new browser for every test scenario.
@@ -34,9 +38,11 @@ export declare class BrowserPool {
34
38
  private readonly maxSize;
35
39
  private readonly headless;
36
40
  private readonly viewport;
41
+ private readonly engine;
37
42
  constructor(size: number, options?: {
38
43
  headless?: boolean;
39
44
  viewport?: ViewportSize;
45
+ engine?: BrowserEngine;
40
46
  });
41
47
  /**
42
48
  * Acquires a browser and page from the pool. Reuses an idle browser
@@ -59,6 +65,6 @@ export declare class BrowserPool {
59
65
  /**
60
66
  * Installs Chromium for Playwright using bunx.
61
67
  */
62
- export declare function installBrowser(): Promise<void>;
68
+ export declare function installBrowser(engine?: BrowserEngine): Promise<void>;
63
69
  export {};
64
70
  //# sourceMappingURL=browser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../src/lib/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,YAAY,CAAC;AAI/D,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,aAAa;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,YAAY,CAAC;CACzB;AAED,UAAU,WAAW;IACnB,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AASD;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAgB7E;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,IAAI,CAAC,CAef;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAOlE;AAED;;;GAGG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAmB;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAe;gBAGtC,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,YAAY,CAAA;KAAE;IAO3D;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,IAAI,CAAA;KAAE,CAAC;IAoC1D;;OAEG;IACH,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAO/B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAShC;AAED;;GAEG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CASpD"}
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../src/lib/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,YAAY,CAAC;AAI/D,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,YAAY,CAAC;AAExD,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,aAAa;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,UAAU,WAAW;IACnB,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AASD;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CA2B7E;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,WAAW,GAAG;IAAE,MAAM,CAAC,EAAE,aAAa,CAAA;CAAE,GACjD,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAY1F;AAED;;;GAGG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAmB;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAe;IAExC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;gBAGrC,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,YAAY,CAAC;QAAC,MAAM,CAAC,EAAE,aAAa,CAAA;KAAE;IAQnF;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,IAAI,CAAA;KAAE,CAAC;IAqC1D;;OAEG;IACH,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAO/B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAShC;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAc1E"}
@@ -33,4 +33,5 @@ export declare function checkBudget(estimatedCostCents: number): {
33
33
  };
34
34
  export declare function formatCostsTerminal(summary: CostSummary): string;
35
35
  export declare function formatCostsJSON(summary: CostSummary): string;
36
+ export declare function formatCostsCsv(summary: CostSummary): string;
36
37
  //# 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;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"}
@@ -5,10 +5,17 @@ export interface ReportOptions {
5
5
  }
6
6
  export declare function formatTerminal(run: Run, results: Result[]): string;
7
7
  export declare function formatSummary(run: Run): string;
8
+ export declare function formatActionableSummary(run: Run, results: Result[]): string;
8
9
  export declare function formatJSON(run: Run, results: Result[]): string;
9
10
  export declare function getExitCode(run: Run): number;
10
11
  export declare function formatRunList(runs: Run[]): string;
12
+ export interface ScenarioRunStats {
13
+ lastStatus: "passed" | "failed" | "error" | "skipped" | null;
14
+ passRate: string;
15
+ }
16
+ export declare function getScenarioRunStats(scenarioId: string): ScenarioRunStats;
11
17
  export declare function formatScenarioList(scenarios: Array<{
18
+ id?: string;
12
19
  shortId: string;
13
20
  name: string;
14
21
  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;CACnB;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAoDlE;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"}
@@ -5,9 +5,11 @@ export interface RunOptions {
5
5
  headed?: boolean;
6
6
  parallel?: number;
7
7
  timeout?: number;
8
+ retry?: number;
8
9
  projectId?: string;
9
10
  apiKey?: string;
10
11
  screenshotDir?: string;
12
+ engine?: "playwright" | "lightpanda";
11
13
  }
12
14
  export interface RunEvent {
13
15
  type: "scenario:start" | "scenario:pass" | "scenario:fail" | "scenario:error" | "screenshot:captured" | "run:complete" | "step:tool_call" | "step:tool_result" | "step:thinking";
@@ -22,6 +24,8 @@ export interface RunEvent {
22
24
  toolResult?: string;
23
25
  thinking?: string;
24
26
  stepNumber?: number;
27
+ retryAttempt?: number;
28
+ maxRetries?: number;
25
29
  }
26
30
  export type RunEventHandler = (event: RunEvent) => void;
27
31
  export declare function onRunEvent(handler: RunEventHandler): void;
@@ -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;AAY/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,EACA,gBAAgB,GAChB,eAAe,GACf,eAAe,GACf,gBAAgB,GAChB,qBAAqB,GACrB,cAAc,GACd,gBAAgB,GAChB,kBAAkB,GAClB,eAAe,CAAC;IACpB,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;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;AAIxD,wBAAgB,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAEzD;AAkBD,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,MAAM,CAAC,CAmGjB;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,CAwH1C;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,CAmF1C"}
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;AAa/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,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,YAAY,GAAG,YAAY,CAAC;CACtC;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EACA,gBAAgB,GAChB,eAAe,GACf,eAAe,GACf,gBAAgB,GAChB,qBAAqB,GACrB,cAAc,GACd,gBAAgB,GAChB,kBAAkB,GAClB,eAAe,CAAC;IACpB,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;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;AAIxD,wBAAgB,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAEzD;AAkBD,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,MAAM,CAAC,CAmGjB;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,CAsI1C;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,CAmF1C"}
package/dist/mcp/index.js CHANGED
@@ -11,6 +11,7 @@ var __export = (target, all) => {
11
11
  });
12
12
  };
13
13
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
14
+ var __require = import.meta.require;
14
15
 
15
16
  // src/types/index.ts
16
17
  function projectFromRow(row) {
@@ -448,6 +449,168 @@ var init_database = __esm(() => {
448
449
  ];
449
450
  });
450
451
 
452
+ // src/lib/browser-lightpanda.ts
453
+ var exports_browser_lightpanda = {};
454
+ __export(exports_browser_lightpanda, {
455
+ startLightpandaServer: () => startLightpandaServer,
456
+ launchLightpanda: () => launchLightpanda,
457
+ isLightpandaAvailable: () => isLightpandaAvailable,
458
+ installLightpanda: () => installLightpanda,
459
+ getLightpandaPage: () => getLightpandaPage,
460
+ closeLightpanda: () => closeLightpanda
461
+ });
462
+ import { chromium } from "playwright";
463
+ import { spawn } from "child_process";
464
+ function isLightpandaAvailable() {
465
+ try {
466
+ const possiblePaths = [
467
+ `${process.env["HOME"]}/.cache/lightpanda-node/lightpanda`,
468
+ process.env["LIGHTPANDA_EXECUTABLE_PATH"]
469
+ ];
470
+ for (const p of possiblePaths) {
471
+ if (p) {
472
+ try {
473
+ const { existsSync: existsSync2 } = __require("fs");
474
+ if (existsSync2(p))
475
+ return true;
476
+ } catch {
477
+ continue;
478
+ }
479
+ }
480
+ }
481
+ const { execSync } = __require("child_process");
482
+ execSync("lightpanda --version", { stdio: "ignore", timeout: 5000 });
483
+ return true;
484
+ } catch {
485
+ return false;
486
+ }
487
+ }
488
+ function findLightpandaBinary() {
489
+ const envPath = process.env["LIGHTPANDA_EXECUTABLE_PATH"];
490
+ if (envPath)
491
+ return envPath;
492
+ const cachePath = `${process.env["HOME"]}/.cache/lightpanda-node/lightpanda`;
493
+ try {
494
+ const { existsSync: existsSync2 } = __require("fs");
495
+ if (existsSync2(cachePath))
496
+ return cachePath;
497
+ } catch {}
498
+ return "lightpanda";
499
+ }
500
+ async function startLightpandaServer(port) {
501
+ const binary = findLightpandaBinary();
502
+ const cdpPort = port ?? 9222 + Math.floor(Math.random() * 1000);
503
+ return new Promise((resolve, reject) => {
504
+ const proc = spawn(binary, ["serve", "--port", String(cdpPort)], {
505
+ stdio: ["ignore", "pipe", "pipe"]
506
+ });
507
+ let resolved = false;
508
+ const timeout = setTimeout(() => {
509
+ if (!resolved) {
510
+ resolved = true;
511
+ resolve({
512
+ process: proc,
513
+ wsEndpoint: `ws://127.0.0.1:${cdpPort}`
514
+ });
515
+ }
516
+ }, 5000);
517
+ proc.stdout?.on("data", (data) => {
518
+ const output = data.toString();
519
+ if (output.includes("127.0.0.1") || output.includes("listening") || output.includes("DevTools")) {
520
+ if (!resolved) {
521
+ resolved = true;
522
+ clearTimeout(timeout);
523
+ resolve({
524
+ process: proc,
525
+ wsEndpoint: `ws://127.0.0.1:${cdpPort}`
526
+ });
527
+ }
528
+ }
529
+ });
530
+ proc.stderr?.on("data", (data) => {
531
+ const output = data.toString();
532
+ if (output.includes("127.0.0.1") || output.includes("listening")) {
533
+ if (!resolved) {
534
+ resolved = true;
535
+ clearTimeout(timeout);
536
+ resolve({
537
+ process: proc,
538
+ wsEndpoint: `ws://127.0.0.1:${cdpPort}`
539
+ });
540
+ }
541
+ }
542
+ });
543
+ proc.on("error", (err) => {
544
+ clearTimeout(timeout);
545
+ if (!resolved) {
546
+ resolved = true;
547
+ reject(new BrowserError(`Failed to start Lightpanda: ${err.message}. ` + `Install it with: bun install @lightpanda/browser`));
548
+ }
549
+ });
550
+ proc.on("exit", (code) => {
551
+ if (!resolved) {
552
+ resolved = true;
553
+ clearTimeout(timeout);
554
+ reject(new BrowserError(`Lightpanda exited with code ${code}. ` + `Install it with: bun install @lightpanda/browser`));
555
+ }
556
+ });
557
+ });
558
+ }
559
+ async function launchLightpanda(_options) {
560
+ try {
561
+ const { process: proc, wsEndpoint } = await startLightpandaServer();
562
+ lightpandaProcess = proc;
563
+ const browser = await chromium.connectOverCDP(wsEndpoint);
564
+ return browser;
565
+ } catch (error) {
566
+ const message = error instanceof Error ? error.message : String(error);
567
+ throw new BrowserError(`Failed to launch Lightpanda: ${message}`);
568
+ }
569
+ }
570
+ async function getLightpandaPage(browser, options) {
571
+ try {
572
+ const contexts = browser.contexts();
573
+ const context = contexts.length > 0 ? contexts[0] : await browser.newContext({
574
+ viewport: options?.viewport ?? { width: 1280, height: 720 },
575
+ userAgent: options?.userAgent,
576
+ locale: options?.locale
577
+ });
578
+ const page = await context.newPage();
579
+ return page;
580
+ } catch (error) {
581
+ const message = error instanceof Error ? error.message : String(error);
582
+ throw new BrowserError(`Failed to create Lightpanda page: ${message}`);
583
+ }
584
+ }
585
+ async function closeLightpanda(browser) {
586
+ try {
587
+ await browser.close();
588
+ } catch {}
589
+ if (lightpandaProcess) {
590
+ try {
591
+ lightpandaProcess.kill("SIGTERM");
592
+ lightpandaProcess = null;
593
+ } catch {}
594
+ }
595
+ }
596
+ async function installLightpanda() {
597
+ const { execSync } = __require("child_process");
598
+ try {
599
+ execSync("bun install @lightpanda/browser", {
600
+ stdio: "inherit",
601
+ cwd: process.env["HOME"]
602
+ });
603
+ } catch (error) {
604
+ const message = error instanceof Error ? error.message : String(error);
605
+ throw new BrowserError(`Failed to install Lightpanda: ${message}
606
+ ` + `Try manually: bun install @lightpanda/browser`);
607
+ }
608
+ }
609
+ var lightpandaProcess = null;
610
+ var init_browser_lightpanda = __esm(() => {
611
+ init_types();
612
+ });
613
+
451
614
  // src/db/flows.ts
452
615
  var exports_flows = {};
453
616
  __export(exports_flows, {
@@ -5026,14 +5189,22 @@ function listAgents() {
5026
5189
  }
5027
5190
 
5028
5191
  // src/lib/browser.ts
5029
- import { chromium } from "playwright";
5192
+ import { chromium as chromium2 } from "playwright";
5030
5193
  init_types();
5031
5194
  var DEFAULT_VIEWPORT = { width: 1280, height: 720 };
5032
5195
  async function launchBrowser(options) {
5196
+ const engine = options?.engine ?? process.env["TESTERS_BROWSER_ENGINE"] ?? "playwright";
5197
+ if (engine === "lightpanda") {
5198
+ const { launchLightpanda: launchLightpanda2, isLightpandaAvailable: isLightpandaAvailable2 } = await Promise.resolve().then(() => (init_browser_lightpanda(), exports_browser_lightpanda));
5199
+ if (!isLightpandaAvailable2()) {
5200
+ throw new BrowserError("Lightpanda not installed. Run: testers install-browser --engine lightpanda");
5201
+ }
5202
+ return launchLightpanda2({ viewport: options?.viewport });
5203
+ }
5033
5204
  const headless = options?.headless ?? true;
5034
5205
  const viewport = options?.viewport ?? DEFAULT_VIEWPORT;
5035
5206
  try {
5036
- const browser = await chromium.launch({
5207
+ const browser = await chromium2.launch({
5037
5208
  headless,
5038
5209
  args: [
5039
5210
  `--window-size=${viewport.width},${viewport.height}`
@@ -5046,6 +5217,11 @@ async function launchBrowser(options) {
5046
5217
  }
5047
5218
  }
5048
5219
  async function getPage(browser, options) {
5220
+ const engine = options?.engine ?? "playwright";
5221
+ if (engine === "lightpanda") {
5222
+ const { getLightpandaPage: getLightpandaPage2 } = await Promise.resolve().then(() => (init_browser_lightpanda(), exports_browser_lightpanda));
5223
+ return getLightpandaPage2(browser, options);
5224
+ }
5049
5225
  const viewport = options?.viewport ?? DEFAULT_VIEWPORT;
5050
5226
  try {
5051
5227
  const context = await browser.newContext({
@@ -5060,7 +5236,11 @@ async function getPage(browser, options) {
5060
5236
  throw new BrowserError(`Failed to create page: ${message}`);
5061
5237
  }
5062
5238
  }
5063
- async function closeBrowser(browser) {
5239
+ async function closeBrowser(browser, engine) {
5240
+ if (engine === "lightpanda") {
5241
+ const { closeLightpanda: closeLightpanda2 } = await Promise.resolve().then(() => (init_browser_lightpanda(), exports_browser_lightpanda));
5242
+ return closeLightpanda2(browser);
5243
+ }
5064
5244
  try {
5065
5245
  await browser.close();
5066
5246
  } catch (error) {
@@ -6092,6 +6272,38 @@ async function dispatchWebhooks(event, run, schedule) {
6092
6272
  }
6093
6273
  }
6094
6274
 
6275
+ // src/lib/logs-integration.ts
6276
+ async function pushFailedRunToLogs(run, failedResults, scenarios) {
6277
+ const logsUrl = process.env.LOGS_URL;
6278
+ if (!logsUrl)
6279
+ return;
6280
+ const scenarioMap = new Map(scenarios.map((s) => [s.id, s]));
6281
+ const entries = failedResults.map((result) => {
6282
+ const scenario = scenarioMap.get(result.scenarioId);
6283
+ return {
6284
+ level: "error",
6285
+ source: "sdk",
6286
+ service: "testers",
6287
+ message: `[testers] Scenario failed: ${scenario?.name ?? result.scenarioId}${result.error ? ` \u2014 ${result.error}` : ""}`,
6288
+ metadata: {
6289
+ run_id: run.id,
6290
+ scenario_id: result.scenarioId,
6291
+ scenario_name: scenario?.name,
6292
+ url: run.url,
6293
+ status: result.status,
6294
+ duration_ms: result.durationMs
6295
+ }
6296
+ };
6297
+ });
6298
+ try {
6299
+ await fetch(`${logsUrl.replace(/\/$/, "")}/api/logs`, {
6300
+ method: "POST",
6301
+ headers: { "Content-Type": "application/json" },
6302
+ body: JSON.stringify(entries)
6303
+ });
6304
+ } catch {}
6305
+ }
6306
+
6095
6307
  // src/lib/runner.ts
6096
6308
  var eventHandler = null;
6097
6309
  function emit(event) {
@@ -6101,7 +6313,7 @@ function emit(event) {
6101
6313
  function withTimeout(promise, ms, label) {
6102
6314
  return new Promise((resolve, reject) => {
6103
6315
  const timer = setTimeout(() => {
6104
- reject(new Error(`Scenario timeout after ${ms}ms: ${label}`));
6316
+ reject(new Error(`Scenario '${label}' timed out after ${ms}ms. Try: testers run --timeout ${ms * 2} or simplify the scenario steps.`));
6105
6317
  }, ms);
6106
6318
  promise.then((val) => {
6107
6319
  clearTimeout(timer);
@@ -6129,7 +6341,7 @@ async function runSingleScenario(scenario, runId, options) {
6129
6341
  let browser = null;
6130
6342
  let page = null;
6131
6343
  try {
6132
- browser = await launchBrowser({ headless: !(options.headed ?? false) });
6344
+ browser = await launchBrowser({ headless: !(options.headed ?? false), engine: options.engine });
6133
6345
  page = await getPage(browser, {
6134
6346
  viewport: config.browser.viewport
6135
6347
  });
@@ -6194,7 +6406,7 @@ async function runSingleScenario(scenario, runId, options) {
6194
6406
  return updatedResult;
6195
6407
  } finally {
6196
6408
  if (browser)
6197
- await closeBrowser(browser);
6409
+ await closeBrowser(browser, options.engine);
6198
6410
  }
6199
6411
  }
6200
6412
  async function runBatch(scenarios, options) {
@@ -6234,6 +6446,7 @@ async function runBatch(scenarios, options) {
6234
6446
  } catch {}
6235
6447
  return true;
6236
6448
  };
6449
+ const maxRetries = options.retry ?? 0;
6237
6450
  if (parallel <= 1) {
6238
6451
  for (const scenario of sortedScenarios) {
6239
6452
  if (!await canRun(scenario)) {
@@ -6244,7 +6457,13 @@ async function runBatch(scenarios, options) {
6244
6457
  emit({ type: "scenario:error", scenarioId: scenario.id, scenarioName: scenario.name, error: "Dependency failed \u2014 skipped", runId: run.id });
6245
6458
  continue;
6246
6459
  }
6247
- const result = await runSingleScenario(scenario, run.id, options);
6460
+ let result = await runSingleScenario(scenario, run.id, options);
6461
+ let attempt = 1;
6462
+ while ((result.status === "failed" || result.status === "error") && attempt <= maxRetries) {
6463
+ emit({ type: "scenario:start", scenarioId: scenario.id, scenarioName: scenario.name, runId: run.id, retryAttempt: attempt + 1, maxRetries: maxRetries + 1 });
6464
+ result = await runSingleScenario(scenario, run.id, options);
6465
+ attempt++;
6466
+ }
6248
6467
  results.push(result);
6249
6468
  if (result.status === "failed" || result.status === "error") {
6250
6469
  failedScenarioIds.add(scenario.id);
@@ -6291,6 +6510,10 @@ async function runBatch(scenarios, options) {
6291
6510
  emit({ type: "run:complete", runId: run.id });
6292
6511
  const eventType = finalRun.status === "failed" ? "failed" : "completed";
6293
6512
  dispatchWebhooks(eventType, finalRun).catch(() => {});
6513
+ if (finalRun.status === "failed") {
6514
+ const failedResults = results.filter((r) => r.status === "failed" || r.status === "error");
6515
+ pushFailedRunToLogs(finalRun, failedResults, scenarios).catch(() => {});
6516
+ }
6294
6517
  return { run: finalRun, results };
6295
6518
  }
6296
6519
  async function runByFilter(options) {