From 040acbfe943dcec74c39ff56c206ade107b6d99c Mon Sep 17 00:00:00 2001 From: Ilya Hancharyk Date: Mon, 20 Jan 2025 20:03:15 +0100 Subject: [PATCH 1/5] EPMRPP-95591 || Rewrite suites creation flow --- package.json | 2 +- src/call_order | 1 + src/constants/index.js | 1 + src/reporter.js | 155 ++++++++++++++++----------------------- src/utils/objectUtils.js | 2 +- 5 files changed, 69 insertions(+), 92 deletions(-) create mode 100644 src/call_order diff --git a/package.json b/package.json index 8fd2e14..3ce48d3 100755 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "scripts": { "lint": "eslint . --quiet", "format": "npm run lint -- --fix", - "test": "jest --detectOpenHandles --config ./jest.config.js", + "test": "jest --config ./jest.config.js", "test:coverage": "jest --coverage" }, "author": "ReportPortal.io", diff --git a/src/call_order b/src/call_order new file mode 100644 index 0000000..da6a6f4 --- /dev/null +++ b/src/call_order @@ -0,0 +1 @@ +onRunStart diff --git a/src/constants/index.js b/src/constants/index.js index 0ada2b6..df97a3c 100755 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -51,4 +51,5 @@ module.exports = { INFO: 'info', WARN: 'warn', }, + TEST_ITEM_TYPES: { SUITE: 'SUITE', TEST: 'TEST', STEP: 'STEP' }, }; diff --git a/src/reporter.js b/src/reporter.js index b3c506c..961e5d3 100644 --- a/src/reporter.js +++ b/src/reporter.js @@ -29,7 +29,7 @@ const { getFullTestName, getFullStepName, } = require('./utils/objectUtils'); -const { TEST_ITEM_STATUSES, LOG_LEVEL } = require('./constants'); +const { TEST_ITEM_STATUSES, LOG_LEVEL, TEST_ITEM_TYPES } = require('./constants'); const promiseErrorHandler = (promise) => { promise.catch((err) => { @@ -43,8 +43,8 @@ class JestReportPortal { this.reportOptions = getAgentOptions(getOptions.options(options)); this.client = new RPClient(this.reportOptions, agentInfo); this.tempSuiteIds = new Map(); - this.tempTestIds = new Map(); this.tempStepIds = new Map(); + // TODO: Remove and use `this.tempStepIds` instead. this.tempStepId = null; this.promises = []; @@ -60,18 +60,27 @@ class JestReportPortal { this.promises.push(promise); } + // FYI. Does not even call most of the time. Cannot be used for suites handling. + onTestStart() {} + + _startSuites(suiteTitles, filePath, startTime) { + if (suiteTitles.length > 0) { + suiteTitles.reduce((suitePath, suiteTitle) => { + const fullSuiteName = suitePath ? `${suitePath}/${suiteTitle}` : suiteTitle; + const codeRef = getCodeRef(filePath, fullSuiteName); + + this._startSuite(suiteTitle, suitePath, codeRef, startTime); + return fullSuiteName; + }, ''); + } + } + // Not called for `skipped` and `todo` specs onTestCaseStart(test, testCaseStartInfo) { - if (testCaseStartInfo.ancestorTitles.length > 0) { - this._startSuite(testCaseStartInfo.ancestorTitles[0], test.path); - } - if (testCaseStartInfo.ancestorTitles.length > 1) { - this._startTest(testCaseStartInfo, test.path); - } + this._startSuites(testCaseStartInfo.ancestorTitles, test.path, testCaseStartInfo.startedAt); const isRetried = !!this.tempStepIds.get(getFullStepName(testCaseStartInfo)); - - this._startStep(testCaseStartInfo, isRetried, test.path); + this._startStep(testCaseStartInfo, test.path, isRetried); } // Not called for `skipped` and `todo` specs @@ -79,52 +88,36 @@ class JestReportPortal { this._finishStep(testCaseStartInfo); } - // Handling `skipped` tests and their ancestors onTestResult(test, testResult) { - let suiteDuration = 0; - let testDuration = 0; - + // Handling `skipped` tests and their ancestors const skippedTests = testResult.testResults.filter( (t) => t.status === TEST_ITEM_STATUSES.SKIPPED, ); - for (let index = 0; index < skippedTests.length; index++) { - const currentTest = skippedTests[index]; + skippedTests.forEach((testCaseInfo) => { + const testCase = { startedAt: new Date().valueOf(), ...testCaseInfo }; + this._startSuites(testCaseInfo.ancestorTitles, test.path, testCase.startedAt); - suiteDuration += currentTest.duration; - if (currentTest.ancestorTitles.length !== 1) { - testDuration += currentTest.duration; - } - } + if (testCase.invocations) { + const isRetried = testCase.invocations > 1; - skippedTests.forEach((t) => { - if (t.ancestorTitles.length > 0) { - this._startSuite(t.ancestorTitles[0], test.path, suiteDuration); - } - if (t.ancestorTitles.length > 1) { - this._startTest(t, test.path, testDuration); - } - - if (!t.invocations) { - this._startStep(t, false, test.path); - this._finishStep(t); - return; + for (let i = 0; i < testCase.invocations; i++) { + this._startStep(testCase, test.path, isRetried); + this._finishStep(testCase); + } + } else { + this._startStep(testCase, test.path); + this._finishStep(testCase); } + }); - for (let i = 0; i < t.invocations; i++) { - const isRetried = t.invocations !== 1; + const suiteFilePathToFinish = getCodeRef(testResult.testFilePath); - this._startStep(t, isRetried, test.path); - this._finishStep(t); + this.tempSuiteIds.forEach((suiteTempId, suiteFullName) => { + if (suiteFullName.includes(suiteFilePathToFinish)) { + this._finishSuite(suiteTempId, suiteFullName); } }); - - this.tempTestIds.forEach((tempTestId, key) => { - this._finishTest(tempTestId, key); - }); - this.tempSuiteIds.forEach((tempSuiteId, key) => { - this._finishSuite(tempSuiteId, key); - }); } async onRunComplete() { @@ -138,54 +131,44 @@ class JestReportPortal { await promise; } - _startSuite(suiteName, path, suiteDuration) { - if (this.tempSuiteIds.get(suiteName)) { - return; - } - const codeRef = getCodeRef(path, suiteName); - const { tempId, promise } = this.client.startTestItem( - getSuiteStartObject(suiteName, codeRef, suiteDuration), - this.tempLaunchId, - ); - - this.tempSuiteIds.set(suiteName, tempId); - promiseErrorHandler(promise); - this.promises.push(promise); - } - - _startTest(test, testPath, testDuration) { - if (this.tempTestIds.get(test.ancestorTitles.join('/'))) { + _startSuite(title, suitePath, codeRef, startTime = new Date().valueOf()) { + if (this.tempSuiteIds.get(codeRef)) { return; } - const tempSuiteId = this.tempSuiteIds.get(test.ancestorTitles[0]); - const fullTestName = getFullTestName(test); - const codeRef = getCodeRef(testPath, fullTestName); - const testStartObj = getTestStartObject( - test.ancestorTitles[test.ancestorTitles.length - 1], + const testStartObj = { + type: TEST_ITEM_TYPES.SUITE, + name: title, codeRef, - testDuration, - ); - const parentId = - this.tempTestIds.get(test.ancestorTitles.slice(0, -1).join('/')) || tempSuiteId; + startTime, + }; + const parentId = this.tempSuiteIds.get(suitePath); const { tempId, promise } = this.client.startTestItem( testStartObj, this.tempLaunchId, parentId, ); - this.tempTestIds.set(fullTestName, tempId); + this.tempSuiteIds.set(codeRef, tempId); promiseErrorHandler(promise); this.promises.push(promise); } - _startStep(test, isRetried, testPath) { - const tempSuiteId = this.tempSuiteIds.get(test.ancestorTitles[0]); + _startStep(test, testPath, isRetried = false) { const fullStepName = getFullStepName(test); const codeRef = getCodeRef(testPath, fullStepName); - const stepDuration = test.duration; - const stepStartObj = getStepStartObject(test.title, isRetried, codeRef, stepDuration); - const parentId = this.tempTestIds.get(test.ancestorTitles.join('/')) || tempSuiteId; + const stepStartObj = { + type: TEST_ITEM_TYPES.STEP, + name: test.title, + codeRef, + startTime: test.startedAt, + retry: isRetried, + }; + + const parentFullName = test.ancestorTitles.join('/'); + const parentCodeRef = getCodeRef(testPath, parentFullName); + const parentId = this.tempSuiteIds.get(parentCodeRef); + const { tempId, promise } = this.client.startTestItem( stepStartObj, this.tempLaunchId, @@ -237,7 +220,7 @@ class JestReportPortal { : `\`\`\`error\n${stripAnsi(failureMessage)}\n\`\`\``; const finishTestObj = { status, ...(description && { description }) }; - this.sendLog({ message: failureMessage, level: LOG_LEVEL.ERROR, tempStepId }); + this._sendLog({ message: failureMessage, level: LOG_LEVEL.ERROR, tempStepId }); const { promise } = this.client.finishTestItem(tempStepId, finishTestObj); @@ -245,7 +228,7 @@ class JestReportPortal { this.promises.push(promise); } - sendLog({ level = LOG_LEVEL.INFO, message = '', file, time, tempStepId }) { + _sendLog({ level = LOG_LEVEL.INFO, message = '', file, time, tempStepId }) { const newMessage = stripAnsi(message); const { promise } = this.client.sendLog( tempStepId === undefined ? this.tempStepId : tempStepId, @@ -274,21 +257,13 @@ class JestReportPortal { this.promises.push(promise); } - _finishTest(tempTestId, key) { - if (!tempTestId) return; + _finishSuite(tempTestId, key) { + if (!tempTestId) { + return; + } const { promise } = this.client.finishTestItem(tempTestId, {}); - this.tempTestIds.delete(key); - promiseErrorHandler(promise); - this.promises.push(promise); - } - - _finishSuite(tempSuiteId, key) { - if (!tempSuiteId) return; - - const { promise } = this.client.finishTestItem(tempSuiteId, {}); - this.tempSuiteIds.delete(key); promiseErrorHandler(promise); this.promises.push(promise); diff --git a/src/utils/objectUtils.js b/src/utils/objectUtils.js index 976c125..0584f78 100644 --- a/src/utils/objectUtils.js +++ b/src/utils/objectUtils.js @@ -127,7 +127,7 @@ const getSystemAttributes = (skippedIssue) => { return systemAttr; }; -const getCodeRef = (testPath, title) => { +const getCodeRef = (testPath, title = '') => { const testFileDir = path .parse(path.normalize(path.relative(process.cwd(), testPath))) .dir.replace(new RegExp('\\'.concat(path.sep), 'g'), '/'); From 90667a526af697a0f8826a22253e5dd44cb64f27 Mon Sep 17 00:00:00 2001 From: Ilya Hancharyk Date: Tue, 21 Jan 2025 15:17:02 +0100 Subject: [PATCH 2/5] EPMRPP-95591 || Cleanup project files --- __tests__/objectUtils.spec.js | 65 +---------------------------------- src/call_order | 1 - src/reporter.js | 40 +++++++++------------ src/utils/objectUtils.js | 29 ---------------- 4 files changed, 18 insertions(+), 117 deletions(-) delete mode 100644 src/call_order diff --git a/__tests__/objectUtils.spec.js b/__tests__/objectUtils.spec.js index 094eb5b..7bb037a 100644 --- a/__tests__/objectUtils.spec.js +++ b/__tests__/objectUtils.spec.js @@ -1,5 +1,5 @@ /* - * Copyright 2020 EPAM Systems + * Copyright 2025 EPAM Systems * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,12 +19,8 @@ const path = require('path'); const { getAgentOptions, getStartLaunchObject, - getSuiteStartObject, - getTestStartObject, - getStepStartObject, getAgentInfo, getCodeRef, - getFullTestName, getFullStepName, getSystemAttributes, } = require('../src/utils/objectUtils'); @@ -117,52 +113,6 @@ describe('Object Utils script', () => { }); }); - describe('getStepStartObject', () => { - test('should return step start object with correct values', () => { - const expectedStepStartObject = { - type: 'STEP', - name: 'step title', - retry: true, - startTime: new Date().valueOf() - duration, - }; - - const stepStartObject = getStepStartObject('step title', true, undefined, duration); - - expect(stepStartObject).toBeDefined(); - expect(stepStartObject).toEqual(expectedStepStartObject); - }); - }); - - describe('getTestStartObject', () => { - test('should return test start object with correct values', () => { - const expectedTestStartObject = { - type: 'TEST', - name: 'test title', - startTime: new Date().valueOf() - duration, - }; - - const testStartObject = getTestStartObject('test title', undefined, duration); - - expect(testStartObject).toBeDefined(); - expect(testStartObject).toEqual(expectedTestStartObject); - }); - }); - - describe('getSuiteStartObject', () => { - test('should return suite start object with correct values', () => { - const expectedSuiteStartObject = { - type: 'SUITE', - name: 'suite name', - startTime: new Date().valueOf() - duration, - }; - - const suiteStartObject = getSuiteStartObject('suite name', undefined, duration); - - expect(suiteStartObject).toBeDefined(); - expect(suiteStartObject).toEqual(expectedSuiteStartObject); - }); - }); - describe('getAgentOptions', () => { test( 'should return agent options object with correct values, some parameters taken from' + @@ -301,19 +251,6 @@ describe('Object Utils script', () => { }); }); - describe('getFullTestName', () => { - test('should return correct full test name', () => { - const mockedTest = { - ancestorTitles: ['rootDescribe', 'parentDescribe', 'testTitle'], - }; - const expectedFullTestName = 'rootDescribe/parentDescribe/testTitle'; - - const fullTestName = getFullTestName(mockedTest); - - expect(fullTestName).toEqual(expectedFullTestName); - }); - }); - describe('getFullStepName', () => { test('should return correct full step name', () => { const mockedTest = { diff --git a/src/call_order b/src/call_order deleted file mode 100644 index da6a6f4..0000000 --- a/src/call_order +++ /dev/null @@ -1 +0,0 @@ -onRunStart diff --git a/src/reporter.js b/src/reporter.js index 961e5d3..fd0a6a2 100644 --- a/src/reporter.js +++ b/src/reporter.js @@ -1,5 +1,5 @@ /* - * Copyright 2024 EPAM Systems + * Copyright 2025 EPAM Systems * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,13 +20,9 @@ const getOptions = require('./utils/getOptions'); const ReportingApi = require('./reportingApi'); const { getAgentOptions, - getSuiteStartObject, getStartLaunchObject, - getTestStartObject, - getStepStartObject, getAgentInfo, getCodeRef, - getFullTestName, getFullStepName, } = require('./utils/objectUtils'); const { TEST_ITEM_STATUSES, LOG_LEVEL, TEST_ITEM_TYPES } = require('./constants'); @@ -64,15 +60,13 @@ class JestReportPortal { onTestStart() {} _startSuites(suiteTitles, filePath, startTime) { - if (suiteTitles.length > 0) { - suiteTitles.reduce((suitePath, suiteTitle) => { - const fullSuiteName = suitePath ? `${suitePath}/${suiteTitle}` : suiteTitle; - const codeRef = getCodeRef(filePath, fullSuiteName); - - this._startSuite(suiteTitle, suitePath, codeRef, startTime); - return fullSuiteName; - }, ''); - } + suiteTitles.reduce((suitePath, suiteTitle) => { + const fullSuiteName = suitePath ? `${suitePath}/${suiteTitle}` : suiteTitle; + const codeRef = getCodeRef(filePath, fullSuiteName); + + this._startSuite(suiteTitle, suitePath, codeRef, startTime); + return fullSuiteName; + }, ''); } // Not called for `skipped` and `todo` specs @@ -95,19 +89,19 @@ class JestReportPortal { ); skippedTests.forEach((testCaseInfo) => { - const testCase = { startedAt: new Date().valueOf(), ...testCaseInfo }; - this._startSuites(testCaseInfo.ancestorTitles, test.path, testCase.startedAt); + const testCaseWithStartTime = { startedAt: new Date().valueOf(), ...testCaseInfo }; + this._startSuites(testCaseInfo.ancestorTitles, test.path, testCaseWithStartTime.startedAt); - if (testCase.invocations) { - const isRetried = testCase.invocations > 1; + if (testCaseWithStartTime.invocations) { + for (let i = 0; i < testCaseWithStartTime.invocations; i++) { + const isRetried = i > 1; - for (let i = 0; i < testCase.invocations; i++) { - this._startStep(testCase, test.path, isRetried); - this._finishStep(testCase); + this._startStep(testCaseWithStartTime, test.path, isRetried); + this._finishStep(testCaseWithStartTime); } } else { - this._startStep(testCase, test.path); - this._finishStep(testCase); + this._startStep(testCaseWithStartTime, test.path); + this._finishStep(testCaseWithStartTime); } }); diff --git a/src/utils/objectUtils.js b/src/utils/objectUtils.js index 0584f78..d6bf58d 100644 --- a/src/utils/objectUtils.js +++ b/src/utils/objectUtils.js @@ -20,7 +20,6 @@ const pjson = require('../../package.json'); const PJSON_VERSION = pjson.version; const PJSON_NAME = pjson.name; -const entityType = { SUITE: 'SUITE', TEST: 'TEST', STEP: 'STEP' }; const getStartLaunchObject = (options = {}) => { const systemAttr = getSystemAttributes(options.skippedIssue); @@ -36,28 +35,6 @@ const getStartLaunchObject = (options = {}) => { }; }; -const getStepStartObject = (stepTitle, isRetried, codeRef, stepDuration = 0) => ({ - type: entityType.STEP, - name: stepTitle, - codeRef, - startTime: new Date().valueOf() - stepDuration, - retry: isRetried, -}); - -const getTestStartObject = (testTitle, codeRef, testDuration = 0) => ({ - type: entityType.TEST, - name: testTitle, - codeRef, - startTime: new Date().valueOf() - testDuration, -}); - -const getSuiteStartObject = (suiteName, codeRef, suiteDuration = 0) => ({ - type: entityType.SUITE, - name: suiteName, - codeRef, - startTime: new Date().valueOf() - suiteDuration, -}); - const getAgentOptions = (options = {}) => { const env_attributes = process.env.RP_ATTRIBUTES === undefined @@ -137,19 +114,13 @@ const getCodeRef = (testPath, title = '') => { return `${testFileDir}${separator}${testFile.base}/${title}`; }; -const getFullTestName = (test) => `${test.ancestorTitles.join('/')}`; - const getFullStepName = (test) => `${test.ancestorTitles.join('/')}/${test.title}`; module.exports = { getAgentOptions, getStartLaunchObject, - getSuiteStartObject, - getTestStartObject, - getStepStartObject, getAgentInfo, getCodeRef, - getFullTestName, getFullStepName, getSystemAttributes, }; From d29f2f029b76881ff2c4a3173667c5f96cde4ca7 Mon Sep 17 00:00:00 2001 From: Ilya Hancharyk Date: Thu, 30 Jan 2025 17:35:48 +0100 Subject: [PATCH 3/5] EPMRPP-95591 || Update tests for onTestResult --- __tests__/mocks/data.js | 15 +- __tests__/{index.spec.js => reporter.spec.js} | 210 ++++++++++-------- src/reporter.js | 2 +- 3 files changed, 125 insertions(+), 102 deletions(-) rename __tests__/{index.spec.js => reporter.spec.js} (75%) diff --git a/__tests__/mocks/data.js b/__tests__/mocks/data.js index 5f5a17d..043e2b8 100644 --- a/__tests__/mocks/data.js +++ b/__tests__/mocks/data.js @@ -3,27 +3,32 @@ const { TEST_ITEM_STATUSES } = require('../../src/constants'); const mockDate = 1712900000000; const duration = 5; +const testFilePath = `C:${path.sep}testProject${path.sep}example.js`; const failedTestResult = { title: 'failed title', status: TEST_ITEM_STATUSES.FAILED, ancestorTitles: ['Failed suite name', 'Failed test name'], failureMessages: ['error message'], invocations: 1, - duration, + startedAt: mockDate, }; const skippedTestResult = { title: 'skipped title', status: TEST_ITEM_STATUSES.SKIPPED, ancestorTitles: ['Skipped suite name', 'Skipped test name'], failureMessages: [], - invocations: 1, - duration, + invocations: 2, }; const testResult = { + testResults: [failedTestResult], + testFilePath, +}; +const testResultWithSkipped = { testResults: [failedTestResult, skippedTestResult], + testFilePath, }; const testObj = { - path: `C:${path.sep}testProject${path.sep}example.js`, + path: testFilePath, }; const mockFile = { name: 'test_img_name', type: 'image/png', content: 'content' }; @@ -33,6 +38,8 @@ module.exports = { failedTestResult, skippedTestResult, testResult, + testResultWithSkipped, testObj, mockFile, + testFilePath, }; diff --git a/__tests__/index.spec.js b/__tests__/reporter.spec.js similarity index 75% rename from __tests__/index.spec.js rename to __tests__/reporter.spec.js index a880d52..12e6f9c 100644 --- a/__tests__/index.spec.js +++ b/__tests__/reporter.spec.js @@ -24,14 +24,17 @@ const { duration, skippedTestResult, testResult, + testResultWithSkipped, testObj, mockDate, mockFile, + testFilePath, } = require('./mocks/data'); const GLOBAL_CONFIG = {}; const options = getOptions(); const currentDate = new Date(); +const currentDateInMs = currentDate.valueOf(); const RealDate = Date; const systemAttr = { key: 'agent', @@ -60,7 +63,9 @@ describe('index script', () => { afterEach(() => { jest.clearAllMocks(); reporter.tempLaunchId = ''; + // TODO: remove reporter.tempSuiteIds = new Map(); + // TODO: change to tempSuiteIds reporter.tempTestIds = new Map(); reporter.tempStepId = null; global.Date = RealDate; @@ -97,118 +102,129 @@ describe('index script', () => { }); describe('onTestResult', () => { - test( - 'startSuite, startTest, spyStartStep, finishTest, finishSuite, spyFinishStep' + - 'should be called with parameters', - () => { - const spyStartSuite = jest.spyOn(reporter, '_startSuite'); - const spyStartTest = jest.spyOn(reporter, '_startTest'); - const spyStartStep = jest.spyOn(reporter, '_startStep'); - const spyFinishTest = jest.spyOn(reporter, '_finishTest'); - const spyFinishSuite = jest.spyOn(reporter, '_finishSuite'); - const spyFinishStep = jest.spyOn(reporter, '_finishStep'); - reporter.tempTestIds = new Map([['tempTestId', '1234']]); - reporter.tempSuiteIds = new Map([['tempSuiteId', '4321']]); - - reporter.onTestResult(testObj, testResult); - - expect(spyStartSuite).toHaveBeenCalledWith( - skippedTestResult.ancestorTitles[0], - testObj.path, - duration, - ); - expect(spyStartTest).toHaveBeenCalledWith(skippedTestResult, testObj.path, duration); - expect(spyStartStep).toHaveBeenCalledWith(skippedTestResult, false, testObj.path); - expect(spyFinishStep).toHaveBeenCalledWith(skippedTestResult); - expect(spyFinishTest).toHaveBeenCalledWith('1234', 'tempTestId'); - expect(spyFinishSuite).toHaveBeenCalledWith('4321', 'tempSuiteId'); - }, - ); + test('should call start suites method for skipped tests if any. startedAt should be fell back', () => { + const spyStartSuites = jest.spyOn(reporter, '_startSuites'); - test( - "startStep, finishStep should be called one times with second parameter 'false' if there" + - ' are no retries', - () => { - const spyStartStep = jest.spyOn(reporter, '_startStep'); - const spyFinishStep = jest.spyOn(reporter, '_finishStep'); + reporter.onTestResult(testObj, testResultWithSkipped); - reporter.onTestResult(testObj, testResult); + expect(spyStartSuites).toHaveBeenCalledWith( + skippedTestResult.ancestorTitles, + testObj.path, + currentDateInMs, + ); + }); + test('should not start any suites in case of no skipped tests', () => { + const spyStartSuites = jest.spyOn(reporter, '_startSuites'); - expect(spyStartStep).toHaveBeenCalledWith(skippedTestResult, false, testObj.path); - expect(spyFinishStep).toHaveBeenCalledWith(skippedTestResult); - expect(spyStartStep).toHaveBeenCalledTimes(1); - expect(spyFinishStep).toHaveBeenCalledTimes(1); - }, - ); + reporter.onTestResult(testObj, testResult); - test( - "startStep, finishStep should be called two times with second parameter 'true' if there" + - ' are retries', - () => { - const spyStartStep = jest.spyOn(reporter, '_startStep'); - const spyFinishStep = jest.spyOn(reporter, '_finishStep'); - - const testResult = { - testResults: [ - { - title: 'Title', - status: TEST_ITEM_STATUSES.SKIPPED, - ancestorTitles: ['Suite name', 'Test name'], - failureMessages: [], - invocations: 2, - }, - ], - }; + expect(spyStartSuites).toHaveBeenCalledTimes(0); + }); - reporter.onTestResult(testObj, testResult); + test('should start and finish retries in case of any invocations of skipped tests', () => { + const spyStartStep = jest.spyOn(reporter, '_startStep'); + const spyFinishStep = jest.spyOn(reporter, '_finishStep'); - expect(spyStartStep).toHaveBeenCalledWith(testResult.testResults[0], true, testObj.path); - expect(spyFinishStep).toHaveBeenCalledWith(testResult.testResults[0]); - expect(spyStartStep).toHaveBeenCalledTimes(2); - expect(spyFinishStep).toHaveBeenCalledTimes(2); - }, - ); + reporter.onTestResult(testObj, testResultWithSkipped); + const skippedTestResultWithStartedAt = { startedAt: currentDateInMs, ...skippedTestResult }; - test( - "startStep, finishStep should be called ones with second parameter 'false' if there is" + - ' no invocations', - () => { - const spyStartStep = jest.spyOn(reporter, '_startStep'); - const spyFinishStep = jest.spyOn(reporter, '_finishStep'); - const testResult = { - testResults: [ - { - title: 'Title', - status: TEST_ITEM_STATUSES.SKIPPED, - ancestorTitles: ['Suite name', 'Test name'], - failureMessages: [], - }, - ], - }; + expect(spyStartStep).toHaveBeenNthCalledWith( + 1, + skippedTestResultWithStartedAt, + testObj.path, + false, + ); + expect(spyStartStep).toHaveBeenNthCalledWith( + 2, + skippedTestResultWithStartedAt, + testObj.path, + true, + ); + expect(spyFinishStep).toHaveBeenCalledWith(skippedTestResultWithStartedAt); + expect(spyStartStep).toHaveBeenCalledTimes(2); + expect(spyFinishStep).toHaveBeenCalledTimes(2); + }); - reporter.onTestResult(testObj, testResult); + test('should start and finish just skipped test in case of no or empty invocations', () => { + const spyStartStep = jest.spyOn(reporter, '_startStep'); + const spyFinishStep = jest.spyOn(reporter, '_finishStep'); - expect(spyStartStep).toHaveBeenCalledWith(testResult.testResults[0], false, testObj.path); - expect(spyFinishStep).toHaveBeenCalledWith(testResult.testResults[0]); - }, - ); + const { invocations, ...skippedTestResultWithoutInvocations } = skippedTestResult; - test('startTest should not be called if there are no tests', () => { - const spyStartTest = jest.spyOn(reporter, '_startTest'); const testResult = { - testResults: [ - { - title: 'Title', - status: 'failed', - ancestorTitles: ['Suite name'], - failureMessages: 'error message', - }, - ], + testResults: [skippedTestResultWithoutInvocations], + testFilePath, + }; + + reporter.onTestResult(testObj, testResult); + + const skippedTestResultWithStartedAt = { + startedAt: currentDateInMs, + ...skippedTestResultWithoutInvocations, }; + expect(spyStartStep).toHaveBeenCalledWith(skippedTestResultWithStartedAt, testObj.path); + expect(spyFinishStep).toHaveBeenCalledWith(skippedTestResultWithStartedAt); + expect(spyStartStep).toHaveBeenCalledTimes(1); + expect(spyFinishStep).toHaveBeenCalledTimes(1); + }); + + /* + should finish all suites that belongs to the file where test is located + should finish all suites that belongs to the file where test is located even if no skipped tests + */ + test('should finish all suites that belongs to the file where test is located', () => { + const spyFinishSuite = jest.spyOn(reporter, '_finishSuite'); + reporter.tempSuiteIds = new Map([ + ['C:/testProject/example.js/Failed suite name', 'startTestItem'], + ['C:/testProject/example.js/Failed suite name/Failed test name', 'startTestItem'], + ]); + + reporter.onTestResult(testObj, testResultWithSkipped); + + expect(spyFinishSuite).toHaveBeenCalledTimes(4); + expect(spyFinishSuite).toHaveBeenNthCalledWith( + 1, + 'startTestItem', + 'C:/testProject/example.js/Failed suite name', + ); + expect(spyFinishSuite).toHaveBeenNthCalledWith( + 2, + 'startTestItem', + 'C:/testProject/example.js/Failed suite name/Failed test name', + ); + expect(spyFinishSuite).toHaveBeenNthCalledWith( + 3, + 'startTestItem', + 'C:/testProject/example.js/Skipped suite name', + ); + expect(spyFinishSuite).toHaveBeenNthCalledWith( + 4, + 'startTestItem', + 'C:/testProject/example.js/Skipped suite name/Skipped test name', + ); + }); + + test('should finish all suites that belongs to the file even if there are no skipped tests', () => { + const spyFinishSuite = jest.spyOn(reporter, '_finishSuite'); + reporter.tempSuiteIds = new Map([ + ['C:/testProject/example.js/Failed suite name', 'startTestItem'], + ['C:/testProject/example.js/Failed suite name/Failed test name', 'startTestItem'], + ]); + reporter.onTestResult(testObj, testResult); - expect(spyStartTest).not.toHaveBeenCalled(); + expect(spyFinishSuite).toHaveBeenCalledTimes(2); + expect(spyFinishSuite).toHaveBeenNthCalledWith( + 1, + 'startTestItem', + 'C:/testProject/example.js/Failed suite name', + ); + expect(spyFinishSuite).toHaveBeenNthCalledWith( + 2, + 'startTestItem', + 'C:/testProject/example.js/Failed suite name/Failed test name', + ); }); }); diff --git a/src/reporter.js b/src/reporter.js index fd0a6a2..fd22bce 100644 --- a/src/reporter.js +++ b/src/reporter.js @@ -94,7 +94,7 @@ class JestReportPortal { if (testCaseWithStartTime.invocations) { for (let i = 0; i < testCaseWithStartTime.invocations; i++) { - const isRetried = i > 1; + const isRetried = i > 0; this._startStep(testCaseWithStartTime, test.path, isRetried); this._finishStep(testCaseWithStartTime); From a70c4c5bf1f5d3cf81122c347eea7d9578f576ea Mon Sep 17 00:00:00 2001 From: Ilya Hancharyk Date: Thu, 30 Jan 2025 18:22:33 +0100 Subject: [PATCH 4/5] EPMRPP-95591 || Update tests for _finishSuite and _startStep methods --- __tests__/reporter.spec.js | 185 +++++++++++++------------------------ package-lock.json | 2 +- src/reporter.js | 11 ++- 3 files changed, 71 insertions(+), 127 deletions(-) diff --git a/__tests__/reporter.spec.js b/__tests__/reporter.spec.js index 12e6f9c..083df40 100644 --- a/__tests__/reporter.spec.js +++ b/__tests__/reporter.spec.js @@ -18,7 +18,7 @@ const path = require('path'); const { getOptions, RPClient } = require('./mocks/reportportal-client.mock'); const JestReportPortal = require('../src'); -const { TEST_ITEM_STATUSES, LOG_LEVEL } = require('../src/constants'); +const { TEST_ITEM_STATUSES, LOG_LEVEL, TEST_ITEM_TYPES } = require('../src/constants'); const pjson = require('../package.json'); const { duration, @@ -42,7 +42,7 @@ const systemAttr = { system: true, }; -describe('index script', () => { +describe('Reporter', () => { /** * @type {JestReportPortal} */ @@ -63,10 +63,8 @@ describe('index script', () => { afterEach(() => { jest.clearAllMocks(); reporter.tempLaunchId = ''; - // TODO: remove + reporter.tempStepIds = new Map(); reporter.tempSuiteIds = new Map(); - // TODO: change to tempSuiteIds - reporter.tempTestIds = new Map(); reporter.tempStepId = null; global.Date = RealDate; }); @@ -169,10 +167,6 @@ describe('index script', () => { expect(spyFinishStep).toHaveBeenCalledTimes(1); }); - /* - should finish all suites that belongs to the file where test is located - should finish all suites that belongs to the file where test is located even if no skipped tests - */ test('should finish all suites that belongs to the file where test is located', () => { const spyFinishSuite = jest.spyOn(reporter, '_finishSuite'); reporter.tempSuiteIds = new Map([ @@ -239,138 +233,102 @@ describe('index script', () => { }); describe('_startSuite', () => { - test( - "startTestItem should be called with parameters if tempSuiteIds doesn't contain this suite," + - 'tempSuiteId should be defined', - () => { - jest.spyOn(process, 'cwd').mockImplementation(() => `C:${path.sep}testProject`); - const expectedStartTestItemParameter = { - type: 'SUITE', - name: 'suite name', - codeRef: 'example.js/suite name', - startTime: new Date().valueOf() - duration, - }; - const expectedTempSuiteIds = new Map([['suite name', 'startTestItem']]); - reporter.tempLaunchId = 'tempLaunchId'; - - reporter._startSuite('suite name', testObj.path, duration); + test('startTestItem should be called with parameters to start suite with parent', () => { + const parentCodeRef = 'example.js/Parent suite name'; + const suiteCodeRef = 'example.js/Parent suite name/Suite name'; - expect(reporter.client.startTestItem).toHaveBeenCalledWith( - expectedStartTestItemParameter, - 'tempLaunchId', - ); - expect(reporter.tempSuiteIds).toEqual(expectedTempSuiteIds); - }, - ); + reporter.tempLaunchId = 'tempLaunchId'; + reporter.tempSuiteIds = new Map([[parentCodeRef, 'startTestItem']]); - test('startTestItem should not be called with parameters if tempSuiteIds contains this suite', () => { - reporter.tempSuiteIds = new Map([['suite name', 'startTestItem']]); + reporter._startSuite('Suite name', suiteCodeRef, parentCodeRef, currentDateInMs); - reporter._startSuite('suite name', testObj.path); + const expectedStartSuiteObj = { + type: TEST_ITEM_TYPES.SUITE, + name: 'Suite name', + codeRef: suiteCodeRef, + startTime: currentDateInMs, + }; + const expectedTempSuiteIds = new Map([ + [parentCodeRef, 'startTestItem'], + [suiteCodeRef, 'startTestItem'], + ]); - expect(reporter.client.startTestItem).not.toHaveBeenCalled(); + expect(reporter.client.startTestItem).toHaveBeenCalledWith( + expectedStartSuiteObj, + 'tempLaunchId', + 'startTestItem', + ); + expect(reporter.tempSuiteIds).toEqual(expectedTempSuiteIds); }); - }); - describe('_startTest', () => { - test( - "startTestItem should be called with parameters if tempTestIds doesn't contain this suite," + - 'tempSuiteId should be defined', - () => { - jest.spyOn(process, 'cwd').mockImplementation(() => `C:${path.sep}testProject`); - const expectedStartTestItemParameter = { - type: 'TEST', - name: 'Test name', - codeRef: 'example.js/Suite name/Test name', - startTime: new Date().valueOf() - duration, - }; - const expectedTempTestIds = new Map([['Suite name/Test name', 'startTestItem']]); - reporter.tempLaunchId = 'tempLaunchId'; - reporter.tempSuiteIds = new Map([['Suite name', 'suiteId']]); - - reporter._startTest( - { ancestorTitles: ['Suite name', 'Test name'] }, - testObj.path, - duration, - ); + test('startTestItem should be called with parameters to start suite without parent', () => { + const suiteCodeRef = 'example.js/Suite name'; - expect(reporter.client.startTestItem).toHaveBeenCalledWith( - expectedStartTestItemParameter, - 'tempLaunchId', - 'suiteId', - ); - expect(reporter.tempTestIds).toEqual(expectedTempTestIds); - }, - ); + reporter.tempLaunchId = 'tempLaunchId'; + + reporter._startSuite('Suite name', suiteCodeRef, 'example.js', currentDateInMs); + + const expectedStartSuiteObj = { + type: TEST_ITEM_TYPES.SUITE, + name: 'Suite name', + codeRef: suiteCodeRef, + startTime: currentDateInMs, + }; + const expectedTempSuiteIds = new Map([[suiteCodeRef, 'startTestItem']]); + + expect(reporter.client.startTestItem).toHaveBeenCalledWith( + expectedStartSuiteObj, + 'tempLaunchId', + undefined, + ); + expect(reporter.tempSuiteIds).toEqual(expectedTempSuiteIds); + }); - test('startTestItem should not be called with parameters if tempTestIds contains this test', () => { - reporter.tempTestIds = new Map([['Suite name/Test name', 'startTestItem']]); + test('startTestItem should not be called if suite is already started', () => { + const suiteCodeRef = 'example.js/Suite name'; + reporter.tempSuiteIds = new Map([[suiteCodeRef, 'startTestItem']]); - reporter._startTest({ ancestorTitles: ['Suite name', 'Test name'] }, testObj.path); + reporter._startSuite('Suite name', suiteCodeRef, 'example.js', currentDateInMs); expect(reporter.client.startTestItem).not.toHaveBeenCalled(); }); }); describe('_startStep', () => { - test('startTestItem should be called with parameters with tempTestId, tempStepId should be defined', () => { + test('startTestItem should be called with parameters to start step', () => { jest.spyOn(process, 'cwd').mockImplementation(() => `C:${path.sep}testProject`); - const expectedStartStepItemParameter = { - type: 'STEP', - name: 'Step', - codeRef: 'example.js/Suite/Test/Step', - retry: true, - startTime: new Date().valueOf() - duration, - }; reporter.tempLaunchId = 'tempLaunchId'; - reporter.tempTestIds = new Map([['Suite/Test', 'tempTestId']]); + reporter.tempSuiteIds = new Map([['example.js/Suite', 'startTestItem']]); reporter._startStep( - { title: 'Step', ancestorTitles: ['Suite', 'Test'], duration }, - true, + { title: 'Step', ancestorTitles: ['Suite'], startedAt: currentDateInMs }, testObj.path, ); - expect(reporter.client.startTestItem).toHaveBeenCalledWith( - expectedStartStepItemParameter, - 'tempLaunchId', - 'tempTestId', - ); - expect(reporter.tempStepId).toEqual('startTestItem'); - }); - - test('startTestItem should be called with parameters with tempSuiteId, tempStepId should be defined', () => { - jest.spyOn(process, 'cwd').mockImplementation(() => `C:${path.sep}testProject`); const expectedStartStepItemParameter = { - type: 'STEP', + type: TEST_ITEM_TYPES.STEP, name: 'Step', codeRef: 'example.js/Suite/Step', - retry: true, - startTime: new Date().valueOf() - duration, + startTime: currentDateInMs, + retry: false, }; - reporter.tempLaunchId = 'tempLaunchId'; - reporter.tempSuiteIds = new Map([['Suite', 'tempSuiteId']]); - - reporter._startStep( - { title: 'Step', ancestorTitles: ['Suite'], duration }, - true, - testObj.path, - ); + const expectedTempStepIds = new Map([['Suite/Step', 'startTestItem']]); expect(reporter.client.startTestItem).toHaveBeenCalledWith( expectedStartStepItemParameter, 'tempLaunchId', - 'tempSuiteId', + 'startTestItem', ); - expect(reporter.tempStepId).toEqual('startTestItem'); + expect(reporter.tempStepIds).toEqual(expectedTempStepIds); + expect(reporter.tempStepId).toBe('startTestItem'); }); }); - describe('sendLog', () => { + describe('_sendLog', () => { test('sendLog should be called with parameters', () => { reporter.tempStepId = 'tempStepId'; - reporter.sendLog({ message: 'message', level: LOG_LEVEL.ERROR, file: mockFile }); + reporter._sendLog({ message: 'message', level: LOG_LEVEL.ERROR, file: mockFile }); expect(reporter.client.sendLog).toHaveBeenCalledWith( 'tempStepId', @@ -446,23 +404,6 @@ describe('index script', () => { }); }); - describe('_finishTest', () => { - test('finishTestItem should be called with parameters, tempTestId should be deleted', () => { - reporter.tempTestIds = new Map([['Test', 'tempTestId']]); - - reporter._finishTest('tempTestId', 'Test'); - - expect(reporter.client.finishTestItem).toHaveBeenCalledWith('tempTestId', {}); - expect(reporter.tempTestIds.get('Test')).toEqual(undefined); - }); - - test('finishTestItem should not be called with parameters if there is no tempTestId', () => { - reporter._finishTest(undefined); - - expect(reporter.client.finishTestItem).not.toHaveBeenCalled(); - }); - }); - describe('_finishSuite', () => { test('finishTestItem should be called with parameters, tempSuiteId should be deleted', () => { reporter.tempSuiteIds = new Map([['Suite', 'tempSuiteId']]); @@ -498,7 +439,7 @@ describe('index script', () => { describe('_finishFailedStep', () => { test('sendLog should be called with failure message, finishTestItem should be called with parameters', () => { - const spySendLog = jest.spyOn(reporter, 'sendLog'); + const spySendLog = jest.spyOn(reporter, '_sendLog'); const errorMessage = 'error message'; const tempStepId = 'tempStepId'; const expectedFinishTestItemParameter = { diff --git a/package-lock.json b/package-lock.json index a27f266..20d85da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "@reportportal/agent-js-jest", - "version": "5.1.0", + "version": "5.1.1", "license": "Apache-2.0", "dependencies": { "@reportportal/client-javascript": "~5.1.4", diff --git a/src/reporter.js b/src/reporter.js index fd22bce..4e7c556 100644 --- a/src/reporter.js +++ b/src/reporter.js @@ -56,19 +56,21 @@ class JestReportPortal { this.promises.push(promise); } - // FYI. Does not even call most of the time. Cannot be used for suites handling. + // FYI. In most cases it is not even called. It cannot be used for suites handling. onTestStart() {} _startSuites(suiteTitles, filePath, startTime) { suiteTitles.reduce((suitePath, suiteTitle) => { const fullSuiteName = suitePath ? `${suitePath}/${suiteTitle}` : suiteTitle; const codeRef = getCodeRef(filePath, fullSuiteName); + const parentCodeRef = getCodeRef(filePath, suitePath); - this._startSuite(suiteTitle, suitePath, codeRef, startTime); + this._startSuite(suiteTitle, codeRef, parentCodeRef, startTime); return fullSuiteName; }, ''); } + // TODO: cover with tests // Not called for `skipped` and `todo` specs onTestCaseStart(test, testCaseStartInfo) { this._startSuites(testCaseStartInfo.ancestorTitles, test.path, testCaseStartInfo.startedAt); @@ -77,6 +79,7 @@ class JestReportPortal { this._startStep(testCaseStartInfo, test.path, isRetried); } + // TODO: cover with tests // Not called for `skipped` and `todo` specs onTestCaseResult(test, testCaseStartInfo) { this._finishStep(testCaseStartInfo); @@ -125,7 +128,7 @@ class JestReportPortal { await promise; } - _startSuite(title, suitePath, codeRef, startTime = new Date().valueOf()) { + _startSuite(title, codeRef, parentCodeRef = '', startTime = new Date().valueOf()) { if (this.tempSuiteIds.get(codeRef)) { return; } @@ -136,7 +139,7 @@ class JestReportPortal { codeRef, startTime, }; - const parentId = this.tempSuiteIds.get(suitePath); + const parentId = this.tempSuiteIds.get(parentCodeRef); const { tempId, promise } = this.client.startTestItem( testStartObj, this.tempLaunchId, From ccbfcff9a8de87fd8c2945b9d2a1e59189e9a4b3 Mon Sep 17 00:00:00 2001 From: Ilya Hancharyk Date: Thu, 30 Jan 2025 18:30:35 +0100 Subject: [PATCH 5/5] EPMRPP-95591 || Add comment about suites finishing --- src/reporter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/reporter.js b/src/reporter.js index 4e7c556..dd25bcf 100644 --- a/src/reporter.js +++ b/src/reporter.js @@ -110,6 +110,7 @@ class JestReportPortal { const suiteFilePathToFinish = getCodeRef(testResult.testFilePath); + // Finishing suites that are related to the test file this.tempSuiteIds.forEach((suiteTempId, suiteFullName) => { if (suiteFullName.includes(suiteFilePathToFinish)) { this._finishSuite(suiteTempId, suiteFullName);