blob: 253d1244308fb4710d5b42f532cf8426628c4707 [file] [log] [blame]
// 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);
_activateLoopFuture = () async {
// Some browsers (i.e. Safari) stop actually executing our unit tests if
// their window is occluded or non-visible. This hacky solution of
// re-activating the window every two seconds prevents our unit tests from
// stalling out if the window becomes obscured by some other thing that
// may appear on the system.
while (!_shouldStopActivating) {
await (await _driver.window).setAsActive();
await Future<void>.delayed(const Duration(seconds: 2));
}
}();
}
final WebDriver _driver;
final Uri _url;
final Completer<void> _onExitCompleter = Completer<void>();
bool _shouldStopActivating = false;
late final Future<void> _activateLoopFuture;
@override
Future<void> close() async {
_shouldStopActivating = true;
await _activateLoopFuture;
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());
}
}