long_press
Simulate a long press at specific coordinates on a simulator for a defined duration. Integrates with XcodeBuildMCP for precise UI interactions.
Instructions
Long press at specific coordinates for given duration (ms). Use describe_ui for precise coordinates (don't guess from screenshots).
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| duration | Yes | ||
| simulatorUuid | Yes | ||
| x | Yes | ||
| y | Yes |
Implementation Reference
- Main handler function that performs the long press using AXe 'touch' command with specified coordinates and duration.export async function long_pressLogic( params: LongPressParams, executor: CommandExecutor, axeHelpers: AxeHelpers = { getAxePath, getBundledAxeEnvironment, createAxeNotAvailableResponse, }, ): Promise<ToolResponse> { const toolName = 'long_press'; const { simulatorId, x, y, duration } = params; // AXe uses touch command with --down, --up, and --delay for long press const delayInSeconds = Number(duration) / 1000; // Convert ms to seconds const commandArgs = [ 'touch', '-x', String(x), '-y', String(y), '--down', '--up', '--delay', String(delayInSeconds), ]; log( 'info', `${LOG_PREFIX}/${toolName}: Starting for (${x}, ${y}), ${duration}ms on ${simulatorId}`, ); try { await executeAxeCommand(commandArgs, simulatorId, 'touch', executor, axeHelpers); log('info', `${LOG_PREFIX}/${toolName}: Success for ${simulatorId}`); const warning = getCoordinateWarning(simulatorId); const message = `Long press at (${x}, ${y}) for ${duration}ms simulated successfully.`; if (warning) { return createTextResponse(`${message}\n\n${warning}`); } return createTextResponse(message); } catch (error) { log('error', `${LOG_PREFIX}/${toolName}: Failed - ${error}`); if (error instanceof DependencyError) { return axeHelpers.createAxeNotAvailableResponse(); } else if (error instanceof AxeError) { return createErrorResponse( `Failed to simulate long press at (${x}, ${y}): ${error.message}`, error.axeOutput, ); } else if (error instanceof SystemError) { return createErrorResponse( `System error executing axe: ${error.message}`, error.originalError?.stack, ); } return createErrorResponse( `An unexpected error occurred: ${error instanceof Error ? error.message : String(error)}`, ); } }
- Zod schema definition for long_press parameters including simulatorId, x, y, duration. Public schema omits simulatorId.const longPressSchema = z.object({ simulatorId: z.string().uuid('Invalid Simulator UUID format'), x: z.number().int('X coordinate for the long press'), y: z.number().int('Y coordinate for the long press'), duration: z.number().positive('Duration of the long press in milliseconds'), }); // Use z.infer for type safety type LongPressParams = z.infer<typeof longPressSchema>; const publicSchemaObject = longPressSchema.omit({ simulatorId: true } as const).strict();
- src/mcp/tools/ui-testing/long_press.ts:111-127 (registration)Tool registration exporting the 'long_press' tool with name, description, schema, and session-aware handler wrapping long_pressLogic.export default { name: 'long_press', description: "Long press at specific coordinates for given duration (ms). Use describe_ui for precise coordinates (don't guess from screenshots).", schema: publicSchemaObject.shape, // MCP SDK compatibility handler: createSessionAwareTool<LongPressParams>({ internalSchema: longPressSchema as unknown as z.ZodType<LongPressParams>, logicFunction: (params: LongPressParams, executor: CommandExecutor) => long_pressLogic(params, executor, { getAxePath, getBundledAxeEnvironment, createAxeNotAvailableResponse, }), getExecutor: getDefaultCommandExecutor, requirements: [{ allOf: ['simulatorId'], message: 'simulatorId is required' }], }), };
- Helper function to generate warnings about using describe_ui for coordinates based on session timestamps.function getCoordinateWarning(simulatorId: string): string | null { const session = describeUITimestamps.get(simulatorId); if (!session) { return 'Warning: describe_ui has not been called yet. Consider using describe_ui for precise coordinates instead of guessing from screenshots.'; } const timeSinceDescribe = Date.now() - session.timestamp; if (timeSinceDescribe > DESCRIBE_UI_WARNING_TIMEOUT) { const secondsAgo = Math.round(timeSinceDescribe / 1000); return `Warning: describe_ui was last called ${secondsAgo} seconds ago. Consider refreshing UI coordinates with describe_ui instead of using potentially stale coordinates.`; } return null; }
- Helper function to execute AXe commands, handling binary path, UDID, environment, and errors. Used by long_pressLogic.async function executeAxeCommand( commandArgs: string[], simulatorId: string, commandName: string, executor: CommandExecutor = getDefaultCommandExecutor(), axeHelpers: AxeHelpers = { getAxePath, getBundledAxeEnvironment, createAxeNotAvailableResponse }, ): Promise<void> { // Get the appropriate axe binary path const axeBinary = axeHelpers.getAxePath(); if (!axeBinary) { throw new DependencyError('AXe binary not found'); } // Add --udid parameter to all commands const fullArgs = [...commandArgs, '--udid', simulatorId]; // Construct the full command array with the axe binary as the first element const fullCommand = [axeBinary, ...fullArgs]; try { // Determine environment variables for bundled AXe const axeEnv = axeBinary !== 'axe' ? axeHelpers.getBundledAxeEnvironment() : undefined; const result = await executor(fullCommand, `${LOG_PREFIX}: ${commandName}`, false, axeEnv); if (!result.success) { throw new AxeError( `axe command '${commandName}' failed.`, commandName, result.error ?? result.output, simulatorId, ); } // Check for stderr output in successful commands if (result.error) { log( 'warn', `${LOG_PREFIX}: Command '${commandName}' produced stderr output but exited successfully. Output: ${result.error}`, ); } // Function now returns void - the calling code creates its own response } catch (error) { if (error instanceof Error) { if (error instanceof AxeError) { throw error; } // Otherwise wrap it in a SystemError throw new SystemError(`Failed to execute axe command: ${error.message}`, error); } // For any other type of error throw new SystemError(`Failed to execute axe command: ${String(error)}`); } }