# This workflow is provided via the organization template repository # # https://github.com/nextcloud/.github # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization # # SPDX-FileCopyrightText: 2023-2024 Nextcloud GmbH and Nextcloud contributors # SPDX-License-Identifier: MIT name: Cypress on: pull_request: types: - opened - synchronize - reopened - ready_for_review - labeled concurrency: group: cypress-${{ github.head_ref || github.run_id }} cancel-in-progress: true env: # Adjust APP_NAME if your repository name is different APP_NAME: ${{ github.event.repository.name }} # This represents the server branch to checkout. # Usually it's the base branch of the PR, but for pushes it's the branch itself. # e.g. 'main', 'stable27' or 'feature/my-feature' # n.b. server will use head_ref, as we want to test the PR branch. BRANCH: ${{ github.base_ref || github.ref_name }} permissions: contents: read pull-requests: read jobs: gate: runs-on: ubuntu-latest-low steps: - name: Evaluate e2e tests execution conditions id: gate-e2e uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v8.0.0 with: script: | const pr = context.payload.pull_request const hasForceLabel = pr.labels.some((label) => label.name === 'force-e2e-tests') const hasToReviewLabel = pr.labels.some((label) => label.name === '3. to review') const hasToReleaseLabel = pr.labels.some((label) => label.name === '4. to release') const files = await github.paginate(github.rest.pulls.listFiles, { owner: context.repo.owner, repo: context.repo.repo, pull_number: pr.number, per_page: 100, }) const cypressTouched = files.some((file) => file.filename.startsWith('cypress')) if (hasForceLabel || hasToReviewLabel || hasToReleaseLabel || cypressTouched) { return } else { core.setFailed('Skipping Cypress: draft state, missing labels or no cypress path changes.') } init: runs-on: ubuntu-latest needs: gate outputs: nodeVersion: ${{ steps.versions.outputs.nodeVersion }} npmVersion: ${{ steps.versions.outputs.npmVersion }} env: # We'll install cypress in the cypress job CYPRESS_INSTALL_BINARY: 0 PUPPETEER_SKIP_DOWNLOAD: true steps: - name: Checkout server uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false # We need to checkout submodules for 3rdparty submodules: true - name: Check composer.json id: check_composer uses: andstor/file-existence-action@558493d6c74bf472d87c84eab196434afc2fa029 # v3.1.0 with: files: 'composer.json' - name: Install composer dependencies if: steps.check_composer.outputs.files_exists == 'true' run: composer install --no-dev - name: Read package.json node and npm engines version uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3 id: versions with: fallbackNode: '^24' fallbackNpm: '^11.3' - name: Set up node ${{ steps.versions.outputs.nodeVersion }} uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: ${{ steps.versions.outputs.nodeVersion }} - name: Set up npm ${{ steps.versions.outputs.npmVersion }} run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}' - name: Restore npm cache uses: buildjet/cache/restore@3e70d19e31d6a8030aeddf6ed8dbe601f94d09f4 # v4.0.2 with: path: ~/.npm key: node-${{ steps.versions.outputs.nodeVersion }}-npm-${{ steps.versions.outputs.npmVersion }}-${{ hashFiles('**/package-lock.json') }} - name: Install node dependencies & build app run: | npm ci TESTING=true npm run build --if-present - name: Save npm cache uses: buildjet/cache/save@3e70d19e31d6a8030aeddf6ed8dbe601f94d09f4 # v4.0.2 with: path: ~/.npm key: node-${{ steps.versions.outputs.nodeVersion }}-npm-${{ steps.versions.outputs.npmVersion }}-${{ hashFiles('**/package-lock.json') }} - name: Save context uses: buildjet/cache/save@3e70d19e31d6a8030aeddf6ed8dbe601f94d09f4 # v4.0.2 with: key: cypress-context-${{ github.run_id }} path: ./ cypress: runs-on: ubuntu-latest needs: [gate, init] strategy: fail-fast: false matrix: # Run multiple copies of the current job in parallel # Please increase the number or runners as your tests suite grows (0 based index for e2e tests) containers: ['setup', '0', '1', '2', '3', '4', '5'] # Hack as strategy.job-total includes the "setup" and GitHub does not allow math expressions # Always align this number with the total of e2e runners (max. index + 1) total-containers: [6] services: mysql: # Only start mysql if we are running the setup tests image: ${{matrix.containers == 'setup' && 'ghcr.io/nextcloud/continuous-integration-mysql-8.4:latest' || ''}} # zizmor: ignore[unpinned-images] ports: - '3306/tcp' env: MYSQL_ROOT_PASSWORD: rootpassword MYSQL_USER: oc_autotest MYSQL_PASSWORD: nextcloud MYSQL_DATABASE: oc_autotest options: --health-cmd="mysqladmin ping" --health-interval 5s --health-timeout 2s --health-retries 10 mariadb: # Only start mariadb if we are running the setup tests image: ${{matrix.containers == 'setup' && 'mariadb:11.4' || ''}} # zizmor: ignore[unpinned-images] ports: - '3306/tcp' env: MYSQL_ROOT_PASSWORD: rootpassword MYSQL_USER: oc_autotest MYSQL_PASSWORD: nextcloud MYSQL_DATABASE: oc_autotest options: --health-cmd="mariadb-admin ping" --health-interval 5s --health-timeout 2s --health-retries 5 postgres: # Only start postgres if we are running the setup tests image: ${{matrix.containers == 'setup' && 'ghcr.io/nextcloud/continuous-integration-postgres-17:latest' || ''}} # zizmor: ignore[unpinned-images] ports: - '5432/tcp' env: POSTGRES_USER: root POSTGRES_PASSWORD: rootpassword POSTGRES_DB: nextcloud options: --mount type=tmpfs,destination=/var/lib/postgresql/data --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5 oracle: # Only start oracle if we are running the setup tests image: ${{matrix.containers == 'setup' && 'ghcr.io/gvenzl/oracle-free:23' || ''}} # zizmor: ignore[unpinned-images] ports: - '1521' env: ORACLE_PASSWORD: oracle options: --health-cmd healthcheck.sh --health-interval 20s --health-timeout 10s --health-retries 10 name: runner ${{ matrix.containers }} steps: - name: Restore context id: cache uses: buildjet/cache/restore@3e70d19e31d6a8030aeddf6ed8dbe601f94d09f4 # v4.0.2 with: key: cypress-context-${{ github.run_id }} path: ./ - name: Checkout server uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 if: steps.cache.outputs.cache-hit != 'true' with: persist-credentials: false # We need to checkout submodules for 3rdparty submodules: true - name: Check composer.json id: check_composer if: steps.cache.outputs.cache-hit != 'true' uses: andstor/file-existence-action@558493d6c74bf472d87c84eab196434afc2fa029 # v3.1.0 with: files: 'composer.json' - name: Install composer dependencies if: steps.check_composer.outputs.files_exists == 'true' && steps.cache.outputs.cache-hit != 'true' run: composer install --no-dev - name: Set up node ${{ needs.init.outputs.nodeVersion }} uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: ${{ needs.init.outputs.nodeVersion }} - name: Set up npm ${{ needs.init.outputs.npmVersion }} run: npm i -g 'npm@${{ needs.init.outputs.npmVersion }}' - name: Install node dependencies & build app if: steps.cache.outputs.cache-hit != 'true' run: | npm ci TESTING=true npm run build --if-present - name: Install cypress run: ./node_modules/cypress/bin/cypress install - name: Run ${{ matrix.containers == 'component' && 'component' || 'E2E' }} cypress tests uses: cypress-io/github-action@dace029018fcdf86e0df89a31bc3cfa5b32570d8 # v7.3.0 with: # We already installed the dependencies in the init job install: false component: ${{ matrix.containers == 'component' }} env: # Needs to be prefixed with CYPRESS_ CYPRESS_BRANCH: ${{ env.BRANCH }} # https://github.com/cypress-io/github-action/issues/124 COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }} # Needed for some specific code workarounds TESTING: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SPLIT: ${{ matrix.total-containers }} SPLIT_INDEX: ${{ matrix.containers == 'component' && 0 || matrix.containers }} SPLIT_RANDOM_SEED: ${{ github.run_id }} SETUP_TESTING: ${{ matrix.containers == 'setup' && 'true' || '' }} - name: Upload snapshots and videos uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: always() with: name: snapshots_${{ matrix.containers }} path: | cypress/snapshots cypress/videos - name: Show logs if: failure() && matrix.containers != 'component' run: | for id in $(docker ps -aq); do docker container inspect "$id" --format '=== Logs for container {{.Name}} ===' docker logs "$id" >> nextcloud.log done echo '=== Nextcloud server logs ===' docker exec nextcloud-e2e-test-server_${{ env.APP_NAME }} cat data/nextcloud.log - name: Create data dir archive if: failure() && matrix.containers != 'component' run: docker exec nextcloud-e2e-test-server_${{ env.APP_NAME }} tar -cvjf - data > data.tar - name: Upload data archive uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: failure() && matrix.containers != 'component' with: name: nc_data_${{ matrix.containers }} path: data.tar summary: runs-on: ubuntu-latest-low needs: [init, cypress] if: always() name: cypress-summary steps: - name: Summary status run: if ${{ needs.init.result != 'success' || ( needs.cypress.result != 'success' && needs.cypress.result != 'skipped' ) }}; then exit 1; fi - name: Delete cache on success uses: buildjet/cache-delete@7184288b8396c4492a56728c47dd286fbd1e96ae # v1 if: needs.init.result == 'success' && needs.cypress.result == 'success' with: cache_key: cypress-context-${{ github.run_id }}