SSkilltecabyclaudinhocode
Enviar skill
← Voltar para o catálogo

app-tester

Desenvolvimento

Build and maintain a navigable graph of any iOS, macOS, or Android app's screens. Reads the project's navigation source to understand flows, instruments screen files with structured print logs and accessibility identifiers, then drives flows end-to-end using console logs and the accessibility tree — without requiring screenshots. Use when: (1) user says "test this", "test it", "test [feature/scree

1estrelas
Ver no GitHub ↗Autor: markgravity

App Tester

Tests iOS, macOS, and Android app navigation flows without relying on screenshots. Works on SwiftUI, UIKit, Flutter, and Expo / React Native projects.

Strategy:

  1. Read the project's navigation source to build .tester/app-graph.yaml and .tester/flows/*.yaml at the project root.
  2. Instrument screen files with structured print logs and accessibility identifiers.
  3. Drive flows by tapping elements and confirming transitions via console logs.
  4. Take screenshots only when a step fails.

Bundled scripts:

~/.claude/skills/app-tester/scripts/
  iOS:
    ios.py                — unified entry point: tap, swipe, screenshot, logs
    app_launcher.py       — launch/terminate via xcrun simctl
    screen_mapper.py      — read accessibility tree via idb
    navigator.py          — tap/interact via idb (used by ios.py)
    log_monitor.py        — stream simulator logs
    privacy_manager.py    — pre-grant permissions
    dismiss_prompts.py    — dismiss system dialogs
    common/               — shared idb/simctl utils

  macOS:
    macos_launcher.py     — launch/terminate macOS .app bundles
    macos_screen_mapper.py — read window/toolbar elements via System Events
    macos_navigator.py    — click toolbar/window elements via System Events
    macos_log_monitor.py  — stream app logs via `log stream`

  Android:
    android.py            — unified entry point: tap, swipe, screenshot, logs, hot-reload
    android_launcher.py   — launch/terminate/install via ADB; flutter run management
    android_screen_mapper.py — read UI tree via adb uiautomator dump
    android_log_monitor.py — stream adb logcat

Requirements:

  • iOS: axe for accessibility tree + taps, xcrun simctl for launch + logs. Install: brew tap cameroncooke/axe && brew install axe
  • macOS: osascript (built-in) for accessibility, log (built-in) for logs. No extra deps.
  • Android: adb (Android SDK platform-tools). Ensure $HOME/Library/Android/sdk/platform-tools is on PATH. Flutter apps: ~/fvm/versions/stable/bin/flutter (or flutter on PATH).

Sensitive Credentials (.env)

Some flows require authentication. Store credentials in .env at the project root (add to .gitignore):

TEST_USERNAME=your@email.com
TEST_PASSWORD=yourpassword
TEST_PERMISSIONS=camera,location,notifications   # iOS only
SYSTEM_PROMPT_DISMISS=Ask App Not to Track,Don't Allow,Allow Once,Not Now,Dismiss,OK,Allow

Load before testing: export $(grep -v '^#' .env | xargs)


Step 0: Project Setup

Identify these values before any phase:

iOS / macOS (SwiftUI/UIKit):

ValueHow to find it
PlatformSUPPORTED_PLATFORMS in build settings — macosx = macOS, iphonesimulator = iOS
Bundle IDPRODUCT_BUNDLE_IDENTIFIER in build settings or Info.plist
App nameCFBundleDisplayName / CFBundleName in Info.plist or the Xcode scheme name
Screen filesDirectory containing *View.swift or *ViewController.swift
Navigation sourceFile with Screen/Route enum or coordinator
Log prefix[AppName] — used in all instrumentation

Android / Flutter:

