diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml
index 88771633a..02fbb79ed 100644
--- a/.github/workflows/playwright.yml
+++ b/.github/workflows/playwright.yml
@@ -1,67 +1,189 @@
# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: MIT
+
+# This workflow runs Playwright tests on pull requests to the master and stable branches.
+# It expects the following npm scripts to be defined in package.json:
+# - "playwright": runs the Playwright tests
+# - "playwright:install-ci": installs the Playwright browsers for CI (optional)
+#
+# If you only use the "Desktop Chrome" browser with "channel: chrome" in your Playwright config,
+# you can skip the "playwright:install-ci" script as the GitHub Actions runner already has Chrome Browser installed.
+#
+# For merging the reports, it expects a Playwright merge config file at tests/playwright/merge.config.ts.
+# You can adjust the path in the "Merge into HTML Report" step if you have a different location for the merge config file.
+# The default configuration would look like this:
+# ```js
+# export default {
+# testDir: 'tests/playwright/e2e', // path to your Playwright tests
+# reporter: [['html', { open: 'never' }]],
+# }
+# ```
+
name: Playwright Tests
on:
pull_request:
- branches: [master]
+ branches:
+ - master
+ - stable35
+ - stable34
+ - stable33
permissions:
contents: read
-concurrency:
- group: playwright-${{ github.head_ref || github.run_id }}
- cancel-in-progress: true
-
jobs:
- test:
- timeout-minutes: 60
+ playwright-setup:
+ timeout-minutes: 10
+ name: Playwright setup
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ persist-credentials: false
+ - name: Read package.json
+ uses: nextcloud-libraries/parse-package-engines-action@122ae05d4257008180a514e1ddeb0c1b9d094bdd # v0.1.0
+ id: versions
+ - name: Set up node
+ uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: ${{ steps.versions.outputs.node-version }}
+ - name: Set up npm
+ run: npm i -g 'npm@${{ steps.versions.outputs.package-manager-version }}'
+ - name: Install dependencies and build
+ run: |
+ npm ci
+ npm run build --if-present
+ - name: Save context
+ uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
+ with:
+ key: playwright-context-${{ github.run_id }}
+ path: ./
+
+ playwright-tests:
+ needs: [playwright-setup]
+ timeout-minutes: 30
+ name: Playwright tests ${{ matrix.shardIndex }} / ${{ matrix.shardTotal }}
runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ shardIndex: [1, 2] # adjust to your needs
+ shardTotal: [2]
+ outputs:
+ node-version: ${{ steps.versions.outputs.node-version }}
+ package-manager-version: ${{ steps.versions.outputs.package-manager-version }}
+
+ steps:
+ - name: Restore context
+ id: cache
+ uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
+ with:
+ key: playwright-context-${{ github.run_id }}
+ path: ./
+
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ if: steps.cache.outputs.cache-hit != 'true'
+ with:
+ persist-credentials: false
+
+ - name: Read package.json
+ uses: nextcloud-libraries/parse-package-engines-action@122ae05d4257008180a514e1ddeb0c1b9d094bdd # v0.1.0
+ id: versions
+
+ - name: Set up node
+ uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: ${{ steps.versions.outputs.node-version }}
+
+ - name: Set up npm
+ run: npm i -g 'npm@${{ steps.versions.outputs.package-manager-version }}'
+
+ - name: Install dependencies and build
+ if: steps.cache.outputs.cache-hit != 'true'
+ run: |
+ npm ci
+ npm run build --if-present
+
+ - name: Install Playwright browsers
+ run: npm run playwright:install-ci --if-present
+
+ - name: Run Playwright tests
+ run: npm run playwright -- --shard='${{ matrix.shardIndex }}/${{ matrix.shardTotal }}'
+
+ - name: Upload blob report to GitHub Actions Artifacts
+ if: ${{ !cancelled() }}
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: blob-report-${{ matrix.shardIndex }}
+ path: blob-report
+ retention-days: 1
+
+ merge-reports:
+ # Merge reports after playwright-tests, even if some shards have failed
+ if: ${{ !cancelled() }}
+ needs: [playwright-tests]
+
+ runs-on: ubuntu-latest-low
+ steps:
+ - name: Restore context
+ id: cache
+ uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
+ with:
+ key: playwright-context-${{ github.run_id }}
+ path: ./
+
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ if: steps.cache.outputs.cache-hit != 'true'
+ with:
+ persist-credentials: false
+
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ if: steps.cache.outputs.cache-hit != 'true'
+ with:
+ node-version: ${{ needs.playwright-tests.outputs.node-version }}
+
+ - name: Set up npm
+ if: steps.cache.outputs.cache-hit != 'true'
+ run: npm i -g 'npm@${{ needs.playwright-tests.outputs.package-manager-version }}'
+
+ - name: Install dependencies
+ if: steps.cache.outputs.cache-hit != 'true'
+ run: npm ci
+
+ - name: Download blob reports from GitHub Actions Artifacts
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ path: all-blob-reports
+ pattern: blob-report-*
+ merge-multiple: true
+
+ - name: Merge into HTML Report
+ run: npx playwright merge-reports --config tests/playwright/merge.config.ts --reporter html,github ./all-blob-reports
+
+ - name: Upload HTML report
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: html-report--attempt-${{ github.run_attempt }}
+ path: playwright-report
+ retention-days: 7
+
+ - name: How to show the logs
+ run: |
+ echo 'To view the report:'
+ echo ' 1. Extract the folder from the zip file'
+ echo ' 2. run "npx playwright show-report name-of-my-extracted-playwright-report"'
+
+ summary:
+ permissions:
+ contents: none
+ runs-on: ubuntu-latest-low
+ needs: [playwright-tests]
+
+ if: always()
+
+ name: playwright-test-summary
+
steps:
- - name: Checkout app
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- with:
- persist-credentials: false
-
- - name: Check composer.json
- id: check_composer
- uses: andstor/file-existence-action@558493d6c74bf472d87c84eab196434afc2fa029 # v2
- 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: '^22'
- fallbackNpm: '^10'
-
- - 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: Install node dependencies & build app
- run: |
- npm ci
- npm run build --if-present
-
- - name: Install Playwright Browsers
- run: npx playwright install chromium --only-shell
-
- - name: Run Playwright tests
- run: npx playwright test
-
- - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
- if: always()
- with:
- name: playwright-report
- path: playwright-report/
- retention-days: 30
+ - name: Summary status
+ run: if ${{ needs.playwright-tests.result != 'success' }}; then exit 1; fi
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 4b3f27719..8bea13c68 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -32,7 +32,7 @@
"@nextcloud/e2e-test-server": "^0.5.1",
"@nextcloud/eslint-config": "^9.0.0",
"@nextcloud/vite-config": "^2.5.2",
- "@playwright/test": "^1.60.0",
+ "@playwright/test": "^1.62.1",
"@vitejs/plugin-vue": "^6.0.7",
"@vitest/coverage-v8": "^4.1.6",
"@vue/test-utils": "^2.4.6",
@@ -452,7 +452,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -469,7 +468,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -486,7 +484,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -503,7 +500,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -520,7 +516,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -537,7 +532,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -554,7 +548,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -571,7 +564,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -588,7 +580,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -605,7 +596,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -622,7 +612,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -639,7 +628,6 @@
"cpu": [
"loong64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -656,7 +644,6 @@
"cpu": [
"mips64el"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -673,7 +660,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -690,7 +676,6 @@
"cpu": [
"riscv64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -707,7 +692,6 @@
"cpu": [
"s390x"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -724,7 +708,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -741,7 +724,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -758,7 +740,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -775,7 +756,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -792,7 +772,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -809,7 +788,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -826,7 +804,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -843,7 +820,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -860,7 +836,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -877,7 +852,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2389,7 +2363,6 @@
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
- "dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@@ -2430,7 +2403,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2452,7 +2424,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2474,7 +2445,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2496,7 +2466,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2518,7 +2487,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2540,7 +2508,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2562,7 +2529,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2584,7 +2550,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2606,7 +2571,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2628,7 +2592,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2650,7 +2613,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2672,7 +2634,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2694,7 +2655,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2721,19 +2681,19 @@
}
},
"node_modules/@playwright/test": {
- "version": "1.61.1",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz",
- "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==",
+ "version": "1.62.1",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz",
+ "integrity": "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "playwright": "1.61.1"
+ "playwright": "1.62.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
- "node": ">=18"
+ "node": ">=20"
}
},
"node_modules/@protobufjs/aspromise": {
@@ -2884,7 +2844,6 @@
"cpu": [
"arm"
],
- "dev": true,
"optional": true,
"os": [
"android"
@@ -2897,7 +2856,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"android"
@@ -2910,7 +2868,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"darwin"
@@ -2923,7 +2880,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"darwin"
@@ -2936,7 +2892,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -2949,7 +2904,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -2962,7 +2916,6 @@
"cpu": [
"arm"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -2975,7 +2928,6 @@
"cpu": [
"arm"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -2988,7 +2940,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -3001,7 +2952,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -3014,7 +2964,6 @@
"cpu": [
"loong64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -3027,7 +2976,6 @@
"cpu": [
"loong64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -3040,7 +2988,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -3053,7 +3000,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -3066,7 +3012,6 @@
"cpu": [
"riscv64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -3079,7 +3024,6 @@
"cpu": [
"riscv64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -3092,7 +3036,6 @@
"cpu": [
"s390x"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -3105,7 +3048,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -3118,7 +3060,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -3131,7 +3072,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"openbsd"
@@ -3144,7 +3084,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"openharmony"
@@ -3157,7 +3096,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"win32"
@@ -3170,7 +3108,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"optional": true,
"os": [
"win32"
@@ -3183,7 +3120,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"win32"
@@ -3196,7 +3132,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"win32"
@@ -3507,7 +3442,7 @@
"version": "26.0.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.1.tgz",
"integrity": "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"undici-types": "~8.3.0"
@@ -4833,7 +4768,6 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
- "dev": true,
"license": "MIT",
"optional": true,
"peer": true,
@@ -5251,7 +5185,7 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
@@ -5860,7 +5794,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
- "dev": true,
"license": "Apache-2.0",
"optional": true,
"peer": true,
@@ -6782,7 +6715,6 @@
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
- "dev": true,
"license": "MIT",
"optional": true,
"peer": true,
@@ -6971,7 +6903,6 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@@ -7429,7 +7360,7 @@
"version": "5.1.9",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz",
"integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==",
- "dev": true,
+ "devOptional": true,
"peer": true
},
"node_modules/import-lazy": {
@@ -7574,7 +7505,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -7613,7 +7544,7 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
@@ -7653,7 +7584,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true,
"license": "MIT",
"optional": true,
"peer": true,
@@ -8931,7 +8861,6 @@
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
- "dev": true,
"license": "MIT",
"optional": true,
"peer": true,
@@ -8947,7 +8876,6 @@
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
- "dev": true,
"optional": true,
"peer": true,
"engines": {
@@ -9161,7 +9089,6 @@
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
- "dev": true,
"license": "MIT",
"optional": true,
"peer": true
@@ -9815,35 +9742,35 @@
}
},
"node_modules/playwright": {
- "version": "1.61.1",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz",
- "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==",
+ "version": "1.62.1",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz",
+ "integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "playwright-core": "1.61.1"
+ "playwright-core": "1.62.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
- "node": ">=18"
+ "node": ">=20"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
- "version": "1.61.1",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz",
- "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==",
+ "version": "1.62.1",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz",
+ "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
- "node": ">=18"
+ "node": ">=20"
}
},
"node_modules/playwright/node_modules/fsevents": {
@@ -10116,7 +10043,7 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"peer": true,
"engines": {
@@ -10498,7 +10425,7 @@
"version": "1.90.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz",
"integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
@@ -11388,7 +11315,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dev": true,
"license": "MIT",
"optional": true,
"peer": true,
@@ -11605,7 +11531,7 @@
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/unified": {
@@ -12092,7 +12018,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"optional": true,
"os": [
"aix"
@@ -12108,7 +12033,6 @@
"cpu": [
"arm"
],
- "dev": true,
"optional": true,
"os": [
"android"
@@ -12124,7 +12048,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"android"
@@ -12140,7 +12063,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"android"
@@ -12156,7 +12078,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"darwin"
@@ -12172,7 +12093,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"darwin"
@@ -12188,7 +12108,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -12204,7 +12123,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -12220,7 +12138,6 @@
"cpu": [
"arm"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -12236,7 +12153,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -12252,7 +12168,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -12268,7 +12183,6 @@
"cpu": [
"loong64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -12284,7 +12198,6 @@
"cpu": [
"mips64el"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -12300,7 +12213,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -12316,7 +12228,6 @@
"cpu": [
"riscv64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -12332,7 +12243,6 @@
"cpu": [
"s390x"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -12348,7 +12258,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -12364,7 +12273,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"netbsd"
@@ -12380,7 +12288,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"netbsd"
@@ -12396,7 +12303,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"openbsd"
@@ -12412,7 +12318,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"openbsd"
@@ -12428,7 +12333,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"openharmony"
@@ -12444,7 +12348,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"sunos"
@@ -12460,7 +12363,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"win32"
@@ -12476,7 +12378,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"optional": true,
"os": [
"win32"
@@ -12492,7 +12393,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"win32"
diff --git a/package.json b/package.json
index 554bc37a9..ad2949e34 100644
--- a/package.json
+++ b/package.json
@@ -10,10 +10,9 @@
"dev": "NODE_ENV=development vite --mode development build",
"lint": "eslint",
"lint:fix": "eslint --fix",
- "start:nextcloud": "node playwright/start-nextcloud-server.mjs",
+ "playwright": "playwright test",
"test": "LANG=C vitest",
"test:coverage": "LANG=C vitest run --coverage",
- "test:e2e": "playwright test",
"ts:check": "vue-tsc --noEmit",
"watch": "NODE_ENV=development vite --mode development build --watch"
},
@@ -44,7 +43,7 @@
"@nextcloud/e2e-test-server": "^0.5.1",
"@nextcloud/eslint-config": "^9.0.0",
"@nextcloud/vite-config": "^2.5.2",
- "@playwright/test": "^1.60.0",
+ "@playwright/test": "^1.62.1",
"@vitejs/plugin-vue": "^6.0.7",
"@vitest/coverage-v8": "^4.1.6",
"@vue/test-utils": "^2.4.6",
diff --git a/playwright.config.ts b/playwright.config.ts
index fc9652213..9ac92cb6a 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -1,53 +1,50 @@
-/*!
+/*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
+///
import { defineConfig, devices } from '@playwright/test'
-// See https://playwright.dev/docs/test-configuration
export default defineConfig({
- testDir: './playwright/e2e',
-
- /* Run tests in files in parallel */
+ testDir: './tests/playwright/e2e',
fullyParallel: true,
- /* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
- /* Retry on CI only */
- retries: process.env.CI ? 2 : 0,
- /* Opt out of parallel tests on CI. */
+ retries: process.env.CI ? 1 : 0,
workers: process.env.CI ? 1 : undefined,
- /* Reporter to use. See https://playwright.dev/docs/test-reporters */
- reporter: process.env.CI ? 'github' : 'html',
-
+ timeout: process.env.CI ? 45_000 : undefined, // on CI allow 1.5x the default timeout to compensate for shared server resources
+ reporter: process.env.CI ? [['blob'], ['dot'], ['github']] : 'html',
use: {
- /* Base URL so tests can navigate with relative paths, e.g. page.goto('settings/admin/sharing') */
- baseURL: 'http://localhost:8089/index.php/',
- /* Collect a trace when retrying a failed test. See https://playwright.dev/docs/trace-viewer */
+ baseURL: 'http://localhost:8042/index.php/',
trace: 'on-first-retry',
},
-
projects: [
- // One-off setup: enable circles and apply test config in the container.
- {
- name: 'setup',
- testDir: './playwright/support',
- testMatch: /setup\.ts$/,
- },
{
- name: 'chromium',
- use: { ...devices['Desktop Chrome'] },
- dependencies: ['setup'],
+ name: 'default',
+ use: {
+ ...devices['Desktop Chrome'],
+ channel: process.env.CI
+ ? 'chrome' // on CI use the chrome browser provided by the GitHub Actions runner
+ : 'chromium', // locally use the default playwright chromium browser
+ },
},
],
webServer: {
- // Spins up a throwaway Nextcloud container with circles mounted (see start-nextcloud-server.mjs).
- command: 'npm run start:nextcloud',
- reuseExistingServer: !process.env.CI,
- url: 'http://127.0.0.1:8089',
+ command: 'node tests/playwright/start-nextcloud-server.js',
+ env: {
+ NEXTCLOUD_PORT: '8042',
+ },
stderr: 'pipe',
stdout: 'pipe',
- timeout: 5 * 60 * 1000, // up to 5 minutes for first-run container creation
+ gracefulShutdown: {
+ signal: 'SIGTERM',
+ timeout: 10_000,
+ },
+ reuseExistingServer: !process.env.CI,
+ timeout: 300_000,
+ wait: {
+ stdout: /Nextcloud container ready to run Playwright tests/,
+ },
},
})
diff --git a/playwright/e2e/admin-settings.spec.ts b/tests/playwright/e2e/admin-settings.spec.ts
similarity index 100%
rename from playwright/e2e/admin-settings.spec.ts
rename to tests/playwright/e2e/admin-settings.spec.ts
diff --git a/playwright/e2e/app-page.spec.ts b/tests/playwright/e2e/app-page.spec.ts
similarity index 95%
rename from playwright/e2e/app-page.spec.ts
rename to tests/playwright/e2e/app-page.spec.ts
index 99c996509..51ed3b3f6 100644
--- a/playwright/e2e/app-page.spec.ts
+++ b/tests/playwright/e2e/app-page.spec.ts
@@ -20,8 +20,6 @@ import { userTest as test } from '../support/fixtures.ts'
// - never select by CSS class, especially third-party ones
// - use waitForApiResponse() from ../support/helpers.ts before assertions on mutations
test.describe('Teams app page', () => {
- test.fixme(true, 'The in-app Teams page (circles#2561) is not merged yet')
-
test('loads the app for a regular user', async ({ page }) => {
await page.goto('apps/circles/teams', { waitUntil: 'networkidle' })
await page.waitForURL(/apps\/circles\/teams/)
diff --git a/tests/playwright/merge.config.ts b/tests/playwright/merge.config.ts
new file mode 100644
index 000000000..6d99e6887
--- /dev/null
+++ b/tests/playwright/merge.config.ts
@@ -0,0 +1,11 @@
+/*!
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+// Needed to merge multiple Playwright reports
+// when they are ran on self-hosted and github runners (different test directories are used)
+export default {
+ testDir: 'tests/playwright/e2e',
+ reporter: [['html', { open: 'never' }]],
+}
diff --git a/tests/playwright/start-nextcloud-server.js b/tests/playwright/start-nextcloud-server.js
new file mode 100644
index 000000000..70e41666f
--- /dev/null
+++ b/tests/playwright/start-nextcloud-server.js
@@ -0,0 +1,48 @@
+/*!
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: MIT
+ */
+
+import { configureNextcloud, runExec, startNextcloud, stopNextcloud, waitOnNextcloud } from '@nextcloud/e2e-test-server/docker'
+import { readFileSync } from 'fs'
+import { execSync } from 'node:child_process'
+
+async function start() {
+ const port = Number.parseInt(process.env.NEXTCLOUD_PORT ?? '8042', 10)
+ const appinfo = readFileSync('appinfo/info.xml').toString()
+ const maxVersion = appinfo.match(//)?.[1]
+ let branch = 'master'
+ if (maxVersion) {
+ const refs = execSync('git ls-remote --refs').toString('utf-8')
+ branch = refs.includes(`refs/heads/stable${maxVersion}`) ? `stable${maxVersion}` : branch
+ }
+
+ const ip = await startNextcloud(branch, true, {
+ exposePort: port,
+ forceRecreate: true,
+ })
+
+ await waitOnNextcloud(ip)
+ await configureNextcloud(['circles'])
+
+ process.stdout.write('\nApply custom configuration for Playwright tests\n')
+ await runExec(['php', '-r', '$db = new SQLite3("data/owncloud.db");$db->busyTimeout(5000);$db->exec("PRAGMA journal_mode = wal;");'])
+ process.stdout.write('├─ Enabled SQLite WAL mode for better performance\n')
+
+ process.stdout.write('└─ Nextcloud container ready to run Playwright tests\n')
+}
+
+async function stop() {
+ process.stderr.write('Stopping Nextcloud server…\n')
+ await stopNextcloud()
+ process.exit(0)
+}
+
+process.on('SIGTERM', stop)
+process.on('SIGINT', stop)
+
+await start()
+
+while (true) {
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, 5000))
+}
diff --git a/playwright/start-nextcloud-server.mjs b/tests/playwright/start-nextcloud-server.mjs
similarity index 100%
rename from playwright/start-nextcloud-server.mjs
rename to tests/playwright/start-nextcloud-server.mjs
diff --git a/playwright/support/fixtures.ts b/tests/playwright/support/fixtures.ts
similarity index 100%
rename from playwright/support/fixtures.ts
rename to tests/playwright/support/fixtures.ts
diff --git a/playwright/support/helpers.ts b/tests/playwright/support/helpers.ts
similarity index 100%
rename from playwright/support/helpers.ts
rename to tests/playwright/support/helpers.ts
diff --git a/playwright/support/setup.ts b/tests/playwright/support/setup.ts
similarity index 100%
rename from playwright/support/setup.ts
rename to tests/playwright/support/setup.ts