@hasna/testers 0.0.6 → 0.0.7

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.
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAOtC,iBAAS,GAAG,IAAI,MAAM,CAErB;AAED,iBAAS,IAAI,IAAI,MAAM,CAEtB;AAED,iBAAS,SAAS,IAAI,MAAM,CAE3B;AAgOD,wBAAgB,WAAW,IAAI,QAAQ,CAwBtC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAKpC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAcpC;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,IAAI,CAOf;AAED,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAOtC,iBAAS,GAAG,IAAI,MAAM,CAErB;AAED,iBAAS,IAAI,IAAI,MAAM,CAEtB;AAED,iBAAS,SAAS,IAAI,MAAM,CAE3B;AAwPD,wBAAgB,WAAW,IAAI,QAAQ,CAwBtC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAKpC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAepC;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,IAAI,CAOf;AAED,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC"}
@@ -0,0 +1,22 @@
1
+ export interface Environment {
2
+ id: string;
3
+ name: string;
4
+ url: string;
5
+ authPresetName: string | null;
6
+ projectId: string | null;
7
+ isDefault: boolean;
8
+ createdAt: string;
9
+ }
10
+ export declare function createEnvironment(input: {
11
+ name: string;
12
+ url: string;
13
+ authPresetName?: string;
14
+ projectId?: string;
15
+ isDefault?: boolean;
16
+ }): Environment;
17
+ export declare function getEnvironment(name: string): Environment | null;
18
+ export declare function listEnvironments(projectId?: string): Environment[];
19
+ export declare function deleteEnvironment(name: string): boolean;
20
+ export declare function setDefaultEnvironment(name: string): void;
21
+ export declare function getDefaultEnvironment(): Environment | null;
22
+ //# sourceMappingURL=environments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environments.d.ts","sourceRoot":"","sources":["../../src/db/environments.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAcD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,GAAG,WAAW,CAmBd;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAM/D;AAED,wBAAgB,gBAAgB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,WAAW,EAAE,CAYlE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMvD;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CASxD;AAED,wBAAgB,qBAAqB,IAAI,WAAW,GAAG,IAAI,CAM1D"}
@@ -1 +1 @@
1
- {"version":3,"file":"runs.d.ts","sourceRoot":"","sources":["../../src/db/runs.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,GAAG,EACR,KAAK,MAAM,EACX,KAAK,cAAc,EACnB,KAAK,SAAS,EAEf,MAAM,mBAAmB,CAAC;AAG3B,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,GAAG,CAoBxE;AAED,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI,CAc7C;AAED,wBAAgB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,GAAG,GAAG,EAAE,CAgClD;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,GAAG,CA+DnE;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAO7C"}
1
+ {"version":3,"file":"runs.d.ts","sourceRoot":"","sources":["../../src/db/runs.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,GAAG,EACR,KAAK,MAAM,EACX,KAAK,cAAc,EACnB,KAAK,SAAS,EAEf,MAAM,mBAAmB,CAAC;AAG3B,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,GAAG,CAoBxE;AAED,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI,CAc7C;AAED,wBAAgB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,GAAG,GAAG,EAAE,CAgClD;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,GAAG,CAmEnE;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAO7C"}
@@ -1 +1 @@
1
- {"version":3,"file":"scenarios.d.ts","sourceRoot":"","sources":["../../src/db/scenarios.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EAEb,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EAGpB,MAAM,mBAAmB,CAAC;AAsB3B,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,CA6BnE;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAmBvD;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAIrE;AAED,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,QAAQ,EAAE,CA6CjE;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,CAgFhG;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAOlD"}
1
+ {"version":3,"file":"scenarios.d.ts","sourceRoot":"","sources":["../../src/db/scenarios.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EAEb,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EAGpB,MAAM,mBAAmB,CAAC;AAsB3B,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,CA8BnE;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAmBvD;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAIrE;AAED,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,QAAQ,EAAE,CA6CjE;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,CAoFhG;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAOlD"}
package/dist/index.js CHANGED
@@ -49,6 +49,7 @@ function scenarioFromRow(row) {
49
49
  requiresAuth: row.requires_auth === 1,
50
50
  authConfig: row.auth_config ? JSON.parse(row.auth_config) : null,
51
51
  metadata: row.metadata ? JSON.parse(row.metadata) : null,
52
+ assertions: JSON.parse(row.assertions || "[]"),
52
53
  version: row.version,
53
54
  createdAt: row.created_at,
54
55
  updatedAt: row.updated_at
@@ -68,7 +69,8 @@ function runFromRow(row) {
68
69
  failed: row.failed,
69
70
  startedAt: row.started_at,
70
71
  finishedAt: row.finished_at,
71
- metadata: row.metadata ? JSON.parse(row.metadata) : null
72
+ metadata: row.metadata ? JSON.parse(row.metadata) : null,
73
+ isBaseline: row.is_baseline === 1
72
74
  };
73
75
  }
74
76
  function resultFromRow(row) {
@@ -287,6 +289,7 @@ function resetDatabase() {
287
289
  database.exec("DELETE FROM flows");
288
290
  database.exec("DELETE FROM webhooks");
289
291
  database.exec("DELETE FROM auth_presets");
292
+ database.exec("DELETE FROM environments");
290
293
  database.exec("DELETE FROM schedules");
291
294
  database.exec("DELETE FROM runs");
292
295
  database.exec("DELETE FROM scenarios");
@@ -480,6 +483,24 @@ var init_database = __esm(() => {
480
483
  CREATE INDEX IF NOT EXISTS idx_deps_scenario ON scenario_dependencies(scenario_id);
481
484
  CREATE INDEX IF NOT EXISTS idx_deps_depends ON scenario_dependencies(depends_on);
482
485
  CREATE INDEX IF NOT EXISTS idx_flows_project ON flows(project_id);
486
+ `,
487
+ `
488
+ ALTER TABLE scenarios ADD COLUMN assertions TEXT DEFAULT '[]';
489
+ `,
490
+ `
491
+ CREATE TABLE IF NOT EXISTS environments (
492
+ id TEXT PRIMARY KEY,
493
+ name TEXT NOT NULL UNIQUE,
494
+ url TEXT NOT NULL,
495
+ auth_preset_name TEXT,
496
+ project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
497
+ is_default INTEGER NOT NULL DEFAULT 0,
498
+ metadata TEXT DEFAULT '{}',
499
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
500
+ );
501
+ `,
502
+ `
503
+ ALTER TABLE runs ADD COLUMN is_baseline INTEGER NOT NULL DEFAULT 0;
483
504
  `
484
505
  ];
485
506
  });
@@ -596,6 +617,10 @@ function updateRun(id, updates) {
596
617
  sets.push("metadata = ?");
597
618
  params.push(updates.metadata);
598
619
  }
620
+ if (updates.is_baseline !== undefined) {
621
+ sets.push("is_baseline = ?");
622
+ params.push(updates.is_baseline);
623
+ }
599
624
  if (sets.length === 0) {
600
625
  return existing;
601
626
  }
@@ -792,9 +817,9 @@ function createScenario(input) {
792
817
  const short_id = nextShortId(input.projectId);
793
818
  const timestamp = now();
794
819
  db2.query(`
795
- INSERT INTO scenarios (id, short_id, project_id, name, description, steps, tags, priority, model, timeout_ms, target_path, requires_auth, auth_config, metadata, version, created_at, updated_at)
796
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)
797
- `).run(id, short_id, input.projectId ?? null, input.name, input.description, JSON.stringify(input.steps ?? []), JSON.stringify(input.tags ?? []), input.priority ?? "medium", input.model ?? null, input.timeoutMs ?? null, input.targetPath ?? null, input.requiresAuth ? 1 : 0, input.authConfig ? JSON.stringify(input.authConfig) : null, input.metadata ? JSON.stringify(input.metadata) : null, timestamp, timestamp);
820
+ INSERT INTO scenarios (id, short_id, project_id, name, description, steps, tags, priority, model, timeout_ms, target_path, requires_auth, auth_config, metadata, assertions, version, created_at, updated_at)
821
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)
822
+ `).run(id, short_id, input.projectId ?? null, input.name, input.description, JSON.stringify(input.steps ?? []), JSON.stringify(input.tags ?? []), input.priority ?? "medium", input.model ?? null, input.timeoutMs ?? null, input.targetPath ?? null, input.requiresAuth ? 1 : 0, input.authConfig ? JSON.stringify(input.authConfig) : null, input.metadata ? JSON.stringify(input.metadata) : null, JSON.stringify(input.assertions ?? []), timestamp, timestamp);
798
823
  return getScenario(id);
799
824
  }
800
825
  function getScenario(id) {
@@ -912,6 +937,10 @@ function updateScenario(id, input, version) {
912
937
  sets.push("metadata = ?");
913
938
  params.push(JSON.stringify(input.metadata));
914
939
  }
940
+ if (input.assertions !== undefined) {
941
+ sets.push("assertions = ?");
942
+ params.push(JSON.stringify(input.assertions));
943
+ }
915
944
  if (sets.length === 0) {
916
945
  return existing;
917
946
  }
@@ -2292,6 +2321,160 @@ function createClient(apiKey) {
2292
2321
  }
2293
2322
  // src/lib/runner.ts
2294
2323
  init_runs();
2324
+
2325
+ // src/lib/webhooks.ts
2326
+ init_database();
2327
+ function fromRow(row) {
2328
+ return {
2329
+ id: row.id,
2330
+ url: row.url,
2331
+ events: JSON.parse(row.events),
2332
+ projectId: row.project_id,
2333
+ secret: row.secret,
2334
+ active: row.active === 1,
2335
+ createdAt: row.created_at
2336
+ };
2337
+ }
2338
+ function createWebhook(input) {
2339
+ const db2 = getDatabase();
2340
+ const id = uuid();
2341
+ const events = input.events ?? ["failed"];
2342
+ const secret = input.secret ?? crypto.randomUUID().replace(/-/g, "");
2343
+ db2.query(`
2344
+ INSERT INTO webhooks (id, url, events, project_id, secret, active, created_at)
2345
+ VALUES (?, ?, ?, ?, ?, 1, ?)
2346
+ `).run(id, input.url, JSON.stringify(events), input.projectId ?? null, secret, now());
2347
+ return getWebhook(id);
2348
+ }
2349
+ function getWebhook(id) {
2350
+ const db2 = getDatabase();
2351
+ const row = db2.query("SELECT * FROM webhooks WHERE id = ?").get(id);
2352
+ if (!row) {
2353
+ const rows = db2.query("SELECT * FROM webhooks WHERE id LIKE ? || '%'").all(id);
2354
+ if (rows.length === 1)
2355
+ return fromRow(rows[0]);
2356
+ return null;
2357
+ }
2358
+ return fromRow(row);
2359
+ }
2360
+ function listWebhooks(projectId) {
2361
+ const db2 = getDatabase();
2362
+ let query = "SELECT * FROM webhooks WHERE active = 1";
2363
+ const params = [];
2364
+ if (projectId) {
2365
+ query += " AND (project_id = ? OR project_id IS NULL)";
2366
+ params.push(projectId);
2367
+ }
2368
+ query += " ORDER BY created_at DESC";
2369
+ const rows = db2.query(query).all(...params);
2370
+ return rows.map(fromRow);
2371
+ }
2372
+ function deleteWebhook(id) {
2373
+ const db2 = getDatabase();
2374
+ const webhook = getWebhook(id);
2375
+ if (!webhook)
2376
+ return false;
2377
+ db2.query("DELETE FROM webhooks WHERE id = ?").run(webhook.id);
2378
+ return true;
2379
+ }
2380
+ function signPayload(body, secret) {
2381
+ const encoder = new TextEncoder;
2382
+ const key = encoder.encode(secret);
2383
+ const data = encoder.encode(body);
2384
+ let hash = 0;
2385
+ for (let i = 0;i < data.length; i++) {
2386
+ hash = (hash << 5) - hash + data[i] + (key[i % key.length] ?? 0) | 0;
2387
+ }
2388
+ return `sha256=${Math.abs(hash).toString(16).padStart(16, "0")}`;
2389
+ }
2390
+ function formatSlackPayload(payload) {
2391
+ const status = payload.run.status === "passed" ? ":white_check_mark:" : ":x:";
2392
+ const color = payload.run.status === "passed" ? "#22c55e" : "#ef4444";
2393
+ return {
2394
+ attachments: [
2395
+ {
2396
+ color,
2397
+ blocks: [
2398
+ {
2399
+ type: "section",
2400
+ text: {
2401
+ type: "mrkdwn",
2402
+ text: `${status} *Test Run ${payload.run.status.toUpperCase()}*
2403
+ ` + `URL: ${payload.run.url}
2404
+ ` + `Results: ${payload.run.passed}/${payload.run.total} passed` + (payload.run.failed > 0 ? ` (${payload.run.failed} failed)` : "") + (payload.schedule ? `
2405
+ Schedule: ${payload.schedule.name}` : "")
2406
+ }
2407
+ }
2408
+ ]
2409
+ }
2410
+ ]
2411
+ };
2412
+ }
2413
+ async function dispatchWebhooks(event, run, schedule) {
2414
+ const webhooks = listWebhooks(run.projectId ?? undefined);
2415
+ const payload = {
2416
+ event,
2417
+ run: {
2418
+ id: run.id,
2419
+ url: run.url,
2420
+ status: run.status,
2421
+ passed: run.passed,
2422
+ failed: run.failed,
2423
+ total: run.total
2424
+ },
2425
+ schedule,
2426
+ timestamp: new Date().toISOString()
2427
+ };
2428
+ for (const webhook of webhooks) {
2429
+ if (!webhook.events.includes(event) && !webhook.events.includes("*"))
2430
+ continue;
2431
+ const isSlack = webhook.url.includes("hooks.slack.com");
2432
+ const body = isSlack ? JSON.stringify(formatSlackPayload(payload)) : JSON.stringify(payload);
2433
+ const headers = {
2434
+ "Content-Type": "application/json"
2435
+ };
2436
+ if (webhook.secret) {
2437
+ headers["X-Testers-Signature"] = signPayload(body, webhook.secret);
2438
+ }
2439
+ try {
2440
+ const response = await fetch(webhook.url, {
2441
+ method: "POST",
2442
+ headers,
2443
+ body
2444
+ });
2445
+ if (!response.ok) {
2446
+ await new Promise((r) => setTimeout(r, 5000));
2447
+ await fetch(webhook.url, { method: "POST", headers, body });
2448
+ }
2449
+ } catch {}
2450
+ }
2451
+ }
2452
+ async function testWebhook(id) {
2453
+ const webhook = getWebhook(id);
2454
+ if (!webhook)
2455
+ return false;
2456
+ const testPayload = {
2457
+ event: "test",
2458
+ run: { id: "test-run", url: "http://localhost:3000", status: "passed", passed: 3, failed: 0, total: 3 },
2459
+ timestamp: new Date().toISOString()
2460
+ };
2461
+ try {
2462
+ const body = JSON.stringify(testPayload);
2463
+ const response = await fetch(webhook.url, {
2464
+ method: "POST",
2465
+ headers: {
2466
+ "Content-Type": "application/json",
2467
+ ...webhook.secret ? { "X-Testers-Signature": signPayload(body, webhook.secret) } : {}
2468
+ },
2469
+ body
2470
+ });
2471
+ return response.ok;
2472
+ } catch {
2473
+ return false;
2474
+ }
2475
+ }
2476
+
2477
+ // src/lib/runner.ts
2295
2478
  var eventHandler = null;
2296
2479
  function onRunEvent(handler) {
2297
2480
  eventHandler = handler;
@@ -2300,6 +2483,20 @@ function emit(event) {
2300
2483
  if (eventHandler)
2301
2484
  eventHandler(event);
2302
2485
  }
2486
+ function withTimeout(promise, ms, label) {
2487
+ return new Promise((resolve, reject) => {
2488
+ const timer = setTimeout(() => {
2489
+ reject(new Error(`Scenario timeout after ${ms}ms: ${label}`));
2490
+ }, ms);
2491
+ promise.then((val) => {
2492
+ clearTimeout(timer);
2493
+ resolve(val);
2494
+ }, (err) => {
2495
+ clearTimeout(timer);
2496
+ reject(err);
2497
+ });
2498
+ });
2499
+ }
2303
2500
  async function runSingleScenario(scenario, runId, options) {
2304
2501
  const config = loadConfig();
2305
2502
  const model = resolveModel2(options.model ?? scenario.model ?? config.defaultModel);
@@ -2322,8 +2519,9 @@ async function runSingleScenario(scenario, runId, options) {
2322
2519
  viewport: config.browser.viewport
2323
2520
  });
2324
2521
  const targetUrl = scenario.targetPath ? `${options.url.replace(/\/$/, "")}${scenario.targetPath}` : options.url;
2325
- await page.goto(targetUrl, { timeout: options.timeout ?? config.browser.timeout });
2326
- const agentResult = await runAgentLoop({
2522
+ const scenarioTimeout = scenario.timeoutMs ?? options.timeout ?? config.browser.timeout ?? 60000;
2523
+ await page.goto(targetUrl, { timeout: Math.min(scenarioTimeout, 30000) });
2524
+ const agentResult = await withTimeout(runAgentLoop({
2327
2525
  client,
2328
2526
  page,
2329
2527
  scenario,
@@ -2344,7 +2542,7 @@ async function runSingleScenario(scenario, runId, options) {
2344
2542
  stepNumber: stepEvent.stepNumber
2345
2543
  });
2346
2544
  }
2347
- });
2545
+ }), scenarioTimeout, scenario.name);
2348
2546
  for (const ss of agentResult.screenshots) {
2349
2547
  createScreenshot({
2350
2548
  resultId: result.id,
@@ -2476,6 +2674,8 @@ async function runBatch(scenarios, options) {
2476
2674
  finished_at: new Date().toISOString()
2477
2675
  });
2478
2676
  emit({ type: "run:complete", runId: run.id });
2677
+ const eventType = finalRun.status === "failed" ? "failed" : "completed";
2678
+ dispatchWebhooks(eventType, finalRun).catch(() => {});
2479
2679
  return { run: finalRun, results };
2480
2680
  }
2481
2681
  async function runByFilter(options) {
@@ -2561,6 +2761,9 @@ function startRunAsync(options) {
2561
2761
  finished_at: new Date().toISOString()
2562
2762
  });
2563
2763
  emit({ type: "run:complete", runId: run.id });
2764
+ const asyncRun = getRun(run.id);
2765
+ if (asyncRun)
2766
+ dispatchWebhooks(asyncRun.status === "failed" ? "failed" : "completed", asyncRun).catch(() => {});
2564
2767
  } catch (error) {
2565
2768
  const errorMsg = error instanceof Error ? error.message : String(error);
2566
2769
  updateRun(run.id, {
@@ -2568,6 +2771,9 @@ function startRunAsync(options) {
2568
2771
  finished_at: new Date().toISOString()
2569
2772
  });
2570
2773
  emit({ type: "run:complete", runId: run.id, error: errorMsg });
2774
+ const failedRun = getRun(run.id);
2775
+ if (failedRun)
2776
+ dispatchWebhooks("failed", failedRun).catch(() => {});
2571
2777
  }
2572
2778
  })();
2573
2779
  return { runId: run.id, scenarioCount: scenarios.length };
@@ -4104,7 +4310,7 @@ function listTemplateNames() {
4104
4310
  }
4105
4311
  // src/db/auth-presets.ts
4106
4312
  init_database();
4107
- function fromRow(row) {
4313
+ function fromRow2(row) {
4108
4314
  return {
4109
4315
  id: row.id,
4110
4316
  name: row.name,
@@ -4128,12 +4334,12 @@ function createAuthPreset(input) {
4128
4334
  function getAuthPreset(name) {
4129
4335
  const db2 = getDatabase();
4130
4336
  const row = db2.query("SELECT * FROM auth_presets WHERE name = ?").get(name);
4131
- return row ? fromRow(row) : null;
4337
+ return row ? fromRow2(row) : null;
4132
4338
  }
4133
4339
  function listAuthPresets() {
4134
4340
  const db2 = getDatabase();
4135
4341
  const rows = db2.query("SELECT * FROM auth_presets ORDER BY created_at DESC").all();
4136
- return rows.map(fromRow);
4342
+ return rows.map(fromRow2);
4137
4343
  }
4138
4344
  function deleteAuthPreset(name) {
4139
4345
  const db2 = getDatabase();
@@ -4602,157 +4808,6 @@ async function startWatcher(options) {
4602
4808
  process.on("SIGTERM", cleanup);
4603
4809
  await new Promise(() => {});
4604
4810
  }
4605
- // src/lib/webhooks.ts
4606
- init_database();
4607
- function fromRow2(row) {
4608
- return {
4609
- id: row.id,
4610
- url: row.url,
4611
- events: JSON.parse(row.events),
4612
- projectId: row.project_id,
4613
- secret: row.secret,
4614
- active: row.active === 1,
4615
- createdAt: row.created_at
4616
- };
4617
- }
4618
- function createWebhook(input) {
4619
- const db2 = getDatabase();
4620
- const id = uuid();
4621
- const events = input.events ?? ["failed"];
4622
- const secret = input.secret ?? crypto.randomUUID().replace(/-/g, "");
4623
- db2.query(`
4624
- INSERT INTO webhooks (id, url, events, project_id, secret, active, created_at)
4625
- VALUES (?, ?, ?, ?, ?, 1, ?)
4626
- `).run(id, input.url, JSON.stringify(events), input.projectId ?? null, secret, now());
4627
- return getWebhook(id);
4628
- }
4629
- function getWebhook(id) {
4630
- const db2 = getDatabase();
4631
- const row = db2.query("SELECT * FROM webhooks WHERE id = ?").get(id);
4632
- if (!row) {
4633
- const rows = db2.query("SELECT * FROM webhooks WHERE id LIKE ? || '%'").all(id);
4634
- if (rows.length === 1)
4635
- return fromRow2(rows[0]);
4636
- return null;
4637
- }
4638
- return fromRow2(row);
4639
- }
4640
- function listWebhooks(projectId) {
4641
- const db2 = getDatabase();
4642
- let query = "SELECT * FROM webhooks WHERE active = 1";
4643
- const params = [];
4644
- if (projectId) {
4645
- query += " AND (project_id = ? OR project_id IS NULL)";
4646
- params.push(projectId);
4647
- }
4648
- query += " ORDER BY created_at DESC";
4649
- const rows = db2.query(query).all(...params);
4650
- return rows.map(fromRow2);
4651
- }
4652
- function deleteWebhook(id) {
4653
- const db2 = getDatabase();
4654
- const webhook = getWebhook(id);
4655
- if (!webhook)
4656
- return false;
4657
- db2.query("DELETE FROM webhooks WHERE id = ?").run(webhook.id);
4658
- return true;
4659
- }
4660
- function signPayload(body, secret) {
4661
- const encoder = new TextEncoder;
4662
- const key = encoder.encode(secret);
4663
- const data = encoder.encode(body);
4664
- let hash = 0;
4665
- for (let i = 0;i < data.length; i++) {
4666
- hash = (hash << 5) - hash + data[i] + (key[i % key.length] ?? 0) | 0;
4667
- }
4668
- return `sha256=${Math.abs(hash).toString(16).padStart(16, "0")}`;
4669
- }
4670
- function formatSlackPayload(payload) {
4671
- const status = payload.run.status === "passed" ? ":white_check_mark:" : ":x:";
4672
- const color = payload.run.status === "passed" ? "#22c55e" : "#ef4444";
4673
- return {
4674
- attachments: [
4675
- {
4676
- color,
4677
- blocks: [
4678
- {
4679
- type: "section",
4680
- text: {
4681
- type: "mrkdwn",
4682
- text: `${status} *Test Run ${payload.run.status.toUpperCase()}*
4683
- ` + `URL: ${payload.run.url}
4684
- ` + `Results: ${payload.run.passed}/${payload.run.total} passed` + (payload.run.failed > 0 ? ` (${payload.run.failed} failed)` : "") + (payload.schedule ? `
4685
- Schedule: ${payload.schedule.name}` : "")
4686
- }
4687
- }
4688
- ]
4689
- }
4690
- ]
4691
- };
4692
- }
4693
- async function dispatchWebhooks(event, run, schedule) {
4694
- const webhooks = listWebhooks(run.projectId ?? undefined);
4695
- const payload = {
4696
- event,
4697
- run: {
4698
- id: run.id,
4699
- url: run.url,
4700
- status: run.status,
4701
- passed: run.passed,
4702
- failed: run.failed,
4703
- total: run.total
4704
- },
4705
- schedule,
4706
- timestamp: new Date().toISOString()
4707
- };
4708
- for (const webhook of webhooks) {
4709
- if (!webhook.events.includes(event) && !webhook.events.includes("*"))
4710
- continue;
4711
- const isSlack = webhook.url.includes("hooks.slack.com");
4712
- const body = isSlack ? JSON.stringify(formatSlackPayload(payload)) : JSON.stringify(payload);
4713
- const headers = {
4714
- "Content-Type": "application/json"
4715
- };
4716
- if (webhook.secret) {
4717
- headers["X-Testers-Signature"] = signPayload(body, webhook.secret);
4718
- }
4719
- try {
4720
- const response = await fetch(webhook.url, {
4721
- method: "POST",
4722
- headers,
4723
- body
4724
- });
4725
- if (!response.ok) {
4726
- await new Promise((r) => setTimeout(r, 5000));
4727
- await fetch(webhook.url, { method: "POST", headers, body });
4728
- }
4729
- } catch {}
4730
- }
4731
- }
4732
- async function testWebhook(id) {
4733
- const webhook = getWebhook(id);
4734
- if (!webhook)
4735
- return false;
4736
- const testPayload = {
4737
- event: "test",
4738
- run: { id: "test-run", url: "http://localhost:3000", status: "passed", passed: 3, failed: 0, total: 3 },
4739
- timestamp: new Date().toISOString()
4740
- };
4741
- try {
4742
- const body = JSON.stringify(testPayload);
4743
- const response = await fetch(webhook.url, {
4744
- method: "POST",
4745
- headers: {
4746
- "Content-Type": "application/json",
4747
- ...webhook.secret ? { "X-Testers-Signature": signPayload(body, webhook.secret) } : {}
4748
- },
4749
- body
4750
- });
4751
- return response.ok;
4752
- } catch {
4753
- return false;
4754
- }
4755
- }
4756
4811
  export {
4757
4812
  writeScenarioMeta,
4758
4813
  writeRunMeta,
@@ -0,0 +1,26 @@
1
+ import type { Page } from "playwright";
2
+ import type { Assertion } from "../types/index.js";
3
+ export interface AssertionResult {
4
+ assertion: Assertion;
5
+ passed: boolean;
6
+ actual: string;
7
+ error?: string;
8
+ }
9
+ export declare function evaluateAssertions(page: Page, assertions: Assertion[]): Promise<AssertionResult[]>;
10
+ /**
11
+ * Parse a CLI-format assertion string into an Assertion object.
12
+ *
13
+ * Formats:
14
+ * "selector:.dashboard visible"
15
+ * "selector:.dashboard not-visible"
16
+ * "text:.header contains:Welcome"
17
+ * "text:.header equals:Welcome Home"
18
+ * "no-console-errors"
19
+ * "url:contains:/dashboard"
20
+ * "title:contains:My App"
21
+ * "count:.items eq:5"
22
+ */
23
+ export declare function parseAssertionString(str: string): Assertion;
24
+ export declare function allAssertionsPassed(results: AssertionResult[]): boolean;
25
+ export declare function formatAssertionResults(results: AssertionResult[]): string;
26
+ //# sourceMappingURL=assertions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assertions.d.ts","sourceRoot":"","sources":["../../src/lib/assertions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,SAAS,EAAE,GACtB,OAAO,CAAC,eAAe,EAAE,CAAC,CAkB5B;AAmGD;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CA4E3D;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,OAAO,CAEvE;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,MAAM,CAoBzE"}
@@ -0,0 +1,2 @@
1
+ export declare function generateGitHubActionsWorkflow(): string;
2
+ //# sourceMappingURL=ci.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ci.d.ts","sourceRoot":"","sources":["../../src/lib/ci.ts"],"names":[],"mappings":"AAAA,wBAAgB,6BAA6B,IAAI,MAAM,CA4BtD"}
@@ -0,0 +1,8 @@
1
+ import type { CreateScenarioInput } from "../types/index.js";
2
+ import { createScenario } from "../db/scenarios.js";
3
+ export declare function parseOpenAPISpec(filePathOrUrl: string): CreateScenarioInput[];
4
+ export declare function importFromOpenAPI(filePathOrUrl: string, projectId?: string): {
5
+ imported: number;
6
+ scenarios: ReturnType<typeof createScenario>[];
7
+ };
8
+ //# sourceMappingURL=openapi-import.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi-import.d.ts","sourceRoot":"","sources":["../../src/lib/openapi-import.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAoB,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AA0CpD,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,mBAAmB,EAAE,CAwE7E;AAED,wBAAgB,iBAAiB,CAC/B,aAAa,EAAE,MAAM,EACrB,SAAS,CAAC,EAAE,MAAM,GACjB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,UAAU,CAAC,OAAO,cAAc,CAAC,EAAE,CAAA;CAAE,CAMtE"}
@@ -0,0 +1,24 @@
1
+ import type { CreateScenarioInput } from "../types/index.js";
2
+ import { createScenario } from "../db/scenarios.js";
3
+ export interface RecordedAction {
4
+ type: "navigate" | "click" | "fill" | "select" | "press" | "scroll";
5
+ selector?: string;
6
+ value?: string;
7
+ url?: string;
8
+ key?: string;
9
+ timestamp: number;
10
+ }
11
+ export interface RecordingResult {
12
+ actions: RecordedAction[];
13
+ url: string;
14
+ duration: number;
15
+ }
16
+ export declare function recordSession(url: string, options?: {
17
+ timeout?: number;
18
+ }): Promise<RecordingResult>;
19
+ export declare function actionsToScenarioInput(recording: RecordingResult, name: string, projectId?: string): CreateScenarioInput;
20
+ export declare function recordAndSave(url: string, name: string, projectId?: string): Promise<{
21
+ recording: RecordingResult;
22
+ scenario: ReturnType<typeof createScenario>;
23
+ }>;
24
+ //# sourceMappingURL=recorder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../src/lib/recorder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,UAAU,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7B,OAAO,CAAC,eAAe,CAAC,CAwH1B;AAED,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,eAAe,EAC1B,IAAI,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,MAAM,GACjB,mBAAmB,CAsCrB;AAED,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC;IAAE,SAAS,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,UAAU,CAAC,OAAO,cAAc,CAAC,CAAA;CAAE,CAAC,CAKtF"}
@@ -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,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;AAMD,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,MAAM,CAAC,CAiGjB;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,CAoH1C;AAED,wBAAsB,WAAW,CAC/B,OAAO,EAAE,UAAU,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GACnF,OAAO,CAAC;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAuB1C;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,UAAU,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GACnF;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CA+E1C"}
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"}
@@ -0,0 +1,36 @@
1
+ import type { Run } from "../types/index.js";
2
+ export interface VisualDiffResult {
3
+ scenarioId: string;
4
+ stepNumber: number;
5
+ action: string;
6
+ baselinePath: string;
7
+ currentPath: string;
8
+ diffPercent: number;
9
+ isRegression: boolean;
10
+ }
11
+ /**
12
+ * Mark a run as the visual baseline. Unsets any previous baseline for the same project.
13
+ */
14
+ export declare function setBaseline(runId: string): void;
15
+ /**
16
+ * Get the most recent baseline run, optionally filtered by project.
17
+ */
18
+ export declare function getBaseline(projectId?: string): Run | null;
19
+ /**
20
+ * Compare two image files at the byte level.
21
+ * Since we cannot use sharp/canvas, we do a raw buffer comparison.
22
+ */
23
+ export declare function compareImages(image1Path: string, image2Path: string): {
24
+ diffPercent: number;
25
+ diffPixels: number;
26
+ totalPixels: number;
27
+ };
28
+ /**
29
+ * Compare screenshots from two runs, matching by scenario + step number.
30
+ */
31
+ export declare function compareRunScreenshots(runId: string, baselineRunId: string, threshold?: number): VisualDiffResult[];
32
+ /**
33
+ * Format visual diff results for terminal output with colored diff percentages.
34
+ */
35
+ export declare function formatVisualDiffTerminal(results: VisualDiffResult[], threshold?: number): string;
36
+ //# sourceMappingURL=visual-diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visual-diff.d.ts","sourceRoot":"","sources":["../../src/lib/visual-diff.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAE7C,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;CACvB;AAID;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAiB/C;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI,CAc1D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAuClE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,EACrB,SAAS,GAAE,MAA0B,GACpC,gBAAgB,EAAE,CAiDpB;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,gBAAgB,EAAE,EAC3B,SAAS,GAAE,MAA0B,GACpC,MAAM,CA2CR"}