Writing Maestro Tests - Developer Guide
A practical guide for developers to create effective Maestro tests for FastMediaSorter v2.
Test Structure
Every Maestro test follows this pattern:
appId: com.sza.fastmediasorter.debug
---
# Test: [Test Name]
# Description: [What this test validates]
# Duration: [Expected time]
# Setup phase
- launchApp
- waitForAnimationToEnd
# Action phase
- tapOn:
text: "Button"
# Assertion phase
- assertVisible:
text: "Expected Result"
Core Principles
1. One Test, One Feature
Each test should validate ONE specific feature or user flow.
Good:
# Test: Browse to folder
- launchApp
- tapOn: { text: "Pictures" }
- assertVisible: { text: "Pictures" }
Bad:
# Test: Everything
- launchApp
- tapOn: { text: "Pictures" }
- tapOn: { text: "Settings" }
- tapOn: { text: "Favorites" }
# Too many unrelated actions
2. Make Tests Resilient
Use optional: true for elements that may not always appear:
# Permission dialogs may not appear if already granted
- tapOn:
text: "Allow"
optional: true
# Network-dependent content
- assertVisible:
text: "Connected"
optional: true
timeout: 10000
3. Wait for UI to Stabilize
Always wait after navigation or state changes:
- tapOn:
text: "Settings"
- waitForAnimationToEnd # Critical!
- assertVisible:
text: "Preferences"
4. Use Descriptive Names
# Good: Clear intent
# Test: User can favorite a file
# Bad: Vague
# Test: Test 1
Common Test Patterns
Pattern 1: Navigation Test
appId: com.sza.fastmediasorter.debug
---
# Test: Navigate to Settings
# Validates: Settings screen is accessible
- launchApp
- waitForAnimationToEnd
- tapOn:
text: "Settings"
- assertVisible:
text: "Settings"
- assertVisible:
text: "Dark Mode"
optional: true
Pattern 2: Action + Verification
appId: com.sza.fastmediasorter.debug
---
# Test: Toggle dark mode
- launchApp
- tapOn:
text: "Settings"
- waitForAnimationToEnd
# Action
- tapOn:
text: "Dark Mode"
# Verification (wait for UI change)
- waitForAnimationToEnd
# Optional: Verify theme changed
- assertVisible:
id: ".*dark.*"
optional: true
Pattern 3: Form Input
appId: com.sza.fastmediasorter.debug
---
# Test: Add SMB connection
- launchApp
- tapOn:
id: ".*add.*"
- tapOn:
text: "SMB"
# Input fields
- tapOn:
id: ".*host.*"
- inputText: "192.168.1.100"
- tapOn:
id: ".*username.*"
- inputText: "user"
- tapOn:
id: ".*password.*"
- inputText: "pass"
# Submit
- tapOn:
text: "Connect"
# Verify
- waitForAnimationToEnd
- assertVisible:
text: "192.168.1.100"
timeout: 10000
Pattern 4: List Scrolling
appId: com.sza.fastmediasorter.debug
---
# Test: Scroll through file list
- launchApp
- tapOn:
text: "Browse"
- waitForAnimationToEnd
# Scroll to find item
- scrollUntilVisible:
element:
text: "Downloads"
timeout: 10000
- tapOn:
text: "Downloads"
- assertVisible:
text: "Downloads"
Pattern 5: Long Press Action
appId: com.sza.fastmediasorter.debug
---
# Test: Long press context menu
- launchApp
- waitForAnimationToEnd
# Long press file
- longPressOn:
text: "photo.jpg"
duration: 1000
optional: true
# Verify context menu
- assertVisible:
text: "Copy"
optional: true
- assertVisible:
text: "Delete"
optional: true
Element Selectors
By Text (Visible Text)
- tapOn:
text: "Settings"
By Resource ID
- tapOn:
id: "com.sza.fastmediasorter:id/button_add"
By Coordinates
- tapOn:
point: "50%, 80%" # x%, y%
By Index (Position in List)
- tapOn:
index: 0 # First element
Regex Patterns
# Match any image file
- tapOn:
text: ".*\\.(jpg|png|gif)$"
# Match any text containing "Dark"
- tapOn:
text: ".*[Dd]ark.*"
Relative Selectors
# Tap button below title
- tapOn:
text: "OK"
below:
text: "Confirmation"
# Tap button to the right
- tapOn:
text: "Next"
rightOf:
text: "Previous"
Assertions
Assert Visible
- assertVisible:
text: "Success"
timeout: 5000
Assert Not Visible
- assertNotVisible:
text: "Loading..."
Optional Assertion
# Don't fail if element not found
- assertVisible:
text: "Optional Element"
optional: true
Waits and Timing
Basic Wait
- waitForAnimationToEnd
Extended Wait
- extendedWaitUntil:
visible:
text: "Loaded"
timeout: 15000 # 15 seconds
Wait Until Not Visible
- extendedWaitUntil:
notVisible:
text: "Loading..."
timeout: 30000
Testing Best Practices
1. Test Happy Path First
Start with the ideal user flow where everything works perfectly.
2. Then Add Edge Cases
- Empty states
- Error conditions
- Network failures
- Permission denials
3. Keep Tests Independent
Each test should:
- Start from a clean state
- Not depend on other tests
- Clean up after itself (if needed)
4. Use Test Data
# Use test files that exist on device
- scrollUntilVisible:
element:
text: "test_photo.jpg"
5. Handle Multiple Scenarios
# Handle both new user and returning user
- tapOn:
text: "Skip Tutorial"
optional: true
- tapOn:
text: "Next"
optional: true
Common Mistakes to Avoid
❌ Don’t: Forget to wait
- tapOn: { text: "Settings" }
- assertVisible: { text: "Preferences" }
# May fail if animation is slow
✅ Do: Wait for UI
- tapOn: { text: "Settings" }
- waitForAnimationToEnd
- assertVisible: { text: "Preferences" }
❌ Don’t: Use absolute coordinates
- tapOn:
point: "200, 400" # Breaks on different screens
✅ Do: Use relative coordinates
- tapOn:
point: "50%, 50%" # Works on all screens
❌ Don’t: Hard-code delays
- tapOn: { text: "Load" }
- sleep: 5000 # Bad: Fixed delay
✅ Do: Wait for conditions
- tapOn: { text: "Load" }
- extendedWaitUntil:
visible: { text: "Loaded" }
timeout: 10000 # Wait up to 10s
❌ Don’t: Make tests brittle
# Exact text match may break with updates
- tapOn:
text: "Click here to continue to the next screen"
✅ Do: Use flexible selectors
# Partial match
- tapOn:
text: ".*continue.*"
Debugging Your Tests
1. Run with Debug Flag
maestro test --debug maestro/smoke/my_test.yaml
2. Use Maestro Studio
maestro studio
# Test selectors interactively
3. Add Debug Assertions
# Verify element exists before tapping
- assertVisible:
text: "Button"
- tapOn:
text: "Button"
4. Take Screenshots
# In test
- tapOn: { text: "Settings" }
- runScript:
file: scripts/take_screenshot.js
5. Check Logs
# View test execution log
cat ~/.maestro/tests/<test_name>/maestro.log
Test Organization
Smoke Tests (maestro/smoke/)
- Critical user flows
- Must pass for every build
- Fast execution (< 1 min each)
Example:
app_launch.yamlbrowse_files.yamlplay_media.yaml
Critical Path Tests (maestro/critical/)
- Important features
- Should pass before release
- Medium execution time (1-2 min each)
Example:
file_operations.yamlsettings_persistence.yaml
Feature Tests (maestro/features/)
- Specific feature validation
- Run as needed
- Variable execution time
Example:
smb_connection.yamlimage_editing.yamlcloud_sync.yaml
Example: Complete Test
Here’s a complete test with all best practices:
appId: com.sza.fastmediasorter.debug
---
# Test: Add file to favorites
# Description: Validates that users can mark files as favorites
# Duration: ~30 seconds
# Prerequisites: At least one file in Browse view
# Setup: Launch and navigate
- launchApp
- waitForAnimationToEnd
# Handle any permission dialogs
- tapOn:
text: "Allow"
optional: true
# Navigate to Browse tab
- tapOn:
text: "Browse"
- waitForAnimationToEnd
# Action: Find and open a file
- scrollUntilVisible:
element:
text: ".*\\.(jpg|png|mp4)$"
timeout: 10000
optional: true
- tapOn:
text: ".*\\.(jpg|png|mp4)$"
optional: true
- waitForAnimationToEnd
# Action: Add to favorites
- tapOn:
id: ".*favorite.*"
optional: true
# Or via menu
- tapOn:
id: ".*menu.*"
optional: true
- tapOn:
text: "Add to Favorites"
optional: true
# Wait for action to complete
- waitForAnimationToEnd
# Verification: Check favorites tab
- backPress
- waitForAnimationToEnd
- tapOn:
text: "Favorites"
- waitForAnimationToEnd
# Verify file appears in favorites
- assertVisible:
text: ".*\\.(jpg|png|mp4)$"
optional: true
timeout: 5000
# Cleanup not needed - favorites can remain
Next Steps
- Read existing tests in
maestro/smoke/andmaestro/critical/ - Try modifying an existing test
- Create a new test for a feature you’re working on
- Run your test locally before committing
- Add test to appropriate suite (smoke/critical/features)
Resources
Happy Testing! 🎯