diff --git a/CHANGELOG.md b/CHANGELOG.md index fc0a5e8..1c6d94f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +### 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.12] - 2026-07-06 ### Changed 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..75dd7e1 100644 --- a/index.d.ts +++ b/index.d.ts @@ -200,6 +200,21 @@ 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; + codeRef?: string; + parameters?: Array<{ key: string; value: string }>; + testCaseId?: string; } /** @@ -268,7 +283,10 @@ declare module '@reportportal/client-javascript' { /** * Initializes a new Report Portal client. */ - constructor(config: ReportPortalConfig, agentInfo?: { name?: string; version?: string; framework_version?: string }); + constructor( + config: ReportPortalConfig, + agentInfo?: { name?: string; version?: string; framework_version?: string }, + ); /** * Starts a new launch. diff --git a/lib/report-portal-client.js b/lib/report-portal-client.js index c835772..562d5a4 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(