@validate.qa/runner 1.0.81 → 1.0.82
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 +268 -68
- package/dist/cli.mjs +268 -68
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1024,7 +1024,8 @@ var require_mobile_source_parser = __commonJS({
|
|
|
1024
1024
|
const isGenericView = /android\.view\.View$/i.test(role);
|
|
1025
1025
|
const moderateHitTarget = hasArea && bounds.width >= 24 && bounds.height >= 24 && bounds.width <= 2e3 && bounds.height <= 1800;
|
|
1026
1026
|
const webHybridLabelledControl = isGenericView && hasHumanIdentity && moderateHitTarget && (focusable || clickable || longClickable || Boolean(contentDesc));
|
|
1027
|
-
const
|
|
1027
|
+
const isAnrChrome = typeof resourceId === "string" && /(?:android:id\/)?aerr_/i.test(resourceId) || typeof text === "string" && /isn['’]?t responding/i.test(text) || typeof label === "string" && /isn['’]?t responding/i.test(label);
|
|
1028
|
+
const actionable = !isAnrChrome && enabled && hasArea && (clickable || longClickable || classActionable || focusable && classActionable || focusable && hasHumanIdentity && !isGenericView || webHybridLabelledControl);
|
|
1028
1029
|
return {
|
|
1029
1030
|
role,
|
|
1030
1031
|
label,
|
|
@@ -1202,6 +1203,7 @@ var require_mobile_source_parser = __commonJS({
|
|
|
1202
1203
|
element.secure = fields.secure;
|
|
1203
1204
|
if (fields.enabled !== void 0)
|
|
1204
1205
|
element.enabled = fields.enabled;
|
|
1206
|
+
element.visible = fields.visible;
|
|
1205
1207
|
if (isCredibleStructuralNavigationTarget(element) && chainHasNativeNavigationSemantics(chain, platform3)) {
|
|
1206
1208
|
markNavigation(element, "native-navigation-semantics");
|
|
1207
1209
|
}
|
|
@@ -1576,6 +1578,8 @@ var require_mobile_boundary = __commonJS({
|
|
|
1576
1578
|
"../runner-core/dist/services/mobile/mobile-boundary.js"(exports) {
|
|
1577
1579
|
"use strict";
|
|
1578
1580
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1581
|
+
exports.isAndroidAnrSystemResourceId = isAndroidAnrSystemResourceId;
|
|
1582
|
+
exports.looksLikeAndroidAnrUi = looksLikeAndroidAnrUi;
|
|
1579
1583
|
exports.mobileOwnerFromScreenSignal = mobileOwnerFromScreenSignal;
|
|
1580
1584
|
exports.isTransientMobileSystemOwner = isTransientMobileSystemOwner;
|
|
1581
1585
|
exports.classifyMobileScreenBoundary = classifyMobileScreenBoundary2;
|
|
@@ -1593,6 +1597,28 @@ var require_mobile_boundary = __commonJS({
|
|
|
1593
1597
|
"com.miui.securitycenter",
|
|
1594
1598
|
"com.samsung.android.permissioncontroller"
|
|
1595
1599
|
]);
|
|
1600
|
+
var ANDROID_ANR_RESOURCE_ID_RE = /^(?:android:id\/)?(?:aerr_(?:wait|close|close_app|mute|report|restart)|alertTitle|button1|button2|button3|parentPanel|topPanel|contentPanel|customPanel|buttonPanel|scrollView|textSpacerNoButtons|textSpacerNoTitle)$/i;
|
|
1601
|
+
var ANDROID_ANR_TEXT_RE = /isn['’]?t responding|isn['’]?t responding\.|anr|close app|wait/i;
|
|
1602
|
+
function isAndroidAnrSystemResourceId(resourceId) {
|
|
1603
|
+
const rid = resourceId?.trim();
|
|
1604
|
+
if (!rid)
|
|
1605
|
+
return false;
|
|
1606
|
+
if (/(?:android:id\/)?aerr_/i.test(rid))
|
|
1607
|
+
return true;
|
|
1608
|
+
return ANDROID_ANR_RESOURCE_ID_RE.test(rid) && /aerr_/i.test(rid);
|
|
1609
|
+
}
|
|
1610
|
+
function looksLikeAndroidAnrUi(input) {
|
|
1611
|
+
if (isAndroidAnrSystemResourceId(input.resourceId))
|
|
1612
|
+
return true;
|
|
1613
|
+
const rid = input.resourceId?.trim() ?? "";
|
|
1614
|
+
const blob = [input.text, input.label, input.contentDesc].filter(Boolean).join(" ");
|
|
1615
|
+
if (/(?:android:id\/)?(?:alertTitle|aerr_)/i.test(rid) && ANDROID_ANR_TEXT_RE.test(blob)) {
|
|
1616
|
+
return true;
|
|
1617
|
+
}
|
|
1618
|
+
if (/isn['’]?t responding/i.test(blob))
|
|
1619
|
+
return true;
|
|
1620
|
+
return false;
|
|
1621
|
+
}
|
|
1596
1622
|
var IOS_TRANSIENT_SYSTEM_APP_IDS = /* @__PURE__ */ new Set([
|
|
1597
1623
|
"com.apple.springboard",
|
|
1598
1624
|
"com.apple.authenticationservicesui",
|
|
@@ -1750,7 +1776,7 @@ var init_constants = __esm({
|
|
|
1750
1776
|
MOBILE_APP_ID_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,253}[A-Za-z0-9])?$/;
|
|
1751
1777
|
MOBILE_FOCUS_SETTLE_MS = 150;
|
|
1752
1778
|
MOBILE_POST_ACTION_SETTLE_MS = 1800;
|
|
1753
|
-
MOBILE_COLD_BOOT_SETTLE_MS =
|
|
1779
|
+
MOBILE_COLD_BOOT_SETTLE_MS = 25e3;
|
|
1754
1780
|
MOBILE_DEFAULT_SWIPE_DISTANCE = 500;
|
|
1755
1781
|
MOBILE_MAX_SWIPE_DISTANCE = 2e3;
|
|
1756
1782
|
SELECTOR_PRIORITIES = [
|
|
@@ -74892,7 +74918,7 @@ var require_mobile_explorer = __commonJS({
|
|
|
74892
74918
|
type: "function",
|
|
74893
74919
|
function: {
|
|
74894
74920
|
name: "mobile_relaunch",
|
|
74895
|
-
description: "Terminate and relaunch the app for a clean, hermetic state (sessions are noReset=true, so Home only backgrounds).
|
|
74921
|
+
description: "Terminate and relaunch the app for a clean, hermetic state (sessions are noReset=true, so Home only backgrounds). Only for CLEAR_APP_DATA / cold flows. Blocked when app data is being preserved (PRESERVE_APP_DATA or survey AUTO). Do not use on a working product screen. Re-snapshot afterward.",
|
|
74896
74922
|
parameters: { type: "object", properties: {} }
|
|
74897
74923
|
}
|
|
74898
74924
|
};
|
|
@@ -75692,11 +75718,11 @@ Rules:
|
|
|
75692
75718
|
**Phase 2 \u2014 Logged-in breadth first (~35% of budget).** Once signed in, find the primary navigation (bottom tab bar, drawer, or menu) and visit EVERY top-level destination ONCE, observing each screen, BEFORE drilling into any of them. A survey that maps one tab deeply but never opens the other tabs has failed \u2014 breadth across all tabs/drawer entries is mandatory.
|
|
75693
75719
|
**Phase 3 \u2014 Depth (remaining budget).** Return to each observed tab and explore its sub-screens: open ONE list row per list (rows share a template), open menus/sheets/dialogs, observe detail screens, and mobile_swipe up/down to reveal off-screen content. record_transition whenever you move between screens.
|
|
75694
75720
|
|
|
75695
|
-
Rules that apply throughout: snapshots and post-action observations capture screen evidence automatically. Use capture_screen sparingly to name/classify important stable destinations (use screenType "LANDING" for welcome/onboarding/intro and "AUTH" for sign-in/sign-up), but never spend turns renaming already-observed structural variants. Record transitions on every move; mobile_back to return
|
|
75721
|
+
Rules that apply throughout: snapshots and post-action observations capture screen evidence automatically. Use capture_screen sparingly to name/classify important stable destinations (use screenType "LANDING" for welcome/onboarding/intro and "AUTH" for sign-in/sign-up), but never spend turns renaming already-observed structural variants. Record transitions on every move; mobile_back to return. Prefer staying on the current app instance \u2014 do not cold-relaunch unless the product UI is truly stuck after a long wait. When no new screens or safe navigation controls remain, call finish_exploration.
|
|
75696
75722
|
|
|
75697
75723
|
## Boundaries (survey = map, do not mutate)
|
|
75698
75724
|
- Do NOT submit forms, save, delete, purchase, or send anything. You MAY open modals/menus to catalog them, then dismiss.
|
|
75699
|
-
- Stay inside this app.${targetLine} Do not leave to the system home screen or other apps (mobile_home backgrounds \u2014 avoid it). If a tap opens an app store, settings, browser, or another app, do not explore it; return with mobile_back
|
|
75725
|
+
- Stay inside this app.${targetLine} Do not leave to the system home screen or other apps (mobile_home backgrounds \u2014 avoid it). If a tap opens an app store, settings, browser, or another app, do not explore it; return with mobile_back (not a cold relaunch).
|
|
75700
75726
|
- If you hit a login wall and have no credentials, capture the auth screen and explore what is reachable.
|
|
75701
75727
|
|
|
75702
75728
|
Max iterations: ${maxIterations}. You will be warned near the end.`;
|
|
@@ -76107,6 +76133,8 @@ ${existingScreens.slice(0, 100).map((s) => `- ${s.screenId}${s.name ? ` "${s.nam
|
|
|
76107
76133
|
const kickoff = buildKickoffMessage(mode);
|
|
76108
76134
|
const recentExchanges = [];
|
|
76109
76135
|
const transientDirectives = [];
|
|
76136
|
+
const resetPolicyForTools = ctx.target?.resetPolicy ?? "AUTO";
|
|
76137
|
+
const preserveAppDataForTools = resetPolicyForTools === "PRESERVE_APP_DATA" || mode === "survey" && resetPolicyForTools === "AUTO";
|
|
76110
76138
|
const tools = [
|
|
76111
76139
|
MOBILE_SNAPSHOT_TOOL,
|
|
76112
76140
|
MOBILE_TAP_TOOL,
|
|
@@ -76116,7 +76144,8 @@ ${existingScreens.slice(0, 100).map((s) => `- ${s.screenId}${s.name ? ` "${s.nam
|
|
|
76116
76144
|
MOBILE_SWIPE_TOOL,
|
|
76117
76145
|
MOBILE_BACK_TOOL,
|
|
76118
76146
|
MOBILE_HOME_TOOL,
|
|
76119
|
-
|
|
76147
|
+
// Omit relaunch under preserve — model otherwise cold-restarts RN into splash.
|
|
76148
|
+
...preserveAppDataForTools ? [] : [MOBILE_RELAUNCH_TOOL],
|
|
76120
76149
|
MOBILE_OPEN_DEEPLINK_TOOL,
|
|
76121
76150
|
CAPTURE_SCREEN_TOOL,
|
|
76122
76151
|
RECORD_TRANSITION_TOOL,
|
|
@@ -76472,7 +76501,8 @@ Exploration complete: ${summary}`);
|
|
|
76472
76501
|
credentials: credentialSecrets,
|
|
76473
76502
|
redactionCredentials: credentialValuesForRedaction,
|
|
76474
76503
|
authTracker,
|
|
76475
|
-
inbox: inboxEnabled && inboxState && inboxReader ? { state: inboxState, reader: inboxReader } : null
|
|
76504
|
+
inbox: inboxEnabled && inboxState && inboxReader ? { state: inboxState, reader: inboxReader } : null,
|
|
76505
|
+
allowRelaunch: !preserveAppDataForTools
|
|
76476
76506
|
});
|
|
76477
76507
|
pushResult(handled);
|
|
76478
76508
|
const handledRecord = handled && typeof handled === "object" ? handled : null;
|
|
@@ -76565,7 +76595,7 @@ Exploration complete: ${summary}`);
|
|
|
76565
76595
|
return result;
|
|
76566
76596
|
}
|
|
76567
76597
|
async function dispatchMobileTool(args) {
|
|
76568
|
-
const { toolName, toolArgs, iteration, driver, state: state2, enableRecordJourney, latest, buildResolved, absorbResolvedObservation, resolveRef, resolveHybridRef, currentBoundary, collectedTransitions, counters, journeys, log: log2, credentials, redactionCredentials, authTracker, inbox } = args;
|
|
76598
|
+
const { toolName, toolArgs, iteration, driver, state: state2, enableRecordJourney, latest, buildResolved, absorbResolvedObservation, resolveRef, resolveHybridRef, currentBoundary, collectedTransitions, counters, journeys, log: log2, credentials, redactionCredentials, authTracker, inbox, allowRelaunch } = args;
|
|
76569
76599
|
const absorbActionResult = (actionResult, source, preferCredentialSecondary = false) => {
|
|
76570
76600
|
(0, mobile_driver_errors_js_1.throwIfTerminalMobileActionFailure)(actionResult, source);
|
|
76571
76601
|
const resolved = buildResolved(actionResult.source, latest()?.screen.windowSize ?? null, actionResult.screenSignal);
|
|
@@ -76968,6 +76998,13 @@ Exploration complete: ${summary}`);
|
|
|
76968
76998
|
}
|
|
76969
76999
|
// ----- Relaunch (hermetic reset) -----
|
|
76970
77000
|
case "mobile_relaunch": {
|
|
77001
|
+
if (!allowRelaunch) {
|
|
77002
|
+
return {
|
|
77003
|
+
ok: false,
|
|
77004
|
+
error: "mobile_relaunch is blocked while app data is being preserved. Use mobile_back / mobile_snapshot to continue mapping; request CLEAR_APP_DATA for a true cold restart.",
|
|
77005
|
+
note: "Relaunch refused under preserve policy."
|
|
77006
|
+
};
|
|
77007
|
+
}
|
|
76971
77008
|
authTracker?.noteInterveningMutation(iteration);
|
|
76972
77009
|
await driver.relaunch();
|
|
76973
77010
|
log2(" Relaunched app for hermetic reset.");
|
|
@@ -82030,7 +82067,7 @@ Start by calling mobile_snapshot, then execute the steps above \u2014 the minimu
|
|
|
82030
82067
|
}
|
|
82031
82068
|
}
|
|
82032
82069
|
async function runScenarioLoop(args) {
|
|
82033
|
-
const { scenario, driver, platform: platform3, maxIterations, agentModel, provider, aiClient, auditGroupId, onAICall, onScreenshot, targetAppId, credentials, redactionCredentials, log: emitLog } = args;
|
|
82070
|
+
const { scenario, driver, platform: platform3, maxIterations, agentModel, provider, aiClient, auditGroupId, onAICall, onScreenshot, targetAppId, credentials, redactionCredentials, log: emitLog, allowRelaunch } = args;
|
|
82034
82071
|
const log2 = (line) => emitLog(redactCredentialValues(line, redactionCredentials));
|
|
82035
82072
|
const actions = [];
|
|
82036
82073
|
let assertCount = 0;
|
|
@@ -82604,6 +82641,7 @@ ${statePacket}`, redactionCredentials) },
|
|
|
82604
82641
|
setLatest,
|
|
82605
82642
|
credentials,
|
|
82606
82643
|
screenName: () => lastScreenName || "UnknownScreen",
|
|
82644
|
+
allowRelaunch,
|
|
82607
82645
|
recordAction: (action) => {
|
|
82608
82646
|
const screenHash = getLatest()?.signal.screenHash;
|
|
82609
82647
|
const structuralKey = structuralScreenKey(getLatest());
|
|
@@ -82638,7 +82676,7 @@ ${statePacket}`, redactionCredentials) },
|
|
|
82638
82676
|
return { actions, assertCount, finish, lastSnapshot: lastTargetSnapshot, lastScreenName: lastTargetScreenName };
|
|
82639
82677
|
}
|
|
82640
82678
|
async function dispatchGenerateTool(args) {
|
|
82641
|
-
const { toolName, toolArgs, driver, platform: platform3, targetAppId, latest, absorbObservation, resolveRef, resolveHybridRef, maybeEnrichHybridWeb, setLatest, credentials, screenName, recordAction, recordedActions, log: log2 } = args;
|
|
82679
|
+
const { toolName, toolArgs, driver, platform: platform3, targetAppId, latest, absorbObservation, resolveRef, resolveHybridRef, maybeEnrichHybridWeb, setLatest, credentials, screenName, allowRelaunch, recordAction, recordedActions, log: log2 } = args;
|
|
82642
82680
|
const unknownRef = (ref) => ({
|
|
82643
82681
|
ok: false,
|
|
82644
82682
|
error: `Unknown element ref "${String(ref)}". Call mobile_snapshot first; refs are only valid for the latest snapshot.`
|
|
@@ -83028,6 +83066,16 @@ ${statePacket}`, redactionCredentials) },
|
|
|
83028
83066
|
return actionPayload(result, absorbed);
|
|
83029
83067
|
}
|
|
83030
83068
|
case "mobile_relaunch": {
|
|
83069
|
+
if (!allowRelaunch) {
|
|
83070
|
+
log2(" relaunch refused under PRESERVE_APP_DATA \u2014 staying on current app instance.");
|
|
83071
|
+
const snap2 = await driver.snapshot();
|
|
83072
|
+
const absorbed2 = await absorbObservation(snap2.xml, snap2.screenSignal, snap2.windowSize, snap2.screenshot, "mobile_relaunch_blocked");
|
|
83073
|
+
return {
|
|
83074
|
+
ok: false,
|
|
83075
|
+
error: "mobile_relaunch is blocked while app data is preserved. Continue from the current screen.",
|
|
83076
|
+
observation: absorbed2.observation
|
|
83077
|
+
};
|
|
83078
|
+
}
|
|
83031
83079
|
await driver.relaunch();
|
|
83032
83080
|
const snap = await driver.snapshot();
|
|
83033
83081
|
const absorbed = await absorbObservation(snap.xml, snap.screenSignal, snap.windowSize, snap.screenshot, "mobile_relaunch");
|
|
@@ -83246,14 +83294,48 @@ ${statePacket}`, redactionCredentials) },
|
|
|
83246
83294
|
});
|
|
83247
83295
|
return { ok: true, label: metadata.targetLabel ?? accessibilityId ?? text ?? "element" };
|
|
83248
83296
|
}
|
|
83297
|
+
var AMBIENT_CHROME_LABEL_RE = /^(your\s*profile|you|home|search|profile|more|settings|notifications?|tab\s*\d+(\s*of\s*\d+)?|you,\s*tab.*)$/i;
|
|
83298
|
+
function isDisplayableLandingCandidate(el, windowSize) {
|
|
83299
|
+
if (el.visible === false)
|
|
83300
|
+
return false;
|
|
83301
|
+
const { x, y, width, height } = el.bounds;
|
|
83302
|
+
if (!(width > 0 && height > 0))
|
|
83303
|
+
return false;
|
|
83304
|
+
if (width < 8 || height < 8)
|
|
83305
|
+
return false;
|
|
83306
|
+
if (windowSize && windowSize.width > 0 && windowSize.height > 0) {
|
|
83307
|
+
const centerX = x + width / 2;
|
|
83308
|
+
const centerY = y + height / 2;
|
|
83309
|
+
if (centerX < 0 || centerY < 0 || centerX > windowSize.width || centerY > windowSize.height) {
|
|
83310
|
+
return false;
|
|
83311
|
+
}
|
|
83312
|
+
if (width >= windowSize.width * 0.92 && height >= windowSize.height * 0.55) {
|
|
83313
|
+
return false;
|
|
83314
|
+
}
|
|
83315
|
+
}
|
|
83316
|
+
return true;
|
|
83317
|
+
}
|
|
83318
|
+
function landingAnchorSortKey(el) {
|
|
83319
|
+
const label = (nonEmpty3(el.label) || nonEmpty3(el.text) || "").trim();
|
|
83320
|
+
const ambient = AMBIENT_CHROME_LABEL_RE.test(label) ? 1 : 0;
|
|
83321
|
+
const labelled = nonEmpty3(el.text) || nonEmpty3(el.label) ? 0 : 1;
|
|
83322
|
+
const area = Math.max(1, el.bounds.width * el.bounds.height);
|
|
83323
|
+
const areaBucket = area < 8e4 ? 0 : area < 25e4 ? 1 : 2;
|
|
83324
|
+
return [ambient, labelled, areaBucket];
|
|
83325
|
+
}
|
|
83249
83326
|
function pickLandingAnchor(snapshot) {
|
|
83250
83327
|
if (!snapshot || snapshot.screen.elements.length === 0)
|
|
83251
83328
|
return null;
|
|
83252
83329
|
const elements = snapshot.screen.elements;
|
|
83253
|
-
const
|
|
83254
|
-
stableAnchors.sort((a, b) => {
|
|
83255
|
-
const
|
|
83256
|
-
|
|
83330
|
+
const windowSize = snapshot.screen.windowSize ?? null;
|
|
83331
|
+
const stableAnchors = elements.filter((el) => hasStableId(el) && !weakAssertionTargetReason(el) && isDisplayableLandingCandidate(el, windowSize)).sort((a, b) => {
|
|
83332
|
+
const ka = landingAnchorSortKey(a);
|
|
83333
|
+
const kb = landingAnchorSortKey(b);
|
|
83334
|
+
for (let i = 0; i < ka.length; i += 1) {
|
|
83335
|
+
if (ka[i] !== kb[i])
|
|
83336
|
+
return ka[i] - kb[i];
|
|
83337
|
+
}
|
|
83338
|
+
return 0;
|
|
83257
83339
|
});
|
|
83258
83340
|
const contentfulNonButton = stableAnchors.find((el) => !/button/i.test(el.role) && (nonEmpty3(el.label) || nonEmpty3(el.text)));
|
|
83259
83341
|
if (contentfulNonButton)
|
|
@@ -83263,8 +83345,10 @@ ${statePacket}`, redactionCredentials) },
|
|
|
83263
83345
|
return nonButton;
|
|
83264
83346
|
if (stableAnchors.length > 0)
|
|
83265
83347
|
return stableAnchors[0];
|
|
83266
|
-
const labelled = elements.find((el) => !weakAssertionTargetReason(el) && (nonEmpty3(el.label) || nonEmpty3(el.text)));
|
|
83267
|
-
|
|
83348
|
+
const labelled = elements.find((el) => !weakAssertionTargetReason(el) && isDisplayableLandingCandidate(el, windowSize) && (nonEmpty3(el.label) || nonEmpty3(el.text)));
|
|
83349
|
+
if (labelled)
|
|
83350
|
+
return labelled;
|
|
83351
|
+
return elements.find((el) => !weakAssertionTargetReason(el) && isDisplayableLandingCandidate(el, windowSize)) ?? null;
|
|
83268
83352
|
}
|
|
83269
83353
|
var ALWAYS_PRESENT_CONTAINER_ROLES = /^(XCUIElementTypeApplication|XCUIElementTypeWindow)$/;
|
|
83270
83354
|
var ALWAYS_PRESENT_CONTAINER_ID_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -83362,10 +83446,19 @@ ${statePacket}`, redactionCredentials) },
|
|
|
83362
83446
|
const anchor = pickLandingAnchor(recording.lastSnapshot);
|
|
83363
83447
|
if (!anchor)
|
|
83364
83448
|
return false;
|
|
83449
|
+
const modelAssertCount = actions.filter((a) => a.type === "ASSERT_VISIBLE" || a.type === "ASSERT_TEXT").length;
|
|
83450
|
+
const anchorLabel = (nonEmpty3(anchor.label) || nonEmpty3(anchor.text) || "").trim();
|
|
83451
|
+
if (modelAssertCount > 0 && AMBIENT_CHROME_LABEL_RE.test(anchorLabel)) {
|
|
83452
|
+
return false;
|
|
83453
|
+
}
|
|
83454
|
+
const anchorSelector = (0, mobile_source_parser_js_2.preferredLocator)(anchor);
|
|
83455
|
+
const alreadyAsserted = actions.some((a) => (a.type === "ASSERT_VISIBLE" || a.type === "ASSERT_TEXT") && typeof a.selector === "string" && a.selector === anchorSelector);
|
|
83456
|
+
if (alreadyAsserted)
|
|
83457
|
+
return false;
|
|
83365
83458
|
const screen = recording.lastScreenName || "LandingScreen";
|
|
83366
83459
|
actions.push(finalizeAction({
|
|
83367
83460
|
type: "ASSERT_VISIBLE",
|
|
83368
|
-
selector:
|
|
83461
|
+
selector: anchorSelector,
|
|
83369
83462
|
metadata: metadataForElement(anchor, screen, platform3, nativeElementsOf(recording.lastSnapshot))
|
|
83370
83463
|
}));
|
|
83371
83464
|
return true;
|
|
@@ -84241,7 +84334,8 @@ ${statePacket}`, redactionCredentials) },
|
|
|
84241
84334
|
targetAppId,
|
|
84242
84335
|
credentials: credentialSecrets,
|
|
84243
84336
|
redactionCredentials: credentialValuesForRedaction,
|
|
84244
|
-
log: log2
|
|
84337
|
+
log: log2,
|
|
84338
|
+
allowRelaunch: (ctx.target?.resetPolicy ?? "AUTO") !== "PRESERVE_APP_DATA"
|
|
84245
84339
|
});
|
|
84246
84340
|
consecutiveInfraFailures = 0;
|
|
84247
84341
|
const test = assembleTest({
|
|
@@ -105535,7 +105629,7 @@ var require_typescript = __commonJS({
|
|
|
105535
105629
|
walkUpParenthesizedTypesAndGetParentAndChild: () => walkUpParenthesizedTypesAndGetParentAndChild,
|
|
105536
105630
|
whitespaceOrMapCommentRegExp: () => whitespaceOrMapCommentRegExp,
|
|
105537
105631
|
writeCommentRange: () => writeCommentRange,
|
|
105538
|
-
writeFile: () =>
|
|
105632
|
+
writeFile: () => writeFile3,
|
|
105539
105633
|
writeFileEnsuringDirectories: () => writeFileEnsuringDirectories,
|
|
105540
105634
|
zipWith: () => zipWith
|
|
105541
105635
|
});
|
|
@@ -111867,7 +111961,7 @@ ${lanes.join("\n")}
|
|
|
111867
111961
|
writeOutputIsTTY() {
|
|
111868
111962
|
return process.stdout.isTTY;
|
|
111869
111963
|
},
|
|
111870
|
-
readFile:
|
|
111964
|
+
readFile: readFile3,
|
|
111871
111965
|
writeFile: writeFile22,
|
|
111872
111966
|
watchFile: watchFile2,
|
|
111873
111967
|
watchDirectory,
|
|
@@ -112073,7 +112167,7 @@ ${lanes.join("\n")}
|
|
|
112073
112167
|
callback
|
|
112074
112168
|
);
|
|
112075
112169
|
}
|
|
112076
|
-
function
|
|
112170
|
+
function readFile3(fileName, _encoding) {
|
|
112077
112171
|
let buffer;
|
|
112078
112172
|
try {
|
|
112079
112173
|
buffer = _fs.readFileSync(fileName);
|
|
@@ -124178,7 +124272,7 @@ ${lanes.join("\n")}
|
|
|
124178
124272
|
sourceFilePath = isSourceFileInCommonSourceDirectory ? sourceFilePath.substring(commonSourceDirectory.length) : sourceFilePath;
|
|
124179
124273
|
return combinePaths(newDirPath, sourceFilePath);
|
|
124180
124274
|
}
|
|
124181
|
-
function
|
|
124275
|
+
function writeFile3(host, diagnostics, fileName, text, writeByteOrderMark, sourceFiles, data) {
|
|
124182
124276
|
host.writeFile(
|
|
124183
124277
|
fileName,
|
|
124184
124278
|
text,
|
|
@@ -148629,7 +148723,7 @@ ${lanes.join("\n")}
|
|
|
148629
148723
|
const possibleOption = getSpellingSuggestion(unknownOption, diagnostics.optionDeclarations, getOptionName);
|
|
148630
148724
|
return possibleOption ? createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, diagnostics.unknownDidYouMeanDiagnostic, unknownOptionErrorText || unknownOption, possibleOption.name) : createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, diagnostics.unknownOptionDiagnostic, unknownOptionErrorText || unknownOption);
|
|
148631
148725
|
}
|
|
148632
|
-
function parseCommandLineWorker(diagnostics, commandLine,
|
|
148726
|
+
function parseCommandLineWorker(diagnostics, commandLine, readFile3) {
|
|
148633
148727
|
const options = {};
|
|
148634
148728
|
let watchOptions;
|
|
148635
148729
|
const fileNames = [];
|
|
@@ -148677,7 +148771,7 @@ ${lanes.join("\n")}
|
|
|
148677
148771
|
}
|
|
148678
148772
|
}
|
|
148679
148773
|
function parseResponseFile(fileName) {
|
|
148680
|
-
const text = tryReadFile(fileName,
|
|
148774
|
+
const text = tryReadFile(fileName, readFile3 || ((fileName2) => sys.readFile(fileName2)));
|
|
148681
148775
|
if (!isString(text)) {
|
|
148682
148776
|
errors2.push(text);
|
|
148683
148777
|
return;
|
|
@@ -148780,8 +148874,8 @@ ${lanes.join("\n")}
|
|
|
148780
148874
|
unknownDidYouMeanDiagnostic: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1,
|
|
148781
148875
|
optionTypeMismatchDiagnostic: Diagnostics.Compiler_option_0_expects_an_argument
|
|
148782
148876
|
};
|
|
148783
|
-
function parseCommandLine(commandLine,
|
|
148784
|
-
return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine,
|
|
148877
|
+
function parseCommandLine(commandLine, readFile3) {
|
|
148878
|
+
return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile3);
|
|
148785
148879
|
}
|
|
148786
148880
|
function getOptionFromName(optionName, allowShort) {
|
|
148787
148881
|
return getOptionDeclarationFromName(getOptionsNameMap, optionName, allowShort);
|
|
@@ -148863,8 +148957,8 @@ ${lanes.join("\n")}
|
|
|
148863
148957
|
watchOptionsToExtend
|
|
148864
148958
|
);
|
|
148865
148959
|
}
|
|
148866
|
-
function readConfigFile(fileName,
|
|
148867
|
-
const textOrDiagnostic = tryReadFile(fileName,
|
|
148960
|
+
function readConfigFile(fileName, readFile3) {
|
|
148961
|
+
const textOrDiagnostic = tryReadFile(fileName, readFile3);
|
|
148868
148962
|
return isString(textOrDiagnostic) ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic };
|
|
148869
148963
|
}
|
|
148870
148964
|
function parseConfigFileTextToJson(fileName, jsonText) {
|
|
@@ -148879,14 +148973,14 @@ ${lanes.join("\n")}
|
|
|
148879
148973
|
error: jsonSourceFile.parseDiagnostics.length ? jsonSourceFile.parseDiagnostics[0] : void 0
|
|
148880
148974
|
};
|
|
148881
148975
|
}
|
|
148882
|
-
function readJsonConfigFile(fileName,
|
|
148883
|
-
const textOrDiagnostic = tryReadFile(fileName,
|
|
148976
|
+
function readJsonConfigFile(fileName, readFile3) {
|
|
148977
|
+
const textOrDiagnostic = tryReadFile(fileName, readFile3);
|
|
148884
148978
|
return isString(textOrDiagnostic) ? parseJsonText(fileName, textOrDiagnostic) : { fileName, parseDiagnostics: [textOrDiagnostic] };
|
|
148885
148979
|
}
|
|
148886
|
-
function tryReadFile(fileName,
|
|
148980
|
+
function tryReadFile(fileName, readFile3) {
|
|
148887
148981
|
let text;
|
|
148888
148982
|
try {
|
|
148889
|
-
text =
|
|
148983
|
+
text = readFile3(fileName);
|
|
148890
148984
|
} catch (e) {
|
|
148891
148985
|
return createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message);
|
|
148892
148986
|
}
|
|
@@ -232929,7 +233023,7 @@ ${lanes.join("\n")}
|
|
|
232929
233023
|
return;
|
|
232930
233024
|
}
|
|
232931
233025
|
const buildInfo = host.getBuildInfo() || { version };
|
|
232932
|
-
|
|
233026
|
+
writeFile3(
|
|
232933
233027
|
host,
|
|
232934
233028
|
emitterDiagnostics,
|
|
232935
233029
|
buildInfoPath,
|
|
@@ -233141,7 +233235,7 @@ ${lanes.join("\n")}
|
|
|
233141
233235
|
}
|
|
233142
233236
|
if (sourceMapFilePath) {
|
|
233143
233237
|
const sourceMap = sourceMapGenerator.toString();
|
|
233144
|
-
|
|
233238
|
+
writeFile3(
|
|
233145
233239
|
host,
|
|
233146
233240
|
emitterDiagnostics,
|
|
233147
233241
|
sourceMapFilePath,
|
|
@@ -233156,7 +233250,7 @@ ${lanes.join("\n")}
|
|
|
233156
233250
|
}
|
|
233157
233251
|
const text = writer.getText();
|
|
233158
233252
|
const data = { sourceMapUrlPos, diagnostics: transform2.diagnostics };
|
|
233159
|
-
|
|
233253
|
+
writeFile3(host, emitterDiagnostics, jsFilePath, text, !!compilerOptions.emitBOM, sourceFiles, data);
|
|
233160
233254
|
writer.clear();
|
|
233161
233255
|
return !data.skippedDtsWrite;
|
|
233162
233256
|
}
|
|
@@ -238544,12 +238638,12 @@ ${lanes.join("\n")}
|
|
|
238544
238638
|
function createCompilerHost(options, setParentNodes) {
|
|
238545
238639
|
return createCompilerHostWorker(options, setParentNodes);
|
|
238546
238640
|
}
|
|
238547
|
-
function createGetSourceFile(
|
|
238641
|
+
function createGetSourceFile(readFile3, setParentNodes) {
|
|
238548
238642
|
return (fileName, languageVersionOrOptions, onError) => {
|
|
238549
238643
|
let text;
|
|
238550
238644
|
try {
|
|
238551
238645
|
mark("beforeIORead");
|
|
238552
|
-
text =
|
|
238646
|
+
text = readFile3(fileName);
|
|
238553
238647
|
mark("afterIORead");
|
|
238554
238648
|
measure("I/O Read", "beforeIORead", "afterIORead");
|
|
238555
238649
|
} catch (e) {
|
|
@@ -239453,7 +239547,7 @@ ${lanes.join("\n")}
|
|
|
239453
239547
|
getRedirectFromOutput,
|
|
239454
239548
|
forEachResolvedProjectReference: forEachResolvedProjectReference2
|
|
239455
239549
|
});
|
|
239456
|
-
const
|
|
239550
|
+
const readFile3 = host.readFile.bind(host);
|
|
239457
239551
|
(_e = tracing) == null ? void 0 : _e.push(tracing.Phase.Program, "shouldProgramCreateNewSourceFiles", { hasOldProgram: !!oldProgram });
|
|
239458
239552
|
const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options);
|
|
239459
239553
|
(_f = tracing) == null ? void 0 : _f.pop();
|
|
@@ -239679,7 +239773,7 @@ ${lanes.join("\n")}
|
|
|
239679
239773
|
shouldTransformImportCall,
|
|
239680
239774
|
emitBuildInfo,
|
|
239681
239775
|
fileExists,
|
|
239682
|
-
readFile:
|
|
239776
|
+
readFile: readFile3,
|
|
239683
239777
|
directoryExists,
|
|
239684
239778
|
getSymlinkCache,
|
|
239685
239779
|
realpath: (_o = host.realpath) == null ? void 0 : _o.bind(host),
|
|
@@ -301038,7 +301132,7 @@ ${options.prefix}` : "\n" : options.prefix
|
|
|
301038
301132
|
walkUpParenthesizedTypesAndGetParentAndChild: () => walkUpParenthesizedTypesAndGetParentAndChild,
|
|
301039
301133
|
whitespaceOrMapCommentRegExp: () => whitespaceOrMapCommentRegExp,
|
|
301040
301134
|
writeCommentRange: () => writeCommentRange,
|
|
301041
|
-
writeFile: () =>
|
|
301135
|
+
writeFile: () => writeFile3,
|
|
301042
301136
|
writeFileEnsuringDirectories: () => writeFileEnsuringDirectories,
|
|
301043
301137
|
zipWith: () => zipWith
|
|
301044
301138
|
});
|
|
@@ -337174,10 +337268,11 @@ Fatal error: ${msg}`);
|
|
|
337174
337268
|
const shouldPreserveAppData = resetPolicy === "PRESERVE_APP_DATA" || mode === "survey" && resetPolicy === "AUTO";
|
|
337175
337269
|
if (shouldPreserveAppData) {
|
|
337176
337270
|
try {
|
|
337177
|
-
await driver.
|
|
337271
|
+
await driver.launchApp();
|
|
337272
|
+
log2("App activated with preserved data (no terminate/clear).");
|
|
337178
337273
|
} catch (err) {
|
|
337179
337274
|
(0, mobile_driver_errors_js_1.rethrowIfTerminalMobileDriverError)(err);
|
|
337180
|
-
log2(`App
|
|
337275
|
+
log2(`App activate failed while preserving app data: ${err instanceof Error ? err.message : String(err)}`);
|
|
337181
337276
|
}
|
|
337182
337277
|
return false;
|
|
337183
337278
|
}
|
|
@@ -347540,7 +347635,7 @@ var require_mkdir = __commonJS({
|
|
|
347540
347635
|
const gid = opt.gid;
|
|
347541
347636
|
const doChown = typeof uid === "number" && typeof gid === "number" && (uid !== opt.processUid || gid !== opt.processGid);
|
|
347542
347637
|
const preserve = opt.preserve;
|
|
347543
|
-
const
|
|
347638
|
+
const unlink3 = opt.unlink;
|
|
347544
347639
|
const cache = opt.cache;
|
|
347545
347640
|
const cwd = normPath(opt.cwd);
|
|
347546
347641
|
const done = (er, created) => {
|
|
@@ -347568,33 +347663,33 @@ var require_mkdir = __commonJS({
|
|
|
347568
347663
|
}
|
|
347569
347664
|
const sub = normPath(path.relative(cwd, dir));
|
|
347570
347665
|
const parts = sub.split("/");
|
|
347571
|
-
mkdir_(cwd, parts, mode, cache,
|
|
347666
|
+
mkdir_(cwd, parts, mode, cache, unlink3, cwd, null, done);
|
|
347572
347667
|
};
|
|
347573
|
-
var mkdir_ = (base2, parts, mode, cache,
|
|
347668
|
+
var mkdir_ = (base2, parts, mode, cache, unlink3, cwd, created, cb) => {
|
|
347574
347669
|
if (!parts.length) {
|
|
347575
347670
|
return cb(null, created);
|
|
347576
347671
|
}
|
|
347577
347672
|
const p = parts.shift();
|
|
347578
347673
|
const part = normPath(path.resolve(base2 + "/" + p));
|
|
347579
347674
|
if (cGet(cache, part)) {
|
|
347580
|
-
return mkdir_(part, parts, mode, cache,
|
|
347675
|
+
return mkdir_(part, parts, mode, cache, unlink3, cwd, created, cb);
|
|
347581
347676
|
}
|
|
347582
|
-
fs.mkdir(part, mode, onmkdir(part, parts, mode, cache,
|
|
347677
|
+
fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink3, cwd, created, cb));
|
|
347583
347678
|
};
|
|
347584
|
-
var onmkdir = (part, parts, mode, cache,
|
|
347679
|
+
var onmkdir = (part, parts, mode, cache, unlink3, cwd, created, cb) => (er) => {
|
|
347585
347680
|
if (er) {
|
|
347586
347681
|
fs.lstat(part, (statEr, st) => {
|
|
347587
347682
|
if (statEr) {
|
|
347588
347683
|
statEr.path = statEr.path && normPath(statEr.path);
|
|
347589
347684
|
cb(statEr);
|
|
347590
347685
|
} else if (st.isDirectory()) {
|
|
347591
|
-
mkdir_(part, parts, mode, cache,
|
|
347592
|
-
} else if (
|
|
347686
|
+
mkdir_(part, parts, mode, cache, unlink3, cwd, created, cb);
|
|
347687
|
+
} else if (unlink3) {
|
|
347593
347688
|
fs.unlink(part, (er2) => {
|
|
347594
347689
|
if (er2) {
|
|
347595
347690
|
return cb(er2);
|
|
347596
347691
|
}
|
|
347597
|
-
fs.mkdir(part, mode, onmkdir(part, parts, mode, cache,
|
|
347692
|
+
fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink3, cwd, created, cb));
|
|
347598
347693
|
});
|
|
347599
347694
|
} else if (st.isSymbolicLink()) {
|
|
347600
347695
|
return cb(new SymlinkError(part, part + "/" + parts.join("/")));
|
|
@@ -347604,7 +347699,7 @@ var require_mkdir = __commonJS({
|
|
|
347604
347699
|
});
|
|
347605
347700
|
} else {
|
|
347606
347701
|
created = created || part;
|
|
347607
|
-
mkdir_(part, parts, mode, cache,
|
|
347702
|
+
mkdir_(part, parts, mode, cache, unlink3, cwd, created, cb);
|
|
347608
347703
|
}
|
|
347609
347704
|
};
|
|
347610
347705
|
var checkCwdSync = (dir) => {
|
|
@@ -347629,7 +347724,7 @@ var require_mkdir = __commonJS({
|
|
|
347629
347724
|
const gid = opt.gid;
|
|
347630
347725
|
const doChown = typeof uid === "number" && typeof gid === "number" && (uid !== opt.processUid || gid !== opt.processGid);
|
|
347631
347726
|
const preserve = opt.preserve;
|
|
347632
|
-
const
|
|
347727
|
+
const unlink3 = opt.unlink;
|
|
347633
347728
|
const cache = opt.cache;
|
|
347634
347729
|
const cwd = normPath(opt.cwd);
|
|
347635
347730
|
const done = (created2) => {
|
|
@@ -347668,7 +347763,7 @@ var require_mkdir = __commonJS({
|
|
|
347668
347763
|
if (st.isDirectory()) {
|
|
347669
347764
|
cSet(cache, part, true);
|
|
347670
347765
|
continue;
|
|
347671
|
-
} else if (
|
|
347766
|
+
} else if (unlink3) {
|
|
347672
347767
|
fs.unlinkSync(part);
|
|
347673
347768
|
fs.mkdirSync(part, mode);
|
|
347674
347769
|
created = created || part;
|
|
@@ -353342,7 +353437,7 @@ var require_package4 = __commonJS({
|
|
|
353342
353437
|
"package.json"(exports, module) {
|
|
353343
353438
|
module.exports = {
|
|
353344
353439
|
name: "@validate.qa/runner",
|
|
353345
|
-
version: "1.0.
|
|
353440
|
+
version: "1.0.82",
|
|
353346
353441
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
353347
353442
|
bin: {
|
|
353348
353443
|
"validate-runner": "dist/cli.js"
|
|
@@ -354208,9 +354303,9 @@ async function waitForColdBootSettle(sessionPath) {
|
|
|
354208
354303
|
timeoutMs: MOBILE_COLD_BOOT_SETTLE_MS,
|
|
354209
354304
|
minStableFrames: 2,
|
|
354210
354305
|
pollMs: 400,
|
|
354211
|
-
// WebView
|
|
354212
|
-
//
|
|
354213
|
-
minElements:
|
|
354306
|
+
// Native splash / WebView shell is ~5–8 nodes; RN product screens and rich
|
|
354307
|
+
// onboarding typically flatten to 15+ once JS has painted.
|
|
354308
|
+
minElements: 15
|
|
354214
354309
|
});
|
|
354215
354310
|
}
|
|
354216
354311
|
async function getWindowSize(sessionPath) {
|
|
@@ -354359,20 +354454,33 @@ async function sendKeysToFocusedInput(sessionPath, value) {
|
|
|
354359
354454
|
}
|
|
354360
354455
|
async function typeText(sessionPath, value, bounds) {
|
|
354361
354456
|
const elementId = await resolveTypeTargetElementId(sessionPath, bounds);
|
|
354457
|
+
if (!elementId && bounds === void 0) {
|
|
354458
|
+
throw new Error("No focused element available for TYPE. Tap an input field first.");
|
|
354459
|
+
}
|
|
354362
354460
|
if (elementId) {
|
|
354363
354461
|
try {
|
|
354364
|
-
await appiumRequest(`${sessionPath}/element/${elementId}/
|
|
354462
|
+
await appiumRequest(`${sessionPath}/element/${elementId}/clear`, {
|
|
354365
354463
|
method: "POST",
|
|
354366
|
-
body: JSON.stringify({
|
|
354464
|
+
body: JSON.stringify({})
|
|
354367
354465
|
});
|
|
354368
|
-
return;
|
|
354369
354466
|
} catch (err) {
|
|
354370
354467
|
if (isAppiumDriverDeadError(err)) throw err;
|
|
354371
354468
|
}
|
|
354372
|
-
} else if (bounds === void 0) {
|
|
354373
|
-
throw new Error("No focused element available for TYPE. Tap an input field first.");
|
|
354374
354469
|
}
|
|
354375
|
-
|
|
354470
|
+
try {
|
|
354471
|
+
await sendKeysToFocusedInput(sessionPath, value);
|
|
354472
|
+
return;
|
|
354473
|
+
} catch (err) {
|
|
354474
|
+
if (isAppiumDriverDeadError(err)) throw err;
|
|
354475
|
+
}
|
|
354476
|
+
if (elementId) {
|
|
354477
|
+
await appiumRequest(`${sessionPath}/element/${elementId}/value`, {
|
|
354478
|
+
method: "POST",
|
|
354479
|
+
body: JSON.stringify({ text: value })
|
|
354480
|
+
});
|
|
354481
|
+
return;
|
|
354482
|
+
}
|
|
354483
|
+
throw new Error("TYPE failed: could not send keys to the focused input.");
|
|
354376
354484
|
}
|
|
354377
354485
|
async function submitKeyboard(sessionPath, bounds) {
|
|
354378
354486
|
const elementId = await resolveTypeTargetElementId(sessionPath, bounds);
|
|
@@ -355291,6 +355399,7 @@ import { execFile as execFile3 } from "child_process";
|
|
|
355291
355399
|
import { promisify as promisify3 } from "util";
|
|
355292
355400
|
import { tmpdir as tmpdir3 } from "os";
|
|
355293
355401
|
import { join as join5 } from "path";
|
|
355402
|
+
import { readFile as readFile2, unlink as unlink2, writeFile as writeFile2 } from "fs/promises";
|
|
355294
355403
|
|
|
355295
355404
|
// src/services/device-discovery.ts
|
|
355296
355405
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
@@ -356727,6 +356836,12 @@ async function createSession(target) {
|
|
|
356727
356836
|
// whole survey. The runner owns session teardown explicitly, so a long
|
|
356728
356837
|
// idle window is safe. Env-overridable.
|
|
356729
356838
|
"appium:newCommandTimeout": MOBILE_NEW_COMMAND_TIMEOUT_SECONDS,
|
|
356839
|
+
// Expo / universal-link "Open in App?" system sheets and permission dialogs
|
|
356840
|
+
// otherwise sit on top of the app and can stall WDA snapshot/command proxy
|
|
356841
|
+
// for the entire discovery session (observed: 20+ min hung XCUITest with
|
|
356842
|
+
// ECONNREFUSED on wdaLocalPort). Auto-accept keeps the AUT interactive.
|
|
356843
|
+
// Override with APPIUM_IOS_AUTO_ACCEPT_ALERTS=false for explicit dialog tests.
|
|
356844
|
+
...process.env.APPIUM_IOS_AUTO_ACCEPT_ALERTS !== "false" ? { "appium:autoAcceptAlerts": true } : {},
|
|
356730
356845
|
// Xcode 27+ relocates Simulator.app out of <DEVELOPER_DIR>/Applications, so
|
|
356731
356846
|
// XCUITest's default "open Simulator UI" step fails ("Simulator.app does not
|
|
356732
356847
|
// exist") on this toolchain. The simulator is already booted and driven
|
|
@@ -356982,20 +357097,102 @@ async function startScreenRecording(sessionId, platform3, log2) {
|
|
|
356982
357097
|
if (platform3 === "ANDROID") {
|
|
356983
357098
|
options.bitRate = readPositiveIntEnv("VALIDATEQA_MOBILE_RECORDING_BITRATE", 75e4, 1e5, 4e6);
|
|
356984
357099
|
} else {
|
|
356985
|
-
options.
|
|
357100
|
+
options.videoType = process.env.VALIDATEQA_MOBILE_RECORDING_IOS_VIDEO_TYPE || "libx264";
|
|
357101
|
+
options.videoQuality = process.env.VALIDATEQA_MOBILE_RECORDING_IOS_QUALITY || "medium";
|
|
357102
|
+
options.videoFilters = process.env.VALIDATEQA_MOBILE_RECORDING_IOS_FILTERS || "scale=trunc(iw/2)*2:trunc(ih/2)*2";
|
|
356986
357103
|
}
|
|
356987
357104
|
try {
|
|
356988
357105
|
await wdRequest(`/session/${sessionId}/appium/start_recording_screen`, {
|
|
356989
357106
|
method: "POST",
|
|
356990
357107
|
body: JSON.stringify({ options })
|
|
356991
357108
|
});
|
|
356992
|
-
log2(`[mobile-video] screen recording started (${timeLimit}s max)`);
|
|
357109
|
+
log2(`[mobile-video] screen recording started (${timeLimit}s max${platform3 === "IOS" ? `, type=${options.videoType}` : ""})`);
|
|
356993
357110
|
return true;
|
|
356994
357111
|
} catch (err) {
|
|
357112
|
+
if (platform3 === "IOS" && options.videoType) {
|
|
357113
|
+
try {
|
|
357114
|
+
const fallback = {
|
|
357115
|
+
timeLimit: String(timeLimit),
|
|
357116
|
+
forceRestart: true,
|
|
357117
|
+
videoQuality: process.env.VALIDATEQA_MOBILE_RECORDING_IOS_QUALITY || "medium"
|
|
357118
|
+
};
|
|
357119
|
+
await wdRequest(`/session/${sessionId}/appium/start_recording_screen`, {
|
|
357120
|
+
method: "POST",
|
|
357121
|
+
body: JSON.stringify({ options: fallback })
|
|
357122
|
+
});
|
|
357123
|
+
log2(`[mobile-video] screen recording started with quality-only fallback (${timeLimit}s max); will re-encode on stop`);
|
|
357124
|
+
return true;
|
|
357125
|
+
} catch (fallbackErr) {
|
|
357126
|
+
log2(`[mobile-video] screen recording unavailable: ${fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)}`);
|
|
357127
|
+
return false;
|
|
357128
|
+
}
|
|
357129
|
+
}
|
|
356995
357130
|
log2(`[mobile-video] screen recording unavailable: ${err instanceof Error ? err.message : String(err)}`);
|
|
356996
357131
|
return false;
|
|
356997
357132
|
}
|
|
356998
357133
|
}
|
|
357134
|
+
async function ensureBrowserPlayableMp4Base64(base64Video, log2) {
|
|
357135
|
+
if (process.env.VALIDATEQA_MOBILE_VIDEO_REENCODE === "0") {
|
|
357136
|
+
return base64Video;
|
|
357137
|
+
}
|
|
357138
|
+
const inPath = join5(tmpdir3(), `validateqa-mobile-rec-in-${Date.now()}-${Math.random().toString(36).slice(2)}.mp4`);
|
|
357139
|
+
const outPath = join5(tmpdir3(), `validateqa-mobile-rec-out-${Date.now()}-${Math.random().toString(36).slice(2)}.mp4`);
|
|
357140
|
+
try {
|
|
357141
|
+
await writeFile2(inPath, Buffer.from(base64Video, "base64"));
|
|
357142
|
+
try {
|
|
357143
|
+
const { stdout } = await execFileAsync3("ffprobe", [
|
|
357144
|
+
"-v",
|
|
357145
|
+
"error",
|
|
357146
|
+
"-select_streams",
|
|
357147
|
+
"v:0",
|
|
357148
|
+
"-show_entries",
|
|
357149
|
+
"stream=codec_name",
|
|
357150
|
+
"-of",
|
|
357151
|
+
"csv=p=0",
|
|
357152
|
+
inPath
|
|
357153
|
+
], { timeout: 15e3 });
|
|
357154
|
+
const codec = stdout.trim().toLowerCase();
|
|
357155
|
+
if (codec === "h264" || codec === "avc1") {
|
|
357156
|
+
log2(`[mobile-video] recording already H.264 (${codec}); skipping re-encode`);
|
|
357157
|
+
return base64Video;
|
|
357158
|
+
}
|
|
357159
|
+
log2(`[mobile-video] re-encoding ${codec || "unknown"} \u2192 H.264 for browser preview`);
|
|
357160
|
+
} catch {
|
|
357161
|
+
log2("[mobile-video] codec probe failed; attempting H.264 re-encode anyway");
|
|
357162
|
+
}
|
|
357163
|
+
await execFileAsync3("ffmpeg", [
|
|
357164
|
+
"-y",
|
|
357165
|
+
"-i",
|
|
357166
|
+
inPath,
|
|
357167
|
+
"-vf",
|
|
357168
|
+
"scale=trunc(iw/2)*2:trunc(ih/2)*2",
|
|
357169
|
+
"-c:v",
|
|
357170
|
+
"libx264",
|
|
357171
|
+
"-pix_fmt",
|
|
357172
|
+
"yuv420p",
|
|
357173
|
+
"-preset",
|
|
357174
|
+
"veryfast",
|
|
357175
|
+
"-crf",
|
|
357176
|
+
"28",
|
|
357177
|
+
"-movflags",
|
|
357178
|
+
"+faststart",
|
|
357179
|
+
"-an",
|
|
357180
|
+
outPath
|
|
357181
|
+
], { timeout: 12e4 });
|
|
357182
|
+
const encoded = await readFile2(outPath);
|
|
357183
|
+
if (encoded.length < 100) {
|
|
357184
|
+
log2("[mobile-video] re-encode produced empty file; keeping original");
|
|
357185
|
+
return base64Video;
|
|
357186
|
+
}
|
|
357187
|
+
log2(`[mobile-video] re-encoded for browser (${Math.round(encoded.length / 1024)}KB)`);
|
|
357188
|
+
return encoded.toString("base64");
|
|
357189
|
+
} catch (err) {
|
|
357190
|
+
log2(`[mobile-video] re-encode skipped/failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
357191
|
+
return base64Video;
|
|
357192
|
+
} finally {
|
|
357193
|
+
await Promise.allSettled([unlink2(inPath), unlink2(outPath)]);
|
|
357194
|
+
}
|
|
357195
|
+
}
|
|
356999
357196
|
async function stopScreenRecording(sessionId, log2) {
|
|
357000
357197
|
try {
|
|
357001
357198
|
const result = await wdRequest(`/session/${sessionId}/appium/stop_recording_screen`, {
|
|
@@ -357008,7 +357205,7 @@ async function stopScreenRecording(sessionId, log2) {
|
|
|
357008
357205
|
return void 0;
|
|
357009
357206
|
}
|
|
357010
357207
|
log2(`[mobile-video] screen recording captured (${Math.round(value.length / 1024)}KB base64)`);
|
|
357011
|
-
return value;
|
|
357208
|
+
return ensureBrowserPlayableMp4Base64(value, log2);
|
|
357012
357209
|
} catch (err) {
|
|
357013
357210
|
log2(`[mobile-video] screen recording stop failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
357014
357211
|
return void 0;
|
|
@@ -359394,6 +359591,9 @@ async function startSession(validated, res) {
|
|
|
359394
359591
|
...deviceId ? { "appium:udid": deviceId } : {},
|
|
359395
359592
|
...platformVersion ? { "appium:platformVersion": platformVersion } : {},
|
|
359396
359593
|
"appium:noReset": true,
|
|
359594
|
+
// Keep pace with executor createSession: Expo "Open in App?" sheets and
|
|
359595
|
+
// permission dialogs otherwise freeze WDA for recording/mirror sessions.
|
|
359596
|
+
...process.env.APPIUM_IOS_AUTO_ACCEPT_ALERTS !== "false" ? { "appium:autoAcceptAlerts": true } : {},
|
|
359397
359597
|
// Same Xcode 27+ workaround as the executor's createSession (appium-executor.ts):
|
|
359398
359598
|
// Simulator.app is no longer at <DEVELOPER_DIR>/Applications, so XCUITest's
|
|
359399
359599
|
// "open Simulator UI" step fails. The sim is already booted via CoreSimulator;
|
|
@@ -361293,7 +361493,7 @@ function envInt(name, fallback) {
|
|
|
361293
361493
|
}
|
|
361294
361494
|
var MOBILE_DEVICE_CEILING = Math.max(0, envInt("MOBILE_DEVICE_CEILING", 0));
|
|
361295
361495
|
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",
|
|
361496
|
+
var MOBILE_RUN_WATCHDOG_MS = Math.max(0, envInt("MOBILE_RUN_WATCHDOG_MS", 60 * 6e4));
|
|
361297
361497
|
var MOBILE_RUN_ABORT_GRACE_MS = Math.max(0, envInt("MOBILE_RUN_ABORT_GRACE_MS", 2 * 6e4));
|
|
361298
361498
|
var MOBILE_CANCEL_POLL_MS = Math.max(0, envInt("MOBILE_CANCEL_POLL_MS", 15e3));
|
|
361299
361499
|
var MobileRunWatchdog = class {
|