UNPKG

testplane

Version:

Tests framework based on mocha and wdio

201 lines 8.29 kB
"use strict"; const _ = require("lodash"); const Mocha = require("mocha"); const { MochaEventBus } = require("./mocha-event-bus"); const { TreeBuilderDecorator } = require("./tree-builder-decorator"); const { TestReaderEvents } = require("../../events"); const { MasterEvents } = require("../../events"); const { getMethodsByInterface } = require("./utils"); const logger = require("../../utils/logger"); const { enableSourceMaps } = require("../../utils/typescript"); function getTagParser(original) { return function (title, paramsOrFn, fn) { if (typeof paramsOrFn === "function") { return original.call(this, title, paramsOrFn); } else { const test = original.call(this, title, fn); if (paramsOrFn?.tag) { if (Array.isArray(paramsOrFn.tag)) { test.tags = paramsOrFn.tag.map(title => ({ title, dynamic: false })); } else { test.tags = [{ title: paramsOrFn.tag, dynamic: false }]; } } return test; } }; } async function readFiles(files, { esmDecorator, config, eventBus, runnableOpts, isBrowserEnv = false }) { const mocha = new Mocha(config); mocha.suite.on("pre-require", context => { const originalDescribe = context.describe; const originalIt = context.it; context.describe = getTagParser(originalDescribe); context.context = getTagParser(originalDescribe); context.describe.only = originalDescribe.only; context.describe.skip = originalDescribe.skip; context.it = getTagParser(originalIt); context.specify = getTagParser(originalIt); context.it.only = originalIt.only; context.it.skip = originalIt.skip; }); mocha.fullTrace(); initBuildContext(eventBus); initEventListeners({ rootSuite: mocha.suite, outBus: eventBus, config, runnableOpts }); files.forEach(f => mocha.addFile(f)); try { await mocha.loadFilesAsync({ esmDecorator }); } catch (err) { const errorMessage = (err.message || "").split("\n")[0].trim(); if (isBrowserEnv && err.code === "MODULE_NOT_FOUND" && errorMessage.includes("?")) { logger.warn(`Failed to resolve module with query parameter: ${errorMessage}. ` + `This is likely a Vite-style import (e.g., './file.svg?react'). ` + `Please install @babel/parser version 7 or higher to fix this issue.`); } throw err; } applyOnly(mocha.suite, eventBus); } function initBuildContext(outBus) { outBus.emit(TestReaderEvents.NEW_BUILD_INSTRUCTION, ctx => { ctx.treeBuilder = TreeBuilderDecorator.create(ctx.treeBuilder); }); } function initEventListeners({ rootSuite, outBus, config, runnableOpts }) { const inBus = MochaEventBus.create(rootSuite); forbidSuiteHooks(inBus); passthroughFileEvents(inBus, outBus); addLocationToRunnables(inBus, config, runnableOpts); registerTestObjects(inBus, outBus); inBus.emit(MochaEventBus.events.EVENT_SUITE_ADD_SUITE, rootSuite); } function forbidSuiteHooks(bus) { [MochaEventBus.events.EVENT_SUITE_ADD_HOOK_BEFORE_ALL, MochaEventBus.events.EVENT_SUITE_ADD_HOOK_AFTER_ALL].forEach(event => { bus.on(event, () => { throw new Error('"before" and "after" hooks are forbidden, use "beforeEach" and "afterEach" hooks instead'); }); }); } function passthroughFileEvents(inBus, outBus) { [ [MochaEventBus.events.EVENT_FILE_PRE_REQUIRE, MasterEvents.BEFORE_FILE_READ], [MochaEventBus.events.EVENT_FILE_POST_REQUIRE, MasterEvents.AFTER_FILE_READ], ].forEach(([mochaEvent, ourEvent]) => { inBus.on(mochaEvent, (ctx, file) => outBus.emit(ourEvent, { file })); }); } function registerTestObjects(inBus, outBus) { [ [MochaEventBus.events.EVENT_SUITE_ADD_SUITE, (treeBuilder, suite) => treeBuilder.addSuite(suite)], [MochaEventBus.events.EVENT_SUITE_ADD_TEST, (treeBuilder, test) => treeBuilder.addTest(test)], [ MochaEventBus.events.EVENT_SUITE_ADD_HOOK_BEFORE_EACH, (treeBuilder, hook) => treeBuilder.addBeforeEachHook(hook), ], [ MochaEventBus.events.EVENT_SUITE_ADD_HOOK_AFTER_EACH, (treeBuilder, hook) => treeBuilder.addAfterEachHook(hook), ], ].forEach(([event, instruction]) => { inBus.on(event, testObject => { outBus.emit(TestReaderEvents.NEW_BUILD_INSTRUCTION, ({ treeBuilder }) => instruction(treeBuilder, testObject)); }); }); } function applyOnly(rootSuite, eventBus) { if (!rootSuite.hasOnly()) { return; } const titlesToRun = []; // filterOnly modifies mocha tree removing links between test objects from top to bottom // we are using links from bottom to top (i.e. parent property) // so it is safe to use build instructions after modifying mocha tree rootSuite.filterOnly(); rootSuite.eachTest(mochaTest => titlesToRun.push(mochaTest.fullTitle())); eventBus.emit(TestReaderEvents.NEW_BUILD_INSTRUCTION, ({ treeBuilder }) => { treeBuilder.addTestFilter(test => titlesToRun.includes(test.fullTitle())); }); } function addLocationToRunnables(inBus, config, runnableOpts) { if (!runnableOpts || !runnableOpts.saveLocations) { return; } enableSourceMaps(); const sourceMapSupport = tryToRequireSourceMapSupport(); const { suiteMethods, testMethods } = getMethodsByInterface(config.ui); inBus.on(MochaEventBus.events.EVENT_FILE_PRE_REQUIRE, ctx => { [ { methods: suiteMethods, eventName: MochaEventBus.events.EVENT_SUITE_ADD_SUITE, }, { methods: testMethods, eventName: MochaEventBus.events.EVENT_SUITE_ADD_TEST, }, ].forEach(({ methods, eventName }) => { methods.forEach(methodName => { ctx[methodName] = withLocation(ctx[methodName], { inBus, eventName, sourceMapSupport }); if (ctx[methodName]) { ctx[methodName].only = withLocation(ctx[methodName].only, { inBus, eventName, sourceMapSupport }); ctx[methodName].skip = withLocation(ctx[methodName].skip, { inBus, eventName, sourceMapSupport }); } if (!config.ui || config.ui === "bdd") { const pendingMethodName = `x${methodName}`; ctx[pendingMethodName] = withLocation(ctx[pendingMethodName], { inBus, eventName, sourceMapSupport, }); } }); }); }); } function withLocation(origFn, { inBus, eventName, sourceMapSupport }) { if (!_.isFunction(origFn)) { return origFn; } const wrappedFn = (...args) => { const origStackTraceLimit = Error.stackTraceLimit; const origPrepareStackTrace = Error.prepareStackTrace; Error.stackTraceLimit = 2; Error.prepareStackTrace = (error, stackFrames) => { const frame = sourceMapSupport ? sourceMapSupport.wrapCallSite(stackFrames[1]) : stackFrames[1]; return { line: frame.getLineNumber(), column: frame.getColumnNumber(), }; }; const obj = {}; Error.captureStackTrace(obj); const location = obj.stack; Error.stackTraceLimit = origStackTraceLimit; Error.prepareStackTrace = origPrepareStackTrace; inBus.once(eventName, runnable => { if (!runnable.location) { runnable.location = location; } }); return origFn(...args); }; for (const key of Object.keys(origFn)) { wrappedFn[key] = origFn[key]; } return wrappedFn; } function tryToRequireSourceMapSupport() { try { const module = require("@cspotcode/source-map-support"); module.install({ hookRequire: true }); return module; } catch { } // eslint-disable-line no-empty } module.exports = { readFiles, }; //# sourceMappingURL=index.js.map