@hasna/testers 0.0.2 → 0.0.3

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.
@@ -18,6 +18,9 @@ interface ScreenshotResult {
18
18
  width: number;
19
19
  height: number;
20
20
  timestamp: string;
21
+ description: string | null;
22
+ pageUrl: string | null;
23
+ thumbnailPath: string | null;
21
24
  }
22
25
  interface ToolExecutionResult {
23
26
  result: string;
@@ -49,6 +52,9 @@ interface AgentLoopResult {
49
52
  timestamp: string;
50
53
  action: string;
51
54
  stepNumber: number;
55
+ description: string | null;
56
+ pageUrl: string | null;
57
+ thumbnailPath: string | null;
52
58
  }>;
53
59
  }
54
60
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"ai-client.d.ts","sourceRoot":"","sources":["../../src/lib/ai-client.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAC1C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,KAAK,EAAe,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAI/D;;;GAGG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAKzD;AAID,eAAO,MAAM,aAAa,EAAE,SAAS,CAAC,IAAI,EAyTzC,CAAC;AAIF,UAAU,WAAW;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,gBAAgB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,mBAAmB;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,IAAI,EACV,aAAa,EAAE,aAAa,EAC5B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,mBAAmB,CAAC,CA8P9B;AAID,UAAU,gBAAgB;IACxB,MAAM,EAAE,SAAS,CAAC;IAClB,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,QAAQ,CAAC;IACnB,aAAa,EAAE,aAAa,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,eAAe;IACvB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,KAAK,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;CACJ;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,eAAe,CAAC,CAyK1B;AAID;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAQvD"}
1
+ {"version":3,"file":"ai-client.d.ts","sourceRoot":"","sources":["../../src/lib/ai-client.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAC1C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,KAAK,EAAe,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAI/D;;;GAGG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAKzD;AAID,eAAO,MAAM,aAAa,EAAE,SAAS,CAAC,IAAI,EAyTzC,CAAC;AAIF,UAAU,WAAW;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,gBAAgB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,UAAU,mBAAmB;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,IAAI,EACV,aAAa,EAAE,aAAa,EAC5B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,mBAAmB,CAAC,CA8P9B;AAID,UAAU,gBAAgB;IACxB,MAAM,EAAE,SAAS,CAAC;IAClB,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,QAAQ,CAAC;IACnB,aAAa,EAAE,aAAa,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,eAAe;IACvB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,KAAK,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;KAC9B,CAAC,CAAC;CACJ;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,eAAe,CAAC,CAyK1B;AAID;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAQvD"}
@@ -0,0 +1,36 @@
1
+ export interface CostSummary {
2
+ period: string;
3
+ totalCostCents: number;
4
+ totalTokens: number;
5
+ runCount: number;
6
+ byModel: Record<string, {
7
+ costCents: number;
8
+ tokens: number;
9
+ runs: number;
10
+ }>;
11
+ byScenario: Array<{
12
+ scenarioId: string;
13
+ name: string;
14
+ costCents: number;
15
+ tokens: number;
16
+ runs: number;
17
+ }>;
18
+ avgCostPerRun: number;
19
+ estimatedMonthlyCents: number;
20
+ }
21
+ export interface BudgetConfig {
22
+ maxPerRunCents: number;
23
+ maxPerDayCents: number;
24
+ warnAtPercent: number;
25
+ }
26
+ export declare function getCostSummary(options?: {
27
+ projectId?: string;
28
+ period?: "day" | "week" | "month" | "all";
29
+ }): CostSummary;
30
+ export declare function checkBudget(estimatedCostCents: number): {
31
+ allowed: boolean;
32
+ warning?: string;
33
+ };
34
+ export declare function formatCostsTerminal(summary: CostSummary): string;
35
+ export declare function formatCostsJSON(summary: CostSummary): string;
36
+ //# sourceMappingURL=costs.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,25 @@
1
+ import type { Run } from "../types/index.js";
2
+ export interface DiffResult {
3
+ run1: Run;
4
+ run2: Run;
5
+ regressions: ScenarioDiff[];
6
+ fixes: ScenarioDiff[];
7
+ unchanged: ScenarioDiff[];
8
+ newScenarios: ScenarioDiff[];
9
+ removedScenarios: ScenarioDiff[];
10
+ }
11
+ export interface ScenarioDiff {
12
+ scenarioId: string;
13
+ scenarioName: string | null;
14
+ scenarioShortId: string | null;
15
+ status1: string | null;
16
+ status2: string | null;
17
+ duration1: number | null;
18
+ duration2: number | null;
19
+ tokens1: number | null;
20
+ tokens2: number | null;
21
+ }
22
+ export declare function diffRuns(runId1: string, runId2: string): DiffResult;
23
+ export declare function formatDiffTerminal(diff: DiffResult): string;
24
+ export declare function formatDiffJSON(diff: DiffResult): string;
25
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/lib/diff.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,GAAG,EAAU,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,GAAG,CAAC;IACV,IAAI,EAAE,GAAG,CAAC;IACV,WAAW,EAAE,YAAY,EAAE,CAAC;IAC5B,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,YAAY,EAAE,YAAY,EAAE,CAAC;IAC7B,gBAAgB,EAAE,YAAY,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAuEnE;AA6BD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAiE3D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAEvD"}
@@ -0,0 +1,28 @@
1
+ import { createScenario } from "../db/scenarios.js";
2
+ import { ensureProject } from "../db/projects.js";
3
+ import type { CreateScenarioInput } from "../types/index.js";
4
+ interface FrameworkInfo {
5
+ name: string;
6
+ defaultUrl: string;
7
+ features: string[];
8
+ }
9
+ export declare function detectFramework(dir: string): FrameworkInfo | null;
10
+ export declare function getStarterScenarios(framework: {
11
+ name: string;
12
+ features: string[];
13
+ }, projectId: string): CreateScenarioInput[];
14
+ export interface InitOptions {
15
+ name?: string;
16
+ url?: string;
17
+ path?: string;
18
+ dir?: string;
19
+ }
20
+ export interface InitResult {
21
+ project: ReturnType<typeof ensureProject>;
22
+ scenarios: ReturnType<typeof createScenario>[];
23
+ framework: FrameworkInfo | null;
24
+ url: string;
25
+ }
26
+ export declare function initProject(options: InitOptions): InitResult;
27
+ export {};
28
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,4 @@
1
+ export declare function imageToBase64(filePath: string): string;
2
+ export declare function generateHtmlReport(runId: string): string;
3
+ export declare function generateLatestReport(): string;
4
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/lib/report.ts"],"names":[],"mappings":"AAOA,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAStD;AA4ED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAiIxD;AAED,wBAAgB,oBAAoB,IAAI,MAAM,CAI7C"}
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/lib/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAW/D,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,gBAAgB,GAAG,eAAe,GAAG,eAAe,GAAG,gBAAgB,GAAG,qBAAqB,GAAG,cAAc,CAAC;IACvH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;AAIxD,wBAAgB,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAEzD;AAMD,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,MAAM,CAAC,CAiFjB;AAED,wBAAsB,QAAQ,CAC5B,SAAS,EAAE,QAAQ,EAAE,EACrB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CA4D1C;AAED,wBAAsB,WAAW,CAC/B,OAAO,EAAE,UAAU,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GACnF,OAAO,CAAC;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAuB1C"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/lib/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAW/D,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,gBAAgB,GAAG,eAAe,GAAG,eAAe,GAAG,gBAAgB,GAAG,qBAAqB,GAAG,cAAc,CAAC;IACvH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;AAIxD,wBAAgB,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAEzD;AAMD,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,MAAM,CAAC,CAoFjB;AAED,wBAAsB,QAAQ,CAC5B,SAAS,EAAE,QAAQ,EAAE,EACrB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CA4D1C;AAED,wBAAsB,WAAW,CAC/B,OAAO,EAAE,UAAU,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GACnF,OAAO,CAAC;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAuB1C"}
@@ -1,59 +1,61 @@
1
1
  import type { Page } from "playwright";
