| // Copyright 2013 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| import 'dart:async'; | |
| import 'dart:convert'; | |
| import 'dart:io'; | |
| import 'dart:math'; | |
| import 'package:image/image.dart'; | |
| import 'package:webdriver/async_io.dart' show WebDriver, createDriver; | |
| import 'browser.dart'; | |
| abstract class WebDriverBrowserEnvironment extends BrowserEnvironment { | |
| late int portNumber; | |
| late final Process _driverProcess; | |
| Future<Process> spawnDriverProcess(); | |
| Uri get driverUri; | |
| /// Finds and returns an unused port on the test host in the local port range. | |
| Future<int> pickUnusedPort() async { | |
| // Use bind to allocate an unused port, then unbind from that port to | |
| // make it available for use. | |
| final ServerSocket socket = await ServerSocket.bind('localhost', 0); | |
| final int port = socket.port; | |
| await socket.close(); | |
| return port; | |
| } | |
| @override | |
| Future<void> prepare() async { | |
| portNumber = await pickUnusedPort(); | |
| _driverProcess = await spawnDriverProcess(); | |
| _driverProcess.stderr | |
| .transform(utf8.decoder) | |
| .transform(const LineSplitter()) | |
| .listen((String error) { | |
| print('[Webdriver][Error] $error'); | |
| }); | |
| _driverProcess.stdout | |
| .transform(utf8.decoder) | |
| .transform(const LineSplitter()) | |
| .listen((String log) { | |
| print('[Webdriver] $log'); | |
| }); | |
| } | |
| @override | |
| Future<void> cleanup() async { | |
| _driverProcess.kill(); | |
| } | |
| @override | |
| Future<Browser> launchBrowserInstance(Uri url, {bool debug = false}) async { | |
| while (true) { | |
| try { | |
| final WebDriver driver = await createDriver( | |
| uri: driverUri, desired: <String, dynamic>{'browserName': packageTestRuntime.identifier}); | |
| return WebDriverBrowser(driver, url); | |
| } on SocketException { | |
| // Sometimes we may try to connect before the web driver port is ready. | |
| // So we should retry here. Note that if there was some issue with the | |
| // webdriver process, we may loop infinitely here, so we're relying on | |
| // the test timeout to kill us if it takes too long to connect. | |
| print('Failed to connect to webdriver process. Retrying in 100 ms'); | |
| await Future<void>.delayed(const Duration(milliseconds: 100)); | |
| } catch (exception) { | |
| rethrow; | |
| } | |
| } | |
| } | |
| } | |
| class WebDriverBrowser extends Browser { | |
| WebDriverBrowser(this._driver, this._url) { | |
| _driver.get(_url); | |
| } | |
| final WebDriver _driver; | |
| final Uri _url; | |
| final Completer<void> _onExitCompleter = Completer<void>(); | |
| @override | |
| Future<void> close() async { | |
| await (await _driver.window).close(); | |
| if (!_onExitCompleter.isCompleted) { | |
| _onExitCompleter.complete(); | |
| } | |
| } | |
| @override | |
| Future<void> get onExit => _onExitCompleter.future; | |
| @override | |
| bool get supportsScreenshots => true; | |
| @override | |
| Future<Image> captureScreenshot(Rectangle<num> region) async { | |
| final Image image = decodePng(await _driver.captureScreenshotAsList())!; | |
| return copyCrop(image, region.left.round(), region.top.round(), | |
| region.width.round(), region.height.round()); | |
| } | |
| } |