@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.mjs
CHANGED
|
@@ -105535,7 +105535,7 @@ var require_typescript = __commonJS({
|
|
|
105535
105535
|
walkUpParenthesizedTypesAndGetParentAndChild: () => walkUpParenthesizedTypesAndGetParentAndChild,
|
|
105536
105536
|
whitespaceOrMapCommentRegExp: () => whitespaceOrMapCommentRegExp,
|
|
105537
105537
|
writeCommentRange: () => writeCommentRange,
|
|
105538
|
-
writeFile: () =>
|
|
105538
|
+
writeFile: () => writeFile3,
|
|
105539
105539
|
writeFileEnsuringDirectories: () => writeFileEnsuringDirectories,
|
|
105540
105540
|
zipWith: () => zipWith
|
|
105541
105541
|
});
|
|
@@ -111867,7 +111867,7 @@ ${lanes.join("\n")}
|
|
|
111867
111867
|
writeOutputIsTTY() {
|
|
111868
111868
|
return process.stdout.isTTY;
|
|
111869
111869
|
},
|
|
111870
|
-
readFile:
|
|
111870
|
+
readFile: readFile3,
|
|
111871
111871
|
writeFile: writeFile22,
|
|
111872
111872
|
watchFile: watchFile2,
|
|
111873
111873
|
watchDirectory,
|
|
@@ -112073,7 +112073,7 @@ ${lanes.join("\n")}
|
|
|
112073
112073
|
callback
|
|
112074
112074
|
);
|
|
112075
112075
|
}
|
|
112076
|
-
function
|
|
112076
|
+
function readFile3(fileName, _encoding) {
|
|
112077
112077
|
let buffer;
|
|
112078
112078
|
try {
|
|
112079
112079
|
buffer = _fs.readFileSync(fileName);
|
|
@@ -124178,7 +124178,7 @@ ${lanes.join("\n")}
|
|
|
124178
124178
|
sourceFilePath = isSourceFileInCommonSourceDirectory ? sourceFilePath.substring(commonSourceDirectory.length) : sourceFilePath;
|
|
124179
124179
|
return combinePaths(newDirPath, sourceFilePath);
|
|
124180
124180
|
}
|
|
124181
|
-
function
|
|
124181
|
+
function writeFile3(host, diagnostics, fileName, text, writeByteOrderMark, sourceFiles, data) {
|
|
124182
124182
|
host.writeFile(
|
|
124183
124183
|
fileName,
|
|
124184
124184
|
text,
|
|
@@ -148629,7 +148629,7 @@ ${lanes.join("\n")}
|
|
|
148629
148629
|
const possibleOption = getSpellingSuggestion(unknownOption, diagnostics.optionDeclarations, getOptionName);
|
|
148630
148630
|
return possibleOption ? createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, diagnostics.unknownDidYouMeanDiagnostic, unknownOptionErrorText || unknownOption, possibleOption.name) : createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, diagnostics.unknownOptionDiagnostic, unknownOptionErrorText || unknownOption);
|
|
148631
148631
|
}
|
|
148632
|
-
function parseCommandLineWorker(diagnostics, commandLine,
|
|
148632
|
+
function parseCommandLineWorker(diagnostics, commandLine, readFile3) {
|
|
148633
148633
|
const options = {};
|
|
148634
148634
|
let watchOptions;
|
|
148635
148635
|
const fileNames = [];
|
|
@@ -148677,7 +148677,7 @@ ${lanes.join("\n")}
|
|
|
148677
148677
|
}
|
|
148678
148678
|
}
|
|
148679
148679
|
function parseResponseFile(fileName) {
|
|
148680
|
-
const text = tryReadFile(fileName,
|
|
148680
|
+
const text = tryReadFile(fileName, readFile3 || ((fileName2) => sys.readFile(fileName2)));
|
|
148681
148681
|
if (!isString(text)) {
|
|
148682
148682
|
errors2.push(text);
|
|
148683
148683
|
return;
|
|
@@ -148780,8 +148780,8 @@ ${lanes.join("\n")}
|
|
|
148780
148780
|
unknownDidYouMeanDiagnostic: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1,
|
|
148781
148781
|
optionTypeMismatchDiagnostic: Diagnostics.Compiler_option_0_expects_an_argument
|
|
148782
148782
|
};
|
|
148783
|
-
function parseCommandLine(commandLine,
|
|
148784
|
-
return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine,
|
|
148783
|
+
function parseCommandLine(commandLine, readFile3) {
|
|
148784
|
+
return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile3);
|
|
148785
148785
|
}
|
|
148786
148786
|
function getOptionFromName(optionName, allowShort) {
|
|
148787
148787
|
return getOptionDeclarationFromName(getOptionsNameMap, optionName, allowShort);
|
|
@@ -148863,8 +148863,8 @@ ${lanes.join("\n")}
|
|
|
148863
148863
|
watchOptionsToExtend
|
|
148864
148864
|
);
|
|
148865
148865
|
}
|
|
148866
|
-
function readConfigFile(fileName,
|
|
148867
|
-
const textOrDiagnostic = tryReadFile(fileName,
|
|
148866
|
+
function readConfigFile(fileName, readFile3) {
|
|
148867
|
+
const textOrDiagnostic = tryReadFile(fileName, readFile3);
|
|
148868
148868
|
return isString(textOrDiagnostic) ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic };
|
|
148869
148869
|
}
|
|
148870
148870
|
function parseConfigFileTextToJson(fileName, jsonText) {
|
|
@@ -148879,14 +148879,14 @@ ${lanes.join("\n")}
|
|
|
148879
148879
|
error: jsonSourceFile.parseDiagnostics.length ? jsonSourceFile.parseDiagnostics[0] : void 0
|
|
148880
148880
|
};
|
|
148881
148881
|
}
|
|
148882
|
-
function readJsonConfigFile(fileName,
|
|
148883
|
-
const textOrDiagnostic = tryReadFile(fileName,
|
|
148882
|
+
function readJsonConfigFile(fileName, readFile3) {
|
|
148883
|
+
const textOrDiagnostic = tryReadFile(fileName, readFile3);
|
|
148884
148884
|
return isString(textOrDiagnostic) ? parseJsonText(fileName, textOrDiagnostic) : { fileName, parseDiagnostics: [textOrDiagnostic] };
|
|
148885
148885
|
}
|
|
148886
|
-
function tryReadFile(fileName,
|
|
148886
|
+
function tryReadFile(fileName, readFile3) {
|
|
148887
148887
|
let text;
|
|
148888
148888
|
try {
|
|
148889
|
-
text =
|
|
148889
|
+
text = readFile3(fileName);
|
|
148890
148890
|
} catch (e) {
|
|
148891
148891
|
return createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message);
|
|
148892
148892
|
}
|
|
@@ -232929,7 +232929,7 @@ ${lanes.join("\n")}
|
|
|
232929
232929
|
return;
|
|
232930
232930
|
}
|
|
232931
232931
|
const buildInfo = host.getBuildInfo() || { version };
|
|
232932
|
-
|
|
232932
|
+
writeFile3(
|
|
232933
232933
|
host,
|
|
232934
232934
|
emitterDiagnostics,
|
|
232935
232935
|
buildInfoPath,
|
|
@@ -233141,7 +233141,7 @@ ${lanes.join("\n")}
|
|
|
233141
233141
|
}
|
|
233142
233142
|
if (sourceMapFilePath) {
|
|
233143
233143
|
const sourceMap = sourceMapGenerator.toString();
|
|
233144
|
-
|
|
233144
|
+
writeFile3(
|
|
233145
233145
|
host,
|
|
233146
233146
|
emitterDiagnostics,
|
|
233147
233147
|
sourceMapFilePath,
|
|
@@ -233156,7 +233156,7 @@ ${lanes.join("\n")}
|
|
|
233156
233156
|
}
|
|
233157
233157
|
const text = writer.getText();
|
|
233158
233158
|
const data = { sourceMapUrlPos, diagnostics: transform2.diagnostics };
|
|
233159
|
-
|
|
233159
|
+
writeFile3(host, emitterDiagnostics, jsFilePath, text, !!compilerOptions.emitBOM, sourceFiles, data);
|
|
233160
233160
|
writer.clear();
|
|
233161
233161
|
return !data.skippedDtsWrite;
|
|
233162
233162
|
}
|
|
@@ -238544,12 +238544,12 @@ ${lanes.join("\n")}
|
|
|
238544
238544
|
function createCompilerHost(options, setParentNodes) {
|
|
238545
238545
|
return createCompilerHostWorker(options, setParentNodes);
|
|
238546
238546
|
}
|
|
238547
|
-
function createGetSourceFile(
|
|
238547
|
+
function createGetSourceFile(readFile3, setParentNodes) {
|
|
238548
238548
|
return (fileName, languageVersionOrOptions, onError) => {
|
|
238549
238549
|
let text;
|
|
238550
238550
|
try {
|
|
238551
238551
|
mark("beforeIORead");
|
|
238552
|
-
text =
|
|
238552
|
+
text = readFile3(fileName);
|
|
238553
238553
|
mark("afterIORead");
|
|
238554
238554
|
measure("I/O Read", "beforeIORead", "afterIORead");
|
|
238555
238555
|
} catch (e) {
|
|
@@ -239453,7 +239453,7 @@ ${lanes.join("\n")}
|
|
|
239453
239453
|
getRedirectFromOutput,
|
|
239454
239454
|
forEachResolvedProjectReference: forEachResolvedProjectReference2
|
|
239455
239455
|
});
|
|
239456
|
-
const
|
|
239456
|
+
const readFile3 = host.readFile.bind(host);
|
|
239457
239457
|
(_e = tracing) == null ? void 0 : _e.push(tracing.Phase.Program, "shouldProgramCreateNewSourceFiles", { hasOldProgram: !!oldProgram });
|
|
239458
239458
|
const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options);
|
|
239459
239459
|
(_f = tracing) == null ? void 0 : _f.pop();
|
|
@@ -239679,7 +239679,7 @@ ${lanes.join("\n")}
|
|
|
239679
239679
|
shouldTransformImportCall,
|
|
239680
239680
|
emitBuildInfo,
|
|
239681
239681
|
fileExists,
|
|
239682
|
-
readFile:
|
|
239682
|
+
readFile: readFile3,
|
|
239683
239683
|
directoryExists,
|
|
239684
239684
|
getSymlinkCache,
|
|
239685
239685
|
realpath: (_o = host.realpath) == null ? void 0 : _o.bind(host),
|
|
@@ -301038,7 +301038,7 @@ ${options.prefix}` : "\n" : options.prefix
|
|
|
301038
301038
|
walkUpParenthesizedTypesAndGetParentAndChild: () => walkUpParenthesizedTypesAndGetParentAndChild,
|
|
301039
301039
|
whitespaceOrMapCommentRegExp: () => whitespaceOrMapCommentRegExp,
|
|
301040
301040
|
writeCommentRange: () => writeCommentRange,
|
|
301041
|
-
writeFile: () =>
|
|
301041
|
+
writeFile: () => writeFile3,
|
|
301042
301042
|
writeFileEnsuringDirectories: () => writeFileEnsuringDirectories,
|
|
301043
301043
|
zipWith: () => zipWith
|
|
301044
301044
|
});
|
|
@@ -347540,7 +347540,7 @@ var require_mkdir = __commonJS({
|
|
|
347540
347540
|
const gid = opt.gid;
|
|
347541
347541
|
const doChown = typeof uid === "number" && typeof gid === "number" && (uid !== opt.processUid || gid !== opt.processGid);
|
|
347542
347542
|
const preserve = opt.preserve;
|
|
347543
|
-
const
|
|
347543
|
+
const unlink3 = opt.unlink;
|
|
347544
347544
|
const cache = opt.cache;
|
|
347545
347545
|
const cwd = normPath(opt.cwd);
|
|
347546
347546
|
const done = (er, created) => {
|
|
@@ -347568,33 +347568,33 @@ var require_mkdir = __commonJS({
|
|
|
347568
347568
|
}
|
|
347569
347569
|
const sub = normPath(path.relative(cwd, dir));
|
|
347570
347570
|
const parts = sub.split("/");
|
|
347571
|
-
mkdir_(cwd, parts, mode, cache,
|
|
347571
|
+
mkdir_(cwd, parts, mode, cache, unlink3, cwd, null, done);
|
|
347572
347572
|
};
|
|
347573
|
-
var mkdir_ = (base2, parts, mode, cache,
|
|
347573
|
+
var mkdir_ = (base2, parts, mode, cache, unlink3, cwd, created, cb) => {
|
|
347574
347574
|
if (!parts.length) {
|
|
347575
347575
|
return cb(null, created);
|
|
347576
347576
|
}
|
|
347577
347577
|
const p = parts.shift();
|
|
347578
347578
|
const part = normPath(path.resolve(base2 + "/" + p));
|
|
347579
347579
|
if (cGet(cache, part)) {
|
|
347580
|
-
return mkdir_(part, parts, mode, cache,
|
|
347580
|
+
return mkdir_(part, parts, mode, cache, unlink3, cwd, created, cb);
|
|
347581
347581
|
}
|
|
347582
|
-
fs.mkdir(part, mode, onmkdir(part, parts, mode, cache,
|
|
347582
|
+
fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink3, cwd, created, cb));
|
|
347583
347583
|
};
|
|
347584
|
-
var onmkdir = (part, parts, mode, cache,
|
|
347584
|
+
var onmkdir = (part, parts, mode, cache, unlink3, cwd, created, cb) => (er) => {
|
|
347585
347585
|
if (er) {
|
|
347586
347586
|
fs.lstat(part, (statEr, st) => {
|
|
347587
347587
|
if (statEr) {
|
|
347588
347588
|
statEr.path = statEr.path && normPath(statEr.path);
|
|
347589
347589
|
cb(statEr);
|
|
347590
347590
|
} else if (st.isDirectory()) {
|
|
347591
|
-
mkdir_(part, parts, mode, cache,
|
|
347592
|
-
} else if (
|
|
347591
|
+
mkdir_(part, parts, mode, cache, unlink3, cwd, created, cb);
|
|
347592
|
+
} else if (unlink3) {
|
|
347593
347593
|
fs.unlink(part, (er2) => {
|
|
347594
347594
|
if (er2) {
|
|
347595
347595
|
return cb(er2);
|
|
347596
347596
|
}
|
|
347597
|
-
fs.mkdir(part, mode, onmkdir(part, parts, mode, cache,
|
|
347597
|
+
fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink3, cwd, created, cb));
|
|
347598
347598
|
});
|
|
347599
347599
|
} else if (st.isSymbolicLink()) {
|
|
347600
347600
|
return cb(new SymlinkError(part, part + "/" + parts.join("/")));
|
|
@@ -347604,7 +347604,7 @@ var require_mkdir = __commonJS({
|
|
|
347604
347604
|
});
|
|
347605
347605
|
} else {
|
|
347606
347606
|
created = created || part;
|
|
347607
|
-
mkdir_(part, parts, mode, cache,
|
|
347607
|
+
mkdir_(part, parts, mode, cache, unlink3, cwd, created, cb);
|
|
347608
347608
|
}
|
|
347609
347609
|
};
|
|
347610
347610
|
var checkCwdSync = (dir) => {
|
|
@@ -347629,7 +347629,7 @@ var require_mkdir = __commonJS({
|
|
|
347629
347629
|
const gid = opt.gid;
|
|
347630
347630
|
const doChown = typeof uid === "number" && typeof gid === "number" && (uid !== opt.processUid || gid !== opt.processGid);
|
|
347631
347631
|
const preserve = opt.preserve;
|
|
347632
|
-
const
|
|
347632
|
+
const unlink3 = opt.unlink;
|
|
347633
347633
|
const cache = opt.cache;
|
|
347634
347634
|
const cwd = normPath(opt.cwd);
|
|
347635
347635
|
const done = (created2) => {
|
|
@@ -347668,7 +347668,7 @@ var require_mkdir = __commonJS({
|
|
|
347668
347668
|
if (st.isDirectory()) {
|
|
347669
347669
|
cSet(cache, part, true);
|
|
347670
347670
|
continue;
|
|
347671
|
-
} else if (
|
|
347671
|
+
} else if (unlink3) {
|
|
347672
347672
|
fs.unlinkSync(part);
|
|
347673
347673
|
fs.mkdirSync(part, mode);
|
|
347674
347674
|
created = created || part;
|
|
@@ -353342,7 +353342,7 @@ var require_package4 = __commonJS({
|
|
|
353342
353342
|
"package.json"(exports, module) {
|
|
353343
353343
|
module.exports = {
|
|
353344
353344
|
name: "@validate.qa/runner",
|
|
353345
|
-
version: "1.0.
|
|
353345
|
+
version: "1.0.83",
|
|
353346
353346
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
353347
353347
|
bin: {
|
|
353348
353348
|
"validate-runner": "dist/cli.js"
|
|
@@ -355291,6 +355291,7 @@ import { execFile as execFile3 } from "child_process";
|
|
|
355291
355291
|
import { promisify as promisify3 } from "util";
|
|
355292
355292
|
import { tmpdir as tmpdir3 } from "os";
|
|
355293
355293
|
import { join as join5 } from "path";
|
|
355294
|
+
import { readFile as readFile2, unlink as unlink2, writeFile as writeFile2 } from "fs/promises";
|
|
355294
355295
|
|
|
355295
355296
|
// src/services/device-discovery.ts
|
|
355296
355297
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
@@ -356727,6 +356728,12 @@ async function createSession(target) {
|
|
|
356727
356728
|
// whole survey. The runner owns session teardown explicitly, so a long
|
|
356728
356729
|
// idle window is safe. Env-overridable.
|
|
356729
356730
|
"appium:newCommandTimeout": MOBILE_NEW_COMMAND_TIMEOUT_SECONDS,
|
|
356731
|
+
// Expo / universal-link "Open in App?" system sheets and permission dialogs
|
|
356732
|
+
// otherwise sit on top of the app and can stall WDA snapshot/command proxy
|
|
356733
|
+
// for the entire discovery session (observed: 20+ min hung XCUITest with
|
|
356734
|
+
// ECONNREFUSED on wdaLocalPort). Auto-accept keeps the AUT interactive.
|
|
356735
|
+
// Override with APPIUM_IOS_AUTO_ACCEPT_ALERTS=false for explicit dialog tests.
|
|
356736
|
+
...process.env.APPIUM_IOS_AUTO_ACCEPT_ALERTS !== "false" ? { "appium:autoAcceptAlerts": true } : {},
|
|
356730
356737
|
// Xcode 27+ relocates Simulator.app out of <DEVELOPER_DIR>/Applications, so
|
|
356731
356738
|
// XCUITest's default "open Simulator UI" step fails ("Simulator.app does not
|
|
356732
356739
|
// exist") on this toolchain. The simulator is already booted and driven
|
|
@@ -356982,20 +356989,102 @@ async function startScreenRecording(sessionId, platform3, log2) {
|
|
|
356982
356989
|
if (platform3 === "ANDROID") {
|
|
356983
356990
|
options.bitRate = readPositiveIntEnv("VALIDATEQA_MOBILE_RECORDING_BITRATE", 75e4, 1e5, 4e6);
|
|
356984
356991
|
} else {
|
|
356985
|
-
options.
|
|
356992
|
+
options.videoType = process.env.VALIDATEQA_MOBILE_RECORDING_IOS_VIDEO_TYPE || "libx264";
|
|
356993
|
+
options.videoQuality = process.env.VALIDATEQA_MOBILE_RECORDING_IOS_QUALITY || "medium";
|
|
356994
|
+
options.videoFilters = process.env.VALIDATEQA_MOBILE_RECORDING_IOS_FILTERS || "scale=trunc(iw/2)*2:trunc(ih/2)*2";
|
|
356986
356995
|
}
|
|
356987
356996
|
try {
|
|
356988
356997
|
await wdRequest(`/session/${sessionId}/appium/start_recording_screen`, {
|
|
356989
356998
|
method: "POST",
|
|
356990
356999
|
body: JSON.stringify({ options })
|
|
356991
357000
|
});
|
|
356992
|
-
log2(`[mobile-video] screen recording started (${timeLimit}s max)`);
|
|
357001
|
+
log2(`[mobile-video] screen recording started (${timeLimit}s max${platform3 === "IOS" ? `, type=${options.videoType}` : ""})`);
|
|
356993
357002
|
return true;
|
|
356994
357003
|
} catch (err) {
|
|
357004
|
+
if (platform3 === "IOS" && options.videoType) {
|
|
357005
|
+
try {
|
|
357006
|
+
const fallback = {
|
|
357007
|
+
timeLimit: String(timeLimit),
|
|
357008
|
+
forceRestart: true,
|
|
357009
|
+
videoQuality: process.env.VALIDATEQA_MOBILE_RECORDING_IOS_QUALITY || "medium"
|
|
357010
|
+
};
|
|
357011
|
+
await wdRequest(`/session/${sessionId}/appium/start_recording_screen`, {
|
|
357012
|
+
method: "POST",
|
|
357013
|
+
body: JSON.stringify({ options: fallback })
|
|
357014
|
+
});
|
|
357015
|
+
log2(`[mobile-video] screen recording started with quality-only fallback (${timeLimit}s max); will re-encode on stop`);
|
|
357016
|
+
return true;
|
|
357017
|
+
} catch (fallbackErr) {
|
|
357018
|
+
log2(`[mobile-video] screen recording unavailable: ${fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)}`);
|
|
357019
|
+
return false;
|
|
357020
|
+
}
|
|
357021
|
+
}
|
|
356995
357022
|
log2(`[mobile-video] screen recording unavailable: ${err instanceof Error ? err.message : String(err)}`);
|
|
356996
357023
|
return false;
|
|
356997
357024
|
}
|
|
356998
357025
|
}
|
|
357026
|
+
async function ensureBrowserPlayableMp4Base64(base64Video, log2) {
|
|
357027
|
+
if (process.env.VALIDATEQA_MOBILE_VIDEO_REENCODE === "0") {
|
|
357028
|
+
return base64Video;
|
|
357029
|
+
}
|
|
357030
|
+
const inPath = join5(tmpdir3(), `validateqa-mobile-rec-in-${Date.now()}-${Math.random().toString(36).slice(2)}.mp4`);
|
|
357031
|
+
const outPath = join5(tmpdir3(), `validateqa-mobile-rec-out-${Date.now()}-${Math.random().toString(36).slice(2)}.mp4`);
|
|
357032
|
+
try {
|
|
357033
|
+
await writeFile2(inPath, Buffer.from(base64Video, "base64"));
|
|
357034
|
+
try {
|
|
357035
|
+
const { stdout } = await execFileAsync3("ffprobe", [
|
|
357036
|
+
"-v",
|
|
357037
|
+
"error",
|
|
357038
|
+
"-select_streams",
|
|
357039
|
+
"v:0",
|
|
357040
|
+
"-show_entries",
|
|
357041
|
+
"stream=codec_name",
|
|
357042
|
+
"-of",
|
|
357043
|
+
"csv=p=0",
|
|
357044
|
+
inPath
|
|
357045
|
+
], { timeout: 15e3 });
|
|
357046
|
+
const codec = stdout.trim().toLowerCase();
|
|
357047
|
+
if (codec === "h264" || codec === "avc1") {
|
|
357048
|
+
log2(`[mobile-video] recording already H.264 (${codec}); skipping re-encode`);
|
|
357049
|
+
return base64Video;
|
|
357050
|
+
}
|
|
357051
|
+
log2(`[mobile-video] re-encoding ${codec || "unknown"} \u2192 H.264 for browser preview`);
|
|
357052
|
+
} catch {
|
|
357053
|
+
log2("[mobile-video] codec probe failed; attempting H.264 re-encode anyway");
|
|
357054
|
+
}
|
|
357055
|
+
await execFileAsync3("ffmpeg", [
|
|
357056
|
+
"-y",
|
|
357057
|
+
"-i",
|
|
357058
|
+
inPath,
|
|
357059
|
+
"-vf",
|
|
357060
|
+
"scale=trunc(iw/2)*2:trunc(ih/2)*2",
|
|
357061
|
+
"-c:v",
|
|
357062
|
+
"libx264",
|
|
357063
|
+
"-pix_fmt",
|
|
357064
|
+
"yuv420p",
|
|
357065
|
+
"-preset",
|
|
357066
|
+
"veryfast",
|
|
357067
|
+
"-crf",
|
|
357068
|
+
"28",
|
|
357069
|
+
"-movflags",
|
|
357070
|
+
"+faststart",
|
|
357071
|
+
"-an",
|
|
357072
|
+
outPath
|
|
357073
|
+
], { timeout: 12e4 });
|
|
357074
|
+
const encoded = await readFile2(outPath);
|
|
357075
|
+
if (encoded.length < 100) {
|
|
357076
|
+
log2("[mobile-video] re-encode produced empty file; keeping original");
|
|
357077
|
+
return base64Video;
|
|
357078
|
+
}
|
|
357079
|
+
log2(`[mobile-video] re-encoded for browser (${Math.round(encoded.length / 1024)}KB)`);
|
|
357080
|
+
return encoded.toString("base64");
|
|
357081
|
+
} catch (err) {
|
|
357082
|
+
log2(`[mobile-video] re-encode skipped/failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
357083
|
+
return base64Video;
|
|
357084
|
+
} finally {
|
|
357085
|
+
await Promise.allSettled([unlink2(inPath), unlink2(outPath)]);
|
|
357086
|
+
}
|
|
357087
|
+
}
|
|
356999
357088
|
async function stopScreenRecording(sessionId, log2) {
|
|
357000
357089
|
try {
|
|
357001
357090
|
const result = await wdRequest(`/session/${sessionId}/appium/stop_recording_screen`, {
|
|
@@ -357008,7 +357097,7 @@ async function stopScreenRecording(sessionId, log2) {
|
|
|
357008
357097
|
return void 0;
|
|
357009
357098
|
}
|
|
357010
357099
|
log2(`[mobile-video] screen recording captured (${Math.round(value.length / 1024)}KB base64)`);
|
|
357011
|
-
return value;
|
|
357100
|
+
return ensureBrowserPlayableMp4Base64(value, log2);
|
|
357012
357101
|
} catch (err) {
|
|
357013
357102
|
log2(`[mobile-video] screen recording stop failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
357014
357103
|
return void 0;
|
|
@@ -357093,10 +357182,17 @@ function xpathLiteral3(value) {
|
|
|
357093
357182
|
const parts = value.split("'").map((p) => `'${p}'`);
|
|
357094
357183
|
return `concat(${parts.join(`, "'", `)})`;
|
|
357095
357184
|
}
|
|
357185
|
+
var XPATH_ASCII_UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
357186
|
+
var XPATH_ASCII_LOWER = "abcdefghijklmnopqrstuvwxyz";
|
|
357096
357187
|
function identityTextXpath2(value) {
|
|
357097
357188
|
const lit = xpathLiteral3(value);
|
|
357098
357189
|
return `//*[@text=${lit} or @content-desc=${lit} or @label=${lit} or @name=${lit} or @value=${lit}]`;
|
|
357099
357190
|
}
|
|
357191
|
+
function identityTextXpathCaseInsensitive(value) {
|
|
357192
|
+
const lit = xpathLiteral3(value.toLowerCase());
|
|
357193
|
+
const fold = (attr) => `translate(@${attr}, '${XPATH_ASCII_UPPER}', '${XPATH_ASCII_LOWER}')=${lit}`;
|
|
357194
|
+
return `//*[${fold("text")} or ${fold("content-desc")} or ${fold("label")} or ${fold("name")} or ${fold("value")}]`;
|
|
357195
|
+
}
|
|
357100
357196
|
function iosPredicateLiteral(value) {
|
|
357101
357197
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
357102
357198
|
}
|
|
@@ -357104,19 +357200,28 @@ function iosTextIdentityPredicate(value) {
|
|
|
357104
357200
|
const lit = iosPredicateLiteral(value);
|
|
357105
357201
|
return `label == ${lit} OR name == ${lit} OR value == ${lit}`;
|
|
357106
357202
|
}
|
|
357203
|
+
function iosTextIdentityPredicateCaseInsensitive(value) {
|
|
357204
|
+
const lit = iosPredicateLiteral(value);
|
|
357205
|
+
return `label ==[c] ${lit} OR name ==[c] ${lit} OR value ==[c] ${lit}`;
|
|
357206
|
+
}
|
|
357107
357207
|
function textIdentityStrategies(value, platform3) {
|
|
357108
357208
|
const strategies = [];
|
|
357109
357209
|
if (platform3 === "IOS") {
|
|
357110
357210
|
strategies.push({ using: "-ios predicate string", value: iosTextIdentityPredicate(value) });
|
|
357211
|
+
strategies.push({ using: "-ios predicate string", value: iosTextIdentityPredicateCaseInsensitive(value) });
|
|
357111
357212
|
}
|
|
357112
357213
|
strategies.push({ using: "xpath", value: identityTextXpath2(value) });
|
|
357214
|
+
strategies.push({ using: "xpath", value: identityTextXpathCaseInsensitive(value) });
|
|
357113
357215
|
return strategies;
|
|
357114
357216
|
}
|
|
357115
357217
|
function isIdentityIosPredicate(value) {
|
|
357116
|
-
return /(?:^|[\s(])(?:name|label|value)\s
|
|
357218
|
+
return /(?:^|[\s(])(?:name|label|value)\s*==(?:\[[cC]\])?\s*"/.test(value) && !/\b(?:CONTAINS|BEGINSWITH|ENDSWITH|LIKE|MATCHES|IN)\b/i.test(value);
|
|
357117
357219
|
}
|
|
357118
357220
|
function isIdentityXpath(value) {
|
|
357119
|
-
|
|
357221
|
+
if (/(?:contains|starts-with)\s*\(/i.test(value)) return false;
|
|
357222
|
+
if (/@[A-Za-z_:][-A-Za-z0-9_:.]*\s*=/.test(value)) return true;
|
|
357223
|
+
if (/translate\s*\(\s*@[A-Za-z_:][-A-Za-z0-9_:.]*\s*,/.test(value) && /\)\s*=/.test(value)) return true;
|
|
357224
|
+
return false;
|
|
357120
357225
|
}
|
|
357121
357226
|
function isIdentityStrategy(strategy) {
|
|
357122
357227
|
if (strategy.using === "accessibility id" || strategy.using === "id") return true;
|
|
@@ -357939,7 +358044,12 @@ function mobileMjpegPortForDevice(platform3, deviceId, env = process.env) {
|
|
|
357939
358044
|
return typeof port === "number" && Number.isFinite(port) ? port : null;
|
|
357940
358045
|
}
|
|
357941
358046
|
var PINNED_DEVICE_SNAPSHOT_TTL_MS = 25 * 6e4;
|
|
357942
|
-
var
|
|
358047
|
+
var pinnedDeviceSnapshots = /* @__PURE__ */ new Map();
|
|
358048
|
+
function prunePinnedDeviceSnapshots(now = Date.now()) {
|
|
358049
|
+
for (const [id, entry] of pinnedDeviceSnapshots) {
|
|
358050
|
+
if (now - entry.at >= PINNED_DEVICE_SNAPSHOT_TTL_MS) pinnedDeviceSnapshots.delete(id);
|
|
358051
|
+
}
|
|
358052
|
+
}
|
|
357943
358053
|
async function executeMobileTest(options) {
|
|
357944
358054
|
const logLines = [];
|
|
357945
358055
|
const log2 = (msg) => logLines.push(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}`);
|
|
@@ -358014,8 +358124,10 @@ async function executeMobileTest(options) {
|
|
|
358014
358124
|
}
|
|
358015
358125
|
const platformFilter = options.target.platform;
|
|
358016
358126
|
const pinnedDeviceId = options.target.assignedDeviceId;
|
|
358017
|
-
const
|
|
358018
|
-
|
|
358127
|
+
const now = Date.now();
|
|
358128
|
+
prunePinnedDeviceSnapshots(now);
|
|
358129
|
+
const candidateSnapshot = pinnedDeviceId ? pinnedDeviceSnapshots.get(pinnedDeviceId) : void 0;
|
|
358130
|
+
const reusablePinnedSnapshot = pinnedDeviceId && candidateSnapshot && now - candidateSnapshot.at < PINNED_DEVICE_SNAPSHOT_TTL_MS ? candidateSnapshot : null;
|
|
358019
358131
|
if (reusablePinnedSnapshot) {
|
|
358020
358132
|
assertDeviceNotQuarantined(reusablePinnedSnapshot.device.id);
|
|
358021
358133
|
selectedDevice = reusablePinnedSnapshot.device;
|
|
@@ -358031,22 +358143,24 @@ async function executeMobileTest(options) {
|
|
|
358031
358143
|
`No ${platformFilter} ${platformFilter === "IOS" ? "simulator" : "emulator"} booted. Start one via ${platformFilter === "IOS" ? "Simulator.app" : "Android Studio"} first.`
|
|
358032
358144
|
);
|
|
358033
358145
|
}
|
|
358034
|
-
if (options.target.assignedDeviceId) {
|
|
358035
|
-
|
|
358036
|
-
|
|
358037
|
-
|
|
358038
|
-
`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.`
|
|
358039
|
-
);
|
|
358040
|
-
}
|
|
358041
|
-
}
|
|
358042
|
-
if (!selectedDevice && options.target.recordedDeviceId) {
|
|
358043
|
-
selectedDevice = matchingDevices.find((d) => d.id === options.target.recordedDeviceId);
|
|
358146
|
+
if (!options.target.assignedDeviceId) {
|
|
358147
|
+
throw new Error(
|
|
358148
|
+
"CONFIG_ISSUE: mobile test requires an assigned device lease; refusing unpinned session create."
|
|
358149
|
+
);
|
|
358044
358150
|
}
|
|
358151
|
+
selectedDevice = matchingDevices.find((d) => d.id === options.target.assignedDeviceId);
|
|
358045
358152
|
if (!selectedDevice) {
|
|
358046
|
-
|
|
358153
|
+
throw new Error(
|
|
358154
|
+
`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.`
|
|
358155
|
+
);
|
|
358047
358156
|
}
|
|
358048
358157
|
log2(`Using device: ${selectedDevice.name} (${selectedDevice.id})`);
|
|
358049
|
-
|
|
358158
|
+
if (options.target.assignedDeviceId) {
|
|
358159
|
+
pinnedDeviceSnapshots.set(options.target.assignedDeviceId, {
|
|
358160
|
+
device: selectedDevice,
|
|
358161
|
+
at: Date.now()
|
|
358162
|
+
});
|
|
358163
|
+
}
|
|
358050
358164
|
}
|
|
358051
358165
|
try {
|
|
358052
358166
|
capture = await startMobileNetworkCapture({
|
|
@@ -358382,20 +358496,16 @@ async function createMobileDiscoverySession(target, options) {
|
|
|
358382
358496
|
`No ${platformFilter} ${platformFilter === "IOS" ? "simulator" : "emulator"} booted. Start one via ${platformFilter === "IOS" ? "Simulator.app" : "Android Studio"} first.`
|
|
358383
358497
|
);
|
|
358384
358498
|
}
|
|
358385
|
-
|
|
358386
|
-
|
|
358387
|
-
|
|
358388
|
-
|
|
358389
|
-
throw new Error(
|
|
358390
|
-
`CONFIG_ISSUE: assigned ${platformFilter} device ${target.assignedDeviceId} is no longer booted for mobile discovery; will retry.`
|
|
358391
|
-
);
|
|
358392
|
-
}
|
|
358393
|
-
}
|
|
358394
|
-
if (!selectedDevice && target.recordedDeviceId) {
|
|
358395
|
-
selectedDevice = matchingDevices.find((d) => d.id === target.recordedDeviceId);
|
|
358499
|
+
if (!target.assignedDeviceId) {
|
|
358500
|
+
throw new Error(
|
|
358501
|
+
"CONFIG_ISSUE: mobile discovery requires an assigned device lease; refusing unpinned session create."
|
|
358502
|
+
);
|
|
358396
358503
|
}
|
|
358504
|
+
const selectedDevice = matchingDevices.find((d) => d.id === target.assignedDeviceId);
|
|
358397
358505
|
if (!selectedDevice) {
|
|
358398
|
-
|
|
358506
|
+
throw new Error(
|
|
358507
|
+
`CONFIG_ISSUE: assigned ${platformFilter} device ${target.assignedDeviceId} is no longer booted for mobile discovery; will retry.`
|
|
358508
|
+
);
|
|
358399
358509
|
}
|
|
358400
358510
|
if (selectedDevice.installedApps && selectedDevice.installedApps.length > 0 && !selectedDevice.installedApps.includes(target.appId)) {
|
|
358401
358511
|
throw new Error(
|
|
@@ -358837,6 +358947,10 @@ function computeMobileCapacity(input) {
|
|
|
358837
358947
|
}
|
|
358838
358948
|
return Math.max(0, cap);
|
|
358839
358949
|
}
|
|
358950
|
+
function discoveryKindOf(ctx) {
|
|
358951
|
+
if (!isNativeMobileDiscoveryContext(ctx)) return null;
|
|
358952
|
+
return ctx?.mode === "survey" ? "survey" : "deep";
|
|
358953
|
+
}
|
|
358840
358954
|
function decideMobileDeviceGate(input) {
|
|
358841
358955
|
const {
|
|
358842
358956
|
runMode,
|
|
@@ -358847,8 +358961,10 @@ function decideMobileDeviceGate(input) {
|
|
|
358847
358961
|
readyDevices,
|
|
358848
358962
|
capacity,
|
|
358849
358963
|
freeDeviceId,
|
|
358850
|
-
|
|
358851
|
-
|
|
358964
|
+
discoveryKind,
|
|
358965
|
+
activeMobileSurveys,
|
|
358966
|
+
activeMobileDeeps,
|
|
358967
|
+
maxMobileDeeps
|
|
358852
358968
|
} = input;
|
|
358853
358969
|
if (!platform3) {
|
|
358854
358970
|
return { claim: false, disposition: "pending", reason: "mobile run has no supported target platform" };
|
|
@@ -358860,8 +358976,18 @@ function decideMobileDeviceGate(input) {
|
|
|
358860
358976
|
reason: `no booted ${platform3 === "IOS" ? "iOS" : "Android"} device for mobile run`
|
|
358861
358977
|
};
|
|
358862
358978
|
}
|
|
358863
|
-
if (
|
|
358864
|
-
return { claim: false, disposition: "defer", reason: "native
|
|
358979
|
+
if (discoveryKind === "survey" && activeMobileSurveys >= 1) {
|
|
358980
|
+
return { claim: false, disposition: "defer", reason: "native survey serialized to 1 concurrent" };
|
|
358981
|
+
}
|
|
358982
|
+
if (discoveryKind === "deep") {
|
|
358983
|
+
const deepCap = Math.max(0, maxMobileDeeps);
|
|
358984
|
+
if (activeMobileDeeps >= deepCap) {
|
|
358985
|
+
return {
|
|
358986
|
+
claim: false,
|
|
358987
|
+
disposition: "defer",
|
|
358988
|
+
reason: `native deep discovery capacity reached: ${activeMobileDeeps}/${deepCap}`
|
|
358989
|
+
};
|
|
358990
|
+
}
|
|
358865
358991
|
}
|
|
358866
358992
|
const isHealForHeldTest = runMode === "heal" && !!testCaseId && healHoldTestCaseIds.has(testCaseId);
|
|
358867
358993
|
const reservedForHeals = isHealForHeldTest ? Math.max(0, healHoldTestCaseIds.size - 1) : healHoldTestCaseIds.size;
|
|
@@ -361269,11 +361395,12 @@ var RUNNER_BUILD_ID = process.env.VALIDATE_RUNNER_BUILD_ID ?? process.env.RAILWA
|
|
|
361269
361395
|
var DEFAULT_MAX_CONCURRENT = IS_CLOUD ? 4 : 40;
|
|
361270
361396
|
var DEFAULT_TYPE_LIMITS = {
|
|
361271
361397
|
discovery: IS_CLOUD ? 2 : 15,
|
|
361272
|
-
//
|
|
361273
|
-
//
|
|
361274
|
-
// (
|
|
361275
|
-
// and
|
|
361276
|
-
|
|
361398
|
+
// Max concurrent native deep discoveries on one local runner (survey is always
|
|
361399
|
+
// 1 via decideMobileDeviceGate). Applied by the device gate as maxMobileDeeps
|
|
361400
|
+
// (MAX_MOBILE_DISCOVERY); the resource-governor type key is run.mode='discovery'
|
|
361401
|
+
// and does NOT read this entry — do not treat TYPE_LIMITS['mobile-discovery'] as
|
|
361402
|
+
// the live governor. Env default 2 enables two deeps when two free devices exist.
|
|
361403
|
+
"mobile-discovery": 2,
|
|
361277
361404
|
heal: IS_CLOUD ? 2 : 8,
|
|
361278
361405
|
"regression-suite": IS_CLOUD ? 1 : 2,
|
|
361279
361406
|
"system-regression-suite": IS_CLOUD ? 1 : 2,
|
|
@@ -361293,7 +361420,7 @@ function envInt(name, fallback) {
|
|
|
361293
361420
|
}
|
|
361294
361421
|
var MOBILE_DEVICE_CEILING = Math.max(0, envInt("MOBILE_DEVICE_CEILING", 0));
|
|
361295
361422
|
var MOBILE_HEAL_HOLD_MS = Math.max(0, envInt("MOBILE_HEAL_HOLD_MS", 2e4));
|
|
361296
|
-
var MOBILE_RUN_WATCHDOG_MS = Math.max(0, envInt("MOBILE_RUN_WATCHDOG_MS",
|
|
361423
|
+
var MOBILE_RUN_WATCHDOG_MS = Math.max(0, envInt("MOBILE_RUN_WATCHDOG_MS", 60 * 6e4));
|
|
361297
361424
|
var MOBILE_RUN_ABORT_GRACE_MS = Math.max(0, envInt("MOBILE_RUN_ABORT_GRACE_MS", 2 * 6e4));
|
|
361298
361425
|
var MOBILE_CANCEL_POLL_MS = Math.max(0, envInt("MOBILE_CANCEL_POLL_MS", 15e3));
|
|
361299
361426
|
var MobileRunWatchdog = class {
|
|
@@ -362922,10 +363049,10 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
362922
363049
|
log.fail("ERROR - malformed locator-discovery context");
|
|
362923
363050
|
return;
|
|
362924
363051
|
}
|
|
362925
|
-
if (getBridgeState().
|
|
363052
|
+
if (getBridgeState().recordingActive) {
|
|
362926
363053
|
await postResult(opts.serverUrl, headers, run2.runId, {
|
|
362927
363054
|
status: "ERROR",
|
|
362928
|
-
error: "CONFIG_ISSUE:
|
|
363055
|
+
error: "CONFIG_ISSUE: a mobile recording session is active; retry once it ends."
|
|
362929
363056
|
});
|
|
362930
363057
|
log.fail("ERROR - device busy (recording) for locator discovery");
|
|
362931
363058
|
return;
|
|
@@ -363354,6 +363481,11 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363354
363481
|
if (!mobileTarget || !mobileTarget.appId) {
|
|
363355
363482
|
throw new Error("Native mobile discovery requires a target with an appId (bundleId/appPackage).");
|
|
363356
363483
|
}
|
|
363484
|
+
if (!assignedDeviceId) {
|
|
363485
|
+
throw new Error(
|
|
363486
|
+
"CONFIG_ISSUE: mobile discovery was claimed without a device lease; refusing unpinned session create."
|
|
363487
|
+
);
|
|
363488
|
+
}
|
|
363357
363489
|
const targetPlatform = mobileTarget.platform;
|
|
363358
363490
|
const platform3 = mobileMedium === "IOS_NATIVE" ? "IOS" : mobileMedium === "ANDROID_NATIVE" ? "ANDROID" : targetPlatform === "IOS" || targetPlatform === "ANDROID" ? targetPlatform : null;
|
|
363359
363491
|
if (!platform3) {
|
|
@@ -363362,8 +363494,8 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363362
363494
|
if (mobileMedium === "IOS_NATIVE" && targetPlatform === "ANDROID" || mobileMedium === "ANDROID_NATIVE" && targetPlatform === "IOS") {
|
|
363363
363495
|
throw new Error("Native mobile discovery medium does not match target.platform.");
|
|
363364
363496
|
}
|
|
363365
|
-
if (getBridgeState().
|
|
363366
|
-
throw new Error("CONFIG_ISSUE:
|
|
363497
|
+
if (getBridgeState().recordingActive) {
|
|
363498
|
+
throw new Error("CONFIG_ISSUE: a mobile recording session is active; mobile discovery will retry once it ends.");
|
|
363367
363499
|
}
|
|
363368
363500
|
const mobileProgressLines = [];
|
|
363369
363501
|
const mobileOnLog = (line) => {
|
|
@@ -363841,10 +363973,10 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363841
363973
|
return;
|
|
363842
363974
|
}
|
|
363843
363975
|
if (runMode === "heal" && run2.mobileAiHeal === true) {
|
|
363844
|
-
if (getBridgeState().
|
|
363976
|
+
if (getBridgeState().recordingActive) {
|
|
363845
363977
|
await postResult(opts.serverUrl, headers, run2.runId, {
|
|
363846
363978
|
status: "ERROR",
|
|
363847
|
-
error: "CONFIG_ISSUE:
|
|
363979
|
+
error: "CONFIG_ISSUE: a mobile recording session is active; the AI heal will retry once it ends.",
|
|
363848
363980
|
duration: Date.now() - startTime
|
|
363849
363981
|
});
|
|
363850
363982
|
log.warn("Skipped mobile AI heal (device busy with a recording)");
|
|
@@ -364582,7 +364714,9 @@ async function startRunner(opts) {
|
|
|
364582
364714
|
};
|
|
364583
364715
|
const leasedDeviceIds = /* @__PURE__ */ new Map();
|
|
364584
364716
|
const activeMobileRuns = /* @__PURE__ */ new Map();
|
|
364585
|
-
let
|
|
364717
|
+
let activeMobileSurveys = 0;
|
|
364718
|
+
let activeMobileDeeps = 0;
|
|
364719
|
+
const MAX_MOBILE_DEEPS = Math.max(0, envInt("MAX_MOBILE_DISCOVERY", DEFAULT_TYPE_LIMITS["mobile-discovery"]));
|
|
364586
364720
|
const mobileHealHold = /* @__PURE__ */ new Map();
|
|
364587
364721
|
const pruneMobileHealHolds = () => {
|
|
364588
364722
|
if (mobileHealHold.size === 0) return;
|
|
@@ -364927,7 +365061,7 @@ async function startRunner(opts) {
|
|
|
364927
365061
|
if (activeRuns >= MAX_CONCURRENT) break;
|
|
364928
365062
|
const runMode = run2.mode ?? "run";
|
|
364929
365063
|
const isMobileOp = enableMobile && isMobileDeviceOp(run2);
|
|
364930
|
-
const
|
|
365064
|
+
const discoveryKind = isMobileOp ? discoveryKindOf(run2.discoveryContext) : null;
|
|
364931
365065
|
let leasedDeviceId = null;
|
|
364932
365066
|
let mobileLeaseError;
|
|
364933
365067
|
const runProject = projectKey(run2);
|
|
@@ -364946,7 +365080,7 @@ async function startRunner(opts) {
|
|
|
364946
365080
|
logDeferredOnce(run2.runId, ` Deferred ${run2.runId} (${admission.reason})`);
|
|
364947
365081
|
continue;
|
|
364948
365082
|
}
|
|
364949
|
-
if (enableMobile &&
|
|
365083
|
+
if (enableMobile && isMobileOp && getBridgeState().recordingActive) {
|
|
364950
365084
|
logDeferredOnce(run2.runId, ` Deferred ${run2.runId} (mobile recording session active)`);
|
|
364951
365085
|
continue;
|
|
364952
365086
|
}
|
|
@@ -364962,10 +365096,6 @@ async function startRunner(opts) {
|
|
|
364962
365096
|
logDeferredOnce(run2.runId, ` Left PENDING ${run2.runId} (no booted ${discoveryMedium === "IOS_NATIVE" ? "iOS" : "Android"} device for native mobile discovery)`);
|
|
364963
365097
|
continue;
|
|
364964
365098
|
}
|
|
364965
|
-
if (getBridgeState().hasActiveSession) {
|
|
364966
|
-
logDeferredOnce(run2.runId, ` Deferred ${run2.runId} (mobile recording session active on shared device)`);
|
|
364967
|
-
continue;
|
|
364968
|
-
}
|
|
364969
365099
|
}
|
|
364970
365100
|
const mobilePlatform = isMobileOp ? mobilePlatformOf(run2) : null;
|
|
364971
365101
|
if (isMobileOp && mobilePlatform) {
|
|
@@ -365007,13 +365137,18 @@ async function startRunner(opts) {
|
|
|
365007
365137
|
readyDevices: virtual + physical,
|
|
365008
365138
|
capacity,
|
|
365009
365139
|
freeDeviceId,
|
|
365010
|
-
|
|
365011
|
-
|
|
365140
|
+
discoveryKind,
|
|
365141
|
+
activeMobileSurveys,
|
|
365142
|
+
activeMobileDeeps,
|
|
365143
|
+
maxMobileDeeps: MAX_MOBILE_DEEPS
|
|
365012
365144
|
});
|
|
365013
365145
|
if (!decision.claim) {
|
|
365014
365146
|
logDeferredOnce(run2.runId, ` ${decision.disposition === "pending" ? "Left PENDING" : "Deferred"} ${run2.runId} (${decision.reason})`);
|
|
365015
365147
|
continue;
|
|
365016
365148
|
}
|
|
365149
|
+
log.dim(
|
|
365150
|
+
` [mobile-gate] admit ${discoveryKind ?? runMode} device=${freeDeviceId} surveys=${activeMobileSurveys} deeps=${activeMobileDeeps}/${MAX_MOBILE_DEEPS} ops=${getActiveMobileOps(mobilePlatform)}/${capacity}`
|
|
365151
|
+
);
|
|
365017
365152
|
leasedDeviceId = freeDeviceId;
|
|
365018
365153
|
leasedDeviceIds.set(freeDeviceId, run2.runId);
|
|
365019
365154
|
}
|
|
@@ -365041,7 +365176,8 @@ async function startRunner(opts) {
|
|
|
365041
365176
|
incrementProject(runProject);
|
|
365042
365177
|
if (isMobileOp) {
|
|
365043
365178
|
if (mobilePlatform) bumpActiveMobileOps(mobilePlatform, 1);
|
|
365044
|
-
if (
|
|
365179
|
+
if (discoveryKind === "survey") activeMobileSurveys++;
|
|
365180
|
+
else if (discoveryKind === "deep") activeMobileDeeps++;
|
|
365045
365181
|
if (runMode === "heal" && run2.testCaseId) mobileHealHold.delete(run2.testCaseId);
|
|
365046
365182
|
}
|
|
365047
365183
|
if (claimedThisPoll > 0 && staggerMs > 0) {
|
|
@@ -365054,7 +365190,8 @@ async function startRunner(opts) {
|
|
|
365054
365190
|
decrementProject(runProject);
|
|
365055
365191
|
if (isMobileOp) {
|
|
365056
365192
|
if (mobilePlatform) bumpActiveMobileOps(mobilePlatform, -1);
|
|
365057
|
-
if (
|
|
365193
|
+
if (discoveryKind === "survey") activeMobileSurveys = Math.max(0, activeMobileSurveys - 1);
|
|
365194
|
+
else if (discoveryKind === "deep") activeMobileDeeps = Math.max(0, activeMobileDeeps - 1);
|
|
365058
365195
|
if (leasedDeviceId) leasedDeviceIds.delete(leasedDeviceId);
|
|
365059
365196
|
activeMobileRuns.delete(run2.runId);
|
|
365060
365197
|
}
|