Files
software-mansion__argent/packages/tool-server/test/describe-missing-xcrun-424.test.ts
filip131311 69538e74a3 fix(ax-service): remove unnecessary AutomationEnabled pref write (#256)
## Summary
- Removes the `ensureAutomationEnabled` function and all its call sites
- The ax-service binary uses `AXRuntime`/`AXSpringBoardServer` private
frameworks directly and does not need the `AutomationEnabled` preference
on iOS 26.x simulators
- Writing it pre-boot into the Accessibility plist actively breaks the
AX subsystem by interfering with `cfprefsd` domain initialization,
causing `describe` queries to hang

## Investigation

Tested on erased sims across five device types (iPhone 17e, iPhone 17,
iPhone Air, iPad mini, iPad Pro 11-inch) on iOS 26.5 (runtime `23F77`):

1. **No plist at all** → `describe` returns full hierarchy including
SpringBoard-hosted TCC dialogs ✅
2. **Empty plist** → works ✅
3. **Plist with `AutomationEnabled=true`** → `describe` hangs
indefinitely ❌

The pref was originally added in #98 (April 2026) likely cargo-culted
from older iOS automation patterns. On iOS 26.x the
`AXSpringBoardServer` MIG interface accepts connections from the
ax-service binary without it.

## Test plan
- [x] TypeScript clean
- [x] All affected tests pass (simulator-server-blueprint,
describe-missing-xcrun-424)
- [x] E2E: erased sim → boot → launch Maps → `describe` returns TCC
dialog with all buttons

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-26 10:07:04 +02:00

84 lines
3.1 KiB
TypeScript

/**
* `describe` on iOS shells out to xcrun via the ax-service blueprint factory
* (`spawnDaemon` → `xcrun simctl spawn ...`). When xcrun is missing — Linux
* runner, broken Xcode toolchain, etc. — the spawn rejects with ENOENT and
* the error bubbles up to the HTTP layer as a 500 with a raw "spawn xcrun
* ENOENT" message.
*
* Every other cross-platform iOS tool (`launch-app`, `restart-app`,
* `open-url`, `reinstall-app`) declares `requires: ["xcrun"]` on its
* `dispatchByPlatform` iOS branch, so missing-xcrun is short-circuited into
* a `424 Failed Dependency` with the `xcode-select --install` install hint.
*
* This test pins that contract for `describe`.
*/
import { describe, it, expect, beforeEach, vi } from "vitest";
import request from "supertest";
import { Registry } from "@argent/registry";
const execFileMock = vi.fn();
vi.mock("node:child_process", async () => {
const actual = await vi.importActual<typeof import("node:child_process")>("node:child_process");
return { ...actual, execFile: (...args: unknown[]) => execFileMock(...args) };
});
vi.mock("@argent/native-devtools-ios", () => ({
bootstrapDylibPath: () => "/fake/bootstrap.dylib",
simulatorServerBinaryPath: () => "/fake/sim-server",
simulatorServerBinaryDir: () => "/fake",
axServiceBinaryPath: () => "/fake/ax-service",
}));
import { createHttpApp } from "../src/http";
import { __resetDepCacheForTests } from "../src/utils/check-deps";
import { createDescribeTool } from "../src/tools/describe";
import { axServiceBlueprint } from "../src/blueprints/ax-service";
import { nativeDevtoolsBlueprint } from "../src/blueprints/native-devtools";
describe("describe → 424 when xcrun is missing", () => {
beforeEach(() => {
__resetDepCacheForTests();
execFileMock.mockReset();
});
it("returns 424 (Failed Dependency) with the xcode-select install hint", async () => {
// Both probes — `command -v xcrun` and a bare `xcrun ...` invocation —
// fail. Any deeper xcrun call would also ENOENT, but the dep gate should
// fire first.
execFileMock.mockImplementation(
(
cmd: string,
_args: string[],
_opts: unknown,
cb: (err: Error | null, stdout?: string, stderr?: string) => void
) => {
if (cmd === "/bin/sh") {
cb(new Error("not found"));
return;
}
if (cmd === "xcrun") {
const err = new Error("spawn xcrun ENOENT") as NodeJS.ErrnoException;
err.code = "ENOENT";
cb(err);
return;
}
cb(null, "", "");
}
);
const registry = new Registry();
registry.registerBlueprint(axServiceBlueprint);
registry.registerBlueprint(nativeDevtoolsBlueprint);
registry.registerTool(createDescribeTool(registry));
const { app } = createHttpApp(registry);
const res = await request(app)
.post("/tools/describe")
.send({ udid: "11111111-1111-1111-1111-111111111111" });
expect(res.status).toBe(424);
expect(res.body.error).toMatch(/xcode-select --install/);
expect(res.body.missing).toEqual(["xcrun"]);
});
});