Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
982 changes: 982 additions & 0 deletions .agents/skills/maestro-mobile-testing/SKILL.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,4 @@ android/generated
# React Native Nitro Modules
nitrogen/
repomix-output.xml
.agent-device/test-artifacts/
140 changes: 140 additions & 0 deletions .maestro/timezone-refresh.android.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Verifies that the native Hermes timezone cache is refreshed after Android's
# system timezone changes. The flow returns the emulator to Europe/Warsaw.
#
# Prerequisites:
# - Android emulator running with timezonehermesfix.example installed.
# - The emulator starts in Europe/Warsaw (the flow verifies this).

appId: com.android.settings
name: Refresh Hermes timezone after Android timezone change
tags:
- android
- manual-device-state
- regression
---
# Confirm the initial Hermes-aware card reflects the expected fixed date in
# Europe/Warsaw before changing the device timezone.
- launchApp: timezonehermesfix.example
- extendedWaitUntil:
visible: 'Europe/Warsaw'
timeout: 10000
- assertVisible:
text: '.*TimezoneOffset -120'
below: 'Europe/Warsaw'

# Open Settings > System > Date & time. Clearing Settings' app state ensures
# its activity stack starts at the Settings homepage rather than a prior picker.
- launchApp:
appId: com.android.settings
clearState: true
- scrollUntilVisible:
element: 'System'
direction: DOWN
timeout: 10000
- tapOn: 'System'
- extendedWaitUntil:
visible: 'Date & time'
timeout: 5000
- tapOn: 'Date & time'
- extendedWaitUntil:
visible: 'Automatic time zone'
timeout: 5000

# A network-provided timezone prevents a manual choice. Disable it only when
# it is enabled, so this flow works with either starting switch state.
- runFlow:
when:
visible:
containsDescendants:
- text: 'Automatic time zone'
- id: 'com.android.settings:id/switchWidget'
checked: true
commands:
- tapOn: 'Automatic time zone'

- tapOn:
text: 'Time zone'
below: 'Automatic time zone'
- extendedWaitUntil:
visible: 'Warsaw (GMT+02:00)'
timeout: 5000

# Change Europe/Warsaw to America/Los_Angeles through the device Settings UI.
- tapOn: 'Region'
- extendedWaitUntil:
visible:
id: 'android:id/search_src_text'
timeout: 5000
- inputText: 'United States'
- extendedWaitUntil:
visible:
id: 'com.android.settings:id/title'
text: 'United States'
timeout: 5000
- hideKeyboard
- tapOn:
id: 'com.android.settings:id/title'
text: 'United States'
- extendedWaitUntil:
visible:
id: 'com.android.settings:id/title'
text: 'Los Angeles'
timeout: 5000
- tapOn:
id: 'com.android.settings:id/title'
text: 'Los Angeles'

# Foreground the backgrounded app without restarting it. It must observe the
# changed device timezone rather than return Hermes' prior Europe/Warsaw value.
- launchApp:
appId: timezonehermesfix.example
stopApp: false
- extendedWaitUntil:
visible: 'America/Los_Angeles'
timeout: 10000
- assertVisible:
text: '.*TimezoneOffset 420'
below: 'America/Los_Angeles'
- assertNotVisible: 'Europe/Warsaw'

# Restore the shared emulator state for subsequent tests.
- launchApp:
appId: com.android.settings
clearState: true
- scrollUntilVisible:
element: 'System'
direction: DOWN
timeout: 10000
- tapOn: 'System'
- tapOn: 'Date & time'
- tapOn:
text: 'Time zone'
below: 'Automatic time zone'
- tapOn: 'Region'
- extendedWaitUntil:
visible:
id: 'android:id/search_src_text'
timeout: 5000
- inputText: 'Poland'
- extendedWaitUntil:
visible:
id: 'com.android.settings:id/title'
text: 'Poland'
timeout: 5000
- hideKeyboard
- tapOn:
id: 'com.android.settings:id/title'
text: 'Poland'
- extendedWaitUntil:
visible: 'Warsaw (GMT+02:00)'
timeout: 5000
# Foreground the same process after restoring the device timezone.
- launchApp:
appId: timezonehermesfix.example
stopApp: false
- extendedWaitUntil:
visible: 'Europe/Warsaw'
timeout: 10000
- assertVisible:
text: '.*TimezoneOffset -120'
below: 'Europe/Warsaw'
77 changes: 77 additions & 0 deletions .maestro/timezone-refresh.ios.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Verifies that the native Hermes timezone cache is refreshed after an iPhone
# timezone change. This flow is executed by agent-device's Maestro-compatible
# runner and restores the phone to Europe/Warsaw when it completes.
#
# Prerequisites:
# - A connected iPhone with timezonehermesfix.example installed.
# - The example app starts in Europe/Warsaw.
# - Settings > General > Date & Time is the current Settings screen. The flow
# returns to that screen for its next invocation.
# - Set Automatically is disabled. The flow intentionally keeps that
# manual-state setting unchanged.

