From 72225db03327744844dcfbcc72b40e85de6a2761 Mon Sep 17 00:00:00 2001 From: Chris Breiding Date: Wed, 29 Nov 2023 14:05:14 -0500 Subject: [PATCH 1/5] fix: Resolve types and dist issues with @cypress/puppeteer (#28424) --- npm/puppeteer/CHANGELOG.md | 2 +- npm/puppeteer/README.md | 6 +++--- npm/puppeteer/package.json | 4 ++-- npm/puppeteer/{ => support}/index.d.ts | 0 npm/puppeteer/support/index.js | 1 + npm/puppeteer/tsconfig.json | 4 ++-- 6 files changed, 9 insertions(+), 8 deletions(-) rename npm/puppeteer/{ => support}/index.d.ts (100%) create mode 100644 npm/puppeteer/support/index.js diff --git a/npm/puppeteer/CHANGELOG.md b/npm/puppeteer/CHANGELOG.md index cbadb0e3467c..b828c8085689 100644 --- a/npm/puppeteer/CHANGELOG.md +++ b/npm/puppeteer/CHANGELOG.md @@ -3,4 +3,4 @@ ### Features -* Add @cypress/puppeteer plugin ([#28370](https://github.com/cypress-io/cypress/issues/28370)) ([b34d145](https://github.com/cypress-io/cypress/commit/b34d14571689a9b36efc707a3a48f27edcb98113)) +* Initial release ([#28370](https://github.com/cypress-io/cypress/issues/28370)) ([b34d145](https://github.com/cypress-io/cypress/commit/b34d14571689a9b36efc707a3a48f27edcb98113)) diff --git a/npm/puppeteer/README.md b/npm/puppeteer/README.md index 12ab6f184caa..3e1f1c9448d7 100644 --- a/npm/puppeteer/README.md +++ b/npm/puppeteer/README.md @@ -35,7 +35,7 @@ Add the following in `tsconfig.json`: ```json { "compilerOptions": { - "types": ["cypress", "@cypress/puppeteer"] + "types": ["cypress", "@cypress/puppeteer/support"] } } ``` @@ -55,7 +55,7 @@ While the `cy.puppeteer()` command is executed in the browser, the majority of t In your Cypress config (e.g. `cypress.config.ts`): ```typescript -import { setup } from '@cypress/puppeteer/plugin' +import { setup } from '@cypress/puppeteer' export default defineConfig({ e2e: { @@ -76,7 +76,7 @@ export default defineConfig({ In your support file (e.g. `cypress/support/e2e.ts`): ```typescript -import { setup } from '@cypress/puppeteer/support' +import '@cypress/puppeteer/support' ``` In your spec (e.g. `spec.cy.ts`): diff --git a/npm/puppeteer/package.json b/npm/puppeteer/package.json index 1392286d3a11..50358f58738a 100644 --- a/npm/puppeteer/package.json +++ b/npm/puppeteer/package.json @@ -37,9 +37,9 @@ }, "files": [ "dist", - "index.d.ts" + "support" ], - "types": "index.d.ts", + "types": "dist/plugin/index.d.ts", "license": "MIT", "repository": { "type": "git", diff --git a/npm/puppeteer/index.d.ts b/npm/puppeteer/support/index.d.ts similarity index 100% rename from npm/puppeteer/index.d.ts rename to npm/puppeteer/support/index.d.ts diff --git a/npm/puppeteer/support/index.js b/npm/puppeteer/support/index.js new file mode 100644 index 000000000000..cdde21eaaaff --- /dev/null +++ b/npm/puppeteer/support/index.js @@ -0,0 +1 @@ +require('../dist/support') diff --git a/npm/puppeteer/tsconfig.json b/npm/puppeteer/tsconfig.json index 16bf899f069f..fc6b5493beba 100644 --- a/npm/puppeteer/tsconfig.json +++ b/npm/puppeteer/tsconfig.json @@ -14,11 +14,11 @@ "strictNullChecks": true, "target": "ES2020", "types": [ - "cypress" + "cypress", + "./support" ] }, "include": [ - "index.d.ts", "src/" ] } From 6a895d2a3c9f86d5dfe8bbdf403e1b25ed7fa072 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 29 Nov 2023 14:40:32 -0500 Subject: [PATCH 2/5] chore: release @cypress/puppeteer-v0.1.1 [skip ci] --- npm/puppeteer/CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/npm/puppeteer/CHANGELOG.md b/npm/puppeteer/CHANGELOG.md index b828c8085689..540675e4467a 100644 --- a/npm/puppeteer/CHANGELOG.md +++ b/npm/puppeteer/CHANGELOG.md @@ -1,3 +1,10 @@ +# [@cypress/puppeteer-v0.1.1](https://github.com/cypress-io/cypress/compare/@cypress/puppeteer-v0.1.0...@cypress/puppeteer-v0.1.1) (2023-11-29) + + +### Bug Fixes + +* Resolve types and dist issues with @cypress/puppeteer ([#28424](https://github.com/cypress-io/cypress/issues/28424)) ([72225db](https://github.com/cypress-io/cypress/commit/72225db03327744844dcfbcc72b40e85de6a2761)) + # [@cypress/puppeteer-v0.1.0](https://github.com/cypress-io/cypress/compare/@cypress/puppeteer-v0.0.1...@cypress/puppeteer-v0.1.0) (2023-11-28) From 7ba92b91e5131252438c6beb413cc5cca971e249 Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Wed, 29 Nov 2023 15:33:35 -0500 Subject: [PATCH 3/5] misc: persist upload timings to cy cloud (#28418) * feat: report artifact upload durations to cy cloud for monitoring * round upload durations to nearest ms * update changelog * revert changelog, fix typo in comment * no longer round uploadDuration - api now accepts floats * updates changelog * update api spec for refactored signature on api.updateInstanceArtifacts * Update CHANGELOG.md * Update system-tests/test/record_spec.js Co-authored-by: Chris Breiding * rm defunct comment * rm non-null accessors, this spec is not ts --------- Co-authored-by: Chris Breiding --- cli/CHANGELOG.md | 4 + packages/server/lib/cloud/api.ts | 47 +++--- packages/server/lib/modes/record.js | 15 +- packages/server/lib/util/print-run.ts | 8 +- packages/server/test/unit/cloud/api_spec.js | 12 +- system-tests/__snapshots__/record_spec.js | 168 ++++++++++---------- system-tests/test/record_spec.js | 7 +- 7 files changed, 137 insertions(+), 124 deletions(-) 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') }) }) }) From 57bb0b68eedb72975d96446a8561322d0d03262a Mon Sep 17 00:00:00 2001 From: Matt Schile Date: Wed, 29 Nov 2023 15:09:12 -0700 Subject: [PATCH 4/5] fix: decode urls in prerequest (#28427) --- cli/CHANGELOG.md | 1 + packages/proxy/lib/http/util/prerequests.ts | 6 +- .../test/unit/http/util/prerequests.spec.ts | 21 +++ .../test/integration/http_requests_spec.js | 145 +++++++++++++++++- 4 files changed, 169 insertions(+), 4 deletions(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 3c0ca352b071..9317000e8d18 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -7,6 +7,7 @@ _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). +- Fixed issue where some URLs would timeout in pre-request correlation. Addressed in [#28427](https://github.com/cypress-io/cypress/pull/28427). **Misc:** diff --git a/packages/proxy/lib/http/util/prerequests.ts b/packages/proxy/lib/http/util/prerequests.ts index cbdd41f9a13f..86b2cd7f9f30 100644 --- a/packages/proxy/lib/http/util/prerequests.ts +++ b/packages/proxy/lib/http/util/prerequests.ts @@ -148,7 +148,7 @@ export class PreRequests { addPending (browserPreRequest: BrowserPreRequest) { metrics.browserPreRequestsReceived++ - const key = `${browserPreRequest.method}-${browserPreRequest.url}` + const key = `${browserPreRequest.method}-${decodeURI(browserPreRequest.url)}` const pendingRequest = this.pendingRequests.shift(key) if (pendingRequest) { @@ -193,7 +193,7 @@ export class PreRequests { } addPendingUrlWithoutPreRequest (url: string) { - const key = `GET-${url}` + const key = `GET-${decodeURI(url)}` const pendingRequest = this.pendingRequests.shift(key) if (pendingRequest) { @@ -236,7 +236,7 @@ export class PreRequests { const proxyRequestReceivedTimestamp = performance.now() + performance.timeOrigin metrics.proxyRequestsReceived++ - const key = `${req.method}-${req.proxiedUrl}` + const key = `${req.method}-${decodeURI(req.proxiedUrl)}` const pendingPreRequest = this.pendingPreRequests.shift(key) if (pendingPreRequest) { diff --git a/packages/proxy/test/unit/http/util/prerequests.spec.ts b/packages/proxy/test/unit/http/util/prerequests.spec.ts index 391a73ef957d..37d2b9b966a8 100644 --- a/packages/proxy/test/unit/http/util/prerequests.spec.ts +++ b/packages/proxy/test/unit/http/util/prerequests.spec.ts @@ -275,4 +275,25 @@ describe('http/util/prerequests', () => { expect(callbackCalled).to.be.true }) + + it('decodes the proxied url', () => { + preRequests.get({ proxiedUrl: 'foo%7Cbar', method: 'GET', headers: {} } as CypressIncomingRequest, () => {}, () => {}) + + expect(preRequests.pendingRequests.length).to.eq(1) + expect(preRequests.pendingRequests.shift('GET-foo|bar')).not.to.be.undefined + }) + + it('decodes the pending url without pre-request', () => { + preRequests.addPendingUrlWithoutPreRequest('foo%7Cbar') + + expect(preRequests.pendingUrlsWithoutPreRequests.length).to.eq(1) + expect(preRequests.pendingUrlsWithoutPreRequests.shift('GET-foo|bar')).not.to.be.undefined + }) + + it('decodes pending url', () => { + preRequests.addPending({ requestId: '1234', url: 'foo%7Cbar', method: 'GET' } as BrowserPreRequest) + + expect(preRequests.pendingPreRequests.length).to.eq(1) + expect(preRequests.pendingPreRequests.shift('GET-foo|bar')).not.to.be.undefined + }) }) diff --git a/packages/server/test/integration/http_requests_spec.js b/packages/server/test/integration/http_requests_spec.js index 1fa37e332fe5..a41df8146216 100644 --- a/packages/server/test/integration/http_requests_spec.js +++ b/packages/server/test/integration/http_requests_spec.js @@ -988,7 +988,9 @@ describe('Routes', () => { context('basic request with correlation', () => { beforeEach(function () { - return this.setup('http://www.github.com', undefined, undefined, true) + return this.setup('http://www.github.com', undefined, undefined, true).then(() => { + this.networkProxy.setPreRequestTimeout(2000) + }) }) it('properly correlates when CDP failures come first', function () { @@ -1087,6 +1089,147 @@ describe('Routes', () => { }) }) }) + + it('properly correlates when request has | character', function () { + this.timeout(1500) + + nock(this.server.remoteStates.current().origin) + .get('/?foo=bar|baz') + .reply(200, 'hello from bar!', { + 'Content-Type': 'text/html', + }) + + const requestPromise = this.rp({ + url: 'http://www.github.com/?foo=bar|baz', + headers: { + 'Accept-Encoding': 'identity', + }, + }) + + this.networkProxy.addPendingBrowserPreRequest({ + requestId: '1', + method: 'GET', + url: 'http://www.github.com/?foo=bar|baz', + }) + + return requestPromise.then((res) => { + expect(res.statusCode).to.eq(200) + + expect(res.body).to.include('hello from bar!') + }) + }) + + it('properly correlates when request has | character with addPendingUrlWithoutPreRequest', function () { + this.timeout(1500) + + nock(this.server.remoteStates.current().origin) + .get('/?foo=bar|baz') + .reply(200, 'hello from bar!', { + 'Content-Type': 'text/html', + }) + + const requestPromise = this.rp({ + url: 'http://www.github.com/?foo=bar|baz', + headers: { + 'Accept-Encoding': 'identity', + }, + }) + + this.networkProxy.addPendingUrlWithoutPreRequest('http://www.github.com/?foo=bar|baz') + + return requestPromise.then((res) => { + expect(res.statusCode).to.eq(200) + + expect(res.body).to.include('hello from bar!') + }) + }) + + it('properly correlates when request has encoded | character', function () { + this.timeout(1500) + + nock(this.server.remoteStates.current().origin) + .get('/?foo=bar%7Cbaz') + .reply(200, 'hello from bar!', { + 'Content-Type': 'text/html', + }) + + const requestPromise = this.rp({ + url: 'http://www.github.com/?foo=bar%7Cbaz', + headers: { + 'Accept-Encoding': 'identity', + }, + }) + + this.networkProxy.addPendingBrowserPreRequest({ + requestId: '1', + method: 'GET', + url: 'http://www.github.com/?foo=bar%7Cbaz', + }) + + return requestPromise.then((res) => { + expect(res.statusCode).to.eq(200) + + expect(res.body).to.include('hello from bar!') + }) + }) + + it('properly correlates when request has encoded " " (space) character', function () { + this.timeout(1500) + + nock(this.server.remoteStates.current().origin) + .get('/?foo=bar%20baz') + .reply(200, 'hello from bar!', { + 'Content-Type': 'text/html', + }) + + const requestPromise = this.rp({ + url: 'http://www.github.com/?foo=bar%20baz', + headers: { + 'Accept-Encoding': 'identity', + }, + }) + + this.networkProxy.addPendingBrowserPreRequest({ + requestId: '1', + method: 'GET', + url: 'http://www.github.com/?foo=bar%20baz', + }) + + return requestPromise.then((res) => { + expect(res.statusCode).to.eq(200) + + expect(res.body).to.include('hello from bar!') + }) + }) + + it('properly correlates when request has encoded " character', function () { + this.timeout(1500) + + nock(this.server.remoteStates.current().origin) + .get('/?foo=bar%22') + .reply(200, 'hello from bar!', { + 'Content-Type': 'text/html', + }) + + const requestPromise = this.rp({ + url: 'http://www.github.com/?foo=bar%22', + headers: { + 'Accept-Encoding': 'identity', + }, + }) + + this.networkProxy.addPendingBrowserPreRequest({ + requestId: '1', + method: 'GET', + url: 'http://www.github.com/?foo=bar%22', + }) + + return requestPromise.then((res) => { + expect(res.statusCode).to.eq(200) + + expect(res.body).to.include('hello from bar!') + }) + }) }) context('gzip', () => { From 257861bd6e3f7de77a4c7ede64e9538370c0a2ed Mon Sep 17 00:00:00 2001 From: Chris Breiding Date: Wed, 29 Nov 2023 22:40:02 -0500 Subject: [PATCH 5/5] fix: Fix issue with using privileged commands when baseUrl includes basic auth credentials (#28428) --- cli/CHANGELOG.md | 3 ++- .../lib/privileged-commands/privileged-channel.js | 6 +++++- .../test/unit/browsers/privileged-channel_spec.js | 3 +++ .../privileged-commands/cypress.config.js | 13 +++++++++++++ .../privileged-commands/cypress/e2e/spec.cy.js | 7 +++++++ .../cypress/fixtures/example.json | 5 +++++ system-tests/test/base_url_spec.js | 15 +++++++++++++++ 7 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 system-tests/projects/privileged-commands/cypress.config.js create mode 100644 system-tests/projects/privileged-commands/cypress/e2e/spec.cy.js create mode 100644 system-tests/projects/privileged-commands/cypress/fixtures/example.json diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 9317000e8d18..45a27f6fc002 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -7,7 +7,8 @@ _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). -- Fixed issue where some URLs would timeout in pre-request correlation. Addressed in [#28427](https://github.com/cypress-io/cypress/pull/28427). +- Fixed an issue where commands would fail with the error `must only be invoked from the spec file or support file` if the project's `baseUrl` included basic auth credentials. Fixes [#28336](https://github.com/cypress-io/cypress/issues/28336). +- Fixed an issue where some URLs would timeout in pre-request correlation. Addressed in [#28427](https://github.com/cypress-io/cypress/pull/28427). **Misc:** diff --git a/packages/server/lib/privileged-commands/privileged-channel.js b/packages/server/lib/privileged-commands/privileged-channel.js index b89b882d4e98..7f4eec37e2e2 100644 --- a/packages/server/lib/privileged-commands/privileged-channel.js +++ b/packages/server/lib/privileged-commands/privileged-channel.js @@ -199,7 +199,11 @@ // send it to the server, where it's stored in state. when the command is // run and it sends its message to the server via websocket, we check // that verified status before allowing the command to continue running - const promise = fetch(`/${namespace}/add-verified-command`, { + // + // needs to use the fully-qualified url or else when the baseUrl includes + // basic auth, the fetch fails with a security error + // see https://github.com/cypress-io/cypress/issues/28336 + const promise = fetch(`${win.location.origin}/${namespace}/add-verified-command`, { body: stringify({ args, name: command.name, diff --git a/packages/server/test/unit/browsers/privileged-channel_spec.js b/packages/server/test/unit/browsers/privileged-channel_spec.js index b945ab629831..b8777c3a5c63 100644 --- a/packages/server/test/unit/browsers/privileged-channel_spec.js +++ b/packages/server/test/unit/browsers/privileged-channel_spec.js @@ -36,6 +36,9 @@ describe('privileged channel', () => { Function: { prototype: { toString: Function.prototype.toString, } }, + location: { + origin: 'http://localhost:1234', + }, Math: { imul: Math.imul, }, diff --git a/system-tests/projects/privileged-commands/cypress.config.js b/system-tests/projects/privileged-commands/cypress.config.js new file mode 100644 index 000000000000..deba5ef5a25f --- /dev/null +++ b/system-tests/projects/privileged-commands/cypress.config.js @@ -0,0 +1,13 @@ +module.exports = { + e2e: { + baseUrl: 'http://user:pass@localhost:9999/app', + supportFile: false, + setupNodeEvents (on) { + on('task', { + 'return:arg' (arg) { + return arg + }, + }) + }, + }, +} diff --git a/system-tests/projects/privileged-commands/cypress/e2e/spec.cy.js b/system-tests/projects/privileged-commands/cypress/e2e/spec.cy.js new file mode 100644 index 000000000000..9bfb7fe7a7ff --- /dev/null +++ b/system-tests/projects/privileged-commands/cypress/e2e/spec.cy.js @@ -0,0 +1,7 @@ +it('can run privileged commands with basic auth baseUrl', () => { + cy.visit('/html') + cy.exec('echo "hello"') + cy.readFile('cypress/fixtures/example.json') + cy.writeFile('cypress/_test-output/written.txt', 'contents') + cy.task('return:arg', 'arg') +}) diff --git a/system-tests/projects/privileged-commands/cypress/fixtures/example.json b/system-tests/projects/privileged-commands/cypress/fixtures/example.json new file mode 100644 index 000000000000..02e4254378e9 --- /dev/null +++ b/system-tests/projects/privileged-commands/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/system-tests/test/base_url_spec.js b/system-tests/test/base_url_spec.js index 38ae2717e6b6..dbdbae9e8f3c 100644 --- a/system-tests/test/base_url_spec.js +++ b/system-tests/test/base_url_spec.js @@ -48,6 +48,21 @@ describe('e2e baseUrl', () => { }) }) + // https://github.com/cypress-io/cypress/issues/28336 + context('basic auth + privileged commands', () => { + systemTests.setup({ + servers: { + port: 9999, + onServer, + }, + }) + + systemTests.it('passes', { + browser: 'chrome', + project: 'privileged-commands', + }) + }) + context('http', () => { systemTests.setup({ servers: {