From 219138ca4e952edc4af831f2ae16ce659ebdb50b Mon Sep 17 00:00:00 2001 From: Johan Baversjo Date: Tue, 1 Feb 2022 21:00:21 +0100 Subject: [PATCH 01/12] build: allow unified to run cypress on Apple Silicon (arm64) (backport #19067 to 9.x) (#19968) --- packages/electron/app/{app.js => index.js} | 0 packages/electron/package.json | 2 +- yarn.lock | 61 ++++++++++------------ 3 files changed, 29 insertions(+), 34 deletions(-) rename packages/electron/app/{app.js => index.js} (100%) diff --git a/packages/electron/app/app.js b/packages/electron/app/index.js similarity index 100% rename from packages/electron/app/app.js rename to packages/electron/app/index.js diff --git a/packages/electron/package.json b/packages/electron/package.json index 67af3d3faa..bf431cfa6d 100644 --- a/packages/electron/package.json +++ b/packages/electron/package.json @@ -24,7 +24,7 @@ }, "devDependencies": { "electron": "15.3.4", - "electron-packager": "15.1.0", + "electron-packager": "15.4.0", "execa": "4.1.0", "mocha": "3.5.3" }, diff --git a/yarn.lock b/yarn.lock index 43bcb56d18..659247980d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10791,7 +10791,7 @@ asap@^2.0.0, asap@~2.0.3, asap@~2.0.6: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= -asar@^3.0.0, asar@^3.0.3: +asar@^3.0.3, asar@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/asar/-/asar-3.1.0.tgz#70b0509449fe3daccc63beb4d3c7d2e24d3c6473" integrity sha512-vyxPxP5arcAqN4F/ebHd/HhwnAiZtwhglvdmc7BR2f0ywbVNTOpSeyhLDbGXtE/y58hv1oC75TaNIXutnsOZsQ== @@ -15314,6 +15314,15 @@ cross-spawn-async@^2.1.1: lru-cache "^4.0.0" which "^1.2.8" +cross-spawn-windows-exe@^1.1.0, cross-spawn-windows-exe@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/cross-spawn-windows-exe/-/cross-spawn-windows-exe-1.2.0.tgz#46253b0f497676e766faf4a7061004618b5ac5ec" + integrity sha512-mkLtJJcYbDCxEG7Js6eUnUNndWjyUZwJ3H7bErmmtOYU/Zb99DyUkpamuIZE0b3bhmJyZ7D90uS6f+CGxRRjOw== + dependencies: + "@malept/cross-spawn-promise" "^1.1.0" + is-wsl "^2.2.0" + which "^2.0.2" + cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -17378,7 +17387,7 @@ electron-is-dev@^2.0.0: resolved "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-2.0.0.tgz#833487a069b8dad21425c67a19847d9064ab19bd" integrity sha512-3X99K852Yoqu9AcW50qz3ibYBWY79/pBhlMCab8ToEWS48R0T9tyxRiQhwylE7zQdXrMnx2JKqUJyMPmt5FBqA== -electron-notarize@^1.0.0, electron-notarize@^1.1.1: +electron-notarize@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/electron-notarize/-/electron-notarize-1.1.1.tgz#3ed274b36158c1beb1dbef14e7faf5927e028629" integrity sha512-kufsnqh86CTX89AYNG3NCPoboqnku/+32RxeJ2+7A4Rbm4bbOx0Nc7XTy3/gAlBfpj9xPAxHfhZLOHgfi6cJVw== @@ -17386,18 +17395,6 @@ electron-notarize@^1.0.0, electron-notarize@^1.1.1: debug "^4.1.1" fs-extra "^9.0.1" -electron-osx-sign@^0.4.11: - version "0.4.17" - resolved "https://registry.yarnpkg.com/electron-osx-sign/-/electron-osx-sign-0.4.17.tgz#2727ca0c79e1e4e5ccd3861fb3da9c3c913b006c" - integrity sha512-wUJPmZJQCs1zgdlQgeIpRcvrf7M5/COQaOV68Va1J/SgmWx5KL2otgg+fAae7luw6qz9R8Gvu/Qpe9tAOu/3xQ== - dependencies: - bluebird "^3.5.0" - compare-version "^0.1.2" - debug "^2.6.8" - isbinaryfile "^3.0.2" - minimist "^1.2.0" - plist "^3.0.1" - electron-osx-sign@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/electron-osx-sign/-/electron-osx-sign-0.5.0.tgz#fc258c5e896859904bbe3d01da06902c04b51c3a" @@ -17410,16 +17407,17 @@ electron-osx-sign@^0.5.0: minimist "^1.2.0" plist "^3.0.1" -electron-packager@15.1.0: - version "15.1.0" - resolved "https://registry.yarnpkg.com/electron-packager/-/electron-packager-15.1.0.tgz#16a3733e4cad26112a2ac36f0b0f35c3b0170eff" - integrity sha512-THNm4bz1DfvR9f0g51+NjuAYELflM8+1vhQ/iv/G8vyZNKzSMuFd5doobngQKq3rRsLdPNZVnGqDdgS884d7Og== +electron-packager@15.4.0: + version "15.4.0" + resolved "https://registry.yarnpkg.com/electron-packager/-/electron-packager-15.4.0.tgz#07ea036b70cde2062d4c8dce4d907d793b303998" + integrity sha512-JrrLcBP15KGrPj0cZ/ALKGmaQ4gJkn3mocf0E3bRKdR3kxKWYcDRpCvdhksYDXw/r3I6tMEcZ7XzyApWFXdVpw== dependencies: "@electron/get" "^1.6.0" - asar "^3.0.0" + asar "^3.1.0" + cross-spawn-windows-exe "^1.2.0" debug "^4.0.1" - electron-notarize "^1.0.0" - electron-osx-sign "^0.4.11" + electron-notarize "^1.1.1" + electron-osx-sign "^0.5.0" extract-zip "^2.0.0" filenamify "^4.1.0" fs-extra "^9.0.0" @@ -17428,10 +17426,10 @@ electron-packager@15.1.0: junk "^3.1.0" parse-author "^2.0.0" plist "^3.0.0" - rcedit "^2.0.0" + rcedit "^3.0.1" resolve "^1.1.6" semver "^7.1.3" - yargs-parser "^19.0.1" + yargs-parser "^20.0.0" electron-publish@22.13.1: version "22.13.1" @@ -32679,10 +32677,12 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" -rcedit@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/rcedit/-/rcedit-2.3.0.tgz#951685a079db98a4cc8c21ebab75e374d5a0b108" - integrity sha512-h1gNEl9Oai1oijwyJ1WYqYSXTStHnOcv1KYljg/8WM4NAg3H1KBK3azIaKkQ1WQl+d7PoJpcBMscPfLXVKgCLQ== +rcedit@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/rcedit/-/rcedit-3.0.1.tgz#ae21b43e49c075f4d84df1929832a12c302f3c90" + integrity sha512-XM0Jv40/y4hVAqj/MO70o/IWs4uOsaSoo2mLyk3klFDW+SStLnCtzuQu+1OBTIMGlM8CvaK9ftlYCp6DJ+cMsw== + dependencies: + cross-spawn-windows-exe "^1.1.0" "react-15.6.1@npm:react@15.6.1": version "15.6.1" @@ -41812,12 +41812,7 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^19.0.1: - version "19.0.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-19.0.4.tgz#99183a3a59268b205c6b04177f2a5bfb46e79ba7" - integrity sha512-eXeQm7yXRjPFFyf1voPkZgXQZJjYfjgQUmGPbD2TLtZeIYzvacgWX7sQ5a1HsRgVP+pfKAkRZDNtTGev4h9vhw== - -yargs-parser@^20.2.2, yargs-parser@^20.2.3: +yargs-parser@^20.0.0, yargs-parser@^20.2.2, yargs-parser@^20.2.3: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== From 2c1ecabf7dd6b5640eb5bc74500228f0c14d5830 Mon Sep 17 00:00:00 2001 From: Blue F Date: Wed, 2 Feb 2022 07:37:32 -0800 Subject: [PATCH 02/12] fix: Adjust ffmpeg CLI args for performance (#19983) * Adjust ffmpeg CLI args for performance * Properly use and clean up metadata file * Always limit encoder to 1 thread regardless of os.cpu count * Typo in last commit * Add comments to ffmpeg args * Remove threads arg, back to original threading behavior --- packages/server/lib/video_capture.ts | 53 ++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/packages/server/lib/video_capture.ts b/packages/server/lib/video_capture.ts index 27a0a8eaf3..14c2b7d662 100644 --- a/packages/server/lib/video_capture.ts +++ b/packages/server/lib/video_capture.ts @@ -274,30 +274,53 @@ export function start (name, options: StartOptions = {}) { type OnProgress = (p: number) => void export async function process (name, cname, videoCompression, ffmpegchaptersConfig, onProgress: OnProgress = function () {}) { - const metaFileName = `${name}.meta` - - const maybeGenerateMetaFile = Bluebird.method(() => { - if (!ffmpegchaptersConfig) { - return false - } - - // Writing the metadata to filesystem is necessary because fluent-ffmpeg is just a wrapper of ffmpeg command. - return fs.writeFile(metaFileName, ffmpegchaptersConfig).then(() => true) - }) - - const addChaptersMeta = await maybeGenerateMetaFile() - let total = null + const metaFileName = `${name}.meta` + const addChaptersMeta = ffmpegchaptersConfig && await fs.writeFile(metaFileName, ffmpegchaptersConfig).then(() => true) + return new Bluebird((resolve, reject) => { debug('processing video from %s to %s video compression %o', name, cname, videoCompression) const command = ffmpeg() + .addOptions([ + // These flags all serve to reduce initial buffering, especially important + // when dealing with very short videos (such as during component tests). + // See https://ffmpeg.org/ffmpeg-formats.html#Format-Options for details. + '-avioflags direct', + + // Because we're passing in a slideshow of still frames, there's no + // fps metadata to be found in the video stream. This ensures that ffmpeg + // isn't buffering a lot of data waiting for information that's not coming. + '-fpsprobesize 0', + + // Tells ffmpeg to read only the first 32 bytes of the stream for information + // (resolution, stream format, etc). + // Some videos can have long metadata (eg, lots of chapters) or spread out, + // but our streams are always predictable; No need to wait / buffer data before + // starting encoding + '-probesize 32', + + // By default ffmpeg buffers the first 5 seconds of video to analyze it before + // it starts encoding. We're basically telling it "there is no metadata coming, + // start encoding as soon as we give you frames." + '-analyzeduration 0', + ]) + + // See https://trac.ffmpeg.org/wiki/Encode/H.264 for details about h264 options. const outputOptions = [ + // Preset is a tradeoff between encoding speed and filesize. It does not determine video + // quality; It's just a tradeoff between CPU vs size. '-preset fast', - `-crf ${videoCompression}`, - '-pix_fmt yuv420p', + // Compression Rate Factor is essentially the quality dial; 0 would be lossless + // (big files), while 51 (the maximum) would lead to low quality (and small files). + `-crf ${videoCompression}`, + + // Discussion of pixel formats is beyond the scope of these comments. See + // https://en.wikipedia.org/wiki/Chroma_subsampling if you want the gritty details. + // Short version: yuv420p is a standard video format supported everywhere. + '-pix_fmt yuv420p', ] if (addChaptersMeta) { From 15885dc20bea8e25ceccf95a5599aa3556ab80b5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 2 Feb 2022 08:29:47 -0800 Subject: [PATCH 03/12] chore: Update Chrome (stable) to 98.0.4758.80 and Chrome (beta) to 98.0.4758.80 (#19995) * chore: Update Chrome (beta) to 98.0.4758.80 * chore: Update Chrome (stable) to 98.0.4758.80 and Chrome (beta) to 98.0.4758.80 Co-authored-by: cypress-bot[bot] <2f0651858c6e38e0+cypress-bot[bot]@users.noreply.github.com> Co-authored-by: Blue F --- browser-versions.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/browser-versions.json b/browser-versions.json index eef229329f..4e3334648d 100644 --- a/browser-versions.json +++ b/browser-versions.json @@ -1,4 +1,4 @@ { - "chrome:beta": "98.0.4758.74", - "chrome:stable": "97.0.4692.99" + "chrome:beta": "98.0.4758.80", + "chrome:stable": "98.0.4758.80" } From 3987eb34300e3adc36976fa28dc4c0e3d9e3a63b Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Wed, 2 Feb 2022 17:10:39 +0000 Subject: [PATCH 04/12] fix(selectFile): use target window's File/DataTransfer classes (#20003) Co-authored-by: Blue F --- .../integration/commands/actions/selectFile_spec.js | 12 ++++++++++++ .../driver/src/cy/commands/actions/selectFile.ts | 10 ++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/driver/cypress/integration/commands/actions/selectFile_spec.js b/packages/driver/cypress/integration/commands/actions/selectFile_spec.js index cf73e534ed..fdcb604a7d 100644 --- a/packages/driver/cypress/integration/commands/actions/selectFile_spec.js +++ b/packages/driver/cypress/integration/commands/actions/selectFile_spec.js @@ -185,6 +185,18 @@ describe('src/cy/commands/actions/selectFile', () => { }) }) + it('uses the AUT\'s File constructor', () => { + cy.window().then(($autWindow) => { + cy.get('#basic').selectFile('@foo', { action: 'select' }).then((input) => { + expect(input[0].files[0]).to.be.instanceOf($autWindow.File) + }) + + cy.get('#basic').selectFile('@foo', { action: 'drag-drop' }).then((input) => { + expect(input[0].files[0]).to.be.instanceOf($autWindow.File) + }) + }) + }) + describe('shorthands', () => { const validJsonString = `{ "foo": 1, diff --git a/packages/driver/src/cy/commands/actions/selectFile.ts b/packages/driver/src/cy/commands/actions/selectFile.ts index edbc7d4bad..a946250bc5 100644 --- a/packages/driver/src/cy/commands/actions/selectFile.ts +++ b/packages/driver/src/cy/commands/actions/selectFile.ts @@ -35,8 +35,10 @@ const tryMockWebkit = (item) => { return item } -const createDataTransfer = (files: Cypress.FileReferenceObject[]): DataTransfer => { - const dataTransfer = new DataTransfer() +const createDataTransfer = (files: Cypress.FileReferenceObject[], eventTarget: JQuery): DataTransfer => { + // obtain a reference to the `targetWindow` so we can use the right instances of the `File` and `DataTransfer` classes + const targetWindow = (eventTarget[0] as HTMLElement).ownerDocument.defaultView || window + const dataTransfer = new targetWindow.DataTransfer() files.forEach(({ contents, @@ -44,7 +46,7 @@ const createDataTransfer = (files: Cypress.FileReferenceObject[]): DataTransfer mimeType = mime.lookup(fileName) || '', lastModified = Date.now(), }) => { - const file = new File([contents], fileName, { lastModified, type: mimeType }) + const file = new targetWindow.File([contents], fileName, { lastModified, type: mimeType }) dataTransfer.items.add(file) }) @@ -302,7 +304,7 @@ export default (Commands, Cypress, cy, state, config) => { }) } - const dataTransfer = createDataTransfer(filesArray) + const dataTransfer = createDataTransfer(filesArray, eventTarget) ACTIONS[options.action as string](eventTarget.get(0), dataTransfer, coords, state) From 74456fe68a4da63581e68c6b8ece416ad2aabc9e Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Thu, 3 Feb 2022 14:57:12 -0500 Subject: [PATCH 05/12] chore: update automerge workflows (#19982) Co-authored-by: Emily Rohrbough --- .../merge-develop-into-10.0-release.yml | 78 ------------------- .../workflows/merge-master-into-develop.yml | 3 +- 2 files changed, 2 insertions(+), 79 deletions(-) delete mode 100644 .github/workflows/merge-develop-into-10.0-release.yml diff --git a/.github/workflows/merge-develop-into-10.0-release.yml b/.github/workflows/merge-develop-into-10.0-release.yml deleted file mode 100644 index 4fc28f1087..0000000000 --- a/.github/workflows/merge-develop-into-10.0-release.yml +++ /dev/null @@ -1,78 +0,0 @@ -name: Merge develop into 10.0-release -on: - push: - branches: - - develop -jobs: - merge-develop-into-10-0-release: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - name: Set committer info - run: | - git config --local user.email "$(git log --format='%ae' HEAD^!)" - git config --local user.name "$(git log --format='%an' HEAD^!)" - - name: Checkout 10.0-release branch - run: git checkout 10.0-release - - name: Check for merge conflict - id: check-conflict - run: echo "::set-output name=merge_conflict::$(git merge-tree $(git merge-base HEAD develop) develop HEAD | egrep '<<<<<<<')" - - name: Merge develop into 10.0-release - id: merge-develop - run: git merge develop - if: ${{ !steps.check-conflict.outputs.merge_conflict }} - - name: Failed merge, set merged status as failed - run: echo "::set-output name=merge_conflict::'failed merge'" - if: ${{ steps.merge-develop.outcome != 'success' }} - - name: Push - run: git push - if: ${{ !steps.check-conflict.outputs.merge_conflict }} - - name: Checkout develop - run: git checkout develop - if: ${{ steps.check-conflict.outputs.merge_conflict }} - - name: Determine name of new branch - id: gen-names - run: | - echo "::set-output name=sha::$(git rev-parse --short HEAD)" - echo "::set-output name=branch_name::$(git rev-parse --short HEAD)-develop-into-10.0-release" - if: ${{ steps.check-conflict.outputs.merge_conflict }} - - name: Create a copy of develop on a new branch - run: git checkout -b ${{ steps.gen-names.outputs.branch_name }} develop - if: ${{ steps.check-conflict.outputs.merge_conflict }} - - name: Push branch to remote - run: git push origin ${{ steps.gen-names.outputs.branch_name }} - if: ${{ steps.check-conflict.outputs.merge_conflict }} - - name: Create Pull Request - uses: actions/github-script@v3 - with: - script: | - const pull = await github.pulls.create({ - owner: context.repo.owner, - repo: context.repo.repo, - base: '10.0-release', - head: '${{ steps.gen-names.outputs.branch_name }}', - title: 'chore: merge develop (${{ steps.gen-names.outputs.sha }}) into 10.0-release', - body: `There was a merge conflict when trying to automatically merge develop into 10.0-release. Please resolve the conflict and complete the merge. - - DO NOT SQUASH AND MERGE - - @${context.actor}`, - maintainer_can_modify: true, - }) - await github.pulls.requestReviewers({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: pull.data.number, - reviewers: [context.actor], - }) - await github.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pull.data.number, - labels: ['auto-merge'], - }) - if: ${{ steps.check-conflict.outputs.merge_conflict }} diff --git a/.github/workflows/merge-master-into-develop.yml b/.github/workflows/merge-master-into-develop.yml index 28c1a91171..2b15adabc1 100644 --- a/.github/workflows/merge-master-into-develop.yml +++ b/.github/workflows/merge-master-into-develop.yml @@ -11,7 +11,8 @@ jobs: uses: actions/checkout@v2 with: fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} + # the default `GITHUB_TOKEN` cannot push to protected branches, so use `cypress-app-bot`'s token instead + token: ${{ secrets.BOT_GITHUB_TOKEN }} - name: Set committer info run: | git config --local user.email "$(git log --format='%ae' HEAD^!)" From 1395766822dc86e5519514c2a56bfdbb892a939a Mon Sep 17 00:00:00 2001 From: Emily Rohrbough Date: Fri, 4 Feb 2022 08:28:06 -0600 Subject: [PATCH 06/12] chore(driver): move cy.within logic into it's own file (#20036) --- .../commands/{ => querying}/querying_spec.js | 261 +-------------- .../shadow_dom_spec.js} | 30 +- .../commands/querying/within_spec.js | 300 ++++++++++++++++++ packages/driver/src/cy/commands/index.ts | 2 +- .../driver/src/cy/commands/querying/index.ts | 7 + .../cy/commands/{ => querying}/querying.ts | 110 +------ .../driver/src/cy/commands/querying/within.ts | 105 ++++++ 7 files changed, 420 insertions(+), 395 deletions(-) rename packages/driver/cypress/integration/commands/{ => querying}/querying_spec.js (89%) rename packages/driver/cypress/integration/commands/{querying_shadow_dom_spec.js => querying/shadow_dom_spec.js} (87%) create mode 100644 packages/driver/cypress/integration/commands/querying/within_spec.js create mode 100644 packages/driver/src/cy/commands/querying/index.ts rename packages/driver/src/cy/commands/{ => querying}/querying.ts (83%) create mode 100644 packages/driver/src/cy/commands/querying/within.ts diff --git a/packages/driver/cypress/integration/commands/querying_spec.js b/packages/driver/cypress/integration/commands/querying/querying_spec.js similarity index 89% rename from packages/driver/cypress/integration/commands/querying_spec.js rename to packages/driver/cypress/integration/commands/querying/querying_spec.js index e1a6857761..6882f42c8b 100644 --- a/packages/driver/cypress/integration/commands/querying_spec.js +++ b/packages/driver/cypress/integration/commands/querying/querying_spec.js @@ -1,4 +1,4 @@ -const { assertLogLength } = require('../../support/utils') +const { assertLogLength } = require('../../../support/utils') const { _, $, Promise } = Cypress @@ -225,265 +225,6 @@ describe('src/cy/commands/querying', () => { }) }) - context('#within', () => { - it('invokes callback function with runnable.ctx', function () { - const ctx = this - - cy.get('div:first').within(function () { - expect(ctx === this).to.be.true - }) - }) - - it('scopes additional GET finders to the subject', () => { - const input = cy.$$('#by-name input:first') - - cy.get('#by-name').within(() => { - cy.get('input:first').then(($input) => { - expect($input.get(0)).to.eq(input.get(0)) - }) - }) - }) - - it('scopes additional CONTAINS finders to the subject', () => { - const span = cy.$$('#nested-div span:contains(foo)') - - cy.contains('foo').then(($span) => { - expect($span.get(0)).not.to.eq(span.get(0)) - }) - - cy.get('#nested-div').within(() => { - cy.contains('foo').then(($span) => { - expect($span.get(0)).to.eq(span.get(0)) - }) - }) - }) - - it('does not change the subject', () => { - const form = cy.$$('#by-name') - - cy.get('#by-name').within(() => {}).then(($form) => { - expect($form.get(0)).to.eq(form.get(0)) - }) - }) - - it('can call child commands after within on the same subject', () => { - const input = cy.$$('#by-name input:first') - - cy.get('#by-name').within(() => {}).find('input:first').then(($input) => { - expect($input.get(0)).to.eq(input.get(0)) - }) - }) - - it('supports nested withins', () => { - const span = cy.$$('#button-text button span') - - cy.get('#button-text').within(() => { - cy.get('button').within(() => { - cy.get('span').then(($span) => { - expect($span.get(0)).to.eq(span.get(0)) - }) - }) - }) - }) - - it('supports complicated nested withins', () => { - const span1 = cy.$$('#button-text a span') - const span2 = cy.$$('#button-text button span') - - cy.get('#button-text').within(() => { - cy.get('a').within(() => { - cy.get('span').then(($span) => { - expect($span.get(0)).to.eq(span1.get(0)) - }) - }) - - cy.get('button').within(() => { - cy.get('span').then(($span) => { - expect($span.get(0)).to.eq(span2.get(0)) - }) - }) - }) - }) - - it('clears withinSubject after within is over', () => { - const input = cy.$$('input:first') - const span = cy.$$('#button-text button span') - - cy.get('#button-text').within(() => { - cy.get('button').within(() => { - cy.get('span').then(($span) => { - expect($span.get(0)).to.eq(span.get(0)) - }) - }) - }) - - cy.get('input:first').then(($input) => { - expect($input.get(0)).to.eq(input.get(0)) - }) - }) - - it('removes command:start listeners after within is over', () => { - cy.get('#button-text').within(() => { - cy.get('button').within(() => { - cy.get('span') - }) - }) - - cy.then(() => { - expect(cy._events).not.to.have.property('command:start') - }) - }) - - it('clears withinSubject even if next is null', (done) => { - const span = cy.$$('#button-text button span') - - // should be defined here because next would have been - // null and withinSubject would not have been cleared - cy.once('command:queue:before:end', () => { - expect(cy.state('withinSubject')).not.to.be.undefined - }) - - cy.once('command:queue:end', () => { - expect(cy.state('withinSubject')).to.be.null - - done() - }) - - cy.get('#button-text').within(() => { - cy.get('button span').then(($span) => { - expect($span.get(0)).to.eq(span.get(0)) - }) - }) - }) - - // https://github.com/cypress-io/cypress/issues/4757 - it('subject is restored after within() call', () => { - cy.get('#wrapper').within(() => { - cy.get('#upper').should('contain.text', 'New York') - }) - .should('have.id', 'wrapper') - }) - - // https://github.com/cypress-io/cypress/issues/5183 - it('contains() works after within() call', () => { - cy.get(`#wrapper`).within(() => cy.get(`#upper`)).should(`contain.text`, `New York`) - cy.contains(`button`, `button`).should(`exist`) - }) - - describe('.log', () => { - beforeEach(function () { - this.logs = [] - - cy.on('log:added', (attrs, log) => { - if (attrs.name === 'within') { - this.lastLog = log - - this.logs.push(log) - } - }) - - return null - }) - - it('can silence logging', () => { - cy.get('div:first').within({ log: false }, () => {}).then(function () { - assertLogLength(this.logs, 0) - }) - }) - - it('logs immediately before resolving', (done) => { - const div = cy.$$('div:first') - - cy.on('log:added', (attrs, log) => { - if (log.get('name') === 'within') { - expect(log.get('state')).to.eq('pending') - expect(log.get('message')).to.eq('') - expect(log.get('$el').get(0)).to.eq(div.get(0)) - - done() - } - }) - - cy.get('div:first').within(() => {}) - }) - - it('snapshots after clicking', () => { - cy.get('div:first').within(() => {}) - .then(function () { - const { lastLog } = this - - expect(lastLog.get('snapshots').length).to.eq(1) - - expect(lastLog.get('snapshots')[0]).to.be.an('object') - }) - }) - }) - - describe('errors', { - defaultCommandTimeout: 100, - }, () => { - beforeEach(function () { - this.logs = [] - - cy.on('log:added', (attrs, log) => { - this.lastLog = log - - this.logs.push(log) - }) - - return null - }) - - it('logs once when not dom subject', function (done) { - cy.on('fail', (err) => { - const { lastLog } = this - - assertLogLength(this.logs, 1) - expect(lastLog.get('error')).to.eq(err) - - done() - }) - - cy.noop().within(() => {}) - }) - - it('throws when not a DOM subject', (done) => { - cy.on('fail', (err) => { - done() - }) - - cy.noop().within(() => {}) - }) - - _.each(['', [], {}, 1, null, undefined], (value) => { - it(`throws if passed anything other than a function, such as: ${value}`, (done) => { - cy.on('fail', (err) => { - expect(err.message).to.include('`cy.within()` must be called with a function.') - expect(err.docsUrl).to.eq('https://on.cypress.io/within') - - done() - }) - - cy.get('body').within(value) - }) - }) - - it('throws when subject is not in the document', (done) => { - cy.on('command:end', () => { - cy.$$('#list').remove() - }) - - cy.on('fail', (err) => { - expect(err.message).to.include('`cy.within()` failed because this element') - - done() - }) - - cy.get('#list').within(() => {}) - }) - }) - }) - context('#root', () => { it('returns html', () => { const html = cy.$$('html') diff --git a/packages/driver/cypress/integration/commands/querying_shadow_dom_spec.js b/packages/driver/cypress/integration/commands/querying/shadow_dom_spec.js similarity index 87% rename from packages/driver/cypress/integration/commands/querying_shadow_dom_spec.js rename to packages/driver/cypress/integration/commands/querying/shadow_dom_spec.js index 11c6e8e8d4..7e1e8fba32 100644 --- a/packages/driver/cypress/integration/commands/querying_shadow_dom_spec.js +++ b/packages/driver/cypress/integration/commands/querying/shadow_dom_spec.js @@ -1,4 +1,4 @@ -const helpers = require('../../support/helpers') +const helpers = require('../../../support/helpers') const { _ } = Cypress @@ -7,34 +7,6 @@ describe('src/cy/commands/querying - shadow dom', () => { cy.visit('/fixtures/shadow-dom.html') }) - context('#within', () => { - it('finds element within shadow dom with includeShadowDom option', () => { - cy.get('#parent-of-shadow-container-0').within(() => { - cy - .get('p', { includeShadowDom: true }) - .should('have.length', 1) - .should('have.text', 'Shadow Content 3') - }) - }) - - it('when within subject is shadow root, finds element without needing includeShadowDom option', () => { - cy.get('#shadow-element-1').shadow().within(() => { - cy - .get('p') - .should('have.length', 1) - .should('have.text', 'Shadow Content 1') - }) - }) - - it('when within subject is already in shadow dom, finds element without needing includeShadowDom option', () => { - cy.get('.shadow-8-nested-1', { includeShadowDom: true }).within(() => { - cy - .get('.shadow-8-nested-5') - .should('have.text', '8') - }) - }) - }) - context('#get', () => { it('finds elements within shadow roots', () => { cy.get('.shadow-1', { includeShadowDom: true }) diff --git a/packages/driver/cypress/integration/commands/querying/within_spec.js b/packages/driver/cypress/integration/commands/querying/within_spec.js new file mode 100644 index 0000000000..bda7f8eaf5 --- /dev/null +++ b/packages/driver/cypress/integration/commands/querying/within_spec.js @@ -0,0 +1,300 @@ +const { assertLogLength } = require('../../../support/utils') + +const { _ } = Cypress + +describe('src/cy/commands/querying/within', () => { + context('#within', () => { + beforeEach(() => { + cy.visit('/fixtures/dom.html') + }) + + it('invokes callback function with runnable.ctx', function () { + const ctx = this + + cy.get('div:first').within(function () { + expect(ctx === this).to.be.true + }) + }) + + it('scopes additional GET finders to the subject', () => { + const input = cy.$$('#by-name input:first') + + cy.get('#by-name').within(() => { + cy.get('input:first').then(($input) => { + expect($input.get(0)).to.eq(input.get(0)) + }) + }) + }) + + it('scopes additional CONTAINS finders to the subject', () => { + const span = cy.$$('#nested-div span:contains(foo)') + + cy.contains('foo').then(($span) => { + expect($span.get(0)).not.to.eq(span.get(0)) + }) + + cy.get('#nested-div').within(() => { + cy.contains('foo').then(($span) => { + expect($span.get(0)).to.eq(span.get(0)) + }) + }) + }) + + it('does not change the subject', () => { + const form = cy.$$('#by-name') + + cy.get('#by-name').within(() => {}).then(($form) => { + expect($form.get(0)).to.eq(form.get(0)) + }) + }) + + it('can call child commands after within on the same subject', () => { + const input = cy.$$('#by-name input:first') + + cy.get('#by-name').within(() => {}).find('input:first').then(($input) => { + expect($input.get(0)).to.eq(input.get(0)) + }) + }) + + it('supports nested withins', () => { + const span = cy.$$('#button-text button span') + + cy.get('#button-text').within(() => { + cy.get('button').within(() => { + cy.get('span').then(($span) => { + expect($span.get(0)).to.eq(span.get(0)) + }) + }) + }) + }) + + it('supports complicated nested withins', () => { + const span1 = cy.$$('#button-text a span') + const span2 = cy.$$('#button-text button span') + + cy.get('#button-text').within(() => { + cy.get('a').within(() => { + cy.get('span').then(($span) => { + expect($span.get(0)).to.eq(span1.get(0)) + }) + }) + + cy.get('button').within(() => { + cy.get('span').then(($span) => { + expect($span.get(0)).to.eq(span2.get(0)) + }) + }) + }) + }) + + it('clears withinSubject after within is over', () => { + const input = cy.$$('input:first') + const span = cy.$$('#button-text button span') + + cy.get('#button-text').within(() => { + cy.get('button').within(() => { + cy.get('span').then(($span) => { + expect($span.get(0)).to.eq(span.get(0)) + }) + }) + }) + + cy.get('input:first').then(($input) => { + expect($input.get(0)).to.eq(input.get(0)) + }) + }) + + it('removes command:start listeners after within is over', () => { + cy.get('#button-text').within(() => { + cy.get('button').within(() => { + cy.get('span') + }) + }) + + cy.then(() => { + expect(cy._events).not.to.have.property('command:start') + }) + }) + + it('clears withinSubject even if next is null', (done) => { + const span = cy.$$('#button-text button span') + + // should be defined here because next would have been + // null and withinSubject would not have been cleared + cy.once('command:queue:before:end', () => { + expect(cy.state('withinSubject')).not.to.be.undefined + }) + + cy.once('command:queue:end', () => { + expect(cy.state('withinSubject')).to.be.null + + done() + }) + + cy.get('#button-text').within(() => { + cy.get('button span').then(($span) => { + expect($span.get(0)).to.eq(span.get(0)) + }) + }) + }) + + // https://github.com/cypress-io/cypress/issues/4757 + it('subject is restored after within() call', () => { + cy.get('#wrapper').within(() => { + cy.get('#upper').should('contain.text', 'New York') + }) + .should('have.id', 'wrapper') + }) + + // https://github.com/cypress-io/cypress/issues/5183 + it('contains() works after within() call', () => { + cy.get(`#wrapper`).within(() => cy.get(`#upper`)).should(`contain.text`, `New York`) + cy.contains(`button`, `button`).should(`exist`) + }) + + describe('.log', () => { + beforeEach(function () { + this.logs = [] + + cy.on('log:added', (attrs, log) => { + if (attrs.name === 'within') { + this.lastLog = log + + this.logs.push(log) + } + }) + + return null + }) + + it('can silence logging', () => { + cy.get('div:first').within({ log: false }, () => {}).then(function () { + assertLogLength(this.logs, 0) + }) + }) + + it('logs immediately before resolving', (done) => { + const div = cy.$$('div:first') + + cy.on('log:added', (attrs, log) => { + if (log.get('name') === 'within') { + expect(log.get('state')).to.eq('pending') + expect(log.get('message')).to.eq('') + expect(log.get('$el').get(0)).to.eq(div.get(0)) + + done() + } + }) + + cy.get('div:first').within(() => {}) + }) + + it('snapshots after clicking', () => { + cy.get('div:first').within(() => {}) + .then(function () { + const { lastLog } = this + + expect(lastLog.get('snapshots').length).to.eq(1) + + expect(lastLog.get('snapshots')[0]).to.be.an('object') + }) + }) + }) + + describe('errors', { + defaultCommandTimeout: 100, + }, () => { + beforeEach(function () { + this.logs = [] + + cy.on('log:added', (attrs, log) => { + this.lastLog = log + + this.logs.push(log) + }) + + return null + }) + + it('logs once when not dom subject', function (done) { + cy.on('fail', (err) => { + const { lastLog } = this + + assertLogLength(this.logs, 1) + expect(lastLog.get('error')).to.eq(err) + + done() + }) + + cy.noop().within(() => {}) + }) + + it('throws when not a DOM subject', (done) => { + cy.on('fail', (err) => { + done() + }) + + cy.noop().within(() => {}) + }) + + _.each(['', [], {}, 1, null, undefined], (value) => { + it(`throws if passed anything other than a function, such as: ${value}`, (done) => { + cy.on('fail', (err) => { + expect(err.message).to.include('`cy.within()` must be called with a function.') + expect(err.docsUrl).to.eq('https://on.cypress.io/within') + + done() + }) + + cy.get('body').within(value) + }) + }) + + it('throws when subject is not in the document', (done) => { + cy.on('command:end', () => { + cy.$$('#list').remove() + }) + + cy.on('fail', (err) => { + expect(err.message).to.include('`cy.within()` failed because this element') + + done() + }) + + cy.get('#list').within(() => {}) + }) + }) + }) + + context('#within - shadow dom', () => { + beforeEach(() => { + cy.visit('/fixtures/shadow-dom.html') + }) + + it('finds element within shadow dom with includeShadowDom option', () => { + cy.get('#parent-of-shadow-container-0').within(() => { + cy + .get('p', { includeShadowDom: true }) + .should('have.length', 1) + .should('have.text', 'Shadow Content 3') + }) + }) + + it('when within subject is shadow root, finds element without needing includeShadowDom option', () => { + cy.get('#shadow-element-1').shadow().within(() => { + cy + .get('p') + .should('have.length', 1) + .should('have.text', 'Shadow Content 1') + }) + }) + + it('when within subject is already in shadow dom, finds element without needing includeShadowDom option', () => { + cy.get('.shadow-8-nested-1', { includeShadowDom: true }).within(() => { + cy + .get('.shadow-8-nested-5') + .should('have.text', '8') + }) + }) + }) +}) diff --git a/packages/driver/src/cy/commands/index.ts b/packages/driver/src/cy/commands/index.ts index 055d59fdf5..cc49499de9 100644 --- a/packages/driver/src/cy/commands/index.ts +++ b/packages/driver/src/cy/commands/index.ts @@ -71,7 +71,7 @@ export const allCommands = { Misc, Popups, Navigation, - Querying, + ...Querying, Request, Sessions, Screenshot, diff --git a/packages/driver/src/cy/commands/querying/index.ts b/packages/driver/src/cy/commands/querying/index.ts new file mode 100644 index 0000000000..2574bc7fb6 --- /dev/null +++ b/packages/driver/src/cy/commands/querying/index.ts @@ -0,0 +1,7 @@ +import * as Querying from './querying' +import * as Within from './within' + +export { + Querying, + Within, +} diff --git a/packages/driver/src/cy/commands/querying.ts b/packages/driver/src/cy/commands/querying/querying.ts similarity index 83% rename from packages/driver/src/cy/commands/querying.ts rename to packages/driver/src/cy/commands/querying/querying.ts index 7d1b2092c5..e372a9605b 100644 --- a/packages/driver/src/cy/commands/querying.ts +++ b/packages/driver/src/cy/commands/querying/querying.ts @@ -1,12 +1,11 @@ import _ from 'lodash' import Promise from 'bluebird' -import { $Command } from '../../cypress/command' -import $dom from '../../dom' -import $elements from '../../dom/elements' -import $errUtils from '../../cypress/error_utils' -import { resolveShadowDomInclusion } from '../../cypress/shadow_dom_utils' -import { getAliasedRequests, isDynamicAliasingPossible } from '../net-stubbing/aliasing' +import $dom from '../../../dom' +import $elements from '../../../dom/elements' +import $errUtils from '../../../cypress/error_utils' +import { resolveShadowDomInclusion } from '../../../cypress/shadow_dom_utils' +import { getAliasedRequests, isDynamicAliasingPossible } from '../../net-stubbing/aliasing' export default (Commands, Cypress, cy, state) => { Commands.addAll({ @@ -603,105 +602,6 @@ export default (Commands, Cypress, cy, state) => { }, }) - Commands.addAll({ prevSubject: ['element', 'document'] }, { - within (subject, options, fn) { - let userOptions = options - const ctx = this - - if (_.isUndefined(fn)) { - fn = userOptions - userOptions = {} - } - - options = _.defaults({}, userOptions, { log: true }) - - if (options.log) { - options._log = Cypress.log({ - $el: subject, - message: '', - timeout: options.timeout, - }) - } - - if (!_.isFunction(fn)) { - $errUtils.throwErrByPath('within.invalid_argument', { onFail: options._log }) - } - - // reference the next command after this - // within. when that command runs we'll - // know to remove withinSubject - const next = state('current').get('next') - - // backup the current withinSubject - // this prevents a bug where we null out - // withinSubject when there are nested .withins() - // we want the inner within to restore the outer - // once its done - const prevWithinSubject = state('withinSubject') - - state('withinSubject', subject) - - // https://github.com/cypress-io/cypress/pull/8699 - // An internal command is inserted to create a divider between - // commands inside within() callback and commands chained to it. - const restoreCmdIndex = state('index') + 1 - - cy.queue.insert(restoreCmdIndex, $Command.create({ - args: [subject], - name: 'within-restore', - fn: (subject) => subject, - })) - - state('index', restoreCmdIndex) - - fn.call(ctx, subject) - - const cleanup = () => cy.removeListener('command:start', setWithinSubject) - - // we need a mechanism to know when we should remove - // our withinSubject so we dont accidentally keep it - // around after the within callback is done executing - // so when each command starts, check to see if this - // is the command which references our 'next' and - // if so, remove the within subject - const setWithinSubject = (obj) => { - if (obj !== next) { - return - } - - // okay so what we're doing here is creating a property - // which stores the 'next' command which will reset the - // withinSubject. If two 'within' commands reference the - // exact same 'next' command, then this prevents accidentally - // resetting withinSubject more than once. If they point - // to differnet 'next's then its okay - if (next !== state('nextWithinSubject')) { - state('withinSubject', prevWithinSubject || null) - state('nextWithinSubject', next) - } - - // regardless nuke this listeners - cleanup() - } - - // if next is defined then we know we'll eventually - // unbind these listeners - if (next) { - cy.on('command:start', setWithinSubject) - } else { - // remove our listener if we happen to reach the end - // event which will finalize cleanup if there was no next obj - cy.once('command:queue:before:end', () => { - cleanup() - - state('withinSubject', null) - }) - } - - return subject - }, - }) - Commands.add('shadow', { prevSubject: 'element' }, (subject, options) => { const userOptions = options || {} diff --git a/packages/driver/src/cy/commands/querying/within.ts b/packages/driver/src/cy/commands/querying/within.ts new file mode 100644 index 0000000000..ad5ed66c7d --- /dev/null +++ b/packages/driver/src/cy/commands/querying/within.ts @@ -0,0 +1,105 @@ +import _ from 'lodash' + +import { $Command } from '../../../cypress/command' +import $errUtils from '../../../cypress/error_utils' + +export default (Commands, Cypress, cy, state) => { + Commands.addAll({ prevSubject: ['element', 'document'] }, { + within (subject, options, fn) { + let userOptions = options + const ctx = this + + if (_.isUndefined(fn)) { + fn = userOptions + userOptions = {} + } + + options = _.defaults({}, userOptions, { log: true }) + + if (options.log) { + options._log = Cypress.log({ + $el: subject, + message: '', + timeout: options.timeout, + }) + } + + if (!_.isFunction(fn)) { + $errUtils.throwErrByPath('within.invalid_argument', { onFail: options._log }) + } + + // reference the next command after this + // within. when that command runs we'll + // know to remove withinSubject + const next = state('current').get('next') + + // backup the current withinSubject + // this prevents a bug where we null out + // withinSubject when there are nested .withins() + // we want the inner within to restore the outer + // once its done + const prevWithinSubject = state('withinSubject') + + state('withinSubject', subject) + + // https://github.com/cypress-io/cypress/pull/8699 + // An internal command is inserted to create a divider between + // commands inside within() callback and commands chained to it. + const restoreCmdIndex = state('index') + 1 + + cy.queue.insert(restoreCmdIndex, $Command.create({ + args: [subject], + name: 'within-restore', + fn: (subject) => subject, + })) + + state('index', restoreCmdIndex) + + fn.call(ctx, subject) + + const cleanup = () => cy.removeListener('command:start', setWithinSubject) + + // we need a mechanism to know when we should remove + // our withinSubject so we dont accidentally keep it + // around after the within callback is done executing + // so when each command starts, check to see if this + // is the command which references our 'next' and + // if so, remove the within subject + const setWithinSubject = (obj) => { + if (obj !== next) { + return + } + + // okay so what we're doing here is creating a property + // which stores the 'next' command which will reset the + // withinSubject. If two 'within' commands reference the + // exact same 'next' command, then this prevents accidentally + // resetting withinSubject more than once. If they point + // to differnet 'next's then its okay + if (next !== state('nextWithinSubject')) { + state('withinSubject', prevWithinSubject || null) + state('nextWithinSubject', next) + } + + // regardless nuke this listeners + cleanup() + } + + // if next is defined then we know we'll eventually + // unbind these listeners + if (next) { + cy.on('command:start', setWithinSubject) + } else { + // remove our listener if we happen to reach the end + // event which will finalize cleanup if there was no next obj + cy.once('command:queue:before:end', () => { + cleanup() + + state('withinSubject', null) + }) + } + + return subject + }, + }) +} From 17f272e9eea9060e1dc68426e11ab4c4d9aff8d5 Mon Sep 17 00:00:00 2001 From: Emily Rohrbough Date: Fri, 4 Feb 2022 12:00:08 -0600 Subject: [PATCH 07/12] chore: fix cypress npm package artifact upload path (#20023) --- __snapshots__/upload-npm-package-spec.js | 9 - __snapshots__/upload-unique-binary-spec.js | 26 --- assets/cypress-bot-pre-release-comment.png | Bin 0 -> 169345 bytes circle.yml | 56 ++--- cli/lib/tasks/install.js | 2 +- cli/test/lib/tasks/install_spec.js | 6 +- guides/release-process.md | 17 +- scripts/binary/index.js | 33 +-- scripts/binary/move-binaries.ts | 10 +- scripts/binary/upload-build-artifact.js | 145 +++++++++++++ scripts/binary/upload-npm-package.js | 122 ----------- scripts/binary/upload-unique-binary.js | 197 ------------------ scripts/binary/upload.js | 91 ++++---- scripts/binary/util/upload.js | 1 - .../unit/binary/upload-build-artifact-spec.js | 140 +++++++++++++ .../unit/binary/upload-npm-package-spec.js | 23 -- scripts/unit/binary/upload-spec.js | 28 +-- .../unit/binary/upload-unique-binary-spec.js | 62 ------ 18 files changed, 378 insertions(+), 590 deletions(-) delete mode 100644 __snapshots__/upload-npm-package-spec.js delete mode 100644 __snapshots__/upload-unique-binary-spec.js create mode 100644 assets/cypress-bot-pre-release-comment.png create mode 100644 scripts/binary/upload-build-artifact.js delete mode 100644 scripts/binary/upload-npm-package.js delete mode 100644 scripts/binary/upload-unique-binary.js create mode 100644 scripts/unit/binary/upload-build-artifact-spec.js delete mode 100644 scripts/unit/binary/upload-npm-package-spec.js delete mode 100644 scripts/unit/binary/upload-unique-binary-spec.js diff --git a/__snapshots__/upload-npm-package-spec.js b/__snapshots__/upload-npm-package-spec.js deleted file mode 100644 index 5872c9c734..0000000000 --- a/__snapshots__/upload-npm-package-spec.js +++ /dev/null @@ -1,9 +0,0 @@ -exports['getCDN npm package returns CDN s3 path 1'] = { - "input": { - "platform": "darwin-x64", - "filename": "cypress.tgz", - "version": "3.3.0", - "hash": "ci-name-e154a40f3f76abd39a1d85c0ebc0ff9565015706-123" - }, - "result": "https://cdn.cypress.io/beta/npm/3.3.0/ci-name-e154a40f3f76abd39a1d85c0ebc0ff9565015706-123/cypress.tgz" -} diff --git a/__snapshots__/upload-unique-binary-spec.js b/__snapshots__/upload-unique-binary-spec.js deleted file mode 100644 index 9f7c9488d4..0000000000 --- a/__snapshots__/upload-unique-binary-spec.js +++ /dev/null @@ -1,26 +0,0 @@ -exports['getCDN for binary'] = { - "input": { - "platform": "darwin-x64", - "filename": "cypress.zip", - "version": "3.3.0", - "hash": "ci-name-e154a40f3f76abd39a1d85c0ebc0ff9565015706-123" - }, - "result": "https://cdn.cypress.io/beta/binary/3.3.0/darwin-x64/ci-name-e154a40f3f76abd39a1d85c0ebc0ff9565015706-123/cypress.zip" -} - -exports['upload binary folder'] = { - "input": { - "platformArch": "darwin-x64", - "version": "3.3.0", - "hash": "ci-name-e154a40f3f76abd39a1d85c0ebc0ff9565015706-123" - }, - "result": "beta/binary/3.3.0/darwin-x64/ci-name-e154a40f3f76abd39a1d85c0ebc0ff9565015706-123/" -} - -exports['upload binary folder for platform'] = { - "input": { - "platformArch": "darwin-x64", - "version": "3.3.0" - }, - "result": "beta/binary/3.3.0/darwin-x64" -} diff --git a/assets/cypress-bot-pre-release-comment.png b/assets/cypress-bot-pre-release-comment.png new file mode 100644 index 0000000000000000000000000000000000000000..28c8cccdc8f70c20d8feda0b1f0105ad74a53fd7 GIT binary patch literal 169345 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 R { }) } -const betaNpmUrlRe = /^\/beta\/npm\/(?[0-9.]+)\/(?.+?)\/cypress\.tgz$/ +const betaNpmUrlRe = /^\/beta\/npm\/(?[0-9.]+)\/(?.+?)\/(?.+?)\/cypress\.tgz$/ // convert a prerelease NPM package .tgz URL to the corresponding binary .zip URL const getBinaryUrlFromPrereleaseNpmUrl = (npmUrl) => { diff --git a/cli/test/lib/tasks/install_spec.js b/cli/test/lib/tasks/install_spec.js index f8a7d9f66e..ec1b91ac81 100644 --- a/cli/test/lib/tasks/install_spec.js +++ b/cli/test/lib/tasks/install_spec.js @@ -467,13 +467,13 @@ describe('/lib/tasks/install', function () { }) it('returns binary url for prerelease npm url', function () { - expect(install._getBinaryUrlFromPrereleaseNpmUrl('https://cdn.cypress.io/beta/npm/5.1.1/ciprovider-branchname-sha/cypress.tgz')) + expect(install._getBinaryUrlFromPrereleaseNpmUrl('https://cdn.cypress.io/beta/npm/5.1.1/linux-x64/ciprovider-branchname-sha/cypress.tgz')) .to.eq('https://cdn.cypress.io/beta/binary/5.1.1/linux-x64/ciprovider-branchname-sha/cypress.zip') - expect(install._getBinaryUrlFromPrereleaseNpmUrl('https://cdn.cypress.io/beta/npm/5.1.1/circle-develop-3fdfc3b453eb38ad3c0b079531e4dde6668e3dd0-436710/cypress.tgz')) + expect(install._getBinaryUrlFromPrereleaseNpmUrl('https://cdn.cypress.io/beta/npm/5.1.1/inux-x64/circle-develop-3fdfc3b453eb38ad3c0b079531e4dde6668e3dd0-436710/cypress.tgz')) .to.eq('https://cdn.cypress.io/beta/binary/5.1.1/linux-x64/circle-develop-3fdfc3b453eb38ad3c0b079531e4dde6668e3dd0-436710/cypress.zip') - expect(install._getBinaryUrlFromPrereleaseNpmUrl('https://cdn.cypress.io/beta/npm/5.1.1/circle-develop/some/branch-3fdfc3b453eb38ad3c0b079531e4dde6668e3dd0-436710/cypress.tgz')) + expect(install._getBinaryUrlFromPrereleaseNpmUrl('https://cdn.cypress.io/beta/npm/5.1.1/inux-x64/circle-develop/some/branch-3fdfc3b453eb38ad3c0b079531e4dde6668e3dd0-436710/cypress.tgz')) .to.eq('https://cdn.cypress.io/beta/binary/5.1.1/linux-x64/circle-develop/some/branch-3fdfc3b453eb38ad3c0b079531e4dde6668e3dd0-436710/cypress.zip') }) diff --git a/guides/release-process.md b/guides/release-process.md index 86d342c092..ce2c0e8e53 100644 --- a/guides/release-process.md +++ b/guides/release-process.md @@ -67,13 +67,7 @@ of Cypress. You can see the progress of the test projects by opening the status ![Screenshot of status checks](https://i.imgur.com/AsQwzgO.png) -#### :bangbang: Important :bangbang: - -The `linux x64`, `win32 x64`, and `darwin x64` artifacts produced by CI are all placed in the same directory on the CDN. The version that was built last will overwrite the other versions in the directory. Until work is done to complete [#19771](https://github.com/cypress-io/cypress/issues/19771), you must ensure that the `linux` workflow publishes its artifacts **after** the `windows`/`mac` workflows. To guarantee this, you can re-run the `create-build-artifacts` job for the `linux` workflow within CircleCI after the initial builds have completed. - - - -Once the `develop` branch for all test projects are reliably passing with the new changes and the `linux` binary is present at `https://cdn.cypress.io/beta/npm/X.Y.Z//cypress.tgz`, publishing can proceed. +Once the `develop` branch for all test projects are reliably passing with the new changes and the `linux-x64` binary is present at `https://cdn.cypress.io/beta/binary/X.Y.Z/linux-x64//cypress.zip`, and the `linux-x64` cypress npm package is present at `https://cdn.cypress.io/beta/binary/X.Y.Z/linux-x64//cypress.tgz`, publishing can proceed. ### Steps to Publish a New Version @@ -93,14 +87,15 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy - To find the link to the package file `cypress.tgz`: 1. In GitHub, go to the latest commit (the one whose sha you used in the last step). ![commit-link](https://user-images.githubusercontent.com/1157043/80608728-33fe6100-8a05-11ea-8b53-375303757b67.png) - 2. Scroll down past the changes to the comments. The first comment should be a `cypress-bot` comment that includes a line beginning `npm install ...`. Grab the `https://cdn.../npm/X.Y.Z//cypress.tgz` link. - ![cdn-tgz-link](https://user-images.githubusercontent.com/1157043/80608736-3791e800-8a05-11ea-8d75-e4f80128e857.png) - - Make sure the linux binaries are present at that location. See [Before Publishing a New Version](#before-publishing-a-new-version). - - Publish to the npm registry straight from the URL: + 2. Scroll down past the changes to the comments. The first comment should be a `cypress-bot` comment that includes a line beginning `npm install ...`. Grab the `https://cdn.../npm/X.Y.Z///cypress.tgz` link. + ![commit-bot-comment](../assets/cypress-bot-pre-release-comment.png) + - Make sure the `linux-x64` binary and npm package are present at the commented locations. See [Before Publishing a New Version](#before-publishing-a-new-version). + - Publish the `linux-x64` distribution to the npm registry straight from the URL: ```shell npm publish https://cdn.cypress.io/beta/npm/X.Y.Z//cypress.tgz --tag dev ``` + :bangbang: Important :bangbang: Be sure to release the `linux-x64` distribution. 5. Double-check that the new version has been published under the `dev` tag using `npm info cypress` or [available-versions](https://github.com/bahmutov/available-versions). `latest` should still point to the previous version. Example output: diff --git a/scripts/binary/index.js b/scripts/binary/index.js index 30efde1260..7e15711670 100644 --- a/scripts/binary/index.js +++ b/scripts/binary/index.js @@ -21,8 +21,7 @@ const meta = require('./meta') const build = require('./build') const upload = require('./upload') const uploadUtils = require('./util/upload') -const { uploadNpmPackage } = require('./upload-npm-package') -const { uploadUniqueBinary } = require('./upload-unique-binary') +const { uploadArtifactToS3 } = require('./upload-build-artifact') const { moveBinaries } = require('./move-binaries') // initialize on existing repo @@ -252,18 +251,11 @@ const deploy = { }) }, - // upload Cypress NPM package file - 'upload-npm-package' (args = process.argv) { - console.log('#packageUpload') + // upload Cypress binary or NPM Package zip file under unique hash + 'upload-build-artifact' (args = process.argv) { + console.log('#uploadBuildArtifact') - return uploadNpmPackage(args) - }, - - // upload Cypress binary zip file under unique hash - 'upload-unique-binary' (args = process.argv) { - console.log('#uniqueBinaryUpload') - - return uploadUniqueBinary(args) + return uploadArtifactToS3(args) }, // uploads a single built Cypress binary ZIP file @@ -288,10 +280,21 @@ const deploy = { console.log('for platform %s version %s', options.platform, options.version) - return upload.toS3({ - zipFile: options.zip, + const uploadPath = upload.getFullUploadPath({ version: options.version, platform: options.platform, + name: upload.zipName, + }) + + return upload.toS3({ + file: options.zip, + uploadPath, + }).then(() => { + return uploadUtils.purgeDesktopAppFromCache({ + version: options.version, + platform: options.platform, + zipName: options.zip, + }) }) }) }, diff --git a/scripts/binary/move-binaries.ts b/scripts/binary/move-binaries.ts index fafbf82542..c458e0b1cc 100644 --- a/scripts/binary/move-binaries.ts +++ b/scripts/binary/move-binaries.ts @@ -18,9 +18,9 @@ import confirm from 'inquirer-confirm' import uploadUtils from './util/upload' // @ts-ignore -import { getUploadDirForPlatform } from './upload-unique-binary' +import { getUploadDirForPlatform } from './upload-build-artifact' // @ts-ignore -import { zipName, getFullUploadName } from './upload' +import { zipName, getFullUploadPath } from './upload' /** * 40 character full sha commit string @@ -160,7 +160,9 @@ export const moveBinaries = async (args = []) => { const uploadDir = getUploadDirForPlatform({ version: releaseOptions.version, - }, platformArch) + uploadFolder: 'binary', + platformArch, + }) console.log('finding binary for %s in %s', platformArch, uploadDir) @@ -216,7 +218,7 @@ export const moveBinaries = async (args = []) => { platformArch: lastBuild.platformArch, name: zipName, } - const destinationPath = getFullUploadName(options) + const destinationPath = getFullUploadPath(options) console.log('copying test runner %s to %s', lastBuild.platformArch, destinationPath) diff --git a/scripts/binary/upload-build-artifact.js b/scripts/binary/upload-build-artifact.js new file mode 100644 index 0000000000..8cecb52815 --- /dev/null +++ b/scripts/binary/upload-build-artifact.js @@ -0,0 +1,145 @@ +const minimist = require('minimist') +const la = require('lazy-ass') +const check = require('check-more-types') +const fs = require('fs') +const hasha = require('hasha') +const _ = require('lodash') + +const upload = require('./upload') +const uploadUtils = require('./util/upload') +const { s3helpers } = require('./s3-api') + +const uploadTypes = { + binary: { + uploadFolder: 'binary', + uploadFileName: 'cypress.zip', + }, + 'npm-package': { + uploadFolder: 'npm', + uploadFileName: 'cypress.tgz', + }, +} + +const getCDN = function (uploadPath) { + return [uploadUtils.getUploadUrl(), uploadPath].join('/') +} + +const getUploadDirForPlatform = function (options) { + const { version, uploadFolder, platformArch } = options + + return ['beta', uploadFolder, version, platformArch].join('/') +} +// the artifact will be uploaded for every platform and uploaded into under a unique folder +// https://cdn.cypress.io/beta/(binary|npm)////cypress.zip +// For binary: +// beta/binary/9.4.2/win32-x64/circle-develop-219138ca4e952edc4af831f2ae16ce659ebdb50b/cypress.zip +// For NPM package: +// beta/npm/9.4.2/circle-develop-219138ca4e952edc4af831f2ae16ce659ebdb50b/cypress.tgz +const getUploadPath = function (options) { + const { hash, uploadFileName } = options + + return [getUploadDirForPlatform(options), hash, uploadFileName].join('/') +} + +const setChecksum = (filename, key) => { + console.log('setting checksum for file %s', filename) + console.log('on s3 object %s', key) + + la(check.unemptyString(filename), 'expected filename', filename) + la(check.unemptyString(key), 'expected uploaded S3 key', key) + + const checksum = hasha.fromFileSync(filename, { algorithm: 'sha512' }) + const { + size, + } = fs.statSync(filename) + + console.log('SHA256 checksum %s', checksum) + console.log('size', size) + + const aws = uploadUtils.getS3Credentials() + const s3 = s3helpers.makeS3(aws) + // S3 object metadata can only have string values + const metadata = { + checksum, + size: String(size), + } + + // by default s3.copyObject does not preserve ACL when copying + // thus we need to reset it for our public files + return s3helpers.setUserMetadata(aws.bucket, key, metadata, + 'application/zip', 'public-read', s3) +} + +const validateOptions = (options) => { + const { type, version, platform } = options + const supportedUploadTypes = Object.keys(uploadTypes) + + la(check.defined(type) && supportedUploadTypes.includes(type), + `specify which upload type you\'d like to upload. One of ${supportedUploadTypes.join(',')}`, type) + + const { uploadFolder, uploadFileName } = uploadTypes[type] + + options.uploadFolder = uploadFolder + options.uploadFileName = uploadFileName + + la(check.unemptyString(version) && check.semver(version), 'invalid version', version) + + if (!options.hash) { + options.hash = uploadUtils.formHashFromEnvironment() + } + + la(check.unemptyString(options.hash), 'missing hash to give', options) + + options.platformArch = uploadUtils.getUploadNameByOsAndArch(platform || process.platform) + + return options +} + +const uploadArtifactToS3 = function (args = []) { + const supportedOptions = ['type', 'version', 'file', 'hash', 'platform'] + let options = minimist(args, { + string: supportedOptions, + }) + + console.log('Upload options') + console.log(_.pick(options, supportedOptions)) + + validateOptions(options) + + const uploadPath = getUploadPath(options) + + return upload.toS3({ file: options.file, uploadPath }) + .then(() => { + return setChecksum(options.file, uploadPath) + }) + .then(() => { + const cdnUrl = getCDN(uploadPath) + + if (options.type === 'binary') { + console.log('Binary can be downloaded using URL') + console.log(cdnUrl) + } else { + console.log('NPM package can be installed using URL') + console.log('npm install %s', cdnUrl) + } + + return cdnUrl + }) + .then(uploadUtils.saveUrl(`${options.type}-url.json`)) + .catch((e) => { + console.error('There was an issue uploading the artifact.') + console.error(e) + }) +} + +module.exports = { + getCDN, + getUploadDirForPlatform, + getUploadPath, + setChecksum, + uploadArtifactToS3, +} + +if (!module.parent) { + uploadArtifactToS3(process.argv) +} diff --git a/scripts/binary/upload-npm-package.js b/scripts/binary/upload-npm-package.js deleted file mode 100644 index 141661fee4..0000000000 --- a/scripts/binary/upload-npm-package.js +++ /dev/null @@ -1,122 +0,0 @@ -const minimist = require('minimist') -const Promise = require('bluebird') -const la = require('lazy-ass') -const check = require('check-more-types') -const fs = require('fs') -const path = require('path') -const awspublish = require('gulp-awspublish') -const rename = require('gulp-rename') -const gulpDebug = require('gulp-debug') -const gulp = require('gulp') -const uploadUtils = require('./util/upload') - -const npmPackageExtension = '.tgz' -const uploadFileName = 'cypress.tgz' - -const isNpmPackageFile = check.extension(npmPackageExtension) - -// the package tgz file will be uploaded into unique folder -// in our case something like this -// https://cdn.cypress.io/beta/npm///cypress.tgz -const rootFolder = 'beta' -const npmFolder = 'npm' - -const getCDN = function ({ version, hash, filename }) { - la(check.semver(version), 'invalid version', version) - la(check.unemptyString(hash), 'missing hash', hash) - la(check.unemptyString(filename), 'missing filename', filename) - la(isNpmPackageFile(filename), 'wrong extension for file', filename) - const url = uploadUtils.getUploadUrl() - - la(check.url(url), 'could not get upload url', url) - - return [url, rootFolder, npmFolder, version, hash, filename].join('/') -} - -const getUploadDirName = function (options) { - la(check.unemptyString(options.version), 'missing version', options) - la(check.unemptyString(options.hash), 'missing hash', options) - const dir = [rootFolder, npmFolder, options.version, options.hash, null].join('/') - - return dir -} - -const uploadFile = (options) => { - return new Promise((resolve, reject) => { - const publisher = uploadUtils.getPublisher() - - const headers = {} - - headers['Cache-Control'] = 'no-cache' - - return gulp.src(options.file) - .pipe(rename((p) => { - p.basename = path.basename(uploadFileName, npmPackageExtension) - p.dirname = getUploadDirName(options) - console.log('renaming upload to', p.dirname, p.basename) - la(check.unemptyString(p.basename), 'missing basename') - la(check.unemptyString(p.dirname), 'missing dirname') - - return p - })).pipe(gulpDebug()) - .pipe(publisher.publish(headers)) - .pipe(awspublish.reporter()) - .on('error', reject) - .on('end', resolve) - }) -} - -const uploadNpmPackage = function (args = []) { - console.log(args) - const options = minimist(args, { - string: ['version', 'file', 'hash'], - alias: { - version: 'v', - file: 'f', - hash: 'h', - }, - }) - - console.log('Upload NPM package options') - console.log(options) - - la(check.unemptyString(options.file), 'missing file to upload', options) - la(isNpmPackageFile(options.file), - 'invalid file to upload extension', options.file) - - if (!options.hash) { - options.hash = uploadUtils.formHashFromEnvironment() - } - - la(check.unemptyString(options.hash), 'missing hash to give', options) - la(check.unemptyString(options.version), 'missing version', options) - - la(fs.existsSync(options.file), 'cannot find file', options.file) - - return uploadFile(options) - .then(() => { - const cdnUrl = getCDN({ - version: options.version, - hash: options.hash, - filename: uploadFileName, - }) - - console.log('NPM package can be installed using URL') - console.log('npm install %s', cdnUrl) - - return cdnUrl - }).then(uploadUtils.saveUrl('npm-package-url.json')) -} - -// for now disable purging from CDN cache -// because each upload should be unique by hash -// .then R.tap(uploadUtils.purgeCache) - -module.exports = { - uploadNpmPackage, - getCDN, -} - -if (!module.parent) { - uploadNpmPackage(process.argv) -} diff --git a/scripts/binary/upload-unique-binary.js b/scripts/binary/upload-unique-binary.js deleted file mode 100644 index f2e381f2a3..0000000000 --- a/scripts/binary/upload-unique-binary.js +++ /dev/null @@ -1,197 +0,0 @@ -const minimist = require('minimist') -const Promise = require('bluebird') -const la = require('lazy-ass') -const check = require('check-more-types') -const fs = require('fs') -const path = require('path') -const awspublish = require('gulp-awspublish') -const rename = require('gulp-rename') -const gulpDebug = require('gulp-debug') -const gulp = require('gulp') -const hasha = require('hasha') -const _ = require('lodash') - -const uploadUtils = require('./util/upload') -const { - s3helpers, -} = require('./s3-api') - -// we zip the binary on every platform and upload under same name -const binaryExtension = '.zip' -const uploadFileName = 'cypress.zip' - -const isBinaryFile = check.extension(binaryExtension) - -const rootFolder = 'beta' -const folder = 'binary' - -// the binary will be uploaded into unique folder -// in our case something like this -// https://cdn.cypress.io/desktop/binary/0.20.2///cypress.zip -const getCDN = function ({ version, hash, filename, platform }) { - la(check.semver(version), 'invalid version', version) - la(check.unemptyString(hash), 'missing hash', hash) - la(check.unemptyString(filename), 'missing filename', filename) - la(isBinaryFile(filename), 'wrong extension for file', filename) - la(check.unemptyString(platform), 'missing platform', platform) - - const cdnUrl = uploadUtils.getUploadUrl() - - la(check.url(cdnUrl), 'could not get cdn url', cdnUrl) - - return [cdnUrl, rootFolder, folder, version, platform, hash, filename].join('/') -} - -// returns folder that contains beta (unreleased) binaries for given version -// -const getUploadVersionDirName = function (options) { - la(check.unemptyString(options.version), 'missing version', options) - - const dir = [rootFolder, folder, options.version].join('/') - - return dir -} - -const getUploadDirForPlatform = function (options, platformArch) { - la(uploadUtils.isValidPlatformArch(platformArch), - 'missing or invalid platformArch', platformArch) - - const versionDir = getUploadVersionDirName(options) - - la(check.unemptyString(versionDir), 'could not form folder from', options) - - const dir = [versionDir, platformArch].join('/') - - return dir -} - -const getUploadDirName = function (options) { - la(check.unemptyString(options.hash), 'missing hash', options) - - const uploadFolder = getUploadDirForPlatform(options, options.platformArch) - - la(check.unemptyString(uploadFolder), 'could not form folder from', options) - - const dir = [uploadFolder, options.hash, null].join('/') - - return dir -} - -const uploadFile = (options) => { - return new Promise((resolve, reject) => { - const publisher = uploadUtils.getPublisher() - - const headers = {} - - headers['Cache-Control'] = 'no-cache' - - let key = null - - return gulp.src(options.file) - .pipe(rename((p) => { - p.basename = path.basename(uploadFileName, binaryExtension) - p.dirname = getUploadDirName(options) - console.log('renaming upload to', p.dirname, p.basename) - la(check.unemptyString(p.basename), 'missing basename') - la(check.unemptyString(p.dirname), 'missing dirname') - key = p.dirname + uploadFileName - - return p - })).pipe(gulpDebug()) - .pipe(publisher.publish(headers)) - .pipe(awspublish.reporter()) - .on('error', reject) - .on('end', () => { - return resolve(key) - }) - }) -} - -const setChecksum = (filename, key) => { - console.log('setting checksum for file %s', filename) - console.log('on s3 object %s', key) - - la(check.unemptyString(filename), 'expected filename', filename) - la(check.unemptyString(key), 'expected uploaded S3 key', key) - - const checksum = hasha.fromFileSync(filename, { algorithm: 'sha512' }) - const { - size, - } = fs.statSync(filename) - - console.log('SHA256 checksum %s', checksum) - console.log('size', size) - - const aws = uploadUtils.getS3Credentials() - const s3 = s3helpers.makeS3(aws) - // S3 object metadata can only have string values - const metadata = { - checksum, - size: String(size), - } - - // by default s3.copyObject does not preserve ACL when copying - // thus we need to reset it for our public files - return s3helpers.setUserMetadata(aws.bucket, key, metadata, - 'application/zip', 'public-read', s3) -} - -const uploadUniqueBinary = function (args = []) { - const options = minimist(args, { - string: ['version', 'file', 'hash', 'platform'], - alias: { - version: 'v', - file: 'f', - hash: 'h', - }, - }) - - console.log('Upload unique binary options') - - console.log(_.pick(options, ['file', 'version', 'hash'])) - - la(check.unemptyString(options.file), 'missing file to upload', options) - la(isBinaryFile(options.file), - 'invalid file to upload extension', options.file) - - if (!options.hash) { - options.hash = uploadUtils.formHashFromEnvironment() - } - - la(check.unemptyString(options.hash), 'missing hash to give', options) - la(check.unemptyString(options.version), 'missing version', options) - - la(fs.existsSync(options.file), 'cannot find file', options.file) - - const platform = options.platform != null ? options.platform : process.platform - - options.platformArch = uploadUtils.getUploadNameByOsAndArch(platform) - - return uploadFile(options) - .then((key) => { - return setChecksum(options.file, key) - }).then(() => { - const cdnUrl = getCDN({ - version: options.version, - hash: options.hash, - filename: uploadFileName, - platform: options.platformArch, - }) - - console.log('Binary can be downloaded using URL') - console.log(cdnUrl) - - return cdnUrl - }).then(uploadUtils.saveUrl('binary-url.json')) -} - -module.exports = { - getUploadDirName, - getUploadDirForPlatform, - uploadUniqueBinary, - getCDN, -} - -if (!module.parent) { - uploadUniqueBinary(process.argv) -} diff --git a/scripts/binary/upload.js b/scripts/binary/upload.js index 247e0a7854..0db0d12e30 100644 --- a/scripts/binary/upload.js +++ b/scripts/binary/upload.js @@ -5,9 +5,9 @@ let fs = require('fs-extra') const path = require('path') const gulp = require('gulp') const Promise = require('bluebird') -const meta = require('./meta') const la = require('lazy-ass') const check = require('check-more-types') + const uploadUtils = require('./util/upload') fs = Promise.promisifyAll(fs) @@ -30,17 +30,25 @@ module.exports = { // returns desktop folder for a given folder without platform // something like desktop/0.20.1 - getUploadeVersionFolder (aws, version) { + getUploadVersionFolder (aws, version) { la(check.unemptyString(aws.folder), 'aws object is missing desktop folder', aws.folder) const dirName = [aws.folder, version].join('/') return dirName }, - getFullUploadName ({ folder, version, platformArch, name }) { - la(check.unemptyString(folder), 'missing folder', folder) - la(check.semver(version), 'missing or invalid version', version) - la(check.unemptyString(name), 'missing file name', name) + // store uploaded application in subfolders by version and platform + // something like desktop/0.20.1/darwin-x64/ + getFullUploadPath (options) { + let { folder, version, platformArch, name } = options + + if (!folder) { + folder = this.getAwsObj().folder + } + + la(check.unemptyString(folder), 'missing folder', options) + la(check.semver(version), 'missing or invalid version', options) + la(check.unemptyString(name), 'missing file name', options) la(uploadUtils.isValidPlatformArch(platformArch), 'invalid platform and arch', platformArch) @@ -49,20 +57,6 @@ module.exports = { return fileName }, - // store uploaded application in subfolders by platform and version - // something like desktop/0.20.1/darwin-x64/ - getUploadDirName ({ version, platform }) { - const aws = this.getAwsObj() - const platformArch = uploadUtils.getUploadNameByOsAndArch(platform) - - const versionFolder = this.getUploadeVersionFolder(aws, version) - const dirName = [versionFolder, platformArch, null].join('/') - - console.log('target directory %s', dirName) - - return dirName - }, - getManifestUrl (folder, version, uploadOsName) { const url = uploadUtils.getUploadUrl() @@ -141,48 +135,35 @@ module.exports = { }) }, - toS3 ({ zipFile, version, platform }) { + toS3 ({ file, uploadPath }) { console.log('#uploadToS3 ⏳') + console.log('uploading', file, 'to', uploadPath) - la(check.unemptyString(version), 'expected version string', version) - la(check.unemptyString(zipFile), 'expected zip filename', zipFile) - la(check.extension('zip', zipFile), - 'zip filename should end with .zip', zipFile) + la(check.unemptyString(file), 'missing file to upload', file) + la(fs.existsSync(file), 'cannot find file', file) + la(check.extension(path.extname(uploadPath))(file), + 'invalid file to upload extension', file) - la(meta.isValidPlatform(platform), 'invalid platform', platform) + return new Promise((resolve, reject) => { + const publisher = this.getPublisher() - console.log(`zip filename ${zipFile}`) + const headers = {} - if (!fs.existsSync(zipFile)) { - throw new Error(`Cannot find zip file ${zipFile}`) - } + headers['Cache-Control'] = 'no-cache' - const upload = () => { - return new Promise((resolve, reject) => { - const publisher = this.getPublisher() + return gulp.src(file) + .pipe(rename((p) => { + // rename to standard filename for upload + p.basename = path.basename(uploadPath, path.extname(uploadPath)) + p.dirname = path.dirname(uploadPath) - const headers = {} - - headers['Cache-Control'] = 'no-cache' - - return gulp.src(zipFile) - .pipe(rename((p) => { - // rename to standard filename zipName - p.basename = path.basename(zipName, p.extname) - p.dirname = this.getUploadDirName({ version, platform }) - - return p - })).pipe(gulpDebug()) - .pipe(publisher.publish(headers)) - .pipe(awspublish.reporter()) - .on('error', reject) - .on('end', resolve) - }) - } - - return upload() - .then(() => { - return uploadUtils.purgeDesktopAppFromCache({ version, platform, zipName }) + return p + })) + .pipe(gulpDebug()) + .pipe(publisher.publish(headers)) + .pipe(awspublish.reporter()) + .on('error', reject) + .on('end', resolve) }) }, } diff --git a/scripts/binary/util/upload.js b/scripts/binary/util/upload.js index 2d0d24d675..597f894698 100644 --- a/scripts/binary/util/upload.js +++ b/scripts/binary/util/upload.js @@ -15,7 +15,6 @@ const getUploadUrl = function () { const url = konfig('cdn_url') la(check.url(url), 'could not get CDN url', url) - console.log('upload url', url) return url } diff --git a/scripts/unit/binary/upload-build-artifact-spec.js b/scripts/unit/binary/upload-build-artifact-spec.js new file mode 100644 index 0000000000..c09f9a9a4d --- /dev/null +++ b/scripts/unit/binary/upload-build-artifact-spec.js @@ -0,0 +1,140 @@ +const { sinon } = require('@packages/https-proxy/test/spec_helper') +const { expect } = require('chai') +const hasha = require('hasha') +const fs = require('fs') + +const { + getCDN, + getUploadDirForPlatform, + getUploadPath, + uploadArtifactToS3, +} = require('../../binary/upload-build-artifact') +const upload = require('../../binary/upload') +const uploadUtils = require('../../binary/util/upload') +const { s3helpers } = require('../../binary/s3-api') + +/* eslint-env mocha */ +describe('upload-release-artifact', () => { + describe('.getCDN', () => { + it('returns CDN s3 url', () => { + const uploadUrl = 'dir/path/file' + const result = getCDN(uploadUrl) + + expect(result).to.eq('https://cdn.cypress.io/dir/path/file') + }) + }) + + describe('.getUploadDirForPlatform', () => { + it('returns folder for given version and platform', () => { + const options = { + uploadFolder: 'binary', + platformArch: 'darwin-x64', + version: '3.3.0', + } + const result = getUploadDirForPlatform(options) + + expect(result).to.eq('beta/binary/3.3.0/darwin-x64') + }) + }) + + describe('.getUploadPath', () => { + it('returns s3 upload path', () => { + const options = { + uploadFolder: 'binary', + platformArch: 'darwin-x64', + version: '3.3.0', + hash: 'hash', + uploadFileName: 'file', + } + const result = getUploadPath(options) + + expect(result).to.eq('beta/binary/3.3.0/darwin-x64/hash/file') + }) + }) + + describe('.uploadArtifactToS3', () => { + let sandbox + + beforeEach(function () { + sandbox = sinon.sandbox.create() + sandbox.stub(hasha, 'fromFileSync').returns('checksum') + sandbox.stub(fs, 'statSync').returns('size') + sandbox.stub(s3helpers, 'makeS3').returns('size') + sandbox.stub(s3helpers, 'setUserMetadata') + sandbox.stub(upload, 'toS3') + sandbox.stub(uploadUtils, 'formHashFromEnvironment') + sandbox.stub(uploadUtils, 'getS3Credentials').returns({ bucket: 'beta' }) + sandbox.stub(uploadUtils, 'getUploadNameByOsAndArch') + sandbox.stub(uploadUtils, 'saveUrl') + }) + + afterEach(function () { + sandbox.restore() + }) + + it('throws error if type argument is missing', () => { + expect(() => uploadArtifactToS3()).to.throw(/specify which upload type you'd like to upload/) + }) + + it('throws error if type argument is not binary or npm-package', () => { + expect(() => uploadArtifactToS3(['--type', 'npm'])).to.throw(/specify which upload type you'd like to upload/) + }) + + it('throws error if version argument is missing', () => { + expect(() => uploadArtifactToS3(['--type', 'binary'])).to.throw(/invalid version/) + }) + + it('throws error if version argument is not a semver', () => { + expect(() => uploadArtifactToS3(['--type', 'npm-package', '--version', '.1'])).to.throw(/invalid version/) + }) + + it('throws error if not ran in CircleCI to generate unique hash', () => { + uploadUtils.formHashFromEnvironment.throws() + expect(() => uploadArtifactToS3(['--type', 'npm-package', '--version', '1.0.0'])).to.throw() + }) + + it('uploads binary to s3 and saves url to json', () => { + uploadUtils.formHashFromEnvironment.returns('hash') + uploadUtils.getUploadNameByOsAndArch.returns('darwin-x64') + upload.toS3.resolves(true) + + const args = ['--file', 'my.zip', '--type', 'binary', '--version', '1.0.0'] + + uploadArtifactToS3(args) + + expect(uploadUtils.formHashFromEnvironment).to.have.calledOnce + expect(uploadUtils.getUploadNameByOsAndArch).to.have.calledOnce + + expect(upload.toS3).to.have.been.calledOnce + expect(upload.toS3.lastCall.args).to.have.lengthOf(1) + expect(upload.toS3.lastCall.args[0]).to.have.property('file', 'my.zip') + expect(upload.toS3.lastCall.args[0]).to.have.property('uploadPath', 'beta/binary/1.0.0/darwin-x64/hash/cypress.zip') + + expect(uploadUtils.saveUrl).to.have.calledOnce + expect(uploadUtils.saveUrl.lastCall.args).to.have.lengthOf(1) + expect(uploadUtils.saveUrl.lastCall.args[0]).to.eq('binary-url.json') + }) + + it('uploads npm-package to s3 and saves url to json', () => { + uploadUtils.formHashFromEnvironment.returns('hash') + uploadUtils.getUploadNameByOsAndArch.returns('darwin-x64') + upload.toS3.resolves(true) + + const args = ['--file', 'my.zip', '--type', 'npm-package', '--version', '1.0.0'] + + uploadArtifactToS3(args) + + expect(uploadUtils.formHashFromEnvironment).to.have.calledOnce + expect(uploadUtils.getUploadNameByOsAndArch).to.have.calledOnce + + expect(upload.toS3).to.have.been.calledOnce + expect(upload.toS3.lastCall.args).to.have.lengthOf(1) + expect(upload.toS3.lastCall.args[0]).to.have.property('file', 'my.zip') + expect(upload.toS3.lastCall.args[0]).to.have.property('uploadPath', 'beta/npm/1.0.0/darwin-x64/hash/cypress.tgz') + + expect(uploadUtils.saveUrl).to.have.calledOnce + expect(uploadUtils.saveUrl.lastCall.args).to.have.lengthOf(1) + expect(uploadUtils.saveUrl.lastCall.args[0]).to.eq('npm-package-url.json') + }) + }) +}) diff --git a/scripts/unit/binary/upload-npm-package-spec.js b/scripts/unit/binary/upload-npm-package-spec.js deleted file mode 100644 index b8035aed1e..0000000000 --- a/scripts/unit/binary/upload-npm-package-spec.js +++ /dev/null @@ -1,23 +0,0 @@ -const snapshot = require('snap-shot-it') - -/* eslint-env mocha */ -describe('getCDN', () => { - context('npm package', () => { - const { getCDN } = require('../../binary/upload-npm-package') - - it('returns CDN s3 path', () => { - const options = { - platform: 'darwin-x64', - filename: 'cypress.tgz', - version: '3.3.0', - // ci name + commit sha + build number - hash: 'ci-name-e154a40f3f76abd39a1d85c0ebc0ff9565015706-123', - } - - snapshot({ - input: options, - result: getCDN(options), - }) - }) - }) -}) diff --git a/scripts/unit/binary/upload-spec.js b/scripts/unit/binary/upload-spec.js index 3309da47a2..9d6e92c25b 100644 --- a/scripts/unit/binary/upload-spec.js +++ b/scripts/unit/binary/upload-spec.js @@ -2,10 +2,8 @@ require('../../spec-helper') const snapshot = require('snap-shot-it') const la = require('lazy-ass') -const os = require('os') /* eslint-env mocha */ -/* global sinon */ describe('upload', () => { const upload = require('../../binary/upload') @@ -19,36 +17,14 @@ describe('upload', () => { }) }) - context('getUploadeVersionFolder', () => { + context('getUploadVersionFolder', () => { it('returns folder', () => { const aws = { folder: 'desktop', } - const folder = upload.getUploadeVersionFolder(aws, '3.3.0') + const folder = upload.getUploadVersionFolder(aws, '3.3.0') la(folder === 'desktop/3.3.0', 'wrong desktop folder', folder) }) }) - - context('getUploadDirName', () => { - it('returns folder with platform', () => { - const aws = { - folder: 'desktop', - } - - sinon.stub(upload, 'getAwsObj').returns(aws) - sinon.stub(os, 'arch').returns('x64') - - const folder = upload.getUploadDirName({ - platform: 'darwin', - version: '3.3.0', - }) - - la( - folder === 'desktop/3.3.0/darwin-x64/', - 'wrong upload desktop folder', - folder, - ) - }) - }) }) diff --git a/scripts/unit/binary/upload-unique-binary-spec.js b/scripts/unit/binary/upload-unique-binary-spec.js deleted file mode 100644 index 3f401593ed..0000000000 --- a/scripts/unit/binary/upload-unique-binary-spec.js +++ /dev/null @@ -1,62 +0,0 @@ -const snapshot = require('snap-shot-it') - -/* eslint-env mocha */ -describe('upload-unique-binary', () => { - describe('getUploadDirName', () => { - const { getUploadDirName } = require('../../binary/upload-unique-binary') - - it('returns folder for given version', () => { - const options = { - platformArch: 'darwin-x64', - version: '3.3.0', - // ci name + commit sha + build number - hash: 'ci-name-e154a40f3f76abd39a1d85c0ebc0ff9565015706-123', - } - - snapshot('upload binary folder', { - input: options, - result: getUploadDirName(options), - }) - }) - }) - - describe('getUploadDirForPlatform', () => { - const { - getUploadDirForPlatform, - } = require('../../binary/upload-unique-binary') - - it('returns folder for given version and platform', () => { - const options = { - platformArch: 'darwin-x64', - version: '3.3.0', - } - const result = getUploadDirForPlatform(options, options.platformArch) - - snapshot('upload binary folder for platform', { - input: options, - result, - }) - }) - }) - - describe('getCDN', () => { - context('binary', () => { - const { getCDN } = require('../../binary/upload-unique-binary') - - it('returns CDN s3 path', () => { - const options = { - platform: 'darwin-x64', - filename: 'cypress.zip', - version: '3.3.0', - // ci name + commit sha + build number - hash: 'ci-name-e154a40f3f76abd39a1d85c0ebc0ff9565015706-123', - } - - snapshot('getCDN for binary', { - input: options, - result: getCDN(options), - }) - }) - }) - }) -}) From 589f93b11f1c5dc74cd4ffed06c847887756fe82 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Mon, 7 Feb 2022 14:27:27 -0500 Subject: [PATCH 08/12] test: fix network_error_handling_spec for external contributors (#20075) --- packages/server/.gitignore | 5 -- packages/server/package.json | 1 - system-tests/package.json | 1 - .../test/network_error_handling_spec.js | 52 ++++++++----------- yarn.lock | 24 ++------- 5 files changed, 25 insertions(+), 58 deletions(-) delete mode 100644 packages/server/.gitignore diff --git a/packages/server/.gitignore b/packages/server/.gitignore deleted file mode 100644 index 5e1717fe3e..0000000000 --- a/packages/server/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# we do not explicitly ignore JavaScript files in "lib/browsers" folder -# because when we add TS files we do not transpile them as a build step -# instead always use require hooks to transpile TS files on the fly - -.http-mitm-proxy diff --git a/packages/server/package.json b/packages/server/package.json index 45b1dc1fdd..d7c95a7e27 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -163,7 +163,6 @@ "eventsource": "1.0.7", "express-session": "1.16.1", "express-useragent": "1.0.15", - "http-mitm-proxy": "0.7.0", "https-proxy-agent": "3.0.1", "istanbul": "0.4.5", "mocha": "7.1.0", diff --git a/system-tests/package.json b/system-tests/package.json index 0975359f95..08a7e4b0ae 100644 --- a/system-tests/package.json +++ b/system-tests/package.json @@ -50,7 +50,6 @@ "fluent-ffmpeg": "2.1.2", "fs-extra": "8.1.0", "glob": "7.2.0", - "http-mitm-proxy": "0.7.0", "https-proxy-agent": "3.0.1", "human-interval": "1.0.0", "image-size": "0.8.3", diff --git a/system-tests/test/network_error_handling_spec.js b/system-tests/test/network_error_handling_spec.js index 3a4b714c35..4ba0fd7b20 100644 --- a/system-tests/test/network_error_handling_spec.js +++ b/system-tests/test/network_error_handling_spec.js @@ -1,7 +1,5 @@ const _ = require('lodash') const express = require('express') -const http = require('http') -const https = require('https') const path = require('path') const debug = require('debug')('cypress:server:network-error-handling-spec') const Promise = require('bluebird') @@ -12,7 +10,6 @@ const chrome = require('@packages/server/lib/browsers/chrome') const systemTests = require('../lib/system-tests').default const random = require('@packages/server/lib/util/random') const Fixtures = require('../lib/fixtures') -let mitmProxy = require('http-mitm-proxy') const PORT = 13370 const PROXY_PORT = 13371 @@ -348,19 +345,19 @@ describe('e2e network error handling', function () { }) context('Cypress', () => { + let debugProxy + beforeEach(() => { delete process.env.HTTP_PROXY delete process.env.HTTPS_PROXY - return delete process.env.NO_PROXY + delete process.env.NO_PROXY }) - afterEach(function () { - if (this.debugProxy) { - return this.debugProxy.stop() - .then(() => { - this.debugProxy = null - }) + afterEach(async function () { + if (debugProxy) { + await debugProxy.stop() + debugProxy = null } }) @@ -415,11 +412,11 @@ describe('e2e network error handling', function () { return true }) - this.debugProxy = new DebugProxy({ + debugProxy = new DebugProxy({ onConnect, }) - return this.debugProxy + return debugProxy .start(PROXY_PORT) .then(() => { process.env.HTTP_PROXY = `http://localhost:${PROXY_PORT}` @@ -465,9 +462,9 @@ describe('e2e network error handling', function () { }) it('behind a proxy', function () { - this.debugProxy = new DebugProxy() + debugProxy = new DebugProxy() - return this.debugProxy + return debugProxy .start(PROXY_PORT) .then(() => { process.env.HTTP_PROXY = `http://localhost:${PROXY_PORT}` @@ -485,27 +482,22 @@ describe('e2e network error handling', function () { }) }) - it('behind a proxy with transfer-encoding: chunked', function () { - mitmProxy = mitmProxy() - - mitmProxy.onRequest((ctx, callback) => { - return callback() - }) - - mitmProxy.listen({ - host: '127.0.0.1', - port: PROXY_PORT, - keepAlive: true, - httpAgent: http.globalAgent, - httpsAgent: https.globalAgent, - forceSNI: false, - forceChunkedRequest: true, + it('behind a proxy with transfer-encoding: chunked', async function () { + debugProxy = new DebugProxy({ + onRequest: (reqUrl, req, res) => { + expect(req.headers).to.have.property('content-length') + // delete content-length to force te: chunked + delete req.headers['content-length'] + debugProxy._onRequest(reqUrl, req, res) + }, }) process.env.HTTP_PROXY = `http://localhost:${PROXY_PORT}` process.env.NO_PROXY = '' - return systemTests.exec(this, { + await debugProxy.start(PROXY_PORT) + + await systemTests.exec(this, { spec: 'network_error_304_handling_spec.js', video: false, config: { diff --git a/yarn.lock b/yarn.lock index 659247980d..b224be6bb2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10983,7 +10983,7 @@ async@>=0.2.9, async@^3.2.0: resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== -async@^2.1.4, async@^2.4.1, async@^2.5.0, async@^2.6.2: +async@^2.1.4, async@^2.4.1, async@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== @@ -21991,19 +21991,6 @@ http-errors@~1.6.2: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" -http-mitm-proxy@0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/http-mitm-proxy/-/http-mitm-proxy-0.7.0.tgz#82933137ae1c06713961afe50f38ca84cf80bb0c" - integrity sha512-rRMRfQCVwEO31Q6GFiQHfECdMn3Z0ddWWLNgmeyIUDMf0gr/Ek+lhZ17gWzKL4NXZkMc1h982BYl8blRXv7/og== - dependencies: - async "^2.5.0" - debug "^4.1.0" - mkdirp "^0.5.1" - node-forge "^0.8.0" - optimist "^0.6.1" - semaphore "^1.1.0" - ws "^3.2.0" - http-parser-js@>=0.5.1: version "0.5.3" resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9" @@ -28361,11 +28348,6 @@ node-forge@^0.10.0: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== -node-forge@^0.8.0: - version "0.8.5" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.8.5.tgz#57906f07614dc72762c84cef442f427c0e1b86ee" - integrity sha512-vFMQIWt+J/7FLNyKouZ9TazT74PRV3wgv9UT4cRjC8BffxFbKXkgIWR42URCPSnHm/QDz6BOlb2Q0U4+VQT67Q== - node-gyp@^5.0.2, node-gyp@^5.1.0: version "5.1.1" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-5.1.1.tgz#eb915f7b631c937d282e33aed44cb7a025f62a3e" @@ -35188,7 +35170,7 @@ semantic-release@17.4.2: signale "^1.2.1" yargs "^16.2.0" -semaphore@1.1.0, semaphore@^1.1.0: +semaphore@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/semaphore/-/semaphore-1.1.0.tgz#aaad8b86b20fe8e9b32b16dc2ee682a8cd26a8aa" integrity sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA== @@ -41508,7 +41490,7 @@ write@1.0.3: dependencies: mkdirp "^0.5.1" -ws@3.3.x, ws@^3.2.0: +ws@3.3.x: version "3.3.3" resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA== From 52ed6edcf3930ff89a35f1fb28ad23601470a411 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Tue, 8 Feb 2022 18:38:29 +0200 Subject: [PATCH 09/12] chore(deps): Security upgrade shelljs to 0.8.5 (#19685) Co-authored-by: Emily Rohrbough Co-authored-by: Emily Rohrbough --- cli/package.json | 2 +- npm/eslint-plugin-dev/package.json | 2 +- package.json | 2 +- packages/example/package.json | 2 +- packages/launcher/package.json | 2 +- yarn.lock | 10 +++++----- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cli/package.json b/cli/package.json index 2a9089f6c5..68c2cc06eb 100644 --- a/cli/package.json +++ b/cli/package.json @@ -93,7 +93,7 @@ "postinstall-postinstall": "2.1.0", "proxyquire": "2.1.3", "resolve-pkg": "2.0.0", - "shelljs": "0.8.4", + "shelljs": "0.8.5", "sinon": "7.2.2", "snap-shot-it": "7.9.6", "spawn-mock": "1.0.0", diff --git a/npm/eslint-plugin-dev/package.json b/npm/eslint-plugin-dev/package.json index 2062cad777..35344ec2e1 100644 --- a/npm/eslint-plugin-dev/package.json +++ b/npm/eslint-plugin-dev/package.json @@ -14,7 +14,7 @@ "chalk": "^2.4.2", "eslint-rule-composer": "^0.3.0", "lodash": "^4.17.15", - "shelljs": "^0.8.3" + "shelljs": "0.8.5" }, "devDependencies": { "eslint": "^7.22.0", diff --git a/package.json b/package.json index 948f809ed6..d44e0c3514 100644 --- a/package.json +++ b/package.json @@ -175,7 +175,7 @@ "semantic-release": "17.2.3", "semantic-release-monorepo": "7.0.3", "semver": "7.3.2", - "shelljs": "0.8.3", + "shelljs": "0.8.5", "shx": "0.3.3", "sinon": "7.3.2", "snap-shot-it": "7.9.3", diff --git a/packages/example/package.json b/packages/example/package.json index d9fe37f507..fcc6a1d12a 100644 --- a/packages/example/package.json +++ b/packages/example/package.json @@ -35,6 +35,6 @@ "gulp-rev-all": "2.0.2", "mocha": "2.5.3", "resolve-pkg": "2.0.0", - "shelljs": "0.8.4" + "shelljs": "0.8.5" } } diff --git a/packages/launcher/package.json b/packages/launcher/package.json index ee1c36e45f..4686478286 100644 --- a/packages/launcher/package.json +++ b/packages/launcher/package.json @@ -26,7 +26,7 @@ "chai-as-promised": "7.1.1", "cross-env": "6.0.3", "mocha": "3.5.3", - "shelljs": "0.8.3", + "shelljs": "0.8.5", "sinon": "^10.0.0", "sinon-chai": "3.4.0", "typescript": "^4.2.3" diff --git a/yarn.lock b/yarn.lock index b224be6bb2..d2b0f7d255 100644 --- a/yarn.lock +++ b/yarn.lock @@ -35559,16 +35559,16 @@ shell-quote@1.7.2, shell-quote@^1.4.2, shell-quote@^1.6.1: resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== -shelljs@0.8.3: - version "0.8.3" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097" - integrity sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A== +shelljs@0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" + integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== dependencies: glob "^7.0.0" interpret "^1.0.0" rechoir "^0.6.2" -shelljs@0.8.4, shelljs@^0.8.3, shelljs@^0.8.4: +shelljs@^0.8.4: version "0.8.4" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2" integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ== From 62d109cee2664da0423b665c55b62afdba0c5986 Mon Sep 17 00:00:00 2001 From: Deisling Eduard <41228762+edikdeisling@users.noreply.github.com> Date: Tue, 8 Feb 2022 20:55:48 +0300 Subject: [PATCH 10/12] fix(driver): change request log message (#20009) (#20019) Co-authored-by: Jennifer Shehane Co-authored-by: Zach Bloomquist Co-authored-by: Zach Bloomquist --- .../cypress/integration/commands/request_spec.js | 12 ++++++++++++ packages/driver/src/cy/commands/request.ts | 10 +++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/driver/cypress/integration/commands/request_spec.js b/packages/driver/cypress/integration/commands/request_spec.js index b44ca47346..f02f972490 100644 --- a/packages/driver/cypress/integration/commands/request_spec.js +++ b/packages/driver/cypress/integration/commands/request_spec.js @@ -759,6 +759,18 @@ describe('src/cy/commands/request', () => { }) }) + describe('when request origin equals browsers origin', () => { + it('sends correct message', () => { + Cypress.backend + .withArgs('http:request') + .resolves({ isOkStatusCode: true, status: 201 }) + + cy.request(`${window.location.origin}/foo`).then(function () { + expect(this.lastLog.invoke('renderProps').message).to.equal('GET 201 /foo') + }) + }) + }) + describe('when response is successful', () => { it('sends correct indicator', () => { Cypress.backend diff --git a/packages/driver/src/cy/commands/request.ts b/packages/driver/src/cy/commands/request.ts index fe71b312d9..74eb8caca0 100644 --- a/packages/driver/src/cy/commands/request.ts +++ b/packages/driver/src/cy/commands/request.ts @@ -51,6 +51,14 @@ const whichAreOptional = (val, key) => { return (val === null) && OPTIONAL_OPTS.includes(key) } +const getDisplayUrl = (url: string) => { + if (url.startsWith(window.location.origin)) { + return url.slice(window.location.origin.length) + } + + return url +} + const needsFormSpecified = (options: any = {}) => { const { body, json, headers } = options @@ -273,7 +281,7 @@ export default (Commands, Cypress, cy, state, config) => { } return { - message: `${options.method} ${status} ${options.url}`, + message: `${options.method} ${status} ${getDisplayUrl(options.url)}`, indicator, } }, From bc6d240a5069312341509a45de03775283a0eaa3 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Tue, 8 Feb 2022 16:03:10 -0500 Subject: [PATCH 11/12] chore: set up semantic-pull-request GitHub Action (#20091) Co-authored-by: Emily Rohrbough --- .github/semantic.yml | 2 -- .github/workflows/semantic-pull-request.yml | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) delete mode 100644 .github/semantic.yml create mode 100644 .github/workflows/semantic-pull-request.yml diff --git a/.github/semantic.yml b/.github/semantic.yml deleted file mode 100644 index 4168a3cdee..0000000000 --- a/.github/semantic.yml +++ /dev/null @@ -1,2 +0,0 @@ -# Always validate the PR title, and ignore the commits -titleOnly: true diff --git a/.github/workflows/semantic-pull-request.yml b/.github/workflows/semantic-pull-request.yml new file mode 100644 index 0000000000..469b50ef54 --- /dev/null +++ b/.github/workflows/semantic-pull-request.yml @@ -0,0 +1,21 @@ +name: "Semantic Pull Request" + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +jobs: + main: + name: Lint Title + runs-on: ubuntu-latest + steps: + # use a fork of the GitHub action - we cannot pull in untrusted third party actions + # see https://github.com/cypress-io/cypress/pull/20091#discussion_r801799647 + - uses: cypress-io/action-semantic-pull-request@v4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + validateSingleCommit: true \ No newline at end of file From bf0d4dbcc32fbd9f2f44b27f82318710547db251 Mon Sep 17 00:00:00 2001 From: Kukhyeon Heo Date: Wed, 9 Feb 2022 07:48:30 +0900 Subject: [PATCH 12/12] chore: Remove pkg/driver //@ts-nocheck part 3 (#19837) Co-authored-by: Emily Rohrbough Co-authored-by: Zach Bloomquist --- packages/driver/src/config/jquery.ts | 12 +++- .../driver/src/cy/commands/actions/check.ts | 11 ++-- .../driver/src/cy/commands/actions/trigger.ts | 4 +- .../driver/src/cy/commands/actions/type.ts | 12 ++-- packages/driver/src/cy/commands/connectors.ts | 18 ++--- packages/driver/src/cy/commands/cookies.ts | 19 +++--- packages/driver/src/cy/commands/exec.ts | 5 +- packages/driver/src/cy/commands/files.ts | 7 +- packages/driver/src/cy/commands/fixtures.ts | 4 +- packages/driver/src/cy/commands/location.ts | 8 +-- packages/driver/src/cy/commands/misc.ts | 4 +- packages/driver/src/cy/commands/navigation.ts | 66 +++++++++++-------- packages/driver/src/cypress/browser.ts | 9 ++- packages/driver/src/cypress/chai_jquery.ts | 8 ++- packages/driver/src/cypress/commands.ts | 6 +- packages/driver/src/cypress/cookies.ts | 12 ++-- packages/driver/src/cypress/error_utils.ts | 58 ++++++++-------- packages/driver/src/cypress/location.ts | 5 +- packages/driver/src/cypress/stack_utils.ts | 7 +- packages/driver/src/cypress/utils.ts | 4 +- 20 files changed, 159 insertions(+), 120 deletions(-) diff --git a/packages/driver/src/config/jquery.ts b/packages/driver/src/config/jquery.ts index 0b16e6f1c3..d998e6af70 100644 --- a/packages/driver/src/config/jquery.ts +++ b/packages/driver/src/config/jquery.ts @@ -1,11 +1,17 @@ -// @ts-nocheck - -import $ from 'jquery' +import JQuery from 'jquery' import _ from 'lodash' import { scrollTo } from './jquery.scrollto' import $dom from '../dom' +// Add missing types. +interface ExtendedJQueryStatic extends JQueryStatic { + find: any + expr: JQuery.Selectors & { filters: any } +} + +const $: ExtendedJQueryStatic = JQuery as any + // force jquery to have the same visible // and hidden logic as cypress diff --git a/packages/driver/src/cy/commands/actions/check.ts b/packages/driver/src/cy/commands/actions/check.ts index 41dc231e50..490bbd3559 100644 --- a/packages/driver/src/cy/commands/actions/check.ts +++ b/packages/driver/src/cy/commands/actions/check.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import _ from 'lodash' import Promise from 'bluebird' @@ -7,7 +6,7 @@ import $utils from '../../../cypress/utils' import $errUtils from '../../../cypress/error_utils' import $elements from '../../../dom/elements' -const checkOrUncheck = (Cypress, cy, type, subject, values = [], userOptions = {}) => { +const checkOrUncheck = (Cypress, cy, type, subject, values: any[] = [], userOptions = {}) => { // we're not handling conversion of values to strings // in case we've received numbers @@ -18,15 +17,15 @@ const checkOrUncheck = (Cypress, cy, type, subject, values = [], userOptions = { values = [] } else { // make sure we're an array of values - values = [].concat(values) + values = ([] as any[]).concat(values) } // keep an array of subjects which // are potentially reduced down // to new filtered subjects - const matchingElements = [] + const matchingElements: HTMLElement[] = [] - const options = _.defaults({}, userOptions, { + const options: Record = _.defaults({}, userOptions, { $el: subject, log: true, force: false, @@ -75,7 +74,7 @@ const checkOrUncheck = (Cypress, cy, type, subject, values = [], userOptions = { matchingElements.push(el) } - const consoleProps = { + const consoleProps: Record = { 'Applied To': $dom.getElements($el), 'Elements': $el.length, } diff --git a/packages/driver/src/cy/commands/actions/trigger.ts b/packages/driver/src/cy/commands/actions/trigger.ts index cdef7d59c6..1241cb4e64 100644 --- a/packages/driver/src/cy/commands/actions/trigger.ts +++ b/packages/driver/src/cy/commands/actions/trigger.ts @@ -1,5 +1,3 @@ -// @ts-nocheck - import _ from 'lodash' import Promise from 'bluebird' @@ -59,7 +57,7 @@ export default (Commands, Cypress, cy, state, config) => { ({ options: userOptions, position, x, y } = $actionability.getPositionFromArguments(positionOrX, y, userOptions)) - const options = _.defaults({}, userOptions, { + const options: Record = _.defaults({}, userOptions, { log: true, $el: subject, bubbles: true, diff --git a/packages/driver/src/cy/commands/actions/type.ts b/packages/driver/src/cy/commands/actions/type.ts index 4e3e8a3a03..4ef46395b1 100644 --- a/packages/driver/src/cy/commands/actions/type.ts +++ b/packages/driver/src/cy/commands/actions/type.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import _ from 'lodash' import Promise from 'bluebird' @@ -15,7 +14,11 @@ const debug = debugFn('cypress:driver:command:type') export default function (Commands, Cypress, cy, state, config) { const { keyboard } = cy.devices - function type (subject, chars, options = {}) { + // Note: These "change type of `any` to X" comments are written instead of changing them directly + // because Cypress extends user-given options with Cypress internal options. + // These comments will be removed after removing `// @ts-nocheck` comments in `packages/driver`. + // TODO: change the type of `any` to `Partial` + function type (subject, chars, options: any = {}) { const userOptions = options let updateTable @@ -366,7 +369,7 @@ export default function (Commands, Cypress, cy, state, config) { // Firefox sends a click event automatically. if (!Cypress.isBrowser('firefox')) { const ctor = $dom.getDocumentFromElement(el).defaultView?.PointerEvent - const event = new ctor('click') + const event = new ctor!('click') el.dispatchEvent(event) } @@ -510,7 +513,8 @@ export default function (Commands, Cypress, cy, state, config) { }) } - function clear (subject, options = {}) { + // TODO: change the type of `any` to `Partial` + function clear (subject, options: any = {}) { const userOptions = options options = _.defaults({}, userOptions, { diff --git a/packages/driver/src/cy/commands/connectors.ts b/packages/driver/src/cy/commands/connectors.ts index 730da1c3e4..22dd18dfb5 100644 --- a/packages/driver/src/cy/commands/connectors.ts +++ b/packages/driver/src/cy/commands/connectors.ts @@ -1,13 +1,11 @@ -// @ts-nocheck - import _ from 'lodash' import Promise from 'bluebird' import $dom from '../../dom' import $utils from '../../cypress/utils' -import $errUtils from '../../cypress/error_utils' +import $errUtils, { CypressError } from '../../cypress/error_utils' -const returnFalseIfThenable = (key, ...args) => { +const returnFalseIfThenable = (key, ...args): boolean => { if ((key === 'then') && _.isFunction(args[0]) && _.isFunction(args[1])) { // https://github.com/cypress-io/cypress/issues/111 // if we're inside of a promise then the promise lib will naturally @@ -22,6 +20,8 @@ const returnFalseIfThenable = (key, ...args) => { return false } + + return true } const primitiveToObject = (memo) => { @@ -181,7 +181,7 @@ export default function (Commands, Cypress, cy, state) { const invokeFn = (subject, userOptionsOrStr, ...args) => { const userOptionsPassed = _.isObject(userOptionsOrStr) && !_.isFunction(userOptionsOrStr) - let userOptions = null + let userOptions: Record | null = null let str = null if (!userOptionsPassed) { @@ -219,7 +219,7 @@ export default function (Commands, Cypress, cy, state) { const message = getMessage() - let traversalErr = null + let traversalErr: CypressError | null = null // copy userOptions because _log is added below. const options = _.extend({}, userOptions) @@ -568,7 +568,7 @@ export default function (Commands, Cypress, cy, state) { return ret } - return thenFn(el, userOptions, callback, state) + return thenFn(el, userOptions, callback) } // generate a real array since bluebird is finicky and @@ -586,9 +586,9 @@ export default function (Commands, Cypress, cy, state) { // cy.resolve + cy.wrap are upgraded to handle // promises Commands.addAll({ prevSubject: 'optional' }, { - then () { + then (subject, userOptions, fn) { // eslint-disable-next-line prefer-rest-params - return thenFn.apply(this, arguments) + return thenFn.apply(this, [subject, userOptions, fn]) }, }) diff --git a/packages/driver/src/cy/commands/cookies.ts b/packages/driver/src/cy/commands/cookies.ts index 3c8f09e36d..9002763dc0 100644 --- a/packages/driver/src/cy/commands/cookies.ts +++ b/packages/driver/src/cy/commands/cookies.ts @@ -1,5 +1,3 @@ -// @ts-nocheck - import _ from 'lodash' import Promise from 'bluebird' @@ -110,7 +108,7 @@ export default function (Commands, Cypress, cy, state, config) { }) } - const getAndClear = (log, timeout, options = {}) => { + const getAndClear = (log?, timeout?, options = {}) => { return automateCookies('get:cookies', options, log, timeout) .then((resp) => { // bail early if we got no cookies! @@ -166,7 +164,8 @@ export default function (Commands, Cypress, cy, state, config) { }) return Commands.addAll({ - getCookie (name, options = {}) { + // TODO: change the type of `any` to `Partial` + getCookie (name, options: any = {}) { const userOptions = options options = _.defaults({}, userOptions, { @@ -212,7 +211,8 @@ export default function (Commands, Cypress, cy, state, config) { .catch(handleBackendError('getCookie', 'reading the requested cookie from', onFail)) }, - getCookies (options = {}) { + // TODO: change the type of `any` to `Partial` + getCookies (options: any = {}) { const userOptions = options options = _.defaults({}, userOptions, { @@ -250,7 +250,8 @@ export default function (Commands, Cypress, cy, state, config) { .catch(handleBackendError('getCookies', 'reading cookies from', options._log)) }, - setCookie (name, value, options = {}) { + // TODO: change the type of `any` to `Partial` + setCookie (name, value, options: any = {}) { const userOptions = options options = _.defaults({}, userOptions, { @@ -331,7 +332,8 @@ export default function (Commands, Cypress, cy, state, config) { }).catch(handleBackendError('setCookie', 'setting the requested cookie in', onFail)) }, - clearCookie (name, options = {}) { + // TODO: change the type of `any` to `Partial` + clearCookie (name, options: any = {}) { const userOptions = options options = _.defaults({}, userOptions, { @@ -380,7 +382,8 @@ export default function (Commands, Cypress, cy, state, config) { .catch(handleBackendError('clearCookie', 'clearing the requested cookie in', onFail)) }, - clearCookies (options = {}) { + // TODO: change the type of `any` to `Partial` + clearCookies (options: any = {}) { const userOptions = options options = _.defaults({}, userOptions, { diff --git a/packages/driver/src/cy/commands/exec.ts b/packages/driver/src/cy/commands/exec.ts index 9727db3b44..d611459057 100644 --- a/packages/driver/src/cy/commands/exec.ts +++ b/packages/driver/src/cy/commands/exec.ts @@ -1,5 +1,3 @@ -// @ts-nocheck - import _ from 'lodash' import Promise from 'bluebird' @@ -7,7 +5,8 @@ import $errUtils from '../../cypress/error_utils' export default (Commands, Cypress, cy) => { Commands.addAll({ - exec (cmd, options = {}) { + // TODO: change the type of `any` to `Partical` + exec (cmd, options: any = {}) { const userOptions = options options = _.defaults({}, userOptions, { diff --git a/packages/driver/src/cy/commands/files.ts b/packages/driver/src/cy/commands/files.ts index d88b1279e7..a87a9f9ee2 100644 --- a/packages/driver/src/cy/commands/files.ts +++ b/packages/driver/src/cy/commands/files.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import _ from 'lodash' import { basename } from 'path' @@ -6,7 +5,8 @@ import $errUtils from '../../cypress/error_utils' export default (Commands, Cypress, cy, state) => { Commands.addAll({ - readFile (file, encoding, options = {}) { + // TODO: change the type of `any` to `Partial` + readFile (file, encoding, options: any = {}) { let userOptions = options if (_.isObject(encoding)) { @@ -109,7 +109,8 @@ export default (Commands, Cypress, cy, state) => { return verifyAssertions() }, - writeFile (fileName, contents, encoding, options = {}) { + // TODO: change the type of `any` to `Partial` + writeFile (fileName, contents, encoding, options: any = {}) { let userOptions = options if (_.isObject(encoding)) { diff --git a/packages/driver/src/cy/commands/fixtures.ts b/packages/driver/src/cy/commands/fixtures.ts index c4a84b40e4..9d142df77b 100644 --- a/packages/driver/src/cy/commands/fixtures.ts +++ b/packages/driver/src/cy/commands/fixtures.ts @@ -1,5 +1,3 @@ -// @ts-nocheck - import _ from 'lodash' import Promise from 'bluebird' import { basename } from 'path' @@ -44,7 +42,7 @@ export default (Commands, Cypress, cy, state, config) => { return Promise.resolve(clone(resp)) } - let options = {} + let options: Record = {} if (_.isObject(args[0])) { options = args[0] diff --git a/packages/driver/src/cy/commands/location.ts b/packages/driver/src/cy/commands/location.ts index d0bbf37ab0..837d39b8a3 100644 --- a/packages/driver/src/cy/commands/location.ts +++ b/packages/driver/src/cy/commands/location.ts @@ -1,5 +1,3 @@ -// @ts-nocheck - import _ from 'lodash' import Promise from 'bluebird' @@ -8,7 +6,8 @@ const { throwErrByPath } = $errUtils export default (Commands, Cypress, cy) => { Commands.addAll({ - url (options = {}) { + // TODO: change the type of `any` to `Partial` + url (options: any = {}) { const userOptions = options options = _.defaults({}, userOptions, { log: true }) @@ -39,7 +38,8 @@ export default (Commands, Cypress, cy) => { return resolveHref() }, - hash (options = {}) { + // TODO: change the type of `any` to `Partial` + hash (options: any = {}) { const userOptions = options options = _.defaults({}, userOptions, { log: true }) diff --git a/packages/driver/src/cy/commands/misc.ts b/packages/driver/src/cy/commands/misc.ts index c0594562be..5a0db7a0f0 100644 --- a/packages/driver/src/cy/commands/misc.ts +++ b/packages/driver/src/cy/commands/misc.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import _ from 'lodash' import Promise from 'bluebird' @@ -50,7 +49,8 @@ export default (Commands, Cypress, cy, state) => { return null }, - wrap (arg, options = {}) { + // TODO: change the type of `any` to `Partial` + wrap (arg, options: any = {}) { const userOptions = options options = _.defaults({}, userOptions, { diff --git a/packages/driver/src/cy/commands/navigation.ts b/packages/driver/src/cy/commands/navigation.ts index 07f184ba04..c3afb98731 100644 --- a/packages/driver/src/cy/commands/navigation.ts +++ b/packages/driver/src/cy/commands/navigation.ts @@ -1,5 +1,3 @@ -// @ts-nocheck -/* global cy, Cypress */ import _ from 'lodash' import whatIsCircular from '@cypress/what-is-circular' import UrlParse from 'url-parse' @@ -15,10 +13,10 @@ import debugFn from 'debug' const debug = debugFn('cypress:driver:navigation') let id = null -let previousDomainVisited = null -let hasVisitedAboutBlank = null -let currentlyVisitingAboutBlank = null -let knownCommandCausedInstability = null +let previousDomainVisited: boolean = false +let hasVisitedAboutBlank: boolean = false +let currentlyVisitingAboutBlank: boolean = false +let knownCommandCausedInstability: boolean = false const REQUEST_URL_OPTS = 'auth failOnStatusCode retryOnNetworkFailure retryOnStatusCodeFailure retryIntervals method body headers' .split(' ') @@ -27,7 +25,7 @@ const VISIT_OPTS = 'url log onBeforeLoad onLoad timeout requestTimeout' .split(' ') .concat(REQUEST_URL_OPTS) -const reset = (test = {}) => { +const reset = (test: any = {}) => { knownCommandCausedInstability = false // continuously reset this @@ -62,7 +60,7 @@ const timedOutWaitingForPageLoad = (ms, log) => { } const cannotVisitDifferentOrigin = (origin, previousUrlVisited, remoteUrl, existingUrl, log) => { - const differences = [] + const differences: string[] = [] if (remoteUrl.protocol !== existingUrl.protocol) { differences.push('protocol') @@ -171,7 +169,7 @@ const navigationChanged = (Cypress, cy, state, source, arg) => { end: true, snapshot: true, consoleProps () { - const obj = { + const obj: Record = { 'New Url': url, } @@ -265,7 +263,7 @@ const stabilityChanged = (Cypress, state, config, stable) => { return } - const options = {} + const options: Record = {} _.defaults(options, { timeout: config('pageLoadTimeout'), @@ -404,6 +402,14 @@ const normalizeTimeoutOptions = (options) => { .value() } +type NotOkResponseError = Error & { + gotResponse: boolean +} + +type InvalidContentTypeError = Error & { + invalidContentType: boolean +} + export default (Commands, Cypress, cy, state, config) => { reset() @@ -420,7 +426,7 @@ export default (Commands, Cypress, cy, state, config) => { Cypress.on('stability:changed', (bool, event) => { // only send up page loading events when we're // not stable! - stabilityChanged(Cypress, state, config, bool, event) + stabilityChanged(Cypress, state, config, bool) }) Cypress.on('navigation:changed', (source, arg) => { @@ -445,11 +451,11 @@ export default (Commands, Cypress, cy, state, config) => { url, normalizeTimeoutOptions(options), ) - .then((resp = {}) => { + .then((resp: any = {}) => { if (!resp.isOkStatusCode) { // if we didn't even get an OK response // then immediately die - const err = new Error + const err: NotOkResponseError = new Error as any err.gotResponse = true _.extend(err, resp) @@ -459,7 +465,7 @@ export default (Commands, Cypress, cy, state, config) => { if (!resp.isHtml) { // throw invalid contentType error - const err = new Error + const err: InvalidContentTypeError = new Error as any err.invalidContentType = true _.extend(err, resp) @@ -525,7 +531,7 @@ export default (Commands, Cypress, cy, state, config) => { // clear the current timeout cy.clearTimeout('reload') - let cleanup = null + let cleanup: (() => any) | null = null const options = _.defaults({}, userOptions, { log: true, timeout: config('pageLoadTimeout'), @@ -579,7 +585,7 @@ export default (Commands, Cypress, cy, state, config) => { }, go (numberOrString, userOptions = {}) { - const options = _.defaults({}, userOptions, { + const options: Record = _.defaults({}, userOptions, { log: true, timeout: config('pageLoadTimeout'), }) @@ -595,7 +601,7 @@ export default (Commands, Cypress, cy, state, config) => { $errUtils.throwErrByPath('go.invalid_number', { onFail: options._log }) } - let cleanup = null + let cleanup: (() => any) | null = null if (options._log) { options._log.snapshot('before', { next: 'after' }) @@ -669,7 +675,7 @@ export default (Commands, Cypress, cy, state, config) => { case 'forward': return goNumber(1) case 'back': return goNumber(-1) default: - $errUtils.throwErrByPath('go.invalid_direction', { + return $errUtils.throwErrByPath('go.invalid_direction', { onFail: options._log, args: { str }, }) @@ -684,10 +690,11 @@ export default (Commands, Cypress, cy, state, config) => { return goString(numberOrString) } - $errUtils.throwErrByPath('go.invalid_argument', { onFail: options._log }) + return $errUtils.throwErrByPath('go.invalid_argument', { onFail: options._log }) }, - visit (url, options = {}) { + // TODO: Change the type of `any` to `Partial`. + visit (url, options: any = {}) { if (options.url && url) { $errUtils.throwErrByPath('visit.no_duplicate_url', { args: { optionsUrl: options.url, url } }) } @@ -778,7 +785,7 @@ export default (Commands, Cypress, cy, state, config) => { url = $Location.mergeUrlWithParams(url, qs) } - let cleanup = null + let cleanup: (() => any) | null = null // clear the current timeout cy.clearTimeout('visit') @@ -801,7 +808,7 @@ export default (Commands, Cypress, cy, state, config) => { }) options.onBeforeLoad?.call(runnable.ctx, contentWindow) - } catch (err) { + } catch (err: any) { err.isCallbackError = true onBeforeLoadError = err } @@ -847,7 +854,10 @@ export default (Commands, Cypress, cy, state, config) => { }) } - const onLoad = ({ runOnLoadCallback, totalTime }) => { + const onLoad = ({ runOnLoadCallback, totalTime }: { + runOnLoadCallback?: boolean + totalTime?: number + }) => { // reset window on load win = state('window') @@ -855,7 +865,7 @@ export default (Commands, Cypress, cy, state, config) => { if (runOnLoadCallback !== false) { try { options.onLoad?.call(runnable.ctx, win) - } catch (err) { + } catch (err: any) { // mark these as user callback errors, so they're treated differently // than Node.js errors when caught below err.isCallbackError = true @@ -930,7 +940,9 @@ export default (Commands, Cypress, cy, state, config) => { } return changeIframeSrc(remote.href, 'hashchange') - .then(onLoad) + .then(() => { + return onLoad({}) + }) } if (existingHash) { @@ -945,7 +957,7 @@ export default (Commands, Cypress, cy, state, config) => { } return requestUrl(url, options) - .then((resp = {}) => { + .then((resp: any = {}) => { let { url, originalUrl, cookies, redirects, filePath } = resp // reapply the existing hash @@ -1000,7 +1012,7 @@ export default (Commands, Cypress, cy, state, config) => { // tell our backend we're changing domains // TODO: add in other things we want to preserve // state for like scrollTop - let s = { + let s: Record = { currentId: id, tests: Cypress.runner.getTestsState(), startTime: Cypress.runner.getStartTime(), diff --git a/packages/driver/src/cypress/browser.ts b/packages/driver/src/cypress/browser.ts index 0a3c3678cd..28352d4a4e 100644 --- a/packages/driver/src/cypress/browser.ts +++ b/packages/driver/src/cypress/browser.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import _ from 'lodash' import $utils from './utils' import $errUtils from './error_utils' @@ -36,12 +35,16 @@ const _isBrowser = (browser, matcher, errPrefix) => { } } -const isBrowser = (config, obj = '', errPrefix = '`Cypress.isBrowser()`') => { +// TODO: change the type of `any` to `IsBrowserMatcher` +const isBrowser = (config, obj: any = '', errPrefix: string = '`Cypress.isBrowser()`') => { return _ .chain(obj) .concat([]) .map((matcher) => _isBrowser(config.browser, matcher, errPrefix)) - .reduce((a, b) => { + .reduce(( + a: null | { isMatch: boolean, exclusive: boolean }, + b: { isMatch: boolean, exclusive: boolean }, + ) => { if (!a) return b if (a.exclusive && b.exclusive) { diff --git a/packages/driver/src/cypress/chai_jquery.ts b/packages/driver/src/cypress/chai_jquery.ts index f4f7f675c3..23a13ada95 100644 --- a/packages/driver/src/cypress/chai_jquery.ts +++ b/packages/driver/src/cypress/chai_jquery.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import _ from 'lodash' import $ from 'jquery' import $dom from '../dom' @@ -33,7 +32,12 @@ const maybeCastNumberToString = (num) => { return _.isFinite(num) ? `${num}` : num } -export const $chaiJquery = (chai, chaiUtils, callbacks = {}) => { +interface Callbacks { + onInvalid: (method, obj) => void + onError: (err, method, obj, negated) => void +} + +export const $chaiJquery = (chai, chaiUtils, callbacks: Callbacks) => { const { inspect, flag } = chaiUtils const assertDom = (ctx, method, ...args) => { diff --git a/packages/driver/src/cypress/commands.ts b/packages/driver/src/cypress/commands.ts index 574b62a6ad..0384c887d4 100644 --- a/packages/driver/src/cypress/commands.ts +++ b/packages/driver/src/cypress/commands.ts @@ -1,5 +1,3 @@ -// @ts-nocheck - import _ from 'lodash' import $errUtils from './error_utils' @@ -9,6 +7,8 @@ import { allCommands } from '../cy/commands' import { addCommand } from '../cy/net-stubbing' const builtInCommands = [ + // `default` is necessary if a file uses `export default` syntax. + // @ts-ignore ..._.toArray(allCommands).map((c) => c.default || c), addCommand, ] @@ -74,7 +74,7 @@ export default { const overridden = _.clone(original) overridden.fn = function (...args) { - args = [].concat(originalFn, args) + args = ([] as any).concat(originalFn, args) return fn.apply(this, args) } diff --git a/packages/driver/src/cypress/cookies.ts b/packages/driver/src/cypress/cookies.ts index c5bf498a5c..28d15317cc 100644 --- a/packages/driver/src/cypress/cookies.ts +++ b/packages/driver/src/cypress/cookies.ts @@ -1,5 +1,3 @@ -// @ts-nocheck - import _ from 'lodash' import Cookies from 'js-cookie' @@ -10,13 +8,13 @@ let isDebuggingVerbose = false const preserved = {} -const defaults = { +const defaults: any = { preserve: null, } const warnOnWhitelistRenamed = (obj, type) => { if (obj.whitelist) { - return $errUtils.throwErrByPath('cookies.whitelist_renamed', { args: { type } }) + $errUtils.throwErrByPath('cookies.whitelist_renamed', { args: { type } }) } } @@ -53,10 +51,12 @@ export const $Cookies = (namespace, domain) => { if (preserved[name]) { return delete preserved[name] } + + return false } const API = { - debug (bool = true, options = {}) { + debug (bool = true, options: any = {}) { _.defaults(options, { verbose: true, }) @@ -82,7 +82,7 @@ export const $Cookies = (namespace, domain) => { return console[m].apply(console, args) }, - getClearableCookies (cookies = []) { + getClearableCookies (cookies: any[] = []) { return _.filter(cookies, (cookie) => { return !isAllowed(cookie) && !removePreserved(cookie.name) }) diff --git a/packages/driver/src/cypress/error_utils.ts b/packages/driver/src/cypress/error_utils.ts index 0645822e71..7d7cf105ef 100644 --- a/packages/driver/src/cypress/error_utils.ts +++ b/packages/driver/src/cypress/error_utils.ts @@ -1,5 +1,3 @@ -// @ts-nocheck - // See: ./errorScenarios.md for details about error messages and stack traces import _ from 'lodash' @@ -7,7 +5,7 @@ import chai from 'chai' import $dom from '../dom' import $utils from './utils' -import $stackUtils from './stack_utils' +import $stackUtils, { StackAndCodeFrameIndex } from './stack_utils' import $errorMessages from './error_messages' const ERROR_PROPS = 'message type name stack sourceMappedStack parsedStack fileName lineNumber columnNumber host uncaught actual expected showDiff isPending docsUrl codeFrame'.split(' ') @@ -17,9 +15,9 @@ const crossOriginScriptRe = /^script error/i if (!Error.captureStackTrace) { Error.captureStackTrace = (err, fn) => { - const stack = (new Error()).stack + const stack = (new Error()).stack; - err.stack = $stackUtils.stackWithLinesDroppedFromMarker(stack, fn.name) + (err as Error).stack = $stackUtils.stackWithLinesDroppedFromMarker(stack, fn?.name) } } @@ -63,15 +61,15 @@ const wrapErr = (err) => { return $utils.reduceProps(err, ERROR_PROPS) } -const isAssertionErr = (err = {}) => { +const isAssertionErr = (err: Error) => { return err.name === 'AssertionError' } -const isChaiValidationErr = (err = {}) => { +const isChaiValidationErr = (err: Error) => { return _.startsWith(err.message, 'Invalid Chai property') } -const isCypressErr = (err = {}) => { +const isCypressErr = (err: Error): boolean => { return err.name === 'CypressError' } @@ -79,7 +77,7 @@ const isSpecError = (spec, err) => { return _.includes(err.stack, spec.relative) } -const mergeErrProps = (origErr: Error, ...newProps) => { +const mergeErrProps = (origErr: Error, ...newProps): Error => { return _.extend(origErr, ...newProps) } @@ -197,7 +195,7 @@ const makeErrFromObj = (obj) => { return err2 } -const throwErr = (err, options = {}) => { +const makeErrFromErr = (err, options: any = {}) => { if (_.isString(err)) { err = cypressErr({ message: err }) } @@ -205,12 +203,12 @@ const throwErr = (err, options = {}) => { let { onFail, errProps } = options // assume onFail is a command if - //# onFail is present and isn't a function + // onFail is present and isn't a function if (onFail && !_.isFunction(onFail)) { const command = onFail - //# redefine onFail and automatically - //# hook this into our command + // redefine onFail and automatically + // hook this into our command onFail = (err) => { return command.error(err) } @@ -224,10 +222,14 @@ const throwErr = (err, options = {}) => { _.extend(err, errProps) } - throw err + return err } -const throwErrByPath = (errPath, options = {}) => { +const throwErr = (err, options: any = {}): never => { + throw makeErrFromErr(err, options) +} + +const throwErrByPath = (errPath, options: any = {}): never => { const err = errByPath(errPath, options.args) if (options.stack) { @@ -237,15 +239,16 @@ const throwErrByPath = (errPath, options = {}) => { Error.captureStackTrace(err, throwErrByPath) } - throwErr(err, options) + throw makeErrFromErr(err, options) } -const warnByPath = (errPath, options = {}) => { +const warnByPath = (errPath, options: any = {}) => { const errObj = errByPath(errPath, options.args) let err = errObj.message + const docsUrl = (errObj as CypressError).docsUrl - if (errObj.docsUrl) { - err += `\n\n${errObj.docsUrl}` + if (docsUrl) { + err += `\n\n${docsUrl}` } $utils.warning(err) @@ -266,6 +269,7 @@ export class InternalCypressError extends Error { export class CypressError extends Error { docsUrl?: string retry?: boolean + userInvocationStack?: any constructor (message) { super(message) @@ -297,10 +301,10 @@ const internalErr = (err): InternalCypressError => { const cypressErr = (err): CypressError => { const newErr = new CypressError(err.message) - return mergeErrProps(newErr, err) + return mergeErrProps(newErr, err) as CypressError } -const cypressErrByPath = (errPath, options = {}) => { +const cypressErrByPath = (errPath, options: any = {}) => { const errObj = errByPath(errPath, options.args) return cypressErr(errObj) @@ -376,7 +380,7 @@ const createUncaughtException = ({ frameType, handlerType, state, err }) => { let uncaughtErr = errByPath(errPath, { errMsg: err.message, promiseAddendum: handlerType === 'unhandledrejection' ? ' It was caused by an unhandled promise rejection.' : '', - }) + }) as CypressError modifyErrMsg(err, uncaughtErr.message, () => uncaughtErr.message) @@ -394,7 +398,7 @@ const createUncaughtException = ({ frameType, handlerType, state, err }) => { // stacks from command failures and assertion failures have the right message // but the stack points to cypress internals. here we replace the internal // cypress stack with the invocation stack, which points to the user's code -const stackAndCodeFrameIndex = (err, userInvocationStack) => { +const stackAndCodeFrameIndex = (err, userInvocationStack): StackAndCodeFrameIndex => { if (!userInvocationStack) return { stack: err.stack } if (isCypressErr(err) || isChaiValidationErr(err)) { @@ -427,7 +431,7 @@ const enhanceStack = ({ err, userInvocationStack, projectRoot }) => { // all errors flow through this function before they're finally thrown // or used to reject promises -const processErr = (errObj = {}, config) => { +const processErr = (errObj: CypressError, config) => { let docsUrl = errObj.docsUrl if (config('isInteractive') || !docsUrl) { @@ -482,7 +486,7 @@ const errorFromErrorEvent = (event): ErrorFromErrorEvent => { // reset the message on a cross origin script error // since no details are accessible if (crossOriginScriptRe.test(message)) { - const crossOriginErr = errByPath('uncaught.cross_origin_script') + const crossOriginErr = errByPath('uncaught.cross_origin_script') as CypressError message = crossOriginErr.message docsUrl = crossOriginErr.docsUrl @@ -490,9 +494,9 @@ const errorFromErrorEvent = (event): ErrorFromErrorEvent => { // it's possible the error was thrown as a string (throw 'some error') // so create it in the case it's not already an object - const err = _.isObject(error) ? error : convertErrorEventPropertiesToObject({ + const err = (_.isObject(error) ? error : convertErrorEventPropertiesToObject({ message, filename, lineno, colno, - }) + })) as CypressError err.docsUrl = docsUrl diff --git a/packages/driver/src/cypress/location.ts b/packages/driver/src/cypress/location.ts index 661a445f05..96837afd3b 100644 --- a/packages/driver/src/cypress/location.ts +++ b/packages/driver/src/cypress/location.ts @@ -1,4 +1,3 @@ -// @ts-nocheck // TODO: // 1. test these method implementations using encoded characters // look at the spec to figure out whether we SHOULD be decoding them @@ -18,6 +17,8 @@ const reLocalHost = /^(localhost|0\.0\.0\.0|127\.0\.0\.1)/ const reQueryParam = /\?[^/]+/ export class $Location { + remote: UrlParse + constructor (remote) { this.remote = new UrlParse(remote) } @@ -38,6 +39,8 @@ export class $Location { password, } } + + return } getHash () { diff --git a/packages/driver/src/cypress/stack_utils.ts b/packages/driver/src/cypress/stack_utils.ts index 0a3d2a188d..a3f3c12ee6 100644 --- a/packages/driver/src/cypress/stack_utils.ts +++ b/packages/driver/src/cypress/stack_utils.ts @@ -66,7 +66,12 @@ const stackWithReplacementMarkerLineRemoved = (stack) => { }) } -const stackWithUserInvocationStackSpliced = (err, userInvocationStack) => { +export type StackAndCodeFrameIndex = { + stack: string + index?: number +} + +const stackWithUserInvocationStackSpliced = (err, userInvocationStack): StackAndCodeFrameIndex => { const stack = _.trim(err.stack, '\n') // trim newlines from end const [messageLines, stackLines] = splitStack(stack) const userInvocationStackWithoutMessage = stackWithoutMessage(userInvocationStack) diff --git a/packages/driver/src/cypress/utils.ts b/packages/driver/src/cypress/utils.ts index 9718338887..9b83e5c82f 100644 --- a/packages/driver/src/cypress/utils.ts +++ b/packages/driver/src/cypress/utils.ts @@ -98,7 +98,7 @@ export default { throw new Error(`The switch/case value: '${value}' did not match any cases: ${keys.join(', ')}.`) }, - reduceProps (obj, props = []) { + reduceProps (obj, props: string[] = []) { if (!obj) { return null } @@ -355,7 +355,7 @@ export default { // normalize more than {maxNewLines} new lines into // exactly {replacementNumLines} new lines - normalizeNewLines (str, maxNewLines, replacementNumLines) { + normalizeNewLines (str, maxNewLines, replacementNumLines?) { const moreThanMaxNewLinesRe = new RegExp(`\\n{${maxNewLines},}`) const replacementWithNumLines = replacementNumLines ?? maxNewLines