2020-07-02 21:05:38 +03:00
|
|
|
/**
|
|
|
|
* Copyright 2019 Google Inc. All rights reserved.
|
|
|
|
* Modifications copyright (c) Microsoft Corporation.
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
const utils = require('./utils');
|
|
|
|
const fs = require('fs');
|
|
|
|
const path = require('path');
|
|
|
|
const rm = require('rimraf').sync;
|
2020-07-14 01:00:20 +03:00
|
|
|
const childProcess = require('child_process');
|
2020-07-02 21:05:38 +03:00
|
|
|
const {TestServer} = require('../utils/testserver/');
|
|
|
|
const { DispatcherConnection } = require('../lib/rpc/server/dispatcher');
|
|
|
|
const { Connection } = require('../lib/rpc/client/connection');
|
2020-07-14 01:00:20 +03:00
|
|
|
const { Transport } = require('../lib/rpc/transport');
|
2020-07-09 04:42:04 +03:00
|
|
|
const { PlaywrightDispatcher } = require('../lib/rpc/server/playwrightDispatcher');
|
2020-07-16 00:04:39 +03:00
|
|
|
const { setUseApiName } = require('../lib/progress');
|
2020-07-02 21:05:38 +03:00
|
|
|
|
|
|
|
class ServerEnvironment {
|
|
|
|
async beforeAll(state) {
|
|
|
|
const assetsPath = path.join(__dirname, 'assets');
|
|
|
|
const cachedPath = path.join(__dirname, 'assets', 'cached');
|
|
|
|
|
|
|
|
const port = 8907 + state.parallelIndex * 2;
|
|
|
|
state.server = await TestServer.create(assetsPath, port);
|
|
|
|
state.server.enableHTTPCache(cachedPath);
|
|
|
|
state.server.PORT = port;
|
|
|
|
state.server.PREFIX = `http://localhost:${port}`;
|
|
|
|
state.server.CROSS_PROCESS_PREFIX = `http://127.0.0.1:${port}`;
|
|
|
|
state.server.EMPTY_PAGE = `http://localhost:${port}/empty.html`;
|
|
|
|
|
|
|
|
const httpsPort = port + 1;
|
|
|
|
state.httpsServer = await TestServer.createHTTPS(assetsPath, httpsPort);
|
|
|
|
state.httpsServer.enableHTTPCache(cachedPath);
|
|
|
|
state.httpsServer.PORT = httpsPort;
|
|
|
|
state.httpsServer.PREFIX = `https://localhost:${httpsPort}`;
|
|
|
|
state.httpsServer.CROSS_PROCESS_PREFIX = `https://127.0.0.1:${httpsPort}`;
|
|
|
|
state.httpsServer.EMPTY_PAGE = `https://localhost:${httpsPort}/empty.html`;
|
|
|
|
}
|
|
|
|
|
|
|
|
async afterAll({server, httpsServer}) {
|
|
|
|
await Promise.all([
|
|
|
|
server.stop(),
|
|
|
|
httpsServer.stop(),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
async beforeEach(state) {
|
|
|
|
state.server.reset();
|
|
|
|
state.httpsServer.reset();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class DefaultBrowserOptionsEnvironment {
|
|
|
|
constructor(defaultBrowserOptions, dumpLogOnFailure, playwrightPath) {
|
|
|
|
this._defaultBrowserOptions = defaultBrowserOptions;
|
|
|
|
this._dumpLogOnFailure = dumpLogOnFailure;
|
|
|
|
this._playwrightPath = playwrightPath;
|
|
|
|
this._loggerSymbol = Symbol('DefaultBrowserOptionsEnvironment.logger');
|
|
|
|
}
|
|
|
|
|
|
|
|
async beforeAll(state) {
|
|
|
|
state[this._loggerSymbol] = utils.createTestLogger(this._dumpLogOnFailure, null, 'extra');
|
|
|
|
state.defaultBrowserOptions = {
|
|
|
|
...this._defaultBrowserOptions,
|
|
|
|
logger: state[this._loggerSymbol],
|
|
|
|
};
|
|
|
|
state.playwrightPath = this._playwrightPath;
|
|
|
|
}
|
|
|
|
|
|
|
|
async beforeEach(state, testRun) {
|
|
|
|
state[this._loggerSymbol].setTestRun(testRun);
|
|
|
|
}
|
|
|
|
|
|
|
|
async afterEach(state) {
|
|
|
|
state[this._loggerSymbol].setTestRun(null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// simulate globalSetup per browserType that happens only once regardless of TestWorker.
|
|
|
|
const hasBeenCleaned = new Set();
|
|
|
|
|
|
|
|
class GoldenEnvironment {
|
|
|
|
async beforeAll(state) {
|
|
|
|
const { OUTPUT_DIR, GOLDEN_DIR } = utils.testOptions(state.browserType);
|
|
|
|
if (!hasBeenCleaned.has(state.browserType)) {
|
|
|
|
hasBeenCleaned.add(state.browserType);
|
|
|
|
if (fs.existsSync(OUTPUT_DIR))
|
|
|
|
rm(OUTPUT_DIR);
|
|
|
|
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
|
|
}
|
|
|
|
state.golden = goldenName => ({ goldenPath: GOLDEN_DIR, outputPath: OUTPUT_DIR, goldenName });
|
|
|
|
}
|
|
|
|
|
|
|
|
async afterAll(state) {
|
|
|
|
delete state.golden;
|
|
|
|
}
|
|
|
|
|
|
|
|
async afterEach(state, testRun) {
|
|
|
|
if (state.browser && state.browser.contexts().length !== 0) {
|
|
|
|
if (testRun.ok())
|
|
|
|
console.warn(`\nWARNING: test "${testRun.test().fullName()}" (${testRun.test().location()}) did not close all created contexts!\n`);
|
|
|
|
await Promise.all(state.browser.contexts().map(context => context.close()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class TraceTestEnvironment {
|
|
|
|
static enableForTest(test) {
|
|
|
|
test.setTimeout(100000000);
|
|
|
|
test.addEnvironment(new TraceTestEnvironment());
|
|
|
|
}
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
this._session = null;
|
|
|
|
}
|
|
|
|
|
2020-07-14 02:03:24 +03:00
|
|
|
async beforeEach(state, testRun) {
|
|
|
|
const t = testRun.test();
|
2020-07-02 21:05:38 +03:00
|
|
|
const inspector = require('inspector');
|
|
|
|
const fs = require('fs');
|
|
|
|
const util = require('util');
|
|
|
|
const url = require('url');
|
|
|
|
const readFileAsync = util.promisify(fs.readFile.bind(fs));
|
|
|
|
this._session = new inspector.Session();
|
|
|
|
this._session.connect();
|
|
|
|
const postAsync = util.promisify(this._session.post.bind(this._session));
|
|
|
|
await postAsync('Debugger.enable');
|
|
|
|
const setBreakpointCommands = [];
|
|
|
|
const N = t.body().toString().split('\n').length;
|
|
|
|
const location = t.location();
|
|
|
|
const lines = (await readFileAsync(location.filePath(), 'utf8')).split('\n');
|
|
|
|
for (let line = 0; line < N; ++line) {
|
|
|
|
const lineNumber = line + location.lineNumber();
|
|
|
|
setBreakpointCommands.push(postAsync('Debugger.setBreakpointByUrl', {
|
|
|
|
url: url.pathToFileURL(location.filePath()),
|
|
|
|
lineNumber,
|
|
|
|
condition: `console.log('${String(lineNumber + 1).padStart(6, ' ')} | ' + ${JSON.stringify(lines[lineNumber])})`,
|
|
|
|
}).catch(e => {}));
|
|
|
|
}
|
|
|
|
await Promise.all(setBreakpointCommands);
|
|
|
|
}
|
|
|
|
|
|
|
|
async afterEach() {
|
|
|
|
this._session.disconnect();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class PlaywrightEnvironment {
|
|
|
|
constructor(playwright) {
|
|
|
|
this._playwright = playwright;
|
2020-07-14 01:00:20 +03:00
|
|
|
this.spawnedProcess = undefined;
|
2020-07-18 02:14:23 +03:00
|
|
|
this.onExit = undefined;
|
2020-07-02 21:05:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
name() { return 'Playwright'; };
|
|
|
|
|
|
|
|
async beforeAll(state) {
|
|
|
|
if (process.env.PWCHANNEL) {
|
2020-07-16 00:04:39 +03:00
|
|
|
setUseApiName(false);
|
2020-07-02 21:05:38 +03:00
|
|
|
const connection = new Connection();
|
2020-07-14 01:00:20 +03:00
|
|
|
if (process.env.PWCHANNEL === 'wire') {
|
|
|
|
this.spawnedProcess = childProcess.fork(path.join(__dirname, '..', 'lib', 'rpc', 'server'), [], {
|
|
|
|
stdio: 'pipe',
|
2020-07-18 02:14:23 +03:00
|
|
|
detached: true,
|
2020-07-14 01:00:20 +03:00
|
|
|
});
|
2020-07-18 02:14:23 +03:00
|
|
|
this.spawnedProcess.unref();
|
|
|
|
this.onExit = (exitCode, signal) => {
|
|
|
|
throw new Error(`Server closed with exitCode=${exitCode} signal=${signal}`);
|
|
|
|
};
|
|
|
|
this.spawnedProcess.once('exit', this.onExit);
|
2020-07-14 01:00:20 +03:00
|
|
|
const transport = new Transport(this.spawnedProcess.stdin, this.spawnedProcess.stdout);
|
|
|
|
connection.onmessage = message => transport.send(JSON.stringify(message));
|
|
|
|
transport.onmessage = message => connection.dispatch(JSON.parse(message));
|
|
|
|
} else {
|
|
|
|
const dispatcherConnection = new DispatcherConnection();
|
|
|
|
dispatcherConnection.onmessage = async message => {
|
|
|
|
setImmediate(() => connection.dispatch(message));
|
|
|
|
};
|
|
|
|
connection.onmessage = async message => {
|
|
|
|
const result = await dispatcherConnection.dispatch(message);
|
|
|
|
await new Promise(f => setImmediate(f));
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
new PlaywrightDispatcher(dispatcherConnection.rootDispatcher(), this._playwright);
|
|
|
|
state.toImpl = x => dispatcherConnection._dispatchers.get(x._guid)._object;
|
|
|
|
}
|
|
|
|
state.playwright = await connection.waitForObjectWithKnownName('playwright');
|
|
|
|
} else {
|
|
|
|
state.toImpl = x => x;
|
|
|
|
state.playwright = this._playwright;
|
2020-07-02 21:05:38 +03:00
|
|
|
}
|
2020-07-09 04:42:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async afterAll(state) {
|
2020-07-14 01:00:20 +03:00
|
|
|
if (this.spawnedProcess) {
|
2020-07-18 02:14:23 +03:00
|
|
|
this.spawnedProcess.removeListener('exit', this.onExit);
|
|
|
|
this.spawnedProcess.stdin.destroy();
|
|
|
|
this.spawnedProcess.stdout.destroy();
|
|
|
|
this.spawnedProcess.stderr.destroy();
|
2020-07-14 01:00:20 +03:00
|
|
|
}
|
2020-07-09 04:42:04 +03:00
|
|
|
delete state.playwright;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class BrowserTypeEnvironment {
|
|
|
|
constructor(browserName) {
|
|
|
|
this._browserName = browserName;
|
|
|
|
}
|
|
|
|
|
|
|
|
async beforeAll(state) {
|
|
|
|
state.browserType = state.playwright[this._browserName];
|
2020-07-02 21:05:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async afterAll(state) {
|
|
|
|
delete state.browserType;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class BrowserEnvironment {
|
|
|
|
constructor(launchOptions, dumpLogOnFailure) {
|
|
|
|
this._launchOptions = launchOptions;
|
|
|
|
this._dumpLogOnFailure = dumpLogOnFailure;
|
|
|
|
this._loggerSymbol = Symbol('BrowserEnvironment.logger');
|
|
|
|
}
|
|
|
|
|
|
|
|
async beforeAll(state) {
|
|
|
|
state[this._loggerSymbol] = utils.createTestLogger(this._dumpLogOnFailure);
|
|
|
|
state.browser = await state.browserType.launch({
|
|
|
|
...this._launchOptions,
|
|
|
|
logger: state[this._loggerSymbol],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async afterAll(state) {
|
|
|
|
await state.browser.close();
|
|
|
|
delete state.browser;
|
|
|
|
}
|
|
|
|
|
|
|
|
async beforeEach(state, testRun) {
|
|
|
|
state[this._loggerSymbol].setTestRun(testRun);
|
|
|
|
}
|
|
|
|
|
|
|
|
async afterEach(state, testRun) {
|
|
|
|
state[this._loggerSymbol].setTestRun(null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class PageEnvironment {
|
|
|
|
async beforeEach(state) {
|
|
|
|
state.context = await state.browser.newContext();
|
|
|
|
state.page = await state.context.newPage();
|
|
|
|
}
|
|
|
|
|
|
|
|
async afterEach(state) {
|
|
|
|
await state.context.close();
|
|
|
|
state.context = null;
|
|
|
|
state.page = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
ServerEnvironment,
|
|
|
|
GoldenEnvironment,
|
|
|
|
TraceTestEnvironment,
|
|
|
|
DefaultBrowserOptionsEnvironment,
|
|
|
|
PlaywrightEnvironment,
|
|
|
|
BrowserTypeEnvironment,
|
|
|
|
BrowserEnvironment,
|
|
|
|
PageEnvironment,
|
|
|
|
};
|