Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit bbc8c9a

Browse files
committed
[web] replace browser-related conditional logic with BrowserEnvironment
1 parent 1e3fe23 commit bbc8c9a

10 files changed

Lines changed: 416 additions & 322 deletions

lib/web_ui/dev/browser.dart

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,50 @@
55
import 'dart:async';
66
import 'dart:convert';
77
import 'dart:io';
8+
import 'dart:math' as math;
89

10+
import 'package:image/image.dart';
911
import 'package:pedantic/pedantic.dart';
1012
import 'package:stack_trace/stack_trace.dart';
13+
import 'package:test_api/src/backend/runtime.dart';
1114
import 'package:typed_data/typed_buffers.dart';
1215

16+
/// Provides the environment for a specific web browser.
17+
abstract class BrowserEnvironment {
18+
/// The [Runtime] used by `package:test` to identify this browser type.
19+
Runtime get packageTestRuntime;
20+
21+
/// The name of the configuration YAML file used to configure `package:test`.
22+
///
23+
/// The configuration file is expected to be a direct child of the `web_ui`
24+
/// directory.
25+
String get packageTestConfigurationYamlFile;
26+
27+
/// Prepares the OS environment to run tests for this browser.
28+
///
29+
/// This may include things like staring web drivers, iOS Simulators, and/or
30+
/// Android emulators.
31+
///
32+
/// Typically the browser environment is prepared once and supports multiple
33+
/// browser instances.
34+
Future<void> prepareEnvironment();
35+
36+
/// Launches a browser instance.
37+
///
38+
/// The browser will be directed to open the provided [url].
39+
///
40+
/// If [debug] is true and the browser supports debugging, launches the
41+
/// browser in debug mode by pausing test execution after the code is loaded
42+
/// but before calling the `main()` function of the test, giving the
43+
/// developer a chance to set breakpoints.
44+
Browser launchBrowserInstance(Uri url, {bool debug = false});
45+
46+
/// Returns the screenshot manager used by this browser.
47+
///
48+
/// If the browser does not support screenshots, returns null.
49+
ScreenshotManager? getScreenshotManager();
50+
}
51+
1352
/// An interface for running browser instances.
1453
///
1554
/// This is intentionally coarse-grained: browsers are controlled primary from
@@ -147,3 +186,18 @@ abstract class Browser {
147186
return onExit.catchError((dynamic _) {});
148187
}
149188
}
189+
190+
/// Interface for capturing screenshots from a browser.
191+
abstract class ScreenshotManager {
192+
/// Capture a screenshot.
193+
///
194+
/// Please read the details for the implementing classes.
195+
Future<Image> capture(math.Rectangle region);
196+
197+
/// Suffix to be added to the end of the filename.
198+
///
199+
/// Example file names:
200+
/// - Chrome, no-suffix: backdrop_filter_clip_moved.actual.png
201+
/// - iOS Safari: backdrop_filter_clip_moved.actual.iOS_Safari.png
202+
String get filenameSuffix;
203+
}

lib/web_ui/dev/chrome.dart

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,43 @@
55
import 'dart:async';
66
import 'dart:convert';
77
import 'dart:io';
8+
import 'dart:math' as math;
89

10+
import 'package:image/image.dart';
911
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;
1015

1116
import 'browser.dart';
1217
import 'chrome_installer.dart';
1318
import 'common.dart';
1419
import 'environment.dart';
1520

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+
1645
/// A class for running an instance of Chrome.
1746
///
1847
/// Most of the communication with the browser is expected to happen via HTTP,
@@ -174,3 +203,64 @@ Future<Uri> getRemoteDebuggerUrl(Uri base) async {
174203
return base;
175204
}
176205
}
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+
}

lib/web_ui/dev/edge.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,34 @@
55
import 'dart:async';
66
import 'dart:io';
77

8+
import 'package:test_api/src/backend/runtime.dart';
9+
810
import 'browser.dart';
911
import 'common.dart';
1012
import 'edge_installation.dart';
1113

14+
/// Provides an environment for the desktop Microsoft Edge (Chromium-based).
15+
class EdgeEnvironment implements BrowserEnvironment {
16+
@override
17+
Browser launchBrowserInstance(Uri url, {bool debug = false}) {
18+
return Edge(url, debug: debug);
19+
}
20+
21+
@override
22+
Runtime get packageTestRuntime => Runtime.internetExplorer;
23+
24+
@override
25+
Future<void> prepareEnvironment() async {
26+
// Edge doesn't need any special prep.
27+
}
28+
29+
@override
30+
ScreenshotManager? getScreenshotManager() => null;
31+
32+
@override
33+
String get packageTestConfigurationYamlFile => 'dart_test_edge.yaml';
34+
}
35+
1236
/// A class for running an instance of Edge.
1337
///
1438
/// Most of the communication with the browser is expected to happen via HTTP,

lib/web_ui/dev/firefox.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,36 @@ import 'dart:io';
88
import 'package:pedantic/pedantic.dart';
99

1010
import 'package:path/path.dart' as path;
11+
import 'package:test_api/src/backend/runtime.dart';
1112
import 'package:test_core/src/util/io.dart';
1213

1314
import 'browser.dart';
1415
import 'common.dart';
1516
import 'environment.dart';
1617
import 'firefox_installer.dart';
1718

19+
/// Provides an environment for the desktop Firefox.
20+
class FirefoxEnvironment implements BrowserEnvironment {
21+
@override
22+
Browser launchBrowserInstance(Uri url, {bool debug = false}) {
23+
return Firefox(url, debug: debug);
24+
}
25+
26+
@override
27+
Runtime get packageTestRuntime => Runtime.firefox;
28+
29+
@override
30+
Future<void> prepareEnvironment() async {
31+
// Firefox doesn't need any special prep.
32+
}
33+
34+
@override
35+
String get packageTestConfigurationYamlFile => 'dart_test_firefox.yaml';
36+
37+
@override
38+
ScreenshotManager? getScreenshotManager() => null;
39+
}
40+
1841
/// A class for running an instance of Firefox.
1942
///
2043
/// Most of the communication with the browser is expected to happen via HTTP,

lib/web_ui/dev/safari.dart

Lines changed: 0 additions & 74 deletions
This file was deleted.

0 commit comments

Comments
 (0)