Skip to main content
Glama
build.integration.test.ts6.17 kB
/** * iOS Xcode Build Integration Tests * Tests against real KMM project (specter-test-subject) * * Prerequisites: * - macOS with Xcode installed * - iOS Simulator available * - specter-test-subject project exists */ import { describe, it, expect, beforeAll } from 'vitest'; import { executeShell } from '../../../src/utils/shell.js'; import { buildXcode, XcodeBuildOptions } from '../../../src/platforms/ios/xcodebuild.js'; import { parseXcodeLog } from '../../../src/tools/build/log-parser.js'; import * as path from 'path'; import * as os from 'os'; const TEST_PROJECT_PATH = path.resolve(__dirname, '../../../test-apps/specter-test-subject/iosApp'); async function isXcodeAvailable(): Promise<boolean> { try { const result = await executeShell('xcodebuild', ['-version']); return result.exitCode === 0; } catch { return false; } } async function projectExists(): Promise<boolean> { try { const result = await executeShell('ls', [path.join(TEST_PROJECT_PATH, 'SpecterCounter.xcodeproj')]); return result.exitCode === 0; } catch { return false; } } async function getAvailableSimulator(): Promise<string | null> { try { const result = await executeShell('xcrun', ['simctl', 'list', 'devices', 'available', '--json']); const data = JSON.parse(result.stdout); // Find an iPhone simulator for (const [runtime, devices] of Object.entries(data.devices) as [string, Array<{ name: string; state: string }>][]) { if (runtime.includes('iOS')) { const iphone = devices.find(d => d.name.includes('iPhone') && d.state === 'Booted'); if (iphone) { return iphone.name; } // Try first available iPhone const anyIphone = devices.find(d => d.name.includes('iPhone')); if (anyIphone) { return anyIphone.name; } } } return null; } catch { return null; } } describe('iOS Xcode Build Integration', () => { let xcodeAvailable = false; let projectReady = false; let simulatorName: string | null = null; beforeAll(async () => { // Only run on macOS if (os.platform() !== 'darwin') { console.log('Skipping iOS tests on non-macOS platform'); return; } xcodeAvailable = await isXcodeAvailable(); projectReady = await projectExists(); simulatorName = await getAvailableSimulator(); console.log(`Xcode available: ${xcodeAvailable}`); console.log(`Project ready: ${projectReady}`); console.log(`Project path: ${TEST_PROJECT_PATH}`); console.log(`Simulator: ${simulatorName}`); }); describe('buildXcode', () => { it('should build iOS app successfully', async () => { expect(os.platform(), 'This test requires macOS').toBe('darwin'); expect(xcodeAvailable, 'Xcode not available').toBe(true); expect(projectReady, `Project not found at ${TEST_PROJECT_PATH}`).toBe(true); expect(simulatorName, 'No iOS simulator available').toBeTruthy(); const options: XcodeBuildOptions = { projectPath: TEST_PROJECT_PATH, scheme: 'SpecterCounter', configuration: 'Debug', destination: `platform=iOS Simulator,name=${simulatorName}`, clean: false, timeoutMs: 600000, // 10 minutes }; const result = await buildXcode(options); console.log(`Build success: ${result.success}`); console.log(`Duration: ${result.durationMs}ms`); if (result.success) { expect(result.artifactPath).toBeDefined(); console.log(`Artifact: ${result.artifactPath}`); } else { const errors = result.errorSummary?.topErrors ?? []; console.log(`Build failed with ${errors.length} errors`); for (const error of errors.slice(0, 5)) { console.log(` - ${error.file}:${error.line}: ${error.message}`); } } expect(result).toHaveProperty('success'); expect(result).toHaveProperty('durationMs'); }, 600000); it('should clean and build', async () => { expect(os.platform(), 'This test requires macOS').toBe('darwin'); expect(xcodeAvailable, 'Xcode not available').toBe(true); expect(projectReady, `Project not found at ${TEST_PROJECT_PATH}`).toBe(true); expect(simulatorName, 'No iOS simulator available').toBeTruthy(); const options: XcodeBuildOptions = { projectPath: TEST_PROJECT_PATH, scheme: 'SpecterCounter', configuration: 'Debug', destination: `platform=iOS Simulator,name=${simulatorName}`, clean: true, timeoutMs: 900000, // 15 minutes for clean build }; const result = await buildXcode(options); console.log(`Clean build success: ${result.success}`); console.log(`Duration: ${result.durationMs}ms`); expect(result).toHaveProperty('success'); }, 900000); }); describe('parseXcodeLog', () => { it('should parse successful iOS build logs', () => { const logs = ` Build settings from command line: CONFIGURATION = Debug ** BUILD SUCCEEDED ** `; const result = parseXcodeLog(logs); // ParsedLog has errors array, not success boolean expect(result.errors).toHaveLength(0); expect(result.warnings).toBeDefined(); }); it('should parse iOS build with errors', () => { const logs = ` /Users/test/project/iosApp/ContentView.swift:25:12: error: cannot find 'unknownFunction' in scope /Users/test/project/iosApp/ContentView.swift:30:5: error: type 'String' has no member 'nonExistent' ** BUILD FAILED ** `; const result = parseXcodeLog(logs); // Has errors = failed build expect(result.errors.length).toBeGreaterThan(0); expect(result.errors[0].file).toContain('ContentView.swift'); expect(result.errors[0].line).toBe(25); }); it('should detect Xcode build failure patterns', () => { const logs = ` xcodebuild: error: Could not find scheme 'NonExistent'. `; const result = parseXcodeLog(logs); // xcodebuild errors should be captured // Note: This might not be captured by current parser (linker pattern only) expect(result).toBeDefined(); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/abd3lraouf/specter-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server