Skip to content

Commit 6b060a0

Browse files
paullewisCommit Bot
authored and
Commit Bot
committed
[test] Adds 'hello world' screenshot test
This CL is the initial commit for a screenshot test. A further CL will provide tooling for updating the goldens. Bug: 1051458 Change-Id: Id61dd0a804ad33566451e0f56f6981e3e7bd9067 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2051984 Commit-Queue: Paul Lewis <[email protected]> Reviewed-by: Tim van der Lippe <[email protected]>
1 parent ded23fb commit 6b060a0

File tree

10 files changed

+275
-2
lines changed

10 files changed

+275
-2
lines changed

.gitignore

+8-2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ npm-debug.log
3535
!/third_party/pyjson5
3636
!/third_party/typescript
3737

38-
test/e2e/**/*[.js, .tsbuildinfo]
38+
test/e2e/**/*.js
3939
!test/e2e/resources/**/*.js
40-
test/shared/**/*[.js, .tsbuildinfo]
40+
test/e2e/**/*.tsbuildinfo
41+
test/shared/**/*.js
42+
test/shared/**/helper.d.ts
43+
test/shared/**/*.tsbuildinfo
44+
test/screenshots/**/*.js
45+
test/screenshots/**/*.tsbuildinfo
46+
test/screenshots/.generated

