@uuv/playwright
Version:
A solution to facilitate the writing and execution of E2E tests understandable by any human being using cucumber(BDD) and playwright
586 lines (585 loc) • 25.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GeneratedReportType = void 0;
const gherkin_1 = require("@cucumber/gherkin");
const gherkin_utils_1 = require("@cucumber/gherkin-utils");
const messages_1 = require("@cucumber/messages");
const fs_1 = __importDefault(require("fs"));
const multiple_cucumber_html_reporter_1 = __importDefault(require("multiple-cucumber-html-reporter"));
const nanoid_1 = require("nanoid");
const chalk_1 = __importDefault(require("chalk"));
const chalk_table_1 = __importDefault(require("chalk-table"));
const uuv_custom_formatter_1 = require("./uuv-custom-formatter");
const tag_expressions_1 = __importDefault(require("@cucumber/tag-expressions"));
const path_1 = __importDefault(require("path"));
const event_1 = require("@uuv/runner-commons/runner/event");
const utils_1 = require("@uuv/runner-commons/runner/utils");
const NANOS_IN_SECOND = 1000000000;
const NANOS_IN_MILLISSECOND = 1000000;
var GeneratedReportType;
(function (GeneratedReportType) {
GeneratedReportType["CONSOLE"] = "console";
GeneratedReportType["HTML"] = "html";
})(GeneratedReportType || (exports.GeneratedReportType = GeneratedReportType = {}));
class ReportOfFeature {
passed = 0;
skipped = 0;
failed = 0;
retries = 0;
increment(status, retry) {
if (retry > 0) {
this.retries++;
}
if (status === "passed") {
this.passed++;
}
else if (status === "skipped") {
this.skipped++;
}
else if (status === "failed") {
this.failed++;
}
}
}
class UuvPlaywrightReporterHelper {
testDir;
queries = new Map();
envelopes = [];
featureFileAndTestCaseStatusMap = new Map();
testCasesAndTestCasesStartedIdMap = new Map();
testCasesAndPickleIdMap = new Map();
testCasesTestStepStartedIdMap = new Map();
testStepLocationAndTestStepIdMap = new Map();
consoleReportMap = new Map();
errors = [];
currentFeatureFile;
foundConf;
constructor() {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const bddgenConfAsString = process.env.PLAYWRIGHT_BDD_CONFIGS;
if (bddgenConfAsString) {
const bddgenConf = JSON.parse(bddgenConfAsString);
if (Object.keys.length > 0) {
const foundPath = Object.keys(bddgenConf)[0];
this.foundConf = bddgenConf[foundPath];
}
}
}
createTestRunStartedEnvelope(config, suite, startTimestamp) {
this.testDir = config.projects[0].testDir;
const featureFiles = this.getFeatureFiles(suite);
this.initializeCucumberReportNdJson(suite, featureFiles);
this.envelopes.push(this.createEnvelope({
testRunStarted: {
timestamp: startTimestamp
}
}));
}
createTestCaseStartedEnvelope(test, result, featureFile, startTimestamp) {
const currentQuery = this.queries.get(featureFile);
if (currentQuery) {
const newTestCaseEnvelope = this.createEnvelope({
testCase: {
id: test.id,
pickleId: this.testCasesAndPickleIdMap.get(test.id),
testSteps: this.generateTestStep(currentQuery, test)
}
});
let newCurrentQuery = currentQuery.update(newTestCaseEnvelope);
this.envelopes.push(newTestCaseEnvelope);
const testCaseStartedId = (0, nanoid_1.nanoid)();
const newTestCaseStartedEnvelope = this.createEnvelope({
testCaseStarted: {
id: testCaseStartedId,
testCaseId: test.id,
attempt: result.retry,
timestamp: startTimestamp
}
});
newCurrentQuery = newCurrentQuery.update(newTestCaseStartedEnvelope);
this.envelopes.push(newTestCaseStartedEnvelope);
this.queries.set(featureFile, newCurrentQuery);
this.testCasesAndTestCasesStartedIdMap.set(test.id, testCaseStartedId);
this.initConsoleReportIfNotExists(featureFile);
}
event_1.UUVEventEmitter.getInstance().emitTestStarted({
testName: test.title,
testSuiteName: this.getTestSuiteName(featureFile),
testSuitelocation: featureFile
});
}
createTestStepStartedEnvelope(test, step, featureFile, startTimestamp) {
const currentQuery = this.queries.get(featureFile);
if (currentQuery && step.location) {
let newIndexStep = this.testCasesTestStepStartedIdMap.get(test.id);
newIndexStep = newIndexStep ? newIndexStep + 1 : 1;
const pickleId = this.testCasesAndPickleIdMap.get(test.id);
const pickle = currentQuery.getPickles()
.find(pickle => pickle.id === pickleId);
const testStep = pickle?.steps[newIndexStep - 1];
if (testStep?.id && step.title.endsWith(testStep.text)) {
this.testCasesTestStepStartedIdMap.set(test.id, newIndexStep);
this.testStepLocationAndTestStepIdMap.set(this.getTestStepKey(step.location), testStep.id);
const newTestStepEnvelope = this.createEnvelope({
testStepStarted: {
testStepId: testStep.id,
testCaseStartedId: this.testCasesAndTestCasesStartedIdMap.get(test.id),
timestamp: startTimestamp
}
});
this.envelopes.push(newTestStepEnvelope);
}
}
}
createTestStepSkippedEnvelope(test, featureFile, timestamp) {
const currentQuery = this.queries.get(featureFile);
if (currentQuery) {
const newIndexStepTemp = this.testCasesTestStepStartedIdMap.get(test.id);
const newIndexStep = newIndexStepTemp !== undefined ? newIndexStepTemp + 1 : 1;
if (newIndexStep !== undefined) {
const pickleId = this.testCasesAndPickleIdMap.get(test.id);
currentQuery.getPickles()
.find(pickle => pickle.id === pickleId)
?.steps
.filter((value, index) => index >= (newIndexStep - 1))
.forEach((pickleStep) => {
const newTestStepStartedEnvelope = this.createEnvelope({
testStepStarted: {
testStepId: pickleStep.id,
testCaseStartedId: this.testCasesAndTestCasesStartedIdMap.get(test.id),
timestamp: timestamp
}
});
this.envelopes.push(newTestStepStartedEnvelope);
const newTestStepSkippedEnvelope = this.createEnvelope({
testStepFinished: {
testStepId: this.testCasesAndTestCasesStartedIdMap.get(test.id),
testCaseStartedId: pickleStep.id,
testStepResult: {
status: "SKIPPED",
duration: {
seconds: 0,
nanos: 0
}
},
timestamp: timestamp
}
});
this.envelopes.push(newTestStepSkippedEnvelope);
});
}
}
}
createTestStepFinishedEnvelope(test, step, result, featureFile, startTimestamp) {
const currentQuery = this.queries.get(featureFile);
if (currentQuery && step.location) {
const testStepId = this.testStepLocationAndTestStepIdMap.get(this.getTestStepKey(step.location));
if (testStepId) {
const newTestStepEnvelope = this.createEnvelope({
testStepFinished: {
testStepId: testStepId,
testCaseStartedId: this.testCasesAndTestCasesStartedIdMap.get(test.id),
testStepResult: {
status: this.getStatus(step),
duration: {
seconds: result.duration / 1000,
nanos: result.duration * NANOS_IN_MILLISSECOND
},
// eslint-disable-next-line no-control-regex
message: step.error?.message?.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "")
},
timestamp: startTimestamp
}
});
this.envelopes.push(newTestStepEnvelope);
}
}
}
getTestStepKey(location) {
return `${location.file.replaceAll("\\", "_")}-${location.line}-${location.column}`;
}
createTestCaseFinishedEnvelope(test, result, featureFile, endTimestamp) {
this.handleTestEnd(result, test, featureFile);
this.updateTestcaseStatus(featureFile, test.id, "done");
const currentQuery = this.queries.get(featureFile);
if (currentQuery) {
if (result.status === "skipped" || result.status === "failed") {
this.createTestStepSkippedEnvelope(test, featureFile, endTimestamp);
}
const testCaseStartedId = this.testCasesAndTestCasesStartedIdMap.get(test.id);
const newTestCaseFinishedEnvelope = this.createEnvelope({
testCaseFinished: {
testCaseStartedId: testCaseStartedId,
attempt: result.retry,
timestamp: endTimestamp
}
});
currentQuery.update(newTestCaseFinishedEnvelope);
this.envelopes.push(newTestCaseFinishedEnvelope);
this.updateConsoleReport(featureFile, result);
this.addResultErrors(result, test, featureFile);
this.createTestCaseErrorAttachmentsEnvelope(testCaseStartedId, result);
}
this.handleTestSuiteFinishedIfNeeded(featureFile);
}
handleTestEnd(result, test, featureFile) {
switch (result.status) {
case "passed":
event_1.UUVEventEmitter.getInstance().emitTestFinished({
testName: test.title,
testSuiteName: this.getTestSuiteName(featureFile),
duration: result.duration
});
break;
case "failed":
event_1.UUVEventEmitter.getInstance().emitTestFailed({
testName: test.title,
testSuiteName: this.getTestSuiteName(featureFile),
duration: result.duration
});
break;
default:
event_1.UUVEventEmitter.getInstance().emitTestIgnored({
testName: test.title,
testSuiteName: this.getTestSuiteName(featureFile)
});
}
}
handleTestSuiteFinishedIfNeeded(featureFile) {
const featureTestCaseStatus = this.featureFileAndTestCaseStatusMap.get(featureFile);
if (featureTestCaseStatus) {
if (Object.entries(featureTestCaseStatus).find(([, value]) => value === "todo") === undefined) {
event_1.UUVEventEmitter.getInstance().emitTestSuiteFinished({
testSuiteName: this.getTestSuiteName(featureFile)
});
}
}
}
addResultErrors(result, test, featureFile) {
result.errors.forEach(error => {
const scenario = this.getCurrentRunningScenario(test, featureFile);
this.addError({
scenario: scenario,
error: error.message
});
});
}
createTestCaseErrorAttachmentsEnvelope(testCaseStartedId, result) {
result.attachments.forEach(attachment => {
if (attachment.path && attachment.path.endsWith("test-failed-1.png")) {
const attachmentBody = fs_1.default.readFileSync(attachment.path, { encoding: "base64" });
const failedStep = result.steps.find(step => step.error);
if (failedStep?.location) {
const testCaseAttachmentEnvelope = this.createEnvelope({
attachment: {
testCaseStartedId: testCaseStartedId,
testStepId: this.testStepLocationAndTestStepIdMap.get(this.getTestStepKey(failedStep.location)),
body: attachmentBody,
contentEncoding: messages_1.AttachmentContentEncoding.BASE64,
mediaType: attachment.contentType
}
});
this.envelopes.push(testCaseAttachmentEnvelope);
}
}
});
}
initConsoleReportIfNotExists(featureFile) {
if (!this.consoleReportMap.get(featureFile)) {
event_1.UUVEventEmitter.getInstance().emitTestSuiteStarted({
testSuiteName: this.getTestSuiteName(featureFile),
testSuitelocation: featureFile
});
this.consoleReportMap.set(featureFile, new ReportOfFeature());
}
}
updateConsoleReport(featureFile, result) {
this.consoleReportMap.get(featureFile)?.increment(result.status, result.retry);
}
createTestRunFinishedEnvelope(result) {
const endDate = new Date();
const durationInSecond = endDate.getTime() / 1000;
this.envelopes.push((0, messages_1.parseEnvelope)(JSON.stringify({
testRunFinished: {
success: result.status === "passed",
timestamp: {
seconds: durationInSecond,
nanos: Math.floor(durationInSecond * NANOS_IN_SECOND + durationInSecond)
}
}
})));
}
async generateReport(reportType) {
this.displayConsoleReport();
if (reportType === GeneratedReportType.HTML) {
await this.generateHtmlReport();
}
}
getTimestamp(inpuDate) {
const date = inpuDate ? inpuDate : new Date();
const durationInSecond = date.getTime() / 1000;
const NANOS_IN_SECOND = 1000000000;
return {
seconds: durationInSecond,
nanos: Math.floor(durationInSecond * NANOS_IN_SECOND + durationInSecond)
};
}
getOriginalFeatureFile(generatedFile) {
if (this.foundConf) {
return path_1.default.relative(process.cwd(), generatedFile
.replaceAll(this.foundConf.outputDir, this.foundConf.featuresRoot)
.replaceAll(".feature.spec.js", ".feature"));
}
return;
}
getTestSuiteName(featureFile) {
if (this.foundConf) {
return path_1.default.relative(this.foundConf.featuresRoot, path_1.default.join(process.cwd(), featureFile));
}
return featureFile;
}
getCurrentRunningScenario(test, featureFile) {
const counter = `[${test.parent.allTests().filter(test => test.results.length > 0).length}/${test.parent.allTests().length}]`;
return `File ${featureFile} > Scenario ${counter} - ${test.title}`;
}
addError(newError) {
this.errors.push(newError);
}
getStatus(step) {
if (step.error === undefined) {
return "PASSED";
}
else {
return "FAILED";
}
}
createEnvelope(content) {
return (0, messages_1.parseEnvelope)(JSON.stringify(content));
}
createCucumberNdJsonFile(outputFileName) {
try {
console.log(`writting file ${outputFileName}`);
this.envelopes.forEach((envelope, index) => {
fs_1.default.writeFileSync(outputFileName, `${JSON.stringify(envelope)}\r\n`, {
flag: index === 0 ? "w+" : "a"
});
});
}
catch (err) {
console.log(err);
}
}
initializeCucumberReportNdJson(suite, featureFiles) {
featureFiles.forEach(featureFile => {
const originalFile = this.getOriginalFeatureFile(featureFile);
const tagsParameter = this.getTagsParameter();
const tagsExpression = (0, tag_expressions_1.default)(tagsParameter ? tagsParameter : "");
if (originalFile) {
const currentEnvelopes = (0, gherkin_1.generateMessages)(fs_1.default.readFileSync(originalFile).toString(), originalFile, messages_1.SourceMediaType.TEXT_X_CUCUMBER_GHERKIN_PLAIN, {
includeSource: true,
includeGherkinDocument: true,
newId: messages_1.IdGenerator.uuid(),
includePickles: true
});
const currentQuery = new gherkin_utils_1.Query();
currentEnvelopes
.map(envelope => {
if (tagsParameter && envelope?.gherkinDocument?.feature) {
envelope.gherkinDocument.feature.children = envelope.gherkinDocument.feature.children.filter(child => child.scenario?.tags && tagsExpression.evaluate(child.scenario.tags.map(tag => tag.name)));
}
return envelope;
})
.filter(envelope => {
if (tagsParameter && envelope?.pickle?.steps) {
return tagsExpression.evaluate(envelope.pickle.tags.map(pickleTag => pickleTag.name));
}
return true;
})
.forEach(envelope => currentQuery.update(envelope));
this.queries.set(originalFile, currentQuery);
this.envelopes = this.envelopes.concat(currentEnvelopes);
this.featureFileAndTestCaseStatusMap.set(originalFile, []);
this.populateTestCasesAndPickleIdMap(currentQuery, suite, featureFile, originalFile);
}
});
}
populateTestCasesAndPickleIdMap(currentQuery, suite, featureFile, originalFile) {
const pickles = currentQuery.getPickles();
const featureFileTestSuite = suite.allTests()
.filter(testCase => testCase.location.file === featureFile);
featureFileTestSuite.forEach((testCase, index) => {
this.testCasesAndPickleIdMap.set(testCase.id, pickles[index].id);
this.updateTestcaseStatus(originalFile, testCase.id, "todo");
});
}
updateTestcaseStatus(originalFile, testCaseId, newStatus) {
const featureTestCaseStatus = this.featureFileAndTestCaseStatusMap.get(originalFile);
if (featureTestCaseStatus) {
featureTestCaseStatus[`${testCaseId}`] = newStatus;
this.featureFileAndTestCaseStatusMap.set(originalFile, featureTestCaseStatus);
}
}
generateTestStep(currentQuery, test) {
const pickleId = this.testCasesAndPickleIdMap.get(test.id);
return currentQuery.getPickles()
.find(pickle => pickle.id === pickleId)
?.steps
.map((step) => {
return {
id: step.id,
pickleStepId: step.id,
stepDefinitionIds: [this.createStepDefinitionEnvelope()]
};
});
}
createStepDefinitionEnvelope() {
const stepDefinitionId = (0, nanoid_1.nanoid)();
this.envelopes.push(this.createEnvelope({
stepDefinition: {
id: stepDefinitionId,
pattern: {
source: "a step",
type: "CUCUMBER_EXPRESSION"
},
sourceReference: {
uri: "not available",
location: {
line: 0
}
}
}
}));
return stepDefinitionId;
}
getFeatureFiles(suite) {
return [...new Set(suite.allTests().map(testCase => testCase.location?.file))];
}
async formatCucumberMessageFile(inputMessageFile, outputFormattedFileJson) {
const formatter = new uuv_custom_formatter_1.UuvCustomFormatter();
await formatter.parseCucumberJson(inputMessageFile, outputFormattedFileJson);
}
generateHtmlReportFromJson(reportDirHtml, reportDirJson) {
const UNKNOWN_VALUE = "unknown";
multiple_cucumber_html_reporter_1.default.generate({
jsonDir: reportDirJson,
reportPath: reportDirHtml,
useCDN: true,
metadata: {
browser: {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
name: process.env.browser,
version: UNKNOWN_VALUE
},
device: UNKNOWN_VALUE,
platform: {
name: UNKNOWN_VALUE,
version: UNKNOWN_VALUE,
},
},
});
(0, utils_1.updateChartJsVersion)(reportDirHtml, "2.5.0", "2.6.0");
}
async generateHtmlReport() {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const reportDir = `${process.env.CONFIG_DIR}/reports/e2e`;
const outputMessageFile = `${reportDir}/cucumber-messages.ndjson`;
const reportDirHtml = `${reportDir}/html`;
const reportDirJson = `${reportDir}/json`;
const outputFormattedFileJson = `${reportDirJson}/cucumber-report.json`;
this.createdDirIfNeeded(reportDir);
this.createdDirIfNeeded(reportDirHtml);
this.createdDirIfNeeded(reportDirJson);
this.createCucumberNdJsonFile(outputMessageFile);
await this.formatCucumberMessageFile(outputMessageFile, outputFormattedFileJson);
this.generateHtmlReportFromJson(reportDirHtml, reportDirJson);
}
createdDirIfNeeded(reportDir) {
// Creating needed dirs
if (!fs_1.default.existsSync(reportDir)) {
fs_1.default.mkdirSync(reportDir, { recursive: true });
}
}
displayConsoleReport() {
const consoleReport = [];
const chalkTableOptions = {
leftPad: 2,
columns: [
{ field: "id", name: "Id" },
{ field: "file", name: "File" },
{ field: "passed", name: chalk_1.default.green("Passed") },
{ field: "skipped", name: chalk_1.default.yellow("Skipped") },
{ field: "failed", name: chalk_1.default.redBright("Failed") },
{ field: "retries", name: chalk_1.default.yellow("retries") }
]
};
let index = 1;
this.consoleReportMap.forEach((value, key) => {
consoleReport.push({
id: index,
file: key,
passed: chalk_1.default.green(value.passed),
retries: chalk_1.default.yellow(value.retries),
skipped: chalk_1.default.yellow(value.skipped),
failed: value.failed ? chalk_1.default.redBright(value.failed) : chalk_1.default.red(value.failed)
});
index++;
});
if (this.errors.length > 0) {
console.log("\n\n");
console.log(chalk_1.default.underline(chalk_1.default.bgRed(`${this.errors.length} Error(s) :`)));
this.errors.forEach(error => {
console.log(error.scenario);
console.log(chalk_1.default.red(error.error.toString()));
console.log("");
});
console.log("");
}
console.log(chalk_1.default.underline(chalk_1.default.bgBlue("Test Execution Report :")));
console.log((0, chalk_table_1.default)(chalkTableOptions, consoleReport));
console.log("\n\n");
}
logTestBegin(testCase, featureFile) {
if (this.currentFeatureFile !== featureFile) {
console.info(chalk_1.default.blueBright(` Feature: ${featureFile}`));
this.currentFeatureFile = featureFile;
}
}
logTestEnd(testCase, result) {
console.info(` ${this.getResultIcon(testCase)} ${this.getTestCaseTitle(testCase, result)}`);
}
getResultIcon(testCase) {
if (testCase.expectedStatus === "skipped") {
return chalk_1.default.yellowBright("\u2192");
}
else if (testCase.ok()) {
return chalk_1.default.green("\u2713");
}
else {
return chalk_1.default.redBright("\u166D");
}
}
getTestCaseTitle(testCase, result) {
const message = `${testCase.title} (${result.duration}ms)`;
if (testCase.expectedStatus === "skipped") {
return chalk_1.default.yellowBright(message);
}
else if (testCase.ok()) {
return chalk_1.default.gray(message);
}
else {
return chalk_1.default.redBright(message);
}
}
getTagsParameter() {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return process.env.TAGS;
}
}
exports.default = UuvPlaywrightReporterHelper;