@hasna/testers 0.0.50 → 0.0.51

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
@@ -12394,6 +12394,18 @@ __export(exports_scenarios, {
12394
12394
  createScenario: () => createScenario,
12395
12395
  countScenarios: () => countScenarios
12396
12396
  });
12397
+ function stableJson(value) {
12398
+ if (value === undefined)
12399
+ return "";
12400
+ if (value === null)
12401
+ return "null";
12402
+ if (Array.isArray(value))
12403
+ return `[${value.map(stableJson).join(",")}]`;
12404
+ if (typeof value === "object") {
12405
+ return `{${Object.entries(value).filter(([, val]) => val !== undefined).sort(([a], [b]) => a.localeCompare(b)).map(([key, val]) => `${JSON.stringify(key)}:${stableJson(val)}`).join(",")}}`;
12406
+ }
12407
+ return JSON.stringify(value);
12408
+ }
12397
12409
  function nextShortId(projectId) {
12398
12410
  const db2 = getDatabase();
12399
12411
  if (projectId) {
@@ -12653,9 +12665,12 @@ function upsertScenario(input) {
12653
12665
  }
12654
12666
  const existingSteps = JSON.parse(existing.steps);
12655
12667
  const existingTags = JSON.parse(existing.tags);
12668
+ const existingMetadata = existing.metadata ? JSON.parse(existing.metadata) : undefined;
12669
+ const existingAssertions = JSON.parse(existing.assertions || "[]");
12670
+ const existingParameters = existing.parameters ? JSON.parse(existing.parameters) : undefined;
12656
12671
  const newSteps = input.steps ?? [];
12657
12672
  const newTags = input.tags ?? [];
12658
- const isIdentical = existing.description === (input.description ?? "") && existingSteps.length === newSteps.length && existingSteps.every((s, i) => s === newSteps[i]) && existingTags.length === newTags.length && existingTags.every((t, i) => t === newTags[i]) && existing.priority === (input.priority ?? "medium");
12673
+ const isIdentical = existing.description === (input.description ?? "") && existingSteps.length === newSteps.length && existingSteps.every((s, i) => s === newSteps[i]) && existingTags.length === newTags.length && existingTags.every((t, i) => t === newTags[i]) && existing.priority === (input.priority ?? "medium") && existing.target_path === (input.targetPath ?? null) && Boolean(existing.requires_auth) === Boolean(input.requiresAuth) && stableJson(existingMetadata) === stableJson(input.metadata) && stableJson(existingAssertions) === stableJson(input.assertions ?? []) && stableJson(existingParameters) === stableJson(input.parameters);
12659
12674
  if (isIdentical) {
12660
12675
  return { scenario: scenarioFromRow(existing), action: "deduped" };
12661
12676
  }
@@ -12705,6 +12720,10 @@ function upsertScenario(input) {
12705
12720
  sets.push("assertions = ?");
12706
12721
  params.push(JSON.stringify(input.assertions));
12707
12722
  }
12723
+ if (input.parameters !== undefined) {
12724
+ sets.push("parameters = ?");
12725
+ params.push(JSON.stringify(input.parameters));
12726
+ }
12708
12727
  sets.push("version = ?", "updated_at = ?");
12709
12728
  params.push(existing.version + 1, now());
12710
12729
  params.push(existing.id);
@@ -13057,6 +13076,159 @@ var init_screenshots = __esm(() => {
13057
13076
  init_database();
13058
13077
  });
13059
13078
 
13079
+ // src/lib/route-fixtures.ts
13080
+ function isRecord2(value) {
13081
+ return typeof value === "object" && value !== null && !Array.isArray(value);
13082
+ }
13083
+ function envNameForParam(prefix, param) {
13084
+ return `${prefix}_${param.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").toUpperCase()}`;
13085
+ }
13086
+ function readString(value) {
13087
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
13088
+ }
13089
+ function resolveReference(value, env) {
13090
+ if (value.startsWith("$?"))
13091
+ return env[value.slice(2)]?.trim() || undefined;
13092
+ if (value.startsWith("$"))
13093
+ return env[value.slice(1)]?.trim() || undefined;
13094
+ return value;
13095
+ }
13096
+ function scenarioFixtureValue(params, name, env) {
13097
+ if (!params)
13098
+ return;
13099
+ const routeFixtures = isRecord2(params["routeFixtures"]) ? params["routeFixtures"] : {};
13100
+ const raw = readString(routeFixtures[name]) ?? readString(params[name]);
13101
+ return raw ? resolveReference(raw, env) : undefined;
13102
+ }
13103
+ function envFixtureValue(name, env) {
13104
+ const candidates = [
13105
+ envNameForParam("TESTERS_ROUTE", name),
13106
+ envNameForParam("TESTERS_FIXTURE", name),
13107
+ envNameForParam("ALUMIA_FIXTURE", name),
13108
+ ...PARAM_ENV_CANDIDATES[name] ?? []
13109
+ ];
13110
+ for (const candidate of candidates) {
13111
+ const value = env[candidate]?.trim();
13112
+ if (value)
13113
+ return value;
13114
+ }
13115
+ return;
13116
+ }
13117
+ function defaultFixtureValue(name) {
13118
+ if (name === "orgSlug")
13119
+ return "test-org";
13120
+ if (name.toLowerCase().endsWith("slug")) {
13121
+ return `test-${name.replace(/Slug$/i, "").replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase() || "slug"}`;
13122
+ }
13123
+ if (name === "id" || name.toLowerCase().endsWith("id"))
13124
+ return DEFAULT_UUID;
13125
+ if (name.toLowerCase().includes("token"))
13126
+ return "test-token";
13127
+ return `test-${name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()}`;
13128
+ }
13129
+ function routeParamsFromPath(path) {
13130
+ if (!path)
13131
+ return [];
13132
+ const params = new Set;
13133
+ for (const match of path.matchAll(/:([A-Za-z0-9_]+)(?:\*\??)?/g)) {
13134
+ if (match[1])
13135
+ params.add(match[1]);
13136
+ }
13137
+ return [...params];
13138
+ }
13139
+ function defaultRouteFixturesForParams(params) {
13140
+ return Object.fromEntries(params.map((param) => [param, defaultFixtureValue(param)]));
13141
+ }
13142
+ function resolveRouteFixtures(scenario, env = process.env) {
13143
+ const metadataParams = Array.isArray(scenario.metadata?.["fixtureParams"]) ? scenario.metadata["fixtureParams"].filter((value) => typeof value === "string") : [];
13144
+ const params = [...new Set([...metadataParams, ...routeParamsFromPath(scenario.targetPath)])];
13145
+ const values = {};
13146
+ const sources = {};
13147
+ const synthetic = [];
13148
+ for (const param of params) {
13149
+ const scenarioValue = scenarioFixtureValue(scenario.parameters, param, env);
13150
+ if (scenarioValue) {
13151
+ values[param] = scenarioValue;
13152
+ sources[param] = "scenario";
13153
+ continue;
13154
+ }
13155
+ const envValue = envFixtureValue(param, env);
13156
+ if (envValue) {
13157
+ values[param] = envValue;
13158
+ sources[param] = "env";
13159
+ continue;
13160
+ }
13161
+ values[param] = defaultFixtureValue(param);
13162
+ sources[param] = "default";
13163
+ synthetic.push(param);
13164
+ }
13165
+ const resolvedPath = scenario.targetPath ? resolveRoutePath(scenario.targetPath, values) : null;
13166
+ return {
13167
+ originalPath: scenario.targetPath,
13168
+ resolvedPath,
13169
+ params,
13170
+ values,
13171
+ sources,
13172
+ synthetic
13173
+ };
13174
+ }
13175
+ function resolveRoutePath(path, values) {
13176
+ return path.replace(/\/:([A-Za-z0-9_]+)\*\?/g, (_match, name) => {
13177
+ const value = values[name];
13178
+ return value ? `/${encodeRouteFixture(value, true)}` : "";
13179
+ }).replace(/:([A-Za-z0-9_]+)\*/g, (_match, name) => encodeRouteFixture(values[name] ?? defaultFixtureValue(name), true)).replace(/:([A-Za-z0-9_]+)/g, (_match, name) => encodeRouteFixture(values[name] ?? defaultFixtureValue(name), false));
13180
+ }
13181
+ function encodeRouteFixture(value, allowSlash) {
13182
+ if (allowSlash)
13183
+ return value.split("/").filter(Boolean).map(encodeURIComponent).join("/");
13184
+ return encodeURIComponent(value);
13185
+ }
13186
+ function materializeScenarioRoute(scenario, env = process.env) {
13187
+ const resolution = resolveRouteFixtures(scenario, env);
13188
+ if (!resolution.resolvedPath || resolution.resolvedPath === scenario.targetPath) {
13189
+ return { scenario, resolution };
13190
+ }
13191
+ const steps = scenario.steps.map((step) => {
13192
+ let next = step;
13193
+ for (const [name, value] of Object.entries(resolution.values)) {
13194
+ next = next.replaceAll(`:${name}`, value).replaceAll(`[${name}]`, value).replaceAll(`{${name}}`, value);
13195
+ }
13196
+ return next.replaceAll(scenario.targetPath ?? "", resolution.resolvedPath ?? "");
13197
+ });
13198
+ return {
13199
+ scenario: {
13200
+ ...scenario,
13201
+ targetPath: resolution.resolvedPath,
13202
+ steps,
13203
+ metadata: {
13204
+ ...scenario.metadata ?? {},
13205
+ routeFixtureResolution: resolution
13206
+ }
13207
+ },
13208
+ resolution
13209
+ };
13210
+ }
13211
+ function resolveStartUrl(baseUrl, targetPath) {
13212
+ try {
13213
+ return new URL(targetPath, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`).toString();
13214
+ } catch {
13215
+ return `${baseUrl.replace(/\/+$/, "")}/${targetPath.replace(/^\/+/, "")}`;
13216
+ }
13217
+ }
13218
+ var DEFAULT_UUID = "00000000-0000-4000-8000-000000000000", PARAM_ENV_CANDIDATES;
13219
+ var init_route_fixtures = __esm(() => {
13220
+ PARAM_ENV_CANDIDATES = {
13221
+ orgSlug: ["TESTERS_ORG_SLUG", "SMOKE_ORG_SLUG", "ORG_SLUG"],
13222
+ orgId: ["TESTERS_ORG_ID", "SMOKE_ORG_ID", "ORG_ID"],
13223
+ projectSlug: ["TESTERS_PROJECT_SLUG", "SMOKE_PROJECT_SLUG", "PROJECT_SLUG"],
13224
+ projectId: ["TESTERS_PROJECT_ID", "SMOKE_PROJECT_ID", "PROJECT_ID"],
13225
+ workspaceId: ["TESTERS_WORKSPACE_ID", "SMOKE_WORKSPACE_ID", "WORKSPACE_ID"],
13226
+ agentId: ["TESTERS_AGENT_ID", "SMOKE_AGENT_ID", "AGENT_ID"],
13227
+ sessionId: ["TESTERS_SESSION_ID", "SMOKE_SESSION_ID", "SESSION_ID"],
13228
+ userId: ["TESTERS_USER_ID", "SMOKE_USER_ID", "USER_ID"]
13229
+ };
13230
+ });
13231
+
13060
13232
  // src/lib/browser-lightpanda.ts
13061
13233
  var exports_browser_lightpanda = {};
13062
13234
  __export(exports_browser_lightpanda, {
@@ -14777,33 +14949,35 @@ ${filtered.join(`
14777
14949
  return { result: `Error executing ${toolName}: ${message}` };
14778
14950
  }
14779
14951
  }
14780
- function resolveStartUrl(baseUrl, targetPath) {
14781
- try {
14782
- return new URL(targetPath, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`).toString();
14783
- } catch {
14784
- return `${baseUrl.replace(/\/+$/, "")}/${targetPath.replace(/^\/+/, "")}`;
14785
- }
14786
- }
14787
14952
  function buildScenarioUserMessage(scenario, baseUrl) {
14953
+ const { scenario: materializedScenario, resolution } = materializeScenarioRoute(scenario);
14788
14954
  const userParts = [
14789
- `**Scenario:** ${scenario.name}`,
14790
- `**Description:** ${scenario.description}`
14955
+ `**Scenario:** ${materializedScenario.name}`,
14956
+ `**Description:** ${materializedScenario.description}`
14791
14957
  ];
14792
14958
  if (baseUrl) {
14793
14959
  const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
14794
14960
  userParts.push(`**Base URL:** ${normalizedBaseUrl}`);
14795
- if (scenario.targetPath) {
14796
- userParts.push(`**Start URL:** ${resolveStartUrl(normalizedBaseUrl, scenario.targetPath)}`);
14961
+ if (materializedScenario.targetPath) {
14962
+ userParts.push(`**Start URL:** ${resolveStartUrl(normalizedBaseUrl, materializedScenario.targetPath)}`);
14797
14963
  }
14798
14964
  userParts.push("**Navigation Boundary:** Treat the Base URL as the application under test. Resolve relative paths and in-app navigation against this origin. Do not navigate to another host unless a step explicitly includes an absolute external URL.");
14799
14965
  }
14800
- if (scenario.targetPath) {
14801
- userParts.push(`**Target Path:** ${scenario.targetPath}`);
14966
+ if (materializedScenario.targetPath) {
14967
+ userParts.push(`**Target Path:** ${materializedScenario.targetPath}`);
14968
+ }
14969
+ if (resolution.params.length > 0) {
14970
+ userParts.push("**Route Fixtures:**");
14971
+ for (const param of resolution.params) {
14972
+ const source = resolution.sources[param];
14973
+ const synthetic = source === "default" ? " synthetic" : "";
14974
+ userParts.push(`- :${param} = ${resolution.values[param]} (${source}${synthetic})`);
14975
+ }
14802
14976
  }
14803
- if (scenario.steps.length > 0) {
14977
+ if (materializedScenario.steps.length > 0) {
14804
14978
  userParts.push("**Steps:**");
14805
- for (let i = 0;i < scenario.steps.length; i++) {
14806
- userParts.push(`${i + 1}. ${scenario.steps[i]}`);
14979
+ for (let i = 0;i < materializedScenario.steps.length; i++) {
14980
+ userParts.push(`${i + 1}. ${materializedScenario.steps[i]}`);
14807
14981
  }
14808
14982
  }
14809
14983
  return userParts.join(`
@@ -15101,6 +15275,7 @@ function createClientForModel(model, apiKey) {
15101
15275
  var activeHARs, activeCoverage, BROWSER_TOOLS;
15102
15276
  var init_ai_client = __esm(() => {
15103
15277
  init_types();
15278
+ init_route_fixtures();
15104
15279
  activeHARs = new Map;
15105
15280
  activeCoverage = new Map;
15106
15281
  BROWSER_TOOLS = [
@@ -18321,9 +18496,10 @@ function withTimeout(promise, ms, label) {
18321
18496
  });
18322
18497
  }
18323
18498
  async function runSingleScenario(scenario, runId, options) {
18324
- const scenarioType = scenario.scenarioType ?? "browser";
18499
+ const { scenario: materializedScenario, resolution: routeFixtureResolution } = materializeScenarioRoute(scenario);
18500
+ const scenarioType = materializedScenario.scenarioType ?? "browser";
18325
18501
  if (scenarioType === "eval") {
18326
- return runEvalScenario(scenario, { runId, baseUrl: options.url });
18502
+ return runEvalScenario(materializedScenario, { runId, baseUrl: options.url });
18327
18503
  }
18328
18504
  const config = loadConfig();
18329
18505
  if (options.selfHeal !== undefined)
@@ -18364,7 +18540,7 @@ async function runSingleScenario(scenario, runId, options) {
18364
18540
  runId,
18365
18541
  scenarioId: scenario.id,
18366
18542
  model,
18367
- stepsTotal: scenario.steps.length || 10,
18543
+ stepsTotal: materializedScenario.steps.length || 10,
18368
18544
  personaId: persona?.id ?? null,
18369
18545
  personaName: persona?.name ?? null
18370
18546
  });
@@ -18400,12 +18576,12 @@ async function runSingleScenario(scenario, runId, options) {
18400
18576
  engine: effectiveOptions.engine
18401
18577
  });
18402
18578
  }
18403
- const targetUrl = scenario.targetPath ? `${options.url.replace(/\/$/, "")}${scenario.targetPath}` : options.url;
18404
- const scenarioTimeout = scenario.timeoutMs ?? options.timeout ?? config.browser.timeout ?? 60000;
18579
+ const targetUrl = materializedScenario.targetPath ? resolveStartUrl(options.url.replace(/\/$/, ""), materializedScenario.targetPath) : options.url;
18580
+ const scenarioTimeout = materializedScenario.timeoutMs ?? options.timeout ?? config.browser.timeout ?? 60000;
18405
18581
  registerSession({
18406
18582
  resultId: result.id,
18407
18583
  runId,
18408
- scenarioId: scenario.id,
18584
+ scenarioId: materializedScenario.id,
18409
18585
  engine: effectiveOptions.engine ?? "playwright",
18410
18586
  startUrl: targetUrl
18411
18587
  });
@@ -18459,7 +18635,7 @@ async function runSingleScenario(scenario, runId, options) {
18459
18635
  const agentResult = await withTimeout(runAgentLoop({
18460
18636
  client,
18461
18637
  page,
18462
- scenario,
18638
+ scenario: materializedScenario,
18463
18639
  screenshotter,
18464
18640
  model,
18465
18641
  runId,
@@ -18550,7 +18726,7 @@ async function runSingleScenario(scenario, runId, options) {
18550
18726
  const baseReasoning = agentResult.reasoning ? agentResult.reasoning + lightpandaNote : lightpandaNote || "";
18551
18727
  const assertionOutcome = await applyStructuredAssertionsToResult({
18552
18728
  page,
18553
- scenario,
18729
+ scenario: materializedScenario,
18554
18730
  consoleErrors,
18555
18731
  status: agentResult.status,
18556
18732
  reasoning: baseReasoning
@@ -18571,6 +18747,7 @@ async function runSingleScenario(scenario, runId, options) {
18571
18747
  costCents: estimateCost(model, agentResult.tokensUsed),
18572
18748
  metadata: {
18573
18749
  consoleLogs,
18750
+ ...routeFixtureResolution.params.length > 0 ? { routeFixtureResolution } : {},
18574
18751
  ...networkErrors.length > 0 ? networkMeta : {},
18575
18752
  ...structuredAssertionMeta
18576
18753
  }
@@ -18920,6 +19097,7 @@ var init_runner = __esm(() => {
18920
19097
  init_browser();
18921
19098
  init_screenshotter();
18922
19099
  init_ai_client();
19100
+ init_route_fixtures();
18923
19101
  init_config2();
18924
19102
  init_persona_auth();
18925
19103
  init_session_tracker();
@@ -59225,6 +59403,10 @@ function scenarioInputForNextRoute(item, projectId) {
59225
59403
  actionCount: item.actions.length,
59226
59404
  groups: item.groups
59227
59405
  },
59406
+ parameters: item.fixtureParams.length > 0 ? {
59407
+ routeFixtures: defaultRouteFixturesForParams(item.fixtureParams),
59408
+ routeFixtureParams: item.fixtureParams
59409
+ } : undefined,
59228
59410
  projectId
59229
59411
  };
59230
59412
  }
@@ -59627,6 +59809,7 @@ var ROUTE_FILE_NAMES, WALK_EXCLUDES, SAFE_PAGE_ASSERTIONS, IMPORT_SCAN_LIMIT = 4
59627
59809
  var init_next_route_inventory = __esm(() => {
59628
59810
  init_scenarios();
59629
59811
  init_workflows();
59812
+ init_route_fixtures();
59630
59813
  ROUTE_FILE_NAMES = new Set([
59631
59814
  "page.tsx",
59632
59815
  "page.ts",
@@ -95041,7 +95224,7 @@ import chalk6 from "chalk";
95041
95224
  // package.json
95042
95225
  var package_default = {
95043
95226
  name: "@hasna/testers",
95044
- version: "0.0.50",
95227
+ version: "0.0.51",
95045
95228
  description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
95046
95229
  type: "module",
95047
95230
  main: "dist/index.js",
@@ -1 +1 @@
1
- {"version":3,"file":"scenarios.d.ts","sourceRoot":"","sources":["../../src/db/scenarios.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EAEb,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EAGpB,MAAM,mBAAmB,CAAC;AA6B3B,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,CA+BnE;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAmBvD;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAIrE;AAED,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,QAAQ,EAAE,CAoFjE;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,CAwFhG;AAED,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,MAAM,CA8B9D;AAED,MAAM,WAAW,aAAc,SAAQ,QAAQ;IAC7C,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAmBhE;AAED,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAIvE;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAOlD;AAED,MAAM,MAAM,oBAAoB,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAA;CAAE,CAAC;AAErG;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,oBAAoB,CAuDlG"}
1
+ {"version":3,"file":"scenarios.d.ts","sourceRoot":"","sources":["../../src/db/scenarios.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EAEb,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EAGpB,MAAM,mBAAmB,CAAC;AA2C3B,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,CA+BnE;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAmBvD;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAIrE;AAED,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,QAAQ,EAAE,CAoFjE;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,CAwFhG;AAED,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,MAAM,CA8B9D;AAED,MAAM,WAAW,aAAc,SAAQ,QAAQ;IAC7C,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAmBhE;AAED,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAIvE;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAOlD;AAED,MAAM,MAAM,oBAAoB,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAA;CAAE,CAAC;AAErG;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,oBAAoB,CAgElG"}
package/dist/index.js CHANGED
@@ -11567,6 +11567,156 @@ var init_browser = __esm(() => {
11567
11567
  DEFAULT_VIEWPORT = { width: 1280, height: 720 };
11568
11568
  });
11569
11569
 
11570
+ // src/lib/route-fixtures.ts
11571
+ function isRecord2(value) {
11572
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11573
+ }
11574
+ function envNameForParam(prefix, param) {
11575
+ return `${prefix}_${param.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").toUpperCase()}`;
11576
+ }
11577
+ function readString(value) {
11578
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
11579
+ }
11580
+ function resolveReference(value, env) {
11581
+ if (value.startsWith("$?"))
11582
+ return env[value.slice(2)]?.trim() || undefined;
11583
+ if (value.startsWith("$"))
11584
+ return env[value.slice(1)]?.trim() || undefined;
11585
+ return value;
11586
+ }
11587
+ function scenarioFixtureValue(params, name, env) {
11588
+ if (!params)
11589
+ return;
11590
+ const routeFixtures = isRecord2(params["routeFixtures"]) ? params["routeFixtures"] : {};
11591
+ const raw = readString(routeFixtures[name]) ?? readString(params[name]);
11592
+ return raw ? resolveReference(raw, env) : undefined;
11593
+ }
11594
+ function envFixtureValue(name, env) {
11595
+ const candidates = [
11596
+ envNameForParam("TESTERS_ROUTE", name),
11597
+ envNameForParam("TESTERS_FIXTURE", name),
11598
+ envNameForParam("ALUMIA_FIXTURE", name),
11599
+ ...PARAM_ENV_CANDIDATES[name] ?? []
11600
+ ];
11601
+ for (const candidate of candidates) {
11602
+ const value = env[candidate]?.trim();
11603
+ if (value)
11604
+ return value;
11605
+ }
11606
+ return;
11607
+ }
11608
+ function defaultFixtureValue(name) {
11609
+ if (name === "orgSlug")
11610
+ return "test-org";
11611
+ if (name.toLowerCase().endsWith("slug")) {
11612
+ return `test-${name.replace(/Slug$/i, "").replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase() || "slug"}`;
11613
+ }
11614
+ if (name === "id" || name.toLowerCase().endsWith("id"))
11615
+ return DEFAULT_UUID;
11616
+ if (name.toLowerCase().includes("token"))
11617
+ return "test-token";
11618
+ return `test-${name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()}`;
11619
+ }
11620
+ function routeParamsFromPath(path) {
11621
+ if (!path)
11622
+ return [];
11623
+ const params = new Set;
11624
+ for (const match of path.matchAll(/:([A-Za-z0-9_]+)(?:\*\??)?/g)) {
11625
+ if (match[1])
11626
+ params.add(match[1]);
11627
+ }
11628
+ return [...params];
11629
+ }
11630
+ function resolveRouteFixtures(scenario, env = process.env) {
11631
+ const metadataParams = Array.isArray(scenario.metadata?.["fixtureParams"]) ? scenario.metadata["fixtureParams"].filter((value) => typeof value === "string") : [];
11632
+ const params = [...new Set([...metadataParams, ...routeParamsFromPath(scenario.targetPath)])];
11633
+ const values = {};
11634
+ const sources = {};
11635
+ const synthetic = [];
11636
+ for (const param of params) {
11637
+ const scenarioValue = scenarioFixtureValue(scenario.parameters, param, env);
11638
+ if (scenarioValue) {
11639
+ values[param] = scenarioValue;
11640
+ sources[param] = "scenario";
11641
+ continue;
11642
+ }
11643
+ const envValue = envFixtureValue(param, env);
11644
+ if (envValue) {
11645
+ values[param] = envValue;
11646
+ sources[param] = "env";
11647
+ continue;
11648
+ }
11649
+ values[param] = defaultFixtureValue(param);
11650
+ sources[param] = "default";
11651
+ synthetic.push(param);
11652
+ }
11653
+ const resolvedPath = scenario.targetPath ? resolveRoutePath(scenario.targetPath, values) : null;
11654
+ return {
11655
+ originalPath: scenario.targetPath,
11656
+ resolvedPath,
11657
+ params,
11658
+ values,
11659
+ sources,
11660
+ synthetic
11661
+ };
11662
+ }
11663
+ function resolveRoutePath(path, values) {
11664
+ return path.replace(/\/:([A-Za-z0-9_]+)\*\?/g, (_match, name) => {
11665
+ const value = values[name];
11666
+ return value ? `/${encodeRouteFixture(value, true)}` : "";
11667
+ }).replace(/:([A-Za-z0-9_]+)\*/g, (_match, name) => encodeRouteFixture(values[name] ?? defaultFixtureValue(name), true)).replace(/:([A-Za-z0-9_]+)/g, (_match, name) => encodeRouteFixture(values[name] ?? defaultFixtureValue(name), false));
11668
+ }
11669
+ function encodeRouteFixture(value, allowSlash) {
11670
+ if (allowSlash)
11671
+ return value.split("/").filter(Boolean).map(encodeURIComponent).join("/");
11672
+ return encodeURIComponent(value);
11673
+ }
11674
+ function materializeScenarioRoute(scenario, env = process.env) {
11675
+ const resolution = resolveRouteFixtures(scenario, env);
11676
+ if (!resolution.resolvedPath || resolution.resolvedPath === scenario.targetPath) {
11677
+ return { scenario, resolution };
11678
+ }
11679
+ const steps = scenario.steps.map((step) => {
11680
+ let next = step;
11681
+ for (const [name, value] of Object.entries(resolution.values)) {
11682
+ next = next.replaceAll(`:${name}`, value).replaceAll(`[${name}]`, value).replaceAll(`{${name}}`, value);
11683
+ }
11684
+ return next.replaceAll(scenario.targetPath ?? "", resolution.resolvedPath ?? "");
11685
+ });
11686
+ return {
11687
+ scenario: {
11688
+ ...scenario,
11689
+ targetPath: resolution.resolvedPath,
11690
+ steps,
11691
+ metadata: {
11692
+ ...scenario.metadata ?? {},
11693
+ routeFixtureResolution: resolution
11694
+ }
11695
+ },
11696
+ resolution
11697
+ };
11698
+ }
11699
+ function resolveStartUrl(baseUrl, targetPath) {
11700
+ try {
11701
+ return new URL(targetPath, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`).toString();
11702
+ } catch {
11703
+ return `${baseUrl.replace(/\/+$/, "")}/${targetPath.replace(/^\/+/, "")}`;
11704
+ }
11705
+ }
11706
+ var DEFAULT_UUID = "00000000-0000-4000-8000-000000000000", PARAM_ENV_CANDIDATES;
11707
+ var init_route_fixtures = __esm(() => {
11708
+ PARAM_ENV_CANDIDATES = {
11709
+ orgSlug: ["TESTERS_ORG_SLUG", "SMOKE_ORG_SLUG", "ORG_SLUG"],
11710
+ orgId: ["TESTERS_ORG_ID", "SMOKE_ORG_ID", "ORG_ID"],
11711
+ projectSlug: ["TESTERS_PROJECT_SLUG", "SMOKE_PROJECT_SLUG", "PROJECT_SLUG"],
11712
+ projectId: ["TESTERS_PROJECT_ID", "SMOKE_PROJECT_ID", "PROJECT_ID"],
11713
+ workspaceId: ["TESTERS_WORKSPACE_ID", "SMOKE_WORKSPACE_ID", "WORKSPACE_ID"],
11714
+ agentId: ["TESTERS_AGENT_ID", "SMOKE_AGENT_ID", "AGENT_ID"],
11715
+ sessionId: ["TESTERS_SESSION_ID", "SMOKE_SESSION_ID", "SESSION_ID"],
11716
+ userId: ["TESTERS_USER_ID", "SMOKE_USER_ID", "USER_ID"]
11717
+ };
11718
+ });
11719
+
11570
11720
  // src/lib/scanners/a11y.ts
11571
11721
  var exports_a11y = {};
11572
11722
  __export(exports_a11y, {
@@ -12409,33 +12559,35 @@ ${filtered.join(`
12409
12559
  return { result: `Error executing ${toolName}: ${message}` };
12410
12560
  }
12411
12561
  }
12412
- function resolveStartUrl(baseUrl, targetPath) {
12413
- try {
12414
- return new URL(targetPath, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`).toString();
12415
- } catch {
12416
- return `${baseUrl.replace(/\/+$/, "")}/${targetPath.replace(/^\/+/, "")}`;
12417
- }
12418
- }
12419
12562
  function buildScenarioUserMessage(scenario, baseUrl) {
12563
+ const { scenario: materializedScenario, resolution } = materializeScenarioRoute(scenario);
12420
12564
  const userParts = [
12421
- `**Scenario:** ${scenario.name}`,
12422
- `**Description:** ${scenario.description}`
12565
+ `**Scenario:** ${materializedScenario.name}`,
12566
+ `**Description:** ${materializedScenario.description}`
12423
12567
  ];
12424
12568
  if (baseUrl) {
12425
12569
  const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
12426
12570
  userParts.push(`**Base URL:** ${normalizedBaseUrl}`);
12427
- if (scenario.targetPath) {
12428
- userParts.push(`**Start URL:** ${resolveStartUrl(normalizedBaseUrl, scenario.targetPath)}`);
12571
+ if (materializedScenario.targetPath) {
12572
+ userParts.push(`**Start URL:** ${resolveStartUrl(normalizedBaseUrl, materializedScenario.targetPath)}`);
12429
12573
  }
12430
12574
  userParts.push("**Navigation Boundary:** Treat the Base URL as the application under test. Resolve relative paths and in-app navigation against this origin. Do not navigate to another host unless a step explicitly includes an absolute external URL.");
12431
12575
  }
12432
- if (scenario.targetPath) {
12433
- userParts.push(`**Target Path:** ${scenario.targetPath}`);
12576
+ if (materializedScenario.targetPath) {
12577
+ userParts.push(`**Target Path:** ${materializedScenario.targetPath}`);
12578
+ }
12579
+ if (resolution.params.length > 0) {
12580
+ userParts.push("**Route Fixtures:**");
12581
+ for (const param of resolution.params) {
12582
+ const source = resolution.sources[param];
12583
+ const synthetic = source === "default" ? " synthetic" : "";
12584
+ userParts.push(`- :${param} = ${resolution.values[param]} (${source}${synthetic})`);
12585
+ }
12434
12586
  }
12435
- if (scenario.steps.length > 0) {
12587
+ if (materializedScenario.steps.length > 0) {
12436
12588
  userParts.push("**Steps:**");
12437
- for (let i = 0;i < scenario.steps.length; i++) {
12438
- userParts.push(`${i + 1}. ${scenario.steps[i]}`);
12589
+ for (let i = 0;i < materializedScenario.steps.length; i++) {
12590
+ userParts.push(`${i + 1}. ${materializedScenario.steps[i]}`);
12439
12591
  }
12440
12592
  }
12441
12593
  return userParts.join(`
@@ -12733,6 +12885,7 @@ function createClientForModel(model, apiKey) {
12733
12885
  var activeHARs, activeCoverage, BROWSER_TOOLS;
12734
12886
  var init_ai_client = __esm(() => {
12735
12887
  init_types();
12888
+ init_route_fixtures();
12736
12889
  activeHARs = new Map;
12737
12890
  activeCoverage = new Map;
12738
12891
  BROWSER_TOOLS = [
@@ -15482,6 +15635,7 @@ function savePersonaAuthCookies(id, cookies) {
15482
15635
  // src/lib/runner.ts
15483
15636
  init_browser();
15484
15637
  init_ai_client();
15638
+ init_route_fixtures();
15485
15639
  init_config2();
15486
15640
 
15487
15641
  // src/lib/secrets-resolver.ts
@@ -16625,9 +16779,10 @@ function withTimeout(promise, ms, label) {
16625
16779
  });
16626
16780
  }
16627
16781
  async function runSingleScenario(scenario, runId, options) {
16628
- const scenarioType = scenario.scenarioType ?? "browser";
16782
+ const { scenario: materializedScenario, resolution: routeFixtureResolution } = materializeScenarioRoute(scenario);
16783
+ const scenarioType = materializedScenario.scenarioType ?? "browser";
16629
16784
  if (scenarioType === "eval") {
16630
- return runEvalScenario(scenario, { runId, baseUrl: options.url });
16785
+ return runEvalScenario(materializedScenario, { runId, baseUrl: options.url });
16631
16786
  }
16632
16787
  const config = loadConfig();
16633
16788
  if (options.selfHeal !== undefined)
@@ -16668,7 +16823,7 @@ async function runSingleScenario(scenario, runId, options) {
16668
16823
  runId,
16669
16824
  scenarioId: scenario.id,
16670
16825
  model,
16671
- stepsTotal: scenario.steps.length || 10,
16826
+ stepsTotal: materializedScenario.steps.length || 10,
16672
16827
  personaId: persona?.id ?? null,
16673
16828
  personaName: persona?.name ?? null
16674
16829
  });
@@ -16704,12 +16859,12 @@ async function runSingleScenario(scenario, runId, options) {
16704
16859
  engine: effectiveOptions.engine
16705
16860
  });
16706
16861
  }
16707
- const targetUrl = scenario.targetPath ? `${options.url.replace(/\/$/, "")}${scenario.targetPath}` : options.url;
16708
- const scenarioTimeout = scenario.timeoutMs ?? options.timeout ?? config.browser.timeout ?? 60000;
16862
+ const targetUrl = materializedScenario.targetPath ? resolveStartUrl(options.url.replace(/\/$/, ""), materializedScenario.targetPath) : options.url;
16863
+ const scenarioTimeout = materializedScenario.timeoutMs ?? options.timeout ?? config.browser.timeout ?? 60000;
16709
16864
  registerSession({
16710
16865
  resultId: result.id,
16711
16866
  runId,
16712
- scenarioId: scenario.id,
16867
+ scenarioId: materializedScenario.id,
16713
16868
  engine: effectiveOptions.engine ?? "playwright",
16714
16869
  startUrl: targetUrl
16715
16870
  });
@@ -16763,7 +16918,7 @@ async function runSingleScenario(scenario, runId, options) {
16763
16918
  const agentResult = await withTimeout(runAgentLoop({
16764
16919
  client,
16765
16920
  page,
16766
- scenario,
16921
+ scenario: materializedScenario,
16767
16922
  screenshotter,
16768
16923
  model,
16769
16924
  runId,
@@ -16854,7 +17009,7 @@ async function runSingleScenario(scenario, runId, options) {
16854
17009
  const baseReasoning = agentResult.reasoning ? agentResult.reasoning + lightpandaNote : lightpandaNote || "";
16855
17010
  const assertionOutcome = await applyStructuredAssertionsToResult({
16856
17011
  page,
16857
- scenario,
17012
+ scenario: materializedScenario,
16858
17013
  consoleErrors,
16859
17014
  status: agentResult.status,
16860
17015
  reasoning: baseReasoning
@@ -16875,6 +17030,7 @@ async function runSingleScenario(scenario, runId, options) {
16875
17030
  costCents: estimateCost(model, agentResult.tokensUsed),
16876
17031
  metadata: {
16877
17032
  consoleLogs,
17033
+ ...routeFixtureResolution.params.length > 0 ? { routeFixtureResolution } : {},
16878
17034
  ...networkErrors.length > 0 ? networkMeta : {},
16879
17035
  ...structuredAssertionMeta
16880
17036
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ai-client.d.ts","sourceRoot":"","sources":["../../src/lib/ai-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ai-client.d.ts","sourceRoot":"","sources":["../../src/lib/ai-client.ts"],"names":[],"mappings":"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"}
@@ -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;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"}
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,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,CA8DrB;AAED,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,+BAA+B,GACvC,8BAA8B,CAuBhC"}
@@ -0,0 +1,20 @@
1
+ import type { Scenario } from "../types/index.js";
2
+ export interface RouteFixtureResolution {
3
+ originalPath: string | null;
4
+ resolvedPath: string | null;
5
+ params: string[];
6
+ values: Record<string, string>;
7
+ sources: Record<string, "scenario" | "env" | "default">;
8
+ synthetic: string[];
9
+ }
10
+ export interface MaterializedRouteScenario {
11
+ scenario: Scenario;
12
+ resolution: RouteFixtureResolution;
13
+ }
14
+ export declare function routeParamsFromPath(path: string | null | undefined): string[];
15
+ export declare function defaultRouteFixturesForParams(params: string[]): Record<string, string>;
16
+ export declare function resolveRouteFixtures(scenario: Scenario, env?: Record<string, string | undefined>): RouteFixtureResolution;
17
+ export declare function resolveRoutePath(path: string, values: Record<string, string>): string;
18
+ export declare function materializeScenarioRoute(scenario: Scenario, env?: Record<string, string | undefined>): MaterializedRouteScenario;
19
+ export declare function resolveStartUrl(baseUrl: string, targetPath: string): string;
20
+ //# sourceMappingURL=route-fixtures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-fixtures.d.ts","sourceRoot":"","sources":["../../src/lib/route-fixtures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAElD,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,KAAK,GAAG,SAAS,CAAC,CAAC;IACxD,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,EAAE,sBAAsB,CAAC;CACpC;AAoED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,CAO7E;AAED,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAEtF;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACpD,sBAAsB,CAoCxB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAQrF;AAOD,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,QAAQ,EAClB,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACpD,yBAAyB,CA6B3B;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAM3E"}
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/lib/runner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/lib/runner.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.50",
55
+ version: "0.0.51",
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",
@@ -15674,6 +15674,156 @@ var init_agents = __esm(() => {
15674
15674
  init_database();
15675
15675
  });
15676
15676
 
15677
+ // src/lib/route-fixtures.ts
15678
+ function isRecord2(value) {
15679
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15680
+ }
15681
+ function envNameForParam(prefix, param) {
15682
+ return `${prefix}_${param.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").toUpperCase()}`;
15683
+ }
15684
+ function readString(value) {
15685
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
15686
+ }
15687
+ function resolveReference(value, env) {
15688
+ if (value.startsWith("$?"))
15689
+ return env[value.slice(2)]?.trim() || undefined;
15690
+ if (value.startsWith("$"))
15691
+ return env[value.slice(1)]?.trim() || undefined;
15692
+ return value;
15693
+ }
15694
+ function scenarioFixtureValue(params, name, env) {
15695
+ if (!params)
15696
+ return;
15697
+ const routeFixtures = isRecord2(params["routeFixtures"]) ? params["routeFixtures"] : {};
15698
+ const raw = readString(routeFixtures[name]) ?? readString(params[name]);
15699
+ return raw ? resolveReference(raw, env) : undefined;
15700
+ }
15701
+ function envFixtureValue(name, env) {
15702
+ const candidates = [
15703
+ envNameForParam("TESTERS_ROUTE", name),
15704
+ envNameForParam("TESTERS_FIXTURE", name),
15705
+ envNameForParam("ALUMIA_FIXTURE", name),
15706
+ ...PARAM_ENV_CANDIDATES[name] ?? []
15707
+ ];
15708
+ for (const candidate of candidates) {
15709
+ const value = env[candidate]?.trim();
15710
+ if (value)
15711
+ return value;
15712
+ }
15713
+ return;
15714
+ }
15715
+ function defaultFixtureValue(name) {
15716
+ if (name === "orgSlug")
15717
+ return "test-org";
15718
+ if (name.toLowerCase().endsWith("slug")) {
15719
+ return `test-${name.replace(/Slug$/i, "").replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase() || "slug"}`;
15720
+ }
15721
+ if (name === "id" || name.toLowerCase().endsWith("id"))
15722
+ return DEFAULT_UUID;
15723
+ if (name.toLowerCase().includes("token"))
15724
+ return "test-token";
15725
+ return `test-${name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()}`;
15726
+ }
15727
+ function routeParamsFromPath(path) {
15728
+ if (!path)
15729
+ return [];
15730
+ const params = new Set;
15731
+ for (const match of path.matchAll(/:([A-Za-z0-9_]+)(?:\*\??)?/g)) {
15732
+ if (match[1])
15733
+ params.add(match[1]);
15734
+ }
15735
+ return [...params];
15736
+ }
15737
+ function resolveRouteFixtures(scenario, env = process.env) {
15738
+ const metadataParams = Array.isArray(scenario.metadata?.["fixtureParams"]) ? scenario.metadata["fixtureParams"].filter((value) => typeof value === "string") : [];
15739
+ const params = [...new Set([...metadataParams, ...routeParamsFromPath(scenario.targetPath)])];
15740
+ const values = {};
15741
+ const sources = {};
15742
+ const synthetic = [];
15743
+ for (const param of params) {
15744
+ const scenarioValue = scenarioFixtureValue(scenario.parameters, param, env);
15745
+ if (scenarioValue) {
15746
+ values[param] = scenarioValue;
15747
+ sources[param] = "scenario";
15748
+ continue;
15749
+ }
15750
+ const envValue = envFixtureValue(param, env);
15751
+ if (envValue) {
15752
+ values[param] = envValue;
15753
+ sources[param] = "env";
15754
+ continue;
15755
+ }
15756
+ values[param] = defaultFixtureValue(param);
15757
+ sources[param] = "default";
15758
+ synthetic.push(param);
15759
+ }
15760
+ const resolvedPath = scenario.targetPath ? resolveRoutePath(scenario.targetPath, values) : null;
15761
+ return {
15762
+ originalPath: scenario.targetPath,
15763
+ resolvedPath,
15764
+ params,
15765
+ values,
15766
+ sources,
15767
+ synthetic
15768
+ };
15769
+ }
15770
+ function resolveRoutePath(path, values) {
15771
+ return path.replace(/\/:([A-Za-z0-9_]+)\*\?/g, (_match, name) => {
15772
+ const value = values[name];
15773
+ return value ? `/${encodeRouteFixture(value, true)}` : "";
15774
+ }).replace(/:([A-Za-z0-9_]+)\*/g, (_match, name) => encodeRouteFixture(values[name] ?? defaultFixtureValue(name), true)).replace(/:([A-Za-z0-9_]+)/g, (_match, name) => encodeRouteFixture(values[name] ?? defaultFixtureValue(name), false));
15775
+ }
15776
+ function encodeRouteFixture(value, allowSlash) {
15777
+ if (allowSlash)
15778
+ return value.split("/").filter(Boolean).map(encodeURIComponent).join("/");
15779
+ return encodeURIComponent(value);
15780
+ }
15781
+ function materializeScenarioRoute(scenario, env = process.env) {
15782
+ const resolution = resolveRouteFixtures(scenario, env);
15783
+ if (!resolution.resolvedPath || resolution.resolvedPath === scenario.targetPath) {
15784
+ return { scenario, resolution };
15785
+ }
15786
+ const steps = scenario.steps.map((step) => {
15787
+ let next = step;
15788
+ for (const [name, value] of Object.entries(resolution.values)) {
15789
+ next = next.replaceAll(`:${name}`, value).replaceAll(`[${name}]`, value).replaceAll(`{${name}}`, value);
15790
+ }
15791
+ return next.replaceAll(scenario.targetPath ?? "", resolution.resolvedPath ?? "");
15792
+ });
15793
+ return {
15794
+ scenario: {
15795
+ ...scenario,
15796
+ targetPath: resolution.resolvedPath,
15797
+ steps,
15798
+ metadata: {
15799
+ ...scenario.metadata ?? {},
15800
+ routeFixtureResolution: resolution
15801
+ }
15802
+ },
15803
+ resolution
15804
+ };
15805
+ }
15806
+ function resolveStartUrl(baseUrl, targetPath) {
15807
+ try {
15808
+ return new URL(targetPath, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`).toString();
15809
+ } catch {
15810
+ return `${baseUrl.replace(/\/+$/, "")}/${targetPath.replace(/^\/+/, "")}`;
15811
+ }
15812
+ }
15813
+ var DEFAULT_UUID = "00000000-0000-4000-8000-000000000000", PARAM_ENV_CANDIDATES;
15814
+ var init_route_fixtures = __esm(() => {
15815
+ PARAM_ENV_CANDIDATES = {
15816
+ orgSlug: ["TESTERS_ORG_SLUG", "SMOKE_ORG_SLUG", "ORG_SLUG"],
15817
+ orgId: ["TESTERS_ORG_ID", "SMOKE_ORG_ID", "ORG_ID"],
15818
+ projectSlug: ["TESTERS_PROJECT_SLUG", "SMOKE_PROJECT_SLUG", "PROJECT_SLUG"],
15819
+ projectId: ["TESTERS_PROJECT_ID", "SMOKE_PROJECT_ID", "PROJECT_ID"],
15820
+ workspaceId: ["TESTERS_WORKSPACE_ID", "SMOKE_WORKSPACE_ID", "WORKSPACE_ID"],
15821
+ agentId: ["TESTERS_AGENT_ID", "SMOKE_AGENT_ID", "AGENT_ID"],
15822
+ sessionId: ["TESTERS_SESSION_ID", "SMOKE_SESSION_ID", "SESSION_ID"],
15823
+ userId: ["TESTERS_USER_ID", "SMOKE_USER_ID", "USER_ID"]
15824
+ };
15825
+ });
15826
+
15677
15827
  // src/lib/browser-lightpanda.ts
15678
15828
  var exports_browser_lightpanda = {};
15679
15829
  __export(exports_browser_lightpanda, {
@@ -17407,33 +17557,35 @@ ${filtered.join(`
17407
17557
  return { result: `Error executing ${toolName}: ${message}` };
17408
17558
  }
17409
17559
  }
17410
- function resolveStartUrl(baseUrl, targetPath) {
17411
- try {
17412
- return new URL(targetPath, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`).toString();
17413
- } catch {
17414
- return `${baseUrl.replace(/\/+$/, "")}/${targetPath.replace(/^\/+/, "")}`;
17415
- }
17416
- }
17417
17560
  function buildScenarioUserMessage(scenario, baseUrl) {
17561
+ const { scenario: materializedScenario, resolution } = materializeScenarioRoute(scenario);
17418
17562
  const userParts = [
17419
- `**Scenario:** ${scenario.name}`,
17420
- `**Description:** ${scenario.description}`
17563
+ `**Scenario:** ${materializedScenario.name}`,
17564
+ `**Description:** ${materializedScenario.description}`
17421
17565
  ];
17422
17566
  if (baseUrl) {
17423
17567
  const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
17424
17568
  userParts.push(`**Base URL:** ${normalizedBaseUrl}`);
17425
- if (scenario.targetPath) {
17426
- userParts.push(`**Start URL:** ${resolveStartUrl(normalizedBaseUrl, scenario.targetPath)}`);
17569
+ if (materializedScenario.targetPath) {
17570
+ userParts.push(`**Start URL:** ${resolveStartUrl(normalizedBaseUrl, materializedScenario.targetPath)}`);
17427
17571
  }
17428
17572
  userParts.push("**Navigation Boundary:** Treat the Base URL as the application under test. Resolve relative paths and in-app navigation against this origin. Do not navigate to another host unless a step explicitly includes an absolute external URL.");
17429
17573
  }
17430
- if (scenario.targetPath) {
17431
- userParts.push(`**Target Path:** ${scenario.targetPath}`);
17574
+ if (materializedScenario.targetPath) {
17575
+ userParts.push(`**Target Path:** ${materializedScenario.targetPath}`);
17576
+ }
17577
+ if (resolution.params.length > 0) {
17578
+ userParts.push("**Route Fixtures:**");
17579
+ for (const param of resolution.params) {
17580
+ const source = resolution.sources[param];
17581
+ const synthetic = source === "default" ? " synthetic" : "";
17582
+ userParts.push(`- :${param} = ${resolution.values[param]} (${source}${synthetic})`);
17583
+ }
17432
17584
  }
17433
- if (scenario.steps.length > 0) {
17585
+ if (materializedScenario.steps.length > 0) {
17434
17586
  userParts.push("**Steps:**");
17435
- for (let i = 0;i < scenario.steps.length; i++) {
17436
- userParts.push(`${i + 1}. ${scenario.steps[i]}`);
17587
+ for (let i = 0;i < materializedScenario.steps.length; i++) {
17588
+ userParts.push(`${i + 1}. ${materializedScenario.steps[i]}`);
17437
17589
  }
17438
17590
  }
17439
17591
  return userParts.join(`
@@ -17731,6 +17883,7 @@ function createClientForModel(model, apiKey) {
17731
17883
  var activeHARs, activeCoverage, BROWSER_TOOLS;
17732
17884
  var init_ai_client = __esm(() => {
17733
17885
  init_types3();
17886
+ init_route_fixtures();
17734
17887
  activeHARs = new Map;
17735
17888
  activeCoverage = new Map;
17736
17889
  BROWSER_TOOLS = [
@@ -21370,9 +21523,10 @@ function withTimeout(promise, ms, label) {
21370
21523
  });
21371
21524
  }
21372
21525
  async function runSingleScenario(scenario, runId, options) {
21373
- const scenarioType = scenario.scenarioType ?? "browser";
21526
+ const { scenario: materializedScenario, resolution: routeFixtureResolution } = materializeScenarioRoute(scenario);
21527
+ const scenarioType = materializedScenario.scenarioType ?? "browser";
21374
21528
  if (scenarioType === "eval") {
21375
- return runEvalScenario(scenario, { runId, baseUrl: options.url });
21529
+ return runEvalScenario(materializedScenario, { runId, baseUrl: options.url });
21376
21530
  }
21377
21531
  const config = loadConfig();
21378
21532
  if (options.selfHeal !== undefined)
@@ -21413,7 +21567,7 @@ async function runSingleScenario(scenario, runId, options) {
21413
21567
  runId,
21414
21568
  scenarioId: scenario.id,
21415
21569
  model,
21416
- stepsTotal: scenario.steps.length || 10,
21570
+ stepsTotal: materializedScenario.steps.length || 10,
21417
21571
  personaId: persona?.id ?? null,
21418
21572
  personaName: persona?.name ?? null
21419
21573
  });
@@ -21449,12 +21603,12 @@ async function runSingleScenario(scenario, runId, options) {
21449
21603
  engine: effectiveOptions.engine
21450
21604
  });
21451
21605
  }
21452
- const targetUrl = scenario.targetPath ? `${options.url.replace(/\/$/, "")}${scenario.targetPath}` : options.url;
21453
- const scenarioTimeout = scenario.timeoutMs ?? options.timeout ?? config.browser.timeout ?? 60000;
21606
+ const targetUrl = materializedScenario.targetPath ? resolveStartUrl(options.url.replace(/\/$/, ""), materializedScenario.targetPath) : options.url;
21607
+ const scenarioTimeout = materializedScenario.timeoutMs ?? options.timeout ?? config.browser.timeout ?? 60000;
21454
21608
  registerSession({
21455
21609
  resultId: result.id,
21456
21610
  runId,
21457
- scenarioId: scenario.id,
21611
+ scenarioId: materializedScenario.id,
21458
21612
  engine: effectiveOptions.engine ?? "playwright",
21459
21613
  startUrl: targetUrl
21460
21614
  });
@@ -21508,7 +21662,7 @@ async function runSingleScenario(scenario, runId, options) {
21508
21662
  const agentResult = await withTimeout(runAgentLoop({
21509
21663
  client,
21510
21664
  page,
21511
- scenario,
21665
+ scenario: materializedScenario,
21512
21666
  screenshotter,
21513
21667
  model,
21514
21668
  runId,
@@ -21599,7 +21753,7 @@ async function runSingleScenario(scenario, runId, options) {
21599
21753
  const baseReasoning = agentResult.reasoning ? agentResult.reasoning + lightpandaNote : lightpandaNote || "";
21600
21754
  const assertionOutcome = await applyStructuredAssertionsToResult({
21601
21755
  page,
21602
- scenario,
21756
+ scenario: materializedScenario,
21603
21757
  consoleErrors,
21604
21758
  status: agentResult.status,
21605
21759
  reasoning: baseReasoning
@@ -21620,6 +21774,7 @@ async function runSingleScenario(scenario, runId, options) {
21620
21774
  costCents: estimateCost(model, agentResult.tokensUsed),
21621
21775
  metadata: {
21622
21776
  consoleLogs,
21777
+ ...routeFixtureResolution.params.length > 0 ? { routeFixtureResolution } : {},
21623
21778
  ...networkErrors.length > 0 ? networkMeta : {},
21624
21779
  ...structuredAssertionMeta
21625
21780
  }
@@ -21969,6 +22124,7 @@ var init_runner = __esm(() => {
21969
22124
  init_browser();
21970
22125
  init_screenshotter();
21971
22126
  init_ai_client();
22127
+ init_route_fixtures();
21972
22128
  init_config2();
21973
22129
  init_persona_auth();
21974
22130
  init_session_tracker();
@@ -14410,6 +14410,156 @@ var init_results = __esm(() => {
14410
14410
  init_database();
14411
14411
  });
14412
14412
 
14413
+ // src/lib/route-fixtures.ts
14414
+ function isRecord2(value) {
14415
+ return typeof value === "object" && value !== null && !Array.isArray(value);
14416
+ }
14417
+ function envNameForParam(prefix, param) {
14418
+ return `${prefix}_${param.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").toUpperCase()}`;
14419
+ }
14420
+ function readString(value) {
14421
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
14422
+ }
14423
+ function resolveReference(value, env) {
14424
+ if (value.startsWith("$?"))
14425
+ return env[value.slice(2)]?.trim() || undefined;
14426
+ if (value.startsWith("$"))
14427
+ return env[value.slice(1)]?.trim() || undefined;
14428
+ return value;
14429
+ }
14430
+ function scenarioFixtureValue(params, name, env) {
14431
+ if (!params)
14432
+ return;
14433
+ const routeFixtures = isRecord2(params["routeFixtures"]) ? params["routeFixtures"] : {};
14434
+ const raw = readString(routeFixtures[name]) ?? readString(params[name]);
14435
+ return raw ? resolveReference(raw, env) : undefined;
14436
+ }
14437
+ function envFixtureValue(name, env) {
14438
+ const candidates = [
14439
+ envNameForParam("TESTERS_ROUTE", name),
14440
+ envNameForParam("TESTERS_FIXTURE", name),
14441
+ envNameForParam("ALUMIA_FIXTURE", name),
14442
+ ...PARAM_ENV_CANDIDATES[name] ?? []
14443
+ ];
14444
+ for (const candidate of candidates) {
14445
+ const value = env[candidate]?.trim();
14446
+ if (value)
14447
+ return value;
14448
+ }
14449
+ return;
14450
+ }
14451
+ function defaultFixtureValue(name) {
14452
+ if (name === "orgSlug")
14453
+ return "test-org";
14454
+ if (name.toLowerCase().endsWith("slug")) {
14455
+ return `test-${name.replace(/Slug$/i, "").replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase() || "slug"}`;
14456
+ }
14457
+ if (name === "id" || name.toLowerCase().endsWith("id"))
14458
+ return DEFAULT_UUID;
14459
+ if (name.toLowerCase().includes("token"))
14460
+ return "test-token";
14461
+ return `test-${name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()}`;
14462
+ }
14463
+ function routeParamsFromPath(path) {
14464
+ if (!path)
14465
+ return [];
14466
+ const params = new Set;
14467
+ for (const match of path.matchAll(/:([A-Za-z0-9_]+)(?:\*\??)?/g)) {
14468
+ if (match[1])
14469
+ params.add(match[1]);
14470
+ }
14471
+ return [...params];
14472
+ }
14473
+ function resolveRouteFixtures(scenario, env = process.env) {
14474
+ const metadataParams = Array.isArray(scenario.metadata?.["fixtureParams"]) ? scenario.metadata["fixtureParams"].filter((value) => typeof value === "string") : [];
14475
+ const params = [...new Set([...metadataParams, ...routeParamsFromPath(scenario.targetPath)])];
14476
+ const values = {};
14477
+ const sources = {};
14478
+ const synthetic = [];
14479
+ for (const param of params) {
14480
+ const scenarioValue = scenarioFixtureValue(scenario.parameters, param, env);
14481
+ if (scenarioValue) {
14482
+ values[param] = scenarioValue;
14483
+ sources[param] = "scenario";
14484
+ continue;
14485
+ }
14486
+ const envValue = envFixtureValue(param, env);
14487
+ if (envValue) {
14488
+ values[param] = envValue;
14489
+ sources[param] = "env";
14490
+ continue;
14491
+ }
14492
+ values[param] = defaultFixtureValue(param);
14493
+ sources[param] = "default";
14494
+ synthetic.push(param);
14495
+ }
14496
+ const resolvedPath = scenario.targetPath ? resolveRoutePath(scenario.targetPath, values) : null;
14497
+ return {
14498
+ originalPath: scenario.targetPath,
14499
+ resolvedPath,
14500
+ params,
14501
+ values,
14502
+ sources,
14503
+ synthetic
14504
+ };
14505
+ }
14506
+ function resolveRoutePath(path, values) {
14507
+ return path.replace(/\/:([A-Za-z0-9_]+)\*\?/g, (_match, name) => {
14508
+ const value = values[name];
14509
+ return value ? `/${encodeRouteFixture(value, true)}` : "";
14510
+ }).replace(/:([A-Za-z0-9_]+)\*/g, (_match, name) => encodeRouteFixture(values[name] ?? defaultFixtureValue(name), true)).replace(/:([A-Za-z0-9_]+)/g, (_match, name) => encodeRouteFixture(values[name] ?? defaultFixtureValue(name), false));
14511
+ }
14512
+ function encodeRouteFixture(value, allowSlash) {
14513
+ if (allowSlash)
14514
+ return value.split("/").filter(Boolean).map(encodeURIComponent).join("/");
14515
+ return encodeURIComponent(value);
14516
+ }
14517
+ function materializeScenarioRoute(scenario, env = process.env) {
14518
+ const resolution = resolveRouteFixtures(scenario, env);
14519
+ if (!resolution.resolvedPath || resolution.resolvedPath === scenario.targetPath) {
14520
+ return { scenario, resolution };
14521
+ }
14522
+ const steps = scenario.steps.map((step) => {
14523
+ let next = step;
14524
+ for (const [name, value] of Object.entries(resolution.values)) {
14525
+ next = next.replaceAll(`:${name}`, value).replaceAll(`[${name}]`, value).replaceAll(`{${name}}`, value);
14526
+ }
14527
+ return next.replaceAll(scenario.targetPath ?? "", resolution.resolvedPath ?? "");
14528
+ });
14529
+ return {
14530
+ scenario: {
14531
+ ...scenario,
14532
+ targetPath: resolution.resolvedPath,
14533
+ steps,
14534
+ metadata: {
14535
+ ...scenario.metadata ?? {},
14536
+ routeFixtureResolution: resolution
14537
+ }
14538
+ },
14539
+ resolution
14540
+ };
14541
+ }
14542
+ function resolveStartUrl(baseUrl, targetPath) {
14543
+ try {
14544
+ return new URL(targetPath, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`).toString();
14545
+ } catch {
14546
+ return `${baseUrl.replace(/\/+$/, "")}/${targetPath.replace(/^\/+/, "")}`;
14547
+ }
14548
+ }
14549
+ var DEFAULT_UUID = "00000000-0000-4000-8000-000000000000", PARAM_ENV_CANDIDATES;
14550
+ var init_route_fixtures = __esm(() => {
14551
+ PARAM_ENV_CANDIDATES = {
14552
+ orgSlug: ["TESTERS_ORG_SLUG", "SMOKE_ORG_SLUG", "ORG_SLUG"],
14553
+ orgId: ["TESTERS_ORG_ID", "SMOKE_ORG_ID", "ORG_ID"],
14554
+ projectSlug: ["TESTERS_PROJECT_SLUG", "SMOKE_PROJECT_SLUG", "PROJECT_SLUG"],
14555
+ projectId: ["TESTERS_PROJECT_ID", "SMOKE_PROJECT_ID", "PROJECT_ID"],
14556
+ workspaceId: ["TESTERS_WORKSPACE_ID", "SMOKE_WORKSPACE_ID", "WORKSPACE_ID"],
14557
+ agentId: ["TESTERS_AGENT_ID", "SMOKE_AGENT_ID", "AGENT_ID"],
14558
+ sessionId: ["TESTERS_SESSION_ID", "SMOKE_SESSION_ID", "SESSION_ID"],
14559
+ userId: ["TESTERS_USER_ID", "SMOKE_USER_ID", "USER_ID"]
14560
+ };
14561
+ });
14562
+
14413
14563
  // src/lib/browser-lightpanda.ts
14414
14564
  var exports_browser_lightpanda = {};
14415
14565
  __export(exports_browser_lightpanda, {
@@ -16094,33 +16244,35 @@ ${filtered.join(`
16094
16244
  return { result: `Error executing ${toolName}: ${message}` };
16095
16245
  }
16096
16246
  }
16097
- function resolveStartUrl(baseUrl, targetPath) {
16098
- try {
16099
- return new URL(targetPath, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`).toString();
16100
- } catch {
16101
- return `${baseUrl.replace(/\/+$/, "")}/${targetPath.replace(/^\/+/, "")}`;
16102
- }
16103
- }
16104
16247
  function buildScenarioUserMessage(scenario, baseUrl) {
16248
+ const { scenario: materializedScenario, resolution } = materializeScenarioRoute(scenario);
16105
16249
  const userParts = [
16106
- `**Scenario:** ${scenario.name}`,
16107
- `**Description:** ${scenario.description}`
16250
+ `**Scenario:** ${materializedScenario.name}`,
16251
+ `**Description:** ${materializedScenario.description}`
16108
16252
  ];
16109
16253
  if (baseUrl) {
16110
16254
  const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
16111
16255
  userParts.push(`**Base URL:** ${normalizedBaseUrl}`);
16112
- if (scenario.targetPath) {
16113
- userParts.push(`**Start URL:** ${resolveStartUrl(normalizedBaseUrl, scenario.targetPath)}`);
16256
+ if (materializedScenario.targetPath) {
16257
+ userParts.push(`**Start URL:** ${resolveStartUrl(normalizedBaseUrl, materializedScenario.targetPath)}`);
16114
16258
  }
16115
16259
  userParts.push("**Navigation Boundary:** Treat the Base URL as the application under test. Resolve relative paths and in-app navigation against this origin. Do not navigate to another host unless a step explicitly includes an absolute external URL.");
16116
16260
  }
16117
- if (scenario.targetPath) {
16118
- userParts.push(`**Target Path:** ${scenario.targetPath}`);
16261
+ if (materializedScenario.targetPath) {
16262
+ userParts.push(`**Target Path:** ${materializedScenario.targetPath}`);
16263
+ }
16264
+ if (resolution.params.length > 0) {
16265
+ userParts.push("**Route Fixtures:**");
16266
+ for (const param of resolution.params) {
16267
+ const source = resolution.sources[param];
16268
+ const synthetic = source === "default" ? " synthetic" : "";
16269
+ userParts.push(`- :${param} = ${resolution.values[param]} (${source}${synthetic})`);
16270
+ }
16119
16271
  }
16120
- if (scenario.steps.length > 0) {
16272
+ if (materializedScenario.steps.length > 0) {
16121
16273
  userParts.push("**Steps:**");
16122
- for (let i = 0;i < scenario.steps.length; i++) {
16123
- userParts.push(`${i + 1}. ${scenario.steps[i]}`);
16274
+ for (let i = 0;i < materializedScenario.steps.length; i++) {
16275
+ userParts.push(`${i + 1}. ${materializedScenario.steps[i]}`);
16124
16276
  }
16125
16277
  }
16126
16278
  return userParts.join(`
@@ -16418,6 +16570,7 @@ function createClientForModel(model, apiKey) {
16418
16570
  var activeHARs, activeCoverage, BROWSER_TOOLS;
16419
16571
  var init_ai_client = __esm(() => {
16420
16572
  init_types2();
16573
+ init_route_fixtures();
16421
16574
  activeHARs = new Map;
16422
16575
  activeCoverage = new Map;
16423
16576
  BROWSER_TOOLS = [
@@ -46937,7 +47090,7 @@ import { join as join14 } from "path";
46937
47090
  // package.json
46938
47091
  var package_default = {
46939
47092
  name: "@hasna/testers",
46940
- version: "0.0.50",
47093
+ version: "0.0.51",
46941
47094
  description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
46942
47095
  type: "module",
46943
47096
  main: "dist/index.js",
@@ -48526,6 +48679,7 @@ class Screenshotter {
48526
48679
 
48527
48680
  // src/lib/runner.ts
48528
48681
  init_ai_client();
48682
+ init_route_fixtures();
48529
48683
  init_config2();
48530
48684
 
48531
48685
  // src/lib/secrets-resolver.ts
@@ -49572,9 +49726,10 @@ function withTimeout(promise, ms, label) {
49572
49726
  });
49573
49727
  }
49574
49728
  async function runSingleScenario(scenario, runId, options) {
49575
- const scenarioType = scenario.scenarioType ?? "browser";
49729
+ const { scenario: materializedScenario, resolution: routeFixtureResolution } = materializeScenarioRoute(scenario);
49730
+ const scenarioType = materializedScenario.scenarioType ?? "browser";
49576
49731
  if (scenarioType === "eval") {
49577
- return runEvalScenario(scenario, { runId, baseUrl: options.url });
49732
+ return runEvalScenario(materializedScenario, { runId, baseUrl: options.url });
49578
49733
  }
49579
49734
  const config = loadConfig();
49580
49735
  if (options.selfHeal !== undefined)
@@ -49615,7 +49770,7 @@ async function runSingleScenario(scenario, runId, options) {
49615
49770
  runId,
49616
49771
  scenarioId: scenario.id,
49617
49772
  model,
49618
- stepsTotal: scenario.steps.length || 10,
49773
+ stepsTotal: materializedScenario.steps.length || 10,
49619
49774
  personaId: persona?.id ?? null,
49620
49775
  personaName: persona?.name ?? null
49621
49776
  });
@@ -49651,12 +49806,12 @@ async function runSingleScenario(scenario, runId, options) {
49651
49806
  engine: effectiveOptions.engine
49652
49807
  });
49653
49808
  }
49654
- const targetUrl = scenario.targetPath ? `${options.url.replace(/\/$/, "")}${scenario.targetPath}` : options.url;
49655
- const scenarioTimeout = scenario.timeoutMs ?? options.timeout ?? config.browser.timeout ?? 60000;
49809
+ const targetUrl = materializedScenario.targetPath ? resolveStartUrl(options.url.replace(/\/$/, ""), materializedScenario.targetPath) : options.url;
49810
+ const scenarioTimeout = materializedScenario.timeoutMs ?? options.timeout ?? config.browser.timeout ?? 60000;
49656
49811
  registerSession({
49657
49812
  resultId: result.id,
49658
49813
  runId,
49659
- scenarioId: scenario.id,
49814
+ scenarioId: materializedScenario.id,
49660
49815
  engine: effectiveOptions.engine ?? "playwright",
49661
49816
  startUrl: targetUrl
49662
49817
  });
@@ -49710,7 +49865,7 @@ async function runSingleScenario(scenario, runId, options) {
49710
49865
  const agentResult = await withTimeout(runAgentLoop({
49711
49866
  client,
49712
49867
  page,
49713
- scenario,
49868
+ scenario: materializedScenario,
49714
49869
  screenshotter,
49715
49870
  model,
49716
49871
  runId,
@@ -49801,7 +49956,7 @@ async function runSingleScenario(scenario, runId, options) {
49801
49956
  const baseReasoning = agentResult.reasoning ? agentResult.reasoning + lightpandaNote : lightpandaNote || "";
49802
49957
  const assertionOutcome = await applyStructuredAssertionsToResult({
49803
49958
  page,
49804
- scenario,
49959
+ scenario: materializedScenario,
49805
49960
  consoleErrors,
49806
49961
  status: agentResult.status,
49807
49962
  reasoning: baseReasoning
@@ -49822,6 +49977,7 @@ async function runSingleScenario(scenario, runId, options) {
49822
49977
  costCents: estimateCost(model, agentResult.tokensUsed),
49823
49978
  metadata: {
49824
49979
  consoleLogs,
49980
+ ...routeFixtureResolution.params.length > 0 ? { routeFixtureResolution } : {},
49825
49981
  ...networkErrors.length > 0 ? networkMeta : {},
49826
49982
  ...structuredAssertionMeta
49827
49983
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/testers",
3
- "version": "0.0.50",
3
+ "version": "0.0.51",
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",