// parse detox configuration from various locations // maps detox device config to sootsim settings import * as fs from 'fs' import * as path from 'path' export interface DetoxConfig { testRunner?: { args?: Record jest?: { setupTimeout?: number; teardownTimeout?: number } } apps?: Record devices?: Record configurations?: Record testMatch?: string[] testDir?: string } export interface SootSimDetoxConfig { testFiles: string[] testTimeout: number sootsimUrl: string deviceModel: string maxWorkers: number } // search order: .detoxrc.js, .detoxrc.json, detox.config.js, detox.config.json, package.json#detox export function loadDetoxConfig(projectDir: string): DetoxConfig | null { const candidates = [ '.detoxrc.js', '.detoxrc.json', 'detox.config.js', 'detox.config.json', ] for (const file of candidates) { const filePath = path.join(projectDir, file) if (fs.existsSync(filePath)) { try { if (file.endsWith('.json')) { return JSON.parse(fs.readFileSync(filePath, 'utf8')) } // for JS files, we can't easily require them without running them // return a basic config indicating file was found return { testDir: 'e2e' } } catch { continue } } } // check package.json const pkgPath = path.join(projectDir, 'package.json') if (fs.existsSync(pkgPath)) { try { const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')) if (pkg.detox) return pkg.detox } catch {} } return null } // find test files matching detox patterns export function findDetoxTestFiles( projectDir: string, config: DetoxConfig | null, ): string[] { const testDirs = ['e2e', 'test/e2e', 'detox', '__tests__/e2e'] const patterns = [ '.test.ts', '.test.js', '.test.tsx', '.test.jsx', '.spec.ts', '.spec.js', ] // if config specifies a test dir, prioritize it if (config?.testDir) { testDirs.unshift(config.testDir) } const files: string[] = [] for (const dir of testDirs) { const fullDir = path.join(projectDir, dir) if (!fs.existsSync(fullDir)) continue const entries = fs.readdirSync(fullDir, { recursive: true }) for (const entry of entries) { const entryStr = String(entry) if (patterns.some((p) => entryStr.endsWith(p))) { files.push(path.join(fullDir, entryStr)) } } } return files } // map detox device type to sootsim device model export function mapDetoxDevice(config: DetoxConfig | null): string { if (!config?.devices) return 'iphone-15-pro' const devices = Object.values(config.devices) for (const dev of devices) { const type = dev.device?.type?.toLowerCase() || '' if (type.includes('se')) return 'iphone-se' if (type.includes('16 pro max') || type.includes('16-pro-max')) return 'iphone-16-pro-max' if (type.includes('16 pro') || type.includes('16-pro')) return 'iphone-16-pro' if (type.includes('16')) return 'iphone-16' if (type.includes('15 pro max') || type.includes('15-pro-max')) return 'iphone-15-pro-max' if (type.includes('15 pro') || type.includes('15-pro')) return 'iphone-15-pro' if (type.includes('15')) return 'iphone-15' } return 'iphone-15-pro' } // generate a sootsim-compatible config from detox config export function toRNXConfig( projectDir: string, config: DetoxConfig | null, ): SootSimDetoxConfig { return { testFiles: findDetoxTestFiles(projectDir, config), testTimeout: config?.testRunner?.jest?.setupTimeout || 120000, sootsimUrl: process.env.RNX_URL || 'http://localhost:5173', deviceModel: mapDetoxDevice(config), maxWorkers: 1, } }