@hasna/testers 0.0.4 → 0.0.6
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 +490 -9
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/flows.d.ts +12 -0
- package/dist/db/flows.d.ts.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +295 -8
- package/dist/lib/ai-client.d.ts +9 -0
- package/dist/lib/ai-client.d.ts.map +1 -1
- package/dist/lib/runner.d.ts +6 -1
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/mcp/index.js +955 -669
- package/dist/server/index.js +380 -85
- package/dist/types/index.d.ts +31 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2165,7 +2165,18 @@ function scheduleFromRow(row) {
|
|
|
2165
2165
|
updatedAt: row.updated_at
|
|
2166
2166
|
};
|
|
2167
2167
|
}
|
|
2168
|
-
|
|
2168
|
+
function flowFromRow(row) {
|
|
2169
|
+
return {
|
|
2170
|
+
id: row.id,
|
|
2171
|
+
projectId: row.project_id,
|
|
2172
|
+
name: row.name,
|
|
2173
|
+
description: row.description,
|
|
2174
|
+
scenarioIds: JSON.parse(row.scenario_ids),
|
|
2175
|
+
createdAt: row.created_at,
|
|
2176
|
+
updatedAt: row.updated_at
|
|
2177
|
+
};
|
|
2178
|
+
}
|
|
2179
|
+
var MODEL_MAP, VersionConflictError, BrowserError, AIClientError, TodosConnectionError, ScheduleNotFoundError, DependencyCycleError;
|
|
2169
2180
|
var init_types = __esm(() => {
|
|
2170
2181
|
MODEL_MAP = {
|
|
2171
2182
|
quick: "claude-haiku-4-5-20251001",
|
|
@@ -2202,6 +2213,12 @@ var init_types = __esm(() => {
|
|
|
2202
2213
|
this.name = "ScheduleNotFoundError";
|
|
2203
2214
|
}
|
|
2204
2215
|
};
|
|
2216
|
+
DependencyCycleError = class DependencyCycleError extends Error {
|
|
2217
|
+
constructor(scenarioId, dependsOn) {
|
|
2218
|
+
super(`Adding dependency ${dependsOn} to ${scenarioId} would create a cycle`);
|
|
2219
|
+
this.name = "DependencyCycleError";
|
|
2220
|
+
}
|
|
2221
|
+
};
|
|
2205
2222
|
});
|
|
2206
2223
|
|
|
2207
2224
|
// src/db/database.ts
|
|
@@ -2425,6 +2442,28 @@ var init_database = __esm(() => {
|
|
|
2425
2442
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2426
2443
|
);
|
|
2427
2444
|
CREATE INDEX IF NOT EXISTS idx_webhooks_active ON webhooks(active);
|
|
2445
|
+
`,
|
|
2446
|
+
`
|
|
2447
|
+
CREATE TABLE IF NOT EXISTS scenario_dependencies (
|
|
2448
|
+
scenario_id TEXT NOT NULL REFERENCES scenarios(id) ON DELETE CASCADE,
|
|
2449
|
+
depends_on TEXT NOT NULL REFERENCES scenarios(id) ON DELETE CASCADE,
|
|
2450
|
+
PRIMARY KEY (scenario_id, depends_on),
|
|
2451
|
+
CHECK (scenario_id != depends_on)
|
|
2452
|
+
);
|
|
2453
|
+
|
|
2454
|
+
CREATE TABLE IF NOT EXISTS flows (
|
|
2455
|
+
id TEXT PRIMARY KEY,
|
|
2456
|
+
project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
|
|
2457
|
+
name TEXT NOT NULL,
|
|
2458
|
+
description TEXT,
|
|
2459
|
+
scenario_ids TEXT NOT NULL DEFAULT '[]',
|
|
2460
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2461
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2462
|
+
);
|
|
2463
|
+
|
|
2464
|
+
CREATE INDEX IF NOT EXISTS idx_deps_scenario ON scenario_dependencies(scenario_id);
|
|
2465
|
+
CREATE INDEX IF NOT EXISTS idx_deps_depends ON scenario_dependencies(depends_on);
|
|
2466
|
+
CREATE INDEX IF NOT EXISTS idx_flows_project ON flows(project_id);
|
|
2428
2467
|
`
|
|
2429
2468
|
];
|
|
2430
2469
|
});
|
|
@@ -2561,6 +2600,157 @@ var init_runs = __esm(() => {
|
|
|
2561
2600
|
init_database();
|
|
2562
2601
|
});
|
|
2563
2602
|
|
|
2603
|
+
// src/db/flows.ts
|
|
2604
|
+
var exports_flows = {};
|
|
2605
|
+
__export(exports_flows, {
|
|
2606
|
+
topologicalSort: () => topologicalSort,
|
|
2607
|
+
removeDependency: () => removeDependency,
|
|
2608
|
+
listFlows: () => listFlows,
|
|
2609
|
+
getTransitiveDependencies: () => getTransitiveDependencies,
|
|
2610
|
+
getFlow: () => getFlow,
|
|
2611
|
+
getDependents: () => getDependents,
|
|
2612
|
+
getDependencies: () => getDependencies,
|
|
2613
|
+
deleteFlow: () => deleteFlow,
|
|
2614
|
+
createFlow: () => createFlow,
|
|
2615
|
+
addDependency: () => addDependency
|
|
2616
|
+
});
|
|
2617
|
+
function addDependency(scenarioId, dependsOn) {
|
|
2618
|
+
const db2 = getDatabase();
|
|
2619
|
+
const visited = new Set;
|
|
2620
|
+
const queue = [dependsOn];
|
|
2621
|
+
while (queue.length > 0) {
|
|
2622
|
+
const current = queue.shift();
|
|
2623
|
+
if (current === scenarioId) {
|
|
2624
|
+
throw new DependencyCycleError(scenarioId, dependsOn);
|
|
2625
|
+
}
|
|
2626
|
+
if (visited.has(current))
|
|
2627
|
+
continue;
|
|
2628
|
+
visited.add(current);
|
|
2629
|
+
const deps = db2.query("SELECT depends_on FROM scenario_dependencies WHERE scenario_id = ?").all(current);
|
|
2630
|
+
for (const dep of deps) {
|
|
2631
|
+
if (!visited.has(dep.depends_on)) {
|
|
2632
|
+
queue.push(dep.depends_on);
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
db2.query("INSERT OR IGNORE INTO scenario_dependencies (scenario_id, depends_on) VALUES (?, ?)").run(scenarioId, dependsOn);
|
|
2637
|
+
}
|
|
2638
|
+
function removeDependency(scenarioId, dependsOn) {
|
|
2639
|
+
const db2 = getDatabase();
|
|
2640
|
+
const result = db2.query("DELETE FROM scenario_dependencies WHERE scenario_id = ? AND depends_on = ?").run(scenarioId, dependsOn);
|
|
2641
|
+
return result.changes > 0;
|
|
2642
|
+
}
|
|
2643
|
+
function getDependencies(scenarioId) {
|
|
2644
|
+
const db2 = getDatabase();
|
|
2645
|
+
const rows = db2.query("SELECT depends_on FROM scenario_dependencies WHERE scenario_id = ?").all(scenarioId);
|
|
2646
|
+
return rows.map((r) => r.depends_on);
|
|
2647
|
+
}
|
|
2648
|
+
function getDependents(scenarioId) {
|
|
2649
|
+
const db2 = getDatabase();
|
|
2650
|
+
const rows = db2.query("SELECT scenario_id FROM scenario_dependencies WHERE depends_on = ?").all(scenarioId);
|
|
2651
|
+
return rows.map((r) => r.scenario_id);
|
|
2652
|
+
}
|
|
2653
|
+
function getTransitiveDependencies(scenarioId) {
|
|
2654
|
+
const db2 = getDatabase();
|
|
2655
|
+
const visited = new Set;
|
|
2656
|
+
const queue = [scenarioId];
|
|
2657
|
+
while (queue.length > 0) {
|
|
2658
|
+
const current = queue.shift();
|
|
2659
|
+
const deps = db2.query("SELECT depends_on FROM scenario_dependencies WHERE scenario_id = ?").all(current);
|
|
2660
|
+
for (const dep of deps) {
|
|
2661
|
+
if (!visited.has(dep.depends_on)) {
|
|
2662
|
+
visited.add(dep.depends_on);
|
|
2663
|
+
queue.push(dep.depends_on);
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
return Array.from(visited);
|
|
2668
|
+
}
|
|
2669
|
+
function topologicalSort(scenarioIds) {
|
|
2670
|
+
const db2 = getDatabase();
|
|
2671
|
+
const idSet = new Set(scenarioIds);
|
|
2672
|
+
const inDegree = new Map;
|
|
2673
|
+
const dependents = new Map;
|
|
2674
|
+
for (const id of scenarioIds) {
|
|
2675
|
+
inDegree.set(id, 0);
|
|
2676
|
+
dependents.set(id, []);
|
|
2677
|
+
}
|
|
2678
|
+
for (const id of scenarioIds) {
|
|
2679
|
+
const deps = db2.query("SELECT depends_on FROM scenario_dependencies WHERE scenario_id = ?").all(id);
|
|
2680
|
+
for (const dep of deps) {
|
|
2681
|
+
if (idSet.has(dep.depends_on)) {
|
|
2682
|
+
inDegree.set(id, (inDegree.get(id) ?? 0) + 1);
|
|
2683
|
+
dependents.get(dep.depends_on).push(id);
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
const queue = [];
|
|
2688
|
+
for (const [id, deg] of inDegree) {
|
|
2689
|
+
if (deg === 0)
|
|
2690
|
+
queue.push(id);
|
|
2691
|
+
}
|
|
2692
|
+
const sorted = [];
|
|
2693
|
+
while (queue.length > 0) {
|
|
2694
|
+
const current = queue.shift();
|
|
2695
|
+
sorted.push(current);
|
|
2696
|
+
for (const dep of dependents.get(current) ?? []) {
|
|
2697
|
+
const newDeg = (inDegree.get(dep) ?? 1) - 1;
|
|
2698
|
+
inDegree.set(dep, newDeg);
|
|
2699
|
+
if (newDeg === 0)
|
|
2700
|
+
queue.push(dep);
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
if (sorted.length !== scenarioIds.length) {
|
|
2704
|
+
throw new DependencyCycleError("multiple", "multiple");
|
|
2705
|
+
}
|
|
2706
|
+
return sorted;
|
|
2707
|
+
}
|
|
2708
|
+
function createFlow(input) {
|
|
2709
|
+
const db2 = getDatabase();
|
|
2710
|
+
const id = uuid();
|
|
2711
|
+
const timestamp = now();
|
|
2712
|
+
db2.query(`
|
|
2713
|
+
INSERT INTO flows (id, project_id, name, description, scenario_ids, created_at, updated_at)
|
|
2714
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
2715
|
+
`).run(id, input.projectId ?? null, input.name, input.description ?? null, JSON.stringify(input.scenarioIds), timestamp, timestamp);
|
|
2716
|
+
return getFlow(id);
|
|
2717
|
+
}
|
|
2718
|
+
function getFlow(id) {
|
|
2719
|
+
const db2 = getDatabase();
|
|
2720
|
+
let row = db2.query("SELECT * FROM flows WHERE id = ?").get(id);
|
|
2721
|
+
if (row)
|
|
2722
|
+
return flowFromRow(row);
|
|
2723
|
+
const fullId = resolvePartialId("flows", id);
|
|
2724
|
+
if (fullId) {
|
|
2725
|
+
row = db2.query("SELECT * FROM flows WHERE id = ?").get(fullId);
|
|
2726
|
+
if (row)
|
|
2727
|
+
return flowFromRow(row);
|
|
2728
|
+
}
|
|
2729
|
+
return null;
|
|
2730
|
+
}
|
|
2731
|
+
function listFlows(projectId) {
|
|
2732
|
+
const db2 = getDatabase();
|
|
2733
|
+
if (projectId) {
|
|
2734
|
+
const rows2 = db2.query("SELECT * FROM flows WHERE project_id = ? ORDER BY created_at DESC").all(projectId);
|
|
2735
|
+
return rows2.map(flowFromRow);
|
|
2736
|
+
}
|
|
2737
|
+
const rows = db2.query("SELECT * FROM flows ORDER BY created_at DESC").all();
|
|
2738
|
+
return rows.map(flowFromRow);
|
|
2739
|
+
}
|
|
2740
|
+
function deleteFlow(id) {
|
|
2741
|
+
const db2 = getDatabase();
|
|
2742
|
+
const flow = getFlow(id);
|
|
2743
|
+
if (!flow)
|
|
2744
|
+
return false;
|
|
2745
|
+
const result = db2.query("DELETE FROM flows WHERE id = ?").run(flow.id);
|
|
2746
|
+
return result.changes > 0;
|
|
2747
|
+
}
|
|
2748
|
+
var init_flows = __esm(() => {
|
|
2749
|
+
init_database();
|
|
2750
|
+
init_database();
|
|
2751
|
+
init_types();
|
|
2752
|
+
});
|
|
2753
|
+
|
|
2564
2754
|
// node_modules/commander/esm.mjs
|
|
2565
2755
|
var import__ = __toESM(require_commander(), 1);
|
|
2566
2756
|
var {
|
|
@@ -3641,7 +3831,8 @@ async function runAgentLoop(options) {
|
|
|
3641
3831
|
screenshotter,
|
|
3642
3832
|
model,
|
|
3643
3833
|
runId,
|
|
3644
|
-
maxTurns = 30
|
|
3834
|
+
maxTurns = 30,
|
|
3835
|
+
onStep
|
|
3645
3836
|
} = options;
|
|
3646
3837
|
const systemPrompt = [
|
|
3647
3838
|
"You are an expert QA testing agent. Your job is to thoroughly test web application scenarios.",
|
|
@@ -3700,8 +3891,8 @@ async function runAgentLoop(options) {
|
|
|
3700
3891
|
}
|
|
3701
3892
|
const toolUseBlocks = response.content.filter((block) => block.type === "tool_use");
|
|
3702
3893
|
if (toolUseBlocks.length === 0 && response.stop_reason === "end_turn") {
|
|
3703
|
-
const
|
|
3704
|
-
const textReasoning =
|
|
3894
|
+
const textBlocks2 = response.content.filter((block) => block.type === "text");
|
|
3895
|
+
const textReasoning = textBlocks2.map((b) => b.text).join(`
|
|
3705
3896
|
`);
|
|
3706
3897
|
return {
|
|
3707
3898
|
status: "error",
|
|
@@ -3712,10 +3903,22 @@ async function runAgentLoop(options) {
|
|
|
3712
3903
|
};
|
|
3713
3904
|
}
|
|
3714
3905
|
const toolResults = [];
|
|
3906
|
+
const textBlocks = response.content.filter((block) => block.type === "text");
|
|
3907
|
+
if (textBlocks.length > 0 && onStep) {
|
|
3908
|
+
const thinking = textBlocks.map((b) => b.text).join(`
|
|
3909
|
+
`);
|
|
3910
|
+
onStep({ type: "thinking", thinking, stepNumber });
|
|
3911
|
+
}
|
|
3715
3912
|
for (const toolBlock of toolUseBlocks) {
|
|
3716
3913
|
stepNumber++;
|
|
3717
3914
|
const toolInput = toolBlock.input;
|
|
3915
|
+
if (onStep) {
|
|
3916
|
+
onStep({ type: "tool_call", toolName: toolBlock.name, toolInput, stepNumber });
|
|
3917
|
+
}
|
|
3718
3918
|
const execResult = await executeTool(page, screenshotter, toolBlock.name, toolInput, { runId, scenarioSlug, stepNumber });
|
|
3919
|
+
if (onStep) {
|
|
3920
|
+
onStep({ type: "tool_result", toolName: toolBlock.name, toolResult: execResult.result, stepNumber });
|
|
3921
|
+
}
|
|
3719
3922
|
if (execResult.screenshot) {
|
|
3720
3923
|
screenshots.push({
|
|
3721
3924
|
...execResult.screenshot,
|
|
@@ -3824,6 +4027,9 @@ function loadConfig() {
|
|
|
3824
4027
|
|
|
3825
4028
|
// src/lib/runner.ts
|
|
3826
4029
|
var eventHandler = null;
|
|
4030
|
+
function onRunEvent(handler) {
|
|
4031
|
+
eventHandler = handler;
|
|
4032
|
+
}
|
|
3827
4033
|
function emit(event) {
|
|
3828
4034
|
if (eventHandler)
|
|
3829
4035
|
eventHandler(event);
|
|
@@ -3858,7 +4064,20 @@ async function runSingleScenario(scenario, runId, options) {
|
|
|
3858
4064
|
screenshotter,
|
|
3859
4065
|
model,
|
|
3860
4066
|
runId,
|
|
3861
|
-
maxTurns: 30
|
|
4067
|
+
maxTurns: 30,
|
|
4068
|
+
onStep: (stepEvent) => {
|
|
4069
|
+
emit({
|
|
4070
|
+
type: `step:${stepEvent.type}`,
|
|
4071
|
+
scenarioId: scenario.id,
|
|
4072
|
+
scenarioName: scenario.name,
|
|
4073
|
+
runId,
|
|
4074
|
+
toolName: stepEvent.toolName,
|
|
4075
|
+
toolInput: stepEvent.toolInput,
|
|
4076
|
+
toolResult: stepEvent.toolResult,
|
|
4077
|
+
thinking: stepEvent.thinking,
|
|
4078
|
+
stepNumber: stepEvent.stepNumber
|
|
4079
|
+
});
|
|
4080
|
+
}
|
|
3862
4081
|
});
|
|
3863
4082
|
for (const ss of agentResult.screenshots) {
|
|
3864
4083
|
createScreenshot({
|
|
@@ -3911,24 +4130,70 @@ async function runBatch(scenarios, options) {
|
|
|
3911
4130
|
projectId: options.projectId
|
|
3912
4131
|
});
|
|
3913
4132
|
updateRun(run.id, { status: "running", total: scenarios.length });
|
|
4133
|
+
let sortedScenarios = scenarios;
|
|
4134
|
+
try {
|
|
4135
|
+
const { topologicalSort: topologicalSort2 } = await Promise.resolve().then(() => (init_flows(), exports_flows));
|
|
4136
|
+
const scenarioIds = scenarios.map((s) => s.id);
|
|
4137
|
+
const sortedIds = topologicalSort2(scenarioIds);
|
|
4138
|
+
const scenarioMap = new Map(scenarios.map((s) => [s.id, s]));
|
|
4139
|
+
sortedScenarios = sortedIds.map((id) => scenarioMap.get(id)).filter((s) => s !== undefined);
|
|
4140
|
+
for (const s of scenarios) {
|
|
4141
|
+
if (!sortedIds.includes(s.id))
|
|
4142
|
+
sortedScenarios.push(s);
|
|
4143
|
+
}
|
|
4144
|
+
} catch {}
|
|
3914
4145
|
const results = [];
|
|
4146
|
+
const failedScenarioIds = new Set;
|
|
4147
|
+
const canRun = async (scenario) => {
|
|
4148
|
+
try {
|
|
4149
|
+
const { getDependencies: getDependencies2 } = await Promise.resolve().then(() => (init_flows(), exports_flows));
|
|
4150
|
+
const deps = getDependencies2(scenario.id);
|
|
4151
|
+
for (const depId of deps) {
|
|
4152
|
+
if (failedScenarioIds.has(depId))
|
|
4153
|
+
return false;
|
|
4154
|
+
}
|
|
4155
|
+
} catch {}
|
|
4156
|
+
return true;
|
|
4157
|
+
};
|
|
3915
4158
|
if (parallel <= 1) {
|
|
3916
|
-
for (const scenario of
|
|
4159
|
+
for (const scenario of sortedScenarios) {
|
|
4160
|
+
if (!await canRun(scenario)) {
|
|
4161
|
+
const result2 = createResult({ runId: run.id, scenarioId: scenario.id, model, stepsTotal: 0 });
|
|
4162
|
+
const skipped = updateResult(result2.id, { status: "skipped", error: "Skipped: dependency failed" });
|
|
4163
|
+
results.push(skipped);
|
|
4164
|
+
failedScenarioIds.add(scenario.id);
|
|
4165
|
+
emit({ type: "scenario:error", scenarioId: scenario.id, scenarioName: scenario.name, error: "Dependency failed \u2014 skipped", runId: run.id });
|
|
4166
|
+
continue;
|
|
4167
|
+
}
|
|
3917
4168
|
const result = await runSingleScenario(scenario, run.id, options);
|
|
3918
4169
|
results.push(result);
|
|
4170
|
+
if (result.status === "failed" || result.status === "error") {
|
|
4171
|
+
failedScenarioIds.add(scenario.id);
|
|
4172
|
+
}
|
|
3919
4173
|
}
|
|
3920
4174
|
} else {
|
|
3921
|
-
const queue = [...
|
|
4175
|
+
const queue = [...sortedScenarios];
|
|
3922
4176
|
const running = [];
|
|
3923
4177
|
const processNext = async () => {
|
|
3924
4178
|
const scenario = queue.shift();
|
|
3925
4179
|
if (!scenario)
|
|
3926
4180
|
return;
|
|
4181
|
+
if (!await canRun(scenario)) {
|
|
4182
|
+
const result2 = createResult({ runId: run.id, scenarioId: scenario.id, model, stepsTotal: 0 });
|
|
4183
|
+
const skipped = updateResult(result2.id, { status: "skipped", error: "Skipped: dependency failed" });
|
|
4184
|
+
results.push(skipped);
|
|
4185
|
+
failedScenarioIds.add(scenario.id);
|
|
4186
|
+
await processNext();
|
|
4187
|
+
return;
|
|
4188
|
+
}
|
|
3927
4189
|
const result = await runSingleScenario(scenario, run.id, options);
|
|
3928
4190
|
results.push(result);
|
|
4191
|
+
if (result.status === "failed" || result.status === "error") {
|
|
4192
|
+
failedScenarioIds.add(scenario.id);
|
|
4193
|
+
}
|
|
3929
4194
|
await processNext();
|
|
3930
4195
|
};
|
|
3931
|
-
const workers = Math.min(parallel,
|
|
4196
|
+
const workers = Math.min(parallel, sortedScenarios.length);
|
|
3932
4197
|
for (let i = 0;i < workers; i++) {
|
|
3933
4198
|
running.push(processNext());
|
|
3934
4199
|
}
|
|
@@ -5361,9 +5626,19 @@ function deleteAuthPreset(name) {
|
|
|
5361
5626
|
}
|
|
5362
5627
|
|
|
5363
5628
|
// src/cli/index.tsx
|
|
5629
|
+
init_flows();
|
|
5364
5630
|
import { existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
5631
|
+
function formatToolInput(input) {
|
|
5632
|
+
const parts = [];
|
|
5633
|
+
for (const [key, value] of Object.entries(input)) {
|
|
5634
|
+
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
5635
|
+
const truncated = str.length > 60 ? str.slice(0, 60) + "..." : str;
|
|
5636
|
+
parts.push(`${key}="${truncated}"`);
|
|
5637
|
+
}
|
|
5638
|
+
return parts.join(" ");
|
|
5639
|
+
}
|
|
5365
5640
|
var program2 = new Command;
|
|
5366
|
-
program2.name("testers").version("0.0.
|
|
5641
|
+
program2.name("testers").version("0.0.4").description("AI-powered browser testing CLI");
|
|
5367
5642
|
var CONFIG_DIR2 = join6(process.env["HOME"] ?? "~", ".testers");
|
|
5368
5643
|
var CONFIG_PATH2 = join6(CONFIG_DIR2, "config.json");
|
|
5369
5644
|
function getActiveProject() {
|
|
@@ -5543,6 +5818,47 @@ program2.command("run <url> [description]").description("Run test scenarios agai
|
|
|
5543
5818
|
console.log(chalk4.dim(` Check progress: testers results ${runId.slice(0, 8)}`));
|
|
5544
5819
|
process.exit(0);
|
|
5545
5820
|
}
|
|
5821
|
+
if (!opts.json && !opts.output) {
|
|
5822
|
+
onRunEvent((event) => {
|
|
5823
|
+
switch (event.type) {
|
|
5824
|
+
case "scenario:start":
|
|
5825
|
+
console.log(chalk4.blue(` [start] ${event.scenarioName ?? event.scenarioId}`));
|
|
5826
|
+
break;
|
|
5827
|
+
case "step:thinking":
|
|
5828
|
+
if (event.thinking) {
|
|
5829
|
+
const preview = event.thinking.length > 120 ? event.thinking.slice(0, 120) + "..." : event.thinking;
|
|
5830
|
+
console.log(chalk4.dim(` [think] ${preview}`));
|
|
5831
|
+
}
|
|
5832
|
+
break;
|
|
5833
|
+
case "step:tool_call":
|
|
5834
|
+
console.log(chalk4.cyan(` [step ${event.stepNumber}] ${event.toolName}${event.toolInput ? ` ${formatToolInput(event.toolInput)}` : ""}`));
|
|
5835
|
+
break;
|
|
5836
|
+
case "step:tool_result":
|
|
5837
|
+
if (event.toolName === "report_result") {
|
|
5838
|
+
console.log(chalk4.bold(` [result] ${event.toolResult}`));
|
|
5839
|
+
} else {
|
|
5840
|
+
const resultPreview = (event.toolResult ?? "").length > 100 ? (event.toolResult ?? "").slice(0, 100) + "..." : event.toolResult ?? "";
|
|
5841
|
+
console.log(chalk4.dim(` [done] ${resultPreview}`));
|
|
5842
|
+
}
|
|
5843
|
+
break;
|
|
5844
|
+
case "screenshot:captured":
|
|
5845
|
+
console.log(chalk4.dim(` [screenshot] ${event.screenshotPath}`));
|
|
5846
|
+
break;
|
|
5847
|
+
case "scenario:pass":
|
|
5848
|
+
console.log(chalk4.green(` [PASS] ${event.scenarioName}`));
|
|
5849
|
+
break;
|
|
5850
|
+
case "scenario:fail":
|
|
5851
|
+
console.log(chalk4.red(` [FAIL] ${event.scenarioName}`));
|
|
5852
|
+
break;
|
|
5853
|
+
case "scenario:error":
|
|
5854
|
+
console.log(chalk4.yellow(` [ERR] ${event.scenarioName}: ${event.error}`));
|
|
5855
|
+
break;
|
|
5856
|
+
}
|
|
5857
|
+
});
|
|
5858
|
+
console.log("");
|
|
5859
|
+
console.log(chalk4.bold(` Running tests against ${url}`));
|
|
5860
|
+
console.log("");
|
|
5861
|
+
}
|
|
5546
5862
|
if (description) {
|
|
5547
5863
|
const scenario = createScenario({
|
|
5548
5864
|
name: description,
|
|
@@ -6274,4 +6590,169 @@ program2.command("costs").description("Show cost tracking and budget status").op
|
|
|
6274
6590
|
process.exit(1);
|
|
6275
6591
|
}
|
|
6276
6592
|
});
|
|
6593
|
+
program2.command("chain <scenario-id>").description("Add a dependency to a scenario").requiredOption("--depends-on <id>", "Scenario ID that must run first").action((scenarioId, opts) => {
|
|
6594
|
+
try {
|
|
6595
|
+
const scenario = getScenario(scenarioId) ?? getScenarioByShortId(scenarioId);
|
|
6596
|
+
if (!scenario) {
|
|
6597
|
+
console.error(chalk4.red(`Scenario not found: ${scenarioId}`));
|
|
6598
|
+
process.exit(1);
|
|
6599
|
+
}
|
|
6600
|
+
const dep = getScenario(opts.dependsOn) ?? getScenarioByShortId(opts.dependsOn);
|
|
6601
|
+
if (!dep) {
|
|
6602
|
+
console.error(chalk4.red(`Dependency scenario not found: ${opts.dependsOn}`));
|
|
6603
|
+
process.exit(1);
|
|
6604
|
+
}
|
|
6605
|
+
addDependency(scenario.id, dep.id);
|
|
6606
|
+
console.log(chalk4.green(`${scenario.shortId} now depends on ${dep.shortId}`));
|
|
6607
|
+
} catch (error) {
|
|
6608
|
+
console.error(chalk4.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
6609
|
+
process.exit(1);
|
|
6610
|
+
}
|
|
6611
|
+
});
|
|
6612
|
+
program2.command("unchain <scenario-id>").description("Remove a dependency from a scenario").requiredOption("--from <id>", "Dependency to remove").action((scenarioId, opts) => {
|
|
6613
|
+
try {
|
|
6614
|
+
const scenario = getScenario(scenarioId) ?? getScenarioByShortId(scenarioId);
|
|
6615
|
+
if (!scenario) {
|
|
6616
|
+
console.error(chalk4.red(`Scenario not found: ${scenarioId}`));
|
|
6617
|
+
process.exit(1);
|
|
6618
|
+
}
|
|
6619
|
+
const dep = getScenario(opts.from) ?? getScenarioByShortId(opts.from);
|
|
6620
|
+
if (!dep) {
|
|
6621
|
+
console.error(chalk4.red(`Dependency not found: ${opts.from}`));
|
|
6622
|
+
process.exit(1);
|
|
6623
|
+
}
|
|
6624
|
+
removeDependency(scenario.id, dep.id);
|
|
6625
|
+
console.log(chalk4.green(`Removed dependency: ${scenario.shortId} no longer depends on ${dep.shortId}`));
|
|
6626
|
+
} catch (error) {
|
|
6627
|
+
console.error(chalk4.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
6628
|
+
process.exit(1);
|
|
6629
|
+
}
|
|
6630
|
+
});
|
|
6631
|
+
program2.command("deps <scenario-id>").description("Show dependencies for a scenario").action((scenarioId) => {
|
|
6632
|
+
try {
|
|
6633
|
+
const scenario = getScenario(scenarioId) ?? getScenarioByShortId(scenarioId);
|
|
6634
|
+
if (!scenario) {
|
|
6635
|
+
console.error(chalk4.red(`Scenario not found: ${scenarioId}`));
|
|
6636
|
+
process.exit(1);
|
|
6637
|
+
}
|
|
6638
|
+
const deps = getDependencies(scenario.id);
|
|
6639
|
+
const dependents = getDependents(scenario.id);
|
|
6640
|
+
console.log("");
|
|
6641
|
+
console.log(chalk4.bold(` Dependencies for ${scenario.shortId}: ${scenario.name}`));
|
|
6642
|
+
console.log("");
|
|
6643
|
+
if (deps.length > 0) {
|
|
6644
|
+
console.log(chalk4.dim(" Depends on:"));
|
|
6645
|
+
for (const depId of deps) {
|
|
6646
|
+
const s = getScenario(depId);
|
|
6647
|
+
console.log(` \u2192 ${s ? `${s.shortId}: ${s.name}` : depId.slice(0, 8)}`);
|
|
6648
|
+
}
|
|
6649
|
+
} else {
|
|
6650
|
+
console.log(chalk4.dim(" No dependencies"));
|
|
6651
|
+
}
|
|
6652
|
+
if (dependents.length > 0) {
|
|
6653
|
+
console.log("");
|
|
6654
|
+
console.log(chalk4.dim(" Required by:"));
|
|
6655
|
+
for (const depId of dependents) {
|
|
6656
|
+
const s = getScenario(depId);
|
|
6657
|
+
console.log(` \u2190 ${s ? `${s.shortId}: ${s.name}` : depId.slice(0, 8)}`);
|
|
6658
|
+
}
|
|
6659
|
+
}
|
|
6660
|
+
console.log("");
|
|
6661
|
+
} catch (error) {
|
|
6662
|
+
console.error(chalk4.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
6663
|
+
process.exit(1);
|
|
6664
|
+
}
|
|
6665
|
+
});
|
|
6666
|
+
var flowCmd = program2.command("flow").description("Manage test flows (ordered scenario chains)");
|
|
6667
|
+
flowCmd.command("create <name>").description("Create a flow from scenario IDs").requiredOption("--chain <ids>", "Comma-separated scenario IDs in order").option("--project <id>", "Project ID").action((name, opts) => {
|
|
6668
|
+
try {
|
|
6669
|
+
const ids = opts.chain.split(",").map((id) => {
|
|
6670
|
+
const s = getScenario(id.trim()) ?? getScenarioByShortId(id.trim());
|
|
6671
|
+
if (!s) {
|
|
6672
|
+
console.error(chalk4.red(`Scenario not found: ${id.trim()}`));
|
|
6673
|
+
process.exit(1);
|
|
6674
|
+
}
|
|
6675
|
+
return s.id;
|
|
6676
|
+
});
|
|
6677
|
+
for (let i = 1;i < ids.length; i++) {
|
|
6678
|
+
try {
|
|
6679
|
+
addDependency(ids[i], ids[i - 1]);
|
|
6680
|
+
} catch {}
|
|
6681
|
+
}
|
|
6682
|
+
const flow = createFlow({ name, scenarioIds: ids, projectId: resolveProject(opts.project) });
|
|
6683
|
+
console.log(chalk4.green(`Flow created: ${flow.id.slice(0, 8)} \u2014 ${flow.name} (${ids.length} scenarios)`));
|
|
6684
|
+
} catch (error) {
|
|
6685
|
+
console.error(chalk4.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
6686
|
+
process.exit(1);
|
|
6687
|
+
}
|
|
6688
|
+
});
|
|
6689
|
+
flowCmd.command("list").description("List all flows").option("--project <id>", "Project ID").action((opts) => {
|
|
6690
|
+
const flows = listFlows(resolveProject(opts.project) ?? undefined);
|
|
6691
|
+
if (flows.length === 0) {
|
|
6692
|
+
console.log(chalk4.dim(`
|
|
6693
|
+
No flows found.
|
|
6694
|
+
`));
|
|
6695
|
+
return;
|
|
6696
|
+
}
|
|
6697
|
+
console.log("");
|
|
6698
|
+
console.log(chalk4.bold(" Flows"));
|
|
6699
|
+
console.log("");
|
|
6700
|
+
for (const f of flows) {
|
|
6701
|
+
console.log(` ${chalk4.dim(f.id.slice(0, 8))} ${f.name} ${chalk4.dim(`(${f.scenarioIds.length} scenarios)`)}`);
|
|
6702
|
+
}
|
|
6703
|
+
console.log("");
|
|
6704
|
+
});
|
|
6705
|
+
flowCmd.command("show <id>").description("Show flow details").action((id) => {
|
|
6706
|
+
const flow = getFlow(id);
|
|
6707
|
+
if (!flow) {
|
|
6708
|
+
console.error(chalk4.red(`Flow not found: ${id}`));
|
|
6709
|
+
process.exit(1);
|
|
6710
|
+
}
|
|
6711
|
+
console.log("");
|
|
6712
|
+
console.log(chalk4.bold(` Flow: ${flow.name}`));
|
|
6713
|
+
console.log(` ID: ${chalk4.dim(flow.id)}`);
|
|
6714
|
+
console.log(` Scenarios (in order):`);
|
|
6715
|
+
for (let i = 0;i < flow.scenarioIds.length; i++) {
|
|
6716
|
+
const s = getScenario(flow.scenarioIds[i]);
|
|
6717
|
+
console.log(` ${i + 1}. ${s ? `${s.shortId}: ${s.name}` : flow.scenarioIds[i].slice(0, 8)}`);
|
|
6718
|
+
}
|
|
6719
|
+
console.log("");
|
|
6720
|
+
});
|
|
6721
|
+
flowCmd.command("delete <id>").description("Delete a flow").action((id) => {
|
|
6722
|
+
if (deleteFlow(id))
|
|
6723
|
+
console.log(chalk4.green("Flow deleted."));
|
|
6724
|
+
else {
|
|
6725
|
+
console.error(chalk4.red("Flow not found."));
|
|
6726
|
+
process.exit(1);
|
|
6727
|
+
}
|
|
6728
|
+
});
|
|
6729
|
+
flowCmd.command("run <id>").description("Run a flow (scenarios in dependency order)").option("-u, --url <url>", "Target URL (required)").option("-m, --model <model>", "AI model").option("--headed", "Run headed", false).option("--json", "JSON output", false).action(async (id, opts) => {
|
|
6730
|
+
try {
|
|
6731
|
+
const flow = getFlow(id);
|
|
6732
|
+
if (!flow) {
|
|
6733
|
+
console.error(chalk4.red(`Flow not found: ${id}`));
|
|
6734
|
+
process.exit(1);
|
|
6735
|
+
}
|
|
6736
|
+
if (!opts.url) {
|
|
6737
|
+
console.error(chalk4.red("--url is required for flow run"));
|
|
6738
|
+
process.exit(1);
|
|
6739
|
+
}
|
|
6740
|
+
console.log(chalk4.blue(`Running flow: ${flow.name} (${flow.scenarioIds.length} scenarios)`));
|
|
6741
|
+
const { run, results } = await runByFilter({
|
|
6742
|
+
url: opts.url,
|
|
6743
|
+
scenarioIds: flow.scenarioIds,
|
|
6744
|
+
model: opts.model,
|
|
6745
|
+
headed: opts.headed,
|
|
6746
|
+
parallel: 1
|
|
6747
|
+
});
|
|
6748
|
+
if (opts.json)
|
|
6749
|
+
console.log(formatJSON(run, results));
|
|
6750
|
+
else
|
|
6751
|
+
console.log(formatTerminal(run, results));
|
|
6752
|
+
process.exit(getExitCode(run));
|
|
6753
|
+
} catch (error) {
|
|
6754
|
+
console.error(chalk4.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
6755
|
+
process.exit(1);
|
|
6756
|
+
}
|
|
6757
|
+
});
|
|
6277
6758
|
program2.parse();
|
|
@@ -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;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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Flow, CreateFlowInput } from "../types/index.js";
|
|
2
|
+
export declare function addDependency(scenarioId: string, dependsOn: string): void;
|
|
3
|
+
export declare function removeDependency(scenarioId: string, dependsOn: string): boolean;
|
|
4
|
+
export declare function getDependencies(scenarioId: string): string[];
|
|
5
|
+
export declare function getDependents(scenarioId: string): string[];
|
|
6
|
+
export declare function getTransitiveDependencies(scenarioId: string): string[];
|
|
7
|
+
export declare function topologicalSort(scenarioIds: string[]): string[];
|
|
8
|
+
export declare function createFlow(input: CreateFlowInput): Flow;
|
|
9
|
+
export declare function getFlow(id: string): Flow | null;
|
|
10
|
+
export declare function listFlows(projectId?: string): Flow[];
|
|
11
|
+
export declare function deleteFlow(id: string): boolean;
|
|
12
|
+
//# sourceMappingURL=flows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flows.d.ts","sourceRoot":"","sources":["../../src/db/flows.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAW,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAKxE,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CA+BzE;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAM/E;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAM5D;AAED,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAM1D;AAED,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAoBtE;AAED,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAiD/D;AAID,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAmBvD;AAED,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAc/C;AAED,wBAAgB,SAAS,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,CAcpD;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAO9C"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export type { ScenarioPriority, RunStatus, ResultStatus, ModelPreset, ProjectRow, AgentRow, ScenarioRow, RunRow, ResultRow, ScreenshotRow, Project, Agent, Scenario, Run, Result, Screenshot, CreateScenarioInput, UpdateScenarioInput, CreateRunInput, ScenarioFilter, RunFilter, ScheduleRow, Schedule, CreateScheduleInput, UpdateScheduleInput, ScheduleFilter, AuthConfig, BrowserConfig, ScreenshotConfig, TestersConfig, } from "./types/index.js";
|
|
2
|
-
export { MODEL_MAP, projectFromRow, agentFromRow, scenarioFromRow, runFromRow, resultFromRow, screenshotFromRow, scheduleFromRow, ScenarioNotFoundError, RunNotFoundError, ResultNotFoundError, VersionConflictError, BrowserError, AIClientError, TodosConnectionError, ProjectNotFoundError, AgentNotFoundError, ScheduleNotFoundError, } from "./types/index.js";
|
|
1
|
+
export type { ScenarioPriority, RunStatus, ResultStatus, ModelPreset, ProjectRow, AgentRow, ScenarioRow, RunRow, ResultRow, ScreenshotRow, Project, Agent, Scenario, Run, Result, Screenshot, CreateScenarioInput, UpdateScenarioInput, CreateRunInput, ScenarioFilter, RunFilter, ScheduleRow, Schedule, CreateScheduleInput, UpdateScheduleInput, ScheduleFilter, FlowRow, Flow, CreateFlowInput, AuthConfig, BrowserConfig, ScreenshotConfig, TestersConfig, } from "./types/index.js";
|
|
2
|
+
export { MODEL_MAP, projectFromRow, agentFromRow, scenarioFromRow, runFromRow, resultFromRow, screenshotFromRow, scheduleFromRow, ScenarioNotFoundError, RunNotFoundError, ResultNotFoundError, VersionConflictError, BrowserError, AIClientError, TodosConnectionError, ProjectNotFoundError, AgentNotFoundError, ScheduleNotFoundError, FlowNotFoundError, DependencyCycleError, flowFromRow, } from "./types/index.js";
|
|
3
3
|
export { getDatabase, closeDatabase, resetDatabase, resolvePartialId, now, uuid, shortUuid, } from "./db/database.js";
|
|
4
4
|
export { createScenario, getScenario, getScenarioByShortId, listScenarios, updateScenario, deleteScenario, } from "./db/scenarios.js";
|
|
5
5
|
export { createRun, getRun, listRuns, updateRun, deleteRun, } from "./db/runs.js";
|
|
@@ -8,6 +8,7 @@ export { createScreenshot, getScreenshot, listScreenshots, getScreenshotsByResul
|
|
|
8
8
|
export { createProject, getProject, getProjectByPath, listProjects, ensureProject, } from "./db/projects.js";
|
|
9
9
|
export { registerAgent, getAgent, getAgentByName, listAgents, } from "./db/agents.js";
|
|
10
10
|
export { createSchedule, getSchedule, listSchedules, updateSchedule, deleteSchedule, getEnabledSchedules, updateLastRun, } from "./db/schedules.js";
|
|
11
|
+
export { addDependency, removeDependency, getDependencies, getDependents, getTransitiveDependencies, topologicalSort, createFlow, getFlow, listFlows, deleteFlow, } from "./db/flows.js";
|
|
11
12
|
export { loadConfig, resolveModel as resolveModelConfig, getDefaultConfig, } from "./lib/config.js";
|
|
12
13
|
export { launchBrowser, getPage, closeBrowser, BrowserPool, installBrowser, } from "./lib/browser.js";
|
|
13
14
|
export { Screenshotter, slugify, generateFilename, getScreenshotDir, ensureDir, } from "./lib/screenshotter.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,gBAAgB,EAChB,SAAS,EACT,YAAY,EACZ,WAAW,EACX,UAAU,EACV,QAAQ,EACR,WAAW,EACX,MAAM,EACN,SAAS,EACT,aAAa,EACb,OAAO,EACP,KAAK,EACL,QAAQ,EACR,GAAG,EACH,MAAM,EACN,UAAU,EACV,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,SAAS,EACT,WAAW,EACX,QAAQ,EACR,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,aAAa,GACd,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,SAAS,EACT,cAAc,EACd,YAAY,EACZ,eAAe,EACf,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,qBAAqB,EACrB,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,qBAAqB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|