doc-detective 4.22.0 → 4.23.0

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.
Files changed (53) hide show
  1. package/dist/common/src/schemas/schemas.json +274 -74
  2. package/dist/common/src/types/generated/config_v3.d.ts +1 -1
  3. package/dist/common/src/types/generated/context_v3.d.ts +1 -1
  4. package/dist/common/src/types/generated/report_v3.d.ts +1 -1
  5. package/dist/common/src/types/generated/resolvedTests_v3.d.ts +2 -2
  6. package/dist/common/src/types/generated/spec_v3.d.ts +1 -1
  7. package/dist/common/src/types/generated/test_v3.d.ts +1 -1
  8. package/dist/core/appium.d.ts +2 -1
  9. package/dist/core/appium.d.ts.map +1 -1
  10. package/dist/core/appium.js +47 -13
  11. package/dist/core/appium.js.map +1 -1
  12. package/dist/core/ptyProbeWorker.d.ts +2 -0
  13. package/dist/core/ptyProbeWorker.d.ts.map +1 -0
  14. package/dist/core/ptyProbeWorker.js +61 -0
  15. package/dist/core/ptyProbeWorker.js.map +1 -0
  16. package/dist/core/ptyWatchdog.d.ts +77 -0
  17. package/dist/core/ptyWatchdog.d.ts.map +1 -0
  18. package/dist/core/ptyWatchdog.js +179 -0
  19. package/dist/core/ptyWatchdog.js.map +1 -0
  20. package/dist/core/resolveTests.d.ts.map +1 -1
  21. package/dist/core/resolveTests.js +16 -4
  22. package/dist/core/resolveTests.js.map +1 -1
  23. package/dist/core/tests/appSurface.d.ts +21 -2
  24. package/dist/core/tests/appSurface.d.ts.map +1 -1
  25. package/dist/core/tests/appSurface.js +117 -3
  26. package/dist/core/tests/appSurface.js.map +1 -1
  27. package/dist/core/tests/browserWait.d.ts +1 -1
  28. package/dist/core/tests/browserWait.d.ts.map +1 -1
  29. package/dist/core/tests/browserWait.js +108 -84
  30. package/dist/core/tests/browserWait.js.map +1 -1
  31. package/dist/core/tests/findElement.d.ts.map +1 -1
  32. package/dist/core/tests/findElement.js +16 -5
  33. package/dist/core/tests/findElement.js.map +1 -1
  34. package/dist/core/tests/mobileBrowser.d.ts +46 -0
  35. package/dist/core/tests/mobileBrowser.d.ts.map +1 -0
  36. package/dist/core/tests/mobileBrowser.js +140 -0
  37. package/dist/core/tests/mobileBrowser.js.map +1 -0
  38. package/dist/core/tests/mobilePlatform.d.ts +1 -7
  39. package/dist/core/tests/mobilePlatform.d.ts.map +1 -1
  40. package/dist/core/tests/mobilePlatform.js +5 -27
  41. package/dist/core/tests/mobilePlatform.js.map +1 -1
  42. package/dist/core/tests.d.ts +7 -1
  43. package/dist/core/tests.d.ts.map +1 -1
  44. package/dist/core/tests.js +234 -58
  45. package/dist/core/tests.js.map +1 -1
  46. package/dist/core/utils.d.ts.map +1 -1
  47. package/dist/core/utils.js +26 -1
  48. package/dist/core/utils.js.map +1 -1
  49. package/dist/index.cjs +1039 -409
  50. package/dist/runtime/loader.d.ts.map +1 -1
  51. package/dist/runtime/loader.js +76 -0
  52. package/dist/runtime/loader.js.map +1 -1
  53. package/package.json +1 -1
@@ -1,37 +1,15 @@
1
1
  // Native app surfaces phase A3: the `android`/`ios` target platforms. Unlike
2
2
  // desktop platforms (where host == target), a mobile platform names the
3
3
  // TARGET a context runs against and is gated by host *capability*, not host
4
- // identity. As of A4, native app contexts can pass on both mobile targets
5
- // when the host is capable. The remaining mobile-target skip reason this
6
- // module composes is mobile browser testing (A5), which is still gated on both
7
- // android and ios. The reason composer is pure so it's unit-testable without
8
- // probing an SDK.
9
- export { isMobileTargetPlatform, mobileContextSkipReason };
4
+ // identity. Native app contexts (A3/A4) and mobile-web browser contexts (A5)
5
+ // both run on capable hosts; the browser-side decisions (support matrix,
6
+ // device-fixed config rejection, mixed-context deferral) live in
7
+ // mobileBrowser.ts.
8
+ export { isMobileTargetPlatform };
10
9
  // Classify a context platform as a mobile target, or null for desktop / junk.
