build(ios): ship the runner host app without its test fixtures

Merged
TH
2h ago
+1457-983

Summary

The npm package ships the Apple runner as build input, so the runner's test code reached users as source. dist/apple/runner/.../AgentDeviceRunnerApp.m (55 KB, 1,101 lines) carried ~40 --agent-device-* fixture screens — text-entry, alert, back-regression, crowded-screen, second-window — compiled into the host app every user builds. They exist only for the UnitTests/ suites.

The fixture screens moved out of the shipped host into AgentDeviceRunnerAppFixtures.m: AgentDeviceRunnerFixtureViewController, a subclass of the host's view controller, compiled only under AGENT_DEVICE_RUNNER_UNIT_TESTS and dropped by scripts/package-apple-runner-source.mjs the way UnitTests/ and the Swift #if blocks already are — one mechanism. The host reaches it through +agentDeviceRootViewControllerClass (a class-name lookup, not a symbol) and -agentDeviceWillAttachToWindow:, so the packaged Xcode project builds with the fixtures file absent and production behavior is unchanged. The unit-test lane now passes -D AGENT_DEVICE_RUNNER_UNIT_TESTS to Objective-C too, through OTHER_CFLAGS, which the runner cache identity records.

Guards so it cannot return: scripts/lib/shipped-test-code.ts is the single owner; check:package fails when the packed tree carries UnitTests/, Tests/ (except the two documented PackStubs), *.test.*/*.spec.* (any trailing extensions), fixtures/, mocks/, a ForTesting name, any occurrence of the AGENT_DEVICE_RUNNER_UNIT_TESTS token in packaged runner source (the packager strips every block it guards, so one mention is a block that survived), or a "--agent-device-*" literal anywhere in the packaged host app target (any extension, any depth). If the packager's drop entry for the fixtures file is ever removed, the last two predicates fire on that file's own bytes — a test asserts exactly that against the real file.

Packed size (npm pack --dry-run --json --ignore-scripts): 652 files / 5,530,857 B → 652 files / 5,481,414 B (-49,443 B). Host app source: 54,974 B → 5,304 B.

Touched files: 12. Scope expansion: the no-test-code path guard covers the whole package, not just the runner — fixtures/, mocks/, __tests__/, *.test.*/*.spec.*, and ForTesting names fail check:package wherever they appear in the packed tree. The diff is mostly the move; git diff -M90% shows AgentDeviceRunnerApp.m as 1,101 → 157 lines plus the moved file.

Rebase note: PR #3918 adds a safe-area-anchored regression button to AgentDeviceRunnerApp.m. That fixture edit must move into AgentDeviceRunnerAppFixtures.m (inside AgentDeviceRunnerFixtureViewController -viewDidLoad), not the host file.

Validation

Tested SHA: d49c4a6e0c (check:affected --run --head receipts at 8fa7f6a33c and fc63b1e9a2; runner sources unchanged since d684861817, whose device lane — 108/108 unit tests + production build + live smoke — still covers this head).

  • npm pack --dry-run --json --ignore-scripts before/after above; the guard predicates report 0 failures over the packed tree.
  • pnpm check:package, pnpm check:xctest-selection, pnpm check:packaged-runner-swift (63 files, line parity + Swift parse), pnpm typecheck, pnpm lint, pnpm format — pass.
  • pnpm package:apple-runner:npm, then xcodebuild build -scheme AgentDeviceRunner on the packaged tree only — BUILD SUCCEEDED with the fixtures file absent.
  • Device, own simulator No Fixtures iPhone (77399684-052A-454D-A22A-A7BDC9642576), state dirs under /private/tmp/no-fixtures: unit-test build (AGENT_DEVICE_XCUITEST_INCLUDE_UNIT_TESTS=1) succeeds and the fixture-dependent suites run green — TextEntry*, TextInputProbe, Alert*, Navigation, SynthesizedInteraction/SynthesizedTextEntry, Interaction, Snapshot, SystemModal, SelectorMatchPolicy (108 selected tests). Production build (no unit tests) succeeds; nm shows 0 fixture symbols in its binary, 17 in the unit-test binary.
  • CI obligations: macOS host lane and iOS PR/nightly lanes at head.
  • Unresolved risks: none known; the fixture class is reached by name, so a rename must update the host lookup (noted in both files).

🤖 Generated with Claude Code