diff --git a/packages/playwright-common/Dockerfile b/packages/playwright-common/Dockerfile new file mode 100644 index 0000000000..d9907f1fc1 --- /dev/null +++ b/packages/playwright-common/Dockerfile @@ -0,0 +1,13 @@ +ARG PLAYWRIGHT_VERSION +FROM mcr.microsoft.com/playwright:v${PLAYWRIGHT_VERSION}-noble + +WORKDIR /work + +# fonts-dejavu is needed for the same RTL rendering as on CI +RUN apt-get update && apt-get -y install docker.io fonts-dejavu +# Install the matching playwright runtime, the docker image only includes browsers +RUN npm i -g playwright@${PLAYWRIGHT_VERSION} + +COPY docker-entrypoint.sh /docker-entrypoint.sh + +ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/packages/playwright-common/README.md b/packages/playwright-common/README.md new file mode 100644 index 0000000000..ab7c192315 --- /dev/null +++ b/packages/playwright-common/README.md @@ -0,0 +1,22 @@ +# @element-hq/element-web-playwright-common + +Set of Playwright utilities to make it easier to write tests for Element Web, Element Web Modules & Element Desktop. + +# This is a partial clone of https://github.com/element-hq/element-modules/tree/main/packages/element-web-playwright-common + +In the future the rest of the package will be brought into this monorepo, for now it serves as an experimental alternative to https://github.com/element-hq/element-modules/pull/188 + +## Releases + +The API is versioned using semver, with the major version incremented for breaking changes. + +## Copyright & License + +Copyright (c) 2026 Element Creations Ltd + +This software is multi licensed by Element Creations Ltd (Element). It can be used either: + +(1) for free under the terms of the GNU Affero General Public License (as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version); OR + +(2) under the terms of a paid-for Element Commercial License agreement between you and Element (the terms of which may vary depending on what you and Element have agreed to). +Unless required by applicable law or agreed to in writing, software distributed under the Licenses is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the Licenses for the specific language governing permissions and limitations under the Licenses. diff --git a/packages/playwright-common/docker-entrypoint.sh b/packages/playwright-common/docker-entrypoint.sh new file mode 100755 index 0000000000..66a71fcd58 --- /dev/null +++ b/packages/playwright-common/docker-entrypoint.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# We use npm here as we used `npm i -g` to install playwright in the Dockerfile +npm exec -- playwright run-server --port "$PORT" --host 0.0.0.0 diff --git a/packages/playwright-common/package.json b/packages/playwright-common/package.json new file mode 100644 index 0000000000..77b33190cc --- /dev/null +++ b/packages/playwright-common/package.json @@ -0,0 +1,21 @@ +{ + "name": "@element-hq/element-web-playwright-common-local", + "type": "module", + "version": "3.0.0", + "license": "SEE LICENSE IN README.md", + "repository": { + "type": "git", + "url": "git+https://github.com/element-hq/element-web.git", + "directory": "packages/playwright-common" + }, + "author": "element-hq", + "engines": { + "node": ">=20.0.0" + }, + "bin": { + "playwright-screenshots": "playwright-screenshots.sh" + }, + "devDependencies": { + "wait-on": "^9.0.4" + } +} diff --git a/packages/playwright-common/playwright-screenshots.sh b/packages/playwright-common/playwright-screenshots.sh new file mode 100755 index 0000000000..8e2bb9306b --- /dev/null +++ b/packages/playwright-common/playwright-screenshots.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +set -e + +# Handle symlinks here as we tend to be executed as an npm binary +SCRIPT_PATH=$(readlink -f "$0") +SCRIPT_DIR=$(dirname "$SCRIPT_PATH") + +IMAGE_NAME="element-web-playwright-server" +WS_PORT=3000 + +# Check the playwright version +PW_VERSION=$(npm exec --silent -- playwright --version | gcut -d" " -f2) +echo "Building $IMAGE_NAME:$PW_VERSION image in $SCRIPT_DIR" + +# Build the image +docker build -t "$IMAGE_NAME" --build-arg "PLAYWRIGHT_VERSION=$PW_VERSION" "$SCRIPT_DIR" + +# Start the playwright-server in docker +CONTAINER=$(docker run --network=host --rm -d -e PORT="$WS_PORT" "$IMAGE_NAME") +# Set up an exit trap to clean up the docker container +clean_up() { + ARG=$? + echo "Stopping playwright-server" + docker stop "$CONTAINER" > /dev/null + exit $ARG +} +trap clean_up EXIT + +# Wait for playwright-server to be ready +echo "Waiting for playwright-server" +pnpm wait-on "tcp:$WS_PORT" + +# Run the test we were given, setting PW_TEST_CONNECT_WS_ENDPOINT accordingly +echo "Running '$@'" +PW_TEST_CONNECT_WS_ENDPOINT="http://localhost:$WS_PORT" "$@" diff --git a/packages/shared-components/package.json b/packages/shared-components/package.json index 5c097f1a88..1b49b76a2c 100644 --- a/packages/shared-components/package.json +++ b/packages/shared-components/package.json @@ -40,7 +40,7 @@ "i18n:lint": "matrix-i18n-lint && prettier --log-level=silent --write src/i18n/strings/ --ignore-path /dev/null", "test:unit": "vitest --project=unit", "test:storybook": "pnpm build:doc && vitest --project=storybook", - "test:storybook:update": "cd ../.. && playwright-screenshots --entrypoint /work/packages/shared-components/scripts/storybook-screenshot-update.sh --with-node-modules --no-link-modules", + "test:storybook:update": "CI=1 pnpm playwright-screenshots pnpm vitest --run --update --project=storybook", "build": "vite build", "prepack": "pnpm run build", "storybook": "storybook dev -p 6007", @@ -63,7 +63,7 @@ "temporal-polyfill": "^0.3.0" }, "devDependencies": { - "@element-hq/element-web-playwright-common": "catalog:", + "@element-hq/element-web-playwright-common-local": "workspace:*", "@fetch-mock/vitest": "^0.2.18", "@matrix-org/react-sdk-module-api": "^2.5.0", "@playwright/test": "catalog:", diff --git a/packages/shared-components/playwright/snapshots/event-textualevent--default-linux.png b/packages/shared-components/playwright/snapshots/event-textualevent--default-linux.png deleted file mode 100644 index ede4361ae3..0000000000 Binary files a/packages/shared-components/playwright/snapshots/event-textualevent--default-linux.png and /dev/null differ diff --git a/packages/shared-components/playwright/snapshots/rightpanel-widgetcontextmenuview--default-linux.png b/packages/shared-components/playwright/snapshots/rightpanel-widgetcontextmenuview--default-linux.png deleted file mode 100644 index a210966744..0000000000 Binary files a/packages/shared-components/playwright/snapshots/rightpanel-widgetcontextmenuview--default-linux.png and /dev/null differ diff --git a/packages/shared-components/playwright/snapshots/rightpanel-widgetcontextmenuview--only-basic-modification-linux.png b/packages/shared-components/playwright/snapshots/rightpanel-widgetcontextmenuview--only-basic-modification-linux.png deleted file mode 100644 index ec57d3d389..0000000000 Binary files a/packages/shared-components/playwright/snapshots/rightpanel-widgetcontextmenuview--only-basic-modification-linux.png and /dev/null differ diff --git a/packages/shared-components/playwright/snapshots/room-list-roomlistheaderview--default-linux.png b/packages/shared-components/playwright/snapshots/room-list-roomlistheaderview--default-linux.png deleted file mode 100644 index aa0e385a97..0000000000 Binary files a/packages/shared-components/playwright/snapshots/room-list-roomlistheaderview--default-linux.png and /dev/null differ diff --git a/packages/shared-components/playwright/snapshots/room-list-roomlistheaderview--no-compose-menu-linux.png b/packages/shared-components/playwright/snapshots/room-list-roomlistheaderview--no-compose-menu-linux.png deleted file mode 100644 index 4e67ae0129..0000000000 Binary files a/packages/shared-components/playwright/snapshots/room-list-roomlistheaderview--no-compose-menu-linux.png and /dev/null differ diff --git a/packages/shared-components/playwright/snapshots/room-list-roomlistheaderview--no-space-menu-linux.png b/packages/shared-components/playwright/snapshots/room-list-roomlistheaderview--no-space-menu-linux.png deleted file mode 100644 index 1466544712..0000000000 Binary files a/packages/shared-components/playwright/snapshots/room-list-roomlistheaderview--no-space-menu-linux.png and /dev/null differ diff --git a/packages/shared-components/scripts/storybook-screenshot-update.sh b/packages/shared-components/scripts/storybook-screenshot-update.sh deleted file mode 100755 index 549ac18d69..0000000000 --- a/packages/shared-components/scripts/storybook-screenshot-update.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -# -# Update storybook screenshots -# -# This script should be used as the entrypoint parameter of the `playwright-screenshots` script. It -# installs the pnpm dependencies, and then runs `vitest --run --update --project=storybook` to update the storybook screenshots. -# -# It requires that `playwright-screenshots` is given the `--with-node-modules` parameter. - -set -e - -# First install dependencies. We have to do this within the playwright container rather than the host, -# because we have which must be built for the right architecture (and some environments use a VM -# to run docker containers, meaning that things inside a container use a different architecture than -# those on the host). -pnpm install --frozen-lockfile - -# Now run the screenshot update, we set CI=1 to inform vis to update the real baselines -CI=1 pnpm --dir packages/shared-components test:storybook --run --update diff --git a/packages/shared-components/vitest.config.ts b/packages/shared-components/vitest.config.ts index 23cc026304..115eac0b07 100644 --- a/packages/shared-components/vitest.config.ts +++ b/packages/shared-components/vitest.config.ts @@ -101,6 +101,8 @@ export default defineConfig({ storybookVis({ // 3px of difference allowed before marking as failed failureThreshold: 3, + // When running in CI=1 mode, set the platform to `linux` as that is the platform where the browser-in-docker is running + snapshotRootDir: ({ ci, platform }) => `__vis__/${ci ? "linux" : platform}`, }), ], test: { @@ -110,7 +112,16 @@ export default defineConfig({ headless: true, provider: playwright({ contextOptions: commonContextOptions, - launchOptions: commonLaunchOptions, + launchOptions: process.env.PW_TEST_CONNECT_WS_ENDPOINT ? undefined : commonLaunchOptions, + connectOptions: process.env.PW_TEST_CONNECT_WS_ENDPOINT + ? { + wsEndpoint: process.env.PW_TEST_CONNECT_WS_ENDPOINT, + exposeNetwork: "", + headers: { + "x-playwright-launch-options": JSON.stringify(commonLaunchOptions), + }, + } + : undefined, }), instances: [{ browser: "chromium" }], }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1b47cde5f3..ad9629eb2b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -711,6 +711,12 @@ importers: specifier: ^2.3.3 version: 2.8.2 + packages/playwright-common: + devDependencies: + wait-on: + specifier: ^9.0.4 + version: 9.0.4 + packages/shared-components: dependencies: '@element-hq/element-web-module-api': @@ -744,9 +750,9 @@ importers: specifier: ^0.3.0 version: 0.3.0 devDependencies: - '@element-hq/element-web-playwright-common': - specifier: 'catalog:' - version: 2.2.7(@element-hq/element-web-module-api@1.9.0(@matrix-org/react-sdk-module-api@2.5.0(patch_hash=016146c9cc96e6363609d2b2ac0896ccef567882eb1d73b75a77b8a30929de96)(react@19.2.4))(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(matrix-web-i18n@3.6.0)(react@19.2.4))(@playwright/test@1.58.1)(playwright-core@1.58.1) + '@element-hq/element-web-playwright-common-local': + specifier: workspace:* + version: link:../playwright-common '@fetch-mock/vitest': specifier: ^0.2.18 version: 0.2.18(vitest@4.0.18) @@ -2268,6 +2274,26 @@ packages: engines: {node: '>=6'} hasBin: true + '@hapi/address@5.1.1': + resolution: {integrity: sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==} + engines: {node: '>=14.0.0'} + + '@hapi/formula@3.0.2': + resolution: {integrity: sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==} + + '@hapi/hoek@11.0.7': + resolution: {integrity: sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==} + + '@hapi/pinpoint@2.0.1': + resolution: {integrity: sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==} + + '@hapi/tlds@1.1.5': + resolution: {integrity: sha512-Vq/1gnIIsvFUpKlDdfrPd/ssHDpAyBP/baVukh3u2KSG2xoNjsnRNjQiPmuyPPGqsn1cqVWWhtZHfOBaLizFRQ==} + engines: {node: '>=14.0.0'} + + '@hapi/topo@6.0.2': + resolution: {integrity: sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==} + '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -4877,6 +4903,9 @@ packages: axios@1.13.4: resolution: {integrity: sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==} + axios@1.13.5: + resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==} + axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} @@ -7352,6 +7381,10 @@ packages: jju@1.4.0: resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + joi@18.0.2: + resolution: {integrity: sha512-RuCOQMIt78LWnktPoeBL0GErkNaJPTBGcYuyaBvUOQSpcpcLfWrHPPihYdOGbV5pam9VTWbeoF7TsGiHugcjGA==} + engines: {node: '>= 20'} + js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} @@ -9195,6 +9228,9 @@ packages: rw@1.3.3: resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + safe-array-concat@1.1.3: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} @@ -10270,6 +10306,11 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} + wait-on@9.0.4: + resolution: {integrity: sha512-k8qrgfwrPVJXTeFY8tl6BxVHiclK11u72DVKhpybHfUL/K6KM4bdyK9EhIVYGytB5MJe/3lq4Tf0hrjM+pvJZQ==} + engines: {node: '>=20.0.0'} + hasBin: true + walk-up-path@4.0.0: resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==} engines: {node: 20 || >=22} @@ -12068,6 +12109,22 @@ snapshots: protobufjs: 7.5.4 yargs: 17.7.2 + '@hapi/address@5.1.1': + dependencies: + '@hapi/hoek': 11.0.7 + + '@hapi/formula@3.0.2': {} + + '@hapi/hoek@11.0.7': {} + + '@hapi/pinpoint@2.0.1': {} + + '@hapi/tlds@1.1.5': {} + + '@hapi/topo@6.0.2': + dependencies: + '@hapi/hoek': 11.0.7 + '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 @@ -15005,6 +15062,14 @@ snapshots: transitivePeerDependencies: - debug + axios@1.13.5: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.5 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + axobject-query@4.1.0: {} b4a@1.7.3: {} @@ -18068,6 +18133,16 @@ snapshots: jju@1.4.0: {} + joi@18.0.2: + dependencies: + '@hapi/address': 5.1.1 + '@hapi/formula': 3.0.2 + '@hapi/hoek': 11.0.7 + '@hapi/pinpoint': 2.0.1 + '@hapi/tlds': 1.1.5 + '@hapi/topo': 6.0.2 + '@standard-schema/spec': 1.1.0 + js-tokens@10.0.0: {} js-tokens@4.0.0: {} @@ -18368,7 +18443,7 @@ snapshots: mailpit-api@1.7.0: dependencies: - axios: 1.13.4 + axios: 1.13.5 partysocket: 1.1.11 ws: 8.19.0 transitivePeerDependencies: @@ -20141,6 +20216,10 @@ snapshots: rw@1.3.3: {} + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + safe-array-concat@1.1.3: dependencies: call-bind: 1.0.8 @@ -21436,6 +21515,16 @@ snapshots: dependencies: xml-name-validator: 5.0.0 + wait-on@9.0.4: + dependencies: + axios: 1.13.5 + joi: 18.0.2 + lodash: 4.17.23 + minimist: 1.2.8 + rxjs: 7.8.2 + transitivePeerDependencies: + - debug + walk-up-path@4.0.0: {} walk@2.3.15: