skutter
Version:
Skutter: Opinionated BDD Browser based test framework
280 lines (279 loc) • 12.6 kB
JavaScript
"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;