├── .buildkite └── pipeline.yml ├── .circleci └── config.yml ├── .dockerignore ├── .github └── workflows │ ├── add-issue-to-triage-board.yml │ ├── chrome-docker.yml │ ├── chrome.yml │ ├── parallel.yml │ ├── semantic-pull-request.yml │ ├── single.yml │ ├── triage_closed_issue_comment.yml │ └── using-action.yml ├── .gitignore ├── .gitlab-ci.yml ├── .husky ├── install.mjs └── pre-commit ├── .node-version ├── .npmrc ├── .release.json ├── .semaphore └── semaphore.yml ├── .travis.yml ├── .vscode └── settings.json ├── CONTRIBUTING.md ├── Dockerfile ├── Jenkinsfile ├── LICENSE ├── README.md ├── amplify.yml ├── app ├── assets │ ├── css │ │ ├── styles.css │ │ ├── todo.css │ │ └── vendor │ │ │ ├── bootstrap.min.css │ │ │ ├── bootstrap.min.css.map │ │ │ └── fira.css │ ├── fonts │ │ ├── eot │ │ │ ├── FiraSans-Medium.eot │ │ │ └── FiraSans-Regular.eot │ │ ├── ttf │ │ │ ├── FiraSans-Medium.ttf │ │ │ └── FiraSans-Regular.ttf │ │ └── woff │ │ │ ├── FiraSans-Medium.woff │ │ │ └── FiraSans-Regular.woff │ ├── img │ │ ├── favicon.ico │ │ └── javascript-logo.png │ └── js │ │ ├── scripts.js │ │ ├── todo │ │ ├── app.js │ │ ├── controller.js │ │ ├── helpers.js │ │ ├── model.js │ │ ├── store.js │ │ ├── template.js │ │ └── view.js │ │ └── vendor │ │ ├── bootstrap.min.js │ │ ├── highlight.pack.js │ │ ├── jquery-1.12.0.min.js │ │ └── jquery-1.12.0.min.map ├── commands │ ├── actions.html │ ├── aliasing.html │ ├── assertions.html │ ├── connectors.html │ ├── cookies.html │ ├── files.html │ ├── location.html │ ├── misc.html │ ├── navigation.html │ ├── network-requests.html │ ├── querying.html │ ├── spies-stubs-clocks.html │ ├── storage.html │ ├── traversal.html │ ├── viewport.html │ ├── waiting.html │ └── window.html ├── cypress-api.html ├── index.html ├── todo.html └── utilities.html ├── appveyor.yml ├── azure-ci.yml ├── basic ├── .circleci │ └── config.yml ├── .gitlab-ci.yml ├── .semaphore.yml ├── .travis.yml ├── Jenkinsfile ├── README.md ├── azure-ci.yml ├── buildspec.yml └── codeship-pro │ ├── Dockerfile │ ├── README.md │ ├── codeship-services.yml │ └── codeship-steps.yml ├── buddy.yml ├── buildspec.yml ├── codeship-services.yml ├── codeship-steps.yml ├── cypress.config.js ├── cypress ├── e2e │ ├── 1-getting-started │ │ └── todo.cy.js │ └── 2-advanced-examples │ │ ├── actions.cy.js │ │ ├── aliasing.cy.js │ │ ├── assertions.cy.js │ │ ├── connectors.cy.js │ │ ├── cookies.cy.js │ │ ├── cypress_api.cy.js │ │ ├── files.cy.js │ │ ├── location.cy.js │ │ ├── misc.cy.js │ │ ├── navigation.cy.js │ │ ├── network_requests.cy.js │ │ ├── querying.cy.js │ │ ├── spies_stubs_clocks.cy.js │ │ ├── storage.cy.js │ │ ├── traversal.cy.js │ │ ├── utilities.cy.js │ │ ├── viewport.cy.js │ │ ├── waiting.cy.js │ │ └── window.cy.js ├── fixtures │ └── example.json └── support │ ├── commands.js │ └── e2e.js ├── env.encrypted ├── eslint.config.mjs ├── package-lock.json ├── package.json ├── renovate.json ├── scripts ├── set-port.js └── start.js └── serve.json /.buildkite/pipeline.yml: -------------------------------------------------------------------------------- 1 | env: 2 | TERM: "xterm" 3 | 4 | steps: 5 | - label: ":cypress: Cypress" 6 | command: | 7 | echo "--- Node version" 8 | node --version 9 | 10 | echo "--- npm version" 11 | npm --version 12 | 13 | echo "--- Install npm dependencies" 14 | npm install 15 | 16 | # if necessary, reinstall "correct" version of Cypress 17 | # a common situation if testing preview binary build 18 | # npx cypress install --force 19 | # 20 | echo "--- Install custom Cypress version" 21 | npm install "$CYPRESS_NPM_PACKAGE_NAME" 22 | 23 | echo "--- Cypress version" 24 | npx cypress version 25 | 26 | echo "+++ Run Cypress tests" 27 | npm run test:ci:record 28 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # testing using CircleCI orb 2 | # see https://on.cypress.io/circleci-orb 3 | 4 | # for non-orb configuration see old commit 5 | # https://github.com/cypress-io/cypress-example-kitchensink/blob/aabb10cc1bb9dee88e1bf28e0af5e9661427ee7a/circle.yml 6 | 7 | # to use CircleCI orbs need to use version >= 2.1 8 | version: 2.1 9 | orbs: 10 | # use Cypress orb from CircleCI registry 11 | cypress: cypress-io/cypress@4.1.0 12 | win: circleci/windows@5.1.0 13 | 14 | executors: 15 | mac: 16 | macos: 17 | xcode: "16.2.0" 18 | resource_class: macos.m1.medium.gen1 19 | 20 | jobs: 21 | win-test: 22 | working_directory: ~/app 23 | executor: 24 | name: win/default 25 | size: medium 26 | shell: bash.exe 27 | steps: 28 | - checkout 29 | - run: 30 | name: Install Node.js 31 | command: | 32 | nvm --version 33 | nvm install 22 34 | nvm use 22 35 | - restore_cache: 36 | key: dependencies-{{ arch }}-{{ .Branch }}-{{ checksum "package.json" }} 37 | 38 | - run: npm ci 39 | - run: npm run cy:verify 40 | - run: npm run cy:info 41 | 42 | - save_cache: 43 | key: dependencies-{{ arch }}-{{ .Branch }}-{{ checksum "package.json" }} 44 | paths: 45 | # could not use environment variables for some reason 46 | - C:\Users\circleci\AppData\Local\Cypress\Cache 47 | - C:\Users\circleci\AppData\Roaming\npm-cache 48 | 49 | - run: 50 | name: 'Start server' 51 | command: npm run start 52 | background: true 53 | 54 | - run: 55 | name: 'Run Cypress tests' 56 | command: npm run e2e:record -- --env circle=true 57 | no_output_timeout: '1m' 58 | - store_artifacts: 59 | path: cypress\screenshots 60 | 61 | win-test-chrome: 62 | working_directory: ~/app 63 | executor: 64 | name: win/default 65 | size: medium 66 | shell: bash.exe 67 | steps: 68 | - checkout 69 | - run: 70 | name: Install Node.js 71 | command: | 72 | nvm --version 73 | nvm install 22 74 | nvm use 22 75 | - restore_cache: 76 | key: dependencies-{{ arch }}-{{ .Branch }}-{{ checksum "package.json" }} 77 | 78 | # install Chrome browser on Windows machine using Chocolatey 79 | # https://chocolatey.org/packages/GoogleChrome 80 | - run: choco install googlechrome --ignore-checksums -y 81 | - run: npm ci 82 | - run: npm run cy:verify 83 | - run: npm run cy:info 84 | 85 | - save_cache: 86 | key: dependencies-{{ arch }}-{{ .Branch }}-{{ checksum "package.json" }} 87 | paths: 88 | # could not use environment variables for some reason 89 | - C:\Users\circleci\AppData\Local\Cypress\Cache 90 | - C:\Users\circleci\AppData\Roaming\npm-cache 91 | 92 | - run: 93 | name: 'Start server' 94 | command: npm run start 95 | background: true 96 | 97 | - run: 98 | name: 'Run Cypress tests' 99 | command: npm run e2e:record:chrome -- --env circle=true 100 | no_output_timeout: '1m' 101 | - store_artifacts: 102 | path: cypress\screenshots 103 | 104 | win-test-firefox: 105 | working_directory: ~/app 106 | executor: 107 | name: win/default 108 | size: medium 109 | shell: bash.exe 110 | steps: 111 | - checkout 112 | - run: 113 | name: Install Node.js 114 | command: | 115 | nvm --version 116 | nvm install 22 117 | nvm use 22 118 | - restore_cache: 119 | key: dependencies-{{ arch }}-{{ .Branch }}-{{ checksum "package.json" }} 120 | 121 | # install Firefox browser on Windows machine using Chocolatey 122 | # https://chocolatey.org/packages/Firefox 123 | - run: choco install firefox -y 124 | - run: npm ci 125 | - run: npm run cy:verify 126 | - run: npm run cy:info 127 | 128 | - save_cache: 129 | key: dependencies-{{ arch }}-{{ .Branch }}-{{ checksum "package.json" }} 130 | paths: 131 | # could not use environment variables for some reason 132 | - C:\Users\circleci\AppData\Local\Cypress\Cache 133 | - C:\Users\circleci\AppData\Roaming\npm-cache 134 | 135 | - run: 136 | name: 'Start server' 137 | command: npm run start 138 | background: true 139 | 140 | - run: 141 | name: 'Run Cypress tests' 142 | command: npm run e2e:record:firefox -- --env circle=true 143 | no_output_timeout: '1m' 144 | - store_artifacts: 145 | path: cypress\screenshots 146 | 147 | mac-test: 148 | executor: mac 149 | steps: 150 | - run: 151 | name: Install Node.js 152 | command: | 153 | nvm install 22 154 | nvm use 22 155 | - cypress/install: 156 | post-install: "npm run build" 157 | # show Cypress cache folder and binary versions 158 | # to check if we are caching previous binary versions 159 | - run: npx cypress cache path 160 | - run: npx cypress cache list 161 | - run: npx cypress info 162 | - cypress/run-tests: 163 | start-command: 'npm run start' 164 | cypress-command: 'npx cypress run --record --group Mac build' 165 | 166 | linux-test: 167 | # checks out code and installs dependencies once 168 | # runs on 3 machines, load balances tests 169 | # and records on Cypress Cloud 170 | parallelism: 3 171 | executor: 172 | name: cypress/default 173 | node-version: '22.15.0' 174 | steps: 175 | - cypress/install: 176 | post-install: 'npm run build' 177 | - cypress/run-tests: 178 | start-command: 'npm run start' 179 | cypress-command: 'npx cypress run --record --parallel --group 3x-electron on CircleCI' 180 | - run: npx cypress cache path 181 | - run: npx cypress cache list 182 | - run: npx cypress info 183 | # let's print version info 184 | - run: npx cypress version 185 | - run: npx cypress version --component package 186 | - run: npx cypress version --component binary 187 | - run: npx cypress version --component electron 188 | - run: npx cypress version --component node 189 | 190 | linux-test-chrome: 191 | # runs on 2 machines using Chrome browser 192 | parallelism: 2 193 | executor: 194 | name: cypress/default 195 | node-version: '22.15.0' 196 | steps: 197 | - cypress/install: 198 | install-browsers: true 199 | - cypress/run-tests: 200 | start-command: 'npm run start' 201 | cypress-command: 'npx cypress run --browser chrome --record --parallel --group 2x-chrome on CircleCI' 202 | 203 | linux-test-firefox: 204 | # runs on 2 machines using Firefox browser 205 | parallelism: 2 206 | executor: 207 | name: cypress/default 208 | node-version: '22.15.0' 209 | steps: 210 | - cypress/install: 211 | install-browsers: true 212 | - cypress/run-tests: 213 | start-command: 'npm run start' 214 | cypress-command: 'npx cypress run --browser firefox --record --parallel --group 2x-firefox on CircleCI' 215 | 216 | release: 217 | executor: 218 | name: cypress/default 219 | node-version: '22.15.0' 220 | steps: 221 | - checkout 222 | - run: npm ci 223 | - run: npx semantic-release 224 | 225 | workflows: 226 | win-build: 227 | jobs: 228 | - win-test 229 | - win-test-chrome 230 | - win-test-firefox 231 | 232 | mac-build: 233 | jobs: 234 | - mac-test 235 | 236 | linux-build: 237 | jobs: 238 | - linux-test 239 | - linux-test-chrome 240 | - linux-test-firefox 241 | 242 | - release: 243 | filters: 244 | branches: 245 | only: 246 | - master 247 | requires: 248 | - linux-test 249 | - linux-test-chrome 250 | - linux-test-firefox 251 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.github/workflows/add-issue-to-triage-board.yml: -------------------------------------------------------------------------------- 1 | name: 'Add issue/PR to Triage Board' 2 | on: 3 | issues: 4 | types: 5 | - opened 6 | pull_request_target: 7 | types: 8 | - opened 9 | jobs: 10 | add-to-triage-project-board: 11 | uses: cypress-io/cypress/.github/workflows/triage_add_to_project.yml@develop 12 | secrets: inherit 13 | -------------------------------------------------------------------------------- /.github/workflows/chrome-docker.yml: -------------------------------------------------------------------------------- 1 | name: Chrome Docker 2 | 3 | on: [push, workflow_dispatch] 4 | 5 | jobs: 6 | # run Chrome inside a Docker container 7 | chrome: 8 | runs-on: ubuntu-24.04 9 | # https://github.com/cypress-io/cypress-docker-images 10 | container: cypress/browsers:22.15.0 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | - name: Chrome 15 | uses: cypress-io/github-action@v6 16 | timeout-minutes: 10 17 | with: 18 | build: npm run build 19 | start: npm start 20 | browser: chrome 21 | -------------------------------------------------------------------------------- /.github/workflows/chrome.yml: -------------------------------------------------------------------------------- 1 | name: Chrome 2 | 3 | on: [push, workflow_dispatch] 4 | 5 | jobs: 6 | chrome: 7 | runs-on: ubuntu-24.04 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v4 11 | - name: Set up Node.js 12 | uses: actions/setup-node@v4 13 | with: 14 | node-version-file: .node-version 15 | - name: Chrome 16 | uses: cypress-io/github-action@v6 17 | timeout-minutes: 10 18 | with: 19 | build: npm run build 20 | start: npm start 21 | browser: chrome 22 | -------------------------------------------------------------------------------- /.github/workflows/parallel.yml: -------------------------------------------------------------------------------- 1 | # GitHub Actions 2 | # https://docs.github.com/en/actions/using-workflows 3 | # See also .github/workflows/using-action.yml in this repository 4 | # for an example of using the Cypress GitHub JavaScript action 5 | # https://github.com/cypress-io/github-action 6 | # 7 | name: Cypress parallel tests 8 | 9 | on: [push, workflow_dispatch] 10 | 11 | jobs: 12 | install: 13 | name: Install npm and Cypress 14 | runs-on: ubuntu-24.04 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | # install a specific version of Node using 19 | # https://github.com/actions/setup-node 20 | - name: Set up Node.js 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version-file: .node-version 24 | 25 | # just so we learn about available environment variables GitHub provides 26 | - name: Print CI env variables 27 | run: | 28 | npm i -g @bahmutov/print-env@2 29 | print-env GITHUB BUILD ACTIONS || true 30 | 31 | # Restore the previous npm modules and Cypress binary archives. 32 | # Any updated archives will be saved automatically after the entire 33 | # workflow successfully finishes. 34 | # See https://github.com/actions/cache 35 | # we use exact restore key to avoid npm module snowballing 36 | # https://glebbahmutov.com/blog/do-not-let-npm-cache-snowball/ 37 | - name: Cache central npm modules 38 | uses: actions/cache@v4 39 | with: 40 | path: ~/.npm 41 | key: ${{ runner.os }}-node-${{ github.ref }}-${{ hashFiles('**/package-lock.json') }} 42 | restore-keys: | 43 | ${{ runner.os }}-node-${{ github.ref }}-${{ hashFiles('**/package-lock.json') }} 44 | 45 | # we use the exact restore key to avoid Cypress binary snowballing 46 | # https://glebbahmutov.com/blog/do-not-let-cypress-cache-snowball/ 47 | - name: Cache Cypress binary 48 | uses: actions/cache@v4 49 | with: 50 | path: ~/.cache/Cypress 51 | key: cypress-${{ runner.os }}-cypress-${{ github.ref }}-${{ hashFiles('**/package-lock.json') }} 52 | restore-keys: | 53 | cypress-${{ runner.os }}-cypress-${{ github.ref }}-${{ hashFiles('**/package-lock.json') }} 54 | 55 | # Cache local node_modules to pass to testing jobs 56 | - name: Cache local node_modules 57 | uses: actions/cache@v4 58 | with: 59 | path: node_modules 60 | key: ${{ runner.os }}-node-modules-${{ github.ref }}-${{ hashFiles('**/package-lock.json') }} 61 | restore-keys: | 62 | ${{ runner.os }}-node-modules-${{ github.ref }}- 63 | 64 | - name: install dependencies and verify Cypress 65 | env: 66 | # make sure every Cypress install prints minimal information 67 | CI: 1 68 | run: | 69 | npm ci 70 | npx cypress cache path 71 | npx cypress cache list 72 | npx cypress verify 73 | npx cypress info 74 | 75 | # Duplicate job definitions - GitHub YAML does not support 76 | # anchor definitions yet, thus we cannot put same steps into a template object yet 77 | test1: 78 | name: Cypress test 1 79 | runs-on: ubuntu-24.04 80 | needs: install 81 | steps: 82 | - uses: actions/checkout@v4 83 | 84 | # install a specific version of Node using 85 | # https://github.com/actions/setup-node 86 | - name: Set up Node.js 87 | uses: actions/setup-node@v4 88 | with: 89 | node-version-file: .node-version 90 | 91 | # Restore just local node_modules and the Cypress binary archives. 92 | - name: Cache Cypress binary 93 | uses: actions/cache@v4 94 | with: 95 | path: ~/.cache/Cypress 96 | key: cypress-${{ runner.os }}-cypress-${{ github.ref }}-${{ hashFiles('**/package-lock.json') }} 97 | restore-keys: | 98 | cypress-${{ runner.os }}-cypress-${{ github.ref }}-${{ hashFiles('**/package-lock.json') }} 99 | 100 | - name: Cache local node_modules 101 | uses: actions/cache@v4 102 | with: 103 | path: node_modules 104 | key: ${{ runner.os }}-node-modules-${{ github.ref }}-${{ hashFiles('**/package-lock.json') }} 105 | restore-keys: | 106 | ${{ runner.os }}-node-modules-${{ github.ref }}- 107 | 108 | # check the restored Cypress binary 109 | - name: Check binary 110 | run: | 111 | npx cypress cache path 112 | npx cypress cache list 113 | 114 | # Starts local server, then runs Cypress tests and records results on Cypress Cloud 115 | - name: Cypress tests 116 | run: | 117 | npm start & 118 | npx cypress run --record --parallel --group "Parallel 2x" 119 | env: 120 | # place your secret record key at 121 | # https://github.com/cypress-io/cypress-example-kitchensink/settings/secrets 122 | CYPRESS_RECORD_KEY: ${{ secrets.dashboardRecordKey }} 123 | TERM: xterm 124 | 125 | # Save screenshots as test artifacts 126 | # https://github.com/actions/upload-artifact 127 | - uses: actions/upload-artifact@v4 128 | # there might be no screenshots created when: 129 | # - there are no test failures 130 | # so only upload screenshots if previous step has failed 131 | if: failure() 132 | with: 133 | name: screenshots 134 | path: cypress/screenshots 135 | 136 | test2: 137 | name: Cypress test 2 138 | runs-on: ubuntu-24.04 139 | needs: install 140 | steps: 141 | - uses: actions/checkout@v4 142 | 143 | # install a specific version of Node using 144 | # https://github.com/actions/setup-node 145 | - name: Set up Node.js 146 | uses: actions/setup-node@v4 147 | with: 148 | node-version-file: .node-version 149 | 150 | # Restore just local node_modules and the Cypress binary archives. 151 | - name: Cache Cypress binary 152 | uses: actions/cache@v4 153 | with: 154 | path: ~/.cache/Cypress 155 | key: cypress-${{ runner.os }}-cypress-${{ github.ref }}-${{ hashFiles('**/package-lock.json') }} 156 | restore-keys: | 157 | cypress-${{ runner.os }}-cypress-${{ github.ref }}- 158 | 159 | - name: Cache local node_modules 160 | uses: actions/cache@v4 161 | with: 162 | path: node_modules 163 | key: ${{ runner.os }}-node-modules-${{ github.ref }}-${{ hashFiles('**/package-lock.json') }} 164 | restore-keys: | 165 | ${{ runner.os }}-node-modules- 166 | 167 | # check the restored Cypress binary 168 | - name: Check binary 169 | run: | 170 | npx cypress cache path 171 | npx cypress cache list 172 | 173 | # Starts local server, then runs Cypress tests and records results on Cypress Cloud 174 | - name: Cypress tests 175 | run: | 176 | npm start & 177 | npx cypress run --record --parallel --group "Parallel 2x" 178 | env: 179 | # place your secret record key at 180 | # https://github.com/cypress-io/cypress-example-kitchensink/settings/secrets 181 | CYPRESS_RECORD_KEY: ${{ secrets.dashboardRecordKey }} 182 | TERM: xterm 183 | 184 | # Save screenshots as test artifacts 185 | # https://github.com/actions/upload-artifact 186 | - uses: actions/upload-artifact@v4 187 | # there might be no screenshots created when: 188 | # - there are no test failures 189 | # so only upload screenshots if previous step has failed 190 | if: failure() 191 | with: 192 | name: screenshots 193 | path: cypress/screenshots 194 | -------------------------------------------------------------------------------- /.github/workflows/semantic-pull-request.yml: -------------------------------------------------------------------------------- 1 | name: "Semantic Pull Request" 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | 10 | jobs: 11 | main: 12 | name: Lint Title 13 | runs-on: ubuntu-latest 14 | steps: 15 | # use a fork of the GitHub action - we cannot pull in untrusted third party actions 16 | # see https://github.com/cypress-io/cypress/pull/20091#discussion_r801799647 17 | - uses: cypress-io/action-semantic-pull-request@v4 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | with: 21 | validateSingleCommit: true 22 | -------------------------------------------------------------------------------- /.github/workflows/single.yml: -------------------------------------------------------------------------------- 1 | # GitHub Actions 2 | # https://docs.github.com/en/actions/using-workflows 3 | name: Cypress single tests 4 | 5 | on: [push, workflow_dispatch] 6 | 7 | jobs: 8 | test1: 9 | name: Cypress test 10 | runs-on: ubuntu-24.04 11 | steps: 12 | - uses: actions/checkout@v4 13 | 14 | # install a specific version of Node using 15 | # https://github.com/actions/setup-node 16 | - name: Set up Node.js 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version-file: .node-version 20 | 21 | # just so we learn about available environment variables GitHub provides 22 | - name: Print env variables 23 | run: | 24 | npm i -g @bahmutov/print-env 25 | print-env GITHUB 26 | 27 | # Restore the previous npm modules and Cypress binary archives. 28 | # In case there's no previous cache the packages will be downloaded 29 | # and saved automatically after the entire workflow successfully finishes. 30 | # See https://github.com/actions/cache 31 | - name: Cache node modules 32 | uses: actions/cache@v4 33 | with: 34 | path: ~/.npm 35 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 36 | 37 | - name: Cache Cypress binary 38 | uses: actions/cache@v4 39 | with: 40 | path: ~/.cache/Cypress 41 | key: cypress-${{ runner.os }}-cypress-${{ hashFiles('**/package-lock.json') }} 42 | 43 | - name: install dependencies and verify Cypress 44 | env: 45 | # make sure every Cypress install prints minimal information 46 | CI: 1 47 | # print Cypress and OS info 48 | run: | 49 | npm ci 50 | npx cypress verify 51 | npx cypress info 52 | npx cypress version 53 | npx cypress version --component package 54 | npx cypress version --component binary 55 | npx cypress version --component electron 56 | npx cypress version --component node 57 | 58 | # Starts local server, then runs Cypress tests and records results on Cypress Cloud 59 | - name: Cypress tests 60 | run: npm run test:ci:record 61 | env: 62 | # place your secret record key at 63 | # https://github.com/cypress-io/cypress-example-kitchensink/settings/secrets 64 | CYPRESS_RECORD_KEY: ${{ secrets.dashboardRecordKey }} 65 | TERM: xterm 66 | 67 | # Save screenshots as test artifacts 68 | # https://github.com/actions/upload-artifact 69 | - uses: actions/upload-artifact@v4 70 | # there might be no screenshots created when: 71 | # - there are no test failures 72 | # so only upload screenshots if previous step has failed 73 | if: failure() 74 | with: 75 | name: screenshots 76 | path: cypress/screenshots 77 | -------------------------------------------------------------------------------- /.github/workflows/triage_closed_issue_comment.yml: -------------------------------------------------------------------------------- 1 | name: 'Handle Comment Workflow' 2 | on: 3 | issue_comment: 4 | types: 5 | - created 6 | jobs: 7 | closed-issue-comment: 8 | uses: cypress-io/cypress/.github/workflows/triage_handle_new_comments.yml@develop 9 | secrets: inherit 10 | -------------------------------------------------------------------------------- /.github/workflows/using-action.yml: -------------------------------------------------------------------------------- 1 | # Note: make sure to use the same version of cypress-io/github-action 2 | # in all jobs, otherwise the last version wins I believe 3 | name: Using Cypress GH Action 4 | 5 | on: [push, workflow_dispatch] 6 | 7 | jobs: 8 | single-run: 9 | runs-on: ubuntu-24.04 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | - name: Set up Node.js 14 | uses: actions/setup-node@v4 15 | with: 16 | node-version-file: .node-version 17 | - name: Cypress run 18 | uses: cypress-io/github-action@v6 19 | timeout-minutes: 10 20 | with: 21 | build: npm run build 22 | start: npm start 23 | 24 | parallel-runs: 25 | name: Parallel 4x 26 | runs-on: ubuntu-24.04 27 | strategy: 28 | # when one test fails, DO NOT cancel the other 29 | # containers, because this will kill Cypress processes 30 | # leaving the Cypress Cloud dashboard hanging ... 31 | # https://github.com/cypress-io/github-action/issues/48 32 | fail-fast: false 33 | matrix: 34 | # Run 4 copies of the current job in parallel. 35 | # The name of the array (containers) and 36 | # the actual items in the array (1, 2, 3, 4) do not matter. 37 | # Based on the array, GitHub Actions will create 38 | # 4 independently running parallel jobs 39 | # (see https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs). 40 | # Cypress Cloud load-balances the Cypress tests across the GitHub Actions jobs. 41 | containers: [1, 2, 3, 4] 42 | steps: 43 | - name: Checkout 44 | uses: actions/checkout@v4 45 | - name: Set up Node.js 46 | uses: actions/setup-node@v4 47 | with: 48 | node-version-file: .node-version 49 | # because of "record" and "parallel" parameters 50 | # these containers will load balance all found tests among themselves 51 | - name: run tests 52 | uses: cypress-io/github-action@v6 53 | timeout-minutes: 5 54 | with: 55 | record: true 56 | parallel: true 57 | group: GH Action parallel 58 | start: npm start 59 | env: 60 | # pass the Dashboard record key as an environment variable 61 | CYPRESS_RECORD_KEY: ${{ secrets.dashboardRecordKey }} 62 | 63 | parallel-runs-across-platforms: 64 | name: every OS 65 | strategy: 66 | # when one test fails, DO NOT cancel the other 67 | # containers, because this will kill Cypress processes 68 | # leaving the Cypress Cloud dashboard hanging ... 69 | # https://github.com/cypress-io/github-action/issues/48 70 | fail-fast: false 71 | matrix: 72 | # run 2 copies of the current job in parallel 73 | # and they will load balance all specs 74 | os: ['ubuntu-24.04', 'windows-latest', 'macos-latest'] 75 | containers: [1, 2] 76 | runs-on: ${{ matrix.os }} 77 | steps: 78 | - name: Checkout 79 | uses: actions/checkout@v4 80 | - name: Set up Node.js 81 | uses: actions/setup-node@v4 82 | with: 83 | node-version-file: .node-version 84 | # because of "record" and "parallel" parameters 85 | # these containers will load balance all found tests among themselves 86 | - name: run tests 87 | uses: cypress-io/github-action@v6 88 | timeout-minutes: 10 89 | with: 90 | record: true 91 | parallel: true 92 | group: Parallel 2x on ${{ matrix.os }} 93 | # on Mac and Linux we can use "npm start" 94 | start: npm start 95 | # but for this particular project on Windows we need a different start command 96 | start-windows: npm run start 97 | env: 98 | # pass the Cypress Cloud (dashboard) record key as an environment variable 99 | CYPRESS_RECORD_KEY: ${{ secrets.dashboardRecordKey }} 100 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | *.aes 4 | cypress/downloads 5 | cypress/screenshots 6 | cypress/videos 7 | cypress/logs 8 | cypress/fixtures/profile.json 9 | cypress/fixtures/users.json 10 | .history 11 | .vscode 12 | env 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # first, install Cypress, then run all tests (in parallel) 2 | stages: 3 | - build 4 | - test 5 | 6 | # to cache both npm modules and Cypress binary we use environment variables 7 | # to point at the folders we can list as paths in "cache" job settings 8 | variables: 9 | npm_config_cache: "$CI_PROJECT_DIR/.npm" 10 | CYPRESS_CACHE_FOLDER: "$CI_PROJECT_DIR/cache/Cypress" 11 | 12 | # cache using branch name 13 | # https://gitlab.com/help/ci/caching/index.md 14 | cache: 15 | key: ${CI_COMMIT_REF_SLUG} 16 | paths: 17 | - .npm 18 | - cache/Cypress 19 | - node_modules 20 | 21 | # this job installs npm dependencies and Cypress 22 | install: 23 | image: cypress/base:22.15.0 24 | stage: build 25 | 26 | script: 27 | - npm ci 28 | # check Cypress binary path and cached versions 29 | # useful to make sure we are not carrying around old versions 30 | - npx cypress cache path 31 | - npx cypress cache list 32 | - npx print-env CI 33 | - npm run cy:verify 34 | - npm run cy:info 35 | 36 | # all jobs that actually run tests can use the same definition 37 | .job_template: 38 | image: cypress/base:22.15.0 39 | stage: test 40 | script: 41 | # print CI environment variables for reference 42 | - npx print-env CI 43 | # start the server in the background 44 | - npm run start & 45 | # run Cypress test in load balancing mode 46 | - npm run e2e:record -- --parallel --group "electrons on GitLab CI" 47 | artifacts: 48 | when: always 49 | paths: 50 | - cypress/screenshots/**/*.png 51 | expire_in: 1 day 52 | 53 | # actual job definitions 54 | # all steps are the same, they come from the template above 55 | electrons-1: 56 | extends: .job_template 57 | electrons-2: 58 | extends: .job_template 59 | electrons-3: 60 | extends: .job_template 61 | electrons-4: 62 | extends: .job_template 63 | electrons-5: 64 | extends: .job_template 65 | -------------------------------------------------------------------------------- /.husky/install.mjs: -------------------------------------------------------------------------------- 1 | // install.mjs 2 | // 3 | // See "CI server and Docker" in 4 | // https://typicode.github.io/husky/how-to.html#ci-server-and-docker 5 | // to avoid permissions issue with npm ci in a Docker container 6 | // 7 | const isCi = process.env.CI !== undefined 8 | if (!isCi) { 9 | const husky = (await import('husky')).default 10 | console.log(husky()) 11 | } 12 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npm run lint 2 | npm run lint:yaml 3 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 22 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | package-lock=true 3 | -------------------------------------------------------------------------------- /.release.json: -------------------------------------------------------------------------------- 1 | { 2 | "commitMessage": "release %s", 3 | "tagAnnotation": "release %s", 4 | "github": { 5 | "release": false, 6 | "releaseName": "release %s" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.semaphore/semaphore.yml: -------------------------------------------------------------------------------- 1 | # copied from https://docs.semaphoreci.com/article/50-pipeline-yaml 2 | # NodeJS and JavaScript docs 3 | # https://docs.semaphoreci.com/article/82-language-javascript-and-nodejs 4 | 5 | # Semaphore v2 API starts with version 1.0 :) 6 | version: v1.0 7 | name: Cypress example Kitchensink 8 | 9 | agent: 10 | # Dummy agent - the real agent is under the "task" 11 | # but seems Semaphore requires top level one 12 | machine: 13 | type: e1-standard-2 14 | 15 | blocks: 16 | - name: Mac E2E tests 17 | # disable Mac tests with built-in Electron 18 | # until the following issue is resolved 19 | # https://github.com/cypress-io/cypress-example-kitchensink/issues/269 20 | # probably by upgrading Electron 21 | # https://github.com/renderedtext/when#skip-block-exection 22 | # still broken in Cypress v3.7.0 23 | skip: 24 | when: "true" 25 | 26 | task: 27 | agent: 28 | machine: 29 | type: a1-standard-4 30 | 31 | prologue: 32 | commands: 33 | - checkout 34 | - node --version 35 | - npm --version 36 | - npm ci 37 | # verify the Cypress test binary so its check status is cached 38 | - npm run cy:verify 39 | - npx cypress cache path 40 | - npx cypress cache list 41 | 42 | # prints SEMAPHORE_* environment variables 43 | - npm run print-env -- SEMAPHORE 44 | jobs: 45 | - name: Cypress E2E 46 | commands: 47 | # prints SEMAPHORE_* environment variables 48 | - npm run print-env -- SEMAPHORE 49 | - npm run e2e 50 | 51 | - name: Linux E2E tests 52 | task: 53 | agent: 54 | machine: 55 | type: e1-standard-2 56 | 57 | # see https://docs.semaphoreci.com/article/66-environment-variables-and-secrets 58 | secrets: 59 | # we have created new secrets object at https://cypress-io.semaphoreci.com/secrets/new 60 | # for this organization. In this job we can now access CYPRESS_RECORD_KEY 61 | - name: dashboard 62 | 63 | env_vars: 64 | # skip Puppeteer download, only needed for Netlify build 65 | - name: PUPPETEER_SKIP_DOWNLOAD 66 | value: "1" 67 | 68 | # common commands that should be done before each E2E test command 69 | prologue: 70 | commands: 71 | - nvm install 22 72 | - npm install -g npm 73 | - checkout 74 | 75 | # restore previously cached files if any 76 | # re-install dependencies if package.json or this semaphore YML file changes 77 | - cache restore npm-$SEMAPHORE_GIT_BRANCH-$(checksum package-lock.json)-$(checksum .semaphore/semaphore.yml) 78 | - cache restore cypress-$SEMAPHORE_GIT_BRANCH-$(checksum package-lock.json)-$(checksum .semaphore/semaphore.yml) 79 | 80 | - npm ci 81 | # verify the Cypress test binary so its check status is cached 82 | - npm run cy:verify 83 | # Cache npm dependencies and Cypress binary 84 | - cache store npm-$SEMAPHORE_GIT_BRANCH-$(checksum package-lock.json)-$(checksum .semaphore/semaphore.yml) ~/.npm 85 | - cache store cypress-$SEMAPHORE_GIT_BRANCH-$(checksum package-lock.json)-$(checksum .semaphore/semaphore.yml) ~/.cache/Cypress 86 | 87 | # prints SEMAPHORE_* environment variables 88 | - npm run print-env -- SEMAPHORE 89 | # finally, build the web application and run end-to-end tests 90 | - npm run build 91 | # start the web application server in the background 92 | - npm run start & 93 | 94 | jobs: 95 | # all "prologue" commands have finished by now 96 | # and we can define a single job to execute in parallel on N machines 97 | # Cypress recognizes Semaphore environment variables 98 | # in order to link these separate steps into a single run. 99 | # see Cypress https://on.cypress.io/parallelization 100 | - name: Cypress E2E 101 | parallelism: 3 102 | commands: 103 | - npx cypress run --record --parallel --group "Semaphore 3x" 104 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | env: 3 | # skip Puppeteer download, only needed for Netlify build 4 | - PUPPETEER_SKIP_DOWNLOAD=1 5 | 6 | node_js: 7 | - 22 8 | 9 | cache: 10 | # cache both npm modules and Cypress binary 11 | directories: 12 | - ~/.npm 13 | - ~/.cache 14 | override: 15 | - npm ci 16 | - npm run cy:verify 17 | - npm run cy:info 18 | 19 | defaults: &defaults 20 | script: 21 | # ## print all Travis environment variables for debugging 22 | - npx print-env TRAVIS 23 | - npm run start & 24 | - npm run cy:run -- --record --parallel --group $STAGE_NAME 25 | # after all tests finish running we need 26 | # to kill all background jobs (like "npm start &") 27 | - kill $(jobs -p) || true 28 | 29 | jobs: 30 | include: 31 | # we have multiple jobs to execute using just a single stage 32 | # but we can pass group name via environment variable to Cypress test runner 33 | - stage: test 34 | env: 35 | - STAGE_NAME="1x-electron on Travis CI" 36 | <<: *defaults 37 | # run tests in parallel by including several test jobs with same name variable 38 | - stage: test 39 | env: 40 | - STAGE_NAME="4x-electron on Travis CI" 41 | <<: *defaults 42 | - stage: test 43 | env: 44 | - STAGE_NAME="4x-electron on Travis CI" 45 | <<: *defaults 46 | - stage: test 47 | env: 48 | - STAGE_NAME="4x-electron on Travis CI" 49 | <<: *defaults 50 | - stage: test 51 | env: 52 | - STAGE_NAME="4x-electron on Travis CI" 53 | <<: *defaults 54 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "standard.enable": false, 3 | "eslint.enable": true, 4 | "typescript.surveys.enabled": false, 5 | "editor.formatOnSave": false 6 | } 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This repo contains the source code that is required to build [https://example.cypress.io](https://example.cypress.io). 4 | 5 | This repo is required by the [example](https://github.com/cypress-io/cypress/tree/develop/packages/example) package of Cypress main code base. 6 | 7 | ## Deployment 8 | 9 | 1. After landing commits with features or fixes, new versions of this npm package are published automatically using `semantic-release`. 10 | 11 | 2. Now the [example](https://github.com/cypress-io/cypress/tree/develop/packages/example) package in Cypress' main code base must be updated. [Follow these instructions](https://github.com/cypress-io/cypress/blob/develop/packages/example/README.md#updating-the-example-app) to update the `example` app. 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # This Docker file is for building this project on Codeship Pro 2 | # https://documentation.codeship.com/pro/languages-frameworks/nodejs/ 3 | 4 | # use Cypress provided image with all dependencies included 5 | FROM cypress/base:22.15.0 6 | RUN node --version 7 | RUN npm --version 8 | WORKDIR /home/node/app 9 | # copy our test application 10 | COPY package.json package-lock.json ./ 11 | COPY app ./app 12 | COPY serve.json ./ 13 | COPY scripts ./scripts 14 | # copy Cypress tests 15 | COPY cypress.config.js cypress ./ 16 | COPY cypress ./cypress 17 | 18 | # avoid many lines of progress bars during install 19 | # https://github.com/cypress-io/cypress/issues/1243 20 | ENV CI=1 21 | 22 | # install npm dependencies and Cypress binary 23 | RUN npm ci 24 | # check if the binary was installed successfully 25 | RUN npx cypress verify 26 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | // Example Jenkins pipeline with Cypress end-to-end tests running in parallel on 2 workers 2 | // Pipeline syntax from https://jenkins.io/doc/book/pipeline/ 3 | 4 | // Setup: 5 | // before starting Jenkins, I have created several volumes to cache 6 | // Jenkins configuration, npm modules and Cypress binary 7 | 8 | // docker volume create jenkins-data 9 | // docker volume create npm-cache 10 | // docker volume create cypress-cache 11 | 12 | // Start Jenkins command line by line: 13 | // - run as "root" user (insecure, contact your admin to configure user and groups!) 14 | // - run Docker in disconnected mode 15 | // - name running container "blue-ocean" 16 | // - map port 8080 with Jenkins UI 17 | // - map volumes for Jenkins data, npm and Cypress caches 18 | // - pass Docker socket which allows Jenkins to start worker containers 19 | // - download and execute the latest BlueOcean Docker image 20 | 21 | // docker run \ 22 | // -u root \ 23 | // -d \ 24 | // --name blue-ocean \ 25 | // -p 8080:8080 \ 26 | // -v jenkins-data:/var/jenkins_home \ 27 | // -v npm-cache:/root/.npm \ 28 | // -v cypress-cache:/root/.cache \ 29 | // -v /var/run/docker.sock:/var/run/docker.sock \ 30 | // jenkinsci/blueocean:latest 31 | 32 | // If you start for the very first time, inspect the logs from the running container 33 | // to see Administrator password - you will need it to configure Jenkins via localhost:8080 UI 34 | // docker logs blue-ocean 35 | 36 | pipeline { 37 | agent { 38 | // this image provides everything needed to run Cypress 39 | docker { 40 | image 'cypress/base:22.15.0' 41 | } 42 | } 43 | 44 | stages { 45 | // first stage installs node dependencies and Cypress binary 46 | stage('build') { 47 | steps { 48 | // there a few default environment variables on Jenkins 49 | // on local Jenkins machine (assuming port 8080) see 50 | // http://localhost:8080/pipeline-syntax/globals#env 51 | echo "Running build ${env.BUILD_ID} on ${env.JENKINS_URL}" 52 | sh 'npm ci' 53 | sh 'npm run cy:verify' 54 | } 55 | } 56 | 57 | stage('start local server') { 58 | steps { 59 | // start local server in the background 60 | // we will shut it down in "post" command block 61 | sh 'nohup npm run start &' 62 | } 63 | } 64 | 65 | // this stage runs end-to-end tests, and each agent uses the workspace 66 | // from the previous stage 67 | stage('cypress parallel tests') { 68 | environment { 69 | // we will be recording test results on Cypress Cloud 70 | // to record we need to set an environment variable 71 | // we can load the record key variable from credentials store 72 | // see https://jenkins.io/doc/book/using/using-credentials/ 73 | CYPRESS_RECORD_KEY = credentials('cypress-example-kitchensink-record-key') 74 | // because parallel steps share the workspace they might race to delete 75 | // screenshots folders. Tell Cypress not to delete these folders 76 | CYPRESS_trashAssetsBeforeRuns = 'false' 77 | } 78 | 79 | // https://jenkins.io/doc/book/pipeline/syntax/#parallel 80 | parallel { 81 | // start several test jobs in parallel, and they all 82 | // will use Cypress Cloud to load balance any found spec files 83 | stage('tester A') { 84 | steps { 85 | echo "Running build ${env.BUILD_ID}" 86 | sh "npm run e2e:record:parallel" 87 | } 88 | } 89 | 90 | // second tester runs the same command 91 | stage('tester B') { 92 | steps { 93 | echo "Running build ${env.BUILD_ID}" 94 | sh "npm run e2e:record:parallel" 95 | } 96 | } 97 | } 98 | 99 | } 100 | } 101 | 102 | post { 103 | // shutdown the server running in the background 104 | always { 105 | echo 'Stopping local server' 106 | sh 'pkill -f http-server' 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Cypress.io, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /amplify.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | frontend: 3 | phases: 4 | preBuild: 5 | commands: 6 | - npm install 7 | build: 8 | commands: 9 | - npm run build 10 | artifacts: 11 | baseDirectory: app 12 | files: 13 | - "**/*" 14 | cache: 15 | paths: 16 | - node_modules/**/* 17 | test: 18 | artifacts: 19 | baseDirectory: cypress 20 | configFilePath: "**/mochawesome.json" 21 | files: 22 | - "**/*.png" 23 | - "**/*.mp4" 24 | phases: 25 | preTest: 26 | commands: 27 | - npm install 28 | - npm install wait-on 29 | - npm install mocha mochawesome mochawesome-merge mochawesome-report-generator 30 | - "npm start & npx wait-on http://127.0.0.1:8080" 31 | test: 32 | commands: 33 | - 'npx cypress run --reporter mochawesome --reporter-options "reportDir=cypress/report/mochawesome-report,overwrite=false,html=false,json=true,timestamp=mmddyyyy_HHMMss"' 34 | postTest: 35 | commands: 36 | - npx mochawesome-merge@4 cypress/report/mochawesome-report/*.json > cypress/report/mochawesome.json 37 | -------------------------------------------------------------------------------- /app/assets/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 0; 3 | font-family: 'Fira Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 4 | } 5 | 6 | h4 { 7 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 8 | font-size: 22px; 9 | } 10 | 11 | code { 12 | font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; 13 | } 14 | 15 | pre { 16 | font-size: 13px; 17 | border: 0; 18 | padding: 16px; 19 | background: #f7f7f7; 20 | border-radius: 3px; 21 | box-shadow: 0 0 0 0 #fff; 22 | word-break: normal; 23 | word-wrap: normal; 24 | } 25 | 26 | table { 27 | background-color: #fff; 28 | } 29 | 30 | pre code { 31 | font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; 32 | padding: 0; 33 | } 34 | 35 | canvas { 36 | background-color: #fff; 37 | border: 1px solid #ddd; 38 | cursor: pointer; 39 | } 40 | 41 | .well { 42 | margin-top: 75px; 43 | margin-bottom: 0; 44 | border: 0; 45 | box-shadow: 0 0 0 #fff; 46 | -webkit-box-shadow: 0 0 0 #fff; 47 | } 48 | 49 | /* Just like well, but for second row, with just a single line of text */ 50 | .well-1-line { 51 | margin-top: 2em; 52 | } 53 | 54 | .placeholder { 55 | width: 100px; 56 | height: 100px; 57 | background: #69f; 58 | } 59 | 60 | /*---------nav----------*/ 61 | 62 | .navbar { 63 | border-radius: 0; 64 | margin-bottom: 0; 65 | } 66 | 67 | .navbar-inverse .navbar-brand { 68 | color: #fff; 69 | } 70 | 71 | /*---------banner----------*/ 72 | 73 | .banner { 74 | background-color: #00bf88; 75 | color: #fff; 76 | padding: 25px 0 35px; 77 | } 78 | 79 | .banner p, 80 | .banner-alt p { 81 | font-size: 18px; 82 | } 83 | 84 | .banner a { 85 | color: #fff; 86 | border-bottom: 1px dashed #fff; 87 | } 88 | 89 | .banner-alt { 90 | background-color: #eef1f4; 91 | color: #6b707c; 92 | padding: 25px 0 35px; 93 | } 94 | 95 | .banner-alt h2 { 96 | color: #555; 97 | } 98 | 99 | .home-list { 100 | list-style: none; 101 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 102 | margin: 35px 0; 103 | padding-left: 0; 104 | line-height: 28px; 105 | } 106 | 107 | li.header { 108 | font-weight: bold; 109 | list-style: none; 110 | } 111 | 112 | .home-list > li { 113 | margin-top: 25px; 114 | padding-bottom: 20px; 115 | border-bottom: 1px solid #ddd; 116 | font-size: 20px; 117 | font-weight: bold; 118 | } 119 | 120 | .home-list > li:last-child { 121 | border-bottom: 0; 122 | } 123 | 124 | .home-list > li > ul { 125 | list-style: none; 126 | padding-left: 0px; 127 | } 128 | 129 | .home-list > li > ul > li { 130 | font-size: 16px; 131 | color: #666; 132 | } 133 | 134 | .home-list > li > ul > li > a { 135 | font-weight: normal; 136 | } 137 | 138 | .content-container { 139 | margin-top: 35px; 140 | } 141 | 142 | #query-form { 143 | border: 1px solid #ddd; 144 | padding: 10px; 145 | } 146 | 147 | .action-focus.focus { 148 | border: 5px solid orange; 149 | } 150 | 151 | .action-blur.error { 152 | border: 5px solid red; 153 | } 154 | 155 | .action-div, .rightclick-action-div { 156 | cursor: pointer; 157 | } 158 | 159 | .action-opacity { 160 | position: relative; 161 | margin-top: 50px; 162 | } 163 | 164 | .action-opacity .opacity-cover { 165 | text-align: center; 166 | line-height: 50px; 167 | color: #333; 168 | font-weight: bold; 169 | font-size: 25px; 170 | position: absolute; 171 | top: -5px; 172 | left: -5px; 173 | width: 235px; 174 | height: 60px; 175 | background-color: red; 176 | opacity: 0.7; 177 | box-shadow: 0 2px 7px #000; 178 | } 179 | 180 | .utlity-jquery li.active { 181 | background-color: #0068a2; 182 | color: white; 183 | } 184 | 185 | .network-btn, 186 | .network-post, 187 | .network-put { 188 | margin-bottom: 20px; 189 | } 190 | 191 | #scroll-horizontal, 192 | #scroll-vertical, 193 | #scroll-both, 194 | #scrollable-horizontal, 195 | #scrollable-vertical, 196 | #scrollable-both { 197 | background-color: #ddd; 198 | border: 1px solid #777; 199 | border-radius: 4px; 200 | margin-bottom: 15px; 201 | } 202 | 203 | #scrollable-horizontal ul, 204 | #scrollable-vertical ul, 205 | #scrollable-both ul { 206 | padding: 0; 207 | overflow: auto; 208 | } 209 | 210 | #scrollable-horizontal ul > li, 211 | #scrollable-vertical ul > li, 212 | #scrollable-both ul > li { 213 | list-style: none; 214 | margin: 20px; 215 | float: left; 216 | } 217 | 218 | #scrollable-horizontal ul > li { 219 | display: inline-block; 220 | } 221 | 222 | ::-webkit-scrollbar { 223 | -webkit-appearance: none; 224 | width: 12px; 225 | height: 12px; 226 | } 227 | 228 | ::-webkit-scrollbar-track { 229 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); 230 | border-radius: 4px; 231 | } 232 | 233 | ::-webkit-scrollbar-thumb { 234 | border-radius: 4px; 235 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.5); 236 | } 237 | 238 | /* 239 | 240 | code highlight styling 241 | 242 | */ 243 | 244 | .hljs { 245 | display: block; 246 | overflow-x: auto; 247 | color: #333; 248 | background: #f7f7f7; 249 | } 250 | 251 | .hljs-comment, 252 | .hljs-quote { 253 | color: #999; 254 | } 255 | 256 | .hljs-keyword, 257 | .hljs-selector-tag, 258 | .hljs-subst { 259 | color: #1d75b3; 260 | font-weight: normal; 261 | } 262 | 263 | .hljs-number, 264 | .hljs-literal, 265 | .hljs-variable, 266 | .hljs-template-variable, 267 | .hljs-tag .hljs-attr { 268 | color: #0086b3; 269 | font-weight: normal; 270 | } 271 | 272 | .hljs-string, 273 | .hljs-doctag { 274 | color: #183691; 275 | } 276 | 277 | .hljs-title, 278 | .hljs-section, 279 | .hljs-selector-id { 280 | color: #900; 281 | font-weight: normal; 282 | } 283 | 284 | .hljs-subst { 285 | font-weight: normal; 286 | } 287 | 288 | .hljs-type, 289 | .hljs-class .hljs-title { 290 | color: #458; 291 | font-weight: normal; 292 | } 293 | 294 | .hljs-tag, 295 | .hljs-name, 296 | .hljs-attribute { 297 | color: #000080; 298 | font-weight: normal; 299 | } 300 | 301 | .hljs-regexp, 302 | .hljs-link { 303 | color: #009926; 304 | } 305 | 306 | .hljs-symbol, 307 | .hljs-bullet { 308 | color: #990073; 309 | } 310 | 311 | .hljs-built_in, 312 | .hljs-builtin-name { 313 | color: #0086b3; 314 | } 315 | 316 | .hljs-meta { 317 | color: #999; 318 | font-weight: normal; 319 | } 320 | 321 | .hljs-deletion { 322 | background: #fdd; 323 | } 324 | 325 | .hljs-addition { 326 | background: #dfd; 327 | } 328 | 329 | .hljs-emphasis { 330 | font-style: normal; 331 | } 332 | 333 | .hljs-strong { 334 | font-weight: normal; 335 | } 336 | -------------------------------------------------------------------------------- /app/assets/css/vendor/fira.css: -------------------------------------------------------------------------------- 1 | @font-face{ 2 | font-family: 'Fira Sans'; 3 | src: url('../../fonts/eot/FiraSans-Regular.eot'); 4 | src: local('Fira Sans Regular'), 5 | url('../../fonts/eot/FiraSans-Regular.eot') format('embedded-opentype'), 6 | url('../../fonts/woff/FiraSans-Regular.woff') format('woff'), 7 | url('../../fonts/ttf/FiraSans-Regular.ttf') format('truetype'); 8 | font-weight: 400; 9 | font-style: normal; 10 | } 11 | 12 | @font-face{ 13 | font-family: 'Fira Sans'; 14 | src: url('../../fonts/eot/FiraSans-Medium.eot'); 15 | src: local('Fira Sans Medium'), 16 | url('../../fonts/eot/FiraSans-Medium.eot') format('embedded-opentype'), 17 | url('../../fonts/woff/FiraSans-Medium.woff') format('woff'), 18 | url('../../fonts/ttf/FiraSans-Medium.ttf') format('truetype'); 19 | font-weight: 500; 20 | font-style: normal; 21 | } -------------------------------------------------------------------------------- /app/assets/fonts/eot/FiraSans-Medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypress-io/cypress-example-kitchensink/29a02fbbbcb89eb0c75e4dfaaad32b99e8c8dddd/app/assets/fonts/eot/FiraSans-Medium.eot -------------------------------------------------------------------------------- /app/assets/fonts/eot/FiraSans-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypress-io/cypress-example-kitchensink/29a02fbbbcb89eb0c75e4dfaaad32b99e8c8dddd/app/assets/fonts/eot/FiraSans-Regular.eot -------------------------------------------------------------------------------- /app/assets/fonts/ttf/FiraSans-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypress-io/cypress-example-kitchensink/29a02fbbbcb89eb0c75e4dfaaad32b99e8c8dddd/app/assets/fonts/ttf/FiraSans-Medium.ttf -------------------------------------------------------------------------------- /app/assets/fonts/ttf/FiraSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypress-io/cypress-example-kitchensink/29a02fbbbcb89eb0c75e4dfaaad32b99e8c8dddd/app/assets/fonts/ttf/FiraSans-Regular.ttf -------------------------------------------------------------------------------- /app/assets/fonts/woff/FiraSans-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypress-io/cypress-example-kitchensink/29a02fbbbcb89eb0c75e4dfaaad32b99e8c8dddd/app/assets/fonts/woff/FiraSans-Medium.woff -------------------------------------------------------------------------------- /app/assets/fonts/woff/FiraSans-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypress-io/cypress-example-kitchensink/29a02fbbbcb89eb0c75e4dfaaad32b99e8c8dddd/app/assets/fonts/woff/FiraSans-Regular.woff -------------------------------------------------------------------------------- /app/assets/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypress-io/cypress-example-kitchensink/29a02fbbbcb89eb0c75e4dfaaad32b99e8c8dddd/app/assets/img/favicon.ico -------------------------------------------------------------------------------- /app/assets/img/javascript-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypress-io/cypress-example-kitchensink/29a02fbbbcb89eb0c75e4dfaaad32b99e8c8dddd/app/assets/img/javascript-logo.png -------------------------------------------------------------------------------- /app/assets/js/scripts.js: -------------------------------------------------------------------------------- 1 | /* global hljs, $ */ 2 | 3 | // initialize highlight.js for JavaScript code highlighting 4 | hljs.initHighlightingOnLoad() 5 | 6 | $(() => { 7 | // initialize Bootstrap popovers 8 | $('[data-toggle="popover"]').popover() 9 | 10 | // begin: draw dots on canvas on mouse click --- 11 | let canvas = document.getElementById('action-canvas') 12 | 13 | let context 14 | 15 | context = typeof canvas !== 'undefined' && canvas !== null ? canvas.getContext('2d') : 0 16 | 17 | $('#action-canvas').on('click', (e) => { 18 | draw(e) 19 | }) 20 | 21 | function draw (e) { 22 | let pos = getMousePos(canvas, e) 23 | let posx = pos.x 24 | let posy = pos.y 25 | 26 | context.fillStyle = 'red' 27 | context.beginPath() 28 | context.arc(posx, posy, 5, 0, 2 * Math.PI) 29 | context.fill() 30 | } 31 | 32 | function getMousePos (canvas, evt) { 33 | let rect = canvas.getBoundingClientRect() 34 | 35 | return { 36 | x: evt.clientX - rect.left, 37 | y: evt.clientY - rect.top, 38 | } 39 | } 40 | // end ----------------------------------------- 41 | 42 | // listen to dblclick to demonstrate logic on double click command 43 | $('.action-div').on('dblclick', (e) => { 44 | $('.action-input-hidden').removeClass('hidden').focus() 45 | $(e.currentTarget).addClass('hidden') 46 | }) 47 | 48 | // listen to contextmenu to demonstrate logic on right click command 49 | $('.rightclick-action-div').on('contextmenu', (e) => { 50 | $('.rightclick-action-input-hidden').removeClass('hidden').focus() 51 | $(e.currentTarget).addClass('hidden') 52 | }) 53 | 54 | // listen to focus to demonstrate logic on focus command 55 | $('.action-focus').on('focus', (e) => { 56 | $(e.currentTarget).addClass('focus') 57 | $(e.currentTarget).prev().css('color', 'orange') 58 | }) 59 | 60 | // listen to blur to demonstrate logic on blur command 61 | $('.action-blur').on('blur', (e) => { 62 | $(e.currentTarget).addClass('error') 63 | $(e.currentTarget).prev().css('color', 'red') 64 | }) 65 | 66 | // listen to submit to demonstrate logic on submit command 67 | $('.action-form').on('submit', (e) => { 68 | e.preventDefault() 69 | 70 | $('

