UNPKG

skutter

Version:

Skutter: Opinionated BDD Browser based test framework

280 lines (279 loc) 12.6 kB
"use strict"; const Yadda = require("yadda"); const Lazy = require("lazy.js"); const test_context_1 = require("../test-context"); const browser_provider_1 = require("./browser-provider"); const process_error_sender_1 = require("../services/process-error-sender"); const execution_logger_1 = require("./execution-logger"); const execution_marks_1 = require("./execution-marks"); function CannotBeUndefined(propertyName) { throw Error(`Executor: Property ${propertyName} was not defined.`); } class Executor { constructor(requiredLibrariesProvider, broadcaster, screenshotter, jasmineResultListener, outputPath) { this.yadda = null; if (!requiredLibrariesProvider) { throw new Error(`[Executor] requiredLibrariesProvider was not specified.`); } if (!broadcaster) { throw new Error(`[Executor] broadcaster was not specified.`); } if (!screenshotter) { throw new Error(`[Executor] screenshotter was not specified.`); } if (!jasmineResultListener) { throw new Error(`[Executor] jasmineResultListener was not specified.`); } this.screenshotter = screenshotter; this.broadcaster = broadcaster; this.requiredLibrariesProvider = requiredLibrariesProvider; this.jasmineResultListener = jasmineResultListener; this.outputPath = outputPath; this.logCapture = new execution_logger_1.ExecutionLogger(console.log); } execute(feature = CannotBeUndefined("feature"), browser) { return new Promise((resolve, reject) => { this.logCapture.mark(execution_marks_1.ExecutionMarks.FeatureSetup); if (!this.yadda) { try { this.yadda = Yadda.createInstance(this.requiredLibrariesProvider.get()); } catch (yaddaInitalisationException) { process_error_sender_1.ProcessErrorSender.send("executor", "Creating Yadda instance", yaddaInitalisationException) .then(() => { reject(yaddaInitalisationException); }); } } if (feature.dequeued) { return this.broadcaster.featureSkipped(feature.id) .then(() => { resolve(); }); } this.browserProvider = new browser_provider_1.BrowserProvider(browser); let lastExecutableScenario = Lazy(feature.scenarios).where((scenario) => { return !scenario.dequeued; }).last(); global.scenarios(feature.scenarios, (scenario) => { let isLastExecutableScenarioInFeature = lastExecutableScenario.id === scenario.id; this.executeScenario(feature, scenario, isLastExecutableScenarioInFeature) .then(() => { if (isLastExecutableScenarioInFeature) { resolve(); } }); }); }); } executeScenario(feature, scenario, isLastExecutableScenario) { return new Promise((resolve, reject) => { if (scenario.dequeued) { this.broadcaster.featureStarted(feature.id) .then(() => { return this.broadcaster.scenarioSkipped(feature.id, scenario.id); }) .then(() => { if (isLastExecutableScenario) { return this.teardownFeature(feature.id); } }) .then(() => { resolve(); }); return; } this.logCapture.mark(execution_marks_1.ExecutionMarks.ScenarioSetup); let scenarioContext = new test_context_1.TestContext(); try { let textSteps = Lazy(scenario.steps).pluck("text").toArray(); let stepsExecuted = 0; let scenarioStepCount = scenario.steps.length - 1; global.steps(textSteps, (stepText, done) => { let doneWithCallback = function (error) { if (error) { scenario.failed = true; } done(error); }; let step = scenario.steps[stepsExecuted]; let isLastStep = stepsExecuted === scenarioStepCount; if (scenario.failed) { this.broadcaster.stepSkipped(feature.id, scenario.id, step.id, "Previous step failed.") .then(() => { done(); stepsExecuted++; if (stepsExecuted === scenarioStepCount) { resolve(); } }); return; } this.preExecuteStep(feature, scenario, step, scenarioContext) .then(() => { this.executeStep(feature.id, scenario.id, step, scenarioContext, doneWithCallback, isLastStep, isLastExecutableScenario, resolve); }); stepsExecuted++; }); } catch (exception) { process_error_sender_1.ProcessErrorSender.send("executor", "Iterating Steps", exception) .then(() => { reject(exception); }); } }); } preExecuteStep(feature, scenario, step, scenarioContext) { return new Promise((resolve, reject) => { scenarioContext.meta.featureTitle = feature.title; scenarioContext.meta.scenarioTitle = scenario.title; scenarioContext.meta.featureId = feature.id; scenarioContext.meta.scenarioId = scenario.id; scenarioContext.meta.stepId = step.id; this.browserProvider.get(feature, scenario) .then((browserInstance) => { scenarioContext.browser = browserInstance.browser; scenarioContext.browserSessionId = browserInstance.browserId; resolve(); }); }); } executeStep(featureId, scenarioId, step, scenarioContext, done, isLastStep, isLastExecutableScenario, scenarioFinished) { let jasmineStepCompletePromise = this.jasmineResultListener.stepStarting(featureId, scenarioId, step.id); let skipJasmineProcessing = false; this.broadcaster.featureStarted(featureId) .then(() => { return this.broadcaster.scenarioStarted(featureId, scenarioId); }) .then(() => { return this.broadcaster.stepStarted(featureId, scenarioId, step.id); }) .then(() => { this.logCapture.mark(execution_marks_1.ExecutionMarks.StepStart, step.id); console.log = this.logCapture.log; let stepError = null; jasmineStepCompletePromise .then((jasmineResult) => { if (skipJasmineProcessing) { return; } stepError = stepError || jasmineResult.error; this.stepCompleted(featureId, scenarioId, step.id, stepError, scenarioContext.browser, done, isLastStep, isLastExecutableScenario, scenarioFinished); }); let stepContext = { context: scenarioContext, skutter_internal: { libraryProvider: this.requiredLibrariesProvider, broadcaster: this.broadcaster, logCapture: this.logCapture, outputPath: this.outputPath } }; this.yadda.run(step.text, stepContext, (error) => { if (error) { stepError = error; fail(error); } done(error); }); }) .catch((error) => { skipJasmineProcessing = true; this.stepCompleted(featureId, scenarioId, step.id, error, scenarioContext.browser, done, isLastStep, isLastExecutableScenario, scenarioFinished); }); } stepCompleted(featureId, scenarioId, stepId, error, browser, done, isLastStep, isLastScenario, scenarioFinished) { this.logCapture.mark(execution_marks_1.ExecutionMarks.StepEnd); if (error) { if (Executor.shouldTakeScreenshot(error)) { let screenshotPromise = Promise.resolve(browser.takeScreenshot()); screenshotPromise .then((screenshotPngData) => { let screenshotFilename = this.screenshotter.saveScreenshot(screenshotPngData, featureId); return this.broadcaster.stepScreenshotted(featureId, scenarioId, stepId, screenshotFilename); }, (screenshottingError) => { return process_error_sender_1.ProcessErrorSender.send("executor", "stepCompleted -> Processing Screenshot", screenshottingError); }) .then(() => { if (Executor.isFailure(error)) { return this.broadcaster.stepFail(featureId, scenarioId, stepId, this.logCapture.getStepLogs(stepId), error); } else { return this.broadcaster.stepError(featureId, scenarioId, stepId, this.logCapture.getStepLogs(stepId), error); } }) .then(() => { this.logCapture.mark(execution_marks_1.ExecutionMarks.ScenarioTeardown); return this.teardownScenario(featureId, scenarioId, isLastScenario, () => { }); }) .then(() => { done(error); }); } else { let resultBroadcast = null; if (Executor.isFailure(error)) { resultBroadcast = this.broadcaster.stepFail(featureId, scenarioId, stepId, this.logCapture.getStepLogs(stepId), error); } else { resultBroadcast = this.broadcaster.stepError(featureId, scenarioId, stepId, this.logCapture.getStepLogs(stepId), error); } resultBroadcast .then(() => { return this.teardownScenario(featureId, scenarioId, isLastScenario, () => { }); }) .then(() => { done(error); }); } } else { this.broadcaster.stepPass(featureId, scenarioId, stepId, this.logCapture.getStepLogs(stepId)) .then(() => { if (isLastStep) { return this.teardownScenario(featureId, scenarioId, isLastScenario, scenarioFinished); } }) .then(() => { done(); }); } } teardownScenario(featureId, scenarioId, isLastScenario, scenarioFinished) { this.logCapture.mark(execution_marks_1.ExecutionMarks.ScenarioTeardown); this.browserProvider.cleanScenario(scenarioId); return this.broadcaster.scenarioFinished(featureId, scenarioId, this.logCapture.getScenarioTeardownLogs()) .then(() => { if (isLastScenario) { return this.teardownFeature(featureId) .then(() => { scenarioFinished(); }); } }); } teardownFeature(featureId) { this.logCapture.mark(execution_marks_1.ExecutionMarks.FeatureTeardown); this.browserProvider.cleanFeature(); return this.broadcaster.featureFinished(featureId, this.logCapture.getFeatureTeardownLogs()); } static isFailure(error) { return typeof (error) === "string"; } static shouldTakeScreenshot(error) { if (!error) { return false; } if (error instanceof Error) { if (error.message.endsWith("<-- Undefined Step")) { return false; } } else { if (error.endsWith("<-- Undefined Step")) { return false; } } return true; } } exports.Executor = Executor;