@validate.qa/runner 1.0.81 → 1.0.83
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 +231 -94
- package/dist/cli.mjs +231 -94
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -105530,7 +105530,7 @@ var require_typescript = __commonJS({
|
|
|
105530
105530
|
walkUpParenthesizedTypesAndGetParentAndChild: () => walkUpParenthesizedTypesAndGetParentAndChild,
|
|
105531
105531
|
whitespaceOrMapCommentRegExp: () => whitespaceOrMapCommentRegExp,
|
|
105532
105532
|
writeCommentRange: () => writeCommentRange,
|
|
105533
|
-
writeFile: () =>
|
|
105533
|
+
writeFile: () => writeFile3,
|
|
105534
105534
|
writeFileEnsuringDirectories: () => writeFileEnsuringDirectories,
|
|
105535
105535
|
zipWith: () => zipWith
|
|
105536
105536
|
});
|
|
@@ -111862,7 +111862,7 @@ ${lanes.join("\n")}
|
|
|
111862
111862
|
writeOutputIsTTY() {
|
|
111863
111863
|
return process.stdout.isTTY;
|
|
111864
111864
|
},
|
|
111865
|
-
readFile:
|
|
111865
|
+
readFile: readFile3,
|
|
111866
111866
|
writeFile: writeFile22,
|
|
111867
111867
|
watchFile: watchFile2,
|
|
111868
111868
|
watchDirectory,
|
|
@@ -112068,7 +112068,7 @@ ${lanes.join("\n")}
|
|
|
112068
112068
|
callback
|
|
112069
112069
|
);
|
|
112070
112070
|
}
|
|
112071
|
-
function
|
|
112071
|
+
function readFile3(fileName, _encoding) {
|
|
112072
112072
|
let buffer;
|
|
112073
112073
|
try {
|
|
112074
112074
|
buffer = _fs.readFileSync(fileName);
|
|
@@ -124173,7 +124173,7 @@ ${lanes.join("\n")}
|
|
|
124173
124173
|
sourceFilePath = isSourceFileInCommonSourceDirectory ? sourceFilePath.substring(commonSourceDirectory.length) : sourceFilePath;
|
|
124174
124174
|
return combinePaths(newDirPath, sourceFilePath);
|
|
124175
124175
|
}
|
|
124176
|
-
function
|
|
124176
|
+
function writeFile3(host, diagnostics, fileName, text, writeByteOrderMark, sourceFiles, data) {
|
|
124177
124177
|
host.writeFile(
|
|
124178
124178
|
fileName,
|
|
124179
124179
|
text,
|
|
@@ -148624,7 +148624,7 @@ ${lanes.join("\n")}
|
|
|
148624
148624
|
const possibleOption = getSpellingSuggestion(unknownOption, diagnostics.optionDeclarations, getOptionName);
|
|
148625
148625
|
return possibleOption ? createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, diagnostics.unknownDidYouMeanDiagnostic, unknownOptionErrorText || unknownOption, possibleOption.name) : createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, diagnostics.unknownOptionDiagnostic, unknownOptionErrorText || unknownOption);
|
|
148626
148626
|
}
|
|
148627
|
-
function parseCommandLineWorker(diagnostics, commandLine,
|
|
148627
|
+
function parseCommandLineWorker(diagnostics, commandLine, readFile3) {
|
|
148628
148628
|
const options = {};
|
|
148629
148629
|
let watchOptions;
|
|
148630
148630
|
const fileNames = [];
|
|
@@ -148672,7 +148672,7 @@ ${lanes.join("\n")}
|
|
|
148672
148672
|
}
|
|
148673
148673
|
}
|
|
148674
148674
|
function parseResponseFile(fileName) {
|
|
148675
|
-
const text = tryReadFile(fileName,
|
|
148675
|
+
const text = tryReadFile(fileName, readFile3 || ((fileName2) => sys.readFile(fileName2)));
|
|
148676
148676
|
if (!isString(text)) {
|
|
148677
148677
|
errors2.push(text);
|
|
148678
148678
|
return;
|
|
@@ -148775,8 +148775,8 @@ ${lanes.join("\n")}
|
|
|
148775
148775
|
unknownDidYouMeanDiagnostic: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1,
|
|
148776
148776
|
optionTypeMismatchDiagnostic: Diagnostics.Compiler_option_0_expects_an_argument
|
|
148777
148777
|
};
|
|
148778
|
-
function parseCommandLine(commandLine,
|
|
148779
|
-
return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine,
|
|
148778
|
+
function parseCommandLine(commandLine, readFile3) {
|
|
148779
|
+
return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile3);
|
|
148780
148780
|
}
|
|
148781
148781
|
function getOptionFromName(optionName, allowShort) {
|
|
148782
148782
|
return getOptionDeclarationFromName(getOptionsNameMap, optionName, allowShort);
|
|
@@ -148858,8 +148858,8 @@ ${lanes.join("\n")}
|
|
|
148858
148858
|
watchOptionsToExtend
|
|
148859
148859
|
);
|
|
148860
148860
|
}
|
|
148861
|
-
function readConfigFile(fileName,
|
|
148862
|
-
const textOrDiagnostic = tryReadFile(fileName,
|
|
148861
|
+
function readConfigFile(fileName, readFile3) {
|
|
148862
|
+
const textOrDiagnostic = tryReadFile(fileName, readFile3);
|
|
148863
148863
|
return isString(textOrDiagnostic) ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic };
|
|
148864
148864
|
}
|
|
148865
148865
|
function parseConfigFileTextToJson(fileName, jsonText) {
|
|
@@ -148874,14 +148874,14 @@ ${lanes.join("\n")}
|
|
|
148874
148874
|
error: jsonSourceFile.parseDiagnostics.length ? jsonSourceFile.parseDiagnostics[0] : void 0
|
|
148875
148875
|
};
|
|
148876
148876
|
}
|
|
148877
|
-
function readJsonConfigFile(fileName,
|
|
148878
|
-
const textOrDiagnostic = tryReadFile(fileName,
|
|
148877
|
+
function readJsonConfigFile(fileName, readFile3) {
|
|
148878
|
+
const textOrDiagnostic = tryReadFile(fileName, readFile3);
|
|
148879
148879
|
return isString(textOrDiagnostic) ? parseJsonText(fileName, textOrDiagnostic) : { fileName, parseDiagnostics: [textOrDiagnostic] };
|
|
148880
148880
|
}
|
|
148881
|
-
function tryReadFile(fileName,
|
|
148881
|
+
function tryReadFile(fileName, readFile3) {
|
|
148882
148882
|
let text;
|
|
148883
148883
|
try {
|
|
148884
|
-
text =
|
|
148884
|
+
text = readFile3(fileName);
|
|
148885
148885
|
} catch (e) {
|
|
148886
148886
|
return createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message);
|
|
148887
148887
|
}
|
|
@@ -232924,7 +232924,7 @@ ${lanes.join("\n")}
|
|
|
232924
232924
|
return;
|
|
232925
232925
|
}
|
|
232926
232926
|
const buildInfo = host.getBuildInfo() || { version };
|
|
232927
|
-
|
|
232927
|
+
writeFile3(
|
|
232928
232928
|
host,
|
|
232929
232929
|
emitterDiagnostics,
|
|
232930
232930
|
buildInfoPath,
|
|
@@ -233136,7 +233136,7 @@ ${lanes.join("\n")}
|
|
|
233136
233136
|
}
|
|
233137
233137
|
if (sourceMapFilePath) {
|
|
233138
233138
|
const sourceMap = sourceMapGenerator.toString();
|
|
233139
|
-
|
|
233139
|
+
writeFile3(
|
|
233140
233140
|
host,
|
|
233141
233141
|
emitterDiagnostics,
|
|
233142
233142
|
sourceMapFilePath,
|
|
@@ -233151,7 +233151,7 @@ ${lanes.join("\n")}
|
|
|
233151
233151
|
}
|
|
233152
233152
|
const text = writer.getText();
|
|
233153
233153
|
const data = { sourceMapUrlPos, diagnostics: transform2.diagnostics };
|
|
233154
|
-
|
|
233154
|
+
writeFile3(host, emitterDiagnostics, jsFilePath, text, !!compilerOptions.emitBOM, sourceFiles, data);
|
|
233155
233155
|
writer.clear();
|
|
233156
233156
|
return !data.skippedDtsWrite;
|
|
233157
233157
|
}
|
|
@@ -238539,12 +238539,12 @@ ${lanes.join("\n")}
|
|
|
238539
238539
|
function createCompilerHost(options, setParentNodes) {
|
|
238540
238540
|
return createCompilerHostWorker(options, setParentNodes);
|
|
238541
238541
|
}
|
|
238542
|
-
function createGetSourceFile(
|
|
238542
|
+
function createGetSourceFile(readFile3, setParentNodes) {
|
|
238543
238543
|
return (fileName, languageVersionOrOptions, onError) => {
|
|
238544
238544
|
let text;
|
|
238545
238545
|
try {
|
|
238546
238546
|
mark("beforeIORead");
|
|
238547
|
-
text =
|
|
238547
|
+
text = readFile3(fileName);
|
|
238548
238548
|
mark("afterIORead");
|
|
238549
238549
|
measure("I/O Read", "beforeIORead", "afterIORead");
|
|
238550
238550
|
} catch (e) {
|
|
@@ -239448,7 +239448,7 @@ ${lanes.join("\n")}
|
|
|
239448
239448
|
getRedirectFromOutput,
|
|
239449
239449
|
forEachResolvedProjectReference: forEachResolvedProjectReference2
|
|
239450
239450
|
});
|
|
239451
|
-
const
|
|
239451
|
+
const readFile3 = host.readFile.bind(host);
|
|
239452
239452
|
(_e = tracing) == null ? void 0 : _e.push(tracing.Phase.Program, "shouldProgramCreateNewSourceFiles", { hasOldProgram: !!oldProgram });
|
|
239453
239453
|
const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options);
|
|
239454
239454
|
(_f = tracing) == null ? void 0 : _f.pop();
|
|
@@ -239674,7 +239674,7 @@ ${lanes.join("\n")}
|
|
|
239674
239674
|
shouldTransformImportCall,
|
|
239675
239675
|
emitBuildInfo,
|
|
239676
239676
|
fileExists,
|
|
239677
|
-
readFile:
|
|
239677
|
+
readFile: readFile3,
|
|
239678
239678
|
directoryExists,
|
|
239679
239679
|
getSymlinkCache,
|
|
239680
239680
|
realpath: (_o = host.realpath) == null ? void 0 : _o.bind(host),
|
|
@@ -301033,7 +301033,7 @@ ${options.prefix}` : "\n" : options.prefix
|
|
|
301033
301033
|
walkUpParenthesizedTypesAndGetParentAndChild: () => walkUpParenthesizedTypesAndGetParentAndChild,
|
|
301034
301034
|
whitespaceOrMapCommentRegExp: () => whitespaceOrMapCommentRegExp,
|
|
301035
301035
|
writeCommentRange: () => writeCommentRange,
|
|
301036
|
-
writeFile: () =>
|
|
301036
|
+
writeFile: () => writeFile3,
|
|
301037
301037
|
writeFileEnsuringDirectories: () => writeFileEnsuringDirectories,
|
|
301038
301038
|
zipWith: () => zipWith
|
|
301039
301039
|
});
|
|
@@ -347535,7 +347535,7 @@ var require_mkdir = __commonJS({
|
|
|
347535
347535
|
const gid = opt.gid;
|
|
347536
347536
|
const doChown = typeof uid === "number" && typeof gid === "number" && (uid !== opt.processUid || gid !== opt.processGid);
|
|
347537
347537
|
const preserve = opt.preserve;
|
|
347538
|
-
const
|
|
347538
|
+
const unlink3 = opt.unlink;
|
|
347539
347539
|
const cache = opt.cache;
|
|
347540
347540
|
const cwd = normPath(opt.cwd);
|
|
347541
347541
|
const done = (er, created) => {
|
|
@@ -347563,33 +347563,33 @@ var require_mkdir = __commonJS({
|
|
|
347563
347563
|
}
|
|
347564
347564
|
const sub = normPath(path.relative(cwd, dir));
|
|
347565
347565
|
const parts = sub.split("/");
|
|
347566
|
-
mkdir_(cwd, parts, mode, cache,
|
|
347566
|
+
mkdir_(cwd, parts, mode, cache, unlink3, cwd, null, done);
|
|
347567
347567
|
};
|
|
347568
|
-
var mkdir_ = (base2, parts, mode, cache,
|
|
347568
|
+
var mkdir_ = (base2, parts, mode, cache, unlink3, cwd, created, cb) => {
|
|
347569
347569
|
if (!parts.length) {
|
|
347570
347570
|
return cb(null, created);
|
|
347571
347571
|
}
|
|
347572
347572
|
const p = parts.shift();
|
|
347573
347573
|
const part = normPath(path.resolve(base2 + "/" + p));
|
|
347574
347574
|
if (cGet(cache, part)) {
|
|
347575
|
-
return mkdir_(part, parts, mode, cache,
|
|
347575
|
+
return mkdir_(part, parts, mode, cache, unlink3, cwd, created, cb);
|
|
347576
347576
|
}
|
|
347577
|
-
fs.mkdir(part, mode, onmkdir(part, parts, mode, cache,
|
|
347577
|
+
fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink3, cwd, created, cb));
|
|
347578
347578
|
};
|
|
347579
|
-
var onmkdir = (part, parts, mode, cache,
|
|
347579
|
+
var onmkdir = (part, parts, mode, cache, unlink3, cwd, created, cb) => (er) => {
|
|
347580
347580
|
if (er) {
|
|
347581
347581
|
fs.lstat(part, (statEr, st) => {
|
|
347582
347582
|
if (statEr) {
|
|
347583
347583
|
statEr.path = statEr.path && normPath(statEr.path);
|
|
347584
347584
|
cb(statEr);
|
|
347585
347585
|
} else if (st.isDirectory()) {
|
|
347586
|
-
mkdir_(part, parts, mode, cache,
|
|
347587
|
-
} else if (
|
|
347586
|
+
mkdir_(part, parts, mode, cache, unlink3, cwd, created, cb);
|
|
347587
|
+
} else if (unlink3) {
|
|
347588
347588
|
fs.unlink(part, (er2) => {
|
|
347589
347589
|
if (er2) {
|
|
347590
347590
|
return cb(er2);
|
|
347591
347591
|
}
|
|
347592
|
-
fs.mkdir(part, mode, onmkdir(part, parts, mode, cache,
|
|
347592
|
+
fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink3, cwd, created, cb));
|
|
347593
347593
|
});
|
|
347594
347594
|
} else if (st.isSymbolicLink()) {
|
|
347595
347595
|
return cb(new SymlinkError(part, part + "/" + parts.join("/")));
|
|
@@ -347599,7 +347599,7 @@ var require_mkdir = __commonJS({
|
|
|
347599
347599
|
});
|
|
347600
347600
|
} else {
|
|
347601
347601
|
created = created || part;
|
|
347602
|
-
mkdir_(part, parts, mode, cache,
|
|
347602
|
+
mkdir_(part, parts, mode, cache, unlink3, cwd, created, cb);
|
|
347603
347603
|
}
|
|
347604
347604
|
};
|
|
347605
347605
|
var checkCwdSync = (dir) => {
|
|
@@ -347624,7 +347624,7 @@ var require_mkdir = __commonJS({
|
|
|
347624
347624
|
const gid = opt.gid;
|
|
347625
347625
|
const doChown = typeof uid === "number" && typeof gid === "number" && (uid !== opt.processUid || gid !== opt.processGid);
|
|
347626
347626
|
const preserve = opt.preserve;
|
|
347627
|
-
const
|
|
347627
|
+
const unlink3 = opt.unlink;
|
|
347628
347628
|
const cache = opt.cache;
|
|
347629
347629
|
const cwd = normPath(opt.cwd);
|
|
347630
347630
|
const done = (created2) => {
|
|
@@ -347663,7 +347663,7 @@ var require_mkdir = __commonJS({
|
|
|
347663
347663
|
if (st.isDirectory()) {
|
|
347664
347664
|
cSet(cache, part, true);
|
|
347665
347665
|
continue;
|
|
347666
|
-
} else if (
|
|
347666
|
+
} else if (unlink3) {
|
|
347667
347667
|
fs.unlinkSync(part);
|
|
347668
347668
|
fs.mkdirSync(part, mode);
|
|
347669
347669
|
created = created || part;
|
|
@@ -353337,7 +353337,7 @@ var require_package4 = __commonJS({
|
|
|
353337
353337
|
"package.json"(exports2, module2) {
|
|
353338
353338
|
module2.exports = {
|
|
353339
353339
|
name: "@validate.qa/runner",
|
|
353340
|
-
version: "1.0.
|
|
353340
|
+
version: "1.0.83",
|
|
353341
353341
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
353342
353342
|
bin: {
|
|
353343
353343
|
"validate-runner": "dist/cli.js"
|
|
@@ -355285,6 +355285,7 @@ var import_node_child_process2 = require("child_process");
|
|
|
355285
355285
|
var import_node_util2 = require("util");
|
|
355286
355286
|
var import_node_os2 = require("os");
|
|
355287
355287
|
var import_node_path2 = require("path");
|
|
355288
|
+
var import_promises3 = require("fs/promises");
|
|
355288
355289
|
var import_runner_core8 = __toESM(require_dist2());
|
|
355289
355290
|
|
|
355290
355291
|
// src/services/device-discovery.ts
|
|
@@ -356719,6 +356720,12 @@ async function createSession(target) {
|
|
|
356719
356720
|
// whole survey. The runner owns session teardown explicitly, so a long
|
|
356720
356721
|
// idle window is safe. Env-overridable.
|
|
356721
356722
|
"appium:newCommandTimeout": MOBILE_NEW_COMMAND_TIMEOUT_SECONDS,
|
|
356723
|
+
// Expo / universal-link "Open in App?" system sheets and permission dialogs
|
|
356724
|
+
// otherwise sit on top of the app and can stall WDA snapshot/command proxy
|
|
356725
|
+
// for the entire discovery session (observed: 20+ min hung XCUITest with
|
|
356726
|
+
// ECONNREFUSED on wdaLocalPort). Auto-accept keeps the AUT interactive.
|
|
356727
|
+
// Override with APPIUM_IOS_AUTO_ACCEPT_ALERTS=false for explicit dialog tests.
|
|
356728
|
+
...process.env.APPIUM_IOS_AUTO_ACCEPT_ALERTS !== "false" ? { "appium:autoAcceptAlerts": true } : {},
|
|
356722
356729
|
// Xcode 27+ relocates Simulator.app out of <DEVELOPER_DIR>/Applications, so
|
|
356723
356730
|
// XCUITest's default "open Simulator UI" step fails ("Simulator.app does not
|
|
356724
356731
|
// exist") on this toolchain. The simulator is already booted and driven
|
|
@@ -356974,20 +356981,102 @@ async function startScreenRecording(sessionId, platform3, log2) {
|
|
|
356974
356981
|
if (platform3 === "ANDROID") {
|
|
356975
356982
|
options.bitRate = readPositiveIntEnv("VALIDATEQA_MOBILE_RECORDING_BITRATE", 75e4, 1e5, 4e6);
|
|
356976
356983
|
} else {
|
|
356977
|
-
options.
|
|
356984
|
+
options.videoType = process.env.VALIDATEQA_MOBILE_RECORDING_IOS_VIDEO_TYPE || "libx264";
|
|
356985
|
+
options.videoQuality = process.env.VALIDATEQA_MOBILE_RECORDING_IOS_QUALITY || "medium";
|
|
356986
|
+
options.videoFilters = process.env.VALIDATEQA_MOBILE_RECORDING_IOS_FILTERS || "scale=trunc(iw/2)*2:trunc(ih/2)*2";
|
|
356978
356987
|
}
|
|
356979
356988
|
try {
|
|
356980
356989
|
await wdRequest(`/session/${sessionId}/appium/start_recording_screen`, {
|
|
356981
356990
|
method: "POST",
|
|
356982
356991
|
body: JSON.stringify({ options })
|
|
356983
356992
|
});
|
|
356984
|
-
log2(`[mobile-video] screen recording started (${timeLimit}s max)`);
|
|
356993
|
+
log2(`[mobile-video] screen recording started (${timeLimit}s max${platform3 === "IOS" ? `, type=${options.videoType}` : ""})`);
|
|
356985
356994
|
return true;
|
|
356986
356995
|
} catch (err) {
|
|
356996
|
+
if (platform3 === "IOS" && options.videoType) {
|
|
356997
|
+
try {
|
|
356998
|
+
const fallback = {
|
|
356999
|
+
timeLimit: String(timeLimit),
|
|
357000
|
+
forceRestart: true,
|
|
357001
|
+
videoQuality: process.env.VALIDATEQA_MOBILE_RECORDING_IOS_QUALITY || "medium"
|
|
357002
|
+
};
|
|
357003
|
+
await wdRequest(`/session/${sessionId}/appium/start_recording_screen`, {
|
|
357004
|
+
method: "POST",
|
|
357005
|
+
body: JSON.stringify({ options: fallback })
|
|
357006
|
+
});
|
|
357007
|
+
log2(`[mobile-video] screen recording started with quality-only fallback (${timeLimit}s max); will re-encode on stop`);
|
|
357008
|
+
return true;
|
|
357009
|
+
} catch (fallbackErr) {
|
|
357010
|
+
log2(`[mobile-video] screen recording unavailable: ${fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)}`);
|
|
357011
|
+
return false;
|
|
357012
|
+
}
|
|
357013
|
+
}
|
|
356987
357014
|
log2(`[mobile-video] screen recording unavailable: ${err instanceof Error ? err.message : String(err)}`);
|
|
356988
357015
|
return false;
|
|
356989
357016
|
}
|
|
356990
357017
|
}
|
|
357018
|
+
async function ensureBrowserPlayableMp4Base64(base64Video, log2) {
|
|
357019
|
+
if (process.env.VALIDATEQA_MOBILE_VIDEO_REENCODE === "0") {
|
|
357020
|
+
return base64Video;
|
|
357021
|
+
}
|
|
357022
|
+
const inPath = (0, import_node_path2.join)((0, import_node_os2.tmpdir)(), `validateqa-mobile-rec-in-${Date.now()}-${Math.random().toString(36).slice(2)}.mp4`);
|
|
357023
|
+
const outPath = (0, import_node_path2.join)((0, import_node_os2.tmpdir)(), `validateqa-mobile-rec-out-${Date.now()}-${Math.random().toString(36).slice(2)}.mp4`);
|
|
357024
|
+
try {
|
|
357025
|
+
await (0, import_promises3.writeFile)(inPath, Buffer.from(base64Video, "base64"));
|
|
357026
|
+
try {
|
|
357027
|
+
const { stdout } = await execFileAsync3("ffprobe", [
|
|
357028
|
+
"-v",
|
|
357029
|
+
"error",
|
|
357030
|
+
"-select_streams",
|
|
357031
|
+
"v:0",
|
|
357032
|
+
"-show_entries",
|
|
357033
|
+
"stream=codec_name",
|
|
357034
|
+
"-of",
|
|
357035
|
+
"csv=p=0",
|
|
357036
|
+
inPath
|
|
357037
|
+
], { timeout: 15e3 });
|
|
357038
|
+
const codec = stdout.trim().toLowerCase();
|
|
357039
|
+
if (codec === "h264" || codec === "avc1") {
|
|
357040
|
+
log2(`[mobile-video] recording already H.264 (${codec}); skipping re-encode`);
|
|
357041
|
+
return base64Video;
|
|
357042
|
+
}
|
|
357043
|
+
log2(`[mobile-video] re-encoding ${codec || "unknown"} \u2192 H.264 for browser preview`);
|
|
357044
|
+
} catch {
|
|
357045
|
+
log2("[mobile-video] codec probe failed; attempting H.264 re-encode anyway");
|
|
357046
|
+
}
|
|
357047
|
+
await execFileAsync3("ffmpeg", [
|
|
357048
|
+
"-y",
|
|
357049
|
+
"-i",
|
|
357050
|
+
inPath,
|
|
357051
|
+
"-vf",
|
|
357052
|
+
"scale=trunc(iw/2)*2:trunc(ih/2)*2",
|
|
357053
|
+
"-c:v",
|
|
357054
|
+
"libx264",
|
|
357055
|
+
"-pix_fmt",
|
|
357056
|
+
"yuv420p",
|
|
357057
|
+
"-preset",
|
|
357058
|
+
"veryfast",
|
|
357059
|
+
"-crf",
|
|
357060
|
+
"28",
|
|
357061
|
+
"-movflags",
|
|
357062
|
+
"+faststart",
|
|
357063
|
+
"-an",
|
|
357064
|
+
outPath
|
|
357065
|
+
], { timeout: 12e4 });
|
|
357066
|
+
const encoded = await (0, import_promises3.readFile)(outPath);
|
|
357067
|
+
if (encoded.length < 100) {
|
|
357068
|
+
log2("[mobile-video] re-encode produced empty file; keeping original");
|
|
357069
|
+
return base64Video;
|
|
357070
|
+
}
|
|
357071
|
+
log2(`[mobile-video] re-encoded for browser (${Math.round(encoded.length / 1024)}KB)`);
|
|
357072
|
+
return encoded.toString("base64");
|
|
357073
|
+
} catch (err) {
|
|
357074
|
+
log2(`[mobile-video] re-encode skipped/failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
357075
|
+
return base64Video;
|
|
357076
|
+
} finally {
|
|
357077
|
+
await Promise.allSettled([(0, import_promises3.unlink)(inPath), (0, import_promises3.unlink)(outPath)]);
|
|
357078
|
+
}
|
|
357079
|
+
}
|
|
356991
357080
|
async function stopScreenRecording(sessionId, log2) {
|
|
356992
357081
|
try {
|
|
356993
357082
|
const result = await wdRequest(`/session/${sessionId}/appium/stop_recording_screen`, {
|
|
@@ -357000,7 +357089,7 @@ async function stopScreenRecording(sessionId, log2) {
|
|
|
357000
357089
|
return void 0;
|
|
357001
357090
|
}
|
|
357002
357091
|
log2(`[mobile-video] screen recording captured (${Math.round(value.length / 1024)}KB base64)`);
|
|
357003
|
-
return value;
|
|
357092
|
+
return ensureBrowserPlayableMp4Base64(value, log2);
|
|
357004
357093
|
} catch (err) {
|
|
357005
357094
|
log2(`[mobile-video] screen recording stop failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
357006
357095
|
return void 0;
|
|
@@ -357085,10 +357174,17 @@ function xpathLiteral3(value) {
|
|
|
357085
357174
|
const parts = value.split("'").map((p) => `'${p}'`);
|
|
357086
357175
|
return `concat(${parts.join(`, "'", `)})`;
|
|
357087
357176
|
}
|
|
357177
|
+
var XPATH_ASCII_UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
357178
|
+
var XPATH_ASCII_LOWER = "abcdefghijklmnopqrstuvwxyz";
|
|
357088
357179
|
function identityTextXpath2(value) {
|
|
357089
357180
|
const lit = xpathLiteral3(value);
|
|
357090
357181
|
return `//*[@text=${lit} or @content-desc=${lit} or @label=${lit} or @name=${lit} or @value=${lit}]`;
|
|
357091
357182
|
}
|
|
357183
|
+
function identityTextXpathCaseInsensitive(value) {
|
|
357184
|
+
const lit = xpathLiteral3(value.toLowerCase());
|
|
357185
|
+
const fold = (attr) => `translate(@${attr}, '${XPATH_ASCII_UPPER}', '${XPATH_ASCII_LOWER}')=${lit}`;
|
|
357186
|
+
return `//*[${fold("text")} or ${fold("content-desc")} or ${fold("label")} or ${fold("name")} or ${fold("value")}]`;
|
|
357187
|
+
}
|
|
357092
357188
|
function iosPredicateLiteral(value) {
|
|
357093
357189
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
357094
357190
|
}
|
|
@@ -357096,19 +357192,28 @@ function iosTextIdentityPredicate(value) {
|
|
|
357096
357192
|
const lit = iosPredicateLiteral(value);
|
|
357097
357193
|
return `label == ${lit} OR name == ${lit} OR value == ${lit}`;
|
|
357098
357194
|
}
|
|
357195
|
+
function iosTextIdentityPredicateCaseInsensitive(value) {
|
|
357196
|
+
const lit = iosPredicateLiteral(value);
|
|
357197
|
+
return `label ==[c] ${lit} OR name ==[c] ${lit} OR value ==[c] ${lit}`;
|
|
357198
|
+
}
|
|
357099
357199
|
function textIdentityStrategies(value, platform3) {
|
|
357100
357200
|
const strategies = [];
|
|
357101
357201
|
if (platform3 === "IOS") {
|
|
357102
357202
|
strategies.push({ using: "-ios predicate string", value: iosTextIdentityPredicate(value) });
|
|
357203
|
+
strategies.push({ using: "-ios predicate string", value: iosTextIdentityPredicateCaseInsensitive(value) });
|
|
357103
357204
|
}
|
|
357104
357205
|
strategies.push({ using: "xpath", value: identityTextXpath2(value) });
|
|
357206
|
+
strategies.push({ using: "xpath", value: identityTextXpathCaseInsensitive(value) });
|
|
357105
357207
|
return strategies;
|
|
357106
357208
|
}
|
|
357107
357209
|
function isIdentityIosPredicate(value) {
|
|
357108
|
-
return /(?:^|[\s(])(?:name|label|value)\s
|
|
357210
|
+
return /(?:^|[\s(])(?:name|label|value)\s*==(?:\[[cC]\])?\s*"/.test(value) && !/\b(?:CONTAINS|BEGINSWITH|ENDSWITH|LIKE|MATCHES|IN)\b/i.test(value);
|
|
357109
357211
|
}
|
|
357110
357212
|
function isIdentityXpath(value) {
|
|
357111
|
-
|
|
357213
|
+
if (/(?:contains|starts-with)\s*\(/i.test(value)) return false;
|
|
357214
|
+
if (/@[A-Za-z_:][-A-Za-z0-9_:.]*\s*=/.test(value)) return true;
|
|
357215
|
+
if (/translate\s*\(\s*@[A-Za-z_:][-A-Za-z0-9_:.]*\s*,/.test(value) && /\)\s*=/.test(value)) return true;
|
|
357216
|
+
return false;
|
|
357112
357217
|
}
|
|
357113
357218
|
function isIdentityStrategy(strategy) {
|
|
357114
357219
|
if (strategy.using === "accessibility id" || strategy.using === "id") return true;
|
|
@@ -357931,7 +358036,12 @@ function mobileMjpegPortForDevice(platform3, deviceId, env = process.env) {
|
|
|
357931
358036
|
return typeof port === "number" && Number.isFinite(port) ? port : null;
|
|
357932
358037
|
}
|
|
357933
358038
|
var PINNED_DEVICE_SNAPSHOT_TTL_MS = 25 * 6e4;
|
|
357934
|
-
var
|
|
358039
|
+
var pinnedDeviceSnapshots = /* @__PURE__ */ new Map();
|
|
358040
|
+
function prunePinnedDeviceSnapshots(now = Date.now()) {
|
|
358041
|
+
for (const [id, entry] of pinnedDeviceSnapshots) {
|
|
358042
|
+
if (now - entry.at >= PINNED_DEVICE_SNAPSHOT_TTL_MS) pinnedDeviceSnapshots.delete(id);
|
|
358043
|
+
}
|
|
358044
|
+
}
|
|
357935
358045
|
async function executeMobileTest(options) {
|
|
357936
358046
|
const logLines = [];
|
|
357937
358047
|
const log2 = (msg) => logLines.push(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}`);
|
|
@@ -358006,8 +358116,10 @@ async function executeMobileTest(options) {
|
|
|
358006
358116
|
}
|
|
358007
358117
|
const platformFilter = options.target.platform;
|
|
358008
358118
|
const pinnedDeviceId = options.target.assignedDeviceId;
|
|
358009
|
-
const
|
|
358010
|
-
|
|
358119
|
+
const now = Date.now();
|
|
358120
|
+
prunePinnedDeviceSnapshots(now);
|
|
358121
|
+
const candidateSnapshot = pinnedDeviceId ? pinnedDeviceSnapshots.get(pinnedDeviceId) : void 0;
|
|
358122
|
+
const reusablePinnedSnapshot = pinnedDeviceId && candidateSnapshot && now - candidateSnapshot.at < PINNED_DEVICE_SNAPSHOT_TTL_MS ? candidateSnapshot : null;
|
|
358011
358123
|
if (reusablePinnedSnapshot) {
|
|
358012
358124
|
assertDeviceNotQuarantined(reusablePinnedSnapshot.device.id);
|
|
358013
358125
|
selectedDevice = reusablePinnedSnapshot.device;
|
|
@@ -358023,22 +358135,24 @@ async function executeMobileTest(options) {
|
|
|
358023
358135
|
`No ${platformFilter} ${platformFilter === "IOS" ? "simulator" : "emulator"} booted. Start one via ${platformFilter === "IOS" ? "Simulator.app" : "Android Studio"} first.`
|
|
358024
358136
|
);
|
|
358025
358137
|
}
|
|
358026
|
-
if (options.target.assignedDeviceId) {
|
|
358027
|
-
|
|
358028
|
-
|
|
358029
|
-
|
|
358030
|
-
`Assigned ${platformFilter} device ${options.target.assignedDeviceId} is no longer booted/available (booted: ${matchingDevices.map((d) => d.id).join(", ") || "none"}). Refusing to fall back to another device.`
|
|
358031
|
-
);
|
|
358032
|
-
}
|
|
358033
|
-
}
|
|
358034
|
-
if (!selectedDevice && options.target.recordedDeviceId) {
|
|
358035
|
-
selectedDevice = matchingDevices.find((d) => d.id === options.target.recordedDeviceId);
|
|
358138
|
+
if (!options.target.assignedDeviceId) {
|
|
358139
|
+
throw new Error(
|
|
358140
|
+
"CONFIG_ISSUE: mobile test requires an assigned device lease; refusing unpinned session create."
|
|
358141
|
+
);
|
|
358036
358142
|
}
|
|
358143
|
+
selectedDevice = matchingDevices.find((d) => d.id === options.target.assignedDeviceId);
|
|
358037
358144
|
if (!selectedDevice) {
|
|
358038
|
-
|
|
358145
|
+
throw new Error(
|
|
358146
|
+
`Assigned ${platformFilter} device ${options.target.assignedDeviceId} is no longer booted/available (booted: ${matchingDevices.map((d) => d.id).join(", ") || "none"}). Refusing to fall back to another device.`
|
|
358147
|
+
);
|
|
358039
358148
|
}
|
|
358040
358149
|
log2(`Using device: ${selectedDevice.name} (${selectedDevice.id})`);
|
|
358041
|
-
|
|
358150
|
+
if (options.target.assignedDeviceId) {
|
|
358151
|
+
pinnedDeviceSnapshots.set(options.target.assignedDeviceId, {
|
|
358152
|
+
device: selectedDevice,
|
|
358153
|
+
at: Date.now()
|
|
358154
|
+
});
|
|
358155
|
+
}
|
|
358042
358156
|
}
|
|
358043
358157
|
try {
|
|
358044
358158
|
capture = await startMobileNetworkCapture({
|
|
@@ -358374,20 +358488,16 @@ async function createMobileDiscoverySession(target, options) {
|
|
|
358374
358488
|
`No ${platformFilter} ${platformFilter === "IOS" ? "simulator" : "emulator"} booted. Start one via ${platformFilter === "IOS" ? "Simulator.app" : "Android Studio"} first.`
|
|
358375
358489
|
);
|
|
358376
358490
|
}
|
|
358377
|
-
|
|
358378
|
-
|
|
358379
|
-
|
|
358380
|
-
|
|
358381
|
-
throw new Error(
|
|
358382
|
-
`CONFIG_ISSUE: assigned ${platformFilter} device ${target.assignedDeviceId} is no longer booted for mobile discovery; will retry.`
|
|
358383
|
-
);
|
|
358384
|
-
}
|
|
358385
|
-
}
|
|
358386
|
-
if (!selectedDevice && target.recordedDeviceId) {
|
|
358387
|
-
selectedDevice = matchingDevices.find((d) => d.id === target.recordedDeviceId);
|
|
358491
|
+
if (!target.assignedDeviceId) {
|
|
358492
|
+
throw new Error(
|
|
358493
|
+
"CONFIG_ISSUE: mobile discovery requires an assigned device lease; refusing unpinned session create."
|
|
358494
|
+
);
|
|
358388
358495
|
}
|
|
358496
|
+
const selectedDevice = matchingDevices.find((d) => d.id === target.assignedDeviceId);
|
|
358389
358497
|
if (!selectedDevice) {
|
|
358390
|
-
|
|
358498
|
+
throw new Error(
|
|
358499
|
+
`CONFIG_ISSUE: assigned ${platformFilter} device ${target.assignedDeviceId} is no longer booted for mobile discovery; will retry.`
|
|
358500
|
+
);
|
|
358391
358501
|
}
|
|
358392
358502
|
if (selectedDevice.installedApps && selectedDevice.installedApps.length > 0 && !selectedDevice.installedApps.includes(target.appId)) {
|
|
358393
358503
|
throw new Error(
|
|
@@ -358829,6 +358939,10 @@ function computeMobileCapacity(input) {
|
|
|
358829
358939
|
}
|
|
358830
358940
|
return Math.max(0, cap);
|
|
358831
358941
|
}
|
|
358942
|
+
function discoveryKindOf(ctx) {
|
|
358943
|
+
if (!isNativeMobileDiscoveryContext(ctx)) return null;
|
|
358944
|
+
return ctx?.mode === "survey" ? "survey" : "deep";
|
|
358945
|
+
}
|
|
358832
358946
|
function decideMobileDeviceGate(input) {
|
|
358833
358947
|
const {
|
|
358834
358948
|
runMode,
|
|
@@ -358839,8 +358953,10 @@ function decideMobileDeviceGate(input) {
|
|
|
358839
358953
|
readyDevices,
|
|
358840
358954
|
capacity,
|
|
358841
358955
|
freeDeviceId,
|
|
358842
|
-
|
|
358843
|
-
|
|
358956
|
+
discoveryKind,
|
|
358957
|
+
activeMobileSurveys,
|
|
358958
|
+
activeMobileDeeps,
|
|
358959
|
+
maxMobileDeeps
|
|
358844
358960
|
} = input;
|
|
358845
358961
|
if (!platform3) {
|
|
358846
358962
|
return { claim: false, disposition: "pending", reason: "mobile run has no supported target platform" };
|
|
@@ -358852,8 +358968,18 @@ function decideMobileDeviceGate(input) {
|
|
|
358852
358968
|
reason: `no booted ${platform3 === "IOS" ? "iOS" : "Android"} device for mobile run`
|
|
358853
358969
|
};
|
|
358854
358970
|
}
|
|
358855
|
-
if (
|
|
358856
|
-
return { claim: false, disposition: "defer", reason: "native
|
|
358971
|
+
if (discoveryKind === "survey" && activeMobileSurveys >= 1) {
|
|
358972
|
+
return { claim: false, disposition: "defer", reason: "native survey serialized to 1 concurrent" };
|
|
358973
|
+
}
|
|
358974
|
+
if (discoveryKind === "deep") {
|
|
358975
|
+
const deepCap = Math.max(0, maxMobileDeeps);
|
|
358976
|
+
if (activeMobileDeeps >= deepCap) {
|
|
358977
|
+
return {
|
|
358978
|
+
claim: false,
|
|
358979
|
+
disposition: "defer",
|
|
358980
|
+
reason: `native deep discovery capacity reached: ${activeMobileDeeps}/${deepCap}`
|
|
358981
|
+
};
|
|
358982
|
+
}
|
|
358857
358983
|
}
|
|
358858
358984
|
const isHealForHeldTest = runMode === "heal" && !!testCaseId && healHoldTestCaseIds.has(testCaseId);
|
|
358859
358985
|
const reservedForHeals = isHealForHeldTest ? Math.max(0, healHoldTestCaseIds.size - 1) : healHoldTestCaseIds.size;
|
|
@@ -361261,11 +361387,12 @@ var RUNNER_BUILD_ID = process.env.VALIDATE_RUNNER_BUILD_ID ?? process.env.RAILWA
|
|
|
361261
361387
|
var DEFAULT_MAX_CONCURRENT = IS_CLOUD ? 4 : 40;
|
|
361262
361388
|
var DEFAULT_TYPE_LIMITS = {
|
|
361263
361389
|
discovery: IS_CLOUD ? 2 : 15,
|
|
361264
|
-
//
|
|
361265
|
-
//
|
|
361266
|
-
// (
|
|
361267
|
-
// and
|
|
361268
|
-
|
|
361390
|
+
// Max concurrent native deep discoveries on one local runner (survey is always
|
|
361391
|
+
// 1 via decideMobileDeviceGate). Applied by the device gate as maxMobileDeeps
|
|
361392
|
+
// (MAX_MOBILE_DISCOVERY); the resource-governor type key is run.mode='discovery'
|
|
361393
|
+
// and does NOT read this entry — do not treat TYPE_LIMITS['mobile-discovery'] as
|
|
361394
|
+
// the live governor. Env default 2 enables two deeps when two free devices exist.
|
|
361395
|
+
"mobile-discovery": 2,
|
|
361269
361396
|
heal: IS_CLOUD ? 2 : 8,
|
|
361270
361397
|
"regression-suite": IS_CLOUD ? 1 : 2,
|
|
361271
361398
|
"system-regression-suite": IS_CLOUD ? 1 : 2,
|
|
@@ -361285,7 +361412,7 @@ function envInt(name, fallback) {
|
|
|
361285
361412
|
}
|
|
361286
361413
|
var MOBILE_DEVICE_CEILING = Math.max(0, envInt("MOBILE_DEVICE_CEILING", 0));
|
|
361287
361414
|
var MOBILE_HEAL_HOLD_MS = Math.max(0, envInt("MOBILE_HEAL_HOLD_MS", 2e4));
|
|
361288
|
-
var MOBILE_RUN_WATCHDOG_MS = Math.max(0, envInt("MOBILE_RUN_WATCHDOG_MS",
|
|
361415
|
+
var MOBILE_RUN_WATCHDOG_MS = Math.max(0, envInt("MOBILE_RUN_WATCHDOG_MS", 60 * 6e4));
|
|
361289
361416
|
var MOBILE_RUN_ABORT_GRACE_MS = Math.max(0, envInt("MOBILE_RUN_ABORT_GRACE_MS", 2 * 6e4));
|
|
361290
361417
|
var MOBILE_CANCEL_POLL_MS = Math.max(0, envInt("MOBILE_CANCEL_POLL_MS", 15e3));
|
|
361291
361418
|
var MobileRunWatchdog = class {
|
|
@@ -362914,10 +363041,10 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
362914
363041
|
log.fail("ERROR - malformed locator-discovery context");
|
|
362915
363042
|
return;
|
|
362916
363043
|
}
|
|
362917
|
-
if (getBridgeState().
|
|
363044
|
+
if (getBridgeState().recordingActive) {
|
|
362918
363045
|
await postResult(opts.serverUrl, headers, run2.runId, {
|
|
362919
363046
|
status: "ERROR",
|
|
362920
|
-
error: "CONFIG_ISSUE:
|
|
363047
|
+
error: "CONFIG_ISSUE: a mobile recording session is active; retry once it ends."
|
|
362921
363048
|
});
|
|
362922
363049
|
log.fail("ERROR - device busy (recording) for locator discovery");
|
|
362923
363050
|
return;
|
|
@@ -363346,6 +363473,11 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363346
363473
|
if (!mobileTarget || !mobileTarget.appId) {
|
|
363347
363474
|
throw new Error("Native mobile discovery requires a target with an appId (bundleId/appPackage).");
|
|
363348
363475
|
}
|
|
363476
|
+
if (!assignedDeviceId) {
|
|
363477
|
+
throw new Error(
|
|
363478
|
+
"CONFIG_ISSUE: mobile discovery was claimed without a device lease; refusing unpinned session create."
|
|
363479
|
+
);
|
|
363480
|
+
}
|
|
363349
363481
|
const targetPlatform = mobileTarget.platform;
|
|
363350
363482
|
const platform3 = mobileMedium === "IOS_NATIVE" ? "IOS" : mobileMedium === "ANDROID_NATIVE" ? "ANDROID" : targetPlatform === "IOS" || targetPlatform === "ANDROID" ? targetPlatform : null;
|
|
363351
363483
|
if (!platform3) {
|
|
@@ -363354,8 +363486,8 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363354
363486
|
if (mobileMedium === "IOS_NATIVE" && targetPlatform === "ANDROID" || mobileMedium === "ANDROID_NATIVE" && targetPlatform === "IOS") {
|
|
363355
363487
|
throw new Error("Native mobile discovery medium does not match target.platform.");
|
|
363356
363488
|
}
|
|
363357
|
-
if (getBridgeState().
|
|
363358
|
-
throw new Error("CONFIG_ISSUE:
|
|
363489
|
+
if (getBridgeState().recordingActive) {
|
|
363490
|
+
throw new Error("CONFIG_ISSUE: a mobile recording session is active; mobile discovery will retry once it ends.");
|
|
363359
363491
|
}
|
|
363360
363492
|
const mobileProgressLines = [];
|
|
363361
363493
|
const mobileOnLog = (line) => {
|
|
@@ -363833,10 +363965,10 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363833
363965
|
return;
|
|
363834
363966
|
}
|
|
363835
363967
|
if (runMode === "heal" && run2.mobileAiHeal === true) {
|
|
363836
|
-
if (getBridgeState().
|
|
363968
|
+
if (getBridgeState().recordingActive) {
|
|
363837
363969
|
await postResult(opts.serverUrl, headers, run2.runId, {
|
|
363838
363970
|
status: "ERROR",
|
|
363839
|
-
error: "CONFIG_ISSUE:
|
|
363971
|
+
error: "CONFIG_ISSUE: a mobile recording session is active; the AI heal will retry once it ends.",
|
|
363840
363972
|
duration: Date.now() - startTime
|
|
363841
363973
|
});
|
|
363842
363974
|
log.warn("Skipped mobile AI heal (device busy with a recording)");
|
|
@@ -364574,7 +364706,9 @@ async function startRunner(opts) {
|
|
|
364574
364706
|
};
|
|
364575
364707
|
const leasedDeviceIds = /* @__PURE__ */ new Map();
|
|
364576
364708
|
const activeMobileRuns = /* @__PURE__ */ new Map();
|
|
364577
|
-
let
|
|
364709
|
+
let activeMobileSurveys = 0;
|
|
364710
|
+
let activeMobileDeeps = 0;
|
|
364711
|
+
const MAX_MOBILE_DEEPS = Math.max(0, envInt("MAX_MOBILE_DISCOVERY", DEFAULT_TYPE_LIMITS["mobile-discovery"]));
|
|
364578
364712
|
const mobileHealHold = /* @__PURE__ */ new Map();
|
|
364579
364713
|
const pruneMobileHealHolds = () => {
|
|
364580
364714
|
if (mobileHealHold.size === 0) return;
|
|
@@ -364919,7 +365053,7 @@ async function startRunner(opts) {
|
|
|
364919
365053
|
if (activeRuns >= MAX_CONCURRENT) break;
|
|
364920
365054
|
const runMode = run2.mode ?? "run";
|
|
364921
365055
|
const isMobileOp = enableMobile && isMobileDeviceOp(run2);
|
|
364922
|
-
const
|
|
365056
|
+
const discoveryKind = isMobileOp ? discoveryKindOf(run2.discoveryContext) : null;
|
|
364923
365057
|
let leasedDeviceId = null;
|
|
364924
365058
|
let mobileLeaseError;
|
|
364925
365059
|
const runProject = projectKey(run2);
|
|
@@ -364938,7 +365072,7 @@ async function startRunner(opts) {
|
|
|
364938
365072
|
logDeferredOnce(run2.runId, ` Deferred ${run2.runId} (${admission.reason})`);
|
|
364939
365073
|
continue;
|
|
364940
365074
|
}
|
|
364941
|
-
if (enableMobile &&
|
|
365075
|
+
if (enableMobile && isMobileOp && getBridgeState().recordingActive) {
|
|
364942
365076
|
logDeferredOnce(run2.runId, ` Deferred ${run2.runId} (mobile recording session active)`);
|
|
364943
365077
|
continue;
|
|
364944
365078
|
}
|
|
@@ -364954,10 +365088,6 @@ async function startRunner(opts) {
|
|
|
364954
365088
|
logDeferredOnce(run2.runId, ` Left PENDING ${run2.runId} (no booted ${discoveryMedium === "IOS_NATIVE" ? "iOS" : "Android"} device for native mobile discovery)`);
|
|
364955
365089
|
continue;
|
|
364956
365090
|
}
|
|
364957
|
-
if (getBridgeState().hasActiveSession) {
|
|
364958
|
-
logDeferredOnce(run2.runId, ` Deferred ${run2.runId} (mobile recording session active on shared device)`);
|
|
364959
|
-
continue;
|
|
364960
|
-
}
|
|
364961
365091
|
}
|
|
364962
365092
|
const mobilePlatform = isMobileOp ? mobilePlatformOf(run2) : null;
|
|
364963
365093
|
if (isMobileOp && mobilePlatform) {
|
|
@@ -364999,13 +365129,18 @@ async function startRunner(opts) {
|
|
|
364999
365129
|
readyDevices: virtual + physical,
|
|
365000
365130
|
capacity,
|
|
365001
365131
|
freeDeviceId,
|
|
365002
|
-
|
|
365003
|
-
|
|
365132
|
+
discoveryKind,
|
|
365133
|
+
activeMobileSurveys,
|
|
365134
|
+
activeMobileDeeps,
|
|
365135
|
+
maxMobileDeeps: MAX_MOBILE_DEEPS
|
|
365004
365136
|
});
|
|
365005
365137
|
if (!decision.claim) {
|
|
365006
365138
|
logDeferredOnce(run2.runId, ` ${decision.disposition === "pending" ? "Left PENDING" : "Deferred"} ${run2.runId} (${decision.reason})`);
|
|
365007
365139
|
continue;
|
|
365008
365140
|
}
|
|
365141
|
+
log.dim(
|
|
365142
|
+
` [mobile-gate] admit ${discoveryKind ?? runMode} device=${freeDeviceId} surveys=${activeMobileSurveys} deeps=${activeMobileDeeps}/${MAX_MOBILE_DEEPS} ops=${getActiveMobileOps(mobilePlatform)}/${capacity}`
|
|
365143
|
+
);
|
|
365009
365144
|
leasedDeviceId = freeDeviceId;
|
|
365010
365145
|
leasedDeviceIds.set(freeDeviceId, run2.runId);
|
|
365011
365146
|
}
|
|
@@ -365033,7 +365168,8 @@ async function startRunner(opts) {
|
|
|
365033
365168
|
incrementProject(runProject);
|
|
365034
365169
|
if (isMobileOp) {
|
|
365035
365170
|
if (mobilePlatform) bumpActiveMobileOps(mobilePlatform, 1);
|
|
365036
|
-
if (
|
|
365171
|
+
if (discoveryKind === "survey") activeMobileSurveys++;
|
|
365172
|
+
else if (discoveryKind === "deep") activeMobileDeeps++;
|
|
365037
365173
|
if (runMode === "heal" && run2.testCaseId) mobileHealHold.delete(run2.testCaseId);
|
|
365038
365174
|
}
|
|
365039
365175
|
if (claimedThisPoll > 0 && staggerMs > 0) {
|
|
@@ -365046,7 +365182,8 @@ async function startRunner(opts) {
|
|
|
365046
365182
|
decrementProject(runProject);
|
|
365047
365183
|
if (isMobileOp) {
|
|
365048
365184
|
if (mobilePlatform) bumpActiveMobileOps(mobilePlatform, -1);
|
|
365049
|
-
if (
|
|
365185
|
+
if (discoveryKind === "survey") activeMobileSurveys = Math.max(0, activeMobileSurveys - 1);
|
|
365186
|
+
else if (discoveryKind === "deep") activeMobileDeeps = Math.max(0, activeMobileDeeps - 1);
|
|
365050
365187
|
if (leasedDeviceId) leasedDeviceIds.delete(leasedDeviceId);
|
|
365051
365188
|
activeMobileRuns.delete(run2.runId);
|
|
365052
365189
|
}
|