@qawolf/cli 1.3.0 → 1.3.2

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.js CHANGED
@@ -8514,7 +8514,7 @@ var require_BufferList = __commonJS((exports, module) => {
8514
8514
  this.head = this.tail = null;
8515
8515
  this.length = 0;
8516
8516
  };
8517
- BufferList.prototype.join = function join32(s3) {
8517
+ BufferList.prototype.join = function join33(s3) {
8518
8518
  if (this.length === 0)
8519
8519
  return "";
8520
8520
  var p3 = this.head;
@@ -23070,7 +23070,7 @@ var require_BufferList2 = __commonJS((exports, module) => {
23070
23070
  this.head = this.tail = null;
23071
23071
  this.length = 0;
23072
23072
  };
23073
- BufferList.prototype.join = function join32(s3) {
23073
+ BufferList.prototype.join = function join33(s3) {
23074
23074
  if (this.length === 0)
23075
23075
  return "";
23076
23076
  var p3 = this.head;
@@ -155181,7 +155181,7 @@ var authMessages = {
155181
155181
  rejected403: (noun) => `QA Wolf API rejected the${noun ? ` ${noun}` : ""} request (HTTP 403). Check that your API key has access to this environment.`,
155182
155182
  notFound404: (noun) => `QA Wolf API could not find ${noun ? `${noun} for that environment` : "that environment"} (HTTP 404). Check the --env value.`,
155183
155183
  failedWithStatus: (status, noun) => `QA Wolf API${noun ? ` ${noun}` : ""} request failed (HTTP ${status}).`,
155184
- networkUnreachable: (baseUrl, noun) => `Could not reach the QA Wolf API at ${baseUrl}${noun ? ` to fetch ${noun}` : ""}. Check your network connection and QAWOLF_API_URL.`,
155184
+ networkUnreachable: (baseUrl, noun) => `Could not reach the QA Wolf API at ${baseUrl}${noun ? ` to fetch ${noun}` : ""}. Check your network connection and QAWOLF_HOST_URL.`,
155185
155185
  unexpectedResponse: (noun) => `Unexpected${noun ? ` ${noun}` : ""} response from the QA Wolf API.`
155186
155186
  },
155187
155187
  bundle: {
@@ -155626,6 +155626,15 @@ function makeDefaultFs() {
155626
155626
  };
155627
155627
  }
155628
155628
 
155629
+ // src/shell/resolveHostUrl.ts
155630
+ var defaultHostUrl = "https://app.qawolf.com";
155631
+ function resolveHostUrl(env2) {
155632
+ const configuredHostUrl = env2["QAWOLF_HOST_URL"]?.trim();
155633
+ if (!configuredHostUrl)
155634
+ return defaultHostUrl;
155635
+ return configuredHostUrl.replace(/\/+$/, "");
155636
+ }
155637
+
155629
155638
  // src/domains/auth/resolve.ts
155630
155639
  import { Entry as Entry3 } from "@napi-rs/keyring";
155631
155640
 
@@ -173377,7 +173386,7 @@ function buildBaseContext(command, signals) {
173377
173386
  logPath: defaultLogPath(),
173378
173387
  ...verboseWrite ? { verboseWrite } : {}
173379
173388
  });
173380
- const apiBaseUrl = env2["QAWOLF_API_URL"]?.replace(/\/+$/, "") || "https://app.qawolf.com";
173389
+ const apiBaseUrl = resolveHostUrl(env2);
173381
173390
  return {
173382
173391
  ctx: {
173383
173392
  ui: createUI(outputMode, {
@@ -173589,7 +173598,7 @@ function registerAuthCommand(program2, signals) {
173589
173598
  // package.json
173590
173599
  var package_default = {
173591
173600
  name: "@qawolf/cli",
173592
- version: "1.3.0",
173601
+ version: "1.3.2",
173593
173602
  description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
173594
173603
  keywords: [
173595
173604
  "automation",
@@ -173652,13 +173661,14 @@ var package_default = {
173652
173661
  "@qawolf/api-contracts": "0.2.0",
173653
173662
  "@qawolf/emails": "1.1.1",
173654
173663
  "@qawolf/flow-targets": "1.0.0",
173655
- "@qawolf/flows": "0.1.1",
173664
+ "@qawolf/flows": "0.1.4",
173656
173665
  "@qawolf/testkit": "1.1.1",
173657
173666
  appium: "2.11.3",
173658
173667
  "appium-uiautomator2-driver": "3.7.0",
173659
173668
  "appium-xcuitest-driver": "9.10.5",
173660
173669
  commander: "14.0.3",
173661
173670
  "env-paths": "4.0.0",
173671
+ "expect-webdriverio": "5.6.5",
173662
173672
  picomatch: "4.0.4",
173663
173673
  pino: "10.3.1",
173664
173674
  playwright: "1.58.2",
@@ -175132,13 +175142,14 @@ import { createHash } from "node:crypto";
175132
175142
  import { join as join12, resolve as resolve3 } from "node:path";
175133
175143
 
175134
175144
  // src/generated/dependencyVersions.ts
175135
- var flowsVersion = "0.1.1";
175145
+ var flowsVersion = "0.1.4";
175136
175146
  var playwrightVersion = "1.58.2";
175137
175147
  var emailsVersion = "1.1.1";
175138
175148
  var testkitVersion = "1.1.1";
175139
175149
  var appiumVersion = "2.11.3";
175140
175150
  var appiumXcuitestDriverVersion = "9.10.5";
175141
175151
  var appiumUiautomator2DriverVersion = "3.7.0";
175152
+ var expectWebdriverioVersion = "5.6.5";
175142
175153
 
175143
175154
  // src/domains/runtimeEnv/pinnedPackages.ts
175144
175155
  var pinnedPackages = [
@@ -175151,7 +175162,8 @@ var pinnedPackages = [
175151
175162
  {
175152
175163
  name: "appium-uiautomator2-driver",
175153
175164
  version: appiumUiautomator2DriverVersion
175154
- }
175165
+ },
175166
+ { name: "expect-webdriverio", version: expectWebdriverioVersion }
175155
175167
  ];
175156
175168
 
175157
175169
  // src/domains/runtimeEnv/managedEnvDir.ts
@@ -180059,7 +180071,7 @@ async function defaultRunWebFlowDeps(cwd = process.cwd(), signals) {
180059
180071
 
180060
180072
  // src/domains/runtimeEnv/prepareRunDir.ts
180061
180073
  import { createHash as createHash3 } from "node:crypto";
180062
- import { basename as basename4, dirname as dirname10, join as join28, resolve as resolve8, sep as sep4 } from "node:path";
180074
+ import { basename as basename4, dirname as dirname10, join as join29, resolve as resolve8, sep as sep4 } from "node:path";
180063
180075
 
180064
180076
  // src/shell/copyDir.ts
180065
180077
  import { cp, readdir as readdir2 } from "node:fs/promises";
@@ -180100,6 +180112,9 @@ async function readPackageJson(pkgPath, fs3) {
180100
180112
  }
180101
180113
 
180102
180114
  // src/domains/runtimeEnv/innerHop.ts
180115
+ import { join as join27 } from "node:path";
180116
+
180117
+ // src/domains/runtimeEnv/linkPinnedPackages.ts
180103
180118
  import { dirname as dirname8, join as join26 } from "node:path";
180104
180119
 
180105
180120
  // src/domains/runtimeEnv/symlinkDir.ts
@@ -180109,25 +180124,35 @@ async function createDirSymlink(source, target) {
180109
180124
  await symlink(source, target, linkType);
180110
180125
  }
180111
180126
 
180112
- // src/domains/runtimeEnv/innerHop.ts
180113
- async function populateInnerHop(args) {
180114
- const { depsRoot, execDir, fs: fs3 } = args;
180127
+ // src/domains/runtimeEnv/linkPinnedPackages.ts
180128
+ async function linkPinnedPackages(args) {
180129
+ const { depsRoot, nodeModulesDir, fs: fs3 } = args;
180115
180130
  const managedModules = join26(depsRoot, "node_modules");
180116
- const innerModules = join26(execDir, "node_modules");
180117
- await fs3.mkdir(innerModules, { recursive: true });
180131
+ await fs3.mkdir(nodeModulesDir, { recursive: true });
180118
180132
  for (const { name } of pinnedPackages) {
180119
180133
  const segments = name.split("/");
180120
- const target = join26(innerModules, ...segments);
180134
+ const target = join26(nodeModulesDir, ...segments);
180121
180135
  if (segments.length > 1) {
180122
180136
  await fs3.mkdir(dirname8(target), { recursive: true });
180123
180137
  }
180138
+ await fs3.rm(target, { recursive: true, force: true });
180124
180139
  await createDirSymlink(join26(managedModules, ...segments), target);
180125
180140
  }
180126
180141
  }
180127
180142
 
180143
+ // src/domains/runtimeEnv/innerHop.ts
180144
+ async function populateInnerHop(args) {
180145
+ const { depsRoot, execDir, fs: fs3 } = args;
180146
+ await linkPinnedPackages({
180147
+ depsRoot,
180148
+ nodeModulesDir: join27(execDir, "node_modules"),
180149
+ fs: fs3
180150
+ });
180151
+ }
180152
+
180128
180153
  // src/domains/runtimeEnv/outerHop.ts
180129
180154
  import { lstat } from "node:fs/promises";
180130
- import { dirname as dirname9, join as join27 } from "node:path";
180155
+ import { dirname as dirname9, join as join28 } from "node:path";
180131
180156
  var executorPackageNames = new Set(pinnedPackages.map((p3) => p3.name));
180132
180157
  async function populateOuterHop(args) {
180133
180158
  const { projectDir, runDir, fs: fs3 } = args;
@@ -180137,23 +180162,28 @@ async function populateOuterHop(args) {
180137
180162
  const depNames = Object.keys(installableDeps);
180138
180163
  const { satisfying, rejected } = await findSatisfyingNodeModulesDir(projectDir, depNames, fs3);
180139
180164
  if (satisfying !== undefined) {
180140
- await createDirSymlink(satisfying, join27(runDir, "node_modules"));
180165
+ await createDirSymlink(satisfying, join28(runDir, "node_modules"));
180141
180166
  return { mode: "symlink", nodeModulesDir: satisfying };
180142
180167
  }
180143
180168
  if (depNames.length === 0)
180144
180169
  return { mode: "none" };
180145
180170
  args.onInstallStart?.(depNames.length);
180146
180171
  await installOuterDeps(runDir, installableDeps, fs3, args.install ?? spawnNpmInstall);
180172
+ await linkPinnedPackages({
180173
+ depsRoot: args.depsRoot,
180174
+ nodeModulesDir: join28(runDir, "node_modules"),
180175
+ fs: fs3
180176
+ });
180147
180177
  return { mode: "install", depCount: depNames.length, rejected };
180148
180178
  }
180149
180179
  async function findSatisfyingNodeModulesDir(startDir, depNames, fs3) {
180150
180180
  const rejected = [];
180151
180181
  let dir = startDir;
180152
180182
  while (true) {
180153
- const candidate = join27(dir, "node_modules");
180183
+ const candidate = join28(dir, "node_modules");
180154
180184
  const stats = await lstatSafe(candidate);
180155
180185
  if (stats !== undefined && stats.isDirectory() && !stats.isSymbolicLink()) {
180156
- const missing = depNames.filter((name) => !fs3.existsSync(join27(candidate, name, "package.json")));
180186
+ const missing = depNames.filter((name) => !fs3.existsSync(join28(candidate, name, "package.json")));
180157
180187
  if (missing.length === 0)
180158
180188
  return { satisfying: candidate, rejected };
180159
180189
  rejected.push({ dir: candidate, missing });
@@ -180174,7 +180204,7 @@ async function lstatSafe(path3) {
180174
180204
  async function readInstallableDeps(projectDir, fs3) {
180175
180205
  let content;
180176
180206
  try {
180177
- content = await fs3.readFile(join27(projectDir, "package.json"));
180207
+ content = await fs3.readFile(join28(projectDir, "package.json"));
180178
180208
  } catch {
180179
180209
  return {};
180180
180210
  }
@@ -180192,8 +180222,8 @@ async function readInstallableDeps(projectDir, fs3) {
180192
180222
  return Object.fromEntries(Object.entries(rawDeps).filter(([name]) => !executorPackageNames.has(name)).filter((entry) => typeof entry[1] === "string"));
180193
180223
  }
180194
180224
  async function installOuterDeps(runDir, deps, fs3, install) {
180195
- await fs3.writeFile(join27(runDir, "package.json"), JSON.stringify({ name: "qawolf-run", private: true, dependencies: deps }, undefined, 2));
180196
- await fs3.writeFile(join27(runDir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
180225
+ await fs3.writeFile(join28(runDir, "package.json"), JSON.stringify({ name: "qawolf-run", private: true, dependencies: deps }, undefined, 2));
180226
+ await fs3.writeFile(join28(runDir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
180197
180227
  `);
180198
180228
  const result = await install(runDir);
180199
180229
  if (result.exitCode !== 0) {
@@ -180207,10 +180237,10 @@ async function prepareRunDir(args) {
180207
180237
  const { files, projectDir, depsRoot, runRoot } = args;
180208
180238
  const fs3 = args.fs ?? makeDefaultFs();
180209
180239
  const runId = buildRunId(projectDir, files);
180210
- const runDir = join28(runRoot, runId);
180240
+ const runDir = join29(runRoot, runId);
180211
180241
  await fs3.rm(runDir, { recursive: true, force: true });
180212
180242
  await fs3.mkdir(runDir, { recursive: true });
180213
- const execDir = join28(runDir, "exec");
180243
+ const execDir = join29(runDir, "exec");
180214
180244
  await fs3.mkdir(execDir, { recursive: true });
180215
180245
  await populateInnerHop({ depsRoot, execDir, fs: fs3 });
180216
180246
  const stagedFiles = await stageFlowFiles({ files, projectDir, execDir, fs: fs3 });
@@ -180220,6 +180250,7 @@ async function prepareRunDir(args) {
180220
180250
  const outerHop = await populateOuterHop({
180221
180251
  projectDir,
180222
180252
  runDir,
180253
+ depsRoot,
180223
180254
  fs: fs3,
180224
180255
  ...args.onInstallStart !== undefined ? { onInstallStart: args.onInstallStart } : {}
180225
180256
  });
@@ -180247,37 +180278,37 @@ async function stageFlowFiles(args) {
180247
180278
  if (files.length > 1) {
180248
180279
  return Promise.all(files.map(async (f3) => {
180249
180280
  const dirHash = createHash3("sha256").update(dirname10(f3)).digest("hex").slice(0, 8);
180250
- const subDir = join28(execDir, dirHash);
180281
+ const subDir = join29(execDir, dirHash);
180251
180282
  await fs3.mkdir(subDir, { recursive: true });
180252
- const dest = join28(subDir, basename4(f3));
180283
+ const dest = join29(subDir, basename4(f3));
180253
180284
  await fs3.copyFile(f3, dest);
180254
180285
  return dest;
180255
180286
  }));
180256
180287
  }
180257
- await Promise.all(files.map((f3) => fs3.copyFile(f3, join28(execDir, basename4(f3)))));
180258
- return files.map((f3) => join28(execDir, basename4(f3)));
180288
+ await Promise.all(files.map((f3) => fs3.copyFile(f3, join29(execDir, basename4(f3)))));
180289
+ return files.map((f3) => join29(execDir, basename4(f3)));
180259
180290
  }
180260
180291
  function remapPath(file2, projectDir, execDir) {
180261
180292
  if (file2 === projectDir)
180262
180293
  return execDir;
180263
180294
  if (file2.startsWith(projectDir + sep4)) {
180264
- return join28(execDir, file2.slice(projectDir.length + 1));
180295
+ return join29(execDir, file2.slice(projectDir.length + 1));
180265
180296
  }
180266
180297
  return file2;
180267
180298
  }
180268
180299
 
180269
180300
  // src/shell/testkit.ts
180270
- import { join as join29 } from "node:path";
180301
+ import { join as join30 } from "node:path";
180271
180302
  function notAvailableLocally(name) {
180272
180303
  throw new Error(runnerMessages.notAvailableLocally(name));
180273
180304
  }
180274
180305
  async function loadSdkDeps(cwd) {
180275
- const testkitDir = join29(cwd, "node_modules", "@qawolf", "testkit");
180306
+ const testkitDir = join30(cwd, "node_modules", "@qawolf", "testkit");
180276
180307
  try {
180277
180308
  const clientMod = await import(resolveFromEnvDir(cwd, "@qawolf/testkit/client"));
180278
180309
  if (typeof clientMod.createTestkitClient !== "function")
180279
180310
  throw new Error("createTestkitClient is not a function");
180280
- const scopeMod = await import(join29(testkitDir, "dist", "clientScope.js"));
180311
+ const scopeMod = await import(join30(testkitDir, "dist", "clientScope.js"));
180281
180312
  if (typeof scopeMod.configureTestkitClient !== "function")
180282
180313
  throw new Error("configureTestkitClient is not a function");
180283
180314
  return {
@@ -180334,9 +180365,9 @@ async function registerLazyEmailsClient(getInbox, cwd, deps) {
180334
180365
  }
180335
180366
 
180336
180367
  // src/commands/flows/envVarDeps.ts
180337
- import { join as join30 } from "node:path";
180368
+ import { join as join31 } from "node:path";
180338
180369
  function envFilePath(envDir) {
180339
- return join30(envDir, ".env");
180370
+ return join31(envDir, ".env");
180340
180371
  }
180341
180372
  async function readEnv(envDir, fs3) {
180342
180373
  try {
@@ -180445,7 +180476,7 @@ async function createFlowRuntimeDeps({
180445
180476
  }
180446
180477
 
180447
180478
  // src/shell/appium/createAppiumServer.ts
180448
- import { join as join31 } from "node:path";
180479
+ import { join as join32 } from "node:path";
180449
180480
 
180450
180481
  // src/shell/appium/spawnAppium.ts
180451
180482
  import { spawn as spawn2 } from "node:child_process";
@@ -180520,7 +180551,7 @@ async function createAppiumServer(envDir, signals, params) {
180520
180551
  const spawnFn = params?.deps?.spawn ?? defaultSpawnAppium;
180521
180552
  const findFreePortFn = params?.deps?.findFreePort ?? findFreePort;
180522
180553
  const resolveAppiumBinFn = params?.deps?.resolveAppiumBin ?? resolveAppiumBin;
180523
- const appiumHome2 = params?.options?.appiumHome ?? join31(envPaths("qawolf").data, "appium");
180554
+ const appiumHome2 = params?.options?.appiumHome ?? join32(envPaths("qawolf").data, "appium");
180524
180555
  const timeoutMs3 = params?.options?.startTimeoutMs ?? defaultStartTimeoutMs;
180525
180556
  const bin = resolveAppiumBinFn(envDir);
180526
180557
  const port = await findFreePortFn();
@@ -180939,7 +180970,7 @@ function formatError2(browser, result) {
180939
180970
  }
180940
180971
 
180941
180972
  // src/domains/runner/runAndroidFlow.ts
180942
- import path13 from "node:path";
180973
+ import path12 from "node:path";
180943
180974
 
180944
180975
  // src/shell/appium/createAndroidLaunchContext.ts
180945
180976
  import { writeFile } from "node:fs/promises";
@@ -181285,66 +181316,6 @@ function createRunner({
181285
181316
  }
181286
181317
 
181287
181318
  // src/domains/runner/runWebFlowUtils.ts
181288
- import crypto from "node:crypto";
181289
- import os6 from "node:os";
181290
- import path12 from "node:path";
181291
- function createLaunch({
181292
- browsers,
181293
- contextSetup,
181294
- launchBrowserOpts,
181295
- signals,
181296
- timeout,
181297
- traceMode = "off",
181298
- tracePath
181299
- }) {
181300
- const openBrowsers = [];
181301
- const openContexts = [];
181302
- const tracingEnabled = traceMode !== "off";
181303
- let closed = false;
181304
- const closeAll = async () => {
181305
- if (closed)
181306
- return;
181307
- closed = true;
181308
- if (tracingEnabled && tracePath !== undefined) {
181309
- await Promise.allSettled(openContexts.map((c2) => c2.tracing.stop({ path: tracePath })));
181310
- }
181311
- await Promise.allSettled(openContexts.map((c2) => c2.close()));
181312
- await Promise.allSettled(openBrowsers.map((b4) => b4.close()));
181313
- };
181314
- const unregister = signals.register(closeAll);
181315
- const trackContext = async (ctx) => {
181316
- openContexts.push(ctx);
181317
- if (tracingEnabled) {
181318
- await ctx.tracing.start({ screenshots: true, snapshots: true });
181319
- }
181320
- };
181321
- const launch = async (launchOpts) => {
181322
- const browserName = normalizeBrowserName(launchOpts?.browser);
181323
- const bt2 = browsers[browserName];
181324
- if (launchOpts?.persistentContext === true) {
181325
- const userDataDir = launchOpts.userDataDir ?? path12.join(os6.tmpdir(), `qawolf-${crypto.randomUUID()}`);
181326
- const context2 = await bt2.launchPersistentContext(userDataDir, {
181327
- ...launchBrowserOpts,
181328
- ...contextSetup
181329
- });
181330
- context2.setDefaultTimeout(timeout);
181331
- await trackContext(context2);
181332
- const page = await context2.newPage();
181333
- return { browserType: browserName, context: context2, page };
181334
- }
181335
- const browser = await bt2.launch(launchBrowserOpts);
181336
- openBrowsers.push(browser);
181337
- const context = await browser.newContext(contextSetup);
181338
- context.setDefaultTimeout(timeout);
181339
- await trackContext(context);
181340
- return { browser, browserType: browserName, context };
181341
- };
181342
- const cleanup = async () => {
181343
- unregister();
181344
- await closeAll();
181345
- };
181346
- return { launch, cleanup };
181347
- }
181348
181319
  function normalizeBrowserName(browser) {
181349
181320
  if (browser === "chrome" || browser === "msedge")
181350
181321
  return "chromium";
@@ -181384,7 +181355,7 @@ async function runAndroidFlow({
181384
181355
  throw new Error("runAndroidFlow: legacy flow functions are not supported; use flow() from @qawolf/flows/android");
181385
181356
  }
181386
181357
  } catch (err) {
181387
- const flowName2 = path13.basename(flowPath, path13.extname(flowPath));
181358
+ const flowName2 = path12.basename(flowPath, path12.extname(flowPath));
181388
181359
  return {
181389
181360
  passed: false,
181390
181361
  testCounts: { passed: 0, total: 0 },
@@ -181452,13 +181423,13 @@ async function runAndroidFlow({
181452
181423
  }
181453
181424
 
181454
181425
  // src/domains/runner/runWebFlow.ts
181455
- import path16 from "node:path";
181426
+ import path17 from "node:path";
181456
181427
 
181457
181428
  // src/domains/runner/initFlowRuntime.ts
181458
- import path14 from "node:path";
181429
+ import path13 from "node:path";
181459
181430
  import { pathToFileURL as pathToFileURL2 } from "node:url";
181460
181431
  async function runnerPathInDir(dir, fs9) {
181461
- const pkgPath = path14.join(dir, "node_modules", "@qawolf", "flows", "package.json");
181432
+ const pkgPath = path13.join(dir, "node_modules", "@qawolf", "flows", "package.json");
181462
181433
  try {
181463
181434
  const pkg = JSON.parse(await fs9.readFile(pkgPath));
181464
181435
  const entry = pkg.exports?.["./_runner"];
@@ -181466,7 +181437,7 @@ async function runnerPathInDir(dir, fs9) {
181466
181437
  if (typeof importPath !== "string") {
181467
181438
  throw new Error(`@qawolf/flows at ${pkgPath} does not export "./_runner" with an "import" condition`);
181468
181439
  }
181469
- return path14.resolve(path14.dirname(pkgPath), importPath);
181440
+ return path13.resolve(path13.dirname(pkgPath), importPath);
181470
181441
  } catch (err) {
181471
181442
  if (!isNoEntError(err))
181472
181443
  throw err;
@@ -181474,12 +181445,12 @@ async function runnerPathInDir(dir, fs9) {
181474
181445
  }
181475
181446
  }
181476
181447
  async function findFlowsRunnerPath(flowPath, fs9) {
181477
- let dir = path14.dirname(flowPath);
181448
+ let dir = path13.dirname(flowPath);
181478
181449
  while (true) {
181479
181450
  const result = await runnerPathInDir(dir, fs9);
181480
181451
  if (result !== undefined)
181481
181452
  return result;
181482
- const parent2 = path14.dirname(dir);
181453
+ const parent2 = path13.dirname(dir);
181483
181454
  if (parent2 === dir)
181484
181455
  throw new Error(`@qawolf/flows not found in node_modules above: ${flowPath}`);
181485
181456
  dir = parent2;
@@ -181511,7 +181482,7 @@ async function doInit(flowPath, timeout, fs9, depsRoot) {
181511
181482
  });
181512
181483
  }
181513
181484
  function initFlowRuntime(flowPath, options, fs9 = makeDefaultFs()) {
181514
- const startDir = path14.dirname(flowPath);
181485
+ const startDir = path13.dirname(flowPath);
181515
181486
  let p3 = initCache.get(startDir);
181516
181487
  if (!p3) {
181517
181488
  p3 = doInit(flowPath, options.timeout, fs9, options.depsRoot);
@@ -181520,13 +181491,137 @@ function initFlowRuntime(flowPath, options, fs9 = makeDefaultFs()) {
181520
181491
  return p3;
181521
181492
  }
181522
181493
 
181523
- // src/domains/runner/web/contextSetup.ts
181494
+ // src/domains/runner/createLaunch.ts
181495
+ import crypto from "node:crypto";
181496
+ import os6 from "node:os";
181524
181497
  import path15 from "node:path";
181498
+
181499
+ // src/domains/runner/web/artifactPaths.ts
181500
+ import path14 from "node:path";
181501
+ function indexedArtifactPath(basePath, index2) {
181502
+ if (index2 === 0)
181503
+ return basePath;
181504
+ const ext = path14.extname(basePath);
181505
+ const base = path14.basename(basePath, ext);
181506
+ return path14.join(path14.dirname(basePath), `${base}-${index2 + 1}${ext}`);
181507
+ }
181508
+ function createContextArtifacts(contextSetup, tracePath) {
181509
+ let contextIndex = 0;
181510
+ const harPaths = [];
181511
+ const tracePaths = [];
181512
+ const nextSetup = (overrides = {}) => {
181513
+ const index2 = contextIndex;
181514
+ contextIndex += 1;
181515
+ const setup = { ...contextSetup, ...overrides };
181516
+ if (contextSetup.recordHar !== undefined && overrides.recordHar === undefined) {
181517
+ setup.recordHar = {
181518
+ ...contextSetup.recordHar,
181519
+ path: indexedArtifactPath(contextSetup.recordHar.path, index2)
181520
+ };
181521
+ }
181522
+ if (setup.recordHar !== undefined)
181523
+ harPaths.push(setup.recordHar.path);
181524
+ return { setup, index: index2 };
181525
+ };
181526
+ const nextTracePath = (index2) => {
181527
+ if (tracePath === undefined)
181528
+ return;
181529
+ const contextTracePath = indexedArtifactPath(tracePath, index2);
181530
+ tracePaths.push(contextTracePath);
181531
+ return contextTracePath;
181532
+ };
181533
+ const artifactPaths = () => ({
181534
+ harPaths: [...harPaths],
181535
+ tracePaths: [...tracePaths]
181536
+ });
181537
+ return { nextSetup, nextTracePath, artifactPaths };
181538
+ }
181539
+
181540
+ // src/domains/runner/createLaunch.ts
181541
+ function createLaunch({
181542
+ browsers,
181543
+ contextSetup,
181544
+ launchBrowserOpts,
181545
+ signals,
181546
+ timeout,
181547
+ traceMode = "off",
181548
+ tracePath
181549
+ }) {
181550
+ const openBrowsers = [];
181551
+ const openContexts = [];
181552
+ const traceByContext = new Map;
181553
+ const tracingEnabled = traceMode !== "off";
181554
+ const { nextSetup, nextTracePath, artifactPaths } = createContextArtifacts(contextSetup, tracePath);
181555
+ let closePromise;
181556
+ const closeAll = () => {
181557
+ closePromise ??= (async () => {
181558
+ if (tracingEnabled) {
181559
+ await Promise.allSettled(openContexts.map((c2) => {
181560
+ const contextTracePath = traceByContext.get(c2);
181561
+ return contextTracePath === undefined ? Promise.resolve() : c2.tracing.stop({ path: contextTracePath });
181562
+ }));
181563
+ }
181564
+ await Promise.allSettled(openContexts.map((c2) => c2.close()));
181565
+ await Promise.allSettled(openBrowsers.map((b4) => b4.close()));
181566
+ })();
181567
+ return closePromise;
181568
+ };
181569
+ const unregister = signals.register(closeAll);
181570
+ const trackContext = async (ctx, index2) => {
181571
+ openContexts.push(ctx);
181572
+ if (tracingEnabled) {
181573
+ const contextTracePath = nextTracePath(index2);
181574
+ if (contextTracePath !== undefined) {
181575
+ traceByContext.set(ctx, contextTracePath);
181576
+ }
181577
+ await ctx.tracing.start({ screenshots: true, snapshots: true });
181578
+ }
181579
+ };
181580
+ const instrumentBrowser = (browser) => {
181581
+ const originalNewContext = browser.newContext.bind(browser);
181582
+ browser.newContext = async (opts) => {
181583
+ const { setup, index: index2 } = nextSetup(opts);
181584
+ const context = await originalNewContext(setup);
181585
+ context.setDefaultTimeout(timeout);
181586
+ await trackContext(context, index2);
181587
+ return context;
181588
+ };
181589
+ };
181590
+ const launch = async (launchOpts) => {
181591
+ const browserName = normalizeBrowserName(launchOpts?.browser);
181592
+ const bt2 = browsers[browserName];
181593
+ if (launchOpts?.persistentContext === true) {
181594
+ const userDataDir = launchOpts.userDataDir ?? path15.join(os6.tmpdir(), `qawolf-${crypto.randomUUID()}`);
181595
+ const { setup, index: index2 } = nextSetup();
181596
+ const context2 = await bt2.launchPersistentContext(userDataDir, {
181597
+ ...launchBrowserOpts,
181598
+ ...setup
181599
+ });
181600
+ context2.setDefaultTimeout(timeout);
181601
+ await trackContext(context2, index2);
181602
+ const page = await context2.newPage();
181603
+ return { browserType: browserName, context: context2, page };
181604
+ }
181605
+ const browser = await bt2.launch(launchBrowserOpts);
181606
+ openBrowsers.push(browser);
181607
+ instrumentBrowser(browser);
181608
+ const context = await browser.newContext({});
181609
+ return { browser, browserType: browserName, context };
181610
+ };
181611
+ const cleanup = async () => {
181612
+ unregister();
181613
+ await closeAll();
181614
+ };
181615
+ return { launch, cleanup, artifactPaths };
181616
+ }
181617
+
181618
+ // src/domains/runner/web/contextSetup.ts
181619
+ import path16 from "node:path";
181525
181620
  function harFlowPath(outputDir, flowName) {
181526
- return path15.join(outputDir, "har", `${flowName}.har`);
181621
+ return path16.join(outputDir, "har", `${flowName}.har`);
181527
181622
  }
181528
181623
  function traceFlowPath(outputDir, flowName) {
181529
- return path15.join(outputDir, "trace", `${flowName}.zip`);
181624
+ return path16.join(outputDir, "trace", `${flowName}.zip`);
181530
181625
  }
181531
181626
  function buildHarContextOpts(harPath, harContent) {
181532
181627
  return {
@@ -181542,7 +181637,7 @@ async function initHar(fs9, options, flowName) {
181542
181637
  if (harMode === "off")
181543
181638
  return { harMode, harPath: undefined };
181544
181639
  const harPath = harFlowPath(options.outputDir, flowName);
181545
- await fs9.mkdir(path15.dirname(harPath), { recursive: true });
181640
+ await fs9.mkdir(path16.dirname(harPath), { recursive: true });
181546
181641
  return { harMode, harPath };
181547
181642
  }
181548
181643
  async function initTrace(fs9, options, flowName) {
@@ -181550,7 +181645,7 @@ async function initTrace(fs9, options, flowName) {
181550
181645
  if (traceMode === "off")
181551
181646
  return { traceMode, tracePath: undefined };
181552
181647
  const tracePath = traceFlowPath(options.outputDir, flowName);
181553
- await fs9.mkdir(path15.dirname(tracePath), { recursive: true });
181648
+ await fs9.mkdir(path16.dirname(tracePath), { recursive: true });
181554
181649
  return { traceMode, tracePath };
181555
181650
  }
181556
181651
  async function maybeCleanupTrace(fs9, tracePath, passed, traceMode, warn) {
@@ -181563,7 +181658,7 @@ async function maybeCleanupTrace(fs9, tracePath, passed, traceMode, warn) {
181563
181658
  }
181564
181659
  }
181565
181660
  function buildContextSetup(videoSize, options, harPath) {
181566
- const videosDir = options.artifactDir ?? path15.join(options.outputDir, "videos");
181661
+ const videosDir = options.artifactDir ?? path16.join(options.outputDir, "videos");
181567
181662
  const harOpts = harPath !== undefined ? buildHarContextOpts(harPath, options.harContent ?? "omit") : {};
181568
181663
  return options.video !== "off" ? {
181569
181664
  viewport: videoSize,
@@ -181599,7 +181694,7 @@ async function runWebFlow({
181599
181694
  depsRoot: deps.depsRoot
181600
181695
  });
181601
181696
  } catch (err) {
181602
- const flowName2 = path16.basename(flowPath, path16.extname(flowPath));
181697
+ const flowName2 = path17.basename(flowPath, path17.extname(flowPath));
181603
181698
  return {
181604
181699
  passed: false,
181605
181700
  testCounts: { passed: 0, total: 0 },
@@ -181608,7 +181703,7 @@ async function runWebFlow({
181608
181703
  };
181609
181704
  }
181610
181705
  const isLegacy = typeof exported === "function";
181611
- const flowName = isLegacy ? path16.basename(flowPath, path16.extname(flowPath)) : exported.name;
181706
+ const flowName = isLegacy ? path17.basename(flowPath, path17.extname(flowPath)) : exported.name;
181612
181707
  const runFn = isLegacy ? exported : exported.run;
181613
181708
  const { harMode, harPath } = await initHar(deps.fs, options, flowName);
181614
181709
  const { traceMode, tracePath } = await initTrace(deps.fs, options, flowName);
@@ -181619,7 +181714,7 @@ async function runWebFlow({
181619
181714
  slowMo: options.slowMo,
181620
181715
  ...options.executablePath !== undefined ? { executablePath: options.executablePath } : {}
181621
181716
  };
181622
- const { launch, cleanup } = createLaunch({
181717
+ const { launch, cleanup, artifactPaths } = createLaunch({
181623
181718
  browsers: {
181624
181719
  chromium: deps.chromium,
181625
181720
  firefox: deps.firefox,
@@ -181661,25 +181756,26 @@ async function runWebFlow({
181661
181756
  } finally {
181662
181757
  await cleanup();
181663
181758
  const warn = (message) => deps.logger?.warn(message);
181664
- if (harPath !== undefined) {
181665
- await maybeCleanupHar(deps.fs, harPath, passed, harMode, warn);
181759
+ const assigned = artifactPaths();
181760
+ for (const contextHarPath of assigned.harPaths) {
181761
+ await maybeCleanupHar(deps.fs, contextHarPath, passed, harMode, warn);
181666
181762
  }
181667
- if (tracePath !== undefined) {
181668
- await maybeCleanupTrace(deps.fs, tracePath, passed, traceMode, warn);
181763
+ for (const contextTracePath of assigned.tracePaths) {
181764
+ await maybeCleanupTrace(deps.fs, contextTracePath, passed, traceMode, warn);
181669
181765
  }
181670
181766
  }
181671
181767
  }
181672
181768
 
181673
181769
  // src/shell/embeddedWorkerCli.ts
181674
181770
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
181675
- import { basename as basename6, join as join32 } from "node:path";
181771
+ import { basename as basename6, join as join33 } from "node:path";
181676
181772
  var embeddedCliPathEnv = "QAWOLF_EMBEDDED_CLI_PATH";
181677
181773
  function extractEmbeddedWorkerCli() {
181678
181774
  const assetPath = process.env[embeddedCliPathEnv];
181679
181775
  if (assetPath === undefined || assetPath === "")
181680
181776
  return;
181681
- const dir = join32(getDataDir(), "worker");
181682
- const dest = join32(dir, basename6(assetPath));
181777
+ const dir = join33(getDataDir(), "worker");
181778
+ const dest = join33(dir, basename6(assetPath));
181683
181779
  if (existsSync3(dest))
181684
181780
  return dest;
181685
181781
  mkdirSync3(dir, { recursive: true });
@@ -181982,7 +182078,7 @@ function createCompositeReporter(reporters) {
181982
182078
  };
181983
182079
  }
181984
182080
  // src/shell/reporter/createJUnitReporter.ts
181985
- import { join as join33 } from "node:path";
182081
+ import { join as join34 } from "node:path";
181986
182082
 
181987
182083
  // src/shell/reporter/junitXml.ts
181988
182084
  function escapeXml(str) {
@@ -182021,7 +182117,7 @@ ${suites}`;
182021
182117
 
182022
182118
  // src/shell/reporter/createJUnitReporter.ts
182023
182119
  function resolveJUnitOutputPath(junit, outputDir) {
182024
- return typeof junit === "string" && junit.trim() !== "" ? junit : join33(outputDir, "junit-report.xml");
182120
+ return typeof junit === "string" && junit.trim() !== "" ? junit : join34(outputDir, "junit-report.xml");
182025
182121
  }
182026
182122
  function createJUnitReporter(deps) {
182027
182123
  const flows = [];
@@ -182108,11 +182204,11 @@ function buildFlowsRunDeps(args) {
182108
182204
 
182109
182205
  // src/commands/flows/loadEnvFile.ts
182110
182206
  import { readFile } from "node:fs/promises";
182111
- import { join as join34 } from "node:path";
182207
+ import { join as join35 } from "node:path";
182112
182208
  async function loadEnvFile(envDir) {
182113
182209
  let content;
182114
182210
  try {
182115
- content = await readFile(join34(envDir, ".env"), "utf8");
182211
+ content = await readFile(join35(envDir, ".env"), "utf8");
182116
182212
  } catch (err) {
182117
182213
  if (isNoEntError(err))
182118
182214
  return;
@@ -182212,7 +182308,7 @@ async function handleFlowsRun(ctx, pattern, flags, deps) {
182212
182308
  }
182213
182309
 
182214
182310
  // src/commands/flows/hybridRunDefaults.ts
182215
- import { join as join35, resolve as resolve9 } from "node:path";
182311
+ import { join as join36, resolve as resolve9 } from "node:path";
182216
182312
  function makeDefaultHybridDeps(fs9) {
182217
182313
  return {
182218
182314
  expandPatterns: (patterns, cwd, logger) => expandPatterns(patterns, cwd, logger, fs9),
@@ -182231,7 +182327,7 @@ async function handleHybridFlowsRun(ctx, pattern, flags, deps) {
182231
182327
  if (validation !== "ok") {
182232
182328
  return { error: validation.error, exitCode: 2 };
182233
182329
  }
182234
- const envDir = resolve9(join35(".qawolf", flags.env));
182330
+ const envDir = resolve9(join36(".qawolf", flags.env));
182235
182331
  const patternArgs = buildPatternArgs(pattern);
182236
182332
  const globFlows = () => resolvedDeps.expandPatterns(patternArgs, envDir, ctx.log("flows"));
182237
182333
  let files = await globFlows();
@@ -182305,7 +182401,7 @@ async function runWorker(signals) {
182305
182401
  throw new Error("worker subprocess currently supports web flows only");
182306
182402
  await configureTestkit(input.resolvedDir);
182307
182403
  const fs9 = makeDefaultFs();
182308
- const apiBaseUrl = process.env["QAWOLF_API_URL"]?.replace(/\/+$/, "") || "https://app.qawolf.com";
182404
+ const apiBaseUrl = resolveHostUrl(process.env);
182309
182405
  const flowRuntimeDeps = await createFlowRuntimeDeps({
182310
182406
  envDir: input.resolvedDir,
182311
182407
  ctx: { apiBaseUrl, configDir: getConfigDir(), fs: fs9 }
@@ -182385,7 +182481,7 @@ function registerFlowsCommand(program2, signals) {
182385
182481
  }
182386
182482
 
182387
182483
  // src/domains/init/init.ts
182388
- import { dirname as dirname13, join as join36, relative as relative7 } from "node:path";
182484
+ import { dirname as dirname13, join as join37, relative as relative7 } from "node:path";
182389
182485
 
182390
182486
  // src/domains/init/templates.ts
182391
182487
  var qawolfConfigTs = `export default {
@@ -182423,9 +182519,9 @@ function makeDefaultInitDeps() {
182423
182519
  async function handleInit(ctx, opts, deps = makeDefaultInitDeps()) {
182424
182520
  ctx.ui.gap();
182425
182521
  ctx.ui.intro(initMessages.title);
182426
- const configPath = join36(deps.cwd, "qawolf.config.ts");
182427
- const flowPath = join36(deps.cwd, "src", "flows", "example.flow.ts");
182428
- const gitignorePath = join36(deps.cwd, ".qawolf", ".gitignore");
182522
+ const configPath = join37(deps.cwd, "qawolf.config.ts");
182523
+ const flowPath = join37(deps.cwd, "src", "flows", "example.flow.ts");
182524
+ const gitignorePath = join37(deps.cwd, ".qawolf", ".gitignore");
182429
182525
  await writeWithPrompt(ctx, configPath, qawolfConfigTs, opts.yes, deps);
182430
182526
  await writeWithPrompt(ctx, flowPath, exampleFlowTs, opts.yes, deps);
182431
182527
  await writeWithPrompt(ctx, gitignorePath, qawolfGitignore, opts.yes, deps);
@@ -182449,7 +182545,7 @@ async function writeWithPrompt(ctx, filePath, content, yes, deps) {
182449
182545
  ctx.ui.step(initMessages.createdFile(relPath));
182450
182546
  }
182451
182547
  async function ensurePackageJson(ctx, yes, deps) {
182452
- const pkgPath = join36(deps.cwd, "package.json");
182548
+ const pkgPath = join37(deps.cwd, "package.json");
182453
182549
  if (!await deps.fs.pathExists(pkgPath)) {
182454
182550
  const confirmed2 = await ctx.ui.confirm(initMessages.createPackageJsonPrompt, { yes });
182455
182551
  if (!confirmed2.ok || !confirmed2.value) {
@@ -182508,19 +182604,19 @@ Examples:
182508
182604
  }
182509
182605
 
182510
182606
  // src/commands/install/android.ts
182511
- import { join as join39 } from "node:path";
182607
+ import { join as join40 } from "node:path";
182512
182608
 
182513
182609
  // src/domains/install/android/index.ts
182514
182610
  import { parseExecutionTarget as parseExecutionTarget4 } from "@qawolf/flow-targets";
182515
182611
 
182516
182612
  // src/domains/install/android/avd.ts
182517
- import { join as join37 } from "node:path";
182613
+ import { join as join38 } from "node:path";
182518
182614
  async function installAvds(ctx, specs, deps) {
182519
182615
  const versionResult = await deps.spawn(deps.sdkManagerPath, ["--version"]);
182520
182616
  if (versionResult.exitCode !== 0) {
182521
182617
  throw new Error(installMessages.android.sdkmanagerNotFound(deps.sdkManagerPath));
182522
182618
  }
182523
- const licenseFile = join37(deps.androidHome, "licenses", "android-sdk-license");
182619
+ const licenseFile = join38(deps.androidHome, "licenses", "android-sdk-license");
182524
182620
  if (deps.checkExists(licenseFile)) {
182525
182621
  ctx.ui.info(installMessages.android.licensesAlreadyAccepted);
182526
182622
  } else {
@@ -182534,7 +182630,7 @@ async function installAvds(ctx, specs, deps) {
182534
182630
  for (const spec of specs) {
182535
182631
  if (installedImages.has(spec.systemImage))
182536
182632
  continue;
182537
- const systemImg = join37(deps.androidHome, ...spec.systemImage.split(";"), "system.img");
182633
+ const systemImg = join38(deps.androidHome, ...spec.systemImage.split(";"), "system.img");
182538
182634
  if (deps.checkExists(systemImg)) {
182539
182635
  ctx.ui.info(installMessages.android.systemImageAlreadyInstalled(spec.systemImage));
182540
182636
  installedImages.add(spec.systemImage);
@@ -182582,9 +182678,9 @@ async function installAvds(ctx, specs, deps) {
182582
182678
  }
182583
182679
 
182584
182680
  // src/domains/install/android/driver.ts
182585
- import { join as join38 } from "node:path";
182681
+ import { join as join39 } from "node:path";
182586
182682
  var appiumEnv = {
182587
- APPIUM_HOME: join38(envPaths("qawolf").data, "appium")
182683
+ APPIUM_HOME: join39(envPaths("qawolf").data, "appium")
182588
182684
  };
182589
182685
  async function installUiautomator2Driver(ctx, deps) {
182590
182686
  const listResult = await deps.spawn(deps.appiumBinPath, ["driver", "list", "--installed"], { env: appiumEnv });
@@ -182671,8 +182767,8 @@ async function handleInstallAndroid(ctx, pattern, envDir) {
182671
182767
  arch: process.arch,
182672
182768
  androidHome: androidHome2,
182673
182769
  checkExists: (path9) => fs9.existsSync(path9),
182674
- sdkManagerPath: join39(androidHome2, "cmdline-tools", "latest", "bin", "sdkmanager"),
182675
- avdManagerPath: join39(androidHome2, "cmdline-tools", "latest", "bin", "avdmanager"),
182770
+ sdkManagerPath: join40(androidHome2, "cmdline-tools", "latest", "bin", "sdkmanager"),
182771
+ avdManagerPath: join40(androidHome2, "cmdline-tools", "latest", "bin", "avdmanager"),
182676
182772
  expandPatterns: (patterns, cwd) => expandPatterns(patterns, cwd ?? process.cwd(), undefined, fs9),
182677
182773
  peekFlowMeta: makePeekFlowMeta(fs9),
182678
182774
  resolveDepsRoot: async (files) => envDir ?? (await resolveDepsRoot({ files, fs: fs9 })).depsRoot,
@@ -183063,4 +183159,4 @@ createProgram({ signals }).parseAsync().catch(() => {
183063
183159
  process.exitCode = 1;
183064
183160
  }).finally(() => flushAndExit(typeof process.exitCode === "number" ? process.exitCode : 0));
183065
183161
 
183066
- //# debugId=64AB4032821CC8E864756E2164756E21
183162
+ //# debugId=724E44C8F9B9A9E264756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qawolf/cli",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
5
5
  "keywords": [
6
6
  "automation",
@@ -63,13 +63,14 @@
63
63
  "@qawolf/api-contracts": "0.2.0",
64
64
  "@qawolf/emails": "1.1.1",
65
65
  "@qawolf/flow-targets": "1.0.0",
66
- "@qawolf/flows": "0.1.1",
66
+ "@qawolf/flows": "0.1.4",
67
67
  "@qawolf/testkit": "1.1.1",
68
68
  "appium": "2.11.3",
69
69
  "appium-uiautomator2-driver": "3.7.0",
70
70
  "appium-xcuitest-driver": "9.10.5",
71
71
  "commander": "14.0.3",
72
72
  "env-paths": "4.0.0",
73
+ "expect-webdriverio": "5.6.5",
73
74
  "picomatch": "4.0.4",
74
75
  "pino": "10.3.1",
75
76
  "playwright": "1.58.2",
@@ -19,6 +19,11 @@ environment variable (or stored credentials from `qawolf auth login`).
19
19
  their table entry notes a flag that switches them to `read`.
20
20
  Verify with `qawolf auth whoami`. Never print or log the key.
21
21
 
22
+ Commands use `https://app.qawolf.com` by default. Set `QAWOLF_HOST_URL` to
23
+ target another deployment host, for example
24
+ `https://app.staging.example.com`. `QAWOLF_API_URL` is a separate API endpoint
25
+ and does not select the deployment host used by CLI commands.
26
+
22
27
  ## Output
23
28
 
24
29
  When consuming output programmatically, always pass `--json` (or `--agent`).