Skip to content

Commit 2d5ea0a

Browse files
Abbondanzometa-codesync[bot]
authored andcommitted
Add image smoke-test examples and Maestro flows to RNTester (react#57306)
Summary: Pull Request resolved: react#57306 Adds three Image examples to RNTester plus matching Maestro end-to-end flows, covering image-loading behaviors that previously had little or no RNTester coverage: - Progressive JPEG (`progressiveRenderingEnabled`) - `blurRadius` combined with `Image.prefetch` (blur postprocessor over a prefetched bitmap) - Wide-gamut (Display-P3) vs sRGB and alpha transparency Each example renders status text (load/error) with stable testIDs so the Maestro flows can assert behavior; the visual cases also capture screenshots. Progressive JPEG is gated to Android (the prop is Android-only); the rest run on both platforms. Changelog: [Internal] Reviewed By: cortinico Differential Revision: D109316705
1 parent d1a93b5 commit 2d5ea0a

9 files changed

Lines changed: 298 additions & 2 deletions

File tree

.github/workflow-scripts/maestro-android.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,10 @@ async function executeFlowInFolder(flowFolder) {
6060
const filePath = `${flowFolder}/${file}`;
6161
if (fs.lstatSync(filePath).isDirectory()) {
6262
await executeFlowInFolder(filePath);
63-
} else {
63+
} else if (file.endsWith('.yml') || file.endsWith('.yaml')) {
6464
await executeFlowWithRetries(filePath, 0);
6565
}
66+
// Skip non-flow files (e.g. screenshot baselines under screenshots/).
6667
}
6768
}
6869

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
appId: ${APP_ID} # iOS: com.meta.RNTester.localDevelopment | Android: com.facebook.react.uiapp
2+
---
3+
# Prefetch an image, then render the same URI with blurRadius so the blur
4+
# postprocessor applies to the already-decoded bitmap. Cross-platform.
5+
- runFlow: ./helpers/launch-app-and-search.yml
6+
- inputText:
7+
text: 'Image'
8+
- assertVisible:
9+
id: 'Image'
10+
- tapOn:
11+
id: 'Image'
12+
- assertVisible:
13+
id: 'example_search'
14+
- tapOn:
15+
id: 'example_search'
16+
- inputText: 'Blur Radius with Prefetch'
17+
- hideKeyboard
18+
- scrollUntilVisible:
19+
element:
20+
id: 'image-blur-prefetch'
21+
direction: DOWN
22+
speed: 40
23+
timeout: 10000
24+
- extendedWaitUntil:
25+
visible: 'prefetch: ok'
26+
timeout: 20000
27+
- extendedWaitUntil:
28+
visible: 'blurred image: loaded'
29+
timeout: 20000
30+
# Change this to takeScreenshot when you want to update the screenshot
31+
- assertScreenshot:
32+
path: screenshots/image-blur-prefetch-${maestro.platform}
33+
cropOn:
34+
id: 'image-blur-prefetch'
35+
thresholdPercentage: 95
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
appId: ${APP_ID} # iOS: com.meta.RNTester.localDevelopment | Android: com.facebook.react.uiapp
2+
tags:
3+
- android-only
4+
---
5+
# Android-only: loads a JPEG with progressiveRenderingEnabled and logs load
6+
# events. iOS skips this flow (empty=pass).
7+
- runFlow:
8+
when:
9+
platform: Android
10+
commands:
11+
- runFlow: ./helpers/launch-app-and-search.yml
12+
- inputText:
13+
text: 'Image'
14+
- assertVisible:
15+
id: 'Image'
16+
- tapOn:
17+
id: 'Image'
18+
- assertVisible:
19+
id: 'example_search'
20+
- tapOn:
21+
id: 'example_search'
22+
- inputText: 'Progressive JPEG'
23+
- hideKeyboard
24+
- scrollUntilVisible:
25+
element:
26+
id: 'image-progressive-jpeg'
27+
direction: DOWN
28+
speed: 40
29+
timeout: 10000
30+
# loadStart fires on mount -> proves the progressive load path engaged.
31+
# Assert by id since each event is its own element (progress % is dynamic).
32+
- extendedWaitUntil:
33+
visible:
34+
id: 'progressive-jpeg-loadstart'
35+
timeout: 15000
36+
# Final load event (network image; relies on the same host as image.yml).
37+
- extendedWaitUntil:
38+
visible:
39+
id: 'progressive-jpeg-load'
40+
timeout: 20000
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
appId: ${APP_ID} # iOS: com.meta.RNTester.localDevelopment | Android: com.facebook.react.uiapp
2+
---
3+
# Alpha transparency + sRGB vs Display-P3 wide-gamut. Color fidelity needs a
4+
# screenshot, so we assert load status only. Cross-platform.
5+
- runFlow: ./helpers/launch-app-and-search.yml
6+
- inputText:
7+
text: 'Image'
8+
- assertVisible:
9+
id: 'Image'
10+
- tapOn:
11+
id: 'Image'
12+
- assertVisible:
13+
id: 'example_search'
14+
- tapOn:
15+
id: 'example_search'
16+
- inputText: 'Wide Gamut'
17+
- hideKeyboard
18+
- scrollUntilVisible:
19+
element:
20+
id: 'image-wide-gamut'
21+
direction: DOWN
22+
speed: 40
23+
timeout: 10000
24+
- extendedWaitUntil:
25+
visible: 'alpha: loaded'
26+
timeout: 15000
27+
- scrollUntilVisible:
28+
element: 'sRGB: loaded'
29+
direction: DOWN
30+
speed: 40
31+
timeout: 20000
32+
# External URL; accept loaded or error.
33+
- scrollUntilVisible:
34+
element: 'P3: (loaded|error)'
35+
direction: DOWN
36+
speed: 40
37+
timeout: 20000
38+
# Change this to takeScreenshot when you want to update the screenshot
39+
- assertScreenshot:
40+
path: screenshots/image-wide-gamut-${maestro.platform}
41+
cropOn:
42+
id: 'image-wide-gamut'
43+
thresholdPercentage: 95
10.5 KB
Loading
44.7 KB
Loading
17.7 KB
Loading
118 KB
Loading

