Skip to content

Commit a68fcdd

Browse files
authored
fix(android): drop the dumpsys scroll-hint probe instead of capping it (#1270) (#1314)
The snapshot helper is the only capture backend and emits `can-scroll-forward`/ `can-scroll-backward` for exactly the nodes Android reports as scrollable. Their absence therefore means nothing on screen scrolls, not that scroll state is unknown — so the `dumpsys activity top` probe only ever ran when there was no scrollable content for it to describe. #1288 bounded that probe at 1.5s rather than removing it, leaving every capture of a screen with a scrollable-typed but non-scrollable node (a list short enough to fit) paying the cap for hints that cannot exist. `dumpsys activity top` serializes a view dump of every top activity through each app's main thread, so one busy app stalls the call until Android's own 10s service-dump timeout — the mechanism behind the 37ms-to-5.9s spread in the issue's evidence. Hints for genuinely scrollable nodes are unaffected: they come from the parsed helper attributes, which is where they already came from on every screen that reports a scroll action.
1 parent 8eeed1d commit a68fcdd

3 files changed

Lines changed: 32 additions & 535 deletions

File tree

src/platforms/android/__tests__/snapshot.test.ts

Lines changed: 17 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,10 +1169,11 @@ test('snapshotAndroid re-probes helper install after helper capture failure', as
11691169
});
11701170

