Description
When running Maestro web tests, tapping a button that triggers page navigation (e.g., login form submission) causes a ClassCastException. The tap action completes successfully (form submits, loading spinner appears), but Maestro crashes before proceeding to the next command.
Error Message
[ERROR] maestro.orchestra.Orchestra.executeCommands: [Command execution] CommandFailed: class java.util.LinkedHashMap cannot be cast to class java.lang.String (java.util.LinkedHashMap and java.lang.String are in module java.base of loader 'bootstrap')
Steps to Reproduce
- Create a simple web login flow:
url: http://localhost:3000/login
---
- launchApp
- assertVisible: "Enter email"
- tapOn: "Enter email"
- inputText: "test@example.com"
- tapOn: "Enter Password"
- inputText: "password123"
- tapOn: "Log In"
- assertVisible: "Dashboard" # Never reached
-
Run: maestro test -p web login.yaml
-
The test fills the form and clicks "Log In", but crashes immediately after the tap triggers navigation.
Expected Behavior
Maestro should wait for the navigation to complete and then proceed to the next assertion.
Actual Behavior
- The tap executes successfully (login form submits, loading spinner visible in screenshots)
- Browser closes immediately
- Test fails with ClassCastException
Environment
- Maestro Version: 2.1.0
- Platform: macOS (Apple Silicon)
- Java Version: OpenJDK 17.0.18
- Browser: Chromium (launched by Maestro)
- App: React SPA with Firebase Auth, uses client-side routing
Analysis
After investigating the source code, the issue appears to be in YamlFluentCommand.kt in the toElementSelector() method. During/after navigation:
WebDriver.kt calls window.maestro.getContentDescription() to read DOM state
- During SPA navigation, this returns unexpected/malformed data
- Jackson deserializes it as
LinkedHashMap instead of typed objects
- The selector parsing code lacks defensive type checking and crashes when casting
The WebDriver.kt waits for document.readyState === "complete", but this doesn't account for:
- SPA client-side routing (React Router, etc.)
- Async auth flows (Firebase, Auth0, etc.)
- DOM changes during async operations
Workarounds Attempted (None Worked)
waitToSettleTimeoutMs: 0 on tapOn
retryTapIfNoChange: true
- Using
pressKey: Enter instead of tap
- Running with
--headless flag
- Adding
extendedWaitUntil after tap
Additional Context
- The same login flow works perfectly with agent-browser (Playwright-based)
- Mobile drivers (iOS/Android) handle navigation differently and may not have this issue
- This appears to be web-beta specific
Debug Output
Screenshots show the form filled correctly with loading spinner visible, confirming the tap worked but Maestro crashed during navigation handling.
Description
When running Maestro web tests, tapping a button that triggers page navigation (e.g., login form submission) causes a
ClassCastException. The tap action completes successfully (form submits, loading spinner appears), but Maestro crashes before proceeding to the next command.Error Message
Steps to Reproduce
Run:
maestro test -p web login.yamlThe test fills the form and clicks "Log In", but crashes immediately after the tap triggers navigation.
Expected Behavior
Maestro should wait for the navigation to complete and then proceed to the next assertion.
Actual Behavior
Environment
Analysis
After investigating the source code, the issue appears to be in
YamlFluentCommand.ktin thetoElementSelector()method. During/after navigation:WebDriver.ktcallswindow.maestro.getContentDescription()to read DOM stateLinkedHashMapinstead of typed objectsThe
WebDriver.ktwaits fordocument.readyState === "complete", but this doesn't account for:Workarounds Attempted (None Worked)
waitToSettleTimeoutMs: 0on tapOnretryTapIfNoChange: truepressKey: Enterinstead of tap--headlessflagextendedWaitUntilafter tapAdditional Context
Debug Output
Screenshots show the form filled correctly with loading spinner visible, confirming the tap worked but Maestro crashed during navigation handling.