|
| 1 | +// Copyright 2013 The Flutter Authors. All rights reserved. |
| 2 | +// Use of this source code is governed by a BSD-style license that can be |
| 3 | +// found in the LICENSE file. |
| 4 | + |
| 5 | +// ignore_for_file: avoid_print |
| 6 | + |
| 7 | +import 'dart:async'; |
| 8 | + |
| 9 | +import 'package:flutter/foundation.dart'; |
| 10 | +import 'package:flutter/material.dart'; |
| 11 | +import 'package:flutter_test/flutter_test.dart'; |
| 12 | +import 'package:test_app/home_page.dart' show aboutPageKey, textKey; |
| 13 | +import 'package:test_app/main.dart'; |
| 14 | +import 'package:web/web.dart'; |
| 15 | +import 'package:web_benchmarks/client.dart'; |
| 16 | + |
| 17 | +import 'common.dart'; |
| 18 | + |
| 19 | +/// A class that automates the test web app. |
| 20 | +class Automator { |
| 21 | + Automator({ |
| 22 | + required this.benchmark, |
| 23 | + required this.stopWarmingUpCallback, |
| 24 | + required this.profile, |
| 25 | + }); |
| 26 | + |
| 27 | + /// The current benchmark. |
| 28 | + final BenchmarkName benchmark; |
| 29 | + |
| 30 | + /// A function to call when warm-up is finished. |
| 31 | + /// |
| 32 | + /// This function is intended to ask `Recorder` to mark the warm-up phase |
| 33 | + /// as over. |
| 34 | + final void Function() stopWarmingUpCallback; |
| 35 | + |
| 36 | + /// The profile collected for the running benchmark |
| 37 | + final Profile profile; |
| 38 | + |
| 39 | + /// Whether the automation has ended. |
| 40 | + bool finished = false; |
| 41 | + |
| 42 | + /// A widget controller for automation. |
| 43 | + late LiveWidgetController controller; |
| 44 | + |
| 45 | + Widget createWidget() { |
| 46 | + Future<void>.delayed(const Duration(milliseconds: 400), automate); |
| 47 | + return const MyApp(); |
| 48 | + } |
| 49 | + |
| 50 | + Future<void> automate() async { |
| 51 | + await warmUp(); |
| 52 | + |
| 53 | + switch (benchmark) { |
| 54 | + case BenchmarkName.appNavigate: |
| 55 | + await _handleAppNavigate(); |
| 56 | + case BenchmarkName.appScroll: |
| 57 | + await _handleAppScroll(); |
| 58 | + case BenchmarkName.appTap: |
| 59 | + await _handleAppTap(); |
| 60 | + case BenchmarkName.simpleCompilationCheck: |
| 61 | + _handleSimpleCompilationCheck(); |
| 62 | + case BenchmarkName.simpleInitialPageCheck: |
| 63 | + _handleSimpleInitialPageCheck(); |
| 64 | + } |
| 65 | + |
| 66 | + // At the end of the test, mark as finished. |
| 67 | + finished = true; |
| 68 | + } |
| 69 | + |
| 70 | + /// Warm up the animation. |
| 71 | + Future<void> warmUp() async { |
| 72 | + // Let animation stop. |
| 73 | + await animationStops(); |
| 74 | + |
| 75 | + // Set controller. |
| 76 | + controller = LiveWidgetController(WidgetsBinding.instance); |
| 77 | + |
| 78 | + await controller.pumpAndSettle(); |
| 79 | + |
| 80 | + // When warm-up finishes, inform the recorder. |
| 81 | + stopWarmingUpCallback(); |
| 82 | + } |
| 83 | + |
| 84 | + Future<void> _handleAppNavigate() async { |
| 85 | + for (int i = 0; i < 10; ++i) { |
| 86 | + print('Testing round $i...'); |
| 87 | + await controller.tap(find.byKey(aboutPageKey)); |
| 88 | + await animationStops(); |
| 89 | + await controller.tap(find.byType(BackButton)); |
| 90 | + await animationStops(); |
| 91 | + } |
| 92 | + } |
| 93 | + |
| 94 | + Future<void> _handleAppScroll() async { |
| 95 | + final ScrollableState scrollable = |
| 96 | + Scrollable.of(find.byKey(textKey).evaluate().single); |
| 97 | + await scrollable.position.animateTo( |
| 98 | + 30000, |
| 99 | + curve: Curves.linear, |
| 100 | + duration: const Duration(seconds: 20), |
| 101 | + ); |
| 102 | + } |
| 103 | + |
| 104 | + Future<void> _handleAppTap() async { |
| 105 | + for (int i = 0; i < 10; ++i) { |
| 106 | + print('Testing round $i...'); |
| 107 | + await controller.tap(find.byIcon(Icons.add)); |
| 108 | + await animationStops(); |
| 109 | + } |
| 110 | + } |
| 111 | + |
| 112 | + void _handleSimpleCompilationCheck() { |
| 113 | + // Record whether we are in wasm mode or not. Ideally, we'd have a more |
| 114 | + // first-class way to add metadata like this, but this will work for us to |
| 115 | + // pass information about the environment back to the server for the |
| 116 | + // purposes of our own tests. |
| 117 | + profile.extraData['isWasm'] = kIsWasm ? 1 : 0; |
| 118 | + } |
| 119 | + |
| 120 | + void _handleSimpleInitialPageCheck() { |
| 121 | + // Record whether the URL contains the expected initial page so we can |
| 122 | + // verify the behavior of setting the `initialPage` on the benchmark server. |
| 123 | + final bool containsExpectedPage = |
| 124 | + window.location.toString().contains(testBenchmarkInitialPage); |
| 125 | + profile.extraData['expectedUrl'] = containsExpectedPage ? 1 : 0; |
| 126 | + } |
| 127 | +} |
| 128 | + |
| 129 | +const Duration _animationCheckingInterval = Duration(milliseconds: 50); |
| 130 | + |
| 131 | +Future<void> animationStops() async { |
| 132 | + if (!WidgetsBinding.instance.hasScheduledFrame) { |
| 133 | + return; |
| 134 | + } |
| 135 | + |
| 136 | + final Completer<void> stopped = Completer<void>(); |
| 137 | + |
| 138 | + Timer.periodic(_animationCheckingInterval, (Timer timer) { |
| 139 | + if (!WidgetsBinding.instance.hasScheduledFrame) { |
| 140 | + stopped.complete(); |
| 141 | + timer.cancel(); |
| 142 | + } |
| 143 | + }); |
| 144 | + |
| 145 | + await stopped.future; |
| 146 | +} |
0 commit comments