appId: timezonehermesfix.example
name: Refresh Hermes timezone after iOS timezone change
tags:
- ios
- manual-device-state
- regression
- agent-device
---
- launchApp: timezonehermesfix.example
- extendedWaitUntil:
visible: 'Europe/Warsaw'
timeout: 10000
- assertVisible:
text: '.*TimezoneOffset -120'

# iOS retains its last Settings screen; this flow starts and ends on Date &
# Time rather than attempting to clear Settings state.
- launchApp:
appId: com.apple.Preferences
stopApp: false
- extendedWaitUntil:
visible: 'Time Zone'
timeout: 5000
- tapOn: 'Time Zone'
- eraseText: 50
- inputText: 'Los Angeles'
- extendedWaitUntil:
visible: 'Los Angeles, USA'
timeout: 5000
- tapOn: 'Los Angeles, USA'

# Foreground the existing process rather than restarting it.
- launchApp:
appId: timezonehermesfix.example
stopApp: false
- extendedWaitUntil:
visible: 'America/Los_Angeles'
timeout: 10000
- assertVisible:
text: '.*TimezoneOffset 420'
- assertNotVisible: 'Europe/Warsaw'

# Restore the shared device state.
- launchApp:
appId: com.apple.Preferences
stopApp: false
- extendedWaitUntil:
visible: 'Time Zone'
timeout: 5000
- tapOn: 'Time Zone'
- eraseText: 50
- inputText: 'Warsaw'
- extendedWaitUntil:
visible: 'Warsaw, Poland'
timeout: 5000
- tapOn: 'Warsaw, Poland'

- launchApp:
appId: timezonehermesfix.example
stopApp: false
- extendedWaitUntil:
visible: 'Europe/Warsaw'
timeout: 10000
- assertVisible:
text: '.*TimezoneOffset -120'
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@

| Library Version | React Native Version |
| --------------- | -------------------- |
| 0.1.x - 0.3.x | 0.80.2 - 0.81.X |
| 0.4.0+ | 0.82.x and above |
| 0.1.x - 0.3.x | 0.80.2 - 0.81.x |
| 0.4.x | 0.82.x - 0.86.x |
| 0.5.x | 0.87.x and above |

## 🔧 The Problem

Expand Down
3 changes: 2 additions & 1 deletion example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ android {
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
manifestPlaceholders = [usesCleartextTraffic: "true"]
Comment thread
troZee marked this conversation as resolved.
}
signingConfigs {
debug {
Expand All @@ -102,7 +103,7 @@ android {
// see https://reactnative.dev/docs/signed-apk-android.
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
android:usesCleartextTraffic="${usesCleartextTraffic}"
Comment thread
troZee marked this conversation as resolved.
android:supportsRtl="true">
<activity
android:name=".MainActivity"
Expand Down
6 changes: 3 additions & 3 deletions example/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
buildscript {
ext {
buildToolsVersion = "36.0.0"
buildToolsVersion = "37.0.0"
minSdkVersion = 24
compileSdkVersion = 36
compileSdkVersion = 37
targetSdkVersion = 36
ndkVersion = "27.1.12297006"
kotlinVersion = "2.0.21"
kotlinVersion = "2.2.0"
}
repositories {
google()
Expand Down
7 changes: 6 additions & 1 deletion example/android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,9 @@ hermesEnabled=true
# Use this property to enable edge-to-edge display support.
# This allows your app to draw behind system bars for an immersive UI.
# Note: Only works with ReactActivity and should not be used with custom Activity.
edgeToEdgeEnabled=false
edgeToEdgeEnabled=true

# Opt out of built-in kotlin and new DSL behavior that ships with AGP 9.
# Starting from AGP 10.x these opt outs will be removed.
android.builtInKotlin=false
android.newDsl=false
2 changes: 1 addition & 1 deletion example/android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
2 changes: 2 additions & 0 deletions example/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { AppRegistry } from 'react-native';
import App from './src/App';
import { name as appName } from './app.json';
import { LogBox } from 'react-native';
LogBox.ignoreAllLogs();

AppRegistry.registerComponent(appName, () => App);
Comment thread
troZee marked this conversation as resolved.
2 changes: 0 additions & 2 deletions example/ios/Podfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
ENV['RCT_NEW_ARCH_ENABLED'] = '1'

# Resolve react_native_pods.rb with node to allow for hoisting
require Pod::Executable.execute_command('node', ['-p',
'require.resolve(
Expand Down
Loading
Loading