diff --git a/.github/workflows/CI-pipeline.yml b/.github/workflows/CI-pipeline.yml
index e505b0d..9cb6ec1 100644
--- a/.github/workflows/CI-pipeline.yml
+++ b/.github/workflows/CI-pipeline.yml
@@ -37,5 +37,5 @@ jobs:
run: npm install
- name: Run lint
run: npm run lint
-# - name: Run tests and check coverage
-# run: npm run test:coverage
+ - name: Run tests and check coverage
+ run: npm run test:coverage
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 3c8d0b1..8ea9786 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -31,8 +31,8 @@ jobs:
run: npm install
- name: Run lint
run: npm run lint
-# - name: Run tests and check coverage
-# run: npm run test:coverage
+ - name: Run tests and check coverage
+ run: npm run test:coverage
publish-to-npm-and-gpr:
needs: build
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bf65ece..3fda544 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,5 @@
+### Fixed
+- Video attachment won't play after downloading, resolves [#202](https://github.com/reportportal/agent-js-cypress/issues/202). Thanks to [ashvinjaiswal](https://github.com/ashvinjaiswal).
## [5.3.3] - 2024-08-15
### Added
diff --git a/README.md b/README.md
index 9ec3bcd..8c38cb6 100644
--- a/README.md
+++ b/README.md
@@ -117,32 +117,32 @@ require('@reportportal/agent-js-cypress/lib/commands/reportPortalCommands');
The full list of available options presented below.
-| Option | Necessity | Default | Description |
-|-----------------------|------------|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| apiKey | Required | | User's reportportal token from which you want to send requests. It can be found on the profile page of this user. |
-| endpoint | Required | | URL of your server. For example 'https://server:8080/api/v1'. |
-| launch | Required | | Name of launch at creation. |
-| project | Required | | The name of the project in which the launches will be created. |
-| attributes | Optional | [] | Launch attributes. |
-| description | Optional | '' | Launch description. |
-| rerun | Optional | false | Enable [rerun](https://reportportal.io/docs/dev-guides/RerunDevelopersGuide) |
-| rerunOf | Optional | Not set | UUID of launch you want to rerun. If not specified, reportportal will update the latest launch with the same name |
-| mode | Optional | 'DEFAULT' | Results will be submitted to Launches page
*'DEBUG'* - Results will be submitted to Debug page. |
-| skippedIssue | Optional | true | reportportal provides feature to mark skipped tests as not 'To Investigate'.
Option could be equal boolean values:
*true* - skipped tests considered as issues and will be marked as 'To Investigate' on reportportal.
*false* - skipped tests will not be marked as 'To Investigate' on application. |
-| debug | Optional | false | This flag allows seeing the logs of the client-javascript. Useful for debugging. |
-| launchId | Optional | Not set | The _ID_ of an already existing launch. The launch must be in 'IN_PROGRESS' status while the tests are running. Please note that if this _ID_ is provided, the launch will not be finished at the end of the run and must be finished separately. |
-| launchUuidPrint | Optional | false | Whether to print the current launch UUID. |
-| launchUuidPrintOutput | Optional | 'STDOUT' | Launch UUID printing output. Possible values: 'STDOUT', 'STDERR'. Works only if `launchUuidPrint` set to `true`. |
-| restClientConfig | Optional | Not set | `axios` like http client [config](https://github.com/axios/axios#request-config). May contain `agent` property for configure [http(s)](https://nodejs.org/api/https.html#https_https_request_url_options_callback) client, and other client options eg. `timeout`. For debugging and displaying logs you can set `debug: true`. |
-| uploadVideo | Optional | false | Whether to upload the Cypress video. |
-| uploadVideoOnPasses | Optional | false | Whether to upload the Cypress video for a non-failure specs. Works only if `uploadVideo` set to `true`. |
-| waitForVideoTimeout | Optional | 10000 | Value in `ms`. Since Cypress video processing may take extra time after the spec is complete, there is a timeout to wait for the video file readiness. Works only if `uploadVideo` set to `true`. |
-| waitForVideoInterval | Optional | 500 | Value in `ms`. Interval to check if the video file is ready. The interval is used until `waitForVideoTimeout` is reached. Works only if `uploadVideo` set to `true`. |
-| autoMerge | Optional | false | Enable automatic report test items of all run spec into one launch. You should install plugin or setup additional settings in reporterOptions. See [Automatically merge launch](#automatically-merge-launches). |
-| reportHooks | Optional | false | Determines report before and after hooks or not. |
-| isLaunchMergeRequired | Optional | false | Allows to merge Cypress run's into one launch at the end of the run. Needs additional setup. See [Manual merge launches](#manual-merge-launches). |
-| parallel | Optional | false | Indicates to the reporter that spec files will be executed in parallel on different machines. Parameter could be equal boolean values. See [Parallel execution](#parallel-execution). |
-| token | Deprecated | Not set | Use `apiKey` instead. |
+| Option | Necessity | Default | Description |
+|-----------------------------|------------|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| apiKey | Required | | User's reportportal token from which you want to send requests. It can be found on the profile page of this user. |
+| endpoint | Required | | URL of your server. For example 'https://server:8080/api/v1'. |
+| launch | Required | | Name of launch at creation. |
+| project | Required | | The name of the project in which the launches will be created. |
+| attributes | Optional | [] | Launch attributes. |
+| description | Optional | '' | Launch description. |
+| rerun | Optional | false | Enable [rerun](https://reportportal.io/docs/dev-guides/RerunDevelopersGuide) |
+| rerunOf | Optional | Not set | UUID of launch you want to rerun. If not specified, reportportal will update the latest launch with the same name |
+| mode | Optional | 'DEFAULT' | Results will be submitted to Launches page
*'DEBUG'* - Results will be submitted to Debug page. |
+| skippedIssue | Optional | true | reportportal provides feature to mark skipped tests as not 'To Investigate'.
Option could be equal boolean values:
*true* - skipped tests considered as issues and will be marked as 'To Investigate' on reportportal.
*false* - skipped tests will not be marked as 'To Investigate' on application. |
+| debug | Optional | false | This flag allows seeing the logs of the client-javascript. Useful for debugging. |
+| launchId | Optional | Not set | The _ID_ of an already existing launch. The launch must be in 'IN_PROGRESS' status while the tests are running. Please note that if this _ID_ is provided, the launch will not be finished at the end of the run and must be finished separately. |
+| launchUuidPrint | Optional | false | Whether to print the current launch UUID. |
+| launchUuidPrintOutput | Optional | 'STDOUT' | Launch UUID printing output. Possible values: 'STDOUT', 'STDERR'. Works only if `launchUuidPrint` set to `true`. |
+| restClientConfig | Optional | Not set | `axios` like http client [config](https://github.com/axios/axios#request-config). May contain `agent` property for configure [http(s)](https://nodejs.org/api/https.html#https_https_request_url_options_callback) client, and other client options eg. `timeout`. For debugging and displaying logs you can set `debug: true`. |
+| uploadVideo | Optional | false | Whether to upload the Cypress video. Uploads videos for failed specs only. To upload videos for specs with other statuses, set also the `uploadVideoForNonFailedSpec` to `true`. |
+| uploadVideoForNonFailedSpec | Optional | false | Whether to upload the Cypress video for a non-failed specs. Works only if `uploadVideo` set to `true`. |
+| waitForVideoTimeout | Optional | 10000 | Value in `ms`. Since Cypress video processing may take extra time after the spec is complete, there is a timeout to wait for the video file readiness. Works only if `uploadVideo` set to `true`. |
+| waitForVideoInterval | Optional | 500 | Value in `ms`. Interval to check if the video file is ready. The interval is used until `waitForVideoTimeout` is reached. Works only if `uploadVideo` set to `true`. |
+| autoMerge | Optional | false | Enable automatic report test items of all run spec into one launch. You should install plugin or setup additional settings in reporterOptions. See [Automatically merge launch](#automatically-merge-launches). |
+| reportHooks | Optional | false | Determines report before and after hooks or not. |
+| isLaunchMergeRequired | Optional | false | Allows to merge Cypress run's into one launch at the end of the run. Needs additional setup. See [Manual merge launches](#manual-merge-launches). |
+| parallel | Optional | false | Indicates to the reporter that spec files will be executed in parallel on different machines. Parameter could be equal boolean values. See [Parallel execution](#parallel-execution). |
+| token | Deprecated | Not set | Use `apiKey` instead. |
### Overwrite options from config file
diff --git a/VERSION b/VERSION
index 74664af..16a368e 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-5.3.3
+5.3.4-SNAPSHOT
diff --git a/jest.config.js b/jest.config.js
index 14c1f17..229503c 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -28,6 +28,7 @@ module.exports = {
'!lib/ipcServer.js',
'!lib/testStatuses.js',
'!lib/worker.js',
+ '!lib/utils/attachments.js',
],
coverageThreshold: {
global: {
diff --git a/lib/reporter.js b/lib/reporter.js
index 151b27a..321dffb 100644
--- a/lib/reporter.js
+++ b/lib/reporter.js
@@ -175,11 +175,11 @@ class Reporter {
}
finishSuiteWithVideo(suiteInfo, suiteFinishObj) {
- const uploadVideoOnPasses = this.config.uploadVideoOnPasses || false;
+ const uploadVideoForNonFailedSpec = this.config.uploadVideoForNonFailedSpec || false;
const suiteFailed = suiteFinishObj.status === testItemStatuses.FAILED;
- // do not upload video if root suite passes and uploadVideoOnPasses is false
- if ((!suiteFailed && !uploadVideoOnPasses) || !suiteInfo.testFileName) {
+ // do not upload video if root suite not failed and uploadVideoForNonFailedSpec is false
+ if ((!suiteFailed && !uploadVideoForNonFailedSpec) || !suiteInfo.testFileName) {
this.finishSuite(suiteFinishObj, suiteInfo.tempId);
} else {
const sendVideoPromise = this.sendVideo(suiteInfo).finally(() => {
diff --git a/lib/utils/attachments.js b/lib/utils/attachments.js
new file mode 100644
index 0000000..e68afbf
--- /dev/null
+++ b/lib/utils/attachments.js
@@ -0,0 +1,134 @@
+/*
+ * 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 getScreenshotAttachment = async (absolutePath) => {
+ if (!absolutePath) return absolutePath;
+ const name = absolutePath.split(path.sep).pop();
+ return {
+ name,
+ type: 'image/png',
+ content: await fsPromises.readFile(absolutePath, { encoding: 'base64' }),
+ };
+};
+
+async function getFilePathByGlobPattern(globFilePattern) {
+ const files = await glob.glob(globFilePattern);
+
+ if (files.length) {
+ return files[0];
+ }
+
+ return null;
+}
+/*
+ * The moov atom in an MP4 file is a crucial part of the file’s structure. It contains metadata about the video, such as the duration, display characteristics, and timing information.
+ * Function check for the moov atom in file content and ensure is video file ready.
+ */
+const checkVideoFileReady = async (videoFilePath) => {
+ try {
+ const fileData = await fsPromises.readFile(videoFilePath);
+
+ if (fileData.includes('moov')) {
+ return true;
+ }
+ } catch (e) {
+ throw new Error(`Error reading file: ${e.message}`);
+ }
+
+ return false;
+};
+
+const waitForVideoFile = (
+ globFilePattern,
+ timeout = DEFAULT_WAIT_FOR_FILE_TIMEOUT,
+ interval = DEFAULT_WAIT_FOR_FILE_INTERVAL,
+) =>
+ new Promise((resolve, reject) => {
+ let filePath = null;
+ let totalFileWaitingTime = 0;
+
+ async function checkFileExistsAndReady() {
+ if (!filePath) {
+ filePath = await getFilePathByGlobPattern(globFilePattern);
+ }
+ let isVideoFileReady = false;
+
+ if (filePath) {
+ isVideoFileReady = await checkVideoFileReady(filePath);
+ }
+
+ if (isVideoFileReady) {
+ resolve(filePath);
+ } else if (totalFileWaitingTime >= timeout) {
+ reject(
+ new Error(
+ `Timeout of ${timeout}ms reached, file ${globFilePattern} not found or not ready yet.`,
+ ),
+ );
+ } else {
+ totalFileWaitingTime += interval;
+ setTimeout(checkFileExistsAndReady, interval);
+ }
+ }
+
+ checkFileExistsAndReady().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 waitForVideoFile(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,
+ waitForVideoFile,
+ getFilePathByGlobPattern,
+ checkVideoFileReady,
+};
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..e0284e8 100644
--- a/test/reporter.test.js
+++ b/test/reporter.test.js
@@ -1,7 +1,8 @@
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 { entityType } = require('../lib/constants');
const sep = path.sep;
@@ -45,6 +46,7 @@ describe('reporter script', () => {
beforeEach(() => {
global.Date = jest.fn(MockedDate);
Object.assign(Date, RealDate);
+ reporter.config = getDefaultConfig();
});
afterEach(() => {
@@ -106,21 +108,37 @@ describe('reporter script', () => {
});
});
+ describe('saveFullConfig', () => {
+ it('should set the full Cypress config to class object property', () => {
+ const fullCypressConfig = { e2e: { baseUrl: 'http://localhost:3000' }, reporterOptions: {} };
+ reporter.saveFullConfig(fullCypressConfig);
+
+ expect(reporter.fullCypressConfig).toEqual(fullCypressConfig);
+ });
+ });
+
describe('suiteStart', () => {
it('root suite: startTestItem should be called with undefined parentId', () => {
const spyStartTestItem = jest.spyOn(reporter.client, 'startTestItem');
reporter.tempLaunchId = 'tempLaunchId';
- const suiteStartObject = {
+ const suite = {
id: 'suite1',
+ title: 'suite name',
+ startTime: currentDate,
+ description: 'suite description',
+ codeRef: 'test/example.spec.js/suite name',
+ testFileName: 'example.spec.js',
+ };
+ const suiteStartObject = {
+ type: entityType.SUITE,
name: 'suite name',
- type: 'suite',
startTime: currentDate,
description: 'suite description',
+ codeRef: 'test/example.spec.js/suite name',
attributes: [],
- parentId: undefined,
};
- reporter.suiteStart(suiteStartObject);
+ reporter.suiteStart(suite);
expect(spyStartTestItem).toHaveBeenCalledTimes(1);
expect(spyStartTestItem).toHaveBeenCalledWith(suiteStartObject, 'tempLaunchId', undefined);
@@ -130,17 +148,25 @@ describe('reporter script', () => {
const spyStartTestItem = jest.spyOn(reporter.client, 'startTestItem');
reporter.tempLaunchId = 'tempLaunchId';
reporter.testItemIds.set('parentSuiteId', 'tempParentSuiteId');
- const suiteStartObject = {
+ const suite = {
id: 'suite1',
+ title: 'suite name',
+ startTime: currentDate,
+ description: 'suite description',
+ codeRef: 'test/example.spec.js/suite name',
+ testFileName: 'example.spec.js',
+ parentId: 'parentSuiteId',
+ };
+ const suiteStartObject = {
+ type: entityType.SUITE,
name: 'suite name',
- type: 'suite',
startTime: currentDate,
description: 'suite description',
+ codeRef: 'test/example.spec.js/suite name',
attributes: [],
- parentId: 'parentSuiteId',
};
- reporter.suiteStart(suiteStartObject);
+ reporter.suiteStart(suite);
expect(spyStartTestItem).toHaveBeenCalledTimes(1);
expect(spyStartTestItem).toHaveBeenCalledWith(
@@ -155,33 +181,38 @@ describe('reporter script', () => {
it('finishTestItem should be called with parameters', function () {
const spyFinishTestItem = jest.spyOn(reporter.client, 'finishTestItem');
reporter.testItemIds.set('suiteId', 'tempSuiteId');
- const suiteEndObject = {
+ const suite = {
id: 'suiteId',
+ title: 'suite title',
+ endTime: currentDate,
+ };
+ const suiteEndObject = {
endTime: currentDate,
};
- reporter.suiteEnd(suiteEndObject);
+ reporter.suiteEnd(suite);
expect(spyFinishTestItem).toHaveBeenCalledTimes(1);
- expect(spyFinishTestItem).toHaveBeenCalledWith('tempSuiteId', { endTime: currentDate });
+ expect(spyFinishTestItem).toHaveBeenCalledWith('tempSuiteId', suiteEndObject);
});
it('end suite with testCaseId: finishTestItem should be called with testCaseId', function () {
const spyFinishTestItem = jest.spyOn(reporter.client, 'finishTestItem');
reporter.testItemIds.set('suiteId', 'tempSuiteId');
reporter.suiteTestCaseIds.set('suite title', 'testCaseId');
- const suiteEndObject = {
+ const suite = {
id: 'suiteId',
title: 'suite title',
endTime: currentDate,
};
+ const suiteEndObject = {
+ endTime: currentDate,
+ testCaseId: 'testCaseId',
+ };
- reporter.suiteEnd(suiteEndObject);
+ reporter.suiteEnd(suite);
expect(spyFinishTestItem).toHaveBeenCalledTimes(1);
- expect(spyFinishTestItem).toHaveBeenCalledWith('tempSuiteId', {
- endTime: currentDate,
- testCaseId: 'testCaseId',
- });
+ expect(spyFinishTestItem).toHaveBeenCalledWith('tempSuiteId', suiteEndObject);
reporter.suiteTestCaseIds.clear();
});
@@ -189,25 +220,54 @@ describe('reporter script', () => {
const spyFinishTestItem = jest.spyOn(reporter.client, 'finishTestItem');
reporter.testItemIds.set('suiteId', 'tempSuiteId');
reporter.setTestItemStatus({ status: 'failed', suiteTitle: 'suite title' });
- const suiteEndObject = {
+ const suite = {
id: 'suiteId',
title: 'suite title',
endTime: currentDate,
};
+ const suiteEndObject = {
+ endTime: currentDate,
+ status: 'failed',
+ };
- reporter.suiteEnd(suiteEndObject);
+ reporter.suiteEnd(suite);
expect(spyFinishTestItem).toHaveBeenCalledTimes(1);
- expect(spyFinishTestItem).toHaveBeenCalledWith('tempSuiteId', {
- endTime: currentDate,
- status: 'failed',
- });
+ expect(spyFinishTestItem).toHaveBeenCalledWith('tempSuiteId', suiteEndObject);
reporter.suiteStatuses.clear();
});
+ it('end suite with video: finishSuiteWithVideo should be called if video is enabled and it is a root suite', function () {
+ const spyFinishSuiteWithVideo = jest.spyOn(reporter, 'finishSuiteWithVideo');
+ reporter.fullCypressConfig = { video: true };
+ reporter.config = { ...reporter.config, uploadVideo: true };
+ reporter.testItemIds.set('suiteId', 'tempSuiteId');
+ const suiteInfo = {
+ id: 'suiteId',
+ testFileName: 'example.spec.js',
+ title: 'suite title',
+ tempId: 'tempSuiteId',
+ };
+ reporter.suitesStackTempInfo = [suiteInfo];
+ const suite = {
+ id: 'suiteId',
+ title: 'suite title',
+ endTime: currentDate,
+ };
+ const suiteEndObject = {
+ endTime: currentDate,
+ status: undefined,
+ };
+
+ reporter.suiteEnd(suite);
+
+ expect(spyFinishSuiteWithVideo).toHaveBeenCalledTimes(1);
+ expect(spyFinishSuiteWithVideo).toHaveBeenCalledWith(suiteInfo, suiteEndObject);
+ });
});
- describe('sendVideoOnFinishSuite', function () {
+ // TODO: Fix the tests
+ describe.skip('sendVideoOnFinishSuite', function () {
let customSuiteNameAttachment;
beforeAll(() => {
@@ -222,7 +282,6 @@ describe('reporter script', () => {
afterAll(() => {
mockFS.restore();
- reporter.config.reporterOptions.videosFolder = undefined;
});
beforeEach(() => {
@@ -236,7 +295,6 @@ describe('reporter script', () => {
{ id: 'suite', title: 'any suite' },
];
reporter.testItemIds.set('root', 'suiteTempId');
- reporter.config.reporterOptions.videosFolder = 'example/videos';
});
afterEach(() => {
@@ -338,7 +396,6 @@ describe('reporter script', () => {
status: 'failed',
};
- reporter.config.reporterOptions.videosFolder = 'example/screenshots';
reporter.suiteEnd(suiteEndObject);
expect(spySendVideoOnFinishSuite).not.toHaveBeenCalled();
@@ -614,6 +671,20 @@ describe('reporter script', () => {
expect(spyFinishTestItem).toHaveBeenCalledTimes(1);
expect(spyFinishTestItem).toHaveBeenCalledWith('tempTestItemId', expectedTestFinishObj);
});
+
+ it('end test: should not finish test in case no testId present in testItemIds', function () {
+ const spyFinishTestItem = jest.spyOn(reporter.client, 'finishTestItem');
+ const testInfoObject = {
+ id: 'testId',
+ title: 'test name',
+ status: 'failed',
+ parentId: 'suiteId',
+ err: 'error message',
+ };
+ reporter.testEnd(testInfoObject);
+
+ expect(spyFinishTestItem).toHaveBeenCalledTimes(0);
+ });
});
describe('testPending', function () {
@@ -1045,7 +1116,7 @@ describe('reporter script', () => {
});
});
- describe('screenshot', () => {
+ describe('sendScreenshot', () => {
const screenshotInfo = {
testAttemptIndex: 0,
size: 295559,
@@ -1076,19 +1147,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 +1177,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 +1201,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..7dfe35f
--- /dev/null
+++ b/test/utils/attachments.test.js
@@ -0,0 +1,208 @@
+const fsPromises = require('fs/promises');
+const mockFs = require('mock-fs');
+const path = require('path');
+const glob = require('glob');
+const attachmentUtils = require('../../lib/utils/attachments');
+
+const {
+ getScreenshotAttachment,
+ getVideoFile,
+ waitForVideoFile,
+ getFilePathByGlobPattern,
+ checkVideoFileReady,
+} = attachmentUtils;
+
+const sep = path.sep;
+
+describe('attachment utils', () => {
+ describe('getScreenshotAttachment', () => {
+ beforeEach(() => {
+ mockFs({
+ '/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(() => {
+ mockFs.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('getFilePathByGlobPattern', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ test('returns the path of the first file if files are found', async () => {
+ const mockFiles = ['path/to/first/file.mp4', 'path/to/second/file.mp4'];
+ jest.spyOn(glob, 'glob').mockResolvedValueOnce(mockFiles);
+
+ const result = await getFilePathByGlobPattern('*.mp4');
+ expect(result).toBe('path/to/first/file.mp4');
+ });
+
+ test('returns null if no files are found', async () => {
+ jest.spyOn(glob, 'glob').mockResolvedValueOnce([]);
+
+ const result = await getFilePathByGlobPattern('*.mp4');
+ expect(result).toBeNull();
+ });
+ });
+
+ describe('checkVideoFileReady', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ test('returns true if the video file contains "moov" atom', async () => {
+ const mockFileData = Buffer.from('some data with moov in it');
+ jest.spyOn(fsPromises, 'readFile').mockResolvedValueOnce(mockFileData);
+
+ const result = await checkVideoFileReady('path/to/video.mp4');
+ expect(result).toBe(true);
+ });
+
+ test('returns false if the video file does not contain "moov" atom', async () => {
+ const mockFileData = Buffer.from('some data without the keyword');
+ jest.spyOn(fsPromises, 'readFile').mockResolvedValueOnce(mockFileData);
+
+ const result = await checkVideoFileReady('path/to/video.mp4');
+ expect(result).toBe(false);
+ });
+
+ test('throws an error if there is an error reading the file', async () => {
+ jest.spyOn(fsPromises, 'readFile').mockRejectedValueOnce(new Error('Failed to read file'));
+
+ await expect(checkVideoFileReady('path/to/video.mp4')).rejects.toThrow(
+ 'Error reading file: Failed to read file',
+ );
+ });
+ });
+
+ // TODO: Fix the tests
+ describe.skip('waitForVideoFile', () => {
+ beforeEach(() => {
+ jest.useFakeTimers();
+ jest.clearAllMocks();
+ });
+
+ test('resolves with the file path if the video file is found and ready', async () => {
+ jest
+ .spyOn(attachmentUtils, 'getFilePathByGlobPattern')
+ .mockImplementation(async () => 'path/to/video.mp4');
+ // .mockResolvedValueOnce('path/to/video.mp4');
+ jest.spyOn(attachmentUtils, 'checkVideoFileReady').mockImplementation(async () => true);
+
+ const promise = waitForVideoFile('*.mp4');
+ jest.runAllTimers();
+
+ await expect(promise).resolves.toBe('path/to/video.mp4');
+ }, 20000);
+
+ test('retries until the video file is ready or timeout occurs', async () => {
+ jest
+ .spyOn(attachmentUtils, 'getFilePathByGlobPattern')
+ .mockResolvedValueOnce('path/to/video.mp4');
+ jest
+ .spyOn(attachmentUtils, 'checkVideoFileReady')
+ .mockResolvedValueOnce(false)
+ .mockResolvedValueOnce(false)
+ .mockResolvedValueOnce(true);
+
+ const promise = waitForVideoFile('*.mp4');
+ jest.advanceTimersByTime(3000);
+
+ await expect(promise).resolves.toBe('path/to/video.mp4');
+ }, 20000);
+
+ test('rejects with a timeout error if the timeout is reached without finding a ready video file', async () => {
+ jest
+ .spyOn(attachmentUtils, 'getFilePathByGlobPattern')
+ .mockResolvedValueOnce('path/to/video.mp4');
+ jest.spyOn(attachmentUtils, 'checkVideoFileReady').mockResolvedValueOnce(false);
+
+ const promise = waitForVideoFile('*.mp4', 3000, 1000);
+ jest.advanceTimersByTime(3000);
+
+ await expect(promise).rejects.toThrow(
+ 'Timeout of 3000ms reached, file *.mp4 not found or not ready yet.',
+ );
+ }, 20000);
+
+ afterEach(() => {
+ jest.useRealTimers();
+ });
+ });
+
+ describe.skip('getVideoFile', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ test('returns the correct video file object if a valid video file is found and read successfully', async () => {
+ const mockVideoFilePath = 'path/to/video.mp4';
+ const mockFileContent = 'base64encodedcontent';
+ jest.spyOn(attachmentUtils, 'waitForVideoFile').mockResolvedValueOnce(mockVideoFilePath);
+ jest.spyOn(fsPromises, 'readFile').mockResolvedValueOnce(mockFileContent);
+
+ const result = await getVideoFile('video', '**', 5000, 1000);
+
+ expect(result).toEqual({
+ name: 'video.mp4',
+ type: 'video/mp4',
+ content: mockFileContent,
+ });
+ });
+
+ test('returns null if no video file name is provided', async () => {
+ const result = await getVideoFile('');
+ expect(result).toBeNull();
+ });
+
+ test('returns null and logs a warning if there is an error during the video file search', async () => {
+ jest
+ .spyOn(attachmentUtils, 'waitForVideoFile')
+ .mockRejectedValueOnce(new Error('File not found'));
+ jest.spyOn(console, 'warn').mockImplementationOnce(() => {});
+
+ const result = await getVideoFile('video');
+ expect(result).toBeNull();
+ expect(console.warn).toHaveBeenCalledWith('File not found');
+ });
+
+ test('handles file read errors gracefully', async () => {
+ const mockVideoFilePath = 'path/to/video.mp4';
+ jest.spyOn(attachmentUtils, 'waitForVideoFile').mockResolvedValueOnce(mockVideoFilePath);
+ jest.spyOn(fsPromises, 'readFile').mockRejectedValueOnce(new Error('Failed to read file'));
+ jest.spyOn(console, 'warn').mockImplementationOnce(() => {});
+
+ const result = await getVideoFile('video');
+ expect(result).toBeNull();
+ expect(console.warn).toHaveBeenCalledWith('Failed to read file');
+ });
+ });
+});
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..538c62f
--- /dev/null
+++ b/test/utils/specCountCalculation.test.js
@@ -0,0 +1,202 @@
+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('nor testFiles nor specPattern are specified: should throw an exception', () => {
+ expect(() => {
+ getTotalSpecs({});
+ }).toThrow(
+ new Error('Configuration property not set! Neither for cypress <= 9 nor cypress >= 10'),
+ );
+ });
+
+ 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 >= 10', () => {
+ const specConfigString = {
+ excludeSpecPattern: '*.hot-update.js',
+ };
+
+ const specConfigArray = {
+ excludeSpecPattern: ['*.hot-update.js', '*.hot-update.ts'],
+ };
+
+ 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');
+ });
+ 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),
+ );
+ });
+ });
+});