test: convert some tests to the jest+fixtures (#2983)

This commit is contained in:
Pavel Feldman 2020-07-16 16:59:45 -07:00 committed by GitHub
parent 1896e8edc0
commit 424f11d165
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 117 additions and 45 deletions

View file

@ -37,7 +37,7 @@ jobs:
# XVFB-RUN merges both STDOUT and STDERR, whereas we need only STDERR
# Wrap `npm run` in a subshell to redirect STDERR to file.
# Enable core dumps in the subshell.
- run: xvfb-run --auto-servernum -- bash -c "ulimit -c unlimited && npm run coverage 2>./testrun.log"
- run: xvfb-run --auto-servernum -- bash -c "ulimit -c unlimited && npm run test 2>./testrun.log"
env:
BROWSER: ${{ matrix.browser }}
DEBUG: "*,-pw:wrapped*"

View file

@ -22,6 +22,10 @@ function isDate(obj: any): obj is Date {
return obj instanceof Date || Object.prototype.toString.call(obj) === '[object Date]';
}
function isError(obj: any): obj is Error {
return obj instanceof Error || (obj && obj.__proto__ && obj.__proto__.name === 'Error');
}
export function parseEvaluationResultValue(value: any, handles: any[] = []): any {
if (value === undefined)
return undefined;
@ -85,7 +89,7 @@ function serialize(value: any, jsHandleSerializer: (value: any) => { fallThrough
if (isPrimitiveValue(value))
return value;
if (value instanceof Error) {
if (isError(value)) {
const error = value;
if ('captureStackTrace' in global.Error) {
// v8

View file

@ -132,6 +132,10 @@ class Helper {
return obj instanceof RegExp || Object.prototype.toString.call(obj) === '[object RegExp]';
}
static isError(obj: any): obj is Error {
return obj instanceof Error || (obj && obj.__proto__ && obj.__proto__.name === 'Error');
}
static isObject(obj: any): obj is NonNullable<object> {
return typeof obj === 'object' && obj !== null;
}

View file

@ -693,7 +693,7 @@ export class PageBinding {
const result = await binding!.playwrightFunction({ frame: context.frame, page, context: page._browserContext }, ...args);
context.evaluateInternal(deliverResult, { name, seq, result }).catch(logError(page._logger));
} catch (error) {
if (error instanceof Error)
if (helper.isError(error))
context.evaluateInternal(deliverError, { name, seq, message: error.message, stack: error.stack }).catch(logError(page._logger));
else
context.evaluateInternal(deliverErrorValue, { name, seq, error }).catch(logError(page._logger));

View file

@ -24,7 +24,7 @@ import { helper, assert } from '../helper';
export function serializeError(e: any): types.Error {
if (e instanceof Error)
if (helper.isError(e))
return { message: e.message, stack: e.stack, name: e.name };
return { value: e };
}

View file

@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CSSCoverage should work with complicated usecases 1`] = `
"[
{
\\"url\\": \\"http://localhost:<PORT>/csscoverage/involved.html\\",
\\"ranges\\": [
{
\\"start\\": 149,
\\"end\\": 297
},
{
\\"start\\": 327,
\\"end\\": 433
}
],
\\"text\\": \\"\\\\n@charset \\\\\\"utf-8\\\\\\";\\\\n@namespace svg url(http://www.w3.org/2000/svg);\\\\n@font-face {\\\\n font-family: \\\\\\"Example Font\\\\\\";\\\\n src: url(\\\\\\"./Dosis-Regular.ttf\\\\\\");\\\\n}\\\\n\\\\n#fluffy {\\\\n border: 1px solid black;\\\\n z-index: 1;\\\\n /* -webkit-disabled-property: rgb(1, 2, 3) */\\\\n -lol-cats: \\\\\\"dogs\\\\\\" /* non-existing property */\\\\n}\\\\n\\\\n@media (min-width: 1px) {\\\\n span {\\\\n -webkit-border-radius: 10px;\\\\n font-family: \\\\\\"Example Font\\\\\\";\\\\n animation: 1s identifier;\\\\n }\\\\n}\\\\n\\"
}
]"
`;

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
const {FFOX, CHROMIUM, WEBKIT} = require('./utils').testOptions(browserType);
const {FFOX, CHROMIUM, WEBKIT} = testOptions;
describe('Accessibility', function() {
it('should work', async function({page}) {

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
const {FFOX, CHROMIUM, WEBKIT, USES_HOOKS} = require('./utils').testOptions(browserType);
const {FFOX, CHROMIUM, WEBKIT, USES_HOOKS} = testOptions;
describe('Auto waiting', () => {
it('should await navigation when clicking anchor', async({page, server}) => {

View file

@ -16,7 +16,7 @@
*/
const utils = require('./utils');
const {FFOX, CHROMIUM, WEBKIT, WIN, HEADLESS, USES_HOOKS} = utils.testOptions(browserType);
const {FFOX, CHROMIUM, WEBKIT, HEADLESS, USES_HOOKS} = testOptions;
async function giveItAChanceToClick(page) {
for (let i = 0; i < 5; i++)
@ -251,7 +251,7 @@ describe('Page.click', function() {
await page.click('label[for="agree"]');
expect(await page.evaluate(() => result.check)).toBe(false);
});
it('should not hang with touch-enabled viewports', async({server, browser}) => {
it('should not hang with touch-enabled viewports', async({browser, playwright}) => {
// @see https://github.com/GoogleChrome/puppeteer/issues/161
const { viewport, hasTouch } = playwright.devices['iPhone 6'];
const context = await browser.newContext({ viewport, hasTouch });

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
const {FFOX, CHROMIUM, WEBKIT, WIN} = require('./utils').testOptions(browserType);
const {FFOX, CHROMIUM, WEBKIT, WIN} = testOptions;
describe('BrowserContext.cookies', function() {
it('should return no cookies in pristine browser context', async({context, page, server}) => {

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
const {FFOX, CHROMIUM, WEBKIT} = require('./utils').testOptions(browserType);
const {FFOX, CHROMIUM, WEBKIT} = testOptions;
describe.skip(CHROMIUM)('Page.coverage missing', function() {
it('should work', async function({page, server}) {
@ -141,11 +141,11 @@ describe.skip(!CHROMIUM)('CSSCoverage', function() {
{start: 17, end: 38}
]);
});
it('should work with complicated usecases', async function({page, server, golden}) {
it('should work with complicated usecases', async function({page, server}) {
await page.coverage.startCSSCoverage();
await page.goto(server.PREFIX + '/csscoverage/involved.html');
const coverage = await page.coverage.stopCSSCoverage();
expect(JSON.stringify(coverage, null, 2).replace(/:\d{4}\//g, ':<PORT>/')).toBeGolden(golden('csscoverage-involved.txt'));
expect(JSON.stringify(coverage, null, 2).replace(/:\d{4}\//g, ':<PORT>/')).toMatchSnapshot();
});
it('should ignore injected stylesheets', async function({page, server}) {
await page.coverage.startCSSCoverage();

View file

@ -17,6 +17,7 @@
const utils = require('./utils');
const path = require('path');
const {FFOX, CHROMIUM, WEBKIT, USES_HOOKS, CHANNEL} = testOptions;
describe('Page.evaluate', function() {
it('should work', async({page, server}) => {
@ -356,7 +357,7 @@ describe('Page.evaluate', function() {
});
expect(result).toEqual([42]);
});
(WEBKIT ? it.skip : it)('should not throw an error when evaluation does a synchronous navigation and returns an object', async({page, server}) => {
it.fail(WEBKIT)('should not throw an error when evaluation does a synchronous navigation and returns an object', async({page, server}) => {
// It is imporant to be on about:blank for sync reload.
const result = await page.evaluate(() => {
window.location.reload();
@ -372,7 +373,7 @@ describe('Page.evaluate', function() {
});
expect(result).toBe(undefined);
});
(CHANNEL ? it.skip : it)('should transfer 100Mb of data from page to node.js', async({page}) => {
it.skip(CHANNEL)('should transfer 100Mb of data from page to node.js', async({page}) => {
const a = await page.evaluate(() => Array(100 * 1024 * 1024 + 1).join('a'));
expect(a.length).toBe(100 * 1024 * 1024);
});
@ -388,7 +389,7 @@ describe('Page.evaluate', function() {
const result = await page.evaluate(() => ({abc: 123}));
expect(result).toEqual({abc: 123});
});
(FFOX ? it.skip : it)('should await promise from popup', async ({page, server}) => {
it.fail(FFOX)('should await promise from popup', async ({page, server}) => {
// Something is wrong about the way Firefox waits for the chained promise
await page.goto(server.EMPTY_PAGE);
const result = await page.evaluate(() => {
@ -573,14 +574,14 @@ describe('Frame.evaluate', function() {
else
expect(pageImpl._delegate._contextIdToContext.size).toBe(count);
}
(USES_HOOKS ? it.skip : it)('should dispose context on navigation', async({page, server, toImpl}) => {
it.skip(USES_HOOKS)('should dispose context on navigation', async({page, server, toImpl}) => {
await page.goto(server.PREFIX + '/frames/one-frame.html');
expect(page.frames().length).toBe(2);
expectContexts(toImpl(page), 4);
await page.goto(server.EMPTY_PAGE);
expectContexts(toImpl(page), 2);
});
(USES_HOOKS ? it.skip : it)('should dispose context on cross-origin navigation', async({page, server, toImpl}) => {
it.skip(USES_HOOKS)('should dispose context on cross-origin navigation', async({page, server, toImpl}) => {
await page.goto(server.PREFIX + '/frames/one-frame.html');
expect(page.frames().length).toBe(2);
expectContexts(toImpl(page), 4);

View file

@ -27,7 +27,7 @@ const { setUseApiName } = require('../../lib/progress');
module.exports = function registerFixtures(global) {
global.registerWorkerFixture('server', async ({}, test) => {
global.registerWorkerFixture('http_server', async ({}, test) => {
const assetsPath = path.join(__dirname, '..', 'assets');
const cachedPath = path.join(__dirname, '..', 'assets', 'cached');
@ -40,14 +40,14 @@ module.exports = function registerFixtures(global) {
server.EMPTY_PAGE = `http://localhost:${port}/empty.html`;
const httpsPort = port + 1;
httpsServer = await TestServer.createHTTPS(assetsPath, httpsPort);
const httpsServer = await TestServer.createHTTPS(assetsPath, httpsPort);
httpsServer.enableHTTPCache(cachedPath);
httpsServer.PORT = httpsPort;
httpsServer.PREFIX = `https://localhost:${httpsPort}`;
httpsServer.CROSS_PROCESS_PREFIX = `https://127.0.0.1:${httpsPort}`;
httpsServer.EMPTY_PAGE = `https://localhost:${httpsPort}/empty.html`;
await test(server);
await test({server, httpsServer});
await Promise.all([
server.stop(),
@ -55,6 +55,14 @@ module.exports = function registerFixtures(global) {
]);
});
global.registerWorkerFixture('defaultBrowserOptions', async({}, test) => {
await test({
handleSIGINT: false,
slowMo: valueFromEnv('SLOW_MO', 0),
headless: !!valueFromEnv('HEADLESS', true),
});
});
global.registerWorkerFixture('playwright', async({}, test) => {
if (process.env.PWCHANNEL) {
setUseApiName(false);
@ -113,8 +121,8 @@ module.exports = function registerFixtures(global) {
await test(playwright[process.env.BROWSER || 'chromium']);
});
global.registerWorkerFixture('browser', async ({browserType}, test) => {
const browser = await browserType.launch({ headless: !!global.HEADLESS });
global.registerWorkerFixture('browser', async ({browserType, defaultBrowserOptions}, test) => {
const browser = await browserType.launch(defaultBrowserOptions);
await test(browser);
await browser.close();
});
@ -129,4 +137,20 @@ module.exports = function registerFixtures(global) {
const page = await context.newPage();
await test(page);
});
global.registerFixture('server', async ({http_server}, test) => {
http_server.server.reset();
await test(http_server.server);
});
global.registerFixture('httpsServer', async ({http_server}, test) => {
http_server.httpsServer.reset();
await test(http_server.httpsServer);
});
}
function valueFromEnv(name, defaultValue) {
if (!(name in process.env))
return defaultValue;
return JSON.parse(process.env[name]);
}

View file

@ -16,17 +16,25 @@
const NodeEnvironment = require('jest-environment-node');
const registerFixtures = require('./fixtures');
const os = require('os');
const platform = os.platform();
class PlaywrightEnvironment extends NodeEnvironment {
constructor(config, context) {
super(config, context);
this.fixturePool = new FixturePool();
this.global.CHROMIUM = process.env.BROWSER === 'chromium' || !process.env.BROWSER;
this.global.FFOX = process.env.BROWSER === 'firefox';
this.global.WEBKIT = process.env.BROWSER === 'webkit';
this.global.USES_HOOKS = process.env.PWCHANNEL === 'wire';
this.global.CHANNEL = !!process.env.PWCHANNEL;
this.global.HEADLESS = !!valueFromEnv('HEADLESS', true);
const testOptions = {};
testOptions.MAC = platform === 'darwin';
testOptions.LINUX = platform === 'linux';
testOptions.WIN = platform === 'win32';
testOptions.CHROMIUM = process.env.BROWSER === 'chromium' || !process.env.BROWSER;
testOptions.FFOX = process.env.BROWSER === 'firefox';
testOptions.WEBKIT = process.env.BROWSER === 'webkit';
testOptions.USES_HOOKS = process.env.PWCHANNEL === 'wire';
testOptions.CHANNEL = !!process.env.PWCHANNEL;
testOptions.HEADLESS = !!valueFromEnv('HEADLESS', true);
this.global.testOptions = testOptions;
this.global.registerFixture = (name, fn) => {
this.fixturePool.registerFixture(name, 'test', fn);
@ -51,6 +59,24 @@ class PlaywrightEnvironment extends NodeEnvironment {
}
async handleTestEvent(event, state) {
if (event.name === 'setup') {
const describeSkip = this.global.describe.skip;
this.global.describe.skip = (...args) => {
if (args.length = 1)
return args[0] ? describeSkip : this.global.describe;
return describeSkip(...args);
};
this.global.describe.fail = this.global.describe.skip;
const itSkip = this.global.it.skip;
this.global.it.skip = (...args) => {
if (args.length = 1)
return args[0] ? itSkip : this.global.it;
return itSkip(...args);
};
this.global.it.fail = this.global.it.skip;
this.global.it.slow = () => this.global.it;
}
if (event.name === 'test_start') {
const fn = event.test.fn;
event.test.fn = async () => {
@ -161,7 +187,7 @@ exports.default = exports.getPlaywrightEnv();
function fixtureParameterNames(fn) {
const text = fn.toString();
const match = text.match(/async\s*\(\s*{\s*([^}]*)\s*}/);
const match = text.match(/async(?:\s+function)?\s*\(\s*{\s*([^}]*)\s*}/);
if (!match || !match[1].trim())
return [];
let signature = match[1];

View file

@ -18,7 +18,7 @@
const fs = require('fs');
const path = require('path');
const utils = require('./utils');
const {FFOX, CHROMIUM, WEBKIT, MAC, WIN} = utils.testOptions(browserType);
const {FFOX, CHROMIUM, WEBKIT, MAC, WIN} = testOptions;
describe('Page.Events.Request', function() {
it('should fire for navigation requests', async({page, server}) => {

View file

@ -18,7 +18,7 @@
const path = require('path');
const util = require('util');
const vm = require('vm');
const {FFOX, CHROMIUM, WEBKIT, WIN, USES_HOOKS, CHANNEL} = require('./utils').testOptions(browserType);
const {FFOX, CHROMIUM, WEBKIT, WIN, USES_HOOKS, CHANNEL} = testOptions;
describe('Page.close', function() {
it('should reject all promises when page is closed', async({context}) => {
@ -113,7 +113,7 @@ describe('Async stacks', () => {
});
});
describe.fail(FFOX && WIN).skip(USES_HOOKS)('Page.Events.Crash', function() {
describe.fail(FFOX && WIN || USES_HOOKS)('Page.Events.Crash', function() {
// Firefox Win: it just doesn't crash sometimes.
function crash(pageImpl) {
@ -329,12 +329,12 @@ describe('Page.waitForRequest', function() {
]);
expect(request.url()).toBe(server.PREFIX + '/digits/2.png');
});
it('should respect timeout', async({page, server}) => {
it('should respect timeout', async({page, playwright}) => {
let error = null;
await page.waitForEvent('request', { predicate: () => false, timeout: 1 }).catch(e => error = e);
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
});
it('should respect default timeout', async({page, server}) => {
it('should respect default timeout', async({page, playwright}) => {
let error = null;
page.setDefaultTimeout(1);
await page.waitForEvent('request', () => false).catch(e => error = e);
@ -400,12 +400,12 @@ describe('Page.waitForResponse', function() {
]);
expect(response.url()).toBe(server.PREFIX + '/digits/2.png');
});
it('should respect timeout', async({page, server}) => {
it('should respect timeout', async({page, playwright}) => {
let error = null;
await page.waitForEvent('response', { predicate: () => false, timeout: 1 }).catch(e => error = e);
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
});
it('should respect default timeout', async({page, server}) => {
it('should respect default timeout', async({page, playwright}) => {
let error = null;
page.setDefaultTimeout(1);
await page.waitForEvent('response', () => false).catch(e => error = e);
@ -663,7 +663,7 @@ describe('Page.setContent', function() {
const result = await page.content();
expect(result).toBe(`${doctype}${expectedOutput}`);
});
it('should respect timeout', async({page, server}) => {
it('should respect timeout', async({page, server, playwright}) => {
const imgPath = '/img.png';
// stall for image
server.setRoute(imgPath, (req, res) => {});
@ -671,7 +671,7 @@ describe('Page.setContent', function() {
await page.setContent(`<img src="${server.PREFIX + imgPath}"></img>`, {timeout: 1}).catch(e => error = e);
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
});
it('should respect default navigation timeout', async({page, server}) => {
it('should respect default navigation timeout', async({page, server, playwright}) => {
page.setDefaultNavigationTimeout(1);
const imgPath = '/img.png';
// stall for image

View file

@ -71,11 +71,6 @@ module.exports = {
specs: [
{
files: [
'./accessibility.spec.js',
'./autowaiting.spec.js',
'./click.spec.js',
'./cookies.spec.js',
'./coverage.spec.js',
'./dialog.spec.js',
'./dispatchevent.spec.js',
'./download.spec.js',
@ -88,8 +83,6 @@ module.exports = {
'./keyboard.spec.ts',
'./mouse.spec.js',
'./navigation.spec.js',
'./network.spec.js',
'./page.spec.js',
'./pdf.spec.js',
'./queryselector.spec.js',
'./screenshot.spec.js',