Babysitter Mobile Testing Frameworks
Comprehensive mobile testing framework expertise
install
source · Clone the upstream repo
git clone https://github.com/a5c-ai/babysitter
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/a5c-ai/babysitter "$T" && mkdir -p ~/.claude/skills && cp -r "$T/library/specializations/mobile-development/skills/mobile-testing" ~/.claude/skills/a5c-ai-babysitter-mobile-testing-frameworks && rm -rf "$T"
manifest:
library/specializations/mobile-development/skills/mobile-testing/SKILL.mdsource content
Mobile Testing Frameworks Skill
Overview
This skill provides comprehensive expertise in mobile testing frameworks across platforms. It enables E2E testing with Detox and Maestro, native testing with XCUITest and Espresso, and cross-platform testing with Appium.
Allowed Tools
- Execute test commands and framework CLIsbash
- Analyze test files and configurationsread
- Generate test cases and configurationswrite
- Update existing testsedit
- Search for test filesglob
- Search for patterns in test codegrep
Capabilities
Detox (React Native)
-
Configuration
- Set up Detox configurations
- Configure iOS and Android builds
- Set up device/simulator targets
- Configure test runners (Jest)
-
Test Writing
- Write element matchers
- Implement actions (tap, type, scroll)
- Configure expectations
- Handle synchronization
-
Advanced Features
- Mock native modules
- Handle permissions
- Configure network mocking
- Implement visual regression
Maestro
-
Flow Configuration
- Write YAML test flows
- Configure app launch
- Set up device targets
- Handle environment variables
-
Actions and Assertions
- Tap, type, swipe gestures
- Assert element visibility
- Take screenshots
- Run JavaScript assertions
XCUITest (iOS)
-
Test Setup
- Configure test schemes
- Set up test plans
- Configure device targets
- Handle launch arguments
-
UI Testing
- Element queries with XCUIElement
- Actions (tap, swipe, pinch)
- Accessibility identifier usage
- Keyboard handling
Espresso (Android)
-
Test Configuration
- Set up instrumentation tests
- Configure test runner
- Handle Hilt injection
- Configure Compose testing
-
UI Testing
- ViewMatchers and ViewActions
- Compose semantics testing
- IdlingResources for async
- Intent verification
Appium
- Cross-Platform Testing
- Configure capabilities
- Set up driver sessions
- Handle multiple platforms
- Configure cloud testing
Device Farms
- Cloud Testing
- AWS Device Farm integration
- Firebase Test Lab setup
- BrowserStack configuration
- Test distribution
Target Processes
This skill integrates with the following processes:
- Testing strategy implementationmobile-testing-strategy.js
- Accessibility testingmobile-accessibility-implementation.js
- Security testingmobile-security-implementation.js
Dependencies
Required
- Node.js (for Detox, Maestro)
- Xcode (for XCUITest)
- Android Studio (for Espresso)
- Platform-specific SDKs
Optional
- Appium
- Device farm accounts
- CI/CD platform
Configuration
Detox Configuration
// .detoxrc.js module.exports = { testRunner: { args: { $0: 'jest', config: 'e2e/jest.config.js', }, jest: { setupTimeout: 120000, }, }, apps: { 'ios.debug': { type: 'ios.app', binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/MyApp.app', build: 'xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build', }, 'ios.release': { type: 'ios.app', binaryPath: 'ios/build/Build/Products/Release-iphonesimulator/MyApp.app', build: 'xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Release -sdk iphonesimulator -derivedDataPath ios/build', }, 'android.debug': { type: 'android.apk', binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk', build: 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug', reversePorts: [8081], }, 'android.release': { type: 'android.apk', binaryPath: 'android/app/build/outputs/apk/release/app-release.apk', build: 'cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release', }, }, devices: { simulator: { type: 'ios.simulator', device: { type: 'iPhone 15 Pro' }, }, emulator: { type: 'android.emulator', device: { avdName: 'Pixel_7_API_34' }, }, }, configurations: { 'ios.sim.debug': { device: 'simulator', app: 'ios.debug', }, 'ios.sim.release': { device: 'simulator', app: 'ios.release', }, 'android.emu.debug': { device: 'emulator', app: 'android.debug', }, 'android.emu.release': { device: 'emulator', app: 'android.release', }, }, };
Usage Examples
Detox Test
// e2e/login.test.ts import { device, element, by, expect } from 'detox'; describe('Login Flow', () => { beforeAll(async () => { await device.launchApp({ newInstance: true, permissions: { notifications: 'YES' }, }); }); beforeEach(async () => { await device.reloadReactNative(); }); it('should show login screen on first launch', async () => { await expect(element(by.id('login-screen'))).toBeVisible(); await expect(element(by.id('email-input'))).toBeVisible(); await expect(element(by.id('password-input'))).toBeVisible(); }); it('should show error for invalid credentials', async () => { await element(by.id('email-input')).typeText('invalid@example.com'); await element(by.id('password-input')).typeText('wrongpassword'); await element(by.id('login-button')).tap(); await expect(element(by.id('error-message'))).toBeVisible(); await expect(element(by.text('Invalid credentials'))).toBeVisible(); }); it('should navigate to home on successful login', async () => { await element(by.id('email-input')).typeText('test@example.com'); await element(by.id('password-input')).typeText('password123'); await element(by.id('login-button')).tap(); await waitFor(element(by.id('home-screen'))) .toBeVisible() .withTimeout(5000); await expect(element(by.id('welcome-message'))).toBeVisible(); }); it('should handle scroll in long list', async () => { // Login first await element(by.id('email-input')).typeText('test@example.com'); await element(by.id('password-input')).typeText('password123'); await element(by.id('login-button')).tap(); await waitFor(element(by.id('home-screen'))).toBeVisible().withTimeout(5000); // Scroll to bottom of list await element(by.id('item-list')).scrollTo('bottom'); await expect(element(by.id('item-50'))).toBeVisible(); // Scroll back to top await element(by.id('item-list')).scrollTo('top'); await expect(element(by.id('item-1'))).toBeVisible(); }); });
Maestro Flow
# flows/login.yaml appId: com.example.myapp --- - launchApp: clearState: true - assertVisible: "Welcome" - tapOn: "Email" - inputText: "test@example.com" - tapOn: "Password" - inputText: "password123" - tapOn: "Sign In" - assertVisible: "Home" - takeScreenshot: "home_after_login" # Test logout flow - tapOn: "Profile" - tapOn: "Sign Out" - assertVisible: "Welcome"
XCUITest
// MyAppUITests/LoginTests.swift import XCTest final class LoginTests: XCTestCase { var app: XCUIApplication! override func setUpWithError() throws { continueAfterFailure = false app = XCUIApplication() app.launchArguments = ["--uitesting"] app.launch() } override func tearDownWithError() throws { app = nil } func testLoginScreenElements() throws { XCTAssertTrue(app.textFields["email-input"].exists) XCTAssertTrue(app.secureTextFields["password-input"].exists) XCTAssertTrue(app.buttons["login-button"].exists) } func testSuccessfulLogin() throws { let emailField = app.textFields["email-input"] let passwordField = app.secureTextFields["password-input"] let loginButton = app.buttons["login-button"] emailField.tap() emailField.typeText("test@example.com") passwordField.tap() passwordField.typeText("password123") loginButton.tap() // Wait for home screen let homeScreen = app.otherElements["home-screen"] XCTAssertTrue(homeScreen.waitForExistence(timeout: 5)) } func testInvalidCredentials() throws { app.textFields["email-input"].tap() app.textFields["email-input"].typeText("invalid@example.com") app.secureTextFields["password-input"].tap() app.secureTextFields["password-input"].typeText("wrong") app.buttons["login-button"].tap() XCTAssertTrue(app.staticTexts["Invalid credentials"].waitForExistence(timeout: 3)) } func testScrollBehavior() throws { // Navigate to list screen app.buttons["list-tab"].tap() let list = app.tables["item-list"] let lastItem = app.cells["item-cell-50"] // Scroll down while !lastItem.isHittable { list.swipeUp() } XCTAssertTrue(lastItem.exists) // Scroll back up let firstItem = app.cells["item-cell-1"] while !firstItem.isHittable { list.swipeDown() } XCTAssertTrue(firstItem.exists) } }
Espresso Test
// app/src/androidTest/java/com/example/myapp/LoginTest.kt package com.example.myapp import androidx.compose.ui.test.* import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @HiltAndroidTest @RunWith(AndroidJUnit4::class) class LoginTest { @get:Rule(order = 0) val hiltRule = HiltAndroidRule(this) @get:Rule(order = 1) val composeRule = createAndroidComposeRule<MainActivity>() @Before fun setup() { hiltRule.inject() } @Test fun loginScreen_displaysAllElements() { composeRule.onNodeWithTag("email-input").assertIsDisplayed() composeRule.onNodeWithTag("password-input").assertIsDisplayed() composeRule.onNodeWithTag("login-button").assertIsDisplayed() } @Test fun login_withValidCredentials_navigatesToHome() { composeRule.onNodeWithTag("email-input") .performTextInput("test@example.com") composeRule.onNodeWithTag("password-input") .performTextInput("password123") composeRule.onNodeWithTag("login-button") .performClick() composeRule.waitUntil(5000) { composeRule.onAllNodesWithTag("home-screen") .fetchSemanticsNodes().isNotEmpty() } composeRule.onNodeWithTag("home-screen").assertIsDisplayed() } @Test fun login_withInvalidCredentials_showsError() { composeRule.onNodeWithTag("email-input") .performTextInput("invalid@example.com") composeRule.onNodeWithTag("password-input") .performTextInput("wrong") composeRule.onNodeWithTag("login-button") .performClick() composeRule.onNodeWithText("Invalid credentials") .assertIsDisplayed() } @Test fun list_scrollsBehavior() { // Login first composeRule.onNodeWithTag("email-input") .performTextInput("test@example.com") composeRule.onNodeWithTag("password-input") .performTextInput("password123") composeRule.onNodeWithTag("login-button") .performClick() composeRule.waitUntil(5000) { composeRule.onAllNodesWithTag("item-list") .fetchSemanticsNodes().isNotEmpty() } // Scroll to item 50 composeRule.onNodeWithTag("item-list") .performScrollToNode(hasTestTag("item-50")) composeRule.onNodeWithTag("item-50").assertIsDisplayed() } }
Firebase Test Lab
# .github/workflows/android-test.yml name: Android Tests on: [push, pull_request] jobs: instrumented-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up JDK uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' - name: Build APKs run: | ./gradlew assembleDebug assembleDebugAndroidTest - name: Authenticate to Google Cloud uses: google-github-actions/auth@v2 with: credentials_json: ${{ secrets.GCP_CREDENTIALS }} - name: Run tests on Firebase Test Lab run: | gcloud firebase test android run \ --type instrumentation \ --app app/build/outputs/apk/debug/app-debug.apk \ --test app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk \ --device model=Pixel6,version=33 \ --device model=Pixel7,version=34 \ --timeout 15m \ --results-bucket gs://my-test-results
Quality Gates
Test Coverage
- Unit test coverage > 80%
- Integration test coverage > 60%
- E2E critical path coverage 100%
Test Reliability
- Flaky test rate < 2%
- Test execution time < 15 minutes
- Retry logic for network tests
Accessibility Testing
- VoiceOver/TalkBack verification
- Color contrast validation
- Touch target size verification
Related Skills
- Accessibility-focused testingaccessibility-testing
- Performance testingmobile-perf
- CI/CD integrationfastlane-cicd
Version History
- 1.0.0 - Initial release with major framework support