11
10
  function isMobileTargetPlatform(platform) {
12
11
  if (platform === "android" || platform === "ios")
13
12
  return platform;
14
13
  return null;
15
14
  }
16
- // Compose the SKIP reason (and log level) for the one mobile-context case still
17
- // gated by a later phase: a mobile-web (browser) step on either target — mobile
18
- // browsers land in A5. Native app contexts (no browser step) are NOT routed
19
- // here; they pass through their platform's context preflight (androidContext-
20
- // Preflight / iosContextPreflight), which capability-gates and runs them. This
21
- // composer is only ever called for a mobile-browser context, so it branches on
22
- // the target platform alone.
23
- function mobileContextSkipReason({ platform, }) {
24
- const roadmap = "docs/design/native-app-surfaces.md";
25
- if (platform === "ios") {
26
- return {
27
- level: "warning",
28
- reason: `Skipping context on 'ios': mobile browser testing on iOS lands in phase A5 of the native app roadmap (${roadmap}). Native iOS app tests run on capable macOS hosts.`,
29
- };
30
- }
31
- // android + a browser step: mobile-web testing on Android is phase A5.
32
- return {
33
- level: "warning",
34
- reason: `Skipping context on 'android': mobile browser testing on Android lands in phase A5 of the native app roadmap (${roadmap}). Native Android app tests (no browser steps) run today.`,
35
- };
36
- }
37
15
  //# sourceMappingURL=mobilePlatform.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mobilePlatform.js","sourceRoot":"","sources":["../../../src/core/tests/mobilePlatform.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,wEAAwE;AACxE,4EAA4E;AAC5E,0EAA0E;AAC1E,yEAAyE;AACzE,+EAA+E;AAC/E,6EAA6E;AAC7E,kBAAkB;AAElB,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,CAAC;AAI3D,8EAA8E;AAC9E,SAAS,sBAAsB,CAAC,QAAiB;IAC/C,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,KAAK;QAAE,OAAO,QAAQ,CAAC;IAClE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,gFAAgF;AAChF,gFAAgF;AAChF,4EAA4E;AAC5E,8EAA8E;AAC9E,+EAA+E;AAC/E,+EAA+E;AAC/E,6BAA6B;AAC7B,SAAS,uBAAuB,CAAC,EAC/B,QAAQ,GAGT;IACC,MAAM,OAAO,GAAG,oCAAoC,CAAC;IACrD,IAAI,QAAQ,KAAK,KAAK,EAAE,CAAC;QACvB,OAAO;YACL,KAAK,EAAE,SAAS;YAChB,MAAM,EAAE,yGAAyG,OAAO,qDAAqD;SAC9K,CAAC;IACJ,CAAC;IACD,uEAAuE;IACvE,OAAO;QACL,KAAK,EAAE,SAAS;QAChB,MAAM,EAAE,iHAAiH,OAAO,2DAA2D;KAC5L,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"mobilePlatform.js","sourceRoot":"","sources":["../../../src/core/tests/mobilePlatform.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,wEAAwE;AACxE,4EAA4E;AAC5E,6EAA6E;AAC7E,yEAAyE;AACzE,iEAAiE;AACjE,oBAAoB;AAEpB,OAAO,EAAE,sBAAsB,EAAE,CAAC;AAIlC,8EAA8E;AAC9E,SAAS,sBAAsB,CAAC,QAAiB;IAC/C,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,KAAK;QAAE,OAAO,QAAQ,CAAC;IAClE,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import { type BrowserAssetName } from "../runtime/browsers.js";
2
2
  import { type AppSessionState } from "./tests/appSurface.js";
3
- export { runSpecs, runViaApi, getRunner, ensureChromeAvailable, ensureContextBrowserInstalled, combinationKey, warmUpDecision, selectWarmUpTargets, getDriverCapabilities, getDefaultBrowser, buildFallbackCandidates, driverSkipDiagnostic, resolveBrowserFallbackPolicy, shouldRepairBeforeFallback, isSupportedContext, contextRequirementsSkipMessage, resolveAutoScreenshot, resolveAutoRecord, buildAutoRecordStep, specIsRouted, killTree, };
3
+ export { runSpecs, runViaApi, getRunner, ensureChromeAvailable, ensureContextBrowserInstalled, combinationKey, warmUpDecision, selectWarmUpTargets, getDriverCapabilities, getDefaultBrowser, buildFallbackCandidates, driverSkipDiagnostic, resolveBrowserFallbackPolicy, shouldRepairBeforeFallback, isSupportedContext, contextRequirementsSkipMessage, resolveAutoScreenshot, resolveAutoRecord, buildAutoRecordStep, specIsRouted, killTree, jobDisplayResources, };
4
4
  declare function killTree(pid?: number, timeoutMs?: number): Promise<void>;
