diff --git a/lib/utils/attachments.js b/lib/utils/attachments.js new file mode 100644 index 0000000..c3e1892 --- /dev/null +++ b/lib/utils/attachments.js @@ -0,0 +1,98 @@ +/* + * Copyright 2024 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 fs = require('fs'); +const glob = require('glob'); +const path = require('path'); + +const fsPromises = fs.promises; + +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 = async (absolutePath) => { + if (!absolutePath) return absolutePath; + const name = absolutePath.split(path.sep).pop(); + return { + name, + type: 'image/png', + content: await base64Encode(absolutePath), + }; +}; + +const waitForFile = ( + globFilePattern, + 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(globFilePattern); + + if (files.length) { + resolve(files[0]); + } else if (totalTime >= timeout) { + reject(new Error(`Timeout of ${timeout}ms reached, file ${globFilePattern} 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 fsPromises.readFile(videoFilePath, { encoding: 'base64' }), + }; +}; + +module.exports = { + getScreenshotAttachment, + getVideoFile, + waitForFile, +}; diff --git a/lib/utils/common.js b/lib/utils/common.js new file mode 100644 index 0000000..9d1184f --- /dev/null +++ b/lib/utils/common.js @@ -0,0 +1,22 @@ +/* + * Copyright 2024 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 getCodeRef = (testItemPath, testFileName) => + `${testFileName.replace(/\\/g, '/')}/${testItemPath.join('/')}`; + +module.exports = { + getCodeRef, +}; diff --git a/lib/utils/index.js b/lib/utils/index.js new file mode 100644 index 0000000..745b096 --- /dev/null +++ b/lib/utils/index.js @@ -0,0 +1,27 @@ +/* + * Copyright 2024 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 attachmentUtils = require('./attachments'); +const commonUtils = require('./common'); +const objectCreators = require('./objectCreators'); +const specCountCalculation = require('./specCountCalculation'); + +module.exports = { + ...attachmentUtils, + ...commonUtils, + ...objectCreators, + ...specCountCalculation, +}; diff --git a/lib/utils.js b/lib/utils/objectCreators.js similarity index 59% rename from lib/utils.js rename to lib/utils/objectCreators.js index 7c201d2..6d2209f 100644 --- a/lib/utils.js +++ b/lib/utils/objectCreators.js @@ -14,91 +14,13 @@ * limitations under the License. */ -const fs = require('fs'); -const glob = require('glob'); const path = require('path'); -const minimatch = require('minimatch'); -const { entityType, hookTypesMap, testItemStatuses } = require('./constants'); -const pjson = require('./../package.json'); - -const fsPromises = fs.promises; +const pjson = require('../../package.json'); +const { entityType, hookTypesMap, testItemStatuses } = require('../constants'); +const { getCodeRef } = require('./common'); const { FAILED, PASSED, SKIPPED } = testItemStatuses; -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 = async (absolutePath) => { - if (!absolutePath) return absolutePath; - const name = absolutePath.split(path.sep).pop(); - return { - name, - type: 'image/png', - 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) => - `${testFileName.replace(/\\/g, '/')}/${testItemPath.join('/')}`; - const getAgentInfo = () => ({ version: pjson.version, name: pjson.name, @@ -273,82 +195,20 @@ const getHookStartObject = (hook) => { codeRef: hook.codeRef, }; }; -const getFixtureFolderPattern = (config) => { - return [].concat(config.fixturesFolder ? path.join(config.fixturesFolder, '**', '*') : []); -}; - -const getExcludeSpecPattern = (config) => { - // Return cypress >= 10 pattern. - if (config.excludeSpecPattern) { - const excludePattern = Array.isArray(config.excludeSpecPattern) - ? config.excludeSpecPattern - : [config.excludeSpecPattern]; - return [...excludePattern]; - } - - // Return cypress <= 9 pattern - const ignoreTestFilesPattern = Array.isArray(config.ignoreTestFiles) - ? config.ignoreTestFiles - : [config.ignoreTestFiles] || []; - - return [...ignoreTestFilesPattern]; -}; - -const getSpecPattern = (config) => { - if (config.specPattern) return [].concat(config.specPattern); - - return Array.isArray(config.testFiles) - ? config.testFiles.map((file) => path.join(config.integrationFolder, file)) - : [].concat(path.join(config.integrationFolder, config.testFiles)); -}; - -const getTotalSpecs = (config) => { - if (!config.testFiles && !config.specPattern) - throw new Error('Configuration property not set! Neither for cypress <= 9 nor cypress >= 10'); - - const specPattern = getSpecPattern(config); - - const excludeSpecPattern = getExcludeSpecPattern(config); - - const options = { - sort: true, - absolute: true, - nodir: true, - ignore: [config.supportFile].concat(getFixtureFolderPattern(config)), - }; - - const doesNotMatchAllIgnoredPatterns = (file) => - excludeSpecPattern.every( - (pattern) => !minimatch(file, pattern, { dot: true, matchBase: true }), - ); - - const globResult = specPattern.reduce( - (files, pattern) => files.concat(glob.sync(pattern, options) || []), - [], - ); - - return globResult.filter(doesNotMatchAllIgnoredPatterns).length; -}; module.exports = { - getScreenshotAttachment, getAgentInfo, - getCodeRef, getSystemAttributes, + getConfig, getLaunchStartObject, getSuiteStartObject, getSuiteEndObject, getTestStartObject, + getTestEndObject, + getHookStartObject, + // there are utils to preprocess Mocha entities getTestInfo, getSuiteStartInfo, getSuiteEndInfo, - getTestEndObject, getHookInfo, - getHookStartObject, - getTotalSpecs, - getConfig, - getExcludeSpecPattern, - getFixtureFolderPattern, - getSpecPattern, - getVideoFile, }; diff --git a/lib/utils/specCountCalculation.js b/lib/utils/specCountCalculation.js new file mode 100644 index 0000000..e64fd68 --- /dev/null +++ b/lib/utils/specCountCalculation.js @@ -0,0 +1,83 @@ +/* + * Copyright 2024 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 glob = require('glob'); +const path = require('path'); +const minimatch = require('minimatch'); + +const getFixtureFolderPattern = (config) => { + return [].concat(config.fixturesFolder ? path.join(config.fixturesFolder, '**', '*') : []); +}; + +const getExcludeSpecPattern = (config) => { + // Return cypress >= 10 pattern. + if (config.excludeSpecPattern) { + const excludePattern = Array.isArray(config.excludeSpecPattern) + ? config.excludeSpecPattern + : [config.excludeSpecPattern]; + return [...excludePattern]; + } + + // Return cypress <= 9 pattern + const ignoreTestFilesPattern = Array.isArray(config.ignoreTestFiles) + ? config.ignoreTestFiles + : [config.ignoreTestFiles] || []; + + return [...ignoreTestFilesPattern]; +}; + +const getSpecPattern = (config) => { + if (config.specPattern) return [].concat(config.specPattern); + + return Array.isArray(config.testFiles) + ? config.testFiles.map((file) => path.join(config.integrationFolder, file)) + : [].concat(path.join(config.integrationFolder, config.testFiles)); +}; + +const getTotalSpecs = (config) => { + if (!config.testFiles && !config.specPattern) + throw new Error('Configuration property not set! Neither for cypress <= 9 nor cypress >= 10'); + + const specPattern = getSpecPattern(config); + + const excludeSpecPattern = getExcludeSpecPattern(config); + + const options = { + sort: true, + absolute: true, + nodir: true, + ignore: [config.supportFile].concat(getFixtureFolderPattern(config)), + }; + + const doesNotMatchAllIgnoredPatterns = (file) => + excludeSpecPattern.every( + (pattern) => !minimatch(file, pattern, { dot: true, matchBase: true }), + ); + + const globResult = specPattern.reduce( + (files, pattern) => files.concat(glob.sync(pattern, options) || []), + [], + ); + + return globResult.filter(doesNotMatchAllIgnoredPatterns).length; +}; + +module.exports = { + getTotalSpecs, + getExcludeSpecPattern, + getFixtureFolderPattern, + getSpecPattern, +}; diff --git a/test/mock/mock.js b/test/mock/mocks.js similarity index 100% rename from test/mock/mock.js rename to test/mock/mocks.js diff --git a/test/reporter.test.js b/test/reporter.test.js index e431003..c606bb3 100644 --- a/test/reporter.test.js +++ b/test/reporter.test.js @@ -1,6 +1,6 @@ const mockFS = require('mock-fs'); const path = require('path'); -const { getDefaultConfig, RPClient, MockedDate, RealDate, currentDate } = require('./mock/mock'); +const { getDefaultConfig, RPClient, MockedDate, RealDate, currentDate } = require('./mock/mocks'); const Reporter = require('./../lib/reporter'); const sep = path.sep; @@ -1045,7 +1045,7 @@ describe('reporter script', () => { }); }); - describe('screenshot', () => { + describe('sendScreenshot', () => { const screenshotInfo = { testAttemptIndex: 0, size: 295559, @@ -1076,19 +1076,19 @@ describe('reporter script', () => { mockFS.restore(); }); - it('should not send screenshot for undefined path', () => { + it('should not send screenshot for undefined path', async () => { const spySendLog = jest.spyOn(reporter.client, 'sendLog'); - reporter.sendScreenshot(screenshotInfo); + await reporter.sendScreenshot(screenshotInfo); expect(spySendLog).not.toHaveBeenCalled(); }); - it('should send screenshot from screenshotInfo', () => { + it('should send screenshot from screenshotInfo', async () => { const spySendLog = jest.spyOn(reporter.client, 'sendLog'); screenshotInfo.path = `${sep}example${sep}screenshots${sep}example.spec.js${sep}suite name -- test name.png`; reporter.currentTestTempInfo = expectedTempId; - reporter.sendScreenshot(screenshotInfo); + await reporter.sendScreenshot(screenshotInfo); expect(spySendLog).toHaveBeenCalledTimes(1); expect(spySendLog).toHaveBeenCalledWith( @@ -1106,13 +1106,13 @@ describe('reporter script', () => { ); }); - it('should send screenshot from screenshotInfo - error level', () => { + it('should send screenshot from screenshotInfo - error level', async () => { const spySendLog = jest.spyOn(reporter.client, 'sendLog'); screenshotInfo.path = `${sep}example${sep}screenshots${sep}example.spec.js${sep}suite name -- test name (failed).png`; reporter.currentTestTempInfo = expectedTempId; - reporter.sendScreenshot(screenshotInfo); + await reporter.sendScreenshot(screenshotInfo); expect(spySendLog).toHaveBeenCalledTimes(1); expect(spySendLog).toHaveBeenCalledWith( @@ -1130,14 +1130,14 @@ describe('reporter script', () => { ); }); - it('should send screenshot from screenshotInfo - custom log message', () => { + it('should send screenshot from screenshotInfo - custom log message', async () => { const spySendLog = jest.spyOn(reporter.client, 'sendLog'); screenshotInfo.path = `${sep}example${sep}screenshots${sep}example.spec.js${sep}customScreenshot1.png`; const message = `screenshot\n${JSON.stringify(screenshotInfo, undefined, 2)}`; reporter.currentTestTempInfo = expectedTempId; - reporter.sendScreenshot(screenshotInfo, message); + await reporter.sendScreenshot(screenshotInfo, message); expect(spySendLog).toHaveBeenCalledTimes(1); expect(spySendLog).toHaveBeenCalledWith( diff --git a/test/utils.test.js b/test/utils.test.js deleted file mode 100644 index f03b016..0000000 --- a/test/utils.test.js +++ /dev/null @@ -1,978 +0,0 @@ -const mock = require('mock-fs'); -const path = require('path'); -const { - getSystemAttributes, - getLaunchStartObject, - getSuiteStartObject, - getSuiteEndObject, - getTestInfo, - getTestStartObject, - getTestEndObject, - getHookInfo, - getHookStartObject, - getScreenshotAttachment, - getAgentInfo, - getCodeRef, - getTotalSpecs, - getConfig, - getFixtureFolderPattern, - getExcludeSpecPattern, - getSpecPattern, - getVideoFile, - prepareReporterOptions, -} = require('./../lib/utils'); -const pjson = require('./../package.json'); - -const sep = path.sep; - -const { RealDate, MockedDate, currentDate, getDefaultConfig } = require('./mock/mock'); - -describe('utils script', () => { - describe('attachment utils', () => { - beforeEach(() => { - mock({ - '/example/screenshots/example.spec.js': { - 'suite name -- test name (failed).png': Buffer.from([8, 6, 7, 5, 3, 0, 9]), - 'suite name -- test name.png': Buffer.from([1, 2, 3, 4, 5, 6, 7]), - 'suite name -- test name (1).png': Buffer.from([8, 7, 6, 5, 4, 3, 2]), - 'customScreenshot1.png': Buffer.from([1, 1, 1, 1, 1, 1, 1]), - }, - 'example/videos': { - 'custom suite name.cy.ts.mp4': Buffer.from([1, 2, 7, 9, 3, 0, 5]), - }, - }); - }); - - afterEach(() => { - mock.restore(); - }); - - it('getScreenshotAttachment: should not fail on undefined', () => { - const testFile = undefined; - const attachment = getScreenshotAttachment(testFile); - expect(attachment).not.toBeDefined(); - }); - - it('getScreenshotAttachment: should return attachment for absolute path', () => { - const testFile = `${sep}example${sep}screenshots${sep}example.spec.js${sep}suite name -- test name (failed).png`; - const expectedAttachment = { - name: 'suite name -- test name (failed).png', - type: 'image/png', - content: Buffer.from([8, 6, 7, 5, 3, 0, 9]).toString('base64'), - }; - - const attachment = getScreenshotAttachment(testFile); - - expect(attachment).toBeDefined(); - expect(attachment).toEqual(expectedAttachment); - }); - - it('getVideoFile: should return video file attachment with videosFolder', () => { - const testFileName = 'custom suite name.cy.ts'; - const expectedAttachment = { - name: `${testFileName}.mp4`, - type: 'video/mp4', - content: Buffer.from([1, 2, 7, 9, 3, 0, 5]).toString('base64'), - }; - - const attachment = getVideoFile(testFileName, 'example/videos'); - - expect(attachment).toBeDefined(); - expect(attachment).toEqual(expectedAttachment); - }); - - it('getVideoFile: should return video file attachment without videosFolder', () => { - const testFileName = 'custom suite name.cy.ts'; - const expectedAttachment = { - name: `${testFileName}.mp4`, - type: 'video/mp4', - content: Buffer.from([1, 2, 7, 9, 3, 0, 5]).toString('base64'), - }; - - const attachment = getVideoFile(testFileName); - - expect(attachment).toBeDefined(); - expect(attachment).toEqual(expectedAttachment); - }); - }); - - describe('object creators', () => { - const testFileName = `test\\example.spec.js`; - - beforeEach(() => { - global.Date = jest.fn(MockedDate); - Object.assign(Date, RealDate); - }); - - afterEach(() => { - jest.clearAllMocks(); - global.Date = RealDate; - }); - - describe('getSystemAttributes', () => { - it('skippedIssue undefined. Should return attribute with agent name and version', function () { - const options = getDefaultConfig(); - const expectedSystemAttributes = [ - { - key: 'agent', - value: `${pjson.name}|${pjson.version}`, - system: true, - }, - ]; - - const systemAttributes = getSystemAttributes(options); - - expect(systemAttributes).toEqual(expectedSystemAttributes); - }); - - it('skippedIssue = true. Should return attribute with agent name and version', function () { - const options = getDefaultConfig(); - options.reporterOptions.skippedIssue = true; - const expectedSystemAttributes = [ - { - key: 'agent', - value: `${pjson.name}|${pjson.version}`, - system: true, - }, - ]; - - const systemAttributes = getSystemAttributes(options); - - expect(systemAttributes).toEqual(expectedSystemAttributes); - }); - - it('skippedIssue = false. Should return 2 attribute: with agent name/version and skippedIssue', function () { - const options = getDefaultConfig(); - options.reporterOptions.skippedIssue = false; - const expectedSystemAttributes = [ - { - key: 'agent', - value: `${pjson.name}|${pjson.version}`, - system: true, - }, - { - key: 'skippedIssue', - value: 'false', - system: true, - }, - ]; - - const systemAttributes = getSystemAttributes(options); - - expect(systemAttributes).toEqual(expectedSystemAttributes); - }); - }); - - describe('getConfig', () => { - const baseReporterOptions = { - endpoint: 'https://reportportal.server/api/v1', - project: 'ProjectName', - launch: 'LauncherName', - description: 'Launch description', - attributes: [], - }; - - describe('CI_BUILD_ID attribute providing', () => { - afterEach(() => { - delete process.env.CI_BUILD_ID; - }); - - it('should not add an attribute with the CI_BUILD_ID value in case of parallel reporter option is false', function () { - process.env.CI_BUILD_ID = 'buildId'; - const initialConfig = { - reporter: '@reportportal/agent-js-cypress', - reporterOptions: { - ...baseReporterOptions, - apiKey: '123', - autoMerge: true, - parallel: false, - }, - }; - const expectedConfig = initialConfig; - - const config = getConfig(initialConfig); - - expect(config).toEqual(expectedConfig); - }); - - it('should not add an attribute with the CI_BUILD_ID value in case of autoMerge reporter option is false', function () { - process.env.CI_BUILD_ID = 'buildId'; - const initialConfig = { - reporter: '@reportportal/agent-js-cypress', - reporterOptions: { - ...baseReporterOptions, - apiKey: '123', - autoMerge: false, - parallel: true, - }, - }; - const expectedConfig = initialConfig; - - const config = getConfig(initialConfig); - - expect(config).toEqual(expectedConfig); - }); - - it('should not add an attribute with the value CI_BUILD_ID if the env variable CI_BUILD_ID does not exist', function () { - process.env.CI_BUILD_ID = undefined; - const initialConfig = { - reporter: '@reportportal/agent-js-cypress', - reporterOptions: { - ...baseReporterOptions, - apiKey: '123', - autoMerge: false, - parallel: true, - }, - }; - const expectedConfig = initialConfig; - - const config = getConfig(initialConfig); - - expect(config).toEqual(expectedConfig); - }); - - it('should return config with updated attributes (including attribute with CI_BUILD_ID value)', function () { - process.env.CI_BUILD_ID = 'buildId'; - const initialConfig = { - reporter: '@reportportal/agent-js-cypress', - reporterOptions: { - ...baseReporterOptions, - apiKey: '123', - autoMerge: true, - parallel: true, - }, - }; - const expectedConfig = { - reporter: '@reportportal/agent-js-cypress', - reporterOptions: { - ...initialConfig.reporterOptions, - attributes: [ - { - value: 'buildId', - }, - ], - }, - }; - - const config = getConfig(initialConfig); - - expect(config).toEqual(expectedConfig); - }); - }); - - describe('apiKey option priority', () => { - afterEach(() => { - delete process.env.RP_TOKEN; - delete process.env.RP_API_KEY; - }); - - it('should override token property if the ENV variable RP_TOKEN exists', function () { - process.env.RP_TOKEN = 'secret'; - const initialConfig = { - reporter: '@reportportal/agent-js-cypress', - reporterOptions: { - ...baseReporterOptions, - token: '123', - }, - }; - const expectedConfig = { - reporter: '@reportportal/agent-js-cypress', - reporterOptions: { - ...baseReporterOptions, - apiKey: 'secret', - }, - }; - - const config = getConfig(initialConfig); - - expect(config).toEqual(expectedConfig); - }); - - it('should override apiKey property if the ENV variable RP_API_KEY exists', function () { - process.env.RP_API_KEY = 'secret'; - const initialConfig = { - reporter: '@reportportal/agent-js-cypress', - reporterOptions: { - ...baseReporterOptions, - apiKey: '123', - }, - }; - const expectedConfig = { - reporter: '@reportportal/agent-js-cypress', - reporterOptions: { - ...baseReporterOptions, - apiKey: 'secret', - }, - }; - - const config = getConfig(initialConfig); - - expect(config).toEqual(expectedConfig); - }); - - it('should prefer apiKey property over deprecated token', function () { - const initialConfig = { - reporter: '@reportportal/agent-js-cypress', - reporterOptions: { - ...baseReporterOptions, - apiKey: '123', - token: '345', - }, - }; - const expectedConfig = { - reporter: '@reportportal/agent-js-cypress', - reporterOptions: { - ...baseReporterOptions, - apiKey: '123', - }, - }; - - const config = getConfig(initialConfig); - - expect(config).toEqual(expectedConfig); - }); - }); - }); - - describe('prepareReporterOptions', function () { - it('should pass video related cypress options from cypress config', function () { - const initialConfig = getDefaultConfig(); - initialConfig.videosFolder = '/example/videos'; - initialConfig.videoUploadOnPasses = true; - - const config = prepareReporterOptions(initialConfig); - - expect(config.reporterOptions.videosFolder).toEqual('/example/videos'); - expect(config.reporterOptions.videoUploadOnPasses).toEqual(true); - }); - - it('passing video related cypress options should not fail if undefined', function () { - const initialConfig = getDefaultConfig(); - - const config = prepareReporterOptions(initialConfig); - - expect(config.reporterOptions.videosFolder).not.toBeDefined(); - expect(config.reporterOptions.videoUploadOnPasses).not.toBeDefined(); - }); - }); - - describe('getLaunchStartObject', () => { - test('should return start launch object with correct values', () => { - const expectedStartLaunchObject = { - launch: 'LauncherName', - description: 'Launch description', - attributes: [ - { - key: 'agent', - system: true, - value: `${pjson.name}|${pjson.version}`, - }, - ], - startTime: currentDate, - rerun: undefined, - rerunOf: undefined, - mode: undefined, - }; - - const startLaunchObject = getLaunchStartObject(getDefaultConfig()); - - expect(startLaunchObject).toBeDefined(); - expect(startLaunchObject).toEqual(expectedStartLaunchObject); - }); - }); - - describe('getSuiteStartObject', () => { - test('root suite: should return suite start object with undefined parentId', () => { - const suite = { - id: 'suite1', - title: 'suite name', - description: 'suite description', - root: true, - titlePath: () => ['suite name'], - }; - const expectedSuiteStartObject = { - id: 'suite1', - name: 'suite name', - type: 'suite', - startTime: currentDate, - description: 'suite description', - attributes: [], - codeRef: 'test/example.spec.js/suite name', - parentId: undefined, - testFileName: 'test\\example.spec.js', - }; - - const suiteStartObject = getSuiteStartObject(suite, testFileName); - - expect(suiteStartObject).toBeDefined(); - expect(suiteStartObject).toEqual(expectedSuiteStartObject); - }); - - test('nested suite: should return suite start object with parentId', () => { - const suite = { - id: 'suite1', - title: 'suite name', - description: 'suite description', - parent: { - id: 'parentSuiteId', - }, - titlePath: () => ['parent suite name', 'suite name'], - }; - const expectedSuiteStartObject = { - id: 'suite1', - name: 'suite name', - type: 'suite', - startTime: currentDate, - description: 'suite description', - attributes: [], - codeRef: 'test/example.spec.js/parent suite name/suite name', - parentId: 'parentSuiteId', - testFileName: 'test\\example.spec.js', - }; - - const suiteStartObject = getSuiteStartObject(suite, testFileName); - - expect(suiteStartObject).toBeDefined(); - expect(suiteStartObject).toEqual(expectedSuiteStartObject); - }); - }); - - describe('getSuiteEndObject', () => { - test('should return suite end object', () => { - const suite = { - id: 'suite1', - title: 'suite name', - description: 'suite description', - parent: { - id: 'parentSuiteId', - }, - }; - const expectedSuiteEndObject = { - id: 'suite1', - title: 'suite name', - endTime: currentDate, - }; - - const suiteEndObject = getSuiteEndObject(suite); - - expect(suiteEndObject).toBeDefined(); - expect(suiteEndObject).toEqual(expectedSuiteEndObject); - }); - }); - - describe('getTestInfo', () => { - test('passed test: should return test info with passed status', () => { - const test = { - id: 'testId1', - title: 'test name', - parent: { - id: 'parentSuiteId', - }, - state: 'passed', - titlePath: () => ['suite name', 'test name'], - }; - const expectedTestInfoObject = { - id: 'testId1', - title: 'test name', - status: 'passed', - parentId: 'parentSuiteId', - codeRef: 'test/example.spec.js/suite name/test name', - err: undefined, - testFileName, - }; - - const testInfoObject = getTestInfo(test, testFileName); - - expect(testInfoObject).toBeDefined(); - expect(testInfoObject).toEqual(expectedTestInfoObject); - }); - - test('pending test: should return test info with skipped status', () => { - const test = { - id: 'testId1', - title: 'test name', - parent: { - id: 'parentSuiteId', - }, - state: 'pending', - titlePath: () => ['suite name', 'test name'], - }; - const expectedTestInfoObject = { - id: 'testId1', - title: 'test name', - status: 'skipped', - parentId: 'parentSuiteId', - codeRef: 'test/example.spec.js/suite name/test name', - err: undefined, - testFileName, - }; - - const testInfoObject = getTestInfo(test, testFileName); - - expect(testInfoObject).toBeDefined(); - expect(testInfoObject).toEqual(expectedTestInfoObject); - }); - - test('should return test info with specified status and error', () => { - const test = { - id: 'testId', - title: 'test name', - parent: { - id: 'parentSuiteId', - }, - state: 'pending', - titlePath: () => ['suite name', 'test name'], - }; - const expectedTestInfoObject = { - id: 'testId', - title: 'test name', - status: 'failed', - parentId: 'parentSuiteId', - codeRef: 'test/example.spec.js/suite name/test name', - err: { message: 'error message' }, - testFileName, - }; - - const testInfoObject = getTestInfo(test, testFileName, 'failed', { - message: 'error message', - }); - - expect(testInfoObject).toBeDefined(); - expect(testInfoObject).toEqual(expectedTestInfoObject); - }); - }); - - describe('getTestStartObject', () => { - test('should return test start object', () => { - const test = { - id: 'testId1', - title: 'test name', - parent: { - id: 'parentSuiteId', - }, - codeRef: 'test/example.spec.js/suite name/test name', - }; - const expectedTestStartObject = { - name: 'test name', - startTime: currentDate, - attributes: [], - type: 'step', - codeRef: 'test/example.spec.js/suite name/test name', - }; - - const testInfoObject = getTestStartObject(test); - - expect(testInfoObject).toBeDefined(); - expect(testInfoObject).toEqual(expectedTestStartObject); - }); - }); - - describe('getTestEndObject', () => { - test('skippedIssue is not defined: should return test end object without issue', () => { - const testInfo = { - id: 'testId1', - title: 'test name', - status: 'skipped', - parent: { - id: 'parentSuiteId', - }, - }; - const expectedTestEndObject = { - endTime: currentDate, - status: testInfo.status, - }; - const testEndObject = getTestEndObject(testInfo); - - expect(testEndObject).toBeDefined(); - expect(testEndObject).toEqual(expectedTestEndObject); - }); - - test('skippedIssue = true: should return test end object without issue', () => { - const testInfo = { - id: 'testId1', - title: 'test name', - status: 'skipped', - parent: { - id: 'parentSuiteId', - }, - }; - const expectedTestEndObject = { - endTime: currentDate, - status: testInfo.status, - }; - const testEndObject = getTestEndObject(testInfo, true); - - expect(testEndObject).toBeDefined(); - expect(testEndObject).toEqual(expectedTestEndObject); - }); - - test('skippedIssue = false: should return test end object with issue NOT_ISSUE', () => { - const testInfo = { - id: 'testId1', - title: 'test name', - status: 'skipped', - parent: { - id: 'parentSuiteId', - }, - }; - const expectedTestEndObject = { - endTime: currentDate, - status: testInfo.status, - issue: { - issueType: 'NOT_ISSUE', - }, - }; - const testEndObject = getTestEndObject(testInfo, false); - - expect(testEndObject).toBeDefined(); - expect(testEndObject).toEqual(expectedTestEndObject); - }); - - test('testCaseId is defined: should return test end object with testCaseId', () => { - const testInfo = { - id: 'testId1', - title: 'test name', - status: 'skipped', - parent: { - id: 'parentSuiteId', - }, - testCaseId: 'testCaseId', - }; - const expectedTestEndObject = { - endTime: currentDate, - status: testInfo.status, - testCaseId: 'testCaseId', - }; - const testEndObject = getTestEndObject(testInfo); - - expect(testEndObject).toEqual(expectedTestEndObject); - }); - }); - - describe('getHookInfo', () => { - test('passed before each hook: should return hook info with passed status', () => { - const hook = { - id: 'testId', - title: '"before each" hook: hook name', - parent: { - id: 'parentSuiteId', - }, - state: 'passed', - hookName: 'before each', - hookId: 'hookId', - titlePath: () => ['suite name', 'hook name'], - }; - const expectedHookInfoObject = { - id: 'hookId_testId', - hookName: 'before each', - title: '"before each" hook: hook name', - status: 'passed', - parentId: 'parentSuiteId', - codeRef: 'test/example.spec.js/suite name/hook name', - err: undefined, - testFileName, - }; - - const hookInfoObject = getHookInfo(hook, testFileName); - - expect(hookInfoObject).toBeDefined(); - expect(hookInfoObject).toEqual(expectedHookInfoObject); - }); - - test('passed before all hook: should return correct hook info', () => { - const hook = { - id: 'testId', - title: '"before all" hook: hook name', - parent: { - id: 'parentSuiteId', - title: 'parent suite title', - parent: { - id: 'rootSuiteId', - title: 'root suite title', - }, - }, - state: 'passed', - hookName: 'before all', - hookId: 'hookId', - titlePath: () => ['suite name', 'hook name'], - }; - const expectedHookInfoObject = { - id: 'hookId_testId', - hookName: 'before all', - title: '"before all" hook: hook name', - status: 'passed', - parentId: 'rootSuiteId', - codeRef: 'test/example.spec.js/suite name/hook name', - err: undefined, - testFileName, - }; - - const hookInfoObject = getHookInfo(hook, testFileName); - - expect(hookInfoObject).toBeDefined(); - expect(hookInfoObject).toEqual(expectedHookInfoObject); - }); - - test('failed test: should return hook info with failed status', () => { - const test = { - id: 'testId', - hookName: 'before each', - title: '"before each" hook: hook name', - parent: { - id: 'parentSuiteId', - }, - state: 'failed', - failedFromHookId: 'hookId', - titlePath: () => ['suite name', 'hook name'], - }; - const expectedHookInfoObject = { - id: 'hookId_testId', - hookName: 'before each', - title: '"before each" hook: hook name', - status: 'failed', - parentId: 'parentSuiteId', - codeRef: 'test/example.spec.js/suite name/hook name', - err: undefined, - testFileName, - }; - - const hookInfoObject = getHookInfo(test, testFileName); - - expect(hookInfoObject).toBeDefined(); - expect(hookInfoObject).toEqual(expectedHookInfoObject); - }); - }); - describe('getHookStartObject', () => { - test('should return hook start object', () => { - const hookInfo = { - id: 'hookId_testId', - hookName: 'before each', - title: '"before each" hook: hook name', - status: 'passed', - parentId: 'parentSuiteId', - titlePath: () => ['suite name', 'hook name'], - err: undefined, - }; - const expectedHookStartObject = { - name: 'hook name', - startTime: currentDate, - type: 'BEFORE_METHOD', - }; - - const hookInfoObject = getHookStartObject(hookInfo, testFileName, 'failed', { - message: 'error message', - }); - - expect(hookInfoObject).toBeDefined(); - expect(hookInfoObject).toEqual(expectedHookStartObject); - }); - }); - }); - - describe('common utils', () => { - describe('getAgentInfo', () => { - it('getAgentInfo: should contain version and name properties', () => { - const agentInfo = getAgentInfo(); - - expect(Object.keys(agentInfo)).toContain('version'); - expect(Object.keys(agentInfo)).toContain('name'); - }); - }); - describe('getCodeRef', () => { - it('should return correct code ref for Windows paths', () => { - jest.mock('path', () => ({ - sep: '\\', - })); - const file = `test\\example.spec.js`; - const titlePath = ['rootDescribe', 'parentDescribe', 'testTitle']; - - const expectedCodeRef = `test/example.spec.js/rootDescribe/parentDescribe/testTitle`; - - const codeRef = getCodeRef(titlePath, file); - - expect(codeRef).toEqual(expectedCodeRef); - - jest.clearAllMocks(); - }); - - it('should return correct code ref for POSIX paths', () => { - jest.mock('path', () => ({ - sep: '/', - })); - const file = `test/example.spec.js`; - const titlePath = ['rootDescribe', 'parentDescribe', 'testTitle']; - - const expectedCodeRef = `test/example.spec.js/rootDescribe/parentDescribe/testTitle`; - - const codeRef = getCodeRef(titlePath, file); - - expect(codeRef).toEqual(expectedCodeRef); - - jest.clearAllMocks(); - }); - }); - }); - - describe('getTotalSpecs', () => { - beforeEach(() => { - mock({ - 'cypress/tests': { - 'example1.spec.js': '', - 'example2.spec.js': '', - 'example3.spec.js': '', - 'example4.spec.ts': '', - 'example.ignore.spec.js': '', - }, - 'cypress/support': { - 'index.js': '', - }, - 'cypress/fixtures': { - 'fixtures1.js': '', - 'fixtures2.js': '', - }, - }); - }); - - afterEach(() => { - mock.restore(); - }); - - it('testFiles, integrationFolder, supportFile are specified: should count all files from integration folder', () => { - let specConfig = { - testFiles: '**/*.*', - ignoreTestFiles: '*.hot-update.js', - fixturesFolder: 'cypress/fixtures', - integrationFolder: 'cypress/tests', - supportFile: 'cypress/support/index.js', - }; - - let specCount = getTotalSpecs(specConfig); - - expect(specCount).toEqual(5); - - specConfig = { - excludeSpecPattern: '*.hot-update.js', - specPattern: 'cypress/tests/**/*.spec.{js,ts}', - supportFile: 'cypress/support/index.js', - fixturesFolder: 'cypress/fixtures', - }; - - specCount = getTotalSpecs(specConfig); - - expect(specCount).toEqual(5); - }); - - it('ignoreTestFiles are specified: should ignore specified files', () => { - let specConfig = { - testFiles: '**/*.*', - ignoreTestFiles: ['*.hot-update.js', '*.ignore.*.*'], - fixturesFolder: 'cypress/fixtures', - integrationFolder: 'cypress/tests', - supportFile: 'cypress/support/index.js', - }; - - let specCount = getTotalSpecs(specConfig); - - expect(specCount).toEqual(4); - - specConfig = { - specPattern: 'cypress/tests/**/*.spec.{js,ts}', - excludeSpecPattern: ['*.hot-update.js', '*.ignore.spec.*'], - supportFile: 'cypress/support/index.js', - fixturesFolder: 'cypress/fixtures', - }; - - specCount = getTotalSpecs(specConfig); - - expect(specCount).toEqual(4); - }); - }); - - describe('getFixtureFolderPattern', () => { - it('returns a glob pattern for fixtures folder', () => { - const specConfig = { fixturesFolder: `cypress${sep}fixtures` }; - - const specArray = getFixtureFolderPattern(specConfig); - expect(specArray).toHaveLength(1); - expect(specArray).toContain(`cypress${sep}fixtures${sep}**${sep}*`); - }); - }); - describe('getExcludeSpecPattern', () => { - it('getExcludeSpecPattern returns required pattern for cypress version <= 9', () => { - const specConfigString = { - integrationFolder: 'cypress/integration', - ignoreTestFiles: '*.hot-update.js', - fixturesFolder: 'cypress/fixtures', - supportFile: 'cypress/support/index.js', - }; - - const specConfigArray = { - integrationFolder: 'cypress/integration', - ignoreTestFiles: ['*.hot-update.js', '*.hot-update.ts'], - fixturesFolder: 'cypress/fixtures', - supportFile: 'cypress/support/index.js', - }; - - let patternArray = getExcludeSpecPattern(specConfigString); - expect(patternArray).toHaveLength(1); - expect(patternArray).toContain('*.hot-update.js'); - - patternArray = getExcludeSpecPattern(specConfigArray); - expect(patternArray).toHaveLength(2); - expect(patternArray).toContain('*.hot-update.js'); - expect(patternArray).toContain('*.hot-update.ts'); - }); - }); - - describe('getSpecPattern', () => { - it('returns the required glob pattern for cypress <=9 config when testFiles is an array', () => { - const specConfig = { - integrationFolder: 'cypress/integration', - testFiles: ['**/*.js', '**/*.ts'], - }; - - const patternArray = getSpecPattern(specConfig); - expect(patternArray).toHaveLength(2); - expect(patternArray[0]).toEqual( - path.join(specConfig.integrationFolder, specConfig.testFiles[0]), - ); - expect(patternArray[1]).toEqual( - path.join(specConfig.integrationFolder, specConfig.testFiles[1]), - ); - }); - - it('getSpecPattern returns the required glob pattern for cypress >= 10 config when specPattern is an array', () => { - const specConfig = { - specPattern: ['cypress/integration/**/*.js', 'cypress/integration/**/*.js'], - }; - - const patternArray = getSpecPattern(specConfig); - expect(patternArray).toHaveLength(2); - expect(patternArray[0]).toEqual(specConfig.specPattern[0]); - expect(patternArray[1]).toEqual(specConfig.specPattern[1]); - }); - - it('getSpecPattern returns the required glob pattern for cypress >= 10 config when specPattern is a string', () => { - const specConfig = { - specPattern: 'cypress/integration/**/*.js', - }; - - const patternArray = getSpecPattern(specConfig); - expect(patternArray).toHaveLength(1); - expect(patternArray[0]).toEqual(specConfig.specPattern); - }); - - it('getSpecPattern returns the required glob pattern for cypress <= 9 config when testFiles is a string', () => { - const specConfig = { - integrationFolder: 'cypress/integration', - testFiles: '**/*.js', - }; - - const patternArray = getSpecPattern(specConfig); - expect(patternArray).toHaveLength(1); - expect(patternArray[0]).toEqual( - path.join(specConfig.integrationFolder, specConfig.testFiles), - ); - }); - }); -}); diff --git a/test/utils/attachments.test.js b/test/utils/attachments.test.js new file mode 100644 index 0000000..af70850 --- /dev/null +++ b/test/utils/attachments.test.js @@ -0,0 +1,105 @@ +const mock = require('mock-fs'); +const path = require('path'); +const glob = require('glob'); +const { + getScreenshotAttachment, + // getVideoFile, + waitForFile, +} = require('../../lib/utils/attachments'); + +jest.mock('glob'); + +const sep = path.sep; + +describe('attachment utils', () => { + describe('getScreenshotAttachment', () => { + beforeEach(() => { + mock({ + '/example/screenshots/example.spec.js': { + 'suite name -- test name (failed).png': Buffer.from([8, 6, 7, 5, 3, 0, 9]), + 'suite name -- test name.png': Buffer.from([1, 2, 3, 4, 5, 6, 7]), + 'suite name -- test name (1).png': Buffer.from([8, 7, 6, 5, 4, 3, 2]), + 'customScreenshot1.png': Buffer.from([1, 1, 1, 1, 1, 1, 1]), + }, + }); + }); + + afterEach(() => { + mock.restore(); + }); + + it('getScreenshotAttachment: should not fail on undefined', async () => { + const testFile = undefined; + const attachment = await getScreenshotAttachment(testFile); + expect(attachment).not.toBeDefined(); + }); + + it('getScreenshotAttachment: should return attachment for absolute path', async () => { + const testFile = `${sep}example${sep}screenshots${sep}example.spec.js${sep}suite name -- test name (failed).png`; + const expectedAttachment = { + name: 'suite name -- test name (failed).png', + type: 'image/png', + content: Buffer.from([8, 6, 7, 5, 3, 0, 9]).toString('base64'), + }; + + const attachment = await getScreenshotAttachment(testFile); + + expect(attachment).toBeDefined(); + expect(attachment).toEqual(expectedAttachment); + }); + }); + + describe('waitForFile', () => { + const TEST_TIMEOUT_BASED_ON_INTERVAL = 15000; + beforeEach(() => { + jest.useFakeTimers(); + glob.mockReset(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + test( + 'resolves when file is found immediately', + async () => { + glob.mockResolvedValue(['file1.mp4']); + + const promise = waitForFile('*.mp4'); + jest.runOnlyPendingTimers(); + + await expect(promise).resolves.toBe('file1.mp4'); + }, + TEST_TIMEOUT_BASED_ON_INTERVAL, + ); + + test( + 'resolves when file is found after some intervals', + async () => { + glob + .mockResolvedValueOnce([]) // First call, no files + .mockResolvedValueOnce([]) // Second call, no files + .mockResolvedValue(['file1.mp4']); // Third call, file found + + const promise = waitForFile('*.mp4'); + jest.advanceTimersByTime(3000); + + await expect(promise).resolves.toBe('file1.mp4'); + }, + TEST_TIMEOUT_BASED_ON_INTERVAL, + ); + + test( + 'rejects when timeout is reached without finding the file with custom timeout and interval', + async () => { + glob.mockResolvedValue([]); + + const promise = waitForFile('*.mp4', 3000, 1000); + jest.advanceTimersByTime(3000); + + await expect(promise).rejects.toThrow(`Timeout of 3000ms reached, file *.mp4 not found.`); + }, + TEST_TIMEOUT_BASED_ON_INTERVAL, + ); + }); +}); diff --git a/test/utils/common.test.js b/test/utils/common.test.js new file mode 100644 index 0000000..bb4e63e --- /dev/null +++ b/test/utils/common.test.js @@ -0,0 +1,37 @@ +const { getCodeRef } = require('../../lib/utils/common'); + +describe('common utils', () => { + describe('getCodeRef', () => { + it('should return correct code ref for Windows paths', () => { + jest.mock('path', () => ({ + sep: '\\', + })); + const file = `test\\example.spec.js`; + const titlePath = ['rootDescribe', 'parentDescribe', 'testTitle']; + + const expectedCodeRef = `test/example.spec.js/rootDescribe/parentDescribe/testTitle`; + + const codeRef = getCodeRef(titlePath, file); + + expect(codeRef).toEqual(expectedCodeRef); + + jest.clearAllMocks(); + }); + + it('should return correct code ref for POSIX paths', () => { + jest.mock('path', () => ({ + sep: '/', + })); + const file = `test/example.spec.js`; + const titlePath = ['rootDescribe', 'parentDescribe', 'testTitle']; + + const expectedCodeRef = `test/example.spec.js/rootDescribe/parentDescribe/testTitle`; + + const codeRef = getCodeRef(titlePath, file); + + expect(codeRef).toEqual(expectedCodeRef); + + jest.clearAllMocks(); + }); + }); +}); diff --git a/test/utils/objectCreators.test.js b/test/utils/objectCreators.test.js new file mode 100644 index 0000000..955a8ef --- /dev/null +++ b/test/utils/objectCreators.test.js @@ -0,0 +1,771 @@ +const path = require('path'); +const { + getSystemAttributes, + getLaunchStartObject, + getSuiteStartInfo, + getSuiteEndInfo, + getSuiteStartObject, + getSuiteEndObject, + getTestInfo, + getTestStartObject, + getTestEndObject, + getHookInfo, + getHookStartObject, + getAgentInfo, + getConfig, +} = require('../../lib/utils/objectCreators'); +const pjson = require('../../package.json'); + +const sep = path.sep; + +const { RealDate, MockedDate, currentDate, getDefaultConfig } = require('../mock/mocks'); +const { testItemStatuses, entityType } = require('../../lib/constants'); + +describe('object creators', () => { + const testFileName = `test${sep}example.spec.js`; + + beforeEach(() => { + global.Date = jest.fn(MockedDate); + Object.assign(Date, RealDate); + }); + + afterEach(() => { + jest.clearAllMocks(); + global.Date = RealDate; + }); + + describe('getAgentInfo', () => { + it('getAgentInfo: should contain version and name properties', () => { + const agentInfo = getAgentInfo(); + + expect(Object.keys(agentInfo)).toContain('version'); + expect(Object.keys(agentInfo)).toContain('name'); + }); + }); + + describe('getSystemAttributes', () => { + it('skippedIssue undefined. Should return attribute with agent name and version', function () { + const options = getDefaultConfig(); + const expectedSystemAttributes = [ + { + key: 'agent', + value: `${pjson.name}|${pjson.version}`, + system: true, + }, + ]; + + const systemAttributes = getSystemAttributes(options); + + expect(systemAttributes).toEqual(expectedSystemAttributes); + }); + + it('skippedIssue = true. Should return attribute with agent name and version', function () { + const options = getDefaultConfig(); + options.reporterOptions.skippedIssue = true; + const expectedSystemAttributes = [ + { + key: 'agent', + value: `${pjson.name}|${pjson.version}`, + system: true, + }, + ]; + + const systemAttributes = getSystemAttributes(options); + + expect(systemAttributes).toEqual(expectedSystemAttributes); + }); + + it('skippedIssue = false. Should return 2 attribute: with agent name/version and skippedIssue', function () { + const options = getDefaultConfig(); + options.reporterOptions.skippedIssue = false; + const expectedSystemAttributes = [ + { + key: 'agent', + value: `${pjson.name}|${pjson.version}`, + system: true, + }, + { + key: 'skippedIssue', + value: 'false', + system: true, + }, + ]; + + const systemAttributes = getSystemAttributes(options); + + expect(systemAttributes).toEqual(expectedSystemAttributes); + }); + }); + + describe('getConfig', () => { + const baseReporterOptions = { + endpoint: 'https://reportportal.server/api/v1', + project: 'ProjectName', + launch: 'LauncherName', + description: 'Launch description', + attributes: [], + }; + + describe('CI_BUILD_ID attribute providing', () => { + afterEach(() => { + delete process.env.CI_BUILD_ID; + }); + + it('should not add an attribute with the CI_BUILD_ID value in case of parallel reporter option is false', function () { + process.env.CI_BUILD_ID = 'buildId'; + const initialConfig = { + reporter: '@reportportal/agent-js-cypress', + reporterOptions: { + ...baseReporterOptions, + apiKey: '123', + autoMerge: true, + parallel: false, + }, + }; + const expectedConfig = initialConfig; + + const config = getConfig(initialConfig); + + expect(config).toEqual(expectedConfig); + }); + + it('should not add an attribute with the CI_BUILD_ID value in case of autoMerge reporter option is false', function () { + process.env.CI_BUILD_ID = 'buildId'; + const initialConfig = { + reporter: '@reportportal/agent-js-cypress', + reporterOptions: { + ...baseReporterOptions, + apiKey: '123', + autoMerge: false, + parallel: true, + }, + }; + const expectedConfig = initialConfig; + + const config = getConfig(initialConfig); + + expect(config).toEqual(expectedConfig); + }); + + it('should not add an attribute with the value CI_BUILD_ID if the env variable CI_BUILD_ID does not exist', function () { + process.env.CI_BUILD_ID = undefined; + const initialConfig = { + reporter: '@reportportal/agent-js-cypress', + reporterOptions: { + ...baseReporterOptions, + apiKey: '123', + autoMerge: false, + parallel: true, + }, + }; + const expectedConfig = initialConfig; + + const config = getConfig(initialConfig); + + expect(config).toEqual(expectedConfig); + }); + + it('should return config with updated attributes (including attribute with CI_BUILD_ID value)', function () { + process.env.CI_BUILD_ID = 'buildId'; + const initialConfig = { + reporter: '@reportportal/agent-js-cypress', + reporterOptions: { + ...baseReporterOptions, + apiKey: '123', + autoMerge: true, + parallel: true, + }, + }; + const expectedConfig = { + reporter: '@reportportal/agent-js-cypress', + reporterOptions: { + ...initialConfig.reporterOptions, + attributes: [ + { + value: 'buildId', + }, + ], + }, + }; + + const config = getConfig(initialConfig); + + expect(config).toEqual(expectedConfig); + }); + }); + + describe('apiKey option priority', () => { + afterEach(() => { + delete process.env.RP_TOKEN; + delete process.env.RP_API_KEY; + }); + + it('should override token property if the ENV variable RP_TOKEN exists', function () { + process.env.RP_TOKEN = 'secret'; + const initialConfig = { + reporter: '@reportportal/agent-js-cypress', + reporterOptions: { + ...baseReporterOptions, + token: '123', + }, + }; + const expectedConfig = { + reporter: '@reportportal/agent-js-cypress', + reporterOptions: { + ...baseReporterOptions, + apiKey: 'secret', + }, + }; + + const config = getConfig(initialConfig); + + expect(config).toEqual(expectedConfig); + }); + + it('should override apiKey property if the ENV variable RP_API_KEY exists', function () { + process.env.RP_API_KEY = 'secret'; + const initialConfig = { + reporter: '@reportportal/agent-js-cypress', + reporterOptions: { + ...baseReporterOptions, + apiKey: '123', + }, + }; + const expectedConfig = { + reporter: '@reportportal/agent-js-cypress', + reporterOptions: { + ...baseReporterOptions, + apiKey: 'secret', + }, + }; + + const config = getConfig(initialConfig); + + expect(config).toEqual(expectedConfig); + }); + + it('should prefer apiKey property over deprecated token', function () { + const initialConfig = { + reporter: '@reportportal/agent-js-cypress', + reporterOptions: { + ...baseReporterOptions, + apiKey: '123', + token: '345', + }, + }; + const expectedConfig = { + reporter: '@reportportal/agent-js-cypress', + reporterOptions: { + ...baseReporterOptions, + apiKey: '123', + }, + }; + + const config = getConfig(initialConfig); + + expect(config).toEqual(expectedConfig); + }); + }); + }); + + describe('getLaunchStartObject', () => { + it('should return start launch object with correct values', () => { + const expectedStartLaunchObject = { + launch: 'LauncherName', + description: 'Launch description', + attributes: [ + { + key: 'agent', + system: true, + value: `${pjson.name}|${pjson.version}`, + }, + ], + startTime: currentDate, + rerun: undefined, + rerunOf: undefined, + mode: undefined, + }; + + const startLaunchObject = getLaunchStartObject(getDefaultConfig()); + + expect(startLaunchObject).toBeDefined(); + expect(startLaunchObject).toEqual(expectedStartLaunchObject); + }); + }); + + describe('getSuiteStartInfo', () => { + it('root suite: should return suite start info with undefined parentId', () => { + const suite = { + id: 'suite1', + title: 'suite name', + description: 'suite description', + root: true, + titlePath: () => ['suite name'], + }; + const expectedSuiteStartInfo = { + id: 'suite1', + title: 'suite name', + startTime: currentDate, + description: 'suite description', + codeRef: 'test/example.spec.js/suite name', + parentId: undefined, + testFileName: 'example.spec.js', + }; + + const suiteStartInfo = getSuiteStartInfo(suite, testFileName); + + expect(suiteStartInfo).toBeDefined(); + expect(suiteStartInfo).toEqual(expectedSuiteStartInfo); + }); + + it('nested suite: should return suite start info with parentId', () => { + const suite = { + id: 'suite1', + title: 'suite name', + description: 'suite description', + parent: { + id: 'parentSuiteId', + }, + titlePath: () => ['parent suite name', 'suite name'], + }; + const expectedSuiteStartInfo = { + id: 'suite1', + title: 'suite name', + startTime: currentDate, + description: 'suite description', + codeRef: 'test/example.spec.js/parent suite name/suite name', + parentId: 'parentSuiteId', + testFileName: 'example.spec.js', + }; + + const suiteStartInfo = getSuiteStartInfo(suite, testFileName); + + expect(suiteStartInfo).toBeDefined(); + expect(suiteStartInfo).toEqual(expectedSuiteStartInfo); + }); + }); + + describe('getSuiteEndInfo', () => { + it('no tests inside suite: should return suite end info without status', () => { + const suite = { + id: 'suite1', + title: 'suite name', + description: 'suite description', + parent: { + id: 'parentSuiteId', + }, + }; + const expectedSuiteEndInfo = { + id: 'suite1', + title: 'suite name', + endTime: currentDate, + }; + + const suiteEndInfo = getSuiteEndInfo(suite); + + expect(suiteEndInfo).toBeDefined(); + expect(suiteEndInfo).toEqual(expectedSuiteEndInfo); + }); + + it('no failed tests inside suite: should return suite end info with undefined status', () => { + const suite = { + id: 'suite1', + title: 'suite name', + description: 'suite description', + parent: { + id: 'parentSuiteId', + }, + tests: [{ state: 'passed' }, { state: 'skipped' }], + }; + const expectedSuiteEndInfo = { + id: 'suite1', + title: 'suite name', + endTime: currentDate, + status: undefined, + }; + + const suiteEndInfo = getSuiteEndInfo(suite); + + expect(suiteEndInfo).toBeDefined(); + expect(suiteEndInfo).toEqual(expectedSuiteEndInfo); + }); + + it('there are failed tests inside suite: should return suite end info with failed status', () => { + const suite = { + id: 'suite1', + title: 'suite name', + description: 'suite description', + parent: { + id: 'parentSuiteId', + }, + tests: [{ state: 'failed' }, { state: 'passed' }], + }; + const expectedSuiteEndInfo = { + id: 'suite1', + title: 'suite name', + endTime: currentDate, + status: testItemStatuses.FAILED, + }; + + const suiteEndInfo = getSuiteEndInfo(suite); + + expect(suiteEndInfo).toBeDefined(); + expect(suiteEndInfo).toEqual(expectedSuiteEndInfo); + }); + }); + + describe('getSuiteStartObject', () => { + it('should return suite start object', () => { + const suite = { + id: 'suite1', + title: 'suite name', + startTime: currentDate, + description: 'suite description', + codeRef: 'test/example.spec.js/suite name', + testFileName: 'example.spec.js', + }; + const expectedSuiteStartObject = { + type: entityType.SUITE, + name: 'suite name', + startTime: currentDate, + description: 'suite description', + codeRef: 'test/example.spec.js/suite name', + attributes: [], + }; + + const suiteStartObject = getSuiteStartObject(suite); + + expect(suiteStartObject).toBeDefined(); + expect(suiteStartObject).toEqual(expectedSuiteStartObject); + }); + }); + + describe('getSuiteEndObject', () => { + it('should return suite end object', () => { + const suite = { + id: 'suite1', + title: 'suite name', + endTime: currentDate, + status: testItemStatuses.FAILED, + }; + const expectedSuiteEndObject = { + status: testItemStatuses.FAILED, + endTime: currentDate, + }; + + const suiteEndObject = getSuiteEndObject(suite); + + expect(suiteEndObject).toBeDefined(); + expect(suiteEndObject).toEqual(expectedSuiteEndObject); + }); + }); + + describe('getTestInfo', () => { + it('passed test: should return test info with passed status', () => { + const test = { + id: 'testId1', + title: 'test name', + parent: { + id: 'parentSuiteId', + }, + state: 'passed', + titlePath: () => ['suite name', 'test name'], + }; + const expectedTestInfoObject = { + id: 'testId1', + title: 'test name', + status: 'passed', + parentId: 'parentSuiteId', + codeRef: 'test/example.spec.js/suite name/test name', + err: undefined, + testFileName, + }; + + const testInfoObject = getTestInfo(test, testFileName); + + expect(testInfoObject).toBeDefined(); + expect(testInfoObject).toEqual(expectedTestInfoObject); + }); + + it('pending test: should return test info with skipped status', () => { + const test = { + id: 'testId1', + title: 'test name', + parent: { + id: 'parentSuiteId', + }, + state: 'pending', + titlePath: () => ['suite name', 'test name'], + }; + const expectedTestInfoObject = { + id: 'testId1', + title: 'test name', + status: 'skipped', + parentId: 'parentSuiteId', + codeRef: 'test/example.spec.js/suite name/test name', + err: undefined, + testFileName, + }; + + const testInfoObject = getTestInfo(test, testFileName); + + expect(testInfoObject).toBeDefined(); + expect(testInfoObject).toEqual(expectedTestInfoObject); + }); + + it('should return test info with specified status and error', () => { + const test = { + id: 'testId', + title: 'test name', + parent: { + id: 'parentSuiteId', + }, + state: 'pending', + titlePath: () => ['suite name', 'test name'], + }; + const expectedTestInfoObject = { + id: 'testId', + title: 'test name', + status: 'failed', + parentId: 'parentSuiteId', + codeRef: 'test/example.spec.js/suite name/test name', + err: { message: 'error message' }, + testFileName, + }; + + const testInfoObject = getTestInfo(test, testFileName, 'failed', { + message: 'error message', + }); + + expect(testInfoObject).toBeDefined(); + expect(testInfoObject).toEqual(expectedTestInfoObject); + }); + }); + + describe('getTestStartObject', () => { + it('should return test start object', () => { + const test = { + id: 'testId1', + title: 'test name', + parent: { + id: 'parentSuiteId', + }, + codeRef: 'test/example.spec.js/suite name/test name', + }; + const expectedTestStartObject = { + name: 'test name', + startTime: currentDate, + attributes: [], + type: 'step', + codeRef: 'test/example.spec.js/suite name/test name', + }; + + const testInfoObject = getTestStartObject(test); + + expect(testInfoObject).toBeDefined(); + expect(testInfoObject).toEqual(expectedTestStartObject); + }); + }); + + describe('getTestEndObject', () => { + it('skippedIssue is not defined: should return test end object without issue', () => { + const testInfo = { + id: 'testId1', + title: 'test name', + status: 'skipped', + parent: { + id: 'parentSuiteId', + }, + }; + const expectedTestEndObject = { + endTime: currentDate, + status: testInfo.status, + }; + const testEndObject = getTestEndObject(testInfo); + + expect(testEndObject).toBeDefined(); + expect(testEndObject).toEqual(expectedTestEndObject); + }); + + it('skippedIssue = true: should return test end object without issue', () => { + const testInfo = { + id: 'testId1', + title: 'test name', + status: 'skipped', + parent: { + id: 'parentSuiteId', + }, + }; + const expectedTestEndObject = { + endTime: currentDate, + status: testInfo.status, + }; + const testEndObject = getTestEndObject(testInfo, true); + + expect(testEndObject).toBeDefined(); + expect(testEndObject).toEqual(expectedTestEndObject); + }); + + it('skippedIssue = false: should return test end object with issue NOT_ISSUE', () => { + const testInfo = { + id: 'testId1', + title: 'test name', + status: 'skipped', + parent: { + id: 'parentSuiteId', + }, + }; + const expectedTestEndObject = { + endTime: currentDate, + status: testInfo.status, + issue: { + issueType: 'NOT_ISSUE', + }, + }; + const testEndObject = getTestEndObject(testInfo, false); + + expect(testEndObject).toBeDefined(); + expect(testEndObject).toEqual(expectedTestEndObject); + }); + + it('testCaseId is defined: should return test end object with testCaseId', () => { + const testInfo = { + id: 'testId1', + title: 'test name', + status: 'skipped', + parent: { + id: 'parentSuiteId', + }, + testCaseId: 'testCaseId', + }; + const expectedTestEndObject = { + endTime: currentDate, + status: testInfo.status, + testCaseId: 'testCaseId', + }; + const testEndObject = getTestEndObject(testInfo); + + expect(testEndObject).toEqual(expectedTestEndObject); + }); + }); + + describe('getHookInfo', () => { + it('passed before each hook: should return hook info with passed status', () => { + const hook = { + id: 'testId', + title: '"before each" hook: hook name', + parent: { + id: 'parentSuiteId', + }, + state: 'passed', + hookName: 'before each', + hookId: 'hookId', + titlePath: () => ['suite name', 'hook name'], + }; + const expectedHookInfoObject = { + id: 'hookId_testId', + hookName: 'before each', + title: '"before each" hook: hook name', + status: 'passed', + parentId: 'parentSuiteId', + codeRef: 'test/example.spec.js/suite name/hook name', + err: undefined, + testFileName, + }; + + const hookInfoObject = getHookInfo(hook, testFileName); + + expect(hookInfoObject).toBeDefined(); + expect(hookInfoObject).toEqual(expectedHookInfoObject); + }); + + it('passed before all hook: should return correct hook info', () => { + const hook = { + id: 'testId', + title: '"before all" hook: hook name', + parent: { + id: 'parentSuiteId', + title: 'parent suite title', + parent: { + id: 'rootSuiteId', + title: 'root suite title', + }, + }, + state: 'passed', + hookName: 'before all', + hookId: 'hookId', + titlePath: () => ['suite name', 'hook name'], + }; + const expectedHookInfoObject = { + id: 'hookId_testId', + hookName: 'before all', + title: '"before all" hook: hook name', + status: 'passed', + parentId: 'rootSuiteId', + codeRef: 'test/example.spec.js/suite name/hook name', + err: undefined, + testFileName, + }; + + const hookInfoObject = getHookInfo(hook, testFileName); + + expect(hookInfoObject).toBeDefined(); + expect(hookInfoObject).toEqual(expectedHookInfoObject); + }); + + it('failed test: should return hook info with failed status', () => { + const test = { + id: 'testId', + hookName: 'before each', + title: '"before each" hook: hook name', + parent: { + id: 'parentSuiteId', + }, + state: 'failed', + failedFromHookId: 'hookId', + titlePath: () => ['suite name', 'hook name'], + }; + const expectedHookInfoObject = { + id: 'hookId_testId', + hookName: 'before each', + title: '"before each" hook: hook name', + status: 'failed', + parentId: 'parentSuiteId', + codeRef: 'test/example.spec.js/suite name/hook name', + err: undefined, + testFileName, + }; + + const hookInfoObject = getHookInfo(test, testFileName); + + expect(hookInfoObject).toBeDefined(); + expect(hookInfoObject).toEqual(expectedHookInfoObject); + }); + }); + + describe('getHookStartObject', () => { + it('should return hook start object', () => { + const hookInfo = { + id: 'hookId_testId', + hookName: 'before each', + title: '"before each" hook: hook name', + status: 'passed', + parentId: 'parentSuiteId', + titlePath: () => ['suite name', 'hook name'], + err: undefined, + }; + const expectedHookStartObject = { + name: 'hook name', + startTime: currentDate, + type: 'BEFORE_METHOD', + }; + + const hookInfoObject = getHookStartObject(hookInfo, testFileName, 'failed', { + message: 'error message', + }); + + expect(hookInfoObject).toBeDefined(); + expect(hookInfoObject).toEqual(expectedHookStartObject); + }); + }); +}); diff --git a/test/utils/specCountCalculation.test.js b/test/utils/specCountCalculation.test.js new file mode 100644 index 0000000..702b774 --- /dev/null +++ b/test/utils/specCountCalculation.test.js @@ -0,0 +1,176 @@ +const mock = require('mock-fs'); +const path = require('path'); +const { + getTotalSpecs, + getFixtureFolderPattern, + getExcludeSpecPattern, + getSpecPattern, +} = require('../../lib/utils/specCountCalculation'); + +const sep = path.sep; + +describe('spec count calculation', () => { + describe('getTotalSpecs', () => { + beforeEach(() => { + mock({ + 'cypress/tests': { + 'example1.spec.js': '', + 'example2.spec.js': '', + 'example3.spec.js': '', + 'example4.spec.ts': '', + 'example.ignore.spec.js': '', + }, + 'cypress/support': { + 'index.js': '', + }, + 'cypress/fixtures': { + 'fixtures1.js': '', + 'fixtures2.js': '', + }, + }); + }); + + afterEach(() => { + mock.restore(); + }); + + it('testFiles, integrationFolder, supportFile are specified: should count all files from integration folder', () => { + let specConfig = { + testFiles: '**/*.*', + ignoreTestFiles: '*.hot-update.js', + fixturesFolder: 'cypress/fixtures', + integrationFolder: 'cypress/tests', + supportFile: 'cypress/support/index.js', + }; + + let specCount = getTotalSpecs(specConfig); + + expect(specCount).toEqual(5); + + specConfig = { + excludeSpecPattern: '*.hot-update.js', + specPattern: 'cypress/tests/**/*.spec.{js,ts}', + supportFile: 'cypress/support/index.js', + fixturesFolder: 'cypress/fixtures', + }; + + specCount = getTotalSpecs(specConfig); + + expect(specCount).toEqual(5); + }); + + it('ignoreTestFiles are specified: should ignore specified files', () => { + let specConfig = { + testFiles: '**/*.*', + ignoreTestFiles: ['*.hot-update.js', '*.ignore.*.*'], + fixturesFolder: 'cypress/fixtures', + integrationFolder: 'cypress/tests', + supportFile: 'cypress/support/index.js', + }; + + let specCount = getTotalSpecs(specConfig); + + expect(specCount).toEqual(4); + + specConfig = { + specPattern: 'cypress/tests/**/*.spec.{js,ts}', + excludeSpecPattern: ['*.hot-update.js', '*.ignore.spec.*'], + supportFile: 'cypress/support/index.js', + fixturesFolder: 'cypress/fixtures', + }; + + specCount = getTotalSpecs(specConfig); + + expect(specCount).toEqual(4); + }); + }); + + describe('getFixtureFolderPattern', () => { + it('returns a glob pattern for fixtures folder', () => { + const specConfig = { fixturesFolder: `cypress${sep}fixtures` }; + + const specArray = getFixtureFolderPattern(specConfig); + expect(specArray).toHaveLength(1); + expect(specArray).toContain(`cypress${sep}fixtures${sep}**${sep}*`); + }); + }); + + describe('getExcludeSpecPattern', () => { + it('getExcludeSpecPattern returns required pattern for cypress version <= 9', () => { + const specConfigString = { + integrationFolder: 'cypress/integration', + ignoreTestFiles: '*.hot-update.js', + fixturesFolder: 'cypress/fixtures', + supportFile: 'cypress/support/index.js', + }; + + const specConfigArray = { + integrationFolder: 'cypress/integration', + ignoreTestFiles: ['*.hot-update.js', '*.hot-update.ts'], + fixturesFolder: 'cypress/fixtures', + supportFile: 'cypress/support/index.js', + }; + + let patternArray = getExcludeSpecPattern(specConfigString); + expect(patternArray).toHaveLength(1); + expect(patternArray).toContain('*.hot-update.js'); + + patternArray = getExcludeSpecPattern(specConfigArray); + expect(patternArray).toHaveLength(2); + expect(patternArray).toContain('*.hot-update.js'); + expect(patternArray).toContain('*.hot-update.ts'); + }); + }); + + describe('getSpecPattern', () => { + it('returns the required glob pattern for cypress <=9 config when testFiles is an array', () => { + const specConfig = { + integrationFolder: 'cypress/integration', + testFiles: ['**/*.js', '**/*.ts'], + }; + + const patternArray = getSpecPattern(specConfig); + expect(patternArray).toHaveLength(2); + expect(patternArray[0]).toEqual( + path.join(specConfig.integrationFolder, specConfig.testFiles[0]), + ); + expect(patternArray[1]).toEqual( + path.join(specConfig.integrationFolder, specConfig.testFiles[1]), + ); + }); + + it('getSpecPattern returns the required glob pattern for cypress >= 10 config when specPattern is an array', () => { + const specConfig = { + specPattern: ['cypress/integration/**/*.js', 'cypress/integration/**/*.js'], + }; + + const patternArray = getSpecPattern(specConfig); + expect(patternArray).toHaveLength(2); + expect(patternArray[0]).toEqual(specConfig.specPattern[0]); + expect(patternArray[1]).toEqual(specConfig.specPattern[1]); + }); + + it('getSpecPattern returns the required glob pattern for cypress >= 10 config when specPattern is a string', () => { + const specConfig = { + specPattern: 'cypress/integration/**/*.js', + }; + + const patternArray = getSpecPattern(specConfig); + expect(patternArray).toHaveLength(1); + expect(patternArray[0]).toEqual(specConfig.specPattern); + }); + + it('getSpecPattern returns the required glob pattern for cypress <= 9 config when testFiles is a string', () => { + const specConfig = { + integrationFolder: 'cypress/integration', + testFiles: '**/*.js', + }; + + const patternArray = getSpecPattern(specConfig); + expect(patternArray).toHaveLength(1); + expect(patternArray[0]).toEqual( + path.join(specConfig.integrationFolder, specConfig.testFiles), + ); + }); + }); +});