11711171
test('snapshotAndroid preserves hidden scroll content hints in interactive snapshots', async () => {
1172+
// Mid-scroll: Android offers both scroll actions, so the helper reports both directions.
11721173
const xml = `<?xml version="1.0" encoding="UTF-8"?>
11731174
<hierarchy rotation="0">
11741175
<node class="android.widget.FrameLayout" bounds="[0,0][390,844]" clickable="false" focusable="false">
1175-
<node class="android.widget.ScrollView" content-desc="Messages" bounds="[0,100][390,600]" clickable="false" focusable="false">
1176+
<node class="android.widget.ScrollView" content-desc="Messages" scrollable="true" can-scroll-forward="true" can-scroll-backward="true" bounds="[0,100][390,600]" clickable="false" focusable="false">
11761177
<node class="android.view.ViewGroup" bounds="[0,100][390,600]" clickable="false" focusable="false">
11771178
<node class="android.widget.Button" text="Earlier message" bounds="[0,100][390,268]" clickable="true" focusable="true" />
11781179
<node class="android.widget.Button" text="Visible message" bounds="[0,268][390,436]" clickable="true" focusable="true" />
@@ -1181,15 +1182,8 @@ test('snapshotAndroid preserves hidden scroll content hints in interactive snaps
11811182
</node>
11821183
</node>
11831184
</hierarchy>`;
1184-
const dump = [
1185-
' com.facebook.react.views.scroll.ReactScrollView{d32a800 VFED.V... ........ 0,0-390,500 #4b2}',
1186-
' com.facebook.react.views.view.ReactViewGroup{77d31ae V.E...... ........ 0,0-390,1000 #4b0}',
1187-
' com.facebook.react.views.view.ReactViewGroup{a V.E...... ........ 0,300-390,468 #1}',
1188-
' com.facebook.react.views.view.ReactViewGroup{b V.E...... ........ 0,468-390,636 #2}',
1189-
' com.facebook.react.views.view.ReactViewGroup{c V.E...... ........ 0,636-390,804 #3}',
1190-
].join('\n');
11911185

1192-
const result = await snapshotAndroidWithHelper(androidSnapshotHelperAdb(xml, dump), {
1186+
const result = await snapshotAndroidWithHelper(androidSnapshotHelperAdb(xml), {
11931187
interactiveOnly: true,
11941188
});
11951189
const scrollArea = result.nodes.find((node) => node.type === 'android.widget.ScrollView');
@@ -1240,7 +1234,11 @@ test('snapshotAndroid skips activity dump when snapshot has no scrollable nodes'
12401234
assert.equal(result.nodes[0]?.label, 'Continue');
12411235
});
12421236

1243-
test('snapshotAndroid caps the activity-top scroll-hint probe at 1.5s', async () => {
1237+
// A scrollable-typed node that Android does not report as scrollable (a list short enough to fit)
1238+
// is the one shape that still reached `dumpsys activity top` after #1288 capped it. The probe can
1239+
// only ever run when the helper reported no scroll actions at all — which is exactly when there is
1240+
// no scrollable content to describe — so it must not run, at any budget (#1270).
1241+
test('snapshotAndroid never probes the activity dump for scroll hints', async () => {
12441242
const xml = `<?xml version="1.0" encoding="UTF-8"?>
12451243
<hierarchy rotation="0">
12461244
<node class="android.widget.FrameLayout" bounds="[0,0][390,844]" clickable="false" focusable="false">
@@ -1249,41 +1247,19 @@ test('snapshotAndroid caps the activity-top scroll-hint probe at 1.5s', async ()
12491247
</node>
12501248
</node>
12511249
</hierarchy>`;
1252-
const activityTimeouts: Array<number | undefined> = [];
1253-
const helperAdb = createHelperAdb({
1254-
instrument: async () => ({ exitCode: 0, stdout: helperOutput(xml), stderr: '' }),
1255-
activity: async (_args, options) => {
1256-
activityTimeouts.push(options?.timeoutMs);
1257-
return { exitCode: 0, stdout: '', stderr: '' };
1258-
},
1259-
});
1260-
1261-
await snapshotAndroidWithHelper(helperAdb);
1262-
1263-
assert.deepEqual(activityTimeouts, [1500]);
1264-
});
1265-
1266-
test('snapshotAndroid skips the activity-top probe when the helper XML already carries scroll-action attributes', async () => {
1267-
const xml = `<?xml version="1.0" encoding="UTF-8"?>
1268-
<hierarchy rotation="0">
1269-
<node class="android.widget.FrameLayout" bounds="[0,0][390,844]" clickable="false" focusable="false">
1270-
<node class="android.widget.ScrollView" scrollable="true" can-scroll-forward="true" can-scroll-backward="false" bounds="[0,100][390,600]" clickable="false" focusable="false">
1271-
<node class="android.widget.Button" text="Continue" bounds="[20,120][200,180]" clickable="true" focusable="true" />
1272-
</node>
1273-
</node>
1274-
</hierarchy>`;
1275-
let activityDumpCalled = false;
1250+
const activityDumpCalls: string[][] = [];
12761251
const helperAdb = createHelperAdb({
12771252
instrument: async () => ({ exitCode: 0, stdout: helperOutput(xml), stderr: '' }),
1278-
activity: async () => {
1279-
activityDumpCalled = true;
1253+
activity: async (args) => {
1254+
activityDumpCalls.push(args);
12801255
return { exitCode: 0, stdout: '', stderr: '' };
12811256
},
12821257
});
12831258

12841259
await snapshotAndroidWithHelper(helperAdb);
1260+
await snapshotAndroidWithHelper(helperAdb, { interactiveOnly: true });
12851261

1286-
assert.equal(activityDumpCalled, false);
1262+
assert.deepEqual(activityDumpCalls, []);
12871263
});
12881264

12891265
test('snapshotAndroid skips hidden content hints when disabled', async () => {
@@ -1412,24 +1388,20 @@ test('snapshotAndroid omits zero-area interactive nodes from interactive snapsho
14121388
);
14131389
});
14141390

1415-
test('snapshotAndroid preserves bottomed-out hidden-above hints in interactive snapshots from a single aligned block', async () => {
1391+
test('snapshotAndroid preserves bottomed-out hidden-above hints in interactive snapshots', async () => {
1392+
// Scrolled to the bottom: only the backward action remains, so nothing is hidden below.
14161393
const xml = `<?xml version="1.0" encoding="UTF-8"?>
14171394
<hierarchy rotation="0">
14181395
<node class="android.widget.FrameLayout" bounds="[0,0][390,844]" clickable="false" focusable="false">
1419-
<node class="android.widget.ScrollView" content-desc="Messages" bounds="[0,100][390,600]" clickable="false" focusable="false">
1396+
<node class="android.widget.ScrollView" content-desc="Messages" scrollable="true" can-scroll-forward="false" can-scroll-backward="true" bounds="[0,100][390,600]" clickable="false" focusable="false">
14201397
<node class="android.view.ViewGroup" bounds="[0,100][390,600]" clickable="false" focusable="false">
14211398
<node class="android.widget.Button" text="Last message" bounds="[0,432][390,600]" clickable="true" focusable="true" />
14221399
</node>
14231400
</node>
14241401
</node>
14251402
</hierarchy>`;
1426-
const dump = [
1427-
' com.facebook.react.views.scroll.ReactScrollView{d32a800 VFED.V... ........ 0,0-390,500 #4b2}',
1428-
' com.facebook.react.views.view.ReactViewGroup{77d31ae V.E...... ........ 0,0-390,804 #4b0}',
1429-
' com.facebook.react.views.view.ReactViewGroup{c V.E...... ........ 0,636-390,804 #3}',
1430-
].join('\n');
14311403

1432-
const result = await snapshotAndroidWithHelper(androidSnapshotHelperAdb(xml, dump), {
1404+
const result = await snapshotAndroidWithHelper(androidSnapshotHelperAdb(xml), {
14331405
interactiveOnly: true,
14341406
});
14351407
const scrollArea = result.nodes.find(

0 commit comments

Comments
 (0)