Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .fallowrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"examples/test-app/**",
"scripts/perf/**",
"scripts/layering/**",
"ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests.xctestplan",
"apple-runner/AgentDeviceRunner/AgentDeviceRunnerUITests.xctestplan",
"scripts/write-xcuitest-cache-metadata.mjs"
],
"ignoreDependencies": ["@microsoft/api-extractor", "@theme"],
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/setup-apple-replay/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ runs:
id: source-hash
run: |
set -euo pipefail
echo "value=${{ hashFiles('ios-runner/**', 'scripts/build-xcuitest-apple.sh', 'scripts/patch-xcuitest-runner-icon.ts', 'scripts/write-xcuitest-cache-metadata.mjs', 'src/platforms/ios/apple-runner-platform.ts', 'src/platforms/ios/runner-icon.ts', 'src/platforms/ios/runner-xctestrun.ts', 'src/platforms/ios/runner-xctestrun-products.ts', '.github/actions/setup-apple-replay/action.yml', 'package.json', 'pnpm-lock.yaml') }}" >> "$GITHUB_OUTPUT"
echo "value=${{ hashFiles('apple-runner/**', 'scripts/build-xcuitest-apple.sh', 'scripts/patch-xcuitest-runner-icon.ts', 'scripts/write-xcuitest-cache-metadata.mjs', 'src/platforms/ios/apple-runner-platform.ts', 'src/platforms/ios/runner-icon.ts', 'src/platforms/ios/runner-xctestrun.ts', 'src/platforms/ios/runner-xctestrun-products.ts', '.github/actions/setup-apple-replay/action.yml', 'package.json', 'pnpm-lock.yaml') }}" >> "$GITHUB_OUTPUT"
shell: bash

- name: Cache replay prebuilt
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:

- name: Disallow trailing commas before closing parenthesis in Swift
run: |
if rg -nU --glob '*.swift' ',\s*\n\s*\)' ios-runner; then
if rg -nU --glob '*.swift' ',\s*\n\s*\)' apple-runner; then
echo "Found trailing commas before ')' in Swift files. This syntax requires Swift 6.1+ and breaks older Xcode toolchains."
exit 1
fi
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ Command-only flags (like `find --first`) that do not flow to the platform layer
- Command identity + command surface: `src/command-catalog.ts`, `src/commands/command-surface.ts`, `src/commands/command-contract.ts`, `src/commands/client-command-contracts.ts`
- CLI grammar: `src/commands/cli-grammar.ts`, `src/commands/cli-grammar/*`
- Daemon request projection: `src/commands/command-projection.ts`
- Platform backends: `src/platforms/apple/*`, `src/platforms/ios/*`, `ios-runner/*`, `src/platforms/android/*`
- Platform backends: `src/platforms/apple/*`, `src/platforms/ios/*`, `apple-runner/*`, `src/platforms/android/*`

