@qawolf/cli 1.37.0 → 1.38.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
@@ -16903,6 +16903,72 @@ var makeCreateRunContract = (ids) => {
16903
16903
  };
16904
16904
  };
16905
16905
 
16906
+ // node_modules/@qawolf/api-contracts/dist/v1/run/listScreenshotComparisons.js
16907
+ var maxListedScreenshotAttempts = 20;
16908
+ var makeListScreenshotComparisonsContract = (ids) => {
16909
+ const input = object({
16910
+ flowIds: array(ids.flow).min(1).max(100).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").optional().describe("Only list the comparisons of these flows. Each flow must have a result in the run. Omit to list every failed flow of the run."),
16911
+ runId: ids.run
16912
+ });
16913
+ const imageUrl = (image) => url().describe(`Signed URL of the full-size ${image} PNG. It stops working at expiresAt.`);
16914
+ const previewUrl = (image) => url().optional().describe(`Signed URL of a JPEG copy of the ${image} image, scaled down so its long edge is at most 1568 pixels. ` + "Fetch this one to look at the image: it is small enough to view inline, while the full-size PNG can be too large. " + "It stops working at expiresAt. Absent when QA Wolf could not make the copy, for example because the full-size image is gone.");
16915
+ const passedEnvironment = object({
16916
+ environmentId: ids.environment,
16917
+ lastPassedAt: exports_iso.datetime().describe("When the latest passing comparison in this environment ran.")
16918
+ });
16919
+ const passingElsewhere = discriminatedUnion("status", [
16920
+ object({
16921
+ status: literal("not-tracked").describe("QA Wolf does not record screenshot comparisons for this workspace, so it cannot tell.")
16922
+ }),
16923
+ object({
16924
+ environments: array(passedEnvironment).describe("Other environments where the current baseline passed this screenshot in the last 14 days, most recent first. " + "Empty when it passed nowhere else, or when the baseline no longer exists."),
16925
+ status: literal("tracked")
16926
+ })
16927
+ ]).describe("Whether the current baseline still passes in other environments. " + "A baseline that passes elsewhere points to a difference in this environment rather than an outdated baseline.");
16928
+ const comparison = object({
16929
+ actualPreviewUrl: previewUrl("actual"),
16930
+ actualUrl: imageUrl("actual (the screenshot the flow took)"),
16931
+ attemptCompletedAt: exports_iso.datetime().describe("When the run attempt that made the comparison finished."),
16932
+ baselineStatus: _enum([
16933
+ "unchanged",
16934
+ "accepted",
16935
+ "changed-since-run",
16936
+ "deleted",
16937
+ "unknown"
16938
+ ]).describe('How the current baseline relates to the expected image this comparison used. "unchanged": the baseline is still the expected image. ' + `"accepted": the baseline is now this comparison's actual screenshot, because someone accepted it, so a reattempt should pass. ` + '"changed-since-run": someone replaced the baseline with another image after this run, so rerun the flow before deciding anything. ' + '"deleted": the baseline no longer exists, and the next run will save its screenshot as the new baseline. ' + `"unknown": the run's copy of the expected image is gone, so QA Wolf cannot compare.`),
16939
+ comparisonId: string2().describe("Identifies this comparison: the run attempt id and the position of the comparison in that attempt."),
16940
+ diffPreviewUrl: previewUrl("diff"),
16941
+ diffUrl: imageUrl("diff (the pixels that differ are highlighted)"),
16942
+ expectedPreviewUrl: previewUrl("expected"),
16943
+ expectedUrl: imageUrl("expected (the baseline as it was when the flow ran)"),
16944
+ filePath: string2().optional().describe("The flow file that made the comparison."),
16945
+ flowId: ids.flow,
16946
+ lineNumber: number2().int().optional().describe("The line of filePath that made the comparison."),
16947
+ name: string2().describe("The screenshot name the flow passed to toHaveScreenshot. The baseline is stored as _screenshots_/<name>.png in team storage."),
16948
+ passingElsewhere,
16949
+ runAttemptId: string2(),
16950
+ similarity: number2().describe("How much of the actual image matches the expected image, in percent."),
16951
+ sizeMismatch: boolean2().describe("The actual and expected images have different dimensions. That fails the comparison whatever the pixels show, and similarity reads 0.")
16952
+ });
16953
+ const output = object({
16954
+ comparisons: array(comparison).describe(`Each screenshot comparison that failed its flow, from the ${maxListedScreenshotAttempts} most recent failed attempts of the selected flows, oldest attempt first.`),
16955
+ expiresAt: exports_iso.datetime().describe("When the image URLs stop working. Call run.listScreenshotComparisons again for fresh ones after it."),
16956
+ truncated: boolean2().describe(`True when the selected flows have more than ${maxListedScreenshotAttempts} failed attempts. Only the ${maxListedScreenshotAttempts} most recent are listed, and the older attempts cannot be listed.`)
16957
+ });
16958
+ return {
16959
+ annotations: {
16960
+ destructiveHint: false,
16961
+ openWorldHint: false,
16962
+ readOnlyHint: false
16963
+ },
16964
+ description: "List the screenshot comparisons that failed a run's flows, with the expected, actual and diff images of each. " + "Only comparisons that caused a failure are listed: a screenshot that differs within tolerance, or that differs on a line that did not fail, is left out. " + "Each comparison tells whether its baseline changed since the run and whether the baseline still passes in other environments. " + "The image URLs expire after a few minutes; fetch the preview URLs to look at the images. " + "The first call for an image makes its missing JPEG preview and keeps it in team storage, so later calls reuse it.",
16965
+ input,
16966
+ kind: "read",
16967
+ name: "run.listScreenshotComparisons",
16968
+ output
16969
+ };
16970
+ };
16971
+
16906
16972
  // node_modules/@qawolf/api-contracts/dist/v1/runner/actionSequence.js
16907
16973
  var maxActionsPerRequest = 10;
16908
16974
  var screenshotModes = ["none", "final", "each"];