5
5
  /**
6
6
  * Stable identity for a "context combination" — the platform + browser pairing
@@ -25,6 +25,12 @@ declare function getDriverCapabilities({ runnerDetails, name, options }: {
25
25
  name: any;
26
26
  options: any;
27
27
  }): any;
28
+ declare function jobDisplayResources(job: any, ctx: {
29
+ platform: string;
30
+ xvfbAvailable: boolean;
31
+ allowOverlappingCaptures?: boolean;
32
+ runHasDisplayRecording: boolean;
33
+ }): string[];
28
34
  declare function isSupportedContext({ context, apps, platform }: {
29
35
  context: any;
30
36
  apps: any[];
@@ -1 +1 @@
1
- {"version":3,"file":"tests.d.ts","sourceRoot":"","sources":["../../src/core/tests.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tests.d.ts","sourceRoot":"","sources":["../../src/core/tests.ts"],"names":[],"mappings":"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"}
@@ -34,7 +34,9 @@ import { clickElement } from "./tests/click.js";
34
34
  import { runCode } from "./tests/runCode.js";
35
35
  import { closeSurface } from "./tests/closeSurface.js";
36
36
  import { createAppSessionState, appSurfacePreflight, isAppDriverRequired, stepTargetsAppSurface, startAppSurface, teardownAppSession, } from "./tests/appSurface.js";
37
- import { isMobileTargetPlatform, mobileContextSkipReason, } from "./tests/mobilePlatform.js";
37
+ import { isMobileTargetPlatform } from "./tests/mobilePlatform.js";
38
+ import { mobileBrowserGate, buildMobileBrowserCapabilities, } from "./tests/mobileBrowser.js";
39
+ import { getCacheDir } from "../runtime/cacheDir.js";
38
40
  import { detectAndroidSdk } from "../runtime/androidSdk.js";
39
41
  import { hostAbi, listInstalledSystemImages, installAndroid, } from "../runtime/androidInstaller.js";
40
42
  import { buildAcquireDeviceDeps, checkEmulatorAcceleration, hostHasKvm, planDeviceAcquisition, planAndroidToolchain, normalizeDeviceDescriptor, acquireDevice, createDeviceRegistry, teardownDeviceRegistry, } from "./tests/androidEmulator.js";
@@ -55,7 +57,7 @@ import http from "node:http";
55
57
  import https from "node:https";
56
58
  import { fileURLToPath } from "node:url";
57
59
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
58
- export { runSpecs, runViaApi, getRunner, ensureChromeAvailable, ensureContextBrowserInstalled, combinationKey, warmUpDecision, selectWarmUpTargets, getDriverCapabilities, getDefaultBrowser, buildFallbackCandidates, driverSkipDiagnostic, resolveBrowserFallbackPolicy, shouldRepairBeforeFallback, isSupportedContext, contextRequirementsSkipMessage, resolveAutoScreenshot, resolveAutoRecord, buildAutoRecordStep, specIsRouted, killTree, };
60
+ export { runSpecs, runViaApi, getRunner, ensureChromeAvailable, ensureContextBrowserInstalled, combinationKey, warmUpDecision, selectWarmUpTargets, getDriverCapabilities, getDefaultBrowser, buildFallbackCandidates, driverSkipDiagnostic, resolveBrowserFallbackPolicy, shouldRepairBeforeFallback, isSupportedContext, contextRequirementsSkipMessage, resolveAutoScreenshot, resolveAutoRecord, buildAutoRecordStep, specIsRouted, killTree, jobDisplayResources, };
59
61
  // exports.appiumStart = appiumStart;
60
62
  // exports.appiumIsReady = appiumIsReady;
61
63
  // exports.driverStart = driverStart;
@@ -295,11 +297,18 @@ function jobDisplayResources(job, ctx) {
295
297
  // emulator is GBs of RAM, so bound their concurrency to one at a time. This
296
298
  // is exclusivity-as-bound on a single resource name (a counted semaphore is
297
299
  // future work); it composes with any recording "display" resource above.
298
- // Only android contexts that will actually attempt the emulator take it — an
299
- // android+browser context deterministically SKIPs (phase A5) before any
300
- // emulator/toolchain work, so it must not needlessly serialize other jobs.
300
+ // Only android contexts that will actually attempt the emulator take it.
301
+ // Native app contexts always do; a mobile-web context (phase A5) does when
302
+ // its browser gate proceeds a context the gate deterministically SKIPs or
303
+ // FAILs (unsupported browser, mixed app+web, device-fixed config) never
304
+ // boots anything, so it must not needlessly serialize other jobs.
301
305
  const attemptsEmulator = job.context?.platform === "android" &&
302
- !isBrowserRequired({ test: job.context });
306
+ mobileBrowserGate({
307
+ platform: "android",
308
+ browser: job.context?.browser,
309
+ hasBrowserStep: isBrowserRequired({ test: job.context }),
310
+ hasAppStep: isAppDriverRequired({ test: job.context }),
311
+ }).action === "proceed";
303
312
  const extra = attemptsEmulator ? ["android-emulator"] : [];
304
313
  if (base.length || extra.length)
305
314
  return [...new Set([...base, ...extra])];
@@ -399,19 +408,29 @@ function requiredAndroidOsVersions(context) {
399
408
  }
400
409
  // Android context preflight (native app phase A3b): host-capability probe →
401
410
  // lazy toolchain install (when the run needs it) → per-device resolvability →
402
- // UiAutomator2 driver install. Every gap is a gating SKIP (never FAIL). The
411
+ // UiAutomator2 driver install. Every environment gap is a gating SKIP (never
412
+ // FAIL); the one FAIL is authored device-fixed browser config (phase A5 gate),
413
+ // which is a contradiction to surface loudly, not a missing capability. The
403
414
  // toolchain (SDK + system image) is NOT installed by default, but IS lazily
404
415
  // installed — with a loud warning surfaced to the terminal AND the output
405
416
  // report — when a run that reaches a capable host actually needs it. On ok,
406
417
  // returns the Appium entry/home, SDK root, injected device-effect bundle, and
407
418
  // any warnings to attach to the context report.
408
419
  async function androidContextPreflight({ config, context, clog, }) {
409
- const hasBrowserStep = isBrowserRequired({ test: context });
410
- if (hasBrowserStep) {
411
- const { level, reason } = mobileContextSkipReason({
412
- platform: "android",
413
- });
414
- return { ok: false, level, reason };
420
+ // Mobile-web gate (phase A5) before ANY toolchain work, so unsupported
421
+ // browsers, mixed app+web contexts, and device-fixed browser config land
422
+ // deterministically on every host without touching the SDK.
423
+ const gate = mobileBrowserGate({
424
+ platform: "android",
425
+ browser: context.browser,
426
+ hasBrowserStep: isBrowserRequired({ test: context }),
427
+ hasAppStep: isAppDriverRequired({ test: context }),
428
+ });
429
+ if (gate.action === "skip") {
430
+ return { ok: false, level: gate.level, reason: gate.reason };
431
+ }
432
+ if (gate.action === "fail") {
433
+ return { ok: false, level: "warning", reason: gate.reason, fail: true };
415
434
  }
416
435
  /* c8 ignore start */
