@qawolf/cli 1.16.0 → 1.17.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 +503 -109
- package/package.json +2 -2
- package/skills/qawolf-cli/SKILL.md +5 -1
- package/skills/qawolf-cli/references/runner.md +80 -6
package/dist/cli.js
CHANGED
|
@@ -326123,7 +326123,18 @@ var interactMessages = {
|
|
|
326123
326123
|
packageInstalled: (name, version) => `Installed ${name}@${version} into the runner's live run.`,
|
|
326124
326124
|
packageJsonUnreadable: (reason) => `package.json could not be read because ${reason}. Fix it and try again.`,
|
|
326125
326125
|
installFailed: (name, version, errorMessage2) => `npm could not install ${name}@${version}${errorMessage2 === undefined ? "" : `. ${errorMessage2}`}. Check the name and the version; waiting will not change this.`,
|
|
326126
|
-
importAnsweredUnknown: (failureReason) => `The runner answered "${failureReason}", which this version of the CLI does not know how to report. Upgrade with npm install -g @qawolf/cli
|
|
326126
|
+
importAnsweredUnknown: (failureReason) => `The runner answered "${failureReason}", which this version of the CLI does not know how to report. Upgrade with npm install -g @qawolf/cli.`,
|
|
326127
|
+
highlightCleared: "Cleared the runner's highlight. Its next screenshot shows the page undrawn on.",
|
|
326128
|
+
highlightMatched: (matchCount, targetPage) => `Highlighted ${pluralize(matchCount, "element", "elements")}${targetPage === undefined ? "" : ` on ${targetPage}`}. The highlight stays until it is replaced or cleared, so take a screenshot to see it.`,
|
|
326129
|
+
highlightMatchedNothing: (selector) => `The page read "${selector}" as a selector and nothing matched it, so nothing is highlighted. The selector is well formed, so this is the page rather than the syntax: check what is actually on it with qawolf runner inspect page-html.`,
|
|
326130
|
+
highlightSelectorInvalid: (selector) => `The page could not read "${selector}" as a selector, so nothing was highlighted. This is the syntax rather than the page, and retrying will not change it.`,
|
|
326131
|
+
highlightNoAnswer: "The page did not answer, so nothing is highlighted. A highlight runs inside the page, so a page that is gone or part-way through navigating does not answer at all rather than answering slowly. Take a screenshot to see where the runner is, then try again.",
|
|
326132
|
+
runnerCannotHighlightSelectors: "This runner has no browser to draw on, so there is nothing to highlight. Retrying will never help: launch a playwright runner instead.",
|
|
326133
|
+
highlightAnsweredUnknown: (failureReason) => `The runner answered "${failureReason}", which this version of the CLI does not know how to report. Upgrade with npm install -g @qawolf/cli.`,
|
|
326134
|
+
snapshotPromoted: (screenshotPath, baselinePath) => `Promoted ${screenshotPath} to the baseline at ${baselinePath}.`,
|
|
326135
|
+
snapshotNotFound: (screenshotPath) => `The run wrote no screenshot at "${screenshotPath}", so nothing was changed. The two paths have to be the ones an image diff reported: read them from the image-diff-artifact entry on qawolf runner events run-events.`,
|
|
326136
|
+
runnerCannotPromoteSnapshots: "This runner stores no screenshots, so it holds no baseline to replace. Retrying will never help.",
|
|
326137
|
+
promoteSnapshotAnsweredUnknown: (failureReason) => `The runner answered "${failureReason}", which this version of the CLI does not know how to report. Upgrade with npm install -g @qawolf/cli.`
|
|
326127
326138
|
};
|
|
326128
326139
|
|
|
326129
326140
|
// src/core/messages/interactiveRunner/lifecycle.ts
|
|
@@ -326138,7 +326149,9 @@ var lifecycleMessages = {
|
|
|
326138
326149
|
launched: (id) => `Launched runner ${id}.`,
|
|
326139
326150
|
launchedForCommand: (id) => `No runner was given, so launched ${id} for this command. Its browser is fresh: nothing has been run on it and nothing is signed in. It bills until it is terminated or idles out, so terminate it with qawolf runner terminate --runner ${id} when you are done.`,
|
|
326140
326151
|
noRunnerIdForImport: `${noRunnerId} An install also needs a live run to go into, which a runner gets from qawolf runner run.`,
|
|
326152
|
+
noRunnerIdForHighlight: `${noRunnerId} Highlighting also needs a live page to draw on, which a runner gets from its first run: run a flow on it with qawolf runner run.`,
|
|
326141
326153
|
noRunnerIdForInspect: `${noRunnerId} Inspecting also needs a page, which a runner gets from its first run: run a flow on it with qawolf runner run.`,
|
|
326154
|
+
noRunnerIdForPromoteSnapshot: `${noRunnerId} A baseline is replaced on the runner that produced the screenshot, so it has to be the runner the diff came from.`,
|
|
326142
326155
|
noRunnerIdForScreenshot: `${noRunnerId} A screenshot also needs a screen, which a runner gets from its first run: run a flow on it with qawolf runner run.`,
|
|
326143
326156
|
noRunnerId,
|
|
326144
326157
|
wasNotRunning: (id) => `Runner ${id} was not running.`,
|
|
@@ -326152,6 +326165,8 @@ var lifecycleMessages = {
|
|
|
326152
326165
|
var runMessages = {
|
|
326153
326166
|
envFileUnparseable: (reason) => `The --env-file could not be read. ${reason}`,
|
|
326154
326167
|
envFileUnreadable: (path) => `No env file at ${path}. Pass a path that exists, or drop --env-file to send no environment.`,
|
|
326168
|
+
envIdBlank: "--env-id was given nothing. Pass the id or alias of a QA Wolf environment, or drop the flag to send no environment.",
|
|
326169
|
+
envIdWithEnvFile: "--env-id and --env-file both give the run its environment, so only one may be passed. Use --env-id for a QA Wolf environment, which QA Wolf reads itself, or --env-file for a dotenv file on this machine.",
|
|
326155
326170
|
fileNotCollected: (path) => `"${path}" is not one of the files that travel to a runner. It must be inside the current directory and end in .ts, .tsx, .js, .mjs, .cjs or .json.`,
|
|
326156
326171
|
fileUnreadable: (path, reason) => `${path} is one of the files that travel to a runner, but it could not be read: ${reason}. Move it out of the current directory, or make it readable.`,
|
|
326157
326172
|
filesUnreadable: (reason) => `One of the files that travel to a runner could not be read: ${reason}.`,
|
|
@@ -342275,7 +342290,7 @@ function refusalForRunEnvironmentVariableName(name) {
|
|
|
342275
342290
|
return;
|
|
342276
342291
|
}
|
|
342277
342292
|
var maxRunEnvironmentVariables = 200;
|
|
342278
|
-
var maxRunEnvironmentValueLength =
|
|
342293
|
+
var maxRunEnvironmentValueLength = 16 * 1024;
|
|
342279
342294
|
var runEnvironmentSchema = exports_external.record(exports_external.string(), exports_external.string().max(maxRunEnvironmentValueLength)).superRefine((environment, refinementContext) => {
|
|
342280
342295
|
for (const name of Object.keys(environment)) {
|
|
342281
342296
|
const refusal = refusalForRunEnvironmentVariableName(name);
|
|
@@ -342384,6 +342399,49 @@ var makeInspectOnRunnerContract = () => {
|
|
|
342384
342399
|
output
|
|
342385
342400
|
};
|
|
342386
342401
|
};
|
|
342402
|
+
// node_modules/@qawolf/api-contracts/dist/v1/runner/inspectMobileShapes.js
|
|
342403
|
+
var boundsSchema = exports_external.object({
|
|
342404
|
+
bottom: exports_external.number(),
|
|
342405
|
+
left: exports_external.number(),
|
|
342406
|
+
right: exports_external.number(),
|
|
342407
|
+
top: exports_external.number()
|
|
342408
|
+
});
|
|
342409
|
+
var pointSchema = exports_external.object({ x: exports_external.number(), y: exports_external.number() });
|
|
342410
|
+
var selectorSchema = exports_external.object({
|
|
342411
|
+
strategy: exports_external.union([
|
|
342412
|
+
exports_external.object({
|
|
342413
|
+
name: exports_external.literal("xpath"),
|
|
342414
|
+
type: exports_external.enum(["unique", "full-path", "sibling-of", "parent-of"])
|
|
342415
|
+
}),
|
|
342416
|
+
exports_external.object({ name: exports_external.literal("ios-predicate") }),
|
|
342417
|
+
exports_external.object({
|
|
342418
|
+
hostSelector: exports_external.string(),
|
|
342419
|
+
innerSelector: exports_external.string(),
|
|
342420
|
+
name: exports_external.literal("shadow")
|
|
342421
|
+
})
|
|
342422
|
+
]),
|
|
342423
|
+
value: exports_external.string()
|
|
342424
|
+
});
|
|
342425
|
+
var elementSchema = exports_external.object({
|
|
342426
|
+
attributes: exports_external.record(exports_external.string(), exports_external.string()),
|
|
342427
|
+
bounds: boundsSchema.optional().describe("The element's on-screen rectangle, absent off-screen."),
|
|
342428
|
+
center: pointSchema.optional().describe("Derived from `bounds`; absent wherever `bounds` is."),
|
|
342429
|
+
selectors: selectorSchema.array().describe("Ways to address this element again, most reliable first."),
|
|
342430
|
+
tag: exports_external.string()
|
|
342431
|
+
});
|
|
342432
|
+
var basePageSourceNodeSchema = exports_external.object({
|
|
342433
|
+
attributes: exports_external.object({
|
|
342434
|
+
bounds: boundsSchema.optional(),
|
|
342435
|
+
rest: exports_external.record(exports_external.string(), exports_external.string()).optional()
|
|
342436
|
+
}).optional(),
|
|
342437
|
+
selectors: selectorSchema.array(),
|
|
342438
|
+
tag: exports_external.string()
|
|
342439
|
+
});
|
|
342440
|
+
var pageSourceNodeSchema = basePageSourceNodeSchema.extend({
|
|
342441
|
+
children: exports_external.lazy(() => exports_external.union([pageSourceNodeSchema, exports_external.string()]).array()).optional().describe("A string child is a text node; there is no element to select.")
|
|
342442
|
+
});
|
|
342443
|
+
var orientationSchema = exports_external.enum(["PORTRAIT", "LANDSCAPE"]);
|
|
342444
|
+
|
|
342387
342445
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/outcome.js
|
|
342388
342446
|
function makeRunnerFailureSchema(reasons) {
|
|
342389
342447
|
return exports_external.object({
|
|
@@ -342392,6 +342450,100 @@ function makeRunnerFailureSchema(reasons) {
|
|
|
342392
342450
|
});
|
|
342393
342451
|
}
|
|
342394
342452
|
|
|
342453
|
+
// node_modules/@qawolf/api-contracts/dist/v1/runner/inspectMobile.js
|
|
342454
|
+
var maxElementTextLength = 500;
|
|
342455
|
+
var inspectMobileRequestSchema = exports_external.discriminatedUnion("what", [
|
|
342456
|
+
exports_external.object({
|
|
342457
|
+
what: exports_external.literal("session").describe("The status of the Appium session driving the device: ready and what it is, or why not.")
|
|
342458
|
+
}),
|
|
342459
|
+
exports_external.object({
|
|
342460
|
+
what: exports_external.literal("contexts").describe("The WebView contexts available on the device, and which one is current.")
|
|
342461
|
+
}),
|
|
342462
|
+
exports_external.object({
|
|
342463
|
+
context: exports_external.string().min(1).optional().describe("Read this context instead of whichever is current."),
|
|
342464
|
+
what: exports_external.literal("page").describe("The page source of the device's current context, as a tree.")
|
|
342465
|
+
}),
|
|
342466
|
+
exports_external.discriminatedUnion("by", [
|
|
342467
|
+
exports_external.object({
|
|
342468
|
+
by: exports_external.literal("point"),
|
|
342469
|
+
context: exports_external.string().min(1).optional(),
|
|
342470
|
+
what: exports_external.literal("elements"),
|
|
342471
|
+
x: exports_external.int().min(0).describe("Whole pixels on the device's own screen."),
|
|
342472
|
+
y: exports_external.int().min(0).describe("Whole pixels on the device's own screen.")
|
|
342473
|
+
}),
|
|
342474
|
+
exports_external.object({
|
|
342475
|
+
by: exports_external.literal("text"),
|
|
342476
|
+
context: exports_external.string().min(1).optional(),
|
|
342477
|
+
partial: exports_external.boolean().optional().describe("Match text containing this, rather than exactly this."),
|
|
342478
|
+
text: exports_external.string().min(1).max(maxElementTextLength),
|
|
342479
|
+
what: exports_external.literal("elements")
|
|
342480
|
+
})
|
|
342481
|
+
]).describe("Elements at a screen point, or elements carrying some text — never both at once.")
|
|
342482
|
+
]);
|
|
342483
|
+
var inspectMobileAnswerSchema = exports_external.discriminatedUnion("what", [
|
|
342484
|
+
exports_external.object({
|
|
342485
|
+
outcome: exports_external.literal("success"),
|
|
342486
|
+
session: exports_external.discriminatedUnion("type", [
|
|
342487
|
+
exports_external.object({
|
|
342488
|
+
deviceName: exports_external.string().optional(),
|
|
342489
|
+
platformName: exports_external.string(),
|
|
342490
|
+
sessionId: exports_external.string(),
|
|
342491
|
+
type: exports_external.literal("ready")
|
|
342492
|
+
}),
|
|
342493
|
+
exports_external.object({
|
|
342494
|
+
error: exports_external.string(),
|
|
342495
|
+
type: exports_external.literal("unreachable")
|
|
342496
|
+
}),
|
|
342497
|
+
exports_external.object({
|
|
342498
|
+
sessionCount: exports_external.number(),
|
|
342499
|
+
type: exports_external.literal("ambiguous")
|
|
342500
|
+
}),
|
|
342501
|
+
exports_external.object({ type: exports_external.literal("no-session") })
|
|
342502
|
+
]),
|
|
342503
|
+
what: exports_external.literal("session")
|
|
342504
|
+
}),
|
|
342505
|
+
exports_external.object({
|
|
342506
|
+
contexts: exports_external.string().array(),
|
|
342507
|
+
current: exports_external.string(),
|
|
342508
|
+
outcome: exports_external.literal("success"),
|
|
342509
|
+
what: exports_external.literal("contexts")
|
|
342510
|
+
}),
|
|
342511
|
+
exports_external.object({
|
|
342512
|
+
context: exports_external.string(),
|
|
342513
|
+
orientation: orientationSchema,
|
|
342514
|
+
outcome: exports_external.literal("success"),
|
|
342515
|
+
pageSource: pageSourceNodeSchema,
|
|
342516
|
+
what: exports_external.literal("page")
|
|
342517
|
+
}),
|
|
342518
|
+
exports_external.object({
|
|
342519
|
+
matches: elementSchema.array(),
|
|
342520
|
+
outcome: exports_external.literal("success"),
|
|
342521
|
+
what: exports_external.literal("elements")
|
|
342522
|
+
})
|
|
342523
|
+
]);
|
|
342524
|
+
var makeInspectMobileOnRunnerContract = () => {
|
|
342525
|
+
const input = exports_external.object({
|
|
342526
|
+
id: runnerIdSchema.describe("Id of the runner to inspect."),
|
|
342527
|
+
request: inspectMobileRequestSchema
|
|
342528
|
+
});
|
|
342529
|
+
const output = exports_external.discriminatedUnion("outcome", [
|
|
342530
|
+
inspectMobileAnswerSchema,
|
|
342531
|
+
makeRunnerFailureSchema([
|
|
342532
|
+
"runner-is-not-mobile",
|
|
342533
|
+
"screen-needs-a-run",
|
|
342534
|
+
"screen-not-ready",
|
|
342535
|
+
runnerUnreachableFailureReason
|
|
342536
|
+
])
|
|
342537
|
+
]);
|
|
342538
|
+
return {
|
|
342539
|
+
description: `Inspect one thing on a mobile interactive runner: the Appium session's status, the WebView contexts available, the current context's page source, or the elements at a point or carrying some text. Mobile only — a browser runner answers \`runner-is-not-mobile\`; call \`runner.inspect\` for a browser's equivalent surface instead. \`what: "session"\` always answers with the session's own status rather than \`screen-needs-a-run\`, since that is the question it exists to answer; the other three request kinds need a live session first and answer the same \`screen-needs-a-run\` or \`screen-not-ready\` outcomes \`runner.performAction\` and \`runner.takeScreenshot\` use, instead of reading anything when there is none. \`screen-needs-a-run\` means no Appium session has started on this runner yet — call \`runner.runFlow\` with a flow that opens one, then inspect again. \`screen-not-ready\` means the runner's Appium session exists but did not answer this instant, or more than one is somehow live — retry once; if it persists, relaunch the runner. \`runner-is-not-mobile\` means there is nothing here to inspect, and retrying will never help — launch an \`android\` or \`ios\` runner instead. ${retryableRunnerUnreachableDescription}`,
|
|
342540
|
+
input,
|
|
342541
|
+
kind: "read",
|
|
342542
|
+
name: "runner.inspectMobile",
|
|
342543
|
+
output
|
|
342544
|
+
};
|
|
342545
|
+
};
|
|
342546
|
+
|
|
342395
342547
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/journal.js
|
|
342396
342548
|
var journalEntrySchema = exports_external.object({
|
|
342397
342549
|
payload: exports_external.unknown(),
|
|
@@ -343090,6 +343242,43 @@ var makeEvaluateSnippetOnRunnerContract = () => {
|
|
|
343090
343242
|
};
|
|
343091
343243
|
};
|
|
343092
343244
|
|
|
343245
|
+
// node_modules/@qawolf/api-contracts/dist/v1/runner/highlightSelector.js
|
|
343246
|
+
var maxHighlightSelectorLength = 2000;
|
|
343247
|
+
var makeHighlightSelectorOnRunnerContract = () => {
|
|
343248
|
+
const input = exports_external.object({
|
|
343249
|
+
id: runnerIdSchema.describe("Id of the runner to highlight on."),
|
|
343250
|
+
selector: exports_external.string().max(maxHighlightSelectorLength).describe("The selector to highlight. An empty string clears whatever is highlighted.")
|
|
343251
|
+
});
|
|
343252
|
+
const output = exports_external.discriminatedUnion("outcome", [
|
|
343253
|
+
exports_external.object({
|
|
343254
|
+
matchCount: exports_external.number().int().gte(0).describe("How many elements the selector matched."),
|
|
343255
|
+
outcome: exports_external.literal("success"),
|
|
343256
|
+
selector: exports_external.string().describe("The selector the page matched on."),
|
|
343257
|
+
status: exports_external.literal(["valid", "invalid", "empty"]).describe("`valid` when the selector parsed and matched, `empty` when it parsed and matched nothing, `invalid` when the page could not read it as a selector."),
|
|
343258
|
+
targetPage: exports_external.string().optional().describe("Present when the match was on a page other than the default one.")
|
|
343259
|
+
}),
|
|
343260
|
+
exports_external.object({
|
|
343261
|
+
outcome: exports_external.literal("cleared")
|
|
343262
|
+
}),
|
|
343263
|
+
exports_external.object({
|
|
343264
|
+
errorMessage: exports_external.string().optional(),
|
|
343265
|
+
failureReason: exports_external.enum([
|
|
343266
|
+
"no-answer",
|
|
343267
|
+
"runner-cannot-highlight-selectors",
|
|
343268
|
+
"runner-unreachable"
|
|
343269
|
+
]),
|
|
343270
|
+
outcome: exports_external.literal("failure")
|
|
343271
|
+
})
|
|
343272
|
+
]);
|
|
343273
|
+
return {
|
|
343274
|
+
description: "Highlight the elements a selector matches on an interactive runner's live page, and answer how many it matched. The highlight stays until it is replaced or cleared, so it is visible in the next `runner.takeScreenshot` — which is the point, since a caller cannot see the runner's screen otherwise. Send an empty selector to clear, which answers `cleared`. `status` tells a selector that matched nothing (`empty`) from one the page could not parse (`invalid`), so a caller can tell a bad locator from a locator pointing at nothing. `no-answer` if the page did not answer in time: the highlight runs inside the page, so a page that is gone or mid-navigation does not answer slowly, it does not answer at all. `runner-cannot-highlight-selectors` on a runner with no browser to draw on. `runner-unreachable` if the runner could not be reached: it may still be starting, or it may have terminated after inactivity.",
|
|
343275
|
+
input,
|
|
343276
|
+
kind: "write",
|
|
343277
|
+
name: "runner.highlightSelector",
|
|
343278
|
+
output
|
|
343279
|
+
};
|
|
343280
|
+
};
|
|
343281
|
+
|
|
343093
343282
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/importPackage.js
|
|
343094
343283
|
var maxPackageNameLength = 214;
|
|
343095
343284
|
var maxPackageVersionLength = 256;
|
|
@@ -343153,6 +343342,34 @@ var makeTerminateRunnerContract = () => {
|
|
|
343153
343342
|
};
|
|
343154
343343
|
};
|
|
343155
343344
|
|
|
343345
|
+
// node_modules/@qawolf/api-contracts/dist/v1/runner/promoteSnapshot.js
|
|
343346
|
+
var maxSnapshotPathLength = 1024;
|
|
343347
|
+
var makePromoteSnapshotOnRunnerContract = () => {
|
|
343348
|
+
const input = exports_external.object({
|
|
343349
|
+
baselinePath: exports_external.string().min(1).max(maxSnapshotPathLength).describe("The baseline to replace, as named in the image diff."),
|
|
343350
|
+
id: runnerIdSchema.describe("Id of the runner holding the screenshot."),
|
|
343351
|
+
screenshotPath: exports_external.string().min(1).max(maxSnapshotPathLength).describe("The screenshot to promote, as named in the image diff.")
|
|
343352
|
+
});
|
|
343353
|
+
const output = exports_external.discriminatedUnion("outcome", [
|
|
343354
|
+
exports_external.object({ outcome: exports_external.literal("success") }),
|
|
343355
|
+
exports_external.object({
|
|
343356
|
+
failureReason: exports_external.enum([
|
|
343357
|
+
"runner-cannot-promote-snapshots",
|
|
343358
|
+
"runner-unreachable",
|
|
343359
|
+
"snapshot-not-found"
|
|
343360
|
+
]),
|
|
343361
|
+
outcome: exports_external.literal("failure")
|
|
343362
|
+
})
|
|
343363
|
+
]);
|
|
343364
|
+
return {
|
|
343365
|
+
description: "Accept a run's screenshot as the new baseline for an image diff, on the runner that produced it. The two paths are the ones the diff reported, which reach a caller as an `image-diff-artifact` entry on the runner's `run-events` journal stream. `snapshot-not-found` if the run wrote no screenshot at that path, which usually means the paths were not taken from a diff this run produced; nothing is changed, so correcting the path and repeating is safe. `runner-cannot-promote-snapshots` on a runner that stores no screenshots. `runner-unreachable` if the runner could not be reached: it may still be starting, or it may have terminated after inactivity. A promotion that lands is not undone by promoting again, so a retry after an unreachable answer is safe.",
|
|
343366
|
+
input,
|
|
343367
|
+
kind: "write",
|
|
343368
|
+
name: "runner.promoteSnapshot",
|
|
343369
|
+
output
|
|
343370
|
+
};
|
|
343371
|
+
};
|
|
343372
|
+
|
|
343156
343373
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/readJournal.js
|
|
343157
343374
|
var makeReadRunnerJournalContract = () => {
|
|
343158
343375
|
const input = readJournalRequestSchema.extend({
|
|
@@ -343184,14 +343401,18 @@ var unchangedFilesSchema = exports_external.record(exports_external.string(), ex
|
|
|
343184
343401
|
});
|
|
343185
343402
|
}
|
|
343186
343403
|
});
|
|
343187
|
-
var makeRunFlowOnRunnerContract = () => {
|
|
343404
|
+
var makeRunFlowOnRunnerContract = (ids) => {
|
|
343188
343405
|
const input = exports_external.object({
|
|
343189
343406
|
entryPointPath: runFilePathSchema.describe("Path of the flow file to run, as it appears in `files`."),
|
|
343190
343407
|
env: runEnvironmentSchema.optional().describe("Environment variables to make available to the run."),
|
|
343408
|
+
environmentId: ids.environmentRef.optional().describe("A QA Wolf environment whose variables the run is given, by id or alias. QA Wolf reads and decrypts them itself, so they never travel in the request and the count and length limits on `env` do not apply to them. Send this or `env`, not both."),
|
|
343191
343409
|
files: runFilesSchema.describe("Every file the run needs keyed by its path — the flow file, everything it imports, package.json and tsconfig.json. A runner holds no copy of your project, so what runs is exactly what is sent here."),
|
|
343192
343410
|
id: runnerIdSchema.describe("Id of the runner to run the flow on."),
|
|
343193
343411
|
selection: runSelectionSchema.optional().describe("Run only these lines, inside the page the runner is already on, instead of running the whole flow from a fresh browser. Omit to run the whole entry point."),
|
|
343194
343412
|
unchangedFiles: unchangedFilesSchema.optional().describe("Files this runner already holds from an earlier run, keyed by path with a hex SHA-256 of the content it should hold. Send this to make `files` only what changed. The runner refuses with `needs-full-sync` if it holds none of a referenced path, so it never runs something other than what was asked for. Omit it to send every file.")
|
|
343413
|
+
}).refine((request) => request.env === undefined || request.environmentId === undefined, {
|
|
343414
|
+
error: "Send env or environmentId, not both. An environment's variables are taken as they are stored, so there is no order in which the two would combine.",
|
|
343415
|
+
path: ["environmentId"]
|
|
343195
343416
|
}).refine((request) => Object.hasOwn(request.files, request.entryPointPath), {
|
|
343196
343417
|
error: "The entry point must be one of the files.",
|
|
343197
343418
|
path: ["files"]
|
|
@@ -343342,12 +343563,15 @@ var makeContractsV1 = (ids) => {
|
|
|
343342
343563
|
},
|
|
343343
343564
|
runner: {
|
|
343344
343565
|
evaluateSnippet: makeEvaluateSnippetOnRunnerContract(),
|
|
343566
|
+
highlightSelector: makeHighlightSelectorOnRunnerContract(),
|
|
343345
343567
|
importPackage: makeImportPackageOnRunnerContract(),
|
|
343346
343568
|
inspect: makeInspectOnRunnerContract(),
|
|
343569
|
+
inspectMobile: makeInspectMobileOnRunnerContract(),
|
|
343347
343570
|
launch: makeLaunchRunnerContract(),
|
|
343348
343571
|
performAction: makePerformActionOnRunnerContract(),
|
|
343572
|
+
promoteSnapshot: makePromoteSnapshotOnRunnerContract(),
|
|
343349
343573
|
readJournal: makeReadRunnerJournalContract(),
|
|
343350
|
-
runFlow: makeRunFlowOnRunnerContract(),
|
|
343574
|
+
runFlow: makeRunFlowOnRunnerContract(resolvedIds),
|
|
343351
343575
|
stopRun: makeStopRunOnRunnerContract(),
|
|
343352
343576
|
takeScreenshot: makeTakeScreenshotOnRunnerContract(),
|
|
343353
343577
|
terminate: makeTerminateRunnerContract()
|
|
@@ -345874,7 +346098,7 @@ function startUpdateCheck(deps) {
|
|
|
345874
346098
|
// package.json
|
|
345875
346099
|
var package_default = {
|
|
345876
346100
|
name: "@qawolf/cli",
|
|
345877
|
-
version: "1.
|
|
346101
|
+
version: "1.17.1",
|
|
345878
346102
|
description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
|
|
345879
346103
|
keywords: [
|
|
345880
346104
|
"automation",
|
|
@@ -345934,7 +346158,7 @@ var package_default = {
|
|
|
345934
346158
|
"@clack/prompts": "1.5.1",
|
|
345935
346159
|
"@napi-rs/keyring": "1.3.0",
|
|
345936
346160
|
"@oxc-node/core": "0.1.0",
|
|
345937
|
-
"@qawolf/api-contracts": "0.
|
|
346161
|
+
"@qawolf/api-contracts": "0.34.0",
|
|
345938
346162
|
"@qawolf/emails": "1.1.1",
|
|
345939
346163
|
"@qawolf/flow-targets": "1.0.0",
|
|
345940
346164
|
"@qawolf/flows": "0.1.4",
|
|
@@ -356368,10 +356592,13 @@ async function handlePublicApiCommand(ctx, spec, options) {
|
|
|
356368
356592
|
var skippedContractNames = new Set([
|
|
356369
356593
|
"flow.list",
|
|
356370
356594
|
"runner.evaluateSnippet",
|
|
356595
|
+
"runner.highlightSelector",
|
|
356371
356596
|
"runner.importPackage",
|
|
356372
356597
|
"runner.inspect",
|
|
356598
|
+
"runner.inspectMobile",
|
|
356373
356599
|
"runner.launch",
|
|
356374
356600
|
"runner.performAction",
|
|
356601
|
+
"runner.promoteSnapshot",
|
|
356375
356602
|
"runner.readJournal",
|
|
356376
356603
|
"runner.runFlow",
|
|
356377
356604
|
"runner.stopRun",
|
|
@@ -356430,45 +356657,6 @@ function registerPublicApiCommands(program2, signals, options = {}) {
|
|
|
356430
356657
|
}
|
|
356431
356658
|
}
|
|
356432
356659
|
|
|
356433
|
-
// src/domains/interactiveRunner/importPackage.ts
|
|
356434
|
-
import { join as join44 } from "node:path";
|
|
356435
|
-
|
|
356436
|
-
// src/core/interactiveRunner/npmDependencies.ts
|
|
356437
|
-
var sectionsInPrecedenceOrder = ["dependencies", "devDependencies"];
|
|
356438
|
-
var isInstallable = (version2) => !version2.startsWith("workspace:");
|
|
356439
|
-
function isRecord5(value) {
|
|
356440
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
356441
|
-
}
|
|
356442
|
-
function readNpmDependencies(packageJsonContent) {
|
|
356443
|
-
let parsed;
|
|
356444
|
-
try {
|
|
356445
|
-
parsed = JSON.parse(packageJsonContent);
|
|
356446
|
-
} catch {
|
|
356447
|
-
return { ok: false, reason: "it is not valid JSON" };
|
|
356448
|
-
}
|
|
356449
|
-
if (!isRecord5(parsed))
|
|
356450
|
-
return { ok: false, reason: "it is not an object" };
|
|
356451
|
-
const dependencies = {};
|
|
356452
|
-
for (const section of sectionsInPrecedenceOrder) {
|
|
356453
|
-
const declared = parsed[section];
|
|
356454
|
-
if (declared === undefined)
|
|
356455
|
-
continue;
|
|
356456
|
-
if (!isRecord5(declared)) {
|
|
356457
|
-
return { ok: false, reason: `${section} is not an object` };
|
|
356458
|
-
}
|
|
356459
|
-
for (const [name, version2] of Object.entries(declared)) {
|
|
356460
|
-
if (typeof version2 !== "string") {
|
|
356461
|
-
return { ok: false, reason: `${section}.${name} is not a string` };
|
|
356462
|
-
}
|
|
356463
|
-
if (Object.hasOwn(dependencies, name))
|
|
356464
|
-
continue;
|
|
356465
|
-
if (isInstallable(version2))
|
|
356466
|
-
dependencies[name] = version2;
|
|
356467
|
-
}
|
|
356468
|
-
}
|
|
356469
|
-
return { dependencies, ok: true };
|
|
356470
|
-
}
|
|
356471
|
-
|
|
356472
356660
|
// src/domains/interactiveRunner/runnerCallOptions.ts
|
|
356473
356661
|
var runnerCallOptions = { timeoutMs: 90000 };
|
|
356474
356662
|
|
|
@@ -356583,71 +356771,73 @@ function announceRunner(ctx, resolved) {
|
|
|
356583
356771
|
}
|
|
356584
356772
|
}
|
|
356585
356773
|
|
|
356586
|
-
// src/domains/interactiveRunner/
|
|
356587
|
-
|
|
356588
|
-
async function handleRunnerImportPackage(ctx, options, deps) {
|
|
356589
|
-
const content = await deps.readFile(join44(deps.cwd, runPackageJsonPath)).catch(() => {
|
|
356590
|
-
return;
|
|
356591
|
-
});
|
|
356592
|
-
if (content === undefined) {
|
|
356593
|
-
return {
|
|
356594
|
-
error: interactiveRunnerMessages.missingPackageJsonForImport,
|
|
356595
|
-
exitCode: exitCodes.config
|
|
356596
|
-
};
|
|
356597
|
-
}
|
|
356598
|
-
const dependencies = readNpmDependencies(content);
|
|
356599
|
-
if (!dependencies.ok) {
|
|
356600
|
-
return {
|
|
356601
|
-
error: interactiveRunnerMessages.packageJsonUnreadable(dependencies.reason),
|
|
356602
|
-
exitCode: exitCodes.config
|
|
356603
|
-
};
|
|
356604
|
-
}
|
|
356774
|
+
// src/domains/interactiveRunner/highlightSelector.ts
|
|
356775
|
+
async function handleRunnerHighlightSelector(ctx, options, deps) {
|
|
356605
356776
|
const resolved = await resolveRunner(ctx, {
|
|
356606
356777
|
autoLaunch: false,
|
|
356607
|
-
noRunnerIdMessage: interactiveRunnerMessages.
|
|
356778
|
+
noRunnerIdMessage: interactiveRunnerMessages.noRunnerIdForHighlight,
|
|
356608
356779
|
runner: options.runner
|
|
356609
356780
|
}, deps);
|
|
356610
356781
|
if (resolved.type === "failed") {
|
|
356611
356782
|
return { ...failureFields(resolved), exitCode: resolved.exitCode };
|
|
356612
356783
|
}
|
|
356613
|
-
const
|
|
356614
|
-
const result = await ctx.platformClient.callPublicApi(publicContractsV1.runner.
|
|
356615
|
-
id: resolved.runnerId,
|
|
356616
|
-
npmDependencies: dependencies.dependencies,
|
|
356617
|
-
packageName: options.name,
|
|
356618
|
-
packageVersion
|
|
356619
|
-
}, runnerCallOptions);
|
|
356784
|
+
const selector = options.selector ?? "";
|
|
356785
|
+
const result = await ctx.platformClient.callPublicApi(publicContractsV1.runner.highlightSelector, { id: resolved.runnerId, selector }, runnerCallOptions);
|
|
356620
356786
|
if (!result.ok) {
|
|
356621
356787
|
return { ...failureFields(result), exitCode: exitCodes.network };
|
|
356622
356788
|
}
|
|
356623
|
-
|
|
356624
|
-
|
|
356625
|
-
|
|
356626
|
-
|
|
356627
|
-
|
|
356789
|
+
const { outcome } = result.value;
|
|
356790
|
+
switch (outcome) {
|
|
356791
|
+
case "cleared":
|
|
356792
|
+
ctx.ui.output(result.value, interactiveRunnerMessages.highlightCleared);
|
|
356793
|
+
return;
|
|
356794
|
+
case "success": {
|
|
356795
|
+
const answer = result.value;
|
|
356796
|
+
if (answer.status === "invalid") {
|
|
356628
356797
|
return {
|
|
356629
|
-
error: interactiveRunnerMessages.
|
|
356798
|
+
error: interactiveRunnerMessages.highlightSelectorInvalid(answer.selector),
|
|
356630
356799
|
exitCode: exitCodes.invalidArgs
|
|
356631
356800
|
};
|
|
356632
|
-
case "runner-unreachable":
|
|
356633
|
-
return {
|
|
356634
|
-
error: interactiveRunnerMessages.runnerUnreachable,
|
|
356635
|
-
exitCode: exitCodes.network
|
|
356636
|
-
};
|
|
356637
|
-
default: {
|
|
356638
|
-
return {
|
|
356639
|
-
error: interactiveRunnerMessages.importAnsweredUnknown(failureReason),
|
|
356640
|
-
exitCode: exitCodes.network
|
|
356641
|
-
};
|
|
356642
356801
|
}
|
|
356802
|
+
ctx.ui.output(answer, answer.status === "empty" ? interactiveRunnerMessages.highlightMatchedNothing(answer.selector) : interactiveRunnerMessages.highlightMatched(answer.matchCount, answer.targetPage));
|
|
356803
|
+
return;
|
|
356643
356804
|
}
|
|
356805
|
+
case "failure":
|
|
356806
|
+
return describeFailure(result.value.failureReason);
|
|
356807
|
+
default:
|
|
356808
|
+
return {
|
|
356809
|
+
error: interactiveRunnerMessages.highlightAnsweredUnknown(outcome),
|
|
356810
|
+
exitCode: exitCodes.network
|
|
356811
|
+
};
|
|
356812
|
+
}
|
|
356813
|
+
}
|
|
356814
|
+
function describeFailure(failureReason) {
|
|
356815
|
+
switch (failureReason) {
|
|
356816
|
+
case "no-answer":
|
|
356817
|
+
return {
|
|
356818
|
+
error: interactiveRunnerMessages.highlightNoAnswer,
|
|
356819
|
+
exitCode: exitCodes.network
|
|
356820
|
+
};
|
|
356821
|
+
case "runner-cannot-highlight-selectors":
|
|
356822
|
+
return {
|
|
356823
|
+
error: interactiveRunnerMessages.runnerCannotHighlightSelectors,
|
|
356824
|
+
exitCode: exitCodes.invalidArgs
|
|
356825
|
+
};
|
|
356826
|
+
case "runner-unreachable":
|
|
356827
|
+
return {
|
|
356828
|
+
error: interactiveRunnerMessages.runnerUnreachable,
|
|
356829
|
+
exitCode: exitCodes.network
|
|
356830
|
+
};
|
|
356831
|
+
default:
|
|
356832
|
+
return {
|
|
356833
|
+
error: interactiveRunnerMessages.highlightAnsweredUnknown(failureReason),
|
|
356834
|
+
exitCode: exitCodes.network
|
|
356835
|
+
};
|
|
356644
356836
|
}
|
|
356645
|
-
ctx.ui.output(result.value, interactiveRunnerMessages.packageInstalled(options.name, packageVersion));
|
|
356646
|
-
return;
|
|
356647
356837
|
}
|
|
356648
356838
|
|
|
356649
356839
|
// src/shell/interactiveRunner/collectRunFiles.ts
|
|
356650
|
-
import { join as
|
|
356840
|
+
import { join as join45 } from "node:path";
|
|
356651
356841
|
|
|
356652
356842
|
// src/core/interactiveRunner/getImports.ts
|
|
356653
356843
|
function getImports(options) {
|
|
@@ -356686,14 +356876,14 @@ function isPathAlias(importPath, paths) {
|
|
|
356686
356876
|
}
|
|
356687
356877
|
|
|
356688
356878
|
// src/core/interactiveRunner/resolveImportPath.ts
|
|
356689
|
-
import { dirname as dirname15, join as
|
|
356879
|
+
import { dirname as dirname15, join as join44, normalize as normalize3 } from "node:path/posix";
|
|
356690
356880
|
|
|
356691
356881
|
// src/core/interactiveRunner/tsconfigPaths.ts
|
|
356692
|
-
function
|
|
356882
|
+
function isRecord5(value) {
|
|
356693
356883
|
return typeof value === "object" && value !== null;
|
|
356694
356884
|
}
|
|
356695
356885
|
function isTsconfigPaths(value) {
|
|
356696
|
-
return
|
|
356886
|
+
return isRecord5(value) && Object.values(value).every((targets) => Array.isArray(targets) && targets.every((target) => typeof target === "string"));
|
|
356697
356887
|
}
|
|
356698
356888
|
function parseTsconfigPaths(tsconfigContent) {
|
|
356699
356889
|
let parsed;
|
|
@@ -356702,10 +356892,10 @@ function parseTsconfigPaths(tsconfigContent) {
|
|
|
356702
356892
|
} catch {
|
|
356703
356893
|
return;
|
|
356704
356894
|
}
|
|
356705
|
-
if (!
|
|
356895
|
+
if (!isRecord5(parsed))
|
|
356706
356896
|
return;
|
|
356707
356897
|
const compilerOptions = parsed["compilerOptions"];
|
|
356708
|
-
if (!
|
|
356898
|
+
if (!isRecord5(compilerOptions))
|
|
356709
356899
|
return;
|
|
356710
356900
|
const paths = compilerOptions["paths"];
|
|
356711
356901
|
return isTsconfigPaths(paths) ? paths : undefined;
|
|
@@ -356739,7 +356929,7 @@ function candidatesForExplicitExtension(options) {
|
|
|
356739
356929
|
}
|
|
356740
356930
|
function resolveImportPath(options) {
|
|
356741
356931
|
const aliasTarget = resolvePathAlias(options.importPath, options.tsconfigPaths);
|
|
356742
|
-
const resolvedPath = aliasTarget === undefined ? normalize3(
|
|
356932
|
+
const resolvedPath = aliasTarget === undefined ? normalize3(join44(dirname15(options.importingFilePath), options.importPath)) : normalize3(aliasTarget);
|
|
356743
356933
|
const hasExplicitExtension = supportedExtensions.some((extension) => options.importPath.endsWith(extension));
|
|
356744
356934
|
const candidates = hasExplicitExtension ? candidatesForExplicitExtension({
|
|
356745
356935
|
importPath: options.importPath,
|
|
@@ -356763,7 +356953,7 @@ function resolveImportPath(options) {
|
|
|
356763
356953
|
var tsconfigPath = "tsconfig.json";
|
|
356764
356954
|
async function collectRunFiles(options) {
|
|
356765
356955
|
const held = await listShippablePaths(options);
|
|
356766
|
-
const readFile2 = (path9) => options.fs.readFile(
|
|
356956
|
+
const readFile2 = (path9) => options.fs.readFile(join45(options.cwd, path9));
|
|
356767
356957
|
const tsconfigContent = held.has(tsconfigPath) ? await readFile2(tsconfigPath).catch(() => {
|
|
356768
356958
|
return;
|
|
356769
356959
|
}) : undefined;
|
|
@@ -356846,7 +357036,7 @@ function makeRunnerId() {
|
|
|
356846
357036
|
}
|
|
356847
357037
|
|
|
356848
357038
|
// src/shell/interactiveRunner/runFilesManifest.ts
|
|
356849
|
-
import { join as
|
|
357039
|
+
import { join as join46 } from "node:path";
|
|
356850
357040
|
var manifestFileName = "runner-files.json";
|
|
356851
357041
|
var manifestSchema2 = exports_external.object({
|
|
356852
357042
|
files: exports_external.array(exports_external.object({ contentHash: exports_external.string(), path: exports_external.string() })),
|
|
@@ -356854,8 +357044,8 @@ var manifestSchema2 = exports_external.object({
|
|
|
356854
357044
|
version: exports_external.literal(1)
|
|
356855
357045
|
});
|
|
356856
357046
|
function makeRunFilesManifestStore(options) {
|
|
356857
|
-
const directory =
|
|
356858
|
-
const path9 =
|
|
357047
|
+
const directory = join46(options.cwd, qawolfDir);
|
|
357048
|
+
const path9 = join46(directory, manifestFileName);
|
|
356859
357049
|
let pendingWrites = 0;
|
|
356860
357050
|
return {
|
|
356861
357051
|
async read() {
|
|
@@ -356885,7 +357075,7 @@ function parseJson(text3) {
|
|
|
356885
357075
|
}
|
|
356886
357076
|
|
|
356887
357077
|
// src/shell/interactiveRunner/runnerStore.ts
|
|
356888
|
-
import { join as
|
|
357078
|
+
import { join as join47 } from "node:path";
|
|
356889
357079
|
var storeFileName = "runner.json";
|
|
356890
357080
|
var storeSchema = exports_external.object({ defaultRunnerId: exports_external.string().optional() });
|
|
356891
357081
|
function parseJson2(text3) {
|
|
@@ -356896,8 +357086,8 @@ function parseJson2(text3) {
|
|
|
356896
357086
|
}
|
|
356897
357087
|
}
|
|
356898
357088
|
function makeRunnerStore(options) {
|
|
356899
|
-
const directory =
|
|
356900
|
-
const path9 =
|
|
357089
|
+
const directory = join47(options.cwd, qawolfDir);
|
|
357090
|
+
const path9 = join47(directory, storeFileName);
|
|
356901
357091
|
let pendingWrites = 0;
|
|
356902
357092
|
const nextPendingPath = () => `${path9}.${process.pid}.${++pendingWrites}.tmp`;
|
|
356903
357093
|
return {
|
|
@@ -356979,6 +357169,118 @@ function runnerDeps(ctx) {
|
|
|
356979
357169
|
});
|
|
356980
357170
|
}
|
|
356981
357171
|
|
|
357172
|
+
// src/commands/runner/highlightSelector.register.ts
|
|
357173
|
+
var highlightExamples = `
|
|
357174
|
+
Examples:
|
|
357175
|
+
$ qawolf runner highlight-selector "text=Sign in"
|
|
357176
|
+
$ qawolf runner highlight-selector "#checkout" && qawolf runner screenshot
|
|
357177
|
+
$ qawolf runner highlight-selector`;
|
|
357178
|
+
function registerRunnerHighlightSelectorCommand(runner, signals) {
|
|
357179
|
+
declareCommandKind(runner.command("highlight-selector [selector]"), "write").description("Highlight what a selector matches on a runner's live page, so the next screenshot shows it. Omit the selector to clear the highlight").option("--runner <id>", runnerFlagDescription).addHelpText("after", highlightExamples).action((selector, opts, command) => withAuthContext(signals, (ctx) => handleRunnerHighlightSelector(ctx, { runner: opts.runner, selector }, runnerDeps(ctx)))(opts, command));
|
|
357180
|
+
}
|
|
357181
|
+
|
|
357182
|
+
// src/domains/interactiveRunner/importPackage.ts
|
|
357183
|
+
import { join as join48 } from "node:path";
|
|
357184
|
+
|
|
357185
|
+
// src/core/interactiveRunner/npmDependencies.ts
|
|
357186
|
+
var sectionsInPrecedenceOrder = ["dependencies", "devDependencies"];
|
|
357187
|
+
var isInstallable = (version2) => !version2.startsWith("workspace:");
|
|
357188
|
+
function isRecord6(value) {
|
|
357189
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
357190
|
+
}
|
|
357191
|
+
function readNpmDependencies(packageJsonContent) {
|
|
357192
|
+
let parsed;
|
|
357193
|
+
try {
|
|
357194
|
+
parsed = JSON.parse(packageJsonContent);
|
|
357195
|
+
} catch {
|
|
357196
|
+
return { ok: false, reason: "it is not valid JSON" };
|
|
357197
|
+
}
|
|
357198
|
+
if (!isRecord6(parsed))
|
|
357199
|
+
return { ok: false, reason: "it is not an object" };
|
|
357200
|
+
const dependencies = {};
|
|
357201
|
+
for (const section of sectionsInPrecedenceOrder) {
|
|
357202
|
+
const declared = parsed[section];
|
|
357203
|
+
if (declared === undefined)
|
|
357204
|
+
continue;
|
|
357205
|
+
if (!isRecord6(declared)) {
|
|
357206
|
+
return { ok: false, reason: `${section} is not an object` };
|
|
357207
|
+
}
|
|
357208
|
+
for (const [name, version2] of Object.entries(declared)) {
|
|
357209
|
+
if (typeof version2 !== "string") {
|
|
357210
|
+
return { ok: false, reason: `${section}.${name} is not a string` };
|
|
357211
|
+
}
|
|
357212
|
+
if (Object.hasOwn(dependencies, name))
|
|
357213
|
+
continue;
|
|
357214
|
+
if (isInstallable(version2))
|
|
357215
|
+
dependencies[name] = version2;
|
|
357216
|
+
}
|
|
357217
|
+
}
|
|
357218
|
+
return { dependencies, ok: true };
|
|
357219
|
+
}
|
|
357220
|
+
|
|
357221
|
+
// src/domains/interactiveRunner/importPackage.ts
|
|
357222
|
+
var defaultPackageVersion = "latest";
|
|
357223
|
+
async function handleRunnerImportPackage(ctx, options, deps) {
|
|
357224
|
+
const content = await deps.readFile(join48(deps.cwd, runPackageJsonPath)).catch(() => {
|
|
357225
|
+
return;
|
|
357226
|
+
});
|
|
357227
|
+
if (content === undefined) {
|
|
357228
|
+
return {
|
|
357229
|
+
error: interactiveRunnerMessages.missingPackageJsonForImport,
|
|
357230
|
+
exitCode: exitCodes.config
|
|
357231
|
+
};
|
|
357232
|
+
}
|
|
357233
|
+
const dependencies = readNpmDependencies(content);
|
|
357234
|
+
if (!dependencies.ok) {
|
|
357235
|
+
return {
|
|
357236
|
+
error: interactiveRunnerMessages.packageJsonUnreadable(dependencies.reason),
|
|
357237
|
+
exitCode: exitCodes.config
|
|
357238
|
+
};
|
|
357239
|
+
}
|
|
357240
|
+
const resolved = await resolveRunner(ctx, {
|
|
357241
|
+
autoLaunch: false,
|
|
357242
|
+
noRunnerIdMessage: interactiveRunnerMessages.noRunnerIdForImport,
|
|
357243
|
+
runner: options.runner
|
|
357244
|
+
}, deps);
|
|
357245
|
+
if (resolved.type === "failed") {
|
|
357246
|
+
return { ...failureFields(resolved), exitCode: resolved.exitCode };
|
|
357247
|
+
}
|
|
357248
|
+
const packageVersion = options.version ?? defaultPackageVersion;
|
|
357249
|
+
const result = await ctx.platformClient.callPublicApi(publicContractsV1.runner.importPackage, {
|
|
357250
|
+
id: resolved.runnerId,
|
|
357251
|
+
npmDependencies: dependencies.dependencies,
|
|
357252
|
+
packageName: options.name,
|
|
357253
|
+
packageVersion
|
|
357254
|
+
}, runnerCallOptions);
|
|
357255
|
+
if (!result.ok) {
|
|
357256
|
+
return { ...failureFields(result), exitCode: exitCodes.network };
|
|
357257
|
+
}
|
|
357258
|
+
if (result.value.outcome === "failure") {
|
|
357259
|
+
const failure = result.value;
|
|
357260
|
+
const { failureReason } = failure;
|
|
357261
|
+
switch (failureReason) {
|
|
357262
|
+
case "install-failed":
|
|
357263
|
+
return {
|
|
357264
|
+
error: interactiveRunnerMessages.installFailed(options.name, packageVersion, failure.errorMessage),
|
|
357265
|
+
exitCode: exitCodes.invalidArgs
|
|
357266
|
+
};
|
|
357267
|
+
case "runner-unreachable":
|
|
357268
|
+
return {
|
|
357269
|
+
error: interactiveRunnerMessages.runnerUnreachable,
|
|
357270
|
+
exitCode: exitCodes.network
|
|
357271
|
+
};
|
|
357272
|
+
default: {
|
|
357273
|
+
return {
|
|
357274
|
+
error: interactiveRunnerMessages.importAnsweredUnknown(failureReason),
|
|
357275
|
+
exitCode: exitCodes.network
|
|
357276
|
+
};
|
|
357277
|
+
}
|
|
357278
|
+
}
|
|
357279
|
+
}
|
|
357280
|
+
ctx.ui.output(result.value, interactiveRunnerMessages.packageInstalled(options.name, packageVersion));
|
|
357281
|
+
return;
|
|
357282
|
+
}
|
|
357283
|
+
|
|
356982
357284
|
// src/commands/runner/importPackage.register.ts
|
|
356983
357285
|
var importPackageExamples = `
|
|
356984
357286
|
Examples:
|
|
@@ -357554,6 +357856,67 @@ function registerRunnerLifecycleCommands(runner, signals) {
|
|
|
357554
357856
|
declareCommandKind(runner.command("keepalive"), "read").description("Reset a runner's inactivity clock, for a caller that pauses between actions").option("--runner <id>", runnerFlagDescription).addHelpText("after", keepaliveExamples).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerKeepalive(ctx, { runner: opts.runner }, runnerDeps(ctx)))(opts, command));
|
|
357555
357857
|
}
|
|
357556
357858
|
|
|
357859
|
+
// src/domains/interactiveRunner/promoteSnapshot.ts
|
|
357860
|
+
async function handleRunnerPromoteSnapshot(ctx, options, deps) {
|
|
357861
|
+
const resolved = await resolveRunner(ctx, {
|
|
357862
|
+
autoLaunch: false,
|
|
357863
|
+
noRunnerIdMessage: interactiveRunnerMessages.noRunnerIdForPromoteSnapshot,
|
|
357864
|
+
runner: options.runner
|
|
357865
|
+
}, deps);
|
|
357866
|
+
if (resolved.type === "failed") {
|
|
357867
|
+
return { ...failureFields(resolved), exitCode: resolved.exitCode };
|
|
357868
|
+
}
|
|
357869
|
+
const result = await ctx.platformClient.callPublicApi(publicContractsV1.runner.promoteSnapshot, {
|
|
357870
|
+
baselinePath: options.baselinePath,
|
|
357871
|
+
id: resolved.runnerId,
|
|
357872
|
+
screenshotPath: options.screenshotPath
|
|
357873
|
+
}, runnerCallOptions);
|
|
357874
|
+
if (!result.ok) {
|
|
357875
|
+
return { ...failureFields(result), exitCode: exitCodes.network };
|
|
357876
|
+
}
|
|
357877
|
+
if (result.value.outcome === "success") {
|
|
357878
|
+
ctx.ui.output(result.value, interactiveRunnerMessages.snapshotPromoted(options.screenshotPath, options.baselinePath));
|
|
357879
|
+
return;
|
|
357880
|
+
}
|
|
357881
|
+
const { failureReason } = result.value;
|
|
357882
|
+
switch (failureReason) {
|
|
357883
|
+
case "snapshot-not-found":
|
|
357884
|
+
return {
|
|
357885
|
+
error: interactiveRunnerMessages.snapshotNotFound(options.screenshotPath),
|
|
357886
|
+
exitCode: exitCodes.invalidArgs
|
|
357887
|
+
};
|
|
357888
|
+
case "runner-cannot-promote-snapshots":
|
|
357889
|
+
return {
|
|
357890
|
+
error: interactiveRunnerMessages.runnerCannotPromoteSnapshots,
|
|
357891
|
+
exitCode: exitCodes.invalidArgs
|
|
357892
|
+
};
|
|
357893
|
+
case "runner-unreachable":
|
|
357894
|
+
return {
|
|
357895
|
+
error: interactiveRunnerMessages.runnerUnreachable,
|
|
357896
|
+
exitCode: exitCodes.network
|
|
357897
|
+
};
|
|
357898
|
+
default: {
|
|
357899
|
+
return {
|
|
357900
|
+
error: interactiveRunnerMessages.promoteSnapshotAnsweredUnknown(failureReason),
|
|
357901
|
+
exitCode: exitCodes.network
|
|
357902
|
+
};
|
|
357903
|
+
}
|
|
357904
|
+
}
|
|
357905
|
+
}
|
|
357906
|
+
|
|
357907
|
+
// src/commands/runner/promoteSnapshot.register.ts
|
|
357908
|
+
var promoteSnapshotExamples = `
|
|
357909
|
+
Examples:
|
|
357910
|
+
$ qawolf runner promote-snapshot --screenshot checkout-1-actual.png --baseline checkout-1.png
|
|
357911
|
+
$ qawolf runner events run-events --tail 20 | jq 'select(.type == "image-diff-artifact")'`;
|
|
357912
|
+
function registerRunnerPromoteSnapshotCommand(runner, signals) {
|
|
357913
|
+
declareCommandKind(runner.command("promote-snapshot"), "write").description("Accept a run's screenshot as the new baseline for an image diff, on the runner that produced it").requiredOption("--screenshot <path>", "The screenshot to promote, as the image diff named it").requiredOption("--baseline <path>", "The baseline to replace, as the image diff named it").option("--runner <id>", runnerFlagDescription).addHelpText("after", promoteSnapshotExamples).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerPromoteSnapshot(ctx, {
|
|
357914
|
+
baselinePath: opts.baseline,
|
|
357915
|
+
runner: opts.runner,
|
|
357916
|
+
screenshotPath: opts.screenshot
|
|
357917
|
+
}, runnerDeps(ctx)))(opts, command));
|
|
357918
|
+
}
|
|
357919
|
+
|
|
357557
357920
|
// src/core/interactiveRunner/followTimeout.ts
|
|
357558
357921
|
var defaultFollowTimeoutSeconds = 3600;
|
|
357559
357922
|
var followTimeoutSchema = exports_external.coerce.number().int().positive();
|
|
@@ -357942,6 +358305,15 @@ var refused = (error51, exitCode) => ({
|
|
|
357942
358305
|
ok: false
|
|
357943
358306
|
});
|
|
357944
358307
|
async function prepareRun(options, deps) {
|
|
358308
|
+
const explicitEnvId = options.envId?.trim();
|
|
358309
|
+
if (explicitEnvId !== undefined && options.envFile !== undefined) {
|
|
358310
|
+
return refused(interactiveRunnerMessages.envIdWithEnvFile, exitCodes.invalidArgs);
|
|
358311
|
+
}
|
|
358312
|
+
if (explicitEnvId === "") {
|
|
358313
|
+
return refused(interactiveRunnerMessages.envIdBlank, exitCodes.invalidArgs);
|
|
358314
|
+
}
|
|
358315
|
+
const fromEnvVar = deps.env["QAWOLF_ENVIRONMENT"]?.trim();
|
|
358316
|
+
const environmentId = explicitEnvId ?? (options.envFile === undefined && fromEnvVar ? fromEnvVar : undefined);
|
|
357945
358317
|
const linesFilePath = options.linesFile === undefined ? undefined : toCollectedPath(deps.cwd, options.linesFile);
|
|
357946
358318
|
const roots = linesFilePath === undefined || linesFilePath === options.entryPointPath ? [options.entryPointPath] : [options.entryPointPath, linesFilePath];
|
|
357947
358319
|
const collected = await collectRunFiles2(deps, roots);
|
|
@@ -357958,14 +358330,26 @@ async function prepareRun(options, deps) {
|
|
|
357958
358330
|
}
|
|
357959
358331
|
const given = environment3?.ok === true ? environment3.environment : undefined;
|
|
357960
358332
|
if (options.lines === undefined) {
|
|
357961
|
-
return options.linesFile === undefined ? {
|
|
358333
|
+
return options.linesFile === undefined ? {
|
|
358334
|
+
environment: given,
|
|
358335
|
+
environmentId,
|
|
358336
|
+
files,
|
|
358337
|
+
ok: true,
|
|
358338
|
+
selection: undefined
|
|
358339
|
+
} : refused(interactiveRunnerMessages.linesFileWithoutLines, exitCodes.invalidArgs);
|
|
357962
358340
|
}
|
|
357963
358341
|
const path9 = linesFilePath ?? options.entryPointPath;
|
|
357964
358342
|
if (!Object.hasOwn(files, path9)) {
|
|
357965
358343
|
return refused(interactiveRunnerMessages.fileNotCollected(path9), exitCodes.invalidArgs);
|
|
357966
358344
|
}
|
|
357967
358345
|
const built = buildRunSelection({ lines: options.lines, path: path9 });
|
|
357968
|
-
return built.ok ? {
|
|
358346
|
+
return built.ok ? {
|
|
358347
|
+
environment: given,
|
|
358348
|
+
environmentId,
|
|
358349
|
+
files,
|
|
358350
|
+
ok: true,
|
|
358351
|
+
selection: built.selection
|
|
358352
|
+
} : refused(built.error, exitCodes.invalidArgs);
|
|
357969
358353
|
}
|
|
357970
358354
|
async function readEnvironment(envFile, deps) {
|
|
357971
358355
|
if (envFile === undefined)
|
|
@@ -358046,6 +358430,7 @@ async function sendRunFlowRequest(ctx, options, delta) {
|
|
|
358046
358430
|
files: delta.files,
|
|
358047
358431
|
id: options.resolved.runnerId,
|
|
358048
358432
|
...options.environment === undefined ? {} : { env: options.environment },
|
|
358433
|
+
...options.environmentId === undefined ? {} : { environmentId: options.environmentId },
|
|
358049
358434
|
...options.selection === undefined ? {} : { selection: options.selection },
|
|
358050
358435
|
...delta.unchangedFiles === undefined ? {} : { unchangedFiles: delta.unchangedFiles }
|
|
358051
358436
|
}, runnerCallOptions);
|
|
@@ -358132,12 +358517,16 @@ async function handleRunnerRun(ctx, options, deps) {
|
|
|
358132
358517
|
const prepared = await prepareRun({
|
|
358133
358518
|
entryPointPath,
|
|
358134
358519
|
envFile: options.envFile,
|
|
358520
|
+
envId: options.envId,
|
|
358135
358521
|
lines: options.lines,
|
|
358136
358522
|
linesFile: options.linesFile
|
|
358137
358523
|
}, deps);
|
|
358138
358524
|
if (!prepared.ok) {
|
|
358139
358525
|
return { error: prepared.error, exitCode: prepared.exitCode };
|
|
358140
358526
|
}
|
|
358527
|
+
if (options.envId === undefined && prepared.environmentId !== undefined) {
|
|
358528
|
+
ctx.ui.info(environmentsMessages.usingFromEnvVar(prepared.environmentId));
|
|
358529
|
+
}
|
|
358141
358530
|
const resolved = await resolveRunner(ctx, { autoLaunch: true, runner: options.runner }, deps);
|
|
358142
358531
|
if (resolved.type === "failed") {
|
|
358143
358532
|
return { ...failureFields(resolved), exitCode: resolved.exitCode };
|
|
@@ -358153,6 +358542,7 @@ async function handleRunnerRun(ctx, options, deps) {
|
|
|
358153
358542
|
const submitted = await submitRun(ctx, {
|
|
358154
358543
|
entryPointPath,
|
|
358155
358544
|
environment: prepared.environment,
|
|
358545
|
+
environmentId: prepared.environmentId,
|
|
358156
358546
|
files: prepared.files,
|
|
358157
358547
|
resolved,
|
|
358158
358548
|
selection: prepared.selection
|
|
@@ -358191,11 +358581,13 @@ Examples:
|
|
|
358191
358581
|
$ qawolf runner run flows/checkout.flow.ts --follow
|
|
358192
358582
|
$ qawolf runner run flows/checkout.flow.ts --follow --logs
|
|
358193
358583
|
$ qawolf runner run flows/checkout.flow.ts --lines 12-40
|
|
358194
|
-
$ qawolf runner run flows/checkout.flow.ts --lines 4-9 --lines-file pages/login.ts
|
|
358584
|
+
$ qawolf runner run flows/checkout.flow.ts --lines 4-9 --lines-file pages/login.ts
|
|
358585
|
+
$ qawolf runner run flows/checkout.flow.ts --env-id staging`;
|
|
358195
358586
|
function registerRunCommand(runner, signals) {
|
|
358196
|
-
declareCommandKind(runner.command("run <flowFile>"), "write").description("Run a flow on an interactive runner, shipping the flow and what it imports").option("--follow", "Report the run's status until it settles: in progress, then passed or failed", false).option("--logs", "Stream every log line the run produces while following. Implies --follow", false).option("--run-events", "Stream the run's progress events as JSON lines while following. Implies --follow", false).option("--recorder-events", "Stream the browser actions the runner records as JSON lines while following, from an anchor taken just before submission: the recorder is runner-wide, not run-scoped. Implies --follow", false).option("--env-
|
|
358587
|
+
declareCommandKind(runner.command("run <flowFile>"), "write").description("Run a flow on an interactive runner, shipping the flow and what it imports").option("--follow", "Report the run's status until it settles: in progress, then passed or failed", false).option("--logs", "Stream every log line the run produces while following. Implies --follow", false).option("--run-events", "Stream the run's progress events as JSON lines while following. Implies --follow", false).option("--recorder-events", "Stream the browser actions the runner records as JSON lines while following, from an anchor taken just before submission: the recorder is runner-wide, not run-scoped. Implies --follow", false).option("--env-id <env>", "QA Wolf environment whose variables the run is given, by id or alias. Defaults to QAWOLF_ENVIRONMENT. QA Wolf reads and decrypts them itself, so they never leave the server and no size limit applies to them").option("--env-file <path>", "Dotenv file on this machine whose variables the run is given. Pass this or --env-id, not both").option("--lines <start-end>", "Run only these 1-indexed lines against the browser as it stands, instead of the whole flow from a fresh one").option("--lines-file <path>", "File the --lines range lives in. Defaults to <flowFile>; pass it only when the lines are in another file, such as a page object").option("--runner <id>", runnerFlagDescription).option("--timeout <seconds>", "Give up following after this long. Following keeps the runner alive, so a run that never settles would otherwise bill until the terminal closed", String(defaultFollowTimeoutSeconds)).addHelpText("after", runExamples2).action((flowFile, opts, command) => withAuthContext(signals, (ctx) => handleRunnerRun(ctx, {
|
|
358197
358588
|
entryPoint: flowFile,
|
|
358198
358589
|
envFile: opts.envFile,
|
|
358590
|
+
envId: opts.envId,
|
|
358199
358591
|
follow: opts.follow,
|
|
358200
358592
|
lines: opts.lines,
|
|
358201
358593
|
linesFile: opts.linesFile,
|
|
@@ -358216,6 +358608,8 @@ function registerRunnerCommand(program2, signals) {
|
|
|
358216
358608
|
registerRunnerInteractCommands(runner, signals);
|
|
358217
358609
|
registerRunnerInspectCommands(runner, signals);
|
|
358218
358610
|
registerRunnerImportPackageCommand(runner, signals);
|
|
358611
|
+
registerRunnerHighlightSelectorCommand(runner, signals);
|
|
358612
|
+
registerRunnerPromoteSnapshotCommand(runner, signals);
|
|
358219
358613
|
}
|
|
358220
358614
|
|
|
358221
358615
|
// src/commands/program.ts
|
|
@@ -358254,4 +358648,4 @@ createProgram({ signals }).parseAsync().catch(() => {
|
|
|
358254
358648
|
process.exitCode = 1;
|
|
358255
358649
|
}).finally(() => flushAndExit(typeof process.exitCode === "number" ? process.exitCode : 0));
|
|
358256
358650
|
|
|
358257
|
-
//# debugId=
|
|
358651
|
+
//# debugId=C71E3AC48D2D260064756E2164756E21
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qawolf/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.1",
|
|
4
4
|
"description": "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"automation",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"@clack/prompts": "1.5.1",
|
|
61
61
|
"@napi-rs/keyring": "1.3.0",
|
|
62
62
|
"@oxc-node/core": "0.1.0",
|
|
63
|
-
"@qawolf/api-contracts": "0.
|
|
63
|
+
"@qawolf/api-contracts": "0.34.0",
|
|
64
64
|
"@qawolf/emails": "1.1.1",
|
|
65
65
|
"@qawolf/flow-targets": "1.0.0",
|
|
66
66
|
"@qawolf/flows": "0.1.4",
|
|
@@ -38,7 +38,9 @@ is set; otherwise select an id or alias from
|
|
|
38
38
|
`qawolf --json environment find` and reuse it. Shell invocations may not share
|
|
39
39
|
exported variables, so pass the selected environment explicitly instead of
|
|
40
40
|
rediscovering it. Check the command's help for its environment flag: variable
|
|
41
|
-
commands use `--environment-id`; `qawolf flows run` uses `--env
|
|
41
|
+
commands use `--environment-id`; `qawolf flows run` uses `--env`; and
|
|
42
|
+
`qawolf runner run` uses `--env-id`, which falls back to `QAWOLF_ENVIRONMENT`
|
|
43
|
+
when neither it nor `--env-file` is passed.
|
|
42
44
|
|
|
43
45
|
If multiple environments are returned and the task context does not identify
|
|
44
46
|
the target, ask instead of guessing. Do not default to the newest environment.
|
|
@@ -145,12 +147,14 @@ current branch.
|
|
|
145
147
|
| `qawolf runner act` | write | Perform one raw action on a runner's screen: click, double_click, scroll, move, drag, keypress, navigate or type. Use - to read a whole action as JSON from stdin |
|
|
146
148
|
| `qawolf runner events` | read | Print a runner's journal, one entry per line. QA Wolf writes console, recorder, run-events, run-logs, run-status |
|
|
147
149
|
| `qawolf runner exec` | write | Evaluate a snippet against a runner's live page. Use - to read the snippet from stdin |
|
|
150
|
+
| `qawolf runner highlight-selector` | write | Highlight what a selector matches on a runner's live page, so the next screenshot shows it. Omit the selector to clear the highlight |
|
|
148
151
|
| `qawolf runner import-package` | write | Install a package into a runner's live run, so a snippet or a selection can import it |
|
|
149
152
|
| `qawolf runner inspect element-html` | read | Print the HTML of the first element a selector matches |
|
|
150
153
|
| `qawolf runner inspect page-html` | read | Print the page's HTML, simplified for a model to read |
|
|
151
154
|
| `qawolf runner inspect variable` | read | Print a top-level variable's value from the running workflow |
|
|
152
155
|
| `qawolf runner keepalive` | read | Reset a runner's inactivity clock, for a caller that pauses between actions |
|
|
153
156
|
| `qawolf runner launch` | write | Launch an interactive runner and make it this directory's default |
|
|
157
|
+
| `qawolf runner promote-snapshot` | write | Accept a run's screenshot as the new baseline for an image diff, on the runner that produced it |
|
|
154
158
|
| `qawolf runner run` | write | Run a flow on an interactive runner, shipping the flow and what it imports |
|
|
155
159
|
| `qawolf runner screenshot` | read | Save a JPEG of an interactive runner's screen to a file |
|
|
156
160
|
| `qawolf runner stop-run` | write | Stop what a runner is currently executing, leaving the runner up |
|
|
@@ -159,6 +159,53 @@ Use `inspect` before reaching for `exec`. Reading a value through a snippet
|
|
|
159
159
|
means printing it and then fishing it back out of the `console` stream, which is
|
|
160
160
|
two calls and a marker; `inspect variable` is one call and the value.
|
|
161
161
|
|
|
162
|
+
## Checking a selector: `highlight-selector`
|
|
163
|
+
|
|
164
|
+
`inspect element-html` tells you what a selector matched. `highlight-selector`
|
|
165
|
+
shows you _where_ it is, by drawing on the page itself:
|
|
166
|
+
|
|
167
|
+
```sh
|
|
168
|
+
qawolf runner highlight-selector "text=Sign in"
|
|
169
|
+
qawolf runner screenshot # the highlight is in this
|
|
170
|
+
qawolf runner highlight-selector # omit the selector to clear
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The highlight stays until it is replaced or cleared, which is the point: you
|
|
174
|
+
cannot see the runner's screen, so the only way to read the result is the next
|
|
175
|
+
screenshot.
|
|
176
|
+
|
|
177
|
+
Three answers are worth telling apart. A selector that matched prints how many
|
|
178
|
+
elements it hit and exits `0`. A selector the page read fine but that matched
|
|
179
|
+
nothing also exits `0`, because the call did what was asked and the count is the
|
|
180
|
+
answer; the message says the syntax was fine so you look at the page, not the
|
|
181
|
+
locator. A selector the page could not read at all exits `2`, because that one
|
|
182
|
+
is yours to correct and retrying will not change it.
|
|
183
|
+
|
|
184
|
+
`runner-cannot-highlight-selectors` exits `2` and means the runner has no
|
|
185
|
+
browser to draw on. `no-answer` exits `4`: a highlight runs inside the page, so
|
|
186
|
+
a page that is gone or mid-navigation does not answer at all rather than
|
|
187
|
+
answering slowly.
|
|
188
|
+
|
|
189
|
+
## Accepting a new baseline: `promote-snapshot`
|
|
190
|
+
|
|
191
|
+
When a run's image diff fails and the new screenshot is the one you want, this
|
|
192
|
+
replaces the baseline on the runner that produced it:
|
|
193
|
+
|
|
194
|
+
```sh
|
|
195
|
+
qawolf runner events run-events --tail 20 | jq 'select(.type == "image-diff-artifact")'
|
|
196
|
+
qawolf runner promote-snapshot --screenshot checkout-1-actual.png --baseline checkout-1.png
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Both paths are the ones the diff reported, and both are named rather than
|
|
200
|
+
positional, because two paths with one unlabelled is easy to get backwards and
|
|
201
|
+
swapping them promotes the wrong image. They are paths inside the run's own
|
|
202
|
+
screenshot storage, not files on your machine.
|
|
203
|
+
|
|
204
|
+
`snapshot-not-found` exits `2` and means the run wrote no screenshot at that
|
|
205
|
+
path, which nearly always means the paths did not come from a diff this run
|
|
206
|
+
produced. Nothing is changed, so correcting the path and repeating is safe.
|
|
207
|
+
Promoting twice is also safe, so an unreachable runner is worth retrying.
|
|
208
|
+
|
|
162
209
|
## Installing a package mid-session
|
|
163
210
|
|
|
164
211
|
`qawolf runner import-package <name>` installs a package into the runner's live
|
|
@@ -255,14 +302,41 @@ runner is addressed, naming the path.
|
|
|
255
302
|
|
|
256
303
|
### Giving the run environment variables
|
|
257
304
|
|
|
305
|
+
There are two ways, and a run takes one of them. `--env-id` names a QA Wolf
|
|
306
|
+
environment by id or alias, the same reference `qawolf flows` takes as `--env`.
|
|
258
307
|
`--env-file .env` gives the run the variables in a dotenv file, in the format
|
|
259
|
-
`qawolf flows pull` writes.
|
|
260
|
-
|
|
308
|
+
`qawolf flows pull` writes.
|
|
309
|
+
|
|
310
|
+
```sh
|
|
311
|
+
qawolf runner run flows/checkout.flow.ts --env-id staging
|
|
312
|
+
qawolf runner run flows/checkout.flow.ts --env-file .env
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**A run with neither flag falls back to `QAWOLF_ENVIRONMENT`**, the same
|
|
316
|
+
variable `qawolf flows` reads, so one export covers both. The run says on
|
|
317
|
+
stderr which environment it picked up, because those variables reach your flow's
|
|
318
|
+
code and a run should never be given an environment silently. `--env-id` wins
|
|
319
|
+
over it, and `--env-file` suppresses it, so a run reading a dotenv file is not
|
|
320
|
+
handed a second environment on top.
|
|
321
|
+
|
|
322
|
+
```sh
|
|
323
|
+
export QAWOLF_ENVIRONMENT=staging
|
|
324
|
+
qawolf runner run flows/checkout.flow.ts # runs against staging
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
**Prefer `--env-id`.** QA Wolf reads and decrypts the environment itself, so the
|
|
328
|
+
values never leave the server, nothing has to be pulled to disk first, and no
|
|
329
|
+
size limit applies to them. It is the only way to run a flow whose environment
|
|
330
|
+
holds something large, such as a session cookie.
|
|
331
|
+
|
|
332
|
+
A run that sends its own variables with `--env-file` may carry at most 200 of
|
|
333
|
+
them, each value at most 16 KiB. Names follow what a shell accepts, and
|
|
334
|
+
`QAWOLF_TEAM_ID` is reserved because QA Wolf sets it from the key you
|
|
335
|
+
authenticated with. All of that is refused before a runner is addressed, naming
|
|
336
|
+
the variable at fault.
|
|
261
337
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
from the key you authenticated with. All of that is refused before a runner is
|
|
265
|
-
addressed, naming the variable at fault.
|
|
338
|
+
Passing both flags is refused. They each give the run its whole environment, so
|
|
339
|
+
there is no order in which they would combine.
|
|
266
340
|
|
|
267
341
|
If the runner had no browser, one is started before your lines run, and the
|
|
268
342
|
command says so on stderr. Those lines then ran against a fresh page rather than
|