Source code
Revision control
Copy as Markdown
Other Tools
/**
* @license
* Copyright 2022 Google Inc.
* SPDX-License-Identifier: Apache-2.0
*/
import Mocha from 'mocha';
import commonInterface from 'mocha/lib/interfaces/common';
import {
setLogCapture,
getCapturedLogs,
} from 'puppeteer-core/internal/common/Debug.js';
import {testIdMatchesExpectationPattern} from './utils.js';
type SuiteFunction = ((this: Mocha.Suite) => void) | undefined;
type ExclusiveSuiteFunction = (this: Mocha.Suite) => void;
const skippedTests: Array<{testIdPattern: string; skip: true}> = process.env[
'PUPPETEER_SKIPPED_TEST_CONFIG'
]
? JSON.parse(process.env['PUPPETEER_SKIPPED_TEST_CONFIG'])
: [];
const deflakeRetries = Number(
process.env['PUPPETEER_DEFLAKE_RETRIES']
? process.env['PUPPETEER_DEFLAKE_RETRIES']
: 100
);
const deflakeTestPattern: string | undefined =
process.env['PUPPETEER_DEFLAKE_TESTS'];
function shouldSkipTest(test: Mocha.Test): boolean {
// TODO: more efficient lookup.
const definition = skippedTests.find(skippedTest => {
return testIdMatchesExpectationPattern(test, skippedTest.testIdPattern);
});
if (definition && definition.skip) {
return true;
}
return false;
}
function shouldDeflakeTest(test: Mocha.Test): boolean {
if (deflakeTestPattern) {
// TODO: cache if we have seen it already
return testIdMatchesExpectationPattern(test, deflakeTestPattern);
}
return false;
}
function dumpLogsIfFail(this: Mocha.Context) {
if (this.currentTest?.state === 'failed') {
console.log(
`\n"${this.currentTest.fullTitle()}" failed. Here is a debug log:`
);
console.log(getCapturedLogs().join('\n') + '\n');
}
setLogCapture(false);
}
function customBDDInterface(suite: Mocha.Suite) {
const suites: [Mocha.Suite] = [suite];
suite.on(
Mocha.Suite.constants.EVENT_FILE_PRE_REQUIRE,
function (context, file, mocha) {
const common = commonInterface(suites, context, mocha);
context['before'] = common.before;
context['after'] = common.after;
context['beforeEach'] = common.beforeEach;
context['afterEach'] = common.afterEach;
if (mocha.options.delay) {
context['run'] = common.runWithSuite(suite);
}
function describe(title: string, fn: SuiteFunction) {
return common.suite.create({
title: title,
file: file,
fn: fn,
});
}
describe.only = function (title: string, fn: ExclusiveSuiteFunction) {
return common.suite.only({
title: title,
file: file,
fn: fn,
isOnly: true,
});
};
describe.skip = function (title: string, fn: SuiteFunction) {
return common.suite.skip({
title: title,
file: file,
fn: fn,
});
};
describe.withDebugLogs = function (
description: string,
body: (this: Mocha.Suite) => void
): void {
context['describe']('with Debug Logs', () => {
context['beforeEach'](() => {
setLogCapture(true);
});
context['afterEach'](dumpLogsIfFail);
context['describe'](description, body);
});
};
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
context['describe'] = describe;
function it(title: string, fn: Mocha.TestFunction, itOnly = false) {
const suite = suites[0]! as Mocha.Suite;
const test = new Mocha.Test(title, suite.isPending() ? undefined : fn);
test.file = file;
test.parent = suite;
const describeOnly = Boolean(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
suite.parent?._onlySuites.find(child => {
return child === suite;
})
);
if (shouldDeflakeTest(test)) {
const deflakeSuit = Mocha.Suite.create(suite, 'with Debug Logs');
test.file = file;
deflakeSuit.beforeEach(function () {
setLogCapture(true);
});
deflakeSuit.afterEach(dumpLogsIfFail);
for (let i = 0; i < deflakeRetries; i++) {
deflakeSuit.addTest(test.clone());
}
return test;
} else if (!(itOnly || describeOnly) && shouldSkipTest(test)) {
const test = new Mocha.Test(title);
test.file = file;
suite.addTest(test);
return test;
} else {
suite.addTest(test);
return test;
}
}
it.only = function (title: string, fn: Mocha.TestFunction) {
return common.test.only(
mocha,
(context['it'] as unknown as typeof it)(title, fn, true)
);
};
it.skip = function (title: string) {
return context['it'](title);
};
function wrapDeflake(
func: Function
): (repeats: number, title: string, fn: Mocha.AsyncFunc) => void {
return (repeats: number, title: string, fn: Mocha.AsyncFunc): void => {
(context['describe'] as unknown as typeof describe).withDebugLogs(
'with Debug Logs',
() => {
for (let i = 1; i <= repeats; i++) {
func(`${i}/${title}`, fn);
}
}
);
};
}
it.deflake = wrapDeflake(it);
it.deflakeOnly = wrapDeflake(it.only);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
context.it = it;
}
);
}
customBDDInterface.description = 'Custom BDD';
module.exports = customBDDInterface;