packages/rn-tester/js/examples/Image/ImageExample.js

Lines changed: 178 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,23 @@ import {
2929
View,
3030
} from 'react-native';
3131

32+
const ALPHA_PNG_ASSET = require('../../assets/alpha-hotdog.png');
33+
3234
const IMAGE1 =
3335
'https://www.facebook.com/assets/fb_lite_messaging/E2EE-settings@3x.png';
3436
const IMAGE2 =
3537
'https://www.facebook.com/ar_effect/external_textures/648609739826677.png';
36-
3738
const base64Icon =
3839
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAQAAACSR7JhAAADtUlEQVR4Ac3YA2Bj6QLH0XPT1Fzbtm29tW3btm3bfLZtv7e2ObZnms7d8Uw098tuetPzrxv8wiISrtVudrG2JXQZ4VOv+qUfmqCGGl1mqLhoA52oZlb0mrjsnhKpgeUNEs91Z0pd1kvihA3ULGVHiQO2narKSHKkEMulm9VgUyE60s1aWoMQUbpZOWE+kaqs4eLEjdIlZTcFZB0ndc1+lhB1lZrIuk5P2aib1NBpZaL+JaOGIt0ls47SKzLC7CqrlGF6RZ09HGoNy1lYl2aRSWL5GuzqWU1KafRdoRp0iOQEiDzgZPnG6DbldcomadViflnl/cL93tOoVbsOLVM2jylvdWjXolWX1hmfZbGR/wjypDjFLSZIRov09BgYmtUqPQPlQrPapecLgTIy0jMgPKtTeob2zWtrGH3xvjUkPCtNg/tm1rjwrMa+mdUkPd3hWbH0jArPGiU9ufCsNNWFZ40wpwn+62/66R2RUtoso1OB34tnLOcy7YB1fUdc9e0q3yru8PGM773vXsuZ5YIZX+5xmHwHGVvlrGPN6ZSiP1smOsMMde40wKv2VmwPPVXNut4sVpUreZiLBHi0qln/VQeI/LTMYXpsJtFiclUN+5HVZazim+Ky+7sAvxWnvjXrJFneVtLWLyPJu9K3cXLWeOlbMTlrIelbMDlrLenrjEQOtIF+fuI9xRp9ZBFp6+b6WT8RrxEpdK64BuvHgDk+vUy+b5hYk6zfyfs051gRoNO1usU12WWRWL73/MMEy9pMi9qIrR4ZpV16Rrvduxazmy1FSvuFXRkqTnE7m2kdb5U8xGjLw/spRr1uTov4uOgQE+0N/DvFrG/Jt7i/FzwxbA9kDanhf2w+t4V97G8lrT7wc08aA2QNUkuTfW/KimT01wdlfK4yEw030VfT0RtZbzjeMprNq8m8tnSTASrTLti64oBNdpmMQm0eEwvfPwRbUBywG5TzjPCsdwk3IeAXjQblLCoXnDVeoAz6SfJNk5TTzytCNZk/POtTSV40NwOFWzw86wNJRpubpXsn60NJFlHeqlYRbslqZm2jnEZ3qcSKgm0kTli3zZVS7y/iivZTweYXJ26Y+RTbV1zh3hYkgyFGSTKPfRVbRqWWVReaxYeSLarYv1Qqsmh1s95S7G+eEWK0f3jYKTbV6bOwepjfhtafsvUsqrQvrGC8YhmnO9cSCk3yuY984F1vesdHYhWJ5FvASlacshUsajFt2mUM9pqzvKGcyNJW0arTKN1GGGzQlH0tXwLDgQTurS8eIQAAAABJRU5ErkJggg==';
3940
const IMAGE_PREFETCH_URL = `${IMAGE1}?r=1&t=${Date.now()}`;
4041
const prefetchTask = Image.prefetch(IMAGE_PREFETCH_URL);
42+
// Remote JPEG (RN OSS test fixture) used by the progressive example. Trusted by
43+
// the API 24 Android CI emulator and reachable on both platforms.
44+
const LARGE_JPEG =
45+
'https://www.facebook.com/assets/react_native_oss_tests/large-image@1x.jpg';
46+
// Display-P3 wide-gamut sample (WebKit color-gamut test image).
47+
const WIDE_GAMUT_P3_URL =
48+
'https://webkit.org/blog-files/color-gamut/Webkit-logo-P3.png';
4149