scripts/test/run_screenshot_tests.py

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#!/usr/bin/env python
2+
#
3+
# Copyright 2020 The Chromium Authors. All rights reserved.
4+
# Use of this source code is governed by a BSD-style license that can be
5+
# found in the LICENSE file.
6+
"""
7+
Run screenshot tests on a pre-built chrome or one specified via --chrome-binary.
8+
"""
9+
10+
import argparse
11+
import os
12+
import platform
13+
import re
14+
import subprocess
15+
import sys
16+
import signal
17+
18+
scripts_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
19+
sys.path.append(scripts_path)
20+
21+
import devtools_paths
22+
23+
is_cygwin = sys.platform == 'cygwin'
24+
25+
26+
def parse_options(cli_args):
27+
parser = argparse.ArgumentParser(description='Run boot perf test')
28+
parser.add_argument('--runs', help='Number of runs', type=int)
29+
parser.add_argument('--chrome-binary', dest='chrome_binary', help='path to Chromium binary')
30+
return parser.parse_args(cli_args)
31+
32+
33+
def check_chrome_binary(chrome_binary):
34+
return os.path.exists(chrome_binary) and os.path.isfile(chrome_binary) and os.access(chrome_binary, os.X_OK)
35+
36+
37+
def popen(arguments, cwd=None, env=None, capture=False):
38+
process = subprocess.Popen(arguments, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env)
39+
if not capture:
40+
return process
41+
42+
def handle_signal(signum, frame):
43+
print 'Sending signal (%i) to process' % signum
44+
process.send_signal(signum)
45+
process.terminate()
46+
47+
# Propagate sigterm / int to the child process.
48+
original_sigint = signal.getsignal(signal.SIGINT)
49+
original_sigterm = signal.getsignal(signal.SIGTERM)
50+
signal.signal(signal.SIGINT, handle_signal)
51+
signal.signal(signal.SIGTERM, handle_signal)
52+
53+
for line in iter(process.stdout.readline, ''):
54+
sys.stdout.write(line)
55+
if process.returncode == 0:
56+
sys.stdout.write('done')
57+
58+
# Restore the original sigterm / int handlers.
59+
signal.signal(signal.SIGINT, original_sigint)
60+
signal.signal(signal.SIGTERM, original_sigterm)
61+
62+
return process
63+
64+
65+
def to_platform_path_exact(filepath):
66+
if not is_cygwin:
67+
return filepath
68+
output, _ = popen(['cygpath', '-w', filepath]).communicate()
69+
# pylint: disable=E1103
70+
return output.strip().replace('\\', '\\\\')
71+
72+
73+
def compile_typescript_test_files():
74+
tsc_compile_errors_found = False
75+
cwd = devtools_paths.devtools_root_path()
76+
env = os.environ.copy()
77+
shared_path = os.path.join(cwd, 'test', 'shared')
78+
screenshots_test_path = os.path.join(cwd, 'test', 'screenshots')
79+
80+
# Compile shared code, e.g. helper and runner.
81+
print("Compiling shared TypeScript")
82+
exec_command = [devtools_paths.node_path(), devtools_paths.typescript_compiler_path(), '-p', shared_path]
83+
tsc_compile_proc = popen(exec_command, cwd=cwd, env=env, capture=True)
84+
tsc_compile_proc.communicate()
85+
if tsc_compile_proc.returncode != 0:
86+
tsc_compile_errors_found = True
87+
88+
# Compile screenshots tests, e.g. helper and runner.
89+
print("Compiling screenshots TypeScript")
90+
exec_command = [devtools_paths.node_path(), devtools_paths.typescript_compiler_path(), '-p', screenshots_test_path]
91+
tsc_compile_proc = popen(exec_command, cwd=cwd, env=env, capture=True)
92+
tsc_compile_proc.communicate()
93+
if tsc_compile_proc.returncode != 0:
94+
tsc_compile_errors_found = True
95+
96+
return tsc_compile_errors_found
97+
98+
99+
def run_screenshots_test(chrome_binary):
100+
screenshots_errors_found = False
101+
cwd = devtools_paths.devtools_root_path()
102+
screenshots_test_path = os.path.join(cwd, 'test', 'shared', 'runner.js')
103+
screenshots_test_list = os.path.join(cwd, 'test', 'screenshots', 'test-list.js')
104+
exec_command = [devtools_paths.node_path(), screenshots_test_path]
105+
106+
env = os.environ.copy()
107+
env['CHROME_BIN'] = chrome_binary
108+
env['TEST_LIST'] = screenshots_test_list
109+
110+
screenshots_proc = popen(exec_command, cwd=cwd, env=env, capture=True)
111+
screenshots_proc.communicate()
112+
if screenshots_proc.returncode != 0:
113+
screenshots_errors_found = True
114+
115+
return screenshots_errors_found
116+
117+
118+
def main():
119+
OPTIONS = parse_options(sys.argv[1:])
120+
is_cygwin = sys.platform == 'cygwin'
121+
chrome_binary = None
122+
123+
# Default to the downloaded / pinned Chromium binary
124+
downloaded_chrome_binary = devtools_paths.downloaded_chrome_binary_path()
125+
if check_chrome_binary(downloaded_chrome_binary):
126+
chrome_binary = downloaded_chrome_binary
127+
128+
# Override with the arg value if provided.
129+
if OPTIONS.chrome_binary:
130+
chrome_binary = OPTIONS.chrome_binary
131+
if not check_chrome_binary(chrome_binary):
132+
print('Unable to find a Chrome binary at \'%s\'' % chrome_binary)
133+
sys.exit(1)
134+
135+
if (chrome_binary is None):
136+
print('Unable to run, no Chrome binary provided')
137+
sys.exit(1)
138+
139+
print('Using Chromium binary (%s)\n' % chrome_binary)
140+
141+
errors_found = False
142+
try:
143+
errors_found = compile_typescript_test_files()
144+
if (errors_found):
145+
raise Exception('Typescript failed to compile')
146+
errors_found = run_screenshots_test(chrome_binary)
147+
except Exception as err:
148+
print(err)
149+
150+
if errors_found:
151+
print('ERRORS DETECTED')
152+
sys.exit(1)
153+
154+
155+
if __name__ == '__main__':
156+
main()
29.7 KB
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2020 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import {describe, it} from 'mocha';
6+
import {click, getBrowserAndPages, resetPages, resourcesPath, assertScreenshotUnchanged} from '../../shared/helper.js';
7+
8+
describe('hello world', () => {
9+
beforeEach(async() => {
10+
await resetPages();
11+
});
12+
13+
it('takes a screenshot', async () => {
14+
const {target, frontend} = getBrowserAndPages();
15+
await target.goto(`${resourcesPath}/console/big-int.html`);
16+
await click('#tab-console');
17+
await frontend.waitForSelector('.console-group-messages');
18+
19+
await assertScreenshotUnchanged(frontend, 'hello-world.png');
20+
});
21+
});

