Skip to content

Commit 5ef945e

Browse files
committed
Take screenshots from pub_integration tests (store locally).
1 parent d3d6ff7 commit 5ef945e

File tree

10 files changed

+146
-6
lines changed

10 files changed

+146
-6
lines changed

pkg/pub_integration/lib/src/pub_puppeteer_helpers.dart

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44
import 'dart:async';
5-
import 'dart:io';
65

76
import 'package:puppeteer/puppeteer.dart';
87

8+
import 'screenshot_utils.dart';
99
import 'test_browser.dart';
1010

1111
const webmastersReadonlyScope =
@@ -46,10 +46,6 @@ Future<ListingPageInfo> listingPageInfo(Page page) async {
4646
}
4747

4848
extension PubPageExt on Page {
49-
Future<void> saveScreenshot(String path) async {
50-
await File(path).writeAsBytes(await screenshot());
51-
}
52-
5349
Future<void> waitFocusAndType(String selector, String text) async {
5450
await waitForSelector(selector, timeout: Duration(seconds: 5));
5551
await focus(selector);
@@ -221,7 +217,7 @@ extension PubPageExt on Page {
221217
return;
222218
}
223219
}
224-
await saveScreenshot('layout-timeout.png');
220+
await writeScreenshotToFile('layout-timeout.png');
225221
throw TimeoutException('Did not have a stable layout in $timeout.');
226222
}
227223

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:io';
6+
7+
import 'package:path/path.dart' as p;
8+
import 'package:puppeteer/puppeteer.dart';
9+
10+
// Default screen with 16:10 ratio.
11+
final desktopDeviceViewport = DeviceViewport(width: 1280, height: 800);
12+
13+
final _screenshotDir = Platform.environment['PUB_SCREENSHOT_DIR'];
14+
final _isScreenshotDirSet =
15+
_screenshotDir != null && _screenshotDir!.isNotEmpty;
16+
17+
// Set this variable to enable screenshot files to be updated with new takes.
18+
// The default is to throw an exception to prevent accidental overrides from
19+
// separate tests.
20+
final _allowScreeshotUpdates =
21+
Platform.environment['PUB_SCREENSHOT_UPDATE'] == '1';
22+
23+
// Note: The default values are the last, so we don't need reset
24+
// the original values after taking the screenshots.
25+
final _themes = ['dark', 'light'];
26+
final _viewports = {
27+
'mobile': DeviceViewport(width: 400, height: 800),
28+
'tablet': DeviceViewport(width: 768, height: 1024),
29+
'desktop': desktopDeviceViewport,
30+
};
31+
32+
extension ScreenshotPageExt on Page {
33+
Future<void> writeScreenshotToFile(String path) async {
34+
await File(path).writeAsBytes(await screenshot());
35+
}
36+
37+
/// Takes screenshots **if** `PUB_SCREENSHOT_DIR` environment variable is set.
38+
///
39+
/// Iterates over viewports and themes, and generates screenshot files with the
40+
/// following pattern:
41+
/// - `PUB_SCREENSHOT_DIR/$prefix-desktop-dark.png`
42+
/// - `PUB_SCREENSHOT_DIR/$prefix-desktop-light.png`
43+
/// - `PUB_SCREENSHOT_DIR/$prefix-mobile-dark.png`
44+
/// - `PUB_SCREENSHOT_DIR/$prefix-mobile-light.png`
45+
/// - `PUB_SCREENSHOT_DIR/$prefix-tablet-dark.png`
46+
/// - `PUB_SCREENSHOT_DIR/$prefix-tablet-light.png`
47+
Future<void> takeScreenshots({
48+
required String selector,
49+
required String prefix,
50+
}) async {
51+
final handle = await $(selector);
52+
await handle.takeScreenshots(prefix);
53+
}
54+
}
55+
56+
extension ScreenshotElementHandleExt on ElementHandle {
57+
/// Takes screenshots **if** `PUB_SCREENSHOT_DIR` environment variable is set.
58+
///
59+
/// Iterates over viewports and themes, and generates screenshot files with the
60+
/// following pattern:
61+
/// - `PUB_SCREENSHOT_DIR/$prefix-desktop-dark.png`
62+
/// - `PUB_SCREENSHOT_DIR/$prefix-desktop-light.png`
63+
/// - `PUB_SCREENSHOT_DIR/$prefix-mobile-dark.png`
64+
/// - `PUB_SCREENSHOT_DIR/$prefix-mobile-light.png`
65+
/// - `PUB_SCREENSHOT_DIR/$prefix-tablet-dark.png`
66+
/// - `PUB_SCREENSHOT_DIR/$prefix-tablet-light.png`
67+
Future<void> takeScreenshots(String prefix) async {
68+
final body = await page.$('body');
69+
final bodyClassAttr =
70+
(await body.evaluate('el => el.getAttribute("class")')) as String;
71+
final bodyClasses = bodyClassAttr.split(' ');
72+
73+
for (final vp in _viewports.entries) {
74+
await page.setViewport(vp.value);
75+
76+
for (final theme in _themes) {
77+
final newClasses = [
78+
...bodyClasses.where((c) => !c.endsWith('-theme')),
79+
'$theme-theme',
80+
];
81+
await body.evaluate('(el, v) => el.setAttribute("class", v)',
82+
args: [newClasses.join(' ')]);
83+
84+
// The presence of the element is verified, continue only if screenshots are enabled.
85+
if (!_isScreenshotDirSet) continue;
86+
87+
final path = p.join(_screenshotDir!, '$prefix-${vp.key}-$theme.png');
88+
await _writeScreenshotToFile(path);
89+
}
90+
}
91+
}
92+
93+
Future<void> _writeScreenshotToFile(String path) async {
94+
final file = File(path);
95+
final exists = await file.exists();
96+
if (exists && !_allowScreeshotUpdates) {
97+
throw Exception('Screenshot update is detected in: $path');
98+
}
99+
await file.parent.create(recursive: true);
100+
await File(path).writeAsBytes(await screenshot());
101+
}
102+
}

