diff --git a/CHANGELOG.md b/CHANGELOG.md index 7800f22..ff71deb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,11 @@ +## [Unreleased] +### Added +- `retry_of` property is now automatically included in the `startTestItem` + request payload when `retry: true` and a previous attempt exists in the + retry chain. This allows the ReportPortal backend to link retry chains + efficiently, improving query performance for large test runs. + ## [5.5.11] - 2026-05-22 ### Added - Google Analytics improvements. diff --git a/__tests__/report-portal-client.spec.js b/__tests__/report-portal-client.spec.js index 4754279..afb5479 100644 --- a/__tests__/report-portal-client.spec.js +++ b/__tests__/report-portal-client.spec.js @@ -879,6 +879,102 @@ describe('ReportPortal javascript client', () => { expect(client.itemRetriesChainMap.get).toHaveBeenCalledWith('id1__name__'); }); + + it('should include retry_of with the previous item UUID when retry is true', async () => { + const client = new RPClient({ + apiKey: 'test', + endpoint: 'https://rp.us/api/v1', + project: 'tst', + }); + const prevRealId = 'prev-item-uuid-1234'; + const prevPromise = Promise.resolve({ id: prevRealId }); + + client.map = { + launchId: { + children: [], + finishSend: false, + promiseStart: Promise.resolve(), + }, + }; + + const itemKey = client.calculateItemRetriesChainMapKey( + 'launchId', undefined, 'My test', undefined, + ); + client.itemRetriesChainMap.set(itemKey, prevPromise); + + jest.spyOn(client.restClient, 'create').mockResolvedValue({ id: 'new-item-uuid' }); + jest.spyOn(client, 'getUniqId').mockReturnValue('newTempId'); + + await client.startTestItem( + { name: 'My test', type: 'STEP', retry: true }, + 'launchId', + ).promise; + + expect(client.restClient.create).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ retry_of: prevRealId }), + ); + }); + + it('should not include retry_of when retry is true but no previous entry exists', async () => { + const client = new RPClient({ + apiKey: 'test', + endpoint: 'https://rp.us/api/v1', + project: 'tst', + }); + client.map = { + launchId: { + children: [], + finishSend: false, + promiseStart: Promise.resolve(), + }, + }; + jest.spyOn(client.restClient, 'create').mockResolvedValue({ id: 'new-item-uuid' }); + jest.spyOn(client, 'getUniqId').mockReturnValue('newTempId'); + + await client.startTestItem( + { name: 'My test', type: 'STEP', retry: true }, + 'launchId', + ).promise; + + expect(client.restClient.create).toHaveBeenCalledWith( + expect.any(String), + expect.not.objectContaining({ retry_of: expect.anything() }), + ); + }); + + it('should not include retry_of when retry is false', async () => { + const client = new RPClient({ + apiKey: 'test', + endpoint: 'https://rp.us/api/v1', + project: 'tst', + }); + const prevPromise = Promise.resolve({ id: 'prev-item-uuid-1234' }); + client.map = { + launchId: { + children: [], + finishSend: false, + promiseStart: Promise.resolve(), + }, + }; + const itemKey = client.calculateItemRetriesChainMapKey( + 'launchId', undefined, 'My test', undefined, + ); + client.itemRetriesChainMap.set(itemKey, prevPromise); + + jest.spyOn(client.restClient, 'create').mockResolvedValue({ id: 'new-item-uuid' }); + jest.spyOn(client, 'getUniqId').mockReturnValue('newTempId'); + + await client.startTestItem( + { name: 'My test', type: 'STEP', retry: false }, + 'launchId', + ).promise; + + expect(client.restClient.create).toHaveBeenCalledWith( + expect.any(String), + expect.not.objectContaining({ retry_of: expect.anything() }), + ); + }); }); describe('finishTestItem', () => { diff --git a/index.d.ts b/index.d.ts index fedd35d..46c684e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -200,6 +200,18 @@ declare module '@reportportal/client-javascript' { startTime?: string | number; attributes?: Array<{ key?: string; value?: string } | string>; hasStats?: boolean; + /** + * Set to true when this item is a retry of a previous attempt. + * The client will automatically populate `retry_of` with the UUID of the + * previous attempt. + */ + retry?: boolean; + /** + * UUID of the immediately-preceding retry attempt. + * Populated automatically by the client when `retry: true` and a previous + * attempt exists. Do not set manually. + */ + retry_of?: string; } /** diff --git a/lib/report-portal-client.js b/lib/report-portal-client.js index 7adbb25..58e14f4 100644 --- a/lib/report-portal-client.js +++ b/lib/report-portal-client.js @@ -562,13 +562,16 @@ class RPClient { const tempId = this.getUniqId(); this.map[tempId] = this.getNewItemObj((resolve, reject) => { (executionItemPromise || parentPromise).then( - () => { + (prevResponse) => { const realLaunchId = this.map[launchTempId].realId; let url = 'item/'; if (parentTempId) { const realParentId = this.map[parentTempId].realId; url += `${realParentId}`; } + if (executionItemPromise && prevResponse?.id) { + testItemData.retry_of = prevResponse.id; + } testItemData.launchUuid = realLaunchId; this.logDebug(`Start test item with tempId ${tempId}`, testItemData); this.restClient.create(url, testItemData).then( diff --git a/package-lock.json b/package-lock.json index 0312957..46b051f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "@reportportal/client-javascript", - "version": "5.5.10", + "version": "5.5.11", "license": "Apache-2.0", "dependencies": { "axios": "^1.15.2",