@qawolf/cli 1.18.0 → 1.19.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 +227 -47
- package/package.json +1 -1
- package/skills/qawolf-cli/SKILL.md +5 -1
- package/skills/qawolf-cli/references/runner.md +63 -0
package/dist/cli.js
CHANGED
|
@@ -326107,6 +326107,8 @@ var interactMessages = {
|
|
|
326107
326107
|
inspectNeedsABrowserRunner: "This runner is a mobile device, and element and page HTML are a browser's shapes. Retrying will never help. Launch a playwright runner to inspect one.",
|
|
326108
326108
|
nothingToInspect: (errorMessage2) => `The runner had nothing to inspect${errorMessage2 === undefined ? "" : `: ${errorMessage2}`}. There is no live page, nothing matched the selector, or no variable has that name; a runner cannot tell those apart. Run a flow on it first, or check the selector or name.`,
|
|
326109
326109
|
inspectAnsweredUnknown: (failureReason) => `The runner answered "${failureReason}", which this version of the CLI does not know how to report. Upgrade with npm install -g @qawolf/cli.`,
|
|
326110
|
+
inspectMobileAnsweredUnknown: (failureReason) => `The runner answered "${failureReason}", which this version of the CLI does not know how to report. Upgrade with npm install -g @qawolf/cli.`,
|
|
326111
|
+
runnerIsNotMobile: "This runner is not a mobile device, so there is nothing here to inspect. Retrying will never help: launch an android or ios runner instead.",
|
|
326110
326112
|
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.",
|
|
326111
326113
|
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.",
|
|
326112
326114
|
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.",
|
|
@@ -342551,7 +342553,6 @@ var makeInspectMobileOnRunnerContract = () => {
|
|
|
342551
342553
|
output
|
|
342552
342554
|
};
|
|
342553
342555
|
};
|
|
342554
|
-
|
|
342555
342556
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/journal.js
|
|
342556
342557
|
var journalEntrySchema = exports_external.object({
|
|
342557
342558
|
payload: exports_external.unknown(),
|
|
@@ -346126,7 +346127,7 @@ function startUpdateCheck(deps) {
|
|
|
346126
346127
|
// package.json
|
|
346127
346128
|
var package_default = {
|
|
346128
346129
|
name: "@qawolf/cli",
|
|
346129
|
-
version: "1.
|
|
346130
|
+
version: "1.19.0",
|
|
346130
346131
|
description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
|
|
346131
346132
|
keywords: [
|
|
346132
346133
|
"automation",
|
|
@@ -357446,14 +357447,190 @@ async function handleRunnerInspect(ctx, options, deps) {
|
|
|
357446
357447
|
return;
|
|
357447
357448
|
}
|
|
357448
357449
|
|
|
357450
|
+
// src/core/interactiveRunner/inspectMobileRequest.ts
|
|
357451
|
+
function toNumber(value) {
|
|
357452
|
+
return value.trim() === "" ? Number.NaN : Number(value);
|
|
357453
|
+
}
|
|
357454
|
+
function buildInspectMobileRequest(what, flags) {
|
|
357455
|
+
if (flags.by === "point" && (flags.text !== undefined || flags.partial !== undefined)) {
|
|
357456
|
+
return {
|
|
357457
|
+
error: "--by point matches by pixel, so --text/--partial would be ignored rather than searching by text. Pass --by text instead, or drop --text/--partial.",
|
|
357458
|
+
ok: false
|
|
357459
|
+
};
|
|
357460
|
+
}
|
|
357461
|
+
if (flags.by === "text" && (flags.x !== undefined || flags.y !== undefined)) {
|
|
357462
|
+
return {
|
|
357463
|
+
error: "--by text matches by text, so --x/--y would be ignored rather than matching a point. Pass --by point instead, or drop --x/--y.",
|
|
357464
|
+
ok: false
|
|
357465
|
+
};
|
|
357466
|
+
}
|
|
357467
|
+
const candidate = {
|
|
357468
|
+
what,
|
|
357469
|
+
...flags.by === undefined ? {} : { by: flags.by },
|
|
357470
|
+
...flags.context === undefined ? {} : { context: flags.context },
|
|
357471
|
+
...flags.partial === undefined ? {} : { partial: flags.partial },
|
|
357472
|
+
...flags.text === undefined ? {} : { text: flags.text },
|
|
357473
|
+
...flags.x === undefined ? {} : { x: toNumber(flags.x) },
|
|
357474
|
+
...flags.y === undefined ? {} : { y: toNumber(flags.y) }
|
|
357475
|
+
};
|
|
357476
|
+
const parsed = inspectMobileRequestSchema.safeParse(candidate);
|
|
357477
|
+
if (!parsed.success) {
|
|
357478
|
+
return { error: exports_external.prettifyError(parsed.error), ok: false };
|
|
357479
|
+
}
|
|
357480
|
+
return { ok: true, request: parsed.data };
|
|
357481
|
+
}
|
|
357482
|
+
|
|
357483
|
+
// src/domains/interactiveRunner/inspectMobile.ts
|
|
357484
|
+
function describeSession(session) {
|
|
357485
|
+
switch (session.type) {
|
|
357486
|
+
case "ready":
|
|
357487
|
+
return session.deviceName === undefined ? `Session ready: ${session.platformName} (${session.sessionId}).` : `Session ready: ${session.platformName} on ${session.deviceName} (${session.sessionId}).`;
|
|
357488
|
+
case "unreachable":
|
|
357489
|
+
return `Session unreachable: ${session.error}`;
|
|
357490
|
+
case "ambiguous":
|
|
357491
|
+
return `${String(session.sessionCount)} Appium sessions are live; expected one.`;
|
|
357492
|
+
case "no-session":
|
|
357493
|
+
return "No Appium session is live.";
|
|
357494
|
+
}
|
|
357495
|
+
}
|
|
357496
|
+
function streamLine(value) {
|
|
357497
|
+
switch (value.what) {
|
|
357498
|
+
case "contexts":
|
|
357499
|
+
return JSON.stringify({
|
|
357500
|
+
contexts: value.contexts,
|
|
357501
|
+
current: value.current
|
|
357502
|
+
});
|
|
357503
|
+
case "page":
|
|
357504
|
+
return JSON.stringify({
|
|
357505
|
+
context: value.context,
|
|
357506
|
+
orientation: value.orientation,
|
|
357507
|
+
pageSource: value.pageSource
|
|
357508
|
+
});
|
|
357509
|
+
case "elements":
|
|
357510
|
+
return JSON.stringify({ matches: value.matches });
|
|
357511
|
+
}
|
|
357512
|
+
}
|
|
357513
|
+
async function handleRunnerInspectMobile(ctx, options, deps) {
|
|
357514
|
+
const built = buildInspectMobileRequest(options.what, options.flags);
|
|
357515
|
+
if (!built.ok)
|
|
357516
|
+
return { error: built.error, exitCode: exitCodes.invalidArgs };
|
|
357517
|
+
const resolved = await resolveRunner(ctx, {
|
|
357518
|
+
autoLaunch: false,
|
|
357519
|
+
noRunnerIdMessage: interactiveRunnerMessages.noRunnerIdForInspect,
|
|
357520
|
+
runner: options.runner
|
|
357521
|
+
}, deps);
|
|
357522
|
+
if (resolved.type === "failed") {
|
|
357523
|
+
return { ...failureFields(resolved), exitCode: resolved.exitCode };
|
|
357524
|
+
}
|
|
357525
|
+
const result = await ctx.platformClient.callPublicApi(publicContractsV1.runner.inspectMobile, { id: resolved.runnerId, request: built.request }, runnerCallOptions);
|
|
357526
|
+
if (!result.ok) {
|
|
357527
|
+
return { ...failureFields(result), exitCode: exitCodes.network };
|
|
357528
|
+
}
|
|
357529
|
+
if (result.value.outcome === "success") {
|
|
357530
|
+
const value = result.value;
|
|
357531
|
+
if (value.what === "session") {
|
|
357532
|
+
ctx.ui.output(value, describeSession(value.session));
|
|
357533
|
+
} else {
|
|
357534
|
+
ctx.ui.stream(value, streamLine(value));
|
|
357535
|
+
}
|
|
357536
|
+
return;
|
|
357537
|
+
}
|
|
357538
|
+
const { failureReason } = result.value;
|
|
357539
|
+
switch (failureReason) {
|
|
357540
|
+
case "runner-is-not-mobile":
|
|
357541
|
+
return {
|
|
357542
|
+
error: interactiveRunnerMessages.runnerIsNotMobile,
|
|
357543
|
+
exitCode: exitCodes.invalidArgs
|
|
357544
|
+
};
|
|
357545
|
+
case "screen-needs-a-run":
|
|
357546
|
+
return {
|
|
357547
|
+
error: interactiveRunnerMessages.screenNeedsARun,
|
|
357548
|
+
exitCode: exitCodes.invalidArgs
|
|
357549
|
+
};
|
|
357550
|
+
case "screen-not-ready":
|
|
357551
|
+
return {
|
|
357552
|
+
error: interactiveRunnerMessages.screenNotReady,
|
|
357553
|
+
exitCode: exitCodes.network
|
|
357554
|
+
};
|
|
357555
|
+
case "runner-unreachable":
|
|
357556
|
+
return {
|
|
357557
|
+
error: interactiveRunnerMessages.runnerUnreachable,
|
|
357558
|
+
exitCode: exitCodes.network
|
|
357559
|
+
};
|
|
357560
|
+
default: {
|
|
357561
|
+
return {
|
|
357562
|
+
error: interactiveRunnerMessages.inspectMobileAnsweredUnknown(failureReason),
|
|
357563
|
+
exitCode: exitCodes.network
|
|
357564
|
+
};
|
|
357565
|
+
}
|
|
357566
|
+
}
|
|
357567
|
+
}
|
|
357568
|
+
|
|
357569
|
+
// src/commands/runner/inspectMobile.register.ts
|
|
357570
|
+
function registerRunnerInspectMobileCommands(inspect, signals) {
|
|
357571
|
+
declareCommandKind(inspect.command("session"), "read").description("Print the Appium session's status: ready, or why not").option("--runner <id>", runnerFlagDescription).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerInspectMobile(ctx, {
|
|
357572
|
+
flags: {
|
|
357573
|
+
by: undefined,
|
|
357574
|
+
context: undefined,
|
|
357575
|
+
partial: undefined,
|
|
357576
|
+
text: undefined,
|
|
357577
|
+
x: undefined,
|
|
357578
|
+
y: undefined
|
|
357579
|
+
},
|
|
357580
|
+
runner: opts.runner,
|
|
357581
|
+
what: "session"
|
|
357582
|
+
}, runnerDeps(ctx)))(opts, command));
|
|
357583
|
+
declareCommandKind(inspect.command("contexts"), "read").description("List the WebView contexts available, and which is current").option("--runner <id>", runnerFlagDescription).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerInspectMobile(ctx, {
|
|
357584
|
+
flags: {
|
|
357585
|
+
by: undefined,
|
|
357586
|
+
context: undefined,
|
|
357587
|
+
partial: undefined,
|
|
357588
|
+
text: undefined,
|
|
357589
|
+
x: undefined,
|
|
357590
|
+
y: undefined
|
|
357591
|
+
},
|
|
357592
|
+
runner: opts.runner,
|
|
357593
|
+
what: "contexts"
|
|
357594
|
+
}, runnerDeps(ctx)))(opts, command));
|
|
357595
|
+
declareCommandKind(inspect.command("page-source"), "read").description("Print the current context's page source, as a tree").option("--context <name>", "Read this context instead of the current one").option("--runner <id>", runnerFlagDescription).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerInspectMobile(ctx, {
|
|
357596
|
+
flags: {
|
|
357597
|
+
by: undefined,
|
|
357598
|
+
context: opts.context,
|
|
357599
|
+
partial: undefined,
|
|
357600
|
+
text: undefined,
|
|
357601
|
+
x: undefined,
|
|
357602
|
+
y: undefined
|
|
357603
|
+
},
|
|
357604
|
+
runner: opts.runner,
|
|
357605
|
+
what: "page"
|
|
357606
|
+
}, runnerDeps(ctx)))(opts, command));
|
|
357607
|
+
declareCommandKind(inspect.command("elements"), "read").description("Find elements at a screen point, or elements carrying some text").requiredOption("--by <by>", "point or text").option("--context <name>", "Read this context instead of the current one").option("--partial", "text: match text containing this, rather than exactly this").option("--runner <id>", runnerFlagDescription).option("--text <text>", "text: the text to match").option("--x <pixels>", "point: whole pixels on the device's own screen").option("--y <pixels>", "point: whole pixels on the device's own screen").action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerInspectMobile(ctx, {
|
|
357608
|
+
flags: {
|
|
357609
|
+
by: opts.by,
|
|
357610
|
+
context: opts.context,
|
|
357611
|
+
partial: opts.partial,
|
|
357612
|
+
text: opts.text,
|
|
357613
|
+
x: opts.x,
|
|
357614
|
+
y: opts.y
|
|
357615
|
+
},
|
|
357616
|
+
runner: opts.runner,
|
|
357617
|
+
what: "elements"
|
|
357618
|
+
}, runnerDeps(ctx)))(opts, command));
|
|
357619
|
+
}
|
|
357620
|
+
|
|
357449
357621
|
// src/commands/runner/inspect.register.ts
|
|
357450
357622
|
var inspectExamples = `
|
|
357451
357623
|
Examples:
|
|
357452
357624
|
$ qawolf runner inspect element-html --selector "#email"
|
|
357453
357625
|
$ qawolf runner inspect page-html > page.html
|
|
357454
|
-
$ qawolf runner inspect variable --name cart | jq .total
|
|
357626
|
+
$ qawolf runner inspect variable --name cart | jq .total
|
|
357627
|
+
$ qawolf runner inspect session
|
|
357628
|
+
$ qawolf runner inspect contexts
|
|
357629
|
+
$ qawolf runner inspect page-source --context WEBVIEW_1
|
|
357630
|
+
$ qawolf runner inspect elements --by point --x 200 --y 400
|
|
357631
|
+
$ qawolf runner inspect elements --by text --text "Sign in" --partial`;
|
|
357455
357632
|
function registerRunnerInspectCommands(runner, signals) {
|
|
357456
|
-
const inspect = runner.command("inspect").description("Read one thing off a runner's live page").addHelpText("after", inspectExamples);
|
|
357633
|
+
const inspect = runner.command("inspect").description("Read one thing off a runner's live page (browser) or Appium session (mobile)").addHelpText("after", inspectExamples);
|
|
357457
357634
|
declareCommandKind(inspect.command("element-html"), "read").description("Print the HTML of the first element a selector matches").requiredOption("--selector <selector>", "Playwright selector to inspect").option("--runner <id>", runnerFlagDescription).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerInspect(ctx, {
|
|
357458
357635
|
flags: { name: undefined, selector: opts.selector },
|
|
357459
357636
|
runner: opts.runner,
|
|
@@ -357469,6 +357646,7 @@ function registerRunnerInspectCommands(runner, signals) {
|
|
|
357469
357646
|
runner: opts.runner,
|
|
357470
357647
|
what: "variable"
|
|
357471
357648
|
}, runnerDeps(ctx)))(opts, command));
|
|
357649
|
+
registerRunnerInspectMobileCommands(inspect, signals);
|
|
357472
357650
|
}
|
|
357473
357651
|
|
|
357474
357652
|
// src/core/interactiveRunner/runFiles.ts
|
|
@@ -357606,46 +357784,6 @@ async function handleRunnerExec(ctx, options, deps) {
|
|
|
357606
357784
|
}
|
|
357607
357785
|
}
|
|
357608
357786
|
|
|
357609
|
-
// src/core/interactiveRunner/browserAction.ts
|
|
357610
|
-
function parseJsonPath(path9) {
|
|
357611
|
-
try {
|
|
357612
|
-
return { ok: true, value: JSON.parse(path9) };
|
|
357613
|
-
} catch {
|
|
357614
|
-
return {
|
|
357615
|
-
error: `--path must be a JSON array of points, for example '[{"x":10,"y":20},{"x":80,"y":90}]'.`,
|
|
357616
|
-
ok: false
|
|
357617
|
-
};
|
|
357618
|
-
}
|
|
357619
|
-
}
|
|
357620
|
-
function toNumber(value) {
|
|
357621
|
-
return value.trim() === "" ? Number.NaN : Number(value);
|
|
357622
|
-
}
|
|
357623
|
-
function buildBrowserAction(type, flags) {
|
|
357624
|
-
const parsedPath = flags.path === undefined ? undefined : parseJsonPath(flags.path);
|
|
357625
|
-
if (parsedPath !== undefined && !parsedPath.ok)
|
|
357626
|
-
return parsedPath;
|
|
357627
|
-
const candidate = {
|
|
357628
|
-
type,
|
|
357629
|
-
...flags.button === undefined ? {} : { button: flags.button },
|
|
357630
|
-
...flags.keys === undefined ? {} : { keys: flags.keys },
|
|
357631
|
-
...parsedPath === undefined ? {} : { path: parsedPath.value },
|
|
357632
|
-
...flags.scrollX === undefined ? {} : { scroll_x: toNumber(flags.scrollX) },
|
|
357633
|
-
...flags.scrollY === undefined ? {} : { scroll_y: toNumber(flags.scrollY) },
|
|
357634
|
-
...flags.text === undefined ? {} : { text: flags.text },
|
|
357635
|
-
...flags.url === undefined ? {} : { url: flags.url },
|
|
357636
|
-
...flags.x === undefined ? {} : { x: toNumber(flags.x) },
|
|
357637
|
-
...flags.y === undefined ? {} : { y: toNumber(flags.y) }
|
|
357638
|
-
};
|
|
357639
|
-
return parseBrowserAction(candidate);
|
|
357640
|
-
}
|
|
357641
|
-
function parseBrowserAction(candidate) {
|
|
357642
|
-
const parsed = browserActionSchema.safeParse(candidate);
|
|
357643
|
-
if (!parsed.success) {
|
|
357644
|
-
return { error: exports_external.prettifyError(parsed.error), ok: false };
|
|
357645
|
-
}
|
|
357646
|
-
return { action: parsed.data, ok: true };
|
|
357647
|
-
}
|
|
357648
|
-
|
|
357649
357787
|
// src/domains/interactiveRunner/performActionFailure.ts
|
|
357650
357788
|
function describePerformActionFailure(options) {
|
|
357651
357789
|
const { actionType, failure } = options;
|
|
@@ -357690,7 +357828,47 @@ function describePerformActionFailure(options) {
|
|
|
357690
357828
|
}
|
|
357691
357829
|
}
|
|
357692
357830
|
|
|
357693
|
-
// src/
|
|
357831
|
+
// src/core/interactiveRunner/browserAction.ts
|
|
357832
|
+
function parseJsonPath(path9) {
|
|
357833
|
+
try {
|
|
357834
|
+
return { ok: true, value: JSON.parse(path9) };
|
|
357835
|
+
} catch {
|
|
357836
|
+
return {
|
|
357837
|
+
error: `--path must be a JSON array of points, for example '[{"x":10,"y":20},{"x":80,"y":90}]'.`,
|
|
357838
|
+
ok: false
|
|
357839
|
+
};
|
|
357840
|
+
}
|
|
357841
|
+
}
|
|
357842
|
+
function toNumber2(value) {
|
|
357843
|
+
return value.trim() === "" ? Number.NaN : Number(value);
|
|
357844
|
+
}
|
|
357845
|
+
function buildBrowserAction(type, flags) {
|
|
357846
|
+
const parsedPath = flags.path === undefined ? undefined : parseJsonPath(flags.path);
|
|
357847
|
+
if (parsedPath !== undefined && !parsedPath.ok)
|
|
357848
|
+
return parsedPath;
|
|
357849
|
+
const candidate = {
|
|
357850
|
+
type,
|
|
357851
|
+
...flags.button === undefined ? {} : { button: flags.button },
|
|
357852
|
+
...flags.keys === undefined ? {} : { keys: flags.keys },
|
|
357853
|
+
...parsedPath === undefined ? {} : { path: parsedPath.value },
|
|
357854
|
+
...flags.scrollX === undefined ? {} : { scroll_x: toNumber2(flags.scrollX) },
|
|
357855
|
+
...flags.scrollY === undefined ? {} : { scroll_y: toNumber2(flags.scrollY) },
|
|
357856
|
+
...flags.text === undefined ? {} : { text: flags.text },
|
|
357857
|
+
...flags.url === undefined ? {} : { url: flags.url },
|
|
357858
|
+
...flags.x === undefined ? {} : { x: toNumber2(flags.x) },
|
|
357859
|
+
...flags.y === undefined ? {} : { y: toNumber2(flags.y) }
|
|
357860
|
+
};
|
|
357861
|
+
return parseBrowserAction(candidate);
|
|
357862
|
+
}
|
|
357863
|
+
function parseBrowserAction(candidate) {
|
|
357864
|
+
const parsed = browserActionSchema.safeParse(candidate);
|
|
357865
|
+
if (!parsed.success) {
|
|
357866
|
+
return { error: exports_external.prettifyError(parsed.error), ok: false };
|
|
357867
|
+
}
|
|
357868
|
+
return { action: parsed.data, ok: true };
|
|
357869
|
+
}
|
|
357870
|
+
|
|
357871
|
+
// src/domains/interactiveRunner/readAction.ts
|
|
357694
357872
|
var stdinArgument2 = "-";
|
|
357695
357873
|
async function readAction(type, flags, deps) {
|
|
357696
357874
|
if (type !== stdinArgument2)
|
|
@@ -357708,6 +357886,8 @@ async function readAction(type, flags, deps) {
|
|
|
357708
357886
|
return { error: interactiveRunnerMessages.actionNotJson, ok: false };
|
|
357709
357887
|
}
|
|
357710
357888
|
}
|
|
357889
|
+
|
|
357890
|
+
// src/domains/interactiveRunner/performAction.ts
|
|
357711
357891
|
async function handleRunnerAct(ctx, options, deps) {
|
|
357712
357892
|
const built = await readAction(options.type, options.flags, deps);
|
|
357713
357893
|
if (!built.ok)
|
|
@@ -357821,7 +358001,7 @@ Examples:
|
|
|
357821
358001
|
$ qawolf runner exec snippet.ts --file flows/checkout.flow.ts`;
|
|
357822
358002
|
function registerRunnerInteractCommands(runner, signals) {
|
|
357823
358003
|
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));
|
|
357824
|
-
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").option("--button <button>", "click: left, right, wheel, back or forward").option("--keys <keys...>", "keypress: modifiers and the key, e.g. Control a").option("--path <json>", "drag: JSON array of points to drag through").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((action2, opts, command) => withAuthContext(signals, (ctx) => handleRunnerAct(ctx, {
|
|
358004
|
+
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((action2, opts, command) => withAuthContext(signals, (ctx) => handleRunnerAct(ctx, {
|
|
357825
358005
|
flags: {
|
|
357826
358006
|
button: opts.button,
|
|
357827
358007
|
keys: opts.keys,
|
|
@@ -358828,4 +359008,4 @@ createProgram({ signals }).parseAsync().catch(() => {
|
|
|
358828
359008
|
process.exitCode = 1;
|
|
358829
359009
|
}).finally(() => flushAndExit(typeof process.exitCode === "number" ? process.exitCode : 0));
|
|
358830
359010
|
|
|
358831
|
-
//# debugId=
|
|
359011
|
+
//# debugId=C46D821567854FC564756E2164756E21
|
package/package.json
CHANGED
|
@@ -155,13 +155,17 @@ that `url`; never guess a route and never send a repository link in its place.
|
|
|
155
155
|
| `qawolf run find` | read | List an environment's recent runs, newest first. |
|
|
156
156
|
| `qawolf run get` | read | Get a run's status, per-flow results, and links. |
|
|
157
157
|
| `qawolf run reattempt` | write | Request new attempts for a run's flows, in the same run. A flow is eligible once its result is failed or canceled and QA Wolf's automatic retries have finished. A fully investigated run no longer accepts reattempts. Attempts run with the latest flow code. Poll run.get for results. |
|
|
158
|
-
| `qawolf runner act` | write | Perform one raw action on a runner's screen: click, double_click, scroll, move, drag, keypress, navigate or type. Use - to read a whole action as JSON from stdin |
|
|
158
|
+
| `qawolf runner act` | write | Perform one raw action on a runner's screen: click, double_click, scroll, move, drag, keypress, navigate or type. Use - to read a whole action as JSON from stdin. On a mobile runner only click (button left), drag and type have a touchscreen equivalent; the rest answer action-not-supported-on-mobile |
|
|
159
159
|
| `qawolf runner events` | read | Print a runner's journal, one entry per line. QA Wolf writes console, recorder, run-events, run-logs, run-status |
|
|
160
160
|
| `qawolf runner exec` | write | Evaluate a snippet against a runner's live page. Use - to read the snippet from stdin |
|
|
161
161
|
| `qawolf runner highlight-selector` | write | Highlight what a selector matches on a runner's live page, so the next screenshot shows it. Omit the selector to clear the highlight |
|
|
162
162
|
| `qawolf runner import-package` | write | Install a package into a runner's live run, so a snippet or a selection can import it |
|
|
163
|
+
| `qawolf runner inspect contexts` | read | List the WebView contexts available, and which is current |
|
|
163
164
|
| `qawolf runner inspect element-html` | read | Print the HTML of the first element a selector matches |
|
|
165
|
+
| `qawolf runner inspect elements` | read | Find elements at a screen point, or elements carrying some text |
|
|
164
166
|
| `qawolf runner inspect page-html` | read | Print the page's HTML, simplified for a model to read |
|
|
167
|
+
| `qawolf runner inspect page-source` | read | Print the current context's page source, as a tree |
|
|
168
|
+
| `qawolf runner inspect session` | read | Print the Appium session's status: ready, or why not |
|
|
165
169
|
| `qawolf runner inspect variable` | read | Print a top-level variable's value from the running workflow |
|
|
166
170
|
| `qawolf runner keepalive` | read | Reset a runner's inactivity clock, for a caller that pauses between actions |
|
|
167
171
|
| `qawolf runner launch` | write | Launch an interactive runner and make it this directory's default |
|
|
@@ -41,6 +41,15 @@ No `read` command ever launches a runner. `screenshot`, `events` and `keepalive`
|
|
|
41
41
|
tell you there is no runner rather than quietly billing one, and so does
|
|
42
42
|
`terminate`, since starting a pod in order to end it would be absurd.
|
|
43
43
|
|
|
44
|
+
`--name` also chooses between a browser and a mobile device:
|
|
45
|
+
`qawolf runner launch --name android` or `--name ios` starts an Appium session
|
|
46
|
+
instead of a browser. A command built for the other kind answers a
|
|
47
|
+
`failureReason` naming the mismatch rather than doing something approximate —
|
|
48
|
+
`runner-is-not-mobile` from `inspect session`/`contexts`/`page-source`/`elements`
|
|
49
|
+
on a browser runner, `runner-is-not-a-browser` from `inspect element-html`/
|
|
50
|
+
`page-html` on a mobile one — so launch the right family up front rather than
|
|
51
|
+
discovering it from a refusal mid-session.
|
|
52
|
+
|
|
44
53
|
## Knowing what you are holding
|
|
45
54
|
|
|
46
55
|
`qawolf runner list` names the runners this directory has launched that are
|
|
@@ -138,6 +147,14 @@ taken effect. On a `4` from `act`, take a screenshot before repeating a click.
|
|
|
138
147
|
looks the same from outside, so treat a `4` from a snippet that changes something
|
|
139
148
|
as "may have run" rather than "did not run".
|
|
140
149
|
|
|
150
|
+
A mobile runner has a touchscreen, not a mouse, so only three of the eight
|
|
151
|
+
actions have a touchscreen equivalent and go through: `click` with
|
|
152
|
+
`button: "left"` taps, `drag` swipes, and `type` types into whatever the last
|
|
153
|
+
tap focused. The rest — `double_click`, `scroll`, `move`, `keypress`,
|
|
154
|
+
`navigate` — answer `action-not-supported-on-mobile` rather than doing
|
|
155
|
+
something approximate. `navigate` is the one to watch for, since it works on a
|
|
156
|
+
browser runner without a run first but has no meaning on mobile at all.
|
|
157
|
+
|
|
141
158
|
## The recorder: what you cannot get from pixels
|
|
142
159
|
|
|
143
160
|
`qawolf runner events recorder` is the capability that has no equivalent in a
|
|
@@ -185,6 +202,52 @@ Use `inspect` before reaching for `exec`. Reading a value through a snippet
|
|
|
185
202
|
means printing it and then fishing it back out of the `console` stream, which is
|
|
186
203
|
two calls and a marker; `inspect variable` is one call and the value.
|
|
187
204
|
|
|
205
|
+
## Reading a mobile screen: `inspect session`/`contexts`/`page-source`/`elements`
|
|
206
|
+
|
|
207
|
+
`element-html`, `page-html` and `variable` are a browser's shapes. A mobile
|
|
208
|
+
runner answers four different ones instead, one subcommand per question:
|
|
209
|
+
|
|
210
|
+
```sh
|
|
211
|
+
qawolf runner inspect session
|
|
212
|
+
qawolf runner inspect contexts
|
|
213
|
+
qawolf runner inspect page-source
|
|
214
|
+
qawolf runner inspect page-source --context WEBVIEW_1 # a specific context, not the current one
|
|
215
|
+
qawolf runner inspect elements --by point --x 240 --y 480
|
|
216
|
+
qawolf runner inspect elements --by text --text "Sign in" --partial
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`session` prints one summary line — ready, or why not — because that line is
|
|
220
|
+
the whole answer. `contexts`, `page-source` and `elements` instead print their
|
|
221
|
+
answer as JSON on stdout, on its own like `inspect element-html`, so you can
|
|
222
|
+
redirect or pipe it (`| jq .current`, `| jq .matches`, `| jq .pageSource`)
|
|
223
|
+
rather than getting a count with no way to see what was actually found.
|
|
224
|
+
|
|
225
|
+
`session` is also the one subcommand that never answers `screen-needs-a-run`:
|
|
226
|
+
readiness is the question it exists to answer, so it reports one of `ready`
|
|
227
|
+
(with the platform, device and session id), `unreachable`, `ambiguous` (more
|
|
228
|
+
than one session is somehow live) or `no-session`, rather than refusing until a
|
|
229
|
+
run has happened. The other three need a live session first and share the same
|
|
230
|
+
readiness contract `act` and `screenshot` use on a browser runner:
|
|
231
|
+
`screen-needs-a-run` exits `2` and means no Appium session has started on this
|
|
232
|
+
runner yet — run a flow that opens one, then inspect again; `screen-not-ready`
|
|
233
|
+
exits `4` and means the session exists but did not answer this instant, or more
|
|
234
|
+
than one is somehow live — retry once, and relaunch the runner if it persists.
|
|
235
|
+
|
|
236
|
+
`elements` takes one of two ways to search, chosen with `--by`: `point` needs
|
|
237
|
+
whole-pixel `--x`/`--y` on the device's own screen, the same coordinates a
|
|
238
|
+
screenshot is measured in; `text` needs `--text` and matches it exactly unless
|
|
239
|
+
`--partial` is passed. The two do not mix — `--by point` with `--text` set, or
|
|
240
|
+
`--by text` with `--x`/`--y` set, is refused before a runner is addressed
|
|
241
|
+
rather than silently searching by the one it picked, since the schema itself
|
|
242
|
+
just strips whichever field the chosen `by` does not define. `--context` on
|
|
243
|
+
`page-source` or `elements` reads a context other than the current one —
|
|
244
|
+
useful once `contexts` has told you which are available.
|
|
245
|
+
|
|
246
|
+
A browser runner answers `runner-is-not-mobile` to all four, exit `2`, since
|
|
247
|
+
retrying never helps: launch with `--name android` or `--name ios` instead.
|
|
248
|
+
`runner-unreachable` exits `4` and is worth retrying, same as everywhere else
|
|
249
|
+
on this surface.
|
|
250
|
+
|
|
188
251
|
## Checking a selector: `highlight-selector`
|
|
189
252
|
|
|
190
253
|
`inspect element-html` tells you what a selector matched. `highlight-selector`
|