## Pull Requests
- Before opening PR: ensure no conflict markers/unmerged paths.
Expand Down
File renamed without changes.
File renamed without changes.
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,13 @@
"files": [
"bin",
"dist",
"ios-runner",
"!ios-runner/**/.build",
"!ios-runner/**/.swiftpm",
"!ios-runner/**/xcuserdata",
"!ios-runner/**/*.xcuserstate",
"!ios-runner/README.md",
"!ios-runner/RUNNER_PROTOCOL.md",
"apple-runner",
"!apple-runner/**/.build",
"!apple-runner/**/.swiftpm",
"!apple-runner/**/xcuserdata",
"!apple-runner/**/*.xcuserstate",
"!apple-runner/README.md",
"!apple-runner/RUNNER_PROTOCOL.md",
"macos-helper",
"!macos-helper/**/.build",
"android-snapshot-helper/dist",
Expand Down
2 changes: 1 addition & 1 deletion plans/perfect-shape.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ Concrete tells:
| Debt | Evidence | Est. LOC |
|---|---|---|
| `client-types.ts` hand-mirrors every Options/Result | 1027 LOC, 83 interfaces, imports 25 modules to re-declare contracts | ~550 derivable |
| Every result is `Promise<Record<string,unknown> \| void>` | `DaemonResponseData`, all Interactor methods, `runIosRunnerCommand` | the weakest seam |
| Every result is `Promise<Record<string,unknown> \| void>` | `DaemonResponseData`, all Interactor methods, `runAppleRunnerCommand` | the weakest seam |
| `core/dispatch*` leaks platform knowledge | ~16 dynamic `import('../platforms/*')` + ~15 branches | ~120 |
| Two platform contracts glued by string round-trip | `Interactor` vs `AgentDeviceBackend`, ~30 ops each | ~130 |
| Batch-step validation reimplemented at 5 layers | `metadata.ts`/`projection.ts` byte-for-byte parallel | ~150 |
Expand Down
10 changes: 5 additions & 5 deletions scripts/build-xcuitest-apple.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
set -eu

PLATFORM="${AGENT_DEVICE_XCUITEST_PLATFORM:-}"
PROJECT_PATH="ios-runner/AgentDeviceRunner/AgentDeviceRunner.xcodeproj"
PROJECT_PATH="apple-runner/AgentDeviceRunner/AgentDeviceRunner.xcodeproj"
SCHEME="AgentDeviceRunner"
DEFAULT_IOS_RUNNER_APP_BUNDLE_ID="com.callstack.agentdevice.runner"

Expand Down Expand Up @@ -80,16 +80,16 @@ try {
resolve_default_derived_path() {
case "$PLATFORM" in
ios)
printf '%s\n' "$HOME/.agent-device/ios-runner/derived"
printf '%s\n' "$HOME/.agent-device/apple-runner/derived"
;;
macos)
printf '%s\n' "$HOME/.agent-device/ios-runner/derived/macos"
printf '%s\n' "$HOME/.agent-device/apple-runner/derived/macos"
;;
tvos)
printf '%s\n' "$HOME/.agent-device/ios-runner/derived/tvos"
printf '%s\n' "$HOME/.agent-device/apple-runner/derived/tvos"
;;
visionos)
printf '%s\n' "$HOME/.agent-device/ios-runner/derived/visionos"
printf '%s\n' "$HOME/.agent-device/apple-runner/derived/visionos"
;;
*)
echo "Unsupported AGENT_DEVICE_XCUITEST_PLATFORM: $PLATFORM" >&2
Expand Down
2 changes: 1 addition & 1 deletion scripts/clean-xcuitest-derived.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';

