playwright-tesults-reporter
Version:
Playwright Tesults Reporter
459 lines (423 loc) • 17.7 kB
JavaScript
// playwright-tesults-reporter.js
// @ts-nocheck
const fs = require('fs');
const path = require('path');
const tesults = require('tesults');
/** @implements {import('@playwright/test/reporter').Reporter} */
class tesultsReporter {
onBegin(config, suite) {
this.disabled = true
this.retries = new Map();
this.data = {
target: 'token',
results: {
cases: []
},
metadata: {
integration_name: "playwright-tesults-reporter",
integration_version: "1.5.0",
test_framework: "playwright"
}
};
this.testIndices = {}
this.caseFiles = (suite, name) => {
let files = [];
if (this.args['tesults-files'] !== undefined) {
try {
const filesPath = path.join(this.args['tesults-files'], suite, name);
fs.readdirSync(filesPath).forEach(function (file) {
if (file !== ".DS_Store") { // Exclude os files
files.push(path.join(filesPath, file));
}
});
} catch (err) {
if (err.code === 'ENOENT') {
// Normal scenario where no files present: console.log('Tesults error reading case files, check supplied tesults-files arg path is correct.');
} else {
console.log('Tesults error reading case files.')
}
}
}
return files;
}
this.startTimes = {};
this.args = {};
this.disabled = false;
this.targetKey = "tesults-target";
this.filesKey = "tesults-files";
this.configKey = "tesults-config";
this.buildNameKey = "tesults-build-name";
this.buildDescKey = "tesults-build-description";
this.buildResultKey = "tesults-build-result";
this.buildReasonKey = "tesults-build-reason";
if (config !== undefined && config !== null) {
if (config.reporter !== undefined && config.reporter !== null) {
if (Array.isArray(config.reporter)) {
for (let i = 0; i < config.reporter.length; i++) {
let reporter = config.reporter[i]
if (Array.isArray(reporter)) {
if (reporter.length > 0) {
let reporterName = reporter[0]
if (reporterName.indexOf("playwright-tesults-reporter") > -1) {
if (reporter.length > 1) {
let reportArgs = reporter[1];
if (reportArgs !== undefined && reportArgs !== null) {
this.args[this.targetKey] = reportArgs[this.targetKey]
this.args[this.filesKey] = reportArgs[this.filesKey]
this.args[this.buildNameKey] = reportArgs[this.buildNameKey]
this.args[this.buildDescKey] = reportArgs[this.buildDescKey]
this.args[this.buildResultKey] = reportArgs[this.buildResultKey]
this.args[this.buildReasonKey] = reportArgs[this.buildReasonKey]
}
}
}
}
}
}
}
}
}
/* Reserved for future use
process.argv.forEach((val, index) => {
if (val.indexOf(this.targetKey) === 0) {
this.args[this.targetKey] = val.substr(this.targetKey.length + 1);
}
if (val.indexOf(this.filesKey) === 0) {
this.args[this.filesKey] = val.substr(this.filesKey.length + 1);
}
if (val.indexOf(this.configKey) === 0) {
this.args[this.configKey] = val.substr(this.configKey.length + 1);
}
if (val.indexOf(this.buildNameKey) === 0) {
this.args[this.buildNameKey] = val.substr(this.buildNameKey.length + 1);
}
if (val.indexOf(this.buildDescKey) === 0) {
this.args[this.buildDescKey] = val.substr(this.buildDescKey.length + 1);
}
if (val.indexOf(this.buildResultKey) === 0) {
this.args[this.buildResultKey] = val.substr(this.buildResultKey.length + 1);
}
if (val.indexOf(this.buildReasonKey) === 0) {
this.args[this.buildReasonKey] = val.substr(this.buildReasonKey.length + 1);
}
});
*/
if (this.args[this.targetKey] === undefined) {
console.log(this.targetKey + " not provided. Tesults disabled.");
this.disabled = true;
}
}
onTestBegin(test, result) {
// Not used
}
onStepBegin (test, result, step) {
// Not used
}
onStepEnd (test, result, step) {
// Not used
}
onTestEnd(test, result) {
if (this.disabled === true) {
return;
}
let testCase = {};
testCase.name = test.title;
const consolidatedSuite = (entity, suite) => {
if (entity.parent !== undefined && entity.parent !== null) {
if (suite === "") {
if (entity.parent.title !== "") {
suite += entity.parent.title;
}
} else {
if (entity.parent.title !== "") {
suite += " - " + entity.parent.title;
}
}
return consolidatedSuite(entity.parent, suite);
} else {
return suite;
}
}
testCase.suite = consolidatedSuite(test, "");
try {
testCase["_Suite location"] = JSON.stringify(test.parent.location);
} catch (err) {
// Unable to handle suite location
}
testCase.rawResult = result.status;
testCase.result = "unknown";
if (result.status === "passed") {
testCase.result = "pass";
}
if (result.status === "failed") {
testCase.result = "fail";
}
if (test.expectedStatus !== "passed") { // "passed" is the default expected status
if (test.expectedStatus === result.status) {
testCase.result = "pass";
} else {
testCase.result = "fail";
}
}
testCase["_Timeout"] = test.timeout;
testCase["_Retries Configuration Setting"] = test.retries;
testCase["_Expected Status"] = test.expectedStatus;
try {
testCase["_Location"] = JSON.stringify(test.location);
} catch (err) {
// Unable to handle location
}
testCase["_Outcome"] = test.outcome();
testCase["_Ok"] = test.ok();
try {
testCase["_Test path"] = JSON.stringify(testCase.titlePath())
} catch (err) {
// Unable to capture test path
}
try {
testCase["_Repeat each index"] = test.repeatEachIndex;
} catch (err) {
// Unable to capture repeat each index
}
try {
testCase.start = result.startTime.getTime();
} catch (err) {
// Unable to capture start time
}
testCase.duration = result.duration;
if (testCase.start !== undefined) {
testCase.end = testCase.start + testCase.duration;
}
testCase["_Worker index"] = result.workerIndex;
testCase.files = [];
try {
if (result.attachments !== undefined && result.attachments !== null) {
if (Array.isArray(result.attachments)) {
for (let i = 0; i < result.attachments.length; i++) {
let attachment = result.attachments[i];
if (attachment.path !== undefined && attachment.path !== null) {
testCase.files.push(attachment.path);
}
}
}
}
} catch (err) {
// Unable to handle attachments
}
if (result.error !== undefined && result.error !== null) {
try {
testCase.reason = JSON.stringify(result.error);
} catch (err) {
// Unable to handle error
}
}
if (result.errors !== undefined && result.errors !== null) {
if (Array.isArray(result.errors)) {
try {
testCase["_Errors"] = JSON.stringify(result.errors);
} catch (err) {
// Unable to handle errors
}
}
}
if (result.stdout !== undefined && result.stdout !== null) {
if (Array.isArray(result.stdout)) {
try {
testCase["_Standard output"] = JSON.stringify(result.stdout);
} catch (err) {
// Unable to handle stdout
}
}
}
if (result.stderr !== undefined && result.stderr !== null) {
if (Array.isArray(result.stderr)) {
try {
testCase["_Standard error"] = JSON.stringify(result.stderr);
} catch (err) {
// Unable to handle stderr
}
}
}
if (result.steps !== undefined && result.steps !== null) {
// Recursive function to process steps and nested steps
const processSteps = (steps) => {
if (!Array.isArray(steps)) {
return [];
}
let processedSteps = [];
for (let i = 0; i < steps.length; i++) {
let step = steps[i];
let stepObj = {};
stepObj.name = step.title;
try {
stepObj.start = step.startTime.getTime();
} catch (err) {
// Unable to handle start
}
stepObj.duration = step.duration;
if (stepObj.start !== undefined) {
stepObj.end = stepObj.start + stepObj.duration;
}
try {
stepObj["_Location"] = JSON.stringify(step.location);
} catch (err) {
// Unable to handle location
}
stepObj.result = "pass";
if (step.error !== undefined && step.error != null) {
stepObj.result = "fail";
try {
stepObj.reason = JSON.stringify(step.error);
} catch (err) {
// Unable to handle reason
}
}
if (step.titlePath !== undefined && typeof step.titlePath === 'function') {
try {
const titlePath = step.titlePath();
if (titlePath !== undefined && titlePath !== null && Array.isArray(titlePath)) {
stepObj["_Path"] = JSON.stringify(titlePath);
}
} catch (err) {
// Unable to handle path
}
}
if (step.category !== undefined && step.category !== null) {
stepObj["_Category"] = step.category;
}
// Recursively process nested steps
if (step.steps !== undefined && step.steps !== null && Array.isArray(step.steps) && step.steps.length > 0) {
stepObj.steps = processSteps(step.steps);
}
processedSteps.push(stepObj);
}
return processedSteps;
};
testCase.steps = processSteps(result.steps);
}
// Add Enhanced Reporting data from annotations
try {
let annotationNum = 1;
if (test.annotations && Array.isArray(test.annotations)) {
test.annotations.forEach((annotation) => {
if (annotation.type === "custom") {
testCase[annotation.key] = annotation.value;
} else if (annotation.type === "desc") {
testCase.desc = annotation.value;
} else {
testCase["_Annotation " + annotationNum] = JSON.stringify(annotation);
annotationNum++;
}
});
}
} catch (err) {
// Unable to capture annotations or enhanced reporting data
}
if (result.retry !== undefined && result.retry !== null) {
testCase["_Retry"] = result.retry;
}
let additionalFiles = this.caseFiles(testCase.suite, testCase.name);
if (additionalFiles.length > 0) {
for (let i = 0; i < additionalFiles.length; i++) {
testCase.files.push(additionalFiles[i]);
}
}
let replace = false;
const hash = testCase.suite + " " + testCase.name;
if (this.testIndices[hash] !== undefined) {
// This is not the first run of this test case
let index = this.testIndices[hash];
if (index < this.data.results.cases.length) {
let t = this.data.results.cases[index];
if (t["_Retry"] !== testCase["_Retry"]) {
// Replacement
replace = true;
const retries = this.retries.get(hash);
testCase["_Retries"] = Array.from(retries);
retries.push(testCase);
this.retries.set(hash, retries);
this.data.results.cases[index] = testCase;
}
}
} else {
// This is the first run of this test case
testCase["_Retries"] = [];
this.retries.set(hash, [testCase]);
this.testIndices[hash] = this.data.results.cases.length
}
if (replace === false) {
this.data.results.cases.push(testCase);
}
}
onEnd (result) {
if (this.disabled === true) {
return;
}
// build case
if (this.args[this.buildNameKey] !== undefined) {
let buildCase = {suite: "[build]"};
buildCase.name = this.args[this.buildNameKey];
if (buildCase.name === "") {
buildCase.name = "-";
}
if (this.args[this.buildDescKey] !== undefined) {
buildCase.desc = this.args[this.buildDescKey];
}
if (this.args[this.buildReasonKey] !== undefined) {
buildCase.reason = this.args[this.buildReasonKey];
}
if (this.args[this.buildResultKey] !== undefined) {
buildCase.result = this.args[this.buildResultKey].toLowerCase();
if (buildCase.result !== "pass" && buildCase.result !== "fail") {
buildCase.result = "unknown";
}
} else {
buildCase.result = "unknown";
}
let files = this.caseFiles(buildCase.suite, buildCase.name);
if (files.length > 0) {
buildCase.files = files;
}
this.data.results.cases.push(buildCase);
}
// Tesults upload
this.data.target = this.args[this.targetKey];
console.log('Tesults results upload...');
return new Promise((resolve, reject) => {
tesults.results(this.data, function (err, response) {
if (err) {
console.log('Tesults library error, failed to upload.');
return reject('Tesults library error, failed to upload.');
} else {
console.log('Success: ' + response.success);
console.log('Message: ' + response.message);
console.log('Warnings: ' + response.warnings.length);
console.log('Errors: ' + response.errors.length);
resolve();
}
});
});
}
}
module.exports = tesultsReporter
// Enhanced reporting functions
module.exports.custom = (key, value) => {
// Use Playwright's test.info().annotations to store custom data
const { test } = require('@playwright/test');
const testInfo = test.info();
testInfo.annotations.push({
type: "custom",
key: "_" + key,
value: value
});
}
module.exports.description = (value) => {
// Use Playwright's test.info().annotations to store custom data
const { test } = require('@playwright/test');
const testInfo = test.info();
testInfo.annotations.push({
type: "desc",
key: "desc",
value: value
});
}