Skip to content

Commit

Permalink
feat: cucumber embeddings (#88)
Browse files Browse the repository at this point in the history
* feat: cucumber embeddings

* fix: tests in windows
  • Loading branch information
ASaiAnudeep authored Sep 1, 2024
1 parent 8c9211b commit 2d17efc
Show file tree
Hide file tree
Showing 11 changed files with 499 additions and 9 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,5 @@ dist
.vscode

.idea/

.testbeats
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "test-results-parser",
"version": "0.2.4",
"version": "0.2.5",
"description": "Parse test results from JUnit, TestNG, xUnit, cucumber and many more",
"main": "src/index.js",
"types": "./src/index.d.ts",
Expand Down
98 changes: 96 additions & 2 deletions src/helpers/helper.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const fs = require('fs');
const path = require('path');
const parser = require('fast-xml-parser');
const { totalist } = require('totalist/sync');
const globrex = require('globrex');
const { XMLParser } = require("fast-xml-parser");
Expand Down Expand Up @@ -94,8 +93,103 @@ function getMatchingFilePaths(file_path) {
return [file_path];
}

/**
*
* @param {string} value
*/
function decodeIfEncoded(value) {
if (!value) {
return value;
}
try {
if (value.length % 4 !== 0) {
return value;
}
const base64Regex = /^[A-Za-z0-9+/]+={0,2}$/;
if (!base64Regex.test(value)) {
return value;
}
return atob(value);
} catch (error) {
return value;
}
}

/**
*
* @param {string} value
* @returns
*/
function isEncoded(value) {
if (!value) {
return false;
}
try {
if (value.length % 4 !== 0) {
return false;
}
const base64Regex = /^[A-Za-z0-9+/]+={0,2}$/;
if (!base64Regex.test(value)) {
return false;
}
atob(value);
return true;
} catch (error) {
return false;
}
}

/**
*
* @param {string} value
*/
function isFilePath(value) {
try {
fs.statSync(value);
return true;
} catch {
return false;
}
}

/**
*
* @param {string} file_name
* @param {string} file_data
* @param {string} file_type
*/
function saveAttachmentToDisk(file_name, file_data, file_type) {
const folder_path = path.join(process.cwd(), '.testbeats', 'attachments');
fs.mkdirSync(folder_path, { recursive: true });
let data = file_data;
if (isEncoded(file_data)) {
data = Buffer.from(file_data, 'base64');
} else {
return '';
}

const file_path = path.join(folder_path, file_name);
let relative_file_path = path.relative(process.cwd(), file_path);
if (file_type.includes('png')) {
relative_file_path = `${relative_file_path}.png`;
fs.writeFileSync(relative_file_path, data);
} else if (file_type.includes('jpeg')) {
relative_file_path = `${relative_file_path}.jpeg`;
fs.writeFileSync(relative_file_path, data);
} else if (file_type.includes('json')) {
relative_file_path = `${relative_file_path}.json`;
fs.writeFileSync(relative_file_path, data);
} else {
return '';
}
return relative_file_path;
}

module.exports = {
getJsonFromXMLFile,
getMatchingFilePaths,
resolveFilePath
resolveFilePath,
decodeIfEncoded,
isFilePath,
saveAttachmentToDisk
}
6 changes: 3 additions & 3 deletions src/parsers/base.parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ class BaseParser {
* @returns
*/
parseStatus(value) {
if (value === 'passed') {
if (value === 'passed' || value === 'PASSED') {
return 'PASS';
}
if (value === 'failed') {
if (value === 'failed' || value === 'FAILED') {
return 'FAIL';
}
if (value === 'skipped') {
if (value === 'skipped' || value === 'SKIPPED') {
return 'SKIP';
}
return 'FAIL';
Expand Down
55 changes: 54 additions & 1 deletion src/parsers/cucumber.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
const { resolveFilePath } = require('../helpers/helper');
const path = require('path');
const fs = require('fs');
const { resolveFilePath, decodeIfEncoded, isFilePath, saveAttachmentToDisk } = require('../helpers/helper');

const TestResult = require('../models/TestResult');
const TestSuite = require('../models/TestSuite');
const TestCase = require('../models/TestCase');
const TestStep = require('../models/TestStep');
const { BaseParser } = require('./base.parser');
const TestAttachment = require('../models/TestAttachment');

class CucumberParser extends BaseParser {

Expand Down Expand Up @@ -91,6 +94,7 @@ class CucumberParser extends BaseParser {
const { tags, metadata } = this.#getTagsAndMetadata(scenario);
test_case.tags = tags;
test_case.metadata = metadata;
test_case.attachments = this.#getAttachments(scenario.steps);
return test_case;
}

Expand Down Expand Up @@ -157,6 +161,55 @@ class CucumberParser extends BaseParser {
return { tags, metadata };
}

/**
*
* @param {import('./cucumber.result').CucumberStep[]} steps
*/
#getAttachments(steps) {
const attachments = [];
const failed_steps = steps.filter(_ => this.parseStatus(_.result.status) === 'FAIL' && _.embeddings && _.embeddings.length > 0);

for (const step of failed_steps) {
for (const embedding of step.embeddings) {
const attachment = this.#getAttachment(step, embedding);
if (attachment) {
attachments.push(attachment);
}
}
}
return attachments;
}

/**
*
* @param {import('./cucumber.result').CucumberStep} step
* @param {import('./cucumber.result').CucumberEmbedding} embedding
*/
#getAttachment(step, embedding) {
try {
const decoded = decodeIfEncoded(embedding.data);
const is_file_path = isFilePath(decoded);
if (is_file_path) {
const attachment = new TestAttachment();
attachment.name = path.parse(decoded).base;
attachment.path = decoded;
return attachment;
} else {
const file_name = step.name.replace(/[^a-zA-Z0-9]/g, '_') + '-' + Date.now();
const file_path = saveAttachmentToDisk(file_name, embedding.data, embedding.mime_type);
if (!file_path) {
return null;
}
const attachment = new TestAttachment();
attachment.name = path.parse(file_path).base;
attachment.path = file_path;
return attachment;
}
} catch (e) {
return null;
}
}

}

function parse(file) {
Expand Down
6 changes: 6 additions & 0 deletions src/parsers/cucumber.result.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,14 @@ export type CucumberStep = {
name: string;
match: CucumberMatch;
result: CucumberResult;
embeddings?: CucumberEmbedding[];
};

export type CucumberEmbedding = {
data: string;
mime_type: string;
}

export type CucumberTag = {
name: string;
line: number;
Expand Down
Binary file added tests/data/attachments/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
117 changes: 117 additions & 0 deletions tests/data/cucumber/test-with-attachments.json

Large diffs are not rendered by default.

117 changes: 117 additions & 0 deletions tests/data/cucumber/test-with-invalid-attachments.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
[
{
"description": "Verify calculator functionalities",
"elements": [
{
"description": "",
"id": "addition;addition-of-two-numbers",
"keyword": "Scenario",
"line": 5,
"name": "Addition of two numbers",
"steps": [
{
"arguments": [],
"keyword": "Given ",
"line": 6,
"name": "I have number 6 in calculator",
"match": {
"location": "features\\support\\steps.js:5"
},
"result": {
"status": "passed",
"duration": 1211400
}
},
{
"arguments": [],
"keyword": "When ",
"line": 7,
"name": "I entered number 7",
"match": {
"location": "features\\support\\steps.js:9"
},
"result": {
"status": "passed",
"duration": 136500
}
},
{
"arguments": [],
"keyword": "Then ",
"line": 8,
"name": "I should see result 13",
"match": {
"location": "features\\support\\steps.js:13"
},
"result": {
"status": "failed",
"duration": 1330499,
"error_message": "AssertionError [ERR_ASSERTION]: 13 == 14\n + expected - actual\n\n -13\n +14\n\n at CustomWorld.<anonymous> (D:\\workspace\\nodejs\\cc-tests\\features\\support\\steps.js:18:12)"
},
"embeddings": [
{
"data": "some-invalid-data-path",
"mime_type": "image/png"
},
{
"data": "tests/data/attachments/screenshot.png",
"mime_type": "image/png"
},
{
"data": "ZGF0YQ==",
"mime_type": ""
}
]
},
{
"arguments": [],
"keyword": "And ",
"line": 49,
"name": "I close the test",
"match": {
"location": "features\\support\\steps.js:14"
},
"result": {
"status": "skipped",
"duration": 0
}
}
],
"tags": [
{
"name": "@green",
"line": 4
},
{
"name": "@fast",
"line": 4
},
{
"name": "@testCase=1234",
"line": 4
}
],
"type": "scenario"
}
],
"id": "addition",
"line": 1,
"keyword": "Feature",
"name": "Addition",
"tags": [
{
"name": "@blue",
"line": 4
},
{
"name": "@slow",
"line": 4
},
{
"name": "@suite=1234",
"line": 4
}
],
"uri": "features\\sample.feature"
}
]
Loading

0 comments on commit 2d17efc

Please sign in to comment.