diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 1abf0484ba5f..3c0ca352b071 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -8,6 +8,10 @@ _Released 12/5/2023 (PENDING)_ - Fixed an issue where pages or downloads opened in a new tab were missing basic auth headers. Fixes [#28350](https://github.com/cypress-io/cypress/issues/28350). - Fixed an issue where request logging would default the `message` to the `args` of the currently running command even though those `args` would not apply to the request log and are not displayed. If the `args` are sufficiently large (e.g. when running the `cy.task` from the [code-coverage](https://github.com/cypress-io/code-coverage/) plugin) there could be performance/memory implications. Addressed in [#28411](https://github.com/cypress-io/cypress/pull/28411). +**Misc:** + +- Artifact upload duration is now reported to Cypress Cloud. Addressed in [#28418](https://github.com/cypress-io/cypress/pull/28418) + ## 13.6.0 _Released 11/21/2023_ diff --git a/packages/server/lib/cloud/api.ts b/packages/server/lib/cloud/api.ts index 727c8f92dd2e..82a7fdc4fad9 100644 --- a/packages/server/lib/cloud/api.ts +++ b/packages/server/lib/cloud/api.ts @@ -274,28 +274,31 @@ type CreateRunResponse = { } | undefined } +type ArtifactMetadata = { + url: string + fileSize?: number + uploadDuration?: number + success: boolean + error?: string +} + +type ProtocolMetadata = ArtifactMetadata & { + specAccess?: { + size: bigint + offset: bigint + } +} + +type UpdateInstanceArtifactsPayload = { + screenshots: ArtifactMetadata[] + video?: ArtifactMetadata + protocol?: ProtocolMetadata +} + type UpdateInstanceArtifactsOptions = { runId: string instanceId: string timeout: number | undefined - protocol: { - url: string - success: boolean - fileSize?: number | undefined - error?: string | undefined - } | undefined - screenshots: { - url: string - success: boolean - fileSize?: number | undefined - error?: string | undefined - }[] | undefined - video: { - url: string - success: boolean - fileSize?: number | undefined - error?: string | undefined - } | undefined } let preflightResult = { @@ -497,17 +500,13 @@ module.exports = { }) }, - updateInstanceArtifacts (options: UpdateInstanceArtifactsOptions) { + updateInstanceArtifacts (options: UpdateInstanceArtifactsOptions, body: UpdateInstanceArtifactsPayload) { return retryWithBackoff((attemptIndex) => { return rp.put({ url: recordRoutes.instanceArtifacts(options.instanceId), json: true, timeout: options.timeout ?? SIXTY_SECONDS, - body: { - protocol: options.protocol, - screenshots: options.screenshots, - video: options.video, - }, + body, headers: { 'x-route-version': '1', 'x-cypress-run-id': options.runId, diff --git a/packages/server/lib/modes/record.js b/packages/server/lib/modes/record.js index 57a68cd23a4f..1fab1ddb2701 100644 --- a/packages/server/lib/modes/record.js +++ b/packages/server/lib/modes/record.js @@ -276,7 +276,7 @@ const uploadArtifactBatch = async (artifacts, protocolManager, quiet) => { url: artifact.uploadUrl, fileSize: artifact.fileSize, key: artifact.reportKey, - duration: performance.now() - startTime, + uploadDuration: performance.now() - startTime, } } @@ -289,7 +289,7 @@ const uploadArtifactBatch = async (artifacts, protocolManager, quiet) => { pathToFile: artifact.filePath, fileSize: artifact.fileSize, key: artifact.reportKey, - duration: performance.now() - startTime, + uploadDuration: performance.now() - startTime, } } catch (err) { debug('failed to upload artifact %o', { @@ -308,7 +308,7 @@ const uploadArtifactBatch = async (artifacts, protocolManager, quiet) => { allErrors: err.errors, url: artifact.uploadUrl, pathToFile: artifact.filePath, - duration: performance.now() - startTime, + uploadDuration: performance.now() - startTime, } } @@ -318,7 +318,7 @@ const uploadArtifactBatch = async (artifacts, protocolManager, quiet) => { error: err.message, url: artifact.uploadUrl, pathToFile: artifact.filePath, - duration: performance.now() - startTime, + uploadDuration: performance.now() - startTime, } } }), @@ -355,8 +355,7 @@ const uploadArtifactBatch = async (artifacts, protocolManager, quiet) => { return skipped && !report.error ? acc : { ...acc, [key]: { - // TODO: once cloud supports reporting duration, no longer omit this - ..._.omit(report, 'duration'), + ...report, error, }, } @@ -452,8 +451,8 @@ const uploadArtifacts = async (options = {}) => { try { debug('upload reprt: %O', uploadReport) const res = await api.updateInstanceArtifacts({ - runId, instanceId, ...uploadReport, - }) + runId, instanceId, + }, uploadReport) return res } catch (err) { diff --git a/packages/server/lib/util/print-run.ts b/packages/server/lib/util/print-run.ts index afc1f4c333b3..d2ef8f12dce2 100644 --- a/packages/server/lib/util/print-run.ts +++ b/packages/server/lib/util/print-run.ts @@ -614,11 +614,11 @@ type ArtifactUploadResultLike = { success: boolean error?: string skipped?: boolean - duration?: number + uploadDuration?: number } export const printCompletedArtifactUpload = (artifactUploadResult: T, labels: Record<'protocol' | 'screenshots' | 'video', string>, num: string): void => { - const { pathToFile, key, fileSize, success, error, skipped, duration } = artifactUploadResult + const { pathToFile, key, fileSize, success, error, skipped, uploadDuration } = artifactUploadResult process.stdout.write(` - ${labels[key]} `) @@ -630,8 +630,8 @@ export const printCompletedArtifactUpload = process.stdout.write(`- Failed Uploading`) } - if (duration) { - const durationOut = humanTime.short(duration, 2) + if (uploadDuration) { + const durationOut = humanTime.short(uploadDuration, 2) process.stdout.write(` ${success ? 'in' : 'after'} ${durationOut}`) } diff --git a/packages/server/test/unit/cloud/api_spec.js b/packages/server/test/unit/cloud/api_spec.js index b92156dac52f..abd281289e6b 100644 --- a/packages/server/test/unit/cloud/api_spec.js +++ b/packages/server/test/unit/cloud/api_spec.js @@ -1524,20 +1524,26 @@ describe('lib/cloud/api', () => { context('.updateInstanceArtifacts', () => { beforeEach(function () { - this.artifactProps = { + this.artifactOptions = { runId: 'run-id-123', instanceId: 'instance-id-123', + } + + this.artifactProps = { screenshots: [{ url: `http://localhost:1234/screenshots/upload/instance-id-123/a877e957-f90e-4ba4-9fa8-569812f148c4.png`, uploadSize: 100, + uploadDuration: 100, }], video: { url: `http://localhost:1234/video/upload/instance-id-123/f17754c4-581d-4e08-a922-1fa402f9c6de.mp4`, uploadSize: 122, + uploadDuration: 100, }, protocol: { url: `http://localhost:1234/protocol/upload/instance-id-123/2ed89c81-e7eb-4b97-8a6e-185c410471df.db`, uploadSize: 123, + uploadDuration: 100, }, } // TODO: add schema validation @@ -1546,7 +1552,7 @@ describe('lib/cloud/api', () => { it('PUTs/instances/:id/artifacts', function () { nock(API_BASEURL) .matchHeader('x-route-version', '1') - .matchHeader('x-cypress-run-id', this.artifactProps.runId) + .matchHeader('x-cypress-run-id', this.artifactOptions.runId) .matchHeader('x-cypress-request-attempt', '0') .matchHeader('x-os-name', 'linux') .matchHeader('x-cypress-version', pkg.version) @@ -1557,7 +1563,7 @@ describe('lib/cloud/api', () => { }) .reply(200) - return api.updateInstanceArtifacts(this.artifactProps) + return api.updateInstanceArtifacts(this.artifactOptions, this.artifactProps) }) }) }) diff --git a/system-tests/__snapshots__/record_spec.js b/system-tests/__snapshots__/record_spec.js index 6979617b65d0..0645cf19f01f 100644 --- a/system-tests/__snapshots__/record_spec.js +++ b/system-tests/__snapshots__/record_spec.js @@ -2844,90 +2844,6 @@ exports['e2e record capture-protocol disabled messaging displays disabled messag Recorded Run: https://dashboard.cypress.io/projects/cjvoj7/runs/12 -` - -exports['e2e record capture-protocol enabled passing retrieves the capture protocol and uploads the db 1'] = ` - -==================================================================================================== - - (Run Starting) - - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Cypress: 1.2.3 │ - │ Browser: FooBrowser 88 │ - │ Specs: 1 found (record_pass.cy.js) │ - │ Searched: cypress/e2e/record_pass* │ - │ Params: Tag: false, Group: false, Parallel: false │ - │ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - - -──────────────────────────────────────────────────────────────────────────────────────────────────── - - Running: record_pass.cy.js (1 of 1) - Estimated: X second(s) - - - record pass - ✓ passes - - is pending - - - 1 passing - 1 pending - - - (Results) - - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Tests: 2 │ - │ Passing: 1 │ - │ Failing: 0 │ - │ Pending: 1 │ - │ Skipped: 0 │ - │ Screenshots: 1 │ - │ Video: false │ - │ Duration: X seconds │ - │ Estimated: X second(s) │ - │ Spec Ran: record_pass.cy.js │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - - - (Screenshots) - - - /XXX/XXX/XXX/cypress/screenshots/record_pass.cy.js/yay it passes.png (400x1022) - - - (Uploading Cloud Artifacts) - - - Video - Nothing to upload - - Screenshot - 1 kB /XXX/XXX/XXX/cypress/screenshots/record_pass.cy.js/yay it passes.png - - Test Replay - 1 kB - - Uploading Cloud Artifacts: . . . . . - - (Uploaded Cloud Artifacts) - - - Screenshot - Done Uploading 1 kB in Xm, Ys ZZ.ZZms 1/2 /XXX/XXX/XXX/cypress/screenshots/record_pass.cy.js/yay it passes.png - - Test Replay - Done Uploading 1 kB in Xm, Ys ZZ.ZZms 2/2 - -==================================================================================================== - - (Run Finished) - - - Spec Tests Passing Failing Pending Skipped - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ ✔ record_pass.cy.js XX:XX 2 1 - 1 - │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - ✔ All specs passed! XX:XX 2 1 - 1 - - - -─────────────────────────────────────────────────────────────────────────────────────────────────────── - - Recorded Run: https://dashboard.cypress.io/projects/cjvoj7/runs/12 - - ` exports['e2e record capture-protocol enabled when the tab crashes in chrome posts accurate test results 1'] = ` @@ -3904,3 +3820,87 @@ exports['capture-protocol api errors error report 500 continues 1'] = ` ` + +exports['e2e record capture-protocol enabled passing retrieves the capture protocol, uploads the db, and updates the artifact upload report 1'] = ` + +==================================================================================================== + + (Run Starting) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Cypress: 1.2.3 │ + │ Browser: FooBrowser 88 │ + │ Specs: 1 found (record_pass.cy.js) │ + │ Searched: cypress/e2e/record_pass* │ + │ Params: Tag: false, Group: false, Parallel: false │ + │ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: record_pass.cy.js (1 of 1) + Estimated: X second(s) + + + record pass + ✓ passes + - is pending + + + 1 passing + 1 pending + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 2 │ + │ Passing: 1 │ + │ Failing: 0 │ + │ Pending: 1 │ + │ Skipped: 0 │ + │ Screenshots: 1 │ + │ Video: false │ + │ Duration: X seconds │ + │ Estimated: X second(s) │ + │ Spec Ran: record_pass.cy.js │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + (Screenshots) + + - /XXX/XXX/XXX/cypress/screenshots/record_pass.cy.js/yay it passes.png (400x1022) + + + (Uploading Cloud Artifacts) + + - Video - Nothing to upload + - Screenshot - 1 kB /XXX/XXX/XXX/cypress/screenshots/record_pass.cy.js/yay it passes.png + - Test Replay - 1 kB + + Uploading Cloud Artifacts: . . . . . + + (Uploaded Cloud Artifacts) + + - Screenshot - Done Uploading 1 kB in Xm, Ys ZZ.ZZms 1/2 /XXX/XXX/XXX/cypress/screenshots/record_pass.cy.js/yay it passes.png + - Test Replay - Done Uploading 1 kB in Xm, Ys ZZ.ZZms 2/2 + +==================================================================================================== + + (Run Finished) + + + Spec Tests Passing Failing Pending Skipped + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ✔ record_pass.cy.js XX:XX 2 1 - 1 - │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + ✔ All specs passed! XX:XX 2 1 - 1 - + + +─────────────────────────────────────────────────────────────────────────────────────────────────────── + + Recorded Run: https://dashboard.cypress.io/projects/cjvoj7/runs/12 + + +` diff --git a/system-tests/test/record_spec.js b/system-tests/test/record_spec.js index a02c7717d556..dac543fc4215 100644 --- a/system-tests/test/record_spec.js +++ b/system-tests/test/record_spec.js @@ -2298,7 +2298,7 @@ describe('e2e record', () => { describe('passing', () => { enableCaptureProtocol() - it('retrieves the capture protocol and uploads the db', function () { + it('retrieves the capture protocol, uploads the db, and updates the artifact upload report', function () { return systemTests.exec(this, { key: 'f858a2bc-b469-4e48-be67-0876339ee7e1', configFile: 'cypress-with-project-id.config.js', @@ -2307,8 +2307,13 @@ describe('e2e record', () => { snapshot: true, }).then((ret) => { const urls = getRequestUrls() + const artifactReport = getRequests().find(({ url }) => url === `PUT /instances/${instanceId}/artifacts`)?.body expect(urls).to.include.members([`PUT ${CAPTURE_PROTOCOL_UPLOAD_URL}`]) + + expect(artifactReport?.protocol).to.an('object') + expect(artifactReport?.protocol?.url).to.be.a('string') + expect(artifactReport?.protocol?.uploadDuration).to.be.a('number') }) }) })