@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.js
CHANGED
|
@@ -1405,7 +1405,7 @@ function selectorForValue(value) {
|
|
|
1405
1405
|
if (value.startsWith("~") || value.startsWith("id="))
|
|
1406
1406
|
return value;
|
|
1407
1407
|
if (value.includes(":id/"))
|
|
1408
|
-
return `id=${value
|
|
1408
|
+
return `id=${value}`;
|
|
1409
1409
|
return `~${value}`;
|
|
1410
1410
|
}
|
|
1411
1411
|
function selectorCandidatesForStep(step) {
|
|
@@ -21738,7 +21738,7 @@ var require_util = __commonJS({
|
|
|
21738
21738
|
return path;
|
|
21739
21739
|
}
|
|
21740
21740
|
exports2.normalize = normalize;
|
|
21741
|
-
function
|
|
21741
|
+
function join5(aRoot, aPath) {
|
|
21742
21742
|
if (aRoot === "") {
|
|
21743
21743
|
aRoot = ".";
|
|
21744
21744
|
}
|
|
@@ -21770,7 +21770,7 @@ var require_util = __commonJS({
|
|
|
21770
21770
|
}
|
|
21771
21771
|
return joined;
|
|
21772
21772
|
}
|
|
21773
|
-
exports2.join =
|
|
21773
|
+
exports2.join = join5;
|
|
21774
21774
|
exports2.isAbsolute = function(aPath) {
|
|
21775
21775
|
return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
|
|
21776
21776
|
};
|
|
@@ -21943,7 +21943,7 @@ var require_util = __commonJS({
|
|
|
21943
21943
|
parsed.path = parsed.path.substring(0, index + 1);
|
|
21944
21944
|
}
|
|
21945
21945
|
}
|
|
21946
|
-
sourceURL =
|
|
21946
|
+
sourceURL = join5(urlGenerate(parsed), sourceURL);
|
|
21947
21947
|
}
|
|
21948
21948
|
return normalize(sourceURL);
|
|
21949
21949
|
}
|
|
@@ -243098,7 +243098,7 @@ var require_thread_stream = __commonJS({
|
|
|
243098
243098
|
var { version } = require_package();
|
|
243099
243099
|
var { EventEmitter: EventEmitter2 } = require("events");
|
|
243100
243100
|
var { Worker } = require("worker_threads");
|
|
243101
|
-
var { join:
|
|
243101
|
+
var { join: join5 } = require("path");
|
|
243102
243102
|
var { pathToFileURL } = require("url");
|
|
243103
243103
|
var { wait } = require_wait();
|
|
243104
243104
|
var {
|
|
@@ -243149,7 +243149,7 @@ var require_thread_stream = __commonJS({
|
|
|
243149
243149
|
function createWorker(stream, opts) {
|
|
243150
243150
|
const { filename, workerData } = opts;
|
|
243151
243151
|
const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
|
|
243152
|
-
const toExecute = bundlerOverrides["thread-stream-worker"] ||
|
|
243152
|
+
const toExecute = bundlerOverrides["thread-stream-worker"] || join5(__dirname, "lib", "worker.js");
|
|
243153
243153
|
const worker = new Worker(toExecute, {
|
|
243154
243154
|
...opts.workerOpts,
|
|
243155
243155
|
name: opts.workerOpts?.name || "thread-stream",
|
|
@@ -243617,7 +243617,7 @@ var require_transport = __commonJS({
|
|
|
243617
243617
|
var { createRequire } = require("module");
|
|
243618
243618
|
var { existsSync: existsSync4 } = require("fs");
|
|
243619
243619
|
var getCallers = require_caller();
|
|
243620
|
-
var { join:
|
|
243620
|
+
var { join: join5, isAbsolute, sep } = require("path");
|
|
243621
243621
|
var { fileURLToPath } = require("url");
|
|
243622
243622
|
var sleep = require_atomic_sleep();
|
|
243623
243623
|
var onExit = require_on_exit_leak_free();
|
|
@@ -243770,7 +243770,7 @@ var require_transport = __commonJS({
|
|
|
243770
243770
|
throw new Error("only one of target or targets can be specified");
|
|
243771
243771
|
}
|
|
243772
243772
|
if (targets) {
|
|
243773
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
243773
|
+
target = bundlerOverrides["pino-worker"] || join5(__dirname, "worker.js");
|
|
243774
243774
|
options.targets = targets.filter((dest) => dest.target).map((dest) => {
|
|
243775
243775
|
return {
|
|
243776
243776
|
...dest,
|
|
@@ -243788,7 +243788,7 @@ var require_transport = __commonJS({
|
|
|
243788
243788
|
});
|
|
243789
243789
|
});
|
|
243790
243790
|
} else if (pipeline) {
|
|
243791
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
243791
|
+
target = bundlerOverrides["pino-worker"] || join5(__dirname, "worker.js");
|
|
243792
243792
|
options.pipelines = [pipeline.map((dest) => {
|
|
243793
243793
|
return {
|
|
243794
243794
|
...dest,
|
|
@@ -243811,7 +243811,7 @@ var require_transport = __commonJS({
|
|
|
243811
243811
|
return origin;
|
|
243812
243812
|
}
|
|
243813
243813
|
if (origin === "pino/file") {
|
|
243814
|
-
return
|
|
243814
|
+
return join5(__dirname, "..", "file.js");
|
|
243815
243815
|
}
|
|
243816
243816
|
let fixTarget2;
|
|
243817
243817
|
for (const filePath of callers) {
|
|
@@ -244792,7 +244792,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
244792
244792
|
return circularValue;
|
|
244793
244793
|
}
|
|
244794
244794
|
let res = "";
|
|
244795
|
-
let
|
|
244795
|
+
let join5 = ",";
|
|
244796
244796
|
const originalIndentation = indentation;
|
|
244797
244797
|
if (Array.isArray(value)) {
|
|
244798
244798
|
if (value.length === 0) {
|
|
@@ -244806,7 +244806,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
244806
244806
|
indentation += spacer;
|
|
244807
244807
|
res += `
|
|
244808
244808
|
${indentation}`;
|
|
244809
|
-
|
|
244809
|
+
join5 = `,
|
|
244810
244810
|
${indentation}`;
|
|
244811
244811
|
}
|
|
244812
244812
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -244814,13 +244814,13 @@ ${indentation}`;
|
|
|
244814
244814
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
244815
244815
|
const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
244816
244816
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
244817
|
-
res +=
|
|
244817
|
+
res += join5;
|
|
244818
244818
|
}
|
|
244819
244819
|
const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
244820
244820
|
res += tmp !== void 0 ? tmp : "null";
|
|
244821
244821
|
if (value.length - 1 > maximumBreadth) {
|
|
244822
244822
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
244823
|
-
res += `${
|
|
244823
|
+
res += `${join5}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
244824
244824
|
}
|
|
244825
244825
|
if (spacer !== "") {
|
|
244826
244826
|
res += `
|
|
@@ -244841,7 +244841,7 @@ ${originalIndentation}`;
|
|
|
244841
244841
|
let separator = "";
|
|
244842
244842
|
if (spacer !== "") {
|
|
244843
244843
|
indentation += spacer;
|
|
244844
|
-
|
|
244844
|
+
join5 = `,
|
|
244845
244845
|
${indentation}`;
|
|
244846
244846
|
whitespace = " ";
|
|
244847
244847
|
}
|
|
@@ -244855,13 +244855,13 @@ ${indentation}`;
|
|
|
244855
244855
|
const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
|
|
244856
244856
|
if (tmp !== void 0) {
|
|
244857
244857
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
244858
|
-
separator =
|
|
244858
|
+
separator = join5;
|
|
244859
244859
|
}
|
|
244860
244860
|
}
|
|
244861
244861
|
if (keyLength > maximumBreadth) {
|
|
244862
244862
|
const removedKeys = keyLength - maximumBreadth;
|
|
244863
244863
|
res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
|
|
244864
|
-
separator =
|
|
244864
|
+
separator = join5;
|
|
244865
244865
|
}
|
|
244866
244866
|
if (spacer !== "" && separator.length > 1) {
|
|
244867
244867
|
res = `
|
|
@@ -244902,7 +244902,7 @@ ${originalIndentation}`;
|
|
|
244902
244902
|
}
|
|
244903
244903
|
const originalIndentation = indentation;
|
|
244904
244904
|
let res = "";
|
|
244905
|
-
let
|
|
244905
|
+
let join5 = ",";
|
|
244906
244906
|
if (Array.isArray(value)) {
|
|
244907
244907
|
if (value.length === 0) {
|
|
244908
244908
|
return "[]";
|
|
@@ -244915,7 +244915,7 @@ ${originalIndentation}`;
|
|
|
244915
244915
|
indentation += spacer;
|
|
244916
244916
|
res += `
|
|
244917
244917
|
${indentation}`;
|
|
244918
|
-
|
|
244918
|
+
join5 = `,
|
|
244919
244919
|
${indentation}`;
|
|
244920
244920
|
}
|
|
244921
244921
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -244923,13 +244923,13 @@ ${indentation}`;
|
|
|
244923
244923
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
244924
244924
|
const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
244925
244925
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
244926
|
-
res +=
|
|
244926
|
+
res += join5;
|
|
244927
244927
|
}
|
|
244928
244928
|
const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
244929
244929
|
res += tmp !== void 0 ? tmp : "null";
|
|
244930
244930
|
if (value.length - 1 > maximumBreadth) {
|
|
244931
244931
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
244932
|
-
res += `${
|
|
244932
|
+
res += `${join5}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
244933
244933
|
}
|
|
244934
244934
|
if (spacer !== "") {
|
|
244935
244935
|
res += `
|
|
@@ -244942,7 +244942,7 @@ ${originalIndentation}`;
|
|
|
244942
244942
|
let whitespace = "";
|
|
244943
244943
|
if (spacer !== "") {
|
|
244944
244944
|
indentation += spacer;
|
|
244945
|
-
|
|
244945
|
+
join5 = `,
|
|
244946
244946
|
${indentation}`;
|
|
244947
244947
|
whitespace = " ";
|
|
244948
244948
|
}
|
|
@@ -244951,7 +244951,7 @@ ${indentation}`;
|
|
|
244951
244951
|
const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
|
|
244952
244952
|
if (tmp !== void 0) {
|
|
244953
244953
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
244954
|
-
separator =
|
|
244954
|
+
separator = join5;
|
|
244955
244955
|
}
|
|
244956
244956
|
}
|
|
244957
244957
|
if (spacer !== "" && separator.length > 1) {
|
|
@@ -245009,20 +245009,20 @@ ${originalIndentation}`;
|
|
|
245009
245009
|
indentation += spacer;
|
|
245010
245010
|
let res2 = `
|
|
245011
245011
|
${indentation}`;
|
|
245012
|
-
const
|
|
245012
|
+
const join6 = `,
|
|
245013
245013
|
${indentation}`;
|
|
245014
245014
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
245015
245015
|
let i = 0;
|
|
245016
245016
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
245017
245017
|
const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
245018
245018
|
res2 += tmp2 !== void 0 ? tmp2 : "null";
|
|
245019
|
-
res2 +=
|
|
245019
|
+
res2 += join6;
|
|
245020
245020
|
}
|
|
245021
245021
|
const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
245022
245022
|
res2 += tmp !== void 0 ? tmp : "null";
|
|
245023
245023
|
if (value.length - 1 > maximumBreadth) {
|
|
245024
245024
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
245025
|
-
res2 += `${
|
|
245025
|
+
res2 += `${join6}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
245026
245026
|
}
|
|
245027
245027
|
res2 += `
|
|
245028
245028
|
${originalIndentation}`;
|
|
@@ -245038,16 +245038,16 @@ ${originalIndentation}`;
|
|
|
245038
245038
|
return '"[Object]"';
|
|
245039
245039
|
}
|
|
245040
245040
|
indentation += spacer;
|
|
245041
|
-
const
|
|
245041
|
+
const join5 = `,
|
|
245042
245042
|
${indentation}`;
|
|
245043
245043
|
let res = "";
|
|
245044
245044
|
let separator = "";
|
|
245045
245045
|
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
|
|
245046
245046
|
if (isTypedArrayWithEntries(value)) {
|
|
245047
|
-
res += stringifyTypedArray(value,
|
|
245047
|
+
res += stringifyTypedArray(value, join5, maximumBreadth);
|
|
245048
245048
|
keys = keys.slice(value.length);
|
|
245049
245049
|
maximumPropertiesToStringify -= value.length;
|
|
245050
|
-
separator =
|
|
245050
|
+
separator = join5;
|
|
245051
245051
|
}
|
|
245052
245052
|
if (deterministic) {
|
|
245053
245053
|
keys = sort(keys, comparator);
|
|
@@ -245058,13 +245058,13 @@ ${indentation}`;
|
|
|
245058
245058
|
const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
|
|
245059
245059
|
if (tmp !== void 0) {
|
|
245060
245060
|
res += `${separator}${strEscape(key2)}: ${tmp}`;
|
|
245061
|
-
separator =
|
|
245061
|
+
separator = join5;
|
|
245062
245062
|
}
|
|
245063
245063
|
}
|
|
245064
245064
|
if (keyLength > maximumBreadth) {
|
|
245065
245065
|
const removedKeys = keyLength - maximumBreadth;
|
|
245066
245066
|
res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
|
|
245067
|
-
separator =
|
|
245067
|
+
separator = join5;
|
|
245068
245068
|
}
|
|
245069
245069
|
if (separator !== "") {
|
|
245070
245070
|
res = `
|
|
@@ -285421,11 +285421,11 @@ var require_core = __commonJS({
|
|
|
285421
285421
|
function mapOuter(r, f) {
|
|
285422
285422
|
return r.matched ? f(r) : r;
|
|
285423
285423
|
}
|
|
285424
|
-
function ab(pa, pb,
|
|
285425
|
-
return (data, i) => mapOuter(pa(data, i), (ma) => mapInner(pb(data, ma.position), (vb, j) =>
|
|
285424
|
+
function ab(pa, pb, join5) {
|
|
285425
|
+
return (data, i) => mapOuter(pa(data, i), (ma) => mapInner(pb(data, ma.position), (vb, j) => join5(ma.value, vb, data, i, j)));
|
|
285426
285426
|
}
|
|
285427
|
-
function abc(pa, pb, pc,
|
|
285428
|
-
return (data, i) => mapOuter(pa(data, i), (ma) => mapOuter(pb(data, ma.position), (mb) => mapInner(pc(data, mb.position), (vc, j) =>
|
|
285427
|
+
function abc(pa, pb, pc, join5) {
|
|
285428
|
+
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))));
|
|
285429
285429
|
}
|
|
285430
285430
|
function action(f) {
|
|
285431
285431
|
return (data, i) => {
|
|
@@ -312619,7 +312619,7 @@ var require_utils2 = __commonJS({
|
|
|
312619
312619
|
module2.exports = Utils;
|
|
312620
312620
|
Utils.prototype.makeDir = function(folder) {
|
|
312621
312621
|
const self = this;
|
|
312622
|
-
function
|
|
312622
|
+
function mkdirSync4(fpath) {
|
|
312623
312623
|
let resolvedPath = fpath.split(self.sep)[0];
|
|
312624
312624
|
fpath.split(self.sep).forEach(function(name) {
|
|
312625
312625
|
if (!name || name.substr(-1, 1) === ":") return;
|
|
@@ -312637,7 +312637,7 @@ var require_utils2 = __commonJS({
|
|
|
312637
312637
|
if (stat && stat.isFile()) throw Errors.FILE_IN_THE_WAY(`"${resolvedPath}"`);
|
|
312638
312638
|
});
|
|
312639
312639
|
}
|
|
312640
|
-
|
|
312640
|
+
mkdirSync4(folder);
|
|
312641
312641
|
};
|
|
312642
312642
|
Utils.prototype.writeFileTo = function(path, content, overwrite, attr) {
|
|
312643
312643
|
const self = this;
|
|
@@ -314256,8 +314256,8 @@ var require_adm_zip = __commonJS({
|
|
|
314256
314256
|
return null;
|
|
314257
314257
|
}
|
|
314258
314258
|
function fixPath(zipPath) {
|
|
314259
|
-
const { join:
|
|
314260
|
-
return
|
|
314259
|
+
const { join: join5, normalize, sep } = pth.posix;
|
|
314260
|
+
return join5(pth.isAbsolute(zipPath) ? "/" : ".", normalize(sep + zipPath.split("\\").join(sep) + sep));
|
|
314261
314261
|
}
|
|
314262
314262
|
function filenameFilter(filterfn) {
|
|
314263
314263
|
if (filterfn instanceof RegExp) {
|
|
@@ -319670,14 +319670,14 @@ var require_path_arg = __commonJS({
|
|
|
319670
319670
|
var require_find_made = __commonJS({
|
|
319671
319671
|
"../../node_modules/tar/node_modules/mkdirp/lib/find-made.js"(exports2, module2) {
|
|
319672
319672
|
"use strict";
|
|
319673
|
-
var { dirname } = require("path");
|
|
319673
|
+
var { dirname: dirname2 } = require("path");
|
|
319674
319674
|
var findMade = (opts, parent, path = void 0) => {
|
|
319675
319675
|
if (path === parent)
|
|
319676
319676
|
return Promise.resolve();
|
|
319677
319677
|
return opts.statAsync(parent).then(
|
|
319678
319678
|
(st) => st.isDirectory() ? path : void 0,
|
|
319679
319679
|
// will fail later
|
|
319680
|
-
(er) => er.code === "ENOENT" ? findMade(opts,
|
|
319680
|
+
(er) => er.code === "ENOENT" ? findMade(opts, dirname2(parent), parent) : void 0
|
|
319681
319681
|
);
|
|
319682
319682
|
};
|
|
319683
319683
|
var findMadeSync = (opts, parent, path = void 0) => {
|
|
@@ -319686,7 +319686,7 @@ var require_find_made = __commonJS({
|
|
|
319686
319686
|
try {
|
|
319687
319687
|
return opts.statSync(parent).isDirectory() ? path : void 0;
|
|
319688
319688
|
} catch (er) {
|
|
319689
|
-
return er.code === "ENOENT" ? findMadeSync(opts,
|
|
319689
|
+
return er.code === "ENOENT" ? findMadeSync(opts, dirname2(parent), parent) : void 0;
|
|
319690
319690
|
}
|
|
319691
319691
|
};
|
|
319692
319692
|
module2.exports = { findMade, findMadeSync };
|
|
@@ -319697,10 +319697,10 @@ var require_find_made = __commonJS({
|
|
|
319697
319697
|
var require_mkdirp_manual = __commonJS({
|
|
319698
319698
|
"../../node_modules/tar/node_modules/mkdirp/lib/mkdirp-manual.js"(exports2, module2) {
|
|
319699
319699
|
"use strict";
|
|
319700
|
-
var { dirname } = require("path");
|
|
319700
|
+
var { dirname: dirname2 } = require("path");
|
|
319701
319701
|
var mkdirpManual = (path, opts, made) => {
|
|
319702
319702
|
opts.recursive = false;
|
|
319703
|
-
const parent =
|
|
319703
|
+
const parent = dirname2(path);
|
|
319704
319704
|
if (parent === path) {
|
|
319705
319705
|
return opts.mkdirAsync(path, opts).catch((er) => {
|
|
319706
319706
|
if (er.code !== "EISDIR")
|
|
@@ -319723,7 +319723,7 @@ var require_mkdirp_manual = __commonJS({
|
|
|
319723
319723
|
});
|
|
319724
319724
|
};
|
|
319725
319725
|
var mkdirpManualSync = (path, opts, made) => {
|
|
319726
|
-
const parent =
|
|
319726
|
+
const parent = dirname2(path);
|
|
319727
319727
|
opts.recursive = false;
|
|
319728
319728
|
if (parent === path) {
|
|
319729
319729
|
try {
|
|
@@ -319759,12 +319759,12 @@ var require_mkdirp_manual = __commonJS({
|
|
|
319759
319759
|
var require_mkdirp_native = __commonJS({
|
|
319760
319760
|
"../../node_modules/tar/node_modules/mkdirp/lib/mkdirp-native.js"(exports2, module2) {
|
|
319761
319761
|
"use strict";
|
|
319762
|
-
var { dirname } = require("path");
|
|
319762
|
+
var { dirname: dirname2 } = require("path");
|
|
319763
319763
|
var { findMade, findMadeSync } = require_find_made();
|
|
319764
319764
|
var { mkdirpManual, mkdirpManualSync } = require_mkdirp_manual();
|
|
319765
319765
|
var mkdirpNative = (path, opts) => {
|
|
319766
319766
|
opts.recursive = true;
|
|
319767
|
-
const parent =
|
|
319767
|
+
const parent = dirname2(path);
|
|
319768
319768
|
if (parent === path)
|
|
319769
319769
|
return opts.mkdirAsync(path, opts);
|
|
319770
319770
|
return findMade(opts, path).then((made) => opts.mkdirAsync(path, opts).then(() => made).catch((er) => {
|
|
@@ -319776,7 +319776,7 @@ var require_mkdirp_native = __commonJS({
|
|
|
319776
319776
|
};
|
|
319777
319777
|
var mkdirpNativeSync = (path, opts) => {
|
|
319778
319778
|
opts.recursive = true;
|
|
319779
|
-
const parent =
|
|
319779
|
+
const parent = dirname2(path);
|
|
319780
319780
|
if (parent === path)
|
|
319781
319781
|
return opts.mkdirSync(path, opts);
|
|
319782
319782
|
const made = findMadeSync(opts, path);
|
|
@@ -320181,7 +320181,7 @@ var require_path_reservations = __commonJS({
|
|
|
320181
320181
|
var assert = require("assert");
|
|
320182
320182
|
var normalize = require_normalize_unicode();
|
|
320183
320183
|
var stripSlashes = require_strip_trailing_slashes();
|
|
320184
|
-
var { join:
|
|
320184
|
+
var { join: join5 } = require("path");
|
|
320185
320185
|
var platform3 = process.env.TESTING_TAR_FAKE_PLATFORM || process.platform;
|
|
320186
320186
|
var isWindows = platform3 === "win32";
|
|
320187
320187
|
module2.exports = () => {
|
|
@@ -320190,7 +320190,7 @@ var require_path_reservations = __commonJS({
|
|
|
320190
320190
|
const getDirs = (path) => {
|
|
320191
320191
|
const dirs = path.split("/").slice(0, -1).reduce((set, path2) => {
|
|
320192
320192
|
if (set.length) {
|
|
320193
|
-
path2 =
|
|
320193
|
+
path2 = join5(set[set.length - 1], path2);
|
|
320194
320194
|
}
|
|
320195
320195
|
set.push(path2 || "/");
|
|
320196
320196
|
return set;
|
|
@@ -320258,7 +320258,7 @@ var require_path_reservations = __commonJS({
|
|
|
320258
320258
|
};
|
|
320259
320259
|
const reserve = (paths, fn) => {
|
|
320260
320260
|
paths = isWindows ? ["win32 parallelization disabled"] : paths.map((p) => {
|
|
320261
|
-
return stripSlashes(
|
|
320261
|
+
return stripSlashes(join5(normalize(p))).toLowerCase();
|
|
320262
320262
|
});
|
|
320263
320263
|
const dirs = new Set(
|
|
320264
320264
|
paths.map((path) => getDirs(path)).reduce((a, b) => a.concat(b))
|
|
@@ -324796,7 +324796,7 @@ var require_package4 = __commonJS({
|
|
|
324796
324796
|
"package.json"(exports2, module2) {
|
|
324797
324797
|
module2.exports = {
|
|
324798
324798
|
name: "@validate.qa/runner",
|
|
324799
|
-
version: "1.0.
|
|
324799
|
+
version: "1.0.3",
|
|
324800
324800
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
324801
324801
|
bin: {
|
|
324802
324802
|
"validate-runner": "dist/cli.js"
|
|
@@ -324804,7 +324804,7 @@ var require_package4 = __commonJS({
|
|
|
324804
324804
|
scripts: {
|
|
324805
324805
|
build: "tsup",
|
|
324806
324806
|
dev: "tsx src/cli.ts",
|
|
324807
|
-
"test:mobile": "node --import tsx --test src/services/mobile/mobile-network-capture.smoke.test.ts",
|
|
324807
|
+
"test:mobile": "node --import tsx --test src/runner.mobile-ai-env.test.ts src/services/mobile/mobile-network-capture.smoke.test.ts",
|
|
324808
324808
|
prepublishOnly: "npm run build"
|
|
324809
324809
|
},
|
|
324810
324810
|
files: [
|
|
@@ -324882,9 +324882,9 @@ function getConfigDir() {
|
|
|
324882
324882
|
}
|
|
324883
324883
|
|
|
324884
324884
|
// src/runner.ts
|
|
324885
|
-
var
|
|
324886
|
-
var
|
|
324887
|
-
var
|
|
324885
|
+
var import_fs4 = require("fs");
|
|
324886
|
+
var import_path3 = require("path");
|
|
324887
|
+
var import_os5 = require("os");
|
|
324888
324888
|
var import_node_crypto2 = require("crypto");
|
|
324889
324889
|
|
|
324890
324890
|
// src/services/playwright-native.ts
|
|
@@ -325297,11 +325297,12 @@ function discoverRealIOSDevices() {
|
|
|
325297
325297
|
for (const [index, node] of iosNodes.entries()) {
|
|
325298
325298
|
const serial = typeof node.serial_num === "string" && node.serial_num.trim() ? node.serial_num.trim() : `usb-ios-device-${index + 1}`;
|
|
325299
325299
|
const name = typeof node._name === "string" && node._name.trim() ? node._name.trim() : "iOS Device";
|
|
325300
|
+
const version = runCommand("ideviceinfo", ["-u", serial, "-k", "ProductVersion"]) || "unknown";
|
|
325300
325301
|
devices.push({
|
|
325301
325302
|
id: serial,
|
|
325302
325303
|
name,
|
|
325303
325304
|
platform: "IOS",
|
|
325304
|
-
platformVersion:
|
|
325305
|
+
platformVersion: version,
|
|
325305
325306
|
state: "BOOTED",
|
|
325306
325307
|
isAvailable: true,
|
|
325307
325308
|
isPhysical: true
|
|
@@ -325376,7 +325377,7 @@ function parseAdbDevices() {
|
|
|
325376
325377
|
const output = runCommand("adb", ["devices", "-l"]);
|
|
325377
325378
|
if (!output) return [];
|
|
325378
325379
|
const lines = output.split("\n").slice(1);
|
|
325379
|
-
return lines.filter((line) => line.
|
|
325380
|
+
return lines.filter((line) => line.trim().length > 0).map((line) => {
|
|
325380
325381
|
const parts = line.trim().split(/\s+/);
|
|
325381
325382
|
const serial = parts[0];
|
|
325382
325383
|
const type = parts[1] ?? "";
|
|
@@ -325392,26 +325393,25 @@ function parseAdbDevices() {
|
|
|
325392
325393
|
device: props["device"],
|
|
325393
325394
|
transportId: props["transport_id"]
|
|
325394
325395
|
};
|
|
325395
|
-
}).filter((d) => d.type === "device");
|
|
325396
|
+
}).filter((d) => d.type === "device" || d.type === "unauthorized");
|
|
325396
325397
|
}
|
|
325397
325398
|
function getAndroidProp(serial, prop) {
|
|
325398
325399
|
return runCommand("adb", ["-s", serial, "shell", "getprop", prop]);
|
|
325399
325400
|
}
|
|
325400
325401
|
function discoverAndroidDevices() {
|
|
325401
325402
|
const adbDevices = parseAdbDevices();
|
|
325402
|
-
return adbDevices.filter((d) => {
|
|
325403
|
-
return d.type === "device";
|
|
325404
|
-
}).map((d) => {
|
|
325403
|
+
return adbDevices.filter((d) => d.type === "device" || d.type === "unauthorized").map((d) => {
|
|
325405
325404
|
const isEmulator = d.serial.startsWith("emulator-");
|
|
325405
|
+
const isAvailable = d.type === "device";
|
|
325406
325406
|
const name = d.model || d.device || (isEmulator ? "Android Emulator" : "Android Device");
|
|
325407
|
-
const platformVersion = getAndroidProp(d.serial, "ro.build.version.release") || "unknown";
|
|
325407
|
+
const platformVersion = isAvailable ? getAndroidProp(d.serial, "ro.build.version.release") || "unknown" : "unknown";
|
|
325408
325408
|
return {
|
|
325409
325409
|
id: d.serial,
|
|
325410
325410
|
name,
|
|
325411
325411
|
platform: "ANDROID",
|
|
325412
325412
|
platformVersion,
|
|
325413
325413
|
state: "BOOTED",
|
|
325414
|
-
isAvailable
|
|
325414
|
+
isAvailable,
|
|
325415
325415
|
// Mark if it's a real device vs emulator
|
|
325416
325416
|
...isEmulator ? {} : { isPhysical: true }
|
|
325417
325417
|
};
|
|
@@ -325444,10 +325444,14 @@ function discoverAllDevices(options) {
|
|
|
325444
325444
|
const androidDevices = discoverAndroidDevices();
|
|
325445
325445
|
if (options?.includeApps) {
|
|
325446
325446
|
for (const device of iosDevices) {
|
|
325447
|
-
|
|
325447
|
+
if (device.isAvailable) {
|
|
325448
|
+
device.installedApps = listIOSInstalledApps(device.id, !!device.isPhysical);
|
|
325449
|
+
}
|
|
325448
325450
|
}
|
|
325449
325451
|
for (const device of androidDevices) {
|
|
325450
|
-
|
|
325452
|
+
if (device.isAvailable) {
|
|
325453
|
+
device.installedApps = listAndroidInstalledApps(device.id);
|
|
325454
|
+
}
|
|
325451
325455
|
}
|
|
325452
325456
|
}
|
|
325453
325457
|
return [...iosDevices, ...androidDevices];
|
|
@@ -325455,11 +325459,12 @@ function discoverAllDevices(options) {
|
|
|
325455
325459
|
function getRunnerCapabilities() {
|
|
325456
325460
|
const iosDevices = discoverIOSDevices();
|
|
325457
325461
|
const androidDevices = discoverAndroidDevices();
|
|
325462
|
+
const isReadyDevice = (device) => device.isAvailable && device.state === "BOOTED";
|
|
325458
325463
|
return {
|
|
325459
325464
|
web: true,
|
|
325460
325465
|
// Always capable of web testing via Playwright
|
|
325461
|
-
ios: iosDevices.
|
|
325462
|
-
android: androidDevices.
|
|
325466
|
+
ios: iosDevices.some(isReadyDevice),
|
|
325467
|
+
android: androidDevices.some(isReadyDevice)
|
|
325463
325468
|
};
|
|
325464
325469
|
}
|
|
325465
325470
|
|
|
@@ -325861,7 +325866,7 @@ async function executeMobileTest(options) {
|
|
|
325861
325866
|
const devices = discoverAllDevices({ includeApps: true });
|
|
325862
325867
|
const platformFilter = options.target.platform;
|
|
325863
325868
|
const matchingDevices = devices.filter(
|
|
325864
|
-
(d) => d.platform === platformFilter && d.state === "BOOTED"
|
|
325869
|
+
(d) => d.platform === platformFilter && d.isAvailable && d.state === "BOOTED"
|
|
325865
325870
|
);
|
|
325866
325871
|
if (matchingDevices.length === 0) {
|
|
325867
325872
|
throw new Error(
|
|
@@ -326025,7 +326030,7 @@ async function createMobileDiscoverySession(target) {
|
|
|
326025
326030
|
const devices = discoverAllDevices({ includeApps: true });
|
|
326026
326031
|
const platformFilter = target.platform;
|
|
326027
326032
|
const matchingDevices = devices.filter(
|
|
326028
|
-
(d) => d.platform === platformFilter && d.state === "BOOTED"
|
|
326033
|
+
(d) => d.platform === platformFilter && d.isAvailable && d.state === "BOOTED"
|
|
326029
326034
|
);
|
|
326030
326035
|
if (matchingDevices.length === 0) {
|
|
326031
326036
|
throw new Error(
|
|
@@ -326076,6 +326081,9 @@ async function createMobileDiscoverySession(target) {
|
|
|
326076
326081
|
var import_http = require("http");
|
|
326077
326082
|
var import_child_process3 = require("child_process");
|
|
326078
326083
|
var import_crypto = require("crypto");
|
|
326084
|
+
var import_os3 = require("os");
|
|
326085
|
+
var import_path2 = require("path");
|
|
326086
|
+
var import_fs2 = require("fs");
|
|
326079
326087
|
init_dist();
|
|
326080
326088
|
|
|
326081
326089
|
// src/services/mobile/appium-actions.ts
|
|
@@ -326361,30 +326369,50 @@ async function getWindowSize3() {
|
|
|
326361
326369
|
return getWindowSize2(`/session/${state.appiumSessionId}`);
|
|
326362
326370
|
}
|
|
326363
326371
|
var BRIDGE_PROCESS_MARKER = "validateqa-mobile-bridge";
|
|
326364
|
-
|
|
326365
|
-
|
|
326372
|
+
var BRIDGE_PORT = MOBILE_BRIDGE_PORT2;
|
|
326373
|
+
var BRIDGE_PID_FILE = (0, import_path2.join)((0, import_os3.homedir)(), ".validate.qa", "mobile-bridge.pid");
|
|
326374
|
+
function readBridgePidFile() {
|
|
326366
326375
|
try {
|
|
326367
|
-
const
|
|
326368
|
-
|
|
326369
|
-
|
|
326370
|
-
|
|
326371
|
-
|
|
326372
|
-
|
|
326373
|
-
|
|
326374
|
-
|
|
326375
|
-
|
|
326376
|
-
|
|
326377
|
-
|
|
326378
|
-
|
|
326379
|
-
|
|
326380
|
-
|
|
326381
|
-
|
|
326382
|
-
|
|
326383
|
-
|
|
326384
|
-
|
|
326385
|
-
|
|
326376
|
+
const raw = (0, import_fs2.readFileSync)(BRIDGE_PID_FILE, "utf-8").trim();
|
|
326377
|
+
const pid = parseInt(raw, 10);
|
|
326378
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
326379
|
+
} catch {
|
|
326380
|
+
return null;
|
|
326381
|
+
}
|
|
326382
|
+
}
|
|
326383
|
+
function writeBridgePidFile(pid) {
|
|
326384
|
+
try {
|
|
326385
|
+
(0, import_fs2.mkdirSync)((0, import_path2.dirname)(BRIDGE_PID_FILE), { recursive: true, mode: 448 });
|
|
326386
|
+
(0, import_fs2.writeFileSync)(BRIDGE_PID_FILE, String(pid), { mode: 384 });
|
|
326387
|
+
} catch {
|
|
326388
|
+
}
|
|
326389
|
+
}
|
|
326390
|
+
function clearBridgePidFile() {
|
|
326391
|
+
try {
|
|
326392
|
+
(0, import_fs2.unlinkSync)(BRIDGE_PID_FILE);
|
|
326393
|
+
} catch {
|
|
326394
|
+
}
|
|
326395
|
+
}
|
|
326396
|
+
function killStaleBridge() {
|
|
326397
|
+
const pid = readBridgePidFile();
|
|
326398
|
+
if (!pid) return;
|
|
326399
|
+
try {
|
|
326400
|
+
(0, import_child_process3.execFileSync)("kill", ["-0", String(pid)], { stdio: "ignore" });
|
|
326401
|
+
} catch {
|
|
326402
|
+
clearBridgePidFile();
|
|
326403
|
+
return;
|
|
326404
|
+
}
|
|
326405
|
+
try {
|
|
326406
|
+
const pidsOnPort = (0, import_child_process3.execFileSync)("lsof", ["-ti", `:${BRIDGE_PORT}`], { encoding: "utf-8" }).trim();
|
|
326407
|
+
const pidList = pidsOnPort.split("\n").filter(Boolean);
|
|
326408
|
+
if (!pidList.includes(String(pid))) {
|
|
326409
|
+
return;
|
|
326386
326410
|
}
|
|
326411
|
+
(0, import_child_process3.execFileSync)("kill", ["-9", String(pid)], { stdio: "ignore" });
|
|
326412
|
+
(0, import_child_process3.execFileSync)("sleep", ["1"], { stdio: "ignore" });
|
|
326387
326413
|
} catch {
|
|
326414
|
+
} finally {
|
|
326415
|
+
clearBridgePidFile();
|
|
326388
326416
|
}
|
|
326389
326417
|
}
|
|
326390
326418
|
function parseBody(req) {
|
|
@@ -327009,7 +327037,7 @@ async function startMobileBridge(options) {
|
|
|
327009
327037
|
if (!process.title.includes(BRIDGE_PROCESS_MARKER)) {
|
|
327010
327038
|
process.title = `${process.title} ${BRIDGE_PROCESS_MARKER}`;
|
|
327011
327039
|
}
|
|
327012
|
-
|
|
327040
|
+
killStaleBridge();
|
|
327013
327041
|
return new Promise((resolve, reject) => {
|
|
327014
327042
|
server2 = (0, import_http.createServer)(route);
|
|
327015
327043
|
server2.on("error", (err) => {
|
|
@@ -327025,6 +327053,7 @@ async function startMobileBridge(options) {
|
|
|
327025
327053
|
state.port = port;
|
|
327026
327054
|
state.tunnelToken = tunnelToken;
|
|
327027
327055
|
state.tunnelUrl = void 0;
|
|
327056
|
+
writeBridgePidFile(process.pid);
|
|
327028
327057
|
let tunnelUrl;
|
|
327029
327058
|
if (enableTunnel) {
|
|
327030
327059
|
try {
|
|
@@ -327092,6 +327121,7 @@ async function startMobileBridge(options) {
|
|
|
327092
327121
|
let checkInterval = null;
|
|
327093
327122
|
const timeout = setTimeout(() => {
|
|
327094
327123
|
if (checkInterval) clearInterval(checkInterval);
|
|
327124
|
+
checkInterval = null;
|
|
327095
327125
|
if (!tunnelUrl) {
|
|
327096
327126
|
reject2(new Error("ngrok tunnel failed to start"));
|
|
327097
327127
|
}
|
|
@@ -327100,6 +327130,7 @@ async function startMobileBridge(options) {
|
|
|
327100
327130
|
if (tunnelUrl) {
|
|
327101
327131
|
clearTimeout(timeout);
|
|
327102
327132
|
if (checkInterval) clearInterval(checkInterval);
|
|
327133
|
+
checkInterval = null;
|
|
327103
327134
|
resolve2();
|
|
327104
327135
|
}
|
|
327105
327136
|
}, 500);
|
|
@@ -327129,6 +327160,7 @@ async function startMobileBridge(options) {
|
|
|
327129
327160
|
async function stopMobileBridge() {
|
|
327130
327161
|
stopAllMirrorClients();
|
|
327131
327162
|
stopSessionReaper();
|
|
327163
|
+
clearBridgePidFile();
|
|
327132
327164
|
if (state.appiumSessionId) {
|
|
327133
327165
|
try {
|
|
327134
327166
|
validateSessionId(state.appiumSessionId);
|
|
@@ -327989,6 +328021,219 @@ async function startMobileNetworkCapture(options) {
|
|
|
327989
328021
|
};
|
|
327990
328022
|
}
|
|
327991
328023
|
|
|
328024
|
+
// src/services/doctor.ts
|
|
328025
|
+
var import_child_process4 = require("child_process");
|
|
328026
|
+
var import_fs3 = require("fs");
|
|
328027
|
+
var import_os4 = require("os");
|
|
328028
|
+
var C = {
|
|
328029
|
+
reset: "\x1B[0m",
|
|
328030
|
+
bold: "\x1B[1m",
|
|
328031
|
+
green: "\x1B[32m",
|
|
328032
|
+
red: "\x1B[31m",
|
|
328033
|
+
yellow: "\x1B[33m",
|
|
328034
|
+
cyan: "\x1B[36m",
|
|
328035
|
+
gray: "\x1B[90m"
|
|
328036
|
+
};
|
|
328037
|
+
function run(cmd) {
|
|
328038
|
+
try {
|
|
328039
|
+
return (0, import_child_process4.execSync)(cmd, { encoding: "utf-8", timeout: 15e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
328040
|
+
} catch {
|
|
328041
|
+
return null;
|
|
328042
|
+
}
|
|
328043
|
+
}
|
|
328044
|
+
function check(name, fn) {
|
|
328045
|
+
try {
|
|
328046
|
+
const result = fn();
|
|
328047
|
+
return { name, ...result };
|
|
328048
|
+
} catch (err) {
|
|
328049
|
+
return { name, ok: false, detail: err instanceof Error ? err.message : "Unknown error" };
|
|
328050
|
+
}
|
|
328051
|
+
}
|
|
328052
|
+
function checkWeb() {
|
|
328053
|
+
const results = [];
|
|
328054
|
+
results.push(check("Node.js", () => {
|
|
328055
|
+
const version = process.version;
|
|
328056
|
+
const major = parseInt(version.slice(1).split(".")[0], 10);
|
|
328057
|
+
return { ok: major >= 20, detail: `${version}${major < 20 ? " (>= 20 required)" : ""}` };
|
|
328058
|
+
}));
|
|
328059
|
+
results.push(check("Playwright", () => {
|
|
328060
|
+
const version = run("npx playwright --version");
|
|
328061
|
+
if (!version) return { ok: false, detail: "Not installed. Run: npm i -D playwright" };
|
|
328062
|
+
return { ok: true, detail: version };
|
|
328063
|
+
}));
|
|
328064
|
+
return results;
|
|
328065
|
+
}
|
|
328066
|
+
function checkIOS() {
|
|
328067
|
+
const results = [];
|
|
328068
|
+
const isMac = (0, import_os4.platform)() === "darwin";
|
|
328069
|
+
results.push(check("macOS", () => ({
|
|
328070
|
+
ok: isMac,
|
|
328071
|
+
detail: isMac ? `${(0, import_os4.platform)()} detected` : "iOS testing requires macOS"
|
|
328072
|
+
})));
|
|
328073
|
+
if (!isMac) return results;
|
|
328074
|
+
results.push(check("Xcode", () => {
|
|
328075
|
+
const version = run("xcodebuild -version");
|
|
328076
|
+
if (!version) return { ok: false, detail: "Not installed. Install from App Store." };
|
|
328077
|
+
const firstLine = version.split("\n")[0];
|
|
328078
|
+
return { ok: true, detail: firstLine };
|
|
328079
|
+
}));
|
|
328080
|
+
results.push(check("Xcode Command Line Tools", () => {
|
|
328081
|
+
const path = run("xcode-select -p");
|
|
328082
|
+
return { ok: !!path, detail: path || "Not installed. Run: xcode-select --install" };
|
|
328083
|
+
}));
|
|
328084
|
+
results.push(check("Appium xcuitest driver", () => {
|
|
328085
|
+
const drivers = run("appium driver list --installed --json");
|
|
328086
|
+
if (!drivers) return { ok: false, detail: "Appium not found. Run: npm i -g appium" };
|
|
328087
|
+
try {
|
|
328088
|
+
const parsed = JSON.parse(drivers);
|
|
328089
|
+
const hasXcuitest = parsed?.xcuitest || typeof parsed === "object" && Object.keys(parsed).some((k) => k.toLowerCase().includes("xcuitest"));
|
|
328090
|
+
return {
|
|
328091
|
+
ok: !!hasXcuitest,
|
|
328092
|
+
detail: hasXcuitest ? "Installed" : "Not installed. Run: appium driver install xcuitest"
|
|
328093
|
+
};
|
|
328094
|
+
} catch {
|
|
328095
|
+
const hasXcuitest = drivers.toLowerCase().includes("xcuitest");
|
|
328096
|
+
return {
|
|
328097
|
+
ok: hasXcuitest,
|
|
328098
|
+
detail: hasXcuitest ? "Installed" : "Not installed. Run: appium driver install xcuitest"
|
|
328099
|
+
};
|
|
328100
|
+
}
|
|
328101
|
+
}));
|
|
328102
|
+
results.push(check("iOS Devices", () => {
|
|
328103
|
+
const json = run("xcrun simctl list devices booted --json");
|
|
328104
|
+
let bootedSimulators = 0;
|
|
328105
|
+
if (json) {
|
|
328106
|
+
try {
|
|
328107
|
+
const parsed = JSON.parse(json);
|
|
328108
|
+
const devices = Object.values(parsed.devices ?? {}).flat();
|
|
328109
|
+
bootedSimulators = devices.filter((d) => d.state === "Booted").length;
|
|
328110
|
+
} catch {
|
|
328111
|
+
}
|
|
328112
|
+
}
|
|
328113
|
+
const realDevices = discoverRealIOSDevices().length;
|
|
328114
|
+
const total = bootedSimulators + realDevices;
|
|
328115
|
+
if (total === 0) {
|
|
328116
|
+
return {
|
|
328117
|
+
ok: false,
|
|
328118
|
+
detail: "No devices found. Boot a simulator or connect a real device via USB."
|
|
328119
|
+
};
|
|
328120
|
+
}
|
|
328121
|
+
const details = [];
|
|
328122
|
+
if (bootedSimulators > 0) details.push(`${bootedSimulators} simulator(s)`);
|
|
328123
|
+
if (realDevices > 0) details.push(`${realDevices} real device(s)`);
|
|
328124
|
+
return {
|
|
328125
|
+
ok: true,
|
|
328126
|
+
detail: `${total} device(s) available: ${details.join(", ")}`
|
|
328127
|
+
};
|
|
328128
|
+
}));
|
|
328129
|
+
return results;
|
|
328130
|
+
}
|
|
328131
|
+
function checkAndroid() {
|
|
328132
|
+
const results = [];
|
|
328133
|
+
const androidHome = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
|
|
328134
|
+
results.push(check("ANDROID_HOME", () => ({
|
|
328135
|
+
ok: !!androidHome && (0, import_fs3.existsSync)(androidHome),
|
|
328136
|
+
detail: androidHome ? (0, import_fs3.existsSync)(androidHome) ? androidHome : `${androidHome} (not found)` : "Not set. Set ANDROID_HOME to your Android SDK path."
|
|
328137
|
+
})));
|
|
328138
|
+
if (!androidHome || !(0, import_fs3.existsSync)(androidHome)) return results;
|
|
328139
|
+
results.push(check("adb", () => {
|
|
328140
|
+
const version = run("adb version");
|
|
328141
|
+
if (!version) return { ok: false, detail: "Not found in PATH. Ensure Android platform-tools are installed." };
|
|
328142
|
+
const firstLine = version.split("\n")[0];
|
|
328143
|
+
return { ok: true, detail: firstLine };
|
|
328144
|
+
}));
|
|
328145
|
+
results.push(check("Appium uiautomator2 driver", () => {
|
|
328146
|
+
const drivers = run("appium driver list --installed --json");
|
|
328147
|
+
if (!drivers) return { ok: false, detail: "Appium not found. Run: npm i -g appium" };
|
|
328148
|
+
try {
|
|
328149
|
+
const parsed = JSON.parse(drivers);
|
|
328150
|
+
const hasUia2 = parsed?.uiautomator2 || typeof parsed === "object" && Object.keys(parsed).some((k) => k.toLowerCase().includes("uiautomator2"));
|
|
328151
|
+
return {
|
|
328152
|
+
ok: !!hasUia2,
|
|
328153
|
+
detail: hasUia2 ? "Installed" : "Not installed. Run: appium driver install uiautomator2"
|
|
328154
|
+
};
|
|
328155
|
+
} catch {
|
|
328156
|
+
const hasUia2 = drivers.toLowerCase().includes("uiautomator2");
|
|
328157
|
+
return {
|
|
328158
|
+
ok: hasUia2,
|
|
328159
|
+
detail: hasUia2 ? "Installed" : "Not installed. Run: appium driver install uiautomator2"
|
|
328160
|
+
};
|
|
328161
|
+
}
|
|
328162
|
+
}));
|
|
328163
|
+
results.push(check("Android Devices", () => {
|
|
328164
|
+
const output = run("adb devices");
|
|
328165
|
+
if (!output) return { ok: false, detail: "adb devices failed" };
|
|
328166
|
+
const lines = output.split("\n").filter((l) => l.includes(" device"));
|
|
328167
|
+
const emulators = lines.filter((l) => l.startsWith("emulator-"));
|
|
328168
|
+
const realDevices = lines.filter((l) => !l.startsWith("emulator-") && l.includes(" device"));
|
|
328169
|
+
const total = emulators.length + realDevices.length;
|
|
328170
|
+
if (total === 0) {
|
|
328171
|
+
return {
|
|
328172
|
+
ok: false,
|
|
328173
|
+
detail: "No devices found. Start an emulator or connect a real device via USB with USB debugging enabled."
|
|
328174
|
+
};
|
|
328175
|
+
}
|
|
328176
|
+
const details = [];
|
|
328177
|
+
if (emulators.length > 0) details.push(`${emulators.length} emulator(s)`);
|
|
328178
|
+
if (realDevices.length > 0) details.push(`${realDevices.length} real device(s)`);
|
|
328179
|
+
return {
|
|
328180
|
+
ok: true,
|
|
328181
|
+
detail: `${total} device(s) available: ${details.join(", ")}`
|
|
328182
|
+
};
|
|
328183
|
+
}));
|
|
328184
|
+
return results;
|
|
328185
|
+
}
|
|
328186
|
+
function checkTunnel() {
|
|
328187
|
+
const version = run("ngrok version") || run("ngrok --version");
|
|
328188
|
+
if (!version) {
|
|
328189
|
+
return [{
|
|
328190
|
+
name: "ngrok",
|
|
328191
|
+
ok: false,
|
|
328192
|
+
detail: "Not installed (only needed for --tunnel with an HTTPS dashboard). Install: brew install ngrok && ngrok config add-authtoken <token>"
|
|
328193
|
+
}];
|
|
328194
|
+
}
|
|
328195
|
+
const match = version.match(/(\d+)\.\d+\.\d+/);
|
|
328196
|
+
const major = match ? parseInt(match[1], 10) : 0;
|
|
328197
|
+
return [{
|
|
328198
|
+
name: "ngrok",
|
|
328199
|
+
ok: major >= 3,
|
|
328200
|
+
detail: major >= 3 ? `${version} (configure an authtoken with: ngrok config add-authtoken <token>)` : `${version} \u2014 ngrok v3+ is recommended (brew install ngrok)`
|
|
328201
|
+
}];
|
|
328202
|
+
}
|
|
328203
|
+
function runDoctor() {
|
|
328204
|
+
const web = checkWeb();
|
|
328205
|
+
const ios = checkIOS();
|
|
328206
|
+
const android = checkAndroid();
|
|
328207
|
+
const tunnel = checkTunnel();
|
|
328208
|
+
const allPassed = [...web, ...ios, ...android].every((c) => c.ok);
|
|
328209
|
+
return { web, ios, android, tunnel, allPassed };
|
|
328210
|
+
}
|
|
328211
|
+
function printDoctorReport(report) {
|
|
328212
|
+
console.log("");
|
|
328213
|
+
console.log(`${C.bold}${C.cyan}\u{1F3E5} validate.qa Doctor${C.reset}`);
|
|
328214
|
+
console.log(`${C.gray}${"\u2500".repeat(40)}${C.reset}`);
|
|
328215
|
+
const printSection = (title, checks) => {
|
|
328216
|
+
console.log("");
|
|
328217
|
+
console.log(` ${C.bold}${title}:${C.reset}`);
|
|
328218
|
+
for (const c of checks) {
|
|
328219
|
+
const icon = c.ok ? `${C.green}\u2713${C.reset}` : `${C.red}\u2717${C.reset}`;
|
|
328220
|
+
console.log(` ${icon} ${c.name}: ${c.detail}`);
|
|
328221
|
+
}
|
|
328222
|
+
};
|
|
328223
|
+
printSection("Web Testing", report.web);
|
|
328224
|
+
printSection("iOS Testing", report.ios);
|
|
328225
|
+
printSection("Android Testing", report.android);
|
|
328226
|
+
printSection("Tunnel (optional \u2014 only for --tunnel / HTTPS dashboard)", report.tunnel);
|
|
328227
|
+
console.log("");
|
|
328228
|
+
if (report.allPassed) {
|
|
328229
|
+
console.log(` ${C.green}\u2705 All checks passed - ready for web + mobile testing${C.reset}`);
|
|
328230
|
+
} else {
|
|
328231
|
+
const failCount = [...report.web, ...report.ios, ...report.android].filter((c) => !c.ok).length;
|
|
328232
|
+
console.log(` ${C.yellow}\u26A0 ${failCount} check(s) need attention${C.reset}`);
|
|
328233
|
+
}
|
|
328234
|
+
console.log("");
|
|
328235
|
+
}
|
|
328236
|
+
|
|
327992
328237
|
// src/runner.ts
|
|
327993
328238
|
init_dist();
|
|
327994
328239
|
var import_runner_core11 = __toESM(require_dist2());
|
|
@@ -328078,7 +328323,7 @@ var browserPool = new import_runner_core6.BrowserPool({
|
|
|
328078
328323
|
maxContextsPerBrowser: envInt("MAX_CONTEXTS_PER_BROWSER", DEFAULT_MAX_CONTEXTS_PER_BROWSER),
|
|
328079
328324
|
retireAfterContexts: 30
|
|
328080
328325
|
});
|
|
328081
|
-
var
|
|
328326
|
+
var C2 = {
|
|
328082
328327
|
reset: "\x1B[0m",
|
|
328083
328328
|
bold: "\x1B[1m",
|
|
328084
328329
|
green: "\x1B[32m",
|
|
@@ -328088,25 +328333,25 @@ var C = {
|
|
|
328088
328333
|
gray: "\x1B[90m"
|
|
328089
328334
|
};
|
|
328090
328335
|
var log = {
|
|
328091
|
-
info: (msg) => console.log(`${
|
|
328092
|
-
ok: (msg) => console.log(`${
|
|
328093
|
-
fail: (msg) => console.log(`${
|
|
328094
|
-
warn: (msg) => console.log(`${
|
|
328095
|
-
dim: (msg) => console.log(`${
|
|
328336
|
+
info: (msg) => console.log(`${C2.cyan}\u2139${C2.reset} ${msg}`),
|
|
328337
|
+
ok: (msg) => console.log(`${C2.green}\u2713${C2.reset} ${msg}`),
|
|
328338
|
+
fail: (msg) => console.log(`${C2.red}\u2717${C2.reset} ${msg}`),
|
|
328339
|
+
warn: (msg) => console.log(`${C2.yellow}\u26A0${C2.reset} ${msg}`),
|
|
328340
|
+
dim: (msg) => console.log(`${C2.gray}${msg}${C2.reset}`)
|
|
328096
328341
|
};
|
|
328097
328342
|
var AUTH_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
328098
328343
|
function getRunsDir() {
|
|
328099
|
-
const dir = process.env.RUNS_DIR || (0,
|
|
328100
|
-
if (!(0,
|
|
328344
|
+
const dir = process.env.RUNS_DIR || (0, import_path3.join)((0, import_os5.homedir)(), ".validate.qa", "runs");
|
|
328345
|
+
if (!(0, import_fs4.existsSync)(dir)) (0, import_fs4.mkdirSync)(dir, { recursive: true });
|
|
328101
328346
|
return dir;
|
|
328102
328347
|
}
|
|
328103
328348
|
function authStatePath(projectId) {
|
|
328104
|
-
return (0,
|
|
328349
|
+
return (0, import_path3.join)(getRunsDir(), `auth-state-${projectId || "default"}.json`);
|
|
328105
328350
|
}
|
|
328106
328351
|
function getValidAuthState(projectId) {
|
|
328107
328352
|
const path = authStatePath(projectId);
|
|
328108
|
-
if (!(0,
|
|
328109
|
-
const age = Date.now() - (0,
|
|
328353
|
+
if (!(0, import_fs4.existsSync)(path)) return void 0;
|
|
328354
|
+
const age = Date.now() - (0, import_fs4.statSync)(path).mtimeMs;
|
|
328110
328355
|
return age < AUTH_MAX_AGE_MS ? path : void 0;
|
|
328111
328356
|
}
|
|
328112
328357
|
function buildHeaders(authToken, projectId) {
|
|
@@ -329048,9 +329293,9 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
329048
329293
|
(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")
|
|
329049
329294
|
);
|
|
329050
329295
|
if (phaseLines.length > 0) {
|
|
329051
|
-
console.log(`${
|
|
329296
|
+
console.log(`${C2.gray}\u2500\u2500 Heal summary \u2500\u2500${C2.reset}`);
|
|
329052
329297
|
for (const line of phaseLines) console.log(line);
|
|
329053
|
-
console.log(`${
|
|
329298
|
+
console.log(`${C2.gray}\u2500\u2500 End summary \u2500\u2500\u2500${C2.reset}`);
|
|
329054
329299
|
}
|
|
329055
329300
|
}
|
|
329056
329301
|
log[result2.passed ? "ok" : "fail"](
|
|
@@ -329589,11 +329834,18 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
329589
329834
|
}
|
|
329590
329835
|
try {
|
|
329591
329836
|
if (isNativeMobile) {
|
|
329592
|
-
const platform3 = mobileMedium === "IOS_NATIVE" ? "IOS" : "ANDROID";
|
|
329593
329837
|
const mobileTarget = ctx.target;
|
|
329594
329838
|
if (!mobileTarget || !mobileTarget.appId) {
|
|
329595
329839
|
throw new Error("Native mobile discovery requires a target with an appId (bundleId/appPackage).");
|
|
329596
329840
|
}
|
|
329841
|
+
const targetPlatform = mobileTarget.platform;
|
|
329842
|
+
const platform3 = mobileMedium === "IOS_NATIVE" ? "IOS" : mobileMedium === "ANDROID_NATIVE" ? "ANDROID" : targetPlatform === "IOS" || targetPlatform === "ANDROID" ? targetPlatform : null;
|
|
329843
|
+
if (!platform3) {
|
|
329844
|
+
throw new Error("Native mobile discovery requires medium IOS_NATIVE/ANDROID_NATIVE or target.platform IOS/ANDROID.");
|
|
329845
|
+
}
|
|
329846
|
+
if (mobileMedium === "IOS_NATIVE" && targetPlatform === "ANDROID" || mobileMedium === "ANDROID_NATIVE" && targetPlatform === "IOS") {
|
|
329847
|
+
throw new Error("Native mobile discovery medium does not match target.platform.");
|
|
329848
|
+
}
|
|
329597
329849
|
if (getBridgeState().hasActiveSession) {
|
|
329598
329850
|
throw new Error("CONFIG_ISSUE: the shared mobile device is busy with an active recording session; mobile discovery will retry once it ends.");
|
|
329599
329851
|
}
|
|
@@ -330022,9 +330274,9 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
330022
330274
|
} else {
|
|
330023
330275
|
log.fail(`FAILED (${duration}ms)${result.error ? ` - ${result.error}` : ""}`);
|
|
330024
330276
|
if (result.logs) {
|
|
330025
|
-
console.log(`${
|
|
330277
|
+
console.log(`${C2.gray}\u2500\u2500 Runner logs \u2500\u2500${C2.reset}`);
|
|
330026
330278
|
console.log(result.logs);
|
|
330027
|
-
console.log(`${
|
|
330279
|
+
console.log(`${C2.gray}\u2500\u2500 End logs \u2500\u2500\u2500\u2500\u2500${C2.reset}`);
|
|
330028
330280
|
}
|
|
330029
330281
|
}
|
|
330030
330282
|
} catch (err) {
|
|
@@ -330046,20 +330298,24 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
330046
330298
|
}
|
|
330047
330299
|
async function startRunner(opts) {
|
|
330048
330300
|
const { serverUrl, authToken, projectId, pollIntervalMs = 2e3, runnerMode = "playwright-native", enableMobile = false, enableMobileTunnel = false } = opts;
|
|
330301
|
+
process.env.SERVER_URL = serverUrl;
|
|
330302
|
+
process.env.AUTH_TOKEN = authToken;
|
|
330303
|
+
process.env.RUNNER_TOKEN = authToken;
|
|
330304
|
+
if (projectId) process.env.PROJECT_ID = projectId;
|
|
330049
330305
|
const headers = buildHeaders(authToken, projectId);
|
|
330050
330306
|
console.log("");
|
|
330051
|
-
console.log(`${
|
|
330052
|
-
console.log(`${
|
|
330307
|
+
console.log(`${C2.bold}${C2.cyan}\u{1F399}\uFE0F validate.qa Local Runner${C2.reset}`);
|
|
330308
|
+
console.log(`${C2.gray}${"\u2500".repeat(40)}${C2.reset}`);
|
|
330053
330309
|
log.info(`Server: ${serverUrl}`);
|
|
330054
330310
|
log.info(`Project: ${projectId}`);
|
|
330055
330311
|
log.info(`Mode: \u{1F3AD} Playwright Native${enableMobile ? " + \u{1F4F1} Mobile" : ""}${enableMobile && enableMobileTunnel ? " \u{1F310} Tunnel" : ""}`);
|
|
330056
330312
|
log.info(`Interval: ${pollIntervalMs}ms`);
|
|
330057
330313
|
if (process.env.DISCOVERY_PROMPT_V3 === "true") {
|
|
330058
|
-
log.ok("Discovery: V3
|
|
330314
|
+
log.ok("Discovery prompt: V3 (per-turn rebuild) \u2014 DISCOVERY_PROMPT_V3=true");
|
|
330059
330315
|
} else if (process.env.DISCOVERY_PROMPT_V3 === "false") {
|
|
330060
|
-
log.ok("Discovery: V2.5
|
|
330316
|
+
log.ok("Discovery prompt: V2.5 (full transcript + control brief) \u2014 DISCOVERY_PROMPT_V3=false");
|
|
330061
330317
|
} else {
|
|
330062
|
-
log.ok("Discovery:
|
|
330318
|
+
log.ok("Discovery prompt: V3 (per-turn rebuild, default) \u2014 set DISCOVERY_PROMPT_V3=false for V2.5");
|
|
330063
330319
|
}
|
|
330064
330320
|
if ((0, import_runner_core5.hasUsableChromeProfile)()) {
|
|
330065
330321
|
log.ok(`Browser: Chrome with user profile (cookies/logins persist)`);
|
|
@@ -330071,8 +330327,8 @@ async function startRunner(opts) {
|
|
|
330071
330327
|
const devices = discoverAllDevices();
|
|
330072
330328
|
const capabilities = {
|
|
330073
330329
|
web: true,
|
|
330074
|
-
ios: devices.some((d) => d.platform === "IOS"),
|
|
330075
|
-
android: devices.some((d) => d.platform === "ANDROID")
|
|
330330
|
+
ios: devices.some((d) => d.platform === "IOS" && d.isAvailable && d.state === "BOOTED"),
|
|
330331
|
+
android: devices.some((d) => d.platform === "ANDROID" && d.isAvailable && d.state === "BOOTED")
|
|
330076
330332
|
};
|
|
330077
330333
|
log.info(`\u{1F4F1} Mobile: iOS ${capabilities.ios ? "\u2713" : "\u2717"} | Android ${capabilities.android ? "\u2713" : "\u2717"} | ${devices.length} device(s)`);
|
|
330078
330334
|
try {
|
|
@@ -330133,6 +330389,33 @@ async function startRunner(opts) {
|
|
|
330133
330389
|
const startedAt = Date.now();
|
|
330134
330390
|
const MAX_BACKOFF_MS = 6e4;
|
|
330135
330391
|
let consecutivePollErrors = 0;
|
|
330392
|
+
let cachedDoctor = null;
|
|
330393
|
+
const DOCTOR_TTL_MS = 5 * 60 * 1e3;
|
|
330394
|
+
let doctorRefreshInFlight = false;
|
|
330395
|
+
const refreshDoctorInBackground = () => {
|
|
330396
|
+
if (doctorRefreshInFlight) return;
|
|
330397
|
+
doctorRefreshInFlight = true;
|
|
330398
|
+
setImmediate(() => {
|
|
330399
|
+
try {
|
|
330400
|
+
const report = runDoctor();
|
|
330401
|
+
cachedDoctor = { report, at: Date.now() };
|
|
330402
|
+
} catch (err) {
|
|
330403
|
+
log.dim(` [doctor] refresh failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
330404
|
+
} finally {
|
|
330405
|
+
doctorRefreshInFlight = false;
|
|
330406
|
+
}
|
|
330407
|
+
});
|
|
330408
|
+
};
|
|
330409
|
+
const getCachedDoctor = () => {
|
|
330410
|
+
if (!enableMobile) return void 0;
|
|
330411
|
+
if (!cachedDoctor) {
|
|
330412
|
+
refreshDoctorInBackground();
|
|
330413
|
+
return void 0;
|
|
330414
|
+
}
|
|
330415
|
+
const now = Date.now();
|
|
330416
|
+
if (now - cachedDoctor.at >= DOCTOR_TTL_MS) refreshDoctorInBackground();
|
|
330417
|
+
return cachedDoctor.report;
|
|
330418
|
+
};
|
|
330136
330419
|
log.info(`Concurrency: ${RESOURCE_GOVERNOR_OPTIONS.adaptive ? `adaptive (${RESOURCE_GOVERNOR_OPTIONS.maxUnits} weighted units, hard cap ${MAX_CONCURRENT})` : `static (hard cap ${MAX_CONCURRENT})`}`);
|
|
330137
330420
|
const poolSize = envInt("BROWSER_POOL_SIZE", DEFAULT_BROWSER_POOL_SIZE);
|
|
330138
330421
|
if (poolSize > 0) {
|
|
@@ -330241,7 +330524,11 @@ async function startRunner(opts) {
|
|
|
330241
330524
|
const sendHeartbeat = async () => {
|
|
330242
330525
|
try {
|
|
330243
330526
|
const devices = enableMobile ? discoverAllDevices() : [];
|
|
330244
|
-
const capabilities = enableMobile ? {
|
|
330527
|
+
const capabilities = enableMobile ? {
|
|
330528
|
+
web: true,
|
|
330529
|
+
ios: devices.some((d) => d.platform === "IOS" && d.isAvailable && d.state === "BOOTED"),
|
|
330530
|
+
android: devices.some((d) => d.platform === "ANDROID" && d.isAvailable && d.state === "BOOTED")
|
|
330531
|
+
} : { web: true, ios: false, android: false };
|
|
330245
330532
|
const bridge = enableMobile ? getBridgeState() : void 0;
|
|
330246
330533
|
const resourceStatus = (0, import_runner_core11.getResourceGovernorStatus)(RESOURCE_GOVERNOR_OPTIONS, Object.fromEntries(activeByType));
|
|
330247
330534
|
const processInventory = await (0, import_runner_core11.getBrowserProcessInventory)();
|
|
@@ -330272,7 +330559,10 @@ async function startRunner(opts) {
|
|
|
330272
330559
|
resourceGovernor: resourceStatus.governor,
|
|
330273
330560
|
processInventory,
|
|
330274
330561
|
aiQueue: (0, import_runner_core11.getRunnerAIQueueStats)(),
|
|
330275
|
-
liveBrowsers: import_runner_core10.liveBrowserRegistry.snapshotForHeartbeat()
|
|
330562
|
+
liveBrowsers: import_runner_core10.liveBrowserRegistry.snapshotForHeartbeat(),
|
|
330563
|
+
// Pre-flight checks (ANDROID_HOME, Xcode, Appium drivers, adb, etc).
|
|
330564
|
+
// Cached & cheap — see getCachedDoctor above.
|
|
330565
|
+
doctor: getCachedDoctor()
|
|
330276
330566
|
}),
|
|
330277
330567
|
signal: AbortSignal.timeout(1e4)
|
|
330278
330568
|
});
|
|
@@ -330472,219 +330762,6 @@ async function startRunner(opts) {
|
|
|
330472
330762
|
}
|
|
330473
330763
|
}
|
|
330474
330764
|
|
|
330475
|
-
// src/services/doctor.ts
|
|
330476
|
-
var import_child_process4 = require("child_process");
|
|
330477
|
-
var import_fs3 = require("fs");
|
|
330478
|
-
var import_os4 = require("os");
|
|
330479
|
-
var C2 = {
|
|
330480
|
-
reset: "\x1B[0m",
|
|
330481
|
-
bold: "\x1B[1m",
|
|
330482
|
-
green: "\x1B[32m",
|
|
330483
|
-
red: "\x1B[31m",
|
|
330484
|
-
yellow: "\x1B[33m",
|
|
330485
|
-
cyan: "\x1B[36m",
|
|
330486
|
-
gray: "\x1B[90m"
|
|
330487
|
-
};
|
|
330488
|
-
function run(cmd) {
|
|
330489
|
-
try {
|
|
330490
|
-
return (0, import_child_process4.execSync)(cmd, { encoding: "utf-8", timeout: 15e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
330491
|
-
} catch {
|
|
330492
|
-
return null;
|
|
330493
|
-
}
|
|
330494
|
-
}
|
|
330495
|
-
function check(name, fn) {
|
|
330496
|
-
try {
|
|
330497
|
-
const result = fn();
|
|
330498
|
-
return { name, ...result };
|
|
330499
|
-
} catch (err) {
|
|
330500
|
-
return { name, ok: false, detail: err instanceof Error ? err.message : "Unknown error" };
|
|
330501
|
-
}
|
|
330502
|
-
}
|
|
330503
|
-
function checkWeb() {
|
|
330504
|
-
const results = [];
|
|
330505
|
-
results.push(check("Node.js", () => {
|
|
330506
|
-
const version = process.version;
|
|
330507
|
-
const major = parseInt(version.slice(1).split(".")[0], 10);
|
|
330508
|
-
return { ok: major >= 20, detail: `${version}${major < 20 ? " (>= 20 required)" : ""}` };
|
|
330509
|
-
}));
|
|
330510
|
-
results.push(check("Playwright", () => {
|
|
330511
|
-
const version = run("npx playwright --version");
|
|
330512
|
-
if (!version) return { ok: false, detail: "Not installed. Run: npm i -D playwright" };
|
|
330513
|
-
return { ok: true, detail: version };
|
|
330514
|
-
}));
|
|
330515
|
-
return results;
|
|
330516
|
-
}
|
|
330517
|
-
function checkIOS() {
|
|
330518
|
-
const results = [];
|
|
330519
|
-
const isMac = (0, import_os4.platform)() === "darwin";
|
|
330520
|
-
results.push(check("macOS", () => ({
|
|
330521
|
-
ok: isMac,
|
|
330522
|
-
detail: isMac ? `${(0, import_os4.platform)()} detected` : "iOS testing requires macOS"
|
|
330523
|
-
})));
|
|
330524
|
-
if (!isMac) return results;
|
|
330525
|
-
results.push(check("Xcode", () => {
|
|
330526
|
-
const version = run("xcodebuild -version");
|
|
330527
|
-
if (!version) return { ok: false, detail: "Not installed. Install from App Store." };
|
|
330528
|
-
const firstLine = version.split("\n")[0];
|
|
330529
|
-
return { ok: true, detail: firstLine };
|
|
330530
|
-
}));
|
|
330531
|
-
results.push(check("Xcode Command Line Tools", () => {
|
|
330532
|
-
const path = run("xcode-select -p");
|
|
330533
|
-
return { ok: !!path, detail: path || "Not installed. Run: xcode-select --install" };
|
|
330534
|
-
}));
|
|
330535
|
-
results.push(check("Appium xcuitest driver", () => {
|
|
330536
|
-
const drivers = run("appium driver list --installed --json");
|
|
330537
|
-
if (!drivers) return { ok: false, detail: "Appium not found. Run: npm i -g appium" };
|
|
330538
|
-
try {
|
|
330539
|
-
const parsed = JSON.parse(drivers);
|
|
330540
|
-
const hasXcuitest = parsed?.xcuitest || typeof parsed === "object" && Object.keys(parsed).some((k) => k.toLowerCase().includes("xcuitest"));
|
|
330541
|
-
return {
|
|
330542
|
-
ok: !!hasXcuitest,
|
|
330543
|
-
detail: hasXcuitest ? "Installed" : "Not installed. Run: appium driver install xcuitest"
|
|
330544
|
-
};
|
|
330545
|
-
} catch {
|
|
330546
|
-
const hasXcuitest = drivers.toLowerCase().includes("xcuitest");
|
|
330547
|
-
return {
|
|
330548
|
-
ok: hasXcuitest,
|
|
330549
|
-
detail: hasXcuitest ? "Installed" : "Not installed. Run: appium driver install xcuitest"
|
|
330550
|
-
};
|
|
330551
|
-
}
|
|
330552
|
-
}));
|
|
330553
|
-
results.push(check("iOS Devices", () => {
|
|
330554
|
-
const json = run("xcrun simctl list devices booted --json");
|
|
330555
|
-
let bootedSimulators = 0;
|
|
330556
|
-
if (json) {
|
|
330557
|
-
try {
|
|
330558
|
-
const parsed = JSON.parse(json);
|
|
330559
|
-
const devices = Object.values(parsed.devices ?? {}).flat();
|
|
330560
|
-
bootedSimulators = devices.filter((d) => d.state === "Booted").length;
|
|
330561
|
-
} catch {
|
|
330562
|
-
}
|
|
330563
|
-
}
|
|
330564
|
-
const realDevices = discoverRealIOSDevices().length;
|
|
330565
|
-
const total = bootedSimulators + realDevices;
|
|
330566
|
-
if (total === 0) {
|
|
330567
|
-
return {
|
|
330568
|
-
ok: false,
|
|
330569
|
-
detail: "No devices found. Boot a simulator or connect a real device via USB."
|
|
330570
|
-
};
|
|
330571
|
-
}
|
|
330572
|
-
const details = [];
|
|
330573
|
-
if (bootedSimulators > 0) details.push(`${bootedSimulators} simulator(s)`);
|
|
330574
|
-
if (realDevices > 0) details.push(`${realDevices} real device(s)`);
|
|
330575
|
-
return {
|
|
330576
|
-
ok: true,
|
|
330577
|
-
detail: `${total} device(s) available: ${details.join(", ")}`
|
|
330578
|
-
};
|
|
330579
|
-
}));
|
|
330580
|
-
return results;
|
|
330581
|
-
}
|
|
330582
|
-
function checkAndroid() {
|
|
330583
|
-
const results = [];
|
|
330584
|
-
const androidHome = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
|
|
330585
|
-
results.push(check("ANDROID_HOME", () => ({
|
|
330586
|
-
ok: !!androidHome && (0, import_fs3.existsSync)(androidHome),
|
|
330587
|
-
detail: androidHome ? (0, import_fs3.existsSync)(androidHome) ? androidHome : `${androidHome} (not found)` : "Not set. Set ANDROID_HOME to your Android SDK path."
|
|
330588
|
-
})));
|
|
330589
|
-
if (!androidHome || !(0, import_fs3.existsSync)(androidHome)) return results;
|
|
330590
|
-
results.push(check("adb", () => {
|
|
330591
|
-
const version = run("adb version");
|
|
330592
|
-
if (!version) return { ok: false, detail: "Not found in PATH. Ensure Android platform-tools are installed." };
|
|
330593
|
-
const firstLine = version.split("\n")[0];
|
|
330594
|
-
return { ok: true, detail: firstLine };
|
|
330595
|
-
}));
|
|
330596
|
-
results.push(check("Appium uiautomator2 driver", () => {
|
|
330597
|
-
const drivers = run("appium driver list --installed --json");
|
|
330598
|
-
if (!drivers) return { ok: false, detail: "Appium not found. Run: npm i -g appium" };
|
|
330599
|
-
try {
|
|
330600
|
-
const parsed = JSON.parse(drivers);
|
|
330601
|
-
const hasUia2 = parsed?.uiautomator2 || typeof parsed === "object" && Object.keys(parsed).some((k) => k.toLowerCase().includes("uiautomator2"));
|
|
330602
|
-
return {
|
|
330603
|
-
ok: !!hasUia2,
|
|
330604
|
-
detail: hasUia2 ? "Installed" : "Not installed. Run: appium driver install uiautomator2"
|
|
330605
|
-
};
|
|
330606
|
-
} catch {
|
|
330607
|
-
const hasUia2 = drivers.toLowerCase().includes("uiautomator2");
|
|
330608
|
-
return {
|
|
330609
|
-
ok: hasUia2,
|
|
330610
|
-
detail: hasUia2 ? "Installed" : "Not installed. Run: appium driver install uiautomator2"
|
|
330611
|
-
};
|
|
330612
|
-
}
|
|
330613
|
-
}));
|
|
330614
|
-
results.push(check("Android Devices", () => {
|
|
330615
|
-
const output = run("adb devices");
|
|
330616
|
-
if (!output) return { ok: false, detail: "adb devices failed" };
|
|
330617
|
-
const lines = output.split("\n").filter((l) => l.includes(" device"));
|
|
330618
|
-
const emulators = lines.filter((l) => l.startsWith("emulator-"));
|
|
330619
|
-
const realDevices = lines.filter((l) => !l.startsWith("emulator-") && l.includes(" device"));
|
|
330620
|
-
const total = emulators.length + realDevices.length;
|
|
330621
|
-
if (total === 0) {
|
|
330622
|
-
return {
|
|
330623
|
-
ok: false,
|
|
330624
|
-
detail: "No devices found. Start an emulator or connect a real device via USB with USB debugging enabled."
|
|
330625
|
-
};
|
|
330626
|
-
}
|
|
330627
|
-
const details = [];
|
|
330628
|
-
if (emulators.length > 0) details.push(`${emulators.length} emulator(s)`);
|
|
330629
|
-
if (realDevices.length > 0) details.push(`${realDevices.length} real device(s)`);
|
|
330630
|
-
return {
|
|
330631
|
-
ok: true,
|
|
330632
|
-
detail: `${total} device(s) available: ${details.join(", ")}`
|
|
330633
|
-
};
|
|
330634
|
-
}));
|
|
330635
|
-
return results;
|
|
330636
|
-
}
|
|
330637
|
-
function checkTunnel() {
|
|
330638
|
-
const version = run("ngrok version") || run("ngrok --version");
|
|
330639
|
-
if (!version) {
|
|
330640
|
-
return [{
|
|
330641
|
-
name: "ngrok",
|
|
330642
|
-
ok: false,
|
|
330643
|
-
detail: "Not installed (only needed for --tunnel with an HTTPS dashboard). Install: brew install ngrok && ngrok config add-authtoken <token>"
|
|
330644
|
-
}];
|
|
330645
|
-
}
|
|
330646
|
-
const match = version.match(/(\d+)\.\d+\.\d+/);
|
|
330647
|
-
const major = match ? parseInt(match[1], 10) : 0;
|
|
330648
|
-
return [{
|
|
330649
|
-
name: "ngrok",
|
|
330650
|
-
ok: major >= 3,
|
|
330651
|
-
detail: major >= 3 ? `${version} (configure an authtoken with: ngrok config add-authtoken <token>)` : `${version} \u2014 ngrok v3+ is recommended (brew install ngrok)`
|
|
330652
|
-
}];
|
|
330653
|
-
}
|
|
330654
|
-
function runDoctor() {
|
|
330655
|
-
const web = checkWeb();
|
|
330656
|
-
const ios = checkIOS();
|
|
330657
|
-
const android = checkAndroid();
|
|
330658
|
-
const tunnel = checkTunnel();
|
|
330659
|
-
const allPassed = [...web, ...ios, ...android].every((c) => c.ok);
|
|
330660
|
-
return { web, ios, android, tunnel, allPassed };
|
|
330661
|
-
}
|
|
330662
|
-
function printDoctorReport(report) {
|
|
330663
|
-
console.log("");
|
|
330664
|
-
console.log(`${C2.bold}${C2.cyan}\u{1F3E5} validate.qa Doctor${C2.reset}`);
|
|
330665
|
-
console.log(`${C2.gray}${"\u2500".repeat(40)}${C2.reset}`);
|
|
330666
|
-
const printSection = (title, checks) => {
|
|
330667
|
-
console.log("");
|
|
330668
|
-
console.log(` ${C2.bold}${title}:${C2.reset}`);
|
|
330669
|
-
for (const c of checks) {
|
|
330670
|
-
const icon = c.ok ? `${C2.green}\u2713${C2.reset}` : `${C2.red}\u2717${C2.reset}`;
|
|
330671
|
-
console.log(` ${icon} ${c.name}: ${c.detail}`);
|
|
330672
|
-
}
|
|
330673
|
-
};
|
|
330674
|
-
printSection("Web Testing", report.web);
|
|
330675
|
-
printSection("iOS Testing", report.ios);
|
|
330676
|
-
printSection("Android Testing", report.android);
|
|
330677
|
-
printSection("Tunnel (optional \u2014 only for --tunnel / HTTPS dashboard)", report.tunnel);
|
|
330678
|
-
console.log("");
|
|
330679
|
-
if (report.allPassed) {
|
|
330680
|
-
console.log(` ${C2.green}\u2705 All checks passed - ready for web + mobile testing${C2.reset}`);
|
|
330681
|
-
} else {
|
|
330682
|
-
const failCount = [...report.web, ...report.ios, ...report.android].filter((c) => !c.ok).length;
|
|
330683
|
-
console.log(` ${C2.yellow}\u26A0 ${failCount} check(s) need attention${C2.reset}`);
|
|
330684
|
-
}
|
|
330685
|
-
console.log("");
|
|
330686
|
-
}
|
|
330687
|
-
|
|
330688
330765
|
// src/cli.ts
|
|
330689
330766
|
var pkg = require_package4();
|
|
330690
330767
|
var program = new import_commander.Command();
|