UNPKG

@kronoslive/codeceptjs

Version:

Supercharged End 2 End Testing Framework for NodeJS

231 lines (194 loc) 6.41 kB
const escapeRe = require('escape-string-regexp'); const Suite = require('mocha/lib/suite'); const Test = require('mocha/lib/test'); const scenario = require('./scenario'); const ScenarioConfig = require('./interfaces/scenarioConfig'); const FeatureConfig = require('./interfaces/featureConfig'); const addDataContext = require('./data/context'); const container = require('./container'); const setContextTranslation = (context) => { const contexts = container.translation().value('contexts'); if (contexts) { for (const key of Object.keys(contexts)) { if (context[key]) { context[contexts[key]] = context[key]; } } } }; /** * Codecept-style interface: * * Feature('login') * Scenario('login as regular user', (I) { * I.fillField(); * I.click() * I.see('Hello, '+data.login); * }); * * @param {Mocha.Suite} suite Root suite. * @ignore */ module.exports = function (suite) { const suites = [suite]; suite.timeout(0); let afterAllHooks; let afterEachHooks; let afterAllHooksAreLoaded; let afterEachHooksAreLoaded; suite.on('pre-require', (context, file, mocha) => { const common = require('mocha/lib/interfaces/common')(suites, context, mocha); const addScenario = function (title, opts = {}, fn) { const suite = suites[0]; if (typeof opts === 'function' && !fn) { fn = opts; opts = {}; } if (suite.pending) { fn = null; } const test = new Test(title, fn); test.fullTitle = () => `${suite.title}: ${test.title}`; test.tags = (suite.tags || []).concat(title.match(/(\@[a-zA-Z0-9-_]+)/g) || []); // match tags from title test.file = file; if (!test.inject) { test.inject = {}; } suite.addTest(scenario.test(test)); if (opts.retries) test.retries(opts.retries); if (opts.timeout) test.timeout(opts.timeout); test.opts = opts; return new ScenarioConfig(test); }; // create dispatcher context.BeforeAll = common.before; context.AfterAll = common.after; context.run = mocha.options.delay && common.runWithSuite(suite); /** * Describe a "suite" with the given `title` * and callback `fn` containing nested suites * and/or tests. * @global * @param {string} title * @param {Object<string, *>} [opts] * @returns {FeatureConfig} */ context.Feature = function (title, opts) { if (suites.length > 1) { suites.shift(); } afterAllHooks = []; afterEachHooks = []; afterAllHooksAreLoaded = false; afterEachHooksAreLoaded = false; const suite = Suite.create(suites[0], title); if (!opts) opts = {}; suite.opts = opts; suite.timeout(0); if (opts.retries) suite.retries(opts.retries); if (opts.timeout) suite.timeout(opts.timeout); suite.tags = title.match(/(\@[a-zA-Z0-9-_]+)/g) || []; // match tags from title suite.file = file; suite.fullTitle = () => `${suite.title}:`; suites.unshift(suite); suite.beforeEach('codeceptjs.before', () => scenario.setup(suite)); afterEachHooks.push(['finalize codeceptjs', () => scenario.teardown(suite)]); suite.beforeAll('codeceptjs.beforeSuite', () => scenario.suiteSetup(suite)); afterAllHooks.push(['codeceptjs.afterSuite', () => scenario.suiteTeardown(suite)]); if (opts.skipInfo && opts.skipInfo.skipped) { suite.pending = true; suite.opts = { ...suite.opts, skipInfo: opts.skipInfo }; } return new FeatureConfig(suite); }; /** * Pending test suite. * @global * @kind constant * @type {CodeceptJS.IFeature} */ context.xFeature = context.Feature.skip = function (title, opts) { const skipInfo = { skipped: true, message: 'Skipped due to "skip" on Feature.', }; return context.Feature(title, { ...opts, skipInfo }); }; context.BeforeSuite = function (fn) { suites[0].beforeAll('BeforeSuite', scenario.injected(fn, suites[0], 'beforeSuite')); }; context.AfterSuite = function (fn) { afterAllHooks.unshift(['AfterSuite', scenario.injected(fn, suites[0], 'afterSuite')]); }; context.Background = context.Before = function (fn) { suites[0].beforeEach('Before', scenario.injected(fn, suites[0], 'before')); }; context.After = function (fn) { afterEachHooks.unshift(['After', scenario.injected(fn, suites[0], 'after')]); }; /** * Describe a specification or test-case * with the given `title` and callback `fn` * acting as a thunk. * @ignore */ context.Scenario = addScenario; /** * Exclusive test-case. * @ignore */ context.Scenario.only = function (title, opts, fn) { const reString = `^${escapeRe(`${suites[0].title}: ${title}`.replace(/( \| {.+})?$/g, ''))}`; mocha.grep(new RegExp(reString)); return addScenario(title, opts, fn); }; /** * Pending test case. * @global * @kind constant * @type {CodeceptJS.IScenario} */ context.xScenario = context.Scenario.skip = function (title) { return context.Scenario(title, {}); }; /** * Pending test case with message: 'Test not implemented!'. * @global * @kind constant * @type {CodeceptJS.IScenario} */ context.Scenario.todo = function (title, opts = {}, fn) { if (typeof opts === 'function' && !fn) { fn = opts; opts = {}; } const skipInfo = { message: 'Test not implemented!', description: fn ? fn.toString() : '', }; return context.Scenario(title, { ...opts, skipInfo }); }; /** * For translation */ setContextTranslation(context); addDataContext(context); }); suite.on('post-require', () => { /** * load hooks from arrays to suite to prevent reordering */ if (!afterEachHooksAreLoaded && Array.isArray(afterEachHooks)) { afterEachHooks.forEach((hook) => { suites[0].afterEach(hook[0], hook[1]); }); afterEachHooksAreLoaded = true; } if (!afterAllHooksAreLoaded && Array.isArray(afterAllHooks)) { afterAllHooks.forEach((hook) => { suites[0].afterAll(hook[0], hook[1]); }); afterAllHooksAreLoaded = true; } }); };