@qawolf/cli 1.0.0 → 1.0.1

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.
Files changed (2) hide show
  1. package/dist/cli.js +80 -40
  2. package/package.json +4 -4
package/dist/cli.js CHANGED
@@ -155308,7 +155308,6 @@ Hint: pass a pattern scoped to one package, e.g \`qawolf flows run '.qawolf/<env
155308
155308
  ${stderr}`
155309
155309
  },
155310
155310
  dotenv: {
155311
- invalidKey: (key) => `Cannot serialize env var with invalid key: ${JSON.stringify(key)}`,
155312
155311
  unparseableLine: (line) => `Cannot parse .env line: ${JSON.stringify(line)}`
155313
155312
  }
155314
155313
  };
@@ -155370,6 +155369,8 @@ var runnerMessages = {
155370
155369
  flowsFailed: (count) => `${count} flow(s) failed`,
155371
155370
  invalidRetries: (retries) => `retries must be a non-negative integer, got ${String(retries)}`,
155372
155371
  manifestStampReadFailed: (file, message) => `failed to read manifest stamp for ${file}: ${message}`,
155372
+ harCleanupFailed: (file, message) => `failed to delete HAR file ${file} (retain-on-failure); remove it manually: ${message}`,
155373
+ traceCleanupFailed: (file, message) => `failed to delete trace file ${file} (retain-on-failure); remove it manually: ${message}`,
155373
155374
  notSupportedInCli: (name) => `${name} is not supported in the CLI runner`,
155374
155375
  notAvailableLocally: (name) => `${name} is not available in local runs yet.`,
155375
155376
  noDefaultExport: (flowPath) => `No default export found in "${flowPath}"`,
@@ -155465,6 +155466,8 @@ function envPaths(name, { suffix = "nodejs" } = {}) {
155465
155466
  }
155466
155467
 
155467
155468
  // src/core/paths.ts
155469
+ var qawolfDir = ".qawolf";
155470
+ var defaultOutputDir = `${qawolfDir}/output`;
155468
155471
  var _paths;
155469
155472
  function getConfigDir() {
155470
155473
  _paths ??= envPaths("qawolf");
@@ -173643,7 +173646,7 @@ function registerAuthCommand(program2, signals) {
173643
173646
  // package.json
173644
173647
  var package_default = {
173645
173648
  name: "@qawolf/cli",
173646
- version: "1.0.0",
173649
+ version: "1.0.1",
173647
173650
  description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
173648
173651
  keywords: [
173649
173652
  "automation",
@@ -173665,7 +173668,7 @@ var package_default = {
173665
173668
  url: "git+https://github.com/qawolf/cli.git"
173666
173669
  },
173667
173670
  bin: {
173668
- qawolf: "./dist/cli.js"
173671
+ qawolf: "dist/cli.js"
173669
173672
  },
173670
173673
  files: [
173671
173674
  "LICENSE",
@@ -173679,7 +173682,7 @@ var package_default = {
173679
173682
  },
173680
173683
  scripts: {
173681
173684
  build: "bun run generate && bun scripts/build.ts",
173682
- "build:binary": "bun scripts/buildBinary.ts",
173685
+ "build:binary": "bun run generate && bun scripts/buildBinary.ts",
173683
173686
  dev: "bun run src/main.ts",
173684
173687
  format: "oxfmt .",
173685
173688
  "format:check": "oxfmt --check .",
@@ -173690,7 +173693,7 @@ var package_default = {
173690
173693
  lint: "oxlint",
173691
173694
  "lint:fix": "oxlint --fix",
173692
173695
  postbuild: "./scripts/postbuild.sh",
173693
- prepare: "git rev-parse --is-inside-work-tree >/dev/null 2>&1 && git config core.hooksPath .githooks || true",
173696
+ prepare: "git config core.hooksPath .githooks || true",
173694
173697
  test: "bun run generate && bun test",
173695
173698
  "test:watch": "bun test --watch",
173696
173699
  typecheck: "bun run generate && tsc --noEmit",
@@ -175929,19 +175932,14 @@ async function applyTeamStorageRewrite(rootDir, fs2 = makeDefaultFs()) {
175929
175932
  // src/domains/flows/pull/envVars.ts
175930
175933
  import { join as join19 } from "node:path";
175931
175934
 
175932
- // src/domains/flows/dotenv.ts
175933
- var envKeyPattern = /^[A-Za-z_][A-Za-z0-9_]*$/;
175934
- var lineRe = /^([A-Za-z_][A-Za-z0-9_]*)="((?:[^"\\]|\\.)*)"$/;
175935
+ // src/core/dotenv.ts
175936
+ var bareKeyPattern = /^[A-Za-z_][A-Za-z0-9_]*$/;
175937
+ var lineRe = /^(?:([A-Za-z_][A-Za-z0-9_]*)|"((?:[^"\\]|\\.)*)")="((?:[^"\\]|\\.)*)"$/;
175935
175938
  function serializeDotenv(vars) {
175936
175939
  const keys = Object.keys(vars).sort();
175937
- for (const key of keys) {
175938
- if (!envKeyPattern.test(key)) {
175939
- throw new Error(flowsMessages.dotenv.invalidKey(key));
175940
- }
175941
- }
175942
175940
  if (keys.length === 0)
175943
175941
  return "";
175944
- return `${keys.map((key) => `${key}=${quote(vars[key] ?? "")}`).join(`
175942
+ return `${keys.map((key) => `${bareKeyPattern.test(key) ? key : quote(key)}=${quote(vars[key] ?? "")}`).join(`
175945
175943
  `)}
175946
175944
  `;
175947
175945
  }
@@ -175961,7 +175959,7 @@ function parseDotenv(content) {
175961
175959
  if (!match) {
175962
175960
  throw new Error(flowsMessages.dotenv.unparseableLine(line));
175963
175961
  }
175964
- out[match[1]] = unquote(match[2]);
175962
+ out[match[1] ?? unquote(match[2])] = unquote(match[3]);
175965
175963
  }
175966
175964
  return out;
175967
175965
  }
@@ -180050,14 +180048,30 @@ function createLaunch({
180050
180048
  contextSetup,
180051
180049
  launchBrowserOpts,
180052
180050
  signals,
180053
- timeout
180051
+ timeout,
180052
+ traceMode = "off",
180053
+ tracePath
180054
180054
  }) {
180055
180055
  const openBrowsers = [];
180056
180056
  const openContexts = [];
180057
- const unregisters = [];
180058
- const trackContext = (ctx) => {
180057
+ const tracingEnabled = traceMode !== "off";
180058
+ let closed = false;
180059
+ const closeAll = async () => {
180060
+ if (closed)
180061
+ return;
180062
+ closed = true;
180063
+ if (tracingEnabled && tracePath !== undefined) {
180064
+ await Promise.allSettled(openContexts.map((c2) => c2.tracing.stop({ path: tracePath })));
180065
+ }
180066
+ await Promise.allSettled(openContexts.map((c2) => c2.close()));
180067
+ await Promise.allSettled(openBrowsers.map((b3) => b3.close()));
180068
+ };
180069
+ const unregister = signals.register(closeAll);
180070
+ const trackContext = async (ctx) => {
180059
180071
  openContexts.push(ctx);
180060
- unregisters.push(signals.register(() => ctx.close()));
180072
+ if (tracingEnabled) {
180073
+ await ctx.tracing.start({ screenshots: true, snapshots: true });
180074
+ }
180061
180075
  };
180062
180076
  const launch = async (launchOpts) => {
180063
180077
  const browserName = normalizeBrowserName(launchOpts?.browser);
@@ -180069,25 +180083,20 @@ function createLaunch({
180069
180083
  ...contextSetup
180070
180084
  });
180071
180085
  context2.setDefaultTimeout(timeout);
180072
- trackContext(context2);
180086
+ await trackContext(context2);
180073
180087
  const page = await context2.newPage();
180074
180088
  return { browserType: browserName, context: context2, page };
180075
180089
  }
180076
180090
  const browser = await bt2.launch(launchBrowserOpts);
180077
180091
  openBrowsers.push(browser);
180078
- unregisters.push(signals.register(() => browser.close()));
180079
180092
  const context = await browser.newContext(contextSetup);
180080
180093
  context.setDefaultTimeout(timeout);
180081
- trackContext(context);
180094
+ await trackContext(context);
180082
180095
  return { browser, browserType: browserName, context };
180083
180096
  };
180084
180097
  const cleanup = async () => {
180085
- for (const unreg of unregisters)
180086
- unreg();
180087
- await Promise.allSettled([
180088
- ...openContexts.map((c2) => c2.close()),
180089
- ...openBrowsers.map((b3) => b3.close())
180090
- ]);
180098
+ unregister();
180099
+ await closeAll();
180091
180100
  };
180092
180101
  return { launch, cleanup };
180093
180102
  }
@@ -180231,7 +180240,7 @@ async function findFlowsRunnerPath(flowPath, fs3) {
180231
180240
  }
180232
180241
  }
180233
180242
  var initCache = new Map;
180234
- async function doInit(flowPath, fs3) {
180243
+ async function doInit(flowPath, timeout, fs3) {
180235
180244
  const runnerPath = await findFlowsRunnerPath(flowPath, fs3);
180236
180245
  const mod = await import(pathToFileURL2(runnerPath).href);
180237
180246
  if (typeof mod.configureFlowRuntime !== "function")
@@ -180242,14 +180251,15 @@ async function doInit(flowPath, fs3) {
180242
180251
  schemaVersion: 1,
180243
180252
  runnerName: "node20WithPlaywright",
180244
180253
  meta: "legacy"
180245
- }
180254
+ },
180255
+ webExpectAttributes: { defaultExpectTimeoutMs: timeout }
180246
180256
  });
180247
180257
  }
180248
- function initFlowRuntime(flowPath, fs3 = makeDefaultFs()) {
180258
+ function initFlowRuntime(flowPath, options, fs3 = makeDefaultFs()) {
180249
180259
  const startDir = path6.dirname(flowPath);
180250
180260
  let p3 = initCache.get(startDir);
180251
180261
  if (!p3) {
180252
- p3 = doInit(flowPath, fs3);
180262
+ p3 = doInit(flowPath, options.timeout, fs3);
180253
180263
  initCache.set(startDir, p3);
180254
180264
  }
180255
180265
  return p3;
@@ -180260,6 +180270,9 @@ import path7 from "node:path";
180260
180270
  function harFlowPath(outputDir, flowName) {
180261
180271
  return path7.join(outputDir, "har", `${flowName}.har`);
180262
180272
  }
180273
+ function traceFlowPath(outputDir, flowName) {
180274
+ return path7.join(outputDir, "trace", `${flowName}.zip`);
180275
+ }
180263
180276
  function buildHarContextOpts(harPath, harContent) {
180264
180277
  return {
180265
180278
  recordHar: {
@@ -180277,6 +180290,23 @@ async function initHar(fs3, options, flowName) {
180277
180290
  await fs3.mkdir(path7.dirname(harPath), { recursive: true });
180278
180291
  return { harMode, harPath };
180279
180292
  }
180293
+ async function initTrace(fs3, options, flowName) {
180294
+ const traceMode = options.trace ?? "off";
180295
+ if (traceMode === "off")
180296
+ return { traceMode, tracePath: undefined };
180297
+ const tracePath = traceFlowPath(options.outputDir, flowName);
180298
+ await fs3.mkdir(path7.dirname(tracePath), { recursive: true });
180299
+ return { traceMode, tracePath };
180300
+ }
180301
+ async function maybeCleanupTrace(fs3, tracePath, passed, traceMode, warn) {
180302
+ if (traceMode !== "retain-on-failure" || !passed)
180303
+ return;
180304
+ try {
180305
+ await fs3.unlink(tracePath);
180306
+ } catch (err) {
180307
+ warn?.(runnerMessages.traceCleanupFailed(tracePath, errorMessage(err)));
180308
+ }
180309
+ }
180280
180310
  function buildContextSetup(videoSize, options, harPath) {
180281
180311
  const videosDir = options.artifactDir ?? path7.join(options.outputDir, "videos");
180282
180312
  const harOpts = harPath !== undefined ? buildHarContextOpts(harPath, options.harContent ?? "omit") : {};
@@ -180287,12 +180317,14 @@ function buildContextSetup(videoSize, options, harPath) {
180287
180317
  ...harOpts
180288
180318
  } : { viewport: videoSize, screen: videoSize, ...harOpts };
180289
180319
  }
180290
- async function maybeCleanupHar(fs3, harPath, passed, harMode) {
180320
+ async function maybeCleanupHar(fs3, harPath, passed, harMode, warn) {
180291
180321
  if (harMode !== "retain-on-failure" || !passed)
180292
180322
  return;
180293
180323
  try {
180294
180324
  await fs3.unlink(harPath);
180295
- } catch {}
180325
+ } catch (err) {
180326
+ warn?.(runnerMessages.harCleanupFailed(harPath, errorMessage(err)));
180327
+ }
180296
180328
  }
180297
180329
 
180298
180330
  // src/domains/runner/runWebFlow.ts
@@ -180301,12 +180333,13 @@ async function runWebFlow({
180301
180333
  options,
180302
180334
  flowPath
180303
180335
  }) {
180304
- await initFlowRuntime(flowPath);
180336
+ await initFlowRuntime(flowPath, { timeout: options.timeout });
180305
180337
  const exported = await loadFlowDefault(flowPath);
180306
180338
  const isLegacy = typeof exported === "function";
180307
180339
  const flowName = isLegacy ? path8.basename(flowPath, path8.extname(flowPath)) : exported.name;
180308
180340
  const runFn = isLegacy ? exported : exported.run;
180309
180341
  const { harMode, harPath } = await initHar(deps.fs, options, flowName);
180342
+ const { traceMode, tracePath } = await initTrace(deps.fs, options, flowName);
180310
180343
  const videoSize = { width: 1280, height: 720 };
180311
180344
  const contextSetup = buildContextSetup(videoSize, options, harPath);
180312
180345
  const launchBrowserOpts = {
@@ -180323,7 +180356,9 @@ async function runWebFlow({
180323
180356
  contextSetup,
180324
180357
  launchBrowserOpts,
180325
180358
  signals: deps.signals,
180326
- timeout: options.timeout
180359
+ timeout: options.timeout,
180360
+ traceMode,
180361
+ tracePath
180327
180362
  });
180328
180363
  const flowDef = {
180329
180364
  name: flowName,
@@ -180352,8 +180387,12 @@ async function runWebFlow({
180352
180387
  return result;
180353
180388
  } finally {
180354
180389
  await cleanup();
180390
+ const warn = (message) => deps.logger?.warn(message);
180355
180391
  if (harPath !== undefined) {
180356
- await maybeCleanupHar(deps.fs, harPath, passed, harMode);
180392
+ await maybeCleanupHar(deps.fs, harPath, passed, harMode, warn);
180393
+ }
180394
+ if (tracePath !== undefined) {
180395
+ await maybeCleanupTrace(deps.fs, tracePath, passed, traceMode, warn);
180357
180396
  }
180358
180397
  }
180359
180398
  }
@@ -180631,6 +180670,7 @@ function buildRunOptions(flags) {
180631
180670
  headed: flags.headed,
180632
180671
  slowMo: 0,
180633
180672
  video: flags.video,
180673
+ trace: flags.trace,
180634
180674
  timeout: flags.timeout,
180635
180675
  har: flags.har,
180636
180676
  harContent: flags.harContent
@@ -181401,7 +181441,7 @@ Examples:
181401
181441
  function registerFlowsRunCommand(flows, signals) {
181402
181442
  declareCommandKind(flows.command("run [pattern]"), "local", {
181403
181443
  kindNote: "read with --env"
181404
- }).description("Run flows matching [pattern], or every flow when omitted; with --env, pull missing flows from that QA Wolf environment").option("--retries <n>", "Retry each failing flow up to this many times", parseInteger("--retries", { min: 0 }), 0).option("--bail", "Stop the run after the first failure", false).option("--workers <n>", "Parallel worker count for web flows", parseInteger("--workers", { min: 1 }), 1).option("--timeout <ms>", "Per-flow timeout in milliseconds", parseInteger("--timeout", { min: 0 }), 30000).option("--video <mode>", "Record video: on | off | retain-on-failure", parseEnum("--video", videoModes), videoDefault).option("--trace <mode>", "Record Playwright trace: on | off | retain-on-failure", parseEnum("--trace", traceModes), traceDefault).option("--har <mode>", "Record HAR network log: on | off | retain-on-failure", parseEnum("--har", harModes), harDefault).option("--har-content <mode>", "HAR response bodies: omit | full (full uses more memory)", parseEnum("--har-content", harContentModes), harContentDefault).option("--output-dir <path>", "Directory for run artifacts (videos, traces, HAR)", "qawolf-output").option("--junit [path]", "Write a JUnit XML report (default: <output-dir>/junit-report.xml)").option("--headed", "Show the browser window instead of headless", false).option("--env <env>", "Pull and run a flow from this environment (UUID or slug) if not cached locally").addHelpText("after", runExamples).action((pattern, opts, command) => {
181444
+ }).description("Run flows matching [pattern], or every flow when omitted; with --env, pull missing flows from that QA Wolf environment").option("--retries <n>", "Retry each failing flow up to this many times", parseInteger("--retries", { min: 0 }), 0).option("--bail", "Stop the run after the first failure", false).option("--workers <n>", "Parallel worker count for web flows", parseInteger("--workers", { min: 1 }), 1).option("--timeout <ms>", "Default timeout for actions and assertions, in milliseconds", parseInteger("--timeout", { min: 0 }), 30000).option("--video <mode>", "Record video: on | off | retain-on-failure", parseEnum("--video", videoModes), videoDefault).option("--trace <mode>", "Record Playwright trace: on | off | retain-on-failure", parseEnum("--trace", traceModes), traceDefault).option("--har <mode>", "Record HAR network log: on | off | retain-on-failure", parseEnum("--har", harModes), harDefault).option("--har-content <mode>", "HAR response bodies: omit | full (full uses more memory)", parseEnum("--har-content", harContentModes), harContentDefault).option("--output-dir <path>", "Directory for run artifacts (videos, traces, HAR)", defaultOutputDir).option("--junit [path]", "Write a JUnit XML report (default: <output-dir>/junit-report.xml)").option("--headed", "Show the browser window instead of headless", false).option("--env <env>", "Pull and run a flow from this environment (UUID or slug) if not cached locally").addHelpText("after", runExamples).action((pattern, opts, command) => {
181405
181445
  if (opts.env !== undefined) {
181406
181446
  const hybridFlags = { ...opts, env: opts.env };
181407
181447
  return withAuthContext(signals, (ctx) => handleHybridFlowsRun(ctx, pattern, hybridFlags))(opts, command);
@@ -181499,7 +181539,7 @@ import { dirname as dirname9, join as join26, relative as relative7 } from "node
181499
181539
 
181500
181540
  // src/domains/init/templates.ts
181501
181541
  var qawolfConfigTs = `export default {
181502
- outputDir: ".qawolf",
181542
+ outputDir: "${defaultOutputDir}",
181503
181543
  timeout: 60_000,
181504
181544
  retries: 0,
181505
181545
  video: "retain-on-failure",
@@ -182178,4 +182218,4 @@ process.on("SIGINT", onSignal("SIGINT"));
182178
182218
  process.on("SIGTERM", onSignal("SIGTERM"));
182179
182219
  createProgram({ signals }).parse();
182180
182220
 
182181
- //# debugId=53F1EE42E10CEF1764756E2164756E21
182221
+ //# debugId=61BE7631A00FDFEA64756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qawolf/cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
5
5
  "keywords": [
6
6
  "automation",
@@ -22,7 +22,7 @@
22
22
  "url": "git+https://github.com/qawolf/cli.git"
23
23
  },
24
24
  "bin": {
25
- "qawolf": "./dist/cli.js"
25
+ "qawolf": "dist/cli.js"
26
26
  },
27
27
  "files": [
28
28
  "LICENSE",
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "scripts": {
38
38
  "build": "bun run generate && bun scripts/build.ts",
39
- "build:binary": "bun scripts/buildBinary.ts",
39
+ "build:binary": "bun run generate && bun scripts/buildBinary.ts",
40
40
  "dev": "bun run src/main.ts",
41
41
  "format": "oxfmt .",
42
42
  "format:check": "oxfmt --check .",
@@ -47,7 +47,7 @@
47
47
  "lint": "oxlint",
48
48
  "lint:fix": "oxlint --fix",
49
49
  "postbuild": "./scripts/postbuild.sh",
50
- "prepare": "git rev-parse --is-inside-work-tree >/dev/null 2>&1 && git config core.hooksPath .githooks || true",
50
+ "prepare": "git config core.hooksPath .githooks || true",
51
51
  "test": "bun run generate && bun test",
52
52
  "test:watch": "bun test --watch",
53
53
  "typecheck": "bun run generate && tsc --noEmit",