@qawolf/cli 1.25.0 → 1.27.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 +633 -409
- package/dist/runner-sdk.js +200 -80
- package/dist/types/runnerSdk/index.d.ts +1 -1
- package/dist/types/runnerSdk/types.d.ts +7 -0
- package/package.json +2 -2
- package/skills/qawolf-cli/SKILL.md +11 -3
- package/skills/qawolf-cli/references/runner.md +20 -5
package/dist/cli.js
CHANGED
|
@@ -8787,7 +8787,17 @@ var interactMessages = {
|
|
|
8787
8787
|
actionMayHaveHappened: "The runner could not be reached, which does not mean the action was not performed: it may have stopped answering mid-action. Take a screenshot before repeating it.",
|
|
8788
8788
|
actionNotJson: `Stdin did not hold a JSON action. Pipe one object, for example '{"type":"click","button":"left","x":480,"y":260}'.`,
|
|
8789
8789
|
actionNotSupportedOnMobile: (type) => `A mobile runner has a touchscreen, so it cannot perform ${type} as asked. It taps with a left-button click, swipes with drag, and types into whatever the last tap focused.`,
|
|
8790
|
+
actionFailedScreenshotToStdout: "Its screen was written to stdout as a JPEG, so look at that rather than sending the action again.",
|
|
8791
|
+
actionFailedScreenshotUnwritten: (detail) => `Its screen could not be written, because ${detail}, so take one with qawolf runner screenshot rather than sending the action again.`,
|
|
8792
|
+
actionFailedScreenshotWritten: (path) => `Its screen was written to ${path}, so look at that rather than sending the action again.`,
|
|
8793
|
+
actionFailedWithoutScreenshot: "The runner answered without a screen, so nothing was written; take one with qawolf runner screenshot rather than sending the action again.",
|
|
8790
8794
|
actionPerformed: (type) => `Performed ${type}.`,
|
|
8795
|
+
actionPerformedScreenshotNotAnImage: (type) => `Performed ${type}, but the screen that came with the answer was not a JPEG, so nothing was written. The action took effect, so do not repeat it: take the screen with qawolf runner screenshot instead, and report it if it keeps happening.`,
|
|
8796
|
+
actionPerformedScreenshotToStdout: (type) => `Performed ${type} and wrote the runner's screen to stdout as a JPEG. Stdout holds the image bytes alone; this line, and the JSON with --json, is on stderr.`,
|
|
8797
|
+
actionPerformedScreenshotStdoutUnwritable: (type, detail) => `Performed ${type}, but its screen could not be written to stdout: ${detail}. The action took effect, so do not repeat it: keep the pipe reading stdout open, or take the screen with qawolf runner screenshot --out <file>.`,
|
|
8798
|
+
actionPerformedScreenshotUnwritable: (type, path, detail) => `Performed ${type}, but its screen could not be written to "${path}": ${detail}. The action took effect, so do not repeat it: take the screen with qawolf runner screenshot, giving --out a path this process can write to.`,
|
|
8799
|
+
actionPerformedScreenshotWritten: (type, path) => `Performed ${type} and wrote the runner's screen to ${path}.`,
|
|
8800
|
+
actionPerformedWithoutScreenshot: (type) => `Performed ${type}, but the runner answered without the screen it was asked for. The action took effect, so do not repeat it: take the screen with qawolf runner screenshot instead. If this keeps happening, the platform or this CLI is behind the other: upgrade with npm install -g @qawolf/cli.`,
|
|
8791
8801
|
actionAnsweredUnknown: (failureReason) => `The runner answered "${failureReason}", which this version of the CLI does not know how to report. Upgrade with npm install -g @qawolf/cli.`,
|
|
8792
8802
|
screenshotAnsweredUnknown: (failureReason) => `The runner answered "${failureReason}", which this version of the CLI does not know how to report. Upgrade with npm install -g @qawolf/cli.`,
|
|
8793
8803
|
runnerHasNoScreen: "This runner does not run a browser on a virtual desktop, so there is nothing about it to see or drive. Retrying will never help: launch a playwright runner instead.",
|
|
@@ -8799,8 +8809,11 @@ var interactMessages = {
|
|
|
8799
8809
|
screenNeedsARun: "This runner has not run anything yet, so its screen has never started. Waiting will not clear this and there is nothing to retry: run a flow on it with qawolf runner run, then ask again. Evaluating a snippet does not start a screen.",
|
|
8800
8810
|
screenNotReady: "The runner has a screen and cannot serve this yet. Its virtual desktop restarts when a run changes the display size, and it serves one request at a time, so something already in flight is the usual reason. Retry in a second or two.",
|
|
8801
8811
|
screenshotNotAnImage: "The screen was captured but did not arrive as a JPEG, so nothing was written. Nothing about the command needs changing: try it again, and report it if it keeps happening.",
|
|
8812
|
+
stdoutIsATerminal: (flag) => `Stdout is a terminal, so the JPEG bytes would have nowhere to go. Redirect stdout to a file or pipe it into a reader, or give ${flag} a file path instead of "-".`,
|
|
8813
|
+
screenshotStdoutUnwritable: (detail) => `The screen was captured but could not be written to stdout: ${detail}. Keep the pipe reading stdout open, or give --out a file path instead of "-".`,
|
|
8802
8814
|
screenshotUnwritable: (path, detail) => `The screen was captured but could not be written to "${path}": ${detail}. Give --out a path this process can write to.`,
|
|
8803
8815
|
screenshotWritten: (path) => `Wrote the runner's screen to ${path}.`,
|
|
8816
|
+
screenshotWrittenToStdout: "Wrote the runner's screen to stdout as a JPEG. Stdout holds the image bytes alone; this line, and the JSON with --json, is on stderr.",
|
|
8804
8817
|
snippetEmpty: (path) => `"${path}" holds no code to evaluate.`,
|
|
8805
8818
|
snippetErrored: (errorMessage) => errorMessage === undefined ? "The snippet threw and reported no message." : `The snippet threw: ${errorMessage}`,
|
|
8806
8819
|
snippetFileUnreadable: (path) => `Could not read "${path}". Name a readable file, or "-" to read the snippet from stdin.`,
|
|
@@ -9083,25 +9096,32 @@ function exit(code, message, proc = process) {
|
|
|
9083
9096
|
}
|
|
9084
9097
|
return proc.exit(code);
|
|
9085
9098
|
}
|
|
9086
|
-
|
|
9087
|
-
|
|
9099
|
+
var backstopMs = 2000;
|
|
9100
|
+
function exitWhenIdle(code, proc = process, scheduleBackstop = (fn) => {
|
|
9101
|
+
setTimeout(fn, backstopMs).unref();
|
|
9088
9102
|
}) {
|
|
9089
|
-
|
|
9090
|
-
|
|
9091
|
-
|
|
9092
|
-
|
|
9093
|
-
|
|
9094
|
-
|
|
9095
|
-
|
|
9096
|
-
|
|
9097
|
-
|
|
9098
|
-
|
|
9099
|
-
|
|
9100
|
-
|
|
9103
|
+
proc.exitCode = code;
|
|
9104
|
+
scheduleBackstop(() => proc.exit(code));
|
|
9105
|
+
}
|
|
9106
|
+
var signalExitCodes = {
|
|
9107
|
+
SIGINT: 130,
|
|
9108
|
+
SIGTERM: 143
|
|
9109
|
+
};
|
|
9110
|
+
var signalGraceMs = 150;
|
|
9111
|
+
function scheduleSignalGrace(fn) {
|
|
9112
|
+
return setTimeout(fn, signalGraceMs);
|
|
9113
|
+
}
|
|
9114
|
+
function createSignalExit(deps) {
|
|
9115
|
+
const proc = deps.proc ?? process;
|
|
9116
|
+
const scheduleGrace = deps.scheduleGrace ?? scheduleSignalGrace;
|
|
9117
|
+
let signalled = false;
|
|
9118
|
+
return (signal) => () => {
|
|
9119
|
+
const code = signalExitCodes[signal];
|
|
9120
|
+
if (signalled)
|
|
9121
|
+
proc.exit(code);
|
|
9122
|
+
signalled = true;
|
|
9123
|
+
deps.shutdown(signal).catch(() => {}).finally(() => exitWhenIdle(code, proc, scheduleGrace));
|
|
9101
9124
|
};
|
|
9102
|
-
proc.stdout.write("", onFlushed);
|
|
9103
|
-
proc.stderr.write("", onFlushed);
|
|
9104
|
-
scheduleBackstop(exitOnce);
|
|
9105
9125
|
}
|
|
9106
9126
|
|
|
9107
9127
|
// src/shell/fs.ts
|
|
@@ -16384,6 +16404,10 @@ var makeEmailResourceSchema = () => resource({
|
|
|
16384
16404
|
replyTo: array(namedAddressSchema).optional(),
|
|
16385
16405
|
text: string2().optional().describe("The plain text body.")
|
|
16386
16406
|
}, { urlFieldDescription: emailUrlFieldDescription });
|
|
16407
|
+
var makeEmailAddressResourceSchema = () => resource({
|
|
16408
|
+
address: string2().describe("The inbox address. Mail sent to any plus-suffixed form, such as inbox+abc@example.com, lands in the same inbox."),
|
|
16409
|
+
isDefault: boolean2().describe("Whether this is the workspace's default inbox address.")
|
|
16410
|
+
}, { urlFieldDescription: emailUrlFieldDescription });
|
|
16387
16411
|
|
|
16388
16412
|
// node_modules/@qawolf/api-contracts/dist/v1/identity/organization.js
|
|
16389
16413
|
var identityOrganization = object({
|
|
@@ -16607,6 +16631,8 @@ var runnerNameSchema = _enum([
|
|
|
16607
16631
|
"android",
|
|
16608
16632
|
"ios"
|
|
16609
16633
|
]);
|
|
16634
|
+
var runnerWorkspaceRequirement = "Required when the credential is not bound to a single workspace, such as a user API key or an OAuth connection whose organization owns several.";
|
|
16635
|
+
var runnerWorkspaceIdDescription = `The workspace the runner belongs to. ${runnerWorkspaceRequirement}`;
|
|
16610
16636
|
var runnerIdSchema = string2().min(1).max(63).regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/, "A runner id may contain only lowercase letters, digits and dashes, and must start and end with a letter or digit.");
|
|
16611
16637
|
var makeRunnerSchema = () => object({
|
|
16612
16638
|
gpuAccelerated: boolean2().describe("Whether the runner was placed on GPU hardware. Chosen by QA Wolf from the team's configuration and what the fleet can provide, not requested."),
|
|
@@ -16634,10 +16660,11 @@ var inspectRequestSchema = discriminatedUnion("what", [
|
|
|
16634
16660
|
what: literal("variable")
|
|
16635
16661
|
})
|
|
16636
16662
|
]);
|
|
16637
|
-
var makeInspectOnRunnerContract = () => {
|
|
16663
|
+
var makeInspectOnRunnerContract = (ids) => {
|
|
16638
16664
|
const input = object({
|
|
16639
16665
|
id: runnerIdSchema.describe("Id of the runner to inspect."),
|
|
16640
|
-
request: inspectRequestSchema
|
|
16666
|
+
request: inspectRequestSchema,
|
|
16667
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
16641
16668
|
});
|
|
16642
16669
|
const output = discriminatedUnion("outcome", [
|
|
16643
16670
|
object({
|
|
@@ -16784,10 +16811,11 @@ var inspectMobileAnswerSchema = discriminatedUnion("what", [
|
|
|
16784
16811
|
what: literal("elements")
|
|
16785
16812
|
})
|
|
16786
16813
|
]);
|
|
16787
|
-
var makeInspectMobileOnRunnerContract = () => {
|
|
16814
|
+
var makeInspectMobileOnRunnerContract = (ids) => {
|
|
16788
16815
|
const input = object({
|
|
16789
16816
|
id: runnerIdSchema.describe("Id of the runner to inspect."),
|
|
16790
|
-
request: inspectMobileRequestSchema
|
|
16817
|
+
request: inspectMobileRequestSchema,
|
|
16818
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
16791
16819
|
});
|
|
16792
16820
|
const output = discriminatedUnion("outcome", [
|
|
16793
16821
|
inspectMobileAnswerSchema,
|
|
@@ -16871,20 +16899,26 @@ var screenFailureReasons = [
|
|
|
16871
16899
|
var screenNotReadyDescription = "`screen-not-ready` if the runner has a screen that cannot serve this instant. Retry in a second or two: the desktop restarts when a run changes the display size, and it serves one see-or-act request at a time, so a screenshot or action already in flight is the usual reason. Do not submit a run to clear this — a run may restart the display and discard what is on it.";
|
|
16872
16900
|
var screenNeedsARunDescription = "`screen-needs-a-run` if the runner's virtual desktop has never started. Waiting will not change this and retrying is pointless — call `runner.runFlow` with a flow that opens a browser, then ask for the screen again. On a runner image with a browser that has never run anything, any `runner.performAction` also starts the browser itself. Evaluating a snippet does not start the desktop.";
|
|
16873
16901
|
var runnerHasNoScreenDescription = "`runner-has-no-screen` if this runner is not one that runs a browser on a virtual desktop. Nothing about it can be seen or driven, and retrying will never help — launch a `playwright` runner instead.";
|
|
16902
|
+
var screenshotImageSchema = string2().min(1).describe("The screenshot, as a base64-encoded JPEG. Decode it and write the bytes to a `.jpg` file — writing this string to the file leaves you with base64 text rather than an image.");
|
|
16874
16903
|
|
|
16875
16904
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/performAction.js
|
|
16876
16905
|
var unreachableDescription = "`runner-unreachable` if the runner could not be reached: it may still be starting, it may have terminated after inactivity, or it may have stopped answering mid-action. This does not mean the action was not performed — take a screenshot before repeating it.";
|
|
16877
16906
|
var notSupportedOnMobileDescription = "`action-not-supported-on-mobile` if the runner is a mobile device and this action has no touchscreen equivalent: `double_click`, `scroll`, `move`, `keypress` and `navigate`, and a `click` whose `button` is not `left`, are all pointer-device concepts a touchscreen has nothing to offer for. `click` taps, `drag` swipes between its path's first and last point, and `type` types into whatever the last tap focused.";
|
|
16907
|
+
var withScreenshotDescription = "With `withScreenshot: true`, the answer also carries `imageJpegBase64`: a screenshot taken after the action, once the screen has changed from just before it or half a second has passed, whichever comes first. One call instead of `runner.performAction` followed by `runner.takeScreenshot`, with no fixed wait in between. Anything animating on the page counts as a change. The action itself is performed exactly as without the option; if the screen could serve no frame after it — a display restart, or a `navigate` on a desktop that is still starting — the action's result comes back without `imageJpegBase64`, so take a screenshot separately rather than repeating the action. On a mobile runner the screenshot is the device's own, taken right after a performed action.";
|
|
16878
16908
|
var maxActionErrorMessageLength = 1000;
|
|
16879
|
-
var makePerformActionOnRunnerContract = () => {
|
|
16909
|
+
var makePerformActionOnRunnerContract = (ids) => {
|
|
16880
16910
|
const input = object({
|
|
16881
16911
|
action: browserActionSchema.describe("The action to perform. Coordinates are pixels on the screenshot's own coordinate space."),
|
|
16882
|
-
id: runnerIdSchema.describe("Id of the runner to act on.")
|
|
16912
|
+
id: runnerIdSchema.describe("Id of the runner to act on."),
|
|
16913
|
+
withScreenshot: boolean2().optional().describe("Also answer with a screenshot taken after the action, once the screen has changed or half a second has passed. Saves the separate `runner.takeScreenshot` call and its fixed wait."),
|
|
16914
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
16883
16915
|
});
|
|
16916
|
+
const screenshotAfterAction = screenshotImageSchema.optional();
|
|
16884
16917
|
const failure = discriminatedUnion("failureReason", [
|
|
16885
16918
|
object({
|
|
16886
16919
|
errorMessage: string2().max(maxActionErrorMessageLength).describe("What stopped the action from taking effect."),
|
|
16887
16920
|
failureReason: literal("action-failed"),
|
|
16921
|
+
imageJpegBase64: screenshotAfterAction,
|
|
16888
16922
|
outcome: literal("failure")
|
|
16889
16923
|
}),
|
|
16890
16924
|
makeRunnerFailureSchema([
|
|
@@ -16894,11 +16928,14 @@ var makePerformActionOnRunnerContract = () => {
|
|
|
16894
16928
|
])
|
|
16895
16929
|
]);
|
|
16896
16930
|
const output = discriminatedUnion("outcome", [
|
|
16897
|
-
object({
|
|
16931
|
+
object({
|
|
16932
|
+
imageJpegBase64: screenshotAfterAction,
|
|
16933
|
+
outcome: literal("success")
|
|
16934
|
+
}),
|
|
16898
16935
|
failure
|
|
16899
16936
|
]);
|
|
16900
16937
|
return {
|
|
16901
|
-
description: `Perform one raw browser action on an interactive runner: click, double_click, move, drag, scroll, keypress, type, or navigate. Coordinates are whole pixels on the runner's virtual desktop, in the same space as \`runner.takeScreenshot\`. One action per request, and the runner serves one at a time. The action shapes follow the computer-use vocabulary, minus \`screenshot\` (use \`runner.takeScreenshot\`) and \`wait\` (delay on the caller's side). A success means the action took effect. \`action-failed\`, with a reason, if it reached the runner and did not take effect. On a runner image with a browser, the first action on a runner that has never run anything starts its browser and waits for it, so it can take up to a minute to answer — no \`runner.runFlow\` is needed before acting; if the browser is still starting when the wait runs out, the answer is \`screen-not-ready\` and retrying converges. On a mobile runner, the same \`screen-needs-a-run\` and \`screen-not-ready\` outcomes mean no Appium session has started yet, or it did not answer this instant; \`runner.runFlow\` is what starts one, same as a browser. \`screen-needs-a-run\` if the browser could not be started that way — usually a runner whose runs all finished without starting its desktop; call \`runner.runFlow\` with a flow that opens a browser. ${screenNotReadyDescription} A \`navigate\` does not go through the screen, so a screen that is not ready does not stop it. ${runnerHasNoScreenDescription} ${notSupportedOnMobileDescription} ${unreachableDescription}`,
|
|
16938
|
+
description: `Perform one raw browser action on an interactive runner: click, double_click, move, drag, scroll, keypress, type, or navigate. Coordinates are whole pixels on the runner's virtual desktop, in the same space as \`runner.takeScreenshot\`. One action per request, and the runner serves one at a time. The action shapes follow the computer-use vocabulary, minus \`screenshot\` (use \`runner.takeScreenshot\`) and \`wait\` (delay on the caller's side). A success means the action took effect. \`action-failed\`, with a reason, if it reached the runner and did not take effect. On a runner image with a browser, the first action on a runner that has never run anything starts its browser and waits for it, so it can take up to a minute to answer — no \`runner.runFlow\` is needed before acting; if the browser is still starting when the wait runs out, the answer is \`screen-not-ready\` and retrying converges. On a mobile runner, the same \`screen-needs-a-run\` and \`screen-not-ready\` outcomes mean no Appium session has started yet, or it did not answer this instant; \`runner.runFlow\` is what starts one, same as a browser. \`screen-needs-a-run\` if the browser could not be started that way — usually a runner whose runs all finished without starting its desktop; call \`runner.runFlow\` with a flow that opens a browser. ${screenNotReadyDescription} A \`navigate\` does not go through the screen, so a screen that is not ready does not stop it. ${runnerHasNoScreenDescription} ${notSupportedOnMobileDescription} ${withScreenshotDescription} ${unreachableDescription}`,
|
|
16902
16939
|
input,
|
|
16903
16940
|
kind: "write",
|
|
16904
16941
|
name: "runner.performAction",
|
|
@@ -16924,9 +16961,11 @@ var makeAgentGetContract = (ids) => {
|
|
|
16924
16961
|
replies: array(agentReplySchema).describe("Everything the AI has said so far, oldest first. A reply carrying choices is a question the work is blocked on."),
|
|
16925
16962
|
sessionId: ids.chatSession,
|
|
16926
16963
|
status: agentSessionStatusSchema
|
|
16927
|
-
}, {
|
|
16964
|
+
}, {
|
|
16965
|
+
urlFieldDescription: "Absolute URL of the live session in the QA Wolf app."
|
|
16966
|
+
});
|
|
16928
16967
|
return {
|
|
16929
|
-
description: '
|
|
16968
|
+
description: 'Monitor a QA Wolf AI session by reading its status and replies. After agent.send, share the returned session URL before monitoring. Wait 30 to 60 seconds between checks; do not call this in a tight loop. Replies accumulate, so compare them with what you have already seen. Continue monitoring silently when the status and replies are unchanged; do not narrate waiting, announce the next check, or ask whether to keep monitoring. Report only substantive new progress, questions, blockers, or the final outcome. A status of "waiting-for-you" means the last reply is a question the work is blocked on, and answering it with agent.send is what unblocks it. Surface an explicit request for user input even if the status still says "working". Include the session URL when reporting a blocker or final outcome. On "completed", stop status checks and verify the requested result before claiming success. For new flows, validation, publication in the target environment, and readiness are separate checks; a Git push or final reply does not prove the flow is active. If every requested result is verified but status remains "working", report the mismatch and stop monitoring. Stop on "failed" or "cancelled" and report any confirmed partial result.',
|
|
16930
16969
|
input,
|
|
16931
16970
|
kind: "read",
|
|
16932
16971
|
name: "agent.get",
|
|
@@ -16945,9 +16984,11 @@ var makeAgentSendContract = (ids) => {
|
|
|
16945
16984
|
const output = resource({
|
|
16946
16985
|
sessionId: ids.chatSession.describe("Follow the work by passing this to agent.get."),
|
|
16947
16986
|
status: agentSessionStatusSchema
|
|
16948
|
-
}, {
|
|
16987
|
+
}, {
|
|
16988
|
+
urlFieldDescription: "Absolute URL of the live session in the QA Wolf app."
|
|
16989
|
+
});
|
|
16949
16990
|
return {
|
|
16950
|
-
description: "
|
|
16991
|
+
description: "Start or continue work with the QA Wolf AI and return a live session URL to share with the user. Use it to cover a user journey, investigate a failing run, or fix a broken flow. This is the one verb that starts work from nothing: every other write acts on a flow, run or issue that already exists. Returns sessionId, status, and url as soon as the request is accepted; work can take minutes to tens of minutes. After each send, make the next action a normal user-visible assistant message containing the exact returned url, before any tool call or wait. Tool output and internal reasoning do not count as sharing the link. Do not run a timer or monitoring call alongside this send. Acceptance does not mean the work is complete. Then monitor the session with agent.get, reporting new progress, blockers, and the final outcome rather than unchanged status. Send here again to answer a question or add context to the same session.",
|
|
16951
16992
|
input,
|
|
16952
16993
|
kind: "write",
|
|
16953
16994
|
name: "agent.send",
|
|
@@ -17050,10 +17091,7 @@ var makeListEmailAddressesContract = (ids) => {
|
|
|
17050
17091
|
...makePaginationInputFields({ defaultLimit: 20, maxLimit: 50 })
|
|
17051
17092
|
});
|
|
17052
17093
|
const output = object({
|
|
17053
|
-
addresses: array(
|
|
17054
|
-
address: string2().describe("The inbox address. Mail sent to any plus-suffixed form, such as inbox+abc@example.com, lands in the same inbox."),
|
|
17055
|
-
isDefault: boolean2().describe("Whether this is the workspace's default inbox address.")
|
|
17056
|
-
}, { urlFieldDescription: emailUrlFieldDescription })).describe("The workspace's permanent inbox addresses, alphabetical."),
|
|
17094
|
+
addresses: array(makeEmailAddressResourceSchema()).describe("The workspace's permanent inbox addresses, alphabetical."),
|
|
17057
17095
|
nextCursor: nextCursorSchema
|
|
17058
17096
|
});
|
|
17059
17097
|
return {
|
|
@@ -17065,6 +17103,28 @@ var makeListEmailAddressesContract = (ids) => {
|
|
|
17065
17103
|
};
|
|
17066
17104
|
};
|
|
17067
17105
|
|
|
17106
|
+
// node_modules/@qawolf/api-contracts/dist/v1/email/registerAddress.js
|
|
17107
|
+
var hasNoPlusSuffix = (address) => {
|
|
17108
|
+
const [localPart] = address.split("@");
|
|
17109
|
+
return localPart !== undefined && !localPart.includes("+");
|
|
17110
|
+
};
|
|
17111
|
+
var makeRegisterEmailAddressContract = (ids) => {
|
|
17112
|
+
const input = object({
|
|
17113
|
+
address: email2().refine(hasNoPlusSuffix, {
|
|
17114
|
+
message: "Register the base address. Mail to a plus-suffixed form lands in the base inbox."
|
|
17115
|
+
}).describe("The inbox address to register. Its domain must be one QA Wolf serves for the workspace."),
|
|
17116
|
+
workspaceId: ids.workspace.optional().describe("The workspace to register the address in. Required when authenticating with an organization or user API key.")
|
|
17117
|
+
});
|
|
17118
|
+
const output = makeEmailAddressResourceSchema();
|
|
17119
|
+
return {
|
|
17120
|
+
description: "Register an inbox address for the workspace. Registering an address the workspace already has changes nothing. A refusal names the domains the workspace can use.",
|
|
17121
|
+
input,
|
|
17122
|
+
kind: "write",
|
|
17123
|
+
name: "email.registerAddress",
|
|
17124
|
+
output
|
|
17125
|
+
};
|
|
17126
|
+
};
|
|
17127
|
+
|
|
17068
17128
|
// node_modules/@qawolf/api-contracts/dist/v1/email/send.js
|
|
17069
17129
|
var recipientsSchema = array(email2()).max(50);
|
|
17070
17130
|
var maxAttachments = 10;
|
|
@@ -17705,14 +17765,31 @@ var makeReattemptRunContract = (ids) => {
|
|
|
17705
17765
|
};
|
|
17706
17766
|
};
|
|
17707
17767
|
|
|
17768
|
+
// node_modules/@qawolf/api-contracts/dist/v1/run/stop.js
|
|
17769
|
+
var makeStopRunContract = (ids) => {
|
|
17770
|
+
const input = object({ runId: ids.run });
|
|
17771
|
+
const output = resource({
|
|
17772
|
+
runId: ids.run,
|
|
17773
|
+
status: _enum(["accepted", "already-finished", "execution-not-found"]).describe('"accepted" means stopping was requested; poll run.get for the final result. "already-finished" means the run has already finished and its result is unchanged. "execution-not-found" means the run exists but no execution was found to stop; it may still be starting or may no longer be available. No stop request was accepted in that case.')
|
|
17774
|
+
}, { urlFieldDescription: "Absolute URL of the run page." });
|
|
17775
|
+
return {
|
|
17776
|
+
description: "Stop a run, including its queued flows and automatic retries. Stopping is asynchronous. 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.",
|
|
17777
|
+
input,
|
|
17778
|
+
kind: "write",
|
|
17779
|
+
name: "run.stop",
|
|
17780
|
+
output
|
|
17781
|
+
};
|
|
17782
|
+
};
|
|
17783
|
+
|
|
17708
17784
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/evaluateSnippet.js
|
|
17709
17785
|
var maxSnippetCodeLength = 64 * 1024;
|
|
17710
|
-
var makeEvaluateSnippetOnRunnerContract = () => {
|
|
17786
|
+
var makeEvaluateSnippetOnRunnerContract = (ids) => {
|
|
17711
17787
|
const input = object({
|
|
17712
17788
|
code: string2().min(1).max(maxSnippetCodeLength).describe("The code to evaluate against the runner's live page, in the scope of `filePath` when one is given."),
|
|
17713
17789
|
filePath: runFilePathSchema.optional().describe("Path of the file the snippet is evaluated in the scope of. Omit to evaluate a snippet that imports nothing of yours."),
|
|
17714
17790
|
files: runFilesSchema.optional().describe("The file named by `filePath` and everything it imports. A runner holds no copy of your project, so a snippet's scope has to travel with it."),
|
|
17715
|
-
id: runnerIdSchema.describe("Id of the runner to evaluate the snippet on.")
|
|
17791
|
+
id: runnerIdSchema.describe("Id of the runner to evaluate the snippet on."),
|
|
17792
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
17716
17793
|
}).refine((request) => request.filePath === undefined || Object.hasOwn(request.files ?? {}, request.filePath), {
|
|
17717
17794
|
error: "A snippet naming a file must carry that file in files.",
|
|
17718
17795
|
path: ["files"]
|
|
@@ -17738,9 +17815,10 @@ var makeEvaluateSnippetOnRunnerContract = () => {
|
|
|
17738
17815
|
};
|
|
17739
17816
|
|
|
17740
17817
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/get.js
|
|
17741
|
-
var makeGetRunnerContract = () => {
|
|
17818
|
+
var makeGetRunnerContract = (ids) => {
|
|
17742
17819
|
const input = object({
|
|
17743
|
-
id: runnerIdSchema.describe("Id of the runner to look up.")
|
|
17820
|
+
id: runnerIdSchema.describe("Id of the runner to look up."),
|
|
17821
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
17744
17822
|
});
|
|
17745
17823
|
const output = object({
|
|
17746
17824
|
id: runnerIdSchema.describe("Id of the runner the call addressed."),
|
|
@@ -17758,10 +17836,11 @@ var makeGetRunnerContract = () => {
|
|
|
17758
17836
|
|
|
17759
17837
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/highlightSelector.js
|
|
17760
17838
|
var maxHighlightSelectorLength = 2000;
|
|
17761
|
-
var makeHighlightSelectorOnRunnerContract = () => {
|
|
17839
|
+
var makeHighlightSelectorOnRunnerContract = (ids) => {
|
|
17762
17840
|
const input = object({
|
|
17763
17841
|
id: runnerIdSchema.describe("Id of the runner to highlight on."),
|
|
17764
|
-
selector: string2().max(maxHighlightSelectorLength).describe("The selector to highlight. An empty string clears whatever is highlighted.")
|
|
17842
|
+
selector: string2().max(maxHighlightSelectorLength).describe("The selector to highlight. An empty string clears whatever is highlighted."),
|
|
17843
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
17765
17844
|
});
|
|
17766
17845
|
const output = discriminatedUnion("outcome", [
|
|
17767
17846
|
object({
|
|
@@ -17796,12 +17875,13 @@ var makeHighlightSelectorOnRunnerContract = () => {
|
|
|
17796
17875
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/importPackage.js
|
|
17797
17876
|
var maxPackageNameLength = 214;
|
|
17798
17877
|
var maxPackageVersionLength = 256;
|
|
17799
|
-
var makeImportPackageOnRunnerContract = () => {
|
|
17878
|
+
var makeImportPackageOnRunnerContract = (ids) => {
|
|
17800
17879
|
const input = object({
|
|
17801
17880
|
id: runnerIdSchema.describe("Id of the runner to install into."),
|
|
17802
17881
|
npmDependencies: record(string2(), string2()).describe("The run's current npm dependencies, so the install resolves against them."),
|
|
17803
17882
|
packageName: string2().min(1).max(maxPackageNameLength),
|
|
17804
|
-
packageVersion: string2().min(1).max(maxPackageVersionLength)
|
|
17883
|
+
packageVersion: string2().min(1).max(maxPackageVersionLength),
|
|
17884
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
17805
17885
|
});
|
|
17806
17886
|
const output = discriminatedUnion("outcome", [
|
|
17807
17887
|
object({ outcome: literal("success") }),
|
|
@@ -17821,10 +17901,11 @@ var makeImportPackageOnRunnerContract = () => {
|
|
|
17821
17901
|
};
|
|
17822
17902
|
|
|
17823
17903
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/index.js
|
|
17824
|
-
var makeLaunchRunnerContract = () => {
|
|
17904
|
+
var makeLaunchRunnerContract = (ids) => {
|
|
17825
17905
|
const input = object({
|
|
17826
17906
|
id: runnerIdSchema.describe("Id to launch the runner under, chosen by the caller. Launching the same id again returns the runner already running under it."),
|
|
17827
|
-
runnerName: runnerNameSchema.optional().describe("The runner family to launch. Defaults to the standard web family, playwright.")
|
|
17907
|
+
runnerName: runnerNameSchema.optional().describe("The runner family to launch. Defaults to the standard web family, playwright."),
|
|
17908
|
+
workspaceId: ids.workspace.optional().describe(`The workspace to launch the runner in. ${runnerWorkspaceRequirement}`)
|
|
17828
17909
|
});
|
|
17829
17910
|
const output = makeRunnerSchema().extend({
|
|
17830
17911
|
alreadyRunning: boolean2().describe("True when a runner was already running under this id and was returned unchanged, so no second runner was started and nothing new is billed. Not an error either way."),
|
|
@@ -17838,9 +17919,10 @@ var makeLaunchRunnerContract = () => {
|
|
|
17838
17919
|
output
|
|
17839
17920
|
};
|
|
17840
17921
|
};
|
|
17841
|
-
var makeTerminateRunnerContract = () => {
|
|
17922
|
+
var makeTerminateRunnerContract = (ids) => {
|
|
17842
17923
|
const input = object({
|
|
17843
|
-
id: runnerIdSchema.describe("Id of the runner to terminate.")
|
|
17924
|
+
id: runnerIdSchema.describe("Id of the runner to terminate."),
|
|
17925
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
17844
17926
|
});
|
|
17845
17927
|
const output = object({
|
|
17846
17928
|
id: runnerIdSchema.describe("Id of the runner the call addressed."),
|
|
@@ -17857,14 +17939,16 @@ var makeTerminateRunnerContract = () => {
|
|
|
17857
17939
|
};
|
|
17858
17940
|
|
|
17859
17941
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/list.js
|
|
17860
|
-
var makeListRunnersContract = () => {
|
|
17861
|
-
const input = object({
|
|
17942
|
+
var makeListRunnersContract = (ids) => {
|
|
17943
|
+
const input = object({
|
|
17944
|
+
workspaceId: ids.workspace.optional().describe(`The workspace whose runners to list. ${runnerWorkspaceRequirement}`)
|
|
17945
|
+
});
|
|
17862
17946
|
const output = object({
|
|
17863
17947
|
outcome: literal("success"),
|
|
17864
17948
|
runners: array(makeRunnerSchema()).describe("The runners running right now, in no particular order. Each entry carries the same fields a launch reports, so any of them can be addressed like a runner you launched yourself.")
|
|
17865
17949
|
});
|
|
17866
17950
|
return {
|
|
17867
|
-
description: "List the runners running on the caller's team right now.
|
|
17951
|
+
description: "List the runners running on the caller's team right now. A team API key already names the team and sends nothing; a credential bound to no single workspace names one in `workspaceId`. A runner that was terminated, or that terminated on its own after inactivity, is not listed, because the run system keeps no record of a runner once it is gone. This is a lookup: it never starts a runner, and it does not reset a runner's inactivity clock the way reading its journal does. It is how to find a runner launched from another machine or in an earlier session, when the id it was launched under is no longer at hand.",
|
|
17868
17952
|
input,
|
|
17869
17953
|
kind: "read",
|
|
17870
17954
|
name: "runner.list",
|
|
@@ -17874,11 +17958,12 @@ var makeListRunnersContract = () => {
|
|
|
17874
17958
|
|
|
17875
17959
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/promoteSnapshot.js
|
|
17876
17960
|
var maxSnapshotPathLength = 1024;
|
|
17877
|
-
var makePromoteSnapshotOnRunnerContract = () => {
|
|
17961
|
+
var makePromoteSnapshotOnRunnerContract = (ids) => {
|
|
17878
17962
|
const input = object({
|
|
17879
17963
|
baselinePath: string2().min(1).max(maxSnapshotPathLength).describe("The baseline to replace, as named in the image diff."),
|
|
17880
17964
|
id: runnerIdSchema.describe("Id of the runner holding the screenshot."),
|
|
17881
|
-
screenshotPath: string2().min(1).max(maxSnapshotPathLength).describe("The screenshot to promote, as named in the image diff.")
|
|
17965
|
+
screenshotPath: string2().min(1).max(maxSnapshotPathLength).describe("The screenshot to promote, as named in the image diff."),
|
|
17966
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
17882
17967
|
});
|
|
17883
17968
|
const output = discriminatedUnion("outcome", [
|
|
17884
17969
|
object({ outcome: literal("success") }),
|
|
@@ -17901,10 +17986,11 @@ var makePromoteSnapshotOnRunnerContract = () => {
|
|
|
17901
17986
|
};
|
|
17902
17987
|
|
|
17903
17988
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/readJournal.js
|
|
17904
|
-
var makeReadRunnerJournalContract = () => {
|
|
17989
|
+
var makeReadRunnerJournalContract = (ids) => {
|
|
17905
17990
|
const input = readJournalRequestSchema.extend({
|
|
17906
17991
|
id: runnerIdSchema.describe("Id of the runner to read the journal of."),
|
|
17907
|
-
stream: journalStreamSchema.describe(`The stream to read. QA Wolf writes ${knownJournalStreams.join(", ")}; an unwritten stream reads as empty rather than as an error.`)
|
|
17992
|
+
stream: journalStreamSchema.describe(`The stream to read. QA Wolf writes ${knownJournalStreams.join(", ")}; an unwritten stream reads as empty rather than as an error.`),
|
|
17993
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
17908
17994
|
});
|
|
17909
17995
|
const output = discriminatedUnion("outcome", [
|
|
17910
17996
|
readJournalResponseSchema.extend({ outcome: literal("success") }),
|
|
@@ -17939,7 +18025,8 @@ var makeRunFlowOnRunnerContract = (ids) => {
|
|
|
17939
18025
|
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."),
|
|
17940
18026
|
id: runnerIdSchema.describe("Id of the runner to run the flow on."),
|
|
17941
18027
|
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."),
|
|
17942
|
-
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.")
|
|
18028
|
+
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."),
|
|
18029
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
17943
18030
|
}).refine((request) => request.env === undefined || request.environmentId === undefined, {
|
|
17944
18031
|
error: "Send env or environmentId, not both. An environment's variables are taken as they are stored, so there is no order in which the two would combine.",
|
|
17945
18032
|
path: ["environmentId"]
|
|
@@ -17991,9 +18078,10 @@ var makeRunFlowOnRunnerContract = (ids) => {
|
|
|
17991
18078
|
};
|
|
17992
18079
|
|
|
17993
18080
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/stopRun.js
|
|
17994
|
-
var makeStopRunOnRunnerContract = () => {
|
|
18081
|
+
var makeStopRunOnRunnerContract = (ids) => {
|
|
17995
18082
|
const input = object({
|
|
17996
|
-
id: runnerIdSchema.describe("Id of the runner to stop the run on.")
|
|
18083
|
+
id: runnerIdSchema.describe("Id of the runner to stop the run on."),
|
|
18084
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
17997
18085
|
});
|
|
17998
18086
|
const output = discriminatedUnion("outcome", [
|
|
17999
18087
|
object({
|
|
@@ -18012,13 +18100,14 @@ var makeStopRunOnRunnerContract = () => {
|
|
|
18012
18100
|
};
|
|
18013
18101
|
|
|
18014
18102
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/takeScreenshot.js
|
|
18015
|
-
var makeTakeScreenshotOnRunnerContract = () => {
|
|
18103
|
+
var makeTakeScreenshotOnRunnerContract = (ids) => {
|
|
18016
18104
|
const input = object({
|
|
18017
|
-
id: runnerIdSchema.describe("Id of the runner to take a screenshot of.")
|
|
18105
|
+
id: runnerIdSchema.describe("Id of the runner to take a screenshot of."),
|
|
18106
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
18018
18107
|
});
|
|
18019
18108
|
const output = discriminatedUnion("outcome", [
|
|
18020
18109
|
object({
|
|
18021
|
-
imageJpegBase64:
|
|
18110
|
+
imageJpegBase64: screenshotImageSchema,
|
|
18022
18111
|
outcome: literal("success")
|
|
18023
18112
|
}),
|
|
18024
18113
|
makeRunnerFailureSchema([
|
|
@@ -18072,6 +18161,7 @@ var makeContractsV1 = (ids) => {
|
|
|
18072
18161
|
get: makeGetEmailContract(resolvedIds),
|
|
18073
18162
|
getAttachment: makeGetEmailAttachmentContract(resolvedIds),
|
|
18074
18163
|
listAddresses: makeListEmailAddressesContract(resolvedIds),
|
|
18164
|
+
registerAddress: makeRegisterEmailAddressContract(resolvedIds),
|
|
18075
18165
|
send: makeSendEmailContract(resolvedIds)
|
|
18076
18166
|
},
|
|
18077
18167
|
environment: {
|
|
@@ -18103,24 +18193,25 @@ var makeContractsV1 = (ids) => {
|
|
|
18103
18193
|
diagnose: makeDiagnoseRunContract(resolvedIds),
|
|
18104
18194
|
find: makeFindRunsContract(resolvedIds),
|
|
18105
18195
|
get: makeGetRunContract(resolvedIds),
|
|
18106
|
-
reattempt: makeReattemptRunContract(resolvedIds)
|
|
18196
|
+
reattempt: makeReattemptRunContract(resolvedIds),
|
|
18197
|
+
stop: makeStopRunContract(resolvedIds)
|
|
18107
18198
|
},
|
|
18108
18199
|
runner: {
|
|
18109
|
-
evaluateSnippet: makeEvaluateSnippetOnRunnerContract(),
|
|
18110
|
-
get: makeGetRunnerContract(),
|
|
18111
|
-
highlightSelector: makeHighlightSelectorOnRunnerContract(),
|
|
18112
|
-
importPackage: makeImportPackageOnRunnerContract(),
|
|
18113
|
-
inspect: makeInspectOnRunnerContract(),
|
|
18114
|
-
inspectMobile: makeInspectMobileOnRunnerContract(),
|
|
18115
|
-
launch: makeLaunchRunnerContract(),
|
|
18116
|
-
list: makeListRunnersContract(),
|
|
18117
|
-
performAction: makePerformActionOnRunnerContract(),
|
|
18118
|
-
promoteSnapshot: makePromoteSnapshotOnRunnerContract(),
|
|
18119
|
-
readJournal: makeReadRunnerJournalContract(),
|
|
18200
|
+
evaluateSnippet: makeEvaluateSnippetOnRunnerContract(resolvedIds),
|
|
18201
|
+
get: makeGetRunnerContract(resolvedIds),
|
|
18202
|
+
highlightSelector: makeHighlightSelectorOnRunnerContract(resolvedIds),
|
|
18203
|
+
importPackage: makeImportPackageOnRunnerContract(resolvedIds),
|
|
18204
|
+
inspect: makeInspectOnRunnerContract(resolvedIds),
|
|
18205
|
+
inspectMobile: makeInspectMobileOnRunnerContract(resolvedIds),
|
|
18206
|
+
launch: makeLaunchRunnerContract(resolvedIds),
|
|
18207
|
+
list: makeListRunnersContract(resolvedIds),
|
|
18208
|
+
performAction: makePerformActionOnRunnerContract(resolvedIds),
|
|
18209
|
+
promoteSnapshot: makePromoteSnapshotOnRunnerContract(resolvedIds),
|
|
18210
|
+
readJournal: makeReadRunnerJournalContract(resolvedIds),
|
|
18120
18211
|
runFlow: makeRunFlowOnRunnerContract(resolvedIds),
|
|
18121
|
-
stopRun: makeStopRunOnRunnerContract(),
|
|
18122
|
-
takeScreenshot: makeTakeScreenshotOnRunnerContract(),
|
|
18123
|
-
terminate: makeTerminateRunnerContract()
|
|
18212
|
+
stopRun: makeStopRunOnRunnerContract(resolvedIds),
|
|
18213
|
+
takeScreenshot: makeTakeScreenshotOnRunnerContract(resolvedIds),
|
|
18214
|
+
terminate: makeTerminateRunnerContract(resolvedIds)
|
|
18124
18215
|
},
|
|
18125
18216
|
tag: {
|
|
18126
18217
|
create: makeCreateTagContract(resolvedIds),
|
|
@@ -18523,6 +18614,34 @@ async function writeTeamStorageAssets(args, deps) {
|
|
|
18523
18614
|
}
|
|
18524
18615
|
}
|
|
18525
18616
|
|
|
18617
|
+
// src/shell/platform/teamStorageMethods.ts
|
|
18618
|
+
function createTeamStorageMethods(trpc, deps, fs, getIdentity) {
|
|
18619
|
+
async function list() {
|
|
18620
|
+
if (deps.workspaceId !== undefined) {
|
|
18621
|
+
return listTeamStorageFiles(trpc, { teamId: deps.workspaceId }, deps);
|
|
18622
|
+
}
|
|
18623
|
+
const identity = await getIdentity();
|
|
18624
|
+
if (!identity.ok)
|
|
18625
|
+
return identity;
|
|
18626
|
+
if (!("team" in identity.value)) {
|
|
18627
|
+
return {
|
|
18628
|
+
ok: false,
|
|
18629
|
+
error: flowsMessages.pull.teamStorageRequiresTeamKey
|
|
18630
|
+
};
|
|
18631
|
+
}
|
|
18632
|
+
return listTeamStorageFiles(trpc, { teamId: identity.value.team.id }, deps);
|
|
18633
|
+
}
|
|
18634
|
+
return {
|
|
18635
|
+
listTeamStorageFiles: list,
|
|
18636
|
+
async syncTeamStorageAssets(assetsAbs, opts) {
|
|
18637
|
+
const files = await list();
|
|
18638
|
+
if (!files.ok)
|
|
18639
|
+
return files;
|
|
18640
|
+
return downloadTeamStorageAssets({ assetsAbs, files: files.value }, { fetch: deps.fetch, fs, onProgress: opts?.onProgress });
|
|
18641
|
+
}
|
|
18642
|
+
};
|
|
18643
|
+
}
|
|
18644
|
+
|
|
18526
18645
|
// src/shell/platform/createPlatformClient.ts
|
|
18527
18646
|
var requestBackoffMs2 = [500, 1500];
|
|
18528
18647
|
function createPlatformClient(apiKey, deps) {
|
|
@@ -18539,8 +18658,10 @@ function createPlatformClient(apiKey, deps) {
|
|
|
18539
18658
|
return result;
|
|
18540
18659
|
return { ok: true, value: { signedUrl: result.value.url } };
|
|
18541
18660
|
}
|
|
18661
|
+
const identityMethods = createIdentityMethods(apiKey, deps, requestBackoffMs2);
|
|
18542
18662
|
return {
|
|
18543
|
-
...
|
|
18663
|
+
...identityMethods,
|
|
18664
|
+
...createTeamStorageMethods(trpc, deps, fs, identityMethods.getIdentity),
|
|
18544
18665
|
getFlowsBundleUrl: getFlowsBundleUrlImpl,
|
|
18545
18666
|
callPublicApi: makeCallPublicApiMethod(trpc, deps, requestBackoffMs2),
|
|
18546
18667
|
async getEnvVars(envId) {
|
|
@@ -18554,24 +18675,6 @@ function createPlatformClient(apiKey, deps) {
|
|
|
18554
18675
|
return result;
|
|
18555
18676
|
return { ok: true, value: result.value.environmentVariables };
|
|
18556
18677
|
},
|
|
18557
|
-
async listTeamStorageFiles() {
|
|
18558
|
-
const identity = await this.getIdentity();
|
|
18559
|
-
if (!identity.ok)
|
|
18560
|
-
return identity;
|
|
18561
|
-
if (!("team" in identity.value)) {
|
|
18562
|
-
return {
|
|
18563
|
-
ok: false,
|
|
18564
|
-
error: flowsMessages.pull.teamStorageRequiresTeamKey
|
|
18565
|
-
};
|
|
18566
|
-
}
|
|
18567
|
-
return listTeamStorageFiles(trpc, { teamId: identity.value.team.id }, deps);
|
|
18568
|
-
},
|
|
18569
|
-
async syncTeamStorageAssets(assetsAbs, opts) {
|
|
18570
|
-
const files = await this.listTeamStorageFiles();
|
|
18571
|
-
if (!files.ok)
|
|
18572
|
-
return files;
|
|
18573
|
-
return downloadTeamStorageAssets({ assetsAbs, files: files.value }, { fetch: deps.fetch, fs, onProgress: opts?.onProgress });
|
|
18574
|
-
},
|
|
18575
18678
|
async downloadBundle(envId) {
|
|
18576
18679
|
const urlResult = await getFlowsBundleUrlImpl(envId);
|
|
18577
18680
|
if (!urlResult.ok)
|
|
@@ -20372,9 +20475,11 @@ var registryUrl = "https://registry.npmjs.org";
|
|
|
20372
20475
|
var timeoutMs4 = 3000;
|
|
20373
20476
|
async function fetchLatestVersion(packageName, deps = {}) {
|
|
20374
20477
|
const fetchFn = deps.fetchFn ?? globalThis.fetch;
|
|
20478
|
+
const deadline = AbortSignal.timeout(timeoutMs4);
|
|
20479
|
+
const signal = deps.signal ? AbortSignal.any([deps.signal, deadline]) : deadline;
|
|
20375
20480
|
try {
|
|
20376
20481
|
const response = await fetchFn(`${registryUrl}/${packageName}/latest`, {
|
|
20377
|
-
signal
|
|
20482
|
+
signal
|
|
20378
20483
|
});
|
|
20379
20484
|
if (!response.ok)
|
|
20380
20485
|
return;
|
|
@@ -20423,14 +20528,16 @@ function startUpdateCheck(deps) {
|
|
|
20423
20528
|
if (deps.env["QAWOLF_NO_UPDATE_CHECK"]) {
|
|
20424
20529
|
return noopNotifier;
|
|
20425
20530
|
}
|
|
20531
|
+
const controller = new AbortController;
|
|
20426
20532
|
let latest;
|
|
20427
|
-
deps.fetchLatestVersion().then((version) => {
|
|
20533
|
+
deps.fetchLatestVersion(controller.signal).then((version) => {
|
|
20428
20534
|
latest = version;
|
|
20429
20535
|
}, () => {
|
|
20430
20536
|
return;
|
|
20431
20537
|
});
|
|
20432
20538
|
return {
|
|
20433
20539
|
async notifyIfOutdated() {
|
|
20540
|
+
controller.abort();
|
|
20434
20541
|
if (latest === undefined)
|
|
20435
20542
|
return;
|
|
20436
20543
|
if (!isNewerVersion(deps.currentVersion, latest))
|
|
@@ -20457,7 +20564,7 @@ function startUpdateCheck(deps) {
|
|
|
20457
20564
|
// package.json
|
|
20458
20565
|
var package_default = {
|
|
20459
20566
|
name: "@qawolf/cli",
|
|
20460
|
-
version: "1.
|
|
20567
|
+
version: "1.27.0",
|
|
20461
20568
|
description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
|
|
20462
20569
|
keywords: [
|
|
20463
20570
|
"automation",
|
|
@@ -20528,7 +20635,7 @@ var package_default = {
|
|
|
20528
20635
|
"@clack/prompts": "1.5.1",
|
|
20529
20636
|
"@napi-rs/keyring": "1.3.0",
|
|
20530
20637
|
"@oxc-node/core": "0.1.0",
|
|
20531
|
-
"@qawolf/api-contracts": "0.
|
|
20638
|
+
"@qawolf/api-contracts": "0.52.0",
|
|
20532
20639
|
"@qawolf/emails": "1.1.1",
|
|
20533
20640
|
"@qawolf/flow-targets": "1.0.0",
|
|
20534
20641
|
"@qawolf/flows": "0.1.4",
|
|
@@ -20594,7 +20701,7 @@ function buildBaseContext(command, signals) {
|
|
|
20594
20701
|
currentVersion: package_default.version,
|
|
20595
20702
|
configDir: getConfigDir(),
|
|
20596
20703
|
fs,
|
|
20597
|
-
fetchLatestVersion: () => fetchLatestVersion(package_default.name),
|
|
20704
|
+
fetchLatestVersion: (signal) => fetchLatestVersion(package_default.name, { signal }),
|
|
20598
20705
|
renderNotice: (body, title) => ui.note(body, title)
|
|
20599
20706
|
});
|
|
20600
20707
|
return {
|
|
@@ -32214,6 +32321,28 @@ function registerPublicApiCommands(program, signals, options = {}) {
|
|
|
32214
32321
|
}
|
|
32215
32322
|
}
|
|
32216
32323
|
|
|
32324
|
+
// src/domains/interactiveRunner/evaluateSnippetFailure.ts
|
|
32325
|
+
function describeEvaluateSnippetFailure(failure) {
|
|
32326
|
+
const { failureReason } = failure;
|
|
32327
|
+
switch (failureReason) {
|
|
32328
|
+
case "runner-cannot-evaluate-snippets":
|
|
32329
|
+
return {
|
|
32330
|
+
error: interactiveRunnerMessages.runnerCannotEvaluateSnippets,
|
|
32331
|
+
exitCode: exitCodes.invalidArgs
|
|
32332
|
+
};
|
|
32333
|
+
case "runner-unreachable":
|
|
32334
|
+
return {
|
|
32335
|
+
error: interactiveRunnerMessages.snippetRunnerUnreachable,
|
|
32336
|
+
exitCode: exitCodes.network
|
|
32337
|
+
};
|
|
32338
|
+
default:
|
|
32339
|
+
return {
|
|
32340
|
+
error: interactiveRunnerMessages.snippetAnsweredUnknown(failureReason),
|
|
32341
|
+
exitCode: exitCodes.network
|
|
32342
|
+
};
|
|
32343
|
+
}
|
|
32344
|
+
}
|
|
32345
|
+
|
|
32217
32346
|
// src/domains/interactiveRunner/runnerCallOptions.ts
|
|
32218
32347
|
var runnerCallOptions = { timeoutMs: 90000 };
|
|
32219
32348
|
|
|
@@ -32326,134 +32455,205 @@ function announceRunner(ctx, resolved) {
|
|
|
32326
32455
|
}
|
|
32327
32456
|
}
|
|
32328
32457
|
|
|
32329
|
-
// src/
|
|
32330
|
-
|
|
32331
|
-
|
|
32332
|
-
|
|
32333
|
-
|
|
32334
|
-
|
|
32335
|
-
|
|
32336
|
-
|
|
32337
|
-
|
|
32338
|
-
|
|
32339
|
-
|
|
32340
|
-
|
|
32341
|
-
|
|
32342
|
-
|
|
32458
|
+
// src/core/interactiveRunner/runFiles.ts
|
|
32459
|
+
import { isAbsolute as isAbsolute5, relative as relative10, resolve as resolve11, sep as sep5 } from "node:path";
|
|
32460
|
+
function toCollectedPath(cwd, path) {
|
|
32461
|
+
const absolute = isAbsolute5(path) ? path : resolve11(cwd, path);
|
|
32462
|
+
return relative10(cwd, absolute).split(sep5).join("/");
|
|
32463
|
+
}
|
|
32464
|
+
var namedLargestFileCount = 3;
|
|
32465
|
+
function largestFiles(files) {
|
|
32466
|
+
return Object.entries(files).map(([path, content]) => ({
|
|
32467
|
+
byteLength: runFilesByteLength({ [path]: content }),
|
|
32468
|
+
path
|
|
32469
|
+
})).sort((a, b) => b.byteLength - a.byteLength).slice(0, namedLargestFileCount);
|
|
32470
|
+
}
|
|
32471
|
+
function checkPresenceAndSize(files, requiredPath) {
|
|
32472
|
+
if (!Object.hasOwn(files, requiredPath)) {
|
|
32473
|
+
return { path: requiredPath, type: "missing-file" };
|
|
32343
32474
|
}
|
|
32344
|
-
const
|
|
32345
|
-
|
|
32346
|
-
|
|
32347
|
-
|
|
32348
|
-
|
|
32349
|
-
|
|
32350
|
-
|
|
32351
|
-
|
|
32352
|
-
return {
|
|
32353
|
-
error: interactiveRunnerMessages.highlightSelectorInvalid(answer.selector),
|
|
32354
|
-
exitCode: exitCodes.invalidArgs
|
|
32355
|
-
};
|
|
32356
|
-
}
|
|
32357
|
-
ctx.ui.output(answer, answer.status === "empty" ? interactiveRunnerMessages.highlightMatchedNothing(answer.selector) : interactiveRunnerMessages.highlightMatched(answer.matchCount, answer.targetPage));
|
|
32358
|
-
return;
|
|
32359
|
-
}
|
|
32360
|
-
case "failure":
|
|
32361
|
-
return describeFailure(result.value.failureReason);
|
|
32362
|
-
default:
|
|
32363
|
-
return {
|
|
32364
|
-
error: interactiveRunnerMessages.highlightAnsweredUnknown(outcome),
|
|
32365
|
-
exitCode: exitCodes.network
|
|
32366
|
-
};
|
|
32475
|
+
const byteLength = runFilesByteLength(files);
|
|
32476
|
+
if (byteLength > maxRunFilesByteLength) {
|
|
32477
|
+
return {
|
|
32478
|
+
byteLength,
|
|
32479
|
+
largest: largestFiles(files),
|
|
32480
|
+
maxByteLength: maxRunFilesByteLength,
|
|
32481
|
+
type: "too-large"
|
|
32482
|
+
};
|
|
32367
32483
|
}
|
|
32368
|
-
}
|
|
32369
|
-
|
|
32370
|
-
|
|
32371
|
-
|
|
32372
|
-
|
|
32373
|
-
|
|
32374
|
-
|
|
32375
|
-
};
|
|
32376
|
-
case "runner-cannot-highlight-selectors":
|
|
32377
|
-
return {
|
|
32378
|
-
error: interactiveRunnerMessages.runnerCannotHighlightSelectors,
|
|
32379
|
-
exitCode: exitCodes.invalidArgs
|
|
32380
|
-
};
|
|
32381
|
-
case "runner-unreachable":
|
|
32382
|
-
return {
|
|
32383
|
-
error: interactiveRunnerMessages.runnerUnreachable,
|
|
32384
|
-
exitCode: exitCodes.network
|
|
32385
|
-
};
|
|
32386
|
-
default:
|
|
32387
|
-
return {
|
|
32388
|
-
error: interactiveRunnerMessages.highlightAnsweredUnknown(failureReason),
|
|
32389
|
-
exitCode: exitCodes.network
|
|
32390
|
-
};
|
|
32484
|
+
const encodedByteLength = Buffer.byteLength(JSON.stringify({ files, path: requiredPath }), "utf8");
|
|
32485
|
+
if (encodedByteLength > maxRunnerRequestEncodedByteLength) {
|
|
32486
|
+
return {
|
|
32487
|
+
byteLength: encodedByteLength,
|
|
32488
|
+
maxByteLength: maxRunnerRequestEncodedByteLength,
|
|
32489
|
+
type: "request-too-large"
|
|
32490
|
+
};
|
|
32391
32491
|
}
|
|
32492
|
+
return { type: "ok" };
|
|
32392
32493
|
}
|
|
32393
|
-
|
|
32394
|
-
|
|
32395
|
-
|
|
32396
|
-
|
|
32397
|
-
|
|
32398
|
-
|
|
32399
|
-
const { typescript: compiler } = options;
|
|
32400
|
-
const sourceFile = compiler.createSourceFile(options.path, options.content, compiler.ScriptTarget.Latest, true);
|
|
32401
|
-
const isLocal = (importPath) => importPath.startsWith("./") || importPath.startsWith("../") || isPathAlias(importPath, options.tsconfigPaths);
|
|
32402
|
-
const found = [];
|
|
32403
|
-
for (const statement of sourceFile.statements) {
|
|
32404
|
-
if (!compiler.isImportDeclaration(statement))
|
|
32405
|
-
continue;
|
|
32406
|
-
const { moduleSpecifier } = statement;
|
|
32407
|
-
if (!compiler.isStringLiteral(moduleSpecifier))
|
|
32408
|
-
continue;
|
|
32409
|
-
if (isLocal(moduleSpecifier.text))
|
|
32410
|
-
found.push(moduleSpecifier.text);
|
|
32494
|
+
function checkRunFiles(files, entryPointPath) {
|
|
32495
|
+
const present = checkPresenceAndSize(files, entryPointPath);
|
|
32496
|
+
if (present.type !== "ok")
|
|
32497
|
+
return present;
|
|
32498
|
+
if (!Object.hasOwn(files, runPackageJsonPath)) {
|
|
32499
|
+
return { type: "missing-package-json" };
|
|
32411
32500
|
}
|
|
32412
|
-
|
|
32413
|
-
if (isDynamicImportCall(node, compiler)) {
|
|
32414
|
-
const [argument] = node.arguments;
|
|
32415
|
-
if (argument !== undefined && compiler.isStringLiteral(argument) && isLocal(argument.text)) {
|
|
32416
|
-
found.push(argument.text);
|
|
32417
|
-
}
|
|
32418
|
-
}
|
|
32419
|
-
compiler.forEachChild(node, visit);
|
|
32420
|
-
};
|
|
32421
|
-
visit(sourceFile);
|
|
32422
|
-
return [...new Set(found)];
|
|
32501
|
+
return { type: "ok" };
|
|
32423
32502
|
}
|
|
32424
|
-
function
|
|
32425
|
-
return
|
|
32503
|
+
function checkSnippetFiles(files, contextFilePath) {
|
|
32504
|
+
return checkPresenceAndSize(files, contextFilePath);
|
|
32426
32505
|
}
|
|
32427
|
-
function
|
|
32428
|
-
|
|
32429
|
-
|
|
32430
|
-
|
|
32506
|
+
function describeRunFilesCheck(check) {
|
|
32507
|
+
switch (check.type) {
|
|
32508
|
+
case "missing-file":
|
|
32509
|
+
return interactiveRunnerMessages.fileNotCollected(check.path);
|
|
32510
|
+
case "missing-package-json":
|
|
32511
|
+
return interactiveRunnerMessages.missingPackageJson;
|
|
32512
|
+
case "too-large":
|
|
32513
|
+
return interactiveRunnerMessages.filesTooLarge(check.byteLength, check.maxByteLength, check.largest);
|
|
32514
|
+
case "request-too-large":
|
|
32515
|
+
return interactiveRunnerMessages.requestTooLarge(check.byteLength, check.maxByteLength);
|
|
32516
|
+
}
|
|
32431
32517
|
}
|
|
32432
32518
|
|
|
32433
|
-
// src/
|
|
32434
|
-
|
|
32435
|
-
|
|
32436
|
-
|
|
32437
|
-
function isRecord4(value) {
|
|
32438
|
-
return typeof value === "object" && value !== null;
|
|
32439
|
-
}
|
|
32440
|
-
function isTsconfigPaths(value) {
|
|
32441
|
-
return isRecord4(value) && Object.values(value).every((targets) => Array.isArray(targets) && targets.every((target) => typeof target === "string"));
|
|
32442
|
-
}
|
|
32443
|
-
function parseTsconfigPaths(tsconfigContent) {
|
|
32444
|
-
let parsed;
|
|
32445
|
-
try {
|
|
32446
|
-
parsed = JSON.parse(tsconfigContent);
|
|
32447
|
-
} catch {
|
|
32448
|
-
return;
|
|
32519
|
+
// src/domains/interactiveRunner/snippetScope.ts
|
|
32520
|
+
async function resolveSnippetScope(contextFile, deps) {
|
|
32521
|
+
if (contextFile === undefined) {
|
|
32522
|
+
return { filePath: undefined, files: undefined, ok: true };
|
|
32449
32523
|
}
|
|
32450
|
-
|
|
32451
|
-
|
|
32452
|
-
const
|
|
32453
|
-
if (
|
|
32454
|
-
return;
|
|
32455
|
-
|
|
32456
|
-
return
|
|
32524
|
+
const filePath = toCollectedPath(deps.cwd, contextFile);
|
|
32525
|
+
const { files } = await deps.collectRunFiles([filePath]);
|
|
32526
|
+
const check = checkSnippetFiles(files, filePath);
|
|
32527
|
+
if (check.type !== "ok") {
|
|
32528
|
+
return { error: describeRunFilesCheck(check), ok: false };
|
|
32529
|
+
}
|
|
32530
|
+
return { filePath, files, ok: true };
|
|
32531
|
+
}
|
|
32532
|
+
|
|
32533
|
+
// src/domains/interactiveRunner/evaluateSnippet.ts
|
|
32534
|
+
var stdinArgument = "-";
|
|
32535
|
+
async function readCode(source, deps) {
|
|
32536
|
+
const code = source === stdinArgument ? await deps.readStdin() : await deps.readFile(source).catch(() => {
|
|
32537
|
+
return;
|
|
32538
|
+
});
|
|
32539
|
+
if (code === undefined) {
|
|
32540
|
+
return {
|
|
32541
|
+
error: interactiveRunnerMessages.snippetFileUnreadable(source),
|
|
32542
|
+
ok: false
|
|
32543
|
+
};
|
|
32544
|
+
}
|
|
32545
|
+
if (code.trim() === "") {
|
|
32546
|
+
return source === stdinArgument ? { error: interactiveRunnerMessages.stdinEmptySnippet, ok: false } : { error: interactiveRunnerMessages.snippetEmpty(source), ok: false };
|
|
32547
|
+
}
|
|
32548
|
+
return { code, ok: true };
|
|
32549
|
+
}
|
|
32550
|
+
async function handleRunnerExec(ctx, options, deps) {
|
|
32551
|
+
const code = await readCode(options.source, deps);
|
|
32552
|
+
if (!code.ok)
|
|
32553
|
+
return { error: code.error, exitCode: exitCodes.invalidArgs };
|
|
32554
|
+
const scope = await resolveSnippetScope(options.contextFile, deps);
|
|
32555
|
+
if (!scope.ok)
|
|
32556
|
+
return { error: scope.error, exitCode: exitCodes.invalidArgs };
|
|
32557
|
+
const resolved = await resolveRunner(ctx, { autoLaunch: true, runner: options.runner }, deps);
|
|
32558
|
+
if (resolved.type === "failed") {
|
|
32559
|
+
return { ...failureFields(resolved), exitCode: resolved.exitCode };
|
|
32560
|
+
}
|
|
32561
|
+
announceRunner(ctx, resolved);
|
|
32562
|
+
const result = await ctx.platformClient.callPublicApi(publicContractsV1.runner.evaluateSnippet, {
|
|
32563
|
+
code: code.code,
|
|
32564
|
+
id: resolved.runnerId,
|
|
32565
|
+
...scope.filePath === undefined ? {} : { filePath: scope.filePath },
|
|
32566
|
+
...scope.files === undefined ? {} : { files: scope.files }
|
|
32567
|
+
}, runnerCallOptions);
|
|
32568
|
+
if (!result.ok) {
|
|
32569
|
+
return {
|
|
32570
|
+
...failureFields(result),
|
|
32571
|
+
exitCode: result.exitCode ?? exitCodes.network
|
|
32572
|
+
};
|
|
32573
|
+
}
|
|
32574
|
+
if (result.value.outcome === "failure") {
|
|
32575
|
+
return describeEvaluateSnippetFailure(result.value);
|
|
32576
|
+
}
|
|
32577
|
+
switch (result.value.result) {
|
|
32578
|
+
case "success":
|
|
32579
|
+
ctx.ui.output({ outcome: "evaluated", result: "success" }, interactiveRunnerMessages.snippetRan);
|
|
32580
|
+
return;
|
|
32581
|
+
case "stopped":
|
|
32582
|
+
return {
|
|
32583
|
+
error: interactiveRunnerMessages.snippetStopped,
|
|
32584
|
+
exitCode: exitCodes.testFailure
|
|
32585
|
+
};
|
|
32586
|
+
case "error":
|
|
32587
|
+
return {
|
|
32588
|
+
error: interactiveRunnerMessages.snippetErrored(result.value.errorMessage),
|
|
32589
|
+
exitCode: exitCodes.testFailure
|
|
32590
|
+
};
|
|
32591
|
+
}
|
|
32592
|
+
}
|
|
32593
|
+
|
|
32594
|
+
// src/shell/interactiveRunner/collectRunFiles.ts
|
|
32595
|
+
import { join as join51 } from "node:path";
|
|
32596
|
+
|
|
32597
|
+
// src/core/interactiveRunner/getImports.ts
|
|
32598
|
+
function getImports(options) {
|
|
32599
|
+
const { typescript: compiler } = options;
|
|
32600
|
+
const sourceFile = compiler.createSourceFile(options.path, options.content, compiler.ScriptTarget.Latest, true);
|
|
32601
|
+
const isLocal = (importPath) => importPath.startsWith("./") || importPath.startsWith("../") || isPathAlias(importPath, options.tsconfigPaths);
|
|
32602
|
+
const found = [];
|
|
32603
|
+
for (const statement of sourceFile.statements) {
|
|
32604
|
+
if (!compiler.isImportDeclaration(statement))
|
|
32605
|
+
continue;
|
|
32606
|
+
const { moduleSpecifier } = statement;
|
|
32607
|
+
if (!compiler.isStringLiteral(moduleSpecifier))
|
|
32608
|
+
continue;
|
|
32609
|
+
if (isLocal(moduleSpecifier.text))
|
|
32610
|
+
found.push(moduleSpecifier.text);
|
|
32611
|
+
}
|
|
32612
|
+
const visit = (node) => {
|
|
32613
|
+
if (isDynamicImportCall(node, compiler)) {
|
|
32614
|
+
const [argument] = node.arguments;
|
|
32615
|
+
if (argument !== undefined && compiler.isStringLiteral(argument) && isLocal(argument.text)) {
|
|
32616
|
+
found.push(argument.text);
|
|
32617
|
+
}
|
|
32618
|
+
}
|
|
32619
|
+
compiler.forEachChild(node, visit);
|
|
32620
|
+
};
|
|
32621
|
+
visit(sourceFile);
|
|
32622
|
+
return [...new Set(found)];
|
|
32623
|
+
}
|
|
32624
|
+
function isDynamicImportCall(node, compiler) {
|
|
32625
|
+
return compiler.isCallExpression(node) && node.expression.kind === compiler.SyntaxKind.ImportKeyword;
|
|
32626
|
+
}
|
|
32627
|
+
function isPathAlias(importPath, paths) {
|
|
32628
|
+
if (paths === undefined)
|
|
32629
|
+
return false;
|
|
32630
|
+
return Object.keys(paths).some((pattern) => importPath.startsWith(pattern.replace("*", "")));
|
|
32631
|
+
}
|
|
32632
|
+
|
|
32633
|
+
// src/core/interactiveRunner/resolveImportPath.ts
|
|
32634
|
+
import { dirname as dirname15, join as join50, normalize as normalize3 } from "node:path/posix";
|
|
32635
|
+
|
|
32636
|
+
// src/core/interactiveRunner/tsconfigPaths.ts
|
|
32637
|
+
function isRecord4(value) {
|
|
32638
|
+
return typeof value === "object" && value !== null;
|
|
32639
|
+
}
|
|
32640
|
+
function isTsconfigPaths(value) {
|
|
32641
|
+
return isRecord4(value) && Object.values(value).every((targets) => Array.isArray(targets) && targets.every((target) => typeof target === "string"));
|
|
32642
|
+
}
|
|
32643
|
+
function parseTsconfigPaths(tsconfigContent) {
|
|
32644
|
+
let parsed;
|
|
32645
|
+
try {
|
|
32646
|
+
parsed = JSON.parse(tsconfigContent);
|
|
32647
|
+
} catch {
|
|
32648
|
+
return;
|
|
32649
|
+
}
|
|
32650
|
+
if (!isRecord4(parsed))
|
|
32651
|
+
return;
|
|
32652
|
+
const compilerOptions = parsed["compilerOptions"];
|
|
32653
|
+
if (!isRecord4(compilerOptions))
|
|
32654
|
+
return;
|
|
32655
|
+
const paths = compilerOptions["paths"];
|
|
32656
|
+
return isTsconfigPaths(paths) ? paths : undefined;
|
|
32457
32657
|
}
|
|
32458
32658
|
function resolvePathAlias(importPath, paths) {
|
|
32459
32659
|
if (paths === undefined)
|
|
@@ -32729,19 +32929,38 @@ function makeRunnerStore(options) {
|
|
|
32729
32929
|
// src/shell/interactiveRunner/writeScreenshot.ts
|
|
32730
32930
|
import { dirname as dirname16 } from "node:path";
|
|
32731
32931
|
var jpegStartOfImage = [255, 216, 255];
|
|
32932
|
+
var stdoutPath = "-";
|
|
32732
32933
|
async function writeScreenshot(options) {
|
|
32733
32934
|
const bytes = Buffer.from(options.imageJpegBase64, "base64");
|
|
32734
32935
|
if (jpegStartOfImage.some((byte, index) => bytes[index] !== byte)) {
|
|
32735
32936
|
return { ok: false, reason: "not-a-jpeg" };
|
|
32736
32937
|
}
|
|
32737
32938
|
try {
|
|
32738
|
-
|
|
32739
|
-
|
|
32939
|
+
if (options.path === stdoutPath) {
|
|
32940
|
+
await writeToStdout(options.stdout ?? process.stdout, bytes);
|
|
32941
|
+
} else {
|
|
32942
|
+
await options.fs.mkdir(dirname16(options.path), { recursive: true });
|
|
32943
|
+
await options.fs.writeFile(options.path, bytes);
|
|
32944
|
+
}
|
|
32740
32945
|
} catch (error) {
|
|
32741
32946
|
return { detail: errorMessage(error), ok: false, reason: "unwritable" };
|
|
32742
32947
|
}
|
|
32743
32948
|
return { ok: true };
|
|
32744
32949
|
}
|
|
32950
|
+
function writeToStdout(stdout, bytes) {
|
|
32951
|
+
return new Promise((resolve, reject) => {
|
|
32952
|
+
const onError = (error) => reject(error);
|
|
32953
|
+
stdout.once("error", onError);
|
|
32954
|
+
stdout.write(bytes, (error) => {
|
|
32955
|
+
if (error) {
|
|
32956
|
+
reject(error);
|
|
32957
|
+
return;
|
|
32958
|
+
}
|
|
32959
|
+
stdout.off("error", onError);
|
|
32960
|
+
resolve();
|
|
32961
|
+
});
|
|
32962
|
+
});
|
|
32963
|
+
}
|
|
32745
32964
|
|
|
32746
32965
|
// src/shell/stdin.ts
|
|
32747
32966
|
async function readStdin2() {
|
|
@@ -32782,6 +33001,81 @@ function runnerDeps(ctx) {
|
|
|
32782
33001
|
});
|
|
32783
33002
|
}
|
|
32784
33003
|
|
|
33004
|
+
// src/commands/runner/exec.register.ts
|
|
33005
|
+
var execExamples = `
|
|
33006
|
+
Examples:
|
|
33007
|
+
$ qawolf runner exec snippet.ts
|
|
33008
|
+
$ echo 'console.log(await page.title())' | qawolf runner exec -
|
|
33009
|
+
$ qawolf runner exec snippet.ts --file flows/checkout.flow.ts`;
|
|
33010
|
+
function registerRunnerExecCommand(runner, signals) {
|
|
33011
|
+
declareCommandKind(runner.command("exec <file>"), "write").description("Evaluate a snippet against a runner's live page. Use - to read the snippet from stdin").option("--file <path>", "File whose scope the snippet is evaluated in; it and the directory's other files travel with it").option("--runner <id>", runnerFlagDescription).addHelpText("after", execExamples).action((file, opts, command) => withAuthContext(signals, (ctx) => handleRunnerExec(ctx, { contextFile: opts.file, runner: opts.runner, source: file }, runnerDeps(ctx)))(opts, command));
|
|
33012
|
+
}
|
|
33013
|
+
|
|
33014
|
+
// src/domains/interactiveRunner/highlightSelector.ts
|
|
33015
|
+
async function handleRunnerHighlightSelector(ctx, options, deps) {
|
|
33016
|
+
const resolved = await resolveRunner(ctx, {
|
|
33017
|
+
autoLaunch: false,
|
|
33018
|
+
noRunnerIdMessage: interactiveRunnerMessages.noRunnerIdForHighlight,
|
|
33019
|
+
runner: options.runner
|
|
33020
|
+
}, deps);
|
|
33021
|
+
if (resolved.type === "failed") {
|
|
33022
|
+
return { ...failureFields(resolved), exitCode: resolved.exitCode };
|
|
33023
|
+
}
|
|
33024
|
+
const selector = options.selector ?? "";
|
|
33025
|
+
const result = await ctx.platformClient.callPublicApi(publicContractsV1.runner.highlightSelector, { id: resolved.runnerId, selector }, runnerCallOptions);
|
|
33026
|
+
if (!result.ok) {
|
|
33027
|
+
return { ...failureFields(result), exitCode: exitCodes.network };
|
|
33028
|
+
}
|
|
33029
|
+
const { outcome } = result.value;
|
|
33030
|
+
switch (outcome) {
|
|
33031
|
+
case "cleared":
|
|
33032
|
+
ctx.ui.output(result.value, interactiveRunnerMessages.highlightCleared);
|
|
33033
|
+
return;
|
|
33034
|
+
case "success": {
|
|
33035
|
+
const answer = result.value;
|
|
33036
|
+
if (answer.status === "invalid") {
|
|
33037
|
+
return {
|
|
33038
|
+
error: interactiveRunnerMessages.highlightSelectorInvalid(answer.selector),
|
|
33039
|
+
exitCode: exitCodes.invalidArgs
|
|
33040
|
+
};
|
|
33041
|
+
}
|
|
33042
|
+
ctx.ui.output(answer, answer.status === "empty" ? interactiveRunnerMessages.highlightMatchedNothing(answer.selector) : interactiveRunnerMessages.highlightMatched(answer.matchCount, answer.targetPage));
|
|
33043
|
+
return;
|
|
33044
|
+
}
|
|
33045
|
+
case "failure":
|
|
33046
|
+
return describeFailure(result.value.failureReason);
|
|
33047
|
+
default:
|
|
33048
|
+
return {
|
|
33049
|
+
error: interactiveRunnerMessages.highlightAnsweredUnknown(outcome),
|
|
33050
|
+
exitCode: exitCodes.network
|
|
33051
|
+
};
|
|
33052
|
+
}
|
|
33053
|
+
}
|
|
33054
|
+
function describeFailure(failureReason) {
|
|
33055
|
+
switch (failureReason) {
|
|
33056
|
+
case "no-answer":
|
|
33057
|
+
return {
|
|
33058
|
+
error: interactiveRunnerMessages.highlightNoAnswer,
|
|
33059
|
+
exitCode: exitCodes.network
|
|
33060
|
+
};
|
|
33061
|
+
case "runner-cannot-highlight-selectors":
|
|
33062
|
+
return {
|
|
33063
|
+
error: interactiveRunnerMessages.runnerCannotHighlightSelectors,
|
|
33064
|
+
exitCode: exitCodes.invalidArgs
|
|
33065
|
+
};
|
|
33066
|
+
case "runner-unreachable":
|
|
33067
|
+
return {
|
|
33068
|
+
error: interactiveRunnerMessages.runnerUnreachable,
|
|
33069
|
+
exitCode: exitCodes.network
|
|
33070
|
+
};
|
|
33071
|
+
default:
|
|
33072
|
+
return {
|
|
33073
|
+
error: interactiveRunnerMessages.highlightAnsweredUnknown(failureReason),
|
|
33074
|
+
exitCode: exitCodes.network
|
|
33075
|
+
};
|
|
33076
|
+
}
|
|
33077
|
+
}
|
|
33078
|
+
|
|
32785
33079
|
// src/commands/runner/highlightSelector.register.ts
|
|
32786
33080
|
var highlightExamples = `
|
|
32787
33081
|
Examples:
|
|
@@ -33171,162 +33465,10 @@ function registerRunnerInspectCommands(runner, signals) {
|
|
|
33171
33465
|
registerRunnerInspectMobileCommands(inspect, signals);
|
|
33172
33466
|
}
|
|
33173
33467
|
|
|
33174
|
-
// src/
|
|
33175
|
-
function
|
|
33176
|
-
const
|
|
33177
|
-
|
|
33178
|
-
case "runner-cannot-evaluate-snippets":
|
|
33179
|
-
return {
|
|
33180
|
-
error: interactiveRunnerMessages.runnerCannotEvaluateSnippets,
|
|
33181
|
-
exitCode: exitCodes.invalidArgs
|
|
33182
|
-
};
|
|
33183
|
-
case "runner-unreachable":
|
|
33184
|
-
return {
|
|
33185
|
-
error: interactiveRunnerMessages.snippetRunnerUnreachable,
|
|
33186
|
-
exitCode: exitCodes.network
|
|
33187
|
-
};
|
|
33188
|
-
default:
|
|
33189
|
-
return {
|
|
33190
|
-
error: interactiveRunnerMessages.snippetAnsweredUnknown(failureReason),
|
|
33191
|
-
exitCode: exitCodes.network
|
|
33192
|
-
};
|
|
33193
|
-
}
|
|
33194
|
-
}
|
|
33195
|
-
|
|
33196
|
-
// src/core/interactiveRunner/runFiles.ts
|
|
33197
|
-
import { isAbsolute as isAbsolute5, relative as relative10, resolve as resolve11, sep as sep5 } from "node:path";
|
|
33198
|
-
function toCollectedPath(cwd, path) {
|
|
33199
|
-
const absolute = isAbsolute5(path) ? path : resolve11(cwd, path);
|
|
33200
|
-
return relative10(cwd, absolute).split(sep5).join("/");
|
|
33201
|
-
}
|
|
33202
|
-
var namedLargestFileCount = 3;
|
|
33203
|
-
function largestFiles(files) {
|
|
33204
|
-
return Object.entries(files).map(([path, content]) => ({
|
|
33205
|
-
byteLength: runFilesByteLength({ [path]: content }),
|
|
33206
|
-
path
|
|
33207
|
-
})).sort((a, b) => b.byteLength - a.byteLength).slice(0, namedLargestFileCount);
|
|
33208
|
-
}
|
|
33209
|
-
function checkPresenceAndSize(files, requiredPath) {
|
|
33210
|
-
if (!Object.hasOwn(files, requiredPath)) {
|
|
33211
|
-
return { path: requiredPath, type: "missing-file" };
|
|
33212
|
-
}
|
|
33213
|
-
const byteLength = runFilesByteLength(files);
|
|
33214
|
-
if (byteLength > maxRunFilesByteLength) {
|
|
33215
|
-
return {
|
|
33216
|
-
byteLength,
|
|
33217
|
-
largest: largestFiles(files),
|
|
33218
|
-
maxByteLength: maxRunFilesByteLength,
|
|
33219
|
-
type: "too-large"
|
|
33220
|
-
};
|
|
33221
|
-
}
|
|
33222
|
-
const encodedByteLength = Buffer.byteLength(JSON.stringify({ files, path: requiredPath }), "utf8");
|
|
33223
|
-
if (encodedByteLength > maxRunnerRequestEncodedByteLength) {
|
|
33224
|
-
return {
|
|
33225
|
-
byteLength: encodedByteLength,
|
|
33226
|
-
maxByteLength: maxRunnerRequestEncodedByteLength,
|
|
33227
|
-
type: "request-too-large"
|
|
33228
|
-
};
|
|
33229
|
-
}
|
|
33230
|
-
return { type: "ok" };
|
|
33231
|
-
}
|
|
33232
|
-
function checkRunFiles(files, entryPointPath) {
|
|
33233
|
-
const present = checkPresenceAndSize(files, entryPointPath);
|
|
33234
|
-
if (present.type !== "ok")
|
|
33235
|
-
return present;
|
|
33236
|
-
if (!Object.hasOwn(files, runPackageJsonPath)) {
|
|
33237
|
-
return { type: "missing-package-json" };
|
|
33238
|
-
}
|
|
33239
|
-
return { type: "ok" };
|
|
33240
|
-
}
|
|
33241
|
-
function checkSnippetFiles(files, contextFilePath) {
|
|
33242
|
-
return checkPresenceAndSize(files, contextFilePath);
|
|
33243
|
-
}
|
|
33244
|
-
function describeRunFilesCheck(check) {
|
|
33245
|
-
switch (check.type) {
|
|
33246
|
-
case "missing-file":
|
|
33247
|
-
return interactiveRunnerMessages.fileNotCollected(check.path);
|
|
33248
|
-
case "missing-package-json":
|
|
33249
|
-
return interactiveRunnerMessages.missingPackageJson;
|
|
33250
|
-
case "too-large":
|
|
33251
|
-
return interactiveRunnerMessages.filesTooLarge(check.byteLength, check.maxByteLength, check.largest);
|
|
33252
|
-
case "request-too-large":
|
|
33253
|
-
return interactiveRunnerMessages.requestTooLarge(check.byteLength, check.maxByteLength);
|
|
33254
|
-
}
|
|
33255
|
-
}
|
|
33256
|
-
|
|
33257
|
-
// src/domains/interactiveRunner/snippetScope.ts
|
|
33258
|
-
async function resolveSnippetScope(contextFile, deps) {
|
|
33259
|
-
if (contextFile === undefined) {
|
|
33260
|
-
return { filePath: undefined, files: undefined, ok: true };
|
|
33261
|
-
}
|
|
33262
|
-
const filePath = toCollectedPath(deps.cwd, contextFile);
|
|
33263
|
-
const { files } = await deps.collectRunFiles([filePath]);
|
|
33264
|
-
const check = checkSnippetFiles(files, filePath);
|
|
33265
|
-
if (check.type !== "ok") {
|
|
33266
|
-
return { error: describeRunFilesCheck(check), ok: false };
|
|
33267
|
-
}
|
|
33268
|
-
return { filePath, files, ok: true };
|
|
33269
|
-
}
|
|
33270
|
-
|
|
33271
|
-
// src/domains/interactiveRunner/evaluateSnippet.ts
|
|
33272
|
-
var stdinArgument = "-";
|
|
33273
|
-
async function readCode(source, deps) {
|
|
33274
|
-
const code = source === stdinArgument ? await deps.readStdin() : await deps.readFile(source).catch(() => {
|
|
33275
|
-
return;
|
|
33276
|
-
});
|
|
33277
|
-
if (code === undefined) {
|
|
33278
|
-
return {
|
|
33279
|
-
error: interactiveRunnerMessages.snippetFileUnreadable(source),
|
|
33280
|
-
ok: false
|
|
33281
|
-
};
|
|
33282
|
-
}
|
|
33283
|
-
if (code.trim() === "") {
|
|
33284
|
-
return source === stdinArgument ? { error: interactiveRunnerMessages.stdinEmptySnippet, ok: false } : { error: interactiveRunnerMessages.snippetEmpty(source), ok: false };
|
|
33285
|
-
}
|
|
33286
|
-
return { code, ok: true };
|
|
33287
|
-
}
|
|
33288
|
-
async function handleRunnerExec(ctx, options, deps) {
|
|
33289
|
-
const code = await readCode(options.source, deps);
|
|
33290
|
-
if (!code.ok)
|
|
33291
|
-
return { error: code.error, exitCode: exitCodes.invalidArgs };
|
|
33292
|
-
const scope = await resolveSnippetScope(options.contextFile, deps);
|
|
33293
|
-
if (!scope.ok)
|
|
33294
|
-
return { error: scope.error, exitCode: exitCodes.invalidArgs };
|
|
33295
|
-
const resolved = await resolveRunner(ctx, { autoLaunch: true, runner: options.runner }, deps);
|
|
33296
|
-
if (resolved.type === "failed") {
|
|
33297
|
-
return { ...failureFields(resolved), exitCode: resolved.exitCode };
|
|
33298
|
-
}
|
|
33299
|
-
announceRunner(ctx, resolved);
|
|
33300
|
-
const result = await ctx.platformClient.callPublicApi(publicContractsV1.runner.evaluateSnippet, {
|
|
33301
|
-
code: code.code,
|
|
33302
|
-
id: resolved.runnerId,
|
|
33303
|
-
...scope.filePath === undefined ? {} : { filePath: scope.filePath },
|
|
33304
|
-
...scope.files === undefined ? {} : { files: scope.files }
|
|
33305
|
-
}, runnerCallOptions);
|
|
33306
|
-
if (!result.ok) {
|
|
33307
|
-
return {
|
|
33308
|
-
...failureFields(result),
|
|
33309
|
-
exitCode: result.exitCode ?? exitCodes.network
|
|
33310
|
-
};
|
|
33311
|
-
}
|
|
33312
|
-
if (result.value.outcome === "failure") {
|
|
33313
|
-
return describeEvaluateSnippetFailure(result.value);
|
|
33314
|
-
}
|
|
33315
|
-
switch (result.value.result) {
|
|
33316
|
-
case "success":
|
|
33317
|
-
ctx.ui.output({ outcome: "evaluated", result: "success" }, interactiveRunnerMessages.snippetRan);
|
|
33318
|
-
return;
|
|
33319
|
-
case "stopped":
|
|
33320
|
-
return {
|
|
33321
|
-
error: interactiveRunnerMessages.snippetStopped,
|
|
33322
|
-
exitCode: exitCodes.testFailure
|
|
33323
|
-
};
|
|
33324
|
-
case "error":
|
|
33325
|
-
return {
|
|
33326
|
-
error: interactiveRunnerMessages.snippetErrored(result.value.errorMessage),
|
|
33327
|
-
exitCode: exitCodes.testFailure
|
|
33328
|
-
};
|
|
33329
|
-
}
|
|
33468
|
+
// src/core/sentences.ts
|
|
33469
|
+
function appendSentence(text, sentence) {
|
|
33470
|
+
const punctuated = /[.!?]$/.test(text.trimEnd()) ? text.trimEnd() : `${text.trimEnd()}.`;
|
|
33471
|
+
return `${punctuated} ${sentence}`;
|
|
33330
33472
|
}
|
|
33331
33473
|
|
|
33332
33474
|
// src/domains/interactiveRunner/performActionFailure.ts
|
|
@@ -33373,6 +33515,56 @@ function describePerformActionFailure(options) {
|
|
|
33373
33515
|
}
|
|
33374
33516
|
}
|
|
33375
33517
|
|
|
33518
|
+
// src/domains/interactiveRunner/performActionScreenshot.ts
|
|
33519
|
+
async function writeActionScreenshot(ctx, options, deps) {
|
|
33520
|
+
const { type } = options.action;
|
|
33521
|
+
if (options.imageJpegBase64 === undefined) {
|
|
33522
|
+
return {
|
|
33523
|
+
error: interactiveRunnerMessages.actionPerformedWithoutScreenshot(type),
|
|
33524
|
+
exitCode: exitCodes.network
|
|
33525
|
+
};
|
|
33526
|
+
}
|
|
33527
|
+
const written = await deps.writeScreenshot({
|
|
33528
|
+
imageJpegBase64: options.imageJpegBase64,
|
|
33529
|
+
path: options.out
|
|
33530
|
+
});
|
|
33531
|
+
if (!written.ok) {
|
|
33532
|
+
return written.reason === "not-a-jpeg" ? {
|
|
33533
|
+
error: interactiveRunnerMessages.actionPerformedScreenshotNotAnImage(type),
|
|
33534
|
+
exitCode: exitCodes.network
|
|
33535
|
+
} : {
|
|
33536
|
+
error: options.out === stdoutPath ? interactiveRunnerMessages.actionPerformedScreenshotStdoutUnwritable(type, written.detail) : interactiveRunnerMessages.actionPerformedScreenshotUnwritable(type, options.out, written.detail),
|
|
33537
|
+
exitCode: exitCodes.network
|
|
33538
|
+
};
|
|
33539
|
+
}
|
|
33540
|
+
if (options.out === stdoutPath) {
|
|
33541
|
+
ctx.ui.success(interactiveRunnerMessages.actionPerformedScreenshotToStdout(type));
|
|
33542
|
+
return;
|
|
33543
|
+
}
|
|
33544
|
+
ctx.ui.output({ action: options.action, outcome: "success", screenshotPath: options.out }, interactiveRunnerMessages.actionPerformedScreenshotWritten(type, options.out));
|
|
33545
|
+
return;
|
|
33546
|
+
}
|
|
33547
|
+
async function addFailureScreenshot(options, deps) {
|
|
33548
|
+
const note = await describeFailureScreenshot(options, deps);
|
|
33549
|
+
return {
|
|
33550
|
+
...options.failure,
|
|
33551
|
+
error: appendSentence(options.failure.error, note)
|
|
33552
|
+
};
|
|
33553
|
+
}
|
|
33554
|
+
async function describeFailureScreenshot(options, deps) {
|
|
33555
|
+
if (options.imageJpegBase64 === undefined) {
|
|
33556
|
+
return interactiveRunnerMessages.actionFailedWithoutScreenshot;
|
|
33557
|
+
}
|
|
33558
|
+
const written = await deps.writeScreenshot({
|
|
33559
|
+
imageJpegBase64: options.imageJpegBase64,
|
|
33560
|
+
path: options.out
|
|
33561
|
+
});
|
|
33562
|
+
if (!written.ok) {
|
|
33563
|
+
return interactiveRunnerMessages.actionFailedScreenshotUnwritten(written.reason === "not-a-jpeg" ? "it did not arrive as a JPEG" : written.detail);
|
|
33564
|
+
}
|
|
33565
|
+
return options.out === stdoutPath ? interactiveRunnerMessages.actionFailedScreenshotToStdout : interactiveRunnerMessages.actionFailedScreenshotWritten(options.out);
|
|
33566
|
+
}
|
|
33567
|
+
|
|
33376
33568
|
// src/core/interactiveRunner/browserAction.ts
|
|
33377
33569
|
function parseJsonPath(path) {
|
|
33378
33570
|
try {
|
|
@@ -33437,30 +33629,56 @@ async function handleRunnerAct(ctx, options, deps) {
|
|
|
33437
33629
|
const built = await readAction(options.type, options.flags, deps);
|
|
33438
33630
|
if (!built.ok)
|
|
33439
33631
|
return { error: built.error, exitCode: exitCodes.invalidArgs };
|
|
33632
|
+
if (options.screenshot === stdoutPath && ctx.outputMode === "human") {
|
|
33633
|
+
return {
|
|
33634
|
+
error: interactiveRunnerMessages.stdoutIsATerminal("--screenshot"),
|
|
33635
|
+
exitCode: exitCodes.invalidArgs
|
|
33636
|
+
};
|
|
33637
|
+
}
|
|
33440
33638
|
const resolved = await resolveRunner(ctx, { autoLaunch: true, runner: options.runner }, deps);
|
|
33441
33639
|
if (resolved.type === "failed") {
|
|
33442
33640
|
return { ...failureFields(resolved), exitCode: resolved.exitCode };
|
|
33443
33641
|
}
|
|
33444
33642
|
announceRunner(ctx, resolved);
|
|
33445
|
-
const result = await ctx.platformClient.callPublicApi(publicContractsV1.runner.performAction, {
|
|
33643
|
+
const result = await ctx.platformClient.callPublicApi(publicContractsV1.runner.performAction, {
|
|
33644
|
+
action: built.action,
|
|
33645
|
+
id: resolved.runnerId,
|
|
33646
|
+
...options.screenshot === undefined ? {} : { withScreenshot: true }
|
|
33647
|
+
}, runnerCallOptions);
|
|
33446
33648
|
if (!result.ok) {
|
|
33447
33649
|
const fields = failureFields(result);
|
|
33448
33650
|
return {
|
|
33449
33651
|
...fields,
|
|
33450
33652
|
...result.mayHaveArrived ? {
|
|
33451
|
-
error:
|
|
33653
|
+
error: appendSentence(fields.error, interactiveRunnerMessages.actionMayHaveHappened)
|
|
33452
33654
|
} : {},
|
|
33453
33655
|
exitCode: exitCodes.network
|
|
33454
33656
|
};
|
|
33455
33657
|
}
|
|
33456
|
-
|
|
33658
|
+
const answer = result.value;
|
|
33659
|
+
if (answer.outcome === "success") {
|
|
33660
|
+
if (options.screenshot !== undefined) {
|
|
33661
|
+
return writeActionScreenshot(ctx, {
|
|
33662
|
+
action: built.action,
|
|
33663
|
+
imageJpegBase64: answer.imageJpegBase64,
|
|
33664
|
+
out: options.screenshot
|
|
33665
|
+
}, deps);
|
|
33666
|
+
}
|
|
33457
33667
|
ctx.ui.output({ action: built.action, outcome: "success" }, interactiveRunnerMessages.actionPerformed(built.action.type));
|
|
33458
33668
|
return;
|
|
33459
33669
|
}
|
|
33460
|
-
|
|
33670
|
+
const failure = describePerformActionFailure({
|
|
33461
33671
|
actionType: built.action.type,
|
|
33462
|
-
failure:
|
|
33672
|
+
failure: answer
|
|
33463
33673
|
});
|
|
33674
|
+
if (options.screenshot === undefined || answer.failureReason !== "action-failed") {
|
|
33675
|
+
return failure;
|
|
33676
|
+
}
|
|
33677
|
+
return addFailureScreenshot({
|
|
33678
|
+
failure,
|
|
33679
|
+
imageJpegBase64: answer.imageJpegBase64,
|
|
33680
|
+
out: options.screenshot
|
|
33681
|
+
}, deps);
|
|
33464
33682
|
}
|
|
33465
33683
|
|
|
33466
33684
|
// src/domains/interactiveRunner/takeScreenshot.ts
|
|
@@ -33473,6 +33691,12 @@ async function handleRunnerScreenshot(ctx, options, deps) {
|
|
|
33473
33691
|
if (resolved.type === "failed") {
|
|
33474
33692
|
return { ...failureFields(resolved), exitCode: resolved.exitCode };
|
|
33475
33693
|
}
|
|
33694
|
+
if (options.out === stdoutPath && ctx.outputMode === "human") {
|
|
33695
|
+
return {
|
|
33696
|
+
error: interactiveRunnerMessages.stdoutIsATerminal("--out"),
|
|
33697
|
+
exitCode: exitCodes.invalidArgs
|
|
33698
|
+
};
|
|
33699
|
+
}
|
|
33476
33700
|
const result = await ctx.platformClient.callPublicApi(publicContractsV1.runner.takeScreenshot, { id: resolved.runnerId }, runnerCallOptions);
|
|
33477
33701
|
if (!result.ok) {
|
|
33478
33702
|
return { ...failureFields(result), exitCode: exitCodes.network };
|
|
@@ -33482,16 +33706,13 @@ async function handleRunnerScreenshot(ctx, options, deps) {
|
|
|
33482
33706
|
imageJpegBase64: result.value.imageJpegBase64,
|
|
33483
33707
|
path: options.out
|
|
33484
33708
|
});
|
|
33485
|
-
if (!written.ok)
|
|
33486
|
-
return written.
|
|
33487
|
-
|
|
33488
|
-
|
|
33489
|
-
|
|
33490
|
-
|
|
33491
|
-
exitCode: exitCodes.invalidArgs
|
|
33492
|
-
};
|
|
33709
|
+
if (!written.ok)
|
|
33710
|
+
return describeUnwritten(written, options.out);
|
|
33711
|
+
if (options.out === stdoutPath) {
|
|
33712
|
+
ctx.ui.success(interactiveRunnerMessages.screenshotWrittenToStdout);
|
|
33713
|
+
} else {
|
|
33714
|
+
ctx.ui.output({ outcome: "success", path: options.out }, interactiveRunnerMessages.screenshotWritten(options.out));
|
|
33493
33715
|
}
|
|
33494
|
-
ctx.ui.output({ outcome: "success", path: options.out }, interactiveRunnerMessages.screenshotWritten(options.out));
|
|
33495
33716
|
return;
|
|
33496
33717
|
}
|
|
33497
33718
|
const { failureReason } = result.value;
|
|
@@ -33524,13 +33745,27 @@ async function handleRunnerScreenshot(ctx, options, deps) {
|
|
|
33524
33745
|
}
|
|
33525
33746
|
}
|
|
33526
33747
|
}
|
|
33748
|
+
function describeUnwritten(written, out) {
|
|
33749
|
+
if (written.reason === "not-a-jpeg") {
|
|
33750
|
+
return {
|
|
33751
|
+
error: interactiveRunnerMessages.screenshotNotAnImage,
|
|
33752
|
+
exitCode: exitCodes.network
|
|
33753
|
+
};
|
|
33754
|
+
}
|
|
33755
|
+
return {
|
|
33756
|
+
error: out === stdoutPath ? interactiveRunnerMessages.screenshotStdoutUnwritable(written.detail) : interactiveRunnerMessages.screenshotUnwritable(out, written.detail),
|
|
33757
|
+
exitCode: exitCodes.invalidArgs
|
|
33758
|
+
};
|
|
33759
|
+
}
|
|
33527
33760
|
|
|
33528
33761
|
// src/commands/runner/interact.register.ts
|
|
33529
33762
|
var defaultScreenshotPath = "screenshot.jpg";
|
|
33530
33763
|
var screenshotExamples = `
|
|
33531
33764
|
Examples:
|
|
33532
33765
|
$ qawolf runner screenshot
|
|
33533
|
-
$ qawolf runner screenshot --out screens/step-3.jpg
|
|
33766
|
+
$ qawolf runner screenshot --out screens/step-3.jpg
|
|
33767
|
+
$ qawolf runner screenshot --out - > step-3.jpg
|
|
33768
|
+
$ qawolf runner screenshot --out - | my-vision-tool`;
|
|
33534
33769
|
var actExamples = `
|
|
33535
33770
|
Examples:
|
|
33536
33771
|
$ qawolf runner act click --button left --x 480 --y 260
|
|
@@ -33538,15 +33773,12 @@ Examples:
|
|
|
33538
33773
|
$ qawolf runner act keypress --keys Control a
|
|
33539
33774
|
$ qawolf runner act navigate --url https://example.com
|
|
33540
33775
|
$ qawolf runner act drag --path '[{"x":10,"y":20},{"x":80,"y":90}]'
|
|
33541
|
-
$
|
|
33542
|
-
|
|
33543
|
-
|
|
33544
|
-
$ qawolf runner exec snippet.ts
|
|
33545
|
-
$ echo 'console.log(await page.title())' | qawolf runner exec -
|
|
33546
|
-
$ qawolf runner exec snippet.ts --file flows/checkout.flow.ts`;
|
|
33776
|
+
$ qawolf runner act click --button left --x 480 --y 260 --screenshot step-4.jpg
|
|
33777
|
+
$ echo '{"type":"click","button":"left","x":1,"y":2}' | qawolf runner act -
|
|
33778
|
+
$ echo '{"type":"click","button":"left","x":1,"y":2}' | qawolf runner act - --screenshot - > step-5.jpg`;
|
|
33547
33779
|
function registerRunnerInteractCommands(runner, signals) {
|
|
33548
|
-
declareCommandKind(runner.command("screenshot"), "read").description("Save a JPEG of an interactive runner's screen to a file").option("--out <path>", "File to write the image to", defaultScreenshotPath).option("--runner <id>", runnerFlagDescription).addHelpText("after", screenshotExamples).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerScreenshot(ctx, { out: opts.out, runner: opts.runner }, runnerDeps(ctx)))(opts, command));
|
|
33549
|
-
declareCommandKind(runner.command("act <action>"), "write").description("Perform one raw action on a runner's screen: click, double_click, scroll, move, drag, keypress, navigate or type. Use - to read a whole action as JSON from stdin. On a mobile runner only click (button left), drag and type have a touchscreen equivalent; the rest answer action-not-supported-on-mobile").option("--button <button>", "click: left, right, wheel, back or forward (mobile: left only)").option("--keys <keys...>", "keypress: modifiers and the key, e.g. Control a").option("--path <json>", "drag: JSON array of points to drag through (mobile: only the first and last are used)").option("--runner <id>", runnerFlagDescription).option("--scroll-x <delta>", "scroll: horizontal wheel delta").option("--scroll-y <delta>", "scroll: vertical wheel delta").option("--text <text>", "type: the text to type").option("--url <url>", "navigate: the http or https URL to go to").option("--x <pixels>", "pointer x, in screenshot pixels").option("--y <pixels>", "pointer y, in screenshot pixels").addHelpText("after", actExamples).action((action, opts, command) => withAuthContext(signals, (ctx) => handleRunnerAct(ctx, {
|
|
33780
|
+
declareCommandKind(runner.command("screenshot"), "read").description("Save a JPEG of an interactive runner's screen to a file, or write it to stdout with --out -").option("--out <path>", "File to write the image to. - writes the JPEG bytes to stdout on their own and moves the confirmation, JSON included, to stderr", defaultScreenshotPath).option("--runner <id>", runnerFlagDescription).addHelpText("after", screenshotExamples).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerScreenshot(ctx, { out: opts.out, runner: opts.runner }, runnerDeps(ctx)))(opts, command));
|
|
33781
|
+
declareCommandKind(runner.command("act <action>"), "write").description("Perform one raw action on a runner's screen: click, double_click, scroll, move, drag, keypress, navigate or type. Use - to read a whole action as JSON from stdin. On a mobile runner only click (button left), drag and type have a touchscreen equivalent; the rest answer action-not-supported-on-mobile").option("--button <button>", "click: left, right, wheel, back or forward (mobile: left only)").option("--keys <keys...>", "keypress: modifiers and the key, e.g. Control a").option("--path <json>", "drag: JSON array of points to drag through (mobile: only the first and last are used)").option("--runner <id>", runnerFlagDescription).option("--screenshot <path>", "Also save a JPEG of the screen, taken after the action, to this file, in place of a separate screenshot. An action that did not take effect answers with one too. - writes it to stdout and moves the confirmation, JSON included, to stderr").option("--scroll-x <delta>", "scroll: horizontal wheel delta").option("--scroll-y <delta>", "scroll: vertical wheel delta").option("--text <text>", "type: the text to type").option("--url <url>", "navigate: the http or https URL to go to").option("--x <pixels>", "pointer x, in screenshot pixels").option("--y <pixels>", "pointer y, in screenshot pixels").addHelpText("after", actExamples).action((action, opts, command) => withAuthContext(signals, (ctx) => handleRunnerAct(ctx, {
|
|
33550
33782
|
flags: {
|
|
33551
33783
|
button: opts.button,
|
|
33552
33784
|
keys: opts.keys,
|
|
@@ -33559,9 +33791,9 @@ function registerRunnerInteractCommands(runner, signals) {
|
|
|
33559
33791
|
y: opts.y
|
|
33560
33792
|
},
|
|
33561
33793
|
runner: opts.runner,
|
|
33794
|
+
screenshot: opts.screenshot,
|
|
33562
33795
|
type: action
|
|
33563
33796
|
}, runnerDeps(ctx)))(opts, command));
|
|
33564
|
-
declareCommandKind(runner.command("exec <file>"), "write").description("Evaluate a snippet against a runner's live page. Use - to read the snippet from stdin").option("--file <path>", "File whose scope the snippet is evaluated in; it and the directory's other files travel with it").option("--runner <id>", runnerFlagDescription).addHelpText("after", execExamples).action((file, opts, command) => withAuthContext(signals, (ctx) => handleRunnerExec(ctx, { contextFile: opts.file, runner: opts.runner, source: file }, runnerDeps(ctx)))(opts, command));
|
|
33565
33797
|
}
|
|
33566
33798
|
|
|
33567
33799
|
// src/domains/interactiveRunner/readJournal.ts
|
|
@@ -34516,6 +34748,7 @@ function registerRunnerCommand(program, signals) {
|
|
|
34516
34748
|
registerRunCommand(runner, signals);
|
|
34517
34749
|
registerRunnerEventsCommand(runner, signals);
|
|
34518
34750
|
registerRunnerInteractCommands(runner, signals);
|
|
34751
|
+
registerRunnerExecCommand(runner, signals);
|
|
34519
34752
|
registerRunnerInspectCommands(runner, signals);
|
|
34520
34753
|
registerRunnerImportPackageCommand(runner, signals);
|
|
34521
34754
|
registerRunnerHighlightSelectorCommand(runner, signals);
|
|
@@ -34541,21 +34774,12 @@ function createProgram({
|
|
|
34541
34774
|
|
|
34542
34775
|
// src/main.ts
|
|
34543
34776
|
var signals = createSignalRegistry();
|
|
34544
|
-
var
|
|
34545
|
-
var onSignal = (sig) => () => {
|
|
34546
|
-
const code = sig === "SIGINT" ? 130 : 143;
|
|
34547
|
-
if (forced)
|
|
34548
|
-
process.exit(code);
|
|
34549
|
-
forced = true;
|
|
34550
|
-
signals.shutdown(sig).finally(() => {
|
|
34551
|
-
process.exit(code);
|
|
34552
|
-
});
|
|
34553
|
-
};
|
|
34777
|
+
var onSignal = createSignalExit({ shutdown: (sig) => signals.shutdown(sig) });
|
|
34554
34778
|
process.on("SIGINT", onSignal("SIGINT"));
|
|
34555
34779
|
process.on("SIGTERM", onSignal("SIGTERM"));
|
|
34556
34780
|
createProgram({ signals }).parseAsync().catch(() => {
|
|
34557
34781
|
if (process.exitCode === undefined)
|
|
34558
34782
|
process.exitCode = 1;
|
|
34559
|
-
}).finally(() =>
|
|
34783
|
+
}).finally(() => exitWhenIdle(typeof process.exitCode === "number" ? process.exitCode : 0));
|
|
34560
34784
|
|
|
34561
|
-
//# debugId=
|
|
34785
|
+
//# debugId=90417CF807BBB8C264756E2164756E21
|