-
-
Notifications
You must be signed in to change notification settings - Fork 250
Adding Unit Testing Tool for iOS #14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
2af4a2f
3d3b137
42088c7
e3fea04
7a0eb89
0e97497
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,186 @@ | ||
| /** | ||
| * iOS Simulator Test Tools - Tools for running tests on iOS applications in simulators | ||
| * | ||
| * This module provides specialized tools for running tests on iOS applications in simulators | ||
| * using xcodebuild test. It supports both workspace and project-based testing with simulator targeting | ||
| * by name or UUID, and includes test failure parsing. | ||
| * | ||
| * Responsibilities: | ||
| * - Running tests on iOS applications in simulators from project files and workspaces | ||
| * - Supporting simulator targeting by name or UUID | ||
| * - Parsing and summarizing test failure results | ||
| * - Handling test configuration and derived data paths | ||
| */ | ||
|
|
||
| import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; | ||
| import { XcodePlatform, executeXcodeCommand } from '../utils/xcode.js'; | ||
| import { executeXcodeBuild } from '../utils/build-utils.js'; | ||
| import { log } from '../utils/logger.js'; | ||
| import { createTextResponse } from '../utils/validation.js'; | ||
| import { ToolResponse, ToolResponseContent } from '../types/common.js'; | ||
| import { | ||
| registerTool, | ||
| workspacePathSchema, | ||
| projectPathSchema, | ||
| schemeSchema, | ||
| configurationSchema, | ||
| derivedDataPathSchema, | ||
| extraArgsSchema, | ||
| simulatorNameSchema, | ||
| simulatorIdSchema, | ||
| useLatestOSSchema | ||
| } from './common.js'; | ||
|
|
||
| // --- internal logic --- | ||
| async function _handleIOSSimulatorTestLogic(params: { | ||
| workspacePath?: string; | ||
| projectPath?: string; | ||
| scheme: string; | ||
| configuration: string; | ||
| simulatorName?: string; | ||
| simulatorId?: string; | ||
| useLatestOS: boolean; | ||
| derivedDataPath?: string; | ||
| extraArgs?: string[]; | ||
| }): Promise<ToolResponse> { | ||
| log('info', `Starting iOS Simulator tests for scheme ${params.scheme} (internal)`); | ||
|
|
||
| const buildResult = await executeXcodeBuild( | ||
| { | ||
| ...params, | ||
| }, | ||
| { | ||
| platform: XcodePlatform.iOSSimulator, | ||
| simulatorName: params.simulatorName, | ||
| simulatorId: params.simulatorId, | ||
| useLatestOS: params.useLatestOS, | ||
| logPrefix: 'iOS Simulator Test', | ||
| }, | ||
| 'test', | ||
| ); | ||
|
|
||
| if (buildResult.isError) return buildResult; | ||
|
|
||
| // --- Parse failures --- | ||
| const raw = buildResult.rawOutput ?? ''; | ||
| const failures = raw | ||
| .split('\n') | ||
| .filter(l => /Test Case .* failed/.test(l)) | ||
| .map(l => { | ||
| const m = l.match(/Test Case '(.*)' failed \((.*)\)/)!; | ||
| return { testCase: m[1], reason: m[2] }; | ||
| }); | ||
|
|
||
| const summary = failures.length | ||
| ? `❌ ${failures.length} test(s) failed` | ||
| : '✅ All tests passed'; | ||
|
|
||
| const content: ToolResponseContent[] = [ | ||
| { type: 'text', text: summary } | ||
| ]; | ||
|
|
||
| // Add failures as formatted text if any exist | ||
| if (failures.length > 0) { | ||
| content.push({ | ||
| type: 'text', | ||
| text: `Test failures:\n${failures.map(f => `- ${f.testCase}: ${f.reason}`).join('\n')}` | ||
| }); | ||
| } | ||
|
|
||
| return { content }; | ||
| } | ||
|
|
||
| /** | ||
| * Register all iOS Simulator test tools with the MCP server | ||
| */ | ||
| export function registerIOSSimulatorTestTools(server: McpServer): void { | ||
| // Common default values | ||
| const defaults = { | ||
| configuration: 'Debug', | ||
| useLatestOS: true, | ||
| }; | ||
|
|
||
| // 1) workspace + name | ||
| registerTool( | ||
| server, | ||
| 'ios_simulator_test_by_name_workspace', | ||
| 'Run tests for an iOS app on a simulator specified by name using a workspace', | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @cameroncooke I think I should improve these if you have any tips. I see now that your other tools have much more detail here. |
||
| { | ||
| workspacePath: workspacePathSchema, | ||
| scheme: schemeSchema, | ||
| simulatorName: simulatorNameSchema, | ||
| configuration: configurationSchema, | ||
| derivedDataPath: derivedDataPathSchema, | ||
| extraArgs: extraArgsSchema, | ||
| useLatestOS: useLatestOSSchema, | ||
| }, | ||
| (params: any) => _handleIOSSimulatorTestLogic({ | ||
| ...params, | ||
| configuration: params.configuration || defaults.configuration, | ||
| useLatestOS: params.useLatestOS ?? defaults.useLatestOS | ||
| }) | ||
| ); | ||
|
|
||
| // 2) project + name | ||
| registerTool( | ||
| server, | ||
| 'ios_simulator_test_by_name_project', | ||
| 'Run tests for an iOS app on a simulator specified by name using a project file', | ||
| { | ||
| projectPath: projectPathSchema, | ||
| scheme: schemeSchema, | ||
| simulatorName: simulatorNameSchema, | ||
| configuration: configurationSchema, | ||
| derivedDataPath: derivedDataPathSchema, | ||
| extraArgs: extraArgsSchema, | ||
| useLatestOS: useLatestOSSchema, | ||
| }, | ||
| (params: any) => _handleIOSSimulatorTestLogic({ | ||
| ...params, | ||
| configuration: params.configuration || defaults.configuration, | ||
| useLatestOS: params.useLatestOS ?? defaults.useLatestOS | ||
| }) | ||
| ); | ||
|
|
||
| // 3) workspace + id | ||
| registerTool( | ||
| server, | ||
| 'ios_simulator_test_by_id_workspace', | ||
| 'Run tests for an iOS app on a simulator specified by ID using a workspace', | ||
| { | ||
| workspacePath: workspacePathSchema, | ||
| scheme: schemeSchema, | ||
| simulatorId: simulatorIdSchema, | ||
| configuration: configurationSchema, | ||
| derivedDataPath: derivedDataPathSchema, | ||
| extraArgs: extraArgsSchema, | ||
| useLatestOS: useLatestOSSchema, | ||
| }, | ||
| (params: any) => _handleIOSSimulatorTestLogic({ | ||
| ...params, | ||
| configuration: params.configuration || defaults.configuration, | ||
| useLatestOS: params.useLatestOS ?? defaults.useLatestOS | ||
| }) | ||
| ); | ||
|
|
||
| // 4) project + id | ||
| registerTool( | ||
| server, | ||
| 'ios_simulator_test_by_id_project', | ||
| 'Run tests for an iOS app on a simulator specified by ID using a project file', | ||
| { | ||
| projectPath: projectPathSchema, | ||
| scheme: schemeSchema, | ||
| simulatorId: simulatorIdSchema, | ||
| configuration: configurationSchema, | ||
| derivedDataPath: derivedDataPathSchema, | ||
| extraArgs: extraArgsSchema, | ||
| useLatestOS: useLatestOSSchema, | ||
| }, | ||
| (params: any) => _handleIOSSimulatorTestLogic({ | ||
| ...params, | ||
| configuration: params.configuration || defaults.configuration, | ||
| useLatestOS: params.useLatestOS ?? defaults.useLatestOS | ||
| }) | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -206,6 +206,7 @@ When done capturing logs, use: stop_and_get_simulator_log({ logSessionId: 'SESSI | |||||
| text: `✅ ${platformOptions.logPrefix} ${buildAction} succeeded for scheme ${params.scheme}.`, | ||||||
| }, | ||||||
| ], | ||||||
| rawOutput: result.output + (result.error ? '\n' + result.error : ''), | ||||||
|
||||||
| rawOutput: result.output + (result.error ? '\n' + result.error : ''), | |
| rawOutput: result.output.trimEnd() + (result.error ? '\n' + result.error : ''), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using a non-null assertion with the regex match may cause a runtime error if the log format ever changes; consider performing a safe check before destructuring the match result.