@hasna/testers 0.0.56 → 0.0.58
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/README.md +4 -2
- package/dist/cli/index.js +162 -6
- package/dist/lib/workflow-fanout.d.ts +40 -0
- package/dist/lib/workflow-fanout.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/server/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,9 +39,11 @@ testers workflow create "Projects CRUD" \
|
|
|
39
39
|
|
|
40
40
|
testers workflow fanout --project alumia --workers 6 --url https://preview.example.com
|
|
41
41
|
testers workflow fanout wf_abc,wf_def wf_xyz --workers 12 --url https://preview.example.com --json
|
|
42
|
+
testers workflow fanout --project alumia --tag action-specific --workers 6 --batch-size 12 --batch 1 --url https://preview.example.com
|
|
43
|
+
testers workflow fanout --project alumia --tag action-specific --workers 6 --batch-size 12 --all-batches --url https://preview.example.com
|
|
42
44
|
```
|
|
43
45
|
|
|
44
|
-
`--workers` is bounded to 1-12 concurrent sandboxes. Fanout preflights provider credentials, required sandbox environment references, `rsync`, and app source directories before launching workers. Use `--dry-run` to inspect the remote commands, upload plans, and preflight checks without spawning sandboxes.
|
|
46
|
+
`--workers` is bounded to 1-12 concurrent sandboxes. Use `--batch-size` with a 1-based `--batch` to run large workflow corpora in deterministic waves, or `--all-batches` with optional `--from-batch` / `--to-batch` to run a staged range in one command. `--offset` is available for a manual selected-workflow cursor. Fanout preflights provider credentials, required sandbox environment references, `rsync`, and app source directories before launching workers. Use `--dry-run` to inspect the remote commands, upload plans, and preflight checks without spawning sandboxes.
|
|
45
47
|
|
|
46
48
|
### Next.js Route and Action Inventory
|
|
47
49
|
|
|
@@ -63,7 +65,7 @@ testers inventory next /path/to/app \
|
|
|
63
65
|
--sandbox-app-wait-url http://127.0.0.1:3000/health \
|
|
64
66
|
--sandbox-env-optional OPENAI_API_KEY
|
|
65
67
|
|
|
66
|
-
testers workflow fanout --project alumia --tag action-specific --workers 6 --url https://preview.example.com --dry-run
|
|
68
|
+
testers workflow fanout --project alumia --tag action-specific --workers 6 --batch-size 12 --all-batches --from-batch 1 --to-batch 3 --url https://preview.example.com --dry-run
|
|
67
69
|
```
|
|
68
70
|
|
|
69
71
|
Use `--action-workflow-grouping action` for one workflow per discovered action, `route` for route-specific workflows, or `area-kind` for broader workflows such as commerce buttons or admin API methods. Add the `--sandbox-app-*` flags when the sandbox should rsync, install, start, and test the app source instead of only testing an already-running URL.
|
package/dist/cli/index.js
CHANGED
|
@@ -60626,8 +60626,11 @@ var init_agents = __esm(() => {
|
|
|
60626
60626
|
// src/lib/workflow-fanout.ts
|
|
60627
60627
|
var exports_workflow_fanout = {};
|
|
60628
60628
|
__export(exports_workflow_fanout, {
|
|
60629
|
+
runWorkflowFanoutBatches: () => runWorkflowFanoutBatches,
|
|
60629
60630
|
runWorkflowFanout: () => runWorkflowFanout,
|
|
60630
60631
|
resolveWorkflowFanoutSelection: () => resolveWorkflowFanoutSelection,
|
|
60632
|
+
resolveWorkflowFanoutBatchRange: () => resolveWorkflowFanoutBatchRange,
|
|
60633
|
+
resolveWorkflowFanoutBatch: () => resolveWorkflowFanoutBatch,
|
|
60631
60634
|
normalizeFanoutWorkerCount: () => normalizeFanoutWorkerCount,
|
|
60632
60635
|
checkWorkflowFanoutReadiness: () => checkWorkflowFanoutReadiness
|
|
60633
60636
|
});
|
|
@@ -60643,6 +60646,55 @@ function normalizeFanoutWorkerCount(value) {
|
|
|
60643
60646
|
}
|
|
60644
60647
|
return workers;
|
|
60645
60648
|
}
|
|
60649
|
+
function resolveWorkflowFanoutBatch(workflows, options = {}) {
|
|
60650
|
+
const batchSize = normalizeOptionalPositiveInteger(options.batchSize, "workflow fanout batch size");
|
|
60651
|
+
const batch = normalizeOptionalPositiveInteger(options.batch, "workflow fanout batch");
|
|
60652
|
+
const offset = normalizeOptionalNonNegativeInteger(options.offset, "workflow fanout offset");
|
|
60653
|
+
if (batch !== undefined && offset !== undefined) {
|
|
60654
|
+
throw new Error("workflow fanout batch and offset cannot both be set");
|
|
60655
|
+
}
|
|
60656
|
+
if (batch !== undefined && batchSize === undefined) {
|
|
60657
|
+
throw new Error("workflow fanout batch requires batch size");
|
|
60658
|
+
}
|
|
60659
|
+
const resolvedOffset = batch !== undefined && batchSize !== undefined ? (batch - 1) * batchSize : offset ?? 0;
|
|
60660
|
+
const limit = batchSize;
|
|
60661
|
+
const selected = workflows.slice(resolvedOffset, limit === undefined ? undefined : resolvedOffset + limit);
|
|
60662
|
+
if (selected.length === 0) {
|
|
60663
|
+
throw new Error(`No testing workflows matched the fanout batch selection (matched ${workflows.length}, offset ${resolvedOffset})`);
|
|
60664
|
+
}
|
|
60665
|
+
return {
|
|
60666
|
+
workflows: selected,
|
|
60667
|
+
selection: {
|
|
60668
|
+
matched: workflows.length,
|
|
60669
|
+
offset: resolvedOffset,
|
|
60670
|
+
...limit !== undefined ? { limit } : {},
|
|
60671
|
+
...batch !== undefined ? { batch } : {},
|
|
60672
|
+
...batchSize !== undefined ? { batchSize, totalBatches: Math.ceil(workflows.length / batchSize) } : {}
|
|
60673
|
+
}
|
|
60674
|
+
};
|
|
60675
|
+
}
|
|
60676
|
+
function resolveWorkflowFanoutBatchRange(matched, options) {
|
|
60677
|
+
const batchSize = normalizeOptionalPositiveInteger(options.batchSize, "workflow fanout batch size");
|
|
60678
|
+
if (batchSize === undefined) {
|
|
60679
|
+
throw new Error("workflow fanout batch range requires batch size");
|
|
60680
|
+
}
|
|
60681
|
+
if (matched < 1) {
|
|
60682
|
+
throw new Error("workflow fanout batch range requires at least one matched workflow");
|
|
60683
|
+
}
|
|
60684
|
+
const totalBatches = Math.ceil(matched / batchSize);
|
|
60685
|
+
const batchStart = normalizeOptionalPositiveInteger(options.batchStart, "workflow fanout start batch") ?? 1;
|
|
60686
|
+
const batchEnd = normalizeOptionalPositiveInteger(options.batchEnd, "workflow fanout end batch") ?? totalBatches;
|
|
60687
|
+
if (batchStart > batchEnd) {
|
|
60688
|
+
throw new Error("workflow fanout start batch must be less than or equal to end batch");
|
|
60689
|
+
}
|
|
60690
|
+
if (batchStart > totalBatches) {
|
|
60691
|
+
throw new Error(`workflow fanout start batch ${batchStart} exceeds total batches ${totalBatches}`);
|
|
60692
|
+
}
|
|
60693
|
+
if (batchEnd > totalBatches) {
|
|
60694
|
+
throw new Error(`workflow fanout end batch ${batchEnd} exceeds total batches ${totalBatches}`);
|
|
60695
|
+
}
|
|
60696
|
+
return { batchSize, batchStart, batchEnd, totalBatches };
|
|
60697
|
+
}
|
|
60646
60698
|
function resolveWorkflowFanoutSelection(options) {
|
|
60647
60699
|
const ids = splitWorkflowIds(options.workflowIds);
|
|
60648
60700
|
const workflows = ids.length > 0 ? ids.map((id) => {
|
|
@@ -60762,7 +60814,8 @@ async function mapWithConcurrency(items, limit, worker) {
|
|
|
60762
60814
|
}
|
|
60763
60815
|
async function runWorkflowFanout(options, dependencies = {}) {
|
|
60764
60816
|
const workers = normalizeFanoutWorkerCount(options.workers);
|
|
60765
|
-
const
|
|
60817
|
+
const matchedWorkflows = resolveWorkflowFanoutSelection(options);
|
|
60818
|
+
const { workflows, selection } = resolveWorkflowFanoutBatch(matchedWorkflows, options);
|
|
60766
60819
|
const {
|
|
60767
60820
|
runTestingWorkflow: runOne = runTestingWorkflow,
|
|
60768
60821
|
preflight: preflightOverride,
|
|
@@ -60783,6 +60836,7 @@ async function runWorkflowFanout(options, dependencies = {}) {
|
|
|
60783
60836
|
return {
|
|
60784
60837
|
status: "failed",
|
|
60785
60838
|
workers,
|
|
60839
|
+
selection,
|
|
60786
60840
|
total: workflows.length,
|
|
60787
60841
|
passed: 0,
|
|
60788
60842
|
failed: workflows.length,
|
|
@@ -60838,6 +60892,7 @@ async function runWorkflowFanout(options, dependencies = {}) {
|
|
|
60838
60892
|
return {
|
|
60839
60893
|
status: dryRun ? "dry-run" : failed > 0 ? "failed" : "passed",
|
|
60840
60894
|
workers,
|
|
60895
|
+
selection,
|
|
60841
60896
|
total: items.length,
|
|
60842
60897
|
passed,
|
|
60843
60898
|
failed,
|
|
@@ -60845,6 +60900,49 @@ async function runWorkflowFanout(options, dependencies = {}) {
|
|
|
60845
60900
|
preflight
|
|
60846
60901
|
};
|
|
60847
60902
|
}
|
|
60903
|
+
async function runWorkflowFanoutBatches(options, dependencies = {}) {
|
|
60904
|
+
if (options.batch !== undefined || options.offset !== undefined) {
|
|
60905
|
+
throw new Error("workflow fanout all-batches cannot be combined with batch or offset");
|
|
60906
|
+
}
|
|
60907
|
+
const workers = normalizeFanoutWorkerCount(options.workers);
|
|
60908
|
+
const matchedWorkflows = resolveWorkflowFanoutSelection(options);
|
|
60909
|
+
const { batchSize, batchStart, batchEnd, totalBatches } = resolveWorkflowFanoutBatchRange(matchedWorkflows.length, options);
|
|
60910
|
+
const batches = [];
|
|
60911
|
+
let stoppedEarly = false;
|
|
60912
|
+
for (let batch = batchStart;batch <= batchEnd; batch++) {
|
|
60913
|
+
const result = await runWorkflowFanout({
|
|
60914
|
+
...options,
|
|
60915
|
+
batchSize,
|
|
60916
|
+
batch,
|
|
60917
|
+
batchStart: undefined,
|
|
60918
|
+
batchEnd: undefined,
|
|
60919
|
+
offset: undefined
|
|
60920
|
+
}, dependencies);
|
|
60921
|
+
batches.push(result);
|
|
60922
|
+
if (result.status === "failed" && !options.continueOnFailure) {
|
|
60923
|
+
stoppedEarly = batch < batchEnd;
|
|
60924
|
+
break;
|
|
60925
|
+
}
|
|
60926
|
+
}
|
|
60927
|
+
const total = batches.reduce((sum, batch) => sum + batch.total, 0);
|
|
60928
|
+
const passed = batches.reduce((sum, batch) => sum + batch.passed, 0);
|
|
60929
|
+
const failed = batches.reduce((sum, batch) => sum + batch.failed, 0);
|
|
60930
|
+
const dryRun = options.dryRun === true;
|
|
60931
|
+
return {
|
|
60932
|
+
status: dryRun ? "dry-run" : failed > 0 || stoppedEarly ? "failed" : "passed",
|
|
60933
|
+
workers,
|
|
60934
|
+
matched: matchedWorkflows.length,
|
|
60935
|
+
batchSize,
|
|
60936
|
+
batchStart,
|
|
60937
|
+
batchEnd,
|
|
60938
|
+
totalBatches,
|
|
60939
|
+
stoppedEarly,
|
|
60940
|
+
total,
|
|
60941
|
+
passed,
|
|
60942
|
+
failed,
|
|
60943
|
+
batches
|
|
60944
|
+
};
|
|
60945
|
+
}
|
|
60848
60946
|
function groupWorkflowsByProvider(workflows) {
|
|
60849
60947
|
const byProvider = new Map;
|
|
60850
60948
|
for (const workflow of workflows) {
|
|
@@ -60871,6 +60969,24 @@ function defaultCommandExists(command) {
|
|
|
60871
60969
|
const result = spawnSync2(command, ["--version"], { encoding: "utf8" });
|
|
60872
60970
|
return !result.error && result.status === 0;
|
|
60873
60971
|
}
|
|
60972
|
+
function normalizeOptionalPositiveInteger(value, label) {
|
|
60973
|
+
if (value === undefined)
|
|
60974
|
+
return;
|
|
60975
|
+
const normalized = Math.floor(value);
|
|
60976
|
+
if (!Number.isFinite(normalized) || normalized < 1) {
|
|
60977
|
+
throw new Error(`${label} must be a positive integer`);
|
|
60978
|
+
}
|
|
60979
|
+
return normalized;
|
|
60980
|
+
}
|
|
60981
|
+
function normalizeOptionalNonNegativeInteger(value, label) {
|
|
60982
|
+
if (value === undefined)
|
|
60983
|
+
return;
|
|
60984
|
+
const normalized = Math.floor(value);
|
|
60985
|
+
if (!Number.isFinite(normalized) || normalized < 0) {
|
|
60986
|
+
throw new Error(`${label} must be a non-negative integer`);
|
|
60987
|
+
}
|
|
60988
|
+
return normalized;
|
|
60989
|
+
}
|
|
60874
60990
|
function collectMissingSandboxEnvRefs(workflows, env, credentialResolver) {
|
|
60875
60991
|
const requiredMissing = [];
|
|
60876
60992
|
const optionalMissing = [];
|
|
@@ -95575,7 +95691,7 @@ import chalk6 from "chalk";
|
|
|
95575
95691
|
// package.json
|
|
95576
95692
|
var package_default = {
|
|
95577
95693
|
name: "@hasna/testers",
|
|
95578
|
-
version: "0.0.
|
|
95694
|
+
version: "0.0.58",
|
|
95579
95695
|
description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
|
|
95580
95696
|
type: "module",
|
|
95581
95697
|
main: "dist/index.js",
|
|
@@ -102360,22 +102476,58 @@ workflowCmd.command("run <id>").description("Run a saved testing workflow").requ
|
|
|
102360
102476
|
workflowCmd.command("fanout [ids...]").description("Run multiple saved sandbox workflows concurrently").requiredOption("-u, --url <url>", "Target URL").option("--project <id>", "Project ID").option("--tag <tag>", "Workflow scenario tag filter (repeatable)", (val, acc) => {
|
|
102361
102477
|
acc.push(val);
|
|
102362
102478
|
return acc;
|
|
102363
|
-
}, []).option("--all", "Include disabled workflows when selecting by project/tag", false).option("--workers <n>", "Concurrent sandboxes, 1-12 (default: 6)", "6").option("-m, --model <model>", "AI model").option("--headed", "Run headed", false).option("--parallel <n>", "Parallel browser workers inside each sandbox").option("--timeout <ms>", "Override workflow timeout").option("--dry-run", "Print resolved sandbox plans without spawning sandboxes", false).option("--json", "Output as JSON", false).action(async (ids, opts) => {
|
|
102479
|
+
}, []).option("--all", "Include disabled workflows when selecting by project/tag", false).option("--workers <n>", "Concurrent sandboxes, 1-12 (default: 6)", "6").option("--batch-size <n>", "Limit this run to a batch of selected workflows").option("--batch <n>", "1-based batch number to run with --batch-size").option("--offset <n>", "0-based selected-workflow offset for staged fanout").option("--all-batches", "Run all selected workflow batches sequentially with --batch-size", false).option("--from-batch <n>", "First batch to run when using --all-batches").option("--to-batch <n>", "Last batch to run when using --all-batches").option("--continue-on-failure", "Continue later batches after a failed batch", false).option("-m, --model <model>", "AI model").option("--headed", "Run headed", false).option("--parallel <n>", "Parallel browser workers inside each sandbox").option("--timeout <ms>", "Override workflow timeout").option("--dry-run", "Print resolved sandbox plans without spawning sandboxes", false).option("--json", "Output as JSON", false).action(async (ids, opts) => {
|
|
102364
102480
|
try {
|
|
102365
|
-
const
|
|
102366
|
-
const result = await runWorkflowFanout2({
|
|
102481
|
+
const fanoutOptions = {
|
|
102367
102482
|
workflowIds: ids,
|
|
102368
102483
|
projectId: opts.project ? resolveProject2(opts.project) : undefined,
|
|
102369
102484
|
tags: opts.tag,
|
|
102370
102485
|
includeDisabled: opts.all,
|
|
102371
102486
|
workers: opts.workers ? parseInt(opts.workers, 10) : undefined,
|
|
102487
|
+
batchSize: opts.batchSize ? parseInt(opts.batchSize, 10) : undefined,
|
|
102488
|
+
batch: opts.batch ? parseInt(opts.batch, 10) : undefined,
|
|
102489
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
102490
|
+
batchStart: opts.fromBatch ? parseInt(opts.fromBatch, 10) : undefined,
|
|
102491
|
+
batchEnd: opts.toBatch ? parseInt(opts.toBatch, 10) : undefined,
|
|
102492
|
+
continueOnFailure: opts.continueOnFailure,
|
|
102372
102493
|
url: opts.url,
|
|
102373
102494
|
model: opts.model,
|
|
102374
102495
|
headed: opts.headed,
|
|
102375
102496
|
parallel: opts.parallel ? parseInt(opts.parallel, 10) : undefined,
|
|
102376
102497
|
timeout: opts.timeout ? parseInt(opts.timeout, 10) : undefined,
|
|
102377
102498
|
dryRun: opts.dryRun
|
|
102378
|
-
}
|
|
102499
|
+
};
|
|
102500
|
+
const runAllBatches = opts.allBatches || opts.fromBatch !== undefined || opts.toBatch !== undefined;
|
|
102501
|
+
if (runAllBatches) {
|
|
102502
|
+
const { runWorkflowFanoutBatches: runWorkflowFanoutBatches2 } = await Promise.resolve().then(() => (init_workflow_fanout(), exports_workflow_fanout));
|
|
102503
|
+
const result2 = await runWorkflowFanoutBatches2(fanoutOptions);
|
|
102504
|
+
if (opts.json || opts.dryRun) {
|
|
102505
|
+
log(JSON.stringify(result2, null, 2));
|
|
102506
|
+
} else {
|
|
102507
|
+
const status = result2.status === "passed" ? chalk6.green("passed") : chalk6.red("failed");
|
|
102508
|
+
const stop = result2.stoppedEarly ? chalk6.yellow(" stopped early") : "";
|
|
102509
|
+
log(chalk6.bold(`Sandbox workflow fanout batches ${status}: ${result2.passed}/${result2.total} passed across ${result2.batches.length} batch(es)${stop}`));
|
|
102510
|
+
log(chalk6.dim(`Selected ${result2.matched} workflow(s), batch size ${result2.batchSize}, running batch ${result2.batchStart}-${result2.batchEnd}/${result2.totalBatches} with ${result2.workers} worker(s).`));
|
|
102511
|
+
for (const batch of result2.batches) {
|
|
102512
|
+
const batchStatus = batch.status === "passed" ? chalk6.green(batch.status) : batch.status === "dry-run" ? chalk6.yellow(batch.status) : chalk6.red(batch.status);
|
|
102513
|
+
const batchNumber = batch.selection.batch ?? "?";
|
|
102514
|
+
log(` ${batchStatus} batch ${batchNumber}/${batch.selection.totalBatches ?? result2.totalBatches}: ${batch.passed}/${batch.total} passed`);
|
|
102515
|
+
const failedItems = batch.items.filter((item) => item.status === "failed").slice(0, 5);
|
|
102516
|
+
for (const item of failedItems) {
|
|
102517
|
+
const error40 = item.error ? chalk6.dim(` ${item.error}`) : "";
|
|
102518
|
+
log(` ${chalk6.red("failed")} ${item.workflowName}${error40}`);
|
|
102519
|
+
}
|
|
102520
|
+
if (batch.items.filter((item) => item.status === "failed").length > failedItems.length) {
|
|
102521
|
+
log(chalk6.dim(` ... ${batch.items.filter((item) => item.status === "failed").length - failedItems.length} more failure(s)`));
|
|
102522
|
+
}
|
|
102523
|
+
}
|
|
102524
|
+
}
|
|
102525
|
+
if (result2.status === "failed")
|
|
102526
|
+
process.exit(1);
|
|
102527
|
+
return;
|
|
102528
|
+
}
|
|
102529
|
+
const { runWorkflowFanout: runWorkflowFanout2 } = await Promise.resolve().then(() => (init_workflow_fanout(), exports_workflow_fanout));
|
|
102530
|
+
const result = await runWorkflowFanout2(fanoutOptions);
|
|
102379
102531
|
if (opts.json || opts.dryRun) {
|
|
102380
102532
|
log(JSON.stringify(result, null, 2));
|
|
102381
102533
|
} else {
|
|
@@ -102392,6 +102544,10 @@ workflowCmd.command("fanout [ids...]").description("Run multiple saved sandbox w
|
|
|
102392
102544
|
log(` ${chalk6.yellow("warning")} ${check2.message}`);
|
|
102393
102545
|
}
|
|
102394
102546
|
}
|
|
102547
|
+
if (result.selection.matched !== result.total) {
|
|
102548
|
+
const batch = result.selection.batch !== undefined && result.selection.totalBatches !== undefined ? ` batch ${result.selection.batch}/${result.selection.totalBatches}` : "";
|
|
102549
|
+
log(chalk6.dim(`Selected ${result.total}/${result.selection.matched} workflow(s) from offset ${result.selection.offset}${batch}.`));
|
|
102550
|
+
}
|
|
102395
102551
|
const status = result.status === "passed" ? chalk6.green("passed") : chalk6.red("failed");
|
|
102396
102552
|
log(chalk6.bold(`Sandbox workflow fanout ${status}: ${result.passed}/${result.total} passed with ${result.workers} worker(s)`));
|
|
102397
102553
|
for (const item of result.items) {
|
|
@@ -6,6 +6,12 @@ export interface WorkflowFanoutOptions extends WorkflowRunOptions {
|
|
|
6
6
|
tags?: string[];
|
|
7
7
|
includeDisabled?: boolean;
|
|
8
8
|
workers?: number;
|
|
9
|
+
batchSize?: number;
|
|
10
|
+
batch?: number;
|
|
11
|
+
offset?: number;
|
|
12
|
+
batchStart?: number;
|
|
13
|
+
batchEnd?: number;
|
|
14
|
+
continueOnFailure?: boolean;
|
|
9
15
|
}
|
|
10
16
|
export interface WorkflowFanoutItem {
|
|
11
17
|
workflowId: string;
|
|
@@ -22,12 +28,27 @@ export interface WorkflowFanoutItem {
|
|
|
22
28
|
export interface WorkflowFanoutResult {
|
|
23
29
|
status: "passed" | "failed" | "dry-run";
|
|
24
30
|
workers: number;
|
|
31
|
+
selection: WorkflowFanoutSelection;
|
|
25
32
|
total: number;
|
|
26
33
|
passed: number;
|
|
27
34
|
failed: number;
|
|
28
35
|
items: WorkflowFanoutItem[];
|
|
29
36
|
preflight?: WorkflowFanoutPreflightResult;
|
|
30
37
|
}
|
|
38
|
+
export interface WorkflowFanoutBatchesResult {
|
|
39
|
+
status: "passed" | "failed" | "dry-run";
|
|
40
|
+
workers: number;
|
|
41
|
+
matched: number;
|
|
42
|
+
batchSize: number;
|
|
43
|
+
batchStart: number;
|
|
44
|
+
batchEnd: number;
|
|
45
|
+
totalBatches: number;
|
|
46
|
+
stoppedEarly: boolean;
|
|
47
|
+
total: number;
|
|
48
|
+
passed: number;
|
|
49
|
+
failed: number;
|
|
50
|
+
batches: WorkflowFanoutResult[];
|
|
51
|
+
}
|
|
31
52
|
export interface WorkflowFanoutDependencies extends WorkflowRunnerDependencies {
|
|
32
53
|
runTestingWorkflow?: typeof runTestingWorkflow;
|
|
33
54
|
preflight?: (workflows: TestingWorkflow[]) => WorkflowFanoutPreflightResult | Promise<WorkflowFanoutPreflightResult>;
|
|
@@ -48,6 +69,14 @@ export interface WorkflowFanoutPreflightResult {
|
|
|
48
69
|
ok: boolean;
|
|
49
70
|
checks: WorkflowFanoutPreflightCheck[];
|
|
50
71
|
}
|
|
72
|
+
export interface WorkflowFanoutSelection {
|
|
73
|
+
matched: number;
|
|
74
|
+
offset: number;
|
|
75
|
+
limit?: number;
|
|
76
|
+
batch?: number;
|
|
77
|
+
batchSize?: number;
|
|
78
|
+
totalBatches?: number;
|
|
79
|
+
}
|
|
51
80
|
interface WorkflowFanoutPreflightDependencies {
|
|
52
81
|
providerApiKeyResolver?: WorkflowFanoutDependencies["providerApiKeyResolver"];
|
|
53
82
|
commandExists?: WorkflowFanoutDependencies["commandExists"];
|
|
@@ -55,8 +84,19 @@ interface WorkflowFanoutPreflightDependencies {
|
|
|
55
84
|
env?: Record<string, string | undefined>;
|
|
56
85
|
}
|
|
57
86
|
export declare function normalizeFanoutWorkerCount(value: number | undefined): number;
|
|
87
|
+
export declare function resolveWorkflowFanoutBatch(workflows: TestingWorkflow[], options?: Pick<WorkflowFanoutOptions, "batchSize" | "batch" | "offset">): {
|
|
88
|
+
workflows: TestingWorkflow[];
|
|
89
|
+
selection: WorkflowFanoutSelection;
|
|
90
|
+
};
|
|
91
|
+
export declare function resolveWorkflowFanoutBatchRange(matched: number, options: Pick<WorkflowFanoutOptions, "batchSize" | "batchStart" | "batchEnd">): {
|
|
92
|
+
batchSize: number;
|
|
93
|
+
batchStart: number;
|
|
94
|
+
batchEnd: number;
|
|
95
|
+
totalBatches: number;
|
|
96
|
+
};
|
|
58
97
|
export declare function resolveWorkflowFanoutSelection(options: Pick<WorkflowFanoutOptions, "workflowIds" | "projectId" | "tags" | "includeDisabled">): TestingWorkflow[];
|
|
59
98
|
export declare function checkWorkflowFanoutReadiness(workflows: TestingWorkflow[], dependencies?: WorkflowFanoutPreflightDependencies): Promise<WorkflowFanoutPreflightResult>;
|
|
60
99
|
export declare function runWorkflowFanout(options: WorkflowFanoutOptions, dependencies?: WorkflowFanoutDependencies): Promise<WorkflowFanoutResult>;
|
|
100
|
+
export declare function runWorkflowFanoutBatches(options: WorkflowFanoutOptions, dependencies?: WorkflowFanoutDependencies): Promise<WorkflowFanoutBatchesResult>;
|
|
61
101
|
export {};
|
|
62
102
|
//# sourceMappingURL=workflow-fanout.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-fanout.d.ts","sourceRoot":"","sources":["../../src/lib/workflow-fanout.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,KAAK,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAEpH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,MAAM,WAAW,qBAAsB,SAAQ,kBAAkB;IAC/D,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"workflow-fanout.d.ts","sourceRoot":"","sources":["../../src/lib/workflow-fanout.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -52,7 +52,7 @@ var package_default;
|
|
|
52
52
|
var init_package = __esm(() => {
|
|
53
53
|
package_default = {
|
|
54
54
|
name: "@hasna/testers",
|
|
55
|
-
version: "0.0.
|
|
55
|
+
version: "0.0.58",
|
|
56
56
|
description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
|
|
57
57
|
type: "module",
|
|
58
58
|
main: "dist/index.js",
|
package/dist/server/index.js
CHANGED
|
@@ -47090,7 +47090,7 @@ import { join as join14 } from "path";
|
|
|
47090
47090
|
// package.json
|
|
47091
47091
|
var package_default = {
|
|
47092
47092
|
name: "@hasna/testers",
|
|
47093
|
-
version: "0.0.
|
|
47093
|
+
version: "0.0.58",
|
|
47094
47094
|
description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
|
|
47095
47095
|
type: "module",
|
|
47096
47096
|
main: "dist/index.js",
|