Skip to content
Closed
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
13 changes: 12 additions & 1 deletion src/ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ export interface IosDevice {
deviceName: string;
}

export type IosPlatform = "ios" | "tvos";

/**
* Real Apple TV units are enumerated over the same go-ios connection as iPhones and
* iPads, so they are distinguished by their product type (e.g. "AppleTV14,1") rather
* than a separate device class.
*/
export const platformFromProductType = (productType: string): IosPlatform =>
productType.startsWith("AppleTV") ? "tvos" : "ios";

const getGoIosPath = (): string => {
if (process.env.GO_IOS_PATH) {
return process.env.GO_IOS_PATH;
Expand Down Expand Up @@ -282,7 +292,7 @@ export class IosManager {
return devices;
}

public listDevicesWithDetails(): Array<IosDevice & { version: string }> {
public listDevicesWithDetails(): Array<IosDevice & { version: string; platform: IosPlatform }> {
if (!this.isGoIosInstalled()) {
console.error("go-ios is not installed, no physical iOS devices can be detected");
return [];
Expand All @@ -296,6 +306,7 @@ export class IosManager {
deviceId: device,
deviceName: info.DeviceName,
version: info.ProductVersion,
platform: platformFromProductType(info.ProductType),
};
});

Expand Down
24 changes: 21 additions & 3 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ChildProcess } from "node:child_process";
import { error, trace } from "./logger";
import { AndroidRobot, AndroidDeviceManager } from "./android";
import { ActionableError, Robot } from "./robot";
import { IosManager, IosRobot } from "./ios";
import { IosManager, IosRobot, platformFromProductType } from "./ios";
import { PNG } from "./png";
import { isScalingAvailable, Image } from "./image-utils";
import { Mobilecli } from "./mobilecli";
Expand Down Expand Up @@ -172,6 +172,24 @@ export const createMcpServer = (): McpServer => {
const iosDevices = iosManager.listDevices();
const iosDevice = iosDevices.find(d => d.deviceId === deviceId);
if (iosDevice) {
// Real Apple TV units are driven through mobilecli, which installs and drives
// the tvOS runner (re-signed with a provisioning profile). iPhones and iPads
// keep using the WebDriverAgent-based IosRobot.
const platform = platformFromProductType(iosManager.getDeviceInfo(deviceId).ProductType);
if (platform === "tvos") {
if (!agentVerifiedSimulators.has(deviceId)) {
const agentStatus = mobilecli.agentStatus(deviceId);
if (agentStatus.status === "fail") {
mobilecli.agentInstall(deviceId);
}

agentVerifiedSimulators.add(deviceId);
}

posthog("get_robot", { "DevicePlatform": "tvos", "DeviceType": "real" }).then();
return new MobileDevice(deviceId);
}

posthog("get_robot", { "DevicePlatform": "ios", "DeviceType": "real" }).then();
return new IosRobot(deviceId);
}
Expand Down Expand Up @@ -240,14 +258,14 @@ export const createMcpServer = (): McpServer => {
});
}

// Get iOS physical devices with details
// Get iOS and tvOS physical devices with details
try {
const iosDevices = iosManager.listDevicesWithDetails();
for (const device of iosDevices) {
devices.push({
id: device.deviceId,
name: device.deviceName,
platform: "ios",
platform: device.platform,
type: "real",
version: device.version,
state: "online",
Expand Down
20 changes: 20 additions & 0 deletions test/ios-platform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { test, expect } from "@playwright/test";

import { platformFromProductType } from "../src/ios";

test.describe("ios platform detection", () => {

test("maps Apple TV product types to tvos", () => {
expect(platformFromProductType("AppleTV14,1")).toBe("tvos");
expect(platformFromProductType("AppleTV11,1")).toBe("tvos");
});

test("maps iPhone and iPad product types to ios", () => {
expect(platformFromProductType("iPhone15,3")).toBe("ios");
expect(platformFromProductType("iPad13,1")).toBe("ios");
});

test("defaults unknown product types to ios", () => {
expect(platformFromProductType("")).toBe("ios");
});
});