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