UNPKG

@reportportal/agent-js-cypress

Version:

This agent helps Cypress to communicate with Report Portal

415 lines (358 loc) 12.5 kB
/* * Copyright 2020 EPAM Systems * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const RPClient = require('@reportportal/client-javascript'); const { entityType, logLevels, testItemStatuses, cucumberKeywordMap } = require('./constants'); const { getScreenshotAttachment, getTestStartObject, getTestEndObject, getHookStartObject, getAgentInfo, getCodeRef, } = require('./utils'); const { createMergeLaunchLockFile, deleteMergeLaunchLockFile } = require('./mergeLaunchesUtils'); const { mergeParallelLaunches } = require('./mergeLaunches'); const { FAILED } = testItemStatuses; const promiseErrorHandler = (promise, message = '') => promise.catch((err) => { console.error(message, err); }); const getInitialTestFinishParams = () => ({ attributes: [], description: '', }); class Reporter { constructor(config) { const agentInfo = getAgentInfo(); this.client = new RPClient(config.reporterOptions, agentInfo); this.testItemIds = new Map(); this.hooks = new Map(); this.config = config; this.currentTestFinishParams = getInitialTestFinishParams(); this.currentTestTempInfo = null; this.suitesStackTempInfo = []; this.suiteTestCaseIds = new Map(); // TODO: use a single Map for test info this.pendingTestsIds = []; // TODO: use a single Map for suite info this.suiteStatuses = new Map(); this.cucumberSteps = new Map(); } resetCurrentTestFinishParams() { this.currentTestFinishParams = getInitialTestFinishParams(); } runStart(launchObj) { const { tempId, promise } = this.client.startLaunch(launchObj); const { launch, isLaunchMergeRequired } = this.config.reporterOptions; if (isLaunchMergeRequired) { createMergeLaunchLockFile(launch, tempId); } promiseErrorHandler(promise, 'Fail to start launch'); this.tempLaunchId = tempId; } runEnd() { const basePromise = this.config.reporterOptions.launchId ? this.client.getPromiseFinishAllItems(this.tempLaunchId) : this.client.finishLaunch( this.tempLaunchId, Object.assign( { endTime: new Date().valueOf(), }, this.launchStatus && { status: this.launchStatus }, ), ).promise; const finishLaunchPromise = basePromise .then(() => { const { launch, isLaunchMergeRequired } = this.config.reporterOptions; if (isLaunchMergeRequired) { deleteMergeLaunchLockFile(launch, this.tempLaunchId); } }) .then(() => { const { parallel, autoMerge } = this.config.reporterOptions; if (!(parallel && autoMerge)) { return Promise.resolve(); } return mergeParallelLaunches(this.client, this.config); }); return promiseErrorHandler(finishLaunchPromise, 'Fail to finish launch'); } suiteStart(suite) { const parentId = suite.parentId && this.testItemIds.get(suite.parentId); const { tempId, promise } = this.client.startTestItem(suite, this.tempLaunchId, parentId); promiseErrorHandler(promise, 'Fail to start suite'); this.testItemIds.set(suite.id, tempId); this.suitesStackTempInfo.push({ tempId, startTime: suite.startTime }); } suiteEnd(suite) { const suiteId = this.testItemIds.get(suite.id); 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(); suiteTestCaseId && this.suiteTestCaseIds.delete(suite.title); suiteStatus && this.suiteStatuses.delete(suite.title); } testStart(test) { const parentId = this.testItemIds.get(test.parentId); const startTestObj = getTestStartObject(test); const { tempId, promise } = this.client.startTestItem( startTestObj, this.tempLaunchId, parentId, ); promiseErrorHandler(promise, 'Fail to start test'); this.testItemIds.set(test.id, tempId); this.currentTestTempInfo = { tempId, codeRef: test.codeRef, startTime: startTestObj.startTime, cucumberStepIds: new Set(), }; if (this.pendingTestsIds.includes(test.id)) { this.testEnd(test); this.pendingTestsIds = this.pendingTestsIds.filter((id) => id !== test.id); } } sendLogOnFinishFailedItem(test, tempTestId) { if (test.status === FAILED) { const sendFailedLogPromise = this.client.sendLog(tempTestId, { message: test.err.stack, level: logLevels.ERROR, time: new Date().valueOf(), }).promise; promiseErrorHandler(sendFailedLogPromise, 'Fail to save error log'); } } testEnd(test) { const testId = this.testItemIds.get(test.id); if (!testId) { return; } this.sendLogOnFinishFailedItem(test, testId); this.finishFailedStep(test); const testInfo = Object.assign({}, test, this.currentTestFinishParams); const finishTestItemPromise = this.client.finishTestItem( testId, getTestEndObject(testInfo, this.config.reporterOptions.skippedIssue), ).promise; promiseErrorHandler(finishTestItemPromise, 'Fail to finish test'); this.resetCurrentTestFinishParams(); this.currentTestTempInfo = null; this.testItemIds.delete(test.id); } testPending(test) { // if test has not been started, save test.id to finish in testStart(). // if testStarted() has been called, call testEnd() directly. if (this.testItemIds.get(test.id)) { this.testEnd(test); } else { this.pendingTestsIds.push(test.id); } } cucumberStepStart(data) { const { testStepId, pickleStep } = data; const parent = this.currentTestTempInfo; if (!parent) return; const keyword = cucumberKeywordMap[pickleStep.type]; const stepName = pickleStep.text; const codeRef = getCodeRef([stepName], parent.codeRef); const stepData = { name: keyword ? `${keyword} ${stepName}` : stepName, startTime: this.client.helpers.now(), type: entityType.STEP, codeRef, hasStats: false, }; const { tempId, promise } = this.client.startTestItem( stepData, this.tempLaunchId, parent.tempId, ); promiseErrorHandler(promise, 'Fail to start step'); this.cucumberSteps.set(testStepId, { tempId, tempParentId: parent.tempId, testStepId }); parent.cucumberStepIds.add(testStepId); } finishFailedStep(test) { if (test.status === FAILED) { const step = this.getCurrentCucumberStep(); if (!step) return; this.cucumberStepEnd({ testStepId: step.testStepId, testStepResult: { status: testItemStatuses.FAILED, message: test.err.stack, }, }); } } cucumberStepEnd(data) { const { testStepId, testStepResult = { status: testItemStatuses.PASSED } } = data; const step = this.cucumberSteps.get(testStepId); if (!step) return; if (testStepResult.status === testItemStatuses.FAILED) { this.sendLog(step.tempId, { time: this.client.helpers.now(), level: logLevels.ERROR, message: testStepResult.message, }); } this.client.finishTestItem(step.tempId, { status: testStepResult.status, endTime: this.client.helpers.now(), }); this.cucumberSteps.delete(testStepId); if (this.currentTestTempInfo) { this.currentTestTempInfo.cucumberStepIds.delete(testStepId); } } hookStart(hook) { const hookStartObject = getHookStartObject(hook); switch (hookStartObject.type) { case entityType.BEFORE_SUITE: hookStartObject.startTime = this.getCurrentSuiteInfo().startTime - 1; break; case entityType.BEFORE_METHOD: hookStartObject.startTime = this.currentTestTempInfo ? this.currentTestTempInfo.startTime - 1 : hookStartObject.startTime; break; default: break; } this.hooks.set(hook.id, hookStartObject); } hookEnd(hook) { const startedHook = this.hooks.get(hook.id); if (!startedHook) return; const { tempId, promise } = this.client.startTestItem( startedHook, this.tempLaunchId, this.testItemIds.get(hook.parentId), ); promiseErrorHandler(promise, 'Fail to start hook'); this.sendLogOnFinishFailedItem(hook, tempId); const finishHookPromise = this.client.finishTestItem(tempId, { status: hook.status, endTime: new Date().valueOf(), }).promise; this.hooks.delete(hook.id); promiseErrorHandler(finishHookPromise, 'Fail to finish hook'); } getCurrentSuiteInfo() { return this.suitesStackTempInfo.length ? this.suitesStackTempInfo[this.suitesStackTempInfo.length - 1] : undefined; } getCurrentSuiteId() { const currentSuiteInfo = this.getCurrentSuiteInfo(); return currentSuiteInfo && currentSuiteInfo.tempId; } getCurrentCucumberStep() { if (this.currentTestTempInfo && this.currentTestTempInfo.cucumberStepIds.size > 0) { const testStepId = Array.from(this.currentTestTempInfo.cucumberStepIds.values())[ this.currentTestTempInfo.cucumberStepIds.size - 1 ]; return this.cucumberSteps.get(testStepId); } return null; } getCurrentCucumberStepId() { const step = this.getCurrentCucumberStep(); return step && step.tempId; } sendLog(tempId, { level, message = '', file }) { return this.client.sendLog( tempId, { message, level, time: new Date().valueOf(), }, file, ).promise; } sendLogToCurrentItem(log) { const tempItemId = this.getCurrentCucumberStepId() || (this.currentTestTempInfo && this.currentTestTempInfo.tempId) || this.getCurrentSuiteId(); if (tempItemId) { const promise = this.sendLog(tempItemId, log); promiseErrorHandler(promise, 'Fail to send log to current item'); } } sendLaunchLog(log) { const promise = this.sendLog(this.tempLaunchId, log); promiseErrorHandler(promise, 'Fail to send launch log'); } addAttributes(attributes) { this.currentTestFinishParams.attributes = this.currentTestFinishParams.attributes.concat( attributes || [], ); } setDescription(description) { this.currentTestFinishParams.description = description; } setTestCaseId({ testCaseId, suiteTitle }) { if (suiteTitle) { this.suiteTestCaseIds.set(suiteTitle, testCaseId); } else { Object.assign(this.currentTestFinishParams, testCaseId && { testCaseId }); } } setTestItemStatus({ status, suiteTitle }) { if (suiteTitle) { this.suiteStatuses.set(suiteTitle, status); } else { Object.assign(this.currentTestFinishParams, status && { status }); } } setLaunchStatus({ status }) { this.launchStatus = status; } sendScreenshot(screenshotInfo, logMessage) { const tempItemId = this.currentTestTempInfo && this.currentTestTempInfo.tempId; const fileName = screenshotInfo.path; if (!fileName || !tempItemId) return; const level = fileName && fileName.includes('(failed)') ? logLevels.ERROR : logLevels.INFO; const file = getScreenshotAttachment(fileName); if (!file) return; const message = logMessage || `screenshot ${file.name}`; const sendScreenshotPromise = this.client.sendLog( tempItemId, { message, level, time: new Date(screenshotInfo.takenAt).valueOf(), }, file, ).promise; promiseErrorHandler(sendScreenshotPromise, 'Fail to save screenshot.'); } } module.exports = Reporter;