@hasna/testers 0.0.54 → 0.0.56
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
|
@@ -54,15 +54,19 @@ testers inventory next /path/to/app \
|
|
|
54
54
|
--create-action-scenarios \
|
|
55
55
|
--create-workflows \
|
|
56
56
|
--create-action-workflows \
|
|
57
|
-
--action-workflow-grouping
|
|
57
|
+
--action-workflow-grouping action \
|
|
58
58
|
--sandbox-provider e2b \
|
|
59
59
|
--sandbox-sync rsync \
|
|
60
|
+
--sandbox-app-source /path/to/app \
|
|
61
|
+
--sandbox-app-start-command "bun install && bun dev --hostname 0.0.0.0" \
|
|
62
|
+
--sandbox-app-url http://127.0.0.1:3000 \
|
|
63
|
+
--sandbox-app-wait-url http://127.0.0.1:3000/health \
|
|
60
64
|
--sandbox-env-optional OPENAI_API_KEY
|
|
61
65
|
|
|
62
|
-
testers workflow fanout --project alumia --tag
|
|
66
|
+
testers workflow fanout --project alumia --tag action-specific --workers 6 --url https://preview.example.com --dry-run
|
|
63
67
|
```
|
|
64
68
|
|
|
65
|
-
Use `--action-workflow-grouping route` for route-specific workflows or
|
|
69
|
+
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.
|
|
66
70
|
|
|
67
71
|
### Common Flags
|
|
68
72
|
|
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");
|
|
@@ -95551,7 +95575,7 @@ import chalk6 from "chalk";
|
|
|
95551
95575
|
// package.json
|
|
95552
95576
|
var package_default = {
|
|
95553
95577
|
name: "@hasna/testers",
|
|
95554
|
-
version: "0.0.
|
|
95578
|
+
version: "0.0.56",
|
|
95555
95579
|
description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
|
|
95556
95580
|
type: "module",
|
|
95557
95581
|
main: "dist/index.js",
|
|
@@ -101176,16 +101200,19 @@ Imported ${imported} scenarios from API spec:`));
|
|
|
101176
101200
|
}
|
|
101177
101201
|
});
|
|
101178
101202
|
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
|
|
101203
|
+
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) => {
|
|
101204
|
+
acc.push(val);
|
|
101205
|
+
return acc;
|
|
101206
|
+
}, []).option("--sandbox-env-optional <name>", "Optional sandbox env var; forwards host NAME only when set (repeatable)", (val, acc) => {
|
|
101180
101207
|
acc.push(val);
|
|
101181
101208
|
return acc;
|
|
101182
|
-
}, []).option("--timeout <ms>", "Workflow timeout in milliseconds").option("--json", "Output as JSON", false).action(async (root, opts) => {
|
|
101209
|
+
}, []).option("--sandbox-app-source <path>", "Local app source directory to upload into the sandbox").option("--sandbox-app-remote-dir <path>", "Remote app directory inside the sandbox (default: <sandbox-remote-dir>/app)").option("--sandbox-app-start-command <command>", "Shell command to start the app before testers runs").option("--sandbox-app-url <url>", "URL testers should target inside the sandbox after the app starts").option("--sandbox-app-wait-url <url>", "URL to poll before starting testers (defaults to --sandbox-app-url)").option("--sandbox-app-wait-timeout <ms>", "App readiness wait timeout in milliseconds").option("--timeout <ms>", "Workflow timeout in milliseconds").option("--json", "Output as JSON", false).action(async (root, opts) => {
|
|
101183
101210
|
try {
|
|
101184
101211
|
const { importNextRouteInventory: importNextRouteInventory2 } = await Promise.resolve().then(() => (init_next_route_inventory(), exports_next_route_inventory));
|
|
101185
101212
|
const projectId = resolveProject2(opts.project) ?? undefined;
|
|
101186
|
-
const env = parseSandboxEnv(
|
|
101187
|
-
if (!["route", "area-kind"].includes(opts.actionWorkflowGrouping)) {
|
|
101188
|
-
throw new Error("--action-workflow-grouping must be route
|
|
101213
|
+
const env = parseSandboxEnv(opts.sandboxEnv, opts.sandboxEnvOptional);
|
|
101214
|
+
if (!["route", "area-kind", "action"].includes(opts.actionWorkflowGrouping)) {
|
|
101215
|
+
throw new Error("--action-workflow-grouping must be route, area-kind, or action");
|
|
101189
101216
|
}
|
|
101190
101217
|
const result = importNextRouteInventory2({
|
|
101191
101218
|
rootDir: root ?? process.cwd(),
|
|
@@ -101204,10 +101231,20 @@ inventoryCmd.command("next [root]").description("Discover Next.js app routes and
|
|
|
101204
101231
|
workflowExecution: {
|
|
101205
101232
|
target: opts.workflowTarget,
|
|
101206
101233
|
provider: opts.workflowTarget === "sandbox" ? opts.sandboxProvider : undefined,
|
|
101234
|
+
sandboxImage: opts.sandboxImage,
|
|
101235
|
+
sandboxRemoteDir: opts.sandboxRemoteDir,
|
|
101207
101236
|
sandboxCleanup: opts.sandboxCleanup,
|
|
101208
101237
|
sandboxSyncStrategy: opts.sandboxSync,
|
|
101238
|
+
setupCommand: opts.sandboxSetupCommand,
|
|
101239
|
+
packageSpec: opts.sandboxPackage,
|
|
101209
101240
|
timeoutMs: opts.timeout ? parseInt(opts.timeout, 10) : undefined,
|
|
101210
|
-
env
|
|
101241
|
+
env,
|
|
101242
|
+
appSourceDir: opts.sandboxAppSource,
|
|
101243
|
+
appRemoteDir: opts.sandboxAppRemoteDir,
|
|
101244
|
+
appStartCommand: opts.sandboxAppStartCommand,
|
|
101245
|
+
appUrl: opts.sandboxAppUrl,
|
|
101246
|
+
appWaitUrl: opts.sandboxAppWaitUrl,
|
|
101247
|
+
appWaitTimeoutMs: opts.sandboxAppWaitTimeout ? parseInt(opts.sandboxAppWaitTimeout, 10) : undefined
|
|
101211
101248
|
}
|
|
101212
101249
|
});
|
|
101213
101250
|
if (opts.json) {
|
|
@@ -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":"AAKA,OAAO,KAAK,EAEV,mBAAmB,EACnB,QAAQ,EACR,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACvB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,KAAK,CAAC;AAC3C,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,YAAY,CAAC;AAEtF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,aAAa,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,EAAE,gBAAgB,CAAC;CAC5B;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,KAAK,EAAE,sBAAsB,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,+BAA+B;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,sBAAsB,CAAC,EAAE,OAAO,GAAG,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"next-route-inventory.d.ts","sourceRoot":"","sources":["../../src/lib/next-route-inventory.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.56",
|
|
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.56",
|
|
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",
|