diff --git a/.github/workflows/end-to-end-tests-netlify.yaml b/.github/workflows/build-and-test-netlify.yaml similarity index 97% rename from .github/workflows/end-to-end-tests-netlify.yaml rename to .github/workflows/build-and-test-netlify.yaml index c5bca0e192..1b3aa2bd69 100644 --- a/.github/workflows/end-to-end-tests-netlify.yaml +++ b/.github/workflows/build-and-test-netlify.yaml @@ -5,7 +5,7 @@ on: # Privilege escalation necessary to publish to Netlify # 🚨 We must not execute any checked out code here. workflow_run: # zizmor: ignore[dangerous-triggers] - workflows: ["End to End Tests"] + workflows: ["Build & Test"] types: - completed diff --git a/.github/workflows/end-to-end-tests.yaml b/.github/workflows/build-and-test.yaml similarity index 73% rename from .github/workflows/end-to-end-tests.yaml rename to .github/workflows/build-and-test.yaml index 3d72750a1d..e13c537d0e 100644 --- a/.github/workflows/end-to-end-tests.yaml +++ b/.github/workflows/build-and-test.yaml @@ -1,7 +1,13 @@ -# Produce a build of element-web with this version of react-sdk -# and any matching branches of element-web and js-sdk, output it -# as an artifact and run end-to-end tests. -name: End to End Tests +# builds Element Web +# runs Playwright tests against the built Element Web +# builds Element Desktop using the built Element Web +# +# Tries to use a matching js-sdk branch for the build. +# +# Produces a `webapp` artifact +# Produces multiple Desktop artifacts +# Produces multiple Playwright report artifacts +name: Build & Test on: # CRON to run all Projects at 6am UTC schedule: @@ -10,7 +16,8 @@ on: merge_group: types: [checks_requested] push: - branches: [develop, master] + # We do not build on push to develop as the merge_group check handles that + branches: [staging, master] repository_dispatch: types: [element-web-notify] @@ -35,15 +42,15 @@ concurrency: env: # fetchdep.sh needs to know our PR number PR_NUMBER: ${{ github.event.pull_request.number }} - # Use 6 runners in the default case, but 4 when running on a schedule where we run all 5 projects (20 runners total) - NUM_RUNNERS: ${{ github.event_name == 'schedule' && 4 || 6 }} + # Use 4 runners in the default case, but only 1 when running on a schedule where we run all 5 projects + NUM_RUNNERS: ${{ github.event_name == 'schedule' && 1 || 4 }} NX_DEFAULT_OUTPUT_STYLE: stream-without-prefixes permissions: {} # No permissions required jobs: - build: - name: "Build Element-Web" + build_ew: + name: "Build Element Web" runs-on: ubuntu-24.04 if: inputs.skip != true outputs: @@ -94,9 +101,9 @@ jobs: const matrix = Array.from({ length: numRunners }, (_, i) => i + 1); core.setOutput("matrix", JSON.stringify(matrix)); - playwright: - name: "Run Tests [${{ matrix.project }}] ${{ matrix.runner }}/${{ needs.build.outputs.num-runners }}" - needs: build + playwright_ew: + name: "Run Tests [${{ matrix.project }}] ${{ matrix.runner }}/${{ needs.build_ew.outputs.num-runners }}" + needs: build_ew if: inputs.skip != true runs-on: ubuntu-24.04 permissions: @@ -107,7 +114,7 @@ jobs: fail-fast: false matrix: # Run multiple instances in parallel to speed up the tests - runner: ${{ fromJSON(needs.build.outputs.runners-matrix) }} + runner: ${{ fromJSON(needs.build_ew.outputs.runners-matrix) }} project: - Chrome - Firefox @@ -179,29 +186,85 @@ jobs: --project="${{ matrix.project }}" \ ${{ (github.event_name == 'pull_request' && matrix.runAllTests == false ) && '--grep-invert @mergequeue' || '' }} env: - SHARD: ${{ format('{0}/{1}', matrix.runner, needs.build.outputs.num-runners) }} + SHARD: ${{ format('{0}/{1}', matrix.runner, needs.build_ew.outputs.num-runners) }} - name: Upload blob report to GitHub Actions Artifacts if: always() uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: - name: all-blob-reports-${{ matrix.project }}-${{ matrix.runner }} + name: blob-report-${{ matrix.project }}-${{ matrix.runner }} path: apps/web/blob-report retention-days: 1 + if-no-files-found: error downstream-modules: name: Downstream Playwright tests [element-modules] - needs: build + needs: build_ew if: inputs.skip != true && github.event_name == 'merge_group' uses: element-hq/element-modules/.github/workflows/reusable-playwright-tests.yml@main # zizmor: ignore[unpinned-uses] with: webapp-artifact: webapp + prepare_ed: + name: "Prepare Element Desktop" + uses: ./.github/workflows/build_desktop_prepare.yaml + needs: build_ew + if: inputs.skip != true + permissions: + contents: read + with: + config: ${{ (github.event.pull_request.base.ref || github.ref_name) == 'develop' && 'element.io/nightly' || 'element.io/release' }} + version: ${{ (github.event.pull_request.base.ref || github.ref_name) == 'develop' && 'develop' || '' }} + webapp-artifact: webapp + + build_ed_windows: + needs: prepare_ed + name: "Desktop Windows" + uses: ./.github/workflows/build_desktop_windows.yaml + if: inputs.skip != true + strategy: + matrix: + arch: [x64, ia32, arm64] + with: + arch: ${{ matrix.arch }} + blob_report: true + + build_ed_linux: + needs: prepare_ed + name: "Desktop Linux" + uses: ./.github/workflows/build_desktop_linux.yaml + if: inputs.skip != true + strategy: + matrix: + sqlcipher: [system, static] + arch: [amd64, arm64] + runAllTests: + - ${{ github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'X-Run-All-Tests') }} + # We ship static sqlcipher builds, so delegate testing the system builds to the merge queue + exclude: + - runAllTests: false + sqlcipher: system + with: + sqlcipher: ${{ matrix.sqlcipher }} + arch: ${{ matrix.arch }} + blob_report: true + + build_ed_macos: + needs: prepare_ed + name: "Desktop macOS" + uses: ./.github/workflows/build_desktop_macos.yaml + if: inputs.skip != true + with: + blob_report: true + complete: name: end-to-end-tests needs: - - playwright + - playwright_ew - downstream-modules + - build_ed_windows + - build_ed_linux + - build_ed_macos if: always() runs-on: ubuntu-24.04 steps: @@ -227,18 +290,20 @@ jobs: if: inputs.skip != true uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: - pattern: all-blob-reports-* - path: apps/web/all-blob-reports + pattern: blob-report-* + path: all-blob-reports merge-multiple: true - name: Merge into HTML Report if: inputs.skip != true - working-directory: apps/web - run: pnpm playwright merge-reports --reporter=html,./playwright/flaky-reporter.ts,@element-hq/element-web-playwright-common/lib/stale-screenshot-reporter.js ./all-blob-reports + run: | + pnpm playwright merge-reports \ + --config=playwright-merge.config.ts \ + ./all-blob-reports env: # Only pass creds to the flaky-reporter on main branch runs GITHUB_TOKEN: ${{ github.ref_name == 'develop' && secrets.ELEMENT_BOT_TOKEN || '' }} - PLAYWRIGHT_HTML_TITLE: ${{ case(github.event_name == 'pull_request', format('EW Playwright Report PR-{0}', env.PR_NUMBER), 'EW Playwright Report') }} + PLAYWRIGHT_HTML_TITLE: ${{ case(github.event_name == 'pull_request', format('Playwright Report PR-{0}', env.PR_NUMBER), 'Playwright Report') }} # Upload the HTML report even if one of our reporters fails, this can happen when stale screenshots are detected - name: Upload HTML report @@ -246,7 +311,7 @@ jobs: uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: html-report - path: apps/web/playwright-report + path: playwright-report retention-days: 14 if-no-files-found: error diff --git a/.github/workflows/build_desktop_and_test.yaml b/.github/workflows/build_desktop_and_test.yaml deleted file mode 100644 index 1ae64a87e4..0000000000 --- a/.github/workflows/build_desktop_and_test.yaml +++ /dev/null @@ -1,89 +0,0 @@ -name: Build and Test -on: - pull_request: {} - push: - branches: [develop, staging, master] -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -permissions: {} # No permissions required -jobs: - fetch: - uses: ./.github/workflows/build_desktop_prepare.yaml - permissions: - contents: read - with: - config: ${{ (github.event.pull_request.base.ref || github.ref_name) == 'develop' && 'element.io/nightly' || 'element.io/release' }} - version: ${{ (github.event.pull_request.base.ref || github.ref_name) == 'develop' && 'develop' || '' }} - branch-matching: true - - windows: - needs: fetch - name: Windows - uses: ./.github/workflows/build_desktop_windows.yaml - strategy: - matrix: - arch: [x64, ia32, arm64] - with: - arch: ${{ matrix.arch }} - blob_report: true - - linux: - needs: fetch - name: "Linux (${{ matrix.arch }}) (sqlcipher: ${{ matrix.sqlcipher }})" - uses: ./.github/workflows/build_desktop_linux.yaml - strategy: - matrix: - sqlcipher: [system, static] - arch: [amd64, arm64] - with: - sqlcipher: ${{ matrix.sqlcipher }} - arch: ${{ matrix.arch }} - blob_report: true - - macos: - needs: fetch - name: macOS - uses: ./.github/workflows/build_desktop_macos.yaml - with: - blob_report: true - - tests-done: - needs: [windows, linux, macos] - runs-on: ubuntu-24.04 - if: ${{ !cancelled() }} - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - persist-credentials: false - - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5 - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - with: - cache: "pnpm" - node-version: "lts/*" - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Download blob reports from GitHub Actions Artifacts - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - with: - pattern: blob-report-* - path: apps/desktop/all-blob-reports - merge-multiple: true - - - name: Merge into HTML Report - working-directory: apps/desktop - run: pnpm playwright merge-reports -c ./playwright.config.ts --reporter=html ./all-blob-reports - - - name: Upload HTML report - if: always() - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 - with: - name: html-report - path: apps/desktop/playwright-report - retention-days: 14 - - - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') - run: exit 1 diff --git a/.github/workflows/build_desktop_linux.yaml b/.github/workflows/build_desktop_linux.yaml index 938ae9e132..d045cc3d7e 100644 --- a/.github/workflows/build_desktop_linux.yaml +++ b/.github/workflows/build_desktop_linux.yaml @@ -28,7 +28,7 @@ on: type: string required: false description: | - The name of the prepare artifact to use, defaults to 'webapp'. + The name of the prepare artifact to use, defaults to 'desktop-prepare'. The artifact must contain the following: + webapp.asar - the asar archive of the webapp to embed in the desktop app + electronVersion - the version of electron to use for cache keying @@ -38,7 +38,7 @@ on: The artifact can also contain any additional files which will be applied as overrides to the checkout root before building, for example icons in the `build/` directory to override the app icons. - default: "webapp" + default: "desktop-prepare" test: type: boolean required: false @@ -73,20 +73,8 @@ jobs: # https://github.com/matrix-org/seshat/issues/135 runs-on: ${{ inputs.runs-on || (inputs.arch == 'arm64' && 'ubuntu-22.04-arm' || 'ubuntu-22.04') }} env: - HAK_DOCKER_IMAGE: ghcr.io/element-hq/element-web/desktop-build-env + HAK_DOCKER_IMAGE: ghcr.io/element-hq/element-web/desktop-build-env:${{ case(github.event_name == 'push', inputs.ref || github.ref_name, github.event_name == 'release', 'staging', 'develop') }} steps: - - name: Resolve docker image tag for push - if: github.event_name == 'push' - run: echo "HAK_DOCKER_IMAGE=$HAK_DOCKER_IMAGE:$REF" >> $GITHUB_ENV - env: - REF: ${{ inputs.ref || github.ref_name }} - - name: Resolve docker image tag for release - if: github.event_name == 'release' - run: echo "HAK_DOCKER_IMAGE=$HAK_DOCKER_IMAGE:staging" >> $GITHUB_ENV - - name: Resolve docker image tag for other triggers - if: github.event_name != 'push' && github.event_name != 'release' - run: echo "HAK_DOCKER_IMAGE=$HAK_DOCKER_IMAGE:develop" >> $GITHUB_ENV - - uses: nbucic/variable-mapper@0673f6891a0619ba7c002ecfed0f9f4f39017b6f id: config with: @@ -95,11 +83,9 @@ jobs: map: | { "amd64": { - "target": "x86_64-unknown-linux-gnu", "arch": "x86-64" }, "arm64": { - "target": "aarch64-unknown-linux-gnu", "arch": "aarch64", "build-args": "--arm64" } @@ -120,7 +106,7 @@ jobs: id: cache uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 with: - key: ${{ runner.os }}-${{ github.ref_name }}-${{ inputs.sqlcipher }}-${{ inputs.arch }}-${{ hashFiles('hakHash', 'electronVersion', 'dockerbuild/*') }} + key: ${{ runner.os }}-${{ github.ref_name }}-${{ inputs.sqlcipher }}-${{ inputs.arch }}-${{ hashFiles('apps/desktop/hakHash', 'apps/desktop/electronVersion', 'apps/desktop/dockerbuild/*') }} path: | apps/desktop/.hak @@ -135,7 +121,7 @@ jobs: - name: Install Deps working-directory: apps/desktop - run: pnpm install --frozen-lockfile + run: "pnpm install --frozen-lockfile --filter element-desktop" - name: "Get modified files" id: changed_files diff --git a/.github/workflows/build_desktop_macos.yaml b/.github/workflows/build_desktop_macos.yaml index c4f828a092..bc5455b5a4 100644 --- a/.github/workflows/build_desktop_macos.yaml +++ b/.github/workflows/build_desktop_macos.yaml @@ -37,7 +37,7 @@ on: type: string required: false description: | - The name of the prepare artifact to use, defaults to 'webapp'. + The name of the prepare artifact to use, defaults to 'desktop-prepare'. The artifact must contain the following: + webapp.asar - the asar archive of the webapp to embed in the desktop app + electronVersion - the version of electron to use for cache keying @@ -46,7 +46,7 @@ on: The artifact can also contain any additional files which will be applied as overrides to the checkout root before building, for example icons in the `build/` directory to override the app icons. - default: "webapp" + default: "desktop-prepare" test: type: boolean required: false @@ -92,7 +92,7 @@ jobs: id: cache uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 with: - key: ${{ runner.os }}-${{ hashFiles('hakHash', 'electronVersion') }} + key: ${{ runner.os }}-${{ hashFiles('apps/desktop/hakHash', 'apps/desktop/electronVersion') }} path: | apps/desktop/.hak @@ -121,7 +121,7 @@ jobs: - name: Install Deps working-directory: apps/desktop - run: "pnpm install --frozen-lockfile" + run: "pnpm install --frozen-lockfile --filter element-desktop" - name: Build Natives if: steps.cache.outputs.cache-hit != 'true' diff --git a/.github/workflows/build_desktop_prepare.yaml b/.github/workflows/build_desktop_prepare.yaml index 517b865ab1..736051b192 100644 --- a/.github/workflows/build_desktop_prepare.yaml +++ b/.github/workflows/build_desktop_prepare.yaml @@ -20,11 +20,10 @@ on: required: false default: false description: "Whether the build should be deployed to production" - branch-matching: - type: boolean + webapp-artifact: + type: string required: false - default: false - description: "Whether the branch name should be matched to find the element-web commit" + description: "Name of the webapp artifact that should be used, will fetch a relevant build if omitted" secrets: # Required if `nightly` is set CF_R2_ACCESS_KEY_ID: @@ -66,28 +65,26 @@ jobs: - name: Install Deps working-directory: apps/desktop - run: "pnpm install --frozen-lockfile" + run: "pnpm install --frozen-lockfile --filter element-desktop" - - name: Fetch Element Web (matching branch) - id: branch-matching - if: inputs.branch-matching + - name: Fetch Element Web (from artifact) + if: inputs.webapp-artifact != '' + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + name: ${{ inputs.webapp-artifact }} + path: apps/desktop/webapp + + - name: Build webapp.asar (from artifact) + if: inputs.webapp-artifact != '' working-directory: apps/desktop - continue-on-error: true run: | - scripts/branch-match.sh - cp "$CONFIG_DIR/config.json" element-web/ - pnpm --cwd element-web install --frozen-lockfile - pnpm --cwd element-web run build - mv element-web/webapp . + cp -f "$CONFIG_DIR/config.json" webapp/config.json pnpm run asar-webapp env: - # These must be set for branch-match.sh to get the right branch - REPOSITORY: ${{ github.repository }} - PR_NUMBER: ${{ github.event.pull_request.number }} CONFIG_DIR: ${{ inputs.config }} - name: Fetch Element Web (${{ inputs.version }}) - if: steps.branch-matching.outcome == 'failure' || steps.branch-matching.outcome == 'skipped' + if: inputs.webapp-artifact == '' working-directory: apps/desktop run: pnpm run fetch --noverify -d ${CONFIG} ${VERSION} env: @@ -189,7 +186,7 @@ jobs: - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: - name: webapp + name: desktop-prepare retention-days: 1 path: | apps/desktop/webapp.asar diff --git a/.github/workflows/build_desktop_test.yaml b/.github/workflows/build_desktop_test.yaml index 09fd9e46e9..ba347fa5b3 100644 --- a/.github/workflows/build_desktop_test.yaml +++ b/.github/workflows/build_desktop_test.yaml @@ -48,8 +48,7 @@ jobs: cache: "pnpm" - name: Install Deps - working-directory: apps/desktop - run: "pnpm install --frozen-lockfile" + run: "pnpm install --frozen-lockfile --filter element-desktop" - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: @@ -85,12 +84,19 @@ jobs: EXECUTABLE: ${{ steps.executable.outputs.path }} - name: Run tests - uses: coactions/setup-xvfb@6b00cf1889f4e1d5a48635647013c0508128ee1a timeout-minutes: 20 - with: - run: pnpm -C apps/desktop test --project=${{ inputs.project }} ${{ runner.os != 'Linux' && '--ignore-snapshots' || '' }} ${{ inputs.blob_report == false && '--reporter=html' || '' }} ${{ inputs.args }} + shell: bash + working-directory: apps/desktop + run: | + $PREFIX pnpm playwright test \ + ${{ runner.os != 'Linux' && '--ignore-snapshots' || '' }} \ + ${{ inputs.blob_report == false && '--reporter=html' || '' }} \ + $ARGS env: + PREFIX: ${{ runner.os == 'Linux' && 'xvfb-run' || '' }} + PW_TAG: ${{ inputs.project }} ELEMENT_DESKTOP_EXECUTABLE: ${{ steps.executable.outputs.path }} + ARGS: ${{ inputs.args }} - name: Upload blob report if: always() && inputs.blob_report @@ -99,6 +105,7 @@ jobs: name: blob-report-${{ inputs.artifact }} path: apps/desktop/blob-report retention-days: 1 + if-no-files-found: error - name: Upload HTML report if: always() && inputs.blob_report == false @@ -107,3 +114,4 @@ jobs: name: ${{ inputs.artifact }}-test path: apps/desktop/playwright-report retention-days: 14 + if-no-files-found: error diff --git a/.github/workflows/build_desktop_windows.yaml b/.github/workflows/build_desktop_windows.yaml index 37fc8679aa..ebad3763f0 100644 --- a/.github/workflows/build_desktop_windows.yaml +++ b/.github/workflows/build_desktop_windows.yaml @@ -42,7 +42,7 @@ on: type: string required: false description: | - The name of the prepare artifact to use, defaults to 'webapp'. + The name of the prepare artifact to use, defaults to 'desktop-prepare'. The artifact must contain the following: + webapp.asar - the asar archive of the webapp to embed in the desktop app + electronVersion - the version of electron to use for cache keying @@ -52,7 +52,7 @@ on: The artifact can also contain any additional files which will be applied as overrides to the checkout root before building, for example icons in the `build/` directory to override the app icons. - default: "webapp" + default: "desktop-prepare" test: type: boolean required: false @@ -123,7 +123,7 @@ jobs: id: cache uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 with: - key: ${{ runner.os }}-${{ inputs.arch }}-${{ hashFiles('hakHash', 'electronVersion') }} + key: ${{ runner.os }}-${{ inputs.arch }}-${{ hashFiles('apps/desktop/hakHash', 'apps/desktop/electronVersion') }} path: | apps/desktop/.hak @@ -160,7 +160,7 @@ jobs: - name: Install Deps working-directory: apps/desktop - run: "pnpm install --frozen-lockfile" + run: "pnpm install --frozen-lockfile --filter element-desktop" - name: Insert config snippet if: steps.config.outputs.extra_config != '' diff --git a/apps/desktop/hak/matrix-seshat/check.ts b/apps/desktop/hak/matrix-seshat/check.ts index a62a239c80..e207451832 100644 --- a/apps/desktop/hak/matrix-seshat/check.ts +++ b/apps/desktop/hak/matrix-seshat/check.ts @@ -14,10 +14,7 @@ import type { Tool } from "../../scripts/hak/hakEnv.ts"; import type { DependencyInfo } from "../../scripts/hak/dep.ts"; export default async function (hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise { - const tools: Tool[] = [ - ["rustc", "--version"], - ["python", "--version"], // node-gyp uses python for reasons beyond comprehension - ]; + const tools: Tool[] = [["rustc", "--version"]]; if (hakEnv.isWin()) { tools.push(["perl", "--version"]); // for openssl configure tools.push(["nasm", "-v"]); // for openssl building @@ -28,6 +25,14 @@ export default async function (hakEnv: HakEnv, moduleInfo: DependencyInfo): Prom } await hakEnv.checkTools(tools); + try { + // node-gyp uses python for reasons beyond comprehension + await hakEnv.checkTools([["python", "--version"]]); + } catch { + // try python3 too + await hakEnv.checkTools([["python3", "--version"]]); + } + // Ensure Rust target exists (nb. we avoid depending on rustup) await new Promise((resolve, reject) => { const rustc = childProcess.execFile( diff --git a/apps/desktop/playwright.config.ts b/apps/desktop/playwright.config.ts index 6b3e76347f..bb8e2b52b4 100644 --- a/apps/desktop/playwright.config.ts +++ b/apps/desktop/playwright.config.ts @@ -8,25 +8,9 @@ Please see LICENSE files in the repository root for full details. import { defineConfig } from "@playwright/test"; -const projects = [ - "macos", - "win-x64", - "win-ia32", - "win-arm64", - "linux-amd64-sqlcipher-system", - "linux-amd64-sqlcipher-static", - "linux-arm64-sqlcipher-system", - "linux-arm64-sqlcipher-static", -]; - export default defineConfig({ - // Allows the GitHub action to specify a project name (OS + arch) for the combined report to make sense - // workaround for https://github.com/microsoft/playwright/issues/33521 - projects: process.env.CI - ? projects.map((name) => ({ - name, - })) - : undefined, + projects: [{ name: "Desktop" }], + tag: process.env.PW_TAG ? `@${process.env.PW_TAG}` : undefined, use: { viewport: { width: 1280, height: 720 }, video: "retain-on-failure", diff --git a/apps/desktop/project.json b/apps/desktop/project.json index bf186a1386..cb43a99d61 100644 --- a/apps/desktop/project.json +++ b/apps/desktop/project.json @@ -1,6 +1,7 @@ { "$schema": "../../node_modules/nx/schemas/project-schema.json", - "projectType": "app", + "projectType": "application", + "implicitDependencies": ["element-web"], "root": "apps/desktop", "targets": { "docker:build": { diff --git a/apps/desktop/scripts/branch-match.sh b/apps/desktop/scripts/branch-match.sh deleted file mode 100755 index c42073dfa4..0000000000 --- a/apps/desktop/scripts/branch-match.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash - -# Script for downloading a branch of element-web matching the branch a PR is contributed from - -set -x - -deforg="element-hq" -defrepo="element-web" - -# The PR_NUMBER variable must be set explicitly. -default_org_repo=${GITHUB_REPOSITORY:-"$deforg/$defrepo"} -PR_ORG=${PR_ORG:-${default_org_repo%%/*}} -PR_REPO=${PR_REPO:-${default_org_repo##*/}} - -# A function that clones a branch of a repo based on the org, repo and branch -clone() { - org=$1 - repo=$2 - branch=$3 - if [ -n "$branch" ] - then - echo "Trying to use $org/$repo#$branch" - # Disable auth prompts: https://serverfault.com/a/665959 - GIT_TERMINAL_PROMPT=0 git clone https://github.com/$org/$repo.git $repo --branch "$branch" --depth 1 && exit 0 - fi -} - -echo "Getting info about a PR with number $PR_NUMBER" -apiEndpoint="https://api.github.com/repos/$PR_ORG/$PR_REPO/pulls/$PR_NUMBER" -head=$(curl "$apiEndpoint" | jq -r '.head.label') - -# for forks, $head will be in the format "fork:branch", so we split it by ":" -# into an array. On non-forks, this has the effect of splitting into a single -# element array given ":" shouldn't appear in the head - it'll just be the -# branch name. Based on the results, we clone. -BRANCH_ARRAY=(${head//:/ }) -TRY_ORG=$deforg -TRY_BRANCH=${BRANCH_ARRAY[0]} -if [[ "$head" == *":"* ]]; then - # ... but only match that fork if it's a real fork - if [ "${BRANCH_ARRAY[0]}" != "$PR_ORG" ]; then - TRY_ORG=${BRANCH_ARRAY[0]} - fi - TRY_BRANCH=${BRANCH_ARRAY[1]} -fi -clone "$TRY_ORG" "$defrepo" "$TRY_BRANCH" - -exit 1 diff --git a/knip.ts b/knip.ts index a4b6b5530b..1dec3f5883 100644 --- a/knip.ts +++ b/knip.ts @@ -75,5 +75,8 @@ export default { nx: { config: ["{nx,package,project}.json", "{apps,packages,modules}/**/{package,project}.json"], }, + playwright: { + config: ["playwright.config.ts", "playwright-merge.config.ts"], + }, tags: ["-knipignore"], } satisfies KnipConfig; diff --git a/package.json b/package.json index d90f102a3f..409412a616 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,11 @@ "devDependencies": { "@action-validator/cli": "^0.6.0", "@action-validator/core": "^0.6.0", + "@element-hq/element-web-playwright-common": "catalog:", "@nx-tools/nx-container": "^7.2.1", "@nx/jest": "^22.5.0", "@types/node": "22", + "@playwright/test": "catalog:", "cronstrue": "^3.0.0", "eslint-plugin-matrix-org": "^3.0.0", "husky": "^9.0.0", diff --git a/apps/web/playwright/flaky-reporter.ts b/packages/playwright-common/flaky-reporter.ts similarity index 95% rename from apps/web/playwright/flaky-reporter.ts rename to packages/playwright-common/flaky-reporter.ts index f816d7651e..520c2553a2 100644 --- a/apps/web/playwright/flaky-reporter.ts +++ b/packages/playwright-common/flaky-reporter.ts @@ -26,7 +26,7 @@ type PaginationLinks = { // We see quite a few test flakes which are caused by the app exploding // so we have some magic strings we check the logs for to better track the flake with its cause -const SPECIAL_CASES = { +const SPECIAL_CASES: Record = { "ChunkLoadError": "ChunkLoadError", "Unreachable code should not be executed": "Rust crypto panic", "Out of bounds memory access": "Rust crypto memory error", @@ -37,7 +37,7 @@ class FlakyReporter implements Reporter { public onTestEnd(test: TestCase): void { // Ignores flakes on Dendrite and Pinecone as they have their own flakes we do not track - if (["Dendrite", "Pinecone"].includes(test.parent.project()?.name)) return; + if (["Dendrite", "Pinecone"].includes(test.parent.project()!.name!)) return; let failures = [`${test.location.file.split("playwright/e2e/")[1]}: ${test.title}`]; if (test.outcome() === "flaky") { const timedOutRuns = test.results.filter((result) => result.status === "timedOut"); @@ -46,7 +46,7 @@ class FlakyReporter implements Reporter { ); // If a test failed due to a systemic fault then the test is not flaky, the app is, record it as such. const specialCases = Object.keys(SPECIAL_CASES).filter((log) => - pageLogs.some((attachment) => attachment.name.startsWith("page-") && attachment.body.includes(log)), + pageLogs.some((attachment) => attachment.name.startsWith("page-") && attachment.body?.includes(log)), ); if (specialCases.length > 0) { failures = specialCases.map((specialCase) => SPECIAL_CASES[specialCase]); @@ -56,7 +56,7 @@ class FlakyReporter implements Reporter { if (!this.flakes.has(title)) { this.flakes.set(title, []); } - this.flakes.get(title).push(test); + this.flakes.get(title)!.push(test); } } } @@ -76,8 +76,8 @@ class FlakyReporter implements Reporter { if (!link) return map; const matches = link.matchAll(/(<(?.+?)>; rel="(?.+?)")/g); for (const match of matches) { - const { link, type } = match.groups; - map[type] = link; + const { link, type } = match.groups!; + map[type as keyof PaginationLinks] = link; } return map; } @@ -102,9 +102,9 @@ class FlakyReporter implements Reporter { issues.push(...fetchedIssues); // Get the next link for fetching more results - const linkHeader = issuesResponse.headers.get("Link"); + const linkHeader = issuesResponse.headers.get("Link")!; const parsed = this.parseLinkHeader(linkHeader); - url = parsed.next; + url = parsed.next!; } return issues; } diff --git a/playwright-merge.config.ts b/playwright-merge.config.ts new file mode 100644 index 0000000000..c4498527a3 --- /dev/null +++ b/playwright-merge.config.ts @@ -0,0 +1,20 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + // Playwright only supports merging using a single testDir, specifying web here + // means that some parts of the report for Desktop will be incorrect, but this is minor. + // https://github.com/microsoft/playwright/issues/39855 + testDir: "apps/web/playwright/e2e", + reporter: [ + ["html", { open: "never" }], + ["./packages/playwright-common/flaky-reporter.ts"], + ["@element-hq/element-web-playwright-common/lib/stale-screenshot-reporter.js"], + ], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fbc006869c..1d6874c74f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,12 +135,18 @@ importers: '@action-validator/core': specifier: ^0.6.0 version: 0.6.0 + '@element-hq/element-web-playwright-common': + specifier: 'catalog:' + version: 2.2.7(@element-hq/element-web-module-api@1.12.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.2)(playwright-core@1.58.2) '@nx-tools/nx-container': specifier: ^7.2.1 version: 7.2.1(@nx/devkit@22.5.3(nx@22.5.4))(@nx/js@22.5.3(@babel/traverse@7.29.0)(nx@22.5.4))(dotenv@17.3.1)(nx@22.5.4)(tslib@2.8.1) '@nx/jest': specifier: ^22.5.0 version: 22.5.3(@babel/traverse@7.29.0)(@types/node@18.19.130)(babel-plugin-macros@3.1.0)(nx@22.5.4)(typescript@5.9.3) + '@playwright/test': + specifier: 'catalog:' + version: 1.58.2 '@types/node': specifier: 18.19.130 version: 18.19.130 @@ -179,10 +185,10 @@ importers: version: 5.9.3 vitepress: specifier: ^1.6.4 - version: 1.6.4(@algolia/client-search@5.50.0)(@types/node@18.19.130)(@types/react@19.2.10)(axios@1.13.5)(jwt-decode@4.0.0)(lightningcss@1.32.0)(postcss@8.5.8)(qrcode@1.5.4)(search-insights@2.17.3)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.0)(typescript@5.9.3) + version: 1.6.4(@algolia/client-search@5.50.0)(@types/node@18.19.130)(@types/react@19.2.10)(axios@1.13.5)(jwt-decode@4.0.0)(lightningcss@1.32.0)(postcss@8.5.8)(qrcode@1.5.4)(react@19.2.4)(search-insights@2.17.3)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.0)(typescript@5.9.3) vitepress-plugin-mermaid: specifier: ^2.0.17 - version: 2.0.17(mermaid@11.13.0)(vitepress@1.6.4(@algolia/client-search@5.50.0)(@types/node@18.19.130)(@types/react@19.2.10)(axios@1.13.5)(jwt-decode@4.0.0)(lightningcss@1.32.0)(postcss@8.5.8)(qrcode@1.5.4)(search-insights@2.17.3)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.0)(typescript@5.9.3)) + version: 2.0.17(mermaid@11.13.0)(vitepress@1.6.4(@algolia/client-search@5.50.0)(@types/node@18.19.130)(@types/react@19.2.10)(axios@1.13.5)(jwt-decode@4.0.0)(lightningcss@1.32.0)(postcss@8.5.8)(qrcode@1.5.4)(react@19.2.4)(search-insights@2.17.3)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.0)(typescript@5.9.3)) yaml: specifier: 2.8.3 version: 2.8.3 @@ -11457,6 +11463,10 @@ packages: sanitize-html@2.17.1: resolution: {integrity: sha512-ehFCW+q1a4CSOWRAdX97BX/6/PDEkCqw7/0JXZAGQV57FQB3YOkTa/rrzHPeJ+Aghy4vZAFfWMYyfxIiB7F/gw==} + sax@1.4.4: + resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==} + engines: {node: '>=11.0.0'} + sax@1.6.0: resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} engines: {node: '>=11.0.0'} @@ -14541,9 +14551,9 @@ snapshots: '@docsearch/css@3.8.2': {} - '@docsearch/js@3.8.2(@algolia/client-search@5.50.0)(@types/react@19.2.10)(search-insights@2.17.3)': + '@docsearch/js@3.8.2(@algolia/client-search@5.50.0)(@types/react@19.2.10)(react@19.2.4)(search-insights@2.17.3)': dependencies: - '@docsearch/react': 3.8.2(@algolia/client-search@5.50.0)(@types/react@19.2.10)(search-insights@2.17.3) + '@docsearch/react': 3.8.2(@algolia/client-search@5.50.0)(@types/react@19.2.10)(react@19.2.4)(search-insights@2.17.3) preact: 10.28.3 transitivePeerDependencies: - '@algolia/client-search' @@ -14552,7 +14562,7 @@ snapshots: - react-dom - search-insights - '@docsearch/react@3.8.2(@algolia/client-search@5.50.0)(@types/react@19.2.10)(search-insights@2.17.3)': + '@docsearch/react@3.8.2(@algolia/client-search@5.50.0)(@types/react@19.2.10)(react@19.2.4)(search-insights@2.17.3)': dependencies: '@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.50.0)(algoliasearch@5.50.0)(search-insights@2.17.3) '@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.50.0)(algoliasearch@5.50.0) @@ -14560,6 +14570,7 @@ snapshots: algoliasearch: 5.50.0 optionalDependencies: '@types/react': 19.2.10 + react: 19.2.4 search-insights: 2.17.3 transitivePeerDependencies: - '@algolia/client-search' @@ -19317,7 +19328,7 @@ snapshots: builder-util-runtime@9.5.1: dependencies: debug: 4.4.3 - sax: 1.6.0 + sax: 1.4.4 transitivePeerDependencies: - supports-color @@ -25261,6 +25272,8 @@ snapshots: parse-srcset: 1.0.2 postcss: 8.5.8 + sax@1.4.4: {} + sax@1.6.0: {} saxes@6.0.0: @@ -26687,17 +26700,17 @@ snapshots: tsx: 4.21.0 yaml: 2.8.3 - vitepress-plugin-mermaid@2.0.17(mermaid@11.13.0)(vitepress@1.6.4(@algolia/client-search@5.50.0)(@types/node@18.19.130)(@types/react@19.2.10)(axios@1.13.5)(jwt-decode@4.0.0)(lightningcss@1.32.0)(postcss@8.5.8)(qrcode@1.5.4)(search-insights@2.17.3)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.0)(typescript@5.9.3)): + vitepress-plugin-mermaid@2.0.17(mermaid@11.13.0)(vitepress@1.6.4(@algolia/client-search@5.50.0)(@types/node@18.19.130)(@types/react@19.2.10)(axios@1.13.5)(jwt-decode@4.0.0)(lightningcss@1.32.0)(postcss@8.5.8)(qrcode@1.5.4)(react@19.2.4)(search-insights@2.17.3)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.0)(typescript@5.9.3)): dependencies: mermaid: 11.13.0 - vitepress: 1.6.4(@algolia/client-search@5.50.0)(@types/node@18.19.130)(@types/react@19.2.10)(axios@1.13.5)(jwt-decode@4.0.0)(lightningcss@1.32.0)(postcss@8.5.8)(qrcode@1.5.4)(search-insights@2.17.3)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.0)(typescript@5.9.3) + vitepress: 1.6.4(@algolia/client-search@5.50.0)(@types/node@18.19.130)(@types/react@19.2.10)(axios@1.13.5)(jwt-decode@4.0.0)(lightningcss@1.32.0)(postcss@8.5.8)(qrcode@1.5.4)(react@19.2.4)(search-insights@2.17.3)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.0)(typescript@5.9.3) optionalDependencies: '@mermaid-js/mermaid-mindmap': 9.3.0 - vitepress@1.6.4(@algolia/client-search@5.50.0)(@types/node@18.19.130)(@types/react@19.2.10)(axios@1.13.5)(jwt-decode@4.0.0)(lightningcss@1.32.0)(postcss@8.5.8)(qrcode@1.5.4)(search-insights@2.17.3)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.0)(typescript@5.9.3): + vitepress@1.6.4(@algolia/client-search@5.50.0)(@types/node@18.19.130)(@types/react@19.2.10)(axios@1.13.5)(jwt-decode@4.0.0)(lightningcss@1.32.0)(postcss@8.5.8)(qrcode@1.5.4)(react@19.2.4)(search-insights@2.17.3)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.0)(typescript@5.9.3): dependencies: '@docsearch/css': 3.8.2 - '@docsearch/js': 3.8.2(@algolia/client-search@5.50.0)(@types/react@19.2.10)(search-insights@2.17.3) + '@docsearch/js': 3.8.2(@algolia/client-search@5.50.0)(@types/react@19.2.10)(react@19.2.4)(search-insights@2.17.3) '@iconify-json/simple-icons': 1.2.75 '@shikijs/core': 2.5.0 '@shikijs/transformers': 2.5.0 diff --git a/sonar-project.properties b/sonar-project.properties index 6ac82b9ffa..e3040eaeae 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -18,11 +18,13 @@ sonar.coverage.exclusions=\ apps/web/scripts/**/*,\ apps/desktop/scripts/**/*,\ apps/desktop/playwright/**/*,\ + apps/desktop/hak/**/*,\ scripts/**/*,\ apps/web/src/components/views/dialogs/devtools/**/*,\ apps/web/src/utils/SessionLock.ts,\ apps/web/src/**/*.d.ts,\ apps/web/src/vector/mobile_guide/**/*,\ packages/shared-components/src/test/**/*,\ - packages/shared-components/src/**/*.stories.tsx + packages/shared-components/src/**/*.stories.tsx,\ + packages/playwright-common/**/* sonar.testExecutionReportPaths=apps/web/coverage/jest-sonar-report.xml