test/screenshots/test-list.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright 2020 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import {join} from 'path';
6+
7+
export const testList = [
8+
join(__dirname, '.', 'hello-world', 'hello-world.js'),
9+
];

test/screenshots/tsconfig.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"compilerOptions": {
3+
"module": "commonjs",
4+
"baseUrl": ".",
5+
6+
"checkJs": false,
7+
"allowJs": false
8+
},
9+
"references": [
10+
{
11+
"path": "../shared/tsconfig.json"
12+
}
13+
],
14+
"extends": "../../tsconfig.json"
15+
}

test/shared/helper.ts

+45
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44

55
import * as puppeteer from 'puppeteer';
66
import {performance} from 'perf_hooks';
7+
import {join} from 'path';
8+
import * as fs from 'fs';
9+
import * as rimraf from 'rimraf';
10+
import * as resemblejs from 'resemblejs';
11+
import {assert} from 'chai';
712

813
interface BrowserAndPages {
914
browser: puppeteer.Browser;
@@ -176,4 +181,44 @@ export const getBrowserAndPages = (): BrowserAndPages => {
176181
};
177182
};
178183

184+
const goldensScreenshotFolder = join(__dirname, '..', 'screenshots', 'goldens');
185+
const generatedScreenshotFolder = join(__dirname, '..', 'screenshots', '.generated');
186+
187+
// Delete and create the generated images.
188+
if (fs.existsSync(generatedScreenshotFolder)) {
189+
rimraf.sync(generatedScreenshotFolder);
190+
}
191+
fs.mkdirSync(generatedScreenshotFolder);
192+
193+
const defaultScreenshotOpts: puppeteer.ScreenshotOptions = {
194+
type: 'png',
195+
fullPage: true,
196+
encoding: 'binary'
197+
};
198+
export const assertScreenshotUnchanged = async (page: puppeteer.Page, fileName: string, options: Partial<puppeteer.ScreenshotOptions> = {}) => {
199+
const goldensScreenshotPath = join(goldensScreenshotFolder, fileName);
200+
const generatedScreenshotPath = join(generatedScreenshotFolder, fileName);
201+
202+
if (fs.existsSync(generatedScreenshotPath)) {
203+
throw new Error(`${generatedScreenshotPath} already exists.`);
204+
}
205+
206+
const opts = {...defaultScreenshotOpts, ...options, path: generatedScreenshotPath};
207+
await page.screenshot(opts);
208+
209+
return new Promise((resolve, reject) => {
210+
resemblejs.compare(generatedScreenshotPath, goldensScreenshotPath, {}, (err, data) => {
211+
if (err) {
212+
reject(err);
213+
return;
214+
}
215+
216+
const {dimensionDifference, rawMisMatchPercentage} = data;
217+
assert.deepEqual(dimensionDifference, { width: 0, height: 0});
218+
assert.isBelow(rawMisMatchPercentage, 1);
219+
resolve();
220+
});
221+
})
222+
};
223+
179224
export const resourcesPath = 'http://localhost:8090/test/e2e/resources';

test/shared/resemblejs.d.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2020 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
declare module 'resemblejs' {
6+
interface ResembleComparisonResult {
7+
dimensionDifference: { width: number, height: number };
8+
rawMisMatchPercentage: number;
9+
}
10+
11+
export function compare(file1: string, file2: string, options: {},
12+
cb:(err: Error, data: ResembleComparisonResult) => void): void;
13+
}

test/shared/rimraf.d.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright 2020 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
declare module 'rimraf' {
6+
export function sync(path: string): void;
7+
}

test/shared/runner.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {};

0 commit comments

Comments
 (0)