@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.
- package/dashboard/dist/assets/index-DvYdwJK-.css +1 -0
- package/dashboard/dist/assets/index-RV9LMdfY.js +49 -0
- package/dashboard/dist/index.html +2 -2
- package/dist/cli/index.js +1395 -365
- package/dist/db/results.d.ts +1 -0
- package/dist/db/results.d.ts.map +1 -1
- package/dist/db/runs.d.ts +1 -0
- package/dist/db/runs.d.ts.map +1 -1
- package/dist/db/scenarios.d.ts +1 -0
- package/dist/db/scenarios.d.ts.map +1 -1
- package/dist/db/screenshots.d.ts +1 -0
- package/dist/db/screenshots.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +462 -31
- package/dist/lib/browser-lightpanda.d.ts +43 -0
- package/dist/lib/browser-lightpanda.d.ts.map +1 -0
- package/dist/lib/browser.d.ts +9 -3
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/costs.d.ts +1 -0
- package/dist/lib/costs.d.ts.map +1 -1
- package/dist/lib/init.d.ts.map +1 -1
- package/dist/lib/logs-integration.d.ts +7 -0
- package/dist/lib/logs-integration.d.ts.map +1 -0
- package/dist/lib/reporter.d.ts +7 -0
- package/dist/lib/reporter.d.ts.map +1 -1
- package/dist/lib/runner.d.ts +4 -0
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/mcp/index.js +230 -7
- package/dist/server/index.js +4461 -125
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/dashboard/dist/assets/index-CDcHt94n.css +0 -1
- package/dashboard/dist/assets/index-DCNDCh61.js +0 -49
|
@@ -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"}
|
package/dist/lib/browser.d.ts
CHANGED
|
@@ -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
|
|
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;
|
|
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"}
|
package/dist/lib/costs.d.ts
CHANGED
|
@@ -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
|
package/dist/lib/costs.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/lib/init.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/lib/reporter.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/lib/runner.d.ts
CHANGED
|
@@ -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;
|
package/dist/lib/runner.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/lib/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;
|
|
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
|
|
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
|
|
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
|
-
|
|
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) {
|