@validate.qa/runner 1.0.2 → 1.0.3
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 +407 -330
- package/dist/cli.mjs +412 -335
- package/package.json +2 -2
package/dist/cli.mjs
CHANGED
|
@@ -1410,7 +1410,7 @@ function selectorForValue(value) {
|
|
|
1410
1410
|
if (value.startsWith("~") || value.startsWith("id="))
|
|
1411
1411
|
return value;
|
|
1412
1412
|
if (value.includes(":id/"))
|
|
1413
|
-
return `id=${value
|
|
1413
|
+
return `id=${value}`;
|
|
1414
1414
|
return `~${value}`;
|
|
1415
1415
|
}
|
|
1416
1416
|
function selectorCandidatesForStep(step) {
|
|
@@ -21743,7 +21743,7 @@ var require_util = __commonJS({
|
|
|
21743
21743
|
return path;
|
|
21744
21744
|
}
|
|
21745
21745
|
exports.normalize = normalize;
|
|
21746
|
-
function
|
|
21746
|
+
function join5(aRoot, aPath) {
|
|
21747
21747
|
if (aRoot === "") {
|
|
21748
21748
|
aRoot = ".";
|
|
21749
21749
|
}
|
|
@@ -21775,7 +21775,7 @@ var require_util = __commonJS({
|
|
|
21775
21775
|
}
|
|
21776
21776
|
return joined;
|
|
21777
21777
|
}
|
|
21778
|
-
exports.join =
|
|
21778
|
+
exports.join = join5;
|
|
21779
21779
|
exports.isAbsolute = function(aPath) {
|
|
21780
21780
|
return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
|
|
21781
21781
|
};
|
|
@@ -21948,7 +21948,7 @@ var require_util = __commonJS({
|
|
|
21948
21948
|
parsed.path = parsed.path.substring(0, index + 1);
|
|
21949
21949
|
}
|
|
21950
21950
|
}
|
|
21951
|
-
sourceURL =
|
|
21951
|
+
sourceURL = join5(urlGenerate(parsed), sourceURL);
|
|
21952
21952
|
}
|
|
21953
21953
|
return normalize(sourceURL);
|
|
21954
21954
|
}
|
|
@@ -243103,7 +243103,7 @@ var require_thread_stream = __commonJS({
|
|
|
243103
243103
|
var { version } = require_package();
|
|
243104
243104
|
var { EventEmitter: EventEmitter2 } = __require("events");
|
|
243105
243105
|
var { Worker } = __require("worker_threads");
|
|
243106
|
-
var { join:
|
|
243106
|
+
var { join: join5 } = __require("path");
|
|
243107
243107
|
var { pathToFileURL } = __require("url");
|
|
243108
243108
|
var { wait } = require_wait();
|
|
243109
243109
|
var {
|
|
@@ -243154,7 +243154,7 @@ var require_thread_stream = __commonJS({
|
|
|
243154
243154
|
function createWorker(stream, opts) {
|
|
243155
243155
|
const { filename, workerData } = opts;
|
|
243156
243156
|
const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
|
|
243157
|
-
const toExecute = bundlerOverrides["thread-stream-worker"] ||
|
|
243157
|
+
const toExecute = bundlerOverrides["thread-stream-worker"] || join5(__dirname, "lib", "worker.js");
|
|
243158
243158
|
const worker = new Worker(toExecute, {
|
|
243159
243159
|
...opts.workerOpts,
|
|
243160
243160
|
name: opts.workerOpts?.name || "thread-stream",
|
|
@@ -243622,7 +243622,7 @@ var require_transport = __commonJS({
|
|
|
243622
243622
|
var { createRequire } = __require("module");
|
|
243623
243623
|
var { existsSync: existsSync4 } = __require("fs");
|
|
243624
243624
|
var getCallers = require_caller();
|
|
243625
|
-
var { join:
|
|
243625
|
+
var { join: join5, isAbsolute, sep } = __require("path");
|
|
243626
243626
|
var { fileURLToPath } = __require("url");
|
|
243627
243627
|
var sleep = require_atomic_sleep();
|
|
243628
243628
|
var onExit = require_on_exit_leak_free();
|
|
@@ -243775,7 +243775,7 @@ var require_transport = __commonJS({
|
|
|
243775
243775
|
throw new Error("only one of target or targets can be specified");
|
|
243776
243776
|
}
|
|
243777
243777
|
if (targets) {
|
|
243778
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
243778
|
+
target = bundlerOverrides["pino-worker"] || join5(__dirname, "worker.js");
|
|
243779
243779
|
options.targets = targets.filter((dest) => dest.target).map((dest) => {
|
|
243780
243780
|
return {
|
|
243781
243781
|
...dest,
|
|
@@ -243793,7 +243793,7 @@ var require_transport = __commonJS({
|
|
|
243793
243793
|
});
|
|
243794
243794
|
});
|
|
243795
243795
|
} else if (pipeline) {
|
|
243796
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
243796
|
+
target = bundlerOverrides["pino-worker"] || join5(__dirname, "worker.js");
|
|
243797
243797
|
options.pipelines = [pipeline.map((dest) => {
|
|
243798
243798
|
return {
|
|
243799
243799
|
...dest,
|
|
@@ -243816,7 +243816,7 @@ var require_transport = __commonJS({
|
|
|
243816
243816
|
return origin;
|
|
243817
243817
|
}
|
|
243818
243818
|
if (origin === "pino/file") {
|
|
243819
|
-
return
|
|
243819
|
+
return join5(__dirname, "..", "file.js");
|
|
243820
243820
|
}
|
|
243821
243821
|
let fixTarget2;
|
|
243822
243822
|
for (const filePath of callers) {
|
|
@@ -244797,7 +244797,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
244797
244797
|
return circularValue;
|
|
244798
244798
|
}
|
|
244799
244799
|
let res = "";
|
|
244800
|
-
let
|
|
244800
|
+
let join5 = ",";
|
|
244801
244801
|
const originalIndentation = indentation;
|
|
244802
244802
|
if (Array.isArray(value)) {
|
|
244803
244803
|
if (value.length === 0) {
|
|
@@ -244811,7 +244811,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
244811
244811
|
indentation += spacer;
|
|
244812
244812
|
res += `
|
|
244813
244813
|
${indentation}`;
|
|
244814
|
-
|
|
244814
|
+
join5 = `,
|
|
244815
244815
|
${indentation}`;
|
|
244816
244816
|
}
|
|
244817
244817
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -244819,13 +244819,13 @@ ${indentation}`;
|
|
|
244819
244819
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
244820
244820
|
const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
244821
244821
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
244822
|
-
res +=
|
|
244822
|
+
res += join5;
|
|
244823
244823
|
}
|
|
244824
244824
|
const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
244825
244825
|
res += tmp !== void 0 ? tmp : "null";
|
|
244826
244826
|
if (value.length - 1 > maximumBreadth) {
|
|
244827
244827
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
244828
|
-
res += `${
|
|
244828
|
+
res += `${join5}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
244829
244829
|
}
|
|
244830
244830
|
if (spacer !== "") {
|
|
244831
244831
|
res += `
|
|
@@ -244846,7 +244846,7 @@ ${originalIndentation}`;
|
|
|
244846
244846
|
let separator = "";
|
|
244847
244847
|
if (spacer !== "") {
|
|
244848
244848
|
indentation += spacer;
|
|
244849
|
-
|
|
244849
|
+
join5 = `,
|
|
244850
244850
|
${indentation}`;
|
|
244851
244851
|
whitespace = " ";
|
|
244852
244852
|
}
|
|
@@ -244860,13 +244860,13 @@ ${indentation}`;
|
|
|
244860
244860
|
const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
|
|
244861
244861
|
if (tmp !== void 0) {
|
|
244862
244862
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
244863
|
-
separator =
|
|
244863
|
+
separator = join5;
|
|
244864
244864
|
}
|
|
244865
244865
|
}
|
|
244866
244866
|
if (keyLength > maximumBreadth) {
|
|
244867
244867
|
const removedKeys = keyLength - maximumBreadth;
|
|
244868
244868
|
res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
|
|
244869
|
-
separator =
|
|
244869
|
+
separator = join5;
|
|
244870
244870
|
}
|
|
244871
244871
|
if (spacer !== "" && separator.length > 1) {
|
|
244872
244872
|
res = `
|
|
@@ -244907,7 +244907,7 @@ ${originalIndentation}`;
|
|
|
244907
244907
|
}
|
|
244908
244908
|
const originalIndentation = indentation;
|
|
244909
244909
|
let res = "";
|
|
244910
|
-
let
|
|
244910
|
+
let join5 = ",";
|
|
244911
244911
|
if (Array.isArray(value)) {
|
|
244912
244912
|
if (value.length === 0) {
|
|
244913
244913
|
return "[]";
|
|
@@ -244920,7 +244920,7 @@ ${originalIndentation}`;
|
|
|
244920
244920
|
indentation += spacer;
|
|
244921
244921
|
res += `
|
|
244922
244922
|
${indentation}`;
|
|
244923
|
-
|
|
244923
|
+
join5 = `,
|
|
244924
244924
|
${indentation}`;
|
|
244925
244925
|
}
|
|
244926
244926
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -244928,13 +244928,13 @@ ${indentation}`;
|
|
|
244928
244928
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
244929
244929
|
const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
244930
244930
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
244931
|
-
res +=
|
|
244931
|
+
res += join5;
|
|
244932
244932
|
}
|
|
244933
244933
|
const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
244934
244934
|
res += tmp !== void 0 ? tmp : "null";
|
|
244935
244935
|
if (value.length - 1 > maximumBreadth) {
|
|
244936
244936
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
244937
|
-
res += `${
|
|
244937
|
+
res += `${join5}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
244938
244938
|
}
|
|
244939
244939
|
if (spacer !== "") {
|
|
244940
244940
|
res += `
|
|
@@ -244947,7 +244947,7 @@ ${originalIndentation}`;
|
|
|
244947
244947
|
let whitespace = "";
|
|
244948
244948
|
if (spacer !== "") {
|
|
244949
244949
|
indentation += spacer;
|
|
244950
|
-
|
|
244950
|
+
join5 = `,
|
|
244951
244951
|
${indentation}`;
|
|
244952
244952
|
whitespace = " ";
|
|
244953
244953
|
}
|
|
@@ -244956,7 +244956,7 @@ ${indentation}`;
|
|
|
244956
244956
|
const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
|
|
244957
244957
|
if (tmp !== void 0) {
|
|
244958
244958
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
244959
|
-
separator =
|
|
244959
|
+
separator = join5;
|
|
244960
244960
|
}
|
|
244961
244961
|
}
|
|
244962
244962
|
if (spacer !== "" && separator.length > 1) {
|
|
@@ -245014,20 +245014,20 @@ ${originalIndentation}`;
|
|
|
245014
245014
|
indentation += spacer;
|
|
245015
245015
|
let res2 = `
|
|
245016
245016
|
${indentation}`;
|
|
245017
|
-
const
|
|
245017
|
+
const join6 = `,
|
|
245018
245018
|
${indentation}`;
|
|
245019
245019
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
245020
245020
|
let i = 0;
|
|
245021
245021
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
245022
245022
|
const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
245023
245023
|
res2 += tmp2 !== void 0 ? tmp2 : "null";
|
|
245024
|
-
res2 +=
|
|
245024
|
+
res2 += join6;
|
|
245025
245025
|
}
|
|
245026
245026
|
const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
245027
245027
|
res2 += tmp !== void 0 ? tmp : "null";
|
|
245028
245028
|
if (value.length - 1 > maximumBreadth) {
|
|
245029
245029
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
245030
|
-
res2 += `${
|
|
245030
|
+
res2 += `${join6}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
245031
245031
|
}
|
|
245032
245032
|
res2 += `
|
|
245033
245033
|
${originalIndentation}`;
|
|
@@ -245043,16 +245043,16 @@ ${originalIndentation}`;
|
|
|
245043
245043
|
return '"[Object]"';
|
|
245044
245044
|
}
|
|
245045
245045
|
indentation += spacer;
|
|
245046
|
-
const
|
|
245046
|
+
const join5 = `,
|
|
245047
245047
|
${indentation}`;
|
|
245048
245048
|
let res = "";
|
|
245049
245049
|
let separator = "";
|
|
245050
245050
|
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
|
|
245051
245051
|
if (isTypedArrayWithEntries(value)) {
|
|
245052
|
-
res += stringifyTypedArray(value,
|
|
245052
|
+
res += stringifyTypedArray(value, join5, maximumBreadth);
|
|
245053
245053
|
keys = keys.slice(value.length);
|
|
245054
245054
|
maximumPropertiesToStringify -= value.length;
|
|
245055
|
-
separator =
|
|
245055
|
+
separator = join5;
|
|
245056
245056
|
}
|
|
245057
245057
|
if (deterministic) {
|
|
245058
245058
|
keys = sort(keys, comparator);
|
|
@@ -245063,13 +245063,13 @@ ${indentation}`;
|
|
|
245063
245063
|
const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
|
|
245064
245064
|
if (tmp !== void 0) {
|
|
245065
245065
|
res += `${separator}${strEscape(key2)}: ${tmp}`;
|
|
245066
|
-
separator =
|
|
245066
|
+
separator = join5;
|
|
245067
245067
|
}
|
|
245068
245068
|
}
|
|
245069
245069
|
if (keyLength > maximumBreadth) {
|
|
245070
245070
|
const removedKeys = keyLength - maximumBreadth;
|
|
245071
245071
|
res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
|
|
245072
|
-
separator =
|
|
245072
|
+
separator = join5;
|
|
245073
245073
|
}
|
|
245074
245074
|
if (separator !== "") {
|
|
245075
245075
|
res = `
|
|
@@ -285426,11 +285426,11 @@ var require_core = __commonJS({
|
|
|
285426
285426
|
function mapOuter(r, f) {
|
|
285427
285427
|
return r.matched ? f(r) : r;
|
|
285428
285428
|
}
|
|
285429
|
-
function ab(pa, pb,
|
|
285430
|
-
return (data, i) => mapOuter(pa(data, i), (ma) => mapInner(pb(data, ma.position), (vb, j) =>
|
|
285429
|
+
function ab(pa, pb, join5) {
|
|
285430
|
+
return (data, i) => mapOuter(pa(data, i), (ma) => mapInner(pb(data, ma.position), (vb, j) => join5(ma.value, vb, data, i, j)));
|
|
285431
285431
|
}
|
|
285432
|
-
function abc(pa, pb, pc,
|
|
285433
|
-
return (data, i) => mapOuter(pa(data, i), (ma) => mapOuter(pb(data, ma.position), (mb) => mapInner(pc(data, mb.position), (vc, j) =>
|
|
285432
|
+
function abc(pa, pb, pc, join5) {
|
|
285433
|
+
return (data, i) => mapOuter(pa(data, i), (ma) => mapOuter(pb(data, ma.position), (mb) => mapInner(pc(data, mb.position), (vc, j) => join5(ma.value, mb.value, vc, data, i, j))));
|
|
285434
285434
|
}
|
|
285435
285435
|
function action(f) {
|
|
285436
285436
|
return (data, i) => {
|
|
@@ -312624,7 +312624,7 @@ var require_utils2 = __commonJS({
|
|
|
312624
312624
|
module.exports = Utils;
|
|
312625
312625
|
Utils.prototype.makeDir = function(folder) {
|
|
312626
312626
|
const self = this;
|
|
312627
|
-
function
|
|
312627
|
+
function mkdirSync4(fpath) {
|
|
312628
312628
|
let resolvedPath = fpath.split(self.sep)[0];
|
|
312629
312629
|
fpath.split(self.sep).forEach(function(name) {
|
|
312630
312630
|
if (!name || name.substr(-1, 1) === ":") return;
|
|
@@ -312642,7 +312642,7 @@ var require_utils2 = __commonJS({
|
|
|
312642
312642
|
if (stat && stat.isFile()) throw Errors.FILE_IN_THE_WAY(`"${resolvedPath}"`);
|
|
312643
312643
|
});
|
|
312644
312644
|
}
|
|
312645
|
-
|
|
312645
|
+
mkdirSync4(folder);
|
|
312646
312646
|
};
|
|
312647
312647
|
Utils.prototype.writeFileTo = function(path, content, overwrite, attr) {
|
|
312648
312648
|
const self = this;
|
|
@@ -314261,8 +314261,8 @@ var require_adm_zip = __commonJS({
|
|
|
314261
314261
|
return null;
|
|
314262
314262
|
}
|
|
314263
314263
|
function fixPath(zipPath) {
|
|
314264
|
-
const { join:
|
|
314265
|
-
return
|
|
314264
|
+
const { join: join5, normalize, sep } = pth.posix;
|
|
314265
|
+
return join5(pth.isAbsolute(zipPath) ? "/" : ".", normalize(sep + zipPath.split("\\").join(sep) + sep));
|
|
314266
314266
|
}
|
|
314267
314267
|
function filenameFilter(filterfn) {
|
|
314268
314268
|
if (filterfn instanceof RegExp) {
|
|
@@ -319675,14 +319675,14 @@ var require_path_arg = __commonJS({
|
|
|
319675
319675
|
var require_find_made = __commonJS({
|
|
319676
319676
|
"../../node_modules/tar/node_modules/mkdirp/lib/find-made.js"(exports, module) {
|
|
319677
319677
|
"use strict";
|
|
319678
|
-
var { dirname } = __require("path");
|
|
319678
|
+
var { dirname: dirname2 } = __require("path");
|
|
319679
319679
|
var findMade = (opts, parent, path = void 0) => {
|
|
319680
319680
|
if (path === parent)
|
|
319681
319681
|
return Promise.resolve();
|
|
319682
319682
|
return opts.statAsync(parent).then(
|
|
319683
319683
|
(st) => st.isDirectory() ? path : void 0,
|
|
319684
319684
|
// will fail later
|
|
319685
|
-
(er) => er.code === "ENOENT" ? findMade(opts,
|
|
319685
|
+
(er) => er.code === "ENOENT" ? findMade(opts, dirname2(parent), parent) : void 0
|
|
319686
319686
|
);
|
|
319687
319687
|
};
|
|
319688
319688
|
var findMadeSync = (opts, parent, path = void 0) => {
|
|
@@ -319691,7 +319691,7 @@ var require_find_made = __commonJS({
|
|
|
319691
319691
|
try {
|
|
319692
319692
|
return opts.statSync(parent).isDirectory() ? path : void 0;
|
|
319693
319693
|
} catch (er) {
|
|
319694
|
-
return er.code === "ENOENT" ? findMadeSync(opts,
|
|
319694
|
+
return er.code === "ENOENT" ? findMadeSync(opts, dirname2(parent), parent) : void 0;
|
|
319695
319695
|
}
|
|
319696
319696
|
};
|
|
319697
319697
|
module.exports = { findMade, findMadeSync };
|
|
@@ -319702,10 +319702,10 @@ var require_find_made = __commonJS({
|
|
|
319702
319702
|
var require_mkdirp_manual = __commonJS({
|
|
319703
319703
|
"../../node_modules/tar/node_modules/mkdirp/lib/mkdirp-manual.js"(exports, module) {
|
|
319704
319704
|
"use strict";
|
|
319705
|
-
var { dirname } = __require("path");
|
|
319705
|
+
var { dirname: dirname2 } = __require("path");
|
|
319706
319706
|
var mkdirpManual = (path, opts, made) => {
|
|
319707
319707
|
opts.recursive = false;
|
|
319708
|
-
const parent =
|
|
319708
|
+
const parent = dirname2(path);
|
|
319709
319709
|
if (parent === path) {
|
|
319710
319710
|
return opts.mkdirAsync(path, opts).catch((er) => {
|
|
319711
319711
|
if (er.code !== "EISDIR")
|
|
@@ -319728,7 +319728,7 @@ var require_mkdirp_manual = __commonJS({
|
|
|
319728
319728
|
});
|
|
319729
319729
|
};
|
|
319730
319730
|
var mkdirpManualSync = (path, opts, made) => {
|
|
319731
|
-
const parent =
|
|
319731
|
+
const parent = dirname2(path);
|
|
319732
319732
|
opts.recursive = false;
|
|
319733
319733
|
if (parent === path) {
|
|
319734
319734
|
try {
|
|
@@ -319764,12 +319764,12 @@ var require_mkdirp_manual = __commonJS({
|
|
|
319764
319764
|
var require_mkdirp_native = __commonJS({
|
|
319765
319765
|
"../../node_modules/tar/node_modules/mkdirp/lib/mkdirp-native.js"(exports, module) {
|
|
319766
319766
|
"use strict";
|
|
319767
|
-
var { dirname } = __require("path");
|
|
319767
|
+
var { dirname: dirname2 } = __require("path");
|
|
319768
319768
|
var { findMade, findMadeSync } = require_find_made();
|
|
319769
319769
|
var { mkdirpManual, mkdirpManualSync } = require_mkdirp_manual();
|
|
319770
319770
|
var mkdirpNative = (path, opts) => {
|
|
319771
319771
|
opts.recursive = true;
|
|
319772
|
-
const parent =
|
|
319772
|
+
const parent = dirname2(path);
|
|
319773
319773
|
if (parent === path)
|
|
319774
319774
|
return opts.mkdirAsync(path, opts);
|
|
319775
319775
|
return findMade(opts, path).then((made) => opts.mkdirAsync(path, opts).then(() => made).catch((er) => {
|
|
@@ -319781,7 +319781,7 @@ var require_mkdirp_native = __commonJS({
|
|
|
319781
319781
|
};
|
|
319782
319782
|
var mkdirpNativeSync = (path, opts) => {
|
|
319783
319783
|
opts.recursive = true;
|
|
319784
|
-
const parent =
|
|
319784
|
+
const parent = dirname2(path);
|
|
319785
319785
|
if (parent === path)
|
|
319786
319786
|
return opts.mkdirSync(path, opts);
|
|
319787
319787
|
const made = findMadeSync(opts, path);
|
|
@@ -320186,7 +320186,7 @@ var require_path_reservations = __commonJS({
|
|
|
320186
320186
|
var assert = __require("assert");
|
|
320187
320187
|
var normalize = require_normalize_unicode();
|
|
320188
320188
|
var stripSlashes = require_strip_trailing_slashes();
|
|
320189
|
-
var { join:
|
|
320189
|
+
var { join: join5 } = __require("path");
|
|
320190
320190
|
var platform3 = process.env.TESTING_TAR_FAKE_PLATFORM || process.platform;
|
|
320191
320191
|
var isWindows = platform3 === "win32";
|
|
320192
320192
|
module.exports = () => {
|
|
@@ -320195,7 +320195,7 @@ var require_path_reservations = __commonJS({
|
|
|
320195
320195
|
const getDirs = (path) => {
|
|
320196
320196
|
const dirs = path.split("/").slice(0, -1).reduce((set, path2) => {
|
|
320197
320197
|
if (set.length) {
|
|
320198
|
-
path2 =
|
|
320198
|
+
path2 = join5(set[set.length - 1], path2);
|
|
320199
320199
|
}
|
|
320200
320200
|
set.push(path2 || "/");
|
|
320201
320201
|
return set;
|
|
@@ -320263,7 +320263,7 @@ var require_path_reservations = __commonJS({
|
|
|
320263
320263
|
};
|
|
320264
320264
|
const reserve = (paths, fn) => {
|
|
320265
320265
|
paths = isWindows ? ["win32 parallelization disabled"] : paths.map((p) => {
|
|
320266
|
-
return stripSlashes(
|
|
320266
|
+
return stripSlashes(join5(normalize(p))).toLowerCase();
|
|
320267
320267
|
});
|
|
320268
320268
|
const dirs = new Set(
|
|
320269
320269
|
paths.map((path) => getDirs(path)).reduce((a, b) => a.concat(b))
|
|
@@ -324801,7 +324801,7 @@ var require_package4 = __commonJS({
|
|
|
324801
324801
|
"package.json"(exports, module) {
|
|
324802
324802
|
module.exports = {
|
|
324803
324803
|
name: "@validate.qa/runner",
|
|
324804
|
-
version: "1.0.
|
|
324804
|
+
version: "1.0.3",
|
|
324805
324805
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
324806
324806
|
bin: {
|
|
324807
324807
|
"validate-runner": "dist/cli.js"
|
|
@@ -324809,7 +324809,7 @@ var require_package4 = __commonJS({
|
|
|
324809
324809
|
scripts: {
|
|
324810
324810
|
build: "tsup",
|
|
324811
324811
|
dev: "tsx src/cli.ts",
|
|
324812
|
-
"test:mobile": "node --import tsx --test src/services/mobile/mobile-network-capture.smoke.test.ts",
|
|
324812
|
+
"test:mobile": "node --import tsx --test src/runner.mobile-ai-env.test.ts src/services/mobile/mobile-network-capture.smoke.test.ts",
|
|
324813
324813
|
prepublishOnly: "npm run build"
|
|
324814
324814
|
},
|
|
324815
324815
|
files: [
|
|
@@ -324887,9 +324887,9 @@ function getConfigDir() {
|
|
|
324887
324887
|
}
|
|
324888
324888
|
|
|
324889
324889
|
// src/runner.ts
|
|
324890
|
-
import { existsSync as
|
|
324891
|
-
import { join as
|
|
324892
|
-
import { homedir as
|
|
324890
|
+
import { existsSync as existsSync3, statSync, mkdirSync as mkdirSync3 } from "fs";
|
|
324891
|
+
import { join as join4 } from "path";
|
|
324892
|
+
import { homedir as homedir3 } from "os";
|
|
324893
324893
|
import { createHash } from "crypto";
|
|
324894
324894
|
|
|
324895
324895
|
// src/services/playwright-native.ts
|
|
@@ -325302,11 +325302,12 @@ function discoverRealIOSDevices() {
|
|
|
325302
325302
|
for (const [index, node] of iosNodes.entries()) {
|
|
325303
325303
|
const serial = typeof node.serial_num === "string" && node.serial_num.trim() ? node.serial_num.trim() : `usb-ios-device-${index + 1}`;
|
|
325304
325304
|
const name = typeof node._name === "string" && node._name.trim() ? node._name.trim() : "iOS Device";
|
|
325305
|
+
const version = runCommand("ideviceinfo", ["-u", serial, "-k", "ProductVersion"]) || "unknown";
|
|
325305
325306
|
devices.push({
|
|
325306
325307
|
id: serial,
|
|
325307
325308
|
name,
|
|
325308
325309
|
platform: "IOS",
|
|
325309
|
-
platformVersion:
|
|
325310
|
+
platformVersion: version,
|
|
325310
325311
|
state: "BOOTED",
|
|
325311
325312
|
isAvailable: true,
|
|
325312
325313
|
isPhysical: true
|
|
@@ -325381,7 +325382,7 @@ function parseAdbDevices() {
|
|
|
325381
325382
|
const output = runCommand("adb", ["devices", "-l"]);
|
|
325382
325383
|
if (!output) return [];
|
|
325383
325384
|
const lines = output.split("\n").slice(1);
|
|
325384
|
-
return lines.filter((line) => line.
|
|
325385
|
+
return lines.filter((line) => line.trim().length > 0).map((line) => {
|
|
325385
325386
|
const parts = line.trim().split(/\s+/);
|
|
325386
325387
|
const serial = parts[0];
|
|
325387
325388
|
const type = parts[1] ?? "";
|
|
@@ -325397,26 +325398,25 @@ function parseAdbDevices() {
|
|
|
325397
325398
|
device: props["device"],
|
|
325398
325399
|
transportId: props["transport_id"]
|
|
325399
325400
|
};
|
|
325400
|
-
}).filter((d) => d.type === "device");
|
|
325401
|
+
}).filter((d) => d.type === "device" || d.type === "unauthorized");
|
|
325401
325402
|
}
|
|
325402
325403
|
function getAndroidProp(serial, prop) {
|
|
325403
325404
|
return runCommand("adb", ["-s", serial, "shell", "getprop", prop]);
|
|
325404
325405
|
}
|
|
325405
325406
|
function discoverAndroidDevices() {
|
|
325406
325407
|
const adbDevices = parseAdbDevices();
|
|
325407
|
-
return adbDevices.filter((d) => {
|
|
325408
|
-
return d.type === "device";
|
|
325409
|
-
}).map((d) => {
|
|
325408
|
+
return adbDevices.filter((d) => d.type === "device" || d.type === "unauthorized").map((d) => {
|
|
325410
325409
|
const isEmulator = d.serial.startsWith("emulator-");
|
|
325410
|
+
const isAvailable = d.type === "device";
|
|
325411
325411
|
const name = d.model || d.device || (isEmulator ? "Android Emulator" : "Android Device");
|
|
325412
|
-
const platformVersion = getAndroidProp(d.serial, "ro.build.version.release") || "unknown";
|
|
325412
|
+
const platformVersion = isAvailable ? getAndroidProp(d.serial, "ro.build.version.release") || "unknown" : "unknown";
|
|
325413
325413
|
return {
|
|
325414
325414
|
id: d.serial,
|
|
325415
325415
|
name,
|
|
325416
325416
|
platform: "ANDROID",
|
|
325417
325417
|
platformVersion,
|
|
325418
325418
|
state: "BOOTED",
|
|
325419
|
-
isAvailable
|
|
325419
|
+
isAvailable,
|
|
325420
325420
|
// Mark if it's a real device vs emulator
|
|
325421
325421
|
...isEmulator ? {} : { isPhysical: true }
|
|
325422
325422
|
};
|
|
@@ -325449,10 +325449,14 @@ function discoverAllDevices(options) {
|
|
|
325449
325449
|
const androidDevices = discoverAndroidDevices();
|
|
325450
325450
|
if (options?.includeApps) {
|
|
325451
325451
|
for (const device of iosDevices) {
|
|
325452
|
-
|
|
325452
|
+
if (device.isAvailable) {
|
|
325453
|
+
device.installedApps = listIOSInstalledApps(device.id, !!device.isPhysical);
|
|
325454
|
+
}
|
|
325453
325455
|
}
|
|
325454
325456
|
for (const device of androidDevices) {
|
|
325455
|
-
|
|
325457
|
+
if (device.isAvailable) {
|
|
325458
|
+
device.installedApps = listAndroidInstalledApps(device.id);
|
|
325459
|
+
}
|
|
325456
325460
|
}
|
|
325457
325461
|
}
|
|
325458
325462
|
return [...iosDevices, ...androidDevices];
|
|
@@ -325460,11 +325464,12 @@ function discoverAllDevices(options) {
|
|
|
325460
325464
|
function getRunnerCapabilities() {
|
|
325461
325465
|
const iosDevices = discoverIOSDevices();
|
|
325462
325466
|
const androidDevices = discoverAndroidDevices();
|
|
325467
|
+
const isReadyDevice = (device) => device.isAvailable && device.state === "BOOTED";
|
|
325463
325468
|
return {
|
|
325464
325469
|
web: true,
|
|
325465
325470
|
// Always capable of web testing via Playwright
|
|
325466
|
-
ios: iosDevices.
|
|
325467
|
-
android: androidDevices.
|
|
325471
|
+
ios: iosDevices.some(isReadyDevice),
|
|
325472
|
+
android: androidDevices.some(isReadyDevice)
|
|
325468
325473
|
};
|
|
325469
325474
|
}
|
|
325470
325475
|
|
|
@@ -325866,7 +325871,7 @@ async function executeMobileTest(options) {
|
|
|
325866
325871
|
const devices = discoverAllDevices({ includeApps: true });
|
|
325867
325872
|
const platformFilter = options.target.platform;
|
|
325868
325873
|
const matchingDevices = devices.filter(
|
|
325869
|
-
(d) => d.platform === platformFilter && d.state === "BOOTED"
|
|
325874
|
+
(d) => d.platform === platformFilter && d.isAvailable && d.state === "BOOTED"
|
|
325870
325875
|
);
|
|
325871
325876
|
if (matchingDevices.length === 0) {
|
|
325872
325877
|
throw new Error(
|
|
@@ -326030,7 +326035,7 @@ async function createMobileDiscoverySession(target) {
|
|
|
326030
326035
|
const devices = discoverAllDevices({ includeApps: true });
|
|
326031
326036
|
const platformFilter = target.platform;
|
|
326032
326037
|
const matchingDevices = devices.filter(
|
|
326033
|
-
(d) => d.platform === platformFilter && d.state === "BOOTED"
|
|
326038
|
+
(d) => d.platform === platformFilter && d.isAvailable && d.state === "BOOTED"
|
|
326034
326039
|
);
|
|
326035
326040
|
if (matchingDevices.length === 0) {
|
|
326036
326041
|
throw new Error(
|
|
@@ -326081,6 +326086,9 @@ async function createMobileDiscoverySession(target) {
|
|
|
326081
326086
|
import { createServer } from "http";
|
|
326082
326087
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
326083
326088
|
import { randomBytes, timingSafeEqual } from "crypto";
|
|
326089
|
+
import { homedir as homedir2 } from "os";
|
|
326090
|
+
import { join as join2, dirname } from "path";
|
|
326091
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
326084
326092
|
init_dist();
|
|
326085
326093
|
|
|
326086
326094
|
// src/services/mobile/appium-actions.ts
|
|
@@ -326366,30 +326374,50 @@ async function getWindowSize3() {
|
|
|
326366
326374
|
return getWindowSize2(`/session/${state.appiumSessionId}`);
|
|
326367
326375
|
}
|
|
326368
326376
|
var BRIDGE_PROCESS_MARKER = "validateqa-mobile-bridge";
|
|
326369
|
-
|
|
326370
|
-
|
|
326377
|
+
var BRIDGE_PORT = MOBILE_BRIDGE_PORT2;
|
|
326378
|
+
var BRIDGE_PID_FILE = join2(homedir2(), ".validate.qa", "mobile-bridge.pid");
|
|
326379
|
+
function readBridgePidFile() {
|
|
326371
326380
|
try {
|
|
326372
|
-
const
|
|
326373
|
-
|
|
326374
|
-
|
|
326375
|
-
|
|
326376
|
-
|
|
326377
|
-
|
|
326378
|
-
|
|
326379
|
-
|
|
326380
|
-
|
|
326381
|
-
|
|
326382
|
-
|
|
326383
|
-
|
|
326384
|
-
|
|
326385
|
-
|
|
326386
|
-
|
|
326387
|
-
|
|
326388
|
-
|
|
326389
|
-
|
|
326390
|
-
|
|
326381
|
+
const raw = readFileSync2(BRIDGE_PID_FILE, "utf-8").trim();
|
|
326382
|
+
const pid = parseInt(raw, 10);
|
|
326383
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
326384
|
+
} catch {
|
|
326385
|
+
return null;
|
|
326386
|
+
}
|
|
326387
|
+
}
|
|
326388
|
+
function writeBridgePidFile(pid) {
|
|
326389
|
+
try {
|
|
326390
|
+
mkdirSync2(dirname(BRIDGE_PID_FILE), { recursive: true, mode: 448 });
|
|
326391
|
+
writeFileSync2(BRIDGE_PID_FILE, String(pid), { mode: 384 });
|
|
326392
|
+
} catch {
|
|
326393
|
+
}
|
|
326394
|
+
}
|
|
326395
|
+
function clearBridgePidFile() {
|
|
326396
|
+
try {
|
|
326397
|
+
unlinkSync2(BRIDGE_PID_FILE);
|
|
326398
|
+
} catch {
|
|
326399
|
+
}
|
|
326400
|
+
}
|
|
326401
|
+
function killStaleBridge() {
|
|
326402
|
+
const pid = readBridgePidFile();
|
|
326403
|
+
if (!pid) return;
|
|
326404
|
+
try {
|
|
326405
|
+
execFileSync3("kill", ["-0", String(pid)], { stdio: "ignore" });
|
|
326406
|
+
} catch {
|
|
326407
|
+
clearBridgePidFile();
|
|
326408
|
+
return;
|
|
326409
|
+
}
|
|
326410
|
+
try {
|
|
326411
|
+
const pidsOnPort = execFileSync3("lsof", ["-ti", `:${BRIDGE_PORT}`], { encoding: "utf-8" }).trim();
|
|
326412
|
+
const pidList = pidsOnPort.split("\n").filter(Boolean);
|
|
326413
|
+
if (!pidList.includes(String(pid))) {
|
|
326414
|
+
return;
|
|
326391
326415
|
}
|
|
326416
|
+
execFileSync3("kill", ["-9", String(pid)], { stdio: "ignore" });
|
|
326417
|
+
execFileSync3("sleep", ["1"], { stdio: "ignore" });
|
|
326392
326418
|
} catch {
|
|
326419
|
+
} finally {
|
|
326420
|
+
clearBridgePidFile();
|
|
326393
326421
|
}
|
|
326394
326422
|
}
|
|
326395
326423
|
function parseBody(req) {
|
|
@@ -327014,7 +327042,7 @@ async function startMobileBridge(options) {
|
|
|
327014
327042
|
if (!process.title.includes(BRIDGE_PROCESS_MARKER)) {
|
|
327015
327043
|
process.title = `${process.title} ${BRIDGE_PROCESS_MARKER}`;
|
|
327016
327044
|
}
|
|
327017
|
-
|
|
327045
|
+
killStaleBridge();
|
|
327018
327046
|
return new Promise((resolve, reject) => {
|
|
327019
327047
|
server2 = createServer(route);
|
|
327020
327048
|
server2.on("error", (err) => {
|
|
@@ -327030,6 +327058,7 @@ async function startMobileBridge(options) {
|
|
|
327030
327058
|
state.port = port;
|
|
327031
327059
|
state.tunnelToken = tunnelToken;
|
|
327032
327060
|
state.tunnelUrl = void 0;
|
|
327061
|
+
writeBridgePidFile(process.pid);
|
|
327033
327062
|
let tunnelUrl;
|
|
327034
327063
|
if (enableTunnel) {
|
|
327035
327064
|
try {
|
|
@@ -327097,6 +327126,7 @@ async function startMobileBridge(options) {
|
|
|
327097
327126
|
let checkInterval = null;
|
|
327098
327127
|
const timeout = setTimeout(() => {
|
|
327099
327128
|
if (checkInterval) clearInterval(checkInterval);
|
|
327129
|
+
checkInterval = null;
|
|
327100
327130
|
if (!tunnelUrl) {
|
|
327101
327131
|
reject2(new Error("ngrok tunnel failed to start"));
|
|
327102
327132
|
}
|
|
@@ -327105,6 +327135,7 @@ async function startMobileBridge(options) {
|
|
|
327105
327135
|
if (tunnelUrl) {
|
|
327106
327136
|
clearTimeout(timeout);
|
|
327107
327137
|
if (checkInterval) clearInterval(checkInterval);
|
|
327138
|
+
checkInterval = null;
|
|
327108
327139
|
resolve2();
|
|
327109
327140
|
}
|
|
327110
327141
|
}, 500);
|
|
@@ -327134,6 +327165,7 @@ async function startMobileBridge(options) {
|
|
|
327134
327165
|
async function stopMobileBridge() {
|
|
327135
327166
|
stopAllMirrorClients();
|
|
327136
327167
|
stopSessionReaper();
|
|
327168
|
+
clearBridgePidFile();
|
|
327137
327169
|
if (state.appiumSessionId) {
|
|
327138
327170
|
try {
|
|
327139
327171
|
validateSessionId(state.appiumSessionId);
|
|
@@ -327392,7 +327424,7 @@ import { execFile, execFileSync as execFileSync4 } from "child_process";
|
|
|
327392
327424
|
import { randomUUID } from "crypto";
|
|
327393
327425
|
import { mkdtemp, rm, writeFile, readFile, readdir, mkdir, unlink } from "fs/promises";
|
|
327394
327426
|
import { tmpdir, networkInterfaces } from "os";
|
|
327395
|
-
import { join as
|
|
327427
|
+
import { join as join3 } from "path";
|
|
327396
327428
|
import { promisify } from "util";
|
|
327397
327429
|
import {
|
|
327398
327430
|
getLocal,
|
|
@@ -327404,7 +327436,7 @@ var MAX_BODY_CAPTURE_BYTES = 64 * 1024;
|
|
|
327404
327436
|
var DEVICE_CMD_TIMEOUT_MS = 15e3;
|
|
327405
327437
|
var CA_KEY_BITS = 2048;
|
|
327406
327438
|
var ANDROID_EMULATOR_HOST_LOOPBACK = "10.0.2.2";
|
|
327407
|
-
var PENDING_CLEANUP_DIR =
|
|
327439
|
+
var PENDING_CLEANUP_DIR = join3(tmpdir(), "validateqa-mobile-capture-pending");
|
|
327408
327440
|
function rawHeadersToPairs(rawHeaders) {
|
|
327409
327441
|
return rawHeaders.map(([name, value]) => ({ name, value }));
|
|
327410
327442
|
}
|
|
@@ -327688,7 +327720,7 @@ async function generateCaCert(dir, onLog) {
|
|
|
327688
327720
|
// rely on stop() to remove the cert from the device promptly after the run
|
|
327689
327721
|
// rather than on a short expiry.
|
|
327690
327722
|
});
|
|
327691
|
-
const caCertPath =
|
|
327723
|
+
const caCertPath = join3(dir, "validateqa-mobile-ca.pem");
|
|
327692
327724
|
await writeFile(caCertPath, cert, "utf-8");
|
|
327693
327725
|
return { caCertPath, caKeyPem: key, caCertPem: cert };
|
|
327694
327726
|
} catch (err) {
|
|
@@ -327794,7 +327826,7 @@ function errMsg(err) {
|
|
|
327794
327826
|
var activeCleanups = /* @__PURE__ */ new Map();
|
|
327795
327827
|
var exitHandlersInstalled = false;
|
|
327796
327828
|
function markerPath(sessionKey) {
|
|
327797
|
-
return
|
|
327829
|
+
return join3(PENDING_CLEANUP_DIR, `${sessionKey}.json`);
|
|
327798
327830
|
}
|
|
327799
327831
|
async function writePendingCleanup(sessionKey, marker) {
|
|
327800
327832
|
try {
|
|
@@ -327836,7 +327868,7 @@ async function reconcilePendingCleanups(onLog) {
|
|
|
327836
327868
|
}
|
|
327837
327869
|
for (const file of files) {
|
|
327838
327870
|
if (!file.endsWith(".json")) continue;
|
|
327839
|
-
const full =
|
|
327871
|
+
const full = join3(PENDING_CLEANUP_DIR, file);
|
|
327840
327872
|
try {
|
|
327841
327873
|
const raw = await readFile(full, "utf-8");
|
|
327842
327874
|
const marker = JSON.parse(raw);
|
|
@@ -327881,7 +327913,7 @@ async function startMobileNetworkCapture(options) {
|
|
|
327881
327913
|
let caKeyPem = null;
|
|
327882
327914
|
let caCertPem = null;
|
|
327883
327915
|
try {
|
|
327884
|
-
tmpDir = await mkdtemp(
|
|
327916
|
+
tmpDir = await mkdtemp(join3(tmpdir(), "validateqa-mobile-ca-"));
|
|
327885
327917
|
const ca = await generateCaCert(tmpDir, onLog);
|
|
327886
327918
|
if (ca) {
|
|
327887
327919
|
caCertPath = ca.caCertPath;
|
|
@@ -327997,6 +328029,219 @@ async function startMobileNetworkCapture(options) {
|
|
|
327997
328029
|
};
|
|
327998
328030
|
}
|
|
327999
328031
|
|
|
328032
|
+
// src/services/doctor.ts
|
|
328033
|
+
import { execSync } from "child_process";
|
|
328034
|
+
import { existsSync as existsSync2 } from "fs";
|
|
328035
|
+
import { platform as platform2 } from "os";
|
|
328036
|
+
var C = {
|
|
328037
|
+
reset: "\x1B[0m",
|
|
328038
|
+
bold: "\x1B[1m",
|
|
328039
|
+
green: "\x1B[32m",
|
|
328040
|
+
red: "\x1B[31m",
|
|
328041
|
+
yellow: "\x1B[33m",
|
|
328042
|
+
cyan: "\x1B[36m",
|
|
328043
|
+
gray: "\x1B[90m"
|
|
328044
|
+
};
|
|
328045
|
+
function run(cmd) {
|
|
328046
|
+
try {
|
|
328047
|
+
return execSync(cmd, { encoding: "utf-8", timeout: 15e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
328048
|
+
} catch {
|
|
328049
|
+
return null;
|
|
328050
|
+
}
|
|
328051
|
+
}
|
|
328052
|
+
function check(name, fn) {
|
|
328053
|
+
try {
|
|
328054
|
+
const result = fn();
|
|
328055
|
+
return { name, ...result };
|
|
328056
|
+
} catch (err) {
|
|
328057
|
+
return { name, ok: false, detail: err instanceof Error ? err.message : "Unknown error" };
|
|
328058
|
+
}
|
|
328059
|
+
}
|
|
328060
|
+
function checkWeb() {
|
|
328061
|
+
const results = [];
|
|
328062
|
+
results.push(check("Node.js", () => {
|
|
328063
|
+
const version = process.version;
|
|
328064
|
+
const major = parseInt(version.slice(1).split(".")[0], 10);
|
|
328065
|
+
return { ok: major >= 20, detail: `${version}${major < 20 ? " (>= 20 required)" : ""}` };
|
|
328066
|
+
}));
|
|
328067
|
+
results.push(check("Playwright", () => {
|
|
328068
|
+
const version = run("npx playwright --version");
|
|
328069
|
+
if (!version) return { ok: false, detail: "Not installed. Run: npm i -D playwright" };
|
|
328070
|
+
return { ok: true, detail: version };
|
|
328071
|
+
}));
|
|
328072
|
+
return results;
|
|
328073
|
+
}
|
|
328074
|
+
function checkIOS() {
|
|
328075
|
+
const results = [];
|
|
328076
|
+
const isMac = platform2() === "darwin";
|
|
328077
|
+
results.push(check("macOS", () => ({
|
|
328078
|
+
ok: isMac,
|
|
328079
|
+
detail: isMac ? `${platform2()} detected` : "iOS testing requires macOS"
|
|
328080
|
+
})));
|
|
328081
|
+
if (!isMac) return results;
|
|
328082
|
+
results.push(check("Xcode", () => {
|
|
328083
|
+
const version = run("xcodebuild -version");
|
|
328084
|
+
if (!version) return { ok: false, detail: "Not installed. Install from App Store." };
|
|
328085
|
+
const firstLine = version.split("\n")[0];
|
|
328086
|
+
return { ok: true, detail: firstLine };
|
|
328087
|
+
}));
|
|
328088
|
+
results.push(check("Xcode Command Line Tools", () => {
|
|
328089
|
+
const path = run("xcode-select -p");
|
|
328090
|
+
return { ok: !!path, detail: path || "Not installed. Run: xcode-select --install" };
|
|
328091
|
+
}));
|
|
328092
|
+
results.push(check("Appium xcuitest driver", () => {
|
|
328093
|
+
const drivers = run("appium driver list --installed --json");
|
|
328094
|
+
if (!drivers) return { ok: false, detail: "Appium not found. Run: npm i -g appium" };
|
|
328095
|
+
try {
|
|
328096
|
+
const parsed = JSON.parse(drivers);
|
|
328097
|
+
const hasXcuitest = parsed?.xcuitest || typeof parsed === "object" && Object.keys(parsed).some((k) => k.toLowerCase().includes("xcuitest"));
|
|
328098
|
+
return {
|
|
328099
|
+
ok: !!hasXcuitest,
|
|
328100
|
+
detail: hasXcuitest ? "Installed" : "Not installed. Run: appium driver install xcuitest"
|
|
328101
|
+
};
|
|
328102
|
+
} catch {
|
|
328103
|
+
const hasXcuitest = drivers.toLowerCase().includes("xcuitest");
|
|
328104
|
+
return {
|
|
328105
|
+
ok: hasXcuitest,
|
|
328106
|
+
detail: hasXcuitest ? "Installed" : "Not installed. Run: appium driver install xcuitest"
|
|
328107
|
+
};
|
|
328108
|
+
}
|
|
328109
|
+
}));
|
|
328110
|
+
results.push(check("iOS Devices", () => {
|
|
328111
|
+
const json = run("xcrun simctl list devices booted --json");
|
|
328112
|
+
let bootedSimulators = 0;
|
|
328113
|
+
if (json) {
|
|
328114
|
+
try {
|
|
328115
|
+
const parsed = JSON.parse(json);
|
|
328116
|
+
const devices = Object.values(parsed.devices ?? {}).flat();
|
|
328117
|
+
bootedSimulators = devices.filter((d) => d.state === "Booted").length;
|
|
328118
|
+
} catch {
|
|
328119
|
+
}
|
|
328120
|
+
}
|
|
328121
|
+
const realDevices = discoverRealIOSDevices().length;
|
|
328122
|
+
const total = bootedSimulators + realDevices;
|
|
328123
|
+
if (total === 0) {
|
|
328124
|
+
return {
|
|
328125
|
+
ok: false,
|
|
328126
|
+
detail: "No devices found. Boot a simulator or connect a real device via USB."
|
|
328127
|
+
};
|
|
328128
|
+
}
|
|
328129
|
+
const details = [];
|
|
328130
|
+
if (bootedSimulators > 0) details.push(`${bootedSimulators} simulator(s)`);
|
|
328131
|
+
if (realDevices > 0) details.push(`${realDevices} real device(s)`);
|
|
328132
|
+
return {
|
|
328133
|
+
ok: true,
|
|
328134
|
+
detail: `${total} device(s) available: ${details.join(", ")}`
|
|
328135
|
+
};
|
|
328136
|
+
}));
|
|
328137
|
+
return results;
|
|
328138
|
+
}
|
|
328139
|
+
function checkAndroid() {
|
|
328140
|
+
const results = [];
|
|
328141
|
+
const androidHome = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
|
|
328142
|
+
results.push(check("ANDROID_HOME", () => ({
|
|
328143
|
+
ok: !!androidHome && existsSync2(androidHome),
|
|
328144
|
+
detail: androidHome ? existsSync2(androidHome) ? androidHome : `${androidHome} (not found)` : "Not set. Set ANDROID_HOME to your Android SDK path."
|
|
328145
|
+
})));
|
|
328146
|
+
if (!androidHome || !existsSync2(androidHome)) return results;
|
|
328147
|
+
results.push(check("adb", () => {
|
|
328148
|
+
const version = run("adb version");
|
|
328149
|
+
if (!version) return { ok: false, detail: "Not found in PATH. Ensure Android platform-tools are installed." };
|
|
328150
|
+
const firstLine = version.split("\n")[0];
|
|
328151
|
+
return { ok: true, detail: firstLine };
|
|
328152
|
+
}));
|
|
328153
|
+
results.push(check("Appium uiautomator2 driver", () => {
|
|
328154
|
+
const drivers = run("appium driver list --installed --json");
|
|
328155
|
+
if (!drivers) return { ok: false, detail: "Appium not found. Run: npm i -g appium" };
|
|
328156
|
+
try {
|
|
328157
|
+
const parsed = JSON.parse(drivers);
|
|
328158
|
+
const hasUia2 = parsed?.uiautomator2 || typeof parsed === "object" && Object.keys(parsed).some((k) => k.toLowerCase().includes("uiautomator2"));
|
|
328159
|
+
return {
|
|
328160
|
+
ok: !!hasUia2,
|
|
328161
|
+
detail: hasUia2 ? "Installed" : "Not installed. Run: appium driver install uiautomator2"
|
|
328162
|
+
};
|
|
328163
|
+
} catch {
|
|
328164
|
+
const hasUia2 = drivers.toLowerCase().includes("uiautomator2");
|
|
328165
|
+
return {
|
|
328166
|
+
ok: hasUia2,
|
|
328167
|
+
detail: hasUia2 ? "Installed" : "Not installed. Run: appium driver install uiautomator2"
|
|
328168
|
+
};
|
|
328169
|
+
}
|
|
328170
|
+
}));
|
|
328171
|
+
results.push(check("Android Devices", () => {
|
|
328172
|
+
const output = run("adb devices");
|
|
328173
|
+
if (!output) return { ok: false, detail: "adb devices failed" };
|
|
328174
|
+
const lines = output.split("\n").filter((l) => l.includes(" device"));
|
|
328175
|
+
const emulators = lines.filter((l) => l.startsWith("emulator-"));
|
|
328176
|
+
const realDevices = lines.filter((l) => !l.startsWith("emulator-") && l.includes(" device"));
|
|
328177
|
+
const total = emulators.length + realDevices.length;
|
|
328178
|
+
if (total === 0) {
|
|
328179
|
+
return {
|
|
328180
|
+
ok: false,
|
|
328181
|
+
detail: "No devices found. Start an emulator or connect a real device via USB with USB debugging enabled."
|
|
328182
|
+
};
|
|
328183
|
+
}
|
|
328184
|
+
const details = [];
|
|
328185
|
+
if (emulators.length > 0) details.push(`${emulators.length} emulator(s)`);
|
|
328186
|
+
if (realDevices.length > 0) details.push(`${realDevices.length} real device(s)`);
|
|
328187
|
+
return {
|
|
328188
|
+
ok: true,
|
|
328189
|
+
detail: `${total} device(s) available: ${details.join(", ")}`
|
|
328190
|
+
};
|
|
328191
|
+
}));
|
|
328192
|
+
return results;
|
|
328193
|
+
}
|
|
328194
|
+
function checkTunnel() {
|
|
328195
|
+
const version = run("ngrok version") || run("ngrok --version");
|
|
328196
|
+
if (!version) {
|
|
328197
|
+
return [{
|
|
328198
|
+
name: "ngrok",
|
|
328199
|
+
ok: false,
|
|
328200
|
+
detail: "Not installed (only needed for --tunnel with an HTTPS dashboard). Install: brew install ngrok && ngrok config add-authtoken <token>"
|
|
328201
|
+
}];
|
|
328202
|
+
}
|
|
328203
|
+
const match = version.match(/(\d+)\.\d+\.\d+/);
|
|
328204
|
+
const major = match ? parseInt(match[1], 10) : 0;
|
|
328205
|
+
return [{
|
|
328206
|
+
name: "ngrok",
|
|
328207
|
+
ok: major >= 3,
|
|
328208
|
+
detail: major >= 3 ? `${version} (configure an authtoken with: ngrok config add-authtoken <token>)` : `${version} \u2014 ngrok v3+ is recommended (brew install ngrok)`
|
|
328209
|
+
}];
|
|
328210
|
+
}
|
|
328211
|
+
function runDoctor() {
|
|
328212
|
+
const web = checkWeb();
|
|
328213
|
+
const ios = checkIOS();
|
|
328214
|
+
const android = checkAndroid();
|
|
328215
|
+
const tunnel = checkTunnel();
|
|
328216
|
+
const allPassed = [...web, ...ios, ...android].every((c) => c.ok);
|
|
328217
|
+
return { web, ios, android, tunnel, allPassed };
|
|
328218
|
+
}
|
|
328219
|
+
function printDoctorReport(report) {
|
|
328220
|
+
console.log("");
|
|
328221
|
+
console.log(`${C.bold}${C.cyan}\u{1F3E5} validate.qa Doctor${C.reset}`);
|
|
328222
|
+
console.log(`${C.gray}${"\u2500".repeat(40)}${C.reset}`);
|
|
328223
|
+
const printSection = (title, checks) => {
|
|
328224
|
+
console.log("");
|
|
328225
|
+
console.log(` ${C.bold}${title}:${C.reset}`);
|
|
328226
|
+
for (const c of checks) {
|
|
328227
|
+
const icon = c.ok ? `${C.green}\u2713${C.reset}` : `${C.red}\u2717${C.reset}`;
|
|
328228
|
+
console.log(` ${icon} ${c.name}: ${c.detail}`);
|
|
328229
|
+
}
|
|
328230
|
+
};
|
|
328231
|
+
printSection("Web Testing", report.web);
|
|
328232
|
+
printSection("iOS Testing", report.ios);
|
|
328233
|
+
printSection("Android Testing", report.android);
|
|
328234
|
+
printSection("Tunnel (optional \u2014 only for --tunnel / HTTPS dashboard)", report.tunnel);
|
|
328235
|
+
console.log("");
|
|
328236
|
+
if (report.allPassed) {
|
|
328237
|
+
console.log(` ${C.green}\u2705 All checks passed - ready for web + mobile testing${C.reset}`);
|
|
328238
|
+
} else {
|
|
328239
|
+
const failCount = [...report.web, ...report.ios, ...report.android].filter((c) => !c.ok).length;
|
|
328240
|
+
console.log(` ${C.yellow}\u26A0 ${failCount} check(s) need attention${C.reset}`);
|
|
328241
|
+
}
|
|
328242
|
+
console.log("");
|
|
328243
|
+
}
|
|
328244
|
+
|
|
328000
328245
|
// src/runner.ts
|
|
328001
328246
|
init_dist();
|
|
328002
328247
|
var import_runner_core11 = __toESM(require_dist2());
|
|
@@ -328086,7 +328331,7 @@ var browserPool = new import_runner_core6.BrowserPool({
|
|
|
328086
328331
|
maxContextsPerBrowser: envInt("MAX_CONTEXTS_PER_BROWSER", DEFAULT_MAX_CONTEXTS_PER_BROWSER),
|
|
328087
328332
|
retireAfterContexts: 30
|
|
328088
328333
|
});
|
|
328089
|
-
var
|
|
328334
|
+
var C2 = {
|
|
328090
328335
|
reset: "\x1B[0m",
|
|
328091
328336
|
bold: "\x1B[1m",
|
|
328092
328337
|
green: "\x1B[32m",
|
|
@@ -328096,24 +328341,24 @@ var C = {
|
|
|
328096
328341
|
gray: "\x1B[90m"
|
|
328097
328342
|
};
|
|
328098
328343
|
var log = {
|
|
328099
|
-
info: (msg) => console.log(`${
|
|
328100
|
-
ok: (msg) => console.log(`${
|
|
328101
|
-
fail: (msg) => console.log(`${
|
|
328102
|
-
warn: (msg) => console.log(`${
|
|
328103
|
-
dim: (msg) => console.log(`${
|
|
328344
|
+
info: (msg) => console.log(`${C2.cyan}\u2139${C2.reset} ${msg}`),
|
|
328345
|
+
ok: (msg) => console.log(`${C2.green}\u2713${C2.reset} ${msg}`),
|
|
328346
|
+
fail: (msg) => console.log(`${C2.red}\u2717${C2.reset} ${msg}`),
|
|
328347
|
+
warn: (msg) => console.log(`${C2.yellow}\u26A0${C2.reset} ${msg}`),
|
|
328348
|
+
dim: (msg) => console.log(`${C2.gray}${msg}${C2.reset}`)
|
|
328104
328349
|
};
|
|
328105
328350
|
var AUTH_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
328106
328351
|
function getRunsDir() {
|
|
328107
|
-
const dir = process.env.RUNS_DIR ||
|
|
328108
|
-
if (!
|
|
328352
|
+
const dir = process.env.RUNS_DIR || join4(homedir3(), ".validate.qa", "runs");
|
|
328353
|
+
if (!existsSync3(dir)) mkdirSync3(dir, { recursive: true });
|
|
328109
328354
|
return dir;
|
|
328110
328355
|
}
|
|
328111
328356
|
function authStatePath(projectId) {
|
|
328112
|
-
return
|
|
328357
|
+
return join4(getRunsDir(), `auth-state-${projectId || "default"}.json`);
|
|
328113
328358
|
}
|
|
328114
328359
|
function getValidAuthState(projectId) {
|
|
328115
328360
|
const path = authStatePath(projectId);
|
|
328116
|
-
if (!
|
|
328361
|
+
if (!existsSync3(path)) return void 0;
|
|
328117
328362
|
const age = Date.now() - statSync(path).mtimeMs;
|
|
328118
328363
|
return age < AUTH_MAX_AGE_MS ? path : void 0;
|
|
328119
328364
|
}
|
|
@@ -329056,9 +329301,9 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
329056
329301
|
(l) => l.includes("Phase 1: Script-driven") || l.includes("Phase 2: Escalating") || l.includes("Phase 2: Script + MCP") || l.includes("Phase 1 succeeded") || l.includes("Phase 2 iteration") || l.includes("\u{1F916} Verdict:") || l.includes("\u{1F916} finish_fix:") || l.includes("native verification PASSED") || l.includes("native verification FAILED") || l.includes("Native run PASSED") || l.includes("Native run FAILED") || l.includes("APP_BUG") || l.includes("CONFIG_ISSUE") || l.includes("needs_browser") || l.includes("--- Attempt") || l.includes("Proven commands") || l.includes("Proven fix:") || l.includes("Same error repeated") || l.includes("aborting heal")
|
|
329057
329302
|
);
|
|
329058
329303
|
if (phaseLines.length > 0) {
|
|
329059
|
-
console.log(`${
|
|
329304
|
+
console.log(`${C2.gray}\u2500\u2500 Heal summary \u2500\u2500${C2.reset}`);
|
|
329060
329305
|
for (const line of phaseLines) console.log(line);
|
|
329061
|
-
console.log(`${
|
|
329306
|
+
console.log(`${C2.gray}\u2500\u2500 End summary \u2500\u2500\u2500${C2.reset}`);
|
|
329062
329307
|
}
|
|
329063
329308
|
}
|
|
329064
329309
|
log[result2.passed ? "ok" : "fail"](
|
|
@@ -329597,11 +329842,18 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
329597
329842
|
}
|
|
329598
329843
|
try {
|
|
329599
329844
|
if (isNativeMobile) {
|
|
329600
|
-
const platform3 = mobileMedium === "IOS_NATIVE" ? "IOS" : "ANDROID";
|
|
329601
329845
|
const mobileTarget = ctx.target;
|
|
329602
329846
|
if (!mobileTarget || !mobileTarget.appId) {
|
|
329603
329847
|
throw new Error("Native mobile discovery requires a target with an appId (bundleId/appPackage).");
|
|
329604
329848
|
}
|
|
329849
|
+
const targetPlatform = mobileTarget.platform;
|
|
329850
|
+
const platform3 = mobileMedium === "IOS_NATIVE" ? "IOS" : mobileMedium === "ANDROID_NATIVE" ? "ANDROID" : targetPlatform === "IOS" || targetPlatform === "ANDROID" ? targetPlatform : null;
|
|
329851
|
+
if (!platform3) {
|
|
329852
|
+
throw new Error("Native mobile discovery requires medium IOS_NATIVE/ANDROID_NATIVE or target.platform IOS/ANDROID.");
|
|
329853
|
+
}
|
|
329854
|
+
if (mobileMedium === "IOS_NATIVE" && targetPlatform === "ANDROID" || mobileMedium === "ANDROID_NATIVE" && targetPlatform === "IOS") {
|
|
329855
|
+
throw new Error("Native mobile discovery medium does not match target.platform.");
|
|
329856
|
+
}
|
|
329605
329857
|
if (getBridgeState().hasActiveSession) {
|
|
329606
329858
|
throw new Error("CONFIG_ISSUE: the shared mobile device is busy with an active recording session; mobile discovery will retry once it ends.");
|
|
329607
329859
|
}
|
|
@@ -330030,9 +330282,9 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
330030
330282
|
} else {
|
|
330031
330283
|
log.fail(`FAILED (${duration}ms)${result.error ? ` - ${result.error}` : ""}`);
|
|
330032
330284
|
if (result.logs) {
|
|
330033
|
-
console.log(`${
|
|
330285
|
+
console.log(`${C2.gray}\u2500\u2500 Runner logs \u2500\u2500${C2.reset}`);
|
|
330034
330286
|
console.log(result.logs);
|
|
330035
|
-
console.log(`${
|
|
330287
|
+
console.log(`${C2.gray}\u2500\u2500 End logs \u2500\u2500\u2500\u2500\u2500${C2.reset}`);
|
|
330036
330288
|
}
|
|
330037
330289
|
}
|
|
330038
330290
|
} catch (err) {
|
|
@@ -330054,20 +330306,24 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
330054
330306
|
}
|
|
330055
330307
|
async function startRunner(opts) {
|
|
330056
330308
|
const { serverUrl, authToken, projectId, pollIntervalMs = 2e3, runnerMode = "playwright-native", enableMobile = false, enableMobileTunnel = false } = opts;
|
|
330309
|
+
process.env.SERVER_URL = serverUrl;
|
|
330310
|
+
process.env.AUTH_TOKEN = authToken;
|
|
330311
|
+
process.env.RUNNER_TOKEN = authToken;
|
|
330312
|
+
if (projectId) process.env.PROJECT_ID = projectId;
|
|
330057
330313
|
const headers = buildHeaders(authToken, projectId);
|
|
330058
330314
|
console.log("");
|
|
330059
|
-
console.log(`${
|
|
330060
|
-
console.log(`${
|
|
330315
|
+
console.log(`${C2.bold}${C2.cyan}\u{1F399}\uFE0F validate.qa Local Runner${C2.reset}`);
|
|
330316
|
+
console.log(`${C2.gray}${"\u2500".repeat(40)}${C2.reset}`);
|
|
330061
330317
|
log.info(`Server: ${serverUrl}`);
|
|
330062
330318
|
log.info(`Project: ${projectId}`);
|
|
330063
330319
|
log.info(`Mode: \u{1F3AD} Playwright Native${enableMobile ? " + \u{1F4F1} Mobile" : ""}${enableMobile && enableMobileTunnel ? " \u{1F310} Tunnel" : ""}`);
|
|
330064
330320
|
log.info(`Interval: ${pollIntervalMs}ms`);
|
|
330065
330321
|
if (process.env.DISCOVERY_PROMPT_V3 === "true") {
|
|
330066
|
-
log.ok("Discovery: V3
|
|
330322
|
+
log.ok("Discovery prompt: V3 (per-turn rebuild) \u2014 DISCOVERY_PROMPT_V3=true");
|
|
330067
330323
|
} else if (process.env.DISCOVERY_PROMPT_V3 === "false") {
|
|
330068
|
-
log.ok("Discovery: V2.5
|
|
330324
|
+
log.ok("Discovery prompt: V2.5 (full transcript + control brief) \u2014 DISCOVERY_PROMPT_V3=false");
|
|
330069
330325
|
} else {
|
|
330070
|
-
log.ok("Discovery:
|
|
330326
|
+
log.ok("Discovery prompt: V3 (per-turn rebuild, default) \u2014 set DISCOVERY_PROMPT_V3=false for V2.5");
|
|
330071
330327
|
}
|
|
330072
330328
|
if ((0, import_runner_core5.hasUsableChromeProfile)()) {
|
|
330073
330329
|
log.ok(`Browser: Chrome with user profile (cookies/logins persist)`);
|
|
@@ -330079,8 +330335,8 @@ async function startRunner(opts) {
|
|
|
330079
330335
|
const devices = discoverAllDevices();
|
|
330080
330336
|
const capabilities = {
|
|
330081
330337
|
web: true,
|
|
330082
|
-
ios: devices.some((d) => d.platform === "IOS"),
|
|
330083
|
-
android: devices.some((d) => d.platform === "ANDROID")
|
|
330338
|
+
ios: devices.some((d) => d.platform === "IOS" && d.isAvailable && d.state === "BOOTED"),
|
|
330339
|
+
android: devices.some((d) => d.platform === "ANDROID" && d.isAvailable && d.state === "BOOTED")
|
|
330084
330340
|
};
|
|
330085
330341
|
log.info(`\u{1F4F1} Mobile: iOS ${capabilities.ios ? "\u2713" : "\u2717"} | Android ${capabilities.android ? "\u2713" : "\u2717"} | ${devices.length} device(s)`);
|
|
330086
330342
|
try {
|
|
@@ -330141,6 +330397,33 @@ async function startRunner(opts) {
|
|
|
330141
330397
|
const startedAt = Date.now();
|
|
330142
330398
|
const MAX_BACKOFF_MS = 6e4;
|
|
330143
330399
|
let consecutivePollErrors = 0;
|
|
330400
|
+
let cachedDoctor = null;
|
|
330401
|
+
const DOCTOR_TTL_MS = 5 * 60 * 1e3;
|
|
330402
|
+
let doctorRefreshInFlight = false;
|
|
330403
|
+
const refreshDoctorInBackground = () => {
|
|
330404
|
+
if (doctorRefreshInFlight) return;
|
|
330405
|
+
doctorRefreshInFlight = true;
|
|
330406
|
+
setImmediate(() => {
|
|
330407
|
+
try {
|
|
330408
|
+
const report = runDoctor();
|
|
330409
|
+
cachedDoctor = { report, at: Date.now() };
|
|
330410
|
+
} catch (err) {
|
|
330411
|
+
log.dim(` [doctor] refresh failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
330412
|
+
} finally {
|
|
330413
|
+
doctorRefreshInFlight = false;
|
|
330414
|
+
}
|
|
330415
|
+
});
|
|
330416
|
+
};
|
|
330417
|
+
const getCachedDoctor = () => {
|
|
330418
|
+
if (!enableMobile) return void 0;
|
|
330419
|
+
if (!cachedDoctor) {
|
|
330420
|
+
refreshDoctorInBackground();
|
|
330421
|
+
return void 0;
|
|
330422
|
+
}
|
|
330423
|
+
const now = Date.now();
|
|
330424
|
+
if (now - cachedDoctor.at >= DOCTOR_TTL_MS) refreshDoctorInBackground();
|
|
330425
|
+
return cachedDoctor.report;
|
|
330426
|
+
};
|
|
330144
330427
|
log.info(`Concurrency: ${RESOURCE_GOVERNOR_OPTIONS.adaptive ? `adaptive (${RESOURCE_GOVERNOR_OPTIONS.maxUnits} weighted units, hard cap ${MAX_CONCURRENT})` : `static (hard cap ${MAX_CONCURRENT})`}`);
|
|
330145
330428
|
const poolSize = envInt("BROWSER_POOL_SIZE", DEFAULT_BROWSER_POOL_SIZE);
|
|
330146
330429
|
if (poolSize > 0) {
|
|
@@ -330249,7 +330532,11 @@ async function startRunner(opts) {
|
|
|
330249
330532
|
const sendHeartbeat = async () => {
|
|
330250
330533
|
try {
|
|
330251
330534
|
const devices = enableMobile ? discoverAllDevices() : [];
|
|
330252
|
-
const capabilities = enableMobile ? {
|
|
330535
|
+
const capabilities = enableMobile ? {
|
|
330536
|
+
web: true,
|
|
330537
|
+
ios: devices.some((d) => d.platform === "IOS" && d.isAvailable && d.state === "BOOTED"),
|
|
330538
|
+
android: devices.some((d) => d.platform === "ANDROID" && d.isAvailable && d.state === "BOOTED")
|
|
330539
|
+
} : { web: true, ios: false, android: false };
|
|
330253
330540
|
const bridge = enableMobile ? getBridgeState() : void 0;
|
|
330254
330541
|
const resourceStatus = (0, import_runner_core11.getResourceGovernorStatus)(RESOURCE_GOVERNOR_OPTIONS, Object.fromEntries(activeByType));
|
|
330255
330542
|
const processInventory = await (0, import_runner_core11.getBrowserProcessInventory)();
|
|
@@ -330280,7 +330567,10 @@ async function startRunner(opts) {
|
|
|
330280
330567
|
resourceGovernor: resourceStatus.governor,
|
|
330281
330568
|
processInventory,
|
|
330282
330569
|
aiQueue: (0, import_runner_core11.getRunnerAIQueueStats)(),
|
|
330283
|
-
liveBrowsers: import_runner_core10.liveBrowserRegistry.snapshotForHeartbeat()
|
|
330570
|
+
liveBrowsers: import_runner_core10.liveBrowserRegistry.snapshotForHeartbeat(),
|
|
330571
|
+
// Pre-flight checks (ANDROID_HOME, Xcode, Appium drivers, adb, etc).
|
|
330572
|
+
// Cached & cheap — see getCachedDoctor above.
|
|
330573
|
+
doctor: getCachedDoctor()
|
|
330284
330574
|
}),
|
|
330285
330575
|
signal: AbortSignal.timeout(1e4)
|
|
330286
330576
|
});
|
|
@@ -330480,219 +330770,6 @@ async function startRunner(opts) {
|
|
|
330480
330770
|
}
|
|
330481
330771
|
}
|
|
330482
330772
|
|
|
330483
|
-
// src/services/doctor.ts
|
|
330484
|
-
import { execSync } from "child_process";
|
|
330485
|
-
import { existsSync as existsSync3 } from "fs";
|
|
330486
|
-
import { platform as platform2 } from "os";
|
|
330487
|
-
var C2 = {
|
|
330488
|
-
reset: "\x1B[0m",
|
|
330489
|
-
bold: "\x1B[1m",
|
|
330490
|
-
green: "\x1B[32m",
|
|
330491
|
-
red: "\x1B[31m",
|
|
330492
|
-
yellow: "\x1B[33m",
|
|
330493
|
-
cyan: "\x1B[36m",
|
|
330494
|
-
gray: "\x1B[90m"
|
|
330495
|
-
};
|
|
330496
|
-
function run(cmd) {
|
|
330497
|
-
try {
|
|
330498
|
-
return execSync(cmd, { encoding: "utf-8", timeout: 15e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
330499
|
-
} catch {
|
|
330500
|
-
return null;
|
|
330501
|
-
}
|
|
330502
|
-
}
|
|
330503
|
-
function check(name, fn) {
|
|
330504
|
-
try {
|
|
330505
|
-
const result = fn();
|
|
330506
|
-
return { name, ...result };
|
|
330507
|
-
} catch (err) {
|
|
330508
|
-
return { name, ok: false, detail: err instanceof Error ? err.message : "Unknown error" };
|
|
330509
|
-
}
|
|
330510
|
-
}
|
|
330511
|
-
function checkWeb() {
|
|
330512
|
-
const results = [];
|
|
330513
|
-
results.push(check("Node.js", () => {
|
|
330514
|
-
const version = process.version;
|
|
330515
|
-
const major = parseInt(version.slice(1).split(".")[0], 10);
|
|
330516
|
-
return { ok: major >= 20, detail: `${version}${major < 20 ? " (>= 20 required)" : ""}` };
|
|
330517
|
-
}));
|
|
330518
|
-
results.push(check("Playwright", () => {
|
|
330519
|
-
const version = run("npx playwright --version");
|
|
330520
|
-
if (!version) return { ok: false, detail: "Not installed. Run: npm i -D playwright" };
|
|
330521
|
-
return { ok: true, detail: version };
|
|
330522
|
-
}));
|
|
330523
|
-
return results;
|
|
330524
|
-
}
|
|
330525
|
-
function checkIOS() {
|
|
330526
|
-
const results = [];
|
|
330527
|
-
const isMac = platform2() === "darwin";
|
|
330528
|
-
results.push(check("macOS", () => ({
|
|
330529
|
-
ok: isMac,
|
|
330530
|
-
detail: isMac ? `${platform2()} detected` : "iOS testing requires macOS"
|
|
330531
|
-
})));
|
|
330532
|
-
if (!isMac) return results;
|
|
330533
|
-
results.push(check("Xcode", () => {
|
|
330534
|
-
const version = run("xcodebuild -version");
|
|
330535
|
-
if (!version) return { ok: false, detail: "Not installed. Install from App Store." };
|
|
330536
|
-
const firstLine = version.split("\n")[0];
|
|
330537
|
-
return { ok: true, detail: firstLine };
|
|
330538
|
-
}));
|
|
330539
|
-
results.push(check("Xcode Command Line Tools", () => {
|
|
330540
|
-
const path = run("xcode-select -p");
|
|
330541
|
-
return { ok: !!path, detail: path || "Not installed. Run: xcode-select --install" };
|
|
330542
|
-
}));
|
|
330543
|
-
results.push(check("Appium xcuitest driver", () => {
|
|
330544
|
-
const drivers = run("appium driver list --installed --json");
|
|
330545
|
-
if (!drivers) return { ok: false, detail: "Appium not found. Run: npm i -g appium" };
|
|
330546
|
-
try {
|
|
330547
|
-
const parsed = JSON.parse(drivers);
|
|
330548
|
-
const hasXcuitest = parsed?.xcuitest || typeof parsed === "object" && Object.keys(parsed).some((k) => k.toLowerCase().includes("xcuitest"));
|
|
330549
|
-
return {
|
|
330550
|
-
ok: !!hasXcuitest,
|
|
330551
|
-
detail: hasXcuitest ? "Installed" : "Not installed. Run: appium driver install xcuitest"
|
|
330552
|
-
};
|
|
330553
|
-
} catch {
|
|
330554
|
-
const hasXcuitest = drivers.toLowerCase().includes("xcuitest");
|
|
330555
|
-
return {
|
|
330556
|
-
ok: hasXcuitest,
|
|
330557
|
-
detail: hasXcuitest ? "Installed" : "Not installed. Run: appium driver install xcuitest"
|
|
330558
|
-
};
|
|
330559
|
-
}
|
|
330560
|
-
}));
|
|
330561
|
-
results.push(check("iOS Devices", () => {
|
|
330562
|
-
const json = run("xcrun simctl list devices booted --json");
|
|
330563
|
-
let bootedSimulators = 0;
|
|
330564
|
-
if (json) {
|
|
330565
|
-
try {
|
|
330566
|
-
const parsed = JSON.parse(json);
|
|
330567
|
-
const devices = Object.values(parsed.devices ?? {}).flat();
|
|
330568
|
-
bootedSimulators = devices.filter((d) => d.state === "Booted").length;
|
|
330569
|
-
} catch {
|
|
330570
|
-
}
|
|
330571
|
-
}
|
|
330572
|
-
const realDevices = discoverRealIOSDevices().length;
|
|
330573
|
-
const total = bootedSimulators + realDevices;
|
|
330574
|
-
if (total === 0) {
|
|
330575
|
-
return {
|
|
330576
|
-
ok: false,
|
|
330577
|
-
detail: "No devices found. Boot a simulator or connect a real device via USB."
|
|
330578
|
-
};
|
|
330579
|
-
}
|
|
330580
|
-
const details = [];
|
|
330581
|
-
if (bootedSimulators > 0) details.push(`${bootedSimulators} simulator(s)`);
|
|
330582
|
-
if (realDevices > 0) details.push(`${realDevices} real device(s)`);
|
|
330583
|
-
return {
|
|
330584
|
-
ok: true,
|
|
330585
|
-
detail: `${total} device(s) available: ${details.join(", ")}`
|
|
330586
|
-
};
|
|
330587
|
-
}));
|
|
330588
|
-
return results;
|
|
330589
|
-
}
|
|
330590
|
-
function checkAndroid() {
|
|
330591
|
-
const results = [];
|
|
330592
|
-
const androidHome = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
|
|
330593
|
-
results.push(check("ANDROID_HOME", () => ({
|
|
330594
|
-
ok: !!androidHome && existsSync3(androidHome),
|
|
330595
|
-
detail: androidHome ? existsSync3(androidHome) ? androidHome : `${androidHome} (not found)` : "Not set. Set ANDROID_HOME to your Android SDK path."
|
|
330596
|
-
})));
|
|
330597
|
-
if (!androidHome || !existsSync3(androidHome)) return results;
|
|
330598
|
-
results.push(check("adb", () => {
|
|
330599
|
-
const version = run("adb version");
|
|
330600
|
-
if (!version) return { ok: false, detail: "Not found in PATH. Ensure Android platform-tools are installed." };
|
|
330601
|
-
const firstLine = version.split("\n")[0];
|
|
330602
|
-
return { ok: true, detail: firstLine };
|
|
330603
|
-
}));
|
|
330604
|
-
results.push(check("Appium uiautomator2 driver", () => {
|
|
330605
|
-
const drivers = run("appium driver list --installed --json");
|
|
330606
|
-
if (!drivers) return { ok: false, detail: "Appium not found. Run: npm i -g appium" };
|
|
330607
|
-
try {
|
|
330608
|
-
const parsed = JSON.parse(drivers);
|
|
330609
|
-
const hasUia2 = parsed?.uiautomator2 || typeof parsed === "object" && Object.keys(parsed).some((k) => k.toLowerCase().includes("uiautomator2"));
|
|
330610
|
-
return {
|
|
330611
|
-
ok: !!hasUia2,
|
|
330612
|
-
detail: hasUia2 ? "Installed" : "Not installed. Run: appium driver install uiautomator2"
|
|
330613
|
-
};
|
|
330614
|
-
} catch {
|
|
330615
|
-
const hasUia2 = drivers.toLowerCase().includes("uiautomator2");
|
|
330616
|
-
return {
|
|
330617
|
-
ok: hasUia2,
|
|
330618
|
-
detail: hasUia2 ? "Installed" : "Not installed. Run: appium driver install uiautomator2"
|
|
330619
|
-
};
|
|
330620
|
-
}
|
|
330621
|
-
}));
|
|
330622
|
-
results.push(check("Android Devices", () => {
|
|
330623
|
-
const output = run("adb devices");
|
|
330624
|
-
if (!output) return { ok: false, detail: "adb devices failed" };
|
|
330625
|
-
const lines = output.split("\n").filter((l) => l.includes(" device"));
|
|
330626
|
-
const emulators = lines.filter((l) => l.startsWith("emulator-"));
|
|
330627
|
-
const realDevices = lines.filter((l) => !l.startsWith("emulator-") && l.includes(" device"));
|
|
330628
|
-
const total = emulators.length + realDevices.length;
|
|
330629
|
-
if (total === 0) {
|
|
330630
|
-
return {
|
|
330631
|
-
ok: false,
|
|
330632
|
-
detail: "No devices found. Start an emulator or connect a real device via USB with USB debugging enabled."
|
|
330633
|
-
};
|
|
330634
|
-
}
|
|
330635
|
-
const details = [];
|
|
330636
|
-
if (emulators.length > 0) details.push(`${emulators.length} emulator(s)`);
|
|
330637
|
-
if (realDevices.length > 0) details.push(`${realDevices.length} real device(s)`);
|
|
330638
|
-
return {
|
|
330639
|
-
ok: true,
|
|
330640
|
-
detail: `${total} device(s) available: ${details.join(", ")}`
|
|
330641
|
-
};
|
|
330642
|
-
}));
|
|
330643
|
-
return results;
|
|
330644
|
-
}
|
|
330645
|
-
function checkTunnel() {
|
|
330646
|
-
const version = run("ngrok version") || run("ngrok --version");
|
|
330647
|
-
if (!version) {
|
|
330648
|
-
return [{
|
|
330649
|
-
name: "ngrok",
|
|
330650
|
-
ok: false,
|
|
330651
|
-
detail: "Not installed (only needed for --tunnel with an HTTPS dashboard). Install: brew install ngrok && ngrok config add-authtoken <token>"
|
|
330652
|
-
}];
|
|
330653
|
-
}
|
|
330654
|
-
const match = version.match(/(\d+)\.\d+\.\d+/);
|
|
330655
|
-
const major = match ? parseInt(match[1], 10) : 0;
|
|
330656
|
-
return [{
|
|
330657
|
-
name: "ngrok",
|
|
330658
|
-
ok: major >= 3,
|
|
330659
|
-
detail: major >= 3 ? `${version} (configure an authtoken with: ngrok config add-authtoken <token>)` : `${version} \u2014 ngrok v3+ is recommended (brew install ngrok)`
|
|
330660
|
-
}];
|
|
330661
|
-
}
|
|
330662
|
-
function runDoctor() {
|
|
330663
|
-
const web = checkWeb();
|
|
330664
|
-
const ios = checkIOS();
|
|
330665
|
-
const android = checkAndroid();
|
|
330666
|
-
const tunnel = checkTunnel();
|
|
330667
|
-
const allPassed = [...web, ...ios, ...android].every((c) => c.ok);
|
|
330668
|
-
return { web, ios, android, tunnel, allPassed };
|
|
330669
|
-
}
|
|
330670
|
-
function printDoctorReport(report) {
|
|
330671
|
-
console.log("");
|
|
330672
|
-
console.log(`${C2.bold}${C2.cyan}\u{1F3E5} validate.qa Doctor${C2.reset}`);
|
|
330673
|
-
console.log(`${C2.gray}${"\u2500".repeat(40)}${C2.reset}`);
|
|
330674
|
-
const printSection = (title, checks) => {
|
|
330675
|
-
console.log("");
|
|
330676
|
-
console.log(` ${C2.bold}${title}:${C2.reset}`);
|
|
330677
|
-
for (const c of checks) {
|
|
330678
|
-
const icon = c.ok ? `${C2.green}\u2713${C2.reset}` : `${C2.red}\u2717${C2.reset}`;
|
|
330679
|
-
console.log(` ${icon} ${c.name}: ${c.detail}`);
|
|
330680
|
-
}
|
|
330681
|
-
};
|
|
330682
|
-
printSection("Web Testing", report.web);
|
|
330683
|
-
printSection("iOS Testing", report.ios);
|
|
330684
|
-
printSection("Android Testing", report.android);
|
|
330685
|
-
printSection("Tunnel (optional \u2014 only for --tunnel / HTTPS dashboard)", report.tunnel);
|
|
330686
|
-
console.log("");
|
|
330687
|
-
if (report.allPassed) {
|
|
330688
|
-
console.log(` ${C2.green}\u2705 All checks passed - ready for web + mobile testing${C2.reset}`);
|
|
330689
|
-
} else {
|
|
330690
|
-
const failCount = [...report.web, ...report.ios, ...report.android].filter((c) => !c.ok).length;
|
|
330691
|
-
console.log(` ${C2.yellow}\u26A0 ${failCount} check(s) need attention${C2.reset}`);
|
|
330692
|
-
}
|
|
330693
|
-
console.log("");
|
|
330694
|
-
}
|
|
330695
|
-
|
|
330696
330773
|
// src/cli.ts
|
|
330697
330774
|
var pkg = require_package4();
|
|
330698
330775
|
var program = new Command();
|