@hasna/testers 0.0.55 → 0.0.57

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 CHANGED
@@ -39,9 +39,10 @@ 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
42
43
  ```
43
44
 
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.
45
+ `--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 `--offset` 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
46
 
46
47
  ### Next.js Route and Action Inventory
47
48
 
@@ -54,7 +55,7 @@ testers inventory next /path/to/app \
54
55
  --create-action-scenarios \
55
56
  --create-workflows \
56
57
  --create-action-workflows \
57
- --action-workflow-grouping route \
58
+ --action-workflow-grouping action \
58
59
  --sandbox-provider e2b \
59
60
  --sandbox-sync rsync \
60
61
  --sandbox-app-source /path/to/app \
@@ -63,10 +64,10 @@ testers inventory next /path/to/app \
63
64
  --sandbox-app-wait-url http://127.0.0.1:3000/health \
64
65
  --sandbox-env-optional OPENAI_API_KEY
65
66
 
66
- testers workflow fanout --project alumia --tag next-action --workers 6 --url https://preview.example.com --dry-run
67
+ testers workflow fanout --project alumia --tag action-specific --workers 6 --batch-size 12 --batch 1 --url https://preview.example.com --dry-run
67
68
  ```
68
69
 
69
- Use `--action-workflow-grouping route` for route-specific workflows or `--action-workflow-grouping 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.
70
+ 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.
70
71
 
71
72
  ### Common Flags
72
73
 
package/dist/cli/index.js CHANGED
@@ -59443,7 +59443,7 @@ function scenarioInputForNextRouteAction(item, action, index, projectId) {
59443
59443
  name: `Next ${label}: ${item.routePath} :: ${action.kind} ${action.label} #${index + 1}`,
59444
59444
  description: `Source-discovered ${label} ${index + 1} from ${action.sourceFile}. Verify ${action.kind} "${action.label}" on ${item.routePath}.`,
59445
59445
  steps: item.kind === "page" ? pageSteps : apiSteps,
59446
- tags: actionTagsForRoute(item, action),
59446
+ tags: actionTagsForRoute(item, action, index),
59447
59447
  priority: action.destructive ? "critical" : item.priority,
59448
59448
  targetPath: item.routePath,
59449
59449
  requiresAuth: item.requiresAuth,
@@ -59564,6 +59564,28 @@ function upsertRouteInventoryActionWorkflows(inventory, options) {
59564
59564
  }
59565
59565
  return workflows;
59566
59566
  }
