diff --git a/.gitignore b/.gitignore index 1534439eee..96e3f84e7a 100644 --- a/.gitignore +++ b/.gitignore @@ -50,8 +50,6 @@ packages/design-system/docs/public/* !/packages/web-runtime/themes/opencloud/ # Report for tests -tests/report/cucumber_report.json -tests/report/cucumber_report.html .vscode/settings.json # third party licenses diff --git a/.woodpecker.star b/.woodpecker.star index b6776eab54..fa07726dd5 100644 --- a/.woodpecker.star +++ b/.woodpecker.star @@ -55,8 +55,8 @@ config = { "earlyFail": True, "skip": False, "suites": [ - "journeys", - "smoke", + "journeys/", + "smoke/", ], "browsers": [ "chromium", @@ -68,8 +68,8 @@ config = { "earlyFail": True, "skip": False, "suites": [ - "admin-settings", - "spaces", + "admin-settings/", + "spaces/", ], }, "3": { @@ -91,10 +91,10 @@ config = { "earlyFail": True, "skip": False, "suites": [ - "navigation", - "user-settings", - "file-action", - "app-store", + "navigation/", + "user-settings/", + "file-action/", + "app-store/", ], }, "a11y": { @@ -112,7 +112,7 @@ config = { "app-provider": { "skip": False, "suites": [ - "app-provider", + "app-provider/", ], "extraServerEnvironment": { "GATEWAY_GRPC_ADDR": "0.0.0.0:9142", @@ -127,7 +127,7 @@ config = { "app-provider-onlyOffice": { "skip": False, "suites": [ - "app-provider-onlyOffice", + "app-provider-onlyOffice/", ], "extraServerEnvironment": { "GATEWAY_GRPC_ADDR": "0.0.0.0:9142", @@ -141,8 +141,8 @@ config = { }, "oidc-refresh-token": { "skip": False, - "features": [ - "cucumber/features/oidc/refreshToken.feature", + "suites": [ + "oidc/refreshToken", ], "extraServerEnvironment": { "IDP_ACCESS_TOKEN_EXPIRATION": 30, @@ -151,8 +151,8 @@ config = { }, "oidc-iframe": { "skip": False, - "features": [ - "cucumber/features/oidc/iframeTokenRenewal.feature", + "suites": [ + "oidc/iframeTokenRenewal", ], "extraServerEnvironment": { "IDP_ACCESS_TOKEN_EXPIRATION": 30, @@ -178,13 +178,13 @@ config = { "mobile-view": { "skip": False, "suites": [ - "mobile-view", + "mobile-view/", ], }, "localization-de": { "skip": False, - "features": [ - "cucumber/features/a11y/smoke.feature", + "suites": [ + "a11y/smoke", ], "extraServerEnvironment": { "OC_DEFAULT_LANGUAGE": "de", @@ -596,8 +596,7 @@ def e2eTests(ctx): "OC_BASE_URL": "opencloud:9200", "OC_SHOW_USER_EMAIL_IN_RESULTS": True, "FAIL_ON_UNCAUGHT_CONSOLE_ERR": True, - "PLAYWRIGHT_BROWSERS_PATH": ".playwright", - "BROWSER": browser_name, + "PLAYWRIGHT_BROWSERS_PATH": "tests/e2e/.playwright", "TERM": "xterm-256color", "FORCE_COLOR": "1", } @@ -635,10 +634,10 @@ def e2eTests(ctx): if browser_name == "firefox" or browser_name == "webkit": environment["FAIL_ON_UNCAUGHT_CONSOLE_ERR"] = "False" - command = "cd tests/e2e && bash run-e2e.sh " + command = "cd tests/e2e && pnpm bddgen && pnpm playwright install '%s' --with-deps && pnpm playwright test --reporter=list --project='%s' " % (browser_name, browser_name) if "suites" in matrix: - command += "--suites %s" % ",".join(params["suites"]) + command += "%s" % " ".join(params["suites"]) elif "features" in matrix: command += "%s" % " ".join(params["features"]) else: @@ -646,13 +645,13 @@ def e2eTests(ctx): return [] if "mobile-view" in suite: - command = "pnpm test:e2e:mobile-parallel" + command = "cd tests/e2e && pnpm bddgen && pnpm playwright test '%s' --reporter=list --project=mobile-chromium --project=mobile-webkit --project=ipad-chromium --project=ipad-landscape-webkit" % suite pipeline_name = "e2e-tests-%s" % suite else: pipeline_name = "e2e-tests-%s-%s" % (suite, browser_name) if "localization-de" in suite: - command = "RUN_LOCALIZATION_TEST_FOR_LANG=de pnpm test:e2e:cucumber tests/e2e/cucumber/features/a11y/smoke.feature" + command = "pnpm playwright install chromium --with-deps && cd tests/e2e && pnpm bddgen && RUN_LOCALIZATION_TEST_FOR_LANG=de playwright test a11y --project=chromium" steps += [{ "name": "e2e-tests", @@ -662,7 +661,7 @@ def e2eTests(ctx): command, ], }] + \ - uploadTracingResult(ctx) + uploadTestArtifacts() pipelines.append({ "name": pipeline_name, @@ -746,7 +745,7 @@ def installBrowsers(): "name": "install-browsers", "image": OC_CI_NODEJS, "environment": { - "PLAYWRIGHT_BROWSERS_PATH": ".playwright", + "PLAYWRIGHT_BROWSERS_PATH": "tests/e2e/.playwright", }, "commands": [ ". ./.woodpecker.env", @@ -1372,24 +1371,24 @@ def pipelineSanityChecks(pipelines): for image in images.keys(): print(" %sx\t%s" % (images[image], image)) -def uploadTracingResult(ctx): - status = ["failure"] - if "with-tracing" in ctx.build.title.lower(): - status = ["failure", "success"] - +def uploadTestArtifacts(): return [{ - "name": "upload-tracing-result", + "name": "upload-tests-artifacts", "image": MINIO_MC, "environment": minio_mc_environment, "commands": [ "mc alias set s3 $MC_HOST $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY", - "mc cp -a %s/reports/e2e/playwright/tracing/* s3/$PUBLIC_BUCKET/web/tracing/$CI_REPO_NAME/$CI_PIPELINE_NUMBER/" % dir["web"], - "cd %s/reports/e2e/playwright/tracing/" % dir["web"], - 'echo "To see the trace, please open the following link in the console"', - 'for f in *.zip; do echo "npx playwright show-trace $MC_HOST/$PUBLIC_BUCKET/web/tracing/$CI_REPO_NAME/$CI_PIPELINE_NUMBER/$f \n"; done', + # upload report and tracing + "ls -la %s/tests/e2e/test-results/" % dir["web"], + "mc cp -a --recursive %s/tests/e2e/test-results/ s3/$PUBLIC_BUCKET/web/artifacts/$CI_REPO_NAME/$CI_PIPELINE_NUMBER/test-results/" % dir["web"], + # print links + 'echo "HTML Report:"', + 'echo "$MC_HOST/$PUBLIC_BUCKET/web/artifacts/$CI_REPO_NAME/$CI_PIPELINE_NUMBER/test-results/index.html"', + 'echo "Traces:"', + 'mc find s3/$PUBLIC_BUCKET/web/artifacts/$CI_REPO_NAME/$CI_PIPELINE_NUMBER/test-results/ --name "trace.zip" | sed "s|s3/$PUBLIC_BUCKET/||" | while read f; do echo "npx playwright show-trace $MC_HOST/$PUBLIC_BUCKET/$f"; done', ], "when": { - "status": status, + "status": ["failure", "success"], }, }] @@ -1627,18 +1626,19 @@ def e2eTestsOnKeycloak(ctx): "REPORT_TRACING": "with-tracing" in ctx.build.title.lower(), "KEYCLOAK": True, "KEYCLOAK_HOST": "keycloak:8443", - "PLAYWRIGHT_BROWSERS_PATH": ".playwright", - "BROWSER": "chromium", + "PLAYWRIGHT_BROWSERS_PATH": "tests/e2e/.playwright", "TERM": "xterm-256color", "FORCE_COLOR": "1", }, "commands": [ "cd tests/e2e", - "bash run-e2e.sh cucumber/features/keycloak", + "pnpm playwright install chromium && pnpm bddgen && pnpm playwright test keycloak --project=chromium --reporter=list", + "pwd && ls -la test-results/", + "ls -la playwright-report/", ], }, ] + \ - uploadTracingResult(ctx) + uploadTestArtifacts() return [{ "name": "e2e-test-on-keycloak", diff --git a/AGENTS.md b/AGENTS.md index 842ba5becb..366610712c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,7 +20,7 @@ OpenCloud Web is a TypeScript/Vue 3 single-page application that serves as the b │ ├── extension-sdk/ # Utilities for custom extensions │ └── web-app-*/ # Standalone apps/extensions (files, search, preview, …) ├── tests/ -│ └── e2e/ # End-to-end tests (Playwright + Cucumber) +│ └── e2e/ # End-to-end tests (Playwright + Playwright BDD) ├── dev/ # Docker/infrastructure config for local development ├── docker-compose.yml ├── vite.config.ts # Root Vite config @@ -100,7 +100,7 @@ Enforced via ESLint (`packages/eslint-config`). Run `pnpm lint` to check. ### End-to-End Tests -- **Framework:** [Playwright](https://playwright.dev/) + [Cucumber](https://cucumber.io/) +- **Framework:** [Playwright](https://playwright.dev/) + [Playwright BDD](https://vitalets.github.io/playwright-bdd/) - **Location:** `tests/e2e/` (outside of `packages/`) - **Prerequisites:** Run `pnpm build` before executing e2e tests. A running OpenCloud backend is also required — use `docker-compose up -d` to start one locally. - **Run:** `pnpm test:e2e:cucumber` diff --git a/cucumber.mjs b/cucumber.mjs deleted file mode 100644 index 45b79bf32a..0000000000 --- a/cucumber.mjs +++ /dev/null @@ -1,25 +0,0 @@ -import path from 'path' -import fs from 'fs' -import { config } from './tests/e2e/config.js' - -if (!fs.existsSync(config.reportDir)) { - fs.mkdirSync(path.join(config.reportDir, 'cucumber'), { recursive: true }) -} - -const e2e = ` - --loader ts-node/esm - --import ./tests/e2e/**/*.ts - --retry ${config.retry} - --format @cucumber/pretty-formatter - --format pretty - --format json:${path.join(config.reportDir, 'cucumber', 'report.json')} - --format message:${path.join(config.reportDir, 'cucumber', 'report.ndjson')} - --format html:${path.join(config.reportDir, 'cucumber', 'report.html')} - --format-options ${JSON.stringify({ - snippetInterface: 'async-await', - snippetSyntax: './tests/e2e/cucumber/environment/snippets-syntax.mjs' - })} - ` - -export { e2e } -export default {} diff --git a/package.json b/package.json index 5cb77abcb8..5510e5d965 100644 --- a/package.json +++ b/package.json @@ -11,24 +11,13 @@ "lint": "eslint vite.config.ts '{packages,tests}/**/*.{js,ts,vue}' --color", "format:check": "prettier . --config packages/prettier-config/index.js --check", "format:write": "prettier . --config packages/prettier-config/index.js --write", - "serve": "SERVER=true pnpm build:w", - "test:e2e:cucumber": "NODE_TLS_REJECT_UNAUTHORIZED=0 TS_NODE_PROJECT=./tests/e2e/cucumber/tsconfig.json cucumber-js --profile=e2e --parallel ${PARALLEL:-1}", - "test:e2e:cucumber:chromium": "BROWSER=chromium NODE_TLS_REJECT_UNAUTHORIZED=0 TS_NODE_PROJECT=./tests/e2e/cucumber/tsconfig.json cucumber-js --profile=e2e --parallel ${PARALLEL:-1}", - "test:e2e:cucumber:firefox": "BROWSER=firefox NODE_TLS_REJECT_UNAUTHORIZED=0 TS_NODE_PROJECT=./tests/e2e/cucumber/tsconfig.json cucumber-js --profile=e2e --parallel ${PARALLEL:-1}", - "test:e2e:cucumber:webkit": "BROWSER=webkit NODE_TLS_REJECT_UNAUTHORIZED=0 TS_NODE_PROJECT=./tests/e2e/cucumber/tsconfig.json cucumber-js --profile=e2e --parallel ${PARALLEL:-1} --tags 'not @webkit-skip'", - "test:e2e:mobile-parallel": "bash -c 'FORCE_COLOR=1 pnpm run test:e2e:mobile-chromium > mobile-chromium.log 2>&1 & pid1=$!; FORCE_COLOR=1 pnpm run test:e2e:mobile-webkit > mobile-webkit.log 2>&1 & pid2=$!; FORCE_COLOR=1 pnpm run test:e2e:ipad-chromium > ipad-chromium.log 2>&1 & pid3=$!; FORCE_COLOR=1 pnpm run test:e2e:ipad-safari > ipad-safari.log 2>&1 & pid4=$!; wait $pid1; ec1=$?; wait $pid2; ec2=$?; wait $pid3; ec3=$?; wait $pid4; ec4=$?; echo \"=== LOG FILES ===\"; for log in *.log; do echo \"--- $log ---\"; cat $log; echo; done; echo \"=== RESULTS ===\"; echo \"Mobile Chromium: $([ $ec1 -eq 0 ] && echo ✅ PASSED || echo ❌ FAILED)\"; echo \"Mobile WebKit: $([ $ec2 -eq 0 ] && echo ✅ PASSED || echo ❌ FAILED)\"; echo \"iPad Chromium: $([ $ec3 -eq 0 ] && echo ✅ PASSED || echo ❌ FAILED)\"; echo \"iPad Safari: $([ $ec4 -eq 0 ] && echo ✅ PASSED || echo ❌ FAILED)\"; if [ $ec1 -ne 0 ] || [ $ec2 -ne 0 ] || [ $ec3 -ne 0 ] || [ $ec4 -ne 0 ]; then exit 1; fi'", - "test:e2e:mobile-chromium": "BROWSER=mobile-chromium NODE_TLS_REJECT_UNAUTHORIZED=0 TS_NODE_PROJECT=./tests/e2e/cucumber/tsconfig.json cucumber-js --profile=e2e ./tests/e2e/cucumber/features/mobile-view", - "test:e2e:mobile-webkit": "FAIL_ON_UNCAUGHT_CONSOLE_ERR=false BROWSER=mobile-webkit NODE_TLS_REJECT_UNAUTHORIZED=0 TS_NODE_PROJECT=./tests/e2e/cucumber/tsconfig.json cucumber-js --profile=e2e ./tests/e2e/cucumber/features/mobile-view", - "test:e2e:ipad-chromium": "BROWSER=ipad-chromium NODE_TLS_REJECT_UNAUTHORIZED=0 TS_NODE_PROJECT=./tests/e2e/cucumber/tsconfig.json cucumber-js --profile=e2e ./tests/e2e/cucumber/features/mobile-view", - "test:e2e:ipad-safari": "BROWSER=ipad-landscape-webkit NODE_TLS_REJECT_UNAUTHORIZED=0 TS_NODE_PROJECT=./tests/e2e/cucumber/tsconfig.json cucumber-js --profile=e2e ./tests/e2e/cucumber/features/mobile-view", "test:unit": "NODE_OPTIONS=--unhandled-rejections=throw vitest --config ./tests/unit/config/vitest.config.ts", "licenses:check": "license-checker-rseidelsohn --summary --relativeLicensePath --onlyAllow 'Python-2.0;Apache*;Apache License, Version 2.0;Apache-2.0;Apache 2.0;Artistic-2.0;BSD;BSD-3-Clause;CC-BY-3.0;CC-BY-4.0;CC0-1.0;ISC;MIT;MPL-2.0;Public Domain;Unicode-TOU;Unlicense;WTFPL;BlueOak-1.0.0' --excludePackages '@opencloud-eu/eslint-config;@opencloud-eu/prettier-config;@opencloud-eu/tsconfig;@opencloud-eu/web-client;@opencloud-eu/web-pkg;external;web-app-files;text-editor;preview;web-app-ocm;@opencloud-eu/design-system;pdf-viewer;web-app-search;admin-settings;webfinger;web-runtime;@opencloud-eu/web-test-helpers'", "licenses:csv": "license-checker-rseidelsohn --relativeLicensePath --csv --out ./third-party-licenses/third-party-licenses.csv", "licenses:save": "license-checker-rseidelsohn --relativeLicensePath --out /dev/null --files ./third-party-licenses/third-party-licenses", "vite": "vite", "check:types": "vue-tsc --noEmit && pnpm -r --parallel check:types", - "check:all": "pnpm check:types && pnpm lint --fix && pnpm format:write && pnpm test:unit", - "check:unused-steps": "TS_NODE_PROJECT=./tests/e2e/cucumber/tsconfig.json cucumber-js --profile=e2e --format usage --dry-run tests/e2e/cucumber/features/*/*.feature" + "check:all": "pnpm check:types && pnpm lint --fix && pnpm format:write && pnpm test:unit" }, "browserslist": [ "last 1 year", @@ -42,8 +31,6 @@ ], "devDependencies": { "@axe-core/playwright": "^4.11.1", - "@cucumber/cucumber": "12.8.2", - "@cucumber/messages": "32.3.1", "@cucumber/pretty-formatter": "3.3.0", "@module-federation/runtime": "2.4.0", "@module-federation/vite": "1.15.4", @@ -72,6 +59,8 @@ "node-fetch": "3.3.2", "pino": "10.3.1", "pino-pretty": "13.1.3", + "playwright": "1.59.1", + "playwright-bdd": "8.5.1", "qs": "^6.15.0", "tailwindcss": "^4.2.2", "ts-node": "10.9.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6c7fcf40ff..ad2ee083b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,12 +19,6 @@ importers: '@axe-core/playwright': specifier: ^4.11.1 version: 4.11.3(playwright-core@1.59.1) - '@cucumber/cucumber': - specifier: 12.8.2 - version: 12.8.2 - '@cucumber/messages': - specifier: 32.3.1 - version: 32.3.1 '@cucumber/pretty-formatter': specifier: 3.3.0 version: 3.3.0(@cucumber/messages@32.3.1) @@ -109,6 +103,12 @@ importers: pino-pretty: specifier: 13.1.3 version: 13.1.3 + playwright: + specifier: 1.59.1 + version: 1.59.1 + playwright-bdd: + specifier: 8.5.1 + version: 8.5.1(@playwright/test@1.59.1) qs: specifier: ^6.15.0 version: 6.15.1 @@ -1148,6 +1148,12 @@ importers: '@ai-zen/node-fetch-event-source': specifier: ^2.1.4 version: 2.1.4 + '@playwright/test': + specifier: ^1.59.1 + version: 1.59.1 + '@types/node': + specifier: ^25.6.0 + version: 25.6.0 fast-xml-parser: specifier: ^5.5.9 version: 5.7.2 @@ -1247,10 +1253,6 @@ packages: peerDependencies: playwright-core: '>= 1.0.0' - '@babel/code-frame@7.29.0': - resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} - engines: {node: '>=6.9.0'} - '@babel/generator@7.29.1': resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} @@ -1329,58 +1331,45 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@cucumber/ci-environment@13.0.0': - resolution: {integrity: sha512-cs+3NzfNkGbcmHPddjEv4TKFiBpZRQ6WJEEufB9mw+ExS22V/4R/zpDSEG+fsJ/iSNCd6A2sATdY8PFOyY3YnA==} + '@cucumber/cucumber-expressions@18.0.1': + resolution: {integrity: sha512-NSid6bI+7UlgMywl5octojY5NXnxR9uq+JisjOrO52VbFsQM6gTWuQFE8syI10KnIBEdPzuEUSVEeZ0VFzRnZA==} - '@cucumber/cucumber-expressions@19.0.0': - resolution: {integrity: sha512-4FKoOQh2Uf6F6/Ln+1OxuK8LkTg6PyAqekhf2Ix8zqV2M54sH+m7XNJNLhOFOAW/t9nxzRbw2CcvXbCLjcvHZg==} - - '@cucumber/cucumber@12.8.2': - resolution: {integrity: sha512-IvprstODr0JYTtVG7CQbphN6AGRpzzAQ1EjG7TSumuS15uvVt0inWm8/9uzX8oJwEv5ReU7JruDFim4938omog==} - engines: {node: 20 || 22 || >=24} + '@cucumber/gherkin-utils@9.2.0': + resolution: {integrity: sha512-3nmRbG1bUAZP3fAaUBNmqWO0z0OSkykZZotfLjyhc8KWwDSOrOmMJlBTd474lpA8EWh4JFLAX3iXgynBqBvKzw==} hasBin: true - '@cucumber/gherkin-streams@6.0.0': - resolution: {integrity: sha512-HLSHMmdDH0vCr7vsVEURcDA4WwnRLdjkhqr6a4HQ3i4RFK1wiDGPjBGVdGJLyuXuRdJpJbFc6QxHvT8pU4t6jw==} - hasBin: true - peerDependencies: - '@cucumber/gherkin': '>=22.0.0' - '@cucumber/message-streams': '>=4.0.0' - '@cucumber/messages': '>=17.1.1' - - '@cucumber/gherkin-utils@11.0.0': - resolution: {integrity: sha512-LJ+s4+TepHTgdKWDR4zbPyT7rQjmYIcukTwNbwNwgqr6i8Gjcmzf6NmtbYDA19m1ZFg6kWbFsmHnj37ZuX+kZA==} - hasBin: true + '@cucumber/gherkin@31.0.0': + resolution: {integrity: sha512-wlZfdPif7JpBWJdqvHk1Mkr21L5vl4EfxVUOS4JinWGf3FLRV6IKUekBv5bb5VX79fkDcfDvESzcQ8WQc07Wgw==} - '@cucumber/gherkin@38.0.0': - resolution: {integrity: sha512-duEXK+KDfQUzu3vsSzXjkxQ2tirF5PRsc1Xrts6THKHJO6mjw4RjM8RV+vliuDasmhhrmdLcOcM7d9nurNTJKw==} + '@cucumber/gherkin@32.2.0': + resolution: {integrity: sha512-X8xuVhSIqlUjxSRifRJ7t0TycVWyX58fygJH3wDNmHINLg9sYEkvQT0SO2G5YlRZnYc11TIFr4YPenscvdlBIw==} - '@cucumber/html-formatter@23.1.0': - resolution: {integrity: sha512-DcCSFoGs6jbwzXPgX1CwgJKEE+ZMcIEzq/0Memg0o24maNn9NJizBFHmoFWG4iv/OxHza+mvc+56cTHetfHndw==} + '@cucumber/html-formatter@21.15.1': + resolution: {integrity: sha512-tjxEpP161sQ7xc3VREc94v1ymwIckR3ySViy7lTvfi1jUpyqy2Hd/p4oE3YT1kQ9fFDvUflPwu5ugK5mA7BQLA==} peerDependencies: '@cucumber/messages': '>=18' - '@cucumber/junit-xml-formatter@0.13.3': - resolution: {integrity: sha512-w9ujOxiuKDtU6fLzJz+wp4Sgp5Xu6ba7ls00LHJccVmQU0Ba7zs+AHnv3iIgPjKZAQe1w8x93dr8Gaubh7Vqkg==} + '@cucumber/junit-xml-formatter@0.7.1': + resolution: {integrity: sha512-AzhX+xFE/3zfoYeqkT7DNq68wAQfBcx4Dk9qS/ocXM2v5tBv6eFQ+w8zaSfsktCjYzu4oYRH/jh4USD1CYHfaQ==} peerDependencies: '@cucumber/messages': '*' - '@cucumber/message-streams@4.1.1': - resolution: {integrity: sha512-QCAntLajesWMyX+mZKrj63YghVAts7yKFlZe46XprLbdJZN0ddB+f/Mr9OnyWKC2DHhJ18jzCfKIFCaqpAmUxg==} - peerDependencies: - '@cucumber/messages': '>=17.1.1' + '@cucumber/messages@26.0.1': + resolution: {integrity: sha512-DIxSg+ZGariumO+Lq6bn4kOUIUET83A4umrnWmidjGFl8XxkBieUZtsmNbLYgH/gnsmP07EfxxdTr0hOchV1Sg==} + + '@cucumber/messages@27.2.0': + resolution: {integrity: sha512-f2o/HqKHgsqzFLdq6fAhfG1FNOQPdBdyMGpKwhb7hZqg0yZtx9BVqkTyuoNk83Fcvk3wjMVfouFXXHNEk4nddA==} '@cucumber/messages@32.3.1': resolution: {integrity: sha512-yNQq1KoXRYaEKrWMFmpUQX7TdeQuU9jeGgJAZ3dArTsC/T4NpJ6DnqaJIIgwPnz/wtQIQTNX7/h0rOuF5xY4qQ==} - '@cucumber/pretty-formatter@1.0.1': - resolution: {integrity: sha512-A1lU4VVP0aUWdOTmpdzvXOyEYuPtBDI0xYwYJnmoMDplzxMdhcHk86lyyvYDoMoPzzq6OkOE3isuosvUU4X7IQ==} + '@cucumber/pretty-formatter@3.3.0': + resolution: {integrity: sha512-3wA2O8RXtHnvzSGKA46z4MI4PuSYYWNtXYiBHYjP9bIO+Ee87ZKsOk8XKdIv4ECwXSsbCmYW0XIN4nXI0yA4Fw==} peerDependencies: - '@cucumber/cucumber': '>=7.0.0' '@cucumber/messages': '*' - '@cucumber/pretty-formatter@3.3.0': - resolution: {integrity: sha512-3wA2O8RXtHnvzSGKA46z4MI4PuSYYWNtXYiBHYjP9bIO+Ee87ZKsOk8XKdIv4ECwXSsbCmYW0XIN4nXI0yA4Fw==} + '@cucumber/query@13.6.0': + resolution: {integrity: sha512-tiDneuD5MoWsJ9VKPBmQok31mSX9Ybl+U4wqDoXeZgsXHDURqzM3rnpWVV3bC34y9W6vuFxrlwF/m7HdOxwqRw==} peerDependencies: '@cucumber/messages': '*' @@ -1389,8 +1378,8 @@ packages: peerDependencies: '@cucumber/messages': '*' - '@cucumber/tag-expressions@9.1.0': - resolution: {integrity: sha512-bvHjcRFZ+J1TqIa9eFNO1wGHqwx4V9ZKV3hYgkuK/VahHx73uiP4rKV3JVrvWSMrwrFvJG6C8aEwnCWSvbyFdQ==} + '@cucumber/tag-expressions@6.2.0': + resolution: {integrity: sha512-KIF0eLcafHbWOuSDWFw0lMmgJOLdDRWjEL1kfXEWrqHmx2119HxVAr35WuEd9z542d3Yyg+XNqSr+81rIKqEdg==} '@docsearch/css@3.8.2': resolution: {integrity: sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==} @@ -1862,6 +1851,18 @@ packages: '@nodable/entities@2.1.0': resolution: {integrity: sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==} + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + '@npmcli/fs@3.1.1': resolution: {integrity: sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -2654,9 +2655,6 @@ packages: '@types/node@25.6.0': resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} - '@types/normalize-package-data@2.4.4': - resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - '@types/qs@6.15.0': resolution: {integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==} @@ -2669,6 +2667,9 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/uuid@10.0.0': + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + '@types/web-bluetooth@0.0.21': resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} @@ -3021,10 +3022,6 @@ packages: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} - ansi-regex@4.1.1: - resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} - engines: {node: '>=6'} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -3037,17 +3034,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - ansi-styles@6.2.3: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -3073,9 +3063,6 @@ packages: assert@2.1.0: resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==} - assertion-error-formatter@3.0.0: - resolution: {integrity: sha512-6YyAVLrEze0kQ7CmJfUgrLHb+Y7XghmL2Ie7ijVa2Y9ynP3LV+VDiwFk62Dn0qtqbmY0BT0ss6p1xxpiF2PYbQ==} - assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -3232,9 +3219,6 @@ packages: caniuse-lite@1.0.30001791: resolution: {integrity: sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==} - capital-case@1.0.4: - resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} - ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -3327,17 +3311,9 @@ packages: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} - commander@14.0.0: - resolution: {integrity: sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==} - engines: {node: '>=20'} - - commander@14.0.2: - resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} - engines: {node: '>=20'} - - commander@14.0.3: - resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} - engines: {node: '>=20'} + commander@13.1.0: + resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} + engines: {node: '>=18'} confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} @@ -3539,9 +3515,6 @@ packages: epubjs@0.3.93: resolution: {integrity: sha512-c06pNSdBxcXv3dZSbXAVLE1/pmleRhOT6mXNZo6INKmvuKpYB65MwU/lO7830czCtjIiK9i+KR+3S+p0wtljrw==} - error-stack-parser@2.1.4: - resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} - es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -3586,10 +3559,6 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -3708,6 +3677,10 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -3724,6 +3697,9 @@ packages: resolution: {integrity: sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==} hasBin: true + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -3737,10 +3713,6 @@ packages: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} - figures@3.2.0: - resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} - engines: {node: '>=8'} - file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -3770,10 +3742,6 @@ packages: '@75lb/nature': optional: true - find-up-simple@1.0.1: - resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} - engines: {node: '>=18'} - find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -3870,14 +3838,6 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true - glob@13.0.6: - resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} - engines: {node: 18 || 20 || >=22} - - global-dirs@3.0.1: - resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} - engines: {node: '>=10'} - global-modules@1.0.0: resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==} engines: {node: '>=0.10.0'} @@ -3897,10 +3857,6 @@ packages: resolution: {integrity: sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ==} engines: {node: '>=20.0.0'} - has-ansi@4.0.1: - resolution: {integrity: sha512-Qr4RtTm30xvEdqUXbSBVWDu+PrTokJOwe/FU+VdfJPk+MXAPoeOzKpRyrDTnZIJwAkQ4oBLTU53nu0HrkF/Z2A==} - engines: {node: '>=8'} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -3954,10 +3910,6 @@ packages: resolution: {integrity: sha512-HVJyzUrLIL1c0QmviVh5E8VGyUS7xCFPS6yydaVd1UegW+ibV/CohqTH9MkOLDp5o+rb82DMo77PTuc9F/8GKw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - hosted-git-info@9.0.2: - resolution: {integrity: sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==} - engines: {node: ^20.17.0 || >=22.9.0} - hot-patcher@2.0.1: resolution: {integrity: sha512-ECg1JFG0YzehicQaogenlcs2qg6WsXQsxtnbr1i696u5tLUjtJdQAh0u2g0Q5YV45f263Ta1GnUJsc8WIfJf4Q==} @@ -3994,24 +3946,12 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - - index-to-position@1.2.0: - resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} - engines: {node: '>=18'} - inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - ini@2.0.0: - resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} - engines: {node: '>=10'} - is-arguments@1.2.0: resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} engines: {node: '>= 0.4'} @@ -4047,10 +3987,6 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} - is-installed-globally@0.4.0: - resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} - engines: {node: '>=10'} - is-nan@1.3.2: resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} engines: {node: '>= 0.4'} @@ -4063,10 +3999,6 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -4147,9 +4079,6 @@ packages: js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -4183,9 +4112,6 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - knuth-shuffle-seeded@1.0.6: - resolution: {integrity: sha512-9pFH0SplrfyKyojCLxZfMcvkhf5hH0d+UwR9nTVJ/DDQJGuzcXjTwB7TP7sDfehSudlGGaOLblmEWqv04ERVWg==} - layerr@3.0.0: resolution: {integrity: sha512-tv754Ki2dXpPVApOrjTyRo4/QegVb9eVFq4mjqp4+NM5NaX7syQvN5BBNfV/ZpAHCEHV24XdUVrBAoka4jt3pA==} @@ -4323,12 +4249,6 @@ packages: lodash.clonedeep@4.5.0: resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - - lodash.mergewith@4.6.2: - resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} - lodash.sortby@4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} @@ -4344,9 +4264,6 @@ packages: long-timeout@0.1.1: resolution: {integrity: sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==} - lower-case@2.0.2: - resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -4410,6 +4327,10 @@ packages: resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} engines: {node: '>=18'} + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + micromark-util-character@2.1.1: resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} @@ -4425,6 +4346,10 @@ packages: micromark-util-types@2.0.2: resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + miller-rabin@4.0.1: resolution: {integrity: sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==} hasBin: true @@ -4433,6 +4358,10 @@ packages: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + mime-match@1.0.2: resolution: {integrity: sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==} @@ -4440,10 +4369,9 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - mime@3.0.0: - resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} - engines: {node: '>=10.0.0'} - hasBin: true + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} @@ -4477,11 +4405,6 @@ packages: engines: {node: '>=10'} hasBin: true - mkdirp@3.0.1: - resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} - engines: {node: '>=10'} - hasBin: true - mlly@1.8.2: resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} @@ -4491,9 +4414,6 @@ packages: muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - n-gram@2.0.2: resolution: {integrity: sha512-S24aGsn+HLBxUGVAUFOwGpKs7LBcG4RudKU//eWzt/mQ97/NMKQxDWHyHx63UNWk/OOdihgmzoETn1tf5nQDzQ==} @@ -4519,9 +4439,6 @@ packages: next-tick@1.1.0: resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} - no-case@3.0.4: - resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} - node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} @@ -4563,10 +4480,6 @@ packages: resolution: {integrity: sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - normalize-package-data@8.0.0: - resolution: {integrity: sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==} - engines: {node: ^20.17.0 || >=22.9.0} - normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -4578,10 +4491,6 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} @@ -4652,10 +4561,6 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - pad-right@0.2.2: - resolution: {integrity: sha512-4cy8M95ioIGolCoMmm2cMntGR1lPLEbOMzOKu8bzjuJP6JpzEMQcDHmh7hHLYGgob+nKe1YHFMaG4V59HQa89g==} - engines: {node: '>=0.10.0'} - pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} @@ -4663,10 +4568,6 @@ packages: resolution: {integrity: sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg==} engines: {node: '>= 0.10'} - parse-json@8.3.0: - resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} - engines: {node: '>=18'} - parse-passwd@1.0.0: resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} engines: {node: '>=0.10.0'} @@ -4763,6 +4664,13 @@ packages: pkg-types@2.3.0: resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + playwright-bdd@8.5.1: + resolution: {integrity: sha512-lDNaDzW8RvbvsKuR8cZaP9LBnRbG9juCOE3tgwm3pr1O0W1ooGPz7X8xH7zdUbqGgHbdOQ+5XpUTlOJrvpY6Tw==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@playwright/test': '>=1.44' + playwright-core@1.59.1: resolution: {integrity: sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==} engines: {node: '>=18'} @@ -4810,16 +4718,9 @@ packages: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} - progress@2.0.3: - resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} - engines: {node: '>=0.4.0'} - proper-lockfile@4.1.2: resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} - property-expr@2.0.6: - resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==} - property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} @@ -4893,6 +4794,9 @@ packages: querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} @@ -4911,14 +4815,6 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} deprecated: This package is no longer supported. Please use @npmcli/package-json instead. - read-package-up@12.0.0: - resolution: {integrity: sha512-Q5hMVBYur/eQNWDdbF4/Wqqr9Bjvtrw2kjGxxBbKLbx8bVCL8gcArjTy8zDUuLGQicftpMuU0riQNcAsbtOVsw==} - engines: {node: '>=20'} - - read-pkg@10.1.0: - resolution: {integrity: sha512-I8g2lArQiP78ll51UeMZojewtYgIRCKCWqZEgOO8c/uefTI+XDXvCSXu3+YNUaTNvZzobrL5+SqHjBrByRRTdg==} - engines: {node: '>=20'} - readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -4961,10 +4857,6 @@ packages: resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} hasBin: true - repeat-string@1.6.1: - resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} - engines: {node: '>=0.10'} - requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} @@ -4989,6 +4881,10 @@ packages: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} @@ -5009,6 +4905,9 @@ packages: rope-sequence@1.3.4: resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} @@ -5037,9 +4936,6 @@ packages: secure-json-parse@4.1.0: resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} - seed-random@2.2.0: - resolution: {integrity: sha512-34EQV6AAHQGhoc0tn/96a9Fsi6v2xdqe/dMUwljGRaFOzR3EgRmECvD0O8vi8X+/uQ50LGHfkNu/Eue5TPKZkQ==} - semver@7.7.4: resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} @@ -5149,9 +5045,6 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - stackframe@1.3.4: - resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} - std-env@4.1.0: resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} @@ -5161,10 +5054,6 @@ packages: stream-http@3.2.0: resolution: {integrity: sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==} - string-argv@0.3.1: - resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} - engines: {node: '>=0.6.19'} - string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -5205,10 +5094,6 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} - supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -5216,10 +5101,6 @@ packages: tabbable@6.4.0: resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} - tagged-tag@1.0.0: - resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} - engines: {node: '>=20'} - tailwindcss@4.2.4: resolution: {integrity: sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==} @@ -5227,13 +5108,6 @@ packages: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - thread-stream@4.0.0: resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==} engines: {node: '>=20'} @@ -5242,9 +5116,6 @@ packages: resolution: {integrity: sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==} engines: {node: '>=0.6.0'} - tiny-case@1.0.3: - resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==} - tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -5268,9 +5139,6 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - toposort@2.0.2: - resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} - tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -5290,10 +5158,6 @@ packages: peerDependencies: typescript: '>=4.8.4' - ts-dedent@2.2.0: - resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} - engines: {node: '>=6.10'} - ts-essentials@10.1.1: resolution: {integrity: sha512-4aTB7KLHKmUvkjNj8V+EdnmuVTiECzn3K+zIbRthumvHu+j44x3w63xpfs0JL3NGIzGXqoQ7AV591xHO+XrOTw==} peerDependencies: @@ -5330,18 +5194,6 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-fest@2.19.0: - resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} - engines: {node: '>=12.20'} - - type-fest@4.41.0: - resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} - engines: {node: '>=16'} - - type-fest@5.5.0: - resolution: {integrity: sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g==} - engines: {node: '>=20'} - type@2.7.3: resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} @@ -5379,10 +5231,6 @@ packages: resolution: {integrity: sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==} engines: {node: '>=20.18.1'} - unicorn-magic@0.4.0: - resolution: {integrity: sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==} - engines: {node: '>=20'} - unist-util-is@6.0.1: resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} @@ -5412,9 +5260,6 @@ packages: peerDependencies: browserslist: '>= 4.21.0' - upper-case-first@2.0.2: - resolution: {integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==} - uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -5429,15 +5274,21 @@ packages: resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==} engines: {node: '>= 0.4'} - util-arity@1.1.0: - resolution: {integrity: sha512-kkyIsXKwemfSy8ZEoaIz06ApApnWsk5hQO0vLjZS6UkBiGiW++Jsyb8vSBoc0WKlffGoGs5yYy/j5pp8zckrFA==} - util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} util@0.12.5: resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). + hasBin: true + + uuid@11.0.5: + resolution: {integrity: sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==} + hasBin: true + uuid@14.0.0: resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==} hasBin: true @@ -5777,9 +5628,6 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - yup@1.7.1: - resolution: {integrity: sha512-GKHFX2nXul2/4Dtfxhozv701jLQHdf6J34YDh2cEkpqoo8le5Mg6/LrdseVLrFarmFygZTlfIhHx/QKfb/QWXw==} - zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} @@ -5911,12 +5759,6 @@ snapshots: axe-core: 4.11.4 playwright-core: 1.59.1 - '@babel/code-frame@7.29.0': - dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 - '@babel/generator@7.29.1': dependencies: '@babel/parser': 7.29.2 @@ -6026,104 +5868,56 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@cucumber/ci-environment@13.0.0': {} - - '@cucumber/cucumber-expressions@19.0.0': + '@cucumber/cucumber-expressions@18.0.1': dependencies: regexp-match-indices: 1.0.2 - '@cucumber/cucumber@12.8.2': - dependencies: - '@cucumber/ci-environment': 13.0.0 - '@cucumber/cucumber-expressions': 19.0.0 - '@cucumber/gherkin': 38.0.0 - '@cucumber/gherkin-streams': 6.0.0(@cucumber/gherkin@38.0.0)(@cucumber/message-streams@4.1.1(@cucumber/messages@32.3.1))(@cucumber/messages@32.3.1) - '@cucumber/gherkin-utils': 11.0.0 - '@cucumber/html-formatter': 23.1.0(@cucumber/messages@32.3.1) - '@cucumber/junit-xml-formatter': 0.13.3(@cucumber/messages@32.3.1) - '@cucumber/message-streams': 4.1.1(@cucumber/messages@32.3.1) - '@cucumber/messages': 32.3.1 - '@cucumber/pretty-formatter': 1.0.1(@cucumber/cucumber@12.8.2)(@cucumber/messages@32.3.1) - '@cucumber/tag-expressions': 9.1.0 - assertion-error-formatter: 3.0.0 - capital-case: 1.0.4 - chalk: 4.1.2 - cli-table3: 0.6.5 - commander: 14.0.3 - debug: 4.4.3(supports-color@8.1.1) - error-stack-parser: 2.1.4 - figures: 3.2.0 - glob: 13.0.6 - has-ansi: 4.0.1 - indent-string: 4.0.0 - is-installed-globally: 0.4.0 - is-stream: 2.0.1 - knuth-shuffle-seeded: 1.0.6 - lodash.merge: 4.6.2 - lodash.mergewith: 4.6.2 - luxon: 3.7.2 - mkdirp: 3.0.1 - mz: 2.7.0 - progress: 2.0.3 - read-package-up: 12.0.0 - semver: 7.7.4 - string-argv: 0.3.1 - supports-color: 8.1.1 - type-fest: 4.41.0 - util-arity: 1.1.0 - yaml: 2.8.3 - yup: 1.7.1 - - '@cucumber/gherkin-streams@6.0.0(@cucumber/gherkin@38.0.0)(@cucumber/message-streams@4.1.1(@cucumber/messages@32.3.1))(@cucumber/messages@32.3.1)': + '@cucumber/gherkin-utils@9.2.0': dependencies: - '@cucumber/gherkin': 38.0.0 - '@cucumber/message-streams': 4.1.1(@cucumber/messages@32.3.1) - '@cucumber/messages': 32.3.1 - commander: 14.0.0 + '@cucumber/gherkin': 31.0.0 + '@cucumber/messages': 27.2.0 + '@teppeis/multimaps': 3.0.0 + commander: 13.1.0 source-map-support: 0.5.21 - '@cucumber/gherkin-utils@11.0.0': + '@cucumber/gherkin@31.0.0': dependencies: - '@cucumber/gherkin': 38.0.0 - '@cucumber/messages': 32.3.1 - '@teppeis/multimaps': 3.0.0 - commander: 14.0.2 - source-map-support: 0.5.21 + '@cucumber/messages': 26.0.1 - '@cucumber/gherkin@38.0.0': + '@cucumber/gherkin@32.2.0': dependencies: - '@cucumber/messages': 32.3.1 + '@cucumber/messages': 27.2.0 - '@cucumber/html-formatter@23.1.0(@cucumber/messages@32.3.1)': + '@cucumber/html-formatter@21.15.1(@cucumber/messages@27.2.0)': dependencies: - '@cucumber/messages': 32.3.1 + '@cucumber/messages': 27.2.0 - '@cucumber/junit-xml-formatter@0.13.3(@cucumber/messages@32.3.1)': + '@cucumber/junit-xml-formatter@0.7.1(@cucumber/messages@27.2.0)': dependencies: - '@cucumber/messages': 32.3.1 - '@cucumber/query': 15.0.1(@cucumber/messages@32.3.1) + '@cucumber/messages': 27.2.0 + '@cucumber/query': 13.6.0(@cucumber/messages@27.2.0) '@teppeis/multimaps': 3.0.0 luxon: 3.7.2 xmlbuilder: 15.1.1 - '@cucumber/message-streams@4.1.1(@cucumber/messages@32.3.1)': + '@cucumber/messages@26.0.1': dependencies: - '@cucumber/messages': 32.3.1 - mime: 3.0.0 + '@types/uuid': 10.0.0 + class-transformer: 0.5.1 + reflect-metadata: 0.2.2 + uuid: 10.0.0 - '@cucumber/messages@32.3.1': + '@cucumber/messages@27.2.0': dependencies: + '@types/uuid': 10.0.0 class-transformer: 0.5.1 reflect-metadata: 0.2.2 + uuid: 11.0.5 - '@cucumber/pretty-formatter@1.0.1(@cucumber/cucumber@12.8.2)(@cucumber/messages@32.3.1)': + '@cucumber/messages@32.3.1': dependencies: - '@cucumber/cucumber': 12.8.2 - '@cucumber/messages': 32.3.1 - ansi-styles: 5.2.0 - cli-table3: 0.6.5 - figures: 3.2.0 - ts-dedent: 2.2.0 + class-transformer: 0.5.1 + reflect-metadata: 0.2.2 '@cucumber/pretty-formatter@3.3.0(@cucumber/messages@32.3.1)': dependencies: @@ -6131,13 +5925,19 @@ snapshots: '@cucumber/query': 15.0.1(@cucumber/messages@32.3.1) luxon: 3.7.2 + '@cucumber/query@13.6.0(@cucumber/messages@27.2.0)': + dependencies: + '@cucumber/messages': 27.2.0 + '@teppeis/multimaps': 3.0.0 + lodash.sortby: 4.7.0 + '@cucumber/query@15.0.1(@cucumber/messages@32.3.1)': dependencies: '@cucumber/messages': 32.3.1 '@teppeis/multimaps': 3.0.0 lodash.sortby: 4.7.0 - '@cucumber/tag-expressions@9.1.0': {} + '@cucumber/tag-expressions@6.2.0': {} '@docsearch/css@3.8.2': {} @@ -6338,7 +6138,7 @@ snapshots: '@eslint/config-array@0.23.5': dependencies: '@eslint/object-schema': 3.0.5 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 minimatch: 10.2.5 transitivePeerDependencies: - supports-color @@ -6503,6 +6303,18 @@ snapshots: '@nodable/entities@2.1.0': {} + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + '@npmcli/fs@3.1.1': dependencies: semver: 7.7.4 @@ -7142,8 +6954,6 @@ snapshots: dependencies: undici-types: 7.19.2 - '@types/normalize-package-data@2.4.4': {} - '@types/qs@6.15.0': {} '@types/retry@0.12.2': {} @@ -7153,6 +6963,8 @@ snapshots: '@types/unist@3.0.3': {} + '@types/uuid@10.0.0': {} + '@types/web-bluetooth@0.0.21': {} '@types/whatwg-mimetype@3.0.2': {} @@ -7183,7 +6995,7 @@ snapshots: '@typescript-eslint/types': 8.59.1 '@typescript-eslint/typescript-estree': 8.59.1(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.59.1 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 eslint: 10.3.0(jiti@2.6.1) typescript: 6.0.3 transitivePeerDependencies: @@ -7193,7 +7005,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.59.1(typescript@6.0.3) '@typescript-eslint/types': 8.59.1 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -7212,7 +7024,7 @@ snapshots: '@typescript-eslint/types': 8.59.1 '@typescript-eslint/typescript-estree': 8.59.1(typescript@6.0.3) '@typescript-eslint/utils': 8.59.1(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 eslint: 10.3.0(jiti@2.6.1) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 @@ -7227,7 +7039,7 @@ snapshots: '@typescript-eslint/tsconfig-utils': 8.59.1(typescript@6.0.3) '@typescript-eslint/types': 8.59.1 '@typescript-eslint/visitor-keys': 8.59.1 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 minimatch: 10.2.5 semver: 7.7.4 tinyglobby: 0.2.16 @@ -7596,8 +7408,6 @@ snapshots: ansi-colors@4.1.3: {} - ansi-regex@4.1.1: {} - ansi-regex@5.0.1: {} ansi-regex@6.2.2: {} @@ -7606,12 +7416,8 @@ snapshots: dependencies: color-convert: 2.0.1 - ansi-styles@5.2.0: {} - ansi-styles@6.2.3: {} - any-promise@1.3.0: {} - anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -7639,12 +7445,6 @@ snapshots: object.assign: 4.1.7 util: 0.12.5 - assertion-error-formatter@3.0.0: - dependencies: - diff: 4.0.4 - pad-right: 0.2.2 - repeat-string: 1.6.1 - assertion-error@2.0.1: {} ast-kit@2.2.0: @@ -7819,12 +7619,6 @@ snapshots: caniuse-lite@1.0.30001791: {} - capital-case@1.0.4: - dependencies: - no-case: 3.0.4 - tslib: 2.8.1 - upper-case-first: 2.0.2 - ccount@2.0.1: {} chai@6.2.2: {} @@ -7916,11 +7710,7 @@ snapshots: commander@10.0.1: {} - commander@14.0.0: {} - - commander@14.0.2: {} - - commander@14.0.3: {} + commander@13.1.0: {} confbox@0.1.8: {} @@ -8022,11 +7812,9 @@ snapshots: dateformat@4.6.3: {} - debug@4.4.3(supports-color@8.1.1): + debug@4.4.3: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 8.1.1 deep-is@0.1.4: {} @@ -8148,10 +7936,6 @@ snapshots: marks-pane: 1.0.9 path-webpack: 0.0.3 - error-stack-parser@2.1.4: - dependencies: - stackframe: 1.3.4 - es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -8245,8 +8029,6 @@ snapshots: escalade@3.2.0: {} - escape-string-regexp@1.0.5: {} - escape-string-regexp@4.0.0: {} eslint-config-prettier@10.1.8(eslint@10.3.0(jiti@2.6.1)): @@ -8297,7 +8079,7 @@ snapshots: '@types/estree': 1.0.8 ajv: 6.14.0 cross-spawn: 7.0.6 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 escape-string-regexp: 4.0.0 eslint-scope: 9.1.2 eslint-visitor-keys: 5.0.1 @@ -8381,6 +8163,14 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} @@ -8398,6 +8188,10 @@ snapshots: path-expression-matcher: 1.5.0 strnum: 2.2.3 + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 @@ -8407,10 +8201,6 @@ snapshots: node-domexception: 1.0.0 web-streams-polyfill: 3.3.3 - figures@3.2.0: - dependencies: - escape-string-regexp: 1.0.5 - file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -8431,8 +8221,6 @@ snapshots: find-replace@5.0.2: {} - find-up-simple@1.0.1: {} - find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -8537,16 +8325,6 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 2.0.2 - glob@13.0.6: - dependencies: - minimatch: 10.2.5 - minipass: 7.1.3 - path-scurry: 2.0.2 - - global-dirs@3.0.1: - dependencies: - ini: 2.0.0 - global-modules@1.0.0: dependencies: global-prefix: 1.0.2 @@ -8577,10 +8355,6 @@ snapshots: - bufferutil - utf-8-validate - has-ansi@4.0.1: - dependencies: - ansi-regex: 4.1.1 - has-flag@4.0.0: {} has-property-descriptors@1.0.2: @@ -8650,10 +8424,6 @@ snapshots: dependencies: lru-cache: 7.18.3 - hosted-git-info@9.0.2: - dependencies: - lru-cache: 11.2.7 - hot-patcher@2.0.1: {} html-escaper@2.0.2: {} @@ -8676,16 +8446,10 @@ snapshots: imurmurhash@0.1.4: {} - indent-string@4.0.0: {} - - index-to-position@1.2.0: {} - inherits@2.0.4: {} ini@1.3.8: {} - ini@2.0.0: {} - is-arguments@1.2.0: dependencies: call-bound: 1.0.4 @@ -8719,11 +8483,6 @@ snapshots: dependencies: is-extglob: 2.1.1 - is-installed-globally@0.4.0: - dependencies: - global-dirs: 3.0.1 - is-path-inside: 3.0.3 - is-nan@1.3.2: dependencies: call-bind: 1.0.8 @@ -8733,8 +8492,6 @@ snapshots: is-number@7.0.0: {} - is-path-inside@3.0.3: {} - is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -8805,8 +8562,6 @@ snapshots: js-tokens@10.0.0: {} - js-tokens@4.0.0: {} - jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -8832,10 +8587,6 @@ snapshots: dependencies: json-buffer: 3.0.1 - knuth-shuffle-seeded@1.0.6: - dependencies: - seed-random: 2.2.0 - layerr@3.0.0: {} levn@0.4.1: @@ -8846,7 +8597,7 @@ snapshots: license-checker-rseidelsohn@4.4.2: dependencies: chalk: 4.1.2 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 lodash.clonedeep: 4.5.0 mkdirp: 1.0.4 nopt: 7.2.1 @@ -8959,10 +8710,6 @@ snapshots: lodash.clonedeep@4.5.0: {} - lodash.merge@4.6.2: {} - - lodash.mergewith@4.6.2: {} - lodash.sortby@4.7.0: {} lodash.throttle@4.1.1: {} @@ -8976,10 +8723,6 @@ snapshots: long-timeout@0.1.1: {} - lower-case@2.0.2: - dependencies: - tslib: 2.8.1 - lru-cache@10.4.3: {} lru-cache@11.2.7: {} @@ -9044,6 +8787,8 @@ snapshots: meow@13.2.0: {} + merge2@1.4.1: {} + micromark-util-character@2.1.1: dependencies: micromark-util-symbol: 2.0.1 @@ -9061,6 +8806,11 @@ snapshots: micromark-util-types@2.0.2: {} + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + miller-rabin@4.0.1: dependencies: bn.js: 4.12.3 @@ -9068,6 +8818,8 @@ snapshots: mime-db@1.52.0: {} + mime-db@1.54.0: {} + mime-match@1.0.2: dependencies: wildcard: 1.1.2 @@ -9076,7 +8828,9 @@ snapshots: dependencies: mime-db: 1.52.0 - mime@3.0.0: {} + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 minimalistic-assert@1.0.1: {} @@ -9100,8 +8854,6 @@ snapshots: mkdirp@1.0.4: {} - mkdirp@3.0.1: {} - mlly@1.8.2: dependencies: acorn: 8.16.0 @@ -9113,12 +8865,6 @@ snapshots: muggle-string@0.4.1: {} - mz@2.7.0: - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - n-gram@2.0.2: {} namespace-emitter@2.0.1: {} @@ -9133,11 +8879,6 @@ snapshots: next-tick@1.1.0: {} - no-case@3.0.4: - dependencies: - lower-case: 2.0.2 - tslib: 2.8.1 - node-addon-api@7.1.1: optional: true @@ -9202,12 +8943,6 @@ snapshots: semver: 7.7.4 validate-npm-package-license: 3.0.4 - normalize-package-data@8.0.0: - dependencies: - hosted-git-info: 9.0.2 - semver: 7.7.4 - validate-npm-package-license: 3.0.4 - normalize-path@3.0.0: {} npm-normalize-package-bin@3.0.1: {} @@ -9216,8 +8951,6 @@ snapshots: dependencies: boolbase: 1.0.0 - object-assign@4.1.1: {} - object-inspect@1.13.4: {} object-is@1.1.6: @@ -9292,10 +9025,6 @@ snapshots: package-json-from-dist@1.0.1: {} - pad-right@0.2.2: - dependencies: - repeat-string: 1.6.1 - pako@1.0.11: {} parse-asn1@5.1.9: @@ -9306,12 +9035,6 @@ snapshots: pbkdf2: 3.1.5 safe-buffer: 5.2.1 - parse-json@8.3.0: - dependencies: - '@babel/code-frame': 7.29.0 - index-to-position: 1.2.0 - type-fest: 4.41.0 - parse-passwd@1.0.0: {} password-sheriff@2.0.0: {} @@ -9420,6 +9143,22 @@ snapshots: exsolve: 1.0.8 pathe: 2.0.3 + playwright-bdd@8.5.1(@playwright/test@1.59.1): + dependencies: + '@cucumber/cucumber-expressions': 18.0.1 + '@cucumber/gherkin': 32.2.0 + '@cucumber/gherkin-utils': 9.2.0 + '@cucumber/html-formatter': 21.15.1(@cucumber/messages@27.2.0) + '@cucumber/junit-xml-formatter': 0.7.1(@cucumber/messages@27.2.0) + '@cucumber/messages': 27.2.0 + '@cucumber/tag-expressions': 6.2.0 + '@playwright/test': 1.59.1 + cli-table3: 0.6.5 + commander: 13.1.0 + fast-glob: 3.3.3 + mime-types: 3.0.2 + xmlbuilder: 15.1.1 + playwright-core@1.59.1: {} playwright@1.59.1: @@ -9455,16 +9194,12 @@ snapshots: process@0.11.10: {} - progress@2.0.3: {} - proper-lockfile@4.1.2: dependencies: graceful-fs: 4.2.11 retry: 0.12.0 signal-exit: 3.0.7 - property-expr@2.0.6: {} - property-information@7.1.0: {} prosemirror-changeset@2.4.1: @@ -9568,6 +9303,8 @@ snapshots: querystringify@2.2.0: {} + queue-microtask@1.2.3: {} + quick-format-unescaped@4.0.4: {} randombytes@2.1.0: @@ -9582,7 +9319,7 @@ snapshots: read-installed-packages@2.0.1: dependencies: '@npmcli/fs': 3.1.1 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 read-package-json: 6.0.4 semver: 7.7.4 slide: 1.1.6 @@ -9598,20 +9335,6 @@ snapshots: normalize-package-data: 5.0.0 npm-normalize-package-bin: 3.0.1 - read-package-up@12.0.0: - dependencies: - find-up-simple: 1.0.1 - read-pkg: 10.1.0 - type-fest: 5.5.0 - - read-pkg@10.1.0: - dependencies: - '@types/normalize-package-data': 2.4.4 - normalize-package-data: 8.0.0 - parse-json: 8.3.0 - type-fest: 5.5.0 - unicorn-magic: 0.4.0 - readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 @@ -9656,8 +9379,6 @@ snapshots: regexp-tree@0.1.27: {} - repeat-string@1.6.1: {} - requires-port@1.0.0: {} resolve-dir@1.0.1: @@ -9681,6 +9402,8 @@ snapshots: retry@0.13.1: {} + reusify@1.1.0: {} + rfdc@1.4.1: {} ripemd160@2.0.3: @@ -9742,6 +9465,10 @@ snapshots: rope-sequence@1.3.4: {} + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + safe-buffer@5.1.2: {} safe-buffer@5.2.1: {} @@ -9768,8 +9495,6 @@ snapshots: secure-json-parse@4.1.0: {} - seed-random@2.2.0: {} - semver@7.7.4: {} set-function-length@1.2.2: @@ -9893,8 +9618,6 @@ snapshots: stackback@0.0.2: {} - stackframe@1.3.4: {} - std-env@4.1.0: {} stream-browserify@3.0.0: @@ -9909,8 +9632,6 @@ snapshots: readable-stream: 3.6.2 xtend: 4.0.2 - string-argv@0.3.1: {} - string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -9956,28 +9677,14 @@ snapshots: dependencies: has-flag: 4.0.0 - supports-color@8.1.1: - dependencies: - has-flag: 4.0.0 - supports-preserve-symlinks-flag@1.0.0: {} tabbable@6.4.0: {} - tagged-tag@1.0.0: {} - tailwindcss@4.2.4: {} tapable@2.3.3: {} - thenify-all@1.6.0: - dependencies: - thenify: 3.3.1 - - thenify@3.3.1: - dependencies: - any-promise: 1.3.0 - thread-stream@4.0.0: dependencies: real-require: 0.2.0 @@ -9986,8 +9693,6 @@ snapshots: dependencies: setimmediate: 1.0.5 - tiny-case@1.0.3: {} - tinybench@2.9.0: {} tinyexec@1.1.1: {} @@ -10009,8 +9714,6 @@ snapshots: dependencies: is-number: 7.0.0 - toposort@2.0.2: {} - tr46@0.0.3: {} treeify@1.1.0: {} @@ -10026,8 +9729,6 @@ snapshots: dependencies: typescript: 6.0.3 - ts-dedent@2.2.0: {} - ts-essentials@10.1.1(typescript@6.0.3): optionalDependencies: typescript: 6.0.3 @@ -10068,14 +9769,6 @@ snapshots: dependencies: prelude-ls: 1.2.1 - type-fest@2.19.0: {} - - type-fest@4.41.0: {} - - type-fest@5.5.0: - dependencies: - tagged-tag: 1.0.0 - type@2.7.3: {} typed-array-buffer@1.0.3: @@ -10107,8 +9800,6 @@ snapshots: undici@7.24.7: {} - unicorn-magic@0.4.0: {} - unist-util-is@6.0.1: dependencies: '@types/unist': 3.0.3 @@ -10149,10 +9840,6 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - upper-case-first@2.0.2: - dependencies: - tslib: 2.8.1 - uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -10169,8 +9856,6 @@ snapshots: punycode: 1.4.1 qs: 6.15.1 - util-arity@1.1.0: {} - util-deprecate@1.0.2: {} util@0.12.5: @@ -10181,6 +9866,10 @@ snapshots: is-typed-array: 1.1.15 which-typed-array: 1.1.20 + uuid@10.0.0: {} + + uuid@11.0.5: {} + uuid@14.0.0: {} v8-compile-cache-lib@3.0.1: {} @@ -10339,7 +10028,7 @@ snapshots: vue-eslint-parser@10.4.0(eslint@10.3.0(jiti@2.6.1)): dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 eslint: 10.3.0(jiti@2.6.1) eslint-scope: 9.1.2 eslint-visitor-keys: 5.0.1 @@ -10503,13 +10192,6 @@ snapshots: yocto-queue@0.1.0: {} - yup@1.7.1: - dependencies: - property-expr: 2.0.6 - tiny-case: 1.0.3 - toposort: 2.0.2 - type-fest: 2.19.0 - zod@4.3.6: {} zwitch@2.0.4: {} diff --git a/tests/e2e/.gitignore b/tests/e2e/.gitignore new file mode 100644 index 0000000000..8ada21e34a --- /dev/null +++ b/tests/e2e/.gitignore @@ -0,0 +1,11 @@ + +# Playwright +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +/playwright/.auth/ + +# Playwright-BDD +/.features-gen/ diff --git a/tests/e2e/config.js b/tests/e2e/config.js deleted file mode 100644 index afad2db5d6..0000000000 --- a/tests/e2e/config.js +++ /dev/null @@ -1,58 +0,0 @@ -const withHttp = (url) => (/^https?:\/\//i.test(url) ? url : `https://${url}`) - -// keep in sync with the type in config.ts -export const config = { - // environment - assets: './tests/e2e/filesForUpload', - tempAssetsPath: './tests/e2e/filesForUpload/temp', - baseUrlOpenCloud: process.env.OC_BASE_URL ?? 'host.docker.internal:9200', - basicAuth: process.env.BASIC_AUTH === 'true', - // keycloak config - keycloak: process.env.KEYCLOAK === 'true', - keycloakHost: process.env.KEYCLOAK_HOST ?? 'keycloak.opencloud.test', - keycloakRealm: process.env.KEYCLOAK_REALM ?? 'openCloud', - keycloakAdminUser: process.env.KEYCLOAK_ADMIN_USER ?? 'admin', - keycloakAdminPassword: process.env.KEYCLOAK_ADMIN_PASSWORD ?? 'admin', - get keycloakUrl() { - return withHttp(this.keycloakHost) - }, - get keycloakLoginUrl() { - return withHttp(this.keycloakHost + '/admin/master/console') - }, - // ocm config - federatedbaseUrlOpenCloud: process.env.OC_FEDERATED_BASE_URL ?? 'federation-opencloud:10200', - federatedServer: false, - get baseUrl() { - return withHttp(this.federatedServer ? this.federatedbaseUrlOpenCloud : this.baseUrlOpenCloud) - }, - debug: process.env.DEBUG === 'true', - logLevel: process.env.LOG_LEVEL || 'silent', - // cucumber - retry: process.env.RETRY || 0, - parallel: parseInt(process.env.PARALLEL) || 1, - // playwright - slowMo: parseInt(process.env.SLOW_MO) || 0, - // timeout for a whole test scenario - testTimeout: parseInt(process.env.TEST_TIMEOUT) || 120, - // timeout used for test actions - get timeout() { - return this.testTimeout / 2 - }, - // double the timeout for large file uploads - get largeUploadTimeout() { - return this.testTimeout * 2 - }, - minTimeout: parseInt(process.env.MIN_TIMEOUT) || 5, - tokenTimeout: parseInt(process.env.TOKEN_TIMEOUT) || 40, - headless: process.env.HEADLESS === 'true', - acceptDownloads: process.env.DOWNLOADS !== 'false', - browser: process.env.BROWSER ?? 'chromium', - reportDir: process.env.REPORT_DIR || 'reports/e2e', - get tracingReportDir() { - return this.reportDir + '/playwright/tracing' - }, - reportVideo: process.env.REPORT_VIDEO === 'true', - reportHar: process.env.REPORT_HAR === 'true', - reportTracing: process.env.REPORT_TRACING === 'true', - failOnUncaughtConsoleError: process.env.FAIL_ON_UNCAUGHT_CONSOLE_ERR === 'true' -} diff --git a/tests/e2e/config.ts b/tests/e2e/config.ts deleted file mode 100644 index eab55833d8..0000000000 --- a/tests/e2e/config.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { config as jsConfig } from './config.js' - -interface Config { - assets: string - tempAssetsPath: string - baseUrlOpenCloud: string - basicAuth: boolean - - keycloak: boolean - keycloakHost: string - keycloakRealm: string - keycloakAdminUser: string - keycloakAdminPassword: string - readonly keycloakUrl: string - readonly keycloakLoginUrl: string - - federatedbaseUrlOpenCloud: string - federatedServer: boolean - readonly baseUrl: string - - debug: boolean - logLevel: string - - retry: string | number - parallel: number - - slowMo: number - testTimeout: number - readonly timeout: number - readonly largeUploadTimeout: number - minTimeout: number - tokenTimeout: number - headless: boolean - acceptDownloads: boolean - browser: string - reportDir: string - readonly tracingReportDir: string - reportVideo: boolean - reportHar: boolean - reportTracing: boolean - failOnUncaughtConsoleError: boolean -} - -// re-export js config as ts config to please typescript compiler. -// otherwhise we would need to ignore all config imports in ts files. -const config = jsConfig as Config - -export { config } diff --git a/tests/e2e/cucumber/environment/index.ts b/tests/e2e/cucumber/environment/index.ts deleted file mode 100644 index 49c4d26cf6..0000000000 --- a/tests/e2e/cucumber/environment/index.ts +++ /dev/null @@ -1,300 +0,0 @@ -import { - Before, - BeforeAll, - setDefaultTimeout, - setWorldConstructor, - ITestCaseHookParameter, - AfterAll, - After, - Status -} from '@cucumber/cucumber' -import pino from 'pino' -import { Browser, chromium, firefox, webkit } from '@playwright/test' -import path from 'path' -import fs from 'fs' -import { v4 as uuidv4 } from 'uuid' - -import { World } from './world' -import { state } from './shared' -import { config } from '../../config' -import { Group, User } from '../../support/types' -import { api, environment, utils, store } from '../../support' - -export { World } - -const logger = pino({ - level: config.logLevel, - transport: { - target: 'pino-pretty', - options: { - colorize: true - } - } -}) - -setDefaultTimeout(config.debug ? -1 : config.testTimeout * 1000) -setWorldConstructor(World) - -Before(async function (this: World, { pickle }: ITestCaseHookParameter) { - this.feature = pickle - this.actorsEnvironment.on('console', (actorId, message): void => { - const msg = { - actor: actorId, - text: message.text(), - type: message.type(), - args: message.args(), - location: message.location() - } - - switch (message.type()) { - case 'debug': - logger.debug(msg) - break - case 'info': - logger.info(msg) - break - case 'error': - logger.error(msg) - break - case 'warning': - logger.warn(msg) - break - } - }) - - if (!config.basicAuth) { - const user = this.usersEnvironment.getUser({ key: 'admin' }) - if (config.keycloak) { - await api.keycloak.setAccessTokenForKeycloakOpenCloudUser(user) - await api.keycloak.setKeycloakAdminAccessToken() - } else { - await api.token.setAccessAndRefreshToken(user) - if (isOcm(pickle)) { - config.federatedServer = true - // need to set tokens for federated OpenCloud admin - await api.token.setAccessAndRefreshToken(user) - config.federatedServer = false - } - } - } - this.uniquePrefix = uuidv4().substring(0, 3) - this.a11yEnabled = pickle.tags.some((tag) => tag.name === '@a11y') -}) - -BeforeAll(async (): Promise => { - const browserType = config.browser ?? 'chromium' - const headless = config.headless - const slowMo = config.slowMo - - const chromiumArgs = ['--use-fake-ui-for-media-stream', '--use-fake-device-for-media-stream'] - - const browsers: Record Promise> = { - firefox: async () => - await firefox.launch({ - headless, - slowMo, - firefoxUserPrefs: { - 'media.navigator.streams.fake': true, - 'media.navigator.permission.disabled': true - } - }), - - webkit: async () => - await webkit.launch({ - headless, - slowMo - }), - - chrome: async () => - await chromium.launch({ - headless, - slowMo, - channel: 'chrome', - args: chromiumArgs - }), - - chromium: async () => - await chromium.launch({ - headless, - slowMo, - args: chromiumArgs - }), - - // Android Pixel 5 - Chromium - 'mobile-chromium': async () => - await chromium.launch({ - headless, - slowMo, - args: chromiumArgs - }), - - // iPhone 12 - Safari/WebKit - 'mobile-webkit': async () => - await webkit.launch({ - headless, - slowMo - }), - - // iPad Pro 11 Portrait - Chromium - 'ipad-chromium': async () => - await chromium.launch({ - headless, - slowMo, - args: chromiumArgs - }), - - // iPad Pro 11 Landscape - Safari/WebKit - 'ipad-landscape-webkit': async () => - await webkit.launch({ - headless, - slowMo - }) - } - - if (!(browserType in browsers)) { - throw new Error(`Unknown browser: ${browserType}`) - } - - state.browser = await browsers[browserType]() -}) - -const defaults = { - reportHar: config.reportHar, - reportTracing: config.reportTracing -} - -After(async function (this: World, { result, willBeRetried, pickle }: ITestCaseHookParameter) { - config.federatedServer = false - if (!result) { - return - } - - await this.actorsEnvironment.close() - - // refresh keycloak admin access token - if (config.keycloak) { - const user = this.usersEnvironment.getUser({ key: 'admin' }) - await api.keycloak.refreshKeycloakAdminAccessToken() - await api.keycloak.refreshAccessTokenForKeycloakOpenCloudUser(user) - } - - if (isOcm(pickle)) { - // need to set federatedServer config to true to delete federated OpenCloud users - config.federatedServer = true - await cleanUpUser(store.federatedUserStore, this.usersEnvironment.getUser({ key: 'admin' })) - config.federatedServer = false - } - await cleanUpUser(store.createdUserStore, this.usersEnvironment.getUser({ key: 'admin' })) - await cleanUpSpaces(this.usersEnvironment.getUser({ key: 'admin' })) - await cleanUpGroup(this.usersEnvironment.getUser({ key: 'admin' })) - - store.createdLinkStore.clear() - store.createdTokenStore.clear() - store.federatedTokenStore.clear() - store.keycloakTokenStore.clear() - utils.removeTempUploadDirectory() - environment.closeSSEConnections() - - if (fs.existsSync(config.tracingReportDir)) { - filterTracingReports(result.status) - } - - // NOTE: config should be changed at the very end of the test - config.reportHar = willBeRetried || defaults.reportHar - config.reportTracing = willBeRetried || defaults.reportTracing -}) - -AfterAll(async () => { - environment.closeSSEConnections() - - if (state.browser) { - await state.browser.close() - } - - // move failed tracing reports - const failedDir = path.dirname(config.tracingReportDir) + '/failed' - if (fs.existsSync(failedDir)) { - fs.mkdirSync(config.tracingReportDir, { recursive: true }) - fs.readdirSync(failedDir).forEach((file) => { - fs.renameSync(failedDir + '/' + file, config.tracingReportDir + '/' + file) - }) - fs.rmSync(failedDir, { recursive: true }) - } -}) - -function filterTracingReports(status: string) { - const traceDir = config.tracingReportDir - const failedDir = path.dirname(config.tracingReportDir) + '/failed' - - if (status !== Status.PASSED) { - if (!fs.existsSync(failedDir)) { - fs.mkdirSync(failedDir, { recursive: true }) - } - const reports = fs.readdirSync(traceDir) - // collect tracings for failed tests - reports.forEach((report) => { - fs.renameSync(`${traceDir}/${report}`, `${failedDir}/${report}`) - }) - } else if (!defaults.reportTracing) { - // clean up the tracing directory if the report tracing was not set explicitly - fs.rmSync(traceDir, { recursive: true }) - } -} - -const cleanUpUser = async (createdUserStore: Map, adminUser: User) => { - const requests: Promise[] = [] - createdUserStore.forEach((user) => { - if (config.keycloak) { - requests.push(api.keycloak.deleteUser({ user })) - } else { - requests.push(api.graph.deleteUser({ user, admin: adminUser })) - } - }) - await Promise.all(requests) - createdUserStore.clear() -} - -const cleanUpSpaces = async (adminUser: User) => { - const requests: Promise[] = [] - store.createdSpaceStore.forEach((space) => { - requests.push( - api.graph - .disableSpace({ - user: adminUser, - space - }) - .then(async (res) => { - if (res.status() === 204) { - await api.graph.deleteSpace({ - user: adminUser, - space - }) - } - }) - ) - }) - await Promise.all(requests) - store.createdSpaceStore.clear() -} - -const cleanUpGroup = async (adminUser: User) => { - const requests: Promise[] = [] - store.createdGroupStore.forEach((group) => { - if (config.keycloak) { - requests.push(api.keycloak.deleteGroup({ group })) - } else { - requests.push(api.graph.deleteGroup({ group, admin: adminUser })) - } - }) - - await Promise.all(requests) - store.createdGroupStore.clear() -} - -const isOcm = (pickle: ITestCaseHookParameter['pickle']): boolean => { - const tags = pickle.tags.map((tag) => tag.name) - if (tags.includes('@ocm')) { - return true - } - return false -} diff --git a/tests/e2e/cucumber/environment/snippets-syntax.mjs b/tests/e2e/cucumber/environment/snippets-syntax.mjs deleted file mode 100644 index 90b1aa9087..0000000000 --- a/tests/e2e/cucumber/environment/snippets-syntax.mjs +++ /dev/null @@ -1,60 +0,0 @@ -// borrowed from https://github.com/orieken/playwright-cucumber-starter -// thanks @orieken if you will ever read this - -function TypeScriptSnippetSyntax(snippetInterface) { - this.snippetInterface = snippetInterface -} - -function addParameters(allParameterNames) { - let prefix = '' - if (allParameterNames.length > 0) { - prefix = ', ' - } - return prefix + allParameterNames.join(', ') -} - -TypeScriptSnippetSyntax.prototype.build = function ({ - generatedExpressions, - functionName, - stepParameterNames -}) { - let functionKeyword = '' - const functionInterfaceKeywords = { - generator: `${functionKeyword}*`, - 'async-await': `async ${functionKeyword}`, - promise: 'async ' - } - - if (this.snippetInterface) { - functionKeyword = `${functionKeyword}${functionInterfaceKeywords[this.snippetInterface]}` - } - - const implementation = [ - 'const { feature, actorsEnvironment, usersEnvironment, filesEnvironment } = this\n', - 'await new Promise(resolve => setTimeout(resolve, 10))' - ] - .map((str) => ` ${str}`) - .join('\n') - - const definitionChoices = generatedExpressions.map((generatedExpression, index) => { - const prefix = index === 0 ? '' : '// ' - - const allParameterNames = generatedExpression.parameterNames - .map((parameterName) => `${parameterName}: any`) - .concat(stepParameterNames.map((stepParameterName) => `${stepParameterName}: any`)) - - return ( - `${prefix}${functionName}('` + - generatedExpression.source.replace(/'/g, "\\'") + - "', " + - functionKeyword + - 'function (this: World' + - addParameters(allParameterNames) + - '): Promise {\n' - ) - }) - - return definitionChoices.join('') + `${implementation}\n});` -} - -export default TypeScriptSnippetSyntax diff --git a/tests/e2e/cucumber/environment/world.ts b/tests/e2e/cucumber/environment/world.ts deleted file mode 100644 index a9203ef35d..0000000000 --- a/tests/e2e/cucumber/environment/world.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { World as CucumberWorld, IWorldOptions } from '@cucumber/cucumber' -import { Pickle } from '@cucumber/messages' -import { config } from '../../config' -import { environment } from '../../support' -import { state } from './shared' - -interface WorldOptions extends IWorldOptions { - parameters: { [key: string]: string } -} - -export class World extends CucumberWorld { - feature: Pickle - actorsEnvironment: environment.ActorsEnvironment - filesEnvironment: environment.FilesEnvironment - linksEnvironment: environment.LinksEnvironment - spacesEnvironment: environment.SpacesEnvironment - usersEnvironment: environment.UsersEnvironment - uniquePrefix: string - a11yEnabled: boolean = false - - constructor(options: WorldOptions) { - super(options) - this.usersEnvironment = new environment.UsersEnvironment() - this.spacesEnvironment = new environment.SpacesEnvironment() - this.filesEnvironment = new environment.FilesEnvironment() - this.linksEnvironment = new environment.LinksEnvironment() - this.actorsEnvironment = new environment.ActorsEnvironment({ - context: { - acceptDownloads: config.acceptDownloads, - reportDir: config.reportDir, - tracingReportDir: config.tracingReportDir, - reportHar: config.reportHar, - reportTracing: config.reportTracing, - reportVideo: config.reportVideo, - failOnUncaughtConsoleError: config.failOnUncaughtConsoleError - }, - browser: state.browser - }) - } -} diff --git a/tests/e2e/cucumber/steps/tempFs.ts b/tests/e2e/cucumber/steps/tempFs.ts deleted file mode 100644 index 9d53eb0fa1..0000000000 --- a/tests/e2e/cucumber/steps/tempFs.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Given } from '@cucumber/cucumber' -import { World } from '../environment' -import * as tempFs from '../../support/utils/runtimeFs' - -Given( - 'the user creates a file {string} of {string} size in the temp upload directory', - function (this: World, fileName: string, fileSize: string): Promise { - return tempFs.createFileWithSize(fileName, tempFs.getBytes(fileSize)) - } -) diff --git a/tests/e2e/cucumber/steps/ui/a11.y.ts b/tests/e2e/cucumber/steps/ui/a11.y.ts deleted file mode 100644 index 951c3e8e92..0000000000 --- a/tests/e2e/cucumber/steps/ui/a11.y.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Then } from '@cucumber/cucumber' -import { World } from '../../environment' -import { checkA11yOrLocalization } from '../../../support/utils/accessibility' - -Then( - '{string} checks the accessibility of the DOM selector {string} on the {string}', - async function (this: World, stepUser: string, selector: string, context: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) - await checkA11yOrLocalization(page, context, selector) - } -) diff --git a/tests/e2e/cucumber/steps/ui/application.ts b/tests/e2e/cucumber/steps/ui/application.ts deleted file mode 100644 index b29b233dfc..0000000000 --- a/tests/e2e/cucumber/steps/ui/application.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { When } from '@cucumber/cucumber' -import { World } from '../../environment' -import { objects } from '../../../support' -import { waitForSSEEvent } from '../../../support/utils/locator' - -When( - '{string} navigates to the project spaces management page', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) - const pageObject = new objects.applicationAdminSettings.page.Spaces({ page }) - await pageObject.navigate() - } -) - -When( - '{string} opens the {string} app', - async function (this: World, stepUser: string, stepApp: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) - const applicationObject = new objects.runtime.Application({ page }) - await applicationObject.open({ name: stepApp }) - } -) - -When('{string} opens the apps menu', async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) - const applicationObject = new objects.runtime.Application({ page }) - await applicationObject.openAppsMenu() -}) - -When('{string} reloads the page', async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) - const applicationObject = new objects.runtime.Application({ page }) - await applicationObject.reloadPage() -}) - -When( - '{string} should get {string} SSE event', - async function (this: World, user: string, event: string): Promise { - await waitForSSEEvent(user, event) - } -) - -When( - '{string} opens the {string} url', - async function (this: World, stepUser: string, url: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) - const applicationObject = new objects.runtime.Application({ page }) - url = url === '%clipboard%' ? await page.evaluate('navigator.clipboard.readText()') : url - await applicationObject.openUrl(url) - } -) - -When('{string} closes the sidebar', async function (this: World, user: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: user }) - const applicationObject = new objects.runtime.Application({ page }) - await applicationObject.closeSidebar() -}) diff --git a/tests/e2e/cucumber/steps/ui/session.ts b/tests/e2e/cucumber/steps/ui/session.ts deleted file mode 100644 index 8571a6dcda..0000000000 --- a/tests/e2e/cucumber/steps/ui/session.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { Given, When, Then } from '@cucumber/cucumber' -import { PickleTag } from '@cucumber/messages' -import { World } from '../../environment' -import { config } from '../../../config' -import { objects } from '../../../support' -import { listenSSE } from '../../../support/environment/sse' -import { expect } from '@playwright/test' - -async function createNewSession(world: World, stepUser: string) { - const { page } = await world.actorsEnvironment.createActor({ - key: stepUser, - namespace: world.actorsEnvironment.generateNamespace(world.feature.name, stepUser) - }) - return new objects.runtime.Session({ page }) -} - -async function LogInUser(this: World, stepUser: string): Promise { - const sessionObject = await createNewSession(this, stepUser) - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) - - const user = - stepUser === 'Admin' - ? this.usersEnvironment.getUser({ key: stepUser }) - : this.usersEnvironment.getCreatedUser({ key: stepUser }) - - await page.goto(config.baseUrl) - await sessionObject.login(user, this.a11yEnabled) - - if (this.feature.tags.length > 0) { - const tags: string[] = [] - this.feature.tags.forEach((tag: PickleTag) => { - !!tag.name && tags.push(tag.name) - }, []) - - // listen to SSE events when running scenarios with '@sse' tag - if (tags.includes('@sse')) { - void listenSSE(config.baseUrl, user) - } - } - - await page.locator('#web-content').waitFor() -} - -When('{string} logs in', LogInUser) - -async function LogOutUser(this: World, stepUser: string): Promise { - const actor = this.actorsEnvironment.getActor({ key: stepUser }) - const canLogout = !!(await actor.page.locator('#_userMenuButton').count()) - - const sessionObject = new objects.runtime.Session({ page: actor.page }) - canLogout && (await sessionObject.logout()) - await actor.close() -} - -When('{string} logs out', LogOutUser) - -Then('{string} fails to log in', async function (this: World, stepUser: string): Promise { - const sessionObject = await createNewSession(this, stepUser) - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) - - await page.goto(config.baseUrl) - await sessionObject.signIn(user.username, user.password) - - const errorLocator = config.keycloak - ? page.locator('.kc-feedback-text', { - hasText: 'Account is disabled, contact your administrator.' - }) - : page.locator('#oc-login-error-message') - - await expect(errorLocator).toBeVisible() -}) - -When( - /^"([^"]*)" waits for token renewal via (iframe|refresh token)$/, - async function (this: World, stepUser: string, renewalType: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) - const application = new objects.runtime.Application({ page }) - - if (renewalType === 'iframe') { - await application.waitForTokenRenewalViaIframe() - } else { - await application.waitForTokenRenewalViaRefreshToken() - } - } -) - -When( - '{string} waits for token to expire', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) - // wait for the token to expire - await page.waitForTimeout(config.tokenTimeout * 1000) - } -) - -When( - '{string} navigates to new tab', - async function (this: World, stepUser: string): Promise { - const actor = this.actorsEnvironment.getActor({ key: stepUser }) - await actor.newTab() - } -) - -When( - '{string} closes the current tab', - async function (this: World, stepUser: string): Promise { - const actor = this.actorsEnvironment.getActor({ key: stepUser }) - await actor.closeCurrentTab() - } -) - -Given('using {string} server', function (this: World, server: string): void { - switch (server) { - case 'LOCAL': - config.federatedServer = false - break - case 'FEDERATED': - config.federatedServer = true - break - default: - throw new Error(`Invalid server type: ${server}\nUse one of these: [LOCAL, FEDERATED]`) - } -}) - -Then( - '{string} should be logged out', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) - await expect(page.locator('#web-content')).toBeHidden() - await expect(page.locator('#exitAnchor')).toBeVisible() - } -) diff --git a/tests/e2e/cucumber/tsconfig.json b/tests/e2e/cucumber/tsconfig.json deleted file mode 100644 index a8c759f0ba..0000000000 --- a/tests/e2e/cucumber/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "@opencloud-eu/tsconfig", - "compilerOptions": { - "rootDir": ".." - }, - "ts-node": { - "esm": true, - "files": true, - "transpileOnly": true, - "experimentalResolver": true, - "experimentalSpecifierResolution": "node" - } -} diff --git a/tests/e2e/environment/fixtures.ts b/tests/e2e/environment/fixtures.ts new file mode 100644 index 0000000000..2021d66570 --- /dev/null +++ b/tests/e2e/environment/fixtures.ts @@ -0,0 +1,22 @@ +import { test as base } from 'playwright-bdd' +import { createBdd } from 'playwright-bdd' +import { World } from '../environment/world' +import { state } from '../environment/shared' + +export type MyFixtures = { + world: World +} + +export const test = base.extend({ + world: async ({ browser }, use, testInfo) => { + state.browser = browser + state.projectName = testInfo.project.name + const world = new World() + + world.uniquePrefix = `test-${testInfo.testId}` + world.tags = testInfo.tags + await use(world) + } +}) + +export const { Given, When, Then, Before, After, AfterAll } = createBdd(test) diff --git a/tests/e2e/environment/hooks.ts b/tests/e2e/environment/hooks.ts new file mode 100644 index 0000000000..c1568b037a --- /dev/null +++ b/tests/e2e/environment/hooks.ts @@ -0,0 +1,110 @@ +import { Before, After, AfterAll } from './fixtures' +import { appConfig } from '../playwright.config' +import { api, environment, utils, store } from '../support' +import { v4 as uuidv4 } from 'uuid' +import { Group, User } from '../support/types' +import { World } from './world' + +Before(async ({ world }) => { + if (!appConfig.basicAuth) { + const adminUser = world.usersEnvironment.getUser({ key: 'admin' }) + + if (appConfig.keycloak) { + await api.keycloak.setAccessTokenForKeycloakOpenCloudUser(adminUser) + await api.keycloak.setKeycloakAdminAccessToken() + } else { + await api.token.setAccessAndRefreshToken(adminUser) + } + } + + world.uniquePrefix = uuidv4().substring(0, 3) + world.a11yEnabled = world.tags?.includes('@a11y') ?? false +}) + +After(async ({ world }) => { + appConfig.federatedServer = false + + await world.actorsEnvironment.close() + + // refresh keycloak admin access token + if (appConfig.keycloak) { + const user = world.usersEnvironment.getUser({ key: 'admin' }) + await api.keycloak.refreshKeycloakAdminAccessToken() + await api.keycloak.refreshAccessTokenForKeycloakOpenCloudUser(user) + } + + if (isOcm(world)) { + appConfig.federatedServer = true + await cleanUpUser(store.federatedUserStore, world.usersEnvironment.getUser({ key: 'admin' })) + appConfig.federatedServer = false + } + + await cleanUpUser(store.createdUserStore, world.usersEnvironment.getUser({ key: 'admin' })) + await cleanUpSpaces(world.usersEnvironment.getUser({ key: 'admin' })) + await cleanUpGroup(world.usersEnvironment.getUser({ key: 'admin' })) + + store.createdLinkStore.clear() + store.createdTokenStore.clear() + store.federatedTokenStore.clear() + store.keycloakTokenStore.clear() + utils.removeTempUploadDirectory() + environment.closeSSEConnections() +}) + +AfterAll(() => { + environment.closeSSEConnections() +}) + +const cleanUpUser = async (createdUserStore: Map, adminUser: User) => { + const requests: Promise[] = [] + createdUserStore.forEach((user) => { + if (appConfig.keycloak) { + requests.push(api.keycloak.deleteUser({ user })) + } else { + requests.push(api.graph.deleteUser({ user, admin: adminUser })) + } + }) + await Promise.all(requests) + createdUserStore.clear() +} + +const cleanUpSpaces = async (adminUser: User) => { + const requests: Promise[] = [] + store.createdSpaceStore.forEach((space) => { + requests.push( + api.graph + .disableSpace({ + user: adminUser, + space + }) + .then(async (res) => { + if (res.status() === 204) { + await api.graph.deleteSpace({ + user: adminUser, + space + }) + } + }) + ) + }) + await Promise.all(requests) + store.createdSpaceStore.clear() +} + +const cleanUpGroup = async (adminUser: User) => { + const requests: Promise[] = [] + store.createdGroupStore.forEach((group) => { + if (appConfig.keycloak) { + requests.push(api.keycloak.deleteGroup({ group })) + } else { + requests.push(api.graph.deleteGroup({ group, admin: adminUser })) + } + }) + + await Promise.all(requests) + store.createdGroupStore.clear() +} + +const isOcm = (world: World): boolean => { + return world.tags?.includes('@ocm') ?? false +} diff --git a/tests/e2e/cucumber/environment/shared.ts b/tests/e2e/environment/shared.ts similarity index 60% rename from tests/e2e/cucumber/environment/shared.ts rename to tests/e2e/environment/shared.ts index 805a452eab..19ae11adcb 100644 --- a/tests/e2e/cucumber/environment/shared.ts +++ b/tests/e2e/environment/shared.ts @@ -2,6 +2,8 @@ import { Browser } from '@playwright/test' export const state: { browser: Browser + projectName: string } = { - browser: undefined + browser: undefined, + projectName: '' } diff --git a/tests/e2e/environment/world.ts b/tests/e2e/environment/world.ts new file mode 100644 index 0000000000..37ba5a30ff --- /dev/null +++ b/tests/e2e/environment/world.ts @@ -0,0 +1,28 @@ +import { appConfig } from '../playwright.config' +import { environment } from '../support' +import { state } from './shared' + +export class World { + actorsEnvironment: environment.ActorsEnvironment + filesEnvironment: environment.FilesEnvironment + linksEnvironment: environment.LinksEnvironment + spacesEnvironment: environment.SpacesEnvironment + usersEnvironment: environment.UsersEnvironment + uniquePrefix: string + a11yEnabled: boolean = false + tags: string[] = [] + + constructor() { + this.usersEnvironment = new environment.UsersEnvironment() + this.spacesEnvironment = new environment.SpacesEnvironment() + this.filesEnvironment = new environment.FilesEnvironment() + this.linksEnvironment = new environment.LinksEnvironment() + this.actorsEnvironment = new environment.ActorsEnvironment({ + context: { + acceptDownloads: appConfig.acceptDownloads, + failOnUncaughtConsoleError: appConfig.failOnUncaughtConsoleError + }, + browser: state.browser + }) + } +} diff --git a/tests/e2e/cucumber/features/a11y/smoke.feature b/tests/e2e/features/a11y/smoke.feature similarity index 100% rename from tests/e2e/cucumber/features/a11y/smoke.feature rename to tests/e2e/features/a11y/smoke.feature diff --git a/tests/e2e/cucumber/features/admin-settings/groups.feature b/tests/e2e/features/admin-settings/groups.feature similarity index 100% rename from tests/e2e/cucumber/features/admin-settings/groups.feature rename to tests/e2e/features/admin-settings/groups.feature diff --git a/tests/e2e/cucumber/features/admin-settings/spaces.feature b/tests/e2e/features/admin-settings/spaces.feature similarity index 100% rename from tests/e2e/cucumber/features/admin-settings/spaces.feature rename to tests/e2e/features/admin-settings/spaces.feature diff --git a/tests/e2e/cucumber/features/admin-settings/users.feature b/tests/e2e/features/admin-settings/users.feature similarity index 100% rename from tests/e2e/cucumber/features/admin-settings/users.feature rename to tests/e2e/features/admin-settings/users.feature diff --git a/tests/e2e/cucumber/features/app-provider-onlyOffice/officeSuites.feature b/tests/e2e/features/app-provider-onlyOffice/officeSuites.feature similarity index 100% rename from tests/e2e/cucumber/features/app-provider-onlyOffice/officeSuites.feature rename to tests/e2e/features/app-provider-onlyOffice/officeSuites.feature diff --git a/tests/e2e/cucumber/features/app-provider-onlyOffice/urlJourneys.feature b/tests/e2e/features/app-provider-onlyOffice/urlJourneys.feature similarity index 100% rename from tests/e2e/cucumber/features/app-provider-onlyOffice/urlJourneys.feature rename to tests/e2e/features/app-provider-onlyOffice/urlJourneys.feature diff --git a/tests/e2e/cucumber/features/app-provider/lock.feature b/tests/e2e/features/app-provider/lock.feature similarity index 100% rename from tests/e2e/cucumber/features/app-provider/lock.feature rename to tests/e2e/features/app-provider/lock.feature diff --git a/tests/e2e/cucumber/features/app-provider/officeSuites.feature b/tests/e2e/features/app-provider/officeSuites.feature similarity index 100% rename from tests/e2e/cucumber/features/app-provider/officeSuites.feature rename to tests/e2e/features/app-provider/officeSuites.feature diff --git a/tests/e2e/cucumber/features/app-provider/secureView.feature b/tests/e2e/features/app-provider/secureView.feature similarity index 100% rename from tests/e2e/cucumber/features/app-provider/secureView.feature rename to tests/e2e/features/app-provider/secureView.feature diff --git a/tests/e2e/cucumber/features/app-provider/urlJourneys.feature b/tests/e2e/features/app-provider/urlJourneys.feature similarity index 100% rename from tests/e2e/cucumber/features/app-provider/urlJourneys.feature rename to tests/e2e/features/app-provider/urlJourneys.feature diff --git a/tests/e2e/cucumber/features/app-store/details.feature b/tests/e2e/features/app-store/details.feature similarity index 100% rename from tests/e2e/cucumber/features/app-store/details.feature rename to tests/e2e/features/app-store/details.feature diff --git a/tests/e2e/cucumber/features/app-store/discovery.feature b/tests/e2e/features/app-store/discovery.feature similarity index 100% rename from tests/e2e/cucumber/features/app-store/discovery.feature rename to tests/e2e/features/app-store/discovery.feature diff --git a/tests/e2e/cucumber/features/embed/embedMode.feature b/tests/e2e/features/embed/embedMode.feature similarity index 91% rename from tests/e2e/cucumber/features/embed/embedMode.feature rename to tests/e2e/features/embed/embedMode.feature index 884269617d..c08779eab9 100644 --- a/tests/e2e/cucumber/features/embed/embedMode.feature +++ b/tests/e2e/features/embed/embedMode.feature @@ -11,4 +11,4 @@ Feature: Embed mode with delegated authentication | Alice | When "Alice" opens the app in embed mode with delegated authentication Then "Alice" should see the embed mode actions - And "Alice" should not see the full web UI + And "Alice" should not see the full web UI \ No newline at end of file diff --git a/tests/e2e/cucumber/features/file-action/copyMove.feature b/tests/e2e/features/file-action/copyMove.feature similarity index 100% rename from tests/e2e/cucumber/features/file-action/copyMove.feature rename to tests/e2e/features/file-action/copyMove.feature diff --git a/tests/e2e/cucumber/features/file-action/delete.feature b/tests/e2e/features/file-action/delete.feature similarity index 100% rename from tests/e2e/cucumber/features/file-action/delete.feature rename to tests/e2e/features/file-action/delete.feature diff --git a/tests/e2e/cucumber/features/file-action/download.feature b/tests/e2e/features/file-action/download.feature similarity index 100% rename from tests/e2e/cucumber/features/file-action/download.feature rename to tests/e2e/features/file-action/download.feature diff --git a/tests/e2e/cucumber/features/file-action/favorites.feature b/tests/e2e/features/file-action/favorites.feature similarity index 100% rename from tests/e2e/cucumber/features/file-action/favorites.feature rename to tests/e2e/features/file-action/favorites.feature diff --git a/tests/e2e/cucumber/features/file-action/groupActions.feature b/tests/e2e/features/file-action/groupActions.feature similarity index 100% rename from tests/e2e/cucumber/features/file-action/groupActions.feature rename to tests/e2e/features/file-action/groupActions.feature diff --git a/tests/e2e/cucumber/features/file-action/rename.feature b/tests/e2e/features/file-action/rename.feature similarity index 100% rename from tests/e2e/cucumber/features/file-action/rename.feature rename to tests/e2e/features/file-action/rename.feature diff --git a/tests/e2e/cucumber/features/journeys/kindergarten.feature b/tests/e2e/features/journeys/kindergarten.feature similarity index 100% rename from tests/e2e/cucumber/features/journeys/kindergarten.feature rename to tests/e2e/features/journeys/kindergarten.feature diff --git a/tests/e2e/cucumber/features/keycloak/smoke.feature b/tests/e2e/features/keycloak/smoke.feature similarity index 100% rename from tests/e2e/cucumber/features/keycloak/smoke.feature rename to tests/e2e/features/keycloak/smoke.feature diff --git a/tests/e2e/cucumber/features/mobile-view/smoke.feature b/tests/e2e/features/mobile-view/smoke.feature similarity index 100% rename from tests/e2e/cucumber/features/mobile-view/smoke.feature rename to tests/e2e/features/mobile-view/smoke.feature diff --git a/tests/e2e/cucumber/features/navigation/applicationMenu.feature b/tests/e2e/features/navigation/applicationMenu.feature similarity index 100% rename from tests/e2e/cucumber/features/navigation/applicationMenu.feature rename to tests/e2e/features/navigation/applicationMenu.feature diff --git a/tests/e2e/cucumber/features/navigation/breadcrumb.feature b/tests/e2e/features/navigation/breadcrumb.feature similarity index 100% rename from tests/e2e/cucumber/features/navigation/breadcrumb.feature rename to tests/e2e/features/navigation/breadcrumb.feature diff --git a/tests/e2e/cucumber/features/navigation/pageNotFoud.feature b/tests/e2e/features/navigation/pageNotFoud.feature similarity index 100% rename from tests/e2e/cucumber/features/navigation/pageNotFoud.feature rename to tests/e2e/features/navigation/pageNotFoud.feature diff --git a/tests/e2e/cucumber/features/navigation/shortcut.feature b/tests/e2e/features/navigation/shortcut.feature similarity index 100% rename from tests/e2e/cucumber/features/navigation/shortcut.feature rename to tests/e2e/features/navigation/shortcut.feature diff --git a/tests/e2e/cucumber/features/navigation/urlJourneys.feature b/tests/e2e/features/navigation/urlJourneys.feature similarity index 100% rename from tests/e2e/cucumber/features/navigation/urlJourneys.feature rename to tests/e2e/features/navigation/urlJourneys.feature diff --git a/tests/e2e/cucumber/features/ocm/ocm.feature b/tests/e2e/features/ocm/ocm.feature similarity index 100% rename from tests/e2e/cucumber/features/ocm/ocm.feature rename to tests/e2e/features/ocm/ocm.feature diff --git a/tests/e2e/cucumber/features/oidc/iframeTokenRenewal.feature b/tests/e2e/features/oidc/iframeTokenRenewal.feature similarity index 100% rename from tests/e2e/cucumber/features/oidc/iframeTokenRenewal.feature rename to tests/e2e/features/oidc/iframeTokenRenewal.feature diff --git a/tests/e2e/cucumber/features/oidc/refreshToken.feature b/tests/e2e/features/oidc/refreshToken.feature similarity index 100% rename from tests/e2e/cucumber/features/oidc/refreshToken.feature rename to tests/e2e/features/oidc/refreshToken.feature diff --git a/tests/e2e/cucumber/features/search/fullTextSearch.feature b/tests/e2e/features/search/fullTextSearch.feature similarity index 100% rename from tests/e2e/cucumber/features/search/fullTextSearch.feature rename to tests/e2e/features/search/fullTextSearch.feature diff --git a/tests/e2e/cucumber/features/search/search.feature b/tests/e2e/features/search/search.feature similarity index 100% rename from tests/e2e/cucumber/features/search/search.feature rename to tests/e2e/features/search/search.feature diff --git a/tests/e2e/cucumber/features/search/searchProjectSpace.feature b/tests/e2e/features/search/searchProjectSpace.feature similarity index 100% rename from tests/e2e/cucumber/features/search/searchProjectSpace.feature rename to tests/e2e/features/search/searchProjectSpace.feature diff --git a/tests/e2e/cucumber/features/shares/link.feature b/tests/e2e/features/shares/link.feature similarity index 100% rename from tests/e2e/cucumber/features/shares/link.feature rename to tests/e2e/features/shares/link.feature diff --git a/tests/e2e/cucumber/features/shares/share.feature b/tests/e2e/features/shares/share.feature similarity index 100% rename from tests/e2e/cucumber/features/shares/share.feature rename to tests/e2e/features/shares/share.feature diff --git a/tests/e2e/cucumber/features/smoke/activity.feature b/tests/e2e/features/smoke/activity.feature similarity index 100% rename from tests/e2e/cucumber/features/smoke/activity.feature rename to tests/e2e/features/smoke/activity.feature diff --git a/tests/e2e/cucumber/features/smoke/sse.feature b/tests/e2e/features/smoke/sse.feature similarity index 100% rename from tests/e2e/cucumber/features/smoke/sse.feature rename to tests/e2e/features/smoke/sse.feature diff --git a/tests/e2e/cucumber/features/smoke/tags.feature b/tests/e2e/features/smoke/tags.feature similarity index 100% rename from tests/e2e/cucumber/features/smoke/tags.feature rename to tests/e2e/features/smoke/tags.feature diff --git a/tests/e2e/cucumber/features/smoke/trashbinDelete.feature b/tests/e2e/features/smoke/trashbinDelete.feature similarity index 100% rename from tests/e2e/cucumber/features/smoke/trashbinDelete.feature rename to tests/e2e/features/smoke/trashbinDelete.feature diff --git a/tests/e2e/cucumber/features/smoke/upload.feature b/tests/e2e/features/smoke/upload.feature similarity index 100% rename from tests/e2e/cucumber/features/smoke/upload.feature rename to tests/e2e/features/smoke/upload.feature diff --git a/tests/e2e/cucumber/features/smoke/uploadResumable.feature b/tests/e2e/features/smoke/uploadResumable.feature similarity index 100% rename from tests/e2e/cucumber/features/smoke/uploadResumable.feature rename to tests/e2e/features/smoke/uploadResumable.feature diff --git a/tests/e2e/cucumber/features/spaces/createSpaceFromSelection.feature b/tests/e2e/features/spaces/createSpaceFromSelection.feature similarity index 100% rename from tests/e2e/cucumber/features/spaces/createSpaceFromSelection.feature rename to tests/e2e/features/spaces/createSpaceFromSelection.feature diff --git a/tests/e2e/cucumber/features/spaces/downloadSpace.feature b/tests/e2e/features/spaces/downloadSpace.feature similarity index 100% rename from tests/e2e/cucumber/features/spaces/downloadSpace.feature rename to tests/e2e/features/spaces/downloadSpace.feature diff --git a/tests/e2e/cucumber/features/spaces/memberExpiry.feature b/tests/e2e/features/spaces/memberExpiry.feature similarity index 100% rename from tests/e2e/cucumber/features/spaces/memberExpiry.feature rename to tests/e2e/features/spaces/memberExpiry.feature diff --git a/tests/e2e/cucumber/features/spaces/participantManagement.feature b/tests/e2e/features/spaces/participantManagement.feature similarity index 100% rename from tests/e2e/cucumber/features/spaces/participantManagement.feature rename to tests/e2e/features/spaces/participantManagement.feature diff --git a/tests/e2e/cucumber/features/spaces/project.feature b/tests/e2e/features/spaces/project.feature similarity index 100% rename from tests/e2e/cucumber/features/spaces/project.feature rename to tests/e2e/features/spaces/project.feature diff --git a/tests/e2e/cucumber/features/spaces/publicLink.feature b/tests/e2e/features/spaces/publicLink.feature similarity index 100% rename from tests/e2e/cucumber/features/spaces/publicLink.feature rename to tests/e2e/features/spaces/publicLink.feature diff --git a/tests/e2e/cucumber/features/user-settings/gdprExport.feature b/tests/e2e/features/user-settings/gdprExport.feature similarity index 100% rename from tests/e2e/cucumber/features/user-settings/gdprExport.feature rename to tests/e2e/features/user-settings/gdprExport.feature diff --git a/tests/e2e/cucumber/features/user-settings/languageChange.feature b/tests/e2e/features/user-settings/languageChange.feature similarity index 100% rename from tests/e2e/cucumber/features/user-settings/languageChange.feature rename to tests/e2e/features/user-settings/languageChange.feature diff --git a/tests/e2e/cucumber/features/user-settings/notifications.feature b/tests/e2e/features/user-settings/notifications.feature similarity index 100% rename from tests/e2e/cucumber/features/user-settings/notifications.feature rename to tests/e2e/features/user-settings/notifications.feature diff --git a/tests/e2e/cucumber/features/user-settings/pagination.feature b/tests/e2e/features/user-settings/pagination.feature similarity index 100% rename from tests/e2e/cucumber/features/user-settings/pagination.feature rename to tests/e2e/features/user-settings/pagination.feature diff --git a/tests/e2e/cucumber/features/user-settings/profilePhoto.feature b/tests/e2e/features/user-settings/profilePhoto.feature similarity index 100% rename from tests/e2e/cucumber/features/user-settings/profilePhoto.feature rename to tests/e2e/features/user-settings/profilePhoto.feature diff --git a/tests/e2e/cucumber/features/user-settings/tiles.feature b/tests/e2e/features/user-settings/tiles.feature similarity index 100% rename from tests/e2e/cucumber/features/user-settings/tiles.feature rename to tests/e2e/features/user-settings/tiles.feature diff --git a/tests/e2e/package.json b/tests/e2e/package.json index 1cd2c42394..69edb5dbf2 100644 --- a/tests/e2e/package.json +++ b/tests/e2e/package.json @@ -2,9 +2,12 @@ "type": "module", "devDependencies": { "@ai-zen/node-fetch-event-source": "^2.1.4", + "@playwright/test": "^1.59.1", + "@types/node": "^25.6.0", "fast-xml-parser": "^5.5.9", "lodash-es": "^4.18.1", "luxon": "^3.7.2", "uuid": "^14.0.0" - } + }, + "scripts": {} } diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts new file mode 100644 index 0000000000..962fec5ecc --- /dev/null +++ b/tests/e2e/playwright.config.ts @@ -0,0 +1,160 @@ +import { defineConfig, devices } from '@playwright/test' +import { defineBddConfig } from 'playwright-bdd' + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +const testDir = defineBddConfig({ + featuresRoot: './features', + steps: ['steps/ui/*.ts', 'steps/*.ts', 'environment/fixtures.ts', 'environment/hooks.ts'] +}) + +const withHttp = (url: string): string => { + return /^https?:\/\//i.test(url) ? url : `https://${url}` +} + +// Disable TLS verification for Node.js (e.g. self-signed certs in test environments) +// needs for @sse tests +process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export const appConfig = { + // Basic auth + basicAuth: process.env.BASIC_AUTH === 'true', + + // Keycloak + keycloak: process.env.KEYCLOAK === 'true', + keycloakHost: process.env.KEYCLOAK_HOST ?? 'keycloak.opencloud.test', + keycloakRealm: process.env.KEYCLOAK_REALM ?? 'openCloud', + keycloakAdminUser: process.env.KEYCLOAK_ADMIN_USER ?? 'admin', + keycloakAdminPassword: process.env.KEYCLOAK_ADMIN_PASSWORD ?? 'admin', + + get keycloakUrl() { + return withHttp(this.keycloakHost) + }, + get keycloakLoginUrl() { + return withHttp(this.keycloakHost + '/admin/master/console') + }, + + // OCM config + baseUrlOpenCloud: process.env.OC_BASE_URL ?? 'host.docker.internal:9200', + federatedBaseUrlOpenCloud: process.env.OC_FEDERATED_BASE_URL ?? 'federation-opencloud:10200', + federatedServer: process.env.FEDERATED_SERVER === 'true' || false, + + get baseUrl() { + const url = this.federatedServer ? this.federatedBaseUrlOpenCloud : this.baseUrlOpenCloud + return withHttp(url) + }, + + // Timeouts + testTimeout: parseInt(process.env.TEST_TIMEOUT || '120'), + get timeout() { + return this.testTimeout / 2 + }, + minTimeout: parseInt(process.env.MIN_TIMEOUT || '5'), + tokenTimeout: parseInt(process.env.TOKEN_TIMEOUT || '40'), + largeUploadTimeout: parseInt(process.env.LARGE_UPLOAD_TIMEOUT || '240'), + + headless: process.env.HEADLESS === 'true', + slowMo: process.env.SLOW_MO ? parseInt(process.env.SLOW_MO) : 0, + acceptDownloads: process.env.DOWNLOADS !== 'false', + assetsPath: 'filesForUpload', + tempAssetsPath: 'filesForUpload/temp', + failOnUncaughtConsoleError: process.env.FAIL_ON_UNCAUGHT_CONSOLE_ERROR === 'false' ? false : true +} + +export default defineConfig({ + testDir: testDir, + /* Run tests in files in parallel */ + 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 ? 1 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('')`. */ + // baseURL: 'http://localhost:3000', + baseURL: appConfig.baseUrl, + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + ignoreHTTPSErrors: true, + headless: appConfig.headless, + locale: 'en-US' + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'], permissions: ['clipboard-read', 'clipboard-write'] } + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] } + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] } + }, + + /* Test against mobile viewports. */ + { + name: 'mobile-chromium', + use: { ...devices['Pixel 5'] } + }, + { + name: 'mobile-webkit', + use: { + ...devices['iPhone 12'], + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1 Mobile/15E148 Safari/604.1' + } + }, + { + name: 'ipad-chromium', + use: { + ...devices['iPad Pro 11'], + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1 Mobile/15E148 Safari/604.1' + } + }, + { + name: 'ipad-landscape-webkit', + use: { + ...devices['iPad Pro 11 landscape'], + userAgent: + 'Mozilla/5.0 (iPad; CPU iPad OS 18_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1 Mobile/15E148 Safari/604.1' + } + } + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ] + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://localhost:3000', + // reuseExistingServer: !process.env.CI, + // }, +}) diff --git a/tests/e2e/cucumber/steps/api.ts b/tests/e2e/steps/api.ts similarity index 56% rename from tests/e2e/cucumber/steps/api.ts rename to tests/e2e/steps/api.ts index 1633e96851..5900fec556 100644 --- a/tests/e2e/cucumber/steps/api.ts +++ b/tests/e2e/steps/api.ts @@ -1,19 +1,19 @@ -import { Given, DataTable, When } from '@cucumber/cucumber' -import { World } from '../environment' -import { api } from '../../support' +import { World } from '../environment/world' +import { api } from '../support' import fs from 'fs' -import { Space } from '../../support/types' +import { Space } from '../support/types' +import { Given, When } from '../environment/fixtures' +import { DataTable } from 'playwright-bdd' Given( '{string} creates following user(s) using API', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const admin = this.usersEnvironment.getUser({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const admin = world.usersEnvironment.getUser({ key: stepUser }) for (const info of stepTable.hashes()) { - const uniqueId = `${info.id}-${this.uniquePrefix}` - // use a unique user name + const uniqueId = `${info.id}-${world.uniquePrefix}` const user = { - ...this.usersEnvironment.getUser({ key: info.id }), + ...world.usersEnvironment.getUser({ key: info.id }), id: info.id, username: uniqueId, email: `${uniqueId}@example.org` @@ -26,12 +26,12 @@ Given( Given( 'admin creates following user(s) using keycloak API', - async function (this: World, stepTable: DataTable): Promise { + async ({ world }: { world: World }, stepTable: DataTable): Promise => { for (const info of stepTable.hashes()) { - const uniqueId = `${info.id}-${this.uniquePrefix}` + const uniqueId = `${info.id}-${world.uniquePrefix}` // use a unique user name const user = { - ...this.usersEnvironment.getUser({ key: info.id }), + ...world.usersEnvironment.getUser({ key: info.id }), id: info.id, username: uniqueId, email: `${uniqueId}@example.org` @@ -44,10 +44,10 @@ Given( Given( '{string} assigns following roles to the users using API', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const admin = this.usersEnvironment.getUser({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const admin = world.usersEnvironment.getUser({ key: stepUser }) for await (const info of stepTable.hashes()) { - const user = this.usersEnvironment.getCreatedUser({ key: info.id }) + const user = world.usersEnvironment.getCreatedUser({ key: info.id }) await api.graph.assignRole(admin, user.uuid, info.role) } } @@ -55,9 +55,9 @@ Given( Given( 'admin assigns following roles to the user(s) using keycloak API', - async function (this: World, stepTable: DataTable): Promise { + async ({ world }: { world: World }, stepTable: DataTable): Promise => { for await (const info of stepTable.hashes()) { - const user = this.usersEnvironment.getCreatedUser({ key: info.id }) + const user = world.usersEnvironment.getCreatedUser({ key: info.id }) await api.keycloak.assignRole({ uuid: user.keycloakUuid, role: info.role }) } } @@ -65,13 +65,13 @@ Given( Given( '{string} creates following group(s) using API', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const admin = this.usersEnvironment.getUser({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const admin = world.usersEnvironment.getUser({ key: stepUser }) for (const info of stepTable.hashes()) { - const uniqueId = `${info.id}-${this.uniquePrefix}` + const uniqueId = `${info.id}-${world.uniquePrefix}` const group = { - ...this.usersEnvironment.getGroup({ key: info.id }), + ...world.usersEnvironment.getGroup({ key: info.id }), id: info.id, displayName: uniqueId } @@ -82,11 +82,11 @@ Given( Given( 'admin creates following group(s) using keycloak API', - async function (this: World, stepTable: DataTable): Promise { + async ({ world }: { world: World }, stepTable: DataTable): Promise => { for (const info of stepTable.hashes()) { - const uniqueId = `${info.id}-${this.uniquePrefix}` + const uniqueId = `${info.id}-${world.uniquePrefix}` const group = { - ...this.usersEnvironment.getGroup({ key: info.id }), + ...world.usersEnvironment.getGroup({ key: info.id }), id: info.id, displayName: uniqueId } @@ -97,12 +97,12 @@ Given( Given( '{string} adds user(s) to the group(s) using API', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const admin = this.usersEnvironment.getUser({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const admin = world.usersEnvironment.getUser({ key: stepUser }) for (const info of stepTable.hashes()) { - const userId = this.usersEnvironment.getCreatedUser({ key: info.user }).uuid - const groupId = this.usersEnvironment.getCreatedGroup({ key: info.group }).uuid + const userId = world.usersEnvironment.getCreatedUser({ key: info.user }).uuid + const groupId = world.usersEnvironment.getCreatedGroup({ key: info.group }).uuid await api.graph.addUserToGroup({ userId, groupId, admin }) } } @@ -110,10 +110,10 @@ Given( Given( 'admin adds user(s) to the group(s) using keycloak API', - async function (this: World, stepTable: DataTable): Promise { + async ({ world }: { world: World }, stepTable: DataTable): Promise => { for (const info of stepTable.hashes()) { - const user = this.usersEnvironment.getCreatedUser({ key: info.user }) - const group = this.usersEnvironment.getCreatedGroup({ key: info.group }) + const user = world.usersEnvironment.getCreatedUser({ key: info.user }) + const group = world.usersEnvironment.getCreatedGroup({ key: info.group }) await api.keycloak.addUserToGroup({ user, group }) } } @@ -121,8 +121,8 @@ Given( Given( '{string} creates the following folder(s) in personal space using API', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) for (const info of stepTable.hashes()) { await api.dav.createFolderInsidePersonalSpace({ user, folder: info.name }) } @@ -131,8 +131,8 @@ Given( Given( '{string} creates {int} folder(s) in personal space using API', - async function (this: World, stepUser: string, numberOfFolders: number): Promise { - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, numberOfFolders: number): Promise => { + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) for (let i = 1; i <= numberOfFolders; i++) { await api.dav.createFolderInsidePersonalSpace({ user, folder: `testFolder${i}` }) } @@ -141,8 +141,8 @@ Given( Given( '{string} shares the following resource using API', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) for (const info of stepTable.hashes()) { await api.share.createShare({ user, @@ -157,16 +157,16 @@ Given( Given( '{string} disables auto-accepting using API', - async function (this: World, stepUser: string): Promise { - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) await api.settings.disableAutoAcceptShare({ user }) } ) Given( '{string} creates the following file(s) into personal space using API', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) for (const info of stepTable.hashes()) { await api.dav.uploadFileInPersonalSpace({ user, @@ -179,8 +179,8 @@ Given( Given( '{string} creates the following file(s) with mtime into personal space using API', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) for (const info of stepTable.hashes()) { await api.dav.uploadFileInPersonalSpace({ user, @@ -194,8 +194,8 @@ Given( Given( '{string} creates {int} file(s) in personal space using API', - async function (this: World, stepUser: string, numberOfFiles: number): Promise { - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, numberOfFiles: number): Promise => { + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) for (let i = 1; i <= numberOfFiles; i++) { await api.dav.uploadFileInPersonalSpace({ user, @@ -208,10 +208,10 @@ Given( Given( '{string} uploads the following local file(s) into personal space using API', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) for (const info of stepTable.hashes()) { - const fileInfo = this.filesEnvironment.getFile({ name: info.localFile }) + const fileInfo = world.filesEnvironment.getFile({ name: info.localFile }) const content = fs.readFileSync(fileInfo.path) await api.dav.uploadFileInPersonalSpace({ user, @@ -224,14 +224,14 @@ Given( Given( '{string} creates the following project space(s) using API', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { const user = stepUser === 'Admin' - ? this.usersEnvironment.getUser({ key: stepUser }) - : this.usersEnvironment.getCreatedUser({ key: stepUser }) + ? world.usersEnvironment.getUser({ key: stepUser }) + : world.usersEnvironment.getCreatedUser({ key: stepUser }) for (const space of stepTable.hashes()) { const spaceId = await api.graph.createSpace({ user, space: space as unknown as Space }) - this.spacesEnvironment.createSpace({ + world.spacesEnvironment.createSpace({ key: space.id || space.name, space: { name: space.name, id: spaceId } }) @@ -241,13 +241,13 @@ Given( Given( '{string} creates the following file(s) in space {string} using API', - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, space: string, stepTable: DataTable - ): Promise { - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + ): Promise => { + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) for (const info of stepTable.hashes()) { await api.dav.uploadFileInsideSpaceBySpaceName({ user, @@ -261,13 +261,13 @@ Given( Given( '{string} creates the following folder(s) in space {string} using API', - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, space: string, stepTable: DataTable - ): Promise { - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + ): Promise => { + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) for (const info of stepTable.hashes()) { await api.dav.createFolderInsideSpaceBySpaceName({ user, @@ -280,16 +280,16 @@ Given( Given( '{string} adds the following member(s) to the space {string} using API', - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, space: string, stepTable: DataTable - ): Promise { + ): Promise => { const user = stepUser === 'Admin' - ? this.usersEnvironment.getUser({ key: stepUser }) - : this.usersEnvironment.getCreatedUser({ key: stepUser }) + ? world.usersEnvironment.getUser({ key: stepUser }) + : world.usersEnvironment.getCreatedUser({ key: stepUser }) for (const info of stepTable.hashes()) { await api.share.addMembersToTheProjectSpace({ user, @@ -304,8 +304,8 @@ Given( Given( '{string} adds the following tags for the following resources using API', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) for (const info of stepTable.hashes()) { await api.dav.addTagToResource({ user, resource: info.resource, tags: info.tags }) } @@ -314,8 +314,8 @@ Given( Given( '{string} creates a public link of following resource using API', - async function (this: World, stepUser: string, stepTable: DataTable) { - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) for (const info of stepTable.hashes()) { await api.share.createLinkShare({ @@ -332,8 +332,8 @@ Given( Given( '{string} creates a public link of the space using API', - async function (this: World, stepUser: string, stepTable: DataTable) { - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) for (const info of stepTable.hashes()) { await api.share.createSpaceLinkShare({ user, @@ -348,9 +348,9 @@ Given( Given( '{string} has uploads the profile image {string} using API', - async function (this: World, stepUser: string, profileImage: string): Promise { - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) - const profileImagePath = this.filesEnvironment.getFile({ name: profileImage }).path + async ({ world }: { world: World }, stepUser: string, profileImage: string): Promise => { + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) + const profileImagePath = world.filesEnvironment.getFile({ name: profileImage }).path await api.graph.uploadProfileImage({ user, profileImage: profileImagePath @@ -360,8 +360,8 @@ Given( Given( '{string} deletes the following resource(s) from personal space using API', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) for (const info of stepTable.hashes()) { await api.dav.deleteFileInPersonalSpace({ user, @@ -373,24 +373,24 @@ Given( When( 'admin disables user {string} using keycloak API', - async function (this: World, stepUser: string): Promise { - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) await api.keycloak.setUserEnabled({ uuid: user.keycloakUuid, enabled: false }) } ) When( 'admin enables user {string} using keycloak API', - async function (this: World, stepUser: string): Promise { - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) await api.keycloak.setUserEnabled({ uuid: user.keycloakUuid, enabled: true }) } ) When( 'admin deletes sessions of user {string} using keycloak API', - async function (this: World, stepUser: string): Promise { - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) await api.keycloak.deleteUserSessions({ uuid: user.keycloakUuid }) } ) diff --git a/tests/e2e/steps/tempFs.ts b/tests/e2e/steps/tempFs.ts new file mode 100644 index 0000000000..0b64d15afd --- /dev/null +++ b/tests/e2e/steps/tempFs.ts @@ -0,0 +1,10 @@ +import { World } from '../environment/world' +import * as tempFs from '../support/utils/runtimeFs' +import { Given } from '../environment/fixtures' + +Given( + 'the user creates a file {string} of {string} size in the temp upload directory', + ({ world }: { world: World }, fileName: string, fileSize: string): Promise => { + return tempFs.createFileWithSize(fileName, tempFs.getBytes(fileSize)) + } +) diff --git a/tests/e2e/steps/ui/a11.y.ts b/tests/e2e/steps/ui/a11.y.ts new file mode 100644 index 0000000000..1fd1f05ca7 --- /dev/null +++ b/tests/e2e/steps/ui/a11.y.ts @@ -0,0 +1,11 @@ +import { Then } from '../../environment/fixtures' +import { World } from '../../environment/world' +import { checkA11yOrLocalization } from '../../support/utils/accessibility' + +Then( + '{string} checks the accessibility of the DOM selector {string} on the {string}', + async ({ world }: { world: World }, stepUser: string, selector: string, context: string) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) + await checkA11yOrLocalization(page, context, selector) + } +) diff --git a/tests/e2e/cucumber/steps/ui/accountMenu.ts b/tests/e2e/steps/ui/accountMenu.ts similarity index 53% rename from tests/e2e/cucumber/steps/ui/accountMenu.ts rename to tests/e2e/steps/ui/accountMenu.ts index 2a358a8b1d..ab4fe63f8b 100644 --- a/tests/e2e/cucumber/steps/ui/accountMenu.ts +++ b/tests/e2e/steps/ui/accountMenu.ts @@ -1,12 +1,13 @@ -import { When, Then, DataTable } from '@cucumber/cucumber' -import { World } from '../../environment' -import { objects } from '../../../support' +import { When, Then } from '../../environment/fixtures' +import { DataTable } from 'playwright-bdd' +import { World } from '../../environment/world' +import { objects } from '../../support' import { expect } from '@playwright/test' Then( '{string} should have quota {string}', - async function (this: World, stepUser: string, quota: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, quota: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const accountObject = new objects.account.Account({ page }) expect(await accountObject.getQuotaValue()).toBe(quota) } @@ -14,8 +15,8 @@ Then( Then( '{string} should have self info:', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const accountObject = new objects.account.Account({ page }) for (const info of stepTable.hashes()) { @@ -28,16 +29,19 @@ Then( } ) -When('{string} opens the user menu', async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) - const accountObject = new objects.account.Account({ page }) - await accountObject.openAccountPage() -}) +When( + '{string} opens the user menu', + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) + const accountObject = new objects.account.Account({ page }) + await accountObject.openAccountPage() + } +) When( '{string} opens {string} on the user menu', - async function (this: World, stepUser: string, subPage: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, subPage: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const accountObject = new objects.account.Account({ page }) await accountObject.openAccountSubPage(subPage) } @@ -45,8 +49,8 @@ When( When( '{string} requests a new GDPR export', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const accountObject = new objects.account.Account({ page }) await accountObject.requestGdprExport() } @@ -54,8 +58,8 @@ When( When( '{string} downloads the GDPR export', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const accountObject = new objects.account.Account({ page }) const downloadedResource = await accountObject.downloadGdprExport() expect(downloadedResource).toContain('personal_data_export.json') @@ -64,8 +68,8 @@ When( When( '{string} changes the language to {string}', - async function (this: World, stepUser: string, language: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, language: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const accountObject = new objects.account.Account({ page }) const isAnonymousUser = stepUser === 'Anonymous' await accountObject.changeLanguage(language, isAnonymousUser) @@ -74,8 +78,8 @@ When( Then( '{string} should see the following account page title {string}', - async function (this: World, stepUser: string, title: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, title: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const accountObject = new objects.account.Account({ page }) const pageTitle = await accountObject.getTitle() expect(pageTitle).toEqual(title) @@ -84,18 +88,18 @@ Then( When( '{string} uploads/changes the profile image {string}', - async function (this: World, stepUser: string, profileImage: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, profileImage: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const accountObject = new objects.account.Account({ page }) - const profileImagePath = this.filesEnvironment.getFile({ name: profileImage }).path + const profileImagePath = world.filesEnvironment.getFile({ name: profileImage }).path await accountObject.uploadProfileImage({ path: profileImagePath }) } ) When( '{string} deletes the profile image', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const accountObject = new objects.account.Account({ page }) await accountObject.deleteProfileImage() } @@ -103,8 +107,8 @@ When( Then( /^"([^"]+)" should( not)? have a profile picture$/, - async function (this: World, stepUser: string, not: string | undefined): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, not: string | undefined): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const accountObject = new objects.account.Account({ page }) const profilePicture = await accountObject.getProfilePicture() diff --git a/tests/e2e/cucumber/steps/ui/adminSettings.ts b/tests/e2e/steps/ui/adminSettings.ts similarity index 71% rename from tests/e2e/cucumber/steps/ui/adminSettings.ts rename to tests/e2e/steps/ui/adminSettings.ts index 53ff9a3dae..50a3ff7347 100644 --- a/tests/e2e/cucumber/steps/ui/adminSettings.ts +++ b/tests/e2e/steps/ui/adminSettings.ts @@ -1,18 +1,19 @@ -import { DataTable, Then, When } from '@cucumber/cucumber' -import { World } from '../../environment' -import { objects } from '../../../support' +import { Then, When } from '../../environment/fixtures' +import { DataTable } from 'playwright-bdd' +import { World } from '../../environment/world' +import { objects } from '../../support' import { expect } from '@playwright/test' -import { shareRoles } from '../../../support/api/share/share' +import { shareRoles } from '../../support/api/share/share' Then( /^"([^"]*)" (should|should not) see the following space(?:s)?$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, actionType: string, stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationAdminSettings.Spaces({ page }) const actualList = await spacesObject.getDisplayedSpaces() @@ -25,8 +26,13 @@ Then( When( /^"([^"]*)" (disables|deletes|enables) the space "([^"]*)" using the context-menu$/, - async function (this: World, stepUser: string, action: string, key: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ( + { world }: { world: World }, + stepUser: string, + action: string, + key: string + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationAdminSettings.Spaces({ page }) const spaceId = spacesObject.getUUID({ key }) switch (action) { @@ -47,14 +53,14 @@ When( When( /^"([^"]*)" (?:changes|updates) the space "([^"]*)" (name|subtitle|quota) to "([^"]*)" using the context-menu$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, key: string, attribute: string, value: string - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationAdminSettings.Spaces({ page }) const spaceId = spacesObject.getUUID({ key }) switch (attribute) { @@ -75,13 +81,8 @@ When( When( /^"([^"]*)" (?:changes|updates) quota of the following space(?:s)? to "([^"]*)" using the batch-actions$/, - async function ( - this: World, - stepUser: string, - value: string, - stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, value: string, stepTable: DataTable) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationAdminSettings.Spaces({ page }) const spaceIds = [] for (const { id: space } of stepTable.hashes()) { @@ -98,13 +99,8 @@ When( When( /^"([^"]*)" (disables|enables|deletes) the following space(?:s)? using the batch-actions$/, - async function ( - this: World, - stepUser: string, - action: string, - stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, action: string, stepTable: DataTable) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationAdminSettings.Spaces({ page }) const spaceIds = [] for (const { id: space } of stepTable.hashes()) { @@ -129,8 +125,8 @@ When( When( '{string} navigates to the users management page', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.applicationAdminSettings.page.Users({ page }) await pageObject.navigate() } @@ -138,8 +134,13 @@ When( When( /^"([^"]*)" (allows|forbids) the login for the following user "([^"]*)" using the sidebar panel$/, - async function (this: World, stepUser: string, action: string, key: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ( + { world }: { world: World }, + stepUser: string, + action: string, + key: string + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const usersObject = new objects.applicationAdminSettings.Users({ page }) switch (action) { @@ -157,8 +158,13 @@ When( When( '{string} changes the quota of the user {string} to {string} using the sidebar panel', - async function (this: World, stepUser: string, key: string, value: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ( + { world }: { world: World }, + stepUser: string, + key: string, + value: string + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const usersObject = new objects.applicationAdminSettings.Users({ page }) await usersObject.changeQuota({ key, value, action: 'context-menu' }) } @@ -166,13 +172,13 @@ When( When( '{string} changes the quota to {string} for users using the batch action', - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, value: string, stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const usersObject = new objects.applicationAdminSettings.Users({ page }) const users = [] for (const { id: user } of stepTable.hashes()) { @@ -185,13 +191,8 @@ When( Then( /^"([^"]*)" (should|should not) see the following user(?:s)?$/, - async function ( - this: World, - stepUser: string, - action: string, - stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, action: string, stepTable: DataTable) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const usersObject = new objects.applicationAdminSettings.Users({ page }) const users = await usersObject.getDisplayedUsers() for (const { user } of stepTable.hashes()) { @@ -211,8 +212,8 @@ Then( When( '{string} sets the following filter(s)', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const usersObject = new objects.applicationAdminSettings.Users({ page }) for (const { filter, values } of stepTable.hashes()) { @@ -223,7 +224,7 @@ When( .split(',') .map( (groupKey) => - this.usersEnvironment.getCreatedGroup({ key: groupKey.trim() }).displayName + world.usersEnvironment.getCreatedGroup({ key: groupKey.trim() }).displayName ) } else { cleanedValues = values.split(',').map((val) => val.trim()) @@ -235,14 +236,14 @@ When( When( /^"([^"]*)" (adds|removes) the following users (?:to|from) the groups "([^"]*)" using the batch actions$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, action: string, groups: string, stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const usersObject = new objects.applicationAdminSettings.Users({ page }) const userIds = [] @@ -254,7 +255,7 @@ When( const formattedGroups = groups .split(',') .map( - (groupKey) => this.usersEnvironment.getCreatedGroup({ key: groupKey.trim() }).displayName + (groupKey) => world.usersEnvironment.getCreatedGroup({ key: groupKey.trim() }).displayName ) switch (action) { @@ -272,14 +273,14 @@ When( When( /^"([^"]*)" changes (userName|displayName|email|password|role) to "([^"]*)" for user "([^"]*)" using the sidebar panel$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, attribute: string, value: string, user: string - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const usersObject = new objects.applicationAdminSettings.Users({ page }) await usersObject.changeUser({ @@ -293,19 +294,19 @@ When( When( /^"([^"]*)" (adds|removes) the user "([^"]*)" (?:to|from) the group(?:s)? "([^"]*)" using the sidebar panel$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, action: string, user: string, groups: string - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const usersObject = new objects.applicationAdminSettings.Users({ page }) const formattedGroups = groups .split(',') .map( - (groupKey) => this.usersEnvironment.getCreatedGroup({ key: groupKey.trim() }).displayName + (groupKey) => world.usersEnvironment.getCreatedGroup({ key: groupKey.trim() }).displayName ) switch (action) { @@ -331,13 +332,13 @@ When( When( /^"([^"]*)" deletes the following (?:user|users) using the (batch actions|context menu)$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, actionType: string, stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const usersObject = new objects.applicationAdminSettings.Users({ page }) const userIds = [] switch (actionType) { @@ -361,8 +362,8 @@ When( When( '{string} navigates to the groups management page', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const groupsObject = new objects.applicationAdminSettings.page.Groups({ page }) await groupsObject.navigate() } @@ -370,8 +371,8 @@ When( When( '{string} creates the following group(s)', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const groupsObject = new objects.applicationAdminSettings.Groups({ page }) for (const info of stepTable.hashes()) { @@ -382,14 +383,14 @@ When( When( '{string} changes {word} to {string} for group {string} using the sidebar panel', - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, attribute: string, value: string, user: string - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const groupsObject = new objects.applicationAdminSettings.Groups({ page }) await groupsObject.changeGroup({ @@ -403,13 +404,13 @@ When( Then( /^"([^"]*)" (should|should not) see the following group(?:s)?$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, action: string, stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const groupsObject = new objects.applicationAdminSettings.Groups({ page }) const groups = await groupsObject.getDisplayedGroups() @@ -430,8 +431,8 @@ Then( When( '{string} creates the following user(s)', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const usersObject = new objects.applicationAdminSettings.Users({ page }) for (const info of stepTable.hashes()) { await usersObject.createUser({ @@ -446,13 +447,13 @@ When( When( /^"([^"]*)" deletes the following group(?:s)? using the (batch actions|context menu)$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, actionType: string, stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const groupsObject = new objects.applicationAdminSettings.Groups({ page }) const groupIds = [] @@ -477,8 +478,13 @@ When( When( /^"([^"]*)" opens the edit panel of user "([^"]*)" using the (quick action|context menu)$/, - async function (this: World, stepUser: string, actionUser: string, action: string) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ( + { world }: { world: World }, + stepUser: string, + actionUser: string, + action: string + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const usersObject = new objects.applicationAdminSettings.Users({ page }) await usersObject.openEditPanel({ key: actionUser, action: action.replace(' ', '-') }) } @@ -486,23 +492,26 @@ When( When( '{string} opens the edit panel of group {string} using the context menu', - async function (this: World, stepUser: string, group: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, group: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const groupsObject = new objects.applicationAdminSettings.Groups({ page }) await groupsObject.openEditPanel({ key: group, action: 'context-menu' }) } ) -Then('{string} should see the edit panel', async function (this: World, stepUser: string) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) - const usersObject = new objects.applicationAdminSettings.Users({ page }) - await usersObject.waitForEditPanelToBeVisible() -}) +Then( + '{string} should see the edit panel', + async ({ world }: { world: World }, stepUser: string) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) + const usersObject = new objects.applicationAdminSettings.Users({ page }) + await usersObject.waitForEditPanelToBeVisible() + } +) When( '{string} lists the members of project space {string} using a sidebar panel', - async function (this: World, stepUser: string, key: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, key: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationAdminSettings.Spaces({ page }) await spacesObject.openPanel({ key }) await spacesObject.openActionSideBarPanel({ action: 'SpaceMembers' }) @@ -511,8 +520,8 @@ When( Then( '{string} should see the following users in the sidebar panel of spaces admin settings', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationAdminSettings.Spaces({ page }) const actualMemberList = { manager: await spacesObject.listMembers({ filter: 'Can manage' }), @@ -528,8 +537,8 @@ Then( When( '{string} selects the user {string}', - async function (this: World, stepUser: string, value: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, value: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const usersObject = new objects.applicationAdminSettings.Users({ page }) await usersObject.selectUser({ key: value }) } @@ -537,8 +546,8 @@ When( When( '{string} selects the group {string}', - async function (this: World, stepUser: string, value: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, value: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const groupsObject = new objects.applicationAdminSettings.Groups({ page }) await groupsObject.selectGroup({ key: value }) } @@ -546,8 +555,8 @@ When( When( '{string} selects the space {string}', - async function (this: World, stepUser: string, value: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, value: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationAdminSettings.Spaces({ page }) await spacesObject.select({ key: value }) } @@ -555,8 +564,8 @@ When( Then( '{string} sees profile photo of the user {string}', - async function (this: World, stepUser: string, key: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, key: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const usersObject = new objects.applicationAdminSettings.Users({ page }) const userProfilePicture = usersObject.getUserProfilePicture({ key }) await expect(userProfilePicture).toHaveAttribute('src', /.+/) diff --git a/tests/e2e/cucumber/steps/ui/appStore.ts b/tests/e2e/steps/ui/appStore.ts similarity index 50% rename from tests/e2e/cucumber/steps/ui/appStore.ts rename to tests/e2e/steps/ui/appStore.ts index 69a8358533..c328cbe0c2 100644 --- a/tests/e2e/cucumber/steps/ui/appStore.ts +++ b/tests/e2e/steps/ui/appStore.ts @@ -1,12 +1,13 @@ -import { DataTable, Then, When } from '@cucumber/cucumber' -import { World } from '../../environment' -import { objects } from '../../../support' +import { When, Then } from '../../environment/fixtures' +import { DataTable } from 'playwright-bdd' +import { World } from '../../environment/world' +import { objects } from '../../support' import { expect } from '@playwright/test' When( '{string} navigates to the app store', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.appStore.AppStore({ page }) await pageObject.openAppStore() } @@ -14,8 +15,8 @@ When( Then( '{string} should see the app store', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.appStore.AppStore({ page }) await pageObject.waitForAppStoreIsVisible() } @@ -23,8 +24,8 @@ Then( Then( '{string} should see the following apps(s)', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.appStore.AppStore({ page }) const apps = await pageObject.getAppsList() for (const { app } of stepTable.hashes()) { @@ -35,8 +36,8 @@ Then( When( '{string} enters the search term {string}', - async function (this: World, stepUser: string, searchTerm: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, searchTerm: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.appStore.AppStore({ page }) await pageObject.setSearchTerm(searchTerm) } @@ -44,8 +45,13 @@ When( When( '{string} clicks on the tag {string} of the app {string}', - async function (this: World, stepUser: string, tag: string, app: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ( + { world }: { world: World }, + stepUser: string, + tag: string, + app: string + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.appStore.AppStore({ page }) await pageObject.selectAppTag({ tag, app }) } @@ -53,8 +59,8 @@ When( When( '{string} clicks on the tag {string}', - async function (this: World, stepUser: string, tag: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, tag: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.appStore.AppStore({ page }) await pageObject.selectTag(tag) } @@ -62,8 +68,8 @@ When( When( '{string} clicks on the app {string}', - async function (this: World, stepUser: string, app: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, app: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.appStore.AppStore({ page }) await pageObject.selectApp(app) } @@ -71,8 +77,8 @@ When( Then( '{string} should see the app details of {string}', - async function (this: World, stepUser: string, app: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, app: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.appStore.AppStore({ page }) await pageObject.waitForAppDetailsIsVisible(app) } @@ -80,16 +86,16 @@ Then( Then( '{string} downloads app version {string}', - async function (this: World, stepUser: string, version: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, version: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.appStore.AppStore({ page }) expect(await pageObject.downloadAppVersion(version)).toContain(version) } ) Then( '{string} downloads the latest version of the app {string}', - async function (this: World, stepUser: string, app: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, app: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.appStore.AppStore({ page }) expect(await pageObject.downloadApp(app)).toBeDefined() } @@ -97,8 +103,8 @@ Then( When( '{string} navigates back to the app store overview', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.appStore.AppStore({ page }) await pageObject.navigateToAppStoreOverview() } diff --git a/tests/e2e/steps/ui/application.ts b/tests/e2e/steps/ui/application.ts new file mode 100644 index 0000000000..dce23a3051 --- /dev/null +++ b/tests/e2e/steps/ui/application.ts @@ -0,0 +1,66 @@ +import { When } from '../../environment/fixtures' +import { World } from '../../environment/world' +import { objects } from '../../support' +import { waitForSSEEvent } from '../../support/utils/locator' + +When( + '{string} navigates to the project spaces management page', + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) + const pageObject = new objects.applicationAdminSettings.page.Spaces({ page }) + await pageObject.navigate() + } +) + +When( + '{string} opens the {string} app', + async ({ world }: { world: World }, stepUser: string, stepApp: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) + const applicationObject = new objects.runtime.Application({ page }) + await applicationObject.open({ name: stepApp }) + } +) + +When( + '{string} opens the apps menu', + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) + const applicationObject = new objects.runtime.Application({ page }) + await applicationObject.openAppsMenu() + } +) + +When( + '{string} reloads the page', + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) + const applicationObject = new objects.runtime.Application({ page }) + await applicationObject.reloadPage() + } +) + +When( + '{string} should get {string} SSE event', + async ({ world }: { world: World }, user: string, event: string): Promise => { + await waitForSSEEvent(user, event) + } +) + +When( + '{string} opens the {string} url', + async ({ world }: { world: World }, stepUser: string, url: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) + const applicationObject = new objects.runtime.Application({ page }) + url = url === '%clipboard%' ? await page.evaluate('navigator.clipboard.readText()') : url + await applicationObject.openUrl(url) + } +) + +When( + '{string} closes the sidebar', + async ({ world }: { world: World }, user: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: user }) + const applicationObject = new objects.runtime.Application({ page }) + await applicationObject.closeSidebar() + } +) diff --git a/tests/e2e/cucumber/steps/ui/embed.ts b/tests/e2e/steps/ui/embed.ts similarity index 64% rename from tests/e2e/cucumber/steps/ui/embed.ts rename to tests/e2e/steps/ui/embed.ts index c327396a51..aaf4501248 100644 --- a/tests/e2e/cucumber/steps/ui/embed.ts +++ b/tests/e2e/steps/ui/embed.ts @@ -1,23 +1,23 @@ -import { When, Then } from '@cucumber/cucumber' -import { World } from '../../environment' -import { config } from '../../../config' +import { When, Then } from '../../environment/fixtures' +import { World } from '../../environment/world' +import { appConfig } from '../../playwright.config' import { expect } from '@playwright/test' -import { TokenEnvironmentFactory } from '../../../support/environment/token' +import { TokenEnvironmentFactory } from '../../support/environment/token' When( '{string} opens the app in embed mode with delegated authentication', - async function (this: World, stepUser: string): Promise { - const { page } = await this.actorsEnvironment.createActor({ + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = await world.actorsEnvironment.createActor({ key: stepUser, - namespace: this.actorsEnvironment.generateNamespace(this.feature.name, stepUser) + namespace: world.actorsEnvironment.generateNamespace(stepUser) }) - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) - const tokenEnvironment = TokenEnvironmentFactory(config.keycloak ? 'keycloak' : null) + const tokenEnvironment = TokenEnvironmentFactory(appConfig.keycloak ? 'keycloak' : null) const { accessToken } = tokenEnvironment.getToken({ user }) - const appUrl = `${config.baseUrl}?embed=true&embed-delegate-authentication=true` + const appUrl = `${appConfig.baseUrl}?embed=true&embed-delegate-authentication=true` // Serve a test harness page at the same origin so postMessage works // with the default targetOrigin. The harness embeds the app in an @@ -45,7 +45,7 @@ When( }) }) - await page.goto(`${config.baseUrl}/embed-test-harness`) + await page.goto(`${appConfig.baseUrl}/embed-test-harness`) // Wait for the embedded app to fully load const frame = page.frameLocator('#embed-frame') @@ -55,8 +55,8 @@ When( Then( '{string} should see the embed mode actions', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const frame = page.frameLocator('#embed-frame') await expect(frame.locator('[data-testid="button-cancel"]')).toBeVisible({ timeout: 15000 }) @@ -66,8 +66,8 @@ Then( Then( '{string} should not see the full web UI', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const frame = page.frameLocator('#embed-frame') // In embed mode the user menu and app switcher should be hidden diff --git a/tests/e2e/cucumber/steps/ui/favorites.ts b/tests/e2e/steps/ui/favorites.ts similarity index 54% rename from tests/e2e/cucumber/steps/ui/favorites.ts rename to tests/e2e/steps/ui/favorites.ts index 115f9ffe4f..dd3f1d50fd 100644 --- a/tests/e2e/cucumber/steps/ui/favorites.ts +++ b/tests/e2e/steps/ui/favorites.ts @@ -1,11 +1,12 @@ -import { DataTable, When } from '@cucumber/cucumber' -import { World } from '../../environment' -import { objects } from '../../../support' +import { When } from '../../environment/fixtures' +import { DataTable } from 'playwright-bdd' +import { World } from '../../environment/world' +import { objects } from '../../support' When( '{string} navigates to the favorites page', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.applicationFiles.page.favorites.Favorites({ page }) await pageObject.navigate() } @@ -13,13 +14,13 @@ When( When( '{string} removes the following resources from favorites using {string}', - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, method: 'context menu' | 'batch action', stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) const resources = stepTable.hashes().map((row) => row.resource) diff --git a/tests/e2e/cucumber/steps/ui/federation.ts b/tests/e2e/steps/ui/federation.ts similarity index 50% rename from tests/e2e/cucumber/steps/ui/federation.ts rename to tests/e2e/steps/ui/federation.ts index 0641258dfa..9a39148e82 100644 --- a/tests/e2e/cucumber/steps/ui/federation.ts +++ b/tests/e2e/steps/ui/federation.ts @@ -1,21 +1,23 @@ -import { Given, When, Then, DataTable } from '@cucumber/cucumber' -import { World } from '../../environment' -import { objects } from '../../../support' +import { Given, When, Then } from '../../environment/fixtures' +import { DataTable } from 'playwright-bdd' +import { World } from '../../environment/world' +import { objects } from '../../support' import { expect } from '@playwright/test' + Given( '{string} generates invitation token for the federation share', - async function (this: World, stepUser: any): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: any): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.scienceMesh.Federation({ page }) - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) await pageObject.generateInvitation(user.id) } ) When( '{string} accepts federated share invitation by local user {string}', - async function (this: World, stepUser: string, sharer: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, sharer: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.scienceMesh.Federation({ page }) await pageObject.acceptInvitation(sharer) } @@ -23,8 +25,8 @@ When( Then( '{string} should see the following federated connections:', - async function (this: World, stepUser: any, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: any, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.scienceMesh.Federation({ page }) for (const info of stepTable.hashes()) { const isConnectionExist = await pageObject.connectionExists(info) diff --git a/tests/e2e/cucumber/steps/ui/links.ts b/tests/e2e/steps/ui/links.ts similarity index 61% rename from tests/e2e/cucumber/steps/ui/links.ts rename to tests/e2e/steps/ui/links.ts index e006a900ee..b32bd34107 100644 --- a/tests/e2e/cucumber/steps/ui/links.ts +++ b/tests/e2e/steps/ui/links.ts @@ -1,13 +1,14 @@ -import { DataTable, Then, When } from '@cucumber/cucumber' +import { When, Then } from '../../environment/fixtures' +import { DataTable } from 'playwright-bdd' import { expect } from '@playwright/test' -import { World } from '../../environment' -import { objects } from '../../../support' -import { securePassword } from '../../../support/store' +import { World } from '../../environment/world' +import { objects } from '../../support' +import { securePassword } from '../../support/store' When( '{string} creates a public link of following resource using the sidebar panel', - async function (this: World, stepUser: string, stepTable: DataTable) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const linkObject = new objects.applicationFiles.Link({ page }) for (const info of stepTable.hashes()) { @@ -16,7 +17,7 @@ When( role: info.role, password: info.password === '%public%' ? securePassword : info.password, name: 'Unnamed link', - a11yEnabled: this.a11yEnabled + a11yEnabled: world.a11yEnabled }) } } @@ -24,8 +25,8 @@ When( When( '{string} creates a public link for the space with password {string} using the sidebar panel', - async function (this: World, stepUser: string, password: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, password: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spaceObject = new objects.applicationFiles.Spaces({ page }) password = password === '%public%' ? securePassword : password await spaceObject.createPublicLink({ password }) @@ -34,8 +35,13 @@ When( When( '{string} renames the most recently created public link of resource {string} to {string}', - async function (this: World, stepUser: string, resource: string, newName: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ( + { world }: { world: World }, + stepUser: string, + resource: string, + newName: string + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const linkObject = new objects.applicationFiles.Link({ page }) const linkName = await linkObject.changeName({ resource, newName }) expect(linkName).toBe(newName) @@ -44,8 +50,8 @@ When( When( '{string} renames the most recently created public link of space to {string}', - async function (this: World, stepUser: any, newName: any): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, newName: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const linkObject = new objects.applicationFiles.Link({ page }) const linkName = await linkObject.changeName({ newName, space: true }) expect(linkName).toBe(newName) @@ -54,14 +60,14 @@ When( When( '{string} sets the expiration date of the public link named {string} of resource {string} to {string}', - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, linkName: string, resource: string, expireDate: string - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const linkObject = new objects.applicationFiles.Link({ page }) await linkObject.addExpiration({ resource, linkName, expireDate }) } @@ -69,14 +75,14 @@ When( When( '{string} changes the password of the public link named {string} of resource {string} to {string}', - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, linkName: string, resource: string, newPassword: string - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const linkObject = new objects.applicationFiles.Link({ page }) await linkObject.addPassword({ resource, linkName, newPassword }) } @@ -84,14 +90,14 @@ When( When( '{string} tries to sets a new password {string} of the public link named {string} of resource {string}', - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, newPassword: string, linkName: string, resource: string - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const linkObject = new objects.applicationFiles.Link({ page }) await linkObject.fillPassword({ resource, linkName, newPassword }) } @@ -99,8 +105,8 @@ When( When( /^"([^"]*)" (reveals|hides) the password of the public link$/, - async function (this: World, stepUser: string, showOrHide: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, showOrHide: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const linkObject = new objects.applicationFiles.Link({ page }) await linkObject.showOrHidePassword({ showOrHide }) } @@ -108,8 +114,8 @@ When( When( '{string} closes the public link password dialog box', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const linkObject = new objects.applicationFiles.Link({ page }) await linkObject.clickOnCancelButton() } @@ -117,8 +123,8 @@ When( When( '{string} copies the password of the public link', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const linkObject = new objects.applicationFiles.Link({ page }) await linkObject.copyEnteredPassword() } @@ -126,8 +132,8 @@ When( When( '{string} generates the password for the public link', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const linkObject = new objects.applicationFiles.Link({ page }) await linkObject.generatePassword() } @@ -135,8 +141,8 @@ When( When( '{string} sets the password of the public link', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const linkObject = new objects.applicationFiles.Link({ page }) await linkObject.setPassword() } @@ -144,14 +150,14 @@ When( When( '{string} edits the public link named {string} of resource {string} changing role to {string}', - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, linkName: any, resource: string, role: string - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const linkObject = new objects.applicationFiles.Link({ page }) const roleText = await linkObject.changeRole({ linkName, resource, role }) expect(roleText.toLowerCase()).toBe(role.toLowerCase()) @@ -160,8 +166,13 @@ When( When( '{string} removes the public link named {string} of resource {string}', - async function (this: World, stepUser: string, name: string, resource: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ( + { world }: { world: World }, + stepUser: string, + name: string, + resource: string + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const linkObject = new objects.applicationFiles.Link({ page }) await linkObject.delete({ resourceName: resource, name }) } @@ -169,13 +180,13 @@ When( Then( /^"([^"]*)" (should|should not) be able to edit the public link named "([^"]*)"$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: any, shouldOrShouldNot: string, linkName: any - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const linkObject = new objects.applicationFiles.Link({ page }) const isVisible = await linkObject.islinkEditButtonVisibile(linkName) expect(isVisible).toBe(shouldOrShouldNot !== 'should not') @@ -184,8 +195,8 @@ Then( Then( '{string} should see an error message', - async function (this: World, stepUser: any, errorMessage: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: any, errorMessage: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const linkObject = new objects.applicationFiles.Link({ page }) const actualErrorMessage = await linkObject.checkErrorMessage() expect(actualErrorMessage).toBe(errorMessage) @@ -194,8 +205,13 @@ Then( When( '{string} edits the public link named {string} of the space changing role to {string}', - async function (this: World, stepUser: string, linkName: string, role: any): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ( + { world }: { world: World }, + stepUser: string, + linkName: string, + role: any + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const linkObject = new objects.applicationFiles.Link({ page }) const newPermission = await linkObject.changeRole({ linkName, role, space: true }) expect(newPermission.toLowerCase()).toBe(role.toLowerCase()) @@ -204,14 +220,14 @@ When( When( '{string} edits the public link named {string} of resource {string} changing role to {string} and setting a password', - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, linkName: any, resource: string, role: string - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const linkObject = new objects.applicationFiles.Link({ page }) const roleText = await linkObject.changeRole({ linkName, @@ -225,23 +241,28 @@ When( When( '{string} copies the link {string} of resource {string}', - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, linkName: string, resource: string - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const linkObject = new objects.applicationFiles.Link({ page }) const clipboard = await linkObject.copyLinkToClipboard({ resource: resource, name: linkName }) - expect(clipboard).toBe(this.linksEnvironment.getLink({ name: linkName }).url) + expect(clipboard).toBe(world.linksEnvironment.getLink({ name: linkName }).url) } ) When( '{string} deletes a password of the public link named {string} of resource {string}', - async function (this: World, stepUser: string, name: string, resource: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ( + { world }: { world: World }, + stepUser: string, + name: string, + resource: string + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const linkObject = new objects.applicationFiles.Link({ page }) await linkObject.deletePassword({ resource, name }) } diff --git a/tests/e2e/cucumber/steps/ui/navigateByUrl.ts b/tests/e2e/steps/ui/navigateByUrl.ts similarity index 54% rename from tests/e2e/cucumber/steps/ui/navigateByUrl.ts rename to tests/e2e/steps/ui/navigateByUrl.ts index c7b7c7a7f7..8f6a6c6b2e 100644 --- a/tests/e2e/cucumber/steps/ui/navigateByUrl.ts +++ b/tests/e2e/steps/ui/navigateByUrl.ts @@ -1,18 +1,18 @@ -import { Then, When } from '@cucumber/cucumber' -import { World } from '../../environment' -import { objects } from '../../../support' +import { When, Then } from '../../environment/fixtures' +import { World } from '../../environment/world' +import { objects } from '../../support' When( '{string} navigates to {string} details panel of file {string} of space {string} through the URL', - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, detailsPanel: string, resource: string, space: string - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) const urlNavObject = new objects.urlNavigation.URLNavigation({ page }) await urlNavObject.navigateToDetailsPanelOfResource({ resource, detailsPanel, user, space }) } @@ -20,9 +20,14 @@ When( When( /^"([^"]*)" opens the (?:resource|file|folder) "([^"]*)" of space "([^"]*)" through the URL$/, - async function (this: World, stepUser: string, resource: string, space: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + async ( + { world }: { world: World }, + stepUser: string, + resource: string, + space: string + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) const urlNavObject = new objects.urlNavigation.URLNavigation({ page }) await urlNavObject.openResourceViaUrl({ resource, user, space }) } @@ -30,16 +35,16 @@ When( When( /^"([^"]*)" opens the file "([^"]*)" of space "([^"]*)" in (Collabora|OnlyOffice) through the URL for (mobile|desktop) client$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, resource: string, space: string, editorName: string, client: string - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) const urlNavObject = new objects.urlNavigation.URLNavigation({ page }) await urlNavObject.openResourceViaUrl({ resource, user, space, editorName, client }) } @@ -47,9 +52,9 @@ When( When( '{string} opens space {string} through the URL', - async function (this: World, stepUser: string, space: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) - const user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, space: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) const urlNavObject = new objects.urlNavigation.URLNavigation({ page }) await urlNavObject.openSpaceViaUrl({ user, space }) } @@ -57,8 +62,8 @@ When( When( '{string} navigates to a non-existing page', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const urlNavObject = new objects.urlNavigation.URLNavigation({ page }) await urlNavObject.navigateToNonExistingPage() } @@ -66,8 +71,8 @@ When( Then( '{string} should see the not found page', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const urlNavObject = new objects.urlNavigation.URLNavigation({ page }) await urlNavObject.waitForNotFoundPageToBeVisible() } diff --git a/tests/e2e/cucumber/steps/ui/notifications.ts b/tests/e2e/steps/ui/notifications.ts similarity index 55% rename from tests/e2e/cucumber/steps/ui/notifications.ts rename to tests/e2e/steps/ui/notifications.ts index 1123835bc1..3cc3469ef6 100644 --- a/tests/e2e/cucumber/steps/ui/notifications.ts +++ b/tests/e2e/steps/ui/notifications.ts @@ -1,12 +1,13 @@ -import { DataTable, Then, When } from '@cucumber/cucumber' -import { World } from '../../environment' -import { objects } from '../../../support' +import { When, Then } from '../../environment/fixtures' +import { DataTable } from 'playwright-bdd' +import { World } from '../../environment/world' +import { objects } from '../../support' import { expect } from '@playwright/test' Then( '{string} should see the following notification(s)', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const application = new objects.runtime.Application({ page }) const messages = await application.getNotificationMessages() for (const { message } of stepTable.hashes()) { @@ -17,8 +18,8 @@ Then( Then( '{string} should see no notification(s)', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const application = new objects.runtime.Application({ page }) const messages = await application.getNotificationMessages() expect(messages.length).toBe(0) @@ -27,8 +28,8 @@ Then( When( '{string} marks all notifications as read', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const application = new objects.runtime.Application({ page }) await application.markNotificationsAsRead() } @@ -36,8 +37,8 @@ When( Then( '{string} should see sharer avatar in the notification', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const application = new objects.runtime.Application({ page }) const avatarLocator = await application.getSharerAvatarFromNotification() await expect(avatarLocator).toBeVisible() @@ -46,8 +47,8 @@ Then( Then( '{string} opens notifications dropdown', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const application = new objects.runtime.Application({ page }) await application.getNotificationMessages() } diff --git a/tests/e2e/cucumber/steps/ui/public.ts b/tests/e2e/steps/ui/public.ts similarity index 57% rename from tests/e2e/cucumber/steps/ui/public.ts rename to tests/e2e/steps/ui/public.ts index 3184d10cdf..ce17019825 100644 --- a/tests/e2e/cucumber/steps/ui/public.ts +++ b/tests/e2e/steps/ui/public.ts @@ -1,27 +1,28 @@ -import { DataTable, Then, When } from '@cucumber/cucumber' +import { When, Then } from '../../environment/fixtures' +import { DataTable } from 'playwright-bdd' import { Page, expect } from '@playwright/test' -import { World } from '../../environment' -import { objects } from '../../../support' +import { World } from '../../environment/world' +import { objects } from '../../support' import { processDelete, processDownload } from './resources' -import { editor } from '../../../support/objects/app-files/utils' -import { securePassword } from '../../../support/store' +import { editor } from '../../support/objects/app-files/utils' +import { securePassword } from '../../support/store' When( '{string} opens the public link {string}', - async function (this: World, stepUser: string, name: string): Promise { + async ({ world }: { world: World }, stepUser: string, name: string): Promise => { let page: Page try { - page = this.actorsEnvironment.getActor({ key: stepUser }).page + page = world.actorsEnvironment.getActor({ key: stepUser }).page } catch { - await this.actorsEnvironment + await world.actorsEnvironment .createActor({ key: stepUser, - namespace: this.actorsEnvironment.generateNamespace(this.feature.name, stepUser) + namespace: world.actorsEnvironment.generateNamespace(stepUser) }) .then((actor) => (page = actor.page)) } - const { url } = this.linksEnvironment.getLink({ name }) + const { url } = world.linksEnvironment.getLink({ name }) const pageObject = new objects.applicationFiles.page.Public({ page }) await pageObject.open({ url }) } @@ -29,8 +30,8 @@ When( When( '{string} unlocks the public link with password {string}', - async function (this: World, stepUser: string, password: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, password: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.applicationFiles.page.Public({ page }) if (password === '%copied_password%') { password = await page.evaluate('navigator.clipboard.readText()') @@ -43,24 +44,24 @@ When( When( '{string} closes the file viewer', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) await editor.close(page) } ) When( '{string} saves the file viewer', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) await editor.save(page) } ) Then( /^"([^"]*)" is in a (text-editor|pdf-viewer|media-viewer)$/, - async function (this: World, stepUser: string, fileViewerType: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, fileViewerType: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const fileViewerLocator = editor.fileViewerLocator({ page, fileViewerType }) await expect(fileViewerLocator).toBeVisible() } @@ -68,13 +69,13 @@ Then( When( '{string} enters the text {string} in editor {string}', - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, text: string, editorToOpen: string - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.applicationFiles.page.Public({ page }) await pageObject.fillContentOfOpenDocumentOrMicrosoftWordDocument({ page, @@ -86,13 +87,13 @@ When( When( '{string} should see the content {string} in editor {string}', - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, expectedContent: string, editorToOpen: string - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.applicationFiles.page.Public({ page }) const actualFileContent = await pageObject.getContentOfOpenDocumentOrMicrosoftWordDocument({ page, @@ -104,21 +105,21 @@ When( When( '{string} drop uploads following resources', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.applicationFiles.page.Public({ page }) const resources = stepTable .hashes() - .map((f) => this.filesEnvironment.getFile({ name: f.resource })) + .map((f) => world.filesEnvironment.getFile({ name: f.resource })) await pageObject.dropUpload({ resources }) } ) When( '{string} refreshes the old link', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.applicationFiles.page.Public({ page }) await pageObject.reload() } @@ -126,13 +127,13 @@ When( When( /^"([^"]*)" downloads the following public link resource(?:s)? using the (sidebar panel|batch action|single share view)$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, actionType: string, stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.applicationFiles.page.Public({ page }) await processDownload(stepTable, pageObject, actionType) } @@ -140,8 +141,8 @@ When( When( '{string} renames the following public link resource(s)', - async function (this: World, stepUser: string, stepTable: DataTable) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.applicationFiles.page.Public({ page }) for (const { resource, as } of stepTable.hashes()) { await pageObject.rename({ resource, newName: as }) @@ -151,13 +152,13 @@ When( When( '{string} uploads the following resource(s) in public link page', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.applicationFiles.page.Public({ page }) for (const info of stepTable.hashes()) { await pageObject.upload({ to: info.to, - resources: [this.filesEnvironment.getFile({ name: info.resource })], + resources: [world.filesEnvironment.getFile({ name: info.resource })], option: info.option, type: info.type }) @@ -167,23 +168,23 @@ When( Then( '{string} should not be able to open the old link {string}', - async function (this: World, stepUser: string, name: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, name: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.applicationFiles.page.Public({ page }) - const { url } = this.linksEnvironment.getLink({ name }) + const { url } = world.linksEnvironment.getLink({ name }) await pageObject.expectThatLinkIsDeleted({ url }) } ) When( /^"([^"]*)" deletes the following resources from public link using (sidebar panel|batch action)$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, actionType: string, stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.applicationFiles.page.Public({ page }) await processDelete(stepTable, pageObject, actionType) } diff --git a/tests/e2e/cucumber/steps/ui/resources.ts b/tests/e2e/steps/ui/resources.ts similarity index 69% rename from tests/e2e/cucumber/steps/ui/resources.ts rename to tests/e2e/steps/ui/resources.ts index 65ffc88bbd..12645f978e 100644 --- a/tests/e2e/cucumber/steps/ui/resources.ts +++ b/tests/e2e/steps/ui/resources.ts @@ -1,26 +1,27 @@ -import { DataTable, When, Then } from '@cucumber/cucumber' +import { When, Then } from '../../environment/fixtures' +import { DataTable } from 'playwright-bdd' import path from 'path' -import { World } from '../../environment' -import { objects } from '../../../support' +import { World } from '../../environment/world' +import { objects } from '../../support' import { expect } from '@playwright/test' -import { config } from '../../../config' +import { appConfig } from '../../playwright.config' import { createResourceTypes, shortcutType, ActionViaType, PanelType -} from '../../../support/objects/app-files/resource/actions' -import { Public } from '../../../support/objects/app-files/page/public' -import { Resource } from '../../../support/objects/app-files' -import * as runtimeFs from '../../../support/utils/runtimeFs' -import { searchFilter } from '../../../support/objects/app-files/resource/actions' -import { File } from '../../../support/types' -import { waitProcessingToFinish } from '../../../support/objects/app-files/fileEvents' +} from '../../support/objects/app-files/resource/actions' +import { Public } from '../../support/objects/app-files/page/public' +import { Resource } from '../../support/objects/app-files' +import * as runtimeFs from '../../support/utils/runtimeFs' +import { searchFilter } from '../../support/objects/app-files/resource/actions' +import { File } from '../../support/types' +import { waitProcessingToFinish } from '../../support/objects/app-files/fileEvents' When( '{string} creates the following resource(s)', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) for (const info of stepTable.hashes()) { @@ -35,13 +36,13 @@ When( When( '{string} uploads the following resource(s)', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) for (const info of stepTable.hashes()) { await resourceObject.upload({ to: info.to, - resources: [this.filesEnvironment.getFile({ name: info.resource })], + resources: [world.filesEnvironment.getFile({ name: info.resource })], option: info.option, type: info.type }) @@ -51,13 +52,13 @@ When( When( '{string} tries to upload the following resource', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) for (const info of stepTable.hashes()) { await resourceObject.tryToUpload({ to: info.to, - resources: [this.filesEnvironment.getFile({ name: info.resource })], + resources: [world.filesEnvironment.getFile({ name: info.resource })], error: info.error }) } @@ -66,15 +67,18 @@ When( When( '{string} starts uploading the following large resource(s) from the temp upload directory', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) for (const info of stepTable.hashes()) { await resourceObject.startUpload({ to: info.to, resources: [ - this.filesEnvironment.getFile({ - name: path.join(runtimeFs.getTempUploadPath().replace(config.assets, ''), info.resource) + world.filesEnvironment.getFile({ + name: path.join( + runtimeFs.getTempUploadPath().replace(appConfig.assetsPath, ''), + info.resource + ) }) ], option: info.option @@ -87,9 +91,9 @@ When( '{string} {word} the file upload', // increase the test timeout for large uploads. // ignores the global timeout. - { timeout: config.largeUploadTimeout * 1000 }, - async function (this: World, stepUser: string, action: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + // { timeout: config.largeUploadTimeout * 1000 }, + async ({ world }: { world: World }, stepUser: string, action: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) switch (action) { case 'pauses': @@ -109,8 +113,13 @@ When( When( /^"([^"]*)" downloads the following resource(?:s)? using the (sidebar panel|batch action|preview topbar)$/, - async function (this: World, stepUser: string, actionType: string, stepTable: DataTable) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ( + { world }: { world: World }, + stepUser: string, + actionType: string, + stepTable: DataTable + ) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await processDownload(stepTable, resourceObject, actionType) } @@ -118,8 +127,13 @@ When( When( /^"([^"]*)" deletes the following resource(?:s)? using the (sidebar panel|batch action)$/, - async function (this: World, stepUser: string, actionType: string, stepTable: DataTable) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ( + { world }: { world: World }, + stepUser: string, + actionType: string, + stepTable: DataTable + ) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await processDelete(stepTable, resourceObject, actionType) } @@ -127,16 +141,16 @@ When( When( /^"([^"]*)" deletes the resource using the app topbar$/, - async function (this: World, stepUser: string) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.deleteResourceViaAppTopbar() } ) When( '{string} renames the following resource(s)', - async function (this: World, stepUser: string, stepTable: DataTable) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) for (const { resource, as } of stepTable.hashes()) { await resourceObject.rename({ resource, newName: as }) @@ -146,14 +160,14 @@ When( When( /^"([^"]*)" (copies|moves) the following resource(?:s)? using (keyboard|drag-drop|drag-drop-breadcrumb|sidebar-panel|dropdown-menu|batch-action)$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, actionType: string, method: string, stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) // drag-n-drop always does MOVE @@ -174,15 +188,15 @@ When( When( /^"([^"]*)" (copies|moves) the following resources to "([^"]*)" at once using (keyboard|drag-drop|drag-drop-breadcrumb|dropdown-menu|batch-action)$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, actionType: string, newLocation: string, method: string, stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) // drag-n-drop always does MOVE @@ -203,8 +217,8 @@ When( When( '{string} restores following resource(s) version', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) const fileInfo = stepTable.hashes().reduce>((acc, stepRow) => { const { to, resource, version, openDetailsPanel } = stepRow @@ -213,7 +227,7 @@ When( acc[to] = [] } - acc[to].push(this.filesEnvironment.getFile({ name: resource })) + acc[to].push(world.filesEnvironment.getFile({ name: resource })) if (version !== '1') { throw new Error('restoring is only supported for the most recent version') @@ -234,8 +248,8 @@ When( When( '{string} downloads old version of the following resource(s)', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) const fileInfo = stepTable.hashes().reduce>((acc, stepRow) => { const { to, resource } = stepRow @@ -244,7 +258,7 @@ When( acc[to] = [] } - acc[to].push(this.filesEnvironment.getFile({ name: resource })) + acc[to].push(world.filesEnvironment.getFile({ name: resource })) return acc }, {}) @@ -257,8 +271,8 @@ When( When( '{string} deletes the following resources from trashbin using the batch action', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) const resources = [].concat(...stepTable.rows()) await resourceObject.deleteTrashbinMultipleResources({ resources }) @@ -267,8 +281,8 @@ When( When( '{string} empties the trashbin', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.emptyTrashbin({ page }) } @@ -276,13 +290,13 @@ When( Then( /^"([^"]*)" (should|should not) be able to delete following resource(?:s)? from the trashbin?$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, actionType: string, stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) for (const info of stepTable.hashes()) { if (actionType === 'should') { @@ -298,13 +312,13 @@ Then( Then( /^"([^"]*)" (should|should not) be able to restore following resource(?:s)? from the trashbin?$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, actionType: string, stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) for (const info of stepTable.hashes()) { if (actionType === 'should') { @@ -324,13 +338,13 @@ Then( Then( /^"([^"]*)" restores the following resource(?:s)? from trashbin( using the batch action)?$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, batchAction: string, stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) if (batchAction) { const resources = stepTable.hashes().map((info) => info.resource) @@ -348,16 +362,16 @@ Then( When( /^"([^"]*)" searches "([^"]*)" using the global search(?: and the "([^"]*)" filter)?( and presses enter)?$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, keyword: string, filter: string, command: string - ): Promise { + ): Promise => { keyword = keyword ?? '' const pressEnter = !!command && command.endsWith('presses enter') - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.searchResource({ keyword, @@ -369,18 +383,18 @@ When( Then( /^following resources (should|should not) be displayed in the (?:files list|Shares|trashbin) for user "([^"]*)"$/, - async function ( - this: World, + async ( + { world }: { world: World }, actionType: string, stepUser: string, stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) for (const info of stepTable.hashes()) { if (actionType === 'should') { await expect(resourceObject.getResourceLocator(info.resource)).toBeVisible({ - timeout: config.timeout * 1000 + timeout: appConfig.timeout * 1000 }) await waitProcessingToFinish(page, info.resource) } else { @@ -392,13 +406,13 @@ Then( Then( /^following resources (should|should not) be displayed in the search list for user "([^"]*)"$/, - async function ( - this: World, + async ( + { world }: { world: World }, actionType: string, stepUser: string, stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) for (const info of stepTable.hashes()) { if (actionType === 'should') { @@ -412,8 +426,8 @@ Then( When( '{string} opens file/folder {string}', - async function (this: World, stepUser: string, resource: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, resource: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.openFolder(resource) } @@ -421,8 +435,8 @@ When( When( '{string} navigates to folder {string} via breadcrumb', - async function (this: World, stepUser: string, resource: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, resource: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.openFolderViaBreadcrumb(resource) } @@ -430,8 +444,8 @@ When( When( '{string} enables/disables the option to display the hidden file', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.showHiddenFiles() } @@ -439,12 +453,12 @@ When( When( '{string} switches to the {string} view', - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, viewMode: 'table' | 'tiles' | 'table-condensed' - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.switchViewMode(viewMode) } @@ -452,8 +466,8 @@ When( When( '{string} sees the resources displayed as {string}', - async function (this: World, stepUser: string, viewMode: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, viewMode: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.expectThatResourcesAreDisplayedAs(viewMode) } @@ -568,8 +582,8 @@ export const processDownload = async ( When( '{string} edits the following resource(s)', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) for (const info of stepTable.hashes()) { @@ -584,13 +598,13 @@ When( When( '{string} clicks the tag {string} on the resource {string}', - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, tagName: string, resourceName: string - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.clickTag({ resource: resourceName, tag: tagName.toLowerCase() }) } @@ -598,8 +612,13 @@ When( When( /^"([^"].*)" opens the following file(?:s)? in (mediaviewer|pdfviewer|texteditor|Collabora|OnlyOffice)$/, - async function (this: World, stepUser: string, actionType: string, stepTable: DataTable) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ( + { world }: { world: World }, + stepUser: string, + actionType: string, + stepTable: DataTable + ) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) for (const info of stepTable.hashes()) { @@ -618,8 +637,13 @@ When( Then( '{string} should see resource {string} of {string} in the mediaviewer controls', - async function (this: World, stepUser: string, currentIndex: string, totalCount: string) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ( + { world }: { world: World }, + stepUser: string, + currentIndex: string, + totalCount: string + ) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.checkMediaViewerCount({ currentIndex: parseInt(currentIndex), @@ -630,8 +654,8 @@ Then( Then( 'the following resource(s) should contain the following tag(s) in the files list for user {string}', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) for (const { resource, tags } of stepTable.hashes()) { const isVisible = await resourceObject.areTagsVisibleForResourceInFilesTable({ @@ -645,8 +669,8 @@ Then( Then( 'the following resource(s) should contain the following tag(s) in the details panel for user {string}', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) for (const { resource, tags } of stepTable.hashes()) { const isVisible = await resourceObject.areTagsVisibleForResourceInDetailsPanel({ @@ -660,8 +684,8 @@ Then( When( '{string} adds the following tag(s) for the following resource(s) using the sidebar panel', - async function (this: World, stepUser: string, stepTable: DataTable) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) for (const { resource, tags } of stepTable.hashes()) { await resourceObject.addTags({ @@ -674,8 +698,8 @@ When( When( '{string} removes the following tag(s) for the following resource(s) using the sidebar panel', - async function (this: World, stepUser: string, stepTable: DataTable) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) for (const { resource, tags } of stepTable.hashes()) { await resourceObject.removeTags({ @@ -688,14 +712,14 @@ When( When( /^"([^"].*)" creates a file from template file "([^"].*)" via "([^"].*)" using the (sidebar panel|context menu)$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, file: string, webOffice: string, via: string - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.createFileFromTemplate(file, webOffice, via) } @@ -703,8 +727,8 @@ When( When( '{string} opens template file {string} via {string} using the context menu', - async function (this: World, stepUser: any, file: any, webOffice: any): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: any, file: any, webOffice: any): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.openTemplateFile(file, webOffice) } @@ -712,14 +736,14 @@ When( When( '{string} creates space {string} from folder {string} using the context menu', - async function (this: World, stepUser: string, spaceName: string, folderName: string) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, spaceName: string, folderName: string) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) const space = await resourceObject.createSpaceFromFolder({ folderName: folderName, spaceName: spaceName }) - this.spacesEnvironment.createSpace({ + world.spacesEnvironment.createSpace({ key: space.name, space: { name: space.name, id: space.id } }) @@ -728,12 +752,17 @@ When( When( '{string} creates space {string} from resources using the context menu', - async function (this: World, stepUser: string, spaceName: string, stepTable: DataTable) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ( + { world }: { world: World }, + stepUser: string, + spaceName: string, + stepTable: DataTable + ) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) const resources = stepTable.hashes().map((item) => item.resource) const space = await resourceObject.createSpaceFromSelection({ resources, spaceName }) - this.spacesEnvironment.createSpace({ + world.spacesEnvironment.createSpace({ key: space.name, space: { name: space.name, id: space.id } }) @@ -742,11 +771,11 @@ When( When( '{string} creates space {string} from all resources using the context menu', - async function (this: World, stepUser: string, spaceName: string) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, spaceName: string) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) const space = await resourceObject.createSpaceFromAll({ spaceName }) - this.spacesEnvironment.createSpace({ + world.spacesEnvironment.createSpace({ key: space.name, space: { name: space.name, id: space.id } }) @@ -755,8 +784,8 @@ When( Then( '{string} should not see the version panel for the file(s)', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) const fileInfo = stepTable.hashes().reduce>((acc, stepRow) => { const { to, resource } = stepRow @@ -765,7 +794,7 @@ Then( acc[to] = [] } - acc[to].push(this.filesEnvironment.getFile({ name: resource })) + acc[to].push(world.filesEnvironment.getFile({ name: resource })) return acc }, {}) @@ -781,8 +810,8 @@ Then( When( '{string} navigates to page {string} of the personal/project space files view', - async function (this: World, stepUser: string, pageNumber: string) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, pageNumber: string) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.changePage({ pageNumber }) } @@ -790,8 +819,8 @@ When( When( '{string} changes the items per page to {string}', - async function (this: World, stepUser: string, itemsPerPage: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, itemsPerPage: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.changeItemsPerPage({ itemsPerPage }) } @@ -799,8 +828,8 @@ When( Then( '{string} should see the text {string} at the footer of the page', - async function (this: World, stepUser: string, expectedText: string) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, expectedText: string) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) const actualText = await resourceObject.getFileListFooterText() expect(actualText).toBe(expectedText) @@ -809,8 +838,8 @@ Then( Then( '{string} should see {int} resources in the personal/project space files view', - async function (this: World, stepUser: string, expectedNumberOfResources: number) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, expectedNumberOfResources: number) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) const actualNumberOfResources = await resourceObject.countNumberOfResourcesInThePage() expect(actualNumberOfResources).toBe(expectedNumberOfResources) @@ -819,8 +848,8 @@ Then( Then( '{string} should not see the pagination in the personal/project space files view', - async function (this: World, stepUser: string) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.expectPageNumberNotToBeVisible() } @@ -828,32 +857,32 @@ Then( When( '{string} uploads the following resource(s) via drag-n-drop', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) const resources = stepTable .hashes() - .map((item) => this.filesEnvironment.getFile({ name: item.resource })) + .map((item) => world.filesEnvironment.getFile({ name: item.resource })) await resourceObject.dropUpload({ resources }) } ) When( '{string} uploads {int} small files in personal space', - async function (this: World, stepUser: string, numberOfFiles: number): Promise { + async ({ world }: { world: World }, stepUser: string, numberOfFiles: number): Promise => { const files = [] for (let i = 0; i < numberOfFiles; i++) { const file = `file${i}.txt` runtimeFs.createFile(file, 'test content') files.push( - this.filesEnvironment.getFile({ - name: path.join(runtimeFs.getTempUploadPath().replace(config.assets, ''), file) + world.filesEnvironment.getFile({ + name: path.join(runtimeFs.getTempUploadPath().replace(appConfig.assetsPath, ''), file) }) ) } - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.uploadLargeNumberOfResources({ resources: files }) @@ -862,8 +891,8 @@ When( When( '{string} creates a shortcut for the following resource(s)', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) for (const info of stepTable.hashes()) { @@ -878,8 +907,8 @@ When( When( '{string} opens a shortcut {string}', - async function (this: World, stepUser: string, name: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, name: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.openShotcut({ name: name }) } @@ -887,8 +916,13 @@ When( Then( '{string} can open a shortcut {string} with external url {string}', - async function (this: World, stepUser: string, name: string, url: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ( + { world }: { world: World }, + stepUser: string, + name: string, + url: string + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.openShotcut({ name: name, url: url }) } @@ -896,22 +930,22 @@ Then( Then( /^for "([^"]*)" file "([^"]*)" (should|should not) be locked$/, - async function (this: World, stepUser: string, file: string, actionType: string) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, file: string, actionType: string) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) const lockLocator = resourceObject.getLockLocator({ resource: file }) actionType === 'should' ? await expect(lockLocator).toBeVisible() : // can take more than 5 seconds for lock to be released in case of OnlyOffice - await expect(lockLocator).not.toBeVisible({ timeout: config.timeout * 1000 }) + await expect(lockLocator).not.toBeVisible({ timeout: appConfig.timeout * 1000 }) } ) When( /^"([^"]*)" navigates to the (next|previous) media resource$/, - async function (this: World, stepUser: string, navigationType: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, navigationType: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.navigateMediaFile(navigationType) } @@ -919,8 +953,8 @@ When( When( '{string} opens a file {string} in the media-viewer using the sidebar panel', - async function (this: World, stepUser: any, file: any): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: any, file: any): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.previewMediaFromSidebarPanel(file) } @@ -928,13 +962,13 @@ When( Then( /^"([^"]*)" (should|should not) be able to edit (?:folder|file) "([^"]*)"$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, actionType: string, resource: string - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) const userCanEdit = await resourceObject.canManageResource({ resource }) expect(userCanEdit).toBe(actionType === 'should' ? true : false) @@ -943,14 +977,14 @@ Then( Then( /^"([^"]*)" (should|should not) see (link-direct|link-indirect|user-direct|user-indirect) indicator on the (?:folder|file) "([^"]*)"$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, actionType: string, buttonLabel: string, resource: string - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) const showShareIndicator = resourceObject.showShareIndicatorSelector({ buttonLabel, @@ -964,13 +998,13 @@ Then( Then( /^"([^"]*)" (should|should not) be able to edit content of following resources?$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, actionType: string, stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) for (const info of stepTable.hashes()) { @@ -982,14 +1016,14 @@ Then( Then( /^"([^"]*)" (should|should not) see following actions for (?:folder|file) "([^"]*)"$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, actionType: string, resource: string, stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) for (const info of stepTable.hashes()) { const actions = await resourceObject.getAllAvailableActions({ resource }) @@ -1004,14 +1038,14 @@ Then( Then( /^"([^"]*)" (should|should not) see (thumbnail and preview|preview) for file "([^"]*)"$/, - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, actionType: string, action: string, resource: string - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) if (actionType === 'should') { await resourceObject.getResourceLocator(resource).waitFor() @@ -1029,8 +1063,8 @@ Then( Then( '{string} should see activity of the following resource(s)', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) for (const info of stepTable.hashes()) { @@ -1041,8 +1075,8 @@ Then( Then( '{string} should not see any activity of the following resource(s)', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, stepTable: DataTable): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) for (const info of stepTable.hashes()) { @@ -1051,16 +1085,16 @@ Then( } ) -When('{string} selects all files', async function (this: World, stepUser: string) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) +When('{string} selects all files', async ({ world }: { world: World }, stepUser: string) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.selectAllFiles() }) Then( 'the download button should be disabled for user {string} with the tooltip:', - async function (this: World, stepUser: string, tooltip: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, tooltip: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) const downloadButton = await resourceObject.getDownloadButtonTooltip() expect(downloadButton).toBe(tooltip) @@ -1069,13 +1103,13 @@ Then( Then( '{string} should see {string} avatar for the resource {string}', - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, avatarType: 'sharer' | 'recipient', resource: string - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) const avatarLocator = await resourceObject.getAvatarLocator({ resource, avatarType }) await expect(avatarLocator).toBeVisible() @@ -1084,13 +1118,13 @@ Then( Then( '{string} should see {string} avatar for the resource {string} in the activity panel', - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, avatarUser: string, resource: string - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) const avatarLocator = await resourceObject.getAvatarLocatorFromActivityPanel({ resource, @@ -1102,13 +1136,18 @@ Then( When( '{string} opens file {string} via {string} using the context menu', - async function (this: World, stepUser: string, file: string, fileViewer: string): Promise { + async ( + { world }: { world: World }, + stepUser: string, + file: string, + fileViewer: string + ): Promise => { const allowedViewers = ['collabora', 'text-editor', 'preview'] as const if (!allowedViewers.includes(fileViewer as any)) { throw new Error(`Unsupported file viewer: ${fileViewer}`) } - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.openFileViaContextMenu(file, fileViewer as (typeof allowedViewers)[number]) @@ -1117,8 +1156,8 @@ When( When( '{string} uploads an image from the clipboard', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string) => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.uploadImageFromClipboard() } @@ -1126,8 +1165,8 @@ When( When( '{string} reduces the tile size', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.reduceTileSize() } @@ -1135,8 +1174,8 @@ When( When( '{string} opens the right sidebar of the resource {string}', - async function (this: World, stepUser: string, resource: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, resource: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.openRightSidebar(resource) } @@ -1144,8 +1183,13 @@ When( When( '{string} opens a {string} panel of the resource {string}', - async function (this: World, stepUser: string, panel: string, resource: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ( + { world }: { world: World }, + stepUser: string, + panel: string, + resource: string + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) await resourceObject.openResourcePanel(panel as PanelType, resource) } @@ -1153,13 +1197,13 @@ When( When( '{string} deletes and immediately undoes the following resource(s) using {string}', - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, method: 'keyboard' | 'undo button', stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) const resources = stepTable.hashes().map((row) => ({ name: row.resource @@ -1175,13 +1219,13 @@ When( When( '{string} marks the following resources as favorite using {string}', - async function ( - this: World, + async ( + { world }: { world: World }, stepUser: string, method: 'context menu' | 'sidebar panel' | 'batch action' | 'preview', stepTable: DataTable - ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + ): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const resourceObject = new objects.applicationFiles.Resource({ page }) const resources = stepTable.hashes().map((row) => row.resource) diff --git a/tests/e2e/cucumber/steps/ui/search.ts b/tests/e2e/steps/ui/search.ts similarity index 55% rename from tests/e2e/cucumber/steps/ui/search.ts rename to tests/e2e/steps/ui/search.ts index 2a5bff80d7..2bb8c3a90f 100644 --- a/tests/e2e/cucumber/steps/ui/search.ts +++ b/tests/e2e/steps/ui/search.ts @@ -1,12 +1,12 @@ -import { When, Then } from '@cucumber/cucumber' -import { World } from '../../environment' -import { objects } from '../../../support' +import { When, Then } from '../../environment/fixtures' +import { World } from '../../environment/world' +import { objects } from '../../support' import { expect } from '@playwright/test' Then( '{string} should see the message {string} on the search result', - async function (this: World, stepUser: string, message: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, message: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const searchObject = new objects.applicationFiles.Search({ page }) const actualMessage = await searchObject.getSearchResultMessage() expect(actualMessage).toBe(message) @@ -15,8 +15,8 @@ Then( When( '{string} selects tag {string} from the search result filter chip', - async function (this: World, stepUser: string, tag: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, tag: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const searchObject = new objects.applicationFiles.Search({ page }) await searchObject.selectTagFilter({ tag }) } @@ -24,32 +24,32 @@ When( When( /^"([^"]*)" (enable|disable)s the option to search title only?$/, - async function (this: World, stepUser: string, enableOrDisable: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, enableOrDisable: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const searchObject = new objects.applicationFiles.Search({ page }) await searchObject.toggleSearchTitleOnly({ enableOrDisable }) } ) When( '{string} selects mediaType {string} from the search result filter chip', - async function (this: World, stepUser: string, mediaType: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, mediaType: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const searchObject = new objects.applicationFiles.Search({ page }) await searchObject.selectMediaTypeFilter({ mediaType }) } ) When( '{string} selects lastModified {string} from the search result filter chip', - async function (this: World, stepUser: string, lastModified: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, lastModified: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const searchObject = new objects.applicationFiles.Search({ page }) await searchObject.selectlastModifiedFilter({ lastModified }) } ) When( /^"([^"].*)" clears (mediaType|tags|lastModified|fullText) filter$/, - async function (this: World, stepUser: string, filter: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string, filter: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const searchObject = new objects.applicationFiles.Search({ page }) await searchObject.clearFilter({ filter: filter as 'mediaType' | 'tags' | 'lastModified' | 'fullText' @@ -59,8 +59,8 @@ When( When( '{string} opens location search panel', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const searchObject = new objects.applicationFiles.Search({ page }) await searchObject.openLocationSearchPanel() } diff --git a/tests/e2e/steps/ui/session.ts b/tests/e2e/steps/ui/session.ts new file mode 100644 index 0000000000..981f886e82 --- /dev/null +++ b/tests/e2e/steps/ui/session.ts @@ -0,0 +1,128 @@ +import { Given, When, Then } from '../../environment/fixtures' +import { World } from '../../environment/world' +import { appConfig } from '../../playwright.config' +import { objects } from '../../support' +import { listenSSE } from '../../support/environment/sse' +import { expect } from '@playwright/test' + +async function createNewSession(world: World, stepUser: string) { + const { page } = await world.actorsEnvironment.createActor({ + key: stepUser, + namespace: world.actorsEnvironment.generateNamespace(stepUser) + }) + return new objects.runtime.Session({ page }) +} + +async function LogInUser({ world }: { world: World }, stepUser: string): Promise { + const sessionObject = await createNewSession(world, stepUser) + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) + + const user = + stepUser === 'Admin' + ? world.usersEnvironment.getUser({ key: stepUser }) + : world.usersEnvironment.getCreatedUser({ key: stepUser }) + + await page.goto(appConfig.baseUrl) + await sessionObject.login(user, world.a11yEnabled) + + if (world.tags?.includes('@sse')) { + void listenSSE(appConfig.baseUrl, user) + } + + await page.locator('#web-content').waitFor() +} + +When('{string} logs in', LogInUser) + +async function LogOutUser({ world }: { world: World }, stepUser: string): Promise { + const actor = world.actorsEnvironment.getActor({ key: stepUser }) + const canLogout = !!(await actor.page.locator('#_userMenuButton').count()) + + const sessionObject = new objects.runtime.Session({ page: actor.page }) + canLogout && (await sessionObject.logout()) + await actor.close() +} + +When('{string} logs out', LogOutUser) + +Then('{string} fails to log in', async ({ world }: { world: World }, stepUser: string) => { + const sessionObject = await createNewSession(world, stepUser) + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) + const user = world.usersEnvironment.getCreatedUser({ key: stepUser }) + + await page.goto(appConfig.baseUrl) + await sessionObject.signIn(user.username, user.password) + + const errorLocator = appConfig.keycloak + ? page.locator('.kc-feedback-text', { + hasText: 'Account is disabled, contact your administrator.' + }) + : page.locator('#oc-login-error-message') + + await expect(errorLocator).toBeVisible() +}) + +When( + /^"([^"]*)" waits for token renewal via (iframe|refresh token)$/, + async function ( + { world }: { world: World }, + stepUser: string, + renewalType: string + ): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) + const application = new objects.runtime.Application({ page }) + + if (renewalType === 'iframe') { + await application.waitForTokenRenewalViaIframe() + } else { + await application.waitForTokenRenewalViaRefreshToken() + } + } +) + +When( + '{string} waits for token to expire', + async function ({ world }: { world: World }, stepUser: string): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) + // wait for the token to expire + await page.waitForTimeout(appConfig.tokenTimeout * 1000) + } +) + +When( + '{string} navigates to new tab', + async function ({ world }: { world: World }, stepUser: string): Promise { + const actor = world.actorsEnvironment.getActor({ key: stepUser }) + await actor.newTab() + } +) + +When( + '{string} closes the current tab', + async function ({ world }: { world: World }, stepUser: string): Promise { + const actor = world.actorsEnvironment.getActor({ key: stepUser }) + await actor.closeCurrentTab() + } +) + +Given('using {string} server', function ({ world }: { world: World }, server: string): void { + switch (server) { + case 'LOCAL': + appConfig.federatedServer = false + break + case 'FEDERATED': + appConfig.federatedServer = true + break + default: + throw new Error(`Invalid server type: ${server}\nUse one of these: [LOCAL, FEDERATED]`) + } +}) + +Then( + '{string} should be logged out', + async ({ world }: { world: World }, stepUser: string): Promise => { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) + await expect(page.locator('#web-content')).toBeHidden() + await expect(page.locator('#exitAnchor')).toBeVisible() + } +) diff --git a/tests/e2e/cucumber/steps/ui/shares.ts b/tests/e2e/steps/ui/shares.ts similarity index 66% rename from tests/e2e/cucumber/steps/ui/shares.ts rename to tests/e2e/steps/ui/shares.ts index a943afd55b..971556df09 100644 --- a/tests/e2e/cucumber/steps/ui/shares.ts +++ b/tests/e2e/steps/ui/shares.ts @@ -1,12 +1,10 @@ -import { DataTable, Then, When } from '@cucumber/cucumber' +import { When, Then } from '../../environment/fixtures' +import { DataTable } from 'playwright-bdd' import { expect } from '@playwright/test' -import { World } from '../../environment' -import { environment, objects } from '../../../support' -import { - CollaboratorType, - ICollaborator -} from '../../../support/objects/app-files/share/collaborator' -import { ActionViaType } from '../../../support/objects/app-files/share/actions' +import { World } from '../../environment/world' +import { environment, objects } from '../../support' +import { CollaboratorType, ICollaborator } from '../../support/objects/app-files/share/collaborator' +import { ActionViaType } from '../../support/objects/app-files/share/actions' const parseShareTable = function ( stepTable: DataTable, @@ -39,10 +37,15 @@ const parseShareTable = function ( When( /^"([^"]*)" shares the following resource(?:s)? using the (sidebar panel|quick action|direct url navigation)$/, - async function (this: World, stepUser: string, actionType: string, stepTable: DataTable) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ( + { world }: { world: World }, + stepUser: string, + actionType: string, + stepTable: DataTable + ) { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const shareObject = new objects.applicationFiles.Share({ page }) - const shareInfo = parseShareTable(stepTable, this.usersEnvironment) + const shareInfo = parseShareTable(stepTable, world.usersEnvironment) let via: ActionViaType switch (actionType) { @@ -71,8 +74,8 @@ When( When( '{string} enables the sync for the following share(s)', - async function (this: World, stepUser: string, stepTable: DataTable) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: string, stepTable: DataTable) { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const shareObject = new objects.applicationFiles.Share({ page }) for (const info of stepTable.hashes()) { @@ -83,10 +86,10 @@ When( When( '{string} updates following sharee role(s)', - async function (this: World, stepUser: string, stepTable: DataTable) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: string, stepTable: DataTable) { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const shareObject = new objects.applicationFiles.Share({ page }) - const shareInfo = parseShareTable(stepTable, this.usersEnvironment) + const shareInfo = parseShareTable(stepTable, world.usersEnvironment) for (const resource of Object.keys(shareInfo)) { await shareObject.changeShareeRole({ @@ -99,10 +102,10 @@ When( When( '{string} removes following sharee(s)', - async function (this: World, stepUser: string, stepTable: DataTable) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: string, stepTable: DataTable) { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const shareObject = new objects.applicationFiles.Share({ page }) - const shareInfo = parseShareTable(stepTable, this.usersEnvironment) + const shareInfo = parseShareTable(stepTable, world.usersEnvironment) for (const resource of Object.keys(shareInfo)) { await shareObject.removeSharee({ resource, recipients: shareInfo[resource] }) @@ -112,10 +115,10 @@ When( Then( '{string} should see the following recipient(s)', - async function (this: World, stepUser: string, stepTable: DataTable) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: string, stepTable: DataTable) { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const shareObject = new objects.applicationFiles.Share({ page }) - const shareInfo = parseShareTable(stepTable, this.usersEnvironment) + const shareInfo = parseShareTable(stepTable, world.usersEnvironment) for (const resource of Object.keys(shareInfo)) { await shareObject.checkSharee({ resource, recipients: shareInfo[resource] }) @@ -125,8 +128,8 @@ Then( When( '{string} navigates to the shared with me page', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: string): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.applicationFiles.page.shares.WithMe({ page }) await pageObject.navigate() } @@ -134,8 +137,8 @@ When( When( '{string} navigates to the shared with others page', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: string): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.applicationFiles.page.shares.WithOthers({ page }) await pageObject.navigate() } @@ -143,8 +146,8 @@ When( When( '{string} navigates to the shared via link page', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: string): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.applicationFiles.page.shares.ViaLink({ page }) await pageObject.navigate() } @@ -152,8 +155,12 @@ When( When( '{string} disables the sync for the following share(s)', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ( + { world }: { world: World }, + stepUser: string, + stepTable: DataTable + ): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const shareObject = new objects.applicationFiles.Share({ page }) for (const resource of stepTable.hashes()) { @@ -164,8 +171,12 @@ When( When( '{string} enables the sync for the following share(s) using the context menu', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ( + { world }: { world: World }, + stepUser: string, + stepTable: DataTable + ): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const shareObject = new objects.applicationFiles.Share({ page }) for (const resource of stepTable.hashes()) { @@ -176,8 +187,8 @@ When( When( '{string} enables the sync for all shares using the batch actions', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: string): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const shareObject = new objects.applicationFiles.Share({ page }) await shareObject.syncAll() } @@ -185,8 +196,12 @@ When( When( '{string} disables the sync for the following share(s) using the context menu', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ( + { world }: { world: World }, + stepUser: string, + stepTable: DataTable + ): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const shareObject = new objects.applicationFiles.Share({ page }) for (const resource of stepTable.hashes()) { @@ -198,13 +213,13 @@ When( When( /"([^"]*)" (should|should not) see a sync status for the (?:folder|file) "([^"]*)"?$/, async function ( - this: World, + { world }: { world: World }, stepUser: string, condition: string, resource: string ): Promise { const shouldSee = condition === 'should' - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const shareObject = new objects.applicationFiles.Share({ page }) expect(await shareObject.resourceIsSynced(resource)).toBe(shouldSee) } @@ -213,13 +228,13 @@ When( Then( /"([^"]*)" (should|should not) be able to see the following shares$/, async function ( - this: World, + { world }: { world: World }, stepUser: string, condition: string, stepTable: DataTable ): Promise { const shouldExist = condition === 'should' - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const shareObject = new objects.applicationFiles.Share({ page }) for (const { resource, owner } of stepTable.hashes()) { const isAcceptedSharePresent = await shareObject.isAcceptedSharePresent(resource, owner) @@ -233,22 +248,22 @@ Then( When( /^"([^"]*)" sets the expiration date of share "([^"]*)" of (group|user) "([^"]*)" to "([^"]*)"?$/, async function ( - this: World, + { world }: { world: World }, stepUser: string, resource: string, collaboratorType: 'user' | 'group', collaboratorName: string, expirationDate: string ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const shareObject = new objects.applicationFiles.Share({ page }) await shareObject.addExpirationDate({ resource, collaborator: { collaborator: collaboratorType === 'group' - ? this.usersEnvironment.getCreatedGroup({ key: collaboratorName }) - : this.usersEnvironment.getCreatedUser({ key: collaboratorName }), + ? world.usersEnvironment.getCreatedGroup({ key: collaboratorName }) + : world.usersEnvironment.getCreatedUser({ key: collaboratorName }), type: collaboratorType } as ICollaborator, expirationDate @@ -259,14 +274,14 @@ When( When( /^"([^"]*)" checks the following access details of share "([^"]*)" for (user|group) "([^"]*)"$/, async function ( - this: World, + { world }: { world: World }, stepUser: string, resource: string, collaboratorType: string, collaboratorName: string, stepTable: DataTable ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const shareObject = new objects.applicationFiles.Share({ page }) const expectedDetails = stepTable.rowsHash() const actualDetails = await shareObject.getAccessDetails({ @@ -274,8 +289,8 @@ When( collaborator: { collaborator: collaboratorType === 'group' - ? this.usersEnvironment.getCreatedGroup({ key: collaboratorName }) - : this.usersEnvironment.getCreatedUser({ key: collaboratorName }), + ? world.usersEnvironment.getCreatedGroup({ key: collaboratorName }) + : world.usersEnvironment.getCreatedUser({ key: collaboratorName }), type: collaboratorType } as ICollaborator }) @@ -289,8 +304,8 @@ When( Then( '{string} should see the message {string} on the webUI', - async function (this: World, stepUser: string, message: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: string, message: string): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const shareObject = new objects.applicationFiles.Share({ page }) const actualMessage = await shareObject.getMessage() expect(actualMessage).toBe(message) @@ -300,19 +315,19 @@ Then( Then( /^"([^"]*)" (should|should not) be able to manage share of a file "([^"]*)" for user "([^"]*)"$/, async function ( - this: World, + { world }: { world: World }, stepUser: any, actionType: string, resource: string, recipient: string ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const shareObject = new objects.applicationFiles.Share({ page }) const changeRole = shareObject.changeRoleLocator( - this.usersEnvironment.getCreatedUser({ key: recipient }) + world.usersEnvironment.getCreatedUser({ key: recipient }) ) const changeShare = shareObject.changeShareLocator( - this.usersEnvironment.getCreatedUser({ key: recipient }) + world.usersEnvironment.getCreatedUser({ key: recipient }) ) await shareObject.openSharingPanel(resource) diff --git a/tests/e2e/cucumber/steps/ui/spaces.ts b/tests/e2e/steps/ui/spaces.ts similarity index 63% rename from tests/e2e/cucumber/steps/ui/spaces.ts rename to tests/e2e/steps/ui/spaces.ts index dfa68872e8..4cb04d903b 100644 --- a/tests/e2e/cucumber/steps/ui/spaces.ts +++ b/tests/e2e/steps/ui/spaces.ts @@ -1,13 +1,14 @@ -import { DataTable, When, Then } from '@cucumber/cucumber' +import { When, Then } from '../../environment/fixtures' +import { DataTable } from 'playwright-bdd' import { expect } from '@playwright/test' -import { World } from '../../environment' -import { objects } from '../../../support' -import { Space } from '../../../support/types' +import { World } from '../../environment/world' +import { objects } from '../../support' +import { Space } from '../../support/types' When( '{string} navigates to the personal space page', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: string): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.applicationFiles.page.spaces.Personal({ page }) await pageObject.navigate() } @@ -15,8 +16,8 @@ When( When( '{string} navigates to the projects space page', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: string): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.applicationFiles.page.spaces.Projects({ page }) await pageObject.navigate() } @@ -24,8 +25,12 @@ When( When( '{string} creates the following project spaces', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ( + { world }: { world: World }, + stepUser: string, + stepTable: DataTable + ): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationFiles.Spaces({ page }) for (const space of stepTable.hashes()) { @@ -36,8 +41,8 @@ When( When( '{string} navigates to the project space {string}', - async function (this: World, stepUser: string, key: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: string, key: string): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationFiles.Spaces({ page }) const pageObject = new objects.applicationFiles.page.spaces.Projects({ page }) await pageObject.navigate() @@ -48,13 +53,13 @@ When( When( /^"([^"]*)" (?:changes|updates) the space "([^"]*)" (name|subtitle|description|quota|image|icon) to "([^"]*)"$/, async function ( - this: World, + { world }: { world: World }, stepUser: string, key: string, attribute: string, value: string ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationFiles.Spaces({ page }) switch (attribute) { @@ -73,7 +78,7 @@ When( case 'image': await spacesObject.changeSpaceImage({ key, - resource: this.filesEnvironment.getFile({ name: value }) + resource: world.filesEnvironment.getFile({ name: value }) }) break case 'icon': @@ -88,13 +93,13 @@ When( When( /^"([^"]*)" changes the space "([^"]*)" (name|subtitle|description|quota|image|icon) to "([^"]*)" using context menu$/, async function ( - this: World, + { world }: { world: World }, stepUser: string, key: string, attribute: string, value: string ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationFiles.Spaces({ page }) switch (attribute) { @@ -113,7 +118,7 @@ When( case 'image': await spacesObject.changeSpaceImage({ key, - resource: this.filesEnvironment.getFile({ name: value }), + resource: world.filesEnvironment.getFile({ name: value }), contextMenu: true }) break @@ -128,8 +133,8 @@ When( When( '{string} deletes the space {string} image using context menu', - async function (this: World, stepUser: string, space: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: string, space: string): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationFiles.Spaces({ page }) await spacesObject.deleteSpaceImage({ space, contextMenu: true }) } @@ -137,8 +142,8 @@ When( When( '{string} deletes the space {string} image', - async function (this: World, stepUser: string, space: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: string, space: string): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationFiles.Spaces({ page }) await spacesObject.deleteSpaceImage({ space }) } @@ -146,14 +151,18 @@ When( When( '{string} adds following user(s) to the project space', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ( + { world }: { world: World }, + stepUser: string, + stepTable: DataTable + ): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationFiles.Spaces({ page }) for (const { user, role, kind } of stepTable.hashes()) { const collaborator = kind === 'user' - ? this.usersEnvironment.getCreatedUser({ key: user }) - : this.usersEnvironment.getCreatedGroup({ key: user }) + ? world.usersEnvironment.getCreatedUser({ key: user }) + : world.usersEnvironment.getCreatedGroup({ key: user }) const collaboratorWithRole = { collaborator, role @@ -165,12 +174,16 @@ When( When( '{string} removes access to following users from the project space', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ( + { world }: { world: World }, + stepUser: string, + stepTable: DataTable + ): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationFiles.Spaces({ page }) for (const { user, role } of stepTable.hashes()) { const member = { - collaborator: this.usersEnvironment.getCreatedUser({ key: user }), + collaborator: world.usersEnvironment.getCreatedUser({ key: user }), role } await spacesObject.removeAccessToMember({ users: [member] }) @@ -180,8 +193,13 @@ When( Then( /^"([^"]*)" (should|should not) see space "([^"]*)"$/, - async function (this: World, stepUser: string, actionType: string, space: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ( + { world }: { world: World }, + stepUser: string, + actionType: string, + space: string + ): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationFiles.Spaces({ page }) const spaceLocator = spacesObject.getSpaceLocator(space) actionType === 'should' @@ -192,12 +210,16 @@ Then( When( '{string} changes the roles of the following users in the project space', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ( + { world }: { world: World }, + stepUser: string, + stepTable: DataTable + ): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationFiles.Spaces({ page }) for (const { user, role } of stepTable.hashes()) { const member = { - collaborator: this.usersEnvironment.getCreatedUser({ key: user }), + collaborator: world.usersEnvironment.getCreatedUser({ key: user }), role } await spacesObject.changeRoles({ users: [member] }) @@ -207,13 +229,13 @@ When( When( '{string} as project manager removes their own access to the project space', - async function (this: World, stepUser: any): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: any): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationFiles.Spaces({ page }) await spacesObject.removeAccessToMember({ users: [ { - collaborator: this.usersEnvironment.getCreatedUser({ key: stepUser }) + collaborator: world.usersEnvironment.getCreatedUser({ key: stepUser }) } ], removeOwnSpaceAccess: true @@ -224,32 +246,36 @@ When( When( '{string} sets the expiration date of the member {string} of the project space to {string}', async function ( - this: World, + { world }: { world: World }, stepUser: string, memberName: string, expirationDate: string ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationFiles.Spaces({ page }) - const member = { collaborator: this.usersEnvironment.getCreatedUser({ key: memberName }) } + const member = { collaborator: world.usersEnvironment.getCreatedUser({ key: memberName }) } await spacesObject.addExpirationDate({ member, expirationDate }) } ) When( '{string} removes the expiration date of the member {string} of the project space', - async function (this: World, stepUser: string, memberName: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ( + { world }: { world: World }, + stepUser: string, + memberName: string + ): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationFiles.Spaces({ page }) - const member = { collaborator: this.usersEnvironment.getCreatedUser({ key: memberName }) } + const member = { collaborator: world.usersEnvironment.getCreatedUser({ key: memberName }) } await spacesObject.removeExpirationDate({ member }) } ) When( /^"([^"]*)" downloads the space (?:"[^"]*")$/, - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: string): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationFiles.Spaces({ page }) const downloadedResource = await spacesObject.downloadSpace() expect(downloadedResource).toContain('download.zip') @@ -258,8 +284,12 @@ When( Then( '{string} should see activity of the space', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ( + { world }: { world: World }, + stepUser: string, + stepTable: DataTable + ): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationFiles.Spaces({ page }) for (const info of stepTable.hashes()) { @@ -270,8 +300,12 @@ Then( Then( '{string} should see activities of the space mathing the following regex', - async function (this: World, stepUser: string, stepTable: DataTable): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ( + { world }: { world: World }, + stepUser: string, + stepTable: DataTable + ): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationFiles.Spaces({ page }) for (const info of stepTable.hashes()) { @@ -283,12 +317,12 @@ Then( Then( 'space image should match {int}\\/{int} ratio for user {string}', async function ( - this: World, + { world }: { world: World }, expectedWidth: number, expectedHeight: number, stepUser: string ): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationFiles.Spaces({ page }) const { width, height } = await spacesObject.getSpaceImageRatio() diff --git a/tests/e2e/cucumber/steps/ui/trashbin.ts b/tests/e2e/steps/ui/trashbin.ts similarity index 55% rename from tests/e2e/cucumber/steps/ui/trashbin.ts rename to tests/e2e/steps/ui/trashbin.ts index 4df56398c8..d1995d9c7a 100644 --- a/tests/e2e/cucumber/steps/ui/trashbin.ts +++ b/tests/e2e/steps/ui/trashbin.ts @@ -1,12 +1,12 @@ -import { When, Then } from '@cucumber/cucumber' -import { World } from '../../environment' -import { objects } from '../../../support' +import { When, Then } from '../../environment/fixtures' +import { World } from '../../environment/world' +import { objects } from '../../support' import { expect } from '@playwright/test' When( '{string} enables/disables the option to show empty trashbins', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: string): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const trashbinObject = new objects.applicationFiles.Trashbin({ page }) await trashbinObject.showEmptyTrashbins() } @@ -14,8 +14,8 @@ When( Then( '{string} should see disabled empty trashbin button for space {string}', - async function (this: World, stepUser: string, space: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: string, space: string): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const trashbinObject = new objects.applicationFiles.Trashbin({ page }) const emptyTrashbinBtn = await trashbinObject.getEmptyTrashbinLocator(space) await expect(emptyTrashbinBtn).toBeDisabled() @@ -24,8 +24,8 @@ Then( When( '{string} empties the trashbin for space {string} using quick action', - async function (this: World, stepUser: string, space: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: string, space: string): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const trashbinObject = new objects.applicationFiles.Trashbin({ page }) await trashbinObject.emptyTrashbinUsingQuickAction(space) } @@ -33,8 +33,8 @@ When( Then( '{string} should see the text {string} at the footer of the trashbin page', - async function (this: World, stepUser: string, expectedText: string) { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: string, expectedText: string) { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const trashbinObject = new objects.applicationFiles.Trashbin({ page }) const actualText = await trashbinObject.getTrashbinListFooterText() expect(actualText).toContain(expectedText) @@ -43,8 +43,8 @@ Then( When( '{string} navigates to the trashbin', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: string): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.applicationFiles.page.trashbin.Overview({ page }) await pageObject.navigate() } @@ -52,8 +52,8 @@ When( When( '{string} opens trashbin of the project space {string}', - async function (this: World, stepUser: string, key: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: string, key: string): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const trashbinObject = new objects.applicationFiles.Trashbin({ page }) await trashbinObject.openTrashbinOfProjectSpace(key) } @@ -61,8 +61,8 @@ When( When( '{string} opens trashbin of the personal space', - async function (this: World, stepUser: string): Promise { - const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + async function ({ world }: { world: World }, stepUser: string): Promise { + const { page } = world.actorsEnvironment.getActor({ key: stepUser }) const trashbinObject = new objects.applicationFiles.Trashbin({ page }) await trashbinObject.openTrashbinOfPersonalSpace() } diff --git a/tests/e2e/support/api/davSpaces/spaces.ts b/tests/e2e/support/api/davSpaces/spaces.ts index c3d6d9aadc..e8cf4c8cca 100644 --- a/tests/e2e/support/api/davSpaces/spaces.ts +++ b/tests/e2e/support/api/davSpaces/spaces.ts @@ -3,7 +3,7 @@ import { User } from '../../types' import { urlJoin } from '../../utils/urlJoin' import { XMLParser } from 'fast-xml-parser' import { getSpaceIdBySpaceName } from '../graph' -import _ from 'lodash-es/object' +import _ from 'lodash-es/object.js' import { createTagsForResource } from '../graph/utils' export const folderExists = async ({ diff --git a/tests/e2e/support/api/graph/userManagement.ts b/tests/e2e/support/api/graph/userManagement.ts index ed9a9a8467..bd4c0c492f 100644 --- a/tests/e2e/support/api/graph/userManagement.ts +++ b/tests/e2e/support/api/graph/userManagement.ts @@ -1,7 +1,7 @@ import { checkResponseStatus, request } from '../http' import { Group, Me, User } from '../../types' import { urlJoin } from '../../utils/urlJoin' -import { config } from '../../../config' +import { appConfig } from '../../../playwright.config' import { getApplicationEntity } from './utils' import { userRoleStore } from '../../store' import { UsersEnvironment } from '../../environment' @@ -124,7 +124,7 @@ export const addUserToGroup = async ({ admin: User }): Promise => { const body = { - '@odata.id': urlJoin(config.baseUrl, 'graph', 'v1.0', 'users', userId) + '@odata.id': urlJoin(appConfig.baseUrl, 'graph', 'v1.0', 'users', userId) } const response = await request({ diff --git a/tests/e2e/support/api/http.ts b/tests/e2e/support/api/http.ts index 1f0e735fe5..955b0ee7e4 100644 --- a/tests/e2e/support/api/http.ts +++ b/tests/e2e/support/api/http.ts @@ -1,7 +1,7 @@ import { urlJoin } from '../utils/urlJoin' import { APIResponse, request as apiRequest } from '@playwright/test' import { User } from '../types' -import { config } from '../../config' +import { appConfig } from '../../playwright.config' import { TokenEnvironmentFactory } from '../environment' export const getAuthHeader = (user: User, isKeycloakRequest: boolean = false) => { @@ -10,7 +10,7 @@ export const getAuthHeader = (user: User, isKeycloakRequest: boolean = false) => Authorization: 'Basic ' + Buffer.from(user.id + ':' + user.password).toString('base64') } - if (!config.basicAuth) { + if (!appConfig.basicAuth) { authHeader.Authorization = 'Bearer ' + tokenEnvironment.getToken({ user }).accessToken } return authHeader @@ -40,7 +40,7 @@ export const request = async ({ ...header } - const baseUrl = isKeycloakRequest ? config.keycloakUrl : config.baseUrl + const baseUrl = isKeycloakRequest ? appConfig.keycloakUrl : appConfig.baseUrl return await context.fetch(urlJoin(baseUrl, path), { method, diff --git a/tests/e2e/support/api/keycloak/openCloudUserToken.ts b/tests/e2e/support/api/keycloak/openCloudUserToken.ts index dd3ed484fc..b57a8fa956 100644 --- a/tests/e2e/support/api/keycloak/openCloudUserToken.ts +++ b/tests/e2e/support/api/keycloak/openCloudUserToken.ts @@ -1,5 +1,5 @@ import { TokenEnvironmentFactory } from '../../environment' -import { config } from '../../../config' +import { appConfig } from '../../../playwright.config' import { User } from '../../types' import { request, APIRequestContext } from '@playwright/test' import { getKeycloakAdminUser } from './utils' @@ -9,10 +9,11 @@ interface openCloudTokenForKeycloak { refresh_token: string } -const authorizationEndpoint = config.keycloakUrl + '/realms/openCloud/protocol/openid-connect/auth' -const tokenEndpoint = config.keycloakUrl + '/realms/openCloud/protocol/openid-connect/token' -const redirectUrl = config.baseUrl + '/oidc-callback.html' -const tokenMasterEndpoint = config.keycloakUrl + '/realms/master/protocol/openid-connect/token' +const authorizationEndpoint = + appConfig.keycloakUrl + '/realms/openCloud/protocol/openid-connect/auth' +const tokenEndpoint = appConfig.keycloakUrl + '/realms/openCloud/protocol/openid-connect/token' +const redirectUrl = appConfig.baseUrl + '/oidc-callback.html' +const tokenMasterEndpoint = appConfig.keycloakUrl + '/realms/master/protocol/openid-connect/token' async function getAuthorizationEndPoint(context: APIRequestContext): Promise { const loginParams = { diff --git a/tests/e2e/support/api/keycloak/user.ts b/tests/e2e/support/api/keycloak/user.ts index 2360c7b4b4..1acee04f22 100644 --- a/tests/e2e/support/api/keycloak/user.ts +++ b/tests/e2e/support/api/keycloak/user.ts @@ -5,7 +5,7 @@ import { checkResponseStatus } from '../http' import { User, KeycloakRealmRole } from '../../types' import { UsersEnvironment } from '../../environment' import { keycloakRealmRoles } from '../../store' -import { state } from '../../../cucumber/environment/shared' +import { state } from '../../../environment/shared' import { initializeUser } from '../../utils/tokenHelper' import { setAccessTokenForKeycloakOpenCloudUser } from './openCloudUserToken' import { getKeycloakAdminUser } from './utils' diff --git a/tests/e2e/support/api/keycloak/utils.ts b/tests/e2e/support/api/keycloak/utils.ts index fd5ee74908..88968281a8 100644 --- a/tests/e2e/support/api/keycloak/utils.ts +++ b/tests/e2e/support/api/keycloak/utils.ts @@ -1,9 +1,9 @@ import { APIResponse } from '@playwright/test' import { request as httpRequest } from '../http' import { User } from '../../types' -import { config } from '../../../config' +import { appConfig } from '../../../playwright.config' -export const realmBasePath = `admin/realms/${config.keycloakRealm}` +export const realmBasePath = `admin/realms/${appConfig.keycloakRealm}` export const request = async (args: { method: 'POST' | 'DELETE' | 'PUT' | 'GET' | 'MKCOL' | 'PROPFIND' | 'PATCH' @@ -21,10 +21,10 @@ export const getUserIdFromResponse = (response: APIResponse): string => { export const getKeycloakAdminUser = () => { return { - id: config.keycloakAdminUser, - username: config.keycloakAdminUser, - displayName: config.keycloakAdminUser, - password: config.keycloakAdminPassword, - email: `${config.keycloakAdminUser}@mail.test` + id: appConfig.keycloakAdminUser, + username: appConfig.keycloakAdminUser, + displayName: appConfig.keycloakAdminUser, + password: appConfig.keycloakAdminPassword, + email: `${appConfig.keycloakAdminUser}@mail.test` } } diff --git a/tests/e2e/support/api/token/utils.ts b/tests/e2e/support/api/token/utils.ts index ae5001a3ac..53e710d9c3 100644 --- a/tests/e2e/support/api/token/utils.ts +++ b/tests/e2e/support/api/token/utils.ts @@ -1,5 +1,5 @@ import { TokenEnvironmentFactory } from '../../environment' -import { config } from '../../../config' +import { appConfig } from '../../../playwright.config' import { request, APIRequestContext } from '@playwright/test' import { User } from '../../types' @@ -25,10 +25,10 @@ export const setAccessAndRefreshToken = async (user: User) => { } const getAuthorizedEndPoint = async (context: APIRequestContext, user: User): Promise => { - const logonResponse = await context.post(config.baseUrl + logonUrl, { + const logonResponse = await context.post(appConfig.baseUrl + logonUrl, { headers: { 'Kopano-Konnect-XSRF': '1', - Referer: config.baseUrl, + Referer: appConfig.baseUrl, 'Content-Type': 'application/json' }, data: { @@ -36,7 +36,7 @@ const getAuthorizedEndPoint = async (context: APIRequestContext, user: User): Pr hello: { scope: 'openid profile email', client_id: 'web', - redirect_uri: config.baseUrl + redirectUrl, + redirect_uri: appConfig.baseUrl + redirectUrl, flow: 'oidc' } } @@ -55,7 +55,7 @@ const getCode = async (context: APIRequestContext, continueUrl: string): Promise const params = new URLSearchParams({ client_id: 'web', prompt: 'none', - redirect_uri: config.baseUrl + redirectUrl, + redirect_uri: appConfig.baseUrl + redirectUrl, response_mode: 'query', response_type: 'code', scope: 'openid profile offline_access email' @@ -84,11 +84,11 @@ interface Token { } const getToken = async (context: APIRequestContext, code: string): Promise => { - const response = await context.post(config.baseUrl + tokenUrl, { + const response = await context.post(appConfig.baseUrl + tokenUrl, { form: { client_id: 'web', code: code, - redirect_uri: config.baseUrl + redirectUrl, + redirect_uri: appConfig.baseUrl + redirectUrl, grant_type: 'authorization_code' } }) diff --git a/tests/e2e/support/environment/actor/actor.ts b/tests/e2e/support/environment/actor/actor.ts index 4f3ad5b754..7e389fd3fa 100644 --- a/tests/e2e/support/environment/actor/actor.ts +++ b/tests/e2e/support/environment/actor/actor.ts @@ -1,8 +1,7 @@ import { BrowserContext, Page, expect } from '@playwright/test' -import path from 'path' import EventEmitter from 'events' import { Actor } from '../../types' -import { ActorOptions, buildBrowserContextOptions } from './shared' +import { ActorOptions } from './shared' export class ActorEnvironment extends EventEmitter implements Actor { private readonly options: ActorOptions @@ -16,16 +15,12 @@ export class ActorEnvironment extends EventEmitter implements Actor { } async setup(): Promise { - this.context = await this.options.browser.newContext(buildBrowserContextOptions(this.options)) + this.context = await this.options.browser.newContext() await this.context.addInitScript(() => { ;(window as any).__E2E__ = true }) - if (this.options.context.reportTracing) { - await this.context.tracing.start({ screenshots: true, snapshots: true, sources: true }) - } - this.page = await this.context.newPage() this.tabs.push(this.page) @@ -74,12 +69,6 @@ export class ActorEnvironment extends EventEmitter implements Actor { } async close(): Promise { - if (this.options.context.reportTracing) { - await this.context?.tracing.stop({ - path: path.join(this.options.context.tracingReportDir, `${this.options.namespace}.zip`) - }) - } - await this.page?.close() await this.context?.close() diff --git a/tests/e2e/support/environment/actor/actors.ts b/tests/e2e/support/environment/actor/actors.ts index ff5f81be1c..5b865786fd 100644 --- a/tests/e2e/support/environment/actor/actors.ts +++ b/tests/e2e/support/environment/actor/actors.ts @@ -43,7 +43,7 @@ export class ActorsEnvironment extends EventEmitter { await Promise.all([...actorStore.values()].map((actor) => actor.close())) } - public generateNamespace(scenarioTitle: string, user: string): string { - return kebabCase([scenarioTitle, user, DateTime.now().toFormat('yyyy-M-d-hh-mm-ss')].join('-')) + public generateNamespace(user: string): string { + return kebabCase([user, DateTime.now().toFormat('yyyy-M-d-hh-mm-ss')].join('-')) } } diff --git a/tests/e2e/support/environment/actor/shared.ts b/tests/e2e/support/environment/actor/shared.ts index 6227eeecac..8dd53a8fc3 100644 --- a/tests/e2e/support/environment/actor/shared.ts +++ b/tests/e2e/support/environment/actor/shared.ts @@ -1,16 +1,9 @@ -import { Browser, BrowserContextOptions, devices } from '@playwright/test' -import path from 'path' -import { config } from '../../../config' +import { Browser } from '@playwright/test' export interface ActorsOptions { browser: Browser context: { acceptDownloads: boolean - reportDir: string - tracingReportDir: string - reportVideo: boolean - reportHar: boolean - reportTracing: boolean failOnUncaughtConsoleError: boolean } } @@ -19,70 +12,3 @@ export interface ActorOptions extends ActorsOptions { id: string namespace: string } - -export const buildBrowserContextOptions = (options: ActorOptions): BrowserContextOptions => { - const getPermissions = (browserName: string): string[] => { - const basePermissions: string[] = [] - - // Clipboard permissions supports only in Chromium-based browsers - if (browserName === 'chromium' || browserName === 'chrome' || browserName === 'msedge') { - return [...basePermissions, 'clipboard-read', 'clipboard-write'] - } - return basePermissions - } - - const contextOptions: BrowserContextOptions = { - acceptDownloads: options.context.acceptDownloads, - permissions: getPermissions( - options.browser ? options.browser.browserType().name() : 'chromium' - ), - ignoreHTTPSErrors: true, - locale: 'en-US' - } - - if (options.context.reportVideo) { - contextOptions.recordVideo = { - dir: path.join(options.context.reportDir, 'playwright', 'video') - } - } - - if (options.context.reportHar) { - contextOptions.recordHar = { - path: path.join(options.context.reportDir, 'playwright', 'har', `${options.namespace}.har`) - } - } - - switch (config.browser) { - case 'mobile-chromium': - Object.assign(contextOptions, devices['Pixel 5']) - break - - case 'mobile-webkit': - Object.assign(contextOptions, { - ...devices['iPhone 12'], - userAgent: - 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1 Mobile/15E148 Safari/604.1' - }) - break - - case 'ipad-chromium': - Object.assign(contextOptions, { - ...devices['iPad Pro 11'], - userAgent: - 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1 Mobile/15E148 Safari/604.1' - }) - break - - case 'ipad-landscape-webkit': - Object.assign(contextOptions, { - ...devices['iPad Pro 11 landscape'], - userAgent: - 'Mozilla/5.0 (iPad; CPU iPad OS 18_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1 Mobile/15E148 Safari/604.1' - }) - break - - default: - break - } - return contextOptions -} diff --git a/tests/e2e/support/environment/file.ts b/tests/e2e/support/environment/file.ts index 0b1c8ada94..fe856ffe6e 100644 --- a/tests/e2e/support/environment/file.ts +++ b/tests/e2e/support/environment/file.ts @@ -1,11 +1,11 @@ import fs from 'fs' import path from 'path' -import { config } from '../../config' +import { appConfig } from '../../playwright.config' import { File } from '../types' export class FilesEnvironment { getFile({ name }: { name: string }): File { - const relPath = path.join(config.assets, name) + const relPath = path.join(appConfig.assetsPath, name) if (!fs.existsSync(relPath)) { throw new Error('TODO: fixture files') } diff --git a/tests/e2e/support/environment/token.ts b/tests/e2e/support/environment/token.ts index a52e558db8..679aff9fc7 100644 --- a/tests/e2e/support/environment/token.ts +++ b/tests/e2e/support/environment/token.ts @@ -1,6 +1,6 @@ import { createdTokenStore, federatedTokenStore, keycloakTokenStore } from '../store/token' import { Token, User } from '../types' -import { config } from '../../config' +import { appConfig } from '../../playwright.config' export type TokenProviderType = 'keycloak' | null | undefined export type TokenEnvironmentType = KeycloakTokenEnvironment | IdpTokenEnvironment @@ -16,18 +16,19 @@ export function TokenEnvironmentFactory(type?: TokenProviderType) { class IdpTokenEnvironment { getToken({ user }: { user: User }): Token { - const store = config.federatedServer ? federatedTokenStore : createdTokenStore + const store = appConfig.federatedServer ? federatedTokenStore : createdTokenStore return store.get(user.username) } setToken({ user, token }: { user: User; token: Token }): Token { - const store = config.federatedServer ? federatedTokenStore : createdTokenStore + const store = appConfig.federatedServer ? federatedTokenStore : createdTokenStore store.set(user.username, token) return token } deleteToken({ user }: { user: User }): void { - createdTokenStore.delete(user.id) + const store = appConfig.federatedServer ? federatedTokenStore : createdTokenStore + store.delete(user.id) } } diff --git a/tests/e2e/support/environment/userManagement.ts b/tests/e2e/support/environment/userManagement.ts index 8888651f29..35f4627250 100644 --- a/tests/e2e/support/environment/userManagement.ts +++ b/tests/e2e/support/environment/userManagement.ts @@ -6,7 +6,7 @@ import { createdGroupStore, federatedUserStore } from '../store' -import { config } from '../../config' +import { appConfig } from '../../playwright.config' export class UsersEnvironment { getUser({ key }: { key: string }): User { @@ -32,7 +32,7 @@ export class UsersEnvironment { } storeCreatedUser({ user }: { user: User }): User { - const store = config.federatedServer ? federatedUserStore : createdUserStore + const store = appConfig.federatedServer ? federatedUserStore : createdUserStore if (store.has(user.id)) { throw new Error(`user '${user.id}' already exists`) } @@ -42,7 +42,7 @@ export class UsersEnvironment { getCreatedUser({ key, shareType }: { key: string; shareType?: string }): User { const store = - shareType === 'external' || config.federatedServer ? federatedUserStore : createdUserStore + shareType === 'external' || appConfig.federatedServer ? federatedUserStore : createdUserStore if (!store.has(key)) { throw new Error(`user with key '${key}' not found`) } @@ -61,7 +61,7 @@ export class UsersEnvironment { } removeCreatedUser({ key }: { key: string }): boolean { - const store = config.federatedServer ? federatedUserStore : createdUserStore + const store = appConfig.federatedServer ? federatedUserStore : createdUserStore if (!store.has(key)) { throw new Error(`user '${key}' not found`) diff --git a/tests/e2e/support/objects/account/actions.ts b/tests/e2e/support/objects/account/actions.ts index 70462d40a3..f6db4ca95f 100644 --- a/tests/e2e/support/objects/account/actions.ts +++ b/tests/e2e/support/objects/account/actions.ts @@ -1,6 +1,6 @@ import { Locator, Page, expect } from '@playwright/test' import util from 'util' -import { config } from '../../../config' +import { appConfig } from '../../../playwright.config' const accountMenuButton = '.oc-topbar-avatar' const quotaValue = '.quota-information-text' @@ -86,7 +86,7 @@ export const requestGdprExport = async (args: { page: Page }): Promise => resp.text().then((text) => text.includes('HTTP/1.1 200 OK')), // generating GDPR report can take a while // so we need to increase the timeout to 60 seconds - { timeout: config.timeout * 1000 } + { timeout: appConfig.timeout * 1000 } ), page.locator(requestExportButton).click() ]) diff --git a/tests/e2e/support/objects/app-admin-settings/spaces/actions.ts b/tests/e2e/support/objects/app-admin-settings/spaces/actions.ts index 93ae68fadf..a34b70632b 100644 --- a/tests/e2e/support/objects/app-admin-settings/spaces/actions.ts +++ b/tests/e2e/support/objects/app-admin-settings/spaces/actions.ts @@ -1,7 +1,7 @@ import { Page } from '@playwright/test' import util from 'util' import { locatorUtils } from '../../../utils' -import { config } from '../../../../config' +import { appConfig } from '../../../../playwright.config' const spaceTrSelector = '.settings-spaces-table tbody > tr' const actionConfirmButton = '.oc-modal-body-actions-confirm' @@ -239,14 +239,14 @@ export const openSpaceAdminActionSidebarPanel = async (args: { const backButton = currentPanel.locator(sideBarBackButton) if (await backButton.count()) { await backButton.click() - if (!config.slowMo) { + if (!appConfig.slowMo) { await locatorUtils.waitForEvent(currentPanel, 'transitionend') } } const panelSelector = page.locator(util.format(sideBarActionButtons, action)) const nextPanel = page.locator(util.format(siderBarActionPanel, action)) await panelSelector.click() - if (!config.slowMo) { + if (!appConfig.slowMo) { await locatorUtils.waitForEvent(nextPanel, 'transitionend') } } diff --git a/tests/e2e/support/objects/app-files/fileEvents.ts b/tests/e2e/support/objects/app-files/fileEvents.ts index e0761d6c76..66dde21cdb 100644 --- a/tests/e2e/support/objects/app-files/fileEvents.ts +++ b/tests/e2e/support/objects/app-files/fileEvents.ts @@ -1,6 +1,6 @@ import { Page, Locator, expect } from '@playwright/test' import util from 'util' -import { config } from '../../../config' +import { appConfig } from '../../../playwright.config' const resourceProcessingIcon = '//*[@data-test-resource-name="%s"]/ancestor::*[self::li or self::tr]//span[@data-test-indicator-type="resource-processing"]' @@ -19,11 +19,11 @@ export const waitProcessingToFinish = async (page: Page, resource: string): Prom await expect( getProcessingLocator(page, resource), 'Waiting for file processing to finish' - ).toBeHidden({ timeout: config.timeout * 1000 }) + ).toBeHidden({ timeout: appConfig.timeout * 1000 }) } export const waitForLockToDisappear = async (page: Page, resource: string): Promise => { await expect(getLockLocator(page, resource), 'Waiting for file lock to be removed').toBeHidden({ - timeout: config.timeout * 1000 + timeout: appConfig.timeout * 1000 }) } diff --git a/tests/e2e/support/objects/app-files/link/actions.ts b/tests/e2e/support/objects/app-files/link/actions.ts index 4ce97bfc6c..17ca1791ea 100644 --- a/tests/e2e/support/objects/app-files/link/actions.ts +++ b/tests/e2e/support/objects/app-files/link/actions.ts @@ -3,8 +3,8 @@ import util from 'util' import { sidebar } from '../utils' import { getActualExpiryDate } from '../../../utils/datePicker' import { clickResource } from '../resource/actions' -import { config } from '../../../../config' import { checkA11yOrLocalization } from '../../../utils/accessibility' +import { state } from '../../../../environment/shared' export interface createLinkArgs { page: Page @@ -166,7 +166,7 @@ export const createLink = async (args: createLinkArgs): Promise => { await clearCurrentPopup(page) // workaround for webkit (safari browser). See bug #1169 - if (config.browser === 'webkit') { + if (state.projectName === 'mobile-webkit') { return (await resp[0].json()).link.webUrl } else { const name = diff --git a/tests/e2e/support/objects/app-files/resource/actions.ts b/tests/e2e/support/objects/app-files/resource/actions.ts index 3793869ab4..ecf3b6bcd3 100644 --- a/tests/e2e/support/objects/app-files/resource/actions.ts +++ b/tests/e2e/support/objects/app-files/resource/actions.ts @@ -4,9 +4,10 @@ import path from 'path' import { waitForResources } from './utils' import { editor, sidebar } from '../utils' import { environment, utils } from '../../../../support' -import { config } from '../../../../config' +import { appConfig } from '../../../../playwright.config' import { File, Space } from '../../../types' import { waitProcessingToFinish } from '../fileEvents' +import { state } from '../../../../environment/shared' const appLoadingSpinner = '#app-loading-spinner' const topbarFilenameSelector = '#app-top-bar-resource .oc-resource-name' @@ -477,7 +478,7 @@ const createDocumentFile = async ( const editorMainFrame = page.frameLocator(externalEditorIframe) switch (editorToOpen) { case 'Collabora': - if (config.browser === 'mobile-chromium' || config.browser === 'mobile-webkit') { + if (state.projectName === 'mobile-chromium' || state.projectName === 'mobile-webkit') { await editorMainFrame.locator('#mobile-edit-button').click() } await editorMainFrame.locator(collaboraDocTextAreaSelector).fill(content) @@ -693,7 +694,7 @@ export const uploadLargeNumberOfResources = async (args: uploadResourceArgs): Pr await page.locator(uploadInfoCloseButton).waitFor() await expect(page.locator(uploadInfoSuccessLabelSelector)).toHaveText( `${resources.length} items uploaded`, - { timeout: config.timeout * 1000 } + { timeout: appConfig.timeout * 1000 } ) } @@ -704,8 +705,8 @@ export const uploadResource = async (args: uploadResourceArgs): Promise => if ( option !== 'skip' && - config.browser !== 'mobile-chromium' && - config.browser !== 'mobile-webkit' + state.projectName !== 'mobile-chromium' && + state.projectName !== 'mobile-webkit' ) { await page.locator(uploadInfoCloseButton).click() } @@ -778,7 +779,7 @@ export const resumeResourceUpload = async (page: Page): Promise => { await page .locator(uploadInfoSuccessLabelSelector) - .waitFor({ timeout: config.largeUploadTimeout * 1000 }) + .waitFor({ timeout: appConfig.largeUploadTimeout * 1000 }) await page.locator(uploadInfoCloseButton).click() } @@ -1646,7 +1647,7 @@ export const getDisplayedResourcesFromFilesList = async (page: Page): Promise => { const { page, target } = args - if (config.browser === 'mobile-chromium' || config.browser === 'mobile-webkit') { + if (state.projectName === 'mobile-chromium' || state.projectName === 'mobile-webkit') { await page.locator(mobileViewmodeSwitchBtn).click() await expect(page.locator(mobileViewmodeSwitchDropdown)).toBeVisible() @@ -1859,7 +1860,7 @@ export const openFileInViewer = async (args: openFileInViewerArgs): Promise @@ -145,7 +145,7 @@ export class Application { resp.request().postDataJSON().grant_type === 'authorization_code' && resp.request().postDataJSON().hasOwnProperty('code') && resp.request().postDataJSON().code, - { timeout: config.tokenTimeout * 1000 } + { timeout: appConfig.tokenTimeout * 1000 } ), waitForIframe ]) diff --git a/tests/e2e/support/objects/runtime/session.ts b/tests/e2e/support/objects/runtime/session.ts index 63f507cc4e..52b0eae7d7 100644 --- a/tests/e2e/support/objects/runtime/session.ts +++ b/tests/e2e/support/objects/runtime/session.ts @@ -1,6 +1,6 @@ import { Page } from '@playwright/test' import { User } from '../../types' -import { config } from '../../../config' +import { appConfig } from '../../../playwright.config' import { checkA11yOrLocalization } from '../../utils/accessibility' export class Session { @@ -11,7 +11,7 @@ export class Session { } signIn(username: string, password: string, a11y = false): Promise { - if (config.keycloak) { + if (appConfig.keycloak) { return this.keycloakSignIn(username, password) } return this.idpSignIn(username, password, a11y) diff --git a/tests/e2e/support/objects/url-navigation/actions.ts b/tests/e2e/support/objects/url-navigation/actions.ts index e4ce3b65b7..b3cd0b3044 100644 --- a/tests/e2e/support/objects/url-navigation/actions.ts +++ b/tests/e2e/support/objects/url-navigation/actions.ts @@ -1,6 +1,6 @@ import { Page } from '@playwright/test' import { dav, graph, external } from '../../api' -import { config } from '../../../config' +import { appConfig } from '../../../playwright.config' import { User } from '../../types' export interface navigateToDetailsPanelOfResourceArgs { @@ -25,7 +25,7 @@ export const navigateToDetailsPanelOfResource = async ( ): Promise => { const { page, resource, detailsPanel, user, space } = args const fileId = await getTheFileIdOfSpaceFile(user, space, resource) - const fullUrl = `${config.baseUrl}/f/${fileId}?details=${detailsPanel}` + const fullUrl = `${appConfig.baseUrl}/f/${fileId}?details=${detailsPanel}` await page.goto(fullUrl) } @@ -36,13 +36,13 @@ export const openResourceViaUrl = async (args: openResourceViaUrlArgs) => { switch (client) { case 'desktop': - fullUrl = `${config.baseUrl}/external/open-with-web/?appName=${editorName}&fileId=${fileId}` + fullUrl = `${appConfig.baseUrl}/external/open-with-web/?appName=${editorName}&fileId=${fileId}` break case 'mobile': fullUrl = await external.getOpenWithWebUrl({ user, fileId, editorName }) break default: - fullUrl = `${config.baseUrl}/f/${fileId}` + fullUrl = `${appConfig.baseUrl}/f/${fileId}` } await page.goto(fullUrl) } @@ -59,7 +59,7 @@ export const openSpaceViaUrl = async (args: openResourceViaUrlArgs) => { spaceType = 'project' } const fileId = await graph.getSpaceIdBySpaceName({ user, spaceType, spaceName }) - const fullUrl = `${config.baseUrl}/f/${fileId}` + const fullUrl = `${appConfig.baseUrl}/f/${fileId}` await page.goto(fullUrl) } @@ -86,7 +86,7 @@ const getTheFileIdOfSpaceFile = async ( } export const navigateToNonExistingPage = async ({ page }: { page: Page }) => { - await page.goto(`${config.baseUrl}/'a-non-existing-page'`) + await page.goto(`${appConfig.baseUrl}/'a-non-existing-page'`) } export const waitForNotFoundPageToBeVisible = async ({ page }: { page: Page }) => { await page.locator('.page-not-found').waitFor() diff --git a/tests/e2e/support/utils/locator.ts b/tests/e2e/support/utils/locator.ts index 22e6ff01df..306fdcd273 100644 --- a/tests/e2e/support/utils/locator.ts +++ b/tests/e2e/support/utils/locator.ts @@ -1,6 +1,6 @@ import { Locator } from '@playwright/test' import { getSSEEvents } from '../environment/sse' -import { config } from '../../config' +import { appConfig } from '../../playwright.config' export const waitForEvent = (locator: Locator, type: keyof SVGElementEventMap): Promise => locator.evaluate( @@ -23,7 +23,7 @@ export const waitForEvent = (locator: Locator, type: keyof SVGElementEventMap): element.addEventListener(arg.type, finalizer) }), - { type, timeout: config.timeout * 1000 } + { type, timeout: appConfig.timeout * 1000 } ) export const buildXpathLiteral = (value: string) => { @@ -45,7 +45,7 @@ export const waitForSSEEvent = (user: string, event: string) => { const startTime = Date.now() const interval = setInterval(function () { const events = getSSEEvents(user) - if (Date.now() - startTime > config.minTimeout * 1000) { + if (Date.now() - startTime > appConfig.minTimeout * 1000) { reject(new Error(`SSE event ${event} was not obtained in the events list:[${events}]`)) clearInterval(interval) } diff --git a/tests/e2e/support/utils/runtimeFs.ts b/tests/e2e/support/utils/runtimeFs.ts index f87baa334f..918eda74ea 100644 --- a/tests/e2e/support/utils/runtimeFs.ts +++ b/tests/e2e/support/utils/runtimeFs.ts @@ -1,6 +1,6 @@ import fs from 'fs' import path from 'path' -import { config } from '../../config' +import { appConfig } from '../../playwright.config' // max file creation size is 10GB export const MAX_FILE_SIZE = Math.pow(1024, 3) * 10 @@ -40,10 +40,10 @@ export const getBytes = (fileSize: string): number => { } export const getTempUploadPath = (): string => { - if (!fs.existsSync(config.tempAssetsPath)) { - fs.mkdirSync(config.tempAssetsPath) + if (!fs.existsSync(appConfig.tempAssetsPath)) { + fs.mkdirSync(appConfig.tempAssetsPath) } - return config.tempAssetsPath + return appConfig.tempAssetsPath } export const createFileWithSize = ( @@ -87,7 +87,7 @@ export const createFile = ( } export const removeTempUploadDirectory = () => { - if (fs.existsSync(config.tempAssetsPath)) { - fs.rmSync(config.tempAssetsPath, { recursive: true }) + if (fs.existsSync(appConfig.tempAssetsPath)) { + fs.rmSync(appConfig.tempAssetsPath, { recursive: true }) } } diff --git a/tests/e2e/support/utils/tokenHelper.ts b/tests/e2e/support/utils/tokenHelper.ts index 18bf27efa6..d9e011b1ea 100644 --- a/tests/e2e/support/utils/tokenHelper.ts +++ b/tests/e2e/support/utils/tokenHelper.ts @@ -1,13 +1,13 @@ import { Browser } from '@playwright/test' import { Session } from '../objects/runtime' import { TokenProviderType } from '../environment' -import { config } from '../../config' +import { appConfig } from '../../playwright.config' import { User } from '../types' export const initializeUser = async ({ browser, user, - url = config.baseUrl, + url = appConfig.baseUrl, waitForSelector = null }: { browser: Browser diff --git a/tests/e2e/tsconfig.json b/tests/e2e/tsconfig.json new file mode 100644 index 0000000000..ffaac4f512 --- /dev/null +++ b/tests/e2e/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@opencloud-eu/tsconfig", + "compilerOptions": { + "rootDir": "..", + "types": ["playwright-bdd", "node"] + } +}