2
- /**
3
- * Convert arbitrary text to a URL/filesystem-safe slug.
4
- * Lowercases, replaces non-alphanumeric runs with a single hyphen, trims
5
- * leading/trailing hyphens.
6
- */
7
2
  export declare function slugify(text: string): string;
8
- /**
9
- * Build a zero-padded screenshot filename.
10
- * Example: stepNumber=1, action="Navigate homepage" → "001-navigate-homepage.png"
11
- */
12
3
  export declare function generateFilename(stepNumber: number, action: string): string;
13
4
  /**
14
- * Derive the directory path for a specific scenario inside a run.
15
- * Layout: `<baseDir>/<runId>/<scenarioSlug>/`
16
- */
17
- export declare function getScreenshotDir(baseDir: string, runId: string, scenarioSlug: string): string;
18
- /**
19
- * Create `dirPath` (and any parents) if it does not already exist.
5
+ * Build the screenshot directory for a run:
6
+ * {baseDir}/{projectName}/{YYYY-MM-DD}/{HH-mm-ss}_{runId-8char}/{scenarioSlug}/
20
7
  */
8
+ export declare function getScreenshotDir(baseDir: string, runId: string, scenarioSlug: string, projectName?: string, timestamp?: Date): string;
21
9
  export declare function ensureDir(dirPath: string): void;