59567
+ if (grouping === "action") {
59568
+ for (const item of inventory.items.filter((route) => route.actions.length > 0)) {
59569
+ item.actions.forEach((action, index) => {
59570
+ const name = `Next action inventory ${item.kind} ${item.routePath} #${index + 1} ${action.kind} ${action.label}`;
59571
+ const scenarioTags = [
59572
+ "next-action",
59573
+ "action-specific",
59574
+ `route:${item.kind}`,
59575
+ `route-path:${item.routePath}`,
59576
+ `action-ordinal:${index + 1}`
59577
+ ];
59578
+ workflows.push(upsertTestingWorkflow(existingWorkflows, name, {
59579
+ name,
59580
+ description: `Source-discovered action #${index + 1} coverage for ${item.kind} route ${item.routePath}: ${action.kind} "${action.label}".`,
59581
+ projectId: options.projectId,
59582
+ scenarioFilter: { tags: scenarioTags },
59583
+ execution: workflowExecutionFromOptions(options)
59584
+ }));
59585
+ });
59586
+ }
59587
+ return workflows;
59588
+ }
59567
59589
  for (const item of inventory.items.filter((route) => route.actions.length > 0)) {
59568
59590
  const name = `Next action inventory ${item.kind} ${item.routePath}`;
59569
59591
  const scenarioTags = ["next-action", `route:${item.kind}`, `route-path:${item.routePath}`];
@@ -59920,12 +59942,14 @@ function tagsForRoute(input) {
59920
59942
  tags.add("api");
59921
59943
  return [...tags];
59922
59944
  }
59923
- function actionTagsForRoute(item, action) {
59945
+ function actionTagsForRoute(item, action, index) {
59924
59946
  const tags = new Set([
59925
59947
  ...item.tags,
59926
59948
  "next-action",
59949
+ "action-specific",
59927
59950
  `action:${action.kind}`,
59928
- `route-path:${item.routePath}`
59951
+ `route-path:${item.routePath}`,
59952
+ `action-ordinal:${index + 1}`
59929
59953
  ]);
59930
59954
  if (action.destructive)
59931
59955
  tags.add("destructive-action");
@@ -60604,6 +60628,7 @@ var exports_workflow_fanout = {};
60604
60628
  __export(exports_workflow_fanout, {
60605
60629
  runWorkflowFanout: () => runWorkflowFanout,
60606
60630
  resolveWorkflowFanoutSelection: () => resolveWorkflowFanoutSelection,
60631
+ resolveWorkflowFanoutBatch: () => resolveWorkflowFanoutBatch,
60607
60632
  normalizeFanoutWorkerCount: () => normalizeFanoutWorkerCount,
60608
60633
  checkWorkflowFanoutReadiness: () => checkWorkflowFanoutReadiness
60609
60634
  });
@@ -60619,6 +60644,33 @@ function normalizeFanoutWorkerCount(value) {
60619
60644
  }
60620
60645
  return workers;
60621
60646
  }
60647
+ function resolveWorkflowFanoutBatch(workflows, options = {}) {
60648
+ const batchSize = normalizeOptionalPositiveInteger(options.batchSize, "workflow fanout batch size");
60649
+ const batch = normalizeOptionalPositiveInteger(options.batch, "workflow fanout batch");
60650
+ const offset = normalizeOptionalNonNegativeInteger(options.offset, "workflow fanout offset");
60651
+ if (batch !== undefined && offset !== undefined) {
60652
+ throw new Error("workflow fanout batch and offset cannot both be set");
60653
+ }
60654
+ if (batch !== undefined && batchSize === undefined) {
60655
+ throw new Error("workflow fanout batch requires batch size");
60656
+ }
60657
+ const resolvedOffset = batch !== undefined && batchSize !== undefined ? (batch - 1) * batchSize : offset ?? 0;
60658
+ const limit = batchSize;
60659
+ const selected = workflows.slice(resolvedOffset, limit === undefined ? undefined : resolvedOffset + limit);
60660
+ if (selected.length === 0) {
60661
+ throw new Error(`No testing workflows matched the fanout batch selection (matched ${workflows.length}, offset ${resolvedOffset})`);
60662
+ }
60663
+ return {
60664
+ workflows: selected,
60665
+ selection: {
60666
+ matched: workflows.length,
60667
+ offset: resolvedOffset,
60668
+ ...limit !== undefined ? { limit } : {},
60669
+ ...batch !== undefined ? { batch } : {},
60670
+ ...batchSize !== undefined ? { batchSize, totalBatches: Math.ceil(workflows.length / batchSize) } : {}
60671
+ }
60672
+ };
60673
+ }
60622
60674
  function resolveWorkflowFanoutSelection(options) {
60623
60675
  const ids = splitWorkflowIds(options.workflowIds);
60624
60676
  const workflows = ids.length > 0 ? ids.map((id) => {
@@ -60738,7 +60790,8 @@ async function mapWithConcurrency(items, limit, worker) {
60738
60790
  }
60739
60791
  async function runWorkflowFanout(options, dependencies = {}) {
60740
60792
  const workers = normalizeFanoutWorkerCount(options.workers);
60741
- const workflows = resolveWorkflowFanoutSelection(options);
60793
+ const matchedWorkflows = resolveWorkflowFanoutSelection(options);
60794
+ const { workflows, selection } = resolveWorkflowFanoutBatch(matchedWorkflows, options);
60742
60795
  const {
60743
60796
  runTestingWorkflow: runOne = runTestingWorkflow,
60744
60797
  preflight: preflightOverride,
@@ -60759,6 +60812,7 @@ async function runWorkflowFanout(options, dependencies = {}) {
60759
60812
  return {
60760
60813
  status: "failed",
60761
60814
  workers,
60815
+ selection,
60762
60816
  total: workflows.length,
60763
60817
  passed: 0,
60764
60818
  failed: workflows.length,
@@ -60814,6 +60868,7 @@ async function runWorkflowFanout(options, dependencies = {}) {
60814
60868
  return {
60815
60869
  status: dryRun ? "dry-run" : failed > 0 ? "failed" : "passed",
60816
60870
  workers,
60871
+ selection,
60817
60872
  total: items.length,
60818
60873
  passed,
60819
60874
  failed,
@@ -60847,6 +60902,24 @@ function defaultCommandExists(command) {
60847
60902
  const result = spawnSync2(command, ["--version"], { encoding: "utf8" });
60848
60903
  return !result.error && result.status === 0;
60849
60904
  }
60905
+ function normalizeOptionalPositiveInteger(value, label) {
60906
+ if (value === undefined)
60907
+ return;
60908
+ const normalized = Math.floor(value);
60909
+ if (!Number.isFinite(normalized) || normalized < 1) {
60910
+ throw new Error(`${label} must be a positive integer`);
60911
+ }
60912
+ return normalized;
60913
+ }
60914
+ function normalizeOptionalNonNegativeInteger(value, label) {
60915
+ if (value === undefined)
60916
+ return;
60917
+ const normalized = Math.floor(value);
60918
+ if (!Number.isFinite(normalized) || normalized < 0) {
60919
+ throw new Error(`${label} must be a non-negative integer`);
60920
+ }
60921
+ return normalized;
60922
+ }
60850
60923
  function collectMissingSandboxEnvRefs(workflows, env, credentialResolver) {
60851
60924
  const requiredMissing = [];
60852
60925
  const optionalMissing = [];
@@ -95551,7 +95624,7 @@ import chalk6 from "chalk";
95551
95624
  // package.json
95552
95625
  var package_default = {
95553
95626
  name: "@hasna/testers",
95554
- version: "0.0.55",
95627
+ version: "0.0.57",
95555
95628
  description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
95556
95629
  type: "module",
95557
95630
  main: "dist/index.js",
@@ -101176,7 +101249,7 @@ Imported ${imported} scenarios from API spec:`));
101176
101249
  }
101177
101250
  });
101178
101251
  var inventoryCmd = program2.command("inventory").description("Discover source-derived app route/action inventories");
101179
- inventoryCmd.command("next [root]").description("Discover Next.js app routes and optionally import route coverage scenarios").option("--app-dir <path>", "Next.js app directory relative to root (default: packages/web/app or app)").option("--project <id>", "Project ID").option("--no-pages", "Do not include page.tsx/page.ts routes").option("--no-api", "Do not include route.ts/route.js API routes").option("--limit <n>", "Limit discovered routes").option("--create-scenarios", "Upsert source-derived route coverage scenarios", false).option("--create-action-scenarios", "Upsert one source-derived scenario per discovered page/API action", false).option("--create-workflows", "Upsert grouped workflows by area and route kind", false).option("--create-action-workflows", "Upsert action-focused workflows for discovered action scenarios", false).option("--action-workflow-grouping <mode>", "Action workflow grouping: route or area-kind", "route").option("--workflow-target <target>", "Workflow execution target: local or sandbox", "sandbox").option("--sandbox-provider <provider>", "Sandbox provider for created workflows", "e2b").option("--sandbox-image <image>", "Sandbox image/template for created workflows").option("--sandbox-remote-dir <path>", "Remote working directory for sandbox runs").option("--sandbox-cleanup <mode>", "Sandbox cleanup mode: delete, stop, or keep", "delete").option("--sandbox-sync <strategy>", "Sandbox upload sync strategy: rsync or archive", "rsync").option("--sandbox-setup-command <command>", "Shell command to run before testers in the sandbox").option("--sandbox-package <spec>", "Package spec to execute in the sandbox", "@hasna/testers").option("--sandbox-env <assignment>", "Sandbox env var; KEY forwards host KEY, KEY=value stores value (repeatable)", (val, acc) => {
101252
+ inventoryCmd.command("next [root]").description("Discover Next.js app routes and optionally import route coverage scenarios").option("--app-dir <path>", "Next.js app directory relative to root (default: packages/web/app or app)").option("--project <id>", "Project ID").option("--no-pages", "Do not include page.tsx/page.ts routes").option("--no-api", "Do not include route.ts/route.js API routes").option("--limit <n>", "Limit discovered routes").option("--create-scenarios", "Upsert source-derived route coverage scenarios", false).option("--create-action-scenarios", "Upsert one source-derived scenario per discovered page/API action", false).option("--create-workflows", "Upsert grouped workflows by area and route kind", false).option("--create-action-workflows", "Upsert action-focused workflows for discovered action scenarios", false).option("--action-workflow-grouping <mode>", "Action workflow grouping: route, area-kind, or action", "route").option("--workflow-target <target>", "Workflow execution target: local or sandbox", "sandbox").option("--sandbox-provider <provider>", "Sandbox provider for created workflows", "e2b").option("--sandbox-image <image>", "Sandbox image/template for created workflows").option("--sandbox-remote-dir <path>", "Remote working directory for sandbox runs").option("--sandbox-cleanup <mode>", "Sandbox cleanup mode: delete, stop, or keep", "delete").option("--sandbox-sync <strategy>", "Sandbox upload sync strategy: rsync or archive", "rsync").option("--sandbox-setup-command <command>", "Shell command to run before testers in the sandbox").option("--sandbox-package <spec>", "Package spec to execute in the sandbox", "@hasna/testers").option("--sandbox-env <assignment>", "Sandbox env var; KEY forwards host KEY, KEY=value stores value (repeatable)", (val, acc) => {
101180
101253
  acc.push(val);
101181
101254
  return acc;
101182
101255
  }, []).option("--sandbox-env-optional <name>", "Optional sandbox env var; forwards host NAME only when set (repeatable)", (val, acc) => {
@@ -101187,8 +101260,8 @@ inventoryCmd.command("next [root]").description("Discover Next.js app routes and
101187
101260
  const { importNextRouteInventory: importNextRouteInventory2 } = await Promise.resolve().then(() => (init_next_route_inventory(), exports_next_route_inventory));
101188
101261
  const projectId = resolveProject2(opts.project) ?? undefined;
101189
101262
  const env = parseSandboxEnv(opts.sandboxEnv, opts.sandboxEnvOptional);
101190
- if (!["route", "area-kind"].includes(opts.actionWorkflowGrouping)) {
101191
- throw new Error("--action-workflow-grouping must be route or area-kind");
101263
+ if (!["route", "area-kind", "action"].includes(opts.actionWorkflowGrouping)) {
101264
+ throw new Error("--action-workflow-grouping must be route, area-kind, or action");
101192
101265
  }
101193
101266
  const result = importNextRouteInventory2({
101194
101267
  rootDir: root ?? process.cwd(),
@@ -102336,7 +102409,7 @@ workflowCmd.command("run <id>").description("Run a saved testing workflow").requ
102336
102409
  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) => {
102337
102410
  acc.push(val);
102338
102411
  return acc;
102339
- }, []).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) => {
102412
+ }, []).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("-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) => {
102340
102413
  try {
102341
102414
  const { runWorkflowFanout: runWorkflowFanout2 } = await Promise.resolve().then(() => (init_workflow_fanout(), exports_workflow_fanout));
102342
102415
  const result = await runWorkflowFanout2({
@@ -102345,6 +102418,9 @@ workflowCmd.command("fanout [ids...]").description("Run multiple saved sandbox w
102345
102418
  tags: opts.tag,
102346
102419
  includeDisabled: opts.all,
102347
102420
  workers: opts.workers ? parseInt(opts.workers, 10) : undefined,
102421
+ batchSize: opts.batchSize ? parseInt(opts.batchSize, 10) : undefined,
102422
+ batch: opts.batch ? parseInt(opts.batch, 10) : undefined,
102423
+ offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
102348
102424
  url: opts.url,
102349
102425
  model: opts.model,
102350
102426
  headed: opts.headed,
@@ -102368,6 +102444,10 @@ workflowCmd.command("fanout [ids...]").description("Run multiple saved sandbox w
102368
102444
  log(` ${chalk6.yellow("warning")} ${check2.message}`);
102369
102445
  }
102370
102446
  }
102447
+ if (result.selection.matched !== result.total) {
102448
+ const batch = result.selection.batch !== undefined && result.selection.totalBatches !== undefined ? ` batch ${result.selection.batch}/${result.selection.totalBatches}` : "";
102449
+ log(chalk6.dim(`Selected ${result.total}/${result.selection.matched} workflow(s) from offset ${result.selection.offset}${batch}.`));
102450
+ }
102371
102451
  const status = result.status === "passed" ? chalk6.green("passed") : chalk6.red("failed");
102372
102452
  log(chalk6.bold(`Sandbox workflow fanout ${status}: ${result.passed}/${result.total} passed with ${result.workers} worker(s)`));
102373
102453
  for (const item of result.items) {
@@ -45,7 +45,7 @@ export interface ImportNextRouteInventoryOptions {
45
45
  createActionScenarios?: boolean;
46
46
  createWorkflows?: boolean;
47
47
  createActionWorkflows?: boolean;
48
- actionWorkflowGrouping?: "route" | "area-kind";
48
+ actionWorkflowGrouping?: "route" | "area-kind" | "action";
49
49
  workflowTarget?: "local" | "sandbox";
50
50
  workflowProvider?: string;
51
51
  workflowExecution?: Partial<WorkflowExecutionInput>;
@@ -1 +1 @@
1
- {"version":3,"file":"next-route-inventory.d.ts","sourceRoot":"","sources":["../../src/lib/next-route-inventory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"next-route-inventory.d.ts","sourceRoot":"","sources":["../../src/lib/next-route-inventory.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,9 @@ 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;
9
12
  }
10
13
  export interface WorkflowFanoutItem {
11
14
  workflowId: string;
@@ -22,6 +25,7 @@ export interface WorkflowFanoutItem {
22
25
  export interface WorkflowFanoutResult {
23
26
  status: "passed" | "failed" | "dry-run";
24
27
  workers: number;
28
+ selection: WorkflowFanoutSelection;
25
29
  total: number;
26
30
  passed: number;
27
31
  failed: number;
@@ -48,6 +52,14 @@ export interface WorkflowFanoutPreflightResult {
48
52
  ok: boolean;
49
53
  checks: WorkflowFanoutPreflightCheck[];
50
54
  }
55
+ export interface WorkflowFanoutSelection {
56
+ matched: number;
57
+ offset: number;
58
+ limit?: number;
59
+ batch?: number;
60
+ batchSize?: number;
61
+ totalBatches?: number;
62
+ }
51
63
  interface WorkflowFanoutPreflightDependencies {
52
64
  providerApiKeyResolver?: WorkflowFanoutDependencies["providerApiKeyResolver"];
53
65
  commandExists?: WorkflowFanoutDependencies["commandExists"];
@@ -55,6 +67,10 @@ interface WorkflowFanoutPreflightDependencies {
55
67
  env?: Record<string, string | undefined>;
56
68
  }
57
69
  export declare function normalizeFanoutWorkerCount(value: number | undefined): number;
70
+ export declare function resolveWorkflowFanoutBatch(workflows: TestingWorkflow[], options?: Pick<WorkflowFanoutOptions, "batchSize" | "batch" | "offset">): {
71
+ workflows: TestingWorkflow[];
72
+ selection: WorkflowFanoutSelection;
73
+ };
58
74
  export declare function resolveWorkflowFanoutSelection(options: Pick<WorkflowFanoutOptions, "workflowIds" | "projectId" | "tags" | "includeDisabled">): TestingWorkflow[];
59
75
  export declare function checkWorkflowFanoutReadiness(workflows: TestingWorkflow[], dependencies?: WorkflowFanoutPreflightDependencies): Promise<WorkflowFanoutPreflightResult>;
60
76
  export declare function runWorkflowFanout(options: WorkflowFanoutOptions, dependencies?: WorkflowFanoutDependencies): Promise<WorkflowFanoutResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-fanout.d.ts","sourceRoot":"","sources":["../../src/lib/workflow-fanout.ts"],"names":[],"mappings":"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"}
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",
55
+ version: "0.0.57",
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",
@@ -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.55",
47093
+ version: "0.0.57",
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/testers",
3
- "version": "0.0.55",
3
+ "version": "0.0.57",
4
4
  "description": "AI-powered QA testing CLI — spawns cheap AI agents to test web apps with headless browsers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",