|
5 | 5 | import 'dart:async'; |
6 | 6 | import 'dart:convert'; |
7 | 7 | import 'dart:io'; |
| 8 | +import 'dart:math' as math; |
8 | 9 |
|
| 10 | +import 'package:image/image.dart'; |
9 | 11 | import 'package:pedantic/pedantic.dart'; |
| 12 | +import 'package:test_api/src/backend/runtime.dart'; |
| 13 | +import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart' |
| 14 | + as wip; |
10 | 15 |
|
11 | 16 | import 'browser.dart'; |
12 | 17 | import 'chrome_installer.dart'; |
13 | 18 | import 'common.dart'; |
14 | 19 | import 'environment.dart'; |
15 | 20 |
|
| 21 | +/// Provides an environment for desktop Chrome. |
| 22 | +class ChromeEnvironment implements BrowserEnvironment { |
| 23 | + @override |
| 24 | + Browser launchBrowserInstance(Uri url, {bool debug = false}) { |
| 25 | + return Chrome(url, debug: debug); |
| 26 | + } |
| 27 | + |
| 28 | + @override |
| 29 | + Runtime get packageTestRuntime => Runtime.chrome; |
| 30 | + |
| 31 | + @override |
| 32 | + Future<void> prepareEnvironment() async { |
| 33 | + // Chrome doesn't need any special prep. |
| 34 | + } |
| 35 | + |
| 36 | + @override |
| 37 | + ScreenshotManager? getScreenshotManager() { |
| 38 | + return ChromeScreenshotManager(); |
| 39 | + } |
| 40 | + |
| 41 | + @override |
| 42 | + String get packageTestConfigurationYamlFile => 'dart_test_chrome.yaml'; |
| 43 | +} |
| 44 | + |
16 | 45 | /// A class for running an instance of Chrome. |
17 | 46 | /// |
18 | 47 | /// Most of the communication with the browser is expected to happen via HTTP, |
@@ -174,3 +203,64 @@ Future<Uri> getRemoteDebuggerUrl(Uri base) async { |
174 | 203 | return base; |
175 | 204 | } |
176 | 205 | } |
| 206 | + |
| 207 | +/// [ScreenshotManager] implementation for Chrome. |
| 208 | +/// |
| 209 | +/// This manager can be used for both macOS and Linux. |
| 210 | +// TODO: https://github.com/flutter/flutter/issues/65673 |
| 211 | +class ChromeScreenshotManager extends ScreenshotManager { |
| 212 | + String get filenameSuffix => ''; |
| 213 | + |
| 214 | + /// Capture a screenshot of the web content. |
| 215 | + /// |
| 216 | + /// Uses Webkit Inspection Protocol server's `captureScreenshot` API. |
| 217 | + /// |
| 218 | + /// [region] is used to decide which part of the web content will be used in |
| 219 | + /// test image. It includes starting coordinate x,y as well as height and |
| 220 | + /// width of the area to capture. |
| 221 | + Future<Image> capture(math.Rectangle? region) async { |
| 222 | + final wip.ChromeConnection chromeConnection = |
| 223 | + wip.ChromeConnection('localhost', kDevtoolsPort); |
| 224 | + final wip.ChromeTab? chromeTab = await chromeConnection.getTab( |
| 225 | + (wip.ChromeTab chromeTab) => chromeTab.url.contains('localhost')); |
| 226 | + if (chromeTab == null) { |
| 227 | + throw StateError( |
| 228 | + 'Failed locate Chrome tab with the test page', |
| 229 | + ); |
| 230 | + } |
| 231 | + final wip.WipConnection wipConnection = await chromeTab.connect(); |
| 232 | + |
| 233 | + Map<String, dynamic>? captureScreenshotParameters = null; |
| 234 | + if (region != null) { |
| 235 | + captureScreenshotParameters = <String, dynamic>{ |
| 236 | + 'format': 'png', |
| 237 | + 'clip': <String, dynamic>{ |
| 238 | + 'x': region.left, |
| 239 | + 'y': region.top, |
| 240 | + 'width': region.width, |
| 241 | + 'height': region.height, |
| 242 | + 'scale': |
| 243 | + // This is NOT the DPI of the page, instead it's the "zoom level". |
| 244 | + 1, |
| 245 | + }, |
| 246 | + }; |
| 247 | + } |
| 248 | + |
| 249 | + // Setting hardware-independent screen parameters: |
| 250 | + // https://chromedevtools.github.io/devtools-protocol/tot/Emulation |
| 251 | + await wipConnection |
| 252 | + .sendCommand('Emulation.setDeviceMetricsOverride', <String, dynamic>{ |
| 253 | + 'width': kMaxScreenshotWidth, |
| 254 | + 'height': kMaxScreenshotHeight, |
| 255 | + 'deviceScaleFactor': 1, |
| 256 | + 'mobile': false, |
| 257 | + }); |
| 258 | + final wip.WipResponse response = await wipConnection.sendCommand( |
| 259 | + 'Page.captureScreenshot', captureScreenshotParameters); |
| 260 | + |
| 261 | + final Image screenshot = |
| 262 | + decodePng(base64.decode(response.result!['data'] as String))!; |
| 263 | + |
| 264 | + return screenshot; |
| 265 | + } |
| 266 | +} |
0 commit comments