The architecture is built so that new test frameworks (XCUITest for native iOS, Espresso for native Android, Detox for React Native, Playwright for web, …) drop in with zero changes to use cases, presentation, or the MCP tool surface.
Take XCUITest as the worked example.
packages/phone-controll/src/mcp_phone_controll/domain/entities.py
class TestFramework(str, Enum):
PATROL = "patrol"
FLUTTER = "flutter"
XCUITEST = "xcuitest" # <-- new
...packages/phone-controll/src/mcp_phone_controll/data/repositories/xcuitest_repository.py
class XcuiTestRepository(TestRepository):
def __init__(self, xcodebuild: XcodebuildCli) -> None:
self._xcodebuild = xcodebuild
async def run_unit_tests(self, project_path):
# `xcodebuild test -project ... -scheme ... -destination 'platform=iOS Simulator,...'`
...
async def run_integration_tests(self, project_path, device_serial, test_path="..."):
...packages/phone-controll/src/mcp_phone_controll/data/repositories/native_ios_project_inspector.py
Detects *.xcodeproj / *.xcworkspace and a Podfile, returns ProjectInfo(type=ProjectType.NATIVE_IOS, test_frameworks=(TestFramework.XCUITEST,)).
packages/phone-controll/src/mcp_phone_controll/container.py
inspector = CompositeProjectInspector([
FlutterProjectInspector(),
NativeIosProjectInspector(), # <-- new
])
xcuitest_repo = XcuiTestRepository(xcodebuild)
test_repo = CompositeTestRepository(
android=flutter_tests,
ios=flutter_tests,
resolver=resolver,
framework_runners={
TestFramework.PATROL: patrol_tests,
TestFramework.XCUITEST: xcuitest_repo, # <-- new
},
inspector=inspector,
)packages/phone-controll/src/mcp_phone_controll/data/repositories/static_capabilities_provider.py — add xcuitest to valid_driver_kinds if you want to invoke it directly from a YAML plan.
packages/phone-controll/src/mcp_phone_controll/data/repositories/yaml_plan_executor.py — add an elif kind == "xcuitest": branch in _driver_phase.
- Existing
run_integration_teststool now auto-routes to your new framework wheninspect_projectreports it. - Existing YAML plans transparently use the new framework — no plan rewrites.
- The HTTP adapter exposes everything; agents just call
run_integration_testsand the right thing happens. - Failure types,
next_actionhints, session tracing, JUnit output — all inherited.
- Pure-function test for the new project inspector (fixture project structure → expected
ProjectInfo). - Use-case test for the new
TestRepositoryagainst a fake CLI runner. - Composite-routing test:
inspect_projectreturns the new framework →run_integration_testsdispatches to the new repo.
That's typically ~half a day of work for a well-scoped framework.