@@ -18968,11 +19034,22 @@ var makeGetIssueContract = (ids) => {
18968
19034
  var investigationFindingStateDescription = 'What the investigation concluded about the cause. Known values: "investigating", "fixed", "fix-proposed", "reported", "report-proposed", "flake", "question" and "dismissed". New values can appear.';
18969
19035
  var makeInvestigationFindingSchema = (ids) => object({
18970
19036
  actual: string2().optional(),
18971
- commitHash: string2().optional(),
19037
+ commitHash: string2().optional().describe("The commit of a commit fix. Same as `fix.commitHash`."),
18972
19038
  decidedBy: _enum(["agent", "user"]).optional().describe("Who decided: the AI on its own, or a person who approved or dismissed it. Absent until someone decides."),
18973
19039
  disputeReason: string2().optional().describe("Why a person disputed what the AI did. Present on a proposal the AI made again after that dispute."),
18974
19040
  expected: string2().optional(),
18975
19041
  findingId: string2(),
19042
+ fix: discriminatedUnion("type", [
19043
+ object({
19044
+ commitHash: string2().describe("The commit that changes the flow code."),
19045
+ type: literal("commit")
19046
+ }),
19047
+ object({
19048
+ baselineChangeId: string2().optional().describe('The baseline change that made the new screenshot the baseline, on a "fixed" finding. `run restoreScreenshotBaseline` undoes it.'),
19049
+ comparisonId: string2().describe("The failed screenshot comparison whose new screenshot becomes the baseline, as `run listScreenshotComparisons` names it."),
19050
+ type: literal("baseline")
19051
+ })
19052
+ ]).optional().describe('The fix of a "fixed" or "fix-proposed" finding: a commit to the flow code, or a new screenshot baseline.'),
18976
19053
  flowIds: array(ids.flow),
18977
19054
  headline: string2().optional(),
18978
19055
  issueId: ids.issue.optional(),
@@ -18983,8 +19060,9 @@ var makeInvestigationFindingSchema = (ids) => object({
18983
19060
  answers: array(object({
18984
19061
  description: string2().optional(),
18985
19062
  title: string2(),
18986
- value: string2().describe('What choosing this answer means. Known values: "apply", "dismiss", "report", "maintenance" and "bug". New values can appear.')
19063
+ value: string2().describe('What choosing this answer means. Known values: "apply", "dismiss", "report", "maintenance", "bug" and "baseline". New values can appear.')
18987
19064
  })),
19065
+ comparisonId: string2().optional().describe('The failed screenshot comparison a screenshot question is about, as `run listScreenshotComparisons` names it. Its answers are "bug" (the old screenshot is right), "maintenance" (both are right) and "baseline" (the new screenshot is right).'),
18988
19066
  text: string2()
18989
19067
  }).optional().describe("The question waiting for a person, with the answers offered."),
18990
19068
  response: object({
@@ -19004,6 +19082,7 @@ var makeInvestigationFindingSchema = (ids) => object({
19004
19082
  var makeGetInvestigationContract = (ids) => {
19005
19083
  const input = object({ runId: ids.run });
19006
19084
  const output = resource({
19085
+ baselineChanges: _enum(["automatic", "needs-approval"]).describe('How the investigation can make a new screenshot the baseline. "automatic": it can accept a baseline on its own. ' + '"needs-approval": it proposes the baseline on a fix-proposed finding, and accepts it only after a person applies the proposal.'),
19007
19086
  findings: array(makeInvestigationFindingSchema(ids)),
19008
19087
  runId: ids.run,
19009
19088
  sessionId: ids.chatSession.optional().describe("The investigation session. Absent when the run has none."),
@@ -19097,7 +19176,8 @@ var makeCreateIssueContract = (ids) => {
19097
19176
  // node_modules/@qawolf/api-contracts/dist/v1/issue/removeFlows.js
19098
19177
  var makeRemoveFlowsFromIssueContract = (ids) => {
19099
19178
  const input = object({
19100
- flowIds: array(ids.flow).min(1).max(300).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").describe("The flows the coverage request no longer covers."),
19179
+ environmentId: ids.environment.optional().describe("The environment whose bug or maintenance reproductions to remove. Required when a selected flow is linked in more than one environment."),
19180
+ flowIds: array(ids.flow).min(1).max(300).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").describe("The flows to remove from the issue."),
19101
19181
  issueId: ids.issue
19102
19182
  });
19103
19183
  const output = object({
@@ -19106,10 +19186,10 @@ var makeRemoveFlowsFromIssueContract = (ids) => {
19106
19186
  return {
19107
19187
  annotations: {
19108
19188
  destructiveHint: true,
19109
- openWorldHint: false,
19189
+ openWorldHint: true,
19110
19190
  readOnlyHint: false
19111
19191
  },
19112
- description: "Remove flows from a coverage request owned by the caller's team. " + "Flows the request does not cover are left alone. " + "Bug and maintenance reports link to flows through the runs that reproduce them, so their flows cannot be set directly.",
19192
+ description: "Remove flows from a coverage request or remove bug/maintenance reproductions in an environment. " + "Unlinked flows are left alone. Reports auto-close when no active reproductions remain and auto-close is enabled. " + "For reports, omit environmentId only when each selected flow is linked in at most one environment.",
19113
19193
  input,
19114
19194
  kind: "write",
19115
19195
  name: "issue.removeFlows",
@@ -19269,6 +19349,49 @@ var makeLegacyTriggerContracts = (ids) => ({
19269
19349
  resume: makeResumeLegacyTriggerContract(ids)
19270
19350
  });
19271
19351
 
19352
+ // node_modules/@qawolf/api-contracts/dist/v1/run/investigationSessionId.js
19353
+ var investigationSessionId = string2().trim().min(1).max(100).optional();
19354
+
19355
+ // node_modules/@qawolf/api-contracts/dist/v1/run/acceptScreenshotBaseline.js
19356
+ var makeAcceptScreenshotBaselineContract = (ids) => {
19357
+ const input = object({
19358
+ chatSessionId: investigationSessionId.describe("The chat session of the investigation that accepts the baseline. A workspace or organization API key acts as an automation, so it must send the session that investigates the run. A person's call ignores it."),
19359
+ comparisonId: string2().trim().min(1).max(100).describe("The failed screenshot comparison whose new screenshot becomes the baseline, as `run.listScreenshotComparisons` names it."),
19360
+ runId: ids.run
19361
+ });
19362
+ const output = discriminatedUnion("outcome", [
19363
+ object({
19364
+ baselineChangeId: string2().describe("The baseline change. Record it on the fixed finding, and pass it to `run.restoreScreenshotBaseline` with this runId to put the old baseline back."),
19365
+ name: string2().describe("The screenshot name whose baseline changed."),
19366
+ outcome: literal("accepted").describe("The new screenshot is now the baseline. The old baseline is kept, so the change can be restored.")
19367
+ }),
19368
+ object({
19369
+ outcome: literal("baseline-changed").describe("The baseline is no longer the one the run compared with: someone changed or deleted it since. Nothing changed. List the comparisons again to see the current baseline.")
19370
+ }),
19371
+ object({
19372
+ outcome: literal("needs-approval").describe("Only a person can approve this change. Nothing changed. Propose the new baseline on a fix-proposed finding and wait for a person to apply it.")
19373
+ }),
19374
+ object({
19375
+ outcome: literal("not-investigation-session").describe("An API key that is not a person's can change a baseline only as the investigation of the run, and chatSessionId is missing or is not the session that investigates the run. Nothing changed.")
19376
+ }),
19377
+ object({
19378
+ outcome: literal("nothing-to-accept").describe("The comparison did not fail, or has no new screenshot to accept. Nothing changed.")
19379
+ })
19380
+ ]);
19381
+ return {
19382
+ annotations: {
19383
+ destructiveHint: true,
19384
+ openWorldHint: false,
19385
+ readOnlyHint: false
19386
+ },
19387
+ description: "Make the new screenshot of a failed screenshot comparison the baseline, so later runs compare with it. " + "Use it only when the difference is an intended change of the app, not a bug, noise, or a page that was not ready. " + "Every later run of every environment of the workspace compares with the new baseline. " + "Refuses without changing anything when the baseline changed since the run.",
19388
+ input,
19389
+ kind: "write",
19390
+ name: "run.acceptScreenshotBaseline",
19391
+ output
19392
+ };
19393
+ };
19394
+
19272
19395
  // node_modules/@qawolf/api-contracts/dist/v1/run/diagnose.js
19273
19396
  var makeDiagnoseRunContract = (ids) => {
19274
19397
  const input = object({
@@ -19285,7 +19408,7 @@ var makeDiagnoseRunContract = (ids) => {
19285
19408
  openWorldHint: true,
19286
19409
  readOnlyHint: false
19287
19410
  },
19288
- description: "Diagnose failed flows in a run as reproductions of a bug or maintenance report owned by the caller's team. " + "The issue's type selects the diagnosis. " + "Each flow must have failed in the run. " + "A flow that is already diagnosed moves to this issue. " + "The diagnosis appears on the run, and the reproduction appears under the issue's reproductions. " + "Coverage requests cannot be diagnosed; use issue.addFlows to cover flows instead.",
19411
+ description: "Diagnose failed flows in a run as reproductions of a bug or maintenance report owned by the caller's team. " + "The issue's type selects the diagnosis. " + "Each flow must have failed in the run. " + "A flow that is already diagnosed in the run moves to this issue, and may stay linked to other open reports. " + "The diagnosis appears on the run, and the reproduction appears under the issue's reproductions. " + "Coverage requests cannot be diagnosed; use issue.addFlows to cover flows instead.",
19289
19412
  input,
19290
19413
  kind: "write",
19291
19414
  name: "run.diagnose",
@@ -19323,6 +19446,22 @@ var makeFindRunsContract = (ids) => {
19323
19446
  };
19324
19447
  };
19325
19448
 
19449
+ // node_modules/@qawolf/api-contracts/dist/v1/run/flowFailure.js
19450
+ var makeFlowFailureSchema = (ids) => {
19451
+ const diagnosis = object({
19452
+ issueId: ids.issue,
19453
+ type: _enum(["bug", "maintenance"])
19454
+ }).optional().describe("QA Wolf's investigation verdict for the failure: `bug` means the " + "application is broken, `maintenance` means the test needed an " + "update and the failure does not indicate an application problem. " + "Absent until the investigation reaches a verdict, and absent when " + "the flow was opted out of investigation. Pass issueId to issue.get " + "for details.");
19455
+ const optedOutOfInvestigation = object({
19456
+ by: _enum(["user", "system"]).describe("Who stopped the investigation: `user` is a person, in the app or " + "with a user API key, or the workspace's automation user for a " + "team or organization API key, for example through " + "run.optOutOfInvestigation; `system` is QA Wolf, when the " + "investigation expired.")
19457
+ }).optional().describe('Present when the failure was marked "do not investigate" (DNI): ' + "it will get no bug or maintenance verdict.");
19458
+ return object({
19459
+ diagnosis,
19460
+ error: string2(),
19461
+ optedOutOfInvestigation
19462
+ });
19463
+ };
19464
+
19326
19465
  // node_modules/@qawolf/api-contracts/dist/v1/run/get.js
19327
19466
  var makeGetRunContract = (ids) => {
19328
19467
  const flowStatus = _enum(publicRunStatusValues);
@@ -19370,16 +19509,9 @@ var makeGetRunContract = (ids) => {
19370
19509
  });
19371
19510
  const attempt = union([automatedAttempt, manualAttempt]);
19372
19511
  const attempts = array(attempt).optional().describe("The flow's finished execution attempts, oldest first, including " + "manual Wolf Browser attempts. Present once at least one attempt has " + "finished, so a flow that passed after retries also lists its failed " + "attempts. Artifact URLs appear only on automated attempts that " + "reached a verdict, stay valid for at least a day (call run.get " + "again for fresh ones), and can return 404 when the attempt did not " + "produce that artifact.");
19373
- const diagnosis = object({
19374
- issueId: ids.issue,
19375
- type: _enum(["bug", "maintenance"])
19376
- }).optional().describe("QA Wolf's investigation verdict for the failure: `bug` means the " + "application is broken, `maintenance` means the test needed an " + "update and the failure does not indicate an application problem. " + "Absent until the investigation reaches a verdict. Pass issueId to " + "issue.get for details.");
19377
19512
  const failedFlow = object({
19378
19513
  attempts,
19379
- failure: object({
19380
- diagnosis,
19381
- error: string2()
19382
- }),
19514
+ failure: makeFlowFailureSchema(ids),
19383
19515
  flowId: ids.flow,
19384
19516
  name: string2(),
19385
19517
  status: literal("failed")
@@ -19488,6 +19620,30 @@ var makeGetRunAttemptArtifactsContract = (ids) => {
19488
19620
  };
19489
19621
  };
19490
19622
 
19623
+ // node_modules/@qawolf/api-contracts/dist/v1/run/optOutOfInvestigation.js
19624
+ var makeOptOutOfInvestigationRunContract = (ids) => {
19625
+ const input = object({
19626
+ flowIds: array(ids.flow).min(1).max(100).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").describe("The failed flows to stop investigating. Every named flow must be " + "eligible or the whole request is rejected."),
19627
+ runId: ids.run
19628
+ });
19629
+ const output = resource({
19630
+ optedOutFlowIds: array(ids.flow).describe("The flows that no longer need investigation."),
19631
+ runId: ids.run
19632
+ }, { urlFieldDescription: "Absolute URL of the run page." });
19633
+ return {
19634
+ annotations: {
19635
+ destructiveHint: true,
19636
+ openWorldHint: true,
19637
+ readOnlyHint: false
19638
+ },
19639
+ description: 'Mark failed flows in a run as "do not investigate" (DNI): the failures need no bug or maintenance report. ' + "Each flow must have failed in the run and still be under investigation. " + "A flow that already has a diagnosis cannot be opted out. " + "A team or organization key records the opt-out as the workspace's automation user. " + "To link a failure to a report instead, use run.diagnose.",
19640
+ input,
19641
+ kind: "write",
19642
+ name: "run.optOutOfInvestigation",
19643
+ output
19644
+ };
19645
+ };
19646
+
19491
19647
  // node_modules/@qawolf/api-contracts/dist/v1/run/reattempt.js
19492
19648
  var makeReattemptRunContract = (ids) => {
19493
19649
  const input = object({
@@ -19512,6 +19668,45 @@ var makeReattemptRunContract = (ids) => {
19512
19668
  };
19513
19669
  };
19514
19670
 
19671
+ // node_modules/@qawolf/api-contracts/dist/v1/run/restoreScreenshotBaseline.js
19672
+ var makeRestoreScreenshotBaselineContract = (ids) => {
19673
+ const input = object({
19674
+ baselineChangeId: string2().trim().min(1).max(100).describe("The baseline change to undo, as `run.acceptScreenshotBaseline` returned it for this run."),
19675
+ chatSessionId: investigationSessionId.describe("The chat session of the investigation that restores the baseline. A workspace or organization API key acts as an automation, so it must send the session that investigates the run. A person's call ignores it."),
19676
+ runId: ids.run
19677
+ });
19678
+ const output = discriminatedUnion("outcome", [
19679
+ object({
19680
+ name: string2().describe("The screenshot name whose baseline changed."),
19681
+ outcome: literal("restored").describe("The baseline the change replaced is the baseline again.")
19682
+ }),
19683
+ object({
19684
+ outcome: literal("baseline-changed").describe("The baseline changed again after this change, so restoring it would undo someone else's change. Nothing changed.")
19685
+ }),
19686
+ object({
19687
+ outcome: literal("needs-dispute").describe("Only a person can restore this change, unless a person disputed the finding that made it. Nothing changed.")
19688
+ }),
19689
+ object({
19690
+ outcome: literal("not-investigation-session").describe("An API key that is not a person's can change a baseline only as the investigation of the run, and chatSessionId is missing or is not the session that investigates the run. Nothing changed.")
19691
+ }),
19692
+ object({
19693
+ outcome: literal("nothing-to-restore").describe("The change was already restored, the name had no baseline before it, or the kept copy of the old baseline is missing or is not the baseline the change replaced. Nothing changed.")
19694
+ })
19695
+ ]);
19696
+ return {
19697
+ annotations: {
19698
+ destructiveHint: true,
19699
+ openWorldHint: false,
19700
+ readOnlyHint: false
19701
+ },
19702
+ description: "Undo a screenshot baseline change that accepted a failed comparison of the run: put back the baseline it replaced, so later runs compare with the old baseline again. " + "Use it when a person disputes a baseline the AI accepted. " + "Refuses without changing anything when the baseline changed again since.",
19703
+ input,
19704
+ kind: "write",
19705
+ name: "run.restoreScreenshotBaseline",
19706
+ output
19707
+ };
19708
+ };
19709
+
19515
19710
  // node_modules/@qawolf/api-contracts/dist/v1/run/stop.js
19516
19711
  var makeStopRunContract = (ids) => {
19517
19712
  const input = object({ runId: ids.run });
@@ -19832,7 +20027,7 @@ var makeRunFlowOnRunnerContract = (ids) => {
19832
20027
  env: runEnvironmentSchema.optional().describe("Environment variables to make available to the run."),
19833
20028
  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."),
19834
20029
  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."),
19835
- flowId: ids.flow.optional().describe("The QA Wolf flow this run is for. The run then sees it as `QAWOLF_WORKFLOW_ID`, the same value a platform run of that flow sees, so fixtures named and cleaned up by it match across both. Omit it when the flow is not in QA Wolf yet, and the run has no `QAWOLF_WORKFLOW_ID`."),
20030
+ flowId: ids.flow.optional().describe("The QA Wolf flow this run is for. The run then sees it as `QAWOLF_WORKFLOW_ID`, the same value a platform run of that flow sees, so fixtures named and cleaned up by it match across both. Omitted, the flow whose file is at `entryPointPath` on the named environment's flow-code branch stands in; a file not in QA Wolf yet, or a run given no environment, leaves the run with no `QAWOLF_WORKFLOW_ID`."),
19836
20031
  id: runnerIdSchema.describe("Id of the runner to run the flow on."),
19837
20032
  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."),
19838
20033
  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."),
@@ -20099,12 +20294,16 @@ var makeContractsV1 = (ids) => {
20099
20294
  },
20100
20295
  legacyTrigger: makeLegacyTriggerContracts(resolvedIds),
20101
20296
  run: {
20297
+ acceptScreenshotBaseline: makeAcceptScreenshotBaselineContract(resolvedIds),
20102
20298
  create: makeCreateRunContract(resolvedIds),
20103
20299
  diagnose: makeDiagnoseRunContract(resolvedIds),
20104
20300
  find: makeFindRunsContract(resolvedIds),
20105
20301
  get: makeGetRunContract(resolvedIds),
20106
20302
  getAttemptArtifacts: makeGetRunAttemptArtifactsContract(resolvedIds),
20303
+ listScreenshotComparisons: makeListScreenshotComparisonsContract(resolvedIds),
20304
+ optOutOfInvestigation: makeOptOutOfInvestigationRunContract(resolvedIds),
20107
20305
  reattempt: makeReattemptRunContract(resolvedIds),
20306
+ restoreScreenshotBaseline: makeRestoreScreenshotBaselineContract(resolvedIds),
20108
20307
  stop: makeStopRunContract(resolvedIds)
20109
20308
  },
20110
20309
  runner: makeRunnerContracts(resolvedIds),
@@ -22621,7 +22820,7 @@ function startUpdateCheck(deps) {
22621
22820
  // package.json
22622
22821
  var package_default = {
22623
22822
  name: "@qawolf/cli",
22624
- version: "1.37.0",
22823
+ version: "1.38.0",
22625
22824
  description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
22626
22825
  keywords: [
22627
22826
  "automation",
@@ -22692,7 +22891,7 @@ var package_default = {
22692
22891
  "@clack/prompts": "1.5.1",
22693
22892
  "@napi-rs/keyring": "1.3.0",
22694
22893
  "@oxc-node/core": "0.1.0",
22695
- "@qawolf/api-contracts": "0.74.0",
22894
+ "@qawolf/api-contracts": "0.77.0",
22696
22895
  "@qawolf/emails": "1.1.1",
22697
22896
  "@qawolf/flow-targets": "1.0.0",
22698
22897
  "@qawolf/flows": "0.1.4",
@@ -38943,4 +39142,4 @@ createProgram({ signals }).parseAsync().catch(() => {
38943
39142
  process.exitCode = 1;
38944
39143
  }).finally(() => exitWhenIdle(typeof process.exitCode === "number" ? process.exitCode : 0));
38945
39144
 
38946
- //# debugId=0EBF9D8CD8CB2E9E64756E2164756E21
39145
+ //# debugId=4535AB8728B9D01A64756E2164756E21
@@ -7265,6 +7265,72 @@ var makeCreateRunContract = (ids) => {
7265
7265
  };
7266
7266
  };
7267
7267
 
7268
+ // node_modules/@qawolf/api-contracts/dist/v1/run/listScreenshotComparisons.js
7269
+ var maxListedScreenshotAttempts = 20;
7270
+ var makeListScreenshotComparisonsContract = (ids) => {
7271
+ const input = object({
7272
+ flowIds: array(ids.flow).min(1).max(100).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").optional().describe("Only list the comparisons of these flows. Each flow must have a result in the run. Omit to list every failed flow of the run."),
7273
+ runId: ids.run
7274
+ });
7275
+ const imageUrl = (image) => url().describe(`Signed URL of the full-size ${image} PNG. It stops working at expiresAt.`);
7276
+ const previewUrl = (image) => url().optional().describe(`Signed URL of a JPEG copy of the ${image} image, scaled down so its long edge is at most 1568 pixels. ` + "Fetch this one to look at the image: it is small enough to view inline, while the full-size PNG can be too large. " + "It stops working at expiresAt. Absent when QA Wolf could not make the copy, for example because the full-size image is gone.");
7277
+ const passedEnvironment = object({
7278
+ environmentId: ids.environment,
7279
+ lastPassedAt: exports_iso.datetime().describe("When the latest passing comparison in this environment ran.")
7280
+ });
7281
+ const passingElsewhere = discriminatedUnion("status", [
7282
+ object({
7283
+ status: literal("not-tracked").describe("QA Wolf does not record screenshot comparisons for this workspace, so it cannot tell.")
7284
+ }),
7285
+ object({
7286
+ environments: array(passedEnvironment).describe("Other environments where the current baseline passed this screenshot in the last 14 days, most recent first. " + "Empty when it passed nowhere else, or when the baseline no longer exists."),
7287
+ status: literal("tracked")
7288
+ })
7289
+ ]).describe("Whether the current baseline still passes in other environments. " + "A baseline that passes elsewhere points to a difference in this environment rather than an outdated baseline.");
7290
+ const comparison = object({
7291
+ actualPreviewUrl: previewUrl("actual"),
7292
+ actualUrl: imageUrl("actual (the screenshot the flow took)"),
7293
+ attemptCompletedAt: exports_iso.datetime().describe("When the run attempt that made the comparison finished."),
7294
+ baselineStatus: _enum([
7295
+ "unchanged",
7296
+ "accepted",
7297
+ "changed-since-run",
7298
+ "deleted",
7299
+ "unknown"
7300
+ ]).describe('How the current baseline relates to the expected image this comparison used. "unchanged": the baseline is still the expected image. ' + `"accepted": the baseline is now this comparison's actual screenshot, because someone accepted it, so a reattempt should pass. ` + '"changed-since-run": someone replaced the baseline with another image after this run, so rerun the flow before deciding anything. ' + '"deleted": the baseline no longer exists, and the next run will save its screenshot as the new baseline. ' + `"unknown": the run's copy of the expected image is gone, so QA Wolf cannot compare.`),
7301
+ comparisonId: string2().describe("Identifies this comparison: the run attempt id and the position of the comparison in that attempt."),
7302
+ diffPreviewUrl: previewUrl("diff"),
7303
+ diffUrl: imageUrl("diff (the pixels that differ are highlighted)"),
7304
+ expectedPreviewUrl: previewUrl("expected"),
7305
+ expectedUrl: imageUrl("expected (the baseline as it was when the flow ran)"),
7306
+ filePath: string2().optional().describe("The flow file that made the comparison."),
7307
+ flowId: ids.flow,
7308
+ lineNumber: number2().int().optional().describe("The line of filePath that made the comparison."),
7309
+ name: string2().describe("The screenshot name the flow passed to toHaveScreenshot. The baseline is stored as _screenshots_/<name>.png in team storage."),
7310
+ passingElsewhere,
7311
+ runAttemptId: string2(),
7312
+ similarity: number2().describe("How much of the actual image matches the expected image, in percent."),
7313
+ sizeMismatch: boolean2().describe("The actual and expected images have different dimensions. That fails the comparison whatever the pixels show, and similarity reads 0.")
7314
+ });
7315
+ const output = object({
7316
+ comparisons: array(comparison).describe(`Each screenshot comparison that failed its flow, from the ${maxListedScreenshotAttempts} most recent failed attempts of the selected flows, oldest attempt first.`),
7317
+ expiresAt: exports_iso.datetime().describe("When the image URLs stop working. Call run.listScreenshotComparisons again for fresh ones after it."),
7318
+ truncated: boolean2().describe(`True when the selected flows have more than ${maxListedScreenshotAttempts} failed attempts. Only the ${maxListedScreenshotAttempts} most recent are listed, and the older attempts cannot be listed.`)
7319
+ });
7320
+ return {
7321
+ annotations: {
7322
+ destructiveHint: false,
7323
+ openWorldHint: false,
7324
+ readOnlyHint: false
7325
+ },
7326
+ description: "List the screenshot comparisons that failed a run's flows, with the expected, actual and diff images of each. " + "Only comparisons that caused a failure are listed: a screenshot that differs within tolerance, or that differs on a line that did not fail, is left out. " + "Each comparison tells whether its baseline changed since the run and whether the baseline still passes in other environments. " + "The image URLs expire after a few minutes; fetch the preview URLs to look at the images. " + "The first call for an image makes its missing JPEG preview and keeps it in team storage, so later calls reuse it.",
7327
+ input,
7328
+ kind: "read",
7329
+ name: "run.listScreenshotComparisons",
7330
+ output
7331
+ };
7332
+ };
7333
+
7268
7334
  // node_modules/@qawolf/api-contracts/dist/v1/runner/actionSequence.js
7269
7335
  var maxActionsPerRequest = 10;
7270
7336
  var screenshotModes = ["none", "final", "each"];
@@ -9337,11 +9403,22 @@ var makeGetIssueContract = (ids) => {
9337
9403
  var investigationFindingStateDescription = 'What the investigation concluded about the cause. Known values: "investigating", "fixed", "fix-proposed", "reported", "report-proposed", "flake", "question" and "dismissed". New values can appear.';
9338
9404
  var makeInvestigationFindingSchema = (ids) => object({
9339
9405
  actual: string2().optional(),
9340
- commitHash: string2().optional(),
9406
+ commitHash: string2().optional().describe("The commit of a commit fix. Same as `fix.commitHash`."),
9341
9407
  decidedBy: _enum(["agent", "user"]).optional().describe("Who decided: the AI on its own, or a person who approved or dismissed it. Absent until someone decides."),
9342
9408
  disputeReason: string2().optional().describe("Why a person disputed what the AI did. Present on a proposal the AI made again after that dispute."),
9343
9409
  expected: string2().optional(),
9344
9410
  findingId: string2(),
9411
+ fix: discriminatedUnion("type", [
9412
+ object({
9413
+ commitHash: string2().describe("The commit that changes the flow code."),
9414
+ type: literal("commit")
9415
+ }),
9416
+ object({
9417
+ baselineChangeId: string2().optional().describe('The baseline change that made the new screenshot the baseline, on a "fixed" finding. `run restoreScreenshotBaseline` undoes it.'),
9418
+ comparisonId: string2().describe("The failed screenshot comparison whose new screenshot becomes the baseline, as `run listScreenshotComparisons` names it."),
9419
+ type: literal("baseline")
9420
+ })
9421
+ ]).optional().describe('The fix of a "fixed" or "fix-proposed" finding: a commit to the flow code, or a new screenshot baseline.'),
9345
9422
  flowIds: array(ids.flow),
9346
9423
  headline: string2().optional(),
9347
9424
  issueId: ids.issue.optional(),
@@ -9352,8 +9429,9 @@ var makeInvestigationFindingSchema = (ids) => object({
9352
9429
  answers: array(object({
9353
9430
  description: string2().optional(),
9354
9431
  title: string2(),
9355
- value: string2().describe('What choosing this answer means. Known values: "apply", "dismiss", "report", "maintenance" and "bug". New values can appear.')
9432
+ value: string2().describe('What choosing this answer means. Known values: "apply", "dismiss", "report", "maintenance", "bug" and "baseline". New values can appear.')
9356
9433
  })),
9434
+ comparisonId: string2().optional().describe('The failed screenshot comparison a screenshot question is about, as `run listScreenshotComparisons` names it. Its answers are "bug" (the old screenshot is right), "maintenance" (both are right) and "baseline" (the new screenshot is right).'),
9357
9435
  text: string2()
9358
9436
  }).optional().describe("The question waiting for a person, with the answers offered."),
9359
9437
  response: object({
@@ -9373,6 +9451,7 @@ var makeInvestigationFindingSchema = (ids) => object({
9373
9451
  var makeGetInvestigationContract = (ids) => {
9374
9452
  const input = object({ runId: ids.run });
9375
9453
  const output = resource({
9454
+ baselineChanges: _enum(["automatic", "needs-approval"]).describe('How the investigation can make a new screenshot the baseline. "automatic": it can accept a baseline on its own. ' + '"needs-approval": it proposes the baseline on a fix-proposed finding, and accepts it only after a person applies the proposal.'),
9376
9455
  findings: array(makeInvestigationFindingSchema(ids)),
9377
9456
  runId: ids.run,
9378
9457
  sessionId: ids.chatSession.optional().describe("The investigation session. Absent when the run has none."),
@@ -9466,7 +9545,8 @@ var makeCreateIssueContract = (ids) => {
9466
9545
  // node_modules/@qawolf/api-contracts/dist/v1/issue/removeFlows.js
9467
9546
  var makeRemoveFlowsFromIssueContract = (ids) => {
9468
9547
  const input = object({
9469
- flowIds: array(ids.flow).min(1).max(300).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").describe("The flows the coverage request no longer covers."),
9548
+ environmentId: ids.environment.optional().describe("The environment whose bug or maintenance reproductions to remove. Required when a selected flow is linked in more than one environment."),
9549
+ flowIds: array(ids.flow).min(1).max(300).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").describe("The flows to remove from the issue."),
9470
9550
  issueId: ids.issue
9471
9551
  });
9472
9552
  const output = object({
@@ -9475,10 +9555,10 @@ var makeRemoveFlowsFromIssueContract = (ids) => {
9475
9555
  return {
9476
9556
  annotations: {
9477
9557
  destructiveHint: true,
9478
- openWorldHint: false,
9558
+ openWorldHint: true,
9479
9559
  readOnlyHint: false
9480
9560
  },
9481
- description: "Remove flows from a coverage request owned by the caller's team. " + "Flows the request does not cover are left alone. " + "Bug and maintenance reports link to flows through the runs that reproduce them, so their flows cannot be set directly.",
9561
+ description: "Remove flows from a coverage request or remove bug/maintenance reproductions in an environment. " + "Unlinked flows are left alone. Reports auto-close when no active reproductions remain and auto-close is enabled. " + "For reports, omit environmentId only when each selected flow is linked in at most one environment.",
9482
9562
  input,
9483
9563
  kind: "write",
9484
9564
  name: "issue.removeFlows",
@@ -9638,6 +9718,49 @@ var makeLegacyTriggerContracts = (ids) => ({
9638
9718
  resume: makeResumeLegacyTriggerContract(ids)
9639
9719
  });
9640
9720
 
9721
+ // node_modules/@qawolf/api-contracts/dist/v1/run/investigationSessionId.js
9722
+ var investigationSessionId = string2().trim().min(1).max(100).optional();
9723
+
9724
+ // node_modules/@qawolf/api-contracts/dist/v1/run/acceptScreenshotBaseline.js
9725
+ var makeAcceptScreenshotBaselineContract = (ids) => {
9726
+ const input = object({
9727
+ chatSessionId: investigationSessionId.describe("The chat session of the investigation that accepts the baseline. A workspace or organization API key acts as an automation, so it must send the session that investigates the run. A person's call ignores it."),
9728
+ comparisonId: string2().trim().min(1).max(100).describe("The failed screenshot comparison whose new screenshot becomes the baseline, as `run.listScreenshotComparisons` names it."),
9729
+ runId: ids.run
9730
+ });
9731
+ const output = discriminatedUnion("outcome", [
9732
+ object({
9733
+ baselineChangeId: string2().describe("The baseline change. Record it on the fixed finding, and pass it to `run.restoreScreenshotBaseline` with this runId to put the old baseline back."),
9734
+ name: string2().describe("The screenshot name whose baseline changed."),
9735
+ outcome: literal("accepted").describe("The new screenshot is now the baseline. The old baseline is kept, so the change can be restored.")
9736
+ }),
9737
+ object({
9738
+ outcome: literal("baseline-changed").describe("The baseline is no longer the one the run compared with: someone changed or deleted it since. Nothing changed. List the comparisons again to see the current baseline.")
9739
+ }),
9740
+ object({
9741
+ outcome: literal("needs-approval").describe("Only a person can approve this change. Nothing changed. Propose the new baseline on a fix-proposed finding and wait for a person to apply it.")
9742
+ }),
9743
+ object({
9744
+ outcome: literal("not-investigation-session").describe("An API key that is not a person's can change a baseline only as the investigation of the run, and chatSessionId is missing or is not the session that investigates the run. Nothing changed.")
9745
+ }),
9746
+ object({
9747
+ outcome: literal("nothing-to-accept").describe("The comparison did not fail, or has no new screenshot to accept. Nothing changed.")
9748
+ })
9749
+ ]);
9750
+ return {
9751
+ annotations: {
9752
+ destructiveHint: true,
9753
+ openWorldHint: false,
9754
+ readOnlyHint: false
9755
+ },
9756
+ description: "Make the new screenshot of a failed screenshot comparison the baseline, so later runs compare with it. " + "Use it only when the difference is an intended change of the app, not a bug, noise, or a page that was not ready. " + "Every later run of every environment of the workspace compares with the new baseline. " + "Refuses without changing anything when the baseline changed since the run.",
9757
+ input,
9758
+ kind: "write",
9759
+ name: "run.acceptScreenshotBaseline",
9760
+ output
9761
+ };
9762
+ };
9763
+
9641
9764
  // node_modules/@qawolf/api-contracts/dist/v1/run/diagnose.js
9642
9765
  var makeDiagnoseRunContract = (ids) => {
9643
9766
  const input = object({
@@ -9654,7 +9777,7 @@ var makeDiagnoseRunContract = (ids) => {
9654
9777
  openWorldHint: true,
9655
9778
  readOnlyHint: false
9656
9779
  },
9657
- description: "Diagnose failed flows in a run as reproductions of a bug or maintenance report owned by the caller's team. " + "The issue's type selects the diagnosis. " + "Each flow must have failed in the run. " + "A flow that is already diagnosed moves to this issue. " + "The diagnosis appears on the run, and the reproduction appears under the issue's reproductions. " + "Coverage requests cannot be diagnosed; use issue.addFlows to cover flows instead.",
9780
+ description: "Diagnose failed flows in a run as reproductions of a bug or maintenance report owned by the caller's team. " + "The issue's type selects the diagnosis. " + "Each flow must have failed in the run. " + "A flow that is already diagnosed in the run moves to this issue, and may stay linked to other open reports. " + "The diagnosis appears on the run, and the reproduction appears under the issue's reproductions. " + "Coverage requests cannot be diagnosed; use issue.addFlows to cover flows instead.",
9658
9781
  input,
9659
9782
  kind: "write",
9660
9783
  name: "run.diagnose",
@@ -9692,6 +9815,22 @@ var makeFindRunsContract = (ids) => {
9692
9815
  };
9693
9816
  };
9694
9817
 
9818
+ // node_modules/@qawolf/api-contracts/dist/v1/run/flowFailure.js
9819
+ var makeFlowFailureSchema = (ids) => {
9820
+ const diagnosis = object({
9821
+ issueId: ids.issue,
9822
+ type: _enum(["bug", "maintenance"])
9823
+ }).optional().describe("QA Wolf's investigation verdict for the failure: `bug` means the " + "application is broken, `maintenance` means the test needed an " + "update and the failure does not indicate an application problem. " + "Absent until the investigation reaches a verdict, and absent when " + "the flow was opted out of investigation. Pass issueId to issue.get " + "for details.");
9824
+ const optedOutOfInvestigation = object({
9825
+ by: _enum(["user", "system"]).describe("Who stopped the investigation: `user` is a person, in the app or " + "with a user API key, or the workspace's automation user for a " + "team or organization API key, for example through " + "run.optOutOfInvestigation; `system` is QA Wolf, when the " + "investigation expired.")
9826
+ }).optional().describe('Present when the failure was marked "do not investigate" (DNI): ' + "it will get no bug or maintenance verdict.");
9827
+ return object({
9828
+ diagnosis,
9829
+ error: string2(),
9830
+ optedOutOfInvestigation
9831
+ });
9832
+ };
9833
+
9695
9834
  // node_modules/@qawolf/api-contracts/dist/v1/run/get.js
9696
9835
  var makeGetRunContract = (ids) => {
9697
9836
  const flowStatus = _enum(publicRunStatusValues);
@@ -9739,16 +9878,9 @@ var makeGetRunContract = (ids) => {
9739
9878
  });
9740
9879
  const attempt = union([automatedAttempt, manualAttempt]);
9741
9880
  const attempts = array(attempt).optional().describe("The flow's finished execution attempts, oldest first, including " + "manual Wolf Browser attempts. Present once at least one attempt has " + "finished, so a flow that passed after retries also lists its failed " + "attempts. Artifact URLs appear only on automated attempts that " + "reached a verdict, stay valid for at least a day (call run.get " + "again for fresh ones), and can return 404 when the attempt did not " + "produce that artifact.");
9742
- const diagnosis = object({
9743
- issueId: ids.issue,
9744
- type: _enum(["bug", "maintenance"])
9745
- }).optional().describe("QA Wolf's investigation verdict for the failure: `bug` means the " + "application is broken, `maintenance` means the test needed an " + "update and the failure does not indicate an application problem. " + "Absent until the investigation reaches a verdict. Pass issueId to " + "issue.get for details.");
9746
9881
  const failedFlow = object({
9747
9882
  attempts,
9748
- failure: object({
9749
- diagnosis,
9750
- error: string2()
9751
- }),
9883
+ failure: makeFlowFailureSchema(ids),
9752
9884
  flowId: ids.flow,
9753
9885
  name: string2(),
9754
9886
  status: literal("failed")
@@ -9857,6 +9989,30 @@ var makeGetRunAttemptArtifactsContract = (ids) => {
9857
9989
  };
9858
9990
  };
9859
9991
 
9992
+ // node_modules/@qawolf/api-contracts/dist/v1/run/optOutOfInvestigation.js
9993
+ var makeOptOutOfInvestigationRunContract = (ids) => {
9994
+ const input = object({
9995
+ flowIds: array(ids.flow).min(1).max(100).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").describe("The failed flows to stop investigating. Every named flow must be " + "eligible or the whole request is rejected."),
9996
+ runId: ids.run
9997
+ });
9998
+ const output = resource({
9999
+ optedOutFlowIds: array(ids.flow).describe("The flows that no longer need investigation."),
10000
+ runId: ids.run
10001
+ }, { urlFieldDescription: "Absolute URL of the run page." });
10002
+ return {
10003
+ annotations: {
10004
+ destructiveHint: true,
10005
+ openWorldHint: true,
10006
+ readOnlyHint: false
10007
+ },
10008
+ description: 'Mark failed flows in a run as "do not investigate" (DNI): the failures need no bug or maintenance report. ' + "Each flow must have failed in the run and still be under investigation. " + "A flow that already has a diagnosis cannot be opted out. " + "A team or organization key records the opt-out as the workspace's automation user. " + "To link a failure to a report instead, use run.diagnose.",
10009
+ input,
10010
+ kind: "write",
10011
+ name: "run.optOutOfInvestigation",
10012
+ output
10013
+ };
10014
+ };
10015
+
9860
10016
  // node_modules/@qawolf/api-contracts/dist/v1/run/reattempt.js
9861
10017
  var makeReattemptRunContract = (ids) => {
9862
10018
  const input = object({
@@ -9881,6 +10037,45 @@ var makeReattemptRunContract = (ids) => {
9881
10037
  };
9882
10038
  };
9883
10039
 
10040
+ // node_modules/@qawolf/api-contracts/dist/v1/run/restoreScreenshotBaseline.js
10041
+ var makeRestoreScreenshotBaselineContract = (ids) => {
10042
+ const input = object({
10043
+ baselineChangeId: string2().trim().min(1).max(100).describe("The baseline change to undo, as `run.acceptScreenshotBaseline` returned it for this run."),
10044
+ chatSessionId: investigationSessionId.describe("The chat session of the investigation that restores the baseline. A workspace or organization API key acts as an automation, so it must send the session that investigates the run. A person's call ignores it."),
10045
+ runId: ids.run
10046
+ });
10047
+ const output = discriminatedUnion("outcome", [
10048
+ object({
10049
+ name: string2().describe("The screenshot name whose baseline changed."),
10050
+ outcome: literal("restored").describe("The baseline the change replaced is the baseline again.")
10051
+ }),
10052
+ object({
10053
+ outcome: literal("baseline-changed").describe("The baseline changed again after this change, so restoring it would undo someone else's change. Nothing changed.")
10054
+ }),
10055
+ object({
10056
+ outcome: literal("needs-dispute").describe("Only a person can restore this change, unless a person disputed the finding that made it. Nothing changed.")
10057
+ }),
10058
+ object({
10059
+ outcome: literal("not-investigation-session").describe("An API key that is not a person's can change a baseline only as the investigation of the run, and chatSessionId is missing or is not the session that investigates the run. Nothing changed.")
10060
+ }),
10061
+ object({
10062
+ outcome: literal("nothing-to-restore").describe("The change was already restored, the name had no baseline before it, or the kept copy of the old baseline is missing or is not the baseline the change replaced. Nothing changed.")
10063
+ })
10064
+ ]);
10065
+ return {
10066
+ annotations: {
10067
+ destructiveHint: true,
10068
+ openWorldHint: false,
10069
+ readOnlyHint: false
10070
+ },
10071
+ description: "Undo a screenshot baseline change that accepted a failed comparison of the run: put back the baseline it replaced, so later runs compare with the old baseline again. " + "Use it when a person disputes a baseline the AI accepted. " + "Refuses without changing anything when the baseline changed again since.",
10072
+ input,
10073
+ kind: "write",
10074
+ name: "run.restoreScreenshotBaseline",
10075
+ output
10076
+ };
10077
+ };
10078
+
9884
10079
  // node_modules/@qawolf/api-contracts/dist/v1/run/stop.js
9885
10080
  var makeStopRunContract = (ids) => {
9886
10081
  const input = object({ runId: ids.run });
@@ -10201,7 +10396,7 @@ var makeRunFlowOnRunnerContract = (ids) => {
10201
10396
  env: runEnvironmentSchema.optional().describe("Environment variables to make available to the run."),
10202
10397
  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."),
10203
10398
  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."),
10204
- flowId: ids.flow.optional().describe("The QA Wolf flow this run is for. The run then sees it as `QAWOLF_WORKFLOW_ID`, the same value a platform run of that flow sees, so fixtures named and cleaned up by it match across both. Omit it when the flow is not in QA Wolf yet, and the run has no `QAWOLF_WORKFLOW_ID`."),
10399
+ flowId: ids.flow.optional().describe("The QA Wolf flow this run is for. The run then sees it as `QAWOLF_WORKFLOW_ID`, the same value a platform run of that flow sees, so fixtures named and cleaned up by it match across both. Omitted, the flow whose file is at `entryPointPath` on the named environment's flow-code branch stands in; a file not in QA Wolf yet, or a run given no environment, leaves the run with no `QAWOLF_WORKFLOW_ID`."),
10205
10400
  id: runnerIdSchema.describe("Id of the runner to run the flow on."),
10206
10401
  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."),
10207
10402
  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."),
@@ -10468,12 +10663,16 @@ var makeContractsV1 = (ids) => {
10468
10663
  },
10469
10664
  legacyTrigger: makeLegacyTriggerContracts(resolvedIds),
10470
10665
  run: {
10666
+ acceptScreenshotBaseline: makeAcceptScreenshotBaselineContract(resolvedIds),
10471
10667
  create: makeCreateRunContract(resolvedIds),
10472
10668
  diagnose: makeDiagnoseRunContract(resolvedIds),
10473
10669
  find: makeFindRunsContract(resolvedIds),
10474
10670
  get: makeGetRunContract(resolvedIds),
10475
10671
  getAttemptArtifacts: makeGetRunAttemptArtifactsContract(resolvedIds),
10672
+ listScreenshotComparisons: makeListScreenshotComparisonsContract(resolvedIds),
10673
+ optOutOfInvestigation: makeOptOutOfInvestigationRunContract(resolvedIds),
10476
10674
  reattempt: makeReattemptRunContract(resolvedIds),
10675
+ restoreScreenshotBaseline: makeRestoreScreenshotBaselineContract(resolvedIds),
10477
10676
  stop: makeStopRunContract(resolvedIds)
10478
10677
  },
10479
10678
  runner: makeRunnerContracts(resolvedIds),
@@ -15108,4 +15307,4 @@ export {
15108
15307
  createRunnerSdk
15109
15308
  };
15110
15309
 
15111
- //# debugId=3EFB50CF5F60CF8264756E2164756E21
15310
+ //# debugId=6A1EED78FDDC38F864756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qawolf/cli",
3
- "version": "1.37.0",
3
+ "version": "1.38.0",
4
4
  "description": "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
5
5
  "keywords": [
6
6
  "automation",
@@ -71,7 +71,7 @@
71
71
  "@clack/prompts": "1.5.1",
72
72
  "@napi-rs/keyring": "1.3.0",
73
73
  "@oxc-node/core": "0.1.0",
74
- "@qawolf/api-contracts": "0.74.0",
74
+ "@qawolf/api-contracts": "0.77.0",
75
75
  "@qawolf/emails": "1.1.1",
76
76
  "@qawolf/flow-targets": "1.0.0",
77
77
  "@qawolf/flows": "0.1.4",
@@ -171,17 +171,21 @@ that `url`; never guess a route and never send a repository link in its place.
171
171
  | `qawolf issue create` | write | Create a bug report, maintenance report, or coverage request issue for the caller's team. A user can create a maintenance report only when they can see the workspace's maintenance reports; a team or organization API key credits it to the workspace's automation user. Link its failed flows afterwards with run.diagnose. |
172
172
  | `qawolf issue find` | read | List the team's bug reports, maintenance reports, or coverage requests, newest first. |
173
173
  | `qawolf issue get` | read | Get an issue by id. |
174
- | `qawolf issue removeFlows` | write | Remove flows from a coverage request owned by the caller's team. Flows the request does not cover are left alone. Bug and maintenance reports link to flows through the runs that reproduce them, so their flows cannot be set directly. |
174
+ | `qawolf issue removeFlows` | write | Remove flows from a coverage request or remove bug/maintenance reproductions in an environment. Unlinked flows are left alone. Reports auto-close when no active reproductions remain and auto-close is enabled. For reports, omit environmentId only when each selected flow is linked in at most one environment. |
175
175
  | `qawolf issue update` | write | Update an issue owned by the caller's team. Omitted fields remain unchanged. |
176
176
  | `qawolf legacyTrigger find` | read | List a workspace's legacy per-environment triggers; trigger.find lists the current ones. For migration only; it goes when legacy triggers go. adaptiveFlowSelection becomes a generativeSuite action, which takes no tags. |
177
177
  | `qawolf legacyTrigger pause` | write | Pause one legacy trigger, the older per-environment kind, so it stops firing; use trigger.pause for a current one. Its copies in pull request environments pause too. For migration only; it goes when legacy triggers go. |
178
178
  | `qawolf legacyTrigger resume` | write | Resume one legacy trigger, the older per-environment kind; use trigger.resume for a current one. Its copies in pull request environments resume too, and a scheduled one jumps to the next upcoming slot even if it was not paused. For migration only; it goes when legacy triggers go. |
179
+ | `qawolf run acceptScreenshotBaseline` | write | Make the new screenshot of a failed screenshot comparison the baseline, so later runs compare with it. Use it only when the difference is an intended change of the app, not a bug, noise, or a page that was not ready. Every later run of every environment of the workspace compares with the new baseline. Refuses without changing anything when the baseline changed since the run. |
179
180
  | `qawolf run create` | write | Create a run for the selected flows and/or tags in an environment. |
180
- | `qawolf run diagnose` | write | Diagnose failed flows in a run as reproductions of a bug or maintenance report owned by the caller's team. The issue's type selects the diagnosis. Each flow must have failed in the run. A flow that is already diagnosed moves to this issue. The diagnosis appears on the run, and the reproduction appears under the issue's reproductions. Coverage requests cannot be diagnosed; use issue.addFlows to cover flows instead. |
181
+ | `qawolf run diagnose` | write | Diagnose failed flows in a run as reproductions of a bug or maintenance report owned by the caller's team. The issue's type selects the diagnosis. Each flow must have failed in the run. A flow that is already diagnosed in the run moves to this issue, and may stay linked to other open reports. The diagnosis appears on the run, and the reproduction appears under the issue's reproductions. Coverage requests cannot be diagnosed; use issue.addFlows to cover flows instead. |
181
182
  | `qawolf run find` | read | List an environment's recent runs, newest first, including failed-attempt discovery metadata. |
182
183
  | `qawolf run get` | read | Get a run's status, per-flow results, links, and how many of its bugs are blocking. |
183
184
  | `qawolf run getAttemptArtifacts` | read | Get metadata and signed artifact URLs for one finished run attempt. |
185
+ | `qawolf run listScreenshotComparisons` | read | List the screenshot comparisons that failed a run's flows, with the expected, actual and diff images of each. Only comparisons that caused a failure are listed: a screenshot that differs within tolerance, or that differs on a line that did not fail, is left out. Each comparison tells whether its baseline changed since the run and whether the baseline still passes in other environments. The image URLs expire after a few minutes; fetch the preview URLs to look at the images. The first call for an image makes its missing JPEG preview and keeps it in team storage, so later calls reuse it. |
186
+ | `qawolf run optOutOfInvestigation` | write | Mark failed flows in a run as "do not investigate" (DNI): the failures need no bug or maintenance report. Each flow must have failed in the run and still be under investigation. A flow that already has a diagnosis cannot be opted out. A team or organization key records the opt-out as the workspace's automation user. To link a failure to a report instead, use run.diagnose. |
184
187
  | `qawolf run reattempt` | write | Request new attempts for a run's flows, in the same run. A flow is eligible once its result is failed or canceled and QA Wolf's automatic retries have finished. A fully investigated run no longer accepts reattempts. Attempts run with the latest flow code. Poll run.get for results. |
188
+ | `qawolf run restoreScreenshotBaseline` | write | Undo a screenshot baseline change that accepted a failed comparison of the run: put back the baseline it replaced, so later runs compare with the old baseline again. Use it when a person disputes a baseline the AI accepted. Refuses without changing anything when the baseline changed again since. |
185
189
  | `qawolf run stop` | write | Stop a run, including its queued flows and automatic retries. Stopping is asynchronous and can update run-status messages and commit statuses in connected integrations. Repeated requests are safe, and finished runs keep their results. A run that is still being created returns not found; retry once run.get returns the run. If run.get returns a different runId, use that ID. Poll run.get for results. |
186
190
  | `qawolf runner act` | write | Perform one raw action on a runner's screen. A browser runner takes click, double_click, scroll, move, drag, keypress, navigate and type; a mobile runner takes tap, swipe, fill and type. A browser runner answers mobile actions with action-not-supported-on-browser; a mobile runner answers the other browser actions with action-not-supported-on-mobile. Use - to read a whole action as JSON from stdin |
187
191
  | `qawolf runner actions` | write | Perform a sequence of up to ten raw actions on a runner's screen in one request, as a JSON array of the same actions `runner act` takes. Use - to read the array from stdin. Actions run back to back, so batch only steps whose targets are on the screen you last saw, and end the sequence at the step that changes the page. Each result carries an effect: performed, not-performed, or unknown when the runner stopped answering and the action may have landed |
@@ -201,9 +201,11 @@ Every documented field of the `run.get` response. `[]` marks an array, so
201
201
  - `flows[].attempts[].kind` — One of: automated, manual
202
202
  - `flows[].attempts[].startedAt` — Absent when the attempt failed before it could start.
203
203
  - `flows[].attempts[].status` — One of: passed, failed, canceled
204
- - `flows[].failure.diagnosis` — QA Wolf's investigation verdict for the failure: `bug` means the application is broken, `maintenance` means the test needed an update and the failure does not indicate an application problem. Absent until the investigation reaches a verdict. Pass issueId to issue.get for details.
204
+ - `flows[].failure.diagnosis` — QA Wolf's investigation verdict for the failure: `bug` means the application is broken, `maintenance` means the test needed an update and the failure does not indicate an application problem. Absent until the investigation reaches a verdict, and absent when the flow was opted out of investigation. Pass issueId to issue.get for details.
205
205
  - `flows[].failure.diagnosis.issueId` — The id of the issue.
206
206
  - `flows[].failure.diagnosis.type` — One of: bug, maintenance
207
+ - `flows[].failure.optedOutOfInvestigation` — Present when the failure was marked "do not investigate" (DNI): it will get no bug or maintenance verdict.
208
+ - `flows[].failure.optedOutOfInvestigation.by` — One of: user, system
207
209
  - `flows[].flowId` — The id of the flow.
208
210
  - `flows[].status` — One of: failed, queued, running, passed, canceled
209
211
  - `url` — Absolute URL of the run page.