diff --git a/.eslintrc b/.eslintrc index b676b19..67846ac 100644 --- a/.eslintrc +++ b/.eslintrc @@ -19,6 +19,7 @@ "es6": true, "jest": true }, + "ignorePatterns": ["__tests__/**/*"], "rules": { "valid-jsdoc": ["error", { "requireReturn": false }], "consistent-return": 0, diff --git a/CHANGELOG.md b/CHANGELOG.md index 49053f9..6d18b9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### Added +- `skippedIsNotIssue` option to not mark skipped tests as 'To Investigate' in ReportPortal. +### Fixed +- Error for empty `restClientConfig` while using HTTP retries. ## [5.5.7] - 2025-12-09 ### Changed diff --git a/README.md b/README.md index f78c9f2..43e66e9 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ rpClient.checkConnect().then(() => { | restClientConfig | Optional | Not set | Check the details in the [HTTP client config](#http-client-options). | | launchUuidPrint | Optional | false | Whether to print the current launch UUID. | | launchUuidPrintOutput | Optional | 'STDOUT' | Launch UUID printing output. Possible values: 'STDOUT', 'STDERR', 'FILE', 'ENVIRONMENT'. Works only if `launchUuidPrint` set to `true`. File format: `rp-launch-uuid-${launch_uuid}.tmp`. Env variable: `RP_LAUNCH_UUID`. | -| token | Deprecated | Not set | Use `apiKey` or `oauth` instead. | +| skippedIsNotIssue | Optional | False | ReportPortal provides feature to mark skipped tests as not 'To Investigate'. Option could be equal boolean values: `true` - skipped tests will not be marked as 'To Investigate' on application. `false` - skipped tests considered as issues and will be marked as 'To Investigate' on application. | ### HTTP client options diff --git a/VERSION b/VERSION index 21f56a9..3aabb7a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.5.7 +5.5.8-SNAPSHOT diff --git a/__tests__/report-portal-client.spec.js b/__tests__/report-portal-client.spec.js index b715aef..e0ced6e 100644 --- a/__tests__/report-portal-client.spec.js +++ b/__tests__/report-portal-client.spec.js @@ -876,6 +876,108 @@ describe('ReportPortal javascript client', () => { }); }); + it('should automatically add NOT_ISSUE when status is SKIPPED and skippedIsNotIssue is true', function (done) { + const mockClient = new RPClient( + { + apiKey: 'test', + endpoint: 'https://reportportal-stub-url', + launch: 'test launch', + project: 'test project', + skippedIsNotIssue: true, + }, + { name: 'test', version: '1.0.0' }, + ); + + const spyFinishTestItemPromiseStart = jest + .spyOn(mockClient, 'finishTestItemPromiseStart') + .mockImplementation(() => {}); + + mockClient.map = { + testItemId: { + children: [], + finishSend: false, + promiseFinish: Promise.resolve(), + resolveFinish: () => {}, + }, + }; + + const finishTestItemRQ = { + status: 'skipped', + }; + + mockClient.finishTestItem('testItemId', finishTestItemRQ); + + setTimeout(() => { + try { + expect(spyFinishTestItemPromiseStart).toHaveBeenCalledWith( + expect.any(Object), + 'testItemId', + expect.objectContaining({ + status: 'skipped', + issue: { issueType: 'NOT_ISSUE' }, + }), + ); + done(); + } catch (error) { + done(error); + } + }, 50); + }); + + it('should not add NOT_ISSUE when status is SKIPPED and skippedIsNotIssue is false', function (done) { + const mockClient = new RPClient( + { + apiKey: 'test', + endpoint: 'https://reportportal-stub-url', + launch: 'test launch', + project: 'test project', + skippedIsNotIssue: false, + }, + { name: 'test', version: '1.0.0' }, + ); + + const spyFinishTestItemPromiseStart = jest + .spyOn(mockClient, 'finishTestItemPromiseStart') + .mockImplementation(() => {}); + + mockClient.map = { + testItemId: { + children: [], + finishSend: false, + promiseFinish: Promise.resolve(), + resolveFinish: () => {}, + }, + }; + + const finishTestItemRQ = { + status: 'skipped', + }; + + mockClient.finishTestItem('testItemId', finishTestItemRQ); + + setTimeout(() => { + try { + expect(spyFinishTestItemPromiseStart).toHaveBeenCalledWith( + expect.any(Object), + 'testItemId', + expect.objectContaining({ + status: 'skipped', + }), + ); + expect(spyFinishTestItemPromiseStart).not.toHaveBeenCalledWith( + expect.any(Object), + 'testItemId', + expect.objectContaining({ + issue: expect.anything(), + }), + ); + done(); + } catch (error) { + done(error); + } + }, 100); + }); + describe('saveLog', () => { it('should return object with tempId and promise', () => { const client = new RPClient({ apiKey: 'any', endpoint: 'https://rp.api', project: 'prj' }); diff --git a/__tests__/rest.spec.js b/__tests__/rest.spec.js index ef70c95..7f6bb06 100644 --- a/__tests__/rest.spec.js +++ b/__tests__/rest.spec.js @@ -134,6 +134,26 @@ describe('RestClient', () => { }; expect(retryConfig.retryCondition(timeoutError)).toBe(true); }); + + it('handles undefined restClientConfig without crashing during retries', () => { + const client = new RestClient({ + baseURL: options.baseURL, + headers: options.headers, + restClientConfig: undefined, + }); + + const retryConfig = client.getRetryConfig(); + expect(retryConfig.retries).toBe(6); + + const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); + const onRetry = retryConfig.onRetry; + + expect(() => { + onRetry(1, { code: 'ECONNABORTED' }, { method: 'GET', url: 'http://test.com' }); + }).not.toThrow(); + + consoleSpy.mockRestore(); + }); }); describe('buildPath', () => { diff --git a/index.d.ts b/index.d.ts index 5a40901..8c7e993 100644 --- a/index.d.ts +++ b/index.d.ts @@ -155,6 +155,7 @@ declare module '@reportportal/client-javascript' { launchUuidPrintOutput?: string; restClientConfig?: RestClientConfig; token?: string; + skippedIsNotIssue?: boolean; /** * OAuth 2.0 configuration object. When provided, OAuth authentication will be used instead of API key. */ diff --git a/lib/commons/config.js b/lib/commons/config.js index 1fdd042..0700b92 100644 --- a/lib/commons/config.js +++ b/lib/commons/config.js @@ -115,6 +115,7 @@ const getClientConfig = (options) => { description: options.description, launchUuidPrint: options.launchUuidPrint, launchUuidPrintOutput, + skippedIsNotIssue: !!options.skippedIsNotIssue, }; } catch (error) { // don't throw the error up to not break the entire process diff --git a/lib/report-portal-client.js b/lib/report-portal-client.js index cd43786..289aa76 100644 --- a/lib/report-portal-client.js +++ b/lib/report-portal-client.js @@ -202,6 +202,14 @@ class RPClient { this.launchUuid = launchDataRQ.id; } else { const systemAttr = helpers.getSystemAttribute(); + if (this.config.skippedIsNotIssue === true) { + const skippedIsNotIssueAttribute = { + key: 'skippedIssue', + value: 'false', + system: true, + }; + systemAttr.push(skippedIsNotIssueAttribute); + } const attributes = Array.isArray(launchDataRQ.attributes) ? launchDataRQ.attributes.concat(systemAttr) : systemAttr; @@ -612,6 +620,13 @@ class RPClient { ...finishTestItemRQ, }; + if ( + finishTestItemData.status === RP_STATUSES.SKIPPED && + this.config.skippedIsNotIssue === true + ) { + finishTestItemData.issue = { issueType: 'NOT_ISSUE' }; + } + itemObj.finishSend = true; this.logDebug(`Finish all children for test item with tempId ${itemTempId}`); Promise.allSettled( diff --git a/lib/rest.js b/lib/rest.js index e8ff4d9..5e3b002 100644 --- a/lib/rest.js +++ b/lib/rest.js @@ -150,7 +150,7 @@ method: ${method}`, getRetryConfig() { const retryOption = this.restClientConfig?.retry; const onRetry = (retryCount, error, requestConfig) => { - if (this.restClientConfig.debug) { + if (this.restClientConfig?.debug) { console.log(`[retry #${retryCount}] ${requestConfig.method?.toUpperCase()} ${requestConfig.url} -> ${error.code || error.message}`); } }; diff --git a/package.json b/package.json index 4e1a62b..04e5521 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "npm run clean && tsc", "clean": "rimraf ./build", - "lint": "eslint ./statistics/**/* ./lib/**/* ./__tests__/**/*", + "lint": "eslint ./statistics/**/* ./lib/**/*", "format": "npm run lint -- --fix", "test": "jest", "test:coverage": "jest --coverage"