test: remove output and golden directory notions (#3456)

This commit is contained in:
Pavel Feldman 2020-08-13 17:32:27 -07:00 committed by GitHub
parent ae5700b3f3
commit 6abc352498
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 192 additions and 215 deletions

View File

@ -16,6 +16,7 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import os from 'os';
import childProcess from 'child_process'; import childProcess from 'child_process';
import { LaunchOptions, BrowserType, Browser, BrowserContext, Page, BrowserServer } from '../index'; import { LaunchOptions, BrowserType, Browser, BrowserContext, Page, BrowserServer } from '../index';
import { TestServer } from '../utils/testserver/'; import { TestServer } from '../utils/testserver/';
@ -24,6 +25,9 @@ import { Transport } from '../lib/rpc/transport';
import { setUnderTest } from '../lib/helper'; import { setUnderTest } from '../lib/helper';
import { installCoverageHooks } from './runner/coverage'; import { installCoverageHooks } from './runner/coverage';
import { valueFromEnv } from './runner/utils'; import { valueFromEnv } from './runner/utils';
import { registerFixture, registerWorkerFixture} from './runner/fixtures';
import {mkdtempAsync, removeFolderAsync} from './utils';
setUnderTest(); // Note: we must call setUnderTest before requiring Playwright setUnderTest(); // Note: we must call setUnderTest before requiring Playwright
@ -31,13 +35,14 @@ const browserName = process.env.BROWSER || 'chromium';
declare global { declare global {
interface WorkerState { interface WorkerState {
asset: (path: string) => string;
parallelIndex: number; parallelIndex: number;
http_server: {server: TestServer, httpsServer: TestServer};
defaultBrowserOptions: LaunchOptions; defaultBrowserOptions: LaunchOptions;
golden: (path: string) => string;
playwright: typeof import('../index'); playwright: typeof import('../index');
browserType: BrowserType<Browser>; browserType: BrowserType<Browser>;
browser: Browser; browser: Browser;
outputDir: string; tmpDir: string;
} }
interface FixtureState { interface FixtureState {
toImpl: (rpcObject: any) => any; toImpl: (rpcObject: any) => any;
@ -53,7 +58,7 @@ registerWorkerFixture('parallelIndex', async ({}, test) => {
await test(parseInt(process.env.JEST_WORKER_ID, 10) - 1); await test(parseInt(process.env.JEST_WORKER_ID, 10) - 1);
}); });
registerWorkerFixture('http_server', async ({parallelIndex}, test) => { registerWorkerFixture('httpService', async ({parallelIndex}, test) => {
const assetsPath = path.join(__dirname, 'assets'); const assetsPath = path.join(__dirname, 'assets');
const cachedPath = path.join(__dirname, 'assets', 'cached'); const cachedPath = path.join(__dirname, 'assets', 'cached');
@ -158,21 +163,30 @@ registerFixture('page', async ({context}, test) => {
await test(page); await test(page);
}); });
registerFixture('server', async ({http_server}, test) => { registerFixture('server', async ({httpService}, test) => {
http_server.server.reset(); httpService.server.reset();
await test(http_server.server); await test(httpService.server);
}); });
registerFixture('httpsServer', async ({http_server}, test) => { registerFixture('browserName', async ({}, test) => {
http_server.httpsServer.reset(); await test(browserName);
await test(http_server.httpsServer);
}); });
registerWorkerFixture('outputDir', async ({}, test) => { registerFixture('httpsServer', async ({httpService}, test) => {
const outputDir = path.join(__dirname, 'output-' + browserName); httpService.httpsServer.reset();
try { await test(httpService.httpsServer);
await fs.promises.mkdir(outputDir, { recursive: true }); });
} catch (e) {
} registerFixture('tmpDir', async ({}, test) => {
await test(outputDir); const tmpDir = await mkdtempAsync(path.join(os.tmpdir(), 'playwright-test-'));
await test(tmpDir);
await removeFolderAsync(tmpDir);
});
registerWorkerFixture('asset', async ({}, test) => {
await test(p => path.join(__dirname, `assets`, p));
});
registerWorkerFixture('golden', async ({browserName}, test) => {
await test(p => path.join(__dirname, `golden-${browserName}`, p));
}); });

View File

@ -18,7 +18,7 @@ import './base.fixture';
import path from 'path'; import path from 'path';
import url from 'url'; import url from 'url';
const {FFOX, CHROMIUM, WEBKIT, WIN, LINUX, ASSETS_DIR} = testOptions; const {FFOX, CHROMIUM, WEBKIT, WIN, LINUX} = testOptions;
it.fail(WEBKIT && WIN)('Web Assembly should work', async function({page, server}) { it.fail(WEBKIT && WIN)('Web Assembly should work', async function({page, server}) {
await page.goto(server.PREFIX + '/wasm/table2.html'); await page.goto(server.PREFIX + '/wasm/table2.html');
@ -51,14 +51,14 @@ it('should respect CSP', async({page, server}) => {
expect(await page.evaluate(() => window['testStatus'])).toBe('SUCCESS'); expect(await page.evaluate(() => window['testStatus'])).toBe('SUCCESS');
}); });
it.fail(WEBKIT && (WIN || LINUX))('should play video', async({page}) => { it.fail(WEBKIT && (WIN || LINUX))('should play video', async({page, asset}) => {
// TODO: the test passes on Windows locally but fails on GitHub Action bot, // TODO: the test passes on Windows locally but fails on GitHub Action bot,
// apparently due to a Media Pack issue in the Windows Server. // apparently due to a Media Pack issue in the Windows Server.
// Also the test is very flaky on Linux WebKit. // Also the test is very flaky on Linux WebKit.
// //
// Safari only plays mp4 so we test WebKit with an .mp4 clip. // Safari only plays mp4 so we test WebKit with an .mp4 clip.
const fileName = WEBKIT ? 'video_mp4.html' : 'video.html'; const fileName = WEBKIT ? 'video_mp4.html' : 'video.html';
const absolutePath = path.join(ASSETS_DIR, fileName); const absolutePath = asset(fileName);
// Our test server doesn't support range requests required to play on Mac, // Our test server doesn't support range requests required to play on Mac,
// so we load the page using a file url. // so we load the page using a file url.
await page.goto(url.pathToFileURL(absolutePath).href); await page.goto(url.pathToFileURL(absolutePath).href);

View File

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import '../base.fixture'; import '../base.fixture';
import { registerFixture } from '../runner/fixtures';
import { Page, Browser, BrowserContext } from '../..'; import { Page, Browser, BrowserContext } from '../..';
const {FFOX, CHROMIUM, WEBKIT} = testOptions; const {FFOX, CHROMIUM, WEBKIT} = testOptions;
@ -188,14 +189,14 @@ it.skip(!CHROMIUM)('should respect route', async({sppBrowser, sppPage, server})
expect(intercepted).toBe(true); expect(intercepted).toBe(true);
}); });
it.skip(!CHROMIUM)('should take screenshot', async({sppBrowser, sppPage, server}) => { it.skip(!CHROMIUM)('should take screenshot', async({sppBrowser, sppPage, server, golden}) => {
const browser = sppBrowser; const browser = sppBrowser;
const page = sppPage; const page = sppPage;
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/dynamic-oopif.html'); await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(page.frames().length).toBe(2); expect(page.frames().length).toBe(2);
expect(await countOOPIFs(browser)).toBe(1); expect(await countOOPIFs(browser)).toBe(1);
expect(await page.screenshot()).toBeGolden('screenshot-oopif.png'); expect(await page.screenshot()).toMatchImage(golden('screenshot-oopif.png'));
}); });
it.skip(!CHROMIUM)('should load oopif iframes with subresources and request interception', async function({sppBrowser, sppPage, server, context}) { it.skip(!CHROMIUM)('should load oopif iframes with subresources and request interception', async function({sppBrowser, sppPage, server, context}) {

View File

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import '../base.fixture'; import '../base.fixture';
import { registerFixture } from '../runner/fixtures';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
@ -24,8 +25,8 @@ declare global {
outputFile: string; outputFile: string;
} }
} }
registerFixture('outputFile', async ({outputDir, parallelIndex}, test) => { registerFixture('outputFile', async ({tmpDir}, test) => {
const outputFile = path.join(outputDir, `trace-${parallelIndex}.json`); const outputFile = path.join(tmpDir, `trace.json`);
await test(outputFile); await test(outputFile);
if (fs.existsSync(outputFile)) if (fs.existsSync(outputFile))
fs.unlinkSync(outputFile); fs.unlinkSync(outputFile);

View File

@ -15,6 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import './base.fixture'; import './base.fixture';
import { registerFixture } from './runner/fixtures';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';

View File

@ -18,22 +18,9 @@ import './base.fixture';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import util from 'util'; import util from 'util';
import os from 'os';
import {mkdtempAsync, removeFolderAsync} from './utils';
const {FFOX, CHROMIUM, WEBKIT, HEADLESS} = testOptions; const {FFOX, CHROMIUM, WEBKIT, HEADLESS} = testOptions;
declare global {
interface FixtureState {
persistentDirectory: string;
}
}
registerFixture('persistentDirectory', async ({}, test) => {
const persistentDirectory = await mkdtempAsync(path.join(os.tmpdir(), 'playwright-test-'));
await test(persistentDirectory);
await removeFolderAsync(persistentDirectory);
});
beforeEach(async ({server}) => { beforeEach(async ({server}) => {
server.setRoute('/download', (req, res) => { server.setRoute('/download', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream'); res.setHeader('Content-Type', 'application/octet-stream');
@ -74,28 +61,28 @@ it('should report downloads with acceptDownloads: true', async({browser, server}
await page.close(); await page.close();
}); });
it('should save to user-specified path', async({persistentDirectory, browser, server}) => { it('should save to user-specified path', async({tmpDir, browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true }); const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`); await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([ const [ download ] = await Promise.all([
page.waitForEvent('download'), page.waitForEvent('download'),
page.click('a') page.click('a')
]); ]);
const userPath = path.join(persistentDirectory, "download.txt"); const userPath = path.join(tmpDir, "download.txt");
await download.saveAs(userPath); await download.saveAs(userPath);
expect(fs.existsSync(userPath)).toBeTruthy(); expect(fs.existsSync(userPath)).toBeTruthy();
expect(fs.readFileSync(userPath).toString()).toBe('Hello world'); expect(fs.readFileSync(userPath).toString()).toBe('Hello world');
await page.close(); await page.close();
}); });
it('should save to user-specified path without updating original path', async({persistentDirectory, browser, server}) => { it('should save to user-specified path without updating original path', async({tmpDir, browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true }); const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`); await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([ const [ download ] = await Promise.all([
page.waitForEvent('download'), page.waitForEvent('download'),
page.click('a') page.click('a')
]); ]);
const userPath = path.join(persistentDirectory, "download.txt"); const userPath = path.join(tmpDir, "download.txt");
await download.saveAs(userPath); await download.saveAs(userPath);
expect(fs.existsSync(userPath)).toBeTruthy(); expect(fs.existsSync(userPath)).toBeTruthy();
expect(fs.readFileSync(userPath).toString()).toBe('Hello world'); expect(fs.readFileSync(userPath).toString()).toBe('Hello world');
@ -106,77 +93,77 @@ it('should save to user-specified path without updating original path', async({p
await page.close(); await page.close();
}); });
it('should save to two different paths with multiple saveAs calls', async({persistentDirectory, browser, server}) => { it('should save to two different paths with multiple saveAs calls', async({tmpDir, browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true }); const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`); await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([ const [ download ] = await Promise.all([
page.waitForEvent('download'), page.waitForEvent('download'),
page.click('a') page.click('a')
]); ]);
const userPath = path.join(persistentDirectory, "download.txt"); const userPath = path.join(tmpDir, "download.txt");
await download.saveAs(userPath); await download.saveAs(userPath);
expect(fs.existsSync(userPath)).toBeTruthy(); expect(fs.existsSync(userPath)).toBeTruthy();
expect(fs.readFileSync(userPath).toString()).toBe('Hello world'); expect(fs.readFileSync(userPath).toString()).toBe('Hello world');
const anotherUserPath = path.join(persistentDirectory, "download (2).txt"); const anotherUserPath = path.join(tmpDir, "download (2).txt");
await download.saveAs(anotherUserPath); await download.saveAs(anotherUserPath);
expect(fs.existsSync(anotherUserPath)).toBeTruthy(); expect(fs.existsSync(anotherUserPath)).toBeTruthy();
expect(fs.readFileSync(anotherUserPath).toString()).toBe('Hello world'); expect(fs.readFileSync(anotherUserPath).toString()).toBe('Hello world');
await page.close(); await page.close();
}); });
it('should save to overwritten filepath', async({persistentDirectory, browser, server}) => { it('should save to overwritten filepath', async({tmpDir, browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true }); const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`); await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([ const [ download ] = await Promise.all([
page.waitForEvent('download'), page.waitForEvent('download'),
page.click('a') page.click('a')
]); ]);
const userPath = path.join(persistentDirectory, "download.txt"); const userPath = path.join(tmpDir, "download.txt");
await download.saveAs(userPath); await download.saveAs(userPath);
expect((await util.promisify(fs.readdir)(persistentDirectory)).length).toBe(1); expect((await util.promisify(fs.readdir)(tmpDir)).length).toBe(1);
await download.saveAs(userPath); await download.saveAs(userPath);
expect((await util.promisify(fs.readdir)(persistentDirectory)).length).toBe(1); expect((await util.promisify(fs.readdir)(tmpDir)).length).toBe(1);
expect(fs.existsSync(userPath)).toBeTruthy(); expect(fs.existsSync(userPath)).toBeTruthy();
expect(fs.readFileSync(userPath).toString()).toBe('Hello world'); expect(fs.readFileSync(userPath).toString()).toBe('Hello world');
await page.close(); await page.close();
}); });
it('should create subdirectories when saving to non-existent user-specified path', async({persistentDirectory, browser, server}) => { it('should create subdirectories when saving to non-existent user-specified path', async({tmpDir, browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true }); const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`); await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([ const [ download ] = await Promise.all([
page.waitForEvent('download'), page.waitForEvent('download'),
page.click('a') page.click('a')
]); ]);
const nestedPath = path.join(persistentDirectory, "these", "are", "directories", "download.txt"); const nestedPath = path.join(tmpDir, "these", "are", "directories", "download.txt");
await download.saveAs(nestedPath) await download.saveAs(nestedPath)
expect(fs.existsSync(nestedPath)).toBeTruthy(); expect(fs.existsSync(nestedPath)).toBeTruthy();
expect(fs.readFileSync(nestedPath).toString()).toBe('Hello world'); expect(fs.readFileSync(nestedPath).toString()).toBe('Hello world');
await page.close(); await page.close();
}); });
it('should error when saving with downloads disabled', async({persistentDirectory, browser, server}) => { it('should error when saving with downloads disabled', async({tmpDir, browser, server}) => {
const page = await browser.newPage({ acceptDownloads: false }); const page = await browser.newPage({ acceptDownloads: false });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`); await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([ const [ download ] = await Promise.all([
page.waitForEvent('download'), page.waitForEvent('download'),
page.click('a') page.click('a')
]); ]);
const userPath = path.join(persistentDirectory, "download.txt"); const userPath = path.join(tmpDir, "download.txt");
const { message } = await download.saveAs(userPath).catch(e => e); const { message } = await download.saveAs(userPath).catch(e => e);
expect(message).toContain('Pass { acceptDownloads: true } when you are creating your browser context'); expect(message).toContain('Pass { acceptDownloads: true } when you are creating your browser context');
await page.close(); await page.close();
}); });
it('should error when saving after deletion', async({persistentDirectory, browser, server}) => { it('should error when saving after deletion', async({tmpDir, browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true }); const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`); await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([ const [ download ] = await Promise.all([
page.waitForEvent('download'), page.waitForEvent('download'),
page.click('a') page.click('a')
]); ]);
const userPath = path.join(persistentDirectory, "download.txt"); const userPath = path.join(tmpDir, "download.txt");
await download.delete(); await download.delete();
const { message } = await download.saveAs(userPath).catch(e => e); const { message } = await download.saveAs(userPath).catch(e => e);
expect(message).toContain('Download already deleted. Save before deleting.'); expect(message).toContain('Download already deleted. Save before deleting.');

View File

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import './base.fixture'; import './base.fixture';
import { registerFixture } from './runner/fixtures';
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';

View File

@ -1,4 +1,21 @@
/**
* Copyright Microsoft Corporation. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import '../base.fixture'; import '../base.fixture';
import { registerFixture } from '../runner/fixtures';
import {ElectronApplication, ElectronLauncher, ElectronPage} from '../../electron-types'; import {ElectronApplication, ElectronLauncher, ElectronPage} from '../../electron-types';
import path from 'path'; import path from 'path';

View File

@ -23,16 +23,16 @@ import {PNG} from 'pngjs';
// Firefox headful produces a different image. // Firefox headful produces a different image.
const ffheadful = FFOX && !HEADLESS; const ffheadful = FFOX && !HEADLESS;
it.skip(ffheadful)('should work', async({page, server}) => { it.skip(ffheadful)('should work', async({page, server, golden}) => {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
await page.evaluate(() => window.scrollBy(50, 100)); await page.evaluate(() => window.scrollBy(50, 100));
const elementHandle = await page.$('.box:nth-of-type(3)'); const elementHandle = await page.$('.box:nth-of-type(3)');
const screenshot = await elementHandle.screenshot(); const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-bounding-box.png'); expect(screenshot).toMatchImage(golden('screenshot-element-bounding-box.png'));
}); });
it.skip(ffheadful)('should take into account padding and border', async({page}) => { it.skip(ffheadful)('should take into account padding and border', async({page, golden}) => {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.setContent(` await page.setContent(`
<div style="height: 14px">oooo</div> <div style="height: 14px">oooo</div>
@ -47,10 +47,10 @@ it.skip(ffheadful)('should take into account padding and border', async({page})
`); `);
const elementHandle = await page.$('div#d'); const elementHandle = await page.$('div#d');
const screenshot = await elementHandle.screenshot(); const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-padding-border.png'); expect(screenshot).toMatchImage(golden('screenshot-element-padding-border.png'));
}); });
it.skip(ffheadful)('should capture full element when larger than viewport in parallel', async({page}) => { it.skip(ffheadful)('should capture full element when larger than viewport in parallel', async({page, golden}) => {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.setContent(` await page.setContent(`
@ -73,12 +73,12 @@ it.skip(ffheadful)('should capture full element when larger than viewport in par
const elementHandles = await page.$$('div.to-screenshot'); const elementHandles = await page.$$('div.to-screenshot');
const promises = elementHandles.map(handle => handle.screenshot()); const promises = elementHandles.map(handle => handle.screenshot());
const screenshots = await Promise.all(promises); const screenshots = await Promise.all(promises);
expect(screenshots[2]).toBeGolden('screenshot-element-larger-than-viewport.png'); expect(screenshots[2]).toMatchImage(golden('screenshot-element-larger-than-viewport.png'));
await utils.verifyViewport(page, 500, 500); await utils.verifyViewport(page, 500, 500);
}); });
it.skip(ffheadful)('should capture full element when larger than viewport', async({page}) => { it.skip(ffheadful)('should capture full element when larger than viewport', async({page, golden}) => {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.setContent(` await page.setContent(`
@ -100,12 +100,12 @@ it.skip(ffheadful)('should capture full element when larger than viewport', asyn
`); `);
const elementHandle = await page.$('div.to-screenshot'); const elementHandle = await page.$('div.to-screenshot');
const screenshot = await elementHandle.screenshot(); const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-larger-than-viewport.png'); expect(screenshot).toMatchImage(golden('screenshot-element-larger-than-viewport.png'));
await utils.verifyViewport(page, 500, 500); await utils.verifyViewport(page, 500, 500);
}); });
it.skip(ffheadful)('should scroll element into view', async({page}) => { it.skip(ffheadful)('should scroll element into view', async({page, golden}) => {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.setContent(` await page.setContent(`
<div style="height: 14px">oooo</div> <div style="height: 14px">oooo</div>
@ -126,10 +126,10 @@ it.skip(ffheadful)('should scroll element into view', async({page}) => {
`); `);
const elementHandle = await page.$('div.to-screenshot'); const elementHandle = await page.$('div.to-screenshot');
const screenshot = await elementHandle.screenshot(); const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-scrolled-into-view.png'); expect(screenshot).toMatchImage(golden('screenshot-element-scrolled-into-view.png'));
}); });
it.skip(ffheadful)('should scroll 15000px into view', async({page}) => { it.skip(ffheadful)('should scroll 15000px into view', async({page, golden}) => {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.setContent(` await page.setContent(`
<div style="height: 14px">oooo</div> <div style="height: 14px">oooo</div>
@ -150,10 +150,10 @@ it.skip(ffheadful)('should scroll 15000px into view', async({page}) => {
`); `);
const elementHandle = await page.$('div.to-screenshot'); const elementHandle = await page.$('div.to-screenshot');
const screenshot = await elementHandle.screenshot(); const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-scrolled-into-view.png'); expect(screenshot).toMatchImage(golden('screenshot-element-scrolled-into-view.png'));
}); });
it.skip(ffheadful)('should work with a rotated element', async({page}) => { it.skip(ffheadful)('should work with a rotated element', async({page, golden}) => {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.setContent(`<div style="position:absolute; await page.setContent(`<div style="position:absolute;
top: 100px; top: 100px;
@ -164,7 +164,7 @@ it.skip(ffheadful)('should work with a rotated element', async({page}) => {
transform: rotateZ(200deg);">&nbsp;</div>`); transform: rotateZ(200deg);">&nbsp;</div>`);
const elementHandle = await page.$('div'); const elementHandle = await page.$('div');
const screenshot = await elementHandle.screenshot(); const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-rotate.png'); expect(screenshot).toMatchImage(golden('screenshot-element-rotate.png'));
}); });
it.skip(ffheadful)('should fail to screenshot a detached element', async({page, server}) => { it.skip(ffheadful)('should fail to screenshot a detached element', async({page, server}) => {
@ -183,7 +183,7 @@ it.skip(ffheadful)('should timeout waiting for visible', async({page, server}) =
expect(error.message).toContain('element is not visible'); expect(error.message).toContain('element is not visible');
}); });
it.skip(ffheadful)('should wait for visible', async({page, server}) => { it.skip(ffheadful)('should wait for visible', async({page, server, golden}) => {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
await page.evaluate(() => window.scrollBy(50, 100)); await page.evaluate(() => window.scrollBy(50, 100));
@ -199,43 +199,43 @@ it.skip(ffheadful)('should wait for visible', async({page, server}) => {
expect(done).toBe(false); expect(done).toBe(false);
await elementHandle.evaluate(e => e.style.visibility = 'visible'); await elementHandle.evaluate(e => e.style.visibility = 'visible');
const screenshot = await promise; const screenshot = await promise;
expect(screenshot).toBeGolden('screenshot-element-bounding-box.png'); expect(screenshot).toMatchImage(golden('screenshot-element-bounding-box.png'));
}); });
it.skip(ffheadful)('should work for an element with fractional dimensions', async({page}) => { it.skip(ffheadful)('should work for an element with fractional dimensions', async({page, golden}) => {
await page.setContent('<div style="width:48.51px;height:19.8px;border:1px solid black;"></div>'); await page.setContent('<div style="width:48.51px;height:19.8px;border:1px solid black;"></div>');
const elementHandle = await page.$('div'); const elementHandle = await page.$('div');
const screenshot = await elementHandle.screenshot(); const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-fractional.png'); expect(screenshot).toMatchImage(golden('screenshot-element-fractional.png'));
}); });
it.skip(FFOX)('should work with a mobile viewport', async({browser, server}) => { it.skip(FFOX)('should work with a mobile viewport', async({browser, server, golden}) => {
const context = await browser.newContext({viewport: { width: 320, height: 480 }, isMobile: true}); const context = await browser.newContext({viewport: { width: 320, height: 480 }, isMobile: true});
const page = await context.newPage(); const page = await context.newPage();
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
await page.evaluate(() => window.scrollBy(50, 100)); await page.evaluate(() => window.scrollBy(50, 100));
const elementHandle = await page.$('.box:nth-of-type(3)'); const elementHandle = await page.$('.box:nth-of-type(3)');
const screenshot = await elementHandle.screenshot(); const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-mobile.png'); expect(screenshot).toMatchImage(golden('screenshot-element-mobile.png'));
await context.close(); await context.close();
}); });
it.skip(FFOX)('should work with device scale factor', async({browser, server}) => { it.skip(FFOX)('should work with device scale factor', async({browser, server, golden}) => {
const context = await browser.newContext({ viewport: { width: 320, height: 480 }, deviceScaleFactor: 2 }); const context = await browser.newContext({ viewport: { width: 320, height: 480 }, deviceScaleFactor: 2 });
const page = await context.newPage(); const page = await context.newPage();
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
await page.evaluate(() => window.scrollBy(50, 100)); await page.evaluate(() => window.scrollBy(50, 100));
const elementHandle = await page.$('.box:nth-of-type(3)'); const elementHandle = await page.$('.box:nth-of-type(3)');
const screenshot = await elementHandle.screenshot(); const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-mobile-dsf.png'); expect(screenshot).toMatchImage(golden('screenshot-element-mobile-dsf.png'));
await context.close(); await context.close();
}); });
it.skip(ffheadful)('should work for an element with an offset', async({page}) => { it.skip(ffheadful)('should work for an element with an offset', async({page, golden}) => {
await page.setContent('<div style="position:absolute; top: 10.3px; left: 20.4px;width:50.3px;height:20.2px;border:1px solid black;"></div>'); await page.setContent('<div style="position:absolute; top: 10.3px; left: 20.4px;width:50.3px;height:20.2px;border:1px solid black;"></div>');
const elementHandle = await page.$('div'); const elementHandle = await page.$('div');
const screenshot = await elementHandle.screenshot(); const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-fractional-offset.png'); expect(screenshot).toMatchImage(golden('screenshot-element-fractional-offset.png'));
}); });
it.skip(ffheadful)('should take screenshots when default viewport is null', async({server, browser}) => { it.skip(ffheadful)('should take screenshots when default viewport is null', async({server, browser}) => {
@ -350,7 +350,7 @@ it.skip(ffheadful || WIRE)('should restore viewport after element screenshot and
await context.close(); await context.close();
}); });
it.skip(ffheadful)('should wait for element to stop moving', async({page, server}) => { it.skip(ffheadful)('should wait for element to stop moving', async({page, server, golden}) => {
await page.setViewportSize({ width: 500, height: 500 }); await page.setViewportSize({ width: 500, height: 500 });
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
const elementHandle = await page.$('.box:nth-of-type(3)'); const elementHandle = await page.$('.box:nth-of-type(3)');
@ -359,7 +359,7 @@ it.skip(ffheadful)('should wait for element to stop moving', async({page, server
return new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))); return new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f)));
}); });
const screenshot = await elementHandle.screenshot(); const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-bounding-box.png'); expect(screenshot).toMatchImage(golden('screenshot-element-bounding-box.png'));
}); });
it.skip(ffheadful)('should take screenshot of disabled button', async({page}) => { it.skip(ffheadful)('should take screenshot of disabled button', async({page}) => {

View File

@ -102,7 +102,7 @@ it('should change document.activeElement', async({page, server}) => {
expect(active).toEqual(['INPUT', 'TEXTAREA']); expect(active).toEqual(['INPUT', 'TEXTAREA']);
}); });
it.skip(FFOX && !HEADLESS)('should not affect screenshots', async({page, server}) => { it.skip(FFOX && !HEADLESS)('should not affect screenshots', async({page, server, golden}) => {
// Firefox headful produces a different image. // Firefox headful produces a different image.
const page2 = await page.context().newPage(); const page2 = await page.context().newPage();
await Promise.all([ await Promise.all([
@ -119,8 +119,8 @@ it.skip(FFOX && !HEADLESS)('should not affect screenshots', async({page, server}
page.screenshot(), page.screenshot(),
page2.screenshot(), page2.screenshot(),
]); ]);
expect(screenshots[0]).toBeGolden('screenshot-sanity.png'); expect(screenshots[0]).toMatchImage(golden('screenshot-sanity.png'));
expect(screenshots[1]).toBeGolden('grid-cell-0.png'); expect(screenshots[1]).toMatchImage(golden('grid-cell-0.png'));
}); });
it('should change focused iframe', async({page, server}) => { it('should change focused iframe', async({page, server}) => {

View File

@ -15,6 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import './base.fixture'; import './base.fixture';
import { registerFixture } from './runner/fixtures';
import path from 'path'; import path from 'path';
import {spawn, execSync} from 'child_process'; import {spawn, execSync} from 'child_process';

View File

@ -19,7 +19,7 @@ import './base.fixture';
import utils from './utils'; import utils from './utils';
import path from 'path'; import path from 'path';
import url from 'url'; import url from 'url';
const {FFOX, CHROMIUM, WEBKIT, ASSETS_DIR, MAC, WIN} = testOptions; const {FFOX, CHROMIUM, WEBKIT, MAC, WIN} = testOptions;
it('should navigate subframes', async({page, server}) => { it('should navigate subframes', async({page, server}) => {
await page.goto(server.PREFIX + '/frames/one-frame.html'); await page.goto(server.PREFIX + '/frames/one-frame.html');

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 B

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 461 B

After

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 B

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 B

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -19,7 +19,7 @@ import './base.fixture';
import utils from './utils'; import utils from './utils';
import path from 'path'; import path from 'path';
import url from 'url'; import url from 'url';
const {FFOX, CHROMIUM, WEBKIT, ASSETS_DIR, MAC, WIN} = testOptions; const {FFOX, CHROMIUM, WEBKIT, MAC, WIN} = testOptions;
it('should work', async({page, server}) => { it('should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);

View File

@ -18,7 +18,7 @@ import './base.fixture';
import path from 'path'; import path from 'path';
import url from 'url'; import url from 'url';
const {FFOX, CHROMIUM, WEBKIT, ASSETS_DIR, MAC, WIN} = testOptions; const {FFOX, CHROMIUM, WEBKIT, MAC, WIN} = testOptions;
it('page.goBack should work', async({page, server}) => { it('page.goBack should work', async({page, server}) => {
expect(await page.goBack()).toBe(null); expect(await page.goBack()).toBe(null);
@ -54,9 +54,9 @@ it('page.goBack should work with HistoryAPI', async({page, server}) => {
expect(page.url()).toBe(server.PREFIX + '/first.html'); expect(page.url()).toBe(server.PREFIX + '/first.html');
}); });
it.fail(WEBKIT && MAC)('page.goBack should work for file urls', async ({page, server}) => { it.fail(WEBKIT && MAC)('page.goBack should work for file urls', async ({page, server, asset}) => {
// WebKit embedder fails to go back/forward to the file url. // WebKit embedder fails to go back/forward to the file url.
const url1 = url.pathToFileURL(path.join(ASSETS_DIR, 'empty.html')).href; const url1 = url.pathToFileURL(asset('empty.html')).href;
const url2 = server.EMPTY_PAGE; const url2 = server.EMPTY_PAGE;
await page.goto(url1); await page.goto(url1);
await page.setContent(`<a href='${url2}'>url2</a>`); await page.setContent(`<a href='${url2}'>url2</a>`);

View File

@ -21,7 +21,7 @@ import path from 'path';
import url from 'url'; import url from 'url';
import { Frame, Page } from '..'; import { Frame, Page } from '..';
import { TestServer } from '../utils/testserver'; import { TestServer } from '../utils/testserver';
const {FFOX, CHROMIUM, WEBKIT, ASSETS_DIR, MAC, WIN} = testOptions; const {FFOX, CHROMIUM, WEBKIT, MAC, WIN} = testOptions;
it('should navigate to empty page with networkidle', async({page, server}) => { it('should navigate to empty page with networkidle', async({page, server}) => {
const response = await page.goto(server.EMPTY_PAGE, { waitUntil: 'networkidle' }); const response = await page.goto(server.EMPTY_PAGE, { waitUntil: 'networkidle' });

View File

@ -17,20 +17,19 @@
import './base.fixture'; import './base.fixture';
import utils from './utils'; import utils from './utils';
const {FFOX, CHROMIUM, WEBKIT, WIRE, HEADLESS} = testOptions; const {FFOX, WEBKIT, HEADLESS} = testOptions;
import {PNG} from 'pngjs';
// Firefox headful produces a different image. // Firefox headful produces a different image.
const ffheadful = FFOX && !HEADLESS; const ffheadful = FFOX && !HEADLESS;
it.skip(ffheadful)('should work', async({page, server}) => { it.skip(ffheadful)('should work', async({page, server, golden}) => {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
const screenshot = await page.screenshot(); const screenshot = await page.screenshot();
expect(screenshot).toBeGolden('screenshot-sanity.png'); expect(screenshot).toMatchImage(golden('screenshot-sanity.png'));
}); });
it.skip(ffheadful)('should clip rect', async({page, server}) => { it.skip(ffheadful)('should clip rect', async({page, server, golden}) => {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
const screenshot = await page.screenshot({ const screenshot = await page.screenshot({
@ -41,10 +40,10 @@ it.skip(ffheadful)('should clip rect', async({page, server}) => {
height: 100 height: 100
} }
}); });
expect(screenshot).toBeGolden('screenshot-clip-rect.png'); expect(screenshot).toMatchImage(golden('screenshot-clip-rect.png'));
}); });
it.skip(ffheadful)('should clip rect with fullPage', async({page, server}) => { it.skip(ffheadful)('should clip rect with fullPage', async({page, server, golden}) => {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
await page.evaluate(() => window.scrollBy(150, 200)); await page.evaluate(() => window.scrollBy(150, 200));
@ -57,10 +56,10 @@ it.skip(ffheadful)('should clip rect with fullPage', async({page, server}) => {
height: 100, height: 100,
}, },
}); });
expect(screenshot).toBeGolden('screenshot-clip-rect.png'); expect(screenshot).toMatchImage(golden('screenshot-clip-rect.png'));
}); });
it.skip(ffheadful)('should clip elements to the viewport', async({page, server}) => { it.skip(ffheadful)('should clip elements to the viewport', async({page, server, golden}) => {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
const screenshot = await page.screenshot({ const screenshot = await page.screenshot({
@ -71,10 +70,10 @@ it.skip(ffheadful)('should clip elements to the viewport', async({page, server})
height: 100 height: 100
} }
}); });
expect(screenshot).toBeGolden('screenshot-offscreen-clip.png'); expect(screenshot).toMatchImage(golden('screenshot-offscreen-clip.png'));
}); });
it.skip(ffheadful)('should throw on clip outside the viewport', async({page, server}) => { it.skip(ffheadful)('should throw on clip outside the viewport', async({page, server, golden}) => {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
const screenshotError = await page.screenshot({ const screenshotError = await page.screenshot({
@ -88,7 +87,7 @@ it.skip(ffheadful)('should throw on clip outside the viewport', async({page, ser
expect(screenshotError.message).toContain('Clipped area is either empty or outside the resulting image'); expect(screenshotError.message).toContain('Clipped area is either empty or outside the resulting image');
}); });
it.skip(ffheadful)('should run in parallel', async({page, server}) => { it.skip(ffheadful)('should run in parallel', async({page, server, golden}) => {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
const promises = []; const promises = [];
@ -103,16 +102,16 @@ it.skip(ffheadful)('should run in parallel', async({page, server}) => {
})); }));
} }
const screenshots = await Promise.all(promises); const screenshots = await Promise.all(promises);
expect(screenshots[1]).toBeGolden('grid-cell-1.png'); expect(screenshots[1]).toMatchImage(golden('grid-cell-1.png'));
}); });
it.skip(ffheadful)('should take fullPage screenshots', async({page, server}) => { it.skip(ffheadful)('should take fullPage screenshots', async({page, server, golden}) => {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
const screenshot = await page.screenshot({ const screenshot = await page.screenshot({
fullPage: true fullPage: true
}); });
expect(screenshot).toBeGolden('screenshot-grid-fullpage.png'); expect(screenshot).toMatchImage(golden('screenshot-grid-fullpage.png'));
}); });
it.skip(ffheadful)('should restore viewport after fullPage screenshot', async({page, server}) => { it.skip(ffheadful)('should restore viewport after fullPage screenshot', async({page, server}) => {
@ -123,7 +122,7 @@ it.skip(ffheadful)('should restore viewport after fullPage screenshot', async({p
await utils.verifyViewport(page, 500, 500); await utils.verifyViewport(page, 500, 500);
}); });
it.skip(ffheadful)('should run in parallel in multiple pages', async({page, server, context}) => { it.skip(ffheadful)('should run in parallel in multiple pages', async({server, context, golden}) => {
const N = 5; const N = 5;
const pages = await Promise.all(Array(N).fill(0).map(async() => { const pages = await Promise.all(Array(N).fill(0).map(async() => {
const page = await context.newPage(); const page = await context.newPage();
@ -135,11 +134,11 @@ it.skip(ffheadful)('should run in parallel in multiple pages', async({page, serv
promises.push(pages[i].screenshot({ clip: { x: 50 * (i % 2), y: 0, width: 50, height: 50 } })); promises.push(pages[i].screenshot({ clip: { x: 50 * (i % 2), y: 0, width: 50, height: 50 } }));
const screenshots = await Promise.all(promises); const screenshots = await Promise.all(promises);
for (let i = 0; i < N; ++i) for (let i = 0; i < N; ++i)
expect(screenshots[i]).toBeGolden(`grid-cell-${i % 2}.png`); expect(screenshots[i]).toMatchImage(golden(`grid-cell-${i % 2}.png`));
await Promise.all(pages.map(page => page.close())); await Promise.all(pages.map(page => page.close()));
}); });
it.fail(FFOX)('should allow transparency', async({page}) => { it.fail(FFOX)('should allow transparency', async({page, golden}) => {
await page.setViewportSize({ width: 50, height: 150 }); await page.setViewportSize({ width: 50, height: 150 });
await page.setContent(` await page.setContent(`
<style> <style>
@ -151,17 +150,17 @@ it.fail(FFOX)('should allow transparency', async({page}) => {
<div style="background:transparent"></div> <div style="background:transparent"></div>
`); `);
const screenshot = await page.screenshot({omitBackground: true}); const screenshot = await page.screenshot({omitBackground: true});
expect(screenshot).toBeGolden('transparent.png'); expect(screenshot).toMatchImage(golden('transparent.png'));
}); });
it.skip(ffheadful)('should render white background on jpeg file', async({page, server}) => { it.skip(ffheadful)('should render white background on jpeg file', async({page, server, golden}) => {
await page.setViewportSize({ width: 100, height: 100 }); await page.setViewportSize({ width: 100, height: 100 });
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const screenshot = await page.screenshot({omitBackground: true, type: 'jpeg'}); const screenshot = await page.screenshot({omitBackground: true, type: 'jpeg'});
expect(screenshot).toBeGolden('white.jpg'); expect(screenshot).toMatchImage(golden('white.jpg'));
}); });
it.skip(ffheadful)('should work with odd clip size on Retina displays', async({page}) => { it.skip(ffheadful)('should work with odd clip size on Retina displays', async({page, golden}) => {
const screenshot = await page.screenshot({ const screenshot = await page.screenshot({
clip: { clip: {
x: 0, x: 0,
@ -170,55 +169,55 @@ it.skip(ffheadful)('should work with odd clip size on Retina displays', async({p
height: 11, height: 11,
} }
}); });
expect(screenshot).toBeGolden('screenshot-clip-odd-size.png'); expect(screenshot).toMatchImage(golden('screenshot-clip-odd-size.png'));
}); });
it.skip(FFOX)('should work with a mobile viewport', async({browser, server}) => { it.skip(FFOX)('should work with a mobile viewport', async({browser, server, golden}) => {
const context = await browser.newContext({ viewport: { width: 320, height: 480 }, isMobile: true }); const context = await browser.newContext({ viewport: { width: 320, height: 480 }, isMobile: true });
const page = await context.newPage(); const page = await context.newPage();
await page.goto(server.PREFIX + '/overflow.html'); await page.goto(server.PREFIX + '/overflow.html');
const screenshot = await page.screenshot(); const screenshot = await page.screenshot();
expect(screenshot).toBeGolden('screenshot-mobile.png'); expect(screenshot).toMatchImage(golden('screenshot-mobile.png'));
await context.close(); await context.close();
}); });
it.skip(FFOX)('should work with a mobile viewport and clip', async({browser, server}) => { it.skip(FFOX)('should work with a mobile viewport and clip', async({browser, server, golden}) => {
const context = await browser.newContext({viewport: { width: 320, height: 480 }, isMobile: true}); const context = await browser.newContext({viewport: { width: 320, height: 480 }, isMobile: true});
const page = await context.newPage(); const page = await context.newPage();
await page.goto(server.PREFIX + '/overflow.html'); await page.goto(server.PREFIX + '/overflow.html');
const screenshot = await page.screenshot({ clip: { x: 10, y: 10, width: 100, height: 150 } }); const screenshot = await page.screenshot({ clip: { x: 10, y: 10, width: 100, height: 150 } });
expect(screenshot).toBeGolden('screenshot-mobile-clip.png'); expect(screenshot).toMatchImage(golden('screenshot-mobile-clip.png'));
await context.close(); await context.close();
}); });
it.skip(FFOX)('should work with a mobile viewport and fullPage', async({browser, server}) => { it.skip(FFOX)('should work with a mobile viewport and fullPage', async({browser, server, golden}) => {
const context = await browser.newContext({viewport: { width: 320, height: 480 }, isMobile: true}); const context = await browser.newContext({viewport: { width: 320, height: 480 }, isMobile: true});
const page = await context.newPage(); const page = await context.newPage();
await page.goto(server.PREFIX + '/overflow-large.html'); await page.goto(server.PREFIX + '/overflow-large.html');
const screenshot = await page.screenshot({ fullPage: true }); const screenshot = await page.screenshot({ fullPage: true });
expect(screenshot).toBeGolden('screenshot-mobile-fullpage.png'); expect(screenshot).toMatchImage(golden('screenshot-mobile-fullpage.png'));
await context.close(); await context.close();
}); });
it.skip(ffheadful)('should work for canvas', async({page, server}) => { it.skip(ffheadful)('should work for canvas', async({page, server, golden}) => {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/screenshots/canvas.html'); await page.goto(server.PREFIX + '/screenshots/canvas.html');
const screenshot = await page.screenshot(); const screenshot = await page.screenshot();
expect(screenshot).toBeGolden('screenshot-canvas.png'); expect(screenshot).toMatchImage(golden('screenshot-canvas.png'));
}); });
it.skip(ffheadful)('should work for translateZ', async({page, server}) => { it.skip(ffheadful)('should work for translateZ', async({page, server, golden}) => {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/screenshots/translateZ.html'); await page.goto(server.PREFIX + '/screenshots/translateZ.html');
const screenshot = await page.screenshot(); const screenshot = await page.screenshot();
expect(screenshot).toBeGolden('screenshot-translateZ.png'); expect(screenshot).toMatchImage(golden('screenshot-translateZ.png'));
}); });
it.fail(FFOX || WEBKIT)('should work for webgl', async({page, server}) => { it.fail(FFOX || WEBKIT)('should work for webgl', async({page, server, golden}) => {
await page.setViewportSize({width: 640, height: 480}); await page.setViewportSize({width: 640, height: 480});
await page.goto(server.PREFIX + '/screenshots/webgl.html'); await page.goto(server.PREFIX + '/screenshots/webgl.html');
const screenshot = await page.screenshot(); const screenshot = await page.screenshot();
expect(screenshot).toBeGolden('screenshot-webgl.png'); expect(screenshot).toMatchImage(golden('screenshot-webgl.png'));
}); });
it.skip(ffheadful)('should work while navigating', async({page, server}) => { it.skip(ffheadful)('should work while navigating', async({page, server}) => {
@ -234,17 +233,17 @@ it.skip(ffheadful)('should work while navigating', async({page, server}) => {
} }
}); });
it.skip(ffheadful)('should work with device scale factor', async({browser, server}) => { it.skip(ffheadful)('should work with device scale factor', async({browser, server, golden}) => {
const context = await browser.newContext({ viewport: { width: 320, height: 480 }, deviceScaleFactor: 2 }); const context = await browser.newContext({ viewport: { width: 320, height: 480 }, deviceScaleFactor: 2 });
const page = await context.newPage(); const page = await context.newPage();
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
const screenshot = await page.screenshot(); const screenshot = await page.screenshot();
expect(screenshot).toBeGolden('screenshot-device-scale-factor.png'); expect(screenshot).toMatchImage(golden('screenshot-device-scale-factor.png'));
await context.close(); await context.close();
}); });
it.skip(ffheadful)('should work with iframe in shadow', async({browser, page, server}) => { it.skip(ffheadful)('should work with iframe in shadow', async({page, server, golden}) => {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid-iframe-in-shadow.html'); await page.goto(server.PREFIX + '/grid-iframe-in-shadow.html');
expect(await page.screenshot()).toBeGolden('screenshot-iframe.png'); expect(await page.screenshot()).toMatchImage(golden('screenshot-iframe.png'));
}); });

View File

@ -20,7 +20,7 @@ import utils from './utils';
import path from 'path'; import path from 'path';
import url from 'url'; import url from 'url';
import { Route } from '..'; import { Route } from '..';
const {FFOX, CHROMIUM, WEBKIT, ASSETS_DIR, MAC, WIN} = testOptions; const {FFOX, CHROMIUM, WEBKIT, MAC, WIN} = testOptions;
it('should pick up ongoing navigation', async({page, server}) => { it('should pick up ongoing navigation', async({page, server}) => {
let response = null; let response = null;

View File

@ -21,7 +21,7 @@ import path from 'path';
import url from 'url'; import url from 'url';
import { Frame } from '..'; import { Frame } from '..';
const {FFOX, CHROMIUM, WEBKIT, ASSETS_DIR, MAC, WIN} = testOptions; const {FFOX, CHROMIUM, WEBKIT, MAC, WIN} = testOptions;
it('should work', async({page, server}) => { it('should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);

View File

@ -21,8 +21,8 @@ import path from 'path'
const {FFOX, CHROMIUM, WEBKIT, HEADLESS} = testOptions; const {FFOX, CHROMIUM, WEBKIT, HEADLESS} = testOptions;
// Printing to pdf is currently only supported in headless chromium. // Printing to pdf is currently only supported in headless chromium.
it.skip(!(HEADLESS && CHROMIUM))('should be able to save file', async({page, outputDir}) => { it.skip(!(HEADLESS && CHROMIUM))('should be able to save file', async({page, tmpDir}) => {
const outputFile = path.join(outputDir, 'output.pdf'); const outputFile = path.join(tmpDir, 'output.pdf');
await page.pdf({path: outputFile}); await page.pdf({path: outputFile});
expect(fs.readFileSync(outputFile).byteLength).toBeGreaterThan(0); expect(fs.readFileSync(outputFile).byteLength).toBeGreaterThan(0);
fs.unlinkSync(outputFile); fs.unlinkSync(outputFile);

View File

@ -52,7 +52,7 @@ it('should work with status code 422', async({page, server}) => {
expect(await page.evaluate(() => document.body.textContent)).toBe('Yo, page!'); expect(await page.evaluate(() => document.body.textContent)).toBe('Yo, page!');
}); });
it.skip(FFOX && !HEADLESS)('should allow mocking binary responses', async({page, server}) => { it.skip(FFOX && !HEADLESS)('should allow mocking binary responses', async({page, server, golden}) => {
// Firefox headful produces a different image. // Firefox headful produces a different image.
await page.route('**/*', route => { await page.route('**/*', route => {
const imageBuffer = fs.readFileSync(path.join(__dirname, 'assets', 'pptr.png')); const imageBuffer = fs.readFileSync(path.join(__dirname, 'assets', 'pptr.png'));
@ -68,10 +68,10 @@ it.skip(FFOX && !HEADLESS)('should allow mocking binary responses', async({page,
return new Promise(fulfill => img.onload = fulfill); return new Promise(fulfill => img.onload = fulfill);
}, server.PREFIX); }, server.PREFIX);
const img = await page.$('img'); const img = await page.$('img');
expect(await img.screenshot()).toBeGolden('mock-binary-response.png'); expect(await img.screenshot()).toMatchImage(golden('mock-binary-response.png'));
}); });
it.skip(FFOX && !HEADLESS)('should allow mocking svg with charset', async({page, server}) => { it.skip(FFOX && !HEADLESS)('should allow mocking svg with charset', async({page, server, golden}) => {
// Firefox headful produces a different image. // Firefox headful produces a different image.
await page.route('**/*', route => { await page.route('**/*', route => {
route.fulfill({ route.fulfill({
@ -86,10 +86,10 @@ it.skip(FFOX && !HEADLESS)('should allow mocking svg with charset', async({page,
return new Promise((f, r) => { img.onload = f; img.onerror = r; }); return new Promise((f, r) => { img.onload = f; img.onerror = r; });
}, server.PREFIX); }, server.PREFIX);
const img = await page.$('img'); const img = await page.$('img');
expect(await img.screenshot()).toBeGolden('mock-svg.png'); expect(await img.screenshot()).toMatchImage(golden('mock-svg.png'));
}); });
it('should work with file path', async({page, server}) => { it('should work with file path', async({page, server, golden}) => {
await page.route('**/*', route => route.fulfill({ contentType: 'shouldBeIgnored', path: path.join(__dirname, 'assets', 'pptr.png') })); await page.route('**/*', route => route.fulfill({ contentType: 'shouldBeIgnored', path: path.join(__dirname, 'assets', 'pptr.png') }));
await page.evaluate(PREFIX => { await page.evaluate(PREFIX => {
const img = document.createElement('img'); const img = document.createElement('img');
@ -98,7 +98,7 @@ it('should work with file path', async({page, server}) => {
return new Promise(fulfill => img.onload = fulfill); return new Promise(fulfill => img.onload = fulfill);
}, server.PREFIX); }, server.PREFIX);
const img = await page.$('img'); const img = await page.$('img');
expect(await img.screenshot()).toBeGolden('mock-binary-response.png'); expect(await img.screenshot()).toMatchImage(golden('mock-binary-response.png'));
}); });
it('should stringify intercepted request response headers', async({page, server}) => { it('should stringify intercepted request response headers', async({page, server}) => {

View File

@ -85,76 +85,53 @@ function compareText(actual, expectedBuffer) {
/** /**
* @param {?Object} actual * @param {?Object} actual
* @param {!{goldenPath: string, outputPath: string, goldenName: string}} golden * @param {string} path
* @return {!{pass: boolean, message: (undefined|string)}} * @return {!{pass: boolean, message: (undefined|string)}}
*/ */
function compare(actual, golden) { function compare(actual, expectedPath) {
const goldenPath = path.normalize(golden.goldenPath);
const outputPath = path.normalize(golden.outputPath);
const goldenName = golden.goldenName;
const expectedPath = path.resolve(goldenPath, goldenName);
const actualPath = path.resolve(outputPath, goldenName);
const messageSuffix = 'Output is saved in "' + path.basename(outputPath + '" directory');
if (!fs.existsSync(expectedPath)) { if (!fs.existsSync(expectedPath)) {
ensureOutputDir(); fs.writeFileSync(expectedPath, actual);
fs.writeFileSync(actualPath, actual);
return { return {
pass: false, pass: false,
message: goldenName + ' is missing in golden results. ' + messageSuffix message: expectedPath + ' is missing in golden results, writing actual.'
}; };
} }
const expected = fs.readFileSync(expectedPath); const expected = fs.readFileSync(expectedPath);
const extension = goldenName.substring(goldenName.lastIndexOf('.') + 1); const extension = path.extname(expectedPath).substring(1);
const mimeType = extensionToMimeType[extension]; const mimeType = extensionToMimeType[extension];
const comparator = GoldenComparators[mimeType]; const comparator = GoldenComparators[mimeType];
if (!comparator) { if (!comparator) {
return { return {
pass: false, pass: false,
message: 'Failed to find comparator with type ' + mimeType + ': ' + goldenName, message: 'Failed to find comparator with type ' + mimeType + ': ' + expectedPath,
}; };
} }
const result = comparator(actual, expected, mimeType); const result = comparator(actual, expected, mimeType);
if (!result) if (!result)
return { pass: true }; return { pass: true };
ensureOutputDir();
const actualPath = addSuffix(expectedPath, '-actual');
const diffPath = addSuffix(expectedPath, '-diff', result.diffExtension);
fs.writeFileSync(actualPath, actual);
if (result.diff)
fs.writeFileSync(diffPath, result.diff);
const output = [ const output = [
c.red(`GOLDEN FAILED: `) + c.yellow('"' + goldenName + '"'), c.red(`Image comparison failed:`),
]; ];
if (result.errorMessage) if (result.errorMessage)
output.push(' ' + result.errorMessage); output.push(' ' + result.errorMessage);
output.push(''); output.push('');
output.push(`Expected: ${c.yellow(expectedPath)}`); output.push(`Expected: ${c.yellow(expectedPath)}`);
if (goldenPath === outputPath) { output.push(`Received: ${c.yellow(actualPath)}`);
const filepath = addSuffix(actualPath, '-actual'); if (result.diff)
fs.writeFileSync(filepath, actual);
output.push(`Received: ${c.yellow(filepath)}`);
} else {
fs.writeFileSync(actualPath, actual);
// Copy expected to the output/ folder for convenience.
fs.writeFileSync(addSuffix(actualPath, '-expected'), expected);
output.push(`Received: ${c.yellow(actualPath)}`);
}
if (result.diff) {
const diffPath = addSuffix(actualPath, '-diff', result.diffExtension);
fs.writeFileSync(diffPath, result.diff);
output.push(` Diff: ${c.yellow(diffPath)}`); output.push(` Diff: ${c.yellow(diffPath)}`);
}
let message = goldenName + ' mismatch!';
if (result.errorMessage)
message += ' ' + result.errorMessage;
return { return {
pass: false, pass: false,
message: message + ' ' + messageSuffix, message: output.join('n'),
formatter: () => output.join('\n'),
}; };
function ensureOutputDir() {
if (!fs.existsSync(outputPath))
fs.mkdirSync(outputPath);
}
} }
/** /**

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
const { FixturePool, registerFixture, registerWorkerFixture } = require('./fixturePool'); const { FixturePool, registerFixture, registerWorkerFixture } = require('./fixtures');
const { Test, Suite } = require('mocha'); const { Test, Suite } = require('mocha');
const {installTransform} = require('./transform'); const {installTransform} = require('./transform');
const commonSuite = require('mocha/lib/interfaces/common'); const commonSuite = require('mocha/lib/interfaces/common');

View File

@ -30,7 +30,5 @@ testOptions.FFOX = browserName === 'firefox';
testOptions.WEBKIT = browserName === 'webkit'; testOptions.WEBKIT = browserName === 'webkit';
testOptions.WIRE = process.env.PWWIRE; testOptions.WIRE = process.env.PWWIRE;
testOptions.HEADLESS = !!valueFromEnv('HEADLESS', true); testOptions.HEADLESS = !!valueFromEnv('HEADLESS', true);
testOptions.ASSETS_DIR = path.join(__dirname, '..', 'assets');
testOptions.GOLDEN_DIR = path.join(__dirname, '..', 'golden-' + browserName);
module.exports = testOptions; module.exports = testOptions;

View File

@ -16,14 +16,10 @@
const path = require('path'); const path = require('path');
const Mocha = require('mocha'); const Mocha = require('mocha');
const { registerWorkerFixture } = require('./fixturePool');
const { fixturesUI, fixturePool } = require('./fixturesUI'); const { fixturesUI, fixturePool } = require('./fixturesUI');
const { gracefullyCloseAll } = require('../../lib/server/processLauncher'); const { gracefullyCloseAll } = require('../../lib/server/processLauncher');
const GoldenUtils = require('./GoldenUtils'); const GoldenUtils = require('./GoldenUtils');
const browserName = process.env.BROWSER || 'chromium';
const goldenPath = path.join(__dirname, '..', 'golden-' + browserName);
const outputPath = path.join(__dirname, '..', 'output-' + browserName);
global.expect = require('expect'); global.expect = require('expect');
global.testOptions = require('./testOptions'); global.testOptions = require('./testOptions');
@ -155,13 +151,9 @@ function serializeError(error) {
} }
function extendExpects() { function extendExpects() {
function toBeGolden(received, goldenName) { function toMatchImage(received, path) {
const {pass, message} = GoldenUtils.compare(received, { const {pass, message} = GoldenUtils.compare(received, path);
goldenPath,
outputPath,
goldenName
});
return {pass, message: () => message}; return {pass, message: () => message};
}; };
global.expect.extend({ toBeGolden }); global.expect.extend({ toMatchImage });
} }

View File

@ -14,29 +14,21 @@
* limitations under the License. * limitations under the License.
*/ */
import './base.fixture'; import './base.fixture';
import { registerFixture } from './runner/fixtures';
import { Page } from '..'; import { Page } from '..';
const fs = require('fs'); const fs = require('fs');
const os = require('os');
const path = require('path'); const path = require('path');
const url = require('url'); const url = require('url');
const {mkdtempAsync, removeFolderAsync} = require('./utils');
const {FFOX, CHROMIUM, WEBKIT, MAC, LINUX, WIN, HEADLESS, WIRE} = testOptions; const {FFOX, CHROMIUM, WEBKIT, MAC, LINUX, WIN, HEADLESS, WIRE} = testOptions;
declare global { declare global {
interface FixtureState { interface FixtureState {
persistentDirectory: string;
videoPlayer: VideoPlayer; videoPlayer: VideoPlayer;
} }
} }
registerFixture('persistentDirectory', async ({}, test) => {
const persistentDirectory = await mkdtempAsync(path.join(os.tmpdir(), 'playwright-test-'));
await test(persistentDirectory);
await removeFolderAsync(persistentDirectory);
});
registerFixture('videoPlayer', async ({playwright, context}, test) => { registerFixture('videoPlayer', async ({playwright, context}, test) => {
let firefox; let firefox;
if (WEBKIT && !LINUX) { if (WEBKIT && !LINUX) {
@ -180,10 +172,10 @@ class VideoPlayer {
} }
} }
it.fail(CHROMIUM)('should capture static page', async({page, persistentDirectory, videoPlayer, toImpl}) => { it.fail(CHROMIUM)('should capture static page', async({page, tmpDir, videoPlayer, toImpl}) => {
if (!toImpl) if (!toImpl)
return; return;
const videoFile = path.join(persistentDirectory, 'v.webm'); const videoFile = path.join(tmpDir, 'v.webm');
await page.evaluate(() => document.body.style.backgroundColor = 'red'); await page.evaluate(() => document.body.style.backgroundColor = 'red');
await toImpl(page)._delegate.startVideoRecording({outputFile: videoFile, width: 640, height: 480}); await toImpl(page)._delegate.startVideoRecording({outputFile: videoFile, width: 640, height: 480});
// TODO: in WebKit figure out why video size is not reported correctly for // TODO: in WebKit figure out why video size is not reported correctly for
@ -206,10 +198,10 @@ it.fail(CHROMIUM)('should capture static page', async({page, persistentDirectory
expectAll(pixels, almostRed); expectAll(pixels, almostRed);
}); });
it.fail(CHROMIUM)('should capture navigation', async({page, persistentDirectory, server, videoPlayer, toImpl}) => { it.fail(CHROMIUM)('should capture navigation', async({page, tmpDir, server, videoPlayer, toImpl}) => {
if (!toImpl) if (!toImpl)
return; return;
const videoFile = path.join(persistentDirectory, 'v.webm'); const videoFile = path.join(tmpDir, 'v.webm');
await page.goto(server.PREFIX + '/background-color.html#rgb(0,0,0)'); await page.goto(server.PREFIX + '/background-color.html#rgb(0,0,0)');
await toImpl(page)._delegate.startVideoRecording({outputFile: videoFile, width: 640, height: 480}); await toImpl(page)._delegate.startVideoRecording({outputFile: videoFile, width: 640, height: 480});
// TODO: in WebKit figure out why video size is not reported correctly for // TODO: in WebKit figure out why video size is not reported correctly for
@ -239,10 +231,10 @@ it.fail(CHROMIUM)('should capture navigation', async({page, persistentDirectory,
} }
}); });
it.fail(CHROMIUM)('should capture css transformation', async({page, persistentDirectory, server, videoPlayer, toImpl}) => { it.fail(CHROMIUM)('should capture css transformation', async({page, tmpDir, server, videoPlayer, toImpl}) => {
if (!toImpl) if (!toImpl)
return; return;
const videoFile = path.join(persistentDirectory, 'v.webm'); const videoFile = path.join(tmpDir, 'v.webm');
await page.goto(server.PREFIX + '/rotate-z.html'); await page.goto(server.PREFIX + '/rotate-z.html');
await toImpl(page)._delegate.startVideoRecording({outputFile: videoFile, width: 640, height: 480}); await toImpl(page)._delegate.startVideoRecording({outputFile: videoFile, width: 640, height: 480});
// TODO: in WebKit figure out why video size is not reported correctly for // TODO: in WebKit figure out why video size is not reported correctly for

6
test/types.d.ts vendored
View File

@ -19,7 +19,7 @@ interface FixtureState {
declare module '' { declare module '' {
module 'expect/build/types' { module 'expect/build/types' {
interface Matchers<R> { interface Matchers<R> {
toBeGolden(name: string): R; toMatchImage(path: string): R;
} }
} }
} }
@ -41,9 +41,6 @@ declare const afterEach: (inner: (state: FixtureState & WorkerState) => Promise<
declare const beforeAll: (inner: (state: WorkerState) => Promise<void>) => void; declare const beforeAll: (inner: (state: WorkerState) => Promise<void>) => void;
declare const afterAll: (inner: (state: WorkerState) => Promise<void>) => void; declare const afterAll: (inner: (state: WorkerState) => Promise<void>) => void;
declare const registerFixture: <T extends keyof FixtureState>(name: T, inner: (state: FixtureState & WorkerState, test: (arg: FixtureState[T]) => Promise<void>) => Promise<void>) => void;
declare const registerWorkerFixture: <T extends keyof WorkerState>(name: T, inner: (state: WorkerState, test: (arg: WorkerState[T]) => Promise<void>) => Promise<void>) => void;
declare const browserType: import('../index').BrowserType<import('../index').Browser>; declare const browserType: import('../index').BrowserType<import('../index').Browser>;
// global variables in assets // global variables in assets
@ -56,7 +53,6 @@ declare const testOptions: {
WIN: boolean; WIN: boolean;
HEADLESS: boolean; HEADLESS: boolean;
WIRE: boolean; WIRE: boolean;
ASSETS_DIR: string;
}; };
declare const testPath : string; declare const testPath : string;

View File

@ -52,7 +52,7 @@ async function testLint(name) {
const jsSources = await Source.readdir(dirPath, '.js'); const jsSources = await Source.readdir(dirPath, '.js');
const messages = await checkPublicAPI(page, mdSources, jsSources.concat(tsSources)); const messages = await checkPublicAPI(page, mdSources, jsSources.concat(tsSources));
const errors = messages.map(message => message.text); const errors = messages.map(message => message.text);
expect(errors.join('\n')).toBeGolden(path.join(dirPath, 'result.txt')); expect(errors.join('\n')).toMatchImage(path.join(dirPath, 'result.txt'));
}); });
} }
@ -61,7 +61,7 @@ async function testMDBuilder(name) {
const dirPath = path.join(__dirname, name); const dirPath = path.join(__dirname, name);
const sources = await Source.readdir(dirPath, '.md'); const sources = await Source.readdir(dirPath, '.md');
const {documentation} = await mdBuilder(page, sources); const {documentation} = await mdBuilder(page, sources);
expect(serialize(documentation)).toBeGolden(path.join(dirPath, 'result.txt')); expect(serialize(documentation)).toMatchImage(path.join(dirPath, 'result.txt'));
}); });
} }
@ -71,7 +71,7 @@ async function testJSBuilder(name) {
const jsSources = await Source.readdir(dirPath, '.js'); const jsSources = await Source.readdir(dirPath, '.js');
const tsSources = await Source.readdir(dirPath, '.ts'); const tsSources = await Source.readdir(dirPath, '.ts');
const {documentation} = await jsBuilder.checkSources(jsSources.concat(tsSources)); const {documentation} = await jsBuilder.checkSources(jsSources.concat(tsSources));
expect(serialize(documentation)).toBeGolden(path.join(dirPath, 'result.txt')); expect(serialize(documentation)).toMatchImage(path.join(dirPath, 'result.txt'));
}); });
} }