const DERIVED_ROOT = path.join(os.homedir(), '.agent-device', 'ios-runner', 'derived');
const DERIVED_ROOT = path.join(os.homedir(), '.agent-device', 'apple-runner', 'derived');
const ROOT_TRANSIENT_ENTRY_NAMES = new Set([
'.agent-device-runner-cache.json',
'Build',
Expand Down
2 changes: 1 addition & 1 deletion scripts/package-ios-simulator-runner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ VERSION="$1"
RELEASE_TAG="$2"
OUTPUT_DIR="$3"

DERIVED_PATH="${AGENT_DEVICE_IOS_RUNNER_DERIVED_PATH:-$HOME/.agent-device/ios-runner/derived}"
DERIVED_PATH="${AGENT_DEVICE_IOS_RUNNER_DERIVED_PATH:-$HOME/.agent-device/apple-runner/derived}"
PRODUCTS_DIR="$DERIVED_PATH/Build/Products"
EXPECTED_RUNNER_BUNDLE_ID="${AGENT_DEVICE_IOS_RUNNER_RELEASE_BUNDLE_ID:-}"
ARCHIVE_BASENAME="agent-device-ios-runner-$VERSION.app.tar.gz"
Expand Down
2 changes: 1 addition & 1 deletion scripts/write-xcuitest-cache-metadata.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function resolveRunnerTestBundleId() {
}

function computeRunnerSourceFingerprint() {
const runnerRoot = path.join(projectRoot, 'ios-runner', 'AgentDeviceRunner');
const runnerRoot = path.join(projectRoot, 'apple-runner', 'AgentDeviceRunner');
const files = collectRunnerSourceFiles(runnerRoot);
const hash = crypto.createHash('sha256');
for (const file of files) {
Expand Down
2 changes: 1 addition & 1 deletion src/cli/parser/cli-help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ Bootstrap:
If app id is unknown, plan devices, apps, then open <discovered-app-id>. Discovery is not enough when the task asks to open/start the app.
Install arguments are app/package id then artifact path. If the task says install, use install; use reinstall only when explicitly requested. Fresh runtime state is open --relaunch after install.
In Apple CI, run prepare ios-runner after boot/install and before replay/test. prepare ios-runner builds/reuses the XCTest runner, health-checks it with a lightweight command, and retries one stuck/non-connecting runner launch before the first snapshot pays that setup cost. It is not a recovery step for "runner already owned by another agent-device daemon"; stop the owning daemon on the Mac with simulator access instead. If the replay/test step starts a separate daemon, stop the prepare daemon before replay/test so the prepared runner does not keep a live lease owned by that daemon.
CI may cache ~/.agent-device/ios-runner/derived with an exact key that includes the agent-device package and Xcode version. Avoid broad restore-key fallbacks; prepare ios-runner already recovers bad restored runner artifacts and one retryable non-connecting runner launch. Runner build/start output is written to the session's runner.log; daemon.log is for daemon lifecycle/startup issues.
CI may cache ~/.agent-device/apple-runner/derived with an exact key that includes the agent-device package and Xcode version. Avoid broad restore-key fallbacks; prepare ios-runner already recovers bad restored runner artifacts and one retryable non-connecting runner launch. Runner build/start output is written to the session's runner.log; daemon.log is for daemon lifecycle/startup issues.
Do not open artifact paths or invent package ids. If apps lookup misses the target and no URL/artifact is provided, ask or stop.

Snapshots and refs:
Expand Down
62 changes: 31 additions & 31 deletions src/core/__tests__/dispatch-interactions.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { beforeEach, test, vi } from 'vitest';
import assert from 'node:assert/strict';

const { mockRunIosRunnerCommand } = vi.hoisted(() => ({
mockRunIosRunnerCommand: vi.fn(),
const { mockRunAppleRunnerCommand } = vi.hoisted(() => ({
mockRunAppleRunnerCommand: vi.fn(),
}));

vi.mock('../../platforms/apple/core/runner/runner-client.ts', async (importOriginal) => {
const actual =
await importOriginal<typeof import('../../platforms/apple/core/runner/runner-client.ts')>();
return { ...actual, runIosRunnerCommand: mockRunIosRunnerCommand };
return { ...actual, runAppleRunnerCommand: mockRunAppleRunnerCommand };
});

import {
Expand All @@ -33,7 +33,7 @@ vi.mock('../../platforms/apple/os/macos/helper.ts', async (importOriginal) => {
});

beforeEach(() => {
mockRunIosRunnerCommand.mockReset();
mockRunAppleRunnerCommand.mockReset();
});

function makeUnusedInteractor(): Interactor {
Expand Down Expand Up @@ -170,7 +170,7 @@ test('handleSwipeCommand preserves iOS swipe duration through dispatch', async (
});

test('handleSwipeCommand fuses repeated swipes into sequence drag steps with ping-pong unrolled', async () => {
mockRunIosRunnerCommand.mockResolvedValueOnce({
mockRunAppleRunnerCommand.mockResolvedValueOnce({
gestureStartUptimeMs: 100,
gestureEndUptimeMs: 720,
completedSteps: 2,
Expand All @@ -193,7 +193,7 @@ test('handleSwipeCommand fuses repeated swipes into sequence drag steps with pin
},
);

assert.deepEqual(mockRunIosRunnerCommand.mock.calls[0]?.[1], {
assert.deepEqual(mockRunAppleRunnerCommand.mock.calls[0]?.[1], {
command: 'sequence',
steps: [
// Ping-pong is unrolled daemon-side: odd indices swap endpoints, replacing the
Expand Down Expand Up @@ -273,7 +273,7 @@ test('handleRotateGestureCommand routes Android through the interactor', async (
});

test('handlePressCommand fuses an iOS jitter series into one sequence runner request', async () => {
mockRunIosRunnerCommand.mockResolvedValueOnce({
mockRunAppleRunnerCommand.mockResolvedValueOnce({
completedSteps: 3,
sequenceResults: [
{ ok: true, kind: 'tap' },
Expand All @@ -292,8 +292,8 @@ test('handlePressCommand fuses an iOS jitter series into one sequence runner req
appBundleId: 'com.example.App',
});

assert.equal(mockRunIosRunnerCommand.mock.calls.length, 1);
const sent = mockRunIosRunnerCommand.mock.calls[0]?.[1] as RunnerCommand;
assert.equal(mockRunAppleRunnerCommand.mock.calls.length, 1);
const sent = mockRunAppleRunnerCommand.mock.calls[0]?.[1] as RunnerCommand;
assert.equal(sent.command, 'sequence');
assert.equal(sent.appBundleId, 'com.example.App');
assert.deepEqual(sent.steps, [
Expand All @@ -306,7 +306,7 @@ test('handlePressCommand fuses an iOS jitter series into one sequence runner req
});

test('handlePressCommand fuses an iOS hold series into longPress sequence steps', async () => {
mockRunIosRunnerCommand.mockResolvedValueOnce({
mockRunAppleRunnerCommand.mockResolvedValueOnce({
completedSteps: 3,
sequenceResults: [
{ ok: true, kind: 'longPress' },
Expand All @@ -321,8 +321,8 @@ test('handlePressCommand fuses an iOS hold series into longPress sequence steps'
holdMs: 300,
});

assert.equal(mockRunIosRunnerCommand.mock.calls.length, 1);
const sent = mockRunIosRunnerCommand.mock.calls[0]?.[1] as RunnerCommand;
assert.equal(mockRunAppleRunnerCommand.mock.calls.length, 1);
const sent = mockRunAppleRunnerCommand.mock.calls[0]?.[1] as RunnerCommand;
assert.equal(sent.command, 'sequence');
assert.deepEqual(sent.steps, [
{ kind: 'longPress', x: 100, y: 200, durationMs: 300 },
Expand All @@ -332,7 +332,7 @@ test('handlePressCommand fuses an iOS hold series into longPress sequence steps'
});

test('handlePressCommand fuses a plain iOS series into sequence tap steps (retired tapSeries route)', async () => {
mockRunIosRunnerCommand.mockResolvedValueOnce({
mockRunAppleRunnerCommand.mockResolvedValueOnce({
completedSteps: 2,
sequenceResults: [
{ ok: true, kind: 'tap' },
Expand All @@ -345,8 +345,8 @@ test('handlePressCommand fuses a plain iOS series into sequence tap steps (retir
intervalMs: 80,
});

assert.equal(mockRunIosRunnerCommand.mock.calls.length, 1);
const sent = mockRunIosRunnerCommand.mock.calls[0]?.[1] as RunnerCommand;
assert.equal(mockRunAppleRunnerCommand.mock.calls.length, 1);
const sent = mockRunAppleRunnerCommand.mock.calls[0]?.[1] as RunnerCommand;
assert.equal(sent.command, 'sequence');
assert.deepEqual(sent.steps, [
{ kind: 'tap', x: 100, y: 200, synthesized: true, pauseMs: 80 },
Expand All @@ -355,7 +355,7 @@ test('handlePressCommand fuses a plain iOS series into sequence tap steps (retir
});

test('handlePressCommand fuses an iOS double-tap series into doubleTap sequence steps', async () => {
mockRunIosRunnerCommand.mockResolvedValueOnce({
mockRunAppleRunnerCommand.mockResolvedValueOnce({
completedSteps: 2,
sequenceResults: [
{ ok: true, kind: 'doubleTap' },
Expand All @@ -369,8 +369,8 @@ test('handlePressCommand fuses an iOS double-tap series into doubleTap sequence
intervalMs: 50,
});

assert.equal(mockRunIosRunnerCommand.mock.calls.length, 1);
const sent = mockRunIosRunnerCommand.mock.calls[0]?.[1] as RunnerCommand;
assert.equal(mockRunAppleRunnerCommand.mock.calls.length, 1);
const sent = mockRunAppleRunnerCommand.mock.calls[0]?.[1] as RunnerCommand;
assert.equal(sent.command, 'sequence');
// doubleTap steps never set synthesized — the runner's doubleTapAt path handles them.
assert.deepEqual(sent.steps, [
Expand All @@ -380,7 +380,7 @@ test('handlePressCommand fuses an iOS double-tap series into doubleTap sequence
});

test('handlePressCommand maps a failed sequence step to an AppError', async () => {
mockRunIosRunnerCommand.mockResolvedValueOnce({
mockRunAppleRunnerCommand.mockResolvedValueOnce({
completedSteps: 1,
failedStepIndex: 1,
sequenceResults: [
Expand All @@ -407,7 +407,7 @@ test('handlePressCommand maps a failed sequence step to an AppError', async () =
test('handlePressCommand rebases a chunk-2 failure to global step/completed indices', async () => {
// 25 jittered taps -> 2 chunks of 20/5. Chunk 2 fails at its LOCAL step index 2 (global 22),
// having completed 2 of its steps locally (global 22). No chunk 3 must be sent.
mockRunIosRunnerCommand
mockRunAppleRunnerCommand
.mockResolvedValueOnce({
completedSteps: 20,
sequenceResults: Array.from({ length: 20 }, () => ({ ok: true, kind: 'tap' })),
Expand Down Expand Up @@ -444,9 +444,9 @@ test('handlePressCommand rebases a chunk-2 failure to global step/completed indi
);

// Both chunk requests were sent; the failure stopped chunk 3 from ever being issued.
assert.equal(mockRunIosRunnerCommand.mock.calls.length, 2);
const chunk1 = mockRunIosRunnerCommand.mock.calls[0]?.[1] as RunnerCommand;
const chunk2 = mockRunIosRunnerCommand.mock.calls[1]?.[1] as RunnerCommand;
assert.equal(mockRunAppleRunnerCommand.mock.calls.length, 2);
const chunk1 = mockRunAppleRunnerCommand.mock.calls[0]?.[1] as RunnerCommand;
const chunk2 = mockRunAppleRunnerCommand.mock.calls[1]?.[1] as RunnerCommand;
assert.equal(chunk1.command, 'sequence');
assert.equal(chunk1.steps?.length, 20);
assert.equal(chunk2.command, 'sequence');
Expand All @@ -456,7 +456,7 @@ test('handlePressCommand rebases a chunk-2 failure to global step/completed indi
test('handlePressCommand aggregates completedSteps and gestureEnd across sequence chunks', async () => {
// 45 jittered taps -> 3 chunks of 20/20/5. The aggregated result must report all 45 steps
// and the LAST chunk's gestureEndUptimeMs, not just the first chunk's.
mockRunIosRunnerCommand
mockRunAppleRunnerCommand
.mockResolvedValueOnce({
completedSteps: 20,
sequenceResults: Array.from({ length: 20 }, () => ({ ok: true, kind: 'tap' })),
Expand All @@ -483,7 +483,7 @@ test('handlePressCommand aggregates completedSteps and gestureEnd across sequenc
jitterPx: 2,
});

assert.equal(mockRunIosRunnerCommand.mock.calls.length, 3);
assert.equal(mockRunAppleRunnerCommand.mock.calls.length, 3);
assert.equal(result.completedSteps, 45);
assert.equal((result.sequenceResults as unknown[]).length, 45);
// First chunk frame/start preserved, last chunk end.
Expand All @@ -495,7 +495,7 @@ test('handlePressCommand aggregates completedSteps and gestureEnd across sequenc
test('handlePressCommand sub-chunks a hold series by estimated duration under the runner watchdog', async () => {
// count=20 hold-ms=2000 is ~40s of holds in one chunk -> over the 30s main-thread watchdog.
// The duration budget must split it into multiple sub-chunks even though step count <= 20.
mockRunIosRunnerCommand.mockResolvedValue({
mockRunAppleRunnerCommand.mockResolvedValue({
completedSteps: 0,
sequenceResults: [],
});
Expand All @@ -506,11 +506,11 @@ test('handlePressCommand sub-chunks a hold series by estimated duration under th
});

assert.ok(
mockRunIosRunnerCommand.mock.calls.length > 1,
`expected multiple chunks, got ${mockRunIosRunnerCommand.mock.calls.length}`,
mockRunAppleRunnerCommand.mock.calls.length > 1,
`expected multiple chunks, got ${mockRunAppleRunnerCommand.mock.calls.length}`,
);
// Every chunk's estimated holds + pauses + overhead must stay under the budget.
for (const call of mockRunIosRunnerCommand.mock.calls) {
for (const call of mockRunAppleRunnerCommand.mock.calls) {
const sent = call[1] as RunnerCommand;
const steps = sent.steps ?? [];
const estimatedMs = steps.reduce(
Expand All @@ -533,7 +533,7 @@ test('handlePressCommand count=1 keeps the direct (non-sequence) path on iOS', a

await handlePressCommand(IOS_SIMULATOR, interactor, ['100', '200'], { count: 1, jitterPx: 2 });

assert.equal(mockRunIosRunnerCommand.mock.calls.length, 0);
assert.equal(mockRunAppleRunnerCommand.mock.calls.length, 0);
assert.deepEqual(taps, [[100, 200]]);
});

Expand All @@ -555,7 +555,7 @@ test('handlePressCommand on Android keeps the direct path even with hold', async
holdMs: 200,
});

assert.equal(mockRunIosRunnerCommand.mock.calls.length, 0);
assert.equal(mockRunAppleRunnerCommand.mock.calls.length, 0);
assert.equal(longPresses.length, 3);
assert.equal(result.pressed, true);
});
Expand Down
12 changes: 6 additions & 6 deletions src/core/__tests__/dispatch-keyboard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ import { promises as fs } from 'node:fs';
vi.mock('../../platforms/apple/core/runner/runner-client.ts', async (importOriginal) => {
const actual =
await importOriginal<typeof import('../../platforms/apple/core/runner/runner-client.ts')>();
return { ...actual, runIosRunnerCommand: vi.fn() };
return { ...actual, runAppleRunnerCommand: vi.fn() };
});

import { dispatchCommand } from '../dispatch.ts';
import { runIosRunnerCommand } from '../../platforms/apple/core/runner/runner-client.ts';
import { runAppleRunnerCommand } from '../../platforms/apple/core/runner/runner-client.ts';
import { ANDROID_EMULATOR, IOS_DEVICE } from '../../__tests__/test-utils/device-fixtures.ts';
import { withMockedAdb } from '../../__tests__/test-utils/mocked-binaries.ts';

const mockRunIosRunnerCommand = vi.mocked(runIosRunnerCommand);
const mockRunAppleRunnerCommand = vi.mocked(runAppleRunnerCommand);

beforeEach(() => {
vi.resetAllMocks();
mockRunIosRunnerCommand.mockResolvedValue({
mockRunAppleRunnerCommand.mockResolvedValue({
message: 'keyboardReturn',
wasVisible: true,
visible: false,
Expand All @@ -41,8 +41,8 @@ test('dispatch keyboard enter sends native iOS keyboard return command', async (

assert.equal(result?.action, 'enter');
assert.equal(result?.wasVisible, true);
assert.equal(mockRunIosRunnerCommand.mock.calls.length, 1);
assert.deepEqual(mockRunIosRunnerCommand.mock.calls[0]?.[1], {
assert.equal(mockRunAppleRunnerCommand.mock.calls.length, 1);
assert.deepEqual(mockRunAppleRunnerCommand.mock.calls[0]?.[1], {
command: 'keyboardReturn',
appBundleId: 'com.example.app',
});
Expand Down
Loading
Loading