@hasna/testers 0.0.49 → 0.0.50

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 CHANGED
@@ -59184,17 +59184,22 @@ function discoverNextRouteInventory(options) {
59184
59184
  function scenarioInputForNextRoute(item, projectId) {
59185
59185
  const label = item.kind === "page" ? "page" : "API route";
59186
59186
  const methodList = item.methods.length > 0 ? item.methods.join(", ") : "discovered methods";
59187
+ const fixtureStep = item.fixtureParams.length > 0 ? `Bind dynamic fixture values for ${item.fixtureParams.map((name) => `:${name}`).join(", ")} before running route actions.` : undefined;
59187
59188
  const dynamicStep = item.dynamic ? "Substitute dynamic path parameters with valid fixture values from the target org before opening or calling the route." : undefined;
59189
+ const actionSteps = item.actions.slice(0, 16).map(formatActionStep);
59188
59190
  const pageSteps = [
59191
+ fixtureStep,
59189
59192
  dynamicStep,
59190
59193
  `Open the Next.js ${label} ${item.routePath}.`,
59191
59194
  "Wait for the route to finish loading and verify it does not show a blank shell, framework error page, or unexpected auth loop.",
59192
- "Exercise visible primary navigation, tabs, filters, dialogs, forms, and safe buttons on this route.",
59195
+ ...actionSteps.length > 0 ? actionSteps : ["Exercise visible primary navigation, tabs, filters, dialogs, forms, and safe buttons on this route."],
59193
59196
  "Verify the route stays within the expected org/workspace context and does not emit console errors."
59194
59197
  ].filter(Boolean);
59195
59198
  const apiSteps = [
59199
+ fixtureStep,
59196
59200
  dynamicStep,
59197
59201
  `Call the ${methodList} handler(s) for ${item.routePath} using safe fixture data.`,
59202
+ ...actionSteps,
59198
59203
  "Verify expected authentication, authorization, validation, and tenant isolation behavior.",
59199
59204
  "For mutating methods, use harmless test payloads and confirm the response does not create cross-org side effects.",
59200
59205
  "Verify response status, JSON shape, and error messages are stable and regression-safe."
@@ -59215,6 +59220,9 @@ function scenarioInputForNextRoute(item, projectId) {
59215
59220
  category: item.category,
59216
59221
  methods: item.methods,
59217
59222
  dynamic: item.dynamic,
59223
+ fixtureParams: item.fixtureParams,
59224
+ actions: item.actions,
59225
+ actionCount: item.actions.length,
59218
59226
  groups: item.groups
59219
59227
  },
59220
59228
  projectId
@@ -59312,9 +59320,20 @@ function routeItemFromFile(rootDir, appDir, file) {
59312
59320
  const pathSegments = routeSegments.filter((segment) => !segment.startsWith("(")).filter((segment) => !segment.startsWith("@")).map(normalizeRouteSegment).filter(Boolean);
59313
59321
  const routePath = `/${pathSegments.join("/")}`.replace(/\/+/g, "/");
59314
59322
  const normalizedRoutePath = routePath === "/" ? "/" : routePath.replace(/\/$/, "");
59315
- const methods = kind === "api" ? extractRouteMethods(file) : [];
59323
+ const sources = collectRouteSources(rootDir, file);
59324
+ const primarySource = sources[0]?.source ?? readFileSync9(file, "utf8");
59325
+ const methods = kind === "api" ? extractRouteMethods(primarySource) : [];
59316
59326
  const category = classifyRoute(normalizedRoutePath, groups, relativeFile);
59317
59327
  const dynamic = routeSegments.some((segment) => segment.includes("["));
59328
+ const fixtureParams = extractFixtureParams(normalizedRoutePath);
59329
+ const actions = kind === "api" ? methods.map((method) => ({
59330
+ kind: "api-method",
59331
+ label: method,
59332
+ target: normalizedRoutePath,
59333
+ sourceFile: relativeFile,
59334
+ destructive: isDestructiveAction(method, normalizedRoutePath),
59335
+ requiresFixture: fixtureParams.length > 0
59336
+ })) : extractPageActions(rootDir, sources, fixtureParams);
59318
59337
  const requiresAuth = inferRequiresAuth(normalizedRoutePath, groups, kind);
59319
59338
  return {
59320
59339
  kind,
@@ -59325,6 +59344,8 @@ function routeItemFromFile(rootDir, appDir, file) {
59325
59344
  methods,
59326
59345
  dynamic,
59327
59346
  requiresAuth,
59347
+ fixtureParams,
59348
+ actions,
59328
59349
  tags: tagsForRoute({ kind, routePath: normalizedRoutePath, category, groups, dynamic, requiresAuth }),
59329
59350
  priority: priorityForRoute(normalizedRoutePath, category, kind)
59330
59351
  };
@@ -59341,8 +59362,7 @@ function normalizeRouteSegment(segment) {
59341
59362
  }
59342
59363
  return segment;
59343
59364
  }
59344
- function extractRouteMethods(file) {
59345
- const source = readFileSync9(file, "utf8");
59365
+ function extractRouteMethods(source) {
59346
59366
  const methods = new Set;
59347
59367
  const pattern = /\b(?:export\s+)?(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\b|\bexport\s+const\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\b/g;
59348
59368
  for (const match of source.matchAll(pattern)) {
@@ -59352,6 +59372,193 @@ function extractRouteMethods(file) {
59352
59372
  }
59353
59373
  return [...methods].sort();
59354
59374
  }
59375
+ function collectRouteSources(rootDir, entryFile) {
59376
+ const seen = new Set;
59377
+ const sources = [];
59378
+ function visit(file, depth) {
59379
+ if (seen.has(file) || sources.length >= IMPORT_SCAN_LIMIT)
59380
+ return;
59381
+ if (!existsSync18(file) || !statSync4(file).isFile())
59382
+ return;
59383
+ seen.add(file);
59384
+ const source = readFileSync9(file, "utf8");
59385
+ sources.push({ file: relative4(rootDir, file), source });
59386
+ if (depth >= IMPORT_SCAN_DEPTH)
59387
+ return;
59388
+ for (const specifier of localImportSpecifiers(source)) {
59389
+ const resolved = resolveImportFile(file, specifier);
59390
+ if (resolved)
59391
+ visit(resolved, depth + 1);
59392
+ }
59393
+ }
59394
+ visit(entryFile, 0);
59395
+ return sources;
59396
+ }
59397
+ function localImportSpecifiers(source) {
59398
+ const specifiers = [];
59399
+ const pattern = /\bimport\b(?:[\s\S]*?\bfrom\s*)?["'](\.{1,2}\/[^"']+)["']|\bimport\(\s*["'](\.{1,2}\/[^"']+)["']\s*\)/g;
59400
+ for (const match of source.matchAll(pattern)) {
59401
+ const specifier = match[1] ?? match[2];
59402
+ if (specifier)
59403
+ specifiers.push(specifier);
59404
+ }
59405
+ return specifiers;
59406
+ }
59407
+ function resolveImportFile(fromFile, specifier) {
59408
+ const base = resolve3(join20(fromFile, ".."), specifier);
59409
+ const candidates = [
59410
+ base,
59411
+ ...SOURCE_EXTENSIONS.map((ext) => `${base}${ext}`),
59412
+ ...SOURCE_EXTENSIONS.map((ext) => join20(base, `index${ext}`))
59413
+ ];
59414
+ return candidates.find((candidate) => existsSync18(candidate) && statSync4(candidate).isFile()) ?? null;
59415
+ }
59416
+ function extractPageActions(rootDir, sources, fixtureParams) {
59417
+ const actions = [];
59418
+ for (const source of sources) {
59419
+ actions.push(...extractLinkedActions(source, fixtureParams));
59420
+ actions.push(...extractButtonActions(source, fixtureParams));
59421
+ actions.push(...extractFormActions(source, fixtureParams));
59422
+ actions.push(...extractInputActions(source, fixtureParams));
59423
+ }
59424
+ const deduped = new Map;
59425
+ for (const action of actions) {
59426
+ const key = [
59427
+ action.kind,
59428
+ normalizeLabel(action.label),
59429
+ action.target ?? "",
59430
+ action.sourceFile
59431
+ ].join("|");
59432
+ if (!deduped.has(key))
59433
+ deduped.set(key, action);
59434
+ }
59435
+ return [...deduped.values()].sort((a2, b2) => `${a2.kind}:${a2.label}:${a2.target ?? ""}`.localeCompare(`${b2.kind}:${b2.label}:${b2.target ?? ""}`)).slice(0, 40).map((action) => ({ ...action, sourceFile: relative4(rootDir, resolve3(rootDir, action.sourceFile)) }));
59436
+ }
59437
+ function extractLinkedActions(source, fixtureParams) {
59438
+ const actions = [];
59439
+ const pattern = /<(Link|a)\b([^>]*)>([\s\S]*?)<\/\1>/g;
59440
+ for (const match of source.source.matchAll(pattern)) {
59441
+ const attrs = match[2] ?? "";
59442
+ const body = match[3] ?? "";
59443
+ const href = attributeValue(attrs, "href");
59444
+ const label = firstNonEmpty(attributeValue(attrs, "aria-label"), attributeValue(attrs, "title"), textFromJsx(body), href);
59445
+ if (!label)
59446
+ continue;
59447
+ actions.push({
59448
+ kind: "link",
59449
+ label: clamp(label),
59450
+ target: href ? clamp(href, 180) : undefined,
59451
+ sourceFile: source.file,
59452
+ destructive: isDestructiveAction(label, href ?? ""),
59453
+ requiresFixture: requiresFixture(href ?? "", fixtureParams)
59454
+ });
59455
+ }
59456
+ return actions;
59457
+ }
59458
+ function extractButtonActions(source, fixtureParams) {
59459
+ const actions = [];
59460
+ const pattern = /<(button|Button|IconButton|DropdownMenuItem|CommandItem|SelectItem|TabsTrigger)\b([^>]*?)(?:>([\s\S]*?)<\/\1>|\/>)/g;
59461
+ for (const match of source.source.matchAll(pattern)) {
59462
+ const attrs = match[2] ?? "";
59463
+ const body = match[3] ?? "";
59464
+ const label = firstNonEmpty(attributeValue(attrs, "aria-label"), attributeValue(attrs, "title"), attributeValue(attrs, "data-testid"), textFromJsx(body), attributeValue(attrs, "value"));
59465
+ if (!label)
59466
+ continue;
59467
+ const target = attributeValue(attrs, "href") ?? attributeValue(attrs, "data-testid");
59468
+ actions.push({
59469
+ kind: "button",
59470
+ label: clamp(label),
59471
+ target: target ? clamp(target, 180) : undefined,
59472
+ sourceFile: source.file,
59473
+ destructive: isDestructiveAction(label, attrs),
59474
+ requiresFixture: requiresFixture(`${label} ${target ?? ""}`, fixtureParams)
59475
+ });
59476
+ }
59477
+ return actions;
59478
+ }
59479
+ function extractFormActions(source, fixtureParams) {
59480
+ const actions = [];
59481
+ const pattern = /<form\b([^>]*)>/g;
59482
+ for (const match of source.source.matchAll(pattern)) {
59483
+ const attrs = match[1] ?? "";
59484
+ const label = firstNonEmpty(attributeValue(attrs, "aria-label"), attributeValue(attrs, "name"), attributeValue(attrs, "data-testid"), attributeValue(attrs, "action"), `form in ${source.file}`);
59485
+ const target = attributeValue(attrs, "action");
59486
+ actions.push({
59487
+ kind: "form",
59488
+ label: clamp(label),
59489
+ target: target ? clamp(target, 180) : undefined,
59490
+ sourceFile: source.file,
59491
+ destructive: isDestructiveAction(label, attrs),
59492
+ requiresFixture: requiresFixture(`${label} ${target ?? ""}`, fixtureParams)
59493
+ });
59494
+ }
59495
+ return actions;
59496
+ }
59497
+ function extractInputActions(source, fixtureParams) {
59498
+ const actions = [];
59499
+ const pattern = /<(input|Input|Textarea|Select|Combobox)\b([^>]*?)(?:\/>|>)/g;
59500
+ for (const match of source.source.matchAll(pattern)) {
59501
+ const attrs = match[2] ?? "";
59502
+ const label = firstNonEmpty(attributeValue(attrs, "aria-label"), attributeValue(attrs, "placeholder"), attributeValue(attrs, "name"), attributeValue(attrs, "data-testid"));
59503
+ if (!label)
59504
+ continue;
59505
+ actions.push({
59506
+ kind: "input",
59507
+ label: clamp(label),
59508
+ target: attributeValue(attrs, "name") ?? attributeValue(attrs, "data-testid") ?? undefined,
59509
+ sourceFile: source.file,
59510
+ destructive: false,
59511
+ requiresFixture: requiresFixture(label, fixtureParams)
59512
+ });
59513
+ }
59514
+ return actions;
59515
+ }
59516
+ function attributeValue(attrs, name) {
59517
+ const pattern = new RegExp(`\\b${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|\\{\\s*["']([^"']+)["']\\s*\\})`, "i");
59518
+ const match = attrs.match(pattern);
59519
+ const value = match?.[1] ?? match?.[2] ?? match?.[3];
59520
+ return value?.trim() || undefined;
59521
+ }
59522
+ function textFromJsx(value) {
59523
+ const text = value.replace(/<[^>]+>/g, " ").replace(/\{[^}]*\}/g, " ").replace(/\s+/g, " ").trim();
59524
+ return text || undefined;
59525
+ }
59526
+ function firstNonEmpty(...values) {
59527
+ return values.find((value) => value && value.trim())?.trim() ?? "";
59528
+ }
59529
+ function clamp(value, max = 120) {
59530
+ const compact = value.replace(/\s+/g, " ").trim();
59531
+ return compact.length > max ? `${compact.slice(0, max - 1)}\u2026` : compact;
59532
+ }
59533
+ function normalizeLabel(value) {
59534
+ return value.toLowerCase().replace(/\s+/g, " ").trim();
59535
+ }
59536
+ function extractFixtureParams(routePath) {
59537
+ const params = new Set;
59538
+ for (const match of routePath.matchAll(/:([A-Za-z0-9_]+)(?:\*\??)?/g)) {
59539
+ if (match[1])
59540
+ params.add(match[1]);
59541
+ }
59542
+ return [...params];
59543
+ }
59544
+ function requiresFixture(value, fixtureParams) {
59545
+ if (fixtureParams.length === 0)
59546
+ return false;
59547
+ const normalized = value.toLowerCase();
59548
+ return fixtureParams.some((param) => normalized.includes(param.toLowerCase()) || normalized.includes(`[${param}]`));
59549
+ }
59550
+ function isDestructiveAction(label, context = "") {
59551
+ return /\b(delete|destroy|remove|revoke|refund|void|archive|suspend|pause|disable|cancel|reset|purge|terminate)\b/i.test(`${label} ${context}`);
59552
+ }
59553
+ function formatActionStep(action) {
59554
+ const target = action.target ? ` (${action.target})` : "";
59555
+ const fixture = action.requiresFixture ? " after binding fixture values" : "";
59556
+ const guard = action.destructive ? " without confirming the destructive final action" : "";
59557
+ if (action.kind === "api-method") {
59558
+ return `Exercise API method ${action.label}${target}${fixture}${guard}.`;
59559
+ }
59560
+ return `Exercise ${action.kind} "${action.label}"${target}${fixture}${guard}.`;
59561
+ }
59355
59562
  function classifyRoute(routePath, groups, file) {
59356
59563
  const haystack = `${routePath} ${groups.join(" ")} ${file}`.toLowerCase();
59357
59564
  if (haystack.includes("admin"))
@@ -59416,7 +59623,7 @@ function priorityForRoute(routePath, category, kind) {
59416
59623
  return "medium";
59417
59624
  return "medium";
59418
59625
  }
59419
- var ROUTE_FILE_NAMES, WALK_EXCLUDES, SAFE_PAGE_ASSERTIONS;
59626
+ var ROUTE_FILE_NAMES, WALK_EXCLUDES, SAFE_PAGE_ASSERTIONS, IMPORT_SCAN_LIMIT = 40, IMPORT_SCAN_DEPTH = 3, SOURCE_EXTENSIONS;
59420
59627
  var init_next_route_inventory = __esm(() => {
59421
59628
  init_scenarios();
59422
59629
  init_workflows();
@@ -59439,6 +59646,13 @@ var init_next_route_inventory = __esm(() => {
59439
59646
  "coverage"
59440
59647
  ]);
59441
59648
  SAFE_PAGE_ASSERTIONS = [{ type: "no_console_errors" }];
59649
+ SOURCE_EXTENSIONS = [
59650
+ ".tsx",
59651
+ ".ts",
59652
+ ".jsx",
59653
+ ".js",
59654
+ ".mdx"
59655
+ ];
59442
59656
  });
59443
59657
 
59444
59658
  // src/lib/generator.ts
@@ -94827,7 +95041,7 @@ import chalk6 from "chalk";
94827
95041
  // package.json
94828
95042
  var package_default = {
94829
95043
  name: "@hasna/testers",
94830
- version: "0.0.49",
95044
+ version: "0.0.50",
94831
95045
  description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
94832
95046
  type: "module",
94833
95047
  main: "dist/index.js",
@@ -1,5 +1,14 @@
1
1
  import type { CreateScenarioInput, Scenario, ScenarioPriority, TestingWorkflow, WorkflowExecutionInput } from "../types/index.js";
2
2
  export type NextRouteKind = "page" | "api";
3
+ export type NextRouteActionKind = "link" | "button" | "form" | "input" | "api-method";
4
+ export interface NextRouteAction {
5
+ kind: NextRouteActionKind;
6
+ label: string;
7
+ target?: string;
8
+ sourceFile: string;
9
+ destructive: boolean;
10
+ requiresFixture: boolean;
11
+ }
3
12
  export interface NextRouteInventoryItem {
4
13
  kind: NextRouteKind;
5
14
  routePath: string;
@@ -9,6 +18,8 @@ export interface NextRouteInventoryItem {
9
18
  methods: string[];
10
19
  dynamic: boolean;
11
20
  requiresAuth: boolean;
21
+ fixtureParams: string[];
22
+ actions: NextRouteAction[];
12
23
  tags: string[];
13
24
  priority: ScenarioPriority;
14
25
  }
@@ -1 +1 @@
1
- {"version":3,"file":"next-route-inventory.d.ts","sourceRoot":"","sources":["../../src/lib/next-route-inventory.ts"],"names":[],"mappings":"AAIA,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;AAE3C,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,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,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,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACrC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,kBAAkB,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,SAAS,EAAE,eAAe,EAAE,CAAC;CAC9B;AAwBD,wBAAgB,0BAA0B,CAAC,OAAO,EAAE;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,kBAAkB,CA4BrB;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,sBAAsB,EAC5B,SAAS,CAAC,EAAE,MAAM,GACjB,mBAAmB,CA0CrB;AAED,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,+BAA+B,GACvC,8BAA8B,CAuBhC"}
1
+ {"version":3,"file":"next-route-inventory.d.ts","sourceRoot":"","sources":["../../src/lib/next-route-inventory.ts"],"names":[],"mappings":"AAIA,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,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,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACrC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,kBAAkB,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,SAAS,EAAE,eAAe,EAAE,CAAC;CAC9B;AAkCD,wBAAgB,0BAA0B,CAAC,OAAO,EAAE;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,kBAAkB,CA4BrB;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,sBAAsB,EAC5B,SAAS,CAAC,EAAE,MAAM,GACjB,mBAAmB,CAwDrB;AAED,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,+BAA+B,GACvC,8BAA8B,CAuBhC"}
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.49",
55
+ version: "0.0.50",
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",
@@ -46937,7 +46937,7 @@ import { join as join14 } from "path";
46937
46937
  // package.json
46938
46938
  var package_default = {
46939
46939
  name: "@hasna/testers",
46940
- version: "0.0.49",
46940
+ version: "0.0.50",
46941
46941
  description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
46942
46942
  type: "module",
46943
46943
  main: "dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/testers",
3
- "version": "0.0.49",
3
+ "version": "0.0.50",
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",