ValueHow to find it
PlatformPresence of android/ directory; Flutter = pubspec.yaml present
Package IDapplicationId in android/app/build.gradle or build.gradle.kts
App nameCFBundleDisplayName in iOS Info.plist or android:label in AndroidManifest.xml
Screen filesFlutter: lib/features/*/screens/ or lib/screens/*Screen.dart or *Page.dart
Navigation sourceFlutter: GoRouter config file, or files with GoRoute/Navigator.push calls
Device serialadb devices — use emulator serial (e.g. emulator-5554)
Log prefix[AppName] — used in print() calls throughout Flutter code

Expo / React Native (iOS + Android):

Identify by package.json containing expo or react-native dependency. Expo Router projects also have an app/ directory with file-based routes.

ValueHow to find it
Platformpackage.json has expo → Expo. react-native only → bare RN. Both iOS and Android are typically supported
Bundle ID (iOS)app.jsonexpo.ios.bundleIdentifier, or ios/<App>/Info.plist for prebuild projects
Package ID (Android)app.jsonexpo.android.package, or android/app/build.gradle applicationId
App nameapp.jsonexpo.name, or app.config.{js,ts}
Router typeapp/ directory exists → Expo Router (file-based). Otherwise look for @react-navigation/* config
Screen filesExpo Router: app/**/*.tsx (route files) and src/features/*/screens/*.tsx (feature screens). Bare RN: src/screens/, screens/
Navigation sourceExpo Router: the app/ tree IS the route graph (each .tsx file = a route). Bare RN: the NavigationContainer + Stack.Navigator config file
Log prefix[AppName] — used in console.log() calls (visible in Metro / npx expo start console and via xcrun simctl spawn booted log stream on iOS / adb logcat on Android)

Phase 1: App Discovery

Run when .tester/app-graph.yaml does not exist, the navigation source has changed, or the user says "rebuild the graph".

1.1 Read the navigation source

Find files defining all screens/routes:

  • NavigationStack / FlowStacks: A Screen or Route enum with all cases
  • Coordinators: navigate(to:) calls covering all destinations
  • UIKit: Router with push/present calls
  • Flutter/GoRouter: Files with GoRoute definitions or context.go()/context.push() calls
  • Flutter/Navigator: Navigator.push()/Navigator.pushNamed() call sites

1.2 Read every screen file

For each screen extract:

  • Outgoing navigation calls (push, present, NavigationLink, etc.) — these are edges
  • .onAppear / viewDidAppear — where appearance logs go
  • Primary action closures — where tap logs go

1.3 Determine feature groupings

Group screens by directory structure, naming conventions, or functional area.

1.4 Write the graph

Create .tester/app-graph.yaml at the project root (screens + metadata). For each named flow, create a separate .tester/flows/<flow-id>.yaml file using the kebab-case flow name (e.g. create-game.yaml, edit-profile.yaml). See Graph Schema below.


Phase 2: Instrumentation

2.1 Accessibility IDs — screen roots

Add .accessibilityIdentifier("snake_case_screen") to the outermost container of each screen's body.

var body: some View {
    VStack { ... }
        .accessibilityIdentifier("game_list_screen")
        .onAppear { viewModel.load() }
}

2.2 Accessibility IDs — action elements

Tag primary navigation triggers:

  • primary_action_button, secondary_action_button, cancel_button
  • Named per feature: create_game_button, invite_button, etc.

2.3 Screen appearance logs

.onAppear {
    print("[AppName] [Feature] ScreenName appeared")
    // existing code
}

2.4 Action tap logs

Button("Create Game") {
    print("[AppName] [Feature] createGame tapped")
    navigator.show(screen: .createGame(group))
}

2.5 Flutter instrumentation (Android)

Flutter apps use print() for log confirmation and Semantics widgets for UI identification.

Screen appearance logs — add to each screen's initState or build:

@override
void initState() {
  super.initState();
  print('[AppName] [Feature] ScreenName appeared');
}

Button tap logs — add before navigation calls:

GestureDetector(
  onTap: () {
    print('[AppName] [Feature] primaryButton tapped');
    context.go('/next-screen');
  },
  child: ...,
)

Semantic labels for UI identification (used by android_screen_mapper.py --find):

Semantics(
  label: 'sign_in_google_button',
  child: GestureDetector(onTap: ..., child: ...),
)

Como adicionar

/plugin marketplace add markgravity/app-tester-skill

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Comentários · Nenhum comentário

Entre para comentar. Entrar

  • Ainda não há comentários. Seja o primeiro.