417
436
  // The rest is effectful (SDK detect/install, emulator probes) so it's
@@ -558,6 +577,7 @@ async function androidContextPreflight({ config, context, clog, }) {
558
577
  sdkRoot: sdk.sdkRoot,
559
578
  deviceDeps,
560
579
  warnings,
580
+ mobileWebBrowserName: gate.browserName,
561
581
  };
562
582
  }
563
583
  catch (error) {
@@ -569,19 +589,26 @@ async function androidContextPreflight({ config, context, clog, }) {
569
589
  }
570
590
  /* c8 ignore stop */
571
591
  }
572
- // iOS context preflight (native app phase A4): mobile-browser contexts are
573
- // still gated to A5; native app contexts probe host capability/toolchain via
574
- // appSurfacePreflight, then validate that the context's default simulator can
575
- // be resolved (booted/created) via simctl. On success return the appium
576
- // entry/home plus the injected simctl effect bundle for the run's
577
- // acquireSimulator closure.
592
+ // iOS context preflight (native app phase A4 + mobile web phase A5): the
593
+ // mobile-browser gate decides first (support matrix / device-fixed config /
594
+ // mixed-context deferral all pure, all pre-toolchain); then host
595
+ // capability/toolchain probes via appSurfacePreflight, then validation that
596
+ // the context's default simulator can be resolved (booted/created) via
597
+ // simctl. On success return the appium entry/home, the injected simctl effect
598
+ // bundle for the run's acquireSimulator closure, and the device browser (if
599
+ // any) to open a session for.
578
600
  async function iosContextPreflight({ config, context, }) {
579
- const hasBrowserStep = isBrowserRequired({ test: context });
580
- if (hasBrowserStep) {
581
- const { level, reason } = mobileContextSkipReason({
582
- platform: "ios",
583
- });
584
- return { ok: false, level, reason };
601
+ const gate = mobileBrowserGate({
602
+ platform: "ios",
603
+ browser: context.browser,
604
+ hasBrowserStep: isBrowserRequired({ test: context }),
605
+ hasAppStep: isAppDriverRequired({ test: context }),
606
+ });
607
+ if (gate.action === "skip") {
608
+ return { ok: false, level: gate.level, reason: gate.reason };
609
+ }
610
+ if (gate.action === "fail") {
611
+ return { ok: false, level: "warning", reason: gate.reason, fail: true };
585
612
  }
586
613
  const pre = await appSurfacePreflight({ config, platform: "ios" });
587
614
  if (!pre.ok)
@@ -622,6 +649,7 @@ async function iosContextPreflight({ config, context, }) {
622
649
  appiumEntry: pre.appiumEntry,
623
650
  appiumHome: pre.appiumHome,
624
651
  simulatorDeps,
652
+ mobileWebBrowserName: gate.browserName,
625
653
  };
626
654
  /* c8 ignore stop */
627
655
  }
@@ -1911,6 +1939,14 @@ function selectWarmUpTargets(jobs, runnerDetails) {
1911
1939
  // would defeat the warm-up/install de-racing the pre-pass exists for.
1912
1940
  if (!context.platform)
1913
1941
  context.platform = platform;
1942
+ // Mobile contexts (android/ios targets) never warm up a desktop engine:
1943
+ // their browser runs ON the device through the per-context app Appium
1944
+ // server (phase A5), so a desktop warm-up would launch the wrong browser
1945
+ // on the wrong machine — and must not write a desktop default browser
1946
+ // onto the context (runContext's mobile branch resolves the device
1947
+ // browser itself).
1948
+ if (isMobileTargetPlatform(context.platform))
1949
+ continue;
1914
1950
  // Size and target the warm-up by isBrowserRequired (not isDriverRequired):
1915
1951
  // app-only contexts run on their own per-context Appium server, so they
1916
1952
  // must not pull a browser into the pre-pass or get a default browser
@@ -2391,8 +2427,13 @@ async function runContext({ config, spec, test, context, runnerDetails, appiumPo
2391
2427
  // If "browser" isn't defined but is required by the test, set it to the
2392
2428
  // first available browser in the sequence of Firefox, Chrome, Safari.
2393
2429
  // App-targeted steps don't count: they run on the app session, so an
2394
- // app-only test never boots a browser it won't use.
2395
- if (!context.browser && isBrowserRequired({ test: context })) {
2430
+ // app-only test never boots a browser it won't use. Mobile contexts don't
2431
+ // count either: their browser is the device's (chrome/safari), resolved by
2432
+ // the mobile branch below — a desktop default here would be wrong on both
2433
+ // the engine and the machine.
2434
+ if (!context.browser &&
2435
+ isBrowserRequired({ test: context }) &&
2436
+ !isMobileTargetPlatform(context.platform)) {
2396
2437
  context.browser = getDefaultBrowser({ runnerDetails });
2397
2438
  }
2398
2439
  // Set context report
@@ -2419,6 +2460,11 @@ async function runContext({ config, spec, test, context, runnerDetails, appiumPo
2419
2460
  // context that passes its preflight (phase A3b). Declared here so the mobile
2420
2461
  // branch can set it and fall through to the shared step-execution path.
2421
2462
  let appSession;
2463
+ // Mobile web (phase A5): set when the mobile preflight resolved a device
2464
+ // browser for this context. The try block below then opens the browser
2465
+ // session on the device (through the app session's Appium server) instead
2466
+ // of the desktop engine path.
2467
+ let mobileWebBrowserName;
2422
2468
  const mobileTarget = isMobileTargetPlatform(context.platform);
2423
2469
  if (mobileTarget) {
2424
2470
  const requirementsSkip = contextRequirementsSkipMessage({ context });
@@ -2428,59 +2474,71 @@ async function runContext({ config, spec, test, context, runnerDetails, appiumPo
2428
2474
  contextReport.resultDescription = requirementsSkip;
2429
2475
  return contextReport;
2430
2476
  }
2477
+ // Both preflights run the mobile-browser gate first (support matrix,
2478
+ // device-fixed config, mixed app+web) — `fail: true` marks an authored
2479
+ // contradiction (FAIL loudly); everything else lands SKIPPED. Applied via
2480
+ // this shared closure so the two typed branches below stay narrowed to
2481
+ // their platform's ok-shape (no cross-platform union casts).
2482
+ const gateOutcome = (pre) => {
2483
+ clog(pre.fail ? "error" : pre.level, pre.reason);
2484
+ contextReport.result = pre.fail ? "FAIL" : "SKIPPED";
2485
+ contextReport.resultDescription = pre.reason;
2486
+ return contextReport;
2487
+ };
2488
+ let gateBrowserName;
2431
2489
  if (mobileTarget === "android") {
2432
- // Android (phase A3b): full preflight. SDK detection is lazy (probed
2433
- // only here, only for android contexts), so a run that never targets
2434
- // android pays nothing. On skip, land SKIPPED with the actionable reason;
2435
- // on ok, prime the app session with the device layer and FALL THROUGH to
2436
- // run the steps.
2490
+ // Android (phase A3b): SDK detection is lazy (probed only here, only
2491
+ // for android contexts), so a run that never targets android pays
2492
+ // nothing. On ok, prime the app session with the device layer and FALL
2493
+ // THROUGH to run the steps.
2437
2494
  const pre = await androidContextPreflight({ config, context, clog });
2438
- if (!pre.ok) {
2439
- clog(pre.level, pre.reason);
2440
- contextReport.result = "SKIPPED";
2441
- contextReport.resultDescription = pre.reason;
2442
- return contextReport;
2443
- }
2495
+ if (!pre.ok)
2496
+ return gateOutcome(pre);
2444
2497
  /* c8 ignore start */
2445
2498
  // The ok path only runs on a host with a real SDK + emulator (CI legs).
2446
2499
  appSession = createAppSessionState();
2447
2500
  appSession.appiumEntry = pre.appiumEntry;
2448
2501
  appSession.appiumHome = pre.appiumHome;
2449
- appSession.defaultDevice = context.device;
2450
2502
  appSession.androidSdkRoot = pre.sdkRoot;
2451
2503
  appSession.androidDeviceRegistry = deviceRegistry;
2452
2504
  appSession.androidDeviceDeps = pre.deviceDeps;
2453
- // resolved device (name) joins the context report the way resolved
2454
- // browser versions do; the concrete udid is known once a surface boots
2455
- // it.
2456
- contextReport.device = context.device ?? { platform: "android" };
2457
2505
  // Surface any preflight warnings (e.g. a lazy toolchain install) in the
2458
2506
  // output report — not just the terminal — so a run that quietly
2459
2507
  // downloaded the multi-GB SDK is auditable after the fact.
2460
2508
  if (pre.warnings.length)
2461
2509
  contextReport.warnings = pre.warnings;
2510
+ gateBrowserName = pre.mobileWebBrowserName;
2462
2511
  /* c8 ignore stop */
2463
2512
  }
2464
2513
  else {
2514
+ // iOS (phase A4): on ok, prime the app session with the simulator layer.
2465
2515
  const pre = await iosContextPreflight({ config, context });
2466
- if (!pre.ok) {
2467
- clog(pre.level, pre.reason);
2468
- contextReport.result = "SKIPPED";
2469
- contextReport.resultDescription = pre.reason;
2470
- return contextReport;
2471
- }
2516
+ if (!pre.ok)
2517
+ return gateOutcome(pre);
2472
2518
  /* c8 ignore start */
2473
- // The ok path only runs on a capable macOS host (CI fixture legs): prime
2474
- // the app session with the simulator layer and FALL THROUGH to run steps.
2519
+ // The ok path only runs on a capable macOS host (CI fixture legs).
2475
2520
  appSession = createAppSessionState();
2476
2521
  appSession.appiumEntry = pre.appiumEntry;
2477
2522
  appSession.appiumHome = pre.appiumHome;
2478
- appSession.defaultDevice = context.device;
2479
2523
  appSession.iosSimulatorRegistry = simulatorRegistry;
2480
2524
  appSession.iosSimulatorDeps = pre.simulatorDeps;
2481
- contextReport.device = context.device ?? { platform: "ios" };
2525
+ gateBrowserName = pre.mobileWebBrowserName;
2482
2526
  /* c8 ignore stop */
2483
2527
  }
2528
+ /* c8 ignore start */
2529
+ appSession.defaultDevice = context.device;
2530
+ // resolved device (name) joins the context report the way resolved
2531
+ // browser versions do; the concrete udid is known once a device boots.
2532
+ contextReport.device = context.device ?? { platform: mobileTarget };
2533
+ if (gateBrowserName) {
2534
+ // Mobile web: pin the context's browser to the device browser the gate
2535
+ // resolved (the authored one, or the platform default) so the report
2536
+ // and the session capabilities agree.
2537
+ mobileWebBrowserName = gateBrowserName;
2538
+ context.browser = { ...(context.browser ?? {}), name: mobileWebBrowserName };
2539
+ contextReport.browser = context.browser;
2540
+ }
2541
+ /* c8 ignore stop */
2484
2542
  }
2485
2543
  // `requires` capability gate: any unmet requirement skips the context with a
2486
2544
  // message naming what's missing. Evaluated only on the platform the context
@@ -2609,8 +2667,10 @@ async function runContext({ config, spec, test, context, runnerDetails, appiumPo
2609
2667
  })
2610
2668
  : [];
2611
2669
  // A driver context with no startable engine is skipped with a diagnostic that
2612
- // names the requested engine and the partial-download cause.
2613
- if (driverRequired && candidateEngines.length === 0) {
2670
+ // names the requested engine and the partial-download cause. Mobile-web
2671
+ // contexts don't participate: their browser lives on the device, not in the
2672
+ // host's engine list (their session path is the mobile block below).
2673
+ if (driverRequired && !mobileWebBrowserName && candidateEngines.length === 0) {
2614
2674
  const errorMessage = freshInstallRedetected
2615
2675
  ? freshInstallOutcome === "installed"
2616
2676
  ? `Skipping context '${requestedBrowserName}' on '${context.platform}': the missing browser dependency was installed but still could not be detected.`
@@ -2659,7 +2719,120 @@ async function runContext({ config, spec, test, context, runnerDetails, appiumPo
2659
2719
  "predicate disagreed; this is a bug.");
2660
2720
  }
2661
2721
  try {
2662
- if (driverRequired) {
2722
+ /* c8 ignore start */
2723
+ // Mobile web (phase A5): only reachable on a capable host (the mobile
2724
+ // preflight gates everything else), so it's exercised by the
2725
+ // mobile-web fixture legs, not the unit suite.
2726
+ if (driverRequired && mobileWebBrowserName) {
2727
+ // The browser session lives ON the managed device, through the app
2728
+ // session's Appium server (homed where the mobile driver lives) — not
2729
+ // the desktop engine pool. Acquire/boot the context's default device,
2730
+ // then open one webdriver session with browserName set so Appium starts
2731
+ // it in a web context; goTo/find/click/screenshot then behave exactly
2732
+ // as on desktop.
2733
+ const env = {
2734
+ APPIUM_HOME: appSession.appiumHome,
2735
+ };
2736
+ if (appSession.androidSdkRoot) {
2737
+ env.ANDROID_HOME = appSession.androidSdkRoot;
2738
+ env.ANDROID_SDK_ROOT = appSession.androidSdkRoot;
2739
+ }
2740
+ // Chromedriver autodownload is an Appium insecure feature; opt in
2741
+ // scoped to the uiautomator2 driver on this run-owned server so it can
2742
+ // fetch the chromedriver matching the device's Chrome.
2743
+ const extraArgs = mobileTarget === "android"
2744
+ ? ["--allow-insecure", "uiautomator2:chromedriver_autodownload"]
2745
+ : [];
2746
+ // Server start + device boot are environment work: any failure there
2747
+ // (port pressure, an emulator that can't finish booting on this host,
2748
+ // simctl trouble) is a gating SKIP with the reason named — never a
2749
+ // FAIL — matching the mobile rule that every environment gap SKIPs.
2750
+ let acquired;
2751
+ try {
2752
+ const server = await startAppiumServer(appSession.appiumEntry, config, undefined, env, extraArgs);
2753
+ appSession.server = { port: server.port, process: server.process };
2754
+ const desc = normalizeDeviceDescriptor({
2755
+ contextDevice: context.device,
2756
+ platform: mobileTarget,
2757
+ });
2758
+ acquired =
2759
+ mobileTarget === "android"
2760
+ ? await acquireDevice({
2761
+ desc,
2762
+ registry: appSession.androidDeviceRegistry,
2763
+ sdkRoot: appSession.androidSdkRoot,
2764
+ deps: appSession.androidDeviceDeps,
2765
+ })
2766
+ : await acquireSimulator({
2767
+ desc,
2768
+ registry: appSession.iosSimulatorRegistry,
2769
+ deps: appSession.iosSimulatorDeps,
2770
+ });
2771
+ }
2772
+ catch (error) {
2773
+ const errorMessage = `Skipping context on '${mobileTarget}': couldn't prepare the device for the ${mobileWebBrowserName} session (${error?.message ?? error}). Check the emulator/simulator toolchain (\`doc-detective install ${mobileTarget}\`).`;
2774
+ clog("warning", errorMessage);
2775
+ contextReport.result = "SKIPPED";
2776
+ contextReport.resultDescription = errorMessage;
2777
+ return contextReport;
2778
+ }
2779
+ if ("skip" in acquired) {
2780
+ clog("warning", acquired.skip);
2781
+ contextReport.result = "SKIPPED";
2782
+ contextReport.resultDescription = acquired.skip;
2783
+ return contextReport;
2784
+ }
2785
+ // The resolved device joins the context report the way resolved
2786
+ // browser versions do.
2787
+ contextReport.device = {
2788
+ ...(typeof contextReport.device === "object"
2789
+ ? contextReport.device
2790
+ : {}),
2791
+ platform: mobileTarget,
2792
+ name: acquired.entry.name,
2793
+ };
2794
+ const capabilities = buildMobileBrowserCapabilities({
2795
+ platform: mobileTarget,
2796
+ udid: acquired.entry.udid,
2797
+ // The resolved cache root (not the raw config field, which is
2798
+ // usually unset) — the chromedriver autodownload dir lives here.
2799
+ cacheDir: getCacheDir({ cacheDir: config?.cacheDir }),
2800
+ });
2801
+ try {
2802
+ driver = await driverStart(capabilities, appSession.server.port, 2, {
2803
+ cacheDir: config?.cacheDir,
2804
+ });
2805
+ }
2806
+ catch (error) {
2807
+ // A capable host that can't open the device browser is an
2808
+ // environment gap (the absent-browser precedent): SKIP with the
2809
+ // likely cause named, never FAIL.
2810
+ const hint = mobileTarget === "android"
2811
+ ? " Chrome may not be present on this emulator image — managed Android mobile-web needs a `google_apis` system image (see `doc-detective install android`)."
2812
+ : " Check that the simulator runtime includes Safari and that WebDriverAgent can build (see `doc-detective install ios`).";
2813
+ const errorMessage = `Skipping context on '${mobileTarget}': couldn't start the ${mobileWebBrowserName} session on device '${acquired.entry.name}' (${error?.message ?? error}).${hint}`;
2814
+ clog("warning", errorMessage);
2815
+ contextReport.result = "SKIPPED";
2816
+ contextReport.resultDescription = errorMessage;
2817
+ return contextReport;
2818
+ }
2819
+ // Register the device browser as the context's one browser surface so
2820
+ // surface-targeted steps resolve it by engine name, same as desktop.
2821
+ // No additional sessions: one device, one browser.
2822
+ browserSessions = createSessionRegistry({
2823
+ open: async () => {
2824
+ throw new Error("Additional browser sessions aren't supported on a managed device; the device browser is the context's only browser surface.");
2825
+ },
2826
+ isNameTaken: (name) => !!processRegistry?.has(name),
2827
+ });
2828
+ registerSession(browserSessions, {
2829
+ name: String(mobileWebBrowserName).toLowerCase(),
2830
+ engine: String(mobileWebBrowserName).toLowerCase(),
2831
+ driver,
2832
+ });
2833
+ /* c8 ignore stop */
2834
+ }
2835
+ else if (driverRequired) {
2663
2836
  // Check out a server for this context's lifetime — released in the
2664
2837
  // finally so the next queued context can reuse it.
2665
2838
  appiumPort = await appiumPool.acquire();
@@ -3633,7 +3806,10 @@ async function runStep({ config = {}, context = {}, step, driver, metaValues = {
3633
3806
  // Start one Appium server on a free port and resolve once it answers /status.
3634
3807
  // Each concurrent runner gets its own server (own port) so parallel contexts
3635
3808
  // never create sessions on the same Appium instance.
3636
- async function startAppiumServer(appiumEntry, config, display, extraEnv) {
3809
+ async function startAppiumServer(appiumEntry, config, display, extraEnv,
3810
+ // Extra CLI args for the server, e.g. the scoped `--allow-insecure`
3811
+ // chromedriver-autodownload opt-in for android mobile-web sessions.
3812
+ extraArgs) {
3637
3813
  const port = await findFreePort();
3638
3814
  log(config, "debug", `Starting Appium on port ${port}`);
3639
3815
  // When a virtual display is supplied (Linux Xvfb recording), launch the
@@ -3648,7 +3824,7 @@ async function startAppiumServer(appiumEntry, config, display, extraEnv) {
3648
3824
  ...(extraEnv ?? {}),
3649
3825
  }
3650
3826
  : process.env;
3651
- const proc = spawn(process.execPath, [appiumEntry, "-a", "127.0.0.1", "-p", String(port)], {
3827
+ const proc = spawn(process.execPath, [appiumEntry, "-a", "127.0.0.1", "-p", String(port), ...(extraArgs ?? [])], {
3652
3828
  windowsHide: true,
3653
3829
  cwd: path.join(__dirname, "../.."),
3654
3830
  env,