just-test
Version:
JavaScript tests runner
215 lines (186 loc) • 7.12 kB
JavaScript
/**
* This will launch browser environment, that is:
* - launching browser main instance
* - loading session runner
* - tracking test run instances and collecting data (coverage, logs)
* - dismissing all upon finalization / crash
*
* @param {Object} envConfig environment configuration
* @param {string} envConfig.browser in this context expected always to equal true
*/
import Logger, { FileOutput } from '../../logger/logger.js';
import { INTEROP_NAMES, SESSION_ENVIRONMENT_KEYS } from '../../../common/constants.js';
import { waitInterval } from '../../../common/time-utils.js';
import { config as serverConfig } from '../../server-service.js';
import { collectTargetSources } from '../../../coverage/coverage-service.js';
import { EnvironmentBase } from '../environment-base.js';
import playwright from 'playwright';
export default launch;
const logger = new Logger({ context: 'browser env service' });
class BrowserEnvImpl extends EnvironmentBase {
/**
* construct browser environment for a specific session
*
* @param {string} sessionId session ID
* @param {object} envConfig environment setup
*/
constructor(sessionId, envConfig) {
super(sessionId);
this.envConfig = envConfig;
this.browser = null;
this.consoleLogger = null;
this.coverageSession = null;
this.scriptsCoverageMap = null;
this.dismissPromise = null;
this.timeoutHandle = null;
Object.seal(this);
}
async launch() {
const browserType = this.envConfig.browser.type;
logger.info(`launching '${browserType}' environment...`);
const browser = await playwright[browserType].launch();
this.consoleLogger = new FileOutput(`./reports/logs/${browserType}-${browser.version()}.log`);
const bLogger = new Logger({
context: `${browserType}-${browser.version()}`,
outputs: [this.consoleLogger]
});
const browsingContext = await browser.newContext();
const mainPage = await browsingContext.newPage();
if (this.envConfig.coverage) {
await this.installCoverageCollector(this.envConfig.coverage, browsingContext, mainPage);
}
mainPage.on('console', async msg => {
const type = msg.type();
for (const msgArg of msg.args()) {
const consoleMessage = await msgArg.evaluate(o => o);
bLogger[type](consoleMessage);
}
});
mainPage.on('error', e => {
bLogger.error('"error" event fired on page', e);
});
mainPage.on('pageerror', e => {
bLogger.error('"pageerror" event fired on page:');
bLogger.error(e);
bLogger.info('dismissing the environment due to previous error/s...');
this.dismiss();
});
logger.info(`setting timeout for the whole tests execution to ${this.envConfig.tests.ttl}ms as per configuration`);
this.timeoutHandle = setTimeout(() => {
logger.error('tests execution timed out, dismissing the environment...');
this.dismiss();
}, this.envConfig.tests.ttl);
browser.once('disconnected', () => this.onDisconnected());
const envEntryUrl = `${serverConfig.origin}/core/runner/environments/browser/browser-session-runner.html` +
`?${SESSION_ENVIRONMENT_KEYS.SESSION_ID}=${this.sessionId}` +
`&${SESSION_ENVIRONMENT_KEYS.ENVIRONMENT_ID}=${this.envConfig.id}`;
logger.info(`navigating testing environment to '${envEntryUrl}'...`);
await mainPage.goto(envEntryUrl);
this.browser = browser;
}
async installCoverageCollector(coverageConfig, browsingContext, _mainPage) {
const coverageTargets = await collectTargetSources(coverageConfig);
if (!coverageTargets || !coverageTargets.length) {
console.log('no coverage targets found, skipping coverage collection');
return;
}
// install coverage session
this.coverageSession = await browsingContext.newCDPSession(_mainPage);
await Promise.all([
this.coverageSession.send('Profiler.enable'),
this.coverageSession.send('Debugger.enable'),
]);
await Promise.all([
this.coverageSession.send('Profiler.startPreciseCoverage', { callCount: true, detailed: true }),
this.coverageSession.send('Debugger.setSkipAllPauses', { skip: true })
]);
// install test registrar and scripts listener
this.scriptsCoverageMap = {};
browsingContext.exposeBinding(INTEROP_NAMES.REGISTER_TEST_FOR_COVERAGE, async ({ page }, testId) => {
const session = await browsingContext.newCDPSession(page);
await Promise.all([
session.send('Debugger.enable'),
session.send('Debugger.setSkipAllPauses', { skip: true })
]);
session.on('Debugger.scriptParsed', e => {
if (e.url.startsWith(`${serverConfig.origin}/aut/`)) {
if (!this.scriptsCoverageMap[e.scriptId]) {
this.scriptsCoverageMap[e.scriptId] = testId;
} else {
console.error(`unexpected duplication of script ${e.scriptId} [${e.url}]`);
}
}
});
});
}
async dismiss() {
if (!this.dismissPromise) {
logger.info(`dismissing environment '${this.envConfig.id}'...`);
this.dismissPromise = waitInterval(999)
.then(async () => {
await this.consoleLogger.close();
const artifacts = await this.collectArtifacts();
await BrowserEnvImpl.closeBrowser(this.browser);
logger.info(`... environment '${this.envConfig.id}' dismissed`);
return artifacts;
});
}
return this.dismissPromise;
}
onDisconnected() {
clearTimeout(this.timeoutHandle);
logger.info(`browser environment '${this.envConfig.id}' disconnected`);
}
async collectArtifacts() {
const [coverage, logs] = await Promise.all([
this.collectCoverage(),
this.collectLogs()
]);
return { coverage, logs };
}
async collectCoverage() {
if (!this.coverageSession) {
return null;
}
const result = {};
const jsCoverage = (await this.coverageSession.send('Profiler.takePreciseCoverage')).result;
for (const scriptCoverage of jsCoverage) {
const testId = this.scriptsCoverageMap[scriptCoverage.scriptId];
if (!testId) {
continue;
}
const trPath = scriptCoverage.url.replace(`${serverConfig.origin}/aut/`, './');
const trSCov = { ...scriptCoverage };
trSCov.url = trPath;
result[testId] = result[testId] || [];
throw new Error('replace the single cov process with the new methods');
//result[testId].push(processV8ScriptCoverage(trSCov));
}
return result;
}
async collectLogs() {
// TODO
return null;
}
static async closeBrowser(browser) {
logger.info('closing browser...');
await browser.close();
logger.info('... browser closed');
}
}
/**
* launches managed browsing environment and executes tests in it
* - TODO: consider to separate auto-run
*
* @param {string} sessionId
* @param {object} envConfig
* @returns environment
*/
async function launch(sessionId, envConfig) {
if (!envConfig || !envConfig.browser) {
throw new Error(`env configuration expected to have browser set to some value; got ${JSON.stringify(envConfig)}`);
}
const result = new BrowserEnvImpl(sessionId, envConfig);
await result.launch();
return result;
}