@qawolf/cli 1.10.0 → 1.11.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 +216 -88
- package/package.json +2 -2
- package/skills/qawolf-cli/SKILL.md +11 -1
- package/skills/qawolf-cli/references/run-results.md +169 -0
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
|
|
8517
|
+
BufferList.prototype.join = function join36(s3) {
|
|
8518
8518
|
if (this.length === 0)
|
|
8519
8519
|
return "";
|
|
8520
8520
|
var p3 = this.head;
|
|
@@ -23182,7 +23182,7 @@ var require_BufferList2 = __commonJS((exports, module) => {
|
|
|
23182
23182
|
this.head = this.tail = null;
|
|
23183
23183
|
this.length = 0;
|
|
23184
23184
|
};
|
|
23185
|
-
BufferList.prototype.join = function
|
|
23185
|
+
BufferList.prototype.join = function join36(s3) {
|
|
23186
23186
|
if (this.length === 0)
|
|
23187
23187
|
return "";
|
|
23188
23188
|
var p3 = this.head;
|
|
@@ -134457,7 +134457,7 @@ var require_snapshot_utils = __commonJS((exports, module) => {
|
|
|
134457
134457
|
// node_modules/undici/lib/mock/snapshot-recorder.js
|
|
134458
134458
|
var require_snapshot_recorder = __commonJS((exports, module) => {
|
|
134459
134459
|
var { writeFile, readFile, mkdir } = __require("node:fs/promises");
|
|
134460
|
-
var { dirname:
|
|
134460
|
+
var { dirname: dirname12, resolve: resolve10 } = __require("node:path");
|
|
134461
134461
|
var { setTimeout: setTimeout3, clearTimeout: clearTimeout2 } = __require("node:timers");
|
|
134462
134462
|
var { InvalidArgumentError: InvalidArgumentError3, UndiciError } = require_errors7();
|
|
134463
134463
|
var { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = require_snapshot_utils();
|
|
@@ -134652,7 +134652,7 @@ var require_snapshot_recorder = __commonJS((exports, module) => {
|
|
|
134652
134652
|
throw new InvalidArgumentError3("Snapshot path is required");
|
|
134653
134653
|
}
|
|
134654
134654
|
const resolvedPath = resolve10(path4);
|
|
134655
|
-
await mkdir(
|
|
134655
|
+
await mkdir(dirname12(resolvedPath), { recursive: true });
|
|
134656
134656
|
const data2 = Array.from(this.#snapshots.entries()).map(([hash2, snapshot]) => ({
|
|
134657
134657
|
hash: hash2,
|
|
134658
134658
|
snapshot
|
|
@@ -156448,6 +156448,12 @@ var interactiveRunnerMessages = {
|
|
|
156448
156448
|
targetMismatch: (runnerName, requiredRunnerName) => `This flow needs a ${requiredRunnerName} runner and ${runnerName} is running. Launch one with --name ${requiredRunnerName}.`
|
|
156449
156449
|
};
|
|
156450
156450
|
// src/core/messages/runner.ts
|
|
156451
|
+
var maxListedNames = 5;
|
|
156452
|
+
function listNames(names) {
|
|
156453
|
+
const shown = names.slice(0, maxListedNames).join(", ");
|
|
156454
|
+
const rest = names.length - maxListedNames;
|
|
156455
|
+
return rest > 0 ? `${shown} and ${rest} more` : shown;
|
|
156456
|
+
}
|
|
156451
156457
|
var runnerMessages = {
|
|
156452
156458
|
playwrightLoadFailed: (envDir, detail) => packageLoadFailed("Playwright", envDir, detail),
|
|
156453
156459
|
androidWorkersUnsupported: "Android flows are not yet supported with --workers > 1; rerun Android flows with --workers 1.",
|
|
@@ -156477,6 +156483,12 @@ ${dir}`,
|
|
|
156477
156483
|
].join(`
|
|
156478
156484
|
`),
|
|
156479
156485
|
outerHopCandidateRejected: (dir, missing) => `outer-hop candidate rejected: ${dir} missing ${missing.join(", ")}`,
|
|
156486
|
+
outerHopFallbackNotice: (dir, missing) => [
|
|
156487
|
+
`Not reusing ${dir} — it is missing ${listNames(missing)}.`,
|
|
156488
|
+
`Installing the declared dependencies instead. Run 'npm install' there to reuse it.`
|
|
156489
|
+
].join(`
|
|
156490
|
+
`),
|
|
156491
|
+
outerHopCarriedOver: (names) => `Carried over ${pluralize(names.length, "undeclared package")} from the project node_modules: ${listNames(names)}. Declare them in package.json "dependencies".`,
|
|
156480
156492
|
moduleNotFoundHint: (pkg, projectDir) => projectDir === undefined ? `Hint: '${pkg}' could not be resolved. Run from within your flows project so its dependencies can be found.` : `Hint: '${pkg}' could not be resolved. Ensure it is declared in ${projectDir}/package.json "dependencies" and run npm install in that project.`
|
|
156481
156493
|
};
|
|
156482
156494
|
// src/core/messages/updateCheck.ts
|
|
@@ -175911,7 +175923,7 @@ function startUpdateCheck(deps) {
|
|
|
175911
175923
|
// package.json
|
|
175912
175924
|
var package_default = {
|
|
175913
175925
|
name: "@qawolf/cli",
|
|
175914
|
-
version: "1.
|
|
175926
|
+
version: "1.11.2",
|
|
175915
175927
|
description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
|
|
175916
175928
|
keywords: [
|
|
175917
175929
|
"automation",
|
|
@@ -175979,7 +175991,6 @@ var package_default = {
|
|
|
175979
175991
|
appium: "2.11.3",
|
|
175980
175992
|
commander: "14.0.3",
|
|
175981
175993
|
"env-paths": "4.0.0",
|
|
175982
|
-
"expect-webdriverio": "5.6.5",
|
|
175983
175994
|
picomatch: "4.0.4",
|
|
175984
175995
|
pino: "10.3.1",
|
|
175985
175996
|
playwright: "1.62.0",
|
|
@@ -175996,6 +176007,7 @@ var package_default = {
|
|
|
175996
176007
|
"@types/bun": "1.3.14",
|
|
175997
176008
|
"@types/picomatch": "4.0.3",
|
|
175998
176009
|
"appium-uiautomator2-driver": "4.2.9",
|
|
176010
|
+
"expect-webdriverio": "5.6.5",
|
|
175999
176011
|
knip: "6.16.1",
|
|
176000
176012
|
oxfmt: "0.54.0",
|
|
176001
176013
|
oxlint: "1.69.0",
|
|
@@ -177789,7 +177801,8 @@ async function expandPatterns(patterns, cwd, logger, fs3) {
|
|
|
177789
177801
|
for (const root of roots) {
|
|
177790
177802
|
const matches = await glob(effectivePatterns, {
|
|
177791
177803
|
cwd: root,
|
|
177792
|
-
absolute: true
|
|
177804
|
+
absolute: true,
|
|
177805
|
+
ignore: ["**/node_modules/**"]
|
|
177793
177806
|
});
|
|
177794
177807
|
for (const match of matches) {
|
|
177795
177808
|
const file2 = resolve3(match);
|
|
@@ -182468,6 +182481,14 @@ function buildPatternArgs(pattern) {
|
|
|
182468
182481
|
return pattern ? [pattern] : [];
|
|
182469
182482
|
}
|
|
182470
182483
|
|
|
182484
|
+
// src/core/flowFailureHint.ts
|
|
182485
|
+
function flowFailureHint(errText, projectDir) {
|
|
182486
|
+
const missingPackage = extractMissingPackage(errText);
|
|
182487
|
+
if (missingPackage === undefined)
|
|
182488
|
+
return;
|
|
182489
|
+
return runnerMessages.moduleNotFoundHint(missingPackage, projectDir);
|
|
182490
|
+
}
|
|
182491
|
+
|
|
182471
182492
|
// src/shell/reporter/createCompositeReporter.ts
|
|
182472
182493
|
function callEach(reporters, pick2, event) {
|
|
182473
182494
|
for (const r2 of reporters) {
|
|
@@ -182534,6 +182555,26 @@ function formatErrorWithCause(err) {
|
|
|
182534
182555
|
`);
|
|
182535
182556
|
}
|
|
182536
182557
|
|
|
182558
|
+
// src/domains/runner/jsonFailureDetails.ts
|
|
182559
|
+
function collectJsonFailureDetails(reporter, projectDir) {
|
|
182560
|
+
const details = [];
|
|
182561
|
+
return {
|
|
182562
|
+
reporter: createCompositeReporter([
|
|
182563
|
+
reporter,
|
|
182564
|
+
{
|
|
182565
|
+
onFlowFail: ({ err }) => {
|
|
182566
|
+
const detail = formatErrorWithCause(err);
|
|
182567
|
+
const hint = flowFailureHint(detail, projectDir);
|
|
182568
|
+
details.push(hint === undefined ? detail : `${detail}
|
|
182569
|
+
|
|
182570
|
+
${hint}`);
|
|
182571
|
+
}
|
|
182572
|
+
}
|
|
182573
|
+
]),
|
|
182574
|
+
details
|
|
182575
|
+
};
|
|
182576
|
+
}
|
|
182577
|
+
|
|
182537
182578
|
// src/domains/runner/runAndroidFlowUtils.ts
|
|
182538
182579
|
import { parseExecutionTarget as parseExecutionTarget3 } from "@qawolf/flow-targets";
|
|
182539
182580
|
|
|
@@ -182589,7 +182630,7 @@ class FlowRunError extends Error {
|
|
|
182589
182630
|
}
|
|
182590
182631
|
}
|
|
182591
182632
|
|
|
182592
|
-
// src/domains/runner/
|
|
182633
|
+
// src/domains/runner/dispatchFlow.ts
|
|
182593
182634
|
async function dispatchFlow({
|
|
182594
182635
|
deps,
|
|
182595
182636
|
flow,
|
|
@@ -182881,13 +182922,9 @@ async function flowsRun(ctx, files, flags, deps) {
|
|
|
182881
182922
|
return { error: runnerMessages.androidWorkersUnsupported, exitCode: 2 };
|
|
182882
182923
|
}
|
|
182883
182924
|
const { webOptions, androidOptions } = buildRunOptions(flags);
|
|
182884
|
-
const
|
|
182885
|
-
const
|
|
182886
|
-
|
|
182887
|
-
{
|
|
182888
|
-
onFlowFail: ({ err }) => failureDetails.push(formatErrorWithCause(err))
|
|
182889
|
-
}
|
|
182890
|
-
]) : deps.reporter;
|
|
182925
|
+
const collected = ctx.outputMode === "json" ? collectJsonFailureDetails(deps.reporter, deps.projectDir) : undefined;
|
|
182926
|
+
const failureDetails = collected?.details ?? [];
|
|
182927
|
+
const reporter = collected?.reporter ?? deps.reporter;
|
|
182891
182928
|
const result = await executeFlows({
|
|
182892
182929
|
ctx,
|
|
182893
182930
|
deps: { ...deps, reporter },
|
|
@@ -182975,7 +183012,7 @@ async function defaultRunWebFlowDeps(envDir = process.cwd(), signals) {
|
|
|
182975
183012
|
|
|
182976
183013
|
// src/domains/runtimeEnv/prepareRunDir.ts
|
|
182977
183014
|
import { createHash as createHash4 } from "node:crypto";
|
|
182978
|
-
import { join as
|
|
183015
|
+
import { join as join33, resolve as resolve9 } from "node:path";
|
|
182979
183016
|
|
|
182980
183017
|
// src/domains/runtimeEnv/execSubpathImports.ts
|
|
182981
183018
|
import { join as join26 } from "node:path";
|
|
@@ -183044,9 +183081,82 @@ async function populateInnerHop(args) {
|
|
|
183044
183081
|
}
|
|
183045
183082
|
|
|
183046
183083
|
// src/domains/runtimeEnv/outerHop.ts
|
|
183084
|
+
import { lstat as lstat2 } from "node:fs/promises";
|
|
183085
|
+
import { dirname as dirname10, join as join30 } from "node:path";
|
|
183086
|
+
|
|
183087
|
+
// src/domains/runtimeEnv/carryOverPackages.ts
|
|
183047
183088
|
import { lstat } from "node:fs/promises";
|
|
183048
183089
|
import { dirname as dirname9, join as join29 } from "node:path";
|
|
183090
|
+
|
|
183091
|
+
// src/domains/runtimeEnv/carryOverPlan.ts
|
|
183049
183092
|
var executorPackageNames = new Set(pinnedPackages.map((p3) => p3.name));
|
|
183093
|
+
function planCarryOver(args) {
|
|
183094
|
+
const installed = new Set(args.installed);
|
|
183095
|
+
return args.present.filter((name) => !name.startsWith(".") && !installed.has(name) && !executorPackageNames.has(name));
|
|
183096
|
+
}
|
|
183097
|
+
|
|
183098
|
+
// src/domains/runtimeEnv/carryOverPackages.ts
|
|
183099
|
+
async function carryOverPackages(args) {
|
|
183100
|
+
const { projectModulesDir, nodeModulesDir, fs: fs4 } = args;
|
|
183101
|
+
const present = await listPackageNames(projectModulesDir, fs4);
|
|
183102
|
+
if (present.length === 0)
|
|
183103
|
+
return [];
|
|
183104
|
+
const names = planCarryOver({
|
|
183105
|
+
present,
|
|
183106
|
+
installed: await listPackageNames(nodeModulesDir, fs4)
|
|
183107
|
+
});
|
|
183108
|
+
const linked = [];
|
|
183109
|
+
for (const name of names) {
|
|
183110
|
+
const segments = name.split("/");
|
|
183111
|
+
const target = join29(nodeModulesDir, ...segments);
|
|
183112
|
+
if (await pathOccupied(target))
|
|
183113
|
+
continue;
|
|
183114
|
+
if (segments.length > 1)
|
|
183115
|
+
await fs4.mkdir(dirname9(target), { recursive: true });
|
|
183116
|
+
await createDirSymlink(join29(projectModulesDir, ...segments), target);
|
|
183117
|
+
linked.push(name);
|
|
183118
|
+
}
|
|
183119
|
+
return linked;
|
|
183120
|
+
}
|
|
183121
|
+
async function pathOccupied(path3) {
|
|
183122
|
+
try {
|
|
183123
|
+
await lstat(path3);
|
|
183124
|
+
return true;
|
|
183125
|
+
} catch {
|
|
183126
|
+
return false;
|
|
183127
|
+
}
|
|
183128
|
+
}
|
|
183129
|
+
async function listPackageNames(dir, fs4) {
|
|
183130
|
+
let entries;
|
|
183131
|
+
try {
|
|
183132
|
+
entries = await fs4.readdir(dir);
|
|
183133
|
+
} catch {
|
|
183134
|
+
return [];
|
|
183135
|
+
}
|
|
183136
|
+
const names = [];
|
|
183137
|
+
for (const entry of entries) {
|
|
183138
|
+
if (!entry.startsWith("@")) {
|
|
183139
|
+
if (fs4.existsSync(join29(dir, entry, "package.json")))
|
|
183140
|
+
names.push(entry);
|
|
183141
|
+
continue;
|
|
183142
|
+
}
|
|
183143
|
+
let scoped;
|
|
183144
|
+
try {
|
|
183145
|
+
scoped = await fs4.readdir(join29(dir, entry));
|
|
183146
|
+
} catch {
|
|
183147
|
+
continue;
|
|
183148
|
+
}
|
|
183149
|
+
for (const inner of scoped) {
|
|
183150
|
+
if (fs4.existsSync(join29(dir, entry, inner, "package.json"))) {
|
|
183151
|
+
names.push(`${entry}/${inner}`);
|
|
183152
|
+
}
|
|
183153
|
+
}
|
|
183154
|
+
}
|
|
183155
|
+
return names;
|
|
183156
|
+
}
|
|
183157
|
+
|
|
183158
|
+
// src/domains/runtimeEnv/outerHop.ts
|
|
183159
|
+
var executorPackageNames2 = new Set(pinnedPackages.map((p3) => p3.name));
|
|
183050
183160
|
async function populateOuterHop(args) {
|
|
183051
183161
|
const { projectDir, runDir, fs: fs4 } = args;
|
|
183052
183162
|
if (projectDir === undefined)
|
|
@@ -183055,7 +183165,7 @@ async function populateOuterHop(args) {
|
|
|
183055
183165
|
const depNames = Object.keys(installableDeps);
|
|
183056
183166
|
const { satisfying, rejected } = await findSatisfyingNodeModulesDir(projectDir, depNames, fs4);
|
|
183057
183167
|
if (satisfying !== undefined) {
|
|
183058
|
-
await createDirSymlink(satisfying,
|
|
183168
|
+
await createDirSymlink(satisfying, join30(runDir, "node_modules"));
|
|
183059
183169
|
return { mode: "symlink", nodeModulesDir: satisfying };
|
|
183060
183170
|
}
|
|
183061
183171
|
if (depNames.length === 0)
|
|
@@ -183064,24 +183174,29 @@ async function populateOuterHop(args) {
|
|
|
183064
183174
|
await installOuterDeps(runDir, installableDeps, fs4, args.install ?? spawnNpmInstall);
|
|
183065
183175
|
await linkPinnedPackages({
|
|
183066
183176
|
depsRoot: args.depsRoot,
|
|
183067
|
-
nodeModulesDir:
|
|
183177
|
+
nodeModulesDir: join30(runDir, "node_modules"),
|
|
183068
183178
|
fs: fs4
|
|
183069
183179
|
});
|
|
183070
|
-
|
|
183180
|
+
const carriedOver = await carryOverPackages({
|
|
183181
|
+
projectModulesDir: join30(projectDir, "node_modules"),
|
|
183182
|
+
nodeModulesDir: join30(runDir, "node_modules"),
|
|
183183
|
+
fs: fs4
|
|
183184
|
+
});
|
|
183185
|
+
return { mode: "install", depCount: depNames.length, rejected, carriedOver };
|
|
183071
183186
|
}
|
|
183072
183187
|
async function findSatisfyingNodeModulesDir(startDir, depNames, fs4) {
|
|
183073
183188
|
const rejected = [];
|
|
183074
183189
|
let dir = startDir;
|
|
183075
183190
|
while (true) {
|
|
183076
|
-
const candidate =
|
|
183191
|
+
const candidate = join30(dir, "node_modules");
|
|
183077
183192
|
const stats = await lstatSafe(candidate);
|
|
183078
183193
|
if (stats !== undefined && stats.isDirectory() && !stats.isSymbolicLink()) {
|
|
183079
|
-
const missing = depNames.filter((name) => !fs4.existsSync(
|
|
183194
|
+
const missing = depNames.filter((name) => !fs4.existsSync(join30(candidate, name, "package.json")));
|
|
183080
183195
|
if (missing.length === 0)
|
|
183081
183196
|
return { satisfying: candidate, rejected };
|
|
183082
183197
|
rejected.push({ dir: candidate, missing });
|
|
183083
183198
|
}
|
|
183084
|
-
const parent =
|
|
183199
|
+
const parent = dirname10(dir);
|
|
183085
183200
|
if (parent === dir)
|
|
183086
183201
|
return { satisfying: undefined, rejected };
|
|
183087
183202
|
dir = parent;
|
|
@@ -183089,7 +183204,7 @@ async function findSatisfyingNodeModulesDir(startDir, depNames, fs4) {
|
|
|
183089
183204
|
}
|
|
183090
183205
|
async function lstatSafe(path3) {
|
|
183091
183206
|
try {
|
|
183092
|
-
return await
|
|
183207
|
+
return await lstat2(path3);
|
|
183093
183208
|
} catch {
|
|
183094
183209
|
return;
|
|
183095
183210
|
}
|
|
@@ -183097,7 +183212,7 @@ async function lstatSafe(path3) {
|
|
|
183097
183212
|
async function readInstallableDeps(projectDir, fs4) {
|
|
183098
183213
|
let content;
|
|
183099
183214
|
try {
|
|
183100
|
-
content = await fs4.readFile(
|
|
183215
|
+
content = await fs4.readFile(join30(projectDir, "package.json"));
|
|
183101
183216
|
} catch {
|
|
183102
183217
|
return {};
|
|
183103
183218
|
}
|
|
@@ -183112,11 +183227,11 @@ async function readInstallableDeps(projectDir, fs4) {
|
|
|
183112
183227
|
const rawDeps = parsed["dependencies"];
|
|
183113
183228
|
if (typeof rawDeps !== "object" || rawDeps === null)
|
|
183114
183229
|
return {};
|
|
183115
|
-
return Object.fromEntries(Object.entries(rawDeps).filter(([name]) => !
|
|
183230
|
+
return Object.fromEntries(Object.entries(rawDeps).filter(([name]) => !executorPackageNames2.has(name)).filter((entry) => typeof entry[1] === "string"));
|
|
183116
183231
|
}
|
|
183117
183232
|
async function installOuterDeps(runDir, deps, fs4, install) {
|
|
183118
|
-
await fs4.writeFile(
|
|
183119
|
-
await fs4.writeFile(
|
|
183233
|
+
await fs4.writeFile(join30(runDir, "package.json"), JSON.stringify({ name: "qawolf-run", private: true, dependencies: deps }, undefined, 2));
|
|
183234
|
+
await fs4.writeFile(join30(runDir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
|
|
183120
183235
|
`);
|
|
183121
183236
|
const result = await install(runDir);
|
|
183122
183237
|
if (result.exitCode !== 0) {
|
|
@@ -183126,14 +183241,14 @@ async function installOuterDeps(runDir, deps, fs4, install) {
|
|
|
183126
183241
|
|
|
183127
183242
|
// src/domains/runtimeEnv/stageFlowFiles.ts
|
|
183128
183243
|
import { createHash as createHash3 } from "node:crypto";
|
|
183129
|
-
import { basename as basename4, dirname as
|
|
183244
|
+
import { basename as basename4, dirname as dirname11, join as join32, sep as sep4 } from "node:path";
|
|
183130
183245
|
|
|
183131
183246
|
// src/shell/copyDir.ts
|
|
183132
183247
|
import { cp, readdir as readdir2 } from "node:fs/promises";
|
|
183133
|
-
import { basename as basename3, join as
|
|
183248
|
+
import { basename as basename3, join as join31 } from "node:path";
|
|
183134
183249
|
async function copyDirExcluding(source, destination, excludedNames) {
|
|
183135
183250
|
const entries = await readdir2(source, { withFileTypes: true });
|
|
183136
|
-
await Promise.all(entries.filter((entry) => !excludedNames.has(entry.name)).map((entry) => cp(
|
|
183251
|
+
await Promise.all(entries.filter((entry) => !excludedNames.has(entry.name)).map((entry) => cp(join31(source, entry.name), join31(destination, entry.name), {
|
|
183137
183252
|
recursive: true,
|
|
183138
183253
|
filter: (path3) => !excludedNames.has(basename3(path3))
|
|
183139
183254
|
})));
|
|
@@ -183150,14 +183265,14 @@ async function stageFlowFiles(args) {
|
|
|
183150
183265
|
if (files.length > 1) {
|
|
183151
183266
|
return Promise.all(files.map((f3) => stageOneFile(f3, execDir, fs4)));
|
|
183152
183267
|
}
|
|
183153
|
-
await Promise.all(files.map((f3) => fs4.copyFile(f3,
|
|
183154
|
-
return files.map((f3) =>
|
|
183268
|
+
await Promise.all(files.map((f3) => fs4.copyFile(f3, join32(execDir, basename4(f3)))));
|
|
183269
|
+
return files.map((f3) => join32(execDir, basename4(f3)));
|
|
183155
183270
|
}
|
|
183156
183271
|
async function stageOneFile(file2, execDir, fs4) {
|
|
183157
|
-
const dirHash = createHash3("sha256").update(
|
|
183158
|
-
const subDir =
|
|
183272
|
+
const dirHash = createHash3("sha256").update(dirname11(file2)).digest("hex").slice(0, 8);
|
|
183273
|
+
const subDir = join32(execDir, dirHash);
|
|
183159
183274
|
await fs4.mkdir(subDir, { recursive: true });
|
|
183160
|
-
const dest =
|
|
183275
|
+
const dest = join32(subDir, basename4(file2));
|
|
183161
183276
|
await fs4.copyFile(file2, dest);
|
|
183162
183277
|
return dest;
|
|
183163
183278
|
}
|
|
@@ -183165,7 +183280,7 @@ function remapPath(file2, projectDir, execDir) {
|
|
|
183165
183280
|
if (file2 === projectDir)
|
|
183166
183281
|
return execDir;
|
|
183167
183282
|
if (file2.startsWith(projectDir + sep4)) {
|
|
183168
|
-
return
|
|
183283
|
+
return join32(execDir, file2.slice(projectDir.length + 1));
|
|
183169
183284
|
}
|
|
183170
183285
|
return;
|
|
183171
183286
|
}
|
|
@@ -183175,10 +183290,10 @@ async function prepareRunDir(args) {
|
|
|
183175
183290
|
const { files, projectDir, depsRoot, runRoot } = args;
|
|
183176
183291
|
const fs4 = args.fs ?? makeDefaultFs();
|
|
183177
183292
|
const runId = buildRunId(projectDir, files);
|
|
183178
|
-
const runDir =
|
|
183293
|
+
const runDir = join33(runRoot, runId);
|
|
183179
183294
|
await fs4.rm(runDir, { recursive: true, force: true });
|
|
183180
183295
|
await fs4.mkdir(runDir, { recursive: true });
|
|
183181
|
-
const execDir =
|
|
183296
|
+
const execDir = join33(runDir, "exec");
|
|
183182
183297
|
await fs4.mkdir(execDir, { recursive: true });
|
|
183183
183298
|
await populateInnerHop({ depsRoot, execDir, fs: fs4 });
|
|
183184
183299
|
const stagedFiles = await stageFlowFiles({ files, projectDir, execDir, fs: fs4 });
|
|
@@ -183209,17 +183324,17 @@ function buildRunId(projectDir, files) {
|
|
|
183209
183324
|
}
|
|
183210
183325
|
|
|
183211
183326
|
// src/shell/testkit.ts
|
|
183212
|
-
import { join as
|
|
183327
|
+
import { join as join34 } from "node:path";
|
|
183213
183328
|
function notAvailableLocally(name) {
|
|
183214
183329
|
throw new Error(runnerMessages.notAvailableLocally(name));
|
|
183215
183330
|
}
|
|
183216
183331
|
async function loadSdkDeps(cwd) {
|
|
183217
|
-
const testkitDir =
|
|
183332
|
+
const testkitDir = join34(cwd, "node_modules", "@qawolf", "testkit");
|
|
183218
183333
|
try {
|
|
183219
183334
|
const clientMod = await importFromPath(resolveFromEnvDir(cwd, "@qawolf/testkit/client"));
|
|
183220
183335
|
if (typeof clientMod.createTestkitClient !== "function")
|
|
183221
183336
|
throw new Error("createTestkitClient is not a function");
|
|
183222
|
-
const scopeMod = await importFromPath(
|
|
183337
|
+
const scopeMod = await importFromPath(join34(testkitDir, "dist", "clientScope.js"));
|
|
183223
183338
|
if (typeof scopeMod.configureTestkitClient !== "function")
|
|
183224
183339
|
throw new Error("configureTestkitClient is not a function");
|
|
183225
183340
|
return {
|
|
@@ -183277,9 +183392,9 @@ async function registerLazyEmailsClient(getInbox, cwd, deps) {
|
|
|
183277
183392
|
}
|
|
183278
183393
|
|
|
183279
183394
|
// src/commands/flows/envVarDeps.ts
|
|
183280
|
-
import { join as
|
|
183395
|
+
import { join as join35 } from "node:path";
|
|
183281
183396
|
function envFilePath(envDir) {
|
|
183282
|
-
return
|
|
183397
|
+
return join35(envDir, ".env");
|
|
183283
183398
|
}
|
|
183284
183399
|
async function readEnv(envDir, fs4) {
|
|
183285
183400
|
try {
|
|
@@ -183383,6 +183498,9 @@ async function createFlowRuntimeDeps({
|
|
|
183383
183498
|
};
|
|
183384
183499
|
}
|
|
183385
183500
|
|
|
183501
|
+
// src/commands/flows/runStagedFlows.ts
|
|
183502
|
+
import { join as join39 } from "node:path";
|
|
183503
|
+
|
|
183386
183504
|
// src/shell/appium/createAppiumServer.ts
|
|
183387
183505
|
import { existsSync as existsSync2 } from "node:fs";
|
|
183388
183506
|
|
|
@@ -183746,12 +183864,12 @@ Install Android Studio and open Tools > SDK Manager.`);
|
|
|
183746
183864
|
}
|
|
183747
183865
|
|
|
183748
183866
|
// src/shell/manifest/lookup.ts
|
|
183749
|
-
import { basename as basename5, dirname as
|
|
183867
|
+
import { basename as basename5, dirname as dirname12, relative as relative6 } from "node:path";
|
|
183750
183868
|
async function findFlowStamp(flowAbsPath) {
|
|
183751
183869
|
let envDir;
|
|
183752
|
-
let current =
|
|
183753
|
-
while (current !==
|
|
183754
|
-
const parent2 =
|
|
183870
|
+
let current = dirname12(flowAbsPath);
|
|
183871
|
+
while (current !== dirname12(current)) {
|
|
183872
|
+
const parent2 = dirname12(current);
|
|
183755
183873
|
if (basename5(parent2) === ".qawolf") {
|
|
183756
183874
|
envDir = current;
|
|
183757
183875
|
break;
|
|
@@ -184683,14 +184801,14 @@ async function runWebFlow({
|
|
|
184683
184801
|
|
|
184684
184802
|
// src/shell/embeddedWorkerCli.ts
|
|
184685
184803
|
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
184686
|
-
import { basename as basename6, join as
|
|
184804
|
+
import { basename as basename6, join as join36 } from "node:path";
|
|
184687
184805
|
var embeddedCliPathEnv = "QAWOLF_EMBEDDED_CLI_PATH";
|
|
184688
184806
|
function extractEmbeddedWorkerCli() {
|
|
184689
184807
|
const assetPath = process.env[embeddedCliPathEnv];
|
|
184690
184808
|
if (assetPath === undefined || assetPath === "")
|
|
184691
184809
|
return;
|
|
184692
|
-
const dir =
|
|
184693
|
-
const dest =
|
|
184810
|
+
const dir = join36(getDataDir(), "worker");
|
|
184811
|
+
const dest = join36(dir, basename6(assetPath));
|
|
184694
184812
|
if (existsSync3(dest))
|
|
184695
184813
|
return dest;
|
|
184696
184814
|
mkdirSync3(dir, { recursive: true });
|
|
@@ -184845,7 +184963,7 @@ function makePooledDispatch(resolvedDir) {
|
|
|
184845
184963
|
}
|
|
184846
184964
|
|
|
184847
184965
|
// src/commands/flows/buildRunReporter.ts
|
|
184848
|
-
import { dirname as
|
|
184966
|
+
import { dirname as dirname13 } from "node:path";
|
|
184849
184967
|
|
|
184850
184968
|
// src/shell/reporter/createConsoleReporter.ts
|
|
184851
184969
|
import { styleText as styleText3 } from "node:util";
|
|
@@ -184892,9 +185010,9 @@ function createConsoleReporter(deps) {
|
|
|
184892
185010
|
deps.stderr.write(`${formatted}
|
|
184893
185011
|
|
|
184894
185012
|
`);
|
|
184895
|
-
const
|
|
184896
|
-
if (
|
|
184897
|
-
deps.stderr.write(`${styleText3("yellow", `${indent}${
|
|
185013
|
+
const hint = flowFailureHint(errStr, deps.projectDir);
|
|
185014
|
+
if (hint !== undefined) {
|
|
185015
|
+
deps.stderr.write(`${styleText3("yellow", `${indent}${hint}`)}
|
|
184898
185016
|
|
|
184899
185017
|
`);
|
|
184900
185018
|
}
|
|
@@ -184928,7 +185046,7 @@ ${styleText3("dim", "─".repeat(width))}
|
|
|
184928
185046
|
};
|
|
184929
185047
|
}
|
|
184930
185048
|
// src/shell/reporter/createJUnitReporter.ts
|
|
184931
|
-
import { join as
|
|
185049
|
+
import { join as join37 } from "node:path";
|
|
184932
185050
|
|
|
184933
185051
|
// src/shell/reporter/junitXml.ts
|
|
184934
185052
|
function escapeXml(str) {
|
|
@@ -184967,7 +185085,7 @@ ${suites}`;
|
|
|
184967
185085
|
|
|
184968
185086
|
// src/shell/reporter/createJUnitReporter.ts
|
|
184969
185087
|
function resolveJUnitOutputPath(junit, outputDir) {
|
|
184970
|
-
return typeof junit === "string" && junit.trim() !== "" ? junit :
|
|
185088
|
+
return typeof junit === "string" && junit.trim() !== "" ? junit : join37(outputDir, "junit-report.xml");
|
|
184971
185089
|
}
|
|
184972
185090
|
function createJUnitReporter(deps) {
|
|
184973
185091
|
const flows = [];
|
|
@@ -185006,7 +185124,7 @@ function buildRunReporter(flags, deps) {
|
|
|
185006
185124
|
const junit = createJUnitReporter({
|
|
185007
185125
|
outputPath: resolveJUnitOutputPath(flags.junit, flags.outputDir),
|
|
185008
185126
|
writeFile: (path9, content) => {
|
|
185009
|
-
deps.fs.mkdirSync(
|
|
185127
|
+
deps.fs.mkdirSync(dirname13(path9), { recursive: true });
|
|
185010
185128
|
deps.fs.writeFileSync(path9, content);
|
|
185011
185129
|
},
|
|
185012
185130
|
stderr
|
|
@@ -185045,6 +185163,7 @@ function buildFlowsRunDeps(args) {
|
|
|
185045
185163
|
findFlowStamp,
|
|
185046
185164
|
warn: (message) => ctx.ui.warn(message),
|
|
185047
185165
|
logger: ctx.log("runner"),
|
|
185166
|
+
...projectDir !== undefined ? { projectDir } : {},
|
|
185048
185167
|
reporter: buildRunReporter(flags, {
|
|
185049
185168
|
fs: ctx.fs,
|
|
185050
185169
|
stdout: { write: (text3) => ctx.ui.write(text3) },
|
|
@@ -185057,11 +185176,11 @@ function buildFlowsRunDeps(args) {
|
|
|
185057
185176
|
|
|
185058
185177
|
// src/commands/flows/loadEnvFile.ts
|
|
185059
185178
|
import { readFile } from "node:fs/promises";
|
|
185060
|
-
import { join as
|
|
185179
|
+
import { join as join38 } from "node:path";
|
|
185061
185180
|
async function loadEnvFile(envDir) {
|
|
185062
185181
|
let content;
|
|
185063
185182
|
try {
|
|
185064
|
-
content = await readFile(
|
|
185183
|
+
content = await readFile(join38(envDir, ".env"), "utf8");
|
|
185065
185184
|
} catch (err) {
|
|
185066
185185
|
if (isNoEntError(err))
|
|
185067
185186
|
return;
|
|
@@ -185106,6 +185225,14 @@ async function runStagedFlows(args) {
|
|
|
185106
185225
|
for (const candidate of staged.outerHop.rejected) {
|
|
185107
185226
|
log31.debug(runnerMessages.outerHopCandidateRejected(candidate.dir, candidate.missing));
|
|
185108
185227
|
}
|
|
185228
|
+
const ownModules = projectDir === undefined ? undefined : join39(projectDir, "node_modules");
|
|
185229
|
+
const own = staged.outerHop.rejected.find((c3) => c3.dir === ownModules);
|
|
185230
|
+
if (own !== undefined) {
|
|
185231
|
+
ctx.ui.warn(runnerMessages.outerHopFallbackNotice(own.dir, own.missing));
|
|
185232
|
+
}
|
|
185233
|
+
if (staged.outerHop.carriedOver.length > 0) {
|
|
185234
|
+
ctx.ui.warn(runnerMessages.outerHopCarriedOver(staged.outerHop.carriedOver));
|
|
185235
|
+
}
|
|
185109
185236
|
}
|
|
185110
185237
|
const unregisterCleanup = ctx.signals.register(staged.cleanup);
|
|
185111
185238
|
try {
|
|
@@ -185162,7 +185289,7 @@ async function handleFlowsRun(ctx, pattern, flags, deps) {
|
|
|
185162
185289
|
}
|
|
185163
185290
|
|
|
185164
185291
|
// src/commands/flows/hybridRunDefaults.ts
|
|
185165
|
-
import { join as
|
|
185292
|
+
import { join as join40, resolve as resolve10 } from "node:path";
|
|
185166
185293
|
function makeDefaultHybridDeps(fs9) {
|
|
185167
185294
|
return {
|
|
185168
185295
|
expandPatterns: (patterns, cwd, logger) => expandPatterns(patterns, cwd, logger, fs9),
|
|
@@ -185181,7 +185308,7 @@ async function handleHybridFlowsRun(ctx, pattern, flags, deps) {
|
|
|
185181
185308
|
if (validation !== "ok") {
|
|
185182
185309
|
return { error: validation.error, exitCode: 2 };
|
|
185183
185310
|
}
|
|
185184
|
-
const envDir = resolve10(
|
|
185311
|
+
const envDir = resolve10(join40(".qawolf", flags.env));
|
|
185185
185312
|
const patternArgs = buildPatternArgs(pattern);
|
|
185186
185313
|
const globFlows = () => resolvedDeps.expandPatterns(patternArgs, envDir, ctx.log("flows"));
|
|
185187
185314
|
let files = await globFlows();
|
|
@@ -185327,12 +185454,12 @@ function registerFlowsCommand(program2, signals) {
|
|
|
185327
185454
|
}
|
|
185328
185455
|
|
|
185329
185456
|
// src/domains/init/init.ts
|
|
185330
|
-
import { dirname as
|
|
185457
|
+
import { dirname as dirname14, join as join42, relative as relative7 } from "node:path";
|
|
185331
185458
|
|
|
185332
185459
|
// src/domains/init/ensurePackageJson.ts
|
|
185333
|
-
import { join as
|
|
185460
|
+
import { join as join41 } from "node:path";
|
|
185334
185461
|
async function ensurePackageJson(ctx, yes, deps) {
|
|
185335
|
-
const pkgPath =
|
|
185462
|
+
const pkgPath = join41(deps.cwd, "package.json");
|
|
185336
185463
|
if (!await deps.fs.pathExists(pkgPath)) {
|
|
185337
185464
|
const confirmed2 = await ctx.ui.confirm(initMessages.createPackageJsonPrompt, { yes });
|
|
185338
185465
|
if (!confirmed2.ok || !confirmed2.value) {
|
|
@@ -185458,9 +185585,9 @@ function makeDefaultInitDeps() {
|
|
|
185458
185585
|
async function handleInit(ctx, opts, deps = makeDefaultInitDeps()) {
|
|
185459
185586
|
ctx.ui.gap();
|
|
185460
185587
|
ctx.ui.intro(initMessages.title);
|
|
185461
|
-
const configPath =
|
|
185462
|
-
const flowPath =
|
|
185463
|
-
const gitignorePath =
|
|
185588
|
+
const configPath = join42(deps.cwd, "qawolf.config.ts");
|
|
185589
|
+
const flowPath = join42(deps.cwd, "src", "flows", "example.flow.ts");
|
|
185590
|
+
const gitignorePath = join42(deps.cwd, ".qawolf", ".gitignore");
|
|
185464
185591
|
await writeWithPrompt(ctx, configPath, qawolfConfigTs, opts.yes, deps);
|
|
185465
185592
|
await writeWithPrompt(ctx, flowPath, exampleFlowTs, opts.yes, deps);
|
|
185466
185593
|
await writeWithPrompt(ctx, gitignorePath, qawolfGitignore, opts.yes, deps);
|
|
@@ -185479,7 +185606,7 @@ async function writeWithPrompt(ctx, filePath, content, yes, deps) {
|
|
|
185479
185606
|
return;
|
|
185480
185607
|
}
|
|
185481
185608
|
}
|
|
185482
|
-
await deps.fs.mkdir(
|
|
185609
|
+
await deps.fs.mkdir(dirname14(filePath), { recursive: true });
|
|
185483
185610
|
await deps.fs.writeFile(filePath, content);
|
|
185484
185611
|
ctx.ui.step(initMessages.createdFile(relPath));
|
|
185485
185612
|
}
|
|
@@ -185498,7 +185625,7 @@ Examples:
|
|
|
185498
185625
|
import { parseExecutionTarget as parseExecutionTarget4 } from "@qawolf/flow-targets";
|
|
185499
185626
|
|
|
185500
185627
|
// src/domains/install/android/avd.ts
|
|
185501
|
-
import { join as
|
|
185628
|
+
import { join as join43 } from "node:path";
|
|
185502
185629
|
async function installAvds(ctx, specs, deps) {
|
|
185503
185630
|
const sdkManagerPath = sdkManagerBin(deps.androidHome, deps.platform);
|
|
185504
185631
|
const avdManagerPath = avdManagerBin(deps.androidHome, deps.platform);
|
|
@@ -185508,7 +185635,7 @@ async function installAvds(ctx, specs, deps) {
|
|
|
185508
185635
|
if (versionResult.exitCode !== 0) {
|
|
185509
185636
|
throw new Error(installMessages.android.sdkmanagerNotFound(sdkManagerPath));
|
|
185510
185637
|
}
|
|
185511
|
-
const licenseFile =
|
|
185638
|
+
const licenseFile = join43(deps.androidHome, "licenses", "android-sdk-license");
|
|
185512
185639
|
if (deps.checkExists(licenseFile)) {
|
|
185513
185640
|
ctx.ui.info(installMessages.android.licensesAlreadyAccepted);
|
|
185514
185641
|
} else {
|
|
@@ -185523,7 +185650,7 @@ async function installAvds(ctx, specs, deps) {
|
|
|
185523
185650
|
for (const spec of specs) {
|
|
185524
185651
|
if (installedImages.has(spec.systemImage))
|
|
185525
185652
|
continue;
|
|
185526
|
-
const systemImg =
|
|
185653
|
+
const systemImg = join43(deps.androidHome, ...spec.systemImage.split(";"), "system.img");
|
|
185527
185654
|
if (deps.checkExists(systemImg)) {
|
|
185528
185655
|
ctx.ui.info(installMessages.android.systemImageAlreadyInstalled(spec.systemImage));
|
|
185529
185656
|
installedImages.add(spec.systemImage);
|
|
@@ -185970,6 +186097,14 @@ function flagUsage(field, kind) {
|
|
|
185970
186097
|
return `${name} <KEY=VALUE...>`;
|
|
185971
186098
|
return `${name} <value>`;
|
|
185972
186099
|
}
|
|
186100
|
+
function describeFlag(schema) {
|
|
186101
|
+
const description = schema.description ?? "";
|
|
186102
|
+
const values = schema.enum ?? schema.items?.enum;
|
|
186103
|
+
if (!values?.length)
|
|
186104
|
+
return description;
|
|
186105
|
+
const choices = `One of: ${values.join(", ")}`;
|
|
186106
|
+
return description ? `${description} ${choices}` : choices;
|
|
186107
|
+
}
|
|
185973
186108
|
function buildFlagSpecs(inputSchema) {
|
|
185974
186109
|
const jsonSchema = exports_external.toJSONSchema(inputSchema, {
|
|
185975
186110
|
io: "input"
|
|
@@ -185985,7 +186120,7 @@ function buildFlagSpecs(inputSchema) {
|
|
|
185985
186120
|
flags.push({
|
|
185986
186121
|
field,
|
|
185987
186122
|
flag: flagUsage(field, kind.kind),
|
|
185988
|
-
description: fieldSchema
|
|
186123
|
+
description: describeFlag(fieldSchema),
|
|
185989
186124
|
required: result.shape.required.has(field),
|
|
185990
186125
|
kind: kind.kind
|
|
185991
186126
|
});
|
|
@@ -186092,7 +186227,7 @@ async function handlePublicApiCommand(ctx, spec, options) {
|
|
|
186092
186227
|
}
|
|
186093
186228
|
|
|
186094
186229
|
// src/domains/publicApi/skippedContracts.ts
|
|
186095
|
-
var
|
|
186230
|
+
var skippedContractNames = new Set([
|
|
186096
186231
|
"flow.list",
|
|
186097
186232
|
"runner.evaluateSnippet",
|
|
186098
186233
|
"runner.launch",
|
|
@@ -186102,13 +186237,6 @@ var handWrittenContractNames = new Set([
|
|
|
186102
186237
|
"runner.stop",
|
|
186103
186238
|
"runner.takeScreenshot"
|
|
186104
186239
|
]);
|
|
186105
|
-
var unexpressibleContractNames = new Set([
|
|
186106
|
-
"issue.update"
|
|
186107
|
-
]);
|
|
186108
|
-
var skippedContractNames = new Set([
|
|
186109
|
-
...handWrittenContractNames,
|
|
186110
|
-
...unexpressibleContractNames
|
|
186111
|
-
]);
|
|
186112
186240
|
|
|
186113
186241
|
// src/commands/publicApi/index.ts
|
|
186114
186242
|
var groupDescriptions = {
|
|
@@ -186574,7 +186702,7 @@ async function handleRunnerScreenshot(ctx, options, deps) {
|
|
|
186574
186702
|
}
|
|
186575
186703
|
|
|
186576
186704
|
// src/shell/interactiveRunner/collectRunFiles.ts
|
|
186577
|
-
import { join as
|
|
186705
|
+
import { join as join44 } from "node:path";
|
|
186578
186706
|
async function collectRunFiles(options) {
|
|
186579
186707
|
const paths = await glob(shippableRunFileExtensions.map((extension) => `**/*${extension}`), {
|
|
186580
186708
|
cwd: options.cwd,
|
|
@@ -186584,7 +186712,7 @@ async function collectRunFiles(options) {
|
|
|
186584
186712
|
const shippable = paths.filter(isShippableRunFilePath).sort();
|
|
186585
186713
|
const files = {};
|
|
186586
186714
|
const read = batchMap(shippable, async (path9) => ({
|
|
186587
|
-
content: await options.fs.readFile(
|
|
186715
|
+
content: await options.fs.readFile(join44(options.cwd, path9)),
|
|
186588
186716
|
path: path9
|
|
186589
186717
|
}), flowBatchSize);
|
|
186590
186718
|
for await (const file2 of read)
|
|
@@ -186599,7 +186727,7 @@ function makeRunnerId() {
|
|
|
186599
186727
|
}
|
|
186600
186728
|
|
|
186601
186729
|
// src/shell/interactiveRunner/runnerStore.ts
|
|
186602
|
-
import { join as
|
|
186730
|
+
import { join as join45 } from "node:path";
|
|
186603
186731
|
var storeFileName = "runner.json";
|
|
186604
186732
|
var storeSchema = exports_external.object({ defaultRunnerId: exports_external.string().optional() });
|
|
186605
186733
|
function parseJson(text3) {
|
|
@@ -186610,8 +186738,8 @@ function parseJson(text3) {
|
|
|
186610
186738
|
}
|
|
186611
186739
|
}
|
|
186612
186740
|
function makeRunnerStore(options) {
|
|
186613
|
-
const directory =
|
|
186614
|
-
const path9 =
|
|
186741
|
+
const directory = join45(options.cwd, qawolfDir);
|
|
186742
|
+
const path9 = join45(directory, storeFileName);
|
|
186615
186743
|
let pendingWrites = 0;
|
|
186616
186744
|
const nextPendingPath = () => `${path9}.${process.pid}.${++pendingWrites}.tmp`;
|
|
186617
186745
|
return {
|
|
@@ -186638,7 +186766,7 @@ function makeRunnerStore(options) {
|
|
|
186638
186766
|
}
|
|
186639
186767
|
|
|
186640
186768
|
// src/shell/interactiveRunner/writeScreenshot.ts
|
|
186641
|
-
import { dirname as
|
|
186769
|
+
import { dirname as dirname15 } from "node:path";
|
|
186642
186770
|
var jpegStartOfImage = [255, 216, 255];
|
|
186643
186771
|
async function writeScreenshot(options) {
|
|
186644
186772
|
const bytes = Buffer.from(options.imageJpegBase64, "base64");
|
|
@@ -186646,7 +186774,7 @@ async function writeScreenshot(options) {
|
|
|
186646
186774
|
return { ok: false, reason: "not-a-jpeg" };
|
|
186647
186775
|
}
|
|
186648
186776
|
try {
|
|
186649
|
-
await options.fs.mkdir(
|
|
186777
|
+
await options.fs.mkdir(dirname15(options.path), { recursive: true });
|
|
186650
186778
|
await options.fs.writeFile(options.path, bytes);
|
|
186651
186779
|
} catch (error51) {
|
|
186652
186780
|
return { detail: errorMessage(error51), ok: false, reason: "unwritable" };
|
|
@@ -187284,4 +187412,4 @@ createProgram({ signals }).parseAsync().catch(() => {
|
|
|
187284
187412
|
process.exitCode = 1;
|
|
187285
187413
|
}).finally(() => flushAndExit(typeof process.exitCode === "number" ? process.exitCode : 0));
|
|
187286
187414
|
|
|
187287
|
-
//# debugId=
|
|
187415
|
+
//# debugId=0187A36B3165B15F64756E2164756E21
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qawolf/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.2",
|
|
4
4
|
"description": "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"automation",
|
|
@@ -68,7 +68,6 @@
|
|
|
68
68
|
"appium": "2.11.3",
|
|
69
69
|
"commander": "14.0.3",
|
|
70
70
|
"env-paths": "4.0.0",
|
|
71
|
-
"expect-webdriverio": "5.6.5",
|
|
72
71
|
"picomatch": "4.0.4",
|
|
73
72
|
"pino": "10.3.1",
|
|
74
73
|
"playwright": "1.62.0",
|
|
@@ -85,6 +84,7 @@
|
|
|
85
84
|
"@types/bun": "1.3.14",
|
|
86
85
|
"@types/picomatch": "4.0.3",
|
|
87
86
|
"appium-uiautomator2-driver": "4.2.9",
|
|
87
|
+
"expect-webdriverio": "5.6.5",
|
|
88
88
|
"knip": "6.16.1",
|
|
89
89
|
"oxfmt": "0.54.0",
|
|
90
90
|
"oxlint": "1.69.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: qawolf-cli
|
|
3
|
-
description: Manage QA Wolf through the qawolf CLI. Use when asked
|
|
3
|
+
description: Manage QA Wolf through the qawolf CLI. Use when asked to create, update, or list coverage requests, bug reports, or maintenance reports; start a run of flows or tags on the QA Wolf platform or read a run's results; list, set, or delete environment variables; manage environments, flows, or tags; request automation of draft flows; run or list flows locally; authenticate; install the local runtime; or drive a live cloud browser (launch a runner, screenshot it, click and type on it, read its recorder) from a shell.
|
|
4
4
|
license: Apache-2.0
|
|
5
5
|
compatibility: Requires the qawolf CLI on PATH. Install it from @qawolf/cli or use a standalone binary from GitHub Releases.
|
|
6
6
|
---
|
|
@@ -72,6 +72,15 @@ each printed line from the payload alone to the whole envelope (`sequence`,
|
|
|
72
72
|
`recordedAt`, `payload`). Both are JSON. Pass it when you want to page by
|
|
73
73
|
sequence, omit it when you want the payloads themselves.
|
|
74
74
|
|
|
75
|
+
A `--json` response shows you most of its own shape, so read it first.
|
|
76
|
+
|
|
77
|
+
`qawolf run get` is the exception worth reading about before you use it. Its
|
|
78
|
+
artifact URLs expire, its failure fields are absent from a passing run, and its
|
|
79
|
+
`traceUrl` downloads a Playwright trace that you can read as JSON without
|
|
80
|
+
opening the trace viewer. **Read
|
|
81
|
+
[`references/run-results.md`](references/run-results.md) before reporting on a
|
|
82
|
+
run's outcome or opening its trace.**
|
|
83
|
+
|
|
75
84
|
## Safety: reads vs writes
|
|
76
85
|
|
|
77
86
|
Read commands do not change team data, but some have operational effects noted
|
|
@@ -127,6 +136,7 @@ current branch.
|
|
|
127
136
|
| `qawolf issue create` | write | Create a bug or coverage request issue for the caller's team. Maintenance issues cannot be created through the public API. |
|
|
128
137
|
| `qawolf issue find` | read | List the team's bug reports, maintenance reports, or coverage requests, newest first. |
|
|
129
138
|
| `qawolf issue get` | read | Get an issue by id. |
|
|
139
|
+
| `qawolf issue update` | write | Update an issue owned by the caller's team. Omitted fields remain unchanged. |
|
|
130
140
|
| `qawolf run create` | write | Create a run for the selected flows and/or tags in an environment. |
|
|
131
141
|
| `qawolf run find` | read | List an environment's recent runs, newest first. |
|
|
132
142
|
| `qawolf run get` | read | Get a run's status, per-flow results, and links. |
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Reading a run's results
|
|
2
|
+
|
|
3
|
+
How to use what `qawolf run get --run-id <id> --json` returns, and how to read
|
|
4
|
+
the Playwright trace it links to.
|
|
5
|
+
|
|
6
|
+
Call `run get` with `--json` and you see most of the response immediately. Read
|
|
7
|
+
this file for the parts a single response cannot show you: fields that appear
|
|
8
|
+
only when something fails, rules about the artifact URLs, and how to read a
|
|
9
|
+
trace without opening the trace viewer.
|
|
10
|
+
|
|
11
|
+
## The shape
|
|
12
|
+
|
|
13
|
+
A run holds flows, a flow holds attempts, and artifacts hang off an attempt:
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
run
|
|
17
|
+
└── flows[]
|
|
18
|
+
├── failure only when the flow failed
|
|
19
|
+
└── attempts[] oldest first
|
|
20
|
+
├── logsUrl
|
|
21
|
+
├── traceUrl
|
|
22
|
+
└── videoUrl
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`runId` in the response is canonical and can differ from the id you asked for.
|
|
26
|
+
Use the returned value for follow-up calls.
|
|
27
|
+
|
|
28
|
+
Poll `status` until it reaches `passed`, `failed` or `canceled`. The other
|
|
29
|
+
values mean the run is still going.
|
|
30
|
+
|
|
31
|
+
## Fields a passing run does not show you
|
|
32
|
+
|
|
33
|
+
- `flows[].failure` exists only when a flow failed. Every flow passing means
|
|
34
|
+
there is no failure object at all, so its diagnosis and issue id are invisible
|
|
35
|
+
until something breaks. Do not conclude the field does not exist.
|
|
36
|
+
- `git` is populated only when a deploy notification started the run. A run
|
|
37
|
+
started manually or with `run create` has an empty object here.
|
|
38
|
+
- An attempt's `kind` and `status` select which other fields it has. Only
|
|
39
|
+
automated attempts that reached a verdict carry artifact URLs; canceled
|
|
40
|
+
attempts and manual Wolf Browser attempts carry none.
|
|
41
|
+
- A flow that passed after a retry still lists its failed attempts. Read the
|
|
42
|
+
last attempt for the outcome, and the earlier ones to see what went wrong.
|
|
43
|
+
|
|
44
|
+
## Artifact URLs
|
|
45
|
+
|
|
46
|
+
Each automated attempt links `logsUrl` (execution logs), `videoUrl` (screen
|
|
47
|
+
recording) and `traceUrl` (a Playwright `trace.zip`).
|
|
48
|
+
|
|
49
|
+
1. They are signed URLs with a limited life. The contract guarantees at least a
|
|
50
|
+
day. Call `run get` again for fresh ones instead of storing them; a stored
|
|
51
|
+
URL becomes a dead link.
|
|
52
|
+
2. A URL can return 404 when that attempt did not produce that artifact. Handle
|
|
53
|
+
the 404 rather than treating the URL's presence as a guarantee of content.
|
|
54
|
+
3. Download with a plain HTTP GET. The signature is in the URL, so no
|
|
55
|
+
authentication header is needed and no QA Wolf credentials are involved.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
qawolf run get --run-id "$RUN_ID" --json \
|
|
59
|
+
| jq -r '.flows[].attempts[-1].traceUrl // empty' \
|
|
60
|
+
| head -1 \
|
|
61
|
+
| xargs -r curl -sS -o trace.zip
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Reading the Playwright trace
|
|
65
|
+
|
|
66
|
+
The usual advice is `npx playwright show-trace trace.zip`, which opens a
|
|
67
|
+
browser window. That is useless in a shell and unnecessary: the zip holds
|
|
68
|
+
newline-delimited JSON files, and reading them directly is faster than
|
|
69
|
+
downloading a viewer.
|
|
70
|
+
|
|
71
|
+
The zip holds `trace.trace` (the events), `trace.network` (one request and
|
|
72
|
+
response per line) and a `resources/` directory of screencast frames. The
|
|
73
|
+
frames are most of the size, so extract only what you need.
|
|
74
|
+
|
|
75
|
+
### The event types
|
|
76
|
+
|
|
77
|
+
Every line of `trace.trace` is one JSON object with a `type`:
|
|
78
|
+
|
|
79
|
+
- `before` — a call started. Carries `callId`, `startTime`, `class`, `method`
|
|
80
|
+
and `params`. `params.selector` or `params.url` is usually the target.
|
|
81
|
+
- `after` — that call finished. Matched to its `before` by `callId`. Carries
|
|
82
|
+
`endTime` and `result`, and an `error` when the call failed.
|
|
83
|
+
- `console` — a browser console message, with `messageType` and `text`.
|
|
84
|
+
- `log` — Playwright's own progress notes for a call.
|
|
85
|
+
- `screencast-frame`, `frame-snapshot` — the filmstrip and DOM snapshots the
|
|
86
|
+
viewer renders. Usually not worth reading directly.
|
|
87
|
+
|
|
88
|
+
Two details cost time if you miss them:
|
|
89
|
+
|
|
90
|
+
- **Times are monotonic milliseconds, not seconds.** A `goto` whose `startTime`
|
|
91
|
+
and `endTime` differ by `161.6` took 161 milliseconds. Subtract the smallest
|
|
92
|
+
`startTime` to get an offset from the start of the trace.
|
|
93
|
+
- **Return values use a serialized envelope.** `{"value":{"s":"passed"}}` is
|
|
94
|
+
the string `passed`, `{"n":640}` is the number `640`, and `{"o":[...]}` is an
|
|
95
|
+
object as a list of key and value pairs.
|
|
96
|
+
|
|
97
|
+
### A worked example
|
|
98
|
+
|
|
99
|
+
Pairing `before` with `after` gives an action timeline with durations and
|
|
100
|
+
failures:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
import json, sys, zipfile
|
|
104
|
+
|
|
105
|
+
with zipfile.ZipFile(sys.argv[1]) as z:
|
|
106
|
+
events = [json.loads(line) for line in z.read("trace.trace").decode().splitlines()]
|
|
107
|
+
|
|
108
|
+
starts = {e["callId"]: e for e in events if e["type"] == "before"}
|
|
109
|
+
ends = {e["callId"]: e for e in events if e["type"] == "after"}
|
|
110
|
+
t0 = min(e["startTime"] for e in starts.values())
|
|
111
|
+
|
|
112
|
+
for call_id, before in starts.items():
|
|
113
|
+
after = ends.get(call_id, {})
|
|
114
|
+
params = before.get("params", {})
|
|
115
|
+
target = params.get("selector") or params.get("url") or ""
|
|
116
|
+
error = after.get("error")
|
|
117
|
+
print(
|
|
118
|
+
f'{(before["startTime"] - t0) / 1000:7.2f}s'
|
|
119
|
+
f' {after.get("endTime", before["startTime"]) - before["startTime"]:7.1f}ms'
|
|
120
|
+
f' {before["class"]}.{before["method"]:<18} {target[:40]}'
|
|
121
|
+
f'{" FAILED: " + json.dumps(error)[:60] if error else ""}'
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
for e in events:
|
|
125
|
+
if e["type"] == "console" and e["messageType"] == "error":
|
|
126
|
+
print(f'console error: {e["text"][:70]}')
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
It prints one line per call, in order:
|
|
130
|
+
|
|
131
|
+
```text
|
|
132
|
+
0.00s 161.6ms Frame.goto https://example.com/
|
|
133
|
+
0.17s 28.3ms Frame.waitForSelector #screen
|
|
134
|
+
0.20s 4.2ms Frame.innerText #fps_stats
|
|
135
|
+
console error: Failed to load resource: the server responded with a status of 404 ()
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
To find why an attempt failed, read the last `after` that carries an `error`,
|
|
139
|
+
then the `console` errors near it in time. To see what the page did, read
|
|
140
|
+
`trace.network`.
|
|
141
|
+
|
|
142
|
+
## Response fields
|
|
143
|
+
|
|
144
|
+
Every documented field of the `run.get` response. `[]` marks an array, so
|
|
145
|
+
`flows[].attempts[].traceUrl` is the trace URL of one attempt of one flow.
|
|
146
|
+
|
|
147
|
+
<!-- fields:start — generated by `bun run generate`, do not edit -->
|
|
148
|
+
|
|
149
|
+
- `completedAt` — When the run finished executing. Absent while queued or running, and also absent for a terminal run that never completed execution (e.g. every flow was canceled or skipped).
|
|
150
|
+
- `git` — The branch and commit under test. The fields are present when a deploy notification started the run, and absent for runs started another way, for example manually or with run.create.
|
|
151
|
+
- `git.commitUrl` — Link to the commit on the code host.
|
|
152
|
+
- `runId` — The run this response describes. Treat it as canonical: it can differ from the id you asked for. A deploy notification returns a run id before the run exists, and if a second notification for the same commit is folded into an earlier run, that id resolves to the earlier run instead.
|
|
153
|
+
- `status` — One of: queued, running, passed, failed, canceled
|
|
154
|
+
- `flows` — The run's flows, ordered alphabetically by name.
|
|
155
|
+
- `flows[].attempts` — The flow's finished execution attempts, oldest first, including manual Wolf Browser attempts. Present once at least one attempt has finished, so a flow that passed after retries also lists its failed attempts. Artifact URLs appear only on automated attempts that reached a verdict, stay valid for at least a day (call run.get again for fresh ones), and can return 404 when the attempt did not produce that artifact.
|
|
156
|
+
- `flows[].attempts[].logsUrl` — Signed URL for the attempt's execution logs.
|
|
157
|
+
- `flows[].attempts[].traceUrl` — Signed URL for the attempt's Playwright trace (a trace.zip; open it with `npx playwright show-trace`).
|
|
158
|
+
- `flows[].attempts[].videoUrl` — Signed URL for the attempt's screen recording.
|
|
159
|
+
- `flows[].attempts[].kind` — One of: automated, manual
|
|
160
|
+
- `flows[].attempts[].startedAt` — Absent when the attempt failed before it could start.
|
|
161
|
+
- `flows[].attempts[].status` — One of: passed, failed, canceled
|
|
162
|
+
- `flows[].failure.diagnosis` — QA Wolf's investigation verdict for the failure: `bug` means the application is broken, `maintenance` means the test needed an update and the failure does not indicate an application problem. Absent until the investigation reaches a verdict. Pass issueId to issue.get for details.
|
|
163
|
+
- `flows[].failure.diagnosis.issueId` — The id of the issue.
|
|
164
|
+
- `flows[].failure.diagnosis.type` — One of: bug, maintenance
|
|
165
|
+
- `flows[].flowId` — The id of the flow.
|
|
166
|
+
- `flows[].status` — One of: failed, queued, running, passed, canceled
|
|
167
|
+
- `url` — Absolute URL of the run page.
|
|
168
|
+
|
|
169
|
+
<!-- fields:end -->
|