Your form has been submitted!

') 71 | .insertAfter(e.currentTarget) 72 | .css('color', '#20B520') 73 | }) 74 | 75 | // hide this div so we can invoke show later 76 | $('.connectors-div').hide() 77 | 78 | // listen to click on misc-table 79 | $('.misc-table tr').on('click', (e) => { 80 | $(e.currentTarget).addClass('info') 81 | }) 82 | 83 | // listen to click on button in .as-table 84 | $('.as-table .btn').on('click', (e) => { 85 | e.preventDefault() 86 | $(e.currentTarget).addClass('btn-success').text('Changed') 87 | }) 88 | 89 | // listen to input range for trigger command 90 | $('.trigger-input-range').on('change', (e) => { 91 | const $range = $(e.target) 92 | 93 | $range.next('p').text($range.val()) 94 | }) 95 | 96 | // begin: Handle our route listeners ------------- 97 | 98 | $('.network-btn').on('click', (e) => { 99 | e.preventDefault() 100 | getComment(e) 101 | }) 102 | 103 | $('.network-post').on('click', (e) => { 104 | e.preventDefault() 105 | postComment(e) 106 | }) 107 | 108 | $('.network-put').on('click', (e) => { 109 | e.preventDefault() 110 | putComment(e) 111 | }) 112 | 113 | $('.fixture-btn').on('click', (e) => { 114 | e.preventDefault() 115 | getComment(e) 116 | }) 117 | // end ----------------------------------------- 118 | 119 | // begin: Handle our route logic ------------- 120 | // we fetch all data from this REST json backend 121 | const root = 'https://jsonplaceholder.cypress.io' 122 | 123 | function getComment () { 124 | $.ajax({ 125 | url: `${root}/comments/1`, 126 | method: 'GET', 127 | }).then((data) => { 128 | $('.network-comment').text(data.body) 129 | }) 130 | } 131 | 132 | function postComment () { 133 | $.ajax({ 134 | url: `${root}/comments`, 135 | method: 'POST', 136 | data: { 137 | name: 'Using POST in cy.intercept()', 138 | email: 'hello@cypress.io', 139 | body: 'You can change the method used for cy.intercept() to be GET, POST, PUT, PATCH, or DELETE', 140 | }, 141 | }).then(() => { 142 | $('.network-post-comment').text('POST successful!') 143 | }) 144 | } 145 | 146 | function putComment () { 147 | $.ajax({ 148 | url: `${root}/comments/1`, 149 | method: 'PUT', 150 | data: { 151 | name: 'Using PUT in cy.intercept()', 152 | email: 'hello@cypress.io', 153 | body: 'You can change the method used for cy.intercept() to be GET, POST, PUT, PATCH, or DELETE', 154 | }, 155 | statusCode: { 156 | 404 (data) { 157 | $('.network-put-comment').text(data.responseJSON.error) 158 | }, 159 | }, 160 | }) 161 | } 162 | // end ----------------------------------------- 163 | 164 | $('.ls-btn').on('click', (e) => { 165 | e.preventDefault() 166 | populateStorage(e) 167 | }) 168 | 169 | // populate localStorage and sessionStorage to demonstrate cy.clearLocalStorage() 170 | function populateStorage () { 171 | localStorage.setItem('prop1', 'red') 172 | localStorage.setItem('prop2', 'blue') 173 | localStorage.setItem('prop3', 'magenta') 174 | 175 | sessionStorage.setItem('prop4', 'cyan') 176 | sessionStorage.setItem('prop5', 'yellow') 177 | sessionStorage.setItem('prop6', 'black') 178 | } 179 | 180 | // setting a cookie 181 | $('.set-a-cookie').on('click', (e) => { 182 | e.preventDefault() 183 | setCookies(e) 184 | }) 185 | 186 | // populate local cookie to demonstrate cy.clearCookies() 187 | function setCookies () { 188 | document.cookie = 'token=123ABC' 189 | } 190 | 191 | $('.utility-jquery li').on('click', (e) => { 192 | let $li = $(e.currentTarget) 193 | 194 | $li.addClass('active') 195 | }) 196 | 197 | $('#clock-div').on('click', (e) => { 198 | let $div = $(e.currentTarget) 199 | 200 | // seconds from the unix epoch 201 | $div.text(new Date().getTime() / 1000) 202 | }) 203 | 204 | $('#tick-div').on('click', (e) => { 205 | let $div = $(e.currentTarget) 206 | 207 | // seconds from the unix epoch 208 | $div.text(new Date().getTime() / 1000) 209 | }) 210 | }) 211 | -------------------------------------------------------------------------------- /app/assets/js/todo/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Forked from TodoMVC 3 | * https://todomvc.com 4 | * 5 | * MIT License © Addy Osmani, Sindre Sorhus, Pascal Hartig, Stephen Sawchuk 6 | */ 7 | 8 | /*global app, $on */ 9 | (function () { 10 | 'use strict' 11 | 12 | /** 13 | * Sets up a brand new Todo list. 14 | * 15 | * @param {string} name The name of your new to do list. 16 | */ 17 | function Todo (name) { 18 | this.storage = new app.Store(name) 19 | this.model = new app.Model(this.storage) 20 | this.template = new app.Template() 21 | this.view = new app.View(this.template) 22 | this.controller = new app.Controller(this.model, this.view) 23 | } 24 | 25 | let todo = new Todo('todos-vanillajs') 26 | 27 | function init () { 28 | setView() 29 | 30 | // initialize with 2 items 31 | todo.storage.findAll((data) => { 32 | if (!data.length) { 33 | todo.controller.addItem('Pay electric bill') 34 | todo.controller.addItem('Walk the dog') 35 | } 36 | }) 37 | } 38 | 39 | function setView () { 40 | todo.controller.setView(document.location.hash) 41 | } 42 | $on(window, 'load', init) 43 | $on(window, 'hashchange', setView) 44 | })() 45 | -------------------------------------------------------------------------------- /app/assets/js/todo/helpers.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Forked from TodoMVC 3 | * https://todomvc.com 4 | * 5 | * MIT License © Addy Osmani, Sindre Sorhus, Pascal Hartig, Stephen Sawchuk 6 | */ 7 | 8 | (function (window) { 9 | 'use strict' 10 | 11 | // Get element(s) by CSS selector: 12 | window.qs = function (selector, scope) { 13 | return (scope || document).querySelector(selector) 14 | } 15 | 16 | window.qsa = function (selector, scope) { 17 | return (scope || document).querySelectorAll(selector) 18 | } 19 | 20 | // addEventListener wrapper: 21 | window.$on = function (target, type, callback, useCapture) { 22 | target.addEventListener(type, callback, !!useCapture) 23 | } 24 | 25 | // Attach a handler to event for all elements that match the selector, 26 | // now or in the future, based on a root element 27 | window.$delegate = function (target, selector, type, handler) { 28 | function dispatchEvent (event) { 29 | let targetElement = event.target 30 | let potentialElements = window.qsa(selector, target) 31 | let hasMatch = Array.prototype.indexOf.call(potentialElements, targetElement) >= 0 32 | 33 | if (hasMatch) { 34 | handler.call(targetElement, event) 35 | } 36 | } 37 | 38 | // https://developer.mozilla.org/en-US/docs/Web/Events/blur 39 | let useCapture = type === 'blur' || type === 'focus' 40 | 41 | window.$on(target, type, dispatchEvent, useCapture) 42 | } 43 | 44 | // Find the element's parent with the given tag name: 45 | // $parent(qs('a'), 'div'); 46 | window.$parent = function (element, tagName) { 47 | if (!element.parentNode) { 48 | return 49 | } 50 | 51 | if (element.parentNode.tagName.toLowerCase() === tagName.toLowerCase()) { 52 | return element.parentNode 53 | } 54 | 55 | return window.$parent(element.parentNode, tagName) 56 | } 57 | 58 | // Allow for looping on nodes by chaining: 59 | // qsa('.foo').forEach(function () {}) 60 | NodeList.prototype.forEach = Array.prototype.forEach 61 | })(window) 62 | -------------------------------------------------------------------------------- /app/assets/js/todo/model.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Forked from TodoMVC 3 | * https://todomvc.com 4 | * 5 | * MIT License © Addy Osmani, Sindre Sorhus, Pascal Hartig, Stephen Sawchuk 6 | */ 7 | 8 | (function (window) { 9 | 'use strict' 10 | 11 | /** 12 | * Creates a new Model instance and hooks up the storage. 13 | * 14 | * @constructor 15 | * @param {object} storage A reference to the client side storage class 16 | */ 17 | function Model (storage) { 18 | this.storage = storage 19 | } 20 | 21 | /** 22 | * Creates a new todo model 23 | * 24 | * @param {string} [title] The title of the task 25 | * @param {function} [callback] The callback to fire after the model is created 26 | */ 27 | Model.prototype.create = function (title, callback) { 28 | title = title || '' 29 | callback = callback || function () {} 30 | 31 | let newItem = { 32 | title: title.trim(), 33 | completed: false, 34 | } 35 | 36 | this.storage.save(newItem, callback) 37 | } 38 | 39 | /** 40 | * Finds and returns a model in storage. If no query is given it'll simply 41 | * return everything. If you pass in a string or number it'll look that up as 42 | * the ID of the model to find. Lastly, you can pass it an object to match 43 | * against. 44 | * 45 | * @param {string|number|object} [query] A query to match models against 46 | * @param {function} [callback] The callback to fire after the model is found 47 | * 48 | * @example 49 | * model.read(1, func); // Will find the model with an ID of 1 50 | * model.read('1'); // Same as above 51 | * //Below will find a model with foo equalling bar and hello equalling world. 52 | * model.read({ foo: 'bar', hello: 'world' }); 53 | */ 54 | Model.prototype.read = function (query, callback) { 55 | let queryType = typeof query 56 | 57 | callback = callback || function () {} 58 | 59 | if (queryType === 'function') { 60 | callback = query 61 | 62 | return this.storage.findAll(callback) 63 | } 64 | 65 | if (queryType === 'string' || queryType === 'number') { 66 | query = parseInt(query, 10) 67 | this.storage.find({ id: query }, callback) 68 | } else { 69 | this.storage.find(query, callback) 70 | } 71 | } 72 | 73 | /** 74 | * Updates a model by giving it an ID, data to update, and a callback to fire when 75 | * the update is complete. 76 | * 77 | * @param {number} id The id of the model to update 78 | * @param {object} data The properties to update and their new value 79 | * @param {function} callback The callback to fire when the update is complete. 80 | */ 81 | Model.prototype.update = function (id, data, callback) { 82 | this.storage.save(data, callback, id) 83 | } 84 | 85 | /** 86 | * Removes a model from storage 87 | * 88 | * @param {number} id The ID of the model to remove 89 | * @param {function} callback The callback to fire when the removal is complete. 90 | */ 91 | Model.prototype.remove = function (id, callback) { 92 | this.storage.remove(id, callback) 93 | } 94 | 95 | /** 96 | * WARNING: Will remove ALL data from storage. 97 | * 98 | * @param {function} callback The callback to fire when the storage is wiped. 99 | */ 100 | Model.prototype.removeAll = function (callback) { 101 | this.storage.drop(callback) 102 | } 103 | 104 | /** 105 | * Returns a count of all todos 106 | */ 107 | Model.prototype.getCount = function (callback) { 108 | let todos = { 109 | active: 0, 110 | completed: 0, 111 | total: 0, 112 | } 113 | 114 | this.storage.findAll(function (data) { 115 | data.forEach(function (todo) { 116 | if (todo.completed) { 117 | todos.completed++ 118 | } else { 119 | todos.active++ 120 | } 121 | 122 | todos.total++ 123 | }) 124 | 125 | callback(todos) 126 | }) 127 | } 128 | 129 | // Export to window 130 | window.app = window.app || {} 131 | window.app.Model = Model 132 | })(window) 133 | -------------------------------------------------------------------------------- /app/assets/js/todo/store.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Forked from TodoMVC 3 | * https://todomvc.com 4 | * 5 | * MIT License © Addy Osmani, Sindre Sorhus, Pascal Hartig, Stephen Sawchuk 6 | */ 7 | 8 | /*jshint eqeqeq:false */ 9 | (function (window) { 10 | 'use strict' 11 | 12 | /** 13 | * Creates a new client side storage object and will create an empty 14 | * collection if no collection already exists. 15 | * 16 | * @param {string} name The name of our DB we want to use 17 | * @param {function} callback Our fake DB uses callbacks because in 18 | * real life you probably would be making AJAX calls 19 | */ 20 | function Store (name, callback) { 21 | callback = callback || function () {} 22 | 23 | this._dbName = name 24 | 25 | if (!localStorage.getItem(name)) { 26 | let todos = [] 27 | 28 | localStorage.setItem(name, JSON.stringify(todos)) 29 | } 30 | 31 | callback.call(this, JSON.parse(localStorage.getItem(name))) 32 | } 33 | 34 | /** 35 | * Finds items based on a query given as a JS object 36 | * 37 | * @param {object} query The query to match against (i.e. {foo: 'bar'}) 38 | * @param {function} callback The callback to fire when the query has 39 | * completed running 40 | * 41 | * @example 42 | * db.find({foo: 'bar', hello: 'world'}, function (data) { 43 | * // data will return any items that have foo: bar and 44 | * // hello: world in their properties 45 | * }); 46 | */ 47 | Store.prototype.find = function (query, callback) { 48 | if (!callback) { 49 | return 50 | } 51 | 52 | let todos = JSON.parse(localStorage.getItem(this._dbName)) 53 | 54 | callback.call(this, todos.filter(function (todo) { 55 | for (let q in query) { 56 | if (query[q] !== todo[q]) { 57 | return false 58 | } 59 | } 60 | 61 | return true 62 | })) 63 | } 64 | 65 | /** 66 | * Will retrieve all data from the collection 67 | * 68 | * @param {function} callback The callback to fire upon retrieving data 69 | */ 70 | Store.prototype.findAll = function (callback) { 71 | callback = callback || function () {} 72 | callback.call(this, JSON.parse(localStorage.getItem(this._dbName))) 73 | } 74 | 75 | /** 76 | * Will save the given data to the DB. If no item exists it will create a new 77 | * item, otherwise it'll simply update an existing item's properties 78 | * 79 | * @param {object} updateData The data to save back into the DB 80 | * @param {function} callback The callback to fire after saving 81 | * @param {number} id An optional param to enter an ID of an item to update 82 | */ 83 | Store.prototype.save = function (updateData, callback, id) { 84 | let todos = JSON.parse(localStorage.getItem(this._dbName)) 85 | 86 | callback = callback || function () {} 87 | 88 | // If an ID was actually given, find the item and update each property 89 | if (id) { 90 | for (let i = 0; i < todos.length; i++) { 91 | if (todos[i].id === id) { 92 | for (let key in updateData) { 93 | todos[i][key] = updateData[key] 94 | } 95 | break 96 | } 97 | } 98 | 99 | localStorage.setItem(this._dbName, JSON.stringify(todos)) 100 | callback.call(this, todos) 101 | } else { 102 | // Generate an ID 103 | updateData.id = new Date().getTime() 104 | 105 | todos.push(updateData) 106 | localStorage.setItem(this._dbName, JSON.stringify(todos)) 107 | callback.call(this, [updateData]) 108 | } 109 | } 110 | 111 | /** 112 | * Will remove an item from the Store based on its ID 113 | * 114 | * @param {number} id The ID of the item you want to remove 115 | * @param {function} callback The callback to fire after saving 116 | */ 117 | Store.prototype.remove = function (id, callback) { 118 | let todos = JSON.parse(localStorage.getItem(this._dbName)) 119 | 120 | for (let i = 0; i < todos.length; i++) { 121 | if (todos[i].id === id) { 122 | todos.splice(i, 1) 123 | break 124 | } 125 | } 126 | 127 | localStorage.setItem(this._dbName, JSON.stringify(todos)) 128 | callback.call(this, todos) 129 | } 130 | 131 | /** 132 | * Will drop all storage and start fresh 133 | * 134 | * @param {function} callback The callback to fire after dropping the data 135 | */ 136 | Store.prototype.drop = function (callback) { 137 | let todos = [] 138 | 139 | localStorage.setItem(this._dbName, JSON.stringify(todos)) 140 | callback.call(this, todos) 141 | } 142 | 143 | // Export to window 144 | window.app = window.app || {} 145 | window.app.Store = Store 146 | })(window) 147 | -------------------------------------------------------------------------------- /app/assets/js/todo/template.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Forked from TodoMVC 3 | * https://todomvc.com 4 | * 5 | * MIT License © Addy Osmani, Sindre Sorhus, Pascal Hartig, Stephen Sawchuk 6 | */ 7 | 8 | /*jshint laxbreak:true */ 9 | (function (window) { 10 | 'use strict' 11 | 12 | let htmlEscapes = { 13 | '&': '&', 14 | '<': '<', 15 | '>': '>', 16 | '"': '"', 17 | '\'': ''', 18 | '`': '`', 19 | } 20 | 21 | let escapeHtmlChar = function (chr) { 22 | return htmlEscapes[chr] 23 | } 24 | 25 | let reUnescapedHtml = /[&<>"'`]/g 26 | let reHasUnescapedHtml = new RegExp(reUnescapedHtml.source) 27 | 28 | let escape = function (string) { 29 | return (string && reHasUnescapedHtml.test(string)) 30 | ? string.replace(reUnescapedHtml, escapeHtmlChar) 31 | : string 32 | } 33 | 34 | /** 35 | * Sets up defaults for all the Template methods such as a default template 36 | * 37 | * @constructor 38 | */ 39 | function Template () { 40 | this.defaultTemplate 41 | = '
  • ' 42 | + '
    ' 43 | + '' 44 | + '' 45 | + '' 46 | + '
    ' 47 | + '
  • ' 48 | } 49 | 50 | /** 51 | * Creates an
  • HTML string and returns it for placement in your app. 52 | * 53 | * NOTE: In real life you should be using a templating engine such as Mustache 54 | * or Handlebars, however, this is a vanilla JS example. 55 | * 56 | * @param {object} data The object containing keys you want to find in the 57 | * template to replace. 58 | * @returns {string} HTML String of an
  • element 59 | * 60 | * @example 61 | * view.show({ 62 | * id: 1, 63 | * title: "Hello World", 64 | * completed: 0, 65 | * }); 66 | */ 67 | Template.prototype.show = function (data) { 68 | let i; let l 69 | let view = '' 70 | 71 | for (i = 0, l = data.length; i < l; i++) { 72 | let template = this.defaultTemplate 73 | let completed = '' 74 | let checked = '' 75 | 76 | if (data[i].completed) { 77 | completed = 'completed' 78 | checked = 'checked' 79 | } 80 | 81 | template = template.replace('{{id}}', data[i].id) 82 | template = template.replace('{{title}}', escape(data[i].title)) 83 | template = template.replace('{{completed}}', completed) 84 | template = template.replace('{{checked}}', checked) 85 | 86 | view = view + template 87 | } 88 | 89 | return view 90 | } 91 | 92 | /** 93 | * Displays a counter of how many to dos are left to complete 94 | * 95 | * @param {number} activeTodos The number of active todos. 96 | * @returns {string} String containing the count 97 | */ 98 | Template.prototype.itemCounter = function (activeTodos) { 99 | let plural = activeTodos === 1 ? '' : 's' 100 | 101 | return `${activeTodos} item${plural} left` 102 | } 103 | 104 | /** 105 | * Updates the text within the "Clear completed" button 106 | * 107 | * @param {[type]} completedTodos The number of completed todos. 108 | * @returns {string} String containing the count 109 | */ 110 | Template.prototype.clearCompletedButton = function (completedTodos) { 111 | if (completedTodos > 0) { 112 | return 'Clear completed' 113 | } 114 | 115 | return '' 116 | } 117 | 118 | // Export to window 119 | window.app = window.app || {} 120 | window.app.Template = Template 121 | })(window) 122 | -------------------------------------------------------------------------------- /app/assets/js/todo/view.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Forked from TodoMVC 3 | * https://todomvc.com 4 | * 5 | * MIT License © Addy Osmani, Sindre Sorhus, Pascal Hartig, Stephen Sawchuk 6 | */ 7 | 8 | /*global qs, qsa, $on, $parent, $delegate */ 9 | 10 | (function (window) { 11 | 'use strict' 12 | 13 | /** 14 | * View that abstracts away the browser's DOM completely. 15 | * It has two simple entry points: 16 | * 17 | * - bind(eventName, handler) 18 | * Takes a todo application event and registers the handler 19 | * - render(command, parameterObject) 20 | * Renders the given command with the options 21 | */ 22 | function View (template) { 23 | this.template = template 24 | 25 | this.ENTER_KEY = 13 26 | this.ESCAPE_KEY = 27 27 | 28 | this.$todoList = qs('.todo-list') 29 | this.$todoItemCounter = qs('.todo-count') 30 | this.$clearCompleted = qs('.clear-completed') 31 | this.$main = qs('.main') 32 | this.$footer = qs('.footer') 33 | this.$toggleAll = qs('.toggle-all') 34 | this.$newTodo = qs('.new-todo') 35 | } 36 | 37 | View.prototype._removeItem = function (id) { 38 | let elem = qs(`[data-id="${id}"]`) 39 | 40 | if (elem) { 41 | this.$todoList.removeChild(elem) 42 | } 43 | } 44 | 45 | View.prototype._clearCompletedButton = function (completedCount, visible) { 46 | this.$clearCompleted.innerHTML = this.template.clearCompletedButton(completedCount) 47 | this.$clearCompleted.style.display = visible ? 'block' : 'none' 48 | } 49 | 50 | View.prototype._setFilter = function (currentPage) { 51 | qs('.filters .selected').className = '' 52 | qs(`.filters [href="#/${currentPage}"]`).className = 'selected' 53 | } 54 | 55 | View.prototype._elementComplete = function (id, completed) { 56 | let listItem = qs(`[data-id="${id}"]`) 57 | 58 | if (!listItem) { 59 | return 60 | } 61 | 62 | listItem.className = completed ? 'completed' : '' 63 | 64 | // In case it was toggled from an event and not by clicking the checkbox 65 | qs('input', listItem).checked = completed 66 | } 67 | 68 | View.prototype._editItem = function (id, title) { 69 | let listItem = qs(`[data-id="${id}"]`) 70 | 71 | if (!listItem) { 72 | return 73 | } 74 | 75 | listItem.className = `${listItem.className} editing` 76 | 77 | let input = document.createElement('input') 78 | 79 | input.className = 'edit' 80 | 81 | listItem.appendChild(input) 82 | input.focus() 83 | input.value = title 84 | } 85 | 86 | View.prototype._editItemDone = function (id, title) { 87 | let listItem = qs(`[data-id="${id}"]`) 88 | 89 | if (!listItem) { 90 | return 91 | } 92 | 93 | let input = qs('input.edit', listItem) 94 | 95 | listItem.removeChild(input) 96 | 97 | listItem.className = listItem.className.replace('editing', '') 98 | 99 | qsa('label', listItem).forEach(function (label) { 100 | label.textContent = title 101 | }) 102 | } 103 | 104 | View.prototype.render = function (viewCmd, parameter) { 105 | let self = this 106 | let viewCommands = { 107 | showEntries () { 108 | self.$todoList.innerHTML = self.template.show(parameter) 109 | }, 110 | removeItem () { 111 | self._removeItem(parameter) 112 | }, 113 | updateElementCount () { 114 | self.$todoItemCounter.innerHTML = self.template.itemCounter(parameter) 115 | }, 116 | clearCompletedButton () { 117 | self._clearCompletedButton(parameter.completed, parameter.visible) 118 | }, 119 | contentBlockVisibility () { 120 | self.$main.style.display = self.$footer.style.display = parameter.visible ? 'block' : 'none' 121 | }, 122 | toggleAll () { 123 | self.$toggleAll.checked = parameter.checked 124 | }, 125 | setFilter () { 126 | self._setFilter(parameter) 127 | }, 128 | clearNewTodo () { 129 | self.$newTodo.value = '' 130 | }, 131 | elementComplete () { 132 | self._elementComplete(parameter.id, parameter.completed) 133 | }, 134 | editItem () { 135 | self._editItem(parameter.id, parameter.title) 136 | }, 137 | editItemDone () { 138 | self._editItemDone(parameter.id, parameter.title) 139 | }, 140 | } 141 | 142 | viewCommands[viewCmd]() 143 | } 144 | 145 | View.prototype._itemId = function (element) { 146 | let li = $parent(element, 'li') 147 | 148 | return parseInt(li.dataset.id, 10) 149 | } 150 | 151 | View.prototype._bindItemEditDone = function (handler) { 152 | let self = this 153 | 154 | $delegate(self.$todoList, 'li .edit', 'blur', function () { 155 | if (!this.dataset.iscanceled) { 156 | handler({ 157 | id: self._itemId(this), 158 | title: this.value, 159 | }) 160 | } 161 | }) 162 | 163 | $delegate(self.$todoList, 'li .edit', 'keypress', function (event) { 164 | if (event.keyCode === self.ENTER_KEY) { 165 | // Remove the cursor from the input when you hit enter just like if it 166 | // were a real form 167 | this.blur() 168 | } 169 | }) 170 | } 171 | 172 | View.prototype._bindItemEditCancel = function (handler) { 173 | let self = this 174 | 175 | $delegate(self.$todoList, 'li .edit', 'keyup', function (event) { 176 | if (event.keyCode === self.ESCAPE_KEY) { 177 | this.dataset.iscanceled = true 178 | this.blur() 179 | 180 | handler({ id: self._itemId(this) }) 181 | } 182 | }) 183 | } 184 | 185 | View.prototype.bind = function (event, handler) { 186 | let self = this 187 | 188 | if (event === 'newTodo') { 189 | $on(self.$newTodo, 'change', function () { 190 | handler(self.$newTodo.value) 191 | }) 192 | } else if (event === 'removeCompleted') { 193 | $on(self.$clearCompleted, 'click', function () { 194 | handler() 195 | }) 196 | } else if (event === 'toggleAll') { 197 | $on(self.$toggleAll, 'click', function () { 198 | handler({ completed: this.checked }) 199 | }) 200 | } else if (event === 'itemEdit') { 201 | $delegate(self.$todoList, 'li label', 'dblclick', function () { 202 | handler({ id: self._itemId(this) }) 203 | }) 204 | } else if (event === 'itemRemove') { 205 | $delegate(self.$todoList, '.destroy', 'click', function () { 206 | handler({ id: self._itemId(this) }) 207 | }) 208 | } else if (event === 'itemToggle') { 209 | $delegate(self.$todoList, '.toggle', 'click', function () { 210 | handler({ 211 | id: self._itemId(this), 212 | completed: this.checked, 213 | }) 214 | }) 215 | } else if (event === 'itemEditDone') { 216 | self._bindItemEditDone(handler) 217 | } else if (event === 'itemEditCancel') { 218 | self._bindItemEditCancel(handler) 219 | } 220 | } 221 | 222 | // Export to window 223 | window.app = window.app || {} 224 | window.app.View = View 225 | }(window)) 226 | -------------------------------------------------------------------------------- /app/commands/aliasing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Cypress.io: Kitchen Sink 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 62 | 63 | 70 |
    71 |
    72 |
    73 | 74 |
    75 |

    .as()

    76 |

    To alias a route or DOM element for use later, use the .as() command.

    77 |
    
     78 | // The following DOM command chain is unwieldy.
     79 | // To avoid repeating it, let's use `.as()`!
     80 | cy.get('.as-table')
     81 |   .find('tbody>tr').first()
     82 |   .find('td').first()
     83 |   .find('button').as('firstBtn')
     84 | 
     85 | // To reference the alias we created, we place an
     86 | // @ in front of its name
     87 | cy.get('@firstBtn').click()
     88 | 
     89 | cy.get('@firstBtn')
     90 |   .should('have.class', 'btn-success')
     91 |   .and('contain', 'Changed')
     92 | 
     93 | // Alias the route to wait for its response
     94 | 
     95 | cy.intercept('GET', '**/comments/*').as('getComment')
     96 | 
     97 | // we have code that gets a comment when
     98 | // the button is clicked in scripts.js
     99 | cy.get('.network-btn').click()
    100 | 
    101 | // https://on.cypress.io/wait
    102 | cy.wait('@getComment').its('response.statusCode').should('eq', 200)
    103 | 
    104 |
    105 |
    106 |
    107 | 108 |
    109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 121 | 124 | 125 | 126 | 129 | 132 | 133 | 134 |
    Column 1Column 2
    119 | Row 1: Cell 1 120 | 122 | Row 1: Cell 2 123 |
    127 | Row 2: Cell 1 128 | 130 | Row 2: Cell 2 131 |
    135 |
    136 |
    137 | 138 |

    139 |
    140 |
    141 |
    142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /app/commands/location.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Cypress.io: Kitchen Sink 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 62 | 63 | 70 |
    71 | 110 |
    111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /app/commands/navigation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Cypress.io: Kitchen Sink 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 62 | 63 | 70 |
    71 | 125 |
    126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /app/commands/viewport.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Cypress.io: Kitchen Sink 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 62 | 63 | 70 |
    71 |
    72 |
    73 | 74 |
    75 |

    cy.viewport()

    76 |

    To set the viewport size and dimension, use the cy.viewport() command.

    77 |
    cy.get('#navbar').should('be.visible')
     78 | cy.viewport(320, 480)
     79 | 
     80 | // the navbar should have collapse since our screen is smaller
     81 | cy.get('#navbar').should('not.be.visible')
     82 | cy.get('.navbar-toggle').should('be.visible').click()
     83 | cy.get('.nav').find('a').should('be.visible')
     84 | 
     85 | // lets see what our app looks like on a super large screen
     86 | cy.viewport(2999, 2999)
     87 | 
     88 | // cy.viewport() accepts a set of preset sizes
     89 | // to easily set the screen to a device's width and height
     90 | 
     91 | // We added a cy.wait() between each viewport change so you can see
     92 | // the change otherwise it is a little too fast to see :)
     93 | 
     94 | cy.viewport('macbook-15')
     95 | cy.wait(200)
     96 | cy.viewport('macbook-13')
     97 | cy.wait(200)
     98 | cy.viewport('macbook-11')
     99 | cy.wait(200)
    100 | cy.viewport('ipad-2')
    101 | cy.wait(200)
    102 | cy.viewport('ipad-mini')
    103 | cy.wait(200)
    104 | cy.viewport('iphone-6+')
    105 | cy.wait(200)
    106 | cy.viewport('iphone-6')
    107 | cy.wait(200)
    108 | cy.viewport('iphone-5')
    109 | cy.wait(200)
    110 | cy.viewport('iphone-4')
    111 | cy.wait(200)
    112 | cy.viewport('iphone-3')
    113 | cy.wait(200)
    114 | 
    115 | // cy.viewport() accepts an orientation for all presets
    116 | // the default orientation is 'portrait'
    117 | cy.viewport('ipad-2', 'portrait')
    118 | cy.wait(200)
    119 | cy.viewport('iphone-4', 'landscape')
    120 | cy.wait(200)
    121 | 
    122 | // The viewport will be reset back to the default dimensions
    123 | // in between tests (the  default can be set in cypress.config.js)
    124 |
    125 |
    126 |
    127 |
    128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /app/commands/waiting.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Cypress.io: Kitchen Sink 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 62 | 63 | 70 |
    71 |
    72 |
    73 |
    74 |

    cy.wait()

    75 |

    To wait for a specific amount of time or resource to resolve, use the cy.wait() command.

    76 |
    cy.get('.wait-input1').type('Wait 1000ms after typing')
     77 | cy.wait(1000)
     78 | cy.get('.wait-input2').type('Wait 1000ms after typing')
     79 | cy.wait(1000)
     80 | cy.get('.wait-input3').type('Wait 1000ms after typing')
     81 | cy.wait(1000)
     82 | 
     83 | // Listen to GET to comments/1
     84 | cy.intercept('GET', '**/comments/*').as('getComment')
     85 | 
     86 | // we have code that gets a comment when
     87 | // the button is clicked in scripts.js
     88 | cy.get('.network-btn').click()
     89 | 
     90 | // wait for GET comments/1
     91 | cy.wait('@getComment').its('response.statusCode').should('be.oneOf', [200, 304])
    92 |
    93 |
    94 |
    95 | 96 |
    97 |
    98 |
    99 | 100 |
    101 |
    102 | 103 |
    104 |
    105 | 106 |
    107 |
    108 |
    109 |
    110 |
    111 |
    More information: 112 | 117 |
    118 |
    119 | 120 |

    121 |
    122 |
    123 |
    124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /app/commands/window.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Cypress.io: Kitchen Sink 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 62 | 63 | 70 |
    71 |
    72 |
    73 | 74 |
    75 |

    cy.window()

    76 |

    To get the global window object, use the cy.window() command.

    77 |
    cy.window().should('have.property', 'top')
    78 |
    79 | 80 |

    81 | 82 |
    83 |

    cy.document()

    84 |

    To get the document object, use the cy.document() command.

    85 |
    cy.document().should('have.property', 'charset').and('eq', 'UTF-8')
    86 |
    87 | 88 |

    89 | 90 |
    91 |

    cy.title()

    92 |

    To get the title, use the cy.title() command.

    93 |
    cy.title().should('include', 'Kitchen Sink')
    94 |
    95 | 96 |

    97 | 98 |
    99 |
    100 |
    101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /app/todo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Cypress.io: Kitchen Sink 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 63 | 64 | 70 | 71 |
    72 |
    73 |
    74 |

    todos

    75 | 76 |
    77 |
    78 | 79 | 80 |
      81 |
      82 |
      83 | 84 | 95 | 96 |
      97 |
      98 |
      99 |

      Double-click to edit a todo

      100 |

      Forked from TodoMVC

      101 |
      102 |
      103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # https://www.appveyor.com/docs/appveyor-yml/ 2 | skip_commits: 3 | # do not run for testing new Linux builds 4 | message: /Testing new linux/ 5 | 6 | # https://www.appveyor.com/docs/build-environment/#build-worker-images 7 | image: Visual Studio 2022 8 | 9 | # https://www.appveyor.com/docs/lang/nodejs-iojs/ 10 | # Test against the latest version of this Node.js version 11 | environment: 12 | nodejs_version: "22" 13 | 14 | # Install scripts. (runs after repo cloning) 15 | install: 16 | - ps: Install-Product node $env:nodejs_version 17 | # Output useful info for debugging. 18 | # we should be using npm v6+ 19 | - node --version 20 | - npm --version 21 | - npm i -g @bahmutov/print-env 22 | - print-env APPVEYOR 23 | - npm ci 24 | 25 | cache: 26 | # cache npm packages and Cypress binary 27 | # and invalidate the cache when package.json file changes 28 | # https://www.appveyor.com/docs/build-cache/ 29 | - '%AppData%\npm -> package.json' 30 | - '%USERPROFILE%\AppData\Local\Cypress\Cache -> package.json' 31 | 32 | # Post-install test scripts. 33 | test_script: 34 | - npm run lint 35 | # only run if there is commit message 36 | # with new version 37 | - npm run cy:version 38 | - npm run cy:verify 39 | - npm run cy:info 40 | - npm run cy:cache:list 41 | 42 | # noticed really slow execution of some specs on Windows 43 | # leading to failures. Trying to increase the command timeout 44 | # maybe it will solve it 45 | - setx CYPRESS_defaultCommandTimeout 20000 46 | - npm run test:ci:record:chrome 47 | - npm run test:ci:record 48 | # removed Firefox test due to flakiness in this environment 49 | # - npm run test:ci:record:firefox 50 | - npm run test:ci:record:edge 51 | 52 | # Don't actually build. 53 | build: off 54 | -------------------------------------------------------------------------------- /azure-ci.yml: -------------------------------------------------------------------------------- 1 | # if you want to configure triggers for Azure CI see 2 | # https://docs.microsoft.com/en-us/azure/devops/pipelines/build/triggers?view=azure-devops&tabs=yaml#tags 3 | 4 | jobs: 5 | 6 | # Example job that runs end-to-end tests using Cypress test runner 7 | # https://www.cypress.io/ 8 | 9 | # Job names can contain alphanumeric characters and '_', cannot start with a number 10 | - job: Cypress_e2e_tests 11 | pool: 12 | vmImage: 'ubuntu-latest' 13 | strategy: 14 | parallel: 4 15 | steps: 16 | - task: NodeTool@0 17 | inputs: 18 | versionSpec: '22.x' 19 | displayName: 'Install Node.js' 20 | 21 | # npm modules and Cypress binary should be cached 22 | # otherwise the install will be too slow 23 | # https://docs.microsoft.com/en-us/azure/devops/pipelines/caching/?view=azure-devops 24 | # since the username / user home directory are not available via system variables 25 | # (there is even an open question about it) 26 | # https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops 27 | # just use "/home/vsts" for now 28 | - task: CacheBeta@1 29 | inputs: 30 | key: npm | $(Agent.OS) | package-lock.json 31 | path: /home/vsts/.npm 32 | restoreKeys: npm | $(Agent.OS) | package-lock.json 33 | displayName: Cache npm packages 34 | - task: CacheBeta@1 35 | inputs: 36 | key: cypress | $(Agent.OS) | package-lock.json 37 | path: /home/vsts/.cache/Cypress 38 | restoreKeys: cypress | $(Agent.OS) | package-lock.json 39 | displayName: Cache Cypress binary 40 | 41 | # Install Node dependencies 42 | - script: npm ci 43 | displayName: 'Install npm dependencies' 44 | 45 | - script: npm run cy:verify 46 | displayName: 'Cypress verify' 47 | 48 | - script: npm run cy:info 49 | displayName: 'Cypress info' 50 | 51 | # The next command starts the server and runs Cypress end-to-end tests against it. 52 | # The test artifacts (screenshots, test output) will be uploaded to Cypress Cloud. 53 | # To record on Cypress Cloud we need to set CYPRESS_RECORD_KEY environment variable. 54 | # For setting ci-build-id, BUILD_BUILDNUMBER is a good candidate 55 | 56 | # Note that `npm run start & npx cypress run` is included below for brevity only. 57 | # This pattern is not ideal because Cypress may run before the server reaches a true ready state. 58 | # For better solutions see https://on.cypress.io/guides/continuous-integration. 59 | - script: | 60 | npx print-env AGENT 61 | npx print-env BUILD 62 | npx print-env SYSTEM 63 | npm run start & 64 | npx cypress run --record --parallel --ci-build-id $BUILD_BUILDNUMBER --group "Azure CI" 65 | displayName: 'Run Cypress tests' 66 | env: 67 | # avoid warnings about terminal 68 | TERM: xterm 69 | # map the secret Cypress record key as environment variable for this step 70 | CYPRESS_RECORD_KEY: $(CYPRESS_RECORD_KEY) 71 | -------------------------------------------------------------------------------- /basic/.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | test: 4 | docker: 5 | - image: cypress/base:22.15.0 6 | steps: 7 | - checkout 8 | # restore folders with npm dependencies and Cypress binary 9 | - restore_cache: 10 | keys: 11 | - cache-{{ checksum "package.json" }} 12 | # install npm dependencies and Cypress binary 13 | # if they were cached, this step is super quick 14 | - run: 15 | name: Install dependencies 16 | command: npm ci 17 | - run: npm run cy:verify 18 | # save npm dependencies and Cypress binary for future runs 19 | - save_cache: 20 | key: cache-{{ checksum "package.json" }} 21 | paths: 22 | - ~/.npm 23 | - ~/.cache 24 | # start server before starting tests 25 | - run: 26 | command: npm run start 27 | background: true 28 | - run: npm run e2e:record 29 | 30 | workflows: 31 | version: 2 32 | build: 33 | jobs: 34 | - test 35 | -------------------------------------------------------------------------------- /basic/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # to cache both npm modules and Cypress binary we use environment variables 2 | # to point at the folders we can list as paths in "cache" job settings 3 | variables: 4 | npm_config_cache: "$CI_PROJECT_DIR/.npm" 5 | CYPRESS_CACHE_FOLDER: "$CI_PROJECT_DIR/cache/Cypress" 6 | 7 | # cache using branch name 8 | # https://gitlab.com/help/ci/caching/index.md 9 | cache: 10 | key: ${CI_COMMIT_REF_SLUG} 11 | paths: 12 | - .npm 13 | - cache/Cypress 14 | 15 | test: 16 | image: cypress/base:22.15.0 17 | stage: test 18 | script: 19 | - npm ci 20 | # print CI environment variables for reference 21 | - npx print-env CI 22 | # make sure Cypress can run 23 | - npm run cy:verify 24 | # start the server in the background 25 | - npm run start & 26 | # run Cypress test in load balancing mode, pass id to tie jobs together 27 | - npm run e2e:record 28 | artifacts: 29 | paths: 30 | - cypress/screenshots 31 | expire_in: 1 day 32 | -------------------------------------------------------------------------------- /basic/.semaphore.yml: -------------------------------------------------------------------------------- 1 | # copied from https://docs.semaphoreci.com/article/50-pipeline-yaml 2 | # NodeJS and JavaScript docs 3 | # https://docs.semaphoreci.com/article/82-language-javascript-and-nodejs 4 | version: v1.0 5 | name: Cypress example Kitchensink 6 | agent: 7 | machine: 8 | type: e1-standard-2 9 | 10 | blocks: 11 | # installs npm dependencies, builds the web application and 12 | # runs Cypress tests on a single machine 13 | - name: E2E tests 14 | task: 15 | # see https://docs.semaphoreci.com/article/66-environment-variables-and-secrets 16 | secrets: 17 | # we have created new secrets object at https://cypress-io.semaphoreci.com/secrets/new 18 | # for this organization. In this job we can now access CYPRESS_RECORD_KEY 19 | - name: dashboard 20 | jobs: 21 | - name: npm ci and cache 22 | commands: 23 | - nvm install 22 24 | - npm install -g npm 25 | - checkout 26 | 27 | # restore previously cached files if any 28 | # re-install dependencies if package.json or this semaphore YML file changes 29 | - cache restore npm-$SEMAPHORE_GIT_BRANCH-$(checksum package-lock.json)-$(checksum basic/semaphore.yml) 30 | - cache restore cypress-$SEMAPHORE_GIT_BRANCH-$(checksum package-lock.json)-$(checksum basic/semaphore.yml) 31 | 32 | - npm ci 33 | # verify the Cypress test binary so its check status is cached 34 | - npm run cy:verify 35 | 36 | # cache npm dependencies and Cypress binary 37 | - cache store npm-$SEMAPHORE_GIT_BRANCH-$(checksum package-lock.json)-$(checksum basic/semaphore.yml) ~/.npm 38 | - cache store cypress-$SEMAPHORE_GIT_BRANCH-$(checksum package-lock.json)-$(checksum basic/semaphore.yml) ~/.cache/Cypress 39 | 40 | # prints SEMAPHORE_* environment variables 41 | - npm run print-env -- SEMAPHORE 42 | # finally, build the web application and run end-to-end tests 43 | - npm run build 44 | - npm run test:ci:record 45 | -------------------------------------------------------------------------------- /basic/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 22 5 | 6 | cache: 7 | # cache both npm modules and Cypress binary 8 | directories: 9 | - ~/.npm 10 | - ~/.cache 11 | # install dependencies and check that Cypress can run 12 | override: 13 | - npm ci 14 | - npm run cy:verify 15 | 16 | script: 17 | # start server in the background 18 | - npm run start & 19 | # run all Cypress tests and record on Cypress Cloud 20 | - npm run cy:run -- --record 21 | # after all tests finish running we need 22 | # to kill all background jobs (like "npm start &") 23 | # this avoids flake in Travis jobs 24 | - kill $(jobs -p) || true 25 | -------------------------------------------------------------------------------- /basic/Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { 3 | // this image provides everything needed to run Cypress 4 | docker { 5 | image 'cypress/base:22.15.0' 6 | } 7 | } 8 | 9 | stages { 10 | stage('build and test') { 11 | environment { 12 | // we will be recording test results on Cypress Cloud 13 | // to record we need to set an environment variable 14 | // we can load the record key variable from credentials store 15 | // see https://jenkins.io/doc/book/using/using-credentials/ 16 | CYPRESS_RECORD_KEY = credentials('cypress-example-kitchensink-record-key') 17 | } 18 | 19 | steps { 20 | sh 'npm ci' 21 | sh "npm run test:ci:record" 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /basic/README.md: -------------------------------------------------------------------------------- 1 | This folder contains basic configuration files for many CI providers. Each of these files should be enough to get you started running Cypress end-to-end tests in CI. 2 | 3 | CI | basic config file 4 | :--- | :--- 5 | AWS CodeBuild | [buildspec.yml](buildspec.yml) 6 | Azure CI | [azure-ci.yml](azure-ci.yml) 7 | CircleCI | [.circleci](.circleci) 8 | CodeShip Pro | [codeship-pro](codeship-pro) 9 | GitLab | [.gitlab-ci.yml](.gitlab-ci.yml) 10 | Jenkins | [Jenkinsfile](Jenkinsfile) 11 | Semaphore | [.semaphore.yml](.semaphore.yml) 12 | Travis | [.travis.yml](.travis.yml) 13 | -------------------------------------------------------------------------------- /basic/azure-ci.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | # Example job that runs end-to-end tests using Cypress test runner 3 | # https://www.cypress.io/ 4 | 5 | # Job names can contain alphanumeric characters and '_', cannot start with a number 6 | - job: Cypress_e2e_tests 7 | pool: 8 | vmImage: 'ubuntu-latest' 9 | steps: 10 | - task: NodeTool@0 11 | inputs: 12 | versionSpec: '22.x' 13 | displayName: 'Install Node.js' 14 | 15 | # npm modules and Cypress binary should be cached 16 | # otherwise the install will be too slow 17 | # https://docs.microsoft.com/en-us/azure/devops/pipelines/caching/?view=azure-devops 18 | # since the username / user home directory are not available via system variables 19 | # (there is even an open question about it) 20 | # https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops 21 | # just use "/home/vsts" for now 22 | - task: CacheBeta@1 23 | inputs: 24 | key: npm | $(Agent.OS) | package-lock.json 25 | path: /home/vsts/.npm 26 | restoreKeys: npm | $(Agent.OS) | package-lock.json 27 | displayName: Cache npm packages 28 | - task: CacheBeta@1 29 | inputs: 30 | key: cypress | $(Agent.OS) | package-lock.json 31 | path: /home/vsts/.cache/Cypress 32 | restoreKeys: cypress | $(Agent.OS) | package-lock.json 33 | displayName: Cache Cypress binary 34 | 35 | - script: npm ci 36 | displayName: 'Install npm dependencies' 37 | 38 | - script: npm run cy:verify 39 | displayName: 'Cypress verify' 40 | 41 | # The next command starts the server and runs Cypress end-to-end tests against it. 42 | # The test artifacts (screenshots & test output) will be uploaded to Cypress Cloud 43 | # To record on Cypress Cloud we need to set CYPRESS_RECORD_KEY environment variable 44 | - script: npm run test:ci:record 45 | displayName: 'Run Cypress tests' 46 | -------------------------------------------------------------------------------- /basic/buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | install: 5 | runtime-versions: 6 | nodejs: 22 7 | commands: 8 | # Set COMMIT_INFO variables to send Git specifics to Cypress Cloud when recording 9 | # https://docs.cypress.io/guides/continuous-integration/introduction#Git-information 10 | - export COMMIT_INFO_BRANCH="$(git rev-parse HEAD | xargs git name-rev | cut -d' ' -f2 | sed 's/remotes\/origin\///g' | sed 's/~.\+$//g')" 11 | - export COMMIT_INFO_MESSAGE="$(git log -1 --pretty=%B)" 12 | - export COMMIT_INFO_EMAIL="$(git log -1 --pretty=%ae)" 13 | - export COMMIT_INFO_AUTHOR="$(git log -1 --pretty=%an)" 14 | - export COMMIT_INFO_SHA="$(git log -1 --pretty=%H)" 15 | - export COMMIT_INFO_REMOTE="$(git config --get remote.origin.url)" 16 | - npm ci 17 | pre_build: 18 | commands: 19 | - npm run cy:verify 20 | - npm run cy:info 21 | build: 22 | commands: 23 | - npm run start & 24 | - npx cypress run --record 25 | -------------------------------------------------------------------------------- /basic/codeship-pro/Dockerfile: -------------------------------------------------------------------------------- 1 | # This Docker file is for building this project on Codeship Pro 2 | # https://documentation.codeship.com/pro/languages-frameworks/nodejs/ 3 | 4 | # use Cypress provided image with all dependencies included 5 | FROM cypress/base:22.15.0 6 | RUN node --version 7 | RUN npm --version 8 | WORKDIR /home/node/app 9 | # copy our test application 10 | COPY package.json package-lock.json ./ 11 | COPY app ./app 12 | COPY serve.json ./ 13 | COPY scripts ./scripts 14 | # copy Cypress tests 15 | COPY cypress.config.js cypress ./ 16 | COPY cypress ./cypress 17 | 18 | # avoid many lines of progress bars during install 19 | # https://github.com/cypress-io/cypress/issues/1243 20 | ENV CI=1 21 | 22 | # install npm dependencies and Cypress binary 23 | RUN npm ci 24 | # check if the binary was installed successfully 25 | RUN npx cypress verify 26 | -------------------------------------------------------------------------------- /basic/codeship-pro/README.md: -------------------------------------------------------------------------------- 1 | This folder has 3 files necessary to configure Codeship Pro to execute Cypress end-to-end tests. 2 | 3 | - [Dockerfile](Dockerfile) 4 | - [codeship-services.yml](codeship-services.yml) 5 | - [codeship-steps.yml](codeship-steps.yml) 6 | -------------------------------------------------------------------------------- /basic/codeship-pro/codeship-services.yml: -------------------------------------------------------------------------------- 1 | cypress-codeship-test: 2 | build: 3 | image: cypress/cypress_codeship_test 4 | dockerfile: Dockerfile 5 | # if we wanted to pass private environment variables like CYPRESS_RECORD_KEY 6 | # we would need to include encrypted env file 7 | # see instructions https://documentation.codeship.com/pro/builds-and-configuration/environment-variables/ 8 | -------------------------------------------------------------------------------- /basic/codeship-pro/codeship-steps.yml: -------------------------------------------------------------------------------- 1 | # starts server and runs all tests 2 | - name: "Run E2E tests" 3 | service: cypress-codeship-test 4 | command: npm run test:ci 5 | -------------------------------------------------------------------------------- /buddy.yml: -------------------------------------------------------------------------------- 1 | - pipeline: "Example Cypress Test" 2 | trigger_mode: "MANUAL" 3 | ref_name: "master" 4 | ref_type: "BRANCH" 5 | trigger_condition: "ALWAYS" 6 | actions: 7 | - action: "Execute: npm run cy:run" 8 | type: "BUILD" 9 | working_directory: "/buddy/cypress-example-kitchensink" 10 | docker_image_name: "cypress/base" 11 | docker_image_tag: "22.15.0" 12 | execute_commands: 13 | - "npm install --force" 14 | - "npm run cy:verify" 15 | - "npm run start &" 16 | - "npm run cy:run" 17 | cached_dirs: 18 | - "/root/.cache/Cypress" 19 | volume_mappings: 20 | - "/:/buddy/cypress-example-kitchensink" 21 | trigger_condition: "ALWAYS" 22 | shell: "BASH" 23 | -------------------------------------------------------------------------------- /buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | # AWS CodeBuild Batch configuration 4 | # https://docs.aws.amazon.com/codebuild/latest/userguide/batch-build-buildspec.html 5 | # Define 5 parallel builds to run using the "build-matrix" 6 | build-matrix: 7 | static: 8 | ignore-failure: false 9 | env: 10 | type: LINUX_CONTAINER 11 | dynamic: 12 | env: 13 | variables: 14 | WORKERS: 15 | - 1 16 | - 2 17 | - 3 18 | - 4 19 | - 5 20 | # Optional: Use Custom Public ECR Image as container 21 | #image: 22 | # - public.ecr.aws/your-namespace/image-name 23 | 24 | phases: 25 | install: 26 | runtime-versions: 27 | nodejs: 22 28 | commands: 29 | # Set COMMIT_INFO variables to send Git specifics to Cypress Cloud when recording 30 | # https://docs.cypress.io/guides/continuous-integration/introduction#Git-information 31 | - export COMMIT_INFO_BRANCH="$(git rev-parse HEAD | xargs git name-rev | cut -d' ' -f2 | sed 's/remotes\/origin\///g' | sed 's/~.\+$//g')" 32 | - export COMMIT_INFO_MESSAGE="$(git log -1 --pretty=%B)" 33 | - export COMMIT_INFO_EMAIL="$(git log -1 --pretty=%ae)" 34 | - export COMMIT_INFO_AUTHOR="$(git log -1 --pretty=%an)" 35 | - export COMMIT_INFO_SHA="$(git log -1 --pretty=%H)" 36 | - export COMMIT_INFO_REMOTE="$(git config --get remote.origin.url)" 37 | - npm ci 38 | pre_build: 39 | commands: 40 | - npm run cy:verify 41 | - npm run cy:info 42 | build: 43 | # Per https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html 44 | # use $CODEBUILD_INITIATOR as it provides the entity that started the build 45 | # which is unique and can be used as the --ci-build-id for the Cypress Cloud 46 | # e.g. awsCodeBuild-cypress-kitchen-sink/AWSCodeBuild-a14fc8e3-b5d6-42f9-9067-345d48a8f0fd 47 | commands: 48 | - npm run start & 49 | - npx cypress run --record --parallel --ci-build-id $CODEBUILD_INITIATOR 50 | -------------------------------------------------------------------------------- /codeship-services.yml: -------------------------------------------------------------------------------- 1 | cypress-codeship-test: 2 | build: 3 | image: cypress/cypress_codeship_test 4 | dockerfile: Dockerfile 5 | # to pass private environment variables like CYPRESS_RECORD_KEY 6 | # follow instructions on including an encrypted env file 7 | # see instructions https://documentation.codeship.com/pro/builds-and-configuration/environment-variables/ 8 | encrypted_env_file: 9 | - env.encrypted 10 | -------------------------------------------------------------------------------- /codeship-steps.yml: -------------------------------------------------------------------------------- 1 | - name: "Print env vars" 2 | service: cypress-codeship-test 3 | command: npx print-env CI 4 | - type: parallel 5 | steps: 6 | # starts server and runs all tests 7 | - service: cypress-codeship-test 8 | command: npm run test:ci:record:parallel 9 | - service: cypress-codeship-test 10 | command: npm run test:ci:record:parallel 11 | -------------------------------------------------------------------------------- /cypress.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | projectId: '4b7344', 3 | e2e: {}, 4 | } 5 | -------------------------------------------------------------------------------- /cypress/e2e/1-getting-started/todo.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // Welcome to Cypress! 4 | // 5 | // This spec file contains a variety of sample tests 6 | // for a todo list app that are designed to demonstrate 7 | // the power of writing tests in Cypress. 8 | // 9 | // To learn more about how Cypress works and 10 | // what makes it such an awesome testing tool, 11 | // please read our getting started guide: 12 | // https://on.cypress.io/introduction-to-cypress 13 | 14 | describe('example to-do app', () => { 15 | beforeEach(() => { 16 | // Cypress starts out with a blank slate for each test 17 | // so we must tell it to visit our website with the `cy.visit()` command. 18 | // Since we want to visit the same URL at the start of all our tests, 19 | // we include it in our beforeEach function so that it runs before each test 20 | cy.visit('http://localhost:8080/todo') 21 | }) 22 | 23 | it('displays two todo items by default', () => { 24 | // We use the `cy.get()` command to get all elements that match the selector. 25 | // Then, we use `should` to assert that there are two matched items, 26 | // which are the two default items. 27 | cy.get('.todo-list li').should('have.length', 2) 28 | 29 | // We can go even further and check that the default todos each contain 30 | // the correct text. We use the `first` and `last` functions 31 | // to get just the first and last matched elements individually, 32 | // and then perform an assertion with `should`. 33 | cy.get('.todo-list li').first().should('have.text', 'Pay electric bill') 34 | cy.get('.todo-list li').last().should('have.text', 'Walk the dog') 35 | }) 36 | 37 | it('can add new todo items', () => { 38 | // We'll store our item text in a variable so we can reuse it 39 | const newItem = 'Feed the cat' 40 | 41 | // Let's get the input element and use the `type` command to 42 | // input our new list item. After typing the content of our item, 43 | // we need to type the enter key as well in order to submit the input. 44 | // This input has a data-test attribute so we'll use that to select the 45 | // element in accordance with best practices: 46 | // https://on.cypress.io/selecting-elements 47 | cy.get('[data-test=new-todo]').type(`${newItem}{enter}`) 48 | 49 | // Now that we've typed our new item, let's check that it actually was added to the list. 50 | // Since it's the newest item, it should exist as the last element in the list. 51 | // In addition, with the two default items, we should have a total of 3 elements in the list. 52 | // Since assertions yield the element that was asserted on, 53 | // we can chain both of these assertions together into a single statement. 54 | cy.get('.todo-list li') 55 | .should('have.length', 3) 56 | .last() 57 | .should('have.text', newItem) 58 | }) 59 | 60 | it('can check off an item as completed', () => { 61 | // In addition to using the `get` command to get an element by selector, 62 | // we can also use the `contains` command to get an element by its contents. 63 | // However, this will yield the
    • element. 76 | // Once we get that element, we can assert that it has the completed class. 77 | cy.contains('Pay electric bill') 78 | .parents('li') 79 | .should('have.class', 'completed') 80 | }) 81 | 82 | context('with a checked task', () => { 83 | beforeEach(() => { 84 | // We'll take the command we used above to check off an element 85 | // Since we want to perform multiple tests that start with checking 86 | // one element, we put it in the beforeEach hook 87 | // so that it runs at the start of every test. 88 | cy.contains('Pay electric bill') 89 | .parent() 90 | .find('input[type=checkbox]') 91 | .check() 92 | }) 93 | 94 | it('can filter for uncompleted tasks', () => { 95 | // We'll click on the "active" button in order to 96 | // display only incomplete items 97 | cy.contains('Active').click() 98 | 99 | // After filtering, we can assert that there is only the one 100 | // incomplete item in the list. 101 | cy.get('.todo-list li') 102 | .should('have.length', 1) 103 | .first() 104 | .should('have.text', 'Walk the dog') 105 | 106 | // For good measure, let's also assert that the task we checked off 107 | // does not exist on the page. 108 | cy.contains('Pay electric bill').should('not.exist') 109 | }) 110 | 111 | it('can filter for completed tasks', () => { 112 | // We can perform similar steps as the test above to ensure 113 | // that only completed tasks are shown 114 | cy.contains('Completed').click() 115 | 116 | cy.get('.todo-list li') 117 | .should('have.length', 1) 118 | .first() 119 | .should('have.text', 'Pay electric bill') 120 | 121 | cy.contains('Walk the dog').should('not.exist') 122 | }) 123 | 124 | it('can delete all completed tasks', () => { 125 | // First, let's click the "Clear completed" button 126 | // `contains` is actually serving two purposes here. 127 | // First, it's ensuring that the button exists within the dom. 128 | // This button only appears when at least one task is checked 129 | // so this command is implicitly verifying that it does exist. 130 | // Second, it selects the button so we can click it. 131 | cy.contains('Clear completed').click() 132 | 133 | // Then we can make sure that there is only one element 134 | // in the list and our element does not exist 135 | cy.get('.todo-list li') 136 | .should('have.length', 1) 137 | .should('not.have.text', 'Pay electric bill') 138 | 139 | // Finally, make sure that the clear button no longer exists. 140 | cy.contains('Clear completed').should('not.exist') 141 | }) 142 | }) 143 | }) 144 | -------------------------------------------------------------------------------- /cypress/e2e/2-advanced-examples/aliasing.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Aliasing', () => { 4 | beforeEach(() => { 5 | cy.visit('http://localhost:8080/commands/aliasing') 6 | }) 7 | 8 | it('.as() - alias a DOM element for later use', () => { 9 | // https://on.cypress.io/as 10 | 11 | // Alias a DOM element for use later 12 | // We don't have to traverse to the element 13 | // later in our code, we reference it with @ 14 | 15 | cy.get('.as-table').find('tbody>tr') 16 | .first().find('td').first() 17 | .find('button').as('firstBtn') 18 | 19 | // when we reference the alias, we place an 20 | // @ in front of its name 21 | cy.get('@firstBtn').click() 22 | 23 | cy.get('@firstBtn') 24 | .should('have.class', 'btn-success') 25 | .and('contain', 'Changed') 26 | }) 27 | 28 | it('.as() - alias a route for later use', () => { 29 | // Alias the route to wait for its response 30 | cy.intercept('GET', '**/comments/*').as('getComment') 31 | 32 | // we have code that gets a comment when 33 | // the button is clicked in scripts.js 34 | cy.get('.network-btn').click() 35 | 36 | // https://on.cypress.io/wait 37 | cy.wait('@getComment').its('response.statusCode').should('eq', 200) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /cypress/e2e/2-advanced-examples/assertions.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Assertions', () => { 4 | beforeEach(() => { 5 | cy.visit('http://localhost:8080/commands/assertions') 6 | }) 7 | 8 | describe('Implicit Assertions', () => { 9 | it('.should() - make an assertion about the current subject', () => { 10 | // https://on.cypress.io/should 11 | cy.get('.assertion-table') 12 | .find('tbody tr:last') 13 | .should('have.class', 'success') 14 | .find('td') 15 | .first() 16 | // checking the text of the element in various ways 17 | .should('have.text', 'Column content') 18 | .should('contain', 'Column content') 19 | .should('have.html', 'Column content') 20 | // chai-jquery uses "is()" to check if element matches selector 21 | .should('match', 'td') 22 | // to match text content against a regular expression 23 | // first need to invoke jQuery method text() 24 | // and then match using regular expression 25 | .invoke('text') 26 | .should('match', /column content/i) 27 | 28 | // a better way to check element's text content against a regular expression 29 | // is to use "cy.contains" 30 | // https://on.cypress.io/contains 31 | cy.get('.assertion-table') 32 | .find('tbody tr:last') 33 | // finds first element with text content matching regular expression 34 | .contains('td', /column content/i) 35 | .should('be.visible') 36 | 37 | // for more information about asserting element's text 38 | // see https://on.cypress.io/using-cypress-faq#How-do-I-get-an-element’s-text-contents 39 | }) 40 | 41 | it('.and() - chain multiple assertions together', () => { 42 | // https://on.cypress.io/and 43 | cy.get('.assertions-link') 44 | .should('have.class', 'active') 45 | .and('have.attr', 'href') 46 | .and('include', 'cypress.io') 47 | }) 48 | }) 49 | 50 | describe('Explicit Assertions', () => { 51 | // https://on.cypress.io/assertions 52 | it('expect - make an assertion about a specified subject', () => { 53 | // We can use Chai's BDD style assertions 54 | expect(true).to.be.true 55 | const o = { foo: 'bar' } 56 | 57 | expect(o).to.equal(o) 58 | expect(o).to.deep.equal({ foo: 'bar' }) 59 | // matching text using regular expression 60 | expect('FooBar').to.match(/bar$/i) 61 | }) 62 | 63 | it('pass your own callback function to should()', () => { 64 | // Pass a function to should that can have any number 65 | // of explicit assertions within it. 66 | // The ".should(cb)" function will be retried 67 | // automatically until it passes all your explicit assertions or times out. 68 | cy.get('.assertions-p') 69 | .find('p') 70 | .should(($p) => { 71 | // https://on.cypress.io/$ 72 | // return an array of texts from all of the p's 73 | const texts = $p.map((i, el) => Cypress.$(el).text()) 74 | 75 | // jquery map returns jquery object 76 | // and .get() convert this to simple array 77 | const paragraphs = texts.get() 78 | 79 | // array should have length of 3 80 | expect(paragraphs, 'has 3 paragraphs').to.have.length(3) 81 | 82 | // use second argument to expect(...) to provide clear 83 | // message with each assertion 84 | expect(paragraphs, 'has expected text in each paragraph').to.deep.eq([ 85 | 'Some text from first p', 86 | 'More text from second p', 87 | 'And even more text from third p', 88 | ]) 89 | }) 90 | }) 91 | 92 | it('finds element by class name regex', () => { 93 | cy.get('.docs-header') 94 | .find('div') 95 | // .should(cb) callback function will be retried 96 | .should(($div) => { 97 | expect($div).to.have.length(1) 98 | 99 | const className = $div[0].className 100 | 101 | expect(className).to.match(/heading-/) 102 | }) 103 | // .then(cb) callback is not retried, 104 | // it either passes or fails 105 | .then(($div) => { 106 | expect($div, 'text content').to.have.text('Introduction') 107 | }) 108 | }) 109 | 110 | it('can throw any error', () => { 111 | cy.get('.docs-header') 112 | .find('div') 113 | .should(($div) => { 114 | if ($div.length !== 1) { 115 | // you can throw your own errors 116 | throw new Error('Did not find 1 element') 117 | } 118 | 119 | const className = $div[0].className 120 | 121 | if (!className.match(/heading-/)) { 122 | throw new Error(`Could not find class "heading-" in ${className}`) 123 | } 124 | }) 125 | }) 126 | 127 | it('matches unknown text between two elements', () => { 128 | /** 129 | * Text from the first element. 130 | * @type {string} 131 | */ 132 | let text 133 | 134 | /** 135 | * Normalizes passed text, 136 | * useful before comparing text with spaces and different capitalization. 137 | * @param {string} s Text to normalize 138 | */ 139 | const normalizeText = (s) => s.replace(/\s/g, '').toLowerCase() 140 | 141 | cy.get('.two-elements') 142 | .find('.first') 143 | .then(($first) => { 144 | // save text from the first element 145 | text = normalizeText($first.text()) 146 | }) 147 | 148 | cy.get('.two-elements') 149 | .find('.second') 150 | .should(($div) => { 151 | // we can massage text before comparing 152 | const secondText = normalizeText($div.text()) 153 | 154 | expect(secondText, 'second text').to.equal(text) 155 | }) 156 | }) 157 | 158 | it('assert - assert shape of an object', () => { 159 | const person = { 160 | name: 'Joe', 161 | age: 20, 162 | } 163 | 164 | assert.isObject(person, 'value is object') 165 | }) 166 | 167 | it('retries the should callback until assertions pass', () => { 168 | cy.get('#random-number') 169 | .should(($div) => { 170 | const n = parseFloat($div.text()) 171 | 172 | expect(n).to.be.gte(1).and.be.lte(10) 173 | }) 174 | }) 175 | }) 176 | }) 177 | -------------------------------------------------------------------------------- /cypress/e2e/2-advanced-examples/connectors.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Connectors', () => { 4 | beforeEach(() => { 5 | cy.visit('http://localhost:8080/commands/connectors') 6 | }) 7 | 8 | it('.each() - iterate over an array of elements', () => { 9 | // https://on.cypress.io/each 10 | cy.get('.connectors-each-ul>li') 11 | .each(($el, index, $list) => { 12 | console.log($el, index, $list) 13 | }) 14 | }) 15 | 16 | it('.its() - get properties on the current subject', () => { 17 | // https://on.cypress.io/its 18 | cy.get('.connectors-its-ul>li') 19 | // calls the 'length' property yielding that value 20 | .its('length') 21 | .should('be.gt', 2) 22 | }) 23 | 24 | it('.invoke() - invoke a function on the current subject', () => { 25 | // our div is hidden in our script.js 26 | // $('.connectors-div').hide() 27 | cy.get('.connectors-div').should('be.hidden') 28 | 29 | // https://on.cypress.io/invoke 30 | // call the jquery method 'show' on the 'div.container' 31 | cy.get('.connectors-div').invoke('show') 32 | 33 | cy.get('.connectors-div').should('be.visible') 34 | }) 35 | 36 | it('.spread() - spread an array as individual args to callback function', () => { 37 | // https://on.cypress.io/spread 38 | const arr = ['foo', 'bar', 'baz'] 39 | 40 | cy.wrap(arr).spread((foo, bar, baz) => { 41 | expect(foo).to.eq('foo') 42 | expect(bar).to.eq('bar') 43 | expect(baz).to.eq('baz') 44 | }) 45 | }) 46 | 47 | describe('.then()', () => { 48 | it('invokes a callback function with the current subject', () => { 49 | // https://on.cypress.io/then 50 | cy.get('.connectors-list > li') 51 | .then(($lis) => { 52 | expect($lis, '3 items').to.have.length(3) 53 | expect($lis.eq(0), 'first item').to.contain('Walk the dog') 54 | expect($lis.eq(1), 'second item').to.contain('Feed the cat') 55 | expect($lis.eq(2), 'third item').to.contain('Write JavaScript') 56 | }) 57 | }) 58 | 59 | it('yields the returned value to the next command', () => { 60 | cy.wrap(1) 61 | .then((num) => { 62 | expect(num).to.equal(1) 63 | 64 | return 2 65 | }) 66 | .then((num) => { 67 | expect(num).to.equal(2) 68 | }) 69 | }) 70 | 71 | it('yields the original subject without return', () => { 72 | cy.wrap(1) 73 | .then((num) => { 74 | expect(num).to.equal(1) 75 | // note that nothing is returned from this callback 76 | }) 77 | .then((num) => { 78 | // this callback receives the original unchanged value 1 79 | expect(num).to.equal(1) 80 | }) 81 | }) 82 | 83 | it('yields the value yielded by the last Cypress command inside', () => { 84 | cy.wrap(1) 85 | .then((num) => { 86 | expect(num).to.equal(1) 87 | // note how we run a Cypress command 88 | // the result yielded by this Cypress command 89 | // will be passed to the second ".then" 90 | cy.wrap(2) 91 | }) 92 | .then((num) => { 93 | // this callback receives the value yielded by "cy.wrap(2)" 94 | expect(num).to.equal(2) 95 | }) 96 | }) 97 | }) 98 | }) 99 | -------------------------------------------------------------------------------- /cypress/e2e/2-advanced-examples/cookies.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Cookies', () => { 4 | beforeEach(() => { 5 | Cypress.Cookies.debug(true) 6 | 7 | cy.visit('http://localhost:8080/commands/cookies') 8 | 9 | // clear cookies again after visiting to remove 10 | // any 3rd party cookies picked up such as cloudflare 11 | cy.clearCookies() 12 | }) 13 | 14 | it('cy.getCookie() - get a browser cookie', () => { 15 | // https://on.cypress.io/getcookie 16 | cy.get('#getCookie .set-a-cookie').click() 17 | 18 | // cy.getCookie() yields a cookie object 19 | cy.getCookie('token').should('have.property', 'value', '123ABC') 20 | }) 21 | 22 | it('cy.getCookies() - get browser cookies for the current domain', () => { 23 | // https://on.cypress.io/getcookies 24 | cy.getCookies().should('be.empty') 25 | 26 | cy.get('#getCookies .set-a-cookie').click() 27 | 28 | // cy.getCookies() yields an array of cookies 29 | cy.getCookies().should('have.length', 1).should((cookies) => { 30 | // each cookie has these properties 31 | expect(cookies[0]).to.have.property('name', 'token') 32 | expect(cookies[0]).to.have.property('value', '123ABC') 33 | expect(cookies[0]).to.have.property('httpOnly', false) 34 | expect(cookies[0]).to.have.property('secure', false) 35 | expect(cookies[0]).to.have.property('domain') 36 | expect(cookies[0]).to.have.property('path') 37 | }) 38 | }) 39 | 40 | it('cy.getAllCookies() - get all browser cookies', () => { 41 | // https://on.cypress.io/getallcookies 42 | cy.getAllCookies().should('be.empty') 43 | 44 | cy.setCookie('key', 'value') 45 | cy.setCookie('key', 'value', { domain: '.example.com' }) 46 | 47 | // cy.getAllCookies() yields an array of cookies 48 | cy.getAllCookies().should('have.length', 2).should((cookies) => { 49 | // each cookie has these properties 50 | expect(cookies[0]).to.have.property('name', 'key') 51 | expect(cookies[0]).to.have.property('value', 'value') 52 | expect(cookies[0]).to.have.property('httpOnly', false) 53 | expect(cookies[0]).to.have.property('secure', false) 54 | expect(cookies[0]).to.have.property('domain') 55 | expect(cookies[0]).to.have.property('path') 56 | 57 | expect(cookies[1]).to.have.property('name', 'key') 58 | expect(cookies[1]).to.have.property('value', 'value') 59 | expect(cookies[1]).to.have.property('httpOnly', false) 60 | expect(cookies[1]).to.have.property('secure', false) 61 | expect(cookies[1]).to.have.property('domain', '.example.com') 62 | expect(cookies[1]).to.have.property('path') 63 | }) 64 | }) 65 | 66 | it('cy.setCookie() - set a browser cookie', () => { 67 | // https://on.cypress.io/setcookie 68 | cy.getCookies().should('be.empty') 69 | 70 | cy.setCookie('foo', 'bar') 71 | 72 | // cy.getCookie() yields a cookie object 73 | cy.getCookie('foo').should('have.property', 'value', 'bar') 74 | }) 75 | 76 | it('cy.clearCookie() - clear a browser cookie', () => { 77 | // https://on.cypress.io/clearcookie 78 | cy.getCookie('token').should('be.null') 79 | 80 | cy.get('#clearCookie .set-a-cookie').click() 81 | 82 | cy.getCookie('token').should('have.property', 'value', '123ABC') 83 | 84 | // cy.clearCookies() yields null 85 | cy.clearCookie('token') 86 | 87 | cy.getCookie('token').should('be.null') 88 | }) 89 | 90 | it('cy.clearCookies() - clear browser cookies for the current domain', () => { 91 | // https://on.cypress.io/clearcookies 92 | cy.getCookies().should('be.empty') 93 | 94 | cy.get('#clearCookies .set-a-cookie').click() 95 | 96 | cy.getCookies().should('have.length', 1) 97 | 98 | // cy.clearCookies() yields null 99 | cy.clearCookies() 100 | 101 | cy.getCookies().should('be.empty') 102 | }) 103 | 104 | it('cy.clearAllCookies() - clear all browser cookies', () => { 105 | // https://on.cypress.io/clearallcookies 106 | cy.getAllCookies().should('be.empty') 107 | 108 | cy.setCookie('key', 'value') 109 | cy.setCookie('key', 'value', { domain: '.example.com' }) 110 | 111 | cy.getAllCookies().should('have.length', 2) 112 | 113 | // cy.clearAllCookies() yields null 114 | cy.clearAllCookies() 115 | 116 | cy.getAllCookies().should('be.empty') 117 | }) 118 | }) 119 | -------------------------------------------------------------------------------- /cypress/e2e/2-advanced-examples/cypress_api.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Cypress APIs', () => { 4 | context('Cypress.Commands', () => { 5 | beforeEach(() => { 6 | cy.visit('http://localhost:8080/cypress-api') 7 | }) 8 | 9 | // https://on.cypress.io/custom-commands 10 | 11 | it('.add() - create a custom command', () => { 12 | Cypress.Commands.add('console', { 13 | prevSubject: true, 14 | }, (subject, method) => { 15 | // the previous subject is automatically received 16 | // and the commands arguments are shifted 17 | 18 | // allow us to change the console method used 19 | method = method || 'log' 20 | 21 | // log the subject to the console 22 | console[method]('The subject is', subject) 23 | 24 | // whatever we return becomes the new subject 25 | // we don't want to change the subject so 26 | // we return whatever was passed in 27 | return subject 28 | }) 29 | 30 | // eslint-disable-next-line no-unused-vars 31 | cy.get('button').console('info').then(($button) => { 32 | // subject is still $button 33 | }) 34 | }) 35 | }) 36 | 37 | context('Cypress.Cookies', () => { 38 | beforeEach(() => { 39 | cy.visit('http://localhost:8080/cypress-api') 40 | }) 41 | 42 | // https://on.cypress.io/cookies 43 | it('.debug() - enable or disable debugging', () => { 44 | Cypress.Cookies.debug(true) 45 | 46 | // Cypress will now log in the console when 47 | // cookies are set or cleared 48 | cy.setCookie('fakeCookie', '123ABC') 49 | cy.clearCookie('fakeCookie') 50 | cy.setCookie('fakeCookie', '123ABC') 51 | cy.clearCookie('fakeCookie') 52 | cy.setCookie('fakeCookie', '123ABC') 53 | }) 54 | }) 55 | 56 | context('Cypress.arch', () => { 57 | beforeEach(() => { 58 | cy.visit('http://localhost:8080/cypress-api') 59 | }) 60 | 61 | it('Get CPU architecture name of underlying OS', () => { 62 | // https://on.cypress.io/arch 63 | expect(Cypress.arch).to.exist 64 | }) 65 | }) 66 | 67 | context('Cypress.config()', () => { 68 | beforeEach(() => { 69 | cy.visit('http://localhost:8080/cypress-api') 70 | }) 71 | 72 | it('Get and set configuration options', () => { 73 | // https://on.cypress.io/config 74 | let myConfig = Cypress.config() 75 | 76 | expect(myConfig).to.have.property('animationDistanceThreshold', 5) 77 | expect(myConfig).to.have.property('baseUrl', null) 78 | expect(myConfig).to.have.property('defaultCommandTimeout', 4000) 79 | expect(myConfig).to.have.property('requestTimeout', 5000) 80 | expect(myConfig).to.have.property('responseTimeout', 30000) 81 | expect(myConfig).to.have.property('viewportHeight', 660) 82 | expect(myConfig).to.have.property('viewportWidth', 1000) 83 | expect(myConfig).to.have.property('pageLoadTimeout', 60000) 84 | expect(myConfig).to.have.property('waitForAnimations', true) 85 | 86 | expect(Cypress.config('pageLoadTimeout')).to.eq(60000) 87 | 88 | // this will change the config for the rest of your tests! 89 | Cypress.config('pageLoadTimeout', 20000) 90 | 91 | expect(Cypress.config('pageLoadTimeout')).to.eq(20000) 92 | 93 | Cypress.config('pageLoadTimeout', 60000) 94 | }) 95 | }) 96 | 97 | context('Cypress.dom', () => { 98 | beforeEach(() => { 99 | cy.visit('http://localhost:8080/cypress-api') 100 | }) 101 | 102 | // https://on.cypress.io/dom 103 | it('.isHidden() - determine if a DOM element is hidden', () => { 104 | let hiddenP = Cypress.$('.dom-p p.hidden').get(0) 105 | let visibleP = Cypress.$('.dom-p p.visible').get(0) 106 | 107 | // our first paragraph has css class 'hidden' 108 | expect(Cypress.dom.isHidden(hiddenP)).to.be.true 109 | expect(Cypress.dom.isHidden(visibleP)).to.be.false 110 | }) 111 | }) 112 | 113 | context('Cypress.env()', () => { 114 | beforeEach(() => { 115 | cy.visit('http://localhost:8080/cypress-api') 116 | }) 117 | 118 | // We can set environment variables for highly dynamic values 119 | 120 | // https://on.cypress.io/environment-variables 121 | it('Get environment variables', () => { 122 | // https://on.cypress.io/env 123 | // set multiple environment variables 124 | Cypress.env({ 125 | host: 'veronica.dev.local', 126 | api_server: 'http://localhost:8888/v1/', 127 | }) 128 | 129 | // get environment variable 130 | expect(Cypress.env('host')).to.eq('veronica.dev.local') 131 | 132 | // set environment variable 133 | Cypress.env('api_server', 'http://localhost:8888/v2/') 134 | expect(Cypress.env('api_server')).to.eq('http://localhost:8888/v2/') 135 | 136 | // get all environment variable 137 | expect(Cypress.env()).to.have.property('host', 'veronica.dev.local') 138 | expect(Cypress.env()).to.have.property('api_server', 'http://localhost:8888/v2/') 139 | }) 140 | }) 141 | 142 | context('Cypress.log', () => { 143 | beforeEach(() => { 144 | cy.visit('http://localhost:8080/cypress-api') 145 | }) 146 | 147 | it('Control what is printed to the Command Log', () => { 148 | // https://on.cypress.io/cypress-log 149 | }) 150 | }) 151 | 152 | context('Cypress.platform', () => { 153 | beforeEach(() => { 154 | cy.visit('http://localhost:8080/cypress-api') 155 | }) 156 | 157 | it('Get underlying OS name', () => { 158 | // https://on.cypress.io/platform 159 | expect(Cypress.platform).to.be.exist 160 | }) 161 | }) 162 | 163 | context('Cypress.version', () => { 164 | beforeEach(() => { 165 | cy.visit('http://localhost:8080/cypress-api') 166 | }) 167 | 168 | it('Get current version of Cypress being run', () => { 169 | // https://on.cypress.io/version 170 | expect(Cypress.version).to.be.exist 171 | }) 172 | }) 173 | 174 | context('Cypress.spec', () => { 175 | beforeEach(() => { 176 | cy.visit('http://localhost:8080/cypress-api') 177 | }) 178 | 179 | it('Get current spec information', () => { 180 | // https://on.cypress.io/spec 181 | // wrap the object so we can inspect it easily by clicking in the command log 182 | cy.wrap(Cypress.spec).should('include.keys', ['name', 'relative', 'absolute']) 183 | }) 184 | }) 185 | }) 186 | -------------------------------------------------------------------------------- /cypress/e2e/2-advanced-examples/files.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | /// JSON fixture file can be loaded directly using 4 | // the built-in JavaScript bundler 5 | const requiredExample = require('../../fixtures/example') 6 | 7 | context('Files', () => { 8 | beforeEach(() => { 9 | cy.visit('http://localhost:8080/commands/files') 10 | 11 | // load example.json fixture file and store 12 | // in the test context object 13 | cy.fixture('example.json').as('example') 14 | }) 15 | 16 | it('cy.fixture() - load a fixture', () => { 17 | // https://on.cypress.io/fixture 18 | 19 | // Instead of writing a response inline you can 20 | // use a fixture file's content. 21 | 22 | // when application makes an Ajax request matching "GET **/comments/*" 23 | // Cypress will intercept it and reply with the object in `example.json` fixture 24 | cy.intercept('GET', '**/comments/*', { fixture: 'example.json' }).as('getComment') 25 | 26 | // we have code that gets a comment when 27 | // the button is clicked in scripts.js 28 | cy.get('.fixture-btn').click() 29 | 30 | cy.wait('@getComment').its('response.body') 31 | .should('have.property', 'name') 32 | .and('include', 'Using fixtures to represent data') 33 | }) 34 | 35 | it('cy.fixture() or require - load a fixture', function () { 36 | // we are inside the "function () { ... }" 37 | // callback and can use test context object "this" 38 | // "this.example" was loaded in "beforeEach" function callback 39 | expect(this.example, 'fixture in the test context') 40 | .to.deep.equal(requiredExample) 41 | 42 | // or use "cy.wrap" and "should('deep.equal', ...)" assertion 43 | cy.wrap(this.example) 44 | .should('deep.equal', requiredExample) 45 | }) 46 | 47 | it('cy.readFile() - read file contents', () => { 48 | // https://on.cypress.io/readfile 49 | 50 | // You can read a file and yield its contents 51 | // The filePath is relative to your project's root. 52 | cy.readFile(Cypress.config('configFile')).then((config) => { 53 | expect(config).to.be.an('string') 54 | }) 55 | }) 56 | 57 | it('cy.writeFile() - write to a file', () => { 58 | // https://on.cypress.io/writefile 59 | 60 | // You can write to a file 61 | 62 | // Use a response from a request to automatically 63 | // generate a fixture file for use later 64 | cy.request('https://jsonplaceholder.cypress.io/users') 65 | .then((response) => { 66 | cy.writeFile('cypress/fixtures/users.json', response.body) 67 | }) 68 | 69 | cy.fixture('users').should((users) => { 70 | expect(users[0].name).to.exist 71 | }) 72 | 73 | // JavaScript arrays and objects are stringified 74 | // and formatted into text. 75 | cy.writeFile('cypress/fixtures/profile.json', { 76 | id: 8739, 77 | name: 'Jane', 78 | email: 'jane@example.com', 79 | }) 80 | 81 | cy.fixture('profile').should((profile) => { 82 | expect(profile.name).to.eq('Jane') 83 | }) 84 | }) 85 | }) 86 | -------------------------------------------------------------------------------- /cypress/e2e/2-advanced-examples/location.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Location', () => { 4 | beforeEach(() => { 5 | cy.visit('http://localhost:8080/commands/location') 6 | }) 7 | 8 | it('cy.hash() - get the current URL hash', () => { 9 | // https://on.cypress.io/hash 10 | cy.hash().should('be.empty') 11 | }) 12 | 13 | it('cy.location() - get window.location', () => { 14 | // https://on.cypress.io/location 15 | cy.location().should((location) => { 16 | expect(location.hash).to.be.empty 17 | expect(location.href).to.eq('http://localhost:8080/commands/location') 18 | expect(location.host).to.eq('localhost:8080') 19 | expect(location.hostname).to.eq('localhost') 20 | expect(location.origin).to.eq('http://localhost:8080') 21 | expect(location.pathname).to.eq('/commands/location') 22 | expect(location.port).to.eq('8080') 23 | expect(location.protocol).to.eq('http:') 24 | expect(location.search).to.be.empty 25 | }) 26 | }) 27 | 28 | it('cy.url() - get the current URL', () => { 29 | // https://on.cypress.io/url 30 | cy.url().should('eq', 'http://localhost:8080/commands/location') 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /cypress/e2e/2-advanced-examples/misc.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Misc', () => { 4 | beforeEach(() => { 5 | cy.visit('http://localhost:8080/commands/misc') 6 | }) 7 | 8 | it('cy.exec() - execute a system command', () => { 9 | // execute a system command. 10 | // so you can take actions necessary for 11 | // your test outside the scope of Cypress. 12 | // https://on.cypress.io/exec 13 | 14 | // we can use Cypress.platform string to 15 | // select appropriate command 16 | // https://on.cypress/io/platform 17 | cy.log(`Platform ${Cypress.platform} architecture ${Cypress.arch}`) 18 | 19 | // on CircleCI Windows build machines we have a failure to run bash shell 20 | // https://github.com/cypress-io/cypress/issues/5169 21 | // so skip some of the tests by passing flag "--env circle=true" 22 | const isCircleOnWindows = Cypress.platform === 'win32' && Cypress.env('circle') 23 | 24 | if (isCircleOnWindows) { 25 | cy.log('Skipping test on CircleCI') 26 | 27 | return 28 | } 29 | 30 | // cy.exec problem on Shippable CI 31 | // https://github.com/cypress-io/cypress/issues/6718 32 | const isShippable = Cypress.platform === 'linux' && Cypress.env('shippable') 33 | 34 | if (isShippable) { 35 | cy.log('Skipping test on ShippableCI') 36 | 37 | return 38 | } 39 | 40 | cy.exec('echo Jane Lane') 41 | .its('stdout').should('contain', 'Jane Lane') 42 | 43 | if (Cypress.platform === 'win32') { 44 | cy.exec(`print ${Cypress.config('configFile')}`) 45 | .its('stderr').should('be.empty') 46 | } 47 | else { 48 | cy.exec(`cat ${Cypress.config('configFile')}`) 49 | .its('stderr').should('be.empty') 50 | 51 | cy.exec('pwd') 52 | .its('code').should('eq', 0) 53 | } 54 | }) 55 | 56 | it('cy.focused() - get the DOM element that has focus', () => { 57 | // https://on.cypress.io/focused 58 | cy.get('.misc-form').find('#name').click() 59 | cy.focused().should('have.id', 'name') 60 | 61 | cy.get('.misc-form').find('#description').click() 62 | cy.focused().should('have.id', 'description') 63 | }) 64 | 65 | context('Cypress.Screenshot', function () { 66 | it('cy.screenshot() - take a screenshot', () => { 67 | // https://on.cypress.io/screenshot 68 | cy.screenshot('my-image') 69 | }) 70 | 71 | it('Cypress.Screenshot.defaults() - change default config of screenshots', function () { 72 | Cypress.Screenshot.defaults({ 73 | blackout: ['.foo'], 74 | capture: 'viewport', 75 | clip: { x: 0, y: 0, width: 200, height: 200 }, 76 | scale: false, 77 | disableTimersAndAnimations: true, 78 | screenshotOnRunFailure: true, 79 | onBeforeScreenshot () { }, 80 | onAfterScreenshot () { }, 81 | }) 82 | }) 83 | }) 84 | 85 | it('cy.wrap() - wrap an object', () => { 86 | // https://on.cypress.io/wrap 87 | cy.wrap({ foo: 'bar' }) 88 | .should('have.property', 'foo') 89 | .and('include', 'bar') 90 | }) 91 | }) 92 | -------------------------------------------------------------------------------- /cypress/e2e/2-advanced-examples/navigation.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Navigation', () => { 4 | beforeEach(() => { 5 | cy.visit('http://localhost:8080') 6 | cy.get('.navbar-nav').contains('Commands').click() 7 | cy.get('.dropdown-menu').contains('Navigation').click() 8 | }) 9 | 10 | it('cy.go() - go back or forward in the browser\'s history', () => { 11 | // https://on.cypress.io/go 12 | 13 | cy.location('pathname').should('include', 'navigation') 14 | 15 | cy.go('back') 16 | cy.location('pathname').should('not.include', 'navigation') 17 | 18 | cy.go('forward') 19 | cy.location('pathname').should('include', 'navigation') 20 | 21 | // clicking back 22 | cy.go(-1) 23 | cy.location('pathname').should('not.include', 'navigation') 24 | 25 | // clicking forward 26 | cy.go(1) 27 | cy.location('pathname').should('include', 'navigation') 28 | }) 29 | 30 | it('cy.reload() - reload the page', () => { 31 | // https://on.cypress.io/reload 32 | cy.reload() 33 | 34 | // reload the page without using the cache 35 | cy.reload(true) 36 | }) 37 | 38 | it('cy.visit() - visit a remote url', () => { 39 | // https://on.cypress.io/visit 40 | 41 | // Visit any sub-domain of your current domain 42 | // Pass options to the visit 43 | cy.visit('http://localhost:8080/commands/navigation', { 44 | timeout: 50000, // increase total time for the visit to resolve 45 | onBeforeLoad (contentWindow) { 46 | // contentWindow is the remote page's window object 47 | expect(typeof contentWindow === 'object').to.be.true 48 | }, 49 | onLoad (contentWindow) { 50 | // contentWindow is the remote page's window object 51 | expect(typeof contentWindow === 'object').to.be.true 52 | }, 53 | }) 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /cypress/e2e/2-advanced-examples/network_requests.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Network Requests', () => { 4 | beforeEach(() => { 5 | cy.visit('http://localhost:8080/commands/network-requests') 6 | }) 7 | 8 | // Manage HTTP requests in your app 9 | 10 | it('cy.request() - make an XHR request', () => { 11 | // https://on.cypress.io/request 12 | cy.request('https://jsonplaceholder.cypress.io/comments') 13 | .should((response) => { 14 | expect(response.status).to.eq(200) 15 | // the server sometimes gets an extra comment posted from another machine 16 | // which gets returned as 1 extra object 17 | expect(response.body).to.have.property('length').and.be.oneOf([500, 501]) 18 | expect(response).to.have.property('headers') 19 | expect(response).to.have.property('duration') 20 | }) 21 | }) 22 | 23 | it('cy.request() - verify response using BDD syntax', () => { 24 | cy.request('https://jsonplaceholder.cypress.io/comments') 25 | .then((response) => { 26 | // https://on.cypress.io/assertions 27 | expect(response).property('status').to.equal(200) 28 | expect(response).property('body').to.have.property('length').and.be.oneOf([500, 501]) 29 | expect(response).to.include.keys('headers', 'duration') 30 | }) 31 | }) 32 | 33 | it('cy.request() with query parameters', () => { 34 | // will execute request 35 | // https://jsonplaceholder.cypress.io/comments?postId=1&id=3 36 | cy.request({ 37 | url: 'https://jsonplaceholder.cypress.io/comments', 38 | qs: { 39 | postId: 1, 40 | id: 3, 41 | }, 42 | }) 43 | .its('body') 44 | .should('be.an', 'array') 45 | .and('have.length', 1) 46 | .its('0') // yields first element of the array 47 | .should('contain', { 48 | postId: 1, 49 | id: 3, 50 | }) 51 | }) 52 | 53 | it('cy.request() - pass result to the second request', () => { 54 | // first, let's find out the userId of the first user we have 55 | cy.request('https://jsonplaceholder.cypress.io/users?_limit=1') 56 | .its('body') // yields the response object 57 | .its('0') // yields the first element of the returned list 58 | // the above two commands its('body').its('0') 59 | // can be written as its('body.0') 60 | // if you do not care about TypeScript checks 61 | .then((user) => { 62 | expect(user).property('id').to.be.a('number') 63 | // make a new post on behalf of the user 64 | cy.request('POST', 'https://jsonplaceholder.cypress.io/posts', { 65 | userId: user.id, 66 | title: 'Cypress Test Runner', 67 | body: 'Fast, easy and reliable testing for anything that runs in a browser.', 68 | }) 69 | }) 70 | // note that the value here is the returned value of the 2nd request 71 | // which is the new post object 72 | .then((response) => { 73 | expect(response).property('status').to.equal(201) // new entity created 74 | expect(response).property('body').to.contain({ 75 | title: 'Cypress Test Runner', 76 | }) 77 | 78 | // we don't know the exact post id - only that it will be > 100 79 | // since JSONPlaceholder has built-in 100 posts 80 | expect(response.body).property('id').to.be.a('number') 81 | .and.to.be.gt(100) 82 | 83 | // we don't know the user id here - since it was in above closure 84 | // so in this test just confirm that the property is there 85 | expect(response.body).property('userId').to.be.a('number') 86 | }) 87 | }) 88 | 89 | it('cy.request() - save response in the shared test context', () => { 90 | // https://on.cypress.io/variables-and-aliases 91 | cy.request('https://jsonplaceholder.cypress.io/users?_limit=1') 92 | .its('body').its('0') // yields the first element of the returned list 93 | .as('user') // saves the object in the test context 94 | .then(function () { 95 | // NOTE 👀 96 | // By the time this callback runs the "as('user')" command 97 | // has saved the user object in the test context. 98 | // To access the test context we need to use 99 | // the "function () { ... }" callback form, 100 | // otherwise "this" points at a wrong or undefined object! 101 | cy.request('POST', 'https://jsonplaceholder.cypress.io/posts', { 102 | userId: this.user.id, 103 | title: 'Cypress Test Runner', 104 | body: 'Fast, easy and reliable testing for anything that runs in a browser.', 105 | }) 106 | .its('body').as('post') // save the new post from the response 107 | }) 108 | .then(function () { 109 | // When this callback runs, both "cy.request" API commands have finished 110 | // and the test context has "user" and "post" objects set. 111 | // Let's verify them. 112 | expect(this.post, 'post has the right user id').property('userId').to.equal(this.user.id) 113 | }) 114 | }) 115 | 116 | it('cy.intercept() - route responses to matching requests', () => { 117 | // https://on.cypress.io/intercept 118 | 119 | let message = 'whoa, this comment does not exist' 120 | 121 | // Listen to GET to comments/1 122 | cy.intercept('GET', '**/comments/*').as('getComment') 123 | 124 | // we have code that gets a comment when 125 | // the button is clicked in scripts.js 126 | cy.get('.network-btn').click() 127 | 128 | // https://on.cypress.io/wait 129 | cy.wait('@getComment').its('response.statusCode').should('be.oneOf', [200, 304]) 130 | 131 | // Listen to POST to comments 132 | cy.intercept('POST', '**/comments').as('postComment') 133 | 134 | // we have code that posts a comment when 135 | // the button is clicked in scripts.js 136 | cy.get('.network-post').click() 137 | cy.wait('@postComment').should(({ request, response }) => { 138 | expect(request.body).to.include('email') 139 | expect(request.headers).to.have.property('content-type') 140 | expect(response && response.body).to.have.property('name', 'Using POST in cy.intercept()') 141 | }) 142 | 143 | // Stub a response to PUT comments/ **** 144 | cy.intercept({ 145 | method: 'PUT', 146 | url: '**/comments/*', 147 | }, { 148 | statusCode: 404, 149 | body: { error: message }, 150 | headers: { 'access-control-allow-origin': '*' }, 151 | delayMs: 500, 152 | }).as('putComment') 153 | 154 | // we have code that puts a comment when 155 | // the button is clicked in scripts.js 156 | cy.get('.network-put').click() 157 | 158 | cy.wait('@putComment') 159 | 160 | // our 404 statusCode logic in scripts.js executed 161 | cy.get('.network-put-comment').should('contain', message) 162 | }) 163 | }) 164 | -------------------------------------------------------------------------------- /cypress/e2e/2-advanced-examples/querying.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Querying', () => { 4 | beforeEach(() => { 5 | cy.visit('http://localhost:8080/commands/querying') 6 | }) 7 | 8 | // The most commonly used query is 'cy.get()', you can 9 | // think of this like the '$' in jQuery 10 | 11 | it('cy.get() - query DOM elements', () => { 12 | // https://on.cypress.io/get 13 | 14 | cy.get('#query-btn').should('contain', 'Button') 15 | 16 | cy.get('.query-btn').should('contain', 'Button') 17 | 18 | cy.get('#querying .well>button:first').should('contain', 'Button') 19 | // ↲ 20 | // Use CSS selectors just like jQuery 21 | 22 | cy.get('[data-test-id="test-example"]').should('have.class', 'example') 23 | 24 | // 'cy.get()' yields jQuery object, you can get its attribute 25 | // by invoking `.attr()` method 26 | cy.get('[data-test-id="test-example"]') 27 | .invoke('attr', 'data-test-id') 28 | .should('equal', 'test-example') 29 | 30 | // or you can get element's CSS property 31 | cy.get('[data-test-id="test-example"]') 32 | .invoke('css', 'position') 33 | .should('equal', 'static') 34 | 35 | // or use assertions directly during 'cy.get()' 36 | // https://on.cypress.io/assertions 37 | cy.get('[data-test-id="test-example"]') 38 | .should('have.attr', 'data-test-id', 'test-example') 39 | .and('have.css', 'position', 'static') 40 | }) 41 | 42 | it('cy.contains() - query DOM elements with matching content', () => { 43 | // https://on.cypress.io/contains 44 | cy.get('.query-list') 45 | .contains('bananas') 46 | .should('have.class', 'third') 47 | 48 | // we can pass a regexp to `.contains()` 49 | cy.get('.query-list') 50 | .contains(/^b\w+/) 51 | .should('have.class', 'third') 52 | 53 | cy.get('.query-list') 54 | .contains('apples') 55 | .should('have.class', 'first') 56 | 57 | // passing a selector to contains will 58 | // yield the selector containing the text 59 | cy.get('#querying') 60 | .contains('ul', 'oranges') 61 | .should('have.class', 'query-list') 62 | 63 | cy.get('.query-button') 64 | .contains('Save Form') 65 | .should('have.class', 'btn') 66 | }) 67 | 68 | it('.within() - query DOM elements within a specific element', () => { 69 | // https://on.cypress.io/within 70 | cy.get('.query-form').within(() => { 71 | cy.get('input:first').should('have.attr', 'placeholder', 'Email') 72 | cy.get('input:last').should('have.attr', 'placeholder', 'Password') 73 | }) 74 | }) 75 | 76 | it('cy.root() - query the root DOM element', () => { 77 | // https://on.cypress.io/root 78 | 79 | // By default, root is the document 80 | cy.root().should('match', 'html') 81 | 82 | cy.get('.query-ul').within(() => { 83 | // In this within, the root is now the ul DOM element 84 | cy.root().should('have.class', 'query-ul') 85 | }) 86 | }) 87 | 88 | it('best practices - selecting elements', () => { 89 | // https://on.cypress.io/best-practices#Selecting-Elements 90 | cy.get('[data-cy=best-practices-selecting-elements]').within(() => { 91 | // Worst - too generic, no context 92 | cy.get('button').click() 93 | 94 | // Bad. Coupled to styling. Highly subject to change. 95 | cy.get('.btn.btn-large').click() 96 | 97 | // Average. Coupled to the `name` attribute which has HTML semantics. 98 | cy.get('[name=submission]').click() 99 | 100 | // Better. But still coupled to styling or JS event listeners. 101 | cy.get('#main').click() 102 | 103 | // Slightly better. Uses an ID but also ensures the element 104 | // has an ARIA role attribute 105 | cy.get('#main[role=button]').click() 106 | 107 | // Much better. But still coupled to text content that may change. 108 | cy.contains('Submit').click() 109 | 110 | // Best. Insulated from all changes. 111 | cy.get('[data-cy=submit]').click() 112 | }) 113 | }) 114 | }) 115 | -------------------------------------------------------------------------------- /cypress/e2e/2-advanced-examples/spies_stubs_clocks.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Spies, Stubs, and Clock', () => { 4 | it('cy.spy() - wrap a method in a spy', () => { 5 | // https://on.cypress.io/spy 6 | cy.visit('http://localhost:8080/commands/spies-stubs-clocks') 7 | 8 | const obj = { 9 | foo () {}, 10 | } 11 | 12 | const spy = cy.spy(obj, 'foo').as('anyArgs') 13 | 14 | obj.foo() 15 | 16 | expect(spy).to.be.called 17 | }) 18 | 19 | it('cy.spy() retries until assertions pass', () => { 20 | cy.visit('http://localhost:8080/commands/spies-stubs-clocks') 21 | 22 | const obj = { 23 | /** 24 | * Prints the argument passed 25 | * @param x {any} 26 | */ 27 | foo (x) { 28 | console.log('obj.foo called with', x) 29 | }, 30 | } 31 | 32 | cy.spy(obj, 'foo').as('foo') 33 | 34 | setTimeout(() => { 35 | obj.foo('first') 36 | }, 500) 37 | 38 | setTimeout(() => { 39 | obj.foo('second') 40 | }, 2500) 41 | 42 | cy.get('@foo').should('have.been.calledTwice') 43 | }) 44 | 45 | it('cy.stub() - create a stub and/or replace a function with stub', () => { 46 | // https://on.cypress.io/stub 47 | cy.visit('http://localhost:8080/commands/spies-stubs-clocks') 48 | 49 | const obj = { 50 | /** 51 | * prints both arguments to the console 52 | * @param a {string} 53 | * @param b {string} 54 | */ 55 | foo (a, b) { 56 | console.log('a', a, 'b', b) 57 | }, 58 | } 59 | 60 | const stub = cy.stub(obj, 'foo').as('foo') 61 | 62 | obj.foo('foo', 'bar') 63 | 64 | expect(stub).to.be.called 65 | }) 66 | 67 | it('cy.clock() - control time in the browser', () => { 68 | // https://on.cypress.io/clock 69 | 70 | // create the date in UTC so it's always the same 71 | // no matter what local timezone the browser is running in 72 | const now = new Date(Date.UTC(2017, 2, 14)).getTime() 73 | 74 | cy.clock(now) 75 | cy.visit('http://localhost:8080/commands/spies-stubs-clocks') 76 | cy.get('#clock-div').click() 77 | cy.get('#clock-div') 78 | .should('have.text', '1489449600') 79 | }) 80 | 81 | it('cy.tick() - move time in the browser', () => { 82 | // https://on.cypress.io/tick 83 | 84 | // create the date in UTC so it's always the same 85 | // no matter what local timezone the browser is running in 86 | const now = new Date(Date.UTC(2017, 2, 14)).getTime() 87 | 88 | cy.clock(now) 89 | cy.visit('http://localhost:8080/commands/spies-stubs-clocks') 90 | cy.get('#tick-div').click() 91 | cy.get('#tick-div') 92 | .should('have.text', '1489449600') 93 | 94 | cy.tick(10000) // 10 seconds passed 95 | cy.get('#tick-div').click() 96 | cy.get('#tick-div') 97 | .should('have.text', '1489449610') 98 | }) 99 | 100 | it('cy.stub() matches depending on arguments', () => { 101 | // see all possible matchers at 102 | // https://sinonjs.org/releases/latest/matchers/ 103 | const greeter = { 104 | /** 105 | * Greets a person 106 | * @param {string} name 107 | */ 108 | greet (name) { 109 | return `Hello, ${name}!` 110 | }, 111 | } 112 | 113 | cy.stub(greeter, 'greet') 114 | .callThrough() // if you want non-matched calls to call the real method 115 | .withArgs(Cypress.sinon.match.string).returns('Hi') 116 | .withArgs(Cypress.sinon.match.number).throws(new Error('Invalid name')) 117 | 118 | expect(greeter.greet('World')).to.equal('Hi') 119 | expect(() => greeter.greet(42)).to.throw('Invalid name') 120 | expect(greeter.greet).to.have.been.calledTwice 121 | 122 | // non-matched calls goes the actual method 123 | expect(greeter.greet()).to.equal('Hello, undefined!') 124 | }) 125 | 126 | it('matches call arguments using Sinon matchers', () => { 127 | // see all possible matchers at 128 | // https://sinonjs.org/releases/latest/matchers/ 129 | const calculator = { 130 | /** 131 | * returns the sum of two arguments 132 | * @param a {number} 133 | * @param b {number} 134 | */ 135 | add (a, b) { 136 | return a + b 137 | }, 138 | } 139 | 140 | const spy = cy.spy(calculator, 'add').as('add') 141 | 142 | expect(calculator.add(2, 3)).to.equal(5) 143 | 144 | // if we want to assert the exact values used during the call 145 | expect(spy).to.be.calledWith(2, 3) 146 | 147 | // let's confirm "add" method was called with two numbers 148 | expect(spy).to.be.calledWith(Cypress.sinon.match.number, Cypress.sinon.match.number) 149 | 150 | // alternatively, provide the value to match 151 | expect(spy).to.be.calledWith(Cypress.sinon.match(2), Cypress.sinon.match(3)) 152 | 153 | // match any value 154 | expect(spy).to.be.calledWith(Cypress.sinon.match.any, 3) 155 | 156 | // match any value from a list 157 | expect(spy).to.be.calledWith(Cypress.sinon.match.in([1, 2, 3]), 3) 158 | 159 | /** 160 | * Returns true if the given number is even 161 | * @param {number} x 162 | */ 163 | const isEven = (x) => x % 2 === 0 164 | 165 | // expect the value to pass a custom predicate function 166 | // the second argument to "sinon.match(predicate, message)" is 167 | // shown if the predicate does not pass and assertion fails 168 | expect(spy).to.be.calledWith(Cypress.sinon.match(isEven, 'isEven'), 3) 169 | 170 | /** 171 | * Returns a function that checks if a given number is larger than the limit 172 | * @param {number} limit 173 | * @returns {(x: number) => boolean} 174 | */ 175 | const isGreaterThan = (limit) => (x) => x > limit 176 | 177 | /** 178 | * Returns a function that checks if a given number is less than the limit 179 | * @param {number} limit 180 | * @returns {(x: number) => boolean} 181 | */ 182 | const isLessThan = (limit) => (x) => x < limit 183 | 184 | // you can combine several matchers using "and", "or" 185 | expect(spy).to.be.calledWith( 186 | Cypress.sinon.match.number, 187 | Cypress.sinon.match(isGreaterThan(2), '> 2').and(Cypress.sinon.match(isLessThan(4), '< 4')), 188 | ) 189 | 190 | expect(spy).to.be.calledWith( 191 | Cypress.sinon.match.number, 192 | Cypress.sinon.match(isGreaterThan(200), '> 200').or(Cypress.sinon.match(3)), 193 | ) 194 | 195 | // matchers can be used from BDD assertions 196 | cy.get('@add').should('have.been.calledWith', 197 | Cypress.sinon.match.number, Cypress.sinon.match(3)) 198 | 199 | // you can alias matchers for shorter test code 200 | const { match: M } = Cypress.sinon 201 | 202 | cy.get('@add').should('have.been.calledWith', M.number, M(3)) 203 | }) 204 | }) 205 | -------------------------------------------------------------------------------- /cypress/e2e/2-advanced-examples/storage.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Local Storage / Session Storage', () => { 4 | beforeEach(() => { 5 | cy.visit('http://localhost:8080/commands/storage') 6 | }) 7 | // Although localStorage is automatically cleared 8 | // in between tests to maintain a clean state 9 | // sometimes we need to clear localStorage manually 10 | 11 | it('cy.clearLocalStorage() - clear all data in localStorage for the current origin', () => { 12 | // https://on.cypress.io/clearlocalstorage 13 | cy.get('.ls-btn').click() 14 | cy.get('.ls-btn').should(() => { 15 | expect(localStorage.getItem('prop1')).to.eq('red') 16 | expect(localStorage.getItem('prop2')).to.eq('blue') 17 | expect(localStorage.getItem('prop3')).to.eq('magenta') 18 | }) 19 | 20 | cy.clearLocalStorage() 21 | cy.getAllLocalStorage().should(() => { 22 | expect(localStorage.getItem('prop1')).to.be.null 23 | expect(localStorage.getItem('prop2')).to.be.null 24 | expect(localStorage.getItem('prop3')).to.be.null 25 | }) 26 | 27 | cy.get('.ls-btn').click() 28 | cy.get('.ls-btn').should(() => { 29 | expect(localStorage.getItem('prop1')).to.eq('red') 30 | expect(localStorage.getItem('prop2')).to.eq('blue') 31 | expect(localStorage.getItem('prop3')).to.eq('magenta') 32 | }) 33 | 34 | // Clear key matching string in localStorage 35 | cy.clearLocalStorage('prop1') 36 | cy.getAllLocalStorage().should(() => { 37 | expect(localStorage.getItem('prop1')).to.be.null 38 | expect(localStorage.getItem('prop2')).to.eq('blue') 39 | expect(localStorage.getItem('prop3')).to.eq('magenta') 40 | }) 41 | 42 | cy.get('.ls-btn').click() 43 | cy.get('.ls-btn').should(() => { 44 | expect(localStorage.getItem('prop1')).to.eq('red') 45 | expect(localStorage.getItem('prop2')).to.eq('blue') 46 | expect(localStorage.getItem('prop3')).to.eq('magenta') 47 | }) 48 | 49 | // Clear keys matching regex in localStorage 50 | cy.clearLocalStorage(/prop1|2/) 51 | cy.getAllLocalStorage().should(() => { 52 | expect(localStorage.getItem('prop1')).to.be.null 53 | expect(localStorage.getItem('prop2')).to.be.null 54 | expect(localStorage.getItem('prop3')).to.eq('magenta') 55 | }) 56 | }) 57 | 58 | it('cy.getAllLocalStorage() - get all data in localStorage for all origins', () => { 59 | // https://on.cypress.io/getalllocalstorage 60 | cy.get('.ls-btn').click() 61 | 62 | // getAllLocalStorage() yields a map of origins to localStorage values 63 | cy.getAllLocalStorage().should((storageMap) => { 64 | expect(storageMap).to.deep.equal({ 65 | // other origins will also be present if localStorage is set on them 66 | 'http://localhost:8080': { 67 | prop1: 'red', 68 | prop2: 'blue', 69 | prop3: 'magenta', 70 | }, 71 | }) 72 | }) 73 | }) 74 | 75 | it('cy.clearAllLocalStorage() - clear all data in localStorage for all origins', () => { 76 | // https://on.cypress.io/clearalllocalstorage 77 | cy.get('.ls-btn').click() 78 | 79 | // clearAllLocalStorage() yields null 80 | cy.clearAllLocalStorage() 81 | cy.getAllLocalStorage().should(() => { 82 | expect(localStorage.getItem('prop1')).to.be.null 83 | expect(localStorage.getItem('prop2')).to.be.null 84 | expect(localStorage.getItem('prop3')).to.be.null 85 | }) 86 | }) 87 | 88 | it('cy.getAllSessionStorage() - get all data in sessionStorage for all origins', () => { 89 | // https://on.cypress.io/getallsessionstorage 90 | cy.get('.ls-btn').click() 91 | 92 | // getAllSessionStorage() yields a map of origins to sessionStorage values 93 | cy.getAllSessionStorage().should((storageMap) => { 94 | expect(storageMap).to.deep.equal({ 95 | // other origins will also be present if sessionStorage is set on them 96 | 'http://localhost:8080': { 97 | prop4: 'cyan', 98 | prop5: 'yellow', 99 | prop6: 'black', 100 | }, 101 | }) 102 | }) 103 | }) 104 | 105 | it('cy.clearAllSessionStorage() - clear all data in sessionStorage for all origins', () => { 106 | // https://on.cypress.io/clearallsessionstorage 107 | cy.get('.ls-btn').click() 108 | 109 | // clearAllSessionStorage() yields null 110 | cy.clearAllSessionStorage() 111 | cy.getAllSessionStorage().should(() => { 112 | expect(sessionStorage.getItem('prop4')).to.be.null 113 | expect(sessionStorage.getItem('prop5')).to.be.null 114 | expect(sessionStorage.getItem('prop6')).to.be.null 115 | }) 116 | }) 117 | }) 118 | -------------------------------------------------------------------------------- /cypress/e2e/2-advanced-examples/traversal.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Traversal', () => { 4 | beforeEach(() => { 5 | cy.visit('http://localhost:8080/commands/traversal') 6 | }) 7 | 8 | it('.children() - get child DOM elements', () => { 9 | // https://on.cypress.io/children 10 | cy.get('.traversal-breadcrumb') 11 | .children('.active') 12 | .should('contain', 'Data') 13 | }) 14 | 15 | it('.closest() - get closest ancestor DOM element', () => { 16 | // https://on.cypress.io/closest 17 | cy.get('.traversal-badge') 18 | .closest('ul') 19 | .should('have.class', 'list-group') 20 | }) 21 | 22 | it('.eq() - get a DOM element at a specific index', () => { 23 | // https://on.cypress.io/eq 24 | cy.get('.traversal-list>li') 25 | .eq(1).should('contain', 'siamese') 26 | }) 27 | 28 | it('.filter() - get DOM elements that match the selector', () => { 29 | // https://on.cypress.io/filter 30 | cy.get('.traversal-nav>li') 31 | .filter('.active').should('contain', 'About') 32 | }) 33 | 34 | it('.find() - get descendant DOM elements of the selector', () => { 35 | // https://on.cypress.io/find 36 | cy.get('.traversal-pagination') 37 | .find('li').find('a') 38 | .should('have.length', 7) 39 | }) 40 | 41 | it('.first() - get first DOM element', () => { 42 | // https://on.cypress.io/first 43 | cy.get('.traversal-table td') 44 | .first().should('contain', '1') 45 | }) 46 | 47 | it('.last() - get last DOM element', () => { 48 | // https://on.cypress.io/last 49 | cy.get('.traversal-buttons .btn') 50 | .last().should('contain', 'Submit') 51 | }) 52 | 53 | it('.next() - get next sibling DOM element', () => { 54 | // https://on.cypress.io/next 55 | cy.get('.traversal-ul') 56 | .contains('apples').next().should('contain', 'oranges') 57 | }) 58 | 59 | it('.nextAll() - get all next sibling DOM elements', () => { 60 | // https://on.cypress.io/nextall 61 | cy.get('.traversal-next-all') 62 | .contains('oranges') 63 | .nextAll().should('have.length', 3) 64 | }) 65 | 66 | it('.nextUntil() - get next sibling DOM elements until next el', () => { 67 | // https://on.cypress.io/nextuntil 68 | cy.get('#veggies') 69 | .nextUntil('#nuts').should('have.length', 3) 70 | }) 71 | 72 | it('.not() - remove DOM elements from set of DOM elements', () => { 73 | // https://on.cypress.io/not 74 | cy.get('.traversal-disabled .btn') 75 | .not('[disabled]').should('not.contain', 'Disabled') 76 | }) 77 | 78 | it('.parent() - get parent DOM element from DOM elements', () => { 79 | // https://on.cypress.io/parent 80 | cy.get('.traversal-mark') 81 | .parent().should('contain', 'Morbi leo risus') 82 | }) 83 | 84 | it('.parents() - get parent DOM elements from DOM elements', () => { 85 | // https://on.cypress.io/parents 86 | cy.get('.traversal-cite') 87 | .parents().should('match', 'blockquote') 88 | }) 89 | 90 | it('.parentsUntil() - get parent DOM elements from DOM elements until el', () => { 91 | // https://on.cypress.io/parentsuntil 92 | cy.get('.clothes-nav') 93 | .find('.active') 94 | .parentsUntil('.clothes-nav') 95 | .should('have.length', 2) 96 | }) 97 | 98 | it('.prev() - get previous sibling DOM element', () => { 99 | // https://on.cypress.io/prev 100 | cy.get('.birds').find('.active') 101 | .prev().should('contain', 'Lorikeets') 102 | }) 103 | 104 | it('.prevAll() - get all previous sibling DOM elements', () => { 105 | // https://on.cypress.io/prevall 106 | cy.get('.fruits-list').find('.third') 107 | .prevAll().should('have.length', 2) 108 | }) 109 | 110 | it('.prevUntil() - get all previous sibling DOM elements until el', () => { 111 | // https://on.cypress.io/prevuntil 112 | cy.get('.foods-list').find('#nuts') 113 | .prevUntil('#veggies').should('have.length', 3) 114 | }) 115 | 116 | it('.siblings() - get all sibling DOM elements', () => { 117 | // https://on.cypress.io/siblings 118 | cy.get('.traversal-pills .active') 119 | .siblings().should('have.length', 2) 120 | }) 121 | }) 122 | -------------------------------------------------------------------------------- /cypress/e2e/2-advanced-examples/utilities.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Utilities', () => { 4 | beforeEach(() => { 5 | cy.visit('http://localhost:8080/utilities') 6 | }) 7 | 8 | it('Cypress._ - call a lodash method', () => { 9 | // https://on.cypress.io/_ 10 | cy.request('https://jsonplaceholder.cypress.io/users') 11 | .then((response) => { 12 | let ids = Cypress._.chain(response.body).map('id').take(3).value() 13 | 14 | expect(ids).to.deep.eq([1, 2, 3]) 15 | }) 16 | }) 17 | 18 | it('Cypress.$ - call a jQuery method', () => { 19 | // https://on.cypress.io/$ 20 | let $li = Cypress.$('.utility-jquery li:first') 21 | 22 | cy.wrap($li).should('not.have.class', 'active') 23 | cy.wrap($li).click() 24 | cy.wrap($li).should('have.class', 'active') 25 | }) 26 | 27 | it('Cypress.Blob - blob utilities and base64 string conversion', () => { 28 | // https://on.cypress.io/blob 29 | cy.get('.utility-blob').then(($div) => { 30 | // https://github.com/nolanlawson/blob-util#imgSrcToDataURL 31 | // get the dataUrl string for the javascript-logo 32 | return Cypress.Blob.imgSrcToDataURL('/assets/img/javascript-logo.png', undefined, 'anonymous') 33 | .then((dataUrl) => { 34 | // create an element and set its src to the dataUrl 35 | let img = Cypress.$('', { src: dataUrl }) 36 | 37 | // need to explicitly return cy here since we are initially returning 38 | // the Cypress.Blob.imgSrcToDataURL promise to our test 39 | // append the image 40 | $div.append(img) 41 | 42 | cy.get('.utility-blob img').click() 43 | cy.get('.utility-blob img').should('have.attr', 'src', dataUrl) 44 | }) 45 | }) 46 | }) 47 | 48 | it('Cypress.minimatch - test out glob patterns against strings', () => { 49 | // https://on.cypress.io/minimatch 50 | let matching = Cypress.minimatch('/users/1/comments', '/users/*/comments', { 51 | matchBase: true, 52 | }) 53 | 54 | expect(matching, 'matching wildcard').to.be.true 55 | 56 | matching = Cypress.minimatch('/users/1/comments/2', '/users/*/comments', { 57 | matchBase: true, 58 | }) 59 | 60 | expect(matching, 'comments').to.be.false 61 | 62 | // ** matches against all downstream path segments 63 | matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/**', { 64 | matchBase: true, 65 | }) 66 | 67 | expect(matching, 'comments').to.be.true 68 | 69 | // whereas * matches only the next path segment 70 | 71 | matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/*', { 72 | matchBase: false, 73 | }) 74 | 75 | expect(matching, 'comments').to.be.false 76 | }) 77 | 78 | it('Cypress.Promise - instantiate a bluebird promise', () => { 79 | // https://on.cypress.io/promise 80 | let waited = false 81 | 82 | /** 83 | * @return Bluebird 84 | */ 85 | function waitOneSecond () { 86 | // return a promise that resolves after 1 second 87 | // eslint-disable-next-line no-unused-vars 88 | return new Cypress.Promise((resolve, reject) => { 89 | setTimeout(() => { 90 | // set waited to true 91 | waited = true 92 | 93 | // resolve with 'foo' string 94 | resolve('foo') 95 | }, 1000) 96 | }) 97 | } 98 | 99 | cy.then(() => { 100 | // return a promise to cy.then() that 101 | // is awaited until it resolves 102 | return waitOneSecond().then((str) => { 103 | expect(str).to.eq('foo') 104 | expect(waited).to.be.true 105 | }) 106 | }) 107 | }) 108 | }) 109 | -------------------------------------------------------------------------------- /cypress/e2e/2-advanced-examples/viewport.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | /* eslint-disable cypress/no-unnecessary-waiting */ 3 | 4 | context('Viewport', () => { 5 | beforeEach(() => { 6 | cy.visit('http://localhost:8080/commands/viewport') 7 | }) 8 | 9 | it('cy.viewport() - set the viewport size and dimension', () => { 10 | // https://on.cypress.io/viewport 11 | 12 | cy.get('#navbar').should('be.visible') 13 | cy.viewport(320, 480) 14 | 15 | // the navbar should have collapse since our screen is smaller 16 | cy.get('#navbar').should('not.be.visible') 17 | cy.get('.navbar-toggle').should('be.visible').click() 18 | cy.get('.nav').find('a').should('be.visible') 19 | 20 | // lets see what our app looks like on a super large screen 21 | cy.viewport(2999, 2999) 22 | 23 | // cy.viewport() accepts a set of preset sizes 24 | // to easily set the screen to a device's width and height 25 | 26 | // We added a cy.wait() between each viewport change so you can see 27 | // the change otherwise it is a little too fast to see :) 28 | 29 | cy.viewport('macbook-15') 30 | cy.wait(200) 31 | cy.viewport('macbook-13') 32 | cy.wait(200) 33 | cy.viewport('macbook-11') 34 | cy.wait(200) 35 | cy.viewport('ipad-2') 36 | cy.wait(200) 37 | cy.viewport('ipad-mini') 38 | cy.wait(200) 39 | cy.viewport('iphone-6+') 40 | cy.wait(200) 41 | cy.viewport('iphone-6') 42 | cy.wait(200) 43 | cy.viewport('iphone-5') 44 | cy.wait(200) 45 | cy.viewport('iphone-4') 46 | cy.wait(200) 47 | cy.viewport('iphone-3') 48 | cy.wait(200) 49 | 50 | // cy.viewport() accepts an orientation for all presets 51 | // the default orientation is 'portrait' 52 | cy.viewport('ipad-2', 'portrait') 53 | cy.wait(200) 54 | cy.viewport('iphone-4', 'landscape') 55 | cy.wait(200) 56 | 57 | // The viewport will be reset back to the default dimensions 58 | // in between tests (the default can be set in cypress.config.{js|ts}) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /cypress/e2e/2-advanced-examples/waiting.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | /* eslint-disable cypress/no-unnecessary-waiting */ 3 | 4 | context('Waiting', () => { 5 | beforeEach(() => { 6 | cy.visit('http://localhost:8080/commands/waiting') 7 | }) 8 | // BE CAREFUL of adding unnecessary wait times. 9 | // https://on.cypress.io/best-practices#Unnecessary-Waiting 10 | 11 | // https://on.cypress.io/wait 12 | it('cy.wait() - wait for a specific amount of time', () => { 13 | cy.get('.wait-input1').type('Wait 1000ms after typing') 14 | cy.wait(1000) 15 | cy.get('.wait-input2').type('Wait 1000ms after typing') 16 | cy.wait(1000) 17 | cy.get('.wait-input3').type('Wait 1000ms after typing') 18 | cy.wait(1000) 19 | }) 20 | 21 | it('cy.wait() - wait for a specific route', () => { 22 | // Listen to GET to comments/1 23 | cy.intercept('GET', '**/comments/*').as('getComment') 24 | 25 | // we have code that gets a comment when 26 | // the button is clicked in scripts.js 27 | cy.get('.network-btn').click() 28 | 29 | // wait for GET comments/1 30 | cy.wait('@getComment').its('response.statusCode').should('be.oneOf', [200, 304]) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /cypress/e2e/2-advanced-examples/window.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Window', () => { 4 | beforeEach(() => { 5 | cy.visit('http://localhost:8080/commands/window') 6 | }) 7 | 8 | it('cy.window() - get the global window object', () => { 9 | // https://on.cypress.io/window 10 | cy.window().should('have.property', 'top') 11 | }) 12 | 13 | it('cy.document() - get the document object', () => { 14 | // https://on.cypress.io/document 15 | cy.document().should('have.property', 'charset').and('eq', 'UTF-8') 16 | }) 17 | 18 | it('cy.title() - get the title', () => { 19 | // https://on.cypress.io/title 20 | cy.title().should('include', 'Kitchen Sink') 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/e2e.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | -------------------------------------------------------------------------------- /env.encrypted: -------------------------------------------------------------------------------- 1 | codeship:v2 2 | VYpDY5q344GUgI3Ru7veAHzt1MfF6zJMK+enZ97jhwXXBlmPfwfnkPYyHdnOyp7AIgmPpqzQ2QJoSbYAH9girO3AuJOgVpTQ/fg6pFwb1FAshuIH1wJ501qSVXtWDuaAXDfZ9VbxGg== -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from 'globals' 2 | import pluginJs from '@eslint/js' 3 | import eslintPluginJsonc from 'eslint-plugin-jsonc' 4 | import stylistic from '@stylistic/eslint-plugin' 5 | import mochaPlugin from 'eslint-plugin-mocha' 6 | import pluginCypress from 'eslint-plugin-cypress' 7 | 8 | export default [ 9 | pluginJs.configs.recommended, 10 | ...eslintPluginJsonc.configs['flat/recommended-with-json'], 11 | mochaPlugin.configs.recommended, 12 | pluginCypress.configs.recommended, 13 | stylistic.configs.recommended, 14 | { 15 | ignores: ['app/assets/js/{vendor,todo}/'], 16 | }, 17 | { 18 | rules: { 19 | '@stylistic/arrow-parens': ['error', 'always'], 20 | '@stylistic/comma-dangle': ['error', 'always-multiline'], 21 | '@stylistic/indent': ['error', 2, { SwitchCase: 1, MemberExpression: 'off' }], 22 | '@stylistic/quotes': ['error', 'single'], 23 | '@stylistic/semi': ['error', 'never'], 24 | '@stylistic/space-before-function-paren': ['error', 'always'], 25 | 'mocha/no-exclusive-tests': 'error', 26 | 'mocha/no-pending-tests': 'error', 27 | 'mocha/no-mocha-arrows': 'off', 28 | }, 29 | languageOptions: { 30 | globals: { 31 | ...globals.node, 32 | }, 33 | }, 34 | }, 35 | ] 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cypress-example-kitchensink", 3 | "version": "0.0.0-development", 4 | "description": "This is an example app used to showcase Cypress.io End-to-End (E2E) testing. For a full reference of our documentation, go to https://docs.cypress.io", 5 | "main": "index.js", 6 | "files": [ 7 | "app", 8 | "cypress" 9 | ], 10 | "scripts": { 11 | "build": "npm run lint", 12 | "start": "node ./scripts/start.js", 13 | "dev": "npm start", 14 | "test": "npm start & cypress run", 15 | "pretest": "npm run lint", 16 | "print-env": "print-env", 17 | "lint": "eslint --fix cypress app/assets/js/scripts.js", 18 | "lint:yaml": "yamllint '*.yml' '.buildkite/*.yml' '.circleci/*.yml' '.github/workflows/*.yml' '.semaphore/*.yml' 'basic/**/*.yml' ", 19 | "colon:names": "colon-names", 20 | "e2e": "cypress run", 21 | "e2e:chrome": "cypress run --browser chrome", 22 | "e2e:edge": "cypress run --browser edge", 23 | "e2e:firefox": "cypress run --browser firefox", 24 | "e2e:record": "cypress run --record", 25 | "e2e:record:chrome": "cypress run --record --browser chrome", 26 | "e2e:record:edge": "cypress run --record --browser edge", 27 | "e2e:record:firefox": "cypress run --record --browser firefox", 28 | "e2e:record:parallel": "cypress run --record --parallel", 29 | "test:ci": "run-p --race start e2e", 30 | "test:ci:chrome": "run-p --race start e2e:chrome", 31 | "test:ci:edge": "run-p --race start e2e:edge", 32 | "test:ci:firefox": "run-p --race start e2e:firefox", 33 | "test:ci:record": "run-p --race start e2e:record", 34 | "test:ci:record:chrome": "run-p --race start e2e:record:chrome", 35 | "test:ci:record:edge": "run-p --race start e2e:record:edge", 36 | "test:ci:record:firefox": "run-p --race start e2e:record:firefox", 37 | "test:ci:record:parallel": "run-p --race start e2e:record:parallel", 38 | "cy:verify": "cypress verify", 39 | "cy:info": "cypress info", 40 | "cy:version": "cypress version", 41 | "cy:cache:list": "cypress cache list", 42 | "cy:run": "cypress run", 43 | "cy:run:record": "cypress run --record", 44 | "cy:open": "cypress open", 45 | "local:open": "start-test 8080 cy:open", 46 | "local:run": "start-test 8080 cy:run", 47 | "local:run:record": "start-test 8080 cy:run:record", 48 | "ci:set-port": "node ./scripts/set-port", 49 | "semantic-release": "semantic-release", 50 | "prepare": "node .husky/install.mjs" 51 | }, 52 | "repository": { 53 | "type": "git", 54 | "url": "git+https://github.com/cypress-io/cypress-example-kitchensink.git" 55 | }, 56 | "author": "Cypress-io", 57 | "license": "MIT", 58 | "bugs": { 59 | "url": "https://github.com/cypress-io/cypress-example-kitchensink/issues" 60 | }, 61 | "homepage": "https://github.com/cypress-io/cypress-example-kitchensink#readme", 62 | "dependencies": { 63 | "npm-run-all2": "7.0.2", 64 | "serve": "14.2.4" 65 | }, 66 | "devDependencies": { 67 | "@bahmutov/print-env": "1.3.0", 68 | "@stylistic/eslint-plugin": "4.4.0", 69 | "cypress": "14.4.0", 70 | "eslint": "9.27.0", 71 | "eslint-plugin-cypress": "5.0.0", 72 | "eslint-plugin-jsonc": "2.20.1", 73 | "eslint-plugin-mocha": "11.1.0", 74 | "globby": "11.1.0", 75 | "husky": "9.0.6", 76 | "semantic-release": "24.2.3", 77 | "start-server-and-test": "2.0.11", 78 | "yaml-lint": "1.7.0" 79 | }, 80 | "engines": { 81 | "node": "^20.0.0 || >=22.0.0" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "automerge": true, 6 | "commitMessage": "{{semanticPrefix}}Update {{depName}} to {{newVersion}} 🌟", 7 | "prTitle": "{{semanticPrefix}}{{#if isPin}}Pin{{else}}Update{{/if}} dependency {{depName}} to version {{newVersion}} 🌟", 8 | "major": { 9 | "automerge": false 10 | }, 11 | "minor": { 12 | "automerge": false 13 | }, 14 | "prConcurrentLimit": 3, 15 | "prHourlyLimit": 2, 16 | "schedule": [ 17 | "after 2am and before 3am on saturday" 18 | ], 19 | "updateNotScheduled": false, 20 | "timezone": "America/New_York", 21 | "lockFileMaintenance": { 22 | "enabled": true 23 | }, 24 | "separatePatchReleases": true, 25 | "separateMultipleMajor": true, 26 | "masterIssue": true, 27 | "labels": [ 28 | "type: dependencies", 29 | "renovate" 30 | ], 31 | "packageRules": [ 32 | { 33 | "packageNames": ["cypress"], 34 | "groupName": "cypress", 35 | "schedule": "before 2am" 36 | }, 37 | { 38 | "packagePatterns": "^eslint", 39 | "groupName": "eslint" 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /scripts/set-port.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | const { readFileSync, writeFileSync } = require('fs') 4 | 5 | // On some CIs like Heroku CI the host assigns random PORT and 6 | // does not allow using default port 8080. 7 | // So we need to replace hardcoded port 8080 with process.env.PORT 8 | // in all our example specs and then run the tests 9 | 10 | // if doing it locally, you can change back to the original port 11 | // with "git restore ." command 12 | 13 | if (!process.env.PORT) { 14 | console.log('PORT environment variable is not set, nothing to do') 15 | process.exit(0) 16 | } 17 | 18 | // replace both url and port if used in assertions, like 19 | // expect(location.port).to.eq('8080') 20 | const defaultPort = 8080 21 | const input = `localhost:${defaultPort}` 22 | const portRegex = new RegExp(`'${defaultPort}'`, 'g') 23 | const urlRegex = new RegExp(input, 'g') 24 | const newPort = `'${process.env.PORT}'` 25 | const newUrl = `localhost:${process.env.PORT}` 26 | 27 | console.log('replacing "%s" with "%s" in all spec files', input, newUrl) 28 | 29 | const getSpecFilenames = () => { 30 | const globby = require('globby') 31 | 32 | return globby(['cypress/e2e/**/*.cy.js']) 33 | } 34 | 35 | const replacePort = (filename) => { 36 | const text = readFileSync(filename, 'utf8') 37 | const replaced = text.replace(urlRegex, newUrl).replace(portRegex, newPort) 38 | 39 | writeFileSync(filename, replaced, 'utf8') 40 | } 41 | 42 | getSpecFilenames() 43 | .then((filenames) => { 44 | filenames.map(replacePort) 45 | }) 46 | .catch((e) => { 47 | console.error(e.message) 48 | process.exit(1) 49 | }) 50 | -------------------------------------------------------------------------------- /scripts/start.js: -------------------------------------------------------------------------------- 1 | const { execSync } = require('child_process') 2 | 3 | const port = process.env.PORT || 8080 4 | let cmd = `serve --listen ${port} --no-request-logging` 5 | 6 | if (process.env.CI) cmd += ' --no-clipboard' 7 | 8 | if (process.argv.length > 2) cmd += ` ${process.argv.slice(2).join(' ')}` 9 | 10 | console.log(`Running "${cmd}"...`) 11 | 12 | execSync(cmd, { stdio: 'inherit' }) 13 | -------------------------------------------------------------------------------- /serve.json: -------------------------------------------------------------------------------- 1 | { 2 | "public": "app", 3 | "headers": [{ 4 | "source": "**/*", 5 | "headers": [{ 6 | "key": "Cache-Control", 7 | "value": "no-cache" 8 | }] 9 | }] 10 | } 11 | --------------------------------------------------------------------------------