mcp-appium-visual
Version:
MCP Server for Appium mobile automation with visual recovery
281 lines • 10.8 kB
JavaScript
import { exec, spawn } from "child_process";
import { promisify } from "util";
const execAsync = promisify(exec);
/**
* Custom error class for Xcode operations
*/
export class XcodeError extends Error {
constructor(message, cause) {
super(message);
this.cause = cause;
this.name = "XcodeError";
}
}
/**
* Helper class for Xcode command line operations
*/
export class XcodeCommands {
/**
* Check if Xcode command line tools are installed
* @returns true if installed, false otherwise
*/
static async isXcodeCliInstalled() {
try {
await execAsync("xcode-select -p");
return true;
}
catch (error) {
return false;
}
}
/**
* Get the Xcode path
* @returns Path to the Xcode installation
*/
static async getXcodePath() {
try {
const { stdout } = await execAsync("xcode-select -p");
return stdout.trim();
}
catch (error) {
throw new XcodeError(`Failed to get Xcode path: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
}
}
/**
* Install Xcode command line tools
* @returns Promise that resolves when installation is complete or rejects on error
* Note: This will show a GUI prompt that the user needs to interact with
*/
static async installXcodeCli() {
try {
await execAsync("xcode-select --install");
console.log("Xcode CLI tools installation has started. Please complete the installation via the GUI prompt.");
}
catch (error) {
throw new XcodeError(`Failed to install Xcode command line tools: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
}
}
/**
* Get a list of iOS simulators
* @returns Array of available simulators
*/
static async getIosSimulators() {
try {
const { stdout } = await execAsync("xcrun simctl list devices available --json");
const simctlOutput = JSON.parse(stdout);
return simctlOutput.devices;
}
catch (error) {
throw new XcodeError(`Failed to get iOS simulators: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
}
}
/**
* Boot an iOS simulator
* @param udid The UDID of the simulator to boot
* @returns Promise that resolves when simulator is booted
*/
static async bootSimulator(udid) {
try {
await execAsync(`xcrun simctl boot ${udid}`);
}
catch (error) {
// If the error includes "already booted" then it's not really an error
if (error instanceof Error && error.message.includes("already booted")) {
return;
}
throw new XcodeError(`Failed to boot simulator: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
}
}
/**
* Shutdown an iOS simulator
* @param udid The UDID of the simulator to shutdown
*/
static async shutdownSimulator(udid) {
try {
await execAsync(`xcrun simctl shutdown ${udid}`);
}
catch (error) {
// If the device is already shutdown, ignore the error
if (error instanceof Error &&
error.message.includes("No devices are booted")) {
return;
}
throw new XcodeError(`Failed to shutdown simulator: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
}
}
/**
* Install an app on a simulator
* @param udid The UDID of the simulator
* @param appPath Path to the .app bundle
*/
static async installApp(udid, appPath) {
try {
await execAsync(`xcrun simctl install ${udid} "${appPath}"`);
}
catch (error) {
throw new XcodeError(`Failed to install app: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
}
}
/**
* Uninstall an app from a simulator
* @param udid The UDID of the simulator
* @param bundleId Bundle identifier of the app
*/
static async uninstallApp(udid, bundleId) {
try {
await execAsync(`xcrun simctl uninstall ${udid} ${bundleId}`);
}
catch (error) {
throw new XcodeError(`Failed to uninstall app: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
}
}
/**
* Launch an app on a simulator
* @param udid The UDID of the simulator
* @param bundleId Bundle identifier of the app
* @returns Process object if the app is launched in foreground mode
*/
static async launchApp(udid, bundleId, args = [], waitForDebugger = false) {
try {
let command = `xcrun simctl launch`;
if (waitForDebugger) {
command += " --wait-for-debugger";
}
// For background launch, use exec
if (args.length === 0 && !waitForDebugger) {
await execAsync(`${command} ${udid} ${bundleId}`);
return null;
}
// For foreground launch or with arguments, use spawn
const argsString = args.length > 0 ? args.join(" ") : "";
const process = spawn("xcrun", ["simctl", "launch", udid, bundleId, ...args], {
stdio: "inherit",
});
return process;
}
catch (error) {
throw new XcodeError(`Failed to launch app: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
}
}
/**
* Terminate an app on a simulator
* @param udid The UDID of the simulator
* @param bundleId Bundle identifier of the app
*/
static async terminateApp(udid, bundleId) {
try {
await execAsync(`xcrun simctl terminate ${udid} ${bundleId}`);
}
catch (error) {
throw new XcodeError(`Failed to terminate app: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
}
}
/**
* Open a URL on a simulator
* @param udid The UDID of the simulator
* @param url The URL to open
*/
static async openUrl(udid, url) {
try {
await execAsync(`xcrun simctl openurl ${udid} "${url}"`);
}
catch (error) {
throw new XcodeError(`Failed to open URL: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
}
}
/**
* Take a screenshot of a simulator
* @param udid The UDID of the simulator
* @param outputPath Path where screenshot should be saved
*/
static async takeScreenshot(udid, outputPath) {
try {
await execAsync(`xcrun simctl io ${udid} screenshot "${outputPath}"`);
}
catch (error) {
throw new XcodeError(`Failed to take screenshot: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
}
}
/**
* Record a video of a simulator
* @param udid The UDID of the simulator
* @param outputPath Path where the video should be saved
* @returns Process object to control recording (must be terminated to stop recording)
*/
static recordVideo(udid, outputPath) {
const process = spawn("xcrun", ["simctl", "io", udid, "recordVideo", outputPath], {
stdio: "inherit",
});
return process;
}
/**
* Get the status of all simulators
* @returns Object with simulator status information
*/
static async getSimulatorStatus() {
try {
const { stdout } = await execAsync("xcrun simctl list devices --json");
return JSON.parse(stdout);
}
catch (error) {
throw new XcodeError(`Failed to get simulator status: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
}
}
/**
* Create a new simulator
* @param name Name for the new simulator
* @param deviceTypeId Device type identifier (e.g., "iPhone11,8")
* @param runtimeId Runtime identifier (e.g., "com.apple.CoreSimulator.SimRuntime.iOS-14-0")
* @returns UDID of the created simulator
*/
static async createSimulator(name, deviceTypeId, runtimeId) {
try {
const { stdout } = await execAsync(`xcrun simctl create "${name}" "${deviceTypeId}" "${runtimeId}"`);
return stdout.trim(); // Returns the UDID of the created simulator
}
catch (error) {
throw new XcodeError(`Failed to create simulator: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
}
}
/**
* Delete a simulator
* @param udid The UDID of the simulator to delete
*/
static async deleteSimulator(udid) {
try {
await execAsync(`xcrun simctl delete ${udid}`);
}
catch (error) {
throw new XcodeError(`Failed to delete simulator: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
}
}
/**
* Get available device types for simulators
* @returns List of available device types
*/
static async getDeviceTypes() {
try {
const { stdout } = await execAsync("xcrun simctl list devicetypes --json");
const output = JSON.parse(stdout);
return output.devicetypes || [];
}
catch (error) {
throw new XcodeError(`Failed to get device types: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
}
}
/**
* Get available runtimes for simulators
* @returns List of available runtimes
*/
static async getRuntimes() {
try {
const { stdout } = await execAsync("xcrun simctl list runtimes --json");
const output = JSON.parse(stdout);
return output.runtimes || [];
}
catch (error) {
throw new XcodeError(`Failed to get runtimes: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
}
}
}
//# sourceMappingURL=xcodeCommands.js.map