@reportportal/agent-js-cypress
Advanced tools
Comparing version 5.3.2 to 5.3.3
@@ -54,2 +54,3 @@ /* | ||
INIT: 'rpInit', | ||
FULL_CONFIG: 'rpFullConfig', | ||
LOG: 'rpLog', | ||
@@ -56,0 +57,0 @@ LAUNCH_LOG: 'rpLaunchLog', |
@@ -38,4 +38,4 @@ /* | ||
getLaunchStartObject, | ||
getSuiteStartObject, | ||
getSuiteEndObject, | ||
getSuiteStartInfo, | ||
getSuiteEndInfo, | ||
getTestInfo, | ||
@@ -67,2 +67,3 @@ getHookInfo, | ||
const configListener = (cypressFullConfig) => { | ||
this.worker.send({ event: reporterEvents.FULL_CONFIG, config: cypressFullConfig }); | ||
CypressReporter.cypressConfig = cypressFullConfig; | ||
@@ -137,3 +138,3 @@ CypressReporter.calcTotalLaunches(); | ||
event: EVENT_SUITE_BEGIN, | ||
suite: getSuiteStartObject(suite, this.runner.suite.file), | ||
suite: getSuiteStartInfo(suite, this.runner.suite.file), | ||
}); | ||
@@ -144,3 +145,3 @@ }); | ||
if (!suite.title) return; | ||
this.worker.send({ event: EVENT_SUITE_END, suite: getSuiteEndObject(suite) }); | ||
this.worker.send({ event: EVENT_SUITE_END, suite: getSuiteEndInfo(suite) }); | ||
}); | ||
@@ -147,0 +148,0 @@ |
@@ -66,3 +66,3 @@ /* | ||
const request = client.getMergeLaunchesRequest(launchIds); | ||
request.description = config.reporterOptions.description; | ||
request.description = config.description; | ||
request.extendSuitesDescription = false; | ||
@@ -69,0 +69,0 @@ const mergeURL = 'launch/merge'; |
/* | ||
* Copyright 2020 EPAM Systems | ||
* Copyright 2024 EPAM Systems | ||
* | ||
@@ -27,2 +27,5 @@ * Licensed under the Apache License, Version 2.0 (the "License"); | ||
getCodeRef, | ||
getVideoFile, | ||
getSuiteStartObject, | ||
getSuiteEndObject, | ||
} = require('./utils'); | ||
@@ -51,3 +54,5 @@ | ||
this.hooks = new Map(); | ||
this.config = config; | ||
this.config = config.reporterOptions; | ||
this.fullCypressConfig = config; | ||
this.videoPromises = []; | ||
@@ -66,2 +71,6 @@ this.currentTestFinishParams = getInitialTestFinishParams(); | ||
saveFullConfig(config) { | ||
this.fullCypressConfig = config; | ||
} | ||
resetCurrentTestFinishParams() { | ||
@@ -73,3 +82,3 @@ this.currentTestFinishParams = getInitialTestFinishParams(); | ||
const { tempId, promise } = this.client.startLaunch(launchObj); | ||
const { launch, isLaunchMergeRequired } = this.config.reporterOptions; | ||
const { launch, isLaunchMergeRequired } = this.config; | ||
if (isLaunchMergeRequired) { | ||
@@ -83,3 +92,3 @@ createMergeLaunchLockFile(launch, tempId); | ||
runEnd() { | ||
const basePromise = this.config.reporterOptions.launchId | ||
const basePromise = this.config.launchId | ||
? this.client.getPromiseFinishAllItems(this.tempLaunchId) | ||
@@ -96,5 +105,5 @@ : this.client.finishLaunch( | ||
const finishLaunchPromise = basePromise | ||
const finishLaunchPromise = Promise.allSettled([basePromise, ...this.videoPromises]) | ||
.then(() => { | ||
const { launch, isLaunchMergeRequired } = this.config.reporterOptions; | ||
const { launch, isLaunchMergeRequired } = this.config; | ||
if (isLaunchMergeRequired) { | ||
@@ -105,3 +114,3 @@ deleteMergeLaunchLockFile(launch, this.tempLaunchId); | ||
.then(() => { | ||
const { parallel, autoMerge } = this.config.reporterOptions; | ||
const { parallel, autoMerge } = this.config; | ||
if (!(parallel && autoMerge)) { | ||
@@ -118,28 +127,101 @@ return Promise.resolve(); | ||
const parentId = suite.parentId && this.testItemIds.get(suite.parentId); | ||
const { tempId, promise } = this.client.startTestItem(suite, this.tempLaunchId, parentId); | ||
const startSuiteObj = getSuiteStartObject(suite); | ||
const { tempId, promise } = this.client.startTestItem( | ||
startSuiteObj, | ||
this.tempLaunchId, | ||
parentId, | ||
); | ||
promiseErrorHandler(promise, 'Fail to start suite'); | ||
this.testItemIds.set(suite.id, tempId); | ||
this.suitesStackTempInfo.push({ tempId, startTime: suite.startTime }); | ||
this.suitesStackTempInfo.push({ | ||
tempId, | ||
startTime: suite.startTime, | ||
title: suite.title || '', | ||
id: suite.id, | ||
testFileName: suite.testFileName, | ||
}); | ||
} | ||
suiteEnd(suite) { | ||
const suiteId = this.testItemIds.get(suite.id); | ||
const { uploadVideo = false } = this.config; | ||
const { video: isVideoRecordingEnabled = false } = this.fullCypressConfig; | ||
const isRootSuite = | ||
this.suitesStackTempInfo.length && suite.id === this.suitesStackTempInfo[0].id; | ||
const suiteFinishObj = this.prepareSuiteToFinish(suite); | ||
if (isVideoRecordingEnabled && uploadVideo && isRootSuite) { | ||
const suiteInfo = this.suitesStackTempInfo[0]; | ||
this.finishSuiteWithVideo(suiteInfo, suiteFinishObj); | ||
} else { | ||
const suiteTempId = this.testItemIds.get(suite.id); | ||
this.finishSuite(suiteFinishObj, suiteTempId); | ||
} | ||
this.suitesStackTempInfo.pop(); | ||
} | ||
prepareSuiteToFinish(suite) { | ||
const suiteTestCaseId = this.suiteTestCaseIds.get(suite.title); | ||
const suiteStatus = this.suiteStatuses.get(suite.title); | ||
const finishTestItemPromise = this.client.finishTestItem( | ||
suiteId, | ||
Object.assign( | ||
{ | ||
endTime: new Date().valueOf(), | ||
}, | ||
suiteTestCaseId && { testCaseId: suiteTestCaseId }, | ||
suiteStatus && { status: suiteStatus }, | ||
), | ||
).promise; | ||
promiseErrorHandler(finishTestItemPromise, 'Fail to finish suite'); | ||
this.suitesStackTempInfo.pop(); | ||
let suiteFinishObj = getSuiteEndObject(suite); | ||
suiteFinishObj = { | ||
...suiteFinishObj, | ||
status: suiteStatus || suite.status, | ||
...(suiteTestCaseId && { testCaseId: suiteTestCaseId }), | ||
}; | ||
suiteTestCaseId && this.suiteTestCaseIds.delete(suite.title); | ||
suiteStatus && this.suiteStatuses.delete(suite.title); | ||
return suiteFinishObj; | ||
} | ||
finishSuite(suiteFinishObj, suiteTempId) { | ||
const finishTestItemPromise = this.client.finishTestItem(suiteTempId, suiteFinishObj).promise; | ||
promiseErrorHandler(finishTestItemPromise, 'Fail to finish suite'); | ||
} | ||
finishSuiteWithVideo(suiteInfo, suiteFinishObj) { | ||
const uploadVideoOnPasses = this.config.uploadVideoOnPasses || false; | ||
const suiteFailed = suiteFinishObj.status === testItemStatuses.FAILED; | ||
// do not upload video if root suite passes and uploadVideoOnPasses is false | ||
if ((!suiteFailed && !uploadVideoOnPasses) || !suiteInfo.testFileName) { | ||
this.finishSuite(suiteFinishObj, suiteInfo.tempId); | ||
} else { | ||
const sendVideoPromise = this.sendVideo(suiteInfo).finally(() => { | ||
this.finishSuite(suiteFinishObj, suiteInfo.tempId); | ||
}); | ||
this.videoPromises.push(sendVideoPromise); | ||
} | ||
} | ||
async sendVideo(suiteInfo) { | ||
const { waitForVideoTimeout, waitForVideoInterval, videosFolder } = this.config; | ||
const { testFileName, tempId, title } = suiteInfo; | ||
const file = await getVideoFile( | ||
testFileName, | ||
videosFolder, | ||
waitForVideoTimeout, | ||
waitForVideoInterval, | ||
); | ||
if (!file) { | ||
return null; | ||
} | ||
const sendVideoPromise = this.client.sendLog( | ||
tempId, | ||
{ | ||
message: `Video: '${title}' (${testFileName}.mp4)`, | ||
level: logLevels.INFO, | ||
time: new Date().valueOf(), | ||
}, | ||
file, | ||
).promise; | ||
promiseErrorHandler(sendVideoPromise, 'Fail to save video'); | ||
return sendVideoPromise; | ||
} | ||
testStart(test) { | ||
@@ -188,3 +270,3 @@ const parentId = this.testItemIds.get(test.parentId); | ||
testId, | ||
getTestEndObject(testInfo, this.config.reporterOptions.skippedIssue), | ||
getTestEndObject(testInfo, this.config.skippedIssue), | ||
).promise; | ||
@@ -389,2 +471,6 @@ promiseErrorHandler(finishTestItemPromise, 'Fail to finish test'); | ||
this.suiteStatuses.set(suiteTitle, status); | ||
const rootSuite = this.suitesStackTempInfo.length && this.suitesStackTempInfo[0]; | ||
if (rootSuite && status === testItemStatuses.FAILED) { | ||
this.suitesStackTempInfo[0].status = status; | ||
} | ||
} else { | ||
@@ -399,11 +485,15 @@ Object.assign(this.currentTestFinishParams, status && { status }); | ||
sendScreenshot(screenshotInfo, logMessage) { | ||
async sendScreenshot(screenshotInfo, logMessage) { | ||
const tempItemId = this.currentTestTempInfo && this.currentTestTempInfo.tempId; | ||
const fileName = screenshotInfo.path; | ||
if (!fileName || !tempItemId) return; | ||
if (!fileName || !tempItemId) { | ||
return; | ||
} | ||
const level = fileName && fileName.includes('(failed)') ? logLevels.ERROR : logLevels.INFO; | ||
const file = getScreenshotAttachment(fileName); | ||
if (!file) return; | ||
const file = await getScreenshotAttachment(fileName); | ||
if (!file) { | ||
return; | ||
} | ||
@@ -410,0 +500,0 @@ const message = logMessage || `screenshot ${file.name}`; |
106
lib/utils.js
/* | ||
* Copyright 2022 EPAM Systems | ||
* Copyright 2024 EPAM Systems | ||
* | ||
@@ -24,10 +24,15 @@ * Licensed under the Apache License, Version 2.0 (the "License"); | ||
const fsPromises = fs.promises; | ||
const { FAILED, PASSED, SKIPPED } = testItemStatuses; | ||
const base64Encode = (file) => { | ||
const bitmap = fs.readFileSync(file); | ||
const DEFAULT_WAIT_FOR_FILE_TIMEOUT = 10000; | ||
const DEFAULT_WAIT_FOR_FILE_INTERVAL = 500; | ||
const base64Encode = async (filePath) => { | ||
const bitmap = await fsPromises.readFile(filePath); | ||
return Buffer.from(bitmap).toString('base64'); | ||
}; | ||
const getScreenshotAttachment = (absolutePath) => { | ||
const getScreenshotAttachment = async (absolutePath) => { | ||
if (!absolutePath) return absolutePath; | ||
@@ -38,6 +43,59 @@ const name = absolutePath.split(path.sep).pop(); | ||
type: 'image/png', | ||
content: base64Encode(absolutePath), | ||
content: await base64Encode(absolutePath), | ||
}; | ||
}; | ||
const waitForFile = ( | ||
globFilePath, | ||
timeout = DEFAULT_WAIT_FOR_FILE_TIMEOUT, | ||
interval = DEFAULT_WAIT_FOR_FILE_INTERVAL, | ||
) => | ||
new Promise((resolve, reject) => { | ||
let totalTime = 0; | ||
async function checkFileExistence() { | ||
const files = await glob(globFilePath); | ||
if (files.length) { | ||
resolve(files[0]); | ||
} else if (totalTime >= timeout) { | ||
reject(new Error(`Timeout of ${timeout}ms reached, file ${globFilePath} not found.`)); | ||
} else { | ||
totalTime += interval; | ||
setTimeout(checkFileExistence, interval); | ||
} | ||
} | ||
checkFileExistence().catch(reject); | ||
}); | ||
const getVideoFile = async ( | ||
specFileName, | ||
videosFolder = '**', | ||
timeout = DEFAULT_WAIT_FOR_FILE_TIMEOUT, | ||
interval = DEFAULT_WAIT_FOR_FILE_INTERVAL, | ||
) => { | ||
if (!specFileName) { | ||
return null; | ||
} | ||
const fileName = specFileName.toLowerCase().endsWith('.mp4') | ||
? specFileName | ||
: `${specFileName}.mp4`; | ||
const globFilePath = `**/${videosFolder}/${fileName}`; | ||
let videoFilePath; | ||
try { | ||
videoFilePath = await waitForFile(globFilePath, timeout, interval); | ||
} catch (e) { | ||
console.warn(e.message); | ||
return null; | ||
} | ||
return { | ||
name: fileName, | ||
type: 'video/mp4', | ||
content: await base64Encode(videoFilePath), | ||
}; | ||
}; | ||
const getCodeRef = (testItemPath, testFileName) => | ||
@@ -119,20 +177,39 @@ `${testFileName.replace(/\\/g, '/')}/${testItemPath.join('/')}`; | ||
const getSuiteStartObject = (suite, testFileName) => ({ | ||
const getSuiteStartInfo = (suite, testFileName) => ({ | ||
id: suite.id, | ||
type: entityType.SUITE, | ||
name: suite.title.slice(0, 255).toString(), | ||
title: suite.title, | ||
startTime: new Date().valueOf(), | ||
description: suite.description, | ||
attributes: [], | ||
codeRef: getCodeRef(suite.titlePath(), testFileName), | ||
parentId: !suite.root ? suite.parent.id : undefined, | ||
testFileName: testFileName.split(path.sep).pop(), | ||
}); | ||
const getSuiteEndInfo = (suite) => { | ||
let failed = false; | ||
if (suite.tests != null) { | ||
failed = suite.tests.some((test) => test.state === testItemStatuses.FAILED); | ||
} | ||
return { | ||
id: suite.id, | ||
status: failed ? testItemStatuses.FAILED : undefined, | ||
title: suite.title, | ||
endTime: new Date().valueOf(), | ||
}; | ||
}; | ||
const getSuiteStartObject = (suite) => ({ | ||
type: entityType.SUITE, | ||
name: suite.title.slice(0, 255).toString(), | ||
startTime: suite.startTime, | ||
description: suite.description, | ||
codeRef: suite.codeRef, | ||
attributes: [], | ||
}); | ||
const getSuiteEndObject = (suite) => ({ | ||
id: suite.id, | ||
title: suite.title, | ||
endTime: new Date().valueOf(), | ||
status: suite.status, | ||
endTime: suite.endTime, | ||
}); | ||
// TODO: update/split to not return the redundant and confusing data for items start | ||
const getTestInfo = (test, testFileName, status, err) => ({ | ||
@@ -269,2 +346,4 @@ id: test.id, | ||
getTestInfo, | ||
getSuiteStartInfo, | ||
getSuiteEndInfo, | ||
getTestEndObject, | ||
@@ -278,2 +357,3 @@ getHookInfo, | ||
getSpecPattern, | ||
getVideoFile, | ||
}; |
@@ -41,2 +41,5 @@ /* | ||
break; | ||
case reporterEvents.FULL_CONFIG: | ||
reporter.saveFullConfig(message.config); | ||
break; | ||
case EVENT_RUN_BEGIN: | ||
@@ -43,0 +46,0 @@ reporter.runStart(message.launch); |
{ | ||
"name": "@reportportal/agent-js-cypress", | ||
"version": "5.3.2", | ||
"version": "5.3.3", | ||
"description": "This agent helps Cypress to communicate with Report Portal", | ||
@@ -34,12 +34,12 @@ "main": "index.js", | ||
"devDependencies": { | ||
"@types/jest": "^29.5.3", | ||
"cypress": "^13.12.0", | ||
"eslint": "^8.45.0", | ||
"@types/jest": "^29.5.12", | ||
"cypress": "^13.13.0", | ||
"eslint": "^8.57.0", | ||
"eslint-config-airbnb-base": "^15.0.0", | ||
"eslint-config-prettier": "^8.8.0", | ||
"eslint-plugin-cypress": "2.12.1", | ||
"eslint-plugin-import": "^2.27.5", | ||
"eslint-config-prettier": "^8.10.0", | ||
"eslint-plugin-cypress": "2.15.2", | ||
"eslint-plugin-import": "^2.29.1", | ||
"eslint-plugin-jest": "^23.20.0", | ||
"eslint-plugin-prettier": "^4.2.1", | ||
"jest": "^29.6.1", | ||
"jest": "^29.7.0", | ||
"mock-fs": "^4.14.0", | ||
@@ -46,0 +46,0 @@ "prettier": "^2.8.8" |
@@ -137,3 +137,7 @@ # @reportportal/agent-js-cypress | ||
| restClientConfig | Optional | Not set | `axios` like http client [config](https://github.com/axios/axios#request-config). May contain `agent` property for configure [http(s)](https://nodejs.org/api/https.html#https_https_request_url_options_callback) client, and other client options eg. `timeout`. For debugging and displaying logs you can set `debug: true`. | | ||
| autoMerge | Optional | false | Enable automatic report test items of all runned spec into one launch. You should install plugin or setup additional settings in reporterOptions. See [Automatically merge launch](#automatically-merge-launches). | | ||
| uploadVideo | Optional | false | Whether to upload the Cypress video. | | ||
| uploadVideoOnPasses | Optional | false | Whether to upload the Cypress video for a non-failure specs. Works only if `uploadVideo` set to `true`. | | ||
| waitForVideoTimeout | Optional | 10000 | Value in `ms`. Since Cypress video processing may take extra time after the spec is complete, there is a timeout to wait for the video file readiness. Works only if `uploadVideo` set to `true`. | | ||
| waitForVideoInterval | Optional | 500 | Value in `ms`. Interval to check if the video file is ready. The interval is used until `waitForVideoTimeout` is reached. Works only if `uploadVideo` set to `true`. | | ||
| autoMerge | Optional | false | Enable automatic report test items of all run spec into one launch. You should install plugin or setup additional settings in reporterOptions. See [Automatically merge launch](#automatically-merge-launches). | | ||
| reportHooks | Optional | false | Determines report before and after hooks or not. | | ||
@@ -156,3 +160,2 @@ | isLaunchMergeRequired | Optional | false | Allows to merge Cypress run's into one launch at the end of the run. Needs additional setup. See [Manual merge launches](#manual-merge-launches). | | ||
}; | ||
``` | ||
@@ -159,0 +162,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
98694
1801
503