diff --git a/packages/cli-build/src/finalize.js b/packages/cli-build/src/finalize.js index 3233be8f7..5c3f579bf 100644 --- a/packages/cli-build/src/finalize.js +++ b/packages/cli-build/src/finalize.js @@ -19,7 +19,7 @@ export const finalize = command('finalize', { log.info('Finalizing parallel build...'); // rely on the parallel nonce to cause the API to return the current running build for the nonce - let { data: build } = await percy.client.createBuild(); + let { data: build } = await percy.client.createBuild({ cliStartTime: percy.cliStartTime }); await percy.client.finalizeBuild(build.id, { all: true }); let { 'build-number': number, 'web-url': url } = build.attributes; diff --git a/packages/client/src/client.js b/packages/client/src/client.js index 78fc50677..8b0e6661b 100644 --- a/packages/client/src/client.js +++ b/packages/client/src/client.js @@ -134,7 +134,7 @@ export class PercyClient { // Creates a build with optional build resources. Only one build can be // created at a time per instance so snapshots and build finalization can be // done more seemlessly without manually tracking build ids - async createBuild({ resources = [], projectType } = {}) { + async createBuild({ resources = [], projectType, cliStartTime = null } = {}) { this.log.debug('Creating a new build...'); let tagsArr = tagsList(this.labels); @@ -158,7 +158,8 @@ export class PercyClient { 'parallel-nonce': this.env.parallel.nonce, 'parallel-total-shards': this.env.parallel.total, partial: this.env.partial, - tags: tagsArr + tags: tagsArr, + 'cli-start-time': cliStartTime }, relationships: { resources: { diff --git a/packages/client/test/client.test.js b/packages/client/test/client.test.js index 0c4a7b4e8..e73e2871b 100644 --- a/packages/client/test/client.test.js +++ b/packages/client/test/client.test.js @@ -137,6 +137,7 @@ describe('PercyClient', () => { }); describe('#createBuild()', () => { + let cliStartTime = new Date().toISOString(); it('creates a new build', async () => { await expectAsync(client.createBuild()).toBeResolvedTo({ data: { @@ -164,6 +165,7 @@ describe('PercyClient', () => { 'pull-request-number': client.env.pullRequest, 'parallel-nonce': client.env.parallel.nonce, 'parallel-total-shards': client.env.parallel.total, + 'cli-start-time': null, partial: client.env.partial, tags: [] } @@ -198,6 +200,7 @@ describe('PercyClient', () => { 'pull-request-number': client.env.pullRequest, 'parallel-nonce': client.env.parallel.nonce, 'parallel-total-shards': client.env.parallel.total, + 'cli-start-time': null, partial: client.env.partial, tags: [] } @@ -279,6 +282,7 @@ describe('PercyClient', () => { 'pull-request-number': client.env.pullRequest, 'parallel-nonce': client.env.parallel.nonce, 'parallel-total-shards': client.env.parallel.total, + 'cli-start-time': null, partial: client.env.partial, tags: [] } @@ -317,6 +321,46 @@ describe('PercyClient', () => { 'pull-request-number': client.env.pullRequest, 'parallel-nonce': client.env.parallel.nonce, 'parallel-total-shards': client.env.parallel.total, + 'cli-start-time': null, + partial: client.env.partial, + tags: [{ id: null, name: 'tag1' }, { id: null, name: 'tag2' }] + } + })); + }); + + it('creates a new build with cliStartTime', async () => { + client = new PercyClient({ + token: 'PERCY_TOKEN', + labels: 'tag1,tag2' + }); + await expectAsync(client.createBuild({ projectType: 'web', cliStartTime })).toBeResolvedTo({ + data: { + id: '123', + attributes: { + 'build-number': 1, + 'web-url': 'https://percy.io/test/test/123' + } + } + }); + + expect(api.requests['/builds'][0].body.data) + .toEqual(jasmine.objectContaining({ + attributes: { + branch: client.env.git.branch, + type: 'web', + 'target-branch': client.env.target.branch, + 'target-commit-sha': client.env.target.commit, + 'commit-sha': client.env.git.sha, + 'commit-committed-at': client.env.git.committedAt, + 'commit-author-name': client.env.git.authorName, + 'commit-author-email': client.env.git.authorEmail, + 'commit-committer-name': client.env.git.committerName, + 'commit-committer-email': client.env.git.committerEmail, + 'commit-message': client.env.git.message, + 'pull-request-number': client.env.pullRequest, + 'parallel-nonce': client.env.parallel.nonce, + 'parallel-total-shards': client.env.parallel.total, + 'cli-start-time': cliStartTime, partial: client.env.partial, tags: [{ id: null, name: 'tag1' }, { id: null, name: 'tag2' }] } diff --git a/packages/core/src/percy.js b/packages/core/src/percy.js index ca08f13e8..333801783 100644 --- a/packages/core/src/percy.js +++ b/packages/core/src/percy.js @@ -84,6 +84,7 @@ export class Percy { labels ??= config.percy?.labels; deferUploads ??= config.percy?.deferUploads; this.config = config; + this.cliStartTime = null; if (testing) loglevel = 'silent'; if (loglevel) this.loglevel(loglevel); @@ -159,6 +160,7 @@ export class Percy { // already starting or started if (this.readyState != null) return; this.readyState = 0; + this.cliStartTime = new Date().toISOString(); try { if (process.env.PERCY_CLIENT_ERROR_LOGS !== 'false') { diff --git a/packages/core/src/snapshot.js b/packages/core/src/snapshot.js index ee192ccbb..bcf58ffd4 100644 --- a/packages/core/src/snapshot.js +++ b/packages/core/src/snapshot.js @@ -314,7 +314,7 @@ export function createSnapshotsQueue(percy) { .handle('start', async () => { try { build = percy.build = {}; - let { data } = await percy.client.createBuild({ projectType: percy.projectType }); + let { data } = await percy.client.createBuild({ projectType: percy.projectType, cliStartTime: percy.cliStartTime }); let url = data.attributes['web-url']; let number = data.attributes['build-number']; percy.client.buildType = data.attributes?.type; diff --git a/packages/core/test/percy.test.js b/packages/core/test/percy.test.js index 7e452b8d1..4228e73c1 100644 --- a/packages/core/test/percy.test.js +++ b/packages/core/test/percy.test.js @@ -364,6 +364,24 @@ describe('Percy', () => { expect(percy.projectType).toEqual('web'); }); + it('has cliStartTime', async () => { + let time = '2024-08-20T13:38:18.570Z'; + percy = new Percy({ token: 'PERCY_TOKEN' }); + // abort when the browser is launched + let ctrl = new AbortController(); + spyOn(Date.prototype, 'toISOString').and.returnValue(time); + spyOn(percy.browser, 'launch'); + spyOn(Date, 'now').and.returnValue(time); + spyOn(percy.client, 'createBuild').and.callThrough(); + + await generatePromise(percy.yield.start(), ctrl.signal); + expect(percy.cliStartTime).toEqual(time); + expect(percy.client.createBuild).toHaveBeenCalledWith(jasmine.objectContaining({ + projectType: null, + cliStartTime: time + })); + }); + it('syncQueue is created', async () => { percy = new Percy({ token: 'PERCY_TOKEN', projectType: 'web' });