test: structure tests to use environments, closer to end user (#1713)

This commit is contained in:
Dmitry Gozman 2020-04-08 14:17:34 -07:00 committed by GitHub
parent be06bb0139
commit 2ef8e26602
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 197 additions and 155 deletions

View file

@ -14,25 +14,16 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const rm = require('rimraf').sync; const rm = require('rimraf').sync;
const readline = require('readline'); const readline = require('readline');
const {TestServer} = require('../utils/testserver/'); const {TestServer} = require('../utils/testserver/');
const {Environment} = require('../utils/testrunner/Test');
const YELLOW_COLOR = '\x1b[33m'; const serverEnvironment = new Environment('TestServer');
const RESET_COLOR = '\x1b[0m'; serverEnvironment.beforeAll(async state => {
/**
* @type {TestSuite}
*/
module.exports.addPlaywrightTests = ({platform, products, playwrightPath, headless, slowMo, dumpProtocolOnFailure, coverage}) => {
const MAC = platform === 'darwin';
const LINUX = platform === 'linux';
const WIN = platform === 'win32';
const playwright = require(playwrightPath);
beforeAll(async state => {
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');
@ -57,91 +48,68 @@ module.exports.addPlaywrightTests = ({platform, products, playwrightPath, headle
state.sourceServer.PORT = sourcePort; state.sourceServer.PORT = sourcePort;
state.sourceServer.PREFIX = `http://localhost:${sourcePort}`; state.sourceServer.PREFIX = `http://localhost:${sourcePort}`;
}); });
serverEnvironment.afterAll(async({server, sourceServer, httpsServer}) => {
afterAll(async({server, sourceServer, httpsServer}) => {
await Promise.all([ await Promise.all([
server.stop(), server.stop(),
httpsServer.stop(), httpsServer.stop(),
sourceServer.stop(), sourceServer.stop(),
]); ]);
}); });
serverEnvironment.beforeEach(async({server, httpsServer}) => {
beforeEach(async({server, httpsServer}) => {
server.reset(); server.reset();
httpsServer.reset(); httpsServer.reset();
}); });
for (const productInfo of products) { const goldenEnvironment = new Environment('Golden');
const product = productInfo.product; goldenEnvironment.beforeAll(async ({browserType}) => {
const browserType = playwright[product.toLowerCase()]; const { OUTPUT_DIR, GOLDEN_DIR } = require('./utils').testOptions(browserType);
const CHROMIUM = product === 'Chromium';
const FFOX = product === 'Firefox';
const WEBKIT = product === 'WebKit';
const defaultBrowserOptions = {
handleSIGINT: false,
executablePath: productInfo.executablePath,
slowMo,
headless,
};
if (defaultBrowserOptions.executablePath) {
console.warn(`${YELLOW_COLOR}WARN: running ${product} tests with ${defaultBrowserOptions.executablePath}${RESET_COLOR}`);
} else {
// Make sure the `npm install` was run after the chromium roll.
if (!fs.existsSync(browserType.executablePath()))
throw new Error(`Browser is not downloaded. Run 'npm install' and try to re-run tests`);
}
const GOLDEN_DIR = path.join(__dirname, 'golden-' + product.toLowerCase());
const OUTPUT_DIR = path.join(__dirname, 'output-' + product.toLowerCase());
if (fs.existsSync(OUTPUT_DIR)) if (fs.existsSync(OUTPUT_DIR))
rm(OUTPUT_DIR); rm(OUTPUT_DIR);
fs.mkdirSync(OUTPUT_DIR, { recursive: true }); fs.mkdirSync(OUTPUT_DIR, { recursive: true });
expect.setupGolden(GOLDEN_DIR, OUTPUT_DIR); expect.setupGolden(GOLDEN_DIR, OUTPUT_DIR);
});
const testOptions = { /**
FFOX, * @type {TestSuite}
WEBKIT, */
CHROMIUM, module.exports.addPlaywrightTests = ({testRunner, products}) => {
MAC, const dumpProtocolOnFailure = valueFromEnv('DEBUGP', false);
LINUX, const playwrightPath = require('./utils').projectRoot();
WIN, const playwright = require(playwrightPath);
browserType,
playwright,
defaultBrowserOptions,
playwrightPath,
headless: !!defaultBrowserOptions.headless,
OUTPUT_DIR,
};
function loadTests(modulePath) { const playwrightEnvironment = new Environment('Playwright');
const module = require(modulePath); playwrightEnvironment.beforeAll(async state => {
if (typeof module.describe === 'function') state.playwright = playwright;
describe('', module.describe, testOptions); });
if (typeof module.fdescribe === 'function') playwrightEnvironment.afterAll(async state => {
fdescribe('', module.fdescribe, testOptions); delete state.playwright;
if (typeof module.xdescribe === 'function') });
xdescribe('', module.xdescribe, testOptions);
}
describe(product, () => { for (const product of products) {
describe('', function() { const browserTypeEnvironment = new Environment('BrowserType');
beforeAll(async state => { browserTypeEnvironment.beforeAll(async state => {
state.browser = await browserType.launch(defaultBrowserOptions); state.browserType = state.playwright[product.toLowerCase()];
});
browserTypeEnvironment.afterAll(async state => {
delete state.browserType;
});
const browserEnvironment = new Environment(product);
browserEnvironment.beforeAll(async state => {
const { defaultBrowserOptions } = require('./utils').testOptions(state.browserType);
state.browser = await state.browserType.launch(defaultBrowserOptions);
state.browserServer = state.browser._ownedServer; state.browserServer = state.browser._ownedServer;
state._stdout = readline.createInterface({ input: state.browserServer.process().stdout }); state._stdout = readline.createInterface({ input: state.browserServer.process().stdout });
state._stderr = readline.createInterface({ input: state.browserServer.process().stderr }); state._stderr = readline.createInterface({ input: state.browserServer.process().stderr });
}); });
browserEnvironment.afterAll(async state => {
afterAll(async state => {
await state.browserServer.close(); await state.browserServer.close();
state.browser = null; state.browser = null;
state.browserServer = null; state.browserServer = null;
state._stdout.close(); state._stdout.close();
state._stderr.close(); state._stderr.close();
}); });
browserEnvironment.beforeEach(async(state, testRun) => {
beforeEach(async(state, testRun) => {
const dumpout = data => testRun.log(`\x1b[33m[pw:stdio:out]\x1b[0m ${data}`); const dumpout = data => testRun.log(`\x1b[33m[pw:stdio:out]\x1b[0m ${data}`);
const dumperr = data => testRun.log(`\x1b[31m[pw:stdio:err]\x1b[0m ${data}`); const dumperr = data => testRun.log(`\x1b[31m[pw:stdio:err]\x1b[0m ${data}`);
state._stdout.on('line', dumpout); state._stdout.on('line', dumpout);
@ -155,8 +123,7 @@ module.exports.addPlaywrightTests = ({platform, products, playwrightPath, headle
delete state.browser._debugProtocol.log; delete state.browser._debugProtocol.log;
}; };
}); });
browserEnvironment.afterEach(async (state, test) => {
afterEach(async (state, test) => {
if (state.browser.contexts().length !== 0) { if (state.browser.contexts().length !== 0) {
if (test.result === 'ok') if (test.result === 'ok')
console.warn(`\nWARNING: test "${test.fullName()}" (${test.location()}) did not close all created contexts!\n`); console.warn(`\nWARNING: test "${test.fullName()}" (${test.location()}) did not close all created contexts!\n`);
@ -165,18 +132,49 @@ module.exports.addPlaywrightTests = ({platform, products, playwrightPath, headle
await state.tearDown(); await state.tearDown();
}); });
describe('', function() { const pageEnvironment = new Environment('Page');
beforeEach(async state => { pageEnvironment.beforeEach(async state => {
state.context = await state.browser.newContext(); state.context = await state.browser.newContext();
state.page = await state.context.newPage(); state.page = await state.context.newPage();
}); });
pageEnvironment.afterEach(async state => {
afterEach(async state => {
await state.context.close(); await state.context.close();
state.context = null; state.context = null;
state.page = null; state.page = null;
}); });
function loadTests(modulePath) {
const testOptions = {
...require('./utils').testOptions(global.browserType),
playwright: global.playwright,
browserType: global.browserType,
};
const module = require(modulePath);
if (typeof module.describe === 'function')
describe('', module.describe, testOptions);
if (typeof module.fdescribe === 'function')
fdescribe('', module.fdescribe, testOptions);
if (typeof module.xdescribe === 'function')
xdescribe('', module.xdescribe, testOptions);
}
testRunner.collector().useEnvironment(serverEnvironment); // Custom global environment.
testRunner.collector().useEnvironment(playwrightEnvironment);
describe(product, () => {
// In addition to state, expose these two on global so that describes can access them.
global.playwright = playwright;
global.browserType = playwright[product.toLowerCase()];
testRunner.collector().useEnvironment(browserTypeEnvironment);
testRunner.collector().useEnvironment(goldenEnvironment); // Custom environment.
describe('', function() {
testRunner.collector().useEnvironment(browserEnvironment);
describe('', function() {
testRunner.collector().useEnvironment(pageEnvironment);
// Page-level tests that are given a browser, a context and a page. // Page-level tests that are given a browser, a context and a page.
// Each test is launched in a new browser context. // Each test is launched in a new browser context.
describe('[Accessibility]', () => loadTests('./accessibility.spec.js')); describe('[Accessibility]', () => loadTests('./accessibility.spec.js'));
@ -210,7 +208,7 @@ module.exports.addPlaywrightTests = ({platform, products, playwrightPath, headle
loadTests('./permissions.spec.js'); loadTests('./permissions.spec.js');
}); });
describe.skip(!CHROMIUM)('[Chromium]', () => { describe.skip(product !== 'Chromium')('[Chromium]', () => {
loadTests('./chromium/chromium.spec.js'); loadTests('./chromium/chromium.spec.js');
loadTests('./chromium/coverage.spec.js'); loadTests('./chromium/coverage.spec.js');
loadTests('./chromium/pdf.spec.js'); loadTests('./chromium/pdf.spec.js');
@ -236,14 +234,23 @@ module.exports.addPlaywrightTests = ({platform, products, playwrightPath, headle
loadTests('./multiclient.spec.js'); loadTests('./multiclient.spec.js');
}); });
describe.skip(!CHROMIUM)('[Chromium]', () => { describe.skip(product !== 'Chromium')('[Chromium]', () => {
loadTests('./chromium/launcher.spec.js'); loadTests('./chromium/launcher.spec.js');
loadTests('./chromium/oopif.spec.js'); loadTests('./chromium/oopif.spec.js');
loadTests('./chromium/tracing.spec.js'); loadTests('./chromium/tracing.spec.js');
}); });
if (coverage) if (process.env.COVERAGE)
loadTests('./apicoverage.spec.js'); loadTests('./apicoverage.spec.js');
delete global.browserType;
delete global.playwright;
}); });
} }
}; };
function valueFromEnv(name, defaultValue) {
if (!(name in process.env))
return defaultValue;
return JSON.parse(process.env[name]);
}

View file

@ -50,34 +50,15 @@ utils.setupTestRunner(testRunner);
console.log('Testing on Node', process.version); console.log('Testing on Node', process.version);
const names = ['Chromium', 'Firefox', 'WebKit'].filter(name => { const products = ['Chromium', 'Firefox', 'WebKit'].filter(name => {
return process.env.BROWSER === name.toLowerCase() || process.env.BROWSER === 'all'; return process.env.BROWSER === name.toLowerCase() || process.env.BROWSER === 'all';
}); });
const products = names.map(name => {
const executablePath = {
'Chromium': process.env.CRPATH,
'Firefox': process.env.FFPATH,
'WebKit': process.env.WKPATH,
}[name];
return { product: name, executablePath };
});
function valueFromEnv(name, defaultValue) {
if (!(name in process.env))
return defaultValue;
return JSON.parse(process.env[name]);
}
for (const [key, value] of Object.entries(testRunner.api())) for (const [key, value] of Object.entries(testRunner.api()))
global[key] = value; global[key] = value;
require('./playwright.spec.js').addPlaywrightTests({ require('./playwright.spec.js').addPlaywrightTests({
playwrightPath: utils.projectRoot(), testRunner,
products, products,
platform: os.platform(),
headless: !!valueFromEnv('HEADLESS', true),
slowMo: valueFromEnv('SLOW_MO', 0),
dumpProtocolOnFailure: valueFromEnv('DEBUGP', false),
coverage: process.env.COVERAGE,
}); });
for (const [key, value] of Object.entries(testRunner.api())) { for (const [key, value] of Object.entries(testRunner.api())) {
// expect is used when running tests, while the rest of api is not. // expect is used when running tests, while the rest of api is not.

View file

@ -213,4 +213,50 @@ const utils = module.exports = {
testRunner.api().xit = testRunner.api().it.skip(true); testRunner.api().xit = testRunner.api().it.skip(true);
testRunner.api().dit = testRunner.api().it.only.debug; testRunner.api().dit = testRunner.api().it.only.debug;
}, },
testOptions(browserType) {
const headless = !!valueFromEnv('HEADLESS', true);
const executablePath = {
'chromium': process.env.CRPATH,
'firefox': process.env.FFPATH,
'webkit': process.env.WKPATH,
}[browserType.name()];
const defaultBrowserOptions = {
handleSIGINT: false,
executablePath,
slowMo: valueFromEnv('SLOW_MO', 0),
headless,
}; };
if (defaultBrowserOptions.executablePath) {
const YELLOW_COLOR = '\x1b[33m';
const RESET_COLOR = '\x1b[0m';
console.warn(`${YELLOW_COLOR}WARN: running ${product} tests with ${defaultBrowserOptions.executablePath}${RESET_COLOR}`);
} else {
// Make sure the `npm install` was run after the chromium roll.
if (!fs.existsSync(browserType.executablePath()))
throw new Error(`Browser is not downloaded. Run 'npm install' and try to re-run tests`);
}
const GOLDEN_DIR = path.join(__dirname, 'golden-' + browserType.name());
const OUTPUT_DIR = path.join(__dirname, 'output-' + browserType.name());
return {
FFOX: browserType.name() === 'firefox',
WEBKIT: browserType.name() === 'webkit',
CHROMIUM: browserType.name() === 'chromium',
MAC: os.platform() === 'darwin',
LINUX: os.platform() === 'linux',
WIN: os.platform() === 'win32',
browserType,
defaultBrowserOptions,
playwrightPath: PROJECT_ROOT,
headless: !!defaultBrowserOptions.headless,
GOLDEN_DIR,
OUTPUT_DIR,
};
},
};
function valueFromEnv(name, defaultValue) {
if (!(name in process.env))
return defaultValue;
return JSON.parse(process.env[name]);
}

View file

@ -159,6 +159,10 @@ class TestCollector {
this._api.afterEach = callback => this._currentSuite.environment().afterEach(callback); this._api.afterEach = callback => this._currentSuite.environment().afterEach(callback);
} }
useEnvironment(environment) {
return this._currentSuite.addEnvironment(environment);
}
addTestModifier(name, callback) { addTestModifier(name, callback) {
this._testModifiers.set(name, callback); this._testModifiers.set(name, callback);
} }

View file

@ -74,6 +74,10 @@ class DefaultTestRunner {
this._api.xit = this._api.it.skip; this._api.xit = this._api.it.skip;
} }
collector() {
return this._collector;
}
api() { api() {
return this._api; return this._api;
} }