From fd4db442e4920440ec63347ed39d7ea45fc78de0 Mon Sep 17 00:00:00 2001 From: Jeppe B <2jepp9350@gmail.com> Date: Thu, 9 Jul 2026 11:29:44 +0200 Subject: [PATCH] Add Playwright component test quarantine flow --- .github/workflows/tests.yml | 1 + package-lock.json | 224 ++++++++++++++++++ package.json | 5 +- playwright.config.ts | 2 +- playwright.ct.config.ts | 53 +++++ playwright/index.html | 12 + playwright/index.ts | 25 ++ scripts/run-playwright-pr.mjs | 22 +- .../displays/buttons/DatePeriodSelector.vue | 12 +- tests/ct/date-period-selector.ct.spec.ts | 61 +++++ tests/ct/fixtures/plenoCt.ts | 14 ++ .../date-period-selector.smoke.spec.js | 0 tests/e2e/quarantine/quarantine.json | 10 + tests/unit/playwright-quarantine.spec.js | 43 ++++ 14 files changed, 469 insertions(+), 15 deletions(-) create mode 100644 playwright.ct.config.ts create mode 100644 playwright/index.html create mode 100644 playwright/index.ts create mode 100644 tests/ct/date-period-selector.ct.spec.ts create mode 100644 tests/ct/fixtures/plenoCt.ts rename tests/e2e/{ => quarantine/component-harness}/date-period-selector.smoke.spec.js (100%) create mode 100644 tests/e2e/quarantine/quarantine.json create mode 100644 tests/unit/playwright-quarantine.spec.js diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2c9870f8..a49f0970 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -486,6 +486,7 @@ jobs: install_dependencies ulimit -n 16384 || true if [[ "$MATRIX_SUITE" == "core" ]]; then + PLAYWRIGHT_ARTIFACT_NAMESPACE="${PLAYWRIGHT_ARTIFACT_NAMESPACE}-ct" npm run test:ct -- --project="$MATRIX_PROJECT" npx playwright test --grep @smoke --project="$MATRIX_PROJECT" npm run test:e2e:pr -- --core-only --project="$MATRIX_PROJECT" else diff --git a/package-lock.json b/package-lock.json index 73ba1a77..f677dfb5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -70,6 +70,7 @@ "@creativebulma/bulma-divider": "^1.1.0", "@eslint/js": "^10.0.1", "@event-calendar/core": "^4.1.0", + "@playwright/experimental-ct-vue": "^1.58.2", "@playwright/test": "^1.58.2", "@types/event-calendar__core": "^3.7.0", "@vitejs/plugin-vue": "^6.0.5", @@ -4219,6 +4220,229 @@ "node": ">=14" } }, + "node_modules/@playwright/experimental-ct-core": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.58.2.tgz", + "integrity": "sha512-Imif9ggQp6YIblHAX6MvJuqDFrCGHYspoibxLP3+1soXp+1wBNuuSRajv0VWXzFbh//4l29I+xy2tpTgej0vEA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.58.2", + "playwright-core": "1.58.2", + "vite": "^6.4.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/fsevents": { + "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, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/@playwright/experimental-ct-vue": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-vue/-/experimental-ct-vue-1.58.2.tgz", + "integrity": "sha512-joLgyXZOV7odSY3PrEkzNRqz6rdaf7eoyTus4deSAAD6u5qvWYgDZl2mDJYiivcE+jDns9SGiZQlkMmyDcjQpg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@playwright/experimental-ct-core": "1.58.2", + "@vitejs/plugin-vue": "^5.2.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@playwright/experimental-ct-vue/node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@playwright/experimental-ct-vue/node_modules/fsevents": { + "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, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/@playwright/experimental-ct-vue/node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "peer": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, "node_modules/@playwright/test": { "version": "1.58.2", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", diff --git a/package.json b/package.json index fdf60065..31155c69 100644 --- a/package.json +++ b/package.json @@ -47,11 +47,13 @@ "test:e2e:live:public": "playwright test --config=playwright.live.config.ts --grep @public-live", "test:e2e:live:roles": "playwright test --config=playwright.live.config.ts --grep @role-live", "test:e2e:release": "npm run test:e2e:prod && npm run test:e2e:live", + "test:ct": "playwright test --config=playwright.ct.config.ts", + "test:ct:pr": "playwright test --config=playwright.ct.config.ts --project=chromium-desktop", "release:verify-upload": "node scripts/release/verify-upload.mjs", "release:update-server-version": "node scripts/release/update-server-version.mjs", "release:upload:lftp": "bash scripts/release/upload-dist-lftp.sh", "test:e2e:pos-mobile-live": "node -e \"const { spawnSync } = require('child_process'); const result = spawnSync('npx', ['playwright', 'test', 'tests/e2e/adminModulePosMobileOrderFlow.spec.ts', '--project=chromium-mobile'], { stdio: 'inherit', shell: true, env: { ...process.env, PLAYWRIGHT_LIVE: '1' } }); process.exit(result.status ?? 1);\"", - "test:all": "npm run test:unit && npm run test:e2e:pr", + "test:all": "npm run test:unit && npm run test:ct:pr && npm run test:e2e:pr", "twa:build": "bubblewrap build", "twa:update": "bubblewrap update", "mobile:sync": "npm run build && npx cap sync", @@ -128,6 +130,7 @@ "@creativebulma/bulma-divider": "^1.1.0", "@eslint/js": "^10.0.1", "@event-calendar/core": "^4.1.0", + "@playwright/experimental-ct-vue": "^1.58.2", "@playwright/test": "^1.58.2", "@types/event-calendar__core": "^3.7.0", "@vitejs/plugin-vue": "^6.0.5", diff --git a/playwright.config.ts b/playwright.config.ts index 5b553ccb..b03d66bd 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -40,7 +40,7 @@ function buildProject(name: string, browserName: "chromium" | "firefox" | "webki export default defineConfig({ testDir: "./tests/e2e", - testIgnore: ["**/release/**"], + testIgnore: ["**/release/**", "**/quarantine/**"], snapshotPathTemplate: "{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}-win32{ext}", timeout: 60_000, fullyParallel: true, diff --git a/playwright.ct.config.ts b/playwright.ct.config.ts new file mode 100644 index 00000000..2257eb7b --- /dev/null +++ b/playwright.ct.config.ts @@ -0,0 +1,53 @@ +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { defineConfig, devices } from "@playwright/experimental-ct-vue"; + +const projectRoot = fileURLToPath(new URL(".", import.meta.url)); +const artifactNamespace = (process.env.PLAYWRIGHT_ARTIFACT_NAMESPACE || "ct").trim(); +const artifactRoot = path.join("output", "playwright", artifactNamespace); +const reporterMode = (process.env.PLAYWRIGHT_REPORTER_MODE || "").trim(); +const reporter = + reporterMode === "line-html" + ? [["line"], ["html", { open: "never", outputFolder: path.join(artifactRoot, "report") }]] + : [["list"], ["html", { open: "never", outputFolder: path.join(artifactRoot, "report") }]]; +const configuredWorkers = Number(process.env.PLAYWRIGHT_WORKERS || 2); +const workers = Number.isFinite(configuredWorkers) && configuredWorkers > 0 ? configuredWorkers : 2; + +export default defineConfig({ + testDir: "./tests/ct", + timeout: 45_000, + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 1 : 0, + workers, + reporter, + outputDir: path.join(artifactRoot, "test-results"), + use: { + trace: "retain-on-failure", + screenshot: "only-on-failure", + video: "retain-on-failure", + ctViteConfig: { + resolve: { + alias: { + "@": path.resolve(projectRoot, "src"), + }, + preserveSymlinks: true, + dedupe: ["vue", "vue-router", "vue-i18n", "@vueuse/core", "@vueuse/head", "@unhead/vue"], + }, + }, + }, + projects: [ + { + name: "chromium-desktop", + use: { + ...devices["Desktop Chrome"], + }, + }, + { + name: "chromium-mobile", + use: { + ...devices["Pixel 5"], + }, + }, + ], +}); diff --git a/playwright/index.html b/playwright/index.html new file mode 100644 index 00000000..acd7e6f1 --- /dev/null +++ b/playwright/index.html @@ -0,0 +1,12 @@ + + +
+ + +