Unverified Commit 2fa03438 authored by Yegor's avatar Yegor Committed by GitHub

add web_long_running_tests shard containing long-running web tests (#67324)

parent 7b0f38b1
...@@ -128,10 +128,8 @@ Future<Command> startCommand(String executable, List<String> arguments, { ...@@ -128,10 +128,8 @@ Future<Command> startCommand(String executable, List<String> arguments, {
.transform(const Utf8Encoder()); .transform(const Utf8Encoder());
switch (outputMode) { switch (outputMode) {
case OutputMode.print: case OutputMode.print:
await Future.wait<void>(<Future<void>>[ stdoutSource.listen(io.stdout.add);
io.stdout.addStream(stdoutSource), process.stderr.listen(io.stderr.add);
io.stderr.addStream(process.stderr),
]);
break; break;
case OutputMode.capture: case OutputMode.capture:
savedStdout = stdoutSource.toList(); savedStdout = stdoutSource.toList();
......
...@@ -75,6 +75,11 @@ int get webShardCount => Platform.environment.containsKey('WEB_SHARD_COUNT') ...@@ -75,6 +75,11 @@ int get webShardCount => Platform.environment.containsKey('WEB_SHARD_COUNT')
? int.parse(Platform.environment['WEB_SHARD_COUNT']) ? int.parse(Platform.environment['WEB_SHARD_COUNT'])
: 8; : 8;
/// The number of shards the long-running Web tests are split into.
///
/// WARNING: this number must match the shard count in LUCI configs.
const int kWebLongRunningTestShardCount = 3;
/// Tests that we don't run on Web for various reasons. /// Tests that we don't run on Web for various reasons.
// //
// TODO(yjbanov): we're getting rid of this as part of https://github.com/flutter/flutter/projects/60 // TODO(yjbanov): we're getting rid of this as part of https://github.com/flutter/flutter/projects/60
...@@ -122,6 +127,7 @@ Future<void> main(List<String> args) async { ...@@ -122,6 +127,7 @@ Future<void> main(List<String> args) async {
'tool_tests': _runToolTests, 'tool_tests': _runToolTests,
'web_tests': _runWebUnitTests, 'web_tests': _runWebUnitTests,
'web_integration_tests': _runWebIntegrationTests, 'web_integration_tests': _runWebIntegrationTests,
'web_long_running_tests': _runWebLongRunningTests,
}); });
} on ExitException catch (error) { } on ExitException catch (error) {
error.apply(); error.apply();
...@@ -813,6 +819,125 @@ Future<void> _runWebUnitTests() async { ...@@ -813,6 +819,125 @@ Future<void> _runWebUnitTests() async {
await selectSubshard(subshards); await selectSubshard(subshards);
} }
/// Coarse-grained integration tests running on the Web.
///
/// These tests are sharded into [kWebLongRunningTestShardCount] shards.
Future<void> _runWebLongRunningTests() async {
final List<ShardRunner> tests = <ShardRunner>[
() => _runGalleryE2eWebTest('debug'),
() => _runGalleryE2eWebTest('debug', canvasKit: true),
() => _runGalleryE2eWebTest('profile'),
() => _runGalleryE2eWebTest('profile', canvasKit: true),
() => _runGalleryE2eWebTest('release'),
() => _runGalleryE2eWebTest('release', canvasKit: true),
].map(_withChromeDriver).toList();
await _selectIndexedSubshard(tests, kWebLongRunningTestShardCount);
}
// The `chromedriver` process created by this test.
//
// If an existing chromedriver is already available on port 4444, the existing
// process is reused and this variable remains null.
Command _chromeDriver;
/// Creates a shard runner that runs the given [originalRunner] with ChromeDriver
/// enabled.
ShardRunner _withChromeDriver(ShardRunner originalRunner) {
return () async {
try {
await _ensureChromeDriverIsRunning();
await originalRunner();
} finally {
await _stopChromeDriver();
}
};
}
Future<bool> _isChromeDriverRunning() async {
try {
(await Socket.connect('localhost', 4444)).destroy();
return true;
} on SocketException {
return false;
}
}
Future<void> _ensureChromeDriverIsRunning() async {
// If we cannot connect to ChromeDriver, assume it is not running. Launch it.
if (!await _isChromeDriverRunning()) {
print('Starting chromedriver');
// Assume chromedriver is in the PATH.
_chromeDriver = await startCommand(
'chromedriver',
<String>['--port=4444'],
);
while (!await _isChromeDriverRunning()) {
await Future<void>.delayed(const Duration(milliseconds: 100));
print('Waiting for chromedriver to start up.');
}
}
final HttpClient client = HttpClient();
final Uri chromeDriverUrl = Uri.parse('http://localhost:4444/status');
final HttpClientRequest request = await client.getUrl(chromeDriverUrl);
final HttpClientResponse response = await request.close();
final Map<String, dynamic> webDriverStatus = json.decode(await response.transform(utf8.decoder).join('')) as Map<String, dynamic>;
client.close();
final bool webDriverReady = webDriverStatus['value']['ready'] as bool;
if (!webDriverReady) {
throw Exception('WebDriver not available.');
}
}
Future<void> _stopChromeDriver() async {
if (_chromeDriver == null) {
return;
}
_chromeDriver.process.kill();
while (await _isChromeDriverRunning()) {
await Future<void>.delayed(const Duration(milliseconds: 100));
print('Waiting for chromedriver to stop.');
}
}
/// Exercises the old gallery in a browser for a long period of time, looking
/// for memory leaks and dangling pointers.
///
/// This is not a performance test.
///
/// If [canvasKit] is set to true, runs the test in CanvasKit mode.
///
/// The test is written using `package:integration_test` (despite the "e2e" in
/// the name, which is there for historic reasons).
Future<void> _runGalleryE2eWebTest(String buildMode, { bool canvasKit = false }) async {
print('${green}Running flutter_gallery integration test in --$buildMode using ${canvasKit ? 'CanvasKit' : 'HTML'} renderer.$reset');
final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'flutter_gallery');
await runCommand(
flutter,
<String>[ 'clean' ],
workingDirectory: testAppDirectory,
);
await runCommand(
flutter,
<String>[
'drive',
if (canvasKit)
'--dart-define=FLUTTER_WEB_USE_SKIA=true',
'--driver=test_driver/transitions_perf_e2e_test.dart',
'--target=test_driver/transitions_perf_e2e.dart',
'--browser-name=chrome',
'-d',
'web-server',
'--$buildMode',
],
workingDirectory: testAppDirectory,
environment: <String, String>{
'FLUTTER_WEB': 'true',
},
);
print('${green}Integration test passed.$reset');
}
Future<void> _runWebIntegrationTests() async { Future<void> _runWebIntegrationTests() async {
await _runWebStackTraceTest('profile', 'lib/stack_trace.dart'); await _runWebStackTraceTest('profile', 'lib/stack_trace.dart');
await _runWebStackTraceTest('release', 'lib/stack_trace.dart'); await _runWebStackTraceTest('release', 'lib/stack_trace.dart');
......
...@@ -5,12 +5,21 @@ ...@@ -5,12 +5,21 @@
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_gallery/demo_lists.dart'; import 'package:flutter_gallery/demo_lists.dart';
const List<String> kSkippedDemos = <String>[]; /// The demos we don't run as part of the integraiton test.
///
/// Demo names are formatted as 'DEMO_NAME@DEMO_CATEGORY' (see
/// `demo_lists.dart` for more examples).
final List<String> kSkippedDemos = <String>[
// The CI uses Chromium, which lacks the video codecs to run this demo.
if (kIsWeb)
'Video@Media',
];
/// Scrolls each demo menu item into view, launches it, then returns to the /// Scrolls each demo menu item into view, launches it, then returns to the
/// home screen twice. /// home screen twice.
......
...@@ -14,8 +14,6 @@ import 'package:flutter_gallery/demo_lists.dart'; ...@@ -14,8 +14,6 @@ import 'package:flutter_gallery/demo_lists.dart';
import 'run_demos.dart'; import 'run_demos.dart';
const List<String> kSkippedDemos = <String>[];
// All of the gallery demos, identified as "title@category". // All of the gallery demos, identified as "title@category".
// //
// These names are reported by the test app, see _handleMessages() // These names are reported by the test app, see _handleMessages()
......
...@@ -187,7 +187,11 @@ class WebFlutterDriver extends FlutterDriver { ...@@ -187,7 +187,11 @@ class WebFlutterDriver extends FlutterDriver {
class FlutterWebConnection { class FlutterWebConnection {
/// Creates a FlutterWebConnection with WebDriver /// Creates a FlutterWebConnection with WebDriver
/// and whether the WebDriver supports timeline action. /// and whether the WebDriver supports timeline action.
FlutterWebConnection(this._driver, this.supportsTimelineAction); FlutterWebConnection(this._driver, this.supportsTimelineAction) {
_driver.logs.get(async_io.LogType.browser).listen((async_io.LogEntry entry) {
print('[${entry.level}]: ${entry.message}');
});
}
final async_io.WebDriver _driver; final async_io.WebDriver _driver;
......
...@@ -186,7 +186,10 @@ Map<String, dynamic> getDesiredCapabilities(Browser browser, bool headless, [Str ...@@ -186,7 +186,10 @@ Map<String, dynamic> getDesiredCapabilities(Browser browser, bool headless, [Str
return <String, dynamic>{ return <String, dynamic>{
'acceptInsecureCerts': true, 'acceptInsecureCerts': true,
'browserName': 'chrome', 'browserName': 'chrome',
'goog:loggingPrefs': <String, String>{ async_io.LogType.performance: 'ALL'}, 'goog:loggingPrefs': <String, String>{
async_io.LogType.browser: 'INFO',
async_io.LogType.performance: 'ALL',
},
'chromeOptions': <String, dynamic>{ 'chromeOptions': <String, dynamic>{
if (chromeBinary != null) if (chromeBinary != null)
'binary': chromeBinary, 'binary': chromeBinary,
......
...@@ -12,7 +12,10 @@ void main() { ...@@ -12,7 +12,10 @@ void main() {
final Map<String, dynamic> expected = <String, dynamic>{ final Map<String, dynamic> expected = <String, dynamic>{
'acceptInsecureCerts': true, 'acceptInsecureCerts': true,
'browserName': 'chrome', 'browserName': 'chrome',
'goog:loggingPrefs': <String, String>{ sync_io.LogType.performance: 'ALL'}, 'goog:loggingPrefs': <String, String>{
sync_io.LogType.browser: 'INFO',
sync_io.LogType.performance: 'ALL',
},
'chromeOptions': <String, dynamic>{ 'chromeOptions': <String, dynamic>{
'w3c': false, 'w3c': false,
'args': <String>[ 'args': <String>[
...@@ -44,7 +47,10 @@ void main() { ...@@ -44,7 +47,10 @@ void main() {
final Map<String, dynamic> expected = <String, dynamic>{ final Map<String, dynamic> expected = <String, dynamic>{
'acceptInsecureCerts': true, 'acceptInsecureCerts': true,
'browserName': 'chrome', 'browserName': 'chrome',
'goog:loggingPrefs': <String, String>{ sync_io.LogType.performance: 'ALL'}, 'goog:loggingPrefs': <String, String>{
sync_io.LogType.browser: 'INFO',
sync_io.LogType.performance: 'ALL',
},
'chromeOptions': <String, dynamic>{ 'chromeOptions': <String, dynamic>{
'binary': chromeBinary, 'binary': chromeBinary,
'w3c': false, 'w3c': false,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment