UNPKG

playwright-test

Version:

Run mocha, zora, uvu, tape and benchmark.js scripts inside real browsers with playwright.

243 lines 7.42 kB
/* eslint-disable no-only-tests/no-only-tests */ /* eslint-disable no-unsafe-finally */ /* eslint-disable no-console */ import kleur from 'kleur'; import pTimeout from 'p-timeout'; import { hrtime, stack } from './utils.js'; /** * @type {import("./types.js").Queue} */ export const TAPS_QUEUE = []; globalThis.TAPS_ONLY = false; /** * * @param {import("./types.js").TestContext} ctx * @param {Error} err */ function formatError(ctx, err) { let out = '\n' + kleur.bgRed().bold(' FAILURE ') + ' ' + kleur.red(`"${ctx.suite ? ctx.suite + ' > ' : ''}${ctx.name}"`) + '\n'; out += `${err.name}: ${err.message}` + kleur.gray(stack(err)); if (err.cause instanceof Error && err.cause.stack) { out += kleur.gray(`\n Caused by ${err.cause.name}: ${err.cause.message} ${stack(err.cause) .split('\n') .join('\n ')}`); } // @ts-ignore if (err.details) { // @ts-ignore out += err.details; } return out; } /** * * @param {string} msg * @param {Error} err */ function formatErrorSuite(msg, err) { const out = `${kleur.bgRed().bold(' FAILURE ')} ${kleur.red(msg)} ${`${err.name}: ${err.message} `} ${kleur.gray(stack(err))} `; return out; } /** * Log the test result. * * @param {import("./types.js").TestContext} ctx * @param {boolean} fail * @param {string} time */ function log(ctx, fail, time) { const symbol = fail ? kleur.red('✘') : ctx.skip ? kleur.yellow('-') : kleur.green('✔'); const _time = kleur.gray(`(${time})`); const _msg = `${ctx.suite ? ctx.suite + ' > ' : ''}${ctx.name}`; const msg = `${symbol} ${kleur.gray(ctx.number)} ${fail ? kleur.red(_msg) : ctx.skip ? kleur.yellow(_msg) : _msg} ${_time}`; console.log(msg); } /** * @type {import("./types.js").Runner} */ async function runner(ctx, testCount) { const { only, tests, name, before, after, beforeEach, afterEach } = ctx; const testsToRun = only.length > 0 || globalThis.TAPS_ONLY ? only : tests; let num = testCount; let passed = 0; let skips = 0; /** @type {string[]} */ const errors = []; const total = testsToRun.length; let skipSuite = false; if (testsToRun.length === 0) { return [errors, passed, skips, total]; } try { // Before Hooks for (const hook of before) { try { await hook(); } catch (error) { const err = /** @type {Error} */ (error); errors.push(formatErrorSuite(`${name}: before hook`, err)); skipSuite = true; } } for (const test of testsToRun) { num++; /** @type {import("./types.js").TestContext} */ const testCtx = { name: test.name, suite: name, skip: test.options.skip || skipSuite, number: num, }; const timer = hrtime(); try { if (testCtx.skip) { skips++; } else { // Before Each Hooks for (const hook of beforeEach) { try { await hook(); } catch (error) { throw new Error('beforeEach hook failed', { cause: error }); } } // @ts-ignore await pTimeout(test.fn(), { milliseconds: test.options.timeout, }); passed++; // After Each Hooks for (const hook of afterEach) { try { await hook(); } catch (error) { throw new Error('afterEach hook failed', { cause: error }); } } } log(testCtx, false, timer()); } catch (error) { const err = /** @type {Error} */ (error); log(testCtx, true, timer()); errors.push(formatError(testCtx, err)); // After Each Hooks (on error) for (const hook of afterEach) { try { await hook(); } catch (error) { errors.push(formatError(testCtx, new Error('afterEach hook failed', { cause: error }))); } } } } } finally { // After Hooks for (const hook of after) { try { await hook(); } catch (error) { const err = /** @type {Error} */ (error); errors.push(formatErrorSuite(`${name}: after hook`, err)); } } return [errors, passed, skips, total]; } } /** * * @param {string} name * @returns {import('./types.js').Suite} */ export function suite(name = '') { /** @type {import("./types.js").SuiteContext} */ const ctx = { tests: [], before: [], after: [], beforeEach: [], afterEach: [], only: [], skips: 0, name, }; const defaultOptions = { skip: false, only: false, timeout: 5000, }; /** * @type {import('./types.js').TestMethod} */ function test(name, fn, options = defaultOptions) { ctx.tests.push({ name, fn, options: { ...defaultOptions, ...options } }); } test.test = test; test.before = function (/** @type {import("./types.js").Hook} */ fn) { ctx.before.push(fn); }; test.after = function (/** @type {import("./types.js").Hook} */ fn) { ctx.after.push(fn); }; test.beforeEach = function (/** @type {import("./types.js").Hook} */ fn) { ctx.beforeEach.push(fn); }; test.afterEach = function (/** @type {import("./types.js").Hook} */ fn) { ctx.afterEach.push(fn); }; /** * @type {import('./types.js').Suite} */ test.skip = function (name, fn, options = defaultOptions) { ctx.tests.push({ name, fn, options: { ...defaultOptions, ...options, skip: true }, }); }; /** * @type {import('./types.js').Suite} */ test.only = function (name, fn, options = defaultOptions) { globalThis.TAPS_ONLY = true; ctx.only.push({ name, fn, options: { ...defaultOptions, ...options, only: true }, }); }; test.only.test = test.only; test.only.skip = test.skip; test.only.only = test.only; test.only.after = test.after; test.only.before = test.before; test.only.beforeEach = test.beforeEach; test.only.afterEach = test.afterEach; test.skip.test = test.skip; test.skip.skip = test.skip; test.skip.only = test.only; test.skip.after = () => { }; test.skip.before = () => { }; test.skip.beforeEach = () => { }; test.skip.afterEach = () => { }; TAPS_QUEUE.push(runner.bind(0, ctx)); return test; } //# sourceMappingURL=harness.js.map