4250
type ImageSource = Readonly<{
4351
uri: string,
@@ -845,6 +853,142 @@ function ImageGetSizePlatformTest(props: PlatformTestComponentBaseProps) {
845853
);
846854
}
847855

856+
function ProgressiveJpegExample(): React.Node {
857+
const [loadStarted, setLoadStarted] = useState(false);
858+
const [progress, setProgress] = useState<?number>(null);
859+
const [loaded, setLoaded] = useState(false);
860+
const [uri] = useState(() => `${LARGE_JPEG}?progressive=${Date.now()}`);
861+
return (
862+
<View testID="image-progressive-jpeg">
863+
<Image
864+
testID="progressive-jpeg-image"
865+
style={styles.base}
866+
source={{uri}}
867+
progressiveRenderingEnabled={true}
868+
onLoadStart={() => setLoadStarted(true)}
869+
onProgress={event => {
870+
const {loaded: bytesLoaded, total} = event.nativeEvent;
871+
if (total > 0) {
872+
setProgress(Math.round((bytesLoaded / total) * 100));
873+
}
874+
}}
875+
onLoad={() => setLoaded(true)}
876+
/>
877+
{loadStarted ? (
878+
<RNTesterText testID="progressive-jpeg-loadstart">
879+
loadStart
880+
</RNTesterText>
881+
) : null}
882+
{progress != null ? (
883+
<RNTesterText testID="progressive-jpeg-progress">
884+
progress {progress}%
885+
</RNTesterText>
886+
) : null}
887+
{loaded ? (
888+
<RNTesterText testID="progressive-jpeg-load">load</RNTesterText>
889+
) : null}
890+
</View>
891+
);
892+
}
893+
894+
function BlurRadiusPrefetchExample(): React.Node {
895+
const [uri] = useState(() => `${IMAGE2}?blurPrefetch=${Date.now()}`);
896+
const [prefetchStatus, setPrefetchStatus] = useState('pending');
897+
const [loadStatus, setLoadStatus] = useState('pending');
898+
899+
useEffect(() => {
900+
let cancelled = false;
901+
void Image.prefetch(uri).then(
902+
() => {
903+
if (!cancelled) {
904+
setPrefetchStatus('ok');
905+
}
906+
},
907+
() => {
908+
if (!cancelled) {
909+
setPrefetchStatus('failed');
910+
}
911+
},
912+
);
913+
return () => {
914+
cancelled = true;
915+
};
916+
}, [uri]);
917+
918+
return (
919+
<View testID="image-blur-prefetch">
920+
<RNTesterText testID="blur-prefetch-prefetch-status">
921+
prefetch: {prefetchStatus}
922+
</RNTesterText>
923+
{prefetchStatus === 'ok' ? (
924+
<Image
925+
testID="blur-prefetch-image"
926+
style={styles.base}
927+
source={{uri}}
928+
blurRadius={15}
929+
onLoad={() => setLoadStatus('loaded')}
930+
onError={() => setLoadStatus('error')}
931+
/>
932+
) : null}
933+
<RNTesterText testID="blur-prefetch-load-status">
934+
blurred image: {loadStatus}
935+
</RNTesterText>
936+
</View>
937+
);
938+
}
939+
940+
function WideGamutTransparencyExample(): React.Node {
941+
const [alphaStatus, setAlphaStatus] = useState('loading');
942+
const [srgbStatus, setSrgbStatus] = useState('loading');
943+
const [p3Status, setP3Status] = useState('loading');
944+
return (
945+
<View testID="image-wide-gamut">
946+
<RNTesterText style={styles.sectionText}>
947+
Alpha / transparency
948+
</RNTesterText>
949+
<View style={styles.checkerBackground}>
950+
<Image
951+
testID="wide-gamut-alpha-image"
952+
style={styles.base}
953+
source={ALPHA_PNG_ASSET}
954+
onLoad={() => setAlphaStatus('loaded')}
955+
onError={() => setAlphaStatus('error')}
956+
/>
957+
</View>
958+
<RNTesterText testID="wide-gamut-alpha-status">
959+
alpha: {alphaStatus}
960+
</RNTesterText>
961+
<RNTesterText style={styles.sectionText}>sRGB vs Display-P3</RNTesterText>
962+
<View style={styles.horizontal}>
963+
<View>
964+
<RNTesterText style={styles.resizeModeText}>sRGB</RNTesterText>
965+
<Image
966+
testID="wide-gamut-srgb-image"
967+
style={styles.base}
968+
source={smallImage}
969+
onLoad={() => setSrgbStatus('loaded')}
970+
onError={() => setSrgbStatus('error')}
971+
/>
972+
</View>
973+
<View style={styles.leftMargin}>
974+
<RNTesterText style={styles.resizeModeText}>Display-P3</RNTesterText>
975+
<Image
976+
testID="wide-gamut-p3-image"
977+
style={styles.base}
978+
source={{uri: WIDE_GAMUT_P3_URL}}
979+
onLoad={() => setP3Status('loaded')}
980+
onError={() => setP3Status('error')}
981+
/>
982+
</View>
983+
</View>
984+
<RNTesterText testID="wide-gamut-srgb-status">
985+
sRGB: {srgbStatus}
986+
</RNTesterText>
987+
<RNTesterText testID="wide-gamut-p3-status">P3: {p3Status}</RNTesterText>
988+
</View>
989+
);
990+
}
991+
848992
const styles = StyleSheet.create({
849993
base: {
850994
width: 64,
@@ -1095,6 +1239,11 @@ const styles = StyleSheet.create({
10951239
alignItems: 'center',
10961240
marginTop: 10,
10971241
},
1242+
checkerBackground: {
1243+
backgroundColor: '#cccccc',
1244+
padding: 4,
1245+
alignSelf: 'flex-start',
1246+
},
10981247
});
10991248
11001249
exports.displayName = undefined as ?string;
@@ -1957,4 +2106,32 @@ exports.examples = [
19572106
},
19582107
platform: 'android',
19592108
},
2109+
{
2110+
title: 'Progressive JPEG',
2111+
name: 'progressive-jpeg',
2112+
description:
2113+
'Loads a JPEG with progressiveRenderingEnabled and logs progress/load events.',
2114+
render: function (): React.Node {
2115+
return <ProgressiveJpegExample />;
2116+
},
2117+
platform: 'android',
2118+
},
2119+
{
2120+
title: 'Blur Radius with Prefetch',
2121+
name: 'blur-radius-prefetch',
2122+
description:
2123+
'Prefetches then renders the same URI with blurRadius to ensure the blur postprocessor is applied on prefetched images.',
2124+
render: function (): React.Node {
2125+
return <BlurRadiusPrefetchExample />;
2126+
},
2127+
},
2128+
{
2129+
title: 'Wide Gamut and Transparency',
2130+
name: 'wide-gamut',
2131+
description:
2132+
'Alpha transparency and sRGB vs Display-P3 comparison targets for screenshot tests.',
2133+
render: function (): React.Node {
2134+
return <WideGamutTransparencyExample />;
2135+
},
2136+
},
19602137
] as Array<RNTesterModuleExample>;

0 commit comments

Comments
 (0)