@qawolf/cli 1.9.2 → 1.10.0
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 +114 -35
- package/package.json +1 -1
- package/skills/qawolf-cli/references/runner.md +13 -3
package/dist/cli.js
CHANGED
|
@@ -156412,12 +156412,14 @@ var interactiveRunnerMessages = {
|
|
|
156412
156412
|
launched: (id) => `Launched runner ${id}.`,
|
|
156413
156413
|
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 stopped or idles out, so stop it with qawolf runner stop --runner ${id} when you are done.`,
|
|
156414
156414
|
followEventsTimedOut: (stream, seconds) => `Stopped following ${stream} after ${formatSeconds(seconds * 1000)}: reading keeps the runner alive and billing, so a follow does not run unbounded. Pass --timeout to wait longer, or follow again to continue.`,
|
|
156415
|
+
followEndCutShort: "The run settled, but the last window of its followed streams could not be read, so the output above may be missing its final lines.",
|
|
156415
156416
|
followTimedOut: (runId, runnerId, seconds) => `Stopped following run ${runId} after ${formatSeconds(seconds * 1000)}. The run may still be going: read it with qawolf runner events run-status --run ${runId}, and stop the runner with qawolf runner stop --runner ${runnerId} when you are done. Pass --timeout to wait longer.`,
|
|
156416
156417
|
missingPackageJson: "No package.json in the current directory. A run reads its npm dependencies from one, so it has to travel with the flow.",
|
|
156417
156418
|
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.`,
|
|
156418
156419
|
noRunnerId,
|
|
156419
156420
|
notRunning: (id) => `Runner ${id} was not running.`,
|
|
156420
156421
|
runFailed: (errorMessage2) => errorMessage2 === undefined ? "The run failed and reported no reason." : `The run failed: ${errorMessage2}`,
|
|
156422
|
+
runInProgress: "The run is in progress.",
|
|
156421
156423
|
runPassed: "The run passed.",
|
|
156422
156424
|
runSettledUnknown: (status) => `The run settled as "${status}", which this version of the CLI does not recognize. Upgrade to read it.`,
|
|
156423
156425
|
runSubmitAnsweredUnknown: (outcome) => `The runner answered the submission with "${outcome}", which this version of the CLI does not recognize. Upgrade to read it.`,
|
|
@@ -175909,7 +175911,7 @@ function startUpdateCheck(deps) {
|
|
|
175909
175911
|
// package.json
|
|
175910
175912
|
var package_default = {
|
|
175911
175913
|
name: "@qawolf/cli",
|
|
175912
|
-
version: "1.
|
|
175914
|
+
version: "1.10.0",
|
|
175913
175915
|
description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
|
|
175914
175916
|
keywords: [
|
|
175915
175917
|
"automation",
|
|
@@ -186853,6 +186855,20 @@ function readRunSettlement(payload) {
|
|
|
186853
186855
|
type: "settled"
|
|
186854
186856
|
};
|
|
186855
186857
|
}
|
|
186858
|
+
function findSettlement(entries) {
|
|
186859
|
+
for (const entry of entries) {
|
|
186860
|
+
const settlement = readRunSettlement(entry.payload);
|
|
186861
|
+
if (settlement.type !== "settled")
|
|
186862
|
+
continue;
|
|
186863
|
+
if (settlement.status === "passed")
|
|
186864
|
+
return { type: "passed" };
|
|
186865
|
+
if (settlement.status === "failed") {
|
|
186866
|
+
return { errorMessage: settlement.errorMessage, type: "failed" };
|
|
186867
|
+
}
|
|
186868
|
+
return { status: settlement.status, type: "unrecognized" };
|
|
186869
|
+
}
|
|
186870
|
+
return;
|
|
186871
|
+
}
|
|
186856
186872
|
var runLogPayloadSchema = exports_external.object({ message: exports_external.string() });
|
|
186857
186873
|
function formatRunLogLine(payload) {
|
|
186858
186874
|
const parsed = runLogPayloadSchema.safeParse(payload);
|
|
@@ -186915,6 +186931,18 @@ function createJournalCursor(ctx, runnerId, request) {
|
|
|
186915
186931
|
return { entries: window2.value.entries, type: "entries" };
|
|
186916
186932
|
};
|
|
186917
186933
|
}
|
|
186934
|
+
function createPrintingCursor(ctx, runnerId, request, format) {
|
|
186935
|
+
const read = createJournalCursor(ctx, runnerId, request);
|
|
186936
|
+
return async () => {
|
|
186937
|
+
const window2 = await read();
|
|
186938
|
+
if (window2.type !== "entries")
|
|
186939
|
+
return window2;
|
|
186940
|
+
for (const entry of window2.entries) {
|
|
186941
|
+
ctx.ui.stream(entry, format(entry.payload));
|
|
186942
|
+
}
|
|
186943
|
+
return window2;
|
|
186944
|
+
};
|
|
186945
|
+
}
|
|
186918
186946
|
var unreachableGraceMs = 60000;
|
|
186919
186947
|
function createUnreachableBudget(pollIntervalMs2) {
|
|
186920
186948
|
const limit = Math.ceil(unreachableGraceMs / pollIntervalMs2);
|
|
@@ -186996,22 +187024,49 @@ function readErrorPath(error51) {
|
|
|
186996
187024
|
return typeof path9 === "string" ? path9 : undefined;
|
|
186997
187025
|
}
|
|
186998
187026
|
|
|
186999
|
-
// src/domains/interactiveRunner/
|
|
187000
|
-
var
|
|
187001
|
-
function
|
|
187002
|
-
|
|
187003
|
-
|
|
187004
|
-
|
|
187005
|
-
|
|
187006
|
-
|
|
187007
|
-
|
|
187008
|
-
|
|
187009
|
-
|
|
187027
|
+
// src/domains/interactiveRunner/followPrinters.ts
|
|
187028
|
+
var anchorPollIntervalMs = 1000;
|
|
187029
|
+
async function resolveRecorderAnchor(ctx, resolved, deps) {
|
|
187030
|
+
if (resolved.type === "launched")
|
|
187031
|
+
return { ok: true, sinceSequence: 0 };
|
|
187032
|
+
return anchorRecorderCursor(ctx, resolved.runnerId, deps);
|
|
187033
|
+
}
|
|
187034
|
+
async function anchorRecorderCursor(ctx, runnerId, deps) {
|
|
187035
|
+
const unreachable = createUnreachableBudget(anchorPollIntervalMs);
|
|
187036
|
+
for (;; ) {
|
|
187037
|
+
const anchor = await readJournal(ctx, runnerId, {
|
|
187038
|
+
stream: "recorder",
|
|
187039
|
+
tail: 1
|
|
187040
|
+
});
|
|
187041
|
+
if (anchor.type === "read") {
|
|
187042
|
+
return { ok: true, sinceSequence: anchor.value.nextSequence };
|
|
187010
187043
|
}
|
|
187011
|
-
|
|
187044
|
+
if (anchor.type === "failed") {
|
|
187045
|
+
return { failure: journalReadFailure(anchor), ok: false };
|
|
187046
|
+
}
|
|
187047
|
+
if (unreachable.exhausted()) {
|
|
187048
|
+
return { failure: { ...unreachableFailure }, ok: false };
|
|
187049
|
+
}
|
|
187050
|
+
await deps.sleep(anchorPollIntervalMs);
|
|
187012
187051
|
}
|
|
187013
|
-
return;
|
|
187014
187052
|
}
|
|
187053
|
+
function createFollowPrinters(ctx, options) {
|
|
187054
|
+
const jsonLine = (payload) => JSON.stringify(payload);
|
|
187055
|
+
const printers = [];
|
|
187056
|
+
if (options.logs) {
|
|
187057
|
+
printers.push(createPrintingCursor(ctx, options.runnerId, { runId: options.runId, stream: "run-logs" }, formatRunLogLine));
|
|
187058
|
+
}
|
|
187059
|
+
if (options.runEvents) {
|
|
187060
|
+
printers.push(createPrintingCursor(ctx, options.runnerId, { runId: options.runId, stream: "run-events" }, jsonLine));
|
|
187061
|
+
}
|
|
187062
|
+
if (options.recorderSinceSequence !== undefined) {
|
|
187063
|
+
printers.push(createPrintingCursor(ctx, options.runnerId, { sinceSequence: options.recorderSinceSequence, stream: "recorder" }, jsonLine));
|
|
187064
|
+
}
|
|
187065
|
+
return printers;
|
|
187066
|
+
}
|
|
187067
|
+
|
|
187068
|
+
// src/domains/interactiveRunner/followRun.ts
|
|
187069
|
+
var pollIntervalMs3 = 1000;
|
|
187015
187070
|
function reportSettlement(ctx, settlement) {
|
|
187016
187071
|
if (settlement.type === "passed") {
|
|
187017
187072
|
ctx.ui.success(interactiveRunnerMessages.runPassed);
|
|
@@ -187023,40 +187078,49 @@ function reportSettlement(ctx, settlement) {
|
|
|
187023
187078
|
};
|
|
187024
187079
|
}
|
|
187025
187080
|
async function followRun(ctx, options, deps) {
|
|
187026
|
-
const
|
|
187027
|
-
runId: options.runId,
|
|
187028
|
-
stream: "run-logs"
|
|
187029
|
-
});
|
|
187081
|
+
const printers = createFollowPrinters(ctx, options);
|
|
187030
187082
|
const readStatus = createJournalCursor(ctx, options.runnerId, {
|
|
187031
187083
|
runId: options.runId,
|
|
187032
187084
|
stream: "run-status"
|
|
187033
187085
|
});
|
|
187034
187086
|
const unreachable = createUnreachableBudget(pollIntervalMs3);
|
|
187035
|
-
const
|
|
187036
|
-
const
|
|
187037
|
-
|
|
187038
|
-
|
|
187039
|
-
|
|
187040
|
-
ctx.ui.stream(entry, formatRunLogLine(entry.payload));
|
|
187087
|
+
const printAll = async () => {
|
|
187088
|
+
for (const print of printers) {
|
|
187089
|
+
const window2 = await print();
|
|
187090
|
+
if (window2.type !== "entries")
|
|
187091
|
+
return window2;
|
|
187041
187092
|
}
|
|
187042
|
-
return
|
|
187093
|
+
return;
|
|
187094
|
+
};
|
|
187095
|
+
let progressReported = false;
|
|
187096
|
+
const printProgress = (entries) => {
|
|
187097
|
+
if (printers.length > 0 || progressReported)
|
|
187098
|
+
return;
|
|
187099
|
+
const entry = entries.find((e) => readRunSettlement(e.payload).type === "in-progress");
|
|
187100
|
+
if (entry === undefined)
|
|
187101
|
+
return;
|
|
187102
|
+
progressReported = true;
|
|
187103
|
+
ctx.ui.stream(entry, interactiveRunnerMessages.runInProgress);
|
|
187043
187104
|
};
|
|
187044
187105
|
const maxPolls = Math.max(1, Math.ceil(options.timeoutSeconds * 1000 / pollIntervalMs3));
|
|
187045
187106
|
for (let poll = 1;; poll++) {
|
|
187046
|
-
const
|
|
187047
|
-
if (
|
|
187048
|
-
return journalReadFailure(
|
|
187049
|
-
const status =
|
|
187107
|
+
const interrupted = await printAll();
|
|
187108
|
+
if (interrupted?.type === "failed")
|
|
187109
|
+
return journalReadFailure(interrupted);
|
|
187110
|
+
const status = interrupted?.type === "unreachable" ? interrupted : await readStatus();
|
|
187050
187111
|
if (status.type === "failed")
|
|
187051
187112
|
return journalReadFailure(status);
|
|
187052
|
-
if (
|
|
187113
|
+
if (status.type === "unreachable") {
|
|
187053
187114
|
if (unreachable.exhausted())
|
|
187054
187115
|
return { ...unreachableFailure };
|
|
187055
187116
|
} else {
|
|
187056
187117
|
unreachable.reset();
|
|
187118
|
+
printProgress(status.entries);
|
|
187057
187119
|
const settlement = findSettlement(status.entries);
|
|
187058
187120
|
if (settlement !== undefined) {
|
|
187059
|
-
await
|
|
187121
|
+
if (await printAll() !== undefined) {
|
|
187122
|
+
ctx.ui.warn(interactiveRunnerMessages.followEndCutShort);
|
|
187123
|
+
}
|
|
187060
187124
|
return reportSettlement(ctx, settlement);
|
|
187061
187125
|
}
|
|
187062
187126
|
}
|
|
@@ -187094,6 +187158,13 @@ async function handleRunnerRun(ctx, options, deps) {
|
|
|
187094
187158
|
return { ...failureFields(resolved), exitCode: resolved.exitCode };
|
|
187095
187159
|
}
|
|
187096
187160
|
announceRunner(ctx, resolved);
|
|
187161
|
+
let recorderSinceSequence;
|
|
187162
|
+
if (options.recorderEvents) {
|
|
187163
|
+
const anchor = await resolveRecorderAnchor(ctx, resolved, deps);
|
|
187164
|
+
if (!anchor.ok)
|
|
187165
|
+
return { ...anchor.failure };
|
|
187166
|
+
recorderSinceSequence = anchor.sinceSequence;
|
|
187167
|
+
}
|
|
187097
187168
|
const result = await ctx.platformClient.callPublicApi(publicContractsV1.runner.runFlow, { entryPointPath, files, id: resolved.runnerId }, runnerCallOptions);
|
|
187098
187169
|
if (!result.ok) {
|
|
187099
187170
|
return { ...failureFields(result), exitCode: exitCodes.network };
|
|
@@ -187111,12 +187182,16 @@ async function handleRunnerRun(ctx, options, deps) {
|
|
|
187111
187182
|
};
|
|
187112
187183
|
case "submitted": {
|
|
187113
187184
|
const runId = result.value.runId;
|
|
187114
|
-
|
|
187185
|
+
const follow = options.follow || options.logs || options.runEvents || options.recorderEvents;
|
|
187186
|
+
if (!follow) {
|
|
187115
187187
|
ctx.ui.output({ runId, runnerId: resolved.runnerId }, interactiveRunnerMessages.runSubmitted(runId));
|
|
187116
187188
|
return;
|
|
187117
187189
|
}
|
|
187118
187190
|
ctx.ui.info(interactiveRunnerMessages.runSubmitted(runId));
|
|
187119
187191
|
return followRun(ctx, {
|
|
187192
|
+
logs: options.logs,
|
|
187193
|
+
recorderSinceSequence,
|
|
187194
|
+
runEvents: options.runEvents,
|
|
187120
187195
|
runId,
|
|
187121
187196
|
runnerId: resolved.runnerId,
|
|
187122
187197
|
timeoutSeconds: timeout.seconds
|
|
@@ -187136,16 +187211,20 @@ async function handleRunnerRun(ctx, options, deps) {
|
|
|
187136
187211
|
var runExamples2 = `
|
|
187137
187212
|
Examples:
|
|
187138
187213
|
$ qawolf runner run flows/checkout.flow.ts
|
|
187139
|
-
$ qawolf runner run flows/checkout.flow.ts --follow
|
|
187214
|
+
$ qawolf runner run flows/checkout.flow.ts --follow
|
|
187215
|
+
$ qawolf runner run flows/checkout.flow.ts --follow --logs`;
|
|
187140
187216
|
var eventsExamples = `
|
|
187141
187217
|
Examples:
|
|
187142
187218
|
$ qawolf runner events recorder --tail 5
|
|
187143
187219
|
$ qawolf runner events run-logs --run <runId> --follow
|
|
187144
187220
|
$ qawolf runner events console --since 120 --json`;
|
|
187145
187221
|
function registerRunnerRunCommands(runner, signals) {
|
|
187146
|
-
declareCommandKind(runner.command("run <file>"), "write").description("Run a flow on an interactive runner, shipping the current directory's files with it").option("--follow", "
|
|
187222
|
+
declareCommandKind(runner.command("run <file>"), "write").description("Run a flow on an interactive runner, shipping the current directory's files with it").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("--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((file2, opts, command) => withAuthContext(signals, (ctx) => handleRunnerRun(ctx, {
|
|
187147
187223
|
entryPoint: file2,
|
|
187148
187224
|
follow: opts.follow,
|
|
187225
|
+
logs: opts.logs,
|
|
187226
|
+
recorderEvents: opts.recorderEvents,
|
|
187227
|
+
runEvents: opts.runEvents,
|
|
187149
187228
|
runner: opts.runner,
|
|
187150
187229
|
timeout: opts.timeout
|
|
187151
187230
|
}, runnerDeps(ctx)))(opts, command));
|
|
@@ -187205,4 +187284,4 @@ createProgram({ signals }).parseAsync().catch(() => {
|
|
|
187205
187284
|
process.exitCode = 1;
|
|
187206
187285
|
}).finally(() => flushAndExit(typeof process.exitCode === "number" ? process.exitCode : 0));
|
|
187207
187286
|
|
|
187208
|
-
//# debugId=
|
|
187287
|
+
//# debugId=8CB687F9691544E564756E2164756E21
|
package/package.json
CHANGED
|
@@ -166,13 +166,23 @@ The call answers with a run id as soon as the run is accepted. **The outcome is
|
|
|
166
166
|
not in that answer**, it is in the `run-status` stream, whose entries carry
|
|
167
167
|
`runId`, `status` and an `errorMessage` when there is one.
|
|
168
168
|
|
|
169
|
-
**Pass `--follow` to `run` and let it wait for you.** It
|
|
170
|
-
|
|
169
|
+
**Pass `--follow` to `run` and let it wait for you.** It reports the run's
|
|
170
|
+
status — in progress, then passed or failed — and ends on the settled status.
|
|
171
|
+
Exit code `1` means the run did not pass. Three flags mirror more streams into
|
|
172
|
+
the follow, and each implies `--follow` on its own: `--logs` streams every log
|
|
173
|
+
line the run produces, `--run-events` streams the run's progress events as JSON
|
|
174
|
+
lines, and `--recorder-events` streams the browser actions the runner records
|
|
175
|
+
as JSON lines — the recorder is runner-wide rather than run-scoped, so that one
|
|
176
|
+
carries whatever is recorded after an anchor taken just before submission. Whatever mirrors are on, the
|
|
177
|
+
follow still ends on the status, never on them, so a run that prints nothing
|
|
171
178
|
still terminates the follow and a run that dies mid-sentence still reports how.
|
|
172
|
-
|
|
179
|
+
Combining mirror flags interleaves their lines with nothing saying which stream
|
|
180
|
+
a line came from — fine for eyeballs; when parsing, follow one stream at a time.
|
|
173
181
|
|
|
174
182
|
```sh
|
|
175
183
|
qawolf runner run flows/checkout.flow.ts --follow
|
|
184
|
+
qawolf runner run flows/checkout.flow.ts --follow --logs
|
|
185
|
+
qawolf runner run flows/checkout.flow.ts --follow --recorder-events
|
|
176
186
|
```
|
|
177
187
|
|
|
178
188
|
If you would rather submit and come back later, note that `--follow` on `events`
|