@validate.qa/runner 1.0.6 → 1.0.9

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.
Files changed (3) hide show
  1. package/dist/cli.js +90 -36
  2. package/dist/cli.mjs +90 -36
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1264,7 +1264,8 @@ var init_constants = __esm({
1264
1264
  "GROK_API_KEY",
1265
1265
  "MINIMAX_API_KEY",
1266
1266
  "ANTHROPIC_API_KEY",
1267
- "NVIDIA_API_KEY"
1267
+ "NVIDIA_API_KEY",
1268
+ "DEEPSEEK_API_KEY"
1268
1269
  ]);
1269
1270
  SPAWNED_ENV_ALLOWLIST = /* @__PURE__ */ new Set([
1270
1271
  // Runtime essentials - Node.js and Playwright need these to function
@@ -1605,11 +1606,11 @@ function buildAppiumCode(testName, target, steps) {
1605
1606
  const x = Math.round(bounds.x + bounds.width / 2);
1606
1607
  const y = Math.round(bounds.y + bounds.height / 2);
1607
1608
  lines.push(` try {`);
1608
- lines.push(` await (await findFirst(driver, ${candidatesLiteral})).addValue(${toJsStringLiteral(step.value ?? "")});`);
1609
- lines.push(` } catch {`);
1610
1609
  lines.push(` await __tapAt(driver, ${x}, ${y});`);
1611
1610
  lines.push(` await driver.pause(${MOBILE_FOCUS_SETTLE_MS});`);
1612
1611
  lines.push(` await __typeIntoFocused(driver, ${toJsStringLiteral(step.value ?? "")});`);
1612
+ lines.push(` } catch {`);
1613
+ lines.push(` await (await findFirst(driver, ${candidatesLiteral})).addValue(${toJsStringLiteral(step.value ?? "")});`);
1613
1614
  lines.push(` }`);
1614
1615
  } else {
1615
1616
  lines.push(` await (await findFirst(driver, ${candidatesLiteral})).addValue(${toJsStringLiteral(step.value ?? "")});`);
@@ -2133,6 +2134,7 @@ var init_audit_pricing = __esm({
2133
2134
  "MiniMax-M2.7": { input: 0.3, output: 1.2 },
2134
2135
  "MiniMax-M2.7-highspeed": { input: 0.6, output: 2.4 },
2135
2136
  "MiniMax-M2": { input: 0.3, output: 1.2 },
2137
+ "deepseek-v4-flash": { input: 0.14, output: 0.28 },
2136
2138
  // Whisper bills per-minute, not per-token
2137
2139
  "whisper-1": null,
2138
2140
  // Historical / deprecated (kept so old audit rows still show a cost).
@@ -12036,7 +12038,7 @@ var require_kill_tree = __commonJS({
12036
12038
  var node_child_process_1 = require("child_process");
12037
12039
  var node_util_1 = require("util");
12038
12040
  var node_fs_1 = require("fs");
12039
- var execFileAsync2 = (0, node_util_1.promisify)(node_child_process_1.execFile);
12041
+ var execFileAsync3 = (0, node_util_1.promisify)(node_child_process_1.execFile);
12040
12042
  var PS_TIMEOUT_MS = 2e3;
12041
12043
  var PS_BUFFER = 1024 * 1024;
12042
12044
  async function gracefulCloseWithTreeKill(options) {
@@ -12090,7 +12092,7 @@ var require_kill_tree = __commonJS({
12090
12092
  timer.unref?.();
12091
12093
  let stdout;
12092
12094
  try {
12093
- const result = await execFileAsync2("ps", ["-A", "-o", "pid=,ppid=,command="], {
12095
+ const result = await execFileAsync3("ps", ["-A", "-o", "pid=,ppid=,command="], {
12094
12096
  maxBuffer: PS_BUFFER,
12095
12097
  signal: controller.signal
12096
12098
  });
@@ -12128,7 +12130,7 @@ var require_kill_tree = __commonJS({
12128
12130
  timer.unref?.();
12129
12131
  let stdout;
12130
12132
  try {
12131
- const result = await execFileAsync2("ps", ["-A", "-o", "pid=,ppid=,command="], {
12133
+ const result = await execFileAsync3("ps", ["-A", "-o", "pid=,ppid=,command="], {
12132
12134
  maxBuffer: PS_BUFFER,
12133
12135
  signal: controller.signal
12134
12136
  });
@@ -12205,7 +12207,7 @@ var require_kill_tree = __commonJS({
12205
12207
  timer.unref?.();
12206
12208
  let stdout;
12207
12209
  try {
12208
- const result = await execFileAsync2("ps", ["-A", "-o", "pid=,ppid="], {
12210
+ const result = await execFileAsync3("ps", ["-A", "-o", "pid=,ppid="], {
12209
12211
  maxBuffer: PS_BUFFER,
12210
12212
  signal: controller.signal
12211
12213
  });
@@ -321093,7 +321095,7 @@ var require_update = __commonJS({
321093
321095
  var require_opts_arg = __commonJS({
321094
321096
  "../../node_modules/tar/node_modules/mkdirp/lib/opts-arg.js"(exports2, module2) {
321095
321097
  "use strict";
321096
- var { promisify: promisify2 } = require("util");
321098
+ var { promisify: promisify3 } = require("util");
321097
321099
  var fs = require("fs");
321098
321100
  var optsArg = (opts) => {
321099
321101
  if (!opts)
@@ -321107,9 +321109,9 @@ var require_opts_arg = __commonJS({
321107
321109
  else
321108
321110
  throw new TypeError("invalid options argument");
321109
321111
  opts.mkdir = opts.mkdir || opts.fs.mkdir || fs.mkdir;
321110
- opts.mkdirAsync = promisify2(opts.mkdir);
321112
+ opts.mkdirAsync = promisify3(opts.mkdir);
321111
321113
  opts.stat = opts.stat || opts.fs.stat || fs.stat;
321112
- opts.statAsync = promisify2(opts.stat);
321114
+ opts.statAsync = promisify3(opts.stat);
321113
321115
  opts.statSync = opts.statSync || opts.fs.statSync || fs.statSync;
321114
321116
  opts.mkdirSync = opts.mkdirSync || opts.fs.mkdirSync || fs.mkdirSync;
321115
321117
  return opts;
@@ -325202,7 +325204,7 @@ var require_process_inventory = __commonJS({
325202
325204
  var node_child_process_1 = require("child_process");
325203
325205
  var node_os_1 = __importDefault(require("os"));
325204
325206
  var node_util_1 = require("util");
325205
- var execFileAsync2 = (0, node_util_1.promisify)(node_child_process_1.execFile);
325207
+ var execFileAsync3 = (0, node_util_1.promisify)(node_child_process_1.execFile);
325206
325208
  var DEFAULT_LIMIT = 60;
325207
325209
  var DEFAULT_TIMEOUT_MS = 800;
325208
325210
  var MAX_COMMAND_LENGTH = 320;
@@ -325264,7 +325266,7 @@ var require_process_inventory = __commonJS({
325264
325266
  const timer = setTimeout(() => controller.abort(), timeoutMs);
325265
325267
  timer.unref?.();
325266
325268
  try {
325267
- const { stdout } = await execFileAsync2(command, args, {
325269
+ const { stdout } = await execFileAsync3(command, args, {
325268
325270
  maxBuffer: 1024 * 1024,
325269
325271
  signal: controller.signal
325270
325272
  });
@@ -326286,7 +326288,7 @@ var require_package4 = __commonJS({
326286
326288
  "package.json"(exports2, module2) {
326287
326289
  module2.exports = {
326288
326290
  name: "@validate.qa/runner",
326289
- version: "1.0.6",
326291
+ version: "1.0.9",
326290
326292
  description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
326291
326293
  bin: {
326292
326294
  "validate-runner": "dist/cli.js"
@@ -326419,6 +326421,8 @@ function shouldUseAuthState(tags, playwrightCode) {
326419
326421
 
326420
326422
  // src/services/appium-executor.ts
326421
326423
  init_dist();
326424
+ var import_node_child_process2 = require("child_process");
326425
+ var import_node_util2 = require("util");
326422
326426
  var import_runner_core8 = __toESM(require_dist2());
326423
326427
 
326424
326428
  // src/services/appium-lifecycle.ts
@@ -327599,6 +327603,7 @@ async function startMobileNetworkCapture(options) {
327599
327603
 
327600
327604
  // src/services/appium-executor.ts
327601
327605
  var DEFAULT_STEP_TIMEOUT_MS = 1e4;
327606
+ var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
327602
327607
  var WebDriverTransportError = class extends Error {
327603
327608
  isTransport = true;
327604
327609
  constructor(message) {
@@ -327920,6 +327925,30 @@ async function sendKeysToFocusedInput(sessionId, value) {
327920
327925
  body: JSON.stringify({ text: value, value: Array.from(value) })
327921
327926
  });
327922
327927
  }
327928
+ function encodeAndroidInputText(value) {
327929
+ return value.replace(/%/g, "%25").replace(/\s/g, "%s");
327930
+ }
327931
+ async function sendAndroidTextToFocusedInput(deviceId, value) {
327932
+ if (!deviceId) {
327933
+ throw new Error("Android keyboard fallback requires a device id");
327934
+ }
327935
+ await execFileAsync2("adb", ["-s", deviceId, "shell", "input", "text", encodeAndroidInputText(value)], {
327936
+ timeout: 1e4,
327937
+ windowsHide: true
327938
+ });
327939
+ }
327940
+ async function trySendElementValue(sessionId, elementId, value) {
327941
+ try {
327942
+ await wdRequest(`/session/${sessionId}/element/${elementId}/value`, {
327943
+ method: "POST",
327944
+ body: JSON.stringify({ text: value })
327945
+ });
327946
+ return null;
327947
+ } catch (error2) {
327948
+ if (isTransportError(error2)) throw error2;
327949
+ return error2;
327950
+ }
327951
+ }
327923
327952
  function shouldAbortAfterStepFailure(action) {
327924
327953
  switch (action) {
327925
327954
  case "launchApp":
@@ -327956,17 +327985,33 @@ async function executeTap(sessionId, step) {
327956
327985
  }
327957
327986
  await tapCoordinates(sessionId, bounds);
327958
327987
  }
327959
- async function executeType(sessionId, step) {
327988
+ async function executeType(sessionId, step, context) {
327960
327989
  if (step.value === void 0 || step.value === null || step.value === "") return;
327961
327990
  const value = String(step.value);
327962
327991
  const bounds = boundsFromStep(step);
327963
327992
  let elementId = null;
327993
+ let lastInputError = null;
327964
327994
  let tappedBounds = false;
327995
+ if (bounds) {
327996
+ await tapCoordinates(sessionId, bounds);
327997
+ tappedBounds = true;
327998
+ await new Promise((resolve) => setTimeout(resolve, MOBILE_FOCUS_SETTLE_MS));
327999
+ elementId = await getActiveElementId(sessionId);
328000
+ if (elementId) {
328001
+ lastInputError = await trySendElementValue(sessionId, elementId, value);
328002
+ if (!lastInputError) return;
328003
+ elementId = null;
328004
+ }
328005
+ }
327965
328006
  if (step.target) {
327966
328007
  try {
327967
328008
  elementId = await findElement(sessionId, step.target, readStepTimeout(step));
328009
+ lastInputError = await trySendElementValue(sessionId, elementId, value);
328010
+ if (!lastInputError) return;
328011
+ elementId = null;
327968
328012
  } catch (error2) {
327969
328013
  if (!bounds) throw error2;
328014
+ lastInputError = error2;
327970
328015
  }
327971
328016
  }
327972
328017
  if (!elementId && bounds) {
@@ -327979,25 +328024,39 @@ async function executeType(sessionId, step) {
327979
328024
  elementId = await getActiveElementId(sessionId);
327980
328025
  }
327981
328026
  if (elementId) {
327982
- try {
327983
- await wdRequest(`/session/${sessionId}/element/${elementId}/value`, {
327984
- method: "POST",
327985
- body: JSON.stringify({ text: value })
327986
- });
327987
- return;
327988
- } catch (error2) {
327989
- if (!bounds) throw error2;
328027
+ lastInputError = await trySendElementValue(sessionId, elementId, value);
328028
+ if (!lastInputError) return;
328029
+ elementId = null;
328030
+ if (!bounds) {
328031
+ throw lastInputError instanceof Error ? lastInputError : new Error(String(lastInputError));
327990
328032
  }
327991
328033
  }
327992
328034
  if (bounds) {
327993
328035
  if (!tappedBounds) {
327994
328036
  await tapCoordinates(sessionId, bounds);
328037
+ tappedBounds = true;
327995
328038
  await new Promise((resolve) => setTimeout(resolve, MOBILE_FOCUS_SETTLE_MS));
327996
328039
  }
327997
- await sendKeysToFocusedInput(sessionId, value);
327998
- return;
328040
+ try {
328041
+ await sendKeysToFocusedInput(sessionId, value);
328042
+ return;
328043
+ } catch (error2) {
328044
+ if (isTransportError(error2)) throw error2;
328045
+ lastInputError = error2;
328046
+ }
327999
328047
  }
328000
- throw new Error("TYPE step could not resolve a target or focused element");
328048
+ if (context.platform === "ANDROID" && tappedBounds) {
328049
+ try {
328050
+ context.log?.(" Appium active-element send failed; using Android focused-keyboard input fallback");
328051
+ await sendAndroidTextToFocusedInput(context.deviceId, value);
328052
+ await new Promise((resolve) => setTimeout(resolve, MOBILE_FOCUS_SETTLE_MS));
328053
+ return;
328054
+ } catch (error2) {
328055
+ if (isTransportError(error2)) throw error2;
328056
+ lastInputError = error2;
328057
+ }
328058
+ }
328059
+ throw new Error(`TYPE step could not send text to the focused target${lastInputError instanceof Error ? `: ${lastInputError.message}` : ""}`);
328001
328060
  }
328002
328061
  async function executeClear(sessionId, step) {
328003
328062
  const bounds = boundsFromStep(step);
@@ -328218,7 +328277,11 @@ async function executeMobileTest(options) {
328218
328277
  await executeTap(sessionId, step);
328219
328278
  break;
328220
328279
  case "type":
328221
- await executeType(sessionId, step);
328280
+ await executeType(sessionId, step, {
328281
+ platform: options.target.platform,
328282
+ deviceId: selectedDevice.id,
328283
+ log: log2
328284
+ });
328222
328285
  break;
328223
328286
  case "clear":
328224
328287
  await executeClear(sessionId, step);
@@ -330657,16 +330720,7 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
330657
330720
  }
330658
330721
  return;
330659
330722
  }
330660
- if (runMode === "heal") {
330661
- if (run2.framework === "APPIUM") {
330662
- await postResult(opts.serverUrl, headers, run2.runId, {
330663
- status: "ERROR",
330664
- error: "CONFIG_ISSUE: Appium (mobile) tests are not auto-healable; skipped heal.",
330665
- duration: Date.now() - startTime
330666
- });
330667
- log.warn("Skipped heal for APPIUM test (mobile tests are not healable)");
330668
- return;
330669
- }
330723
+ if (runMode === "heal" && run2.framework !== "APPIUM") {
330670
330724
  const isApiTest2 = Array.isArray(run2.tags) && run2.tags.includes("@api");
330671
330725
  if (isApiTest2) {
330672
330726
  const testCode = run2.playwrightCode ?? "";
package/dist/cli.mjs CHANGED
@@ -1269,7 +1269,8 @@ var init_constants = __esm({
1269
1269
  "GROK_API_KEY",
1270
1270
  "MINIMAX_API_KEY",
1271
1271
  "ANTHROPIC_API_KEY",
1272
- "NVIDIA_API_KEY"
1272
+ "NVIDIA_API_KEY",
1273
+ "DEEPSEEK_API_KEY"
1273
1274
  ]);
1274
1275
  SPAWNED_ENV_ALLOWLIST = /* @__PURE__ */ new Set([
1275
1276
  // Runtime essentials - Node.js and Playwright need these to function
@@ -1610,11 +1611,11 @@ function buildAppiumCode(testName, target, steps) {
1610
1611
  const x = Math.round(bounds.x + bounds.width / 2);
1611
1612
  const y = Math.round(bounds.y + bounds.height / 2);
1612
1613
  lines.push(` try {`);
1613
- lines.push(` await (await findFirst(driver, ${candidatesLiteral})).addValue(${toJsStringLiteral(step.value ?? "")});`);
1614
- lines.push(` } catch {`);
1615
1614
  lines.push(` await __tapAt(driver, ${x}, ${y});`);
1616
1615
  lines.push(` await driver.pause(${MOBILE_FOCUS_SETTLE_MS});`);
1617
1616
  lines.push(` await __typeIntoFocused(driver, ${toJsStringLiteral(step.value ?? "")});`);
1617
+ lines.push(` } catch {`);
1618
+ lines.push(` await (await findFirst(driver, ${candidatesLiteral})).addValue(${toJsStringLiteral(step.value ?? "")});`);
1618
1619
  lines.push(` }`);
1619
1620
  } else {
1620
1621
  lines.push(` await (await findFirst(driver, ${candidatesLiteral})).addValue(${toJsStringLiteral(step.value ?? "")});`);
@@ -2138,6 +2139,7 @@ var init_audit_pricing = __esm({
2138
2139
  "MiniMax-M2.7": { input: 0.3, output: 1.2 },
2139
2140
  "MiniMax-M2.7-highspeed": { input: 0.6, output: 2.4 },
2140
2141
  "MiniMax-M2": { input: 0.3, output: 1.2 },
2142
+ "deepseek-v4-flash": { input: 0.14, output: 0.28 },
2141
2143
  // Whisper bills per-minute, not per-token
2142
2144
  "whisper-1": null,
2143
2145
  // Historical / deprecated (kept so old audit rows still show a cost).
@@ -12041,7 +12043,7 @@ var require_kill_tree = __commonJS({
12041
12043
  var node_child_process_1 = __require("child_process");
12042
12044
  var node_util_1 = __require("util");
12043
12045
  var node_fs_1 = __require("fs");
12044
- var execFileAsync2 = (0, node_util_1.promisify)(node_child_process_1.execFile);
12046
+ var execFileAsync3 = (0, node_util_1.promisify)(node_child_process_1.execFile);
12045
12047
  var PS_TIMEOUT_MS = 2e3;
12046
12048
  var PS_BUFFER = 1024 * 1024;
12047
12049
  async function gracefulCloseWithTreeKill(options) {
@@ -12095,7 +12097,7 @@ var require_kill_tree = __commonJS({
12095
12097
  timer.unref?.();
12096
12098
  let stdout;
12097
12099
  try {
12098
- const result = await execFileAsync2("ps", ["-A", "-o", "pid=,ppid=,command="], {
12100
+ const result = await execFileAsync3("ps", ["-A", "-o", "pid=,ppid=,command="], {
12099
12101
  maxBuffer: PS_BUFFER,
12100
12102
  signal: controller.signal
12101
12103
  });
@@ -12133,7 +12135,7 @@ var require_kill_tree = __commonJS({
12133
12135
  timer.unref?.();
12134
12136
  let stdout;
12135
12137
  try {
12136
- const result = await execFileAsync2("ps", ["-A", "-o", "pid=,ppid=,command="], {
12138
+ const result = await execFileAsync3("ps", ["-A", "-o", "pid=,ppid=,command="], {
12137
12139
  maxBuffer: PS_BUFFER,
12138
12140
  signal: controller.signal
12139
12141
  });
@@ -12210,7 +12212,7 @@ var require_kill_tree = __commonJS({
12210
12212
  timer.unref?.();
12211
12213
  let stdout;
12212
12214
  try {
12213
- const result = await execFileAsync2("ps", ["-A", "-o", "pid=,ppid="], {
12215
+ const result = await execFileAsync3("ps", ["-A", "-o", "pid=,ppid="], {
12214
12216
  maxBuffer: PS_BUFFER,
12215
12217
  signal: controller.signal
12216
12218
  });
@@ -321098,7 +321100,7 @@ var require_update = __commonJS({
321098
321100
  var require_opts_arg = __commonJS({
321099
321101
  "../../node_modules/tar/node_modules/mkdirp/lib/opts-arg.js"(exports, module) {
321100
321102
  "use strict";
321101
- var { promisify: promisify2 } = __require("util");
321103
+ var { promisify: promisify3 } = __require("util");
321102
321104
  var fs = __require("fs");
321103
321105
  var optsArg = (opts) => {
321104
321106
  if (!opts)
@@ -321112,9 +321114,9 @@ var require_opts_arg = __commonJS({
321112
321114
  else
321113
321115
  throw new TypeError("invalid options argument");
321114
321116
  opts.mkdir = opts.mkdir || opts.fs.mkdir || fs.mkdir;
321115
- opts.mkdirAsync = promisify2(opts.mkdir);
321117
+ opts.mkdirAsync = promisify3(opts.mkdir);
321116
321118
  opts.stat = opts.stat || opts.fs.stat || fs.stat;
321117
- opts.statAsync = promisify2(opts.stat);
321119
+ opts.statAsync = promisify3(opts.stat);
321118
321120
  opts.statSync = opts.statSync || opts.fs.statSync || fs.statSync;
321119
321121
  opts.mkdirSync = opts.mkdirSync || opts.fs.mkdirSync || fs.mkdirSync;
321120
321122
  return opts;
@@ -325207,7 +325209,7 @@ var require_process_inventory = __commonJS({
325207
325209
  var node_child_process_1 = __require("child_process");
325208
325210
  var node_os_1 = __importDefault(__require("os"));
325209
325211
  var node_util_1 = __require("util");
325210
- var execFileAsync2 = (0, node_util_1.promisify)(node_child_process_1.execFile);
325212
+ var execFileAsync3 = (0, node_util_1.promisify)(node_child_process_1.execFile);
325211
325213
  var DEFAULT_LIMIT = 60;
325212
325214
  var DEFAULT_TIMEOUT_MS = 800;
325213
325215
  var MAX_COMMAND_LENGTH = 320;
@@ -325269,7 +325271,7 @@ var require_process_inventory = __commonJS({
325269
325271
  const timer = setTimeout(() => controller.abort(), timeoutMs);
325270
325272
  timer.unref?.();
325271
325273
  try {
325272
- const { stdout } = await execFileAsync2(command, args, {
325274
+ const { stdout } = await execFileAsync3(command, args, {
325273
325275
  maxBuffer: 1024 * 1024,
325274
325276
  signal: controller.signal
325275
325277
  });
@@ -326291,7 +326293,7 @@ var require_package4 = __commonJS({
326291
326293
  "package.json"(exports, module) {
326292
326294
  module.exports = {
326293
326295
  name: "@validate.qa/runner",
326294
- version: "1.0.6",
326296
+ version: "1.0.9",
326295
326297
  description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
326296
326298
  bin: {
326297
326299
  "validate-runner": "dist/cli.js"
@@ -326425,6 +326427,8 @@ function shouldUseAuthState(tags, playwrightCode) {
326425
326427
  // src/services/appium-executor.ts
326426
326428
  init_dist();
326427
326429
  var import_runner_core8 = __toESM(require_dist2());
326430
+ import { execFile as execFile2 } from "child_process";
326431
+ import { promisify as promisify2 } from "util";
326428
326432
 
326429
326433
  // src/services/appium-lifecycle.ts
326430
326434
  import { spawn, execFileSync } from "child_process";
@@ -327607,6 +327611,7 @@ async function startMobileNetworkCapture(options) {
327607
327611
 
327608
327612
  // src/services/appium-executor.ts
327609
327613
  var DEFAULT_STEP_TIMEOUT_MS = 1e4;
327614
+ var execFileAsync2 = promisify2(execFile2);
327610
327615
  var WebDriverTransportError = class extends Error {
327611
327616
  isTransport = true;
327612
327617
  constructor(message) {
@@ -327928,6 +327933,30 @@ async function sendKeysToFocusedInput(sessionId, value) {
327928
327933
  body: JSON.stringify({ text: value, value: Array.from(value) })
327929
327934
  });
327930
327935
  }
327936
+ function encodeAndroidInputText(value) {
327937
+ return value.replace(/%/g, "%25").replace(/\s/g, "%s");
327938
+ }
327939
+ async function sendAndroidTextToFocusedInput(deviceId, value) {
327940
+ if (!deviceId) {
327941
+ throw new Error("Android keyboard fallback requires a device id");
327942
+ }
327943
+ await execFileAsync2("adb", ["-s", deviceId, "shell", "input", "text", encodeAndroidInputText(value)], {
327944
+ timeout: 1e4,
327945
+ windowsHide: true
327946
+ });
327947
+ }
327948
+ async function trySendElementValue(sessionId, elementId, value) {
327949
+ try {
327950
+ await wdRequest(`/session/${sessionId}/element/${elementId}/value`, {
327951
+ method: "POST",
327952
+ body: JSON.stringify({ text: value })
327953
+ });
327954
+ return null;
327955
+ } catch (error2) {
327956
+ if (isTransportError(error2)) throw error2;
327957
+ return error2;
327958
+ }
327959
+ }
327931
327960
  function shouldAbortAfterStepFailure(action) {
327932
327961
  switch (action) {
327933
327962
  case "launchApp":
@@ -327964,17 +327993,33 @@ async function executeTap(sessionId, step) {
327964
327993
  }
327965
327994
  await tapCoordinates(sessionId, bounds);
327966
327995
  }
327967
- async function executeType(sessionId, step) {
327996
+ async function executeType(sessionId, step, context) {
327968
327997
  if (step.value === void 0 || step.value === null || step.value === "") return;
327969
327998
  const value = String(step.value);
327970
327999
  const bounds = boundsFromStep(step);
327971
328000
  let elementId = null;
328001
+ let lastInputError = null;
327972
328002
  let tappedBounds = false;
328003
+ if (bounds) {
328004
+ await tapCoordinates(sessionId, bounds);
328005
+ tappedBounds = true;
328006
+ await new Promise((resolve) => setTimeout(resolve, MOBILE_FOCUS_SETTLE_MS));
328007
+ elementId = await getActiveElementId(sessionId);
328008
+ if (elementId) {
328009
+ lastInputError = await trySendElementValue(sessionId, elementId, value);
328010
+ if (!lastInputError) return;
328011
+ elementId = null;
328012
+ }
328013
+ }
327973
328014
  if (step.target) {
327974
328015
  try {
327975
328016
  elementId = await findElement(sessionId, step.target, readStepTimeout(step));
328017
+ lastInputError = await trySendElementValue(sessionId, elementId, value);
328018
+ if (!lastInputError) return;
328019
+ elementId = null;
327976
328020
  } catch (error2) {
327977
328021
  if (!bounds) throw error2;
328022
+ lastInputError = error2;
327978
328023
  }
327979
328024
  }
327980
328025
  if (!elementId && bounds) {
@@ -327987,25 +328032,39 @@ async function executeType(sessionId, step) {
327987
328032
  elementId = await getActiveElementId(sessionId);
327988
328033
  }
327989
328034
  if (elementId) {
327990
- try {
327991
- await wdRequest(`/session/${sessionId}/element/${elementId}/value`, {
327992
- method: "POST",
327993
- body: JSON.stringify({ text: value })
327994
- });
327995
- return;
327996
- } catch (error2) {
327997
- if (!bounds) throw error2;
328035
+ lastInputError = await trySendElementValue(sessionId, elementId, value);
328036
+ if (!lastInputError) return;
328037
+ elementId = null;
328038
+ if (!bounds) {
328039
+ throw lastInputError instanceof Error ? lastInputError : new Error(String(lastInputError));
327998
328040
  }
327999
328041
  }
328000
328042
  if (bounds) {
328001
328043
  if (!tappedBounds) {
328002
328044
  await tapCoordinates(sessionId, bounds);
328045
+ tappedBounds = true;
328003
328046
  await new Promise((resolve) => setTimeout(resolve, MOBILE_FOCUS_SETTLE_MS));
328004
328047
  }
328005
- await sendKeysToFocusedInput(sessionId, value);
328006
- return;
328048
+ try {
328049
+ await sendKeysToFocusedInput(sessionId, value);
328050
+ return;
328051
+ } catch (error2) {
328052
+ if (isTransportError(error2)) throw error2;
328053
+ lastInputError = error2;
328054
+ }
328007
328055
  }
328008
- throw new Error("TYPE step could not resolve a target or focused element");
328056
+ if (context.platform === "ANDROID" && tappedBounds) {
328057
+ try {
328058
+ context.log?.(" Appium active-element send failed; using Android focused-keyboard input fallback");
328059
+ await sendAndroidTextToFocusedInput(context.deviceId, value);
328060
+ await new Promise((resolve) => setTimeout(resolve, MOBILE_FOCUS_SETTLE_MS));
328061
+ return;
328062
+ } catch (error2) {
328063
+ if (isTransportError(error2)) throw error2;
328064
+ lastInputError = error2;
328065
+ }
328066
+ }
328067
+ throw new Error(`TYPE step could not send text to the focused target${lastInputError instanceof Error ? `: ${lastInputError.message}` : ""}`);
328009
328068
  }
328010
328069
  async function executeClear(sessionId, step) {
328011
328070
  const bounds = boundsFromStep(step);
@@ -328226,7 +328285,11 @@ async function executeMobileTest(options) {
328226
328285
  await executeTap(sessionId, step);
328227
328286
  break;
328228
328287
  case "type":
328229
- await executeType(sessionId, step);
328288
+ await executeType(sessionId, step, {
328289
+ platform: options.target.platform,
328290
+ deviceId: selectedDevice.id,
328291
+ log: log2
328292
+ });
328230
328293
  break;
328231
328294
  case "clear":
328232
328295
  await executeClear(sessionId, step);
@@ -330665,16 +330728,7 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
330665
330728
  }
330666
330729
  return;
330667
330730
  }
330668
- if (runMode === "heal") {
330669
- if (run2.framework === "APPIUM") {
330670
- await postResult(opts.serverUrl, headers, run2.runId, {
330671
- status: "ERROR",
330672
- error: "CONFIG_ISSUE: Appium (mobile) tests are not auto-healable; skipped heal.",
330673
- duration: Date.now() - startTime
330674
- });
330675
- log.warn("Skipped heal for APPIUM test (mobile tests are not healable)");
330676
- return;
330677
- }
330731
+ if (runMode === "heal" && run2.framework !== "APPIUM") {
330678
330732
  const isApiTest2 = Array.isArray(run2.tags) && run2.tags.includes("@api");
330679
330733
  if (isApiTest2) {
330680
330734
  const testCode = run2.playwrightCode ?? "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@validate.qa/runner",
3
- "version": "1.0.6",
3
+ "version": "1.0.9",
4
4
  "description": "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
5
5
  "bin": {
6
6
  "validate-runner": "dist/cli.js"