Skip to content

Commit

Permalink
[webview_flutter] Improve flaky scroll tests (#7621)
Browse files Browse the repository at this point in the history
WebView portion of flutter/flutter#154826

Changes the scroll tests to wait for the `onScrollChange` callback to verify the scroll position rather than using `getScrollPosition` immediately.

Also split `getScrollPosition`, `scrollTo`, and `scrollBy` into their own tests.
  • Loading branch information
bparrishMines authored Sep 14, 2024
1 parent 100a074 commit df88c81
Show file tree
Hide file tree
Showing 3 changed files with 276 additions and 130 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ Future<void> main() async {
});

group('Programmatic Scroll', () {
testWidgets('setAndGetScrollPosition', (WidgetTester tester) async {
Future<WebViewController> pumpScrollTestPage(WidgetTester tester) async {
const String scrollTestPage = '''
<!DOCTYPE html>
<html>
Expand All @@ -518,58 +518,105 @@ Future<void> main() async {

final Completer<void> pageLoaded = Completer<void>();
final WebViewController controller = WebViewController();
ScrollPositionChange? recordedPosition;
await controller.setJavaScriptMode(JavaScriptMode.unrestricted);
await controller.setNavigationDelegate(NavigationDelegate(
onPageFinished: (_) => pageLoaded.complete(),
));
await controller.setOnScrollPositionChange(
(ScrollPositionChange contentOffsetChange) {
recordedPosition = contentOffsetChange;
});

await controller.loadRequest(Uri.parse(
'data:text/html;charset=utf-8;base64,$scrollTestPageBase64',
));

await tester.pumpWidget(WebViewWidget(controller: controller));

await pageLoaded.future;

await tester.pumpAndSettle(const Duration(seconds: 3));

Offset scrollPos = await controller.getScrollPosition();

// Check scrollTo()
const int X_SCROLL = 123;
const int Y_SCROLL = 321;
// Get the initial position; this ensures that scrollTo is actually
// changing something, but also gives the native view's scroll position
// time to settle.
expect(scrollPos.dx, isNot(X_SCROLL));
expect(scrollPos.dy, isNot(Y_SCROLL));
expect(recordedPosition?.x, isNot(X_SCROLL));
expect(recordedPosition?.y, isNot(Y_SCROLL));

await controller.scrollTo(X_SCROLL, Y_SCROLL);
scrollPos = await controller.getScrollPosition();
expect(scrollPos.dx, X_SCROLL);
expect(scrollPos.dy, Y_SCROLL);
expect(recordedPosition?.x, X_SCROLL);
expect(recordedPosition?.y, Y_SCROLL);

// Check scrollBy() (on top of scrollTo())
await controller.scrollBy(X_SCROLL, Y_SCROLL);
scrollPos = await controller.getScrollPosition();
expect(scrollPos.dx, X_SCROLL * 2);
expect(scrollPos.dy, Y_SCROLL * 2);
expect(recordedPosition?.x, X_SCROLL * 2);
expect(recordedPosition?.y, Y_SCROLL * 2);
return controller;
}

testWidgets('getScrollPosition', (WidgetTester tester) async {
final WebViewController controller = await pumpScrollTestPage(tester);
await controller.setJavaScriptMode(JavaScriptMode.unrestricted);

const Offset testScrollPosition = Offset(123, 321);

// Ensure the start scroll position is not equal to the test position.
expect(await controller.getScrollPosition(), isNot(testScrollPosition));

final Completer<void> testScrollPositionCompleter = Completer<void>();
await controller.setOnScrollPositionChange(
(ScrollPositionChange contentOffsetChange) {
if (Offset(contentOffsetChange.x, contentOffsetChange.y) ==
testScrollPosition) {
testScrollPositionCompleter.complete();
}
},
);

await controller.scrollTo(
testScrollPosition.dx.toInt(),
testScrollPosition.dy.toInt(),
);
await testScrollPositionCompleter.future;

expect(await controller.getScrollPosition(), testScrollPosition);
});

testWidgets('scrollTo', (WidgetTester tester) async {
final WebViewController controller = await pumpScrollTestPage(tester);

const Offset testScrollPosition = Offset(123, 321);

// Ensure the start scroll position is not equal to the test position.
expect(await controller.getScrollPosition(), isNot(testScrollPosition));

late ScrollPositionChange lastPositionChange;
await controller.setOnScrollPositionChange(
expectAsyncUntil1(
(ScrollPositionChange contentOffsetChange) {
lastPositionChange = contentOffsetChange;
},
() {
return Offset(lastPositionChange.x, lastPositionChange.y) ==
testScrollPosition;
},
),
);

await controller.scrollTo(
testScrollPosition.dx.toInt(),
testScrollPosition.dy.toInt(),
);
});

testWidgets('scrollBy', (WidgetTester tester) async {
final WebViewController controller = await pumpScrollTestPage(tester);

const Offset testScrollPosition = Offset(123, 321);

// Ensure the start scroll position is not equal to the test position.
expect(await controller.getScrollPosition(), isNot(testScrollPosition));

late ScrollPositionChange lastPositionChange;
await controller.setOnScrollPositionChange(
expectAsyncUntil1(
(ScrollPositionChange contentOffsetChange) {
lastPositionChange = contentOffsetChange;
},
() {
return Offset(lastPositionChange.x, lastPositionChange.y) ==
testScrollPosition;
},
),
);

await controller.scrollTo(0, 0);
await controller.scrollBy(
testScrollPosition.dx.toInt(),
testScrollPosition.dy.toInt(),
);
});
},
// Scroll position is currently not implemented for macOS.
// Flakes on iOS: https://github.com/flutter/flutter/issues/154826
skip: Platform.isMacOS || Platform.isIOS);
skip: Platform.isMacOS);

group('NavigationDelegate', () {
const String blankPage = '<!DOCTYPE html><head></head><body></body></html>';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -758,8 +758,9 @@ Future<void> main() async {
});

group('Programmatic Scroll', () {
testWidgets('setAndGetAndListenScrollPosition',
(WidgetTester tester) async {
Future<PlatformWebViewController> pumpScrollTestPage(
WidgetTester tester,
) async {
const String scrollTestPage = '''
<!DOCTYPE html>
<html>
Expand All @@ -785,28 +786,20 @@ Future<void> main() async {
base64Encode(const Utf8Encoder().convert(scrollTestPage));

final Completer<void> pageLoaded = Completer<void>();
ScrollPositionChange? recordedPosition;
final PlatformWebViewController controller = PlatformWebViewController(
const PlatformWebViewControllerCreationParams(),
);
await controller.setJavaScriptMode(JavaScriptMode.unrestricted);
final PlatformNavigationDelegate delegate = PlatformNavigationDelegate(
const PlatformNavigationDelegateCreationParams(),
);
await delegate.setOnPageFinished((_) => pageLoaded.complete());
await controller.setPlatformNavigationDelegate(delegate);
await controller.setOnScrollPositionChange(
(ScrollPositionChange contentOffsetChange) {
recordedPosition = contentOffsetChange;
});

await controller.loadRequest(
LoadRequestParams(
uri: Uri.parse(
'data:text/html;charset=utf-8;base64,$scrollTestPageBase64',
),
await controller.loadRequest(LoadRequestParams(
uri: Uri.parse(
'data:text/html;charset=utf-8;base64,$scrollTestPageBase64',
),
);
));

await tester.pumpWidget(Builder(
builder: (BuildContext context) {
Expand All @@ -815,37 +808,95 @@ Future<void> main() async {
).build(context);
},
));

await pageLoaded.future;

await tester.pumpAndSettle(const Duration(seconds: 3));

Offset scrollPos = await controller.getScrollPosition();

// Check scrollTo()
const int X_SCROLL = 123;
const int Y_SCROLL = 321;
// Get the initial position; this ensures that scrollTo is actually
// changing something, but also gives the native view's scroll position
// time to settle.
expect(scrollPos.dx, isNot(X_SCROLL));
expect(scrollPos.dy, isNot(Y_SCROLL));
expect(recordedPosition, null);

await controller.scrollTo(X_SCROLL, Y_SCROLL);
scrollPos = await controller.getScrollPosition();
expect(scrollPos.dx, X_SCROLL);
expect(scrollPos.dy, Y_SCROLL);
expect(recordedPosition?.x, X_SCROLL);
expect(recordedPosition?.y, Y_SCROLL);

// Check scrollBy() (on top of scrollTo())
await controller.scrollBy(X_SCROLL, Y_SCROLL);
scrollPos = await controller.getScrollPosition();
expect(scrollPos.dx, X_SCROLL * 2);
expect(scrollPos.dy, Y_SCROLL * 2);
expect(recordedPosition?.x, X_SCROLL * 2);
expect(recordedPosition?.y, Y_SCROLL * 2);
return controller;
}

testWidgets('getScrollPosition', (WidgetTester tester) async {
final PlatformWebViewController controller =
await pumpScrollTestPage(tester);
await controller.setJavaScriptMode(JavaScriptMode.unrestricted);

const Offset testScrollPosition = Offset(123, 321);

// Ensure the start scroll position is not equal to the test position.
expect(await controller.getScrollPosition(), isNot(testScrollPosition));

final Completer<void> testScrollPositionCompleter = Completer<void>();
await controller.setOnScrollPositionChange(
(ScrollPositionChange contentOffsetChange) {
if (Offset(contentOffsetChange.x, contentOffsetChange.y) ==
testScrollPosition) {
testScrollPositionCompleter.complete();
}
},
);

await controller.scrollTo(
testScrollPosition.dx.toInt(),
testScrollPosition.dy.toInt(),
);
await testScrollPositionCompleter.future;

expect(await controller.getScrollPosition(), testScrollPosition);
});

testWidgets('scrollTo', (WidgetTester tester) async {
final PlatformWebViewController controller =
await pumpScrollTestPage(tester);

const Offset testScrollPosition = Offset(123, 321);

// Ensure the start scroll position is not equal to the test position.
expect(await controller.getScrollPosition(), isNot(testScrollPosition));

late ScrollPositionChange lastPositionChange;
await controller.setOnScrollPositionChange(
expectAsyncUntil1(
(ScrollPositionChange contentOffsetChange) {
lastPositionChange = contentOffsetChange;
},
() {
return Offset(lastPositionChange.x, lastPositionChange.y) ==
testScrollPosition;
},
),
);

await controller.scrollTo(
testScrollPosition.dx.toInt(),
testScrollPosition.dy.toInt(),
);
});

testWidgets('scrollBy', (WidgetTester tester) async {
final PlatformWebViewController controller =
await pumpScrollTestPage(tester);

const Offset testScrollPosition = Offset(123, 321);

// Ensure the start scroll position is not equal to the test position.
expect(await controller.getScrollPosition(), isNot(testScrollPosition));

late ScrollPositionChange lastPositionChange;
await controller.setOnScrollPositionChange(
expectAsyncUntil1(
(ScrollPositionChange contentOffsetChange) {
lastPositionChange = contentOffsetChange;
},
() {
return Offset(lastPositionChange.x, lastPositionChange.y) ==
testScrollPosition;
},
),
);

await controller.scrollTo(0, 0);
await controller.scrollBy(
testScrollPosition.dx.toInt(),
testScrollPosition.dy.toInt(),
);
});
});

Expand Down
Loading

0 comments on commit df88c81

Please sign in to comment.