Expand RNTester native interop Maestro coverage (#58444)

Summary:
Pull Request resolved: https://github.com/react/react-native/pull/58444

Add direct-deep-link Maestro coverage for RNTester native interop scenarios and expand the legacy native module flow to validate its platform-specific result set. Make deep-link flows reliable by cold-starting their first route, acknowledging the iOS system confirmation, waiting for route-specific readiness, and using target-driven list scrolling. Extend the Maestro Cloud execution window for the expanded suite and exercise the intended native methods. Exclude the local screenshot-baseline flow from Maestro Cloud.

Reviewed By: Abbondanzo

Differential Revision: D119484762

fbshipit-source-id: 4004cdfd84f85420eb9e28a4fb9e2f0312ea05e0
This commit is contained in:
Nicola Corti
2026-09-23 03:08:07 -07:00
committed by meta-codesync[bot]
parent b9a959643c
commit d950a46cb9
4 changed files with 452 additions and 60 deletions
@@ -0,0 +1,30 @@
appId: ${APP_ID} # iOS: com.meta.RNTester.localDevelopment | Android: com.facebook.react.uiapp
---
- launchApp
- stopApp
- runFlow:
when:
platform: iOS
commands:
- openLink: rntester://example/FabricInteropLayer/add-children
- runFlow: ./helpers/confirm-open-link.yml
- extendedWaitUntil:
visible:
id: 'add-marker-btn'
timeout: 120000
- assertVisible:
id: 'marker_0'
- assertVisible:
id: 'marker_1'
- assertVisible:
id: 'marker_2'
- assertVisible:
id: 'marker_3'
- assertVisible:
id: 'marker_4'
- assertNotVisible:
id: 'marker_5'
- tapOn:
id: 'add-marker-btn'
- assertVisible:
id: 'marker_5'
@@ -1,50 +1,412 @@
appId: ${APP_ID} # iOS: com.meta.RNTester.localDevelopment | Android: com.facebook.react.uiapp
---
- launchApp
# Wait for the JS bundle to render the landing screen instead of asserting
# immediately, which races app cold start on CI.
- stopApp
- openLink: rntester://example/LegacyModule/SampleLegacyModule
- runFlow: ./helpers/confirm-open-link.yml
- extendedWaitUntil:
visible:
text: 'APIs'
timeout: 30000
id: 'run-all-tests'
timeout: 120000
- tapOn:
id: 'apis-tab'
- runFlow: ./helpers/search.yml
- inputText:
text: 'Legacy Native'
- assertVisible:
id: 'Legacy Native Module'
- tapOn:
id: 'Legacy Native Module'
- tapOn:
text: 'voidFunc'
- assertVisible:
id: 'voidFunc-result'
- tapOn:
text: 'getBool'
- assertVisible:
id: 'getBool-result'
- tapOn:
text: 'getEnum'
- assertVisible:
id: 'getEnum-result'
- tapOn:
text: 'getInt'
- assertVisible:
id: 'getInt-result'
- tapOn:
text: 'getString'
- assertVisible:
id: 'getString-result'
- tapOn:
text: 'getObject'
- assertVisible:
id: 'getObject-result'
- tapOn:
text: 'getUnsafeObject'
- assertVisible:
id: 'getUnsafeObject-result'
- tapOn:
text: 'getValue'
- assertVisible:
id: 'getValue-result'
id: 'run-all-tests'
- runFlow:
when:
platform: iOS
commands:
- assertVisible:
id: 'voidFunc-result'
- assertVisible:
id: 'getBool-result'
text: 'true'
- assertVisible:
id: 'getEnum-result'
text: '1'
- scrollUntilVisible:
element:
id: 'getNumber-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getNumber-result'
text: '99.95'
- scrollUntilVisible:
element:
id: 'getFloat-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getFloat-result'
text: '99.94999694824219'
- scrollUntilVisible:
element:
id: 'getInt-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getInt-result'
text: '99'
- scrollUntilVisible:
element:
id: 'getLongLong-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getLongLong-result'
text: '99'
- scrollUntilVisible:
element:
id: 'getUnsignedLongLong-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getUnsignedLongLong-result'
text: '99'
- scrollUntilVisible:
element:
id: 'getNSInteger-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getNSInteger-result'
text: '99'
- scrollUntilVisible:
element:
id: 'getNSUInteger-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getNSUInteger-result'
text: '99'
- scrollUntilVisible:
element:
id: 'getArray-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getArray-result'
text: '.*foo.*'
- scrollUntilVisible:
element:
id: 'getObject-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getObject-result'
text: '.*foo.*'
- scrollUntilVisible:
element:
id: 'getString-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getString-result'
text: '.*Hello.*'
- scrollUntilVisible:
element:
id: 'getNullString-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getNullString-result'
text: 'null'
- scrollUntilVisible:
element:
id: 'getNSNumber-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getNSNumber-result'
text: '20'
- scrollUntilVisible:
element:
id: 'getUnsafeObject-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getUnsafeObject-result'
text: '.*foo.*'
- scrollUntilVisible:
element:
id: 'getRootTag-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getRootTag-result'
text: '11'
- scrollUntilVisible:
element:
id: 'getValue-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getValue-result'
text: '.*test.*'
- scrollUntilVisible:
element:
id: 'callback-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'callback-result'
text: '.*value from callback!.*'
- scrollUntilVisible:
element:
id: 'promise-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'promise-result'
text: '.*result!.*'
- scrollUntilVisible:
element:
id: 'rejectPromise-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'rejectPromise-result'
text: '.*intentional promise rejection.*'
- scrollUntilVisible:
element:
id: 'getConstants-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getConstants-result'
text: '.*something.*'
- scrollUntilVisible:
element:
id: 'getConst1-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getConst1-result'
text: 'true'
- scrollUntilVisible:
element:
id: 'getConst2-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getConst2-result'
text: '390'
- scrollUntilVisible:
element:
id: 'getConst3-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getConst3-result'
text: '.*something.*'
- runFlow:
when:
platform: Android
commands:
- assertVisible:
id: 'voidFunc-result'
- assertVisible:
id: 'getBool-result'
text: 'true'
- assertVisible:
id: 'getEnum-result'
text: '1'
- scrollUntilVisible:
element:
id: 'getDouble-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getDouble-result'
text: '99.95'
- scrollUntilVisible:
element:
id: 'getInt-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getInt-result'
text: '99'
- scrollUntilVisible:
element:
id: 'getFloat-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getFloat-result'
text: '99.94999694824219'
- scrollUntilVisible:
element:
id: 'getObjectDouble-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getObjectDouble-result'
text: '99.95'
- scrollUntilVisible:
element:
id: 'getObjectInteger-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getObjectInteger-result'
text: '99'
- scrollUntilVisible:
element:
id: 'getObjectFloat-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getObjectFloat-result'
text: '99.94999694824219'
- scrollUntilVisible:
element:
id: 'getString-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getString-result'
text: '.*Hello.*'
- scrollUntilVisible:
element:
id: 'getRootTag-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getRootTag-result'
text: '11'
- scrollUntilVisible:
element:
id: 'getObject-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getObject-result'
text: '.*foo.*'
- scrollUntilVisible:
element:
id: 'getUnsafeObject-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getUnsafeObject-result'
text: '.*foo.*'
- scrollUntilVisible:
element:
id: 'getValue-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getValue-result'
text: '.*test.*'
- scrollUntilVisible:
element:
id: 'getDynamicWithNull-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getDynamicWithNull-result'
text: '.*Null.*'
- scrollUntilVisible:
element:
id: 'getDynamicWithBoolean-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getDynamicWithBoolean-result'
text: '.*Boolean.*'
- scrollUntilVisible:
element:
id: 'getDynamicWithNumber-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getDynamicWithNumber-result'
text: '.*42.*'
- scrollUntilVisible:
element:
id: 'getDynamicWithString-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getDynamicWithString-result'
text: '.*The answer is 42.*'
- scrollUntilVisible:
element:
id: 'getDynamicWithArray-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getDynamicWithArray-result'
text: '.*the.*'
- scrollUntilVisible:
element:
id: 'getDynamicWithMap-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getDynamicWithMap-result'
text: '.*answer.*'
- scrollUntilVisible:
element:
id: 'callback-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'callback-result'
text: '.*Value From Callback.*'
- scrollUntilVisible:
element:
id: 'getArray-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getArray-result'
text: '.*foo.*'
- scrollUntilVisible:
element:
id: 'promise-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'promise-result'
text: '.*result.*'
- scrollUntilVisible:
element:
id: 'rejectPromise-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'rejectPromise-result'
text: '.*intentional promise rejection.*'
- scrollUntilVisible:
element:
id: 'getConstants-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getConstants-result'
text: '.*something.*'
- scrollUntilVisible:
element:
id: 'getConst1-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getConst1-result'
text: 'true'
- scrollUntilVisible:
element:
id: 'getConst2-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getConst2-result'
text: '390'
- scrollUntilVisible:
element:
id: 'getConst3-result'
direction: DOWN
speed: 40
- assertVisible:
id: 'getConst3-result'
text: '.*something.*'
@@ -47,10 +47,10 @@ const InteropTestView = (props: InteropTestViewProps) => {
// =============================================================================
function Section({children, title}: SectionProps): React.Node {
function Section({children, testID, title}: SectionProps): React.Node {
const isDarkMode = useColorScheme() === 'dark';
return (
<View style={styles.sectionContainer}>
<View style={styles.sectionContainer} testID={testID}>
<Text
style={[
styles.sectionTitle,
@@ -60,15 +60,7 @@ function Section({children, title}: SectionProps): React.Node {
]}>
{title}
</Text>
<Text
style={[
styles.sectionDescription,
{
color: isDarkMode ? WHITE : BLACK,
},
]}>
{children}
</Text>
<View style={styles.sectionContent}>{children}</View>
</View>
);
}
@@ -90,7 +82,13 @@ function AddChildrenForInteropLayer() {
onPress={addMarker}
testID="add-marker-btn"
/>
<Text>{`Number of squares: ${squares.length}`}</Text>
<Text
style={[
styles.sectionDescription,
{
color: isDarkMode ? WHITE : BLACK,
},
]}>{`Number of squares: ${squares.length}`}</Text>
</Section>
<Section title="Custom native view" testID="interop-view-content">
<InteropTestView style={styles.customView}>
@@ -124,10 +122,12 @@ const styles = StyleSheet.create({
fontWeight: '600',
},
sectionDescription: {
marginTop: 8,
fontSize: 18,
fontWeight: '400',
},
sectionContent: {
marginTop: 8,
},
highlight: {
fontWeight: '700',
},
@@ -87,7 +87,7 @@ class SampleLegacyModuleExample extends React.Component<{}, State> {
getBool: () => getSampleLegacyModule()?.getBool(true),
getEnum: () => getSampleLegacyModule()?.getEnum(1.0),
getNumber: () => getSampleLegacyModule()?.getNumber(99.95),
getFloat: () => getSampleLegacyModule()?.getNumber(99.95),
getFloat: () => getSampleLegacyModule()?.getFloat(99.95),
getInt: () => getSampleLegacyModule()?.getInt(99),
getLongLong: () => getSampleLegacyModule()?.getLongLong(99),
getUnsignedLongLong: () =>
@@ -106,7 +106,7 @@ class SampleLegacyModuleExample extends React.Component<{}, State> {
getNullString: () => getSampleLegacyModule()?.getString(null),
getNSNumber: () => getSampleLegacyModule()?.getNSNumber(20.0),
getUnsafeObject: () =>
getSampleLegacyModule()?.getObject({a: 1, b: 'foo', c: null}),
getSampleLegacyModule()?.getUnsafeObject({a: 1, b: 'foo', c: null}),
getRootTag: () => getSampleLegacyModule()?.getRootTag(11),
getValue: () =>
getSampleLegacyModule()?.getValue(5, 'test', {a: 1, b: 'foo'}),
@@ -144,7 +144,7 @@ class SampleLegacyModuleExample extends React.Component<{}, State> {
getObject: () =>
getSampleLegacyModule()?.getObject({a: 1, b: 'foo', c: null}),
getUnsafeObject: () =>
getSampleLegacyModule()?.getObject({a: 1, b: 'foo', c: null}),
getSampleLegacyModule()?.getUnsafeObject({a: 1, b: 'foo', c: null}),
getValue: () =>
getSampleLegacyModule()?.getValue(5, 'test', {a: 1, b: 'foo'}),
getDynamicWithNull: () => getSampleLegacyModule()?.getDynamic(null),