@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 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.9.2",
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/followRun.ts
187000
- var pollIntervalMs3 = 1000;
187001
- function findSettlement(entries) {
187002
- for (const entry of entries) {
187003
- const settlement = readRunSettlement(entry.payload);
187004
- if (settlement.type !== "settled")
187005
- continue;
187006
- if (settlement.status === "passed")
187007
- return { type: "passed" };
187008
- if (settlement.status === "failed") {
187009
- return { errorMessage: settlement.errorMessage, type: "failed" };
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
- return { status: settlement.status, type: "unrecognized" };
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 readLogs = createJournalCursor(ctx, options.runnerId, {
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 printLogs = async () => {
187036
- const logs = await readLogs();
187037
- if (logs.type !== "entries")
187038
- return logs;
187039
- for (const entry of logs.entries) {
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 logs;
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 logs = await printLogs();
187047
- if (logs.type === "failed")
187048
- return journalReadFailure(logs);
187049
- const status = logs.type === "unreachable" ? logs : await readStatus();
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 (logs.type === "unreachable" || status.type === "unreachable") {
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 printLogs();
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
- if (!options.follow) {
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", "Stream the run's logs until it settles", 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, {
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=80A0CD4106C384B664756E2164756E21
187287
+ //# debugId=8CB687F9691544E564756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qawolf/cli",
3
- "version": "1.9.2",
3
+ "version": "1.10.0",
4
4
  "description": "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
5
5
  "keywords": [
6
6
  "automation",
@@ -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 streams the run's logs
170
- and ends on the settled status, never on the logs, so a run that prints nothing
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
- Exit code `1` means the run did not pass.
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`