22
10
  interface ScreenshotterOptions {
23
11
  baseDir?: string;
24
12
  format?: "png" | "jpeg";
25
13
  quality?: number;
26
14
  fullPage?: boolean;
15
+ projectName?: string;
27
16
  }
28
17
  interface CaptureOptions {
29
18
  runId: string;
30
19
  scenarioSlug: string;
31
20
  stepNumber: number;
32
21
  action: string;
22
+ description?: string;
33
23
  }
34
- interface CaptureResult {
24
+ export interface CaptureResult {
35
25
  filePath: string;
36
26
  width: number;
37
27
  height: number;
38
28
  timestamp: string;
29
+ description: string | null;
30
+ pageUrl: string | null;
31
+ thumbnailPath: string | null;
39
32
  }
33
+ export declare function writeRunMeta(dir: string, meta: {
34
+ runId: string;
35
+ url: string;
36
+ model: string;
37
+ status: string;
38
+ startedAt: string;
39
+ scenarioCount: number;
40
+ }): void;
41
+ export declare function writeScenarioMeta(dir: string, meta: {
42
+ scenarioId: string;
43
+ shortId: string;
44
+ name: string;
45
+ status: string;
46
+ reasoning: string | null;
47
+ durationMs: number;
48
+ }): void;
40
49
  export declare class Screenshotter {
41
50
  private readonly baseDir;
42
51
  private readonly format;
43
52
  private readonly quality;
44
53
  private readonly fullPage;
54
+ private readonly projectName;
55
+ private runTimestamp;
45
56
  constructor(options?: ScreenshotterOptions);
46
- /**
47
- * Capture a screenshot of the current page state.
48
- */
49
57
  capture(page: Page, options: CaptureOptions): Promise<CaptureResult>;
50
- /**
51
- * Capture a full-page screenshot regardless of the instance default.
52
- */
53
58
  captureFullPage(page: Page, options: CaptureOptions): Promise<CaptureResult>;
54
- /**
55
- * Capture a screenshot of a specific element identified by `selector`.
56
- */
57
59
  captureElement(page: Page, selector: string, options: CaptureOptions): Promise<CaptureResult>;
58
60
  }
59
61
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"screenshotter.d.ts","sourceRoot":"","sources":["../../src/lib/screenshotter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAOvC;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK5C;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,GACb,MAAM,CAIR;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,GACnB,MAAM,CAER;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAI/C;AAID,UAAU,oBAAoB;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,aAAa;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAMD,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;gBAEvB,OAAO,GAAE,oBAAyB;IAO9C;;OAEG;IACG,OAAO,CACX,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC;IA4BzB;;OAEG;IACG,eAAe,CACnB,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC;IA4BzB;;OAEG;IACG,cAAc,CAClB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC;CA0B1B"}
1
+ {"version":3,"file":"screenshotter.d.ts","sourceRoot":"","sources":["../../src/lib/screenshotter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAOvC,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK5C;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAI3E;AAUD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,WAAW,CAAC,EAAE,MAAM,EACpB,SAAS,CAAC,EAAE,IAAI,GACf,MAAM,CAMR;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAI/C;AAID,UAAU,oBAAoB;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAuBD,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,GAC5G,IAAI,CAON;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACxH,IAAI,CAON;AAkCD,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,YAAY,CAAO;gBAEf,OAAO,GAAE,oBAAyB;IASxC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAkDpE,eAAe,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAgD5E,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CA4CpG"}
@@ -0,0 +1,25 @@
1
+ import type { Run, Result } from "../types/index.js";
2
+ export interface SmokeResult {
3
+ run: Run;
4
+ result: Result;
5
+ pagesVisited: number;
6
+ issuesFound: SmokeIssue[];
7
+ }
8
+ export interface SmokeIssue {
9
+ type: "js-error" | "404" | "broken-image" | "broken-link" | "visual" | "performance";
10
+ severity: "critical" | "high" | "medium" | "low";
11
+ description: string;
12
+ url: string;
13
+ screenshot?: string;
14
+ }
15
+ export declare function runSmoke(options: {
16
+ url: string;
17
+ model?: string;
18
+ headed?: boolean;
19
+ timeout?: number;
20
+ projectId?: string;
21
+ apiKey?: string;
22
+ }): Promise<SmokeResult>;
23
+ export declare function parseSmokeIssues(reasoning: string): SmokeIssue[];
24
+ export declare function formatSmokeReport(result: SmokeResult): string;
25
+ //# sourceMappingURL=smoke.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smoke.d.ts","sourceRoot":"","sources":["../../src/lib/smoke.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAOrD,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,GAAG,CAAC;IACT,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,UAAU,GAAG,KAAK,GAAG,cAAc,GAAG,aAAa,GAAG,QAAQ,GAAG,aAAa,CAAC;IACrF,QAAQ,EAAE,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAsBD,wBAAsB,QAAQ,CAAC,OAAO,EAAE;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,WAAW,CAAC,CAwEvB;AAwCD,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,EAAE,CAiDhE;AA2BD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAuD7D"}
@@ -0,0 +1,5 @@
1
+ import type { CreateScenarioInput } from "../types/index.js";
2
+ export declare const SCENARIO_TEMPLATES: Record<string, CreateScenarioInput[]>;
3
+ export declare function getTemplate(name: string): CreateScenarioInput[] | null;
4
+ export declare function listTemplateNames(): string[];
5
+ //# sourceMappingURL=templates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/lib/templates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE7D,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,EAAE,CAyBpE,CAAC;AAEF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,EAAE,GAAG,IAAI,CAEtE;AAED,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAE5C"}
@@ -0,0 +1,9 @@
1
+ import type { RunOptions } from "./runner.js";
2
+ export interface WatchOptions extends RunOptions {
3
+ dir: string;
4
+ debounceMs?: number;
5
+ tags?: string[];
6
+ priority?: string;
7
+ }
8
+ export declare function startWatcher(options: WatchOptions): Promise<void>;
9
+ //# sourceMappingURL=watch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/lib/watch.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAsGvE"}
@@ -0,0 +1,41 @@
1
+ import type { Run } from "../types/index.js";
2
+ export interface Webhook {
3
+ id: string;
4
+ url: string;
5
+ events: string[];
6
+ projectId: string | null;
7
+ secret: string | null;
8
+ active: boolean;
9
+ createdAt: string;
10
+ }
11
+ export declare function createWebhook(input: {
12
+ url: string;
13
+ events?: string[];
14
+ projectId?: string;
15
+ secret?: string;
16
+ }): Webhook;
17
+ export declare function getWebhook(id: string): Webhook | null;
18
+ export declare function listWebhooks(projectId?: string): Webhook[];
19
+ export declare function deleteWebhook(id: string): boolean;
20
+ export interface WebhookPayload {
21
+ event: string;
22
+ run: {
23
+ id: string;
24
+ url: string;
25
+ status: string;
26
+ passed: number;
27
+ failed: number;
28
+ total: number;
29
+ };
30
+ schedule?: {
31
+ name: string;
32
+ cronExpression: string;
33
+ };
34
+ timestamp: string;
35
+ }
36
+ export declare function dispatchWebhooks(event: string, run: Run, schedule?: {
37
+ name: string;
38
+ cronExpression: string;
39
+ }): Promise<void>;
40
+ export declare function testWebhook(id: string): Promise<boolean>;
41
+ //# sourceMappingURL=webhooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../src/lib/webhooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAc7C,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAgBD,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAYV;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAUrD;AAED,wBAAgB,YAAY,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,EAAE,CAW1D;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAMjD;AAID,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE;QACH,EAAE,EAAE,MAAM,CAAC;QACX,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,QAAQ,CAAC,EAAE;QACT,IAAI,EAAE,MAAM,CAAC;QACb,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,SAAS,EAAE,MAAM,CAAC;CACnB;AAwCD,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,GAAG,EACR,QAAQ,CAAC,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,GAClD,OAAO,CAAC,IAAI,CAAC,CAgDf;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAwB9D"}
package/dist/mcp/index.js CHANGED
@@ -4080,7 +4080,10 @@ function screenshotFromRow(row) {
4080
4080
  filePath: row.file_path,
4081
4081
  width: row.width,
4082
4082
  height: row.height,
4083
- timestamp: row.timestamp
4083
+ timestamp: row.timestamp,
4084
+ description: row.description,
4085
+ pageUrl: row.page_url,
4086
+ thumbnailPath: row.thumbnail_path
4084
4087
  };
4085
4088
  }
4086
4089
  function scheduleFromRow(row) {
@@ -4289,6 +4292,34 @@ var MIGRATIONS = [
4289
4292
  CREATE INDEX IF NOT EXISTS idx_schedules_project ON schedules(project_id);
4290
4293
  CREATE INDEX IF NOT EXISTS idx_schedules_enabled ON schedules(enabled);
4291
4294
  CREATE INDEX IF NOT EXISTS idx_schedules_next_run ON schedules(next_run_at);
4295
+ `,
4296
+ `
4297
+ ALTER TABLE screenshots ADD COLUMN description TEXT;
4298
+ ALTER TABLE screenshots ADD COLUMN page_url TEXT;
4299
+ ALTER TABLE screenshots ADD COLUMN thumbnail_path TEXT;
4300
+ `,
4301
+ `
4302
+ CREATE TABLE IF NOT EXISTS auth_presets (
4303
+ id TEXT PRIMARY KEY,
4304
+ name TEXT NOT NULL UNIQUE,
4305
+ email TEXT NOT NULL,
4306
+ password TEXT NOT NULL,
4307
+ login_path TEXT DEFAULT '/login',
4308
+ metadata TEXT DEFAULT '{}',
4309
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
4310
+ );
4311
+ `,
4312
+ `
4313
+ CREATE TABLE IF NOT EXISTS webhooks (
4314
+ id TEXT PRIMARY KEY,
4315
+ url TEXT NOT NULL,
4316
+ events TEXT NOT NULL DEFAULT '["failed"]',
4317
+ project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
4318
+ secret TEXT,
4319
+ active INTEGER NOT NULL DEFAULT 1,
4320
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
4321
+ );
4322
+ CREATE INDEX IF NOT EXISTS idx_webhooks_active ON webhooks(active);
4292
4323
  `
4293
4324
  ];
4294
4325
  function applyMigrations(database) {
@@ -4686,9 +4717,9 @@ function createScreenshot(input) {
4686
4717
  const id = uuid();
4687
4718
  const timestamp = now();
4688
4719
  db2.query(`
4689
- INSERT INTO screenshots (id, result_id, step_number, action, file_path, width, height, timestamp)
4690
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
4691
- `).run(id, input.resultId, input.stepNumber, input.action, input.filePath, input.width, input.height, timestamp);
4720
+ INSERT INTO screenshots (id, result_id, step_number, action, file_path, width, height, timestamp, description, page_url, thumbnail_path)
4721
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
4722
+ `).run(id, input.resultId, input.stepNumber, input.action, input.filePath, input.width, input.height, timestamp, input.description ?? null, input.pageUrl ?? null, input.thumbnailPath ?? null);
4692
4723
  return getScreenshot(id);
4693
4724
  }
4694
4725
  function getScreenshot(id) {
@@ -4805,7 +4836,7 @@ async function closeBrowser(browser) {
4805
4836
  }
4806
4837
 
4807
4838
  // src/lib/screenshotter.ts
4808
- import { mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
4839
+ import { mkdirSync as mkdirSync2, existsSync as existsSync2, writeFileSync } from "fs";
4809
4840
  import { join as join2 } from "path";
4810
4841
  import { homedir as homedir2 } from "os";
4811
4842
  function slugify(text) {
@@ -4814,16 +4845,51 @@ function slugify(text) {
4814
4845
  function generateFilename(stepNumber, action) {
4815
4846
  const padded = String(stepNumber).padStart(3, "0");
4816
4847
  const slug = slugify(action);
4817
- return `${padded}-${slug}.png`;
4848
+ return `${padded}_${slug}.png`;
4849
+ }
4850
+ function formatDate(date) {
4851
+ return date.toISOString().slice(0, 10);
4818
4852
  }
4819
- function getScreenshotDir(baseDir, runId, scenarioSlug) {
4820
- return join2(baseDir, runId, scenarioSlug);
4853
+ function formatTime(date) {
4854
+ return date.toISOString().slice(11, 19).replace(/:/g, "-");
4855
+ }
4856
+ function getScreenshotDir(baseDir, runId, scenarioSlug, projectName, timestamp) {
4857
+ const now2 = timestamp ?? new Date;
4858
+ const project = projectName ?? "default";
4859
+ const dateDir = formatDate(now2);
4860
+ const timeDir = `${formatTime(now2)}_${runId.slice(0, 8)}`;
4861
+ return join2(baseDir, project, dateDir, timeDir, scenarioSlug);
4821
4862
  }
4822
4863
  function ensureDir(dirPath) {
4823
4864
  if (!existsSync2(dirPath)) {
4824
4865
  mkdirSync2(dirPath, { recursive: true });
4825
4866
  }
4826
4867
  }
4868
+ function writeMetaSidecar(screenshotPath, meta) {
4869
+ const metaPath = screenshotPath.replace(/\.png$/, ".meta.json").replace(/\.jpeg$/, ".meta.json");
4870
+ try {
4871
+ writeFileSync(metaPath, JSON.stringify(meta, null, 2), "utf-8");
4872
+ } catch {}
4873
+ }
4874
+ async function generateThumbnail(page, screenshotDir, filename) {
4875
+ try {
4876
+ const thumbDir = join2(screenshotDir, "_thumbnail");
4877
+ ensureDir(thumbDir);
4878
+ const thumbFilename = filename.replace(/\.(png|jpeg)$/, ".thumb.$1");
4879
+ const thumbPath = join2(thumbDir, thumbFilename);
4880
+ const viewport = page.viewportSize();
4881
+ if (viewport) {
4882
+ await page.screenshot({
4883
+ path: thumbPath,
4884
+ type: "png",
4885
+ clip: { x: 0, y: 0, width: Math.min(viewport.width, 1280), height: Math.min(viewport.height, 720) }
4886
+ });
4887
+ }
4888
+ return thumbPath;
4889
+ } catch {
4890
+ return null;
4891
+ }
4892
+ }
4827
4893
  var DEFAULT_BASE_DIR = join2(homedir2(), ".testers", "screenshots");
4828
4894
 
4829
4895
  class Screenshotter {
@@ -4831,15 +4897,20 @@ class Screenshotter {
4831
4897
  format;
4832
4898
  quality;
4833
4899
  fullPage;
4900
+ projectName;
4901
+ runTimestamp;
4834
4902
  constructor(options = {}) {
4835
4903
  this.baseDir = options.baseDir ?? DEFAULT_BASE_DIR;
4836
4904
  this.format = options.format ?? "png";
4837
4905
  this.quality = options.quality ?? 90;
4838
4906
  this.fullPage = options.fullPage ?? false;
4907
+ this.projectName = options.projectName ?? "default";
4908
+ this.runTimestamp = new Date;
4839
4909
  }
4840
4910
  async capture(page, options) {
4841
- const dir = getScreenshotDir(this.baseDir, options.runId, options.scenarioSlug);
4842
- const filename = generateFilename(options.stepNumber, options.action);
4911
+ const action = options.description ?? options.action;
4912
+ const dir = getScreenshotDir(this.baseDir, options.runId, options.scenarioSlug, this.projectName, this.runTimestamp);
4913
+ const filename = generateFilename(options.stepNumber, action);
4843
4914
  const filePath = join2(dir, filename);
4844
4915
  ensureDir(dir);
4845
4916
  await page.screenshot({
@@ -4849,16 +4920,32 @@ class Screenshotter {
4849
4920
  quality: this.format === "jpeg" ? this.quality : undefined
4850
4921
  });
4851
4922
  const viewport = page.viewportSize() ?? { width: 0, height: 0 };
4923
+ const pageUrl = page.url();
4924
+ const timestamp = new Date().toISOString();
4925
+ writeMetaSidecar(filePath, {
4926
+ stepNumber: options.stepNumber,
4927
+ action: options.action,
4928
+ description: options.description ?? null,
4929
+ pageUrl,
4930
+ viewport,
4931
+ timestamp,
4932
+ filePath
4933
+ });
4934
+ const thumbnailPath = await generateThumbnail(page, dir, filename);
4852
4935
  return {
4853
4936
  filePath,
4854
4937
  width: viewport.width,
4855
4938
  height: viewport.height,
4856
- timestamp: new Date().toISOString()
4939
+ timestamp,
4940
+ description: options.description ?? null,
4941
+ pageUrl,
4942
+ thumbnailPath
4857
4943
  };
4858
4944
  }
4859
4945
  async captureFullPage(page, options) {
4860
- const dir = getScreenshotDir(this.baseDir, options.runId, options.scenarioSlug);
4861
- const filename = generateFilename(options.stepNumber, options.action);
4946
+ const action = options.description ?? options.action;
4947
+ const dir = getScreenshotDir(this.baseDir, options.runId, options.scenarioSlug, this.projectName, this.runTimestamp);
4948
+ const filename = generateFilename(options.stepNumber, action);
4862
4949
  const filePath = join2(dir, filename);
4863
4950
  ensureDir(dir);
4864
4951
  await page.screenshot({
@@ -4868,16 +4955,32 @@ class Screenshotter {
4868
4955
  quality: this.format === "jpeg" ? this.quality : undefined
4869
4956
  });
4870
4957
  const viewport = page.viewportSize() ?? { width: 0, height: 0 };
4958
+ const pageUrl = page.url();
4959
+ const timestamp = new Date().toISOString();
4960
+ writeMetaSidecar(filePath, {
4961
+ stepNumber: options.stepNumber,
4962
+ action: options.action,
4963
+ description: options.description ?? null,
4964
+ pageUrl,
4965
+ viewport,
4966
+ timestamp,
4967
+ filePath
4968
+ });
4969
+ const thumbnailPath = await generateThumbnail(page, dir, filename);
4871
4970
  return {
4872
4971
  filePath,
4873
4972
  width: viewport.width,
4874
4973
  height: viewport.height,
4875
- timestamp: new Date().toISOString()
4974
+ timestamp,
4975
+ description: options.description ?? null,
4976
+ pageUrl,
4977
+ thumbnailPath
4876
4978
  };
4877
4979
  }
4878
4980
  async captureElement(page, selector, options) {
4879
- const dir = getScreenshotDir(this.baseDir, options.runId, options.scenarioSlug);
4880
- const filename = generateFilename(options.stepNumber, options.action);
4981
+ const action = options.description ?? options.action;
4982
+ const dir = getScreenshotDir(this.baseDir, options.runId, options.scenarioSlug, this.projectName, this.runTimestamp);
4983
+ const filename = generateFilename(options.stepNumber, action);
4881
4984
  const filePath = join2(dir, filename);
4882
4985
  ensureDir(dir);
4883
4986
  await page.locator(selector).screenshot({
@@ -4886,11 +4989,25 @@ class Screenshotter {
4886
4989
  quality: this.format === "jpeg" ? this.quality : undefined
4887
4990
  });
4888
4991
  const viewport = page.viewportSize() ?? { width: 0, height: 0 };
4992
+ const pageUrl = page.url();
4993
+ const timestamp = new Date().toISOString();
4994
+ writeMetaSidecar(filePath, {
4995
+ stepNumber: options.stepNumber,
4996
+ action: options.action,
4997
+ description: options.description ?? null,
4998
+ pageUrl,
4999
+ viewport,
5000
+ timestamp,
5001
+ filePath
5002
+ });
4889
5003
  return {
4890
5004
  filePath,
4891
5005
  width: viewport.width,
4892
5006
  height: viewport.height,
4893
- timestamp: new Date().toISOString()
5007
+ timestamp,
5008
+ description: options.description ?? null,
5009
+ pageUrl,
5010
+ thumbnailPath: null
4894
5011
  };
4895
5012
  }
4896
5013
  }
@@ -5673,7 +5790,10 @@ async function runSingleScenario(scenario, runId, options) {
5673
5790
  action: ss.action,
5674
5791
  filePath: ss.filePath,
5675
5792
  width: ss.width,
5676
- height: ss.height
5793
+ height: ss.height,
5794
+ description: ss.description,
5795
+ pageUrl: ss.pageUrl,
5796
+ thumbnailPath: ss.thumbnailPath
5677
5797
  });
5678
5798
  emit({ type: "screenshot:captured", screenshotPath: ss.filePath, scenarioId: scenario.id, runId });
5679
5799
  }