Skip to main content

Changelog

All notable changes to Stowaway are documented here. The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

0.2.2

Fixed

  • Trace no longer falsely marks the last successful step as failed when a test times out. When an app.step(...) call's body hangs, the test runner used to mark the last entry in the trace as failed - which was the last successful sub-operation (find, tap, etc.), not the hung step. The reserved step() header entry is now marked failed by default and only flipped to "succeeded" when the body actually completes, so a hung step shows up correctly. The auto-attribution heuristic that mis-blamed successful operations has been removed.

  • scrollAndFind no longer silently scrolls the wrong scrollable. On screens with a horizontal scroller (e.g. a pill nav) above the target vertical list, the global scroll heuristic walked the fiber tree depth-first and stopped at the first match - which on these screens was the horizontal ScrollView. Calling scrollTo({y}) on a horizontal scroller is a no-op, so the target list never moved and the call timed out with a generic error. The bridge now scans for scrollables in this order:

    1. Vertical FlatList / VirtualizedList
    2. Vertical ScrollView
    3. Horizontal FlatList / VirtualizedList (fallback)
    4. Horizontal ScrollView (fallback - scrolled along the x axis)

    This means the common multi-scrollable case (horizontal nav + vertical list) now finds the vertical list correctly, while horizontal-only screens still work since the horizontal fallback preserves the pre-0.2.2 behaviour for those.

  • scrollAndFind timeout errors now distinguish the failure mode. Instead of a generic timed out after Nms, the error message identifies which kind of scrollable was scrolled (a vertical FlatList, a horizontal ScrollView, etc.) or reports no scrollable component found in the tree.

Added

  • New scrollAndFind(testID, { within }) option. Accepts a testID or Selector to pin the scroll container when the heuristics can't decide (e.g. two vertical FlatLists on one screen).

  • SLOW=1 env var enables slow mode for the whole run - a configurable delay (default 800 ms, override with SLOW_DELAY) between every Element interaction. Intended for demo recordings where you want a steady cadence between taps, typing, etc. Doesn't affect waitForElement (still resolves as soon as the element appears).

0.2.1

Fixed

  • Visibility filter no longer rejects screens in state 1 or with Animated.Value activityState. The 0.2.0 check rejected fibers whose ancestor chain had activityState < 2, which produced false negatives for elements on screens below the top of a stack (state 1 = transitioning/below-top) and during navigation transitions. The check now only rejects when activityState is the plain number 0 (fully deactivated). Animated values are skipped - they can't be read reliably from JS - and state 1 is allowed through. This fixes cases where elements like tab bar buttons or items in a horizontal scroll on a stack screen were unfindable.

0.2.0

Breaking changes

find() and findAll() now filter by visibility. Queries skip fibers whose ancestor chain contains either activityState < 2 (an inactive react-native-screens screen, e.g. a prerendered tab or background stack screen) or style.display === 'none'. This prevents two common false-positive patterns: matching elements on a prerendered background screen, and returning multiple matches when only one is actually on screen.

If you have tests that intentionally query prerendered or hidden subtrees, you'll need to either navigate to that screen first or restructure the test.

New

  • app.waitForInteractions(opts?) - Sleeps for a configurable delay (default 500 ms) on the test runner side. Use after waitForElement to give React Navigation's native-driven slide transitions time to finish before interacting. Override the delay with { delay: 800 } for slower animations. Unnecessary if you've called disableAnimations().

  • app.printVisibleTree(maxDepth?) - A less noisy version of printTree that prunes inactive screen subtrees and display: none nodes from the output. Useful when printTree is overwhelming because of prerendered tabs or stack screens kept mounted in the background.

Improved

  • waitForElement is now commit-driven. It hooks __REACT_DEVTOOLS_GLOBAL_HOOK__.onCommitFiberRoot via Runtime.addBinding and resolves on the first React commit containing the element - typically within a few milliseconds rather than at the next 250 ms poll boundary. A polling fallback runs in parallel as a safety net, and gracefully degrades to polling-only if Runtime.addBinding isn't available.

  • Deep fiber trees no longer crash the bridge. The internal walk is now iterative, so apps with 200+ levels of nesting (common in modern Expo apps with many providers) no longer hit a call stack overflow.

  • typeText works with more component libraries. When onChangeText isn't found on the ancestor chain, it now falls back to onChange({ nativeEvent: { text } }) (the standard React Native TextInput contract) and then onChange(text) (used by some component libraries that pass the raw string). Strictly additive - apps that use plain onChangeText see no change in behavior.

  • Correct CDP target selection on expo-dev-client. When Metro exposes two pages for the same bundle ID (the dev client shell and the actual app bundle), Stowaway now connects to the actual app instead of the shell.

0.1.0

Initial release.