@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.
- package/dist/cli/index.js +1220 -384
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/environments.d.ts +22 -0
- package/dist/db/environments.d.ts.map +1 -0
- package/dist/db/runs.d.ts.map +1 -1
- package/dist/db/scenarios.d.ts.map +1 -1
- package/dist/index.js +216 -161
- package/dist/lib/assertions.d.ts +26 -0
- package/dist/lib/assertions.d.ts.map +1 -0
- package/dist/lib/ci.d.ts +2 -0
- package/dist/lib/ci.d.ts.map +1 -0
- package/dist/lib/openapi-import.d.ts +8 -0
- package/dist/lib/openapi-import.d.ts.map +1 -0
- package/dist/lib/recorder.d.ts +24 -0
- package/dist/lib/recorder.d.ts.map +1 -0
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/lib/visual-diff.d.ts +36 -0
- package/dist/lib/visual-diff.d.ts.map +1 -0
- package/dist/mcp/index.js +156 -7
- package/dist/server/index.js +150 -7
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -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;
|
|
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"}
|
package/dist/db/runs.d.ts.map
CHANGED
|
@@ -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,
|
|
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,
|
|
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
|
-
|
|
2326
|
-
|
|
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
|
|
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 ?
|
|
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(
|
|
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"}
|
package/dist/lib/ci.d.ts
ADDED
|
@@ -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"}
|
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;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"}
|