mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
test: roll to folio@0.4.0-alpha14 (#6602)
This commit is contained in:
parent
c497c32ec9
commit
4c3bd11820
18
package-lock.json
generated
18
package-lock.json
generated
@ -2659,9 +2659,9 @@
|
||||
}
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.727",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.727.tgz",
|
||||
"integrity": "sha512-Mfz4FIB4FSvEwBpDfdipRIrwd6uo8gUDoRDF4QEYb4h4tSuI3ov594OrjU6on042UlFHouIJpClDODGkPcBSbg==",
|
||||
"version": "1.3.728",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.728.tgz",
|
||||
"integrity": "sha512-SHv4ziXruBpb1Nz4aTuqEHBYi/9GNCJMYIJgDEXrp/2V01nFXMNFUTli5Z85f5ivSkioLilQatqBYFB44wNJrA==",
|
||||
"dev": true
|
||||
},
|
||||
"elliptic": {
|
||||
@ -3483,9 +3483,9 @@
|
||||
}
|
||||
},
|
||||
"folio": {
|
||||
"version": "0.4.0-alpha13",
|
||||
"resolved": "https://registry.npmjs.org/folio/-/folio-0.4.0-alpha13.tgz",
|
||||
"integrity": "sha512-ujrTuD4bSY3jNB2QVf5B2JSZFz2PNtNR0LIIbD+o4vCNutU9IAK0vn1WAiM5uLMt47CadAuFEb7260nanrTCcw==",
|
||||
"version": "0.4.0-alpha14",
|
||||
"resolved": "https://registry.npmjs.org/folio/-/folio-0.4.0-alpha14.tgz",
|
||||
"integrity": "sha512-rQdHvFmczTtMFy2mlBRWMX6keC1Dd0bfJzF3NfU/H9JcYrU9zv6TuXiN662hC7Z+aky14JpIRNawwg+FVi1Bog==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.12.13",
|
||||
@ -5152,9 +5152,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node-releases": {
|
||||
"version": "1.1.71",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz",
|
||||
"integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==",
|
||||
"version": "1.1.72",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz",
|
||||
"integrity": "sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==",
|
||||
"dev": true
|
||||
},
|
||||
"node-stream-zip": {
|
||||
|
||||
@ -80,7 +80,7 @@
|
||||
"eslint-plugin-notice": "^0.9.10",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"file-loader": "^6.1.0",
|
||||
"folio": "=0.4.0-alpha13",
|
||||
"folio": "=0.4.0-alpha14",
|
||||
"formidable": "^1.2.2",
|
||||
"html-webpack-plugin": "^4.4.1",
|
||||
"ncp": "^2.0.0",
|
||||
|
||||
@ -14,48 +14,60 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { AndroidDevice } from '../../index';
|
||||
import { CommonArgs, baseTest } from '../config/baseTest';
|
||||
import type { AndroidDevice, BrowserContext } from '../../index';
|
||||
import { CommonWorkerFixtures, baseTest } from '../config/baseTest';
|
||||
import * as folio from 'folio';
|
||||
import { PageTestFixtures } from '../page/pageTest';
|
||||
export { expect } from 'folio';
|
||||
|
||||
type AndroidTestArgs = {
|
||||
type AndroidWorkerFixtures = {
|
||||
androidDevice: AndroidDevice;
|
||||
};
|
||||
|
||||
export class AndroidEnv {
|
||||
protected _device?: AndroidDevice;
|
||||
protected _browserVersion: string;
|
||||
protected _browserMajorVersion: number;
|
||||
export const androidFixtures: folio.Fixtures<PageTestFixtures & { __androidSetup: void }, AndroidWorkerFixtures & { androidContext: BrowserContext }, {}, CommonWorkerFixtures> = {
|
||||
androidDevice: [ async ({ playwright }, run) => {
|
||||
const device = (await playwright._android.devices())[0];
|
||||
await device.shell('am force-stop org.chromium.webview_shell');
|
||||
await device.shell('am force-stop com.android.chrome');
|
||||
device.setDefaultTimeout(90000);
|
||||
await run(device);
|
||||
await device.close();
|
||||
}, { scope: 'worker' } ],
|
||||
|
||||
async beforeAll(args: CommonArgs, workerInfo: folio.WorkerInfo) {
|
||||
this._device = (await args.playwright._android.devices())[0];
|
||||
await this._device.shell('am force-stop org.chromium.webview_shell');
|
||||
await this._device.shell('am force-stop com.android.chrome');
|
||||
this._browserVersion = (await this._device.shell('dumpsys package com.android.chrome'))
|
||||
browserVersion: async ({ androidDevice }, run) => {
|
||||
const browserVersion = (await androidDevice.shell('dumpsys package com.android.chrome'))
|
||||
.toString('utf8')
|
||||
.split('\n')
|
||||
.find(line => line.includes('versionName='))
|
||||
.trim()
|
||||
.split('=')[1];
|
||||
this._browserMajorVersion = Number(this._browserVersion.split('.')[0]);
|
||||
this._device.setDefaultTimeout(90000);
|
||||
}
|
||||
await run(browserVersion);
|
||||
},
|
||||
|
||||
async beforeEach({}, testInfo: folio.TestInfo): Promise<AndroidTestArgs> {
|
||||
browserMajorVersion: async ({ browserVersion }, run) => {
|
||||
await run(Number(browserVersion.split('.')[0]));
|
||||
},
|
||||
|
||||
isAndroid: true,
|
||||
isElectron: false,
|
||||
|
||||
__androidSetup: [ async ({ browserVersion }, run, testInfo) => {
|
||||
testInfo.data.platform = 'Android';
|
||||
testInfo.data.headful = true;
|
||||
testInfo.data.browserVersion = this._browserVersion;
|
||||
return {
|
||||
androidDevice: this._device!,
|
||||
};
|
||||
}
|
||||
testInfo.data.browserVersion = browserVersion;
|
||||
await run();
|
||||
}, { auto: true } ],
|
||||
|
||||
async afterAll({}, workerInfo: folio.WorkerInfo) {
|
||||
if (this._device)
|
||||
await this._device.close();
|
||||
this._device = undefined;
|
||||
}
|
||||
}
|
||||
androidContext: [ async ({ androidDevice }, run) => {
|
||||
await run(await androidDevice.launchBrowser());
|
||||
}, { scope: 'worker' } ],
|
||||
|
||||
export const androidTest = baseTest.extend(new AndroidEnv());
|
||||
page: async ({ androidContext }, run) => {
|
||||
const page = await androidContext.newPage();
|
||||
await run(page);
|
||||
for (const page of androidContext.pages())
|
||||
await page.close();
|
||||
},
|
||||
};
|
||||
|
||||
export const androidTest = baseTest.extend<PageTestFixtures, AndroidWorkerFixtures>(androidFixtures as any);
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
import { browserTest as it, expect } from './config/browserTest';
|
||||
|
||||
it.useOptions({ proxy: { server: 'per-context' } });
|
||||
it.use({ proxy: { server: 'per-context' } });
|
||||
|
||||
it('should throw for missing global proxy on Chromium Windows', async ({ browserName, platform, browserType, browserOptions, server }) => {
|
||||
it.skip(browserName !== 'chromium' || platform !== 'win32');
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
import { contextTest as it, expect } from '../config/browserTest';
|
||||
|
||||
it.useOptions({ args: ['--site-per-process'] });
|
||||
it.use({ args: ['--site-per-process'] });
|
||||
|
||||
it('should report oopif frames', async function({page, browser, server}) {
|
||||
await page.goto(server.PREFIX + '/dynamic-oopif.html');
|
||||
|
||||
@ -17,34 +17,13 @@
|
||||
import * as folio from 'folio';
|
||||
import * as path from 'path';
|
||||
import { test as pageTest } from '../page/pageTest';
|
||||
import { AndroidEnv } from '../android/androidTest';
|
||||
import type { BrowserContext } from '../../index';
|
||||
import { PlaywrightEnvOptions } from './browserTest';
|
||||
import { androidFixtures } from '../android/androidTest';
|
||||
import { PlaywrightOptions } from './browserTest';
|
||||
import { CommonOptions } from './baseTest';
|
||||
|
||||
class AndroidPageEnv extends AndroidEnv {
|
||||
private _context?: BrowserContext;
|
||||
|
||||
async beforeAll(args: any, workerInfo: folio.WorkerInfo) {
|
||||
await super.beforeAll(args, workerInfo);
|
||||
this._context = await this._device!.launchBrowser();
|
||||
}
|
||||
|
||||
async beforeEach(args: any, testInfo: folio.TestInfo) {
|
||||
const result = await super.beforeEach(args, testInfo);
|
||||
const page = await this._context!.newPage();
|
||||
return { ...result, browserVersion: this._browserVersion, browserMajorVersion: this._browserMajorVersion, page, isAndroid: true, isElectron: false };
|
||||
}
|
||||
|
||||
async afterEach({}, testInfo: folio.TestInfo) {
|
||||
for (const page of this._context!.pages())
|
||||
await page.close();
|
||||
}
|
||||
}
|
||||
|
||||
const outputDir = path.join(__dirname, '..', '..', 'test-results');
|
||||
const testDir = path.join(__dirname, '..');
|
||||
const config: folio.Config<PlaywrightEnvOptions & CommonOptions> = {
|
||||
const config: folio.Config<CommonOptions & PlaywrightOptions> = {
|
||||
testDir,
|
||||
snapshotDir: '__snapshots__',
|
||||
outputDir,
|
||||
@ -62,7 +41,7 @@ const config: folio.Config<PlaywrightEnvOptions & CommonOptions> = {
|
||||
|
||||
config.projects.push({
|
||||
name: 'android',
|
||||
options: {
|
||||
use: {
|
||||
loopback: '10.0.2.2',
|
||||
mode: 'default',
|
||||
browserName: 'chromium',
|
||||
@ -72,13 +51,13 @@ config.projects.push({
|
||||
|
||||
config.projects.push({
|
||||
name: 'android',
|
||||
options: {
|
||||
use: {
|
||||
loopback: '10.0.2.2',
|
||||
mode: 'default',
|
||||
browserName: 'chromium',
|
||||
},
|
||||
testDir: path.join(testDir, 'page'),
|
||||
define: { test: pageTest, env: new AndroidPageEnv() },
|
||||
define: { test: pageTest, fixtures: androidFixtures },
|
||||
});
|
||||
|
||||
export default config;
|
||||
|
||||
@ -23,17 +23,18 @@ import { installCoverageHooks } from './coverage';
|
||||
import * as childProcess from 'child_process';
|
||||
import { start } from '../../lib/outofprocess';
|
||||
import { PlaywrightClient } from '../../lib/remote/playwrightClient';
|
||||
import type { LaunchOptions } from '../../index';
|
||||
|
||||
export type BrowserName = 'chromium' | 'firefox' | 'webkit';
|
||||
type Mode = 'default' | 'driver' | 'service';
|
||||
type BaseOptions = {
|
||||
mode: Mode;
|
||||
browserName: BrowserName;
|
||||
channel?: string;
|
||||
video?: boolean;
|
||||
headless?: boolean;
|
||||
channel: LaunchOptions['channel'];
|
||||
video: boolean | undefined;
|
||||
headless: boolean | undefined;
|
||||
};
|
||||
type BaseWorkerArgs = {
|
||||
type BaseFixtures = {
|
||||
platform: 'win32' | 'darwin' | 'linux';
|
||||
playwright: typeof import('../../index');
|
||||
toImpl: (rpcObject: any) => any;
|
||||
@ -45,7 +46,7 @@ type BaseWorkerArgs = {
|
||||
class DriverMode {
|
||||
private _playwrightObject: any;
|
||||
|
||||
async setup(workerInfo: folio.WorkerInfo) {
|
||||
async setup(workerIndex: number) {
|
||||
this._playwrightObject = await start();
|
||||
return this._playwrightObject;
|
||||
}
|
||||
@ -60,8 +61,8 @@ class ServiceMode {
|
||||
private _client: any;
|
||||
private _serviceProcess: childProcess.ChildProcess;
|
||||
|
||||
async setup(workerInfo: folio.WorkerInfo) {
|
||||
const port = 10507 + workerInfo.workerIndex;
|
||||
async setup(workerIndex: number) {
|
||||
const port = 10507 + workerIndex;
|
||||
this._serviceProcess = childProcess.fork(path.join(__dirname, '..', '..', 'lib', 'cli', 'cli.js'), ['run-server', String(port)], {
|
||||
stdio: 'pipe'
|
||||
});
|
||||
@ -92,7 +93,7 @@ class ServiceMode {
|
||||
}
|
||||
|
||||
class DefaultMode {
|
||||
async setup(workerInfo: folio.WorkerInfo) {
|
||||
async setup(workerIndex: number) {
|
||||
return require('../../index');
|
||||
}
|
||||
|
||||
@ -100,85 +101,67 @@ class DefaultMode {
|
||||
}
|
||||
}
|
||||
|
||||
class BaseEnv {
|
||||
private _mode: DriverMode | ServiceMode | DefaultMode;
|
||||
private _options: BaseOptions;
|
||||
private _playwright: typeof import('../../index');
|
||||
|
||||
hasBeforeAllOptions(options: BaseOptions) {
|
||||
return 'mode' in options || 'browserName' in options || 'channel' in options || 'video' in options || 'headless' in options;
|
||||
}
|
||||
|
||||
async beforeAll(options: BaseOptions, workerInfo: folio.WorkerInfo): Promise<BaseWorkerArgs> {
|
||||
this._options = options;
|
||||
this._mode = {
|
||||
const baseFixtures: folio.Fixtures<{ __baseSetup: void }, BaseOptions & BaseFixtures> = {
|
||||
mode: [ 'default', { scope: 'worker' } ],
|
||||
browserName: [ 'chromium' , { scope: 'worker' } ],
|
||||
channel: [ undefined, { scope: 'worker' } ],
|
||||
video: [ undefined, { scope: 'worker' } ],
|
||||
headless: [ undefined, { scope: 'worker' } ],
|
||||
platform: [ process.platform as 'win32' | 'darwin' | 'linux', { scope: 'worker' } ],
|
||||
playwright: [ async ({ mode }, run, workerInfo) => {
|
||||
const modeImpl = {
|
||||
default: new DefaultMode(),
|
||||
service: new ServiceMode(),
|
||||
driver: new DriverMode(),
|
||||
}[this._options.mode];
|
||||
}[mode];
|
||||
require('../../lib/utils/utils').setUnderTest();
|
||||
this._playwright = await this._mode.setup(workerInfo);
|
||||
return {
|
||||
playwright: this._playwright,
|
||||
isWindows: process.platform === 'win32',
|
||||
isMac: process.platform === 'darwin',
|
||||
isLinux: process.platform === 'linux',
|
||||
platform: process.platform as ('win32' | 'darwin' | 'linux'),
|
||||
toImpl: (this._playwright as any)._toImpl,
|
||||
};
|
||||
}
|
||||
|
||||
async beforeEach({}, testInfo: folio.TestInfo) {
|
||||
testInfo.snapshotPathSegment = this._options.browserName;
|
||||
testInfo.data = { browserName: this._options.browserName };
|
||||
if (!this._options.headless)
|
||||
const playwright = await modeImpl.setup(workerInfo.workerIndex);
|
||||
await run(playwright);
|
||||
await modeImpl.teardown();
|
||||
}, { scope: 'worker' } ],
|
||||
toImpl: [ async ({ playwright }, run) => run((playwright as any)._toImpl), { scope: 'worker' } ],
|
||||
isWindows: [ process.platform === 'win32', { scope: 'worker' } ],
|
||||
isMac: [ process.platform === 'darwin', { scope: 'worker' } ],
|
||||
isLinux: [ process.platform === 'linux', { scope: 'worker' } ],
|
||||
__baseSetup: [ async ({ browserName, headless, mode, video }, run, testInfo) => {
|
||||
testInfo.snapshotPathSegment = browserName;
|
||||
testInfo.data = { browserName };
|
||||
if (!headless)
|
||||
testInfo.data.headful = true;
|
||||
if (this._options.mode !== 'default')
|
||||
testInfo.data.mode = this._options.mode;
|
||||
if (this._options.video)
|
||||
if (mode !== 'default')
|
||||
testInfo.data.mode = mode;
|
||||
if (video)
|
||||
testInfo.data.video = true;
|
||||
return {};
|
||||
}
|
||||
|
||||
async afterAll({}, workerInfo: folio.WorkerInfo) {
|
||||
await this._mode.teardown();
|
||||
}
|
||||
}
|
||||
|
||||
type ServerWorkerArgs = {
|
||||
asset: (path: string) => string;
|
||||
socksPort: number;
|
||||
server: TestServer;
|
||||
httpsServer: TestServer;
|
||||
await run();
|
||||
}, { auto: true } ],
|
||||
};
|
||||
|
||||
type ServerOptions = {
|
||||
loopback?: string;
|
||||
};
|
||||
type ServerFixtures = {
|
||||
server: TestServer;
|
||||
httpsServer: TestServer;
|
||||
socksPort: number;
|
||||
asset: (p: string) => string;
|
||||
};
|
||||
|
||||
class ServerEnv {
|
||||
private _server: TestServer;
|
||||
private _httpsServer: TestServer;
|
||||
private _socksServer: any;
|
||||
private _socksPort: number;
|
||||
|
||||
hasBeforeAllOptions(options: ServerOptions) {
|
||||
return 'loopback' in options;
|
||||
}
|
||||
|
||||
async beforeAll(options: ServerOptions, workerInfo: folio.WorkerInfo) {
|
||||
type ServersInternal = ServerFixtures & { socksServer: any };
|
||||
const serverFixtures: folio.Fixtures<ServerFixtures, ServerOptions & { __servers: ServersInternal }> = {
|
||||
loopback: [ undefined, { scope: 'worker' } ],
|
||||
__servers: [ async ({ loopback }, run, workerInfo) => {
|
||||
const assetsPath = path.join(__dirname, '..', 'assets');
|
||||
const cachedPath = path.join(__dirname, '..', 'assets', 'cached');
|
||||
|
||||
const port = 8907 + workerInfo.workerIndex * 3;
|
||||
this._server = await TestServer.create(assetsPath, port, options.loopback);
|
||||
this._server.enableHTTPCache(cachedPath);
|
||||
const server = await TestServer.create(assetsPath, port, loopback);
|
||||
server.enableHTTPCache(cachedPath);
|
||||
|
||||
const httpsPort = port + 1;
|
||||
this._httpsServer = await TestServer.createHTTPS(assetsPath, httpsPort, options.loopback);
|
||||
this._httpsServer.enableHTTPCache(cachedPath);
|
||||
const httpsServer = await TestServer.createHTTPS(assetsPath, httpsPort, loopback);
|
||||
httpsServer.enableHTTPCache(cachedPath);
|
||||
|
||||
this._socksServer = socks.createServer((info, accept, deny) => {
|
||||
const socksServer = socks.createServer((info, accept, deny) => {
|
||||
let socket;
|
||||
if ((socket = accept(true))) {
|
||||
// Catch and ignore ECONNRESET errors.
|
||||
@ -194,62 +177,68 @@ class ServerEnv {
|
||||
].join('\r\n'));
|
||||
}
|
||||
});
|
||||
this._socksPort = port + 2;
|
||||
this._socksServer.listen(this._socksPort, 'localhost');
|
||||
this._socksServer.useAuth(socks.auth.None());
|
||||
return {
|
||||
const socksPort = port + 2;
|
||||
socksServer.listen(socksPort, 'localhost');
|
||||
socksServer.useAuth(socks.auth.None());
|
||||
|
||||
await run({
|
||||
asset: (p: string) => path.join(__dirname, '..', 'assets', ...p.split('/')),
|
||||
server: this._server,
|
||||
httpsServer: this._httpsServer,
|
||||
socksPort: this._socksPort,
|
||||
};
|
||||
}
|
||||
server,
|
||||
httpsServer,
|
||||
socksPort,
|
||||
socksServer,
|
||||
});
|
||||
|
||||
async beforeEach({}, testInfo: folio.TestInfo) {
|
||||
this._server.reset();
|
||||
this._httpsServer.reset();
|
||||
return {};
|
||||
}
|
||||
|
||||
async afterAll({}, workerInfo: folio.WorkerInfo) {
|
||||
await Promise.all([
|
||||
this._server.stop(),
|
||||
this._httpsServer.stop(),
|
||||
this._socksServer.close(),
|
||||
server.stop(),
|
||||
httpsServer.stop(),
|
||||
socksServer.close(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}, { scope: 'worker' } ],
|
||||
|
||||
server: async ({ __servers }, run) => {
|
||||
__servers.server.reset();
|
||||
await run(__servers.server);
|
||||
},
|
||||
|
||||
httpsServer: async ({ __servers }, run) => {
|
||||
__servers.httpsServer.reset();
|
||||
await run(__servers.httpsServer);
|
||||
},
|
||||
|
||||
socksPort: async ({ __servers }, run) => {
|
||||
await run(__servers.socksPort);
|
||||
},
|
||||
|
||||
asset: async ({ __servers }, run) => {
|
||||
await run(__servers.asset);
|
||||
},
|
||||
};
|
||||
|
||||
type CoverageOptions = {
|
||||
coverageName?: string;
|
||||
};
|
||||
|
||||
class CoverageEnv {
|
||||
private _coverage: ReturnType<typeof installCoverageHooks> | undefined;
|
||||
const coverageFixtures: folio.Fixtures<{}, CoverageOptions & { __collectCoverage: void }> = {
|
||||
coverageName: [ undefined, { scope: 'worker' } ],
|
||||
|
||||
hasBeforeAllOptions(options: CoverageOptions) {
|
||||
return 'coverageName' in options;
|
||||
}
|
||||
|
||||
async beforeAll(options: CoverageOptions, workerInfo: folio.WorkerInfo) {
|
||||
if (options.coverageName)
|
||||
this._coverage = installCoverageHooks(options.coverageName);
|
||||
return {};
|
||||
}
|
||||
|
||||
async afterAll({}, workerInfo: folio.WorkerInfo) {
|
||||
if (!this._coverage)
|
||||
__collectCoverage: [ async ({ coverageName }, run, workerInfo) => {
|
||||
if (!coverageName) {
|
||||
await run();
|
||||
return;
|
||||
const { coverage, uninstall } = this._coverage;
|
||||
}
|
||||
|
||||
const { coverage, uninstall } = installCoverageHooks(coverageName);
|
||||
await run();
|
||||
uninstall();
|
||||
const coveragePath = path.join(__dirname, '..', 'coverage-report', workerInfo.workerIndex + '.json');
|
||||
const coverageJSON = Array.from(coverage.keys()).filter(key => coverage.get(key));
|
||||
await fs.promises.mkdir(path.dirname(coveragePath), { recursive: true });
|
||||
await fs.promises.writeFile(coveragePath, JSON.stringify(coverageJSON, undefined, 2), 'utf8');
|
||||
}
|
||||
}
|
||||
}, { scope: 'worker', auto: true } ],
|
||||
};
|
||||
|
||||
export type CommonOptions = BaseOptions & ServerOptions & CoverageOptions;
|
||||
export type CommonArgs = CommonOptions & BaseWorkerArgs & ServerWorkerArgs;
|
||||
export type CommonWorkerFixtures = CommonOptions & BaseFixtures;
|
||||
|
||||
export const baseTest = folio.test.extend(new CoverageEnv()).extend(new ServerEnv()).extend(new BaseEnv());
|
||||
export const baseTest = folio.test.extend<{}, CoverageOptions>(coverageFixtures).extend<ServerFixtures>(serverFixtures).extend<{}, BaseOptions & BaseFixtures>(baseFixtures);
|
||||
|
||||
@ -22,168 +22,145 @@ import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as util from 'util';
|
||||
import { RemoteServer, RemoteServerOptions } from './remoteServer';
|
||||
import { CommonArgs, baseTest } from './baseTest';
|
||||
import { baseTest, CommonWorkerFixtures } from './baseTest';
|
||||
|
||||
const mkdtempAsync = util.promisify(fs.mkdtemp);
|
||||
|
||||
type PlaywrightTestArgs = {
|
||||
type PlaywrightWorkerOptions = {
|
||||
traceDir: LaunchOptions['traceDir'];
|
||||
executablePath: LaunchOptions['executablePath'];
|
||||
proxy: LaunchOptions['proxy'];
|
||||
args: LaunchOptions['args'];
|
||||
};
|
||||
export type PlaywrightWorkerFixtures = {
|
||||
browserType: BrowserType;
|
||||
browserOptions: LaunchOptions;
|
||||
browser: Browser;
|
||||
browserVersion: string;
|
||||
};
|
||||
type PlaywrightTestOptions = {
|
||||
hasTouch: BrowserContextOptions['hasTouch'];
|
||||
};
|
||||
type PlaywrightTestFixtures = {
|
||||
createUserDataDir: () => Promise<string>;
|
||||
launchPersistent: (options?: Parameters<BrowserType['launchPersistentContext']>[1]) => Promise<{ context: BrowserContext, page: Page }>;
|
||||
startRemoteServer: (options?: RemoteServerOptions) => Promise<RemoteServer>;
|
||||
contextOptions: BrowserContextOptions;
|
||||
contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>;
|
||||
context: BrowserContext;
|
||||
page: Page;
|
||||
};
|
||||
export type PlaywrightOptions = PlaywrightWorkerOptions & PlaywrightTestOptions;
|
||||
|
||||
export type PlaywrightEnvOptions = LaunchOptions;
|
||||
const kLaunchOptionNames = ['args', 'channel', 'chromiumSandbox', 'devtools', 'downloadsPath', 'env', 'executablePath', 'firefoxUserPrefs', 'handleSIGHUP', 'handleSIGINT', 'handleSIGTERM', 'headless', 'ignoreDefaultArgs', 'logger', 'proxy', 'slowMo', 'timeout', 'traceDir'];
|
||||
export const playwrightFixtures: folio.Fixtures<PlaywrightTestOptions & PlaywrightTestFixtures, PlaywrightWorkerOptions & PlaywrightWorkerFixtures, {}, CommonWorkerFixtures> = {
|
||||
traceDir: [ undefined, { scope: 'worker' } ],
|
||||
executablePath: [ undefined, { scope: 'worker' } ],
|
||||
proxy: [ undefined, { scope: 'worker' } ],
|
||||
args: [ undefined, { scope: 'worker' } ],
|
||||
hasTouch: undefined,
|
||||
|
||||
type PlaywrightWorkerArgs = {
|
||||
browserType: BrowserType;
|
||||
browserOptions: LaunchOptions;
|
||||
};
|
||||
browserType: [async ({ playwright, browserName }, run) => {
|
||||
await run(playwright[browserName]);
|
||||
}, { scope: 'worker' } ],
|
||||
|
||||
class PlaywrightEnv {
|
||||
protected _browserOptions: LaunchOptions;
|
||||
protected _browserType: BrowserType;
|
||||
private _userDataDirs: string[] = [];
|
||||
private _persistentContext: BrowserContext | undefined;
|
||||
private _remoteServer: RemoteServer | undefined;
|
||||
|
||||
hasBeforeAllOptions(options: PlaywrightEnvOptions) {
|
||||
return kLaunchOptionNames.some(key => key in options);
|
||||
}
|
||||
|
||||
async beforeAll(args: CommonArgs & PlaywrightEnvOptions, workerInfo: folio.WorkerInfo): Promise<PlaywrightWorkerArgs> {
|
||||
this._browserType = args.playwright[args.browserName];
|
||||
this._browserOptions = {
|
||||
browserOptions: [async ({ headless, channel, executablePath, traceDir, proxy, args }, run) => {
|
||||
await run({
|
||||
headless,
|
||||
channel,
|
||||
executablePath,
|
||||
traceDir,
|
||||
proxy,
|
||||
args,
|
||||
handleSIGINT: false,
|
||||
...args,
|
||||
};
|
||||
return {
|
||||
browserType: this._browserType,
|
||||
browserOptions: this._browserOptions,
|
||||
};
|
||||
}
|
||||
});
|
||||
}, { scope: 'worker' } ],
|
||||
|
||||
private async _createUserDataDir() {
|
||||
browser: [async ({ browserType, browserOptions }, run) => {
|
||||
const browser = await browserType.launch(browserOptions);
|
||||
await run(browser);
|
||||
await browser.close();
|
||||
}, { scope: 'worker' } ],
|
||||
|
||||
browserVersion: [async ({ browser }, run) => {
|
||||
await run(browser.version());
|
||||
}, { scope: 'worker' } ],
|
||||
|
||||
createUserDataDir: async ({}, run) => {
|
||||
const dirs: string[] = [];
|
||||
// We do not put user data dir in testOutputPath,
|
||||
// because we do not want to upload them as test result artifacts.
|
||||
//
|
||||
// Additionally, it is impossible to upload user data dir after test run:
|
||||
// - Firefox removes lock file later, presumably from another watchdog process?
|
||||
// - WebKit has circular symlinks that makes CI go crazy.
|
||||
const dir = await mkdtempAsync(path.join(os.tmpdir(), 'playwright-test-'));
|
||||
this._userDataDirs.push(dir);
|
||||
return dir;
|
||||
}
|
||||
await run(async () => {
|
||||
const dir = await mkdtempAsync(path.join(os.tmpdir(), 'playwright-test-'));
|
||||
dirs.push(dir);
|
||||
return dir;
|
||||
});
|
||||
await removeFolders(dirs);
|
||||
},
|
||||
|
||||
private async _launchPersistent(options?: Parameters<BrowserType['launchPersistentContext']>[1]) {
|
||||
if (this._persistentContext)
|
||||
throw new Error('can only launch one persitent context');
|
||||
const userDataDir = await this._createUserDataDir();
|
||||
this._persistentContext = await this._browserType.launchPersistentContext(userDataDir, { ...this._browserOptions, ...options });
|
||||
const page = this._persistentContext.pages()[0];
|
||||
return { context: this._persistentContext, page };
|
||||
}
|
||||
launchPersistent: async ({ createUserDataDir, browserType, browserOptions }, run) => {
|
||||
let persistentContext: BrowserContext | undefined;
|
||||
await run(async options => {
|
||||
if (persistentContext)
|
||||
throw new Error('can only launch one persitent context');
|
||||
const userDataDir = await createUserDataDir();
|
||||
persistentContext = await browserType.launchPersistentContext(userDataDir, { ...browserOptions, ...options });
|
||||
const page = persistentContext.pages()[0];
|
||||
return { context: persistentContext, page };
|
||||
});
|
||||
if (persistentContext)
|
||||
await persistentContext.close();
|
||||
},
|
||||
|
||||
private async _startRemoteServer(options?: RemoteServerOptions): Promise<RemoteServer> {
|
||||
if (this._remoteServer)
|
||||
throw new Error('can only start one remote server');
|
||||
this._remoteServer = new RemoteServer();
|
||||
await this._remoteServer._start(this._browserType, this._browserOptions, options);
|
||||
return this._remoteServer;
|
||||
}
|
||||
startRemoteServer: async ({ browserType, browserOptions }, run) => {
|
||||
let remoteServer: RemoteServer | undefined;
|
||||
await run(async options => {
|
||||
if (remoteServer)
|
||||
throw new Error('can only start one remote server');
|
||||
remoteServer = new RemoteServer();
|
||||
await remoteServer._start(browserType, browserOptions, options);
|
||||
return remoteServer;
|
||||
});
|
||||
if (remoteServer)
|
||||
await remoteServer.close();
|
||||
},
|
||||
|
||||
async beforeEach({}, testInfo: folio.TestInfo): Promise<PlaywrightTestArgs> {
|
||||
return {
|
||||
createUserDataDir: this._createUserDataDir.bind(this),
|
||||
launchPersistent: this._launchPersistent.bind(this),
|
||||
startRemoteServer: this._startRemoteServer.bind(this),
|
||||
};
|
||||
}
|
||||
|
||||
async afterEach({}, testInfo: folio.TestInfo) {
|
||||
if (this._persistentContext) {
|
||||
await this._persistentContext.close();
|
||||
this._persistentContext = undefined;
|
||||
}
|
||||
if (this._remoteServer) {
|
||||
await this._remoteServer.close();
|
||||
this._remoteServer = undefined;
|
||||
}
|
||||
await removeFolders(this._userDataDirs);
|
||||
this._userDataDirs = [];
|
||||
}
|
||||
}
|
||||
|
||||
type BrowserTestArgs = {
|
||||
browser: Browser;
|
||||
browserVersion: string;
|
||||
contextOptions: BrowserContextOptions;
|
||||
contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>;
|
||||
};
|
||||
|
||||
type BrowserTestOptions = BrowserContextOptions;
|
||||
|
||||
class BrowserEnv {
|
||||
private _browser: Browser | undefined;
|
||||
private _contexts: BrowserContext[] = [];
|
||||
protected _browserVersion: string;
|
||||
|
||||
hasBeforeAllOptions(options: BrowserTestOptions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
async beforeAll(args: PlaywrightWorkerArgs, workerInfo: folio.WorkerInfo) {
|
||||
this._browser = await args.browserType.launch(args.browserOptions);
|
||||
this._browserVersion = this._browser.version();
|
||||
}
|
||||
|
||||
async beforeEach(options: CommonArgs & BrowserTestOptions, testInfo: folio.TestInfo): Promise<BrowserTestArgs> {
|
||||
contextOptions: async ({ video, hasTouch, browserVersion }, run, testInfo) => {
|
||||
testInfo.data.browserVersion = browserVersion;
|
||||
const debugName = path.relative(testInfo.project.outputDir, testInfo.outputDir).replace(/[\/\\]/g, '-');
|
||||
const contextOptions = {
|
||||
recordVideo: options.video ? { dir: testInfo.outputPath('') } : undefined,
|
||||
recordVideo: video ? { dir: testInfo.outputPath('') } : undefined,
|
||||
_debugName: debugName,
|
||||
...options,
|
||||
hasTouch,
|
||||
} as BrowserContextOptions;
|
||||
await run(contextOptions);
|
||||
},
|
||||
|
||||
testInfo.data.browserVersion = this._browserVersion;
|
||||
|
||||
const contextFactory = async (options: BrowserContextOptions = {}) => {
|
||||
const context = await this._browser.newContext({ ...contextOptions, ...options });
|
||||
this._contexts.push(context);
|
||||
contextFactory: async ({ browser, contextOptions }, run) => {
|
||||
const contexts: BrowserContext[] = [];
|
||||
await run(async options => {
|
||||
const context = await browser.newContext({ ...contextOptions, ...options });
|
||||
contexts.push(context);
|
||||
return context;
|
||||
};
|
||||
});
|
||||
await Promise.all(contexts.map(context => context.close()));
|
||||
},
|
||||
|
||||
return {
|
||||
browser: this._browser,
|
||||
browserVersion: this._browserVersion,
|
||||
contextFactory,
|
||||
contextOptions,
|
||||
};
|
||||
}
|
||||
context: async ({ contextFactory }, run) => {
|
||||
await run(await contextFactory());
|
||||
},
|
||||
|
||||
async afterEach({}, testInfo: folio.TestInfo) {
|
||||
for (const context of this._contexts)
|
||||
await context.close();
|
||||
this._contexts = [];
|
||||
}
|
||||
page: async ({ context }, run) => {
|
||||
await run(await context.newPage());
|
||||
},
|
||||
};
|
||||
|
||||
async afterAll({}, workerInfo: folio.WorkerInfo) {
|
||||
if (this._browser)
|
||||
await this._browser.close();
|
||||
this._browser = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
class ContextEnv {
|
||||
async beforeEach(args: BrowserTestArgs, testInfo: folio.TestInfo) {
|
||||
const context = await args.contextFactory();
|
||||
const page = await context.newPage();
|
||||
return { context, page };
|
||||
}
|
||||
}
|
||||
|
||||
export const playwrightTest = baseTest.extend(new PlaywrightEnv());
|
||||
export const browserTest = playwrightTest.extend(new BrowserEnv());
|
||||
export const contextTest = browserTest.extend(new ContextEnv());
|
||||
const test = baseTest.extend<PlaywrightTestOptions & PlaywrightTestFixtures, PlaywrightWorkerOptions & PlaywrightWorkerFixtures>(playwrightFixtures);
|
||||
export const playwrightTest = test;
|
||||
export const browserTest = test;
|
||||
export const contextTest = test;
|
||||
|
||||
export { expect } from 'folio';
|
||||
|
||||
@ -16,10 +16,9 @@
|
||||
|
||||
import * as folio from 'folio';
|
||||
import * as path from 'path';
|
||||
import { PlaywrightEnvOptions } from './browserTest';
|
||||
import { PlaywrightOptions, playwrightFixtures } from './browserTest';
|
||||
import { test as pageTest } from '../page/pageTest';
|
||||
import { BrowserName, CommonArgs, CommonOptions } from './baseTest';
|
||||
import type { Browser, BrowserContext } from '../../index';
|
||||
import { BrowserName, CommonOptions } from './baseTest';
|
||||
|
||||
const getExecutablePath = (browserName: BrowserName) => {
|
||||
if (browserName === 'chromium' && process.env.CRPATH)
|
||||
@ -30,58 +29,23 @@ const getExecutablePath = (browserName: BrowserName) => {
|
||||
return process.env.WKPATH;
|
||||
};
|
||||
|
||||
class PageEnv {
|
||||
private _browser: Browser
|
||||
private _browserVersion: string;
|
||||
private _browserMajorVersion: number;
|
||||
private _context: BrowserContext | undefined;
|
||||
|
||||
async beforeAll(args: PlaywrightEnvOptions & CommonArgs, workerInfo: folio.WorkerInfo) {
|
||||
this._browser = await args.playwright[args.browserName].launch({
|
||||
handleSIGINT: false,
|
||||
...args,
|
||||
} as any);
|
||||
this._browserVersion = this._browser.version();
|
||||
this._browserMajorVersion = Number(this._browserVersion.split('.')[0]);
|
||||
return {};
|
||||
}
|
||||
|
||||
async beforeEach(args: CommonArgs, testInfo: folio.TestInfo) {
|
||||
testInfo.data.browserVersion = this._browserVersion;
|
||||
this._context = await this._browser.newContext({
|
||||
recordVideo: args.video ? { dir: testInfo.outputPath('') } : undefined,
|
||||
...args,
|
||||
});
|
||||
const page = await this._context.newPage();
|
||||
return {
|
||||
context: this._context,
|
||||
page,
|
||||
browserVersion: this._browserVersion,
|
||||
browserMajorVersion: this._browserMajorVersion,
|
||||
isAndroid: false,
|
||||
isElectron: false,
|
||||
};
|
||||
}
|
||||
|
||||
async afterEach({}) {
|
||||
if (this._context)
|
||||
await this._context.close();
|
||||
this._context = undefined;
|
||||
}
|
||||
|
||||
async afterAll({}, workerInfo: folio.WorkerInfo) {
|
||||
await this._browser.close();
|
||||
}
|
||||
}
|
||||
const pageFixtures = {
|
||||
...playwrightFixtures,
|
||||
browserMajorVersion: async ({ browserVersion }, run) => {
|
||||
await run(Number(browserVersion.split('.')[0]));
|
||||
},
|
||||
isAndroid: false,
|
||||
isElectron: false,
|
||||
};
|
||||
|
||||
const mode = (folio.registerCLIOption('mode', 'Transport mode: default, driver or service').value || 'default') as ('default' | 'driver' | 'service');
|
||||
const headed = folio.registerCLIOption('headed', 'Run tests in headed mode (default: headless)', { type: 'boolean' }).value || !!process.env.HEADFUL;
|
||||
const channel = folio.registerCLIOption('channel', 'Browser channel (default: no channel)').value;
|
||||
const channel = folio.registerCLIOption('channel', 'Browser channel (default: no channel)').value as any;
|
||||
const video = !!folio.registerCLIOption('video', 'Record videos for all tests', { type: 'boolean' }).value;
|
||||
|
||||
const outputDir = path.join(__dirname, '..', '..', 'test-results');
|
||||
const testDir = path.join(__dirname, '..');
|
||||
const config: folio.Config<CommonOptions & PlaywrightEnvOptions> = {
|
||||
const config: folio.Config<CommonOptions & PlaywrightOptions> = {
|
||||
testDir,
|
||||
snapshotDir: '__snapshots__',
|
||||
outputDir,
|
||||
@ -108,7 +72,7 @@ for (const browserName of browserNames) {
|
||||
name: browserName,
|
||||
testDir,
|
||||
testIgnore,
|
||||
options: {
|
||||
use: {
|
||||
mode,
|
||||
browserName,
|
||||
headless: !headed,
|
||||
@ -118,7 +82,7 @@ for (const browserName of browserNames) {
|
||||
traceDir: process.env.PWTRACE ? path.join(outputDir, 'trace') : undefined,
|
||||
coverageName: browserName,
|
||||
},
|
||||
define: { test: pageTest, env: new PageEnv() },
|
||||
define: { test: pageTest, fixtures: pageFixtures },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -16,29 +16,14 @@
|
||||
|
||||
import * as folio from 'folio';
|
||||
import * as path from 'path';
|
||||
import { ElectronEnv } from '../electron/electronTest';
|
||||
import { electronFixtures } from '../electron/electronTest';
|
||||
import { test as pageTest } from '../page/pageTest';
|
||||
import { PlaywrightEnvOptions } from './browserTest';
|
||||
import { PlaywrightOptions } from './browserTest';
|
||||
import { CommonOptions } from './baseTest';
|
||||
|
||||
class ElectronPageEnv extends ElectronEnv {
|
||||
async beforeEach(args: any, testInfo: folio.TestInfo) {
|
||||
const result = await super.beforeEach(args, testInfo);
|
||||
const page = await result.newWindow();
|
||||
return {
|
||||
...result,
|
||||
browserVersion: this._browserVersion,
|
||||
browserMajorVersion: this._browserMajorVersion,
|
||||
page,
|
||||
isAndroid: false,
|
||||
isElectron: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const outputDir = path.join(__dirname, '..', '..', 'test-results');
|
||||
const testDir = path.join(__dirname, '..');
|
||||
const config: folio.Config<PlaywrightEnvOptions & CommonOptions> = {
|
||||
const config: folio.Config<CommonOptions & PlaywrightOptions> = {
|
||||
testDir,
|
||||
snapshotDir: '__snapshots__',
|
||||
outputDir,
|
||||
@ -56,7 +41,7 @@ const config: folio.Config<PlaywrightEnvOptions & CommonOptions> = {
|
||||
|
||||
config.projects.push({
|
||||
name: 'electron',
|
||||
options: {
|
||||
use: {
|
||||
mode: 'default',
|
||||
browserName: 'chromium',
|
||||
coverageName: 'electron',
|
||||
@ -66,13 +51,13 @@ config.projects.push({
|
||||
|
||||
config.projects.push({
|
||||
name: 'electron',
|
||||
options: {
|
||||
use: {
|
||||
mode: 'default',
|
||||
browserName: 'chromium',
|
||||
coverageName: 'electron',
|
||||
},
|
||||
testDir: path.join(testDir, 'page'),
|
||||
define: { test: pageTest, env: new ElectronPageEnv() },
|
||||
define: { test: pageTest, fixtures: electronFixtures },
|
||||
});
|
||||
|
||||
export default config;
|
||||
|
||||
@ -14,69 +14,62 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { baseTest, CommonArgs } from '../config/baseTest';
|
||||
import { baseTest, CommonWorkerFixtures } from '../config/baseTest';
|
||||
import { ElectronApplication, Page } from '../../index';
|
||||
import * as folio from 'folio';
|
||||
import * as path from 'path';
|
||||
import { PageTestFixtures } from '../page/pageTest';
|
||||
export { expect } from 'folio';
|
||||
|
||||
type ElectronTestArgs = {
|
||||
type ElectronTestFixtures = PageTestFixtures & {
|
||||
electronApp: ElectronApplication;
|
||||
newWindow: () => Promise<Page>;
|
||||
};
|
||||
|
||||
export class ElectronEnv {
|
||||
private _electronApp: ElectronApplication | undefined;
|
||||
private _windows: Page[] = [];
|
||||
protected _browserVersion: string;
|
||||
protected _browserMajorVersion: number;
|
||||
const electronVersion = require('electron/package.json').version;
|
||||
export const electronFixtures: folio.Fixtures<ElectronTestFixtures, {}, {}, CommonWorkerFixtures> = {
|
||||
browserVersion: electronVersion,
|
||||
browserMajorVersion: Number(electronVersion.split('.')[0]),
|
||||
isAndroid: false,
|
||||
isElectron: true,
|
||||
|
||||
private async _newWindow() {
|
||||
const [ window ] = await Promise.all([
|
||||
this._electronApp!.waitForEvent('window'),
|
||||
this._electronApp!.evaluate(electron => {
|
||||
const window = new electron.BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
// Sandboxed windows share process with their window.open() children
|
||||
// and can script them. We use that heavily in our tests.
|
||||
webPreferences: { sandbox: true }
|
||||
});
|
||||
window.loadURL('about:blank');
|
||||
})
|
||||
]);
|
||||
this._windows.push(window);
|
||||
return window;
|
||||
}
|
||||
|
||||
async beforeAll() {
|
||||
electronApp: async ({ playwright }, run) => {
|
||||
// This env prevents 'Electron Security Policy' console message.
|
||||
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true';
|
||||
this._browserVersion = require('electron/package.json').version;
|
||||
this._browserMajorVersion = Number(this._browserVersion.split('.')[0]);
|
||||
return {};
|
||||
}
|
||||
|
||||
async beforeEach(args: CommonArgs, testInfo: folio.TestInfo): Promise<ElectronTestArgs> {
|
||||
this._electronApp = await args.playwright._electron.launch({
|
||||
const electronApp = await playwright._electron.launch({
|
||||
args: [path.join(__dirname, 'electron-app.js')],
|
||||
});
|
||||
testInfo.data.browserVersion = this._browserVersion;
|
||||
return {
|
||||
electronApp: this._electronApp,
|
||||
newWindow: this._newWindow.bind(this),
|
||||
};
|
||||
}
|
||||
await run(electronApp);
|
||||
await electronApp.close();
|
||||
},
|
||||
|
||||
async afterEach({}, testInfo: folio.TestInfo) {
|
||||
for (const window of this._windows)
|
||||
newWindow: async ({ electronApp }, run) => {
|
||||
const windows: Page[] = [];
|
||||
await run(async () => {
|
||||
const [ window ] = await Promise.all([
|
||||
electronApp.waitForEvent('window'),
|
||||
electronApp.evaluate(electron => {
|
||||
const window = new electron.BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
// Sandboxed windows share process with their window.open() children
|
||||
// and can script them. We use that heavily in our tests.
|
||||
webPreferences: { sandbox: true }
|
||||
});
|
||||
window.loadURL('about:blank');
|
||||
})
|
||||
]);
|
||||
windows.push(window);
|
||||
return window;
|
||||
});
|
||||
for (const window of windows)
|
||||
await window.close();
|
||||
this._windows = [];
|
||||
if (this._electronApp) {
|
||||
await this._electronApp.close();
|
||||
this._electronApp = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
export const electronTest = baseTest.extend(new ElectronEnv());
|
||||
|
||||
page: async ({ newWindow }, run) => {
|
||||
await run(await newWindow());
|
||||
},
|
||||
};
|
||||
|
||||
export const electronTest = baseTest.extend<ElectronTestFixtures>(electronFixtures);
|
||||
|
||||
@ -20,7 +20,6 @@ import * as path from 'path';
|
||||
import type { Source } from '../../src/server/supplements/recorder/recorderTypes';
|
||||
import { ChildProcess, spawn } from 'child_process';
|
||||
import { chromium } from '../../index';
|
||||
import * as folio from 'folio';
|
||||
export { expect } from 'folio';
|
||||
|
||||
type CLITestArgs = {
|
||||
@ -29,39 +28,40 @@ type CLITestArgs = {
|
||||
runCLI: (args: string[]) => CLIMock;
|
||||
};
|
||||
|
||||
export const test = contextTest.extend({
|
||||
async beforeAll({}, workerInfo: folio.WorkerInfo) {
|
||||
process.env.PWTEST_RECORDER_PORT = String(10907 + workerInfo.workerIndex);
|
||||
},
|
||||
|
||||
async beforeEach({ page, context, toImpl, browserName, channel, headless, mode, executablePath }, testInfo: folio.TestInfo): Promise<CLITestArgs> {
|
||||
testInfo.skip(mode === 'service');
|
||||
const recorderPageGetter = async () => {
|
||||
export const test = contextTest.extend<CLITestArgs>({
|
||||
recorderPageGetter: async ({ page, context, toImpl, browserName, channel, headless, mode, executablePath }, run, testInfo) => {
|
||||
process.env.PWTEST_RECORDER_PORT = String(10907 + testInfo.workerIndex);
|
||||
if (mode === 'service')
|
||||
testInfo.skip();
|
||||
await run(async () => {
|
||||
while (!toImpl(context).recorderAppForTest)
|
||||
await new Promise(f => setTimeout(f, 100));
|
||||
const wsEndpoint = toImpl(context).recorderAppForTest.wsEndpoint;
|
||||
const browser = await chromium.connectOverCDP({ wsEndpoint });
|
||||
const c = browser.contexts()[0];
|
||||
return c.pages()[0] || await c.waitForEvent('page');
|
||||
};
|
||||
return {
|
||||
runCLI: (cliArgs: string[]) => {
|
||||
this._cli = new CLIMock(browserName, channel, headless, cliArgs, executablePath);
|
||||
return this._cli;
|
||||
},
|
||||
openRecorder: async () => {
|
||||
await (page.context() as any)._enableRecorder({ language: 'javascript', startRecording: true });
|
||||
return new Recorder(page, await recorderPageGetter());
|
||||
},
|
||||
recorderPageGetter,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
async afterEach({}, testInfo: folio.TestInfo) {
|
||||
if (this._cli) {
|
||||
await this._cli.exited;
|
||||
this._cli = undefined;
|
||||
}
|
||||
runCLI: async ({ browserName, channel, headless, mode, executablePath }, run, testInfo) => {
|
||||
process.env.PWTEST_RECORDER_PORT = String(10907 + testInfo.workerIndex);
|
||||
if (mode === 'service')
|
||||
testInfo.skip();
|
||||
|
||||
let cli: CLIMock | undefined;
|
||||
await run(cliArgs => {
|
||||
cli = new CLIMock(browserName, channel, headless, cliArgs, executablePath);
|
||||
return cli;
|
||||
});
|
||||
if (cli)
|
||||
await cli.exited;
|
||||
},
|
||||
|
||||
openRecorder: async ({ page, recorderPageGetter }, run) => {
|
||||
await run(async () => {
|
||||
await (page.context() as any)._enableRecorder({ language: 'javascript', startRecording: true });
|
||||
return new Recorder(page, await recorderPageGetter());
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -32,51 +32,46 @@ function crash({ page, toImpl, browserName, platform, mode }: any) {
|
||||
}
|
||||
|
||||
it.describe('', () => {
|
||||
it('should emit crash event when page crashes', async args => {
|
||||
const { page } = args;
|
||||
it('should emit crash event when page crashes', async ({ page, toImpl, browserName, platform, mode }) => {
|
||||
await page.setContent(`<div>This page should crash</div>`);
|
||||
crash(args);
|
||||
crash({ page, toImpl, browserName, platform, mode });
|
||||
const crashedPage = await new Promise(f => page.on('crash', f));
|
||||
expect(crashedPage).toBe(page);
|
||||
});
|
||||
|
||||
it('should throw on any action after page crashes', async args => {
|
||||
const { page } = args;
|
||||
it('should throw on any action after page crashes', async ({ page, toImpl, browserName, platform, mode }) => {
|
||||
await page.setContent(`<div>This page should crash</div>`);
|
||||
crash(args);
|
||||
crash({ page, toImpl, browserName, platform, mode });
|
||||
await page.waitForEvent('crash');
|
||||
const err = await page.evaluate(() => {}).then(() => null, e => e);
|
||||
expect(err).toBeTruthy();
|
||||
expect(err.message).toContain('crash');
|
||||
});
|
||||
|
||||
it('should cancel waitForEvent when page crashes', async args => {
|
||||
const { page } = args;
|
||||
it('should cancel waitForEvent when page crashes', async ({ page, toImpl, browserName, platform, mode }) => {
|
||||
await page.setContent(`<div>This page should crash</div>`);
|
||||
const promise = page.waitForEvent('response').catch(e => e);
|
||||
crash(args);
|
||||
crash({ page, toImpl, browserName, platform, mode });
|
||||
const error = await promise;
|
||||
expect(error.message).toContain('Page crashed');
|
||||
});
|
||||
|
||||
it('should cancel navigation when page crashes', async args => {
|
||||
const { page, server } = args;
|
||||
it('should cancel navigation when page crashes', async ({ server, page, toImpl, browserName, platform, mode }) => {
|
||||
await page.setContent(`<div>This page should crash</div>`);
|
||||
server.setRoute('/one-style.css', () => {});
|
||||
const promise = page.goto(server.PREFIX + '/one-style.html').catch(e => e);
|
||||
await page.waitForNavigation({ waitUntil: 'domcontentloaded' });
|
||||
crash(args);
|
||||
crash({ page, toImpl, browserName, platform, mode });
|
||||
const error = await promise;
|
||||
expect(error.message).toContain('Navigation failed because page crashed');
|
||||
});
|
||||
|
||||
it('should be able to close context when page crashes', async args => {
|
||||
it.skip(args.isAndroid);
|
||||
it.skip(args.isElectron);
|
||||
it('should be able to close context when page crashes', async ({ isAndroid, isElectron, page, toImpl, browserName, platform, mode }) => {
|
||||
it.skip(isAndroid);
|
||||
it.skip(isElectron);
|
||||
|
||||
const { page } = args;
|
||||
await page.setContent(`<div>This page should crash</div>`);
|
||||
crash(args);
|
||||
crash({ page, toImpl, browserName, platform, mode });
|
||||
await page.waitForEvent('crash');
|
||||
await page.context().close();
|
||||
});
|
||||
|
||||
@ -19,7 +19,7 @@ import type { Page } from '../../index';
|
||||
export { expect } from 'folio';
|
||||
|
||||
// Page test does not guarantee an isolated context, just a new page (because Android).
|
||||
export type PageTestArgs = {
|
||||
export type PageTestFixtures = {
|
||||
browserVersion: string;
|
||||
browserMajorVersion: number;
|
||||
page: Page;
|
||||
@ -27,4 +27,4 @@ export type PageTestArgs = {
|
||||
isElectron: boolean;
|
||||
};
|
||||
|
||||
export const test = baseTest.declare<PageTestArgs>();
|
||||
export const test = baseTest.declare<PageTestFixtures>();
|
||||
|
||||
@ -19,25 +19,22 @@ import { InMemorySnapshotter } from '../lib/server/snapshot/inMemorySnapshotter'
|
||||
import { HttpServer } from '../lib/utils/httpServer';
|
||||
import { SnapshotServer } from '../lib/server/snapshot/snapshotServer';
|
||||
|
||||
const it = contextTest.extend({
|
||||
async beforeEach({ context, toImpl, mode }, testInfo) {
|
||||
testInfo.skip(mode !== 'default');
|
||||
const snapshotter = new InMemorySnapshotter(toImpl(context));
|
||||
await snapshotter.initialize();
|
||||
this.httpServer = new HttpServer();
|
||||
new SnapshotServer(this.httpServer, snapshotter);
|
||||
const snapshotPort = 11000 + testInfo.workerIndex;
|
||||
await this.httpServer.start(snapshotPort);
|
||||
this.snapshotter = snapshotter;
|
||||
return {
|
||||
snapshotter,
|
||||
snapshotPort,
|
||||
};
|
||||
const it = contextTest.extend<{ snapshotPort: number, snapshotter: InMemorySnapshotter }>({
|
||||
snapshotPort: async ({}, run, testInfo) => {
|
||||
await run(11000 + testInfo.workerIndex);
|
||||
},
|
||||
|
||||
async afterEach() {
|
||||
await this.snapshotter.dispose();
|
||||
await this.httpServer.stop();
|
||||
snapshotter: async ({ mode, toImpl, context, snapshotPort }, run, testInfo) => {
|
||||
if (mode !== 'default')
|
||||
testInfo.skip();
|
||||
const snapshotter = new InMemorySnapshotter(toImpl(context));
|
||||
await snapshotter.initialize();
|
||||
const httpServer = new HttpServer();
|
||||
new SnapshotServer(httpServer, snapshotter);
|
||||
await httpServer.start(snapshotPort);
|
||||
await run(snapshotter);
|
||||
await snapshotter.dispose();
|
||||
await httpServer.stop();
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ import { contextTest as it, expect } from './config/browserTest';
|
||||
import { ElementHandle } from '../index';
|
||||
import type { ServerResponse } from 'http';
|
||||
|
||||
it.useOptions({ hasTouch: true });
|
||||
it.use({ hasTouch: true });
|
||||
|
||||
it('should send all of the correct events', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
|
||||
@ -21,7 +21,7 @@ import removeFolder from 'rimraf';
|
||||
import jpeg from 'jpeg-js';
|
||||
|
||||
const traceDir = path.join(__dirname, '..', 'test-results', 'trace-' + process.env.FOLIO_WORKER_INDEX);
|
||||
test.useOptions({ traceDir });
|
||||
test.use({ traceDir });
|
||||
|
||||
test.beforeEach(async ({ browserName, headless }) => {
|
||||
test.fixme(browserName === 'chromium' && !headless, 'Chromium screencast on headed has a min width issue');
|
||||
@ -29,13 +29,13 @@ test.beforeEach(async ({ browserName, headless }) => {
|
||||
});
|
||||
|
||||
test('should collect trace', async ({ context, page, server, browserName }, testInfo) => {
|
||||
await (context as any).tracing.start({ name: 'test', screenshots: true, snapshots: true });
|
||||
await context.tracing.start({ name: 'test', screenshots: true, snapshots: true });
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
await page.setContent('<button>Click</button>');
|
||||
await page.click('"Click"');
|
||||
await page.close();
|
||||
await (context as any).tracing.stop();
|
||||
await (context as any).tracing.export(testInfo.outputPath('trace.zip'));
|
||||
await context.tracing.stop();
|
||||
await context.tracing.export(testInfo.outputPath('trace.zip'));
|
||||
|
||||
const { events } = await parseTrace(testInfo.outputPath('trace.zip'));
|
||||
expect(events[0].type).toBe('context-options');
|
||||
@ -50,13 +50,13 @@ test('should collect trace', async ({ context, page, server, browserName }, test
|
||||
});
|
||||
|
||||
test('should collect trace', async ({ context, page, server }, testInfo) => {
|
||||
await (context as any).tracing.start({ name: 'test' });
|
||||
await context.tracing.start({ name: 'test' });
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
await page.setContent('<button>Click</button>');
|
||||
await page.click('"Click"');
|
||||
await page.close();
|
||||
await (context as any).tracing.stop();
|
||||
await (context as any).tracing.export(testInfo.outputPath('trace.zip'));
|
||||
await context.tracing.stop();
|
||||
await context.tracing.export(testInfo.outputPath('trace.zip'));
|
||||
|
||||
const { events } = await parseTrace(testInfo.outputPath('trace.zip'));
|
||||
expect(events.some(e => e.type === 'frame-snapshot')).toBeFalsy();
|
||||
@ -64,18 +64,18 @@ test('should collect trace', async ({ context, page, server }, testInfo) => {
|
||||
});
|
||||
|
||||
test('should collect two traces', async ({ context, page, server }, testInfo) => {
|
||||
await (context as any).tracing.start({ name: 'test1', screenshots: true, snapshots: true });
|
||||
await context.tracing.start({ name: 'test1', screenshots: true, snapshots: true });
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
await page.setContent('<button>Click</button>');
|
||||
await page.click('"Click"');
|
||||
await (context as any).tracing.stop();
|
||||
await (context as any).tracing.export(testInfo.outputPath('trace1.zip'));
|
||||
await context.tracing.stop();
|
||||
await context.tracing.export(testInfo.outputPath('trace1.zip'));
|
||||
|
||||
await (context as any).tracing.start({ name: 'test2', screenshots: true, snapshots: true });
|
||||
await context.tracing.start({ name: 'test2', screenshots: true, snapshots: true });
|
||||
await page.dblclick('"Click"');
|
||||
await page.close();
|
||||
await (context as any).tracing.stop();
|
||||
await (context as any).tracing.export(testInfo.outputPath('trace2.zip'));
|
||||
await context.tracing.stop();
|
||||
await context.tracing.export(testInfo.outputPath('trace2.zip'));
|
||||
|
||||
{
|
||||
const { events } = await parseTrace(testInfo.outputPath('trace1.zip'));
|
||||
@ -124,15 +124,15 @@ for (const params of [
|
||||
const previewHeight = params.height * scale;
|
||||
|
||||
const context = await contextFactory({ viewport: { width: params.width, height: params.height }});
|
||||
await (context as any).tracing.start({ name: 'test', screenshots: true, snapshots: true });
|
||||
await context.tracing.start({ name: 'test', screenshots: true, snapshots: true });
|
||||
const page = await context.newPage();
|
||||
// Make sure we have a chance to paint.
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
await page.setContent('<body style="box-sizing: border-box; width: 100%; height: 100%; margin:0; background: red; border: 50px solid blue"></body>');
|
||||
await page.evaluate(() => new Promise(requestAnimationFrame));
|
||||
}
|
||||
await (context as any).tracing.stop();
|
||||
await (context as any).tracing.export(testInfo.outputPath('trace.zip'));
|
||||
await context.tracing.stop();
|
||||
await context.tracing.export(testInfo.outputPath('trace.zip'));
|
||||
|
||||
const { events, resources } = await parseTrace(testInfo.outputPath('trace.zip'));
|
||||
const frames = events.filter(e => e.type === 'screencast-frame');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user