pkg/pub_integration/lib/src/test_browser.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import 'package:_pub_shared/validation/html/html_validation.dart';
99
import 'package:path/path.dart' as p;
1010
import 'package:puppeteer/puppeteer.dart';
1111

12+
import 'screenshot_utils.dart';
13+
1214
/// Creates and tracks the headless Chrome environment, its temp directories and
1315
/// and uncaught exceptions.
1416
class TestBrowser {
@@ -90,6 +92,7 @@ class TestBrowser {
9092
userDataDir: userDataDir.path,
9193
headless: !_displayBrowser,
9294
devTools: false,
95+
defaultViewport: desktopDeviceViewport,
9396
);
9497

9598
// Update the default permissions like clipboard access.

pkg/pub_integration/test/browser_test.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'dart:convert';
77
import 'package:http/http.dart' as http;
88
import 'package:pub_integration/src/fake_test_context_provider.dart';
99
import 'package:pub_integration/src/pub_puppeteer_helpers.dart';
10+
import 'package:pub_integration/src/screenshot_utils.dart';
1011
import 'package:pub_integration/src/test_browser.dart';
1112
import 'package:puppeteer/puppeteer.dart';
1213
import 'package:test/test.dart';
@@ -69,6 +70,8 @@ void main() {
6970
await user.withBrowserPage(
7071
(page) async {
7172
await page.gotoOrigin('/packages/retry');
73+
await page.takeScreenshots(
74+
prefix: 'package-page/readme-page', selector: 'body');
7275

7376
// check pub score
7477
final pubScoreElem = await page
@@ -94,6 +97,14 @@ void main() {
9497

9598
await page.gotoOrigin('/packages/retry/license');
9699
await checkHeaderTitle();
100+
101+
await page.gotoOrigin('/packages/retry/versions');
102+
await page.takeScreenshots(
103+
prefix: 'package-page/versions-page', selector: 'body');
104+
105+
await page.gotoOrigin('/packages/retry/score');
106+
await page.takeScreenshots(
107+
prefix: 'package-page/score-page', selector: 'body');
97108
},
98109
);
99110
});

pkg/pub_integration/test/fake_sign_in_test.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:convert';
66

77
import 'package:http/http.dart' as http;
88
import 'package:pub_integration/src/fake_test_context_provider.dart';
9+
import 'package:pub_integration/src/screenshot_utils.dart';
910
import 'package:pub_integration/src/test_browser.dart';
1011
import 'package:test/test.dart';
1112

@@ -50,6 +51,13 @@ void main() {
5051
String? firstSessionId;
5152
// sign-in page
5253
await browserSession.withBrowserPage((page) async {
54+
await page.gotoOrigin('/');
55+
await page.takeScreenshots(
56+
selector: '.site-header',
57+
prefix: 'landing-page/site-header-public');
58+
await page.takeScreenshots(
59+
selector: '.site-footer', prefix: 'landing-page/site-footer');
60+
5361
{
5462
final rs = await page.gotoOrigin('/[email protected]');
5563
final cookies = await page.cookies();
@@ -63,6 +71,9 @@ void main() {
6371
firstSessionId =
6472
cookies.firstWhere((c) => c.name == 'PUB_SID_INSECURE').value;
6573
}
74+
await page.takeScreenshots(
75+
selector: '.site-header',
76+
prefix: 'landing-page/site-header-authenticated');
6677

6778
// same user sign-in with redirect
6879
{

pkg/pub_integration/test/pkg_admin_page_test.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'dart:io' show Platform;
99
import 'package:http/http.dart' as http;
1010
import 'package:pub_integration/src/fake_test_context_provider.dart';
1111
import 'package:pub_integration/src/pub_puppeteer_helpers.dart';
12+
import 'package:pub_integration/src/screenshot_utils.dart';
1213
import 'package:pub_integration/src/test_browser.dart';
1314
import 'package:test/test.dart';
1415

@@ -52,6 +53,8 @@ void main() {
5253
// github publishing
5354
await user.withBrowserPage((page) async {
5455
await page.gotoOrigin('/packages/test_pkg/admin');
56+
await page.takeScreenshots(
57+
prefix: 'package-page/admin-page', selector: 'body');
5558

5659
await page.waitAndClick('#-pkg-admin-automated-github-enabled');
5760
await page.waitForLayout([

pkg/pub_integration/test/report_test.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:_pub_shared/data/admin_api.dart';
88
import 'package:http/http.dart' as http;
99
import 'package:pub_integration/src/fake_test_context_provider.dart';
1010
import 'package:pub_integration/src/pub_puppeteer_helpers.dart';
11+
import 'package:pub_integration/src/screenshot_utils.dart';
1112
import 'package:pub_integration/src/test_browser.dart';
1213
import 'package:test/test.dart';
1314

@@ -58,6 +59,9 @@ void main() {
5859
(page) async {
5960
await page.gotoOrigin('/report?subject=package:oxygen');
6061
await page.waitAndClick('.report-page-direct-report');
62+
await page.takeScreenshots(
63+
prefix: 'report-page/direct-report',
64+
selector: '#report-page-form');
6165
await page.waitFocusAndType('#report-email', '[email protected]');
6266
await page.waitFocusAndType(
6367
'#report-message', 'Huston, we have a problem.');

pkg/pub_integration/test/search_completition_test.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:convert';
66

77
import 'package:http/http.dart' as http;
88
import 'package:pub_integration/src/fake_test_context_provider.dart';
9+
import 'package:pub_integration/src/screenshot_utils.dart';
910
import 'package:pub_integration/src/test_browser.dart';
1011
import 'package:puppeteer/puppeteer.dart';
1112
import 'package:test/test.dart';
@@ -47,6 +48,8 @@ void main() {
4748
await page.gotoOrigin('/');
4849
await page.keyboard.type('is:un');
4950
await Future.delayed(Duration(milliseconds: 200));
51+
await page.takeScreenshots(
52+
selector: 'body', prefix: 'landing-page/search-completion');
5053
await page.keyboard.press(Key.enter);
5154
await Future.delayed(Duration(milliseconds: 200));
5255
await page.keyboard.press(Key.enter);
@@ -62,6 +65,8 @@ void main() {
6265
// go to the end of the input field and start typing
6366
await page.keyboard.press(Key.arrowDown);
6467
await page.keyboard.type(' -sdk:fl');
68+
await page.takeScreenshots(
69+
selector: 'body', prefix: 'listing-page/search-completion');
6570
await Future.delayed(Duration(milliseconds: 200));
6671
await page.keyboard.press(Key.enter);
6772
await Future.delayed(Duration(milliseconds: 200));

pkg/pub_integration/test/search_update_test.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'dart:convert';
77
import 'package:http/http.dart' as http;
88
import 'package:pub_integration/src/fake_test_context_provider.dart';
99
import 'package:pub_integration/src/pub_puppeteer_helpers.dart';
10+
import 'package:pub_integration/src/screenshot_utils.dart';
1011
import 'package:pub_integration/src/test_browser.dart';
1112
import 'package:puppeteer/puppeteer.dart';
1213
import 'package:test/test.dart';
@@ -99,6 +100,9 @@ void main() {
99100
await _waitOneSecond();
100101
expect(await flutterCB3.boundingBox, isNotNull);
101102

103+
await page.takeScreenshots(
104+
selector: '.search-form', prefix: 'listing-page/search-form');
105+
102106
// click Flutter
103107
await flutterCB3.clickAndWaitOneResponse();
104108
await _waitOneSecond();

pkg/pub_integration/test/theme_switch_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:convert';
66

77
import 'package:http/http.dart' as http;
88
import 'package:pub_integration/src/fake_test_context_provider.dart';
9+
import 'package:pub_integration/src/screenshot_utils.dart';
910
import 'package:pub_integration/src/test_browser.dart';
1011
import 'package:test/test.dart';
1112

0 commit comments

Comments
 (0)