Merge remote-tracking branch 'origin/develop' into hs/enable-profile-updates
7
.github/CODEOWNERS
vendored
@ -4,12 +4,13 @@
|
||||
/pnpm-lock.yaml @element-hq/element-web-team
|
||||
|
||||
/apps/web/src/SecurityManager.ts @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/test/SecurityManager-test.ts @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/test/unit-tests/SecurityManager-test.ts @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/src/async-components/views/dialogs/security/ @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/test/unit-tests/async-components/dialogs/security/ @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/src/components/views/dialogs/security/ @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/test/components/views/dialogs/security/ @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/test/unit-tests/components/views/dialogs/security/ @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/src/stores/SetupEncryptionStore.ts @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/test/stores/SetupEncryptionStore-test.ts @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/test/unit-tests/stores/SetupEncryptionStore-test.ts @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/src/components/views/settings/tabs/user/EncryptionUserSettingsTab.tsx @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/src/components/views/settings/encryption/ @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/test/unit-tests/components/views/settings/encryption/ @element-hq/element-crypto-web-reviewers
|
||||
|
||||
@ -31,7 +31,9 @@ runs:
|
||||
|
||||
- name: Move webapp to out-file-path
|
||||
shell: bash
|
||||
run: mv ${{ runner.temp }}/download-verify-element-tarball/webapp ${{ inputs.out-file-path }}
|
||||
run: mv ${{ runner.temp }}/download-verify-element-tarball/webapp "$OUT_PATH"
|
||||
env:
|
||||
OUT_PATH: ${{ inputs.out-file-path }}
|
||||
|
||||
- name: Clean up temp directory
|
||||
shell: bash
|
||||
|
||||
49
.github/actions/setup-playwright/action.yml
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
name: Setup playwright
|
||||
description: Installs playwright browsers and sets up a cache
|
||||
inputs:
|
||||
needs-webkit:
|
||||
description: Whether to install the additional dependencies for webkit
|
||||
required: false
|
||||
default: "false"
|
||||
write-cache:
|
||||
description: Whether to write the cache back
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Calculate cache key
|
||||
id: key
|
||||
run: |
|
||||
PW_VERSION=$(pnpm --silent -- playwright --version | awk '{print $2}')
|
||||
echo "key=${PREFIX}-playwright-${PW_VERSION}" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
env:
|
||||
PREFIX: ${{ runner.os }}-${{ runner.arch }}
|
||||
|
||||
- name: Cache playwright binaries
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
if: inputs.write-cache == 'true'
|
||||
id: cache
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: ${{ steps.key.outputs.key }}
|
||||
|
||||
# When running in merge queue only restore the cache, never write it
|
||||
- name: Restore playwright binaries cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
if: inputs.write-cache != 'true'
|
||||
id: cache-restore
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: ${{ steps.key.outputs.key }}
|
||||
|
||||
- name: Install Playwright browsers
|
||||
if: (steps.cache.outputs.cache-hit || steps.cache-restore.outputs.cache-hit) != 'true'
|
||||
shell: bash
|
||||
run: pnpm playwright install --with-deps
|
||||
|
||||
# Some WebKit dependencies seem to lay outside the cache and will need to be installed separately
|
||||
- name: Install system dependencies for WebKit
|
||||
if: inputs.needs-webkit == 'true' && (steps.cache.outputs.cache-hit || steps.cache-restore.outputs.cache-hit) == 'true'
|
||||
shell: bash
|
||||
run: pnpm playwright install-deps webkit
|
||||
19
.github/renovate.json
vendored
@ -2,6 +2,14 @@
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["github>matrix-org/renovate-config-element-web"],
|
||||
"postUpdateOptions": ["pnpmDedupe"],
|
||||
"packageRules": [
|
||||
{
|
||||
"groupName": "testcontainers docker digests",
|
||||
"groupSlug": "testcontainers-docker",
|
||||
"matchDepTypes": ["testcontainers-docker"],
|
||||
"matchPackageNames": ["*"]
|
||||
}
|
||||
],
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
@ -9,7 +17,16 @@
|
||||
"versioningTemplate": "loose",
|
||||
"description": "Update testcontainers docker digests",
|
||||
"managerFilePatterns": ["**/testcontainers/*.ts"],
|
||||
"matchStrings": ["\\s+\"(?<depName>[^@]+):(?<currentValue>[^@]+)@(?<currentDigest>sha256:[a-f0-9]+)\""]
|
||||
"matchStrings": ["\\s+\"(?<depName>[^@]+):(?<currentValue>[^@]+)@(?<currentDigest>sha256:[a-f0-9]+)\""],
|
||||
"depTypeTemplate": "testcontainers-docker"
|
||||
},
|
||||
{
|
||||
"customType": "jsonata",
|
||||
"managerFilePatterns": ["/(^|/)package\\.json$/"],
|
||||
"fileFormat": "json",
|
||||
"matchStrings": ["hakDependencies.$each(function($v, $k) { { 'packageName': $k, 'currentValue': $v } })"],
|
||||
"datasourceTemplate": "npm",
|
||||
"depTypeTemplate": "hak"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,9 +16,8 @@ on:
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
push:
|
||||
branches: [develop, master]
|
||||
repository_dispatch:
|
||||
types: [element-web-notify]
|
||||
# We do not build on push to develop as the merge_group check handles that
|
||||
branches: [staging, master]
|
||||
|
||||
# support triggering from other workflows
|
||||
workflow_call:
|
||||
@ -35,20 +40,22 @@ 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:
|
||||
num-runners: ${{ env.NUM_RUNNERS }}
|
||||
runners-matrix: ${{ steps.runner-vars.outputs.matrix }}
|
||||
# Skip pull_request runs on renovate PRs to speed up CI time, delegating to the full run in merge queue
|
||||
skip: ${{ inputs.skip || (github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')) }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
@ -79,7 +86,7 @@ jobs:
|
||||
run: VERSION=$(scripts/get-version-from-git.sh) pnpm run build
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: webapp
|
||||
path: apps/web/webapp
|
||||
@ -87,17 +94,17 @@ jobs:
|
||||
|
||||
- name: Calculate runner variables
|
||||
id: runner-vars
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
||||
with:
|
||||
script: |
|
||||
const numRunners = parseInt(process.env.NUM_RUNNERS, 10);
|
||||
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
|
||||
if: inputs.skip != true
|
||||
playwright_ew:
|
||||
name: "Run Tests [${{ matrix.project }}] ${{ matrix.runner }}/${{ needs.build_ew.outputs.num-runners }}"
|
||||
needs: build_ew
|
||||
if: needs.build_ew.outputs.skip == 'false'
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
actions: read
|
||||
@ -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
|
||||
@ -148,105 +155,147 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Get installed Playwright version
|
||||
id: playwright
|
||||
run: echo "version=$(pnpm --silent -- playwright --version | awk '{print $2}')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache playwright binaries
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
id: playwright-cache
|
||||
- name: Setup playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright.outputs.version }}
|
||||
|
||||
- name: Install Playwright browsers
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
||||
working-directory: apps/web
|
||||
run: pnpm playwright install --with-deps --no-shell
|
||||
|
||||
- name: Install system dependencies for WebKit
|
||||
# Some WebKit dependencies seem to lay outside the cache and will need to be installed separately
|
||||
if: matrix.project == 'WebKit' && steps.playwright-cache.outputs.cache-hit == 'true'
|
||||
working-directory: apps/web
|
||||
run: pnpm playwright install-deps webkit
|
||||
needs-webkit: ${{ matrix.project == 'WebKit' }}
|
||||
write-cache: ${{ github.event_name != 'merge_group' }}
|
||||
|
||||
# We skip tests tagged with @mergequeue when running on PRs, but run them in MQ and everywhere else
|
||||
- name: Run Playwright tests
|
||||
working-directory: apps/web
|
||||
run: |
|
||||
pnpm playwright test \
|
||||
pnpm test:playwright \
|
||||
--shard "$SHARD" \
|
||||
--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
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # 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
|
||||
if: inputs.skip != true && github.event_name == 'merge_group'
|
||||
needs: build_ew
|
||||
if: needs.build_ew.outputs.skip == 'false' && 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
|
||||
reporter: blob
|
||||
|
||||
prepare_ed:
|
||||
name: "Prepare Element Desktop"
|
||||
uses: ./.github/workflows/build_desktop_prepare.yaml
|
||||
needs: build_ew
|
||||
if: needs.build_ew.outputs.skip == 'false'
|
||||
permissions:
|
||||
contents: read
|
||||
with:
|
||||
config: ${{ (github.event.pull_request.base.ref || github.ref_name) == 'develop' && 'element.io/nightly' || 'element.io/release' }}
|
||||
version: ${{ case((github.event.pull_request.base.ref || github.ref_name) == 'develop' || github.event_name == 'merge_group', 'develop', '') }}
|
||||
webapp-artifact: webapp
|
||||
|
||||
build_ed_windows:
|
||||
needs: prepare_ed
|
||||
name: "Desktop Windows"
|
||||
uses: ./.github/workflows/build_desktop_windows.yaml
|
||||
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
|
||||
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
|
||||
with:
|
||||
blob_report: true
|
||||
|
||||
complete:
|
||||
name: end-to-end-tests
|
||||
needs:
|
||||
- playwright
|
||||
- build_ew
|
||||
- playwright_ew
|
||||
- downstream-modules
|
||||
- prepare_ed
|
||||
- build_ed_windows
|
||||
- build_ed_linux
|
||||
- build_ed_macos
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
if: inputs.skip != true
|
||||
if: needs.build_ew.outputs.skip == 'false'
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: element-hq/element-web
|
||||
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
if: inputs.skip != true
|
||||
if: needs.build_ew.outputs.skip == 'false'
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
if: inputs.skip != true
|
||||
if: needs.build_ew.outputs.skip == 'false'
|
||||
with:
|
||||
cache: "pnpm"
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Install dependencies
|
||||
if: inputs.skip != true
|
||||
if: needs.build_ew.outputs.skip == 'false'
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Download blob reports from GitHub Actions Artifacts
|
||||
if: inputs.skip != true
|
||||
if: needs.build_ew.outputs.skip == 'false'
|
||||
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
|
||||
if: needs.build_ew.outputs.skip == 'false'
|
||||
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
|
||||
if: always() && inputs.skip != true
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
if: always() && needs.build_ew.outputs.skip == 'false'
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: html-report
|
||||
path: apps/web/playwright-report
|
||||
path: playwright-report
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
|
||||
2
.github/workflows/build.yml
vendored
@ -69,7 +69,7 @@ jobs:
|
||||
run: VERSION=$(scripts/get-version-from-git.sh) pnpm run build
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: webapp-${{ matrix.image }}
|
||||
path: apps/web/webapp
|
||||
|
||||
2
.github/workflows/build_debian.yaml
vendored
@ -69,7 +69,7 @@ jobs:
|
||||
dpkg-gencontrol -v"$VERSION" -ldebian/tmp/DEBIAN/changelog
|
||||
dpkg-deb -Zxz --root-owner-group --build debian/tmp element-web.deb
|
||||
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: element-web.deb
|
||||
path: apps/web/element-web.deb
|
||||
|
||||
@ -212,7 +212,7 @@ jobs:
|
||||
|
||||
- name: Stash packages.element.io
|
||||
if: needs.prepare.outputs.deploy == 'false'
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: packages.element.io
|
||||
path: packages.element.io
|
||||
@ -250,7 +250,7 @@ jobs:
|
||||
|
||||
- name: Stash debs
|
||||
if: needs.prepare.outputs.deploy == 'false' && needs.linux.result == 'success'
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: debs
|
||||
path: |
|
||||
@ -289,7 +289,7 @@ jobs:
|
||||
id-token: write # This is required for requesting the JWT
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6
|
||||
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6
|
||||
with:
|
||||
role-to-assume: arn:aws:iam::264135176173:role/Push-ElementDesktop-MSI
|
||||
role-session-name: githubaction-run-${{ github.run_id }}
|
||||
|
||||
89
.github/workflows/build_desktop_and_test.yaml
vendored
@ -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
|
||||
30
.github/workflows/build_desktop_linux.yaml
vendored
@ -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"
|
||||
}
|
||||
@ -118,9 +104,9 @@ jobs:
|
||||
|
||||
- name: Cache .hak
|
||||
id: cache
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # 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
|
||||
@ -147,7 +133,7 @@ jobs:
|
||||
|
||||
# This allows contributors to test changes to the dockerbuild image within a pull request
|
||||
- name: Build docker image
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
if: steps.changed_files.outputs.any_modified == 'true'
|
||||
with:
|
||||
file: apps/desktop/dockerbuild/Dockerfile
|
||||
@ -230,7 +216,7 @@ jobs:
|
||||
|
||||
# We exclude *-unpacked as it loses permissions and the tarball contains it with correct permissions
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: ${{ inputs.artifact-prefix }}linux-${{ inputs.arch }}-sqlcipher-${{ inputs.sqlcipher }}
|
||||
path: |
|
||||
|
||||
43
.github/workflows/build_desktop_macos.yaml
vendored
@ -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
|
||||
@ -90,9 +90,9 @@ jobs:
|
||||
|
||||
- name: Cache .hak
|
||||
id: cache
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ hashFiles('hakHash', 'electronVersion') }}
|
||||
key: ${{ runner.os }}-${{ hashFiles('apps/desktop/hakHash', 'apps/desktop/electronVersion') }}
|
||||
path: |
|
||||
apps/desktop/.hak
|
||||
|
||||
@ -121,28 +121,29 @@ 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'
|
||||
working-directory: apps/desktop
|
||||
run: pnpm run build:native:universal
|
||||
|
||||
- name: "Build App"
|
||||
# We split these because electron-builder gets upset if we set CSC_LINK even to an empty string
|
||||
- name: "[Signed] Build App"
|
||||
if: inputs.sign != ''
|
||||
working-directory: apps/desktop
|
||||
run: pnpm run build:universal --publish never -m ${TARGETS}
|
||||
run: |
|
||||
pnpm run build:universal --publish never -m ${TARGETS}
|
||||
env:
|
||||
# Code signing parameters
|
||||
CSC_IDENTITY_AUTO_DISCOVERY: ${{ inputs.sign != '' }}
|
||||
APPLE_TEAM_ID: ${{ case(inputs.sign != '', vars.APPLE_TEAM_ID, '') }}
|
||||
APPLE_ID: ${{ case(inputs.sign != '', secrets.APPLE_ID, '') }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ case(inputs.sign != '', secrets.APPLE_ID_PASSWORD, '') }}
|
||||
CSC_KEY_PASSWORD: ${{ case(inputs.sign != '', secrets.APPLE_CSC_KEY_PASSWORD, '') }}
|
||||
CSC_LINK: ${{ case(inputs.sign != '', secrets.APPLE_CSC_LINK, '') }}
|
||||
APPLE_TEAM_ID: ${{ vars.APPLE_TEAM_ID }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CSC_KEY_PASSWORD }}
|
||||
CSC_LINK: ${{ secrets.APPLE_CSC_LINK }}
|
||||
VARIANT_PATH: variant.json
|
||||
TARGETS: ${{ inputs.targets }}
|
||||
# Only set for Nightly builds
|
||||
VERSION: ${{ inputs.version }}
|
||||
TARGETS: ${{ inputs.targets }}
|
||||
|
||||
- name: Check app was signed & notarised successfully
|
||||
if: inputs.sign != ''
|
||||
@ -153,6 +154,16 @@ jobs:
|
||||
spctl -a -vvv -t install /Volumes/Element/*.app
|
||||
hdiutil detach /Volumes/Element
|
||||
|
||||
- name: "[Unsigned] Build App"
|
||||
if: inputs.sign == ''
|
||||
working-directory: apps/desktop
|
||||
run: |
|
||||
pnpm run build:universal --publish never -m ${TARGETS}
|
||||
env:
|
||||
CSC_IDENTITY_AUTO_DISCOVERY: false
|
||||
VARIANT_PATH: variant.json
|
||||
TARGETS: ${{ inputs.targets }}
|
||||
|
||||
- name: Generate releases.json
|
||||
if: inputs.base-url
|
||||
working-directory: apps/desktop
|
||||
@ -183,7 +194,7 @@ jobs:
|
||||
|
||||
# We exclude mac-universal as the unpacked app takes forever to upload and zip and dmg already contains it
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: ${{ inputs.artifact-prefix }}macos
|
||||
path: |
|
||||
|
||||
38
.github/workflows/build_desktop_prepare.yaml
vendored
@ -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:
|
||||
@ -57,6 +56,7 @@ jobs:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: element-hq/element-web
|
||||
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
@ -66,28 +66,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:
|
||||
@ -187,9 +185,9 @@ jobs:
|
||||
env:
|
||||
NIGHTLY_VERSION: ${{ steps.versions.outputs.nightly }}
|
||||
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: webapp
|
||||
name: desktop-prepare
|
||||
retention-days: 1
|
||||
path: |
|
||||
apps/desktop/webapp.asar
|
||||
|
||||
24
.github/workflows/build_desktop_test.yaml
vendored
@ -38,7 +38,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
repository: ${{ github.repository == 'element-hq/element-web-pro' && 'element-hq/element-web' || github.repository }}
|
||||
repository: element-hq/element-web
|
||||
persist-credentials: false
|
||||
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
@ -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,25 +84,34 @@ 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
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
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
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: ${{ inputs.artifact }}-test
|
||||
path: apps/desktop/playwright-report
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
|
||||
12
.github/workflows/build_desktop_windows.yaml
vendored
@ -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
|
||||
@ -121,9 +121,9 @@ jobs:
|
||||
|
||||
- name: Cache .hak
|
||||
id: cache
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # 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 != ''
|
||||
@ -274,7 +274,7 @@ jobs:
|
||||
| ForEach-Object -Process {. $env:SIGNTOOL_PATH verify /pa $_.FullName; if(!$?) { throw }}
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: ${{ inputs.artifact-prefix }}win-${{ inputs.arch }}
|
||||
path: |
|
||||
|
||||
4
.github/workflows/build_develop.yml
vendored
@ -60,7 +60,7 @@ jobs:
|
||||
- run: mv dist/element-*.tar.gz dist/develop.tar.gz
|
||||
working-directory: apps/web
|
||||
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: webapp
|
||||
path: apps/web/dist/develop.tar.gz
|
||||
@ -111,7 +111,7 @@ jobs:
|
||||
running-workflow-name: "Build & Deploy develop.element.io"
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
wait-interval: 10
|
||||
check-regexp: ^((?!SonarCloud|SonarQube|issue|board|label|Release|prepare|GitHub Pages|Upload).)*$
|
||||
check-regexp: ^((?!SonarCloud|SonarQube|issue|board|label|Release|prepare|GitHub Pages|Upload|Netlify).)*$
|
||||
|
||||
# We keep the latest develop.tar.gz on R2 instead of relying on the github artifact uploaded earlier
|
||||
# as the expires after 24h and requires auth to download.
|
||||
|
||||
9
.github/workflows/cd.yaml
vendored
@ -2,6 +2,13 @@ name: CD # Continuous Delivery
|
||||
on:
|
||||
push:
|
||||
branches: [master, staging, develop]
|
||||
paths:
|
||||
- "**/Dockerfile"
|
||||
- "**/dockerbuild"
|
||||
- "**/docker"
|
||||
- "**/docker-*"
|
||||
- "pnpm-lock.yaml"
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref_name }}
|
||||
|
||||
permissions: {}
|
||||
@ -43,7 +50,7 @@ jobs:
|
||||
run: "pnpm install --frozen-lockfile"
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
|
||||
10
.github/workflows/docker.yaml
vendored
@ -39,7 +39,7 @@ jobs:
|
||||
|
||||
- name: Build and load
|
||||
id: test-build
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
with:
|
||||
context: .
|
||||
file: apps/web/Dockerfile
|
||||
@ -97,14 +97,14 @@ jobs:
|
||||
latest=${{ contains(github.ref_name, '-rc.') && 'false' || 'auto' }}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
registry: ghcr.io
|
||||
@ -140,7 +140,7 @@ jobs:
|
||||
services/web-repositories/secret/data/oci.element.io password | OCI_PASSWORD ;
|
||||
|
||||
- name: Login to oci.element.io Registry
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
registry: oci-push.vpn.infra.element.io
|
||||
@ -149,7 +149,7 @@ jobs:
|
||||
|
||||
- name: Build and push
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
context: .
|
||||
|
||||
2
.github/workflows/docs.yml
vendored
@ -36,7 +36,7 @@ jobs:
|
||||
run: pnpm run docs:build
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4
|
||||
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5
|
||||
with:
|
||||
path: ./docs/.vitepress/dist
|
||||
|
||||
|
||||
4
.github/workflows/issue_closed.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
||||
name: Tidy closed issues
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
||||
id: main
|
||||
with:
|
||||
# PAT needed as the GITHUB_TOKEN won't be able to see cross-references from other orgs (matrix-org)
|
||||
@ -142,7 +142,7 @@ jobs:
|
||||
});
|
||||
}
|
||||
}
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
||||
name: Close duplicate as Not Planned
|
||||
if: steps.main.outputs.closeAsNotPlanned
|
||||
with:
|
||||
|
||||
29
.github/workflows/merge-queue.yaml
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
# Tweaks the behaviour of Merge Queue to skip certain checks
|
||||
name: Merge Queue tweaks
|
||||
on:
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
statuses: write
|
||||
steps:
|
||||
# This is only needed as license/cla at time of writing seems to be extraordinarily flaky
|
||||
# and Github doesn't support conditional checks between PR & merge queue.
|
||||
# This is fine to do as a PR won't make it to merge queue until it has license/cla passing.
|
||||
- name: Skip license/cla on merge queues
|
||||
uses: guibranco/github-status-action-v2@9bfa8773cdbdc6c185747fd43cd7faa9d7c32f09
|
||||
with:
|
||||
authToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
state: success
|
||||
context: license/cla
|
||||
sha: ${{ github.sha }}
|
||||
target_url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
@ -1,6 +1,16 @@
|
||||
name: Publish shared component npm package
|
||||
name: Publish npm package
|
||||
run-name: Publish ${{ inputs.package }}
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
package:
|
||||
description: Which package to release
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- playwright-common
|
||||
- shared-components
|
||||
- module-api
|
||||
|
||||
concurrency: release
|
||||
jobs:
|
||||
@ -29,10 +39,9 @@ jobs:
|
||||
- name: Update npm
|
||||
run: npm install -g npm@latest
|
||||
|
||||
# Need to setup element web too as it needs the translations
|
||||
- name: 🛠️ Setup EW
|
||||
- name: 🛠️ Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: 🚀 Publish to npm
|
||||
working-directory: packages/shared-components
|
||||
working-directory: packages/${{ inputs.package }}
|
||||
run: npm publish --access public --provenance
|
||||
@ -8,7 +8,7 @@ jobs:
|
||||
name: Check PR base branch
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
||||
with:
|
||||
script: |
|
||||
const baseBranch = context.payload.pull_request.base.ref;
|
||||
|
||||
3
.github/workflows/release.yml
vendored
@ -30,7 +30,8 @@ jobs:
|
||||
asset-path: dist/*.tar.gz
|
||||
expected-asset-count: 3
|
||||
# Desktop has no dist script so we only target web here
|
||||
dir: apps/web
|
||||
dist-dir: apps/web
|
||||
version-dirs: apps/web apps/desktop
|
||||
|
||||
check:
|
||||
name: Post release checks
|
||||
|
||||
@ -31,7 +31,7 @@ jobs:
|
||||
working-directory: packages/shared-components
|
||||
run: pnpm build:storybook
|
||||
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: shared-components-storybook
|
||||
path: packages/shared-components/storybook-static
|
||||
|
||||
@ -2,7 +2,9 @@
|
||||
# It uploads the received images and diffs to netlify, printing the URLs to the console
|
||||
name: Upload Shared Component Visual Test Diffs
|
||||
on:
|
||||
workflow_run:
|
||||
# Privilege escalation necessary to deploy to Netlify
|
||||
# 🚨 We must not execute any checked out code here.
|
||||
workflow_run: # zizmor: ignore[dangerous-triggers]
|
||||
workflows: ["Shared Component Visual Tests"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
@ -36,22 +36,10 @@ jobs:
|
||||
working-directory: packages/shared-components
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Get installed Playwright version
|
||||
working-directory: packages/shared-components
|
||||
id: playwright
|
||||
run: echo "version=$(pnpm list @playwright/test --depth=0 --json | jq -r '.[].devDependencies["@playwright/test"].version')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache playwright binaries
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
id: playwright-cache
|
||||
- name: Setup playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright.outputs.version }}-onlyshell
|
||||
|
||||
- name: Install Playwright browsers
|
||||
working-directory: packages/shared-components
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
||||
run: "pnpm playwright install --with-deps --only-shell"
|
||||
write-cache: ${{ github.event_name != 'merge_group' }}
|
||||
|
||||
- name: Run Visual tests
|
||||
working-directory: packages/shared-components
|
||||
@ -65,7 +53,7 @@ jobs:
|
||||
|
||||
- name: Upload received images & diffs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: received-images
|
||||
path: packages/shared-components/__vis__/linux
|
||||
|
||||
4
.github/workflows/sonarqube.yml
vendored
@ -1,6 +1,8 @@
|
||||
name: SonarQube
|
||||
on:
|
||||
workflow_run:
|
||||
# Privilege escalation necessary to call upon SonarCloud
|
||||
# 🚨 We must not execute any checked out code here.
|
||||
workflow_run: # zizmor: ignore[dangerous-triggers]
|
||||
workflows: ["Tests"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
10
.github/workflows/static_analysis.yaml
vendored
@ -5,8 +5,6 @@ on:
|
||||
branches: [develop, master]
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
repository_dispatch:
|
||||
types: [element-web-notify]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
@ -89,7 +87,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run zizmor
|
||||
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
|
||||
uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
|
||||
|
||||
i18n:
|
||||
strategy:
|
||||
@ -127,7 +125,9 @@ jobs:
|
||||
# Dummy job to simplify branch protections
|
||||
ci:
|
||||
name: Static Analysis
|
||||
needs: [lint, i18n]
|
||||
needs: [lint, i18n, zizmor]
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- run: echo "Ok"
|
||||
- if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
|
||||
run: exit 1
|
||||
|
||||
55
.github/workflows/tests.yml
vendored
@ -5,8 +5,6 @@ on:
|
||||
types: [checks_requested]
|
||||
push:
|
||||
branches: [develop, master]
|
||||
repository_dispatch:
|
||||
types: [element-web-notify]
|
||||
workflow_call:
|
||||
inputs:
|
||||
disable_coverage:
|
||||
@ -58,7 +56,7 @@ jobs:
|
||||
JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }}
|
||||
|
||||
- name: Jest Cache
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
with:
|
||||
path: /tmp/jest_cache
|
||||
key: ${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
@ -93,7 +91,7 @@ jobs:
|
||||
|
||||
- name: Upload Artifact
|
||||
if: env.ENABLE_COVERAGE == 'true'
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: coverage-${{ matrix.runner }}
|
||||
path: |
|
||||
@ -102,7 +100,7 @@ jobs:
|
||||
|
||||
complete:
|
||||
name: jest-tests
|
||||
needs: [jest_ew, vitest_sc]
|
||||
needs: [jest_ew, vitest]
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
@ -122,8 +120,13 @@ jobs:
|
||||
sha: ${{ github.sha }}
|
||||
target_url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
vitest_sc:
|
||||
name: Vitest (Shared Components)
|
||||
vitest:
|
||||
name: Vitest
|
||||
strategy:
|
||||
matrix:
|
||||
package:
|
||||
- shared-components
|
||||
- module-api
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@ -139,44 +142,32 @@ jobs:
|
||||
node-version: "lts/*"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install Shared Component Deps
|
||||
working-directory: "packages/shared-components"
|
||||
- name: Install Deps
|
||||
run: "pnpm install"
|
||||
|
||||
- name: Cache storybook & vitest
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
with:
|
||||
path: |
|
||||
packages/shared-components/node_modules/.cache
|
||||
packages/shared-components/node_modules/.vite/vitest
|
||||
packages/${{ matrix.package }}/node_modules/.cache
|
||||
packages/${{ matrix.package }}/node_modules/.vite/vitest
|
||||
key: ${{ hashFiles('pnpm-lock.yaml') }}
|
||||
|
||||
- name: Get installed Playwright version
|
||||
working-directory: packages/shared-components
|
||||
id: playwright
|
||||
run: echo "version=$(pnpm list @playwright/test --depth=0 --json | jq -r '.[].devDependencies["@playwright/test"].version')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache playwright binaries
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
id: playwright-cache
|
||||
- name: Setup playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
if: matrix.package == 'shared-components'
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright.outputs.version }}-onlyshell
|
||||
|
||||
- name: Install Playwright browsers
|
||||
working-directory: packages/shared-components
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
||||
run: "pnpm playwright install --with-deps --only-shell"
|
||||
write-cache: ${{ github.event_name != 'merge_group' }}
|
||||
|
||||
- name: Run tests
|
||||
working-directory: "packages/shared-components"
|
||||
working-directory: "packages/${{ matrix.package }}"
|
||||
run: pnpm test:unit --coverage=$ENABLE_COVERAGE
|
||||
|
||||
- name: Upload Artifact
|
||||
if: env.ENABLE_COVERAGE == 'true'
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: coverage-sharedcomponents
|
||||
name: coverage-${{ matrix.package }}
|
||||
path: |
|
||||
packages/shared-components/coverage
|
||||
!packages/shared-components/coverage/lcov-report
|
||||
packages/${{ matrix.package }}/coverage
|
||||
!packages/${{ matrix.package }}/coverage/lcov-report
|
||||
|
||||
4
.github/workflows/triage-labelled.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
||||
contains(github.event.issue.labels.*.name, 'A-Rich-Text-Editor') ||
|
||||
contains(github.event.issue.labels.*.name, 'A-Element-Call')
|
||||
steps:
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.addLabels({
|
||||
@ -44,7 +44,7 @@ jobs:
|
||||
contains(github.event.issue.labels.*.name, 'good first issue') ||
|
||||
contains(github.event.issue.labels.*.name, 'Hacktoberfest')
|
||||
steps:
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.addLabels({
|
||||
|
||||
2
.github/workflows/triage-unlabelled.yml
vendored
@ -43,7 +43,7 @@ jobs:
|
||||
contains(github.event.issue.labels.*.name, 'A-Element-Call')) &&
|
||||
contains(github.event.issue.labels.*.name, 'Z-Labs')
|
||||
steps:
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.removeLabel({
|
||||
|
||||
2
.github/workflows/update-topics.yaml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
environment: Matrix
|
||||
steps:
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
||||
env:
|
||||
HS_URL: ${{ secrets.BETABOT_HS_URL }}
|
||||
LOBBY_ROOM_ID: ${{ secrets.ROOM_ID }}
|
||||
|
||||
@ -14,7 +14,8 @@ webpack-stats.json
|
||||
.vscode/
|
||||
.env
|
||||
coverage
|
||||
# Auto-generated file
|
||||
# Auto-generated files
|
||||
*.api.md
|
||||
/apps/web/src/modules.ts
|
||||
/apps/web/src/modules.js
|
||||
src/i18n/strings
|
||||
|
||||
29
CHANGELOG.md
@ -1,3 +1,32 @@
|
||||
Changes in [1.12.15](https://github.com/element-hq/element-web/releases/tag/v1.12.15) (2026-04-08)
|
||||
==================================================================================================
|
||||
Fixes Desktop release workflow.
|
||||
|
||||
This release is identical to v1.12.14 otherwise.
|
||||
|
||||
Changes in [1.12.14](https://github.com/element-hq/element-web/releases/tag/v1.12.14) (2026-04-07)
|
||||
==================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Add analytics tracking for URL previews ([#32659](https://github.com/element-hq/element-web/pull/32659)). Contributed by @Half-Shot.
|
||||
* Collapsible Room List - Clicking on separator should expand to last set width ([#32909](https://github.com/element-hq/element-web/pull/32909)). Contributed by @MidhunSureshR.
|
||||
* RoomList: improve performance ([#32919](https://github.com/element-hq/element-web/pull/32919)). Contributed by @florianduros.
|
||||
* Implement collapsible panels for the new room list ([#32742](https://github.com/element-hq/element-web/pull/32742)). Contributed by @MidhunSureshR.
|
||||
* Hide the names of banned users behind a spoiler tag (attempt 2) ([#32636](https://github.com/element-hq/element-web/pull/32636)). Contributed by @andybalaam.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Use the code signing Subject Name as basis for Tray GUID on Windows ([#32939](https://github.com/element-hq/element-web/pull/32939)). Contributed by @t3chguy.
|
||||
* Ensure the incoming verification request appears above the please verify prompt ([#32931](https://github.com/element-hq/element-web/pull/32931)). Contributed by @andybalaam.
|
||||
* Collapsible Room List - Prevent any interaction with the separator when the panel is expanded ([#32910](https://github.com/element-hq/element-web/pull/32910)). Contributed by @MidhunSureshR.
|
||||
* Fix icon size of badges in right panel ([#32952](https://github.com/element-hq/element-web/pull/32952)). Contributed by @florianduros.
|
||||
* Fix room list often showing the wrong icons for calls ([#32881](https://github.com/element-hq/element-web/pull/32881)). Contributed by @robintown.
|
||||
* Fix emoticon slash commands including stale buffers ([#32928](https://github.com/element-hq/element-web/pull/32928)). Contributed by @t3chguy.
|
||||
* Fix presence indicators not showing without cache ([#32880](https://github.com/element-hq/element-web/pull/32880)). Contributed by @DLCSharp.
|
||||
* Show space name instead of 'Empty room' after creation ([#32886](https://github.com/element-hq/element-web/pull/32886)). Contributed by @gugaribeiro05.
|
||||
* Strip ephemeral query params from OIDC redirect URI ([#32875](https://github.com/element-hq/element-web/pull/32875)). Contributed by @azmeuk.
|
||||
|
||||
|
||||
Changes in [1.12.13](https://github.com/element-hq/element-web/releases/tag/v1.12.13) (2026-03-24)
|
||||
==================================================================================================
|
||||
## 🦖 Deprecations
|
||||
|
||||
@ -1 +1 @@
|
||||
24.14.0
|
||||
24.14.1
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# Docker image to facilitate building Element Desktop's native bits using a glibc version (2.31)
|
||||
# with broader compatibility, down to Debian bullseye & Ubuntu focal.
|
||||
|
||||
FROM rust:bullseye@sha256:16950191527a4cb9e0762d9d48b705a6315158e4035e64f7a93ce8656a1b053c
|
||||
FROM rust:bullseye@sha256:bc19574c121fe10c1bc68fc2b1ea9b420d87d047a0c50fb1622b282199700cee
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
|
||||
@ -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<void> {
|
||||
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
|
||||
// Try python3 first, to get a more sensible error if python is not found in the fallback
|
||||
await hakEnv.checkTools([["python3", "--version"]]);
|
||||
} catch {
|
||||
await hakEnv.checkTools([["python", "--version"]]);
|
||||
}
|
||||
|
||||
// Ensure Rust target exists (nb. we avoid depending on rustup)
|
||||
await new Promise((resolve, reject) => {
|
||||
const rustc = childProcess.execFile(
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"productName": "Element",
|
||||
"main": "lib/electron-main.js",
|
||||
"exports": "./lib/electron-main.js",
|
||||
"version": "1.12.13",
|
||||
"version": "1.12.15",
|
||||
"description": "Element: the future of secure communication",
|
||||
"author": {
|
||||
"name": "Element",
|
||||
@ -71,7 +71,7 @@
|
||||
"@babel/core": "^7.18.10",
|
||||
"@babel/preset-env": "^7.18.10",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@electron/asar": "4.1.0",
|
||||
"@electron/asar": "4.1.2",
|
||||
"@playwright/test": "catalog:",
|
||||
"@stylistic/eslint-plugin": "^5.0.0",
|
||||
"@types/auto-launch": "^5.0.1",
|
||||
@ -84,7 +84,7 @@
|
||||
"app-builder-lib": "26.8.2",
|
||||
"chokidar": "^5.0.0",
|
||||
"detect-libc": "^2.0.0",
|
||||
"electron": "41.0.3",
|
||||
"electron": "41.1.0",
|
||||
"electron-builder": "26.8.2",
|
||||
"electron-builder-squirrel-windows": "26.8.2",
|
||||
"electron-devtools-installer": "^4.0.0",
|
||||
@ -105,7 +105,7 @@
|
||||
"typescript": "5.9.3"
|
||||
},
|
||||
"hakDependencies": {
|
||||
"matrix-seshat": "^4.0.1"
|
||||
"matrix-seshat": "4.0.1"
|
||||
},
|
||||
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be"
|
||||
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM mcr.microsoft.com/playwright:v1.58.2-jammy@sha256:4698a73749c5848d3f5fcd42a2174d172fcad2b2283e087843b115424303a565
|
||||
FROM mcr.microsoft.com/playwright:v1.59.1-jammy@sha256:8a0360d39d1973be506dd59002904a774f6d697d4946c94063b3fd006461c8ff
|
||||
|
||||
WORKDIR /work/element-desktop
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
@ -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": {
|
||||
|
||||
@ -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
|
||||
@ -103,6 +103,9 @@ export default class HakEnv {
|
||||
shell: this.isWin(),
|
||||
...options,
|
||||
});
|
||||
proc.on("error", (err) => {
|
||||
reject(err);
|
||||
});
|
||||
proc.on("exit", (code) => {
|
||||
if (code) {
|
||||
reject(code);
|
||||
|
||||
@ -56,6 +56,7 @@ module.exports = {
|
||||
{ from: "res/css/views/rooms/_EditMessageComposer.pcss", type: "css" },
|
||||
{ from: "res/css/views/right_panel/_BaseCard.pcss", type: "css" },
|
||||
{ from: "res/css/views/messages/_MessageActionBar.pcss", type: "css" },
|
||||
{ from: "res/css/views/messages/_ThreadActionBar.pcss", type: "css" },
|
||||
{ from: "res/css/views/voip/LegacyCallView/_LegacyCallViewButtons.pcss", type: "css" },
|
||||
{ from: "res/css/views/elements/_ToggleSwitch.pcss", type: "css" },
|
||||
{ from: "res/css/views/settings/tabs/_SettingsTab.pcss", type: "css" },
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
# Context must be the root of the monorepo
|
||||
|
||||
# Builder
|
||||
FROM --platform=$BUILDPLATFORM node:24-bullseye@sha256:4bfbd78e049926e4ca595c1798810691ca7bb5aedd829ffd8a78b2ab30689810 AS builder
|
||||
FROM --platform=$BUILDPLATFORM node:24-bullseye@sha256:27e462f5db2402700867dfa8ec35e3a68b127fdf61b505db0dd6ab98c38284bb AS builder
|
||||
|
||||
# Support custom branch of the js-sdk. This also helps us build images of element-web develop.
|
||||
ARG USE_CUSTOM_SDKS=false
|
||||
@ -25,7 +25,7 @@ RUN --mount=type=bind,source=.git,target=/src/.git /src/scripts/docker-package.s
|
||||
RUN cp /src/apps/web/config.sample.json /src/apps/web/webapp/config.json
|
||||
|
||||
# App
|
||||
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:4011c42f28e9b54c86b52211598dbc6bcaa520311ddd55f211587cdd71f88a9c
|
||||
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:b5831ee7f7aa827cbae87df4a30a642f62c747d8525f5674365389f3adab278d
|
||||
|
||||
# Need root user to install packages & manipulate the usr directory
|
||||
USER root
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
**/.pnpm-store
|
||||
**/tsconfig.node.tsbuildinfo
|
||||
**/*.md
|
||||
!**/*.api.md
|
||||
**/*.rst
|
||||
|
||||
.idea/
|
||||
|
||||
@ -46,7 +46,7 @@ const config: Config = {
|
||||
"@vector-im/compound-web": "<rootDir>/../../node_modules/@vector-im/compound-web",
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
"/node_modules/(?!(mime|matrix-js-sdk|uuid|p-retry|is-network-error|react-merge-refs|is-ip|ip-regex|super-regex|function-timeout|time-span|convert-hrtime|clone-regexp|is-regexp|matrix-web-i18n|await-lock|@element-hq/web-shared-components|react-virtuoso|lodash)).+$",
|
||||
"/node_modules/(?!(mime|matrix-js-sdk|uuid|p-retry|is-network-error|react-merge-refs|is-ip|ip-regex|super-regex|function-timeout|time-span|convert-hrtime|clone-regexp|is-regexp|matrix-web-i18n|await-lock|@element-hq/web-shared-components|react-virtuoso|lodash|domutils|domhandler|domelementtype|dom-serializer|entities)).+$",
|
||||
],
|
||||
collectCoverageFrom: [
|
||||
"<rootDir>/src/**/*.{js,ts,tsx}",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "element-web",
|
||||
"version": "1.12.13",
|
||||
"version": "1.12.15",
|
||||
"description": "Element: the future of secure communication",
|
||||
"author": "New Vector Ltd.",
|
||||
"repository": {
|
||||
@ -30,15 +30,15 @@
|
||||
"lint:types": "nx lint:types",
|
||||
"lint:style": "stylelint \"res/css/**/*.pcss\"",
|
||||
"test": "nx test:unit",
|
||||
"test:playwright": "playwright test",
|
||||
"test:playwright:open": "pnpm test:playwright --ui",
|
||||
"test:playwright:screenshots": "playwright-screenshots-experimental pnpm playwright test --update-snapshots --project=Chrome --grep @screenshot",
|
||||
"test:playwright": "nx test:playwright --",
|
||||
"test:playwright:open": "nx test:playwright -- --ui",
|
||||
"test:playwright:screenshots": "nx test:playwright:screenshots --",
|
||||
"coverage": "pnpm test --coverage",
|
||||
"analyse:webpack-bundles": "webpack-bundle-analyzer webpack-stats.json webapp"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@element-hq/element-web-module-api": "catalog:",
|
||||
"@element-hq/element-web-module-api": "workspace:*",
|
||||
"@element-hq/web-shared-components": "workspace:*",
|
||||
"@fontsource/fira-code": "^5",
|
||||
"@fontsource/inter": "catalog:",
|
||||
@ -63,11 +63,11 @@
|
||||
"css-tree": "^3.0.0",
|
||||
"diff-dom": "^5.0.0",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
"domutils": "^3.2.2",
|
||||
"domutils": "^4.0.0",
|
||||
"emojibase-regex": "^17.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"file-saver": "^2.0.5",
|
||||
"filesize": "11.0.13",
|
||||
"filesize": "11.0.15",
|
||||
"github-markdown-css": "^5.5.1",
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
"highlight.js": "^11.3.1",
|
||||
@ -78,7 +78,7 @@
|
||||
"jsrsasign": "^11.0.0",
|
||||
"jszip": "^3.7.0",
|
||||
"katex": "^0.16.0",
|
||||
"lodash": "npm:lodash-es@^4.17.21",
|
||||
"lodash": "npm:lodash-es@4.18.1",
|
||||
"maplibre-gl": "^5.0.0",
|
||||
"matrix-encrypt-attachment": "^1.0.3",
|
||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#hs/profile-sync-endpoints",
|
||||
@ -89,7 +89,7 @@
|
||||
"opus-recorder": "^8.0.3",
|
||||
"pako": "^2.0.3",
|
||||
"png-chunks-extract": "^1.0.0",
|
||||
"posthog-js": "1.360.2",
|
||||
"posthog-js": "1.364.7",
|
||||
"qrcode": "1.5.4",
|
||||
"re-resizable": "6.11.2",
|
||||
"react": "catalog:",
|
||||
@ -101,7 +101,7 @@
|
||||
"react-transition-group": "^4.4.1",
|
||||
"rfc4648": "^1.4.0",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"sanitize-html": "2.17.1",
|
||||
"sanitize-html": "2.17.2",
|
||||
"tar-js": "^0.3.0",
|
||||
"ua-parser-js": "1.0.40",
|
||||
"uuid": "^13.0.0",
|
||||
@ -125,10 +125,9 @@
|
||||
"@babel/preset-env": "^7.12.11",
|
||||
"@babel/preset-react": "^7.12.10",
|
||||
"@babel/preset-typescript": "^7.12.7",
|
||||
"@casualbot/jest-sonar-reporter": "2.5.0",
|
||||
"@casualbot/jest-sonar-reporter": "2.5.1",
|
||||
"@element-hq/element-call-embedded": "0.18.0",
|
||||
"@element-hq/element-web-playwright-common": "catalog:",
|
||||
"@element-hq/element-web-playwright-common-local": "workspace:*",
|
||||
"@element-hq/element-web-playwright-common": "workspace:*",
|
||||
"@fetch-mock/jest": "^0.2.20",
|
||||
"@jest/globals": "^30.2.0",
|
||||
"@peculiar/webcrypto": "^1.4.3",
|
||||
@ -203,7 +202,7 @@
|
||||
"jest-raw-loader": "^1.0.1",
|
||||
"jsqr": "^1.4.0",
|
||||
"matrix-web-i18n": "catalog:",
|
||||
"mini-css-extract-plugin": "2.10.1",
|
||||
"mini-css-extract-plugin": "2.10.2",
|
||||
"modernizr": "^3.12.0",
|
||||
"playwright-core": "catalog:",
|
||||
"postcss": "8.5.8",
|
||||
@ -246,6 +245,6 @@
|
||||
"engines": {
|
||||
"node": ">=22.18"
|
||||
},
|
||||
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be",
|
||||
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319",
|
||||
"private": true
|
||||
}
|
||||
|
||||
@ -21,7 +21,6 @@ import {
|
||||
waitForVerificationRequest,
|
||||
} from "./utils";
|
||||
import { type Bot } from "../../pages/bot";
|
||||
import { Toasts } from "../../pages/toasts.ts";
|
||||
import type { ElementAppPage } from "../../pages/ElementAppPage.ts";
|
||||
|
||||
test.describe("Device verification", { tag: "@no-webkit" }, () => {
|
||||
@ -82,7 +81,11 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
|
||||
);
|
||||
|
||||
// Regression test for https://github.com/element-hq/element-web/issues/29110
|
||||
test("No toast after verification, even if the secrets take a while to arrive", async ({ page, credentials }) => {
|
||||
test("No toast after verification, even if the secrets take a while to arrive", async ({
|
||||
page,
|
||||
credentials,
|
||||
toasts,
|
||||
}) => {
|
||||
// Before we log in, the bot creates an encrypted room, so that we can test the toast behaviour that only happens
|
||||
// when we are in an encrypted room.
|
||||
await aliceBotClient.createRoom({
|
||||
@ -121,7 +124,6 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
|
||||
await infoDialog.getByRole("button", { name: "Got it" }).click();
|
||||
|
||||
// There should be no toast (other than the notifications one)
|
||||
const toasts = new Toasts(page);
|
||||
await toasts.rejectToast("Notifications");
|
||||
await toasts.assertNoToasts();
|
||||
|
||||
|
||||
@ -43,11 +43,7 @@ test.describe("Key storage out of sync toast", () => {
|
||||
});
|
||||
|
||||
test("should prompt for recovery key if 'enter recovery key' pressed", { tag: "@screenshot" }, async ({ page }) => {
|
||||
// We need to wait for there to be two toasts as the wait below won't work in isolation:
|
||||
// playwright only evaluates the 'first()' call initially, not subsequent times it checks, so
|
||||
// it would always be checking the same toast, even if another one is now the first.
|
||||
await expect(page.getByRole("alert")).toHaveCount(2);
|
||||
await expect(page.getByRole("alert").first()).toMatchScreenshot(
|
||||
await expect(page.getByRole("alert").filter({ hasText: "Your key storage is out of sync." })).toMatchScreenshot(
|
||||
"key-storage-out-of-sync-toast.png",
|
||||
screenshotOptions,
|
||||
);
|
||||
|
||||
61
apps/web/playwright/e2e/devtools/lowbandwidth.spec.ts
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
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 { test, expect } from "../../element-web-test";
|
||||
import { getSampleFilePath } from "../../sample-files";
|
||||
|
||||
test.describe("Devtools", () => {
|
||||
test.use({
|
||||
displayName: "Alice",
|
||||
});
|
||||
|
||||
test("should allow enabling low bandwidth mode", async ({ page, homeserver, user, app }) => {
|
||||
// Upload a picture
|
||||
const userSettings = await app.settings.openUserSettings("Account");
|
||||
const profileSettings = userSettings.locator(".mx_UserProfileSettings");
|
||||
await profileSettings.getByAltText("Upload").setInputFiles(getSampleFilePath("riot.png"));
|
||||
await app.closeDialog();
|
||||
|
||||
// Create an initial room.
|
||||
const createRoomDialog = await app.openCreateRoomDialog();
|
||||
await createRoomDialog.getByRole("textbox", { name: "Name" }).fill("Test Room");
|
||||
await createRoomDialog.getByRole("button", { name: "Create room" }).click();
|
||||
|
||||
const composer = app.getComposer().locator("[contenteditable]");
|
||||
await composer.fill("/devtools");
|
||||
await composer.press("Enter");
|
||||
const dialog = page.locator(".mx_Dialog");
|
||||
await dialog.getByLabel("Developer mode").check();
|
||||
await dialog.getByLabel("Disable bandwidth-heavy features").click();
|
||||
// Wait for refresh.
|
||||
await page.waitForEvent("domcontentloaded");
|
||||
await app.viewRoomByName("Test Room");
|
||||
|
||||
// This only appears when encryption has been disabled in the client.
|
||||
await expect(page.getByText("The encryption used by this room isn't supported.")).toBeVisible();
|
||||
|
||||
// None of these should be requested.
|
||||
let hasSentTyping = false;
|
||||
let hasRequestedThumbnail = false;
|
||||
await page.route("**/_matrix/client/v3/rooms/*/typing/*", async (route) => {
|
||||
hasSentTyping = true;
|
||||
await route.fulfill({ json: {} });
|
||||
});
|
||||
await page.route("**/_matrix/media/v3/thumbnail/**", async (route) => {
|
||||
hasRequestedThumbnail = true;
|
||||
await route.fulfill({ json: {} });
|
||||
});
|
||||
await page.route("**/_matrix/client/v1/media/thumbnail/**", async (route) => {
|
||||
hasRequestedThumbnail = true;
|
||||
await route.fulfill({ json: {} });
|
||||
});
|
||||
|
||||
await composer.pressSequentially("Provoke typing request", { delay: 5 });
|
||||
expect(hasSentTyping).toEqual(false);
|
||||
expect(hasRequestedThumbnail).toEqual(false);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,288 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector 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 { type Locator, type Page } from "@playwright/test";
|
||||
|
||||
import { expect, test } from "../../../element-web-test";
|
||||
|
||||
test.describe("Room list sections", () => {
|
||||
test.use({
|
||||
displayName: "Alice",
|
||||
labsFlags: ["feature_new_room_list", "feature_room_list_sections"],
|
||||
botCreateOpts: {
|
||||
displayName: "BotBob",
|
||||
autoAcceptInvites: true,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the room list
|
||||
* @param page
|
||||
*/
|
||||
function getRoomList(page: Page): Locator {
|
||||
return page.getByTestId("room-list");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the primary filters
|
||||
* @param page
|
||||
*/
|
||||
function getPrimaryFilters(page: Page): Locator {
|
||||
return page.getByTestId("primary-filters");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a section header toggle button by section name
|
||||
* @param page
|
||||
* @param sectionName The display name of the section (e.g. "Favourites", "Chats", "Low Priority")
|
||||
* @param isUnread Whether to look for the unread version of the section header
|
||||
*/
|
||||
function getSectionHeader(page: Page, sectionName: string, isUnread = false): Locator {
|
||||
return getRoomList(page).getByRole("gridcell", {
|
||||
name: isUnread ? `Toggle ${sectionName} section with unread room(s)` : `Toggle ${sectionName} section`,
|
||||
});
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page, app, user }) => {
|
||||
// The notification toast is displayed above the search section
|
||||
await app.closeNotificationToast();
|
||||
|
||||
// focus the user menu to avoid to have hover decoration
|
||||
await page.getByRole("button", { name: "User menu" }).focus();
|
||||
});
|
||||
|
||||
test.describe("Section rendering", () => {
|
||||
test.beforeEach(async ({ app, user }) => {
|
||||
// Create regular rooms
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await app.client.createRoom({ name: `room${i}` });
|
||||
}
|
||||
});
|
||||
|
||||
test("should render sections with correct rooms in each", { tag: "@screenshot" }, async ({ page, app }) => {
|
||||
// Create a favourite room
|
||||
const favouriteId = await app.client.createRoom({ name: "favourite room" });
|
||||
await app.client.evaluate(async (client, roomId) => {
|
||||
await client.setRoomTag(roomId, "m.favourite");
|
||||
}, favouriteId);
|
||||
|
||||
// Create a low priority room
|
||||
const lowPrioId = await app.client.createRoom({ name: "low prio room" });
|
||||
await app.client.evaluate(async (client, roomId) => {
|
||||
await client.setRoomTag(roomId, "m.lowpriority");
|
||||
}, lowPrioId);
|
||||
|
||||
const roomList = getRoomList(page);
|
||||
|
||||
// All three section headers should be visible
|
||||
await expect(getSectionHeader(page, "Favourites")).toBeVisible();
|
||||
await expect(getSectionHeader(page, "Chats")).toBeVisible();
|
||||
await expect(getSectionHeader(page, "Low Priority")).toBeVisible();
|
||||
|
||||
// Ensure all rooms are visible
|
||||
await expect(roomList.getByRole("row", { name: "Open room favourite room" })).toBeVisible();
|
||||
await expect(roomList.getByRole("row", { name: "Open room low prio room" })).toBeVisible();
|
||||
await expect(roomList.getByRole("row", { name: "Open room room0" })).toBeVisible();
|
||||
|
||||
await expect(roomList).toMatchScreenshot("room-list-sections.png");
|
||||
});
|
||||
|
||||
test("should only show non-empty sections", async ({ page, app }) => {
|
||||
// No low priority rooms created, only regular and favourite rooms
|
||||
const favouriteId = await app.client.createRoom({ name: "favourite room" });
|
||||
await app.client.evaluate(async (client, roomId) => {
|
||||
await client.setRoomTag(roomId, "m.favourite");
|
||||
}, favouriteId);
|
||||
|
||||
// Chats and Favourites sections should still be visible
|
||||
await expect(getSectionHeader(page, "Chats")).toBeVisible();
|
||||
await expect(getSectionHeader(page, "Favourites")).toBeVisible();
|
||||
// Low Priority sections should not be visible
|
||||
await expect(getSectionHeader(page, "Low Priority")).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("should render a flat list when there is only rooms in Chats section", async ({ page, app }) => {
|
||||
// All sections should not be visible
|
||||
await expect(getSectionHeader(page, "Chats")).not.toBeVisible();
|
||||
await expect(getSectionHeader(page, "Favourites")).not.toBeVisible();
|
||||
await expect(getSectionHeader(page, "Low Priority")).not.toBeVisible();
|
||||
// It should be a flat list (using listbox a11y role)
|
||||
await expect(page.getByRole("listbox", { name: "Room list", exact: true })).toBeVisible();
|
||||
await expect(getRoomList(page).getByRole("option", { name: "Open room room0" })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Section collapse and expand", () => {
|
||||
[
|
||||
{ section: "Favourites", roomName: "favourite room", tag: "m.favourite" },
|
||||
{ section: "Low Priority", roomName: "low prio room", tag: "m.lowpriority" },
|
||||
].forEach(({ section, roomName, tag }) => {
|
||||
test(`should collapse and expand the ${section} section`, async ({ page, app }) => {
|
||||
const roomId = await app.client.createRoom({ name: roomName });
|
||||
if (tag) {
|
||||
await app.client.evaluate(
|
||||
async (client, { roomId, tag }) => {
|
||||
await client.setRoomTag(roomId, tag);
|
||||
},
|
||||
{ roomId, tag },
|
||||
);
|
||||
}
|
||||
|
||||
const roomList = getRoomList(page);
|
||||
const sectionHeader = getSectionHeader(page, section);
|
||||
|
||||
// The room should be visible
|
||||
await expect(roomList.getByRole("row", { name: `Open room ${roomName}` })).toBeVisible();
|
||||
|
||||
// Collapse the section
|
||||
await sectionHeader.click();
|
||||
|
||||
// The room should no longer be visible
|
||||
await expect(roomList.getByRole("row", { name: `Open room ${roomName}` })).not.toBeVisible();
|
||||
|
||||
// The section header should still be visible
|
||||
await expect(sectionHeader).toBeVisible();
|
||||
|
||||
// Expand the section again
|
||||
await sectionHeader.click();
|
||||
|
||||
// The room should be visible again
|
||||
await expect(roomList.getByRole("row", { name: `Open room ${roomName}` })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test("should render collapsed section", { tag: "@screenshot" }, async ({ page, app }) => {
|
||||
const favouriteId = await app.client.createRoom({ name: "favourite room" });
|
||||
await app.client.evaluate(async (client, roomId) => {
|
||||
await client.setRoomTag(roomId, "m.favourite");
|
||||
}, favouriteId);
|
||||
|
||||
await app.client.createRoom({ name: "regular room" });
|
||||
|
||||
const roomList = getRoomList(page);
|
||||
|
||||
// Collapse the Favourites section
|
||||
await getSectionHeader(page, "Favourites").click();
|
||||
|
||||
// Verify favourite room is hidden but regular room is still visible
|
||||
await expect(roomList.getByRole("row", { name: "Open room favourite room" })).not.toBeVisible();
|
||||
await expect(roomList.getByRole("row", { name: "Open room regular room" })).toBeVisible();
|
||||
|
||||
await expect(roomList).toMatchScreenshot("room-list-sections-collapsed.png");
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Rooms placement in sections", () => {
|
||||
test("should move a room between sections when tags change", async ({ page, app }) => {
|
||||
await app.client.createRoom({ name: "my room" });
|
||||
|
||||
const roomList = getRoomList(page);
|
||||
|
||||
// Flat list because there is only rooms in the Chats section
|
||||
let roomItem = roomList.getByRole("option", { name: "Open room my room" });
|
||||
await expect(roomItem).toBeVisible();
|
||||
|
||||
// Favourite the room via context menu
|
||||
await roomItem.click({ button: "right" });
|
||||
await page.getByRole("menuitemcheckbox", { name: "Favourited" }).click();
|
||||
|
||||
// The Favourites section header should now be visible and the room should be under it
|
||||
await expect(getSectionHeader(page, "Favourites")).toBeVisible();
|
||||
roomItem = roomList.getByRole("row", { name: "Open room my room" });
|
||||
await expect(roomItem).toBeVisible();
|
||||
|
||||
// Unfavourite the room
|
||||
await roomItem.hover();
|
||||
await roomItem.getByRole("button", { name: "More Options" }).click();
|
||||
await page.getByRole("menuitemcheckbox", { name: "Favourited" }).click();
|
||||
|
||||
// Mark the room as low priority via context menu
|
||||
roomItem = roomList.getByRole("option", { name: "Open room my room" });
|
||||
await roomItem.click({ button: "right" });
|
||||
await page.getByRole("menuitemcheckbox", { name: "Low priority" }).click();
|
||||
|
||||
// The Low Priority section header should now be visible and the room should be under it
|
||||
await expect(getSectionHeader(page, "Low Priority")).toBeVisible();
|
||||
roomItem = roomList.getByRole("row", { name: "Open room my room" });
|
||||
await expect(roomItem).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test("should show unread indicator on section header", async ({ page, app, bot }) => {
|
||||
// Create a favourite room
|
||||
const favouriteId = await app.client.createRoom({ name: "favourite room" });
|
||||
await app.client.evaluate(async (client, roomId) => {
|
||||
await client.setRoomTag(roomId, "m.favourite");
|
||||
}, favouriteId);
|
||||
|
||||
const roomList = getRoomList(page);
|
||||
|
||||
// Invite the bot and have it send a message to generate an unread
|
||||
await app.client.inviteUser(favouriteId, bot.credentials.userId);
|
||||
await bot.joinRoom(favouriteId);
|
||||
await bot.sendMessage(favouriteId, "Hello from bot!");
|
||||
|
||||
let sectionHeader = getSectionHeader(page, "Favourites", true);
|
||||
await expect(sectionHeader).toBeVisible();
|
||||
|
||||
// Open the room to mark it as read
|
||||
await roomList.getByRole("row", { name: "Open room favourite room" }).click();
|
||||
|
||||
// The section should no longer be unread
|
||||
sectionHeader = getSectionHeader(page, "Favourites", false);
|
||||
await expect(sectionHeader).toBeVisible();
|
||||
});
|
||||
|
||||
test.describe("Sections and filters interaction", () => {
|
||||
test("should not show Favourite and Low Priority filters when sections are enabled", async ({ page, app }) => {
|
||||
const primaryFilters = getPrimaryFilters(page);
|
||||
|
||||
// Expand the filter list to see all filters
|
||||
const expandButton = primaryFilters.getByRole("button", { name: "Expand filter list" });
|
||||
await expandButton.click();
|
||||
|
||||
// Favourite and Low Priority filters should NOT be visible since sections handle them
|
||||
await expect(primaryFilters.getByRole("option", { name: "Favourite" })).not.toBeVisible();
|
||||
|
||||
// Other filters should still be present
|
||||
await expect(primaryFilters.getByRole("option", { name: "People" })).toBeVisible();
|
||||
await expect(primaryFilters.getByRole("option", { name: "Rooms" })).toBeVisible();
|
||||
await expect(primaryFilters.getByRole("option", { name: "Unread" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("should maintain sections when a filter is applied", async ({ page, app, bot }) => {
|
||||
// Create a favourite room with unread messages
|
||||
const favouriteId = await app.client.createRoom({ name: "fav with unread" });
|
||||
await app.client.evaluate(async (client, roomId) => {
|
||||
await client.setRoomTag(roomId, "m.favourite");
|
||||
}, favouriteId);
|
||||
await app.client.inviteUser(favouriteId, bot.credentials.userId);
|
||||
await bot.joinRoom(favouriteId);
|
||||
await bot.sendMessage(favouriteId, "Hello from favourite!");
|
||||
|
||||
// Create a regular room with unread messages
|
||||
const regularId = await app.client.createRoom({ name: "regular with unread" });
|
||||
await app.client.inviteUser(regularId, bot.credentials.userId);
|
||||
await bot.joinRoom(regularId);
|
||||
await bot.sendMessage(regularId, "Hello from regular!");
|
||||
|
||||
// Create a room without unread
|
||||
await app.client.createRoom({ name: "no unread room" });
|
||||
|
||||
const roomList = getRoomList(page);
|
||||
const primaryFilters = getPrimaryFilters(page);
|
||||
|
||||
// Apply the Unread filter
|
||||
await primaryFilters.getByRole("option", { name: "Unread" }).click();
|
||||
|
||||
// Only rooms with unreads should be visible
|
||||
await expect(roomList.getByRole("row", { name: "fav with unread" })).toBeVisible();
|
||||
await expect(roomList.getByRole("row", { name: "regular with unread" })).toBeVisible();
|
||||
await expect(roomList.getByRole("row", { name: "no unread room" })).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -328,11 +328,11 @@ test.describe("Room list", () => {
|
||||
|
||||
const roomListView = getRoomList(page);
|
||||
const videoRoom = roomListView.getByRole("option", { name: "video room" });
|
||||
await expect(videoRoom).toHaveAttribute("aria-selected", "true"); // wait for room list update
|
||||
|
||||
// focus the user menu to avoid to have hover decoration
|
||||
await page.getByRole("button", { name: "User menu" }).focus();
|
||||
|
||||
await expect(videoRoom).toBeVisible();
|
||||
await expect(videoRoom).toMatchScreenshot("room-list-item-video.png");
|
||||
});
|
||||
});
|
||||
|
||||
@ -14,7 +14,7 @@ test.describe("Message links", () => {
|
||||
await use({ roomId });
|
||||
},
|
||||
});
|
||||
for (const link of ["https://example.org", "example.org", "ftp://example.org"]) {
|
||||
for (const link of ["https://example.org", "ftp://example.org"]) {
|
||||
test(`should linkify a regular link '${link}'`, async ({ page, user, app, room }) => {
|
||||
await page.goto(`#/room/${room.roomId}`);
|
||||
// Needs to be unformatted so we test linkifing
|
||||
@ -24,6 +24,13 @@ test.describe("Message links", () => {
|
||||
await expect(linkElement).toBeVisible();
|
||||
});
|
||||
}
|
||||
test("should NOT linkify a bare domain", async ({ page, user, app, room }) => {
|
||||
await page.goto(`#/room/${room.roomId}`);
|
||||
// Needs to be unformatted so we test linkifing
|
||||
await app.client.sendMessage(room.roomId, `Check out example.org`);
|
||||
const linkElement = page.locator(".mx_EventTile_last").getByRole("link", { name: "example.org" });
|
||||
await expect(linkElement).not.toBeVisible();
|
||||
});
|
||||
test("should linkify a User ID", async ({ page, user, app, room }) => {
|
||||
await page.goto(`#/room/${room.roomId}`);
|
||||
// Needs to be unformatted so we test linkifing
|
||||
|
||||
@ -14,13 +14,7 @@ test.describe("Topic links", () => {
|
||||
await use({ roomId });
|
||||
},
|
||||
});
|
||||
for (const link of [
|
||||
"https://example.org",
|
||||
"example.org",
|
||||
"ftp://example.org",
|
||||
"#aroom:example.org",
|
||||
"@alice:example.org",
|
||||
]) {
|
||||
for (const link of ["https://example.org", "ftp://example.org", "#aroom:example.org", "@alice:example.org"]) {
|
||||
// Playwright treats '@' as a tag, so replace it to be safe
|
||||
test(`should linkify plaintext '${link.replace("@", "_@")}'`, async ({ page, user, app, room }) => {
|
||||
await app.client.sendStateEvent(
|
||||
|
||||
@ -48,9 +48,9 @@ test.describe("Room Directory", () => {
|
||||
await app.closeDialog();
|
||||
|
||||
const resp = await bot.publicRooms({});
|
||||
expect(resp.total_room_count_estimate).toEqual(1);
|
||||
expect(resp.chunk).toHaveLength(1);
|
||||
expect(resp.chunk[0].room_id).toEqual(roomId);
|
||||
expect(resp.total_room_count_estimate).toBeGreaterThanOrEqual(1);
|
||||
expect(resp.chunk).toHaveLength(resp.total_room_count_estimate);
|
||||
expect(resp.chunk.find((r) => r.room_id === roomId)).toBeTruthy();
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@ -24,7 +24,7 @@ test.describe("Account user settings tab", () => {
|
||||
},
|
||||
});
|
||||
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ uut, user }) => {
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ uut, user, axe }) => {
|
||||
await expect(uut).toMatchScreenshot("account.png");
|
||||
|
||||
// Assert that the top heading is rendered
|
||||
@ -70,6 +70,8 @@ test.describe("Account user settings tab", () => {
|
||||
await expect(accountManagementSection.getByRole("button", { name: "Deactivate Account" })).toHaveClass(
|
||||
/mx_AccessibleButton_kind_danger/,
|
||||
);
|
||||
|
||||
await expect(axe).toHaveNoViolations();
|
||||
});
|
||||
|
||||
test("should respond to small screen sizes", { tag: "@screenshot" }, async ({ page, uut }) => {
|
||||
|
||||
@ -13,7 +13,7 @@ test.describe("Appearance user settings tab", () => {
|
||||
displayName: "Hanako",
|
||||
});
|
||||
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ page, user, app }) => {
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ page, user, app, axe }) => {
|
||||
const tab = await app.settings.openUserSettings("Appearance");
|
||||
|
||||
// Click "Show advanced" link button
|
||||
@ -23,6 +23,8 @@ test.describe("Appearance user settings tab", () => {
|
||||
await expect(tab.getByRole("button", { name: "Hide advanced" })).toBeVisible();
|
||||
|
||||
await expect(tab).toMatchScreenshot("appearance-tab.png");
|
||||
|
||||
await expect(axe).toHaveNoViolations();
|
||||
});
|
||||
|
||||
test(
|
||||
|
||||
@ -23,7 +23,7 @@ test.describe("Appearance user settings tab", () => {
|
||||
test(
|
||||
"should be rendered with the light theme selected",
|
||||
{ tag: "@screenshot" },
|
||||
async ({ page, app, util }) => {
|
||||
async ({ page, app, util, axe }) => {
|
||||
// Assert that 'Match system theme' is not checked
|
||||
await expect(util.getMatchSystemThemeSwitch()).not.toBeChecked();
|
||||
|
||||
@ -34,6 +34,8 @@ test.describe("Appearance user settings tab", () => {
|
||||
await expect(util.getHighContrastTheme()).not.toBeChecked();
|
||||
|
||||
await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-light.png");
|
||||
|
||||
await expect(axe).toHaveNoViolations();
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ test.describe("Device manager", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("should display sessions", async ({ page, app }) => {
|
||||
test("should display sessions", async ({ page, app, axe }) => {
|
||||
await app.settings.openUserSettings("Sessions");
|
||||
const tab = page.locator(".mx_SettingsTab");
|
||||
|
||||
@ -85,7 +85,7 @@ test.describe("Device manager", () => {
|
||||
// session name updated in details
|
||||
await expect(firstSession.locator(".mx_DeviceDetailHeading h4").getByText(sessionName)).toBeVisible();
|
||||
// and main list item
|
||||
await expect(firstSession.locator(".mx_DeviceTile h4").getByText(sessionName)).toBeVisible();
|
||||
await expect(firstSession.locator(".mx_DeviceTile h3").getByText(sessionName)).toBeVisible();
|
||||
|
||||
// sign out using the device details sign out
|
||||
await firstSession.getByRole("button", { name: "Remove this session" }).click();
|
||||
@ -96,5 +96,7 @@ test.describe("Device manager", () => {
|
||||
// no other sessions or security recommendations sections when only one session
|
||||
await expect(tab.getByText("Other sessions")).not.toBeVisible();
|
||||
await expect(tab.getByTestId("security-recommendations-section")).not.toBeVisible();
|
||||
|
||||
await expect(axe).toHaveNoViolations();
|
||||
});
|
||||
});
|
||||
|
||||
@ -16,7 +16,7 @@ test.describe("Advanced section in Encryption tab", () => {
|
||||
await bootstrapCrossSigningForClient(clientHandle, credentials, true);
|
||||
});
|
||||
|
||||
test("should show the encryption details", { tag: "@screenshot" }, async ({ page, app, util }) => {
|
||||
test("should show the encryption details", { tag: "@screenshot" }, async ({ page, app, util, axe }) => {
|
||||
await util.openEncryptionTab();
|
||||
const section = util.getEncryptionDetailsSection();
|
||||
|
||||
@ -26,6 +26,8 @@ test.describe("Advanced section in Encryption tab", () => {
|
||||
await expect(section).toMatchScreenshot("encryption-details.png", {
|
||||
mask: [section.getByTestId("deviceId"), section.getByTestId("sessionKey")],
|
||||
});
|
||||
|
||||
await expect(axe).toHaveNoViolations();
|
||||
});
|
||||
|
||||
test("should show the import room keys dialog", async ({ page, app, util }) => {
|
||||
|
||||
@ -47,6 +47,9 @@ test.describe("Encryption tab", () => {
|
||||
|
||||
await util.verifyDevice(recoveryKey);
|
||||
|
||||
// Prevent flakiness by scrolling to top of the tab
|
||||
await page.getByRole("heading", { name: "Key storage" }).scrollIntoViewIfNeeded();
|
||||
|
||||
await expect(content).toMatchScreenshot("default-tab.png", {
|
||||
mask: [content.getByTestId("deviceId"), content.getByTestId("sessionKey")],
|
||||
});
|
||||
|
||||
@ -20,7 +20,7 @@ test.describe("General room settings tab", () => {
|
||||
await app.viewRoomByName(roomName);
|
||||
});
|
||||
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ page, app }) => {
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ page, app, axe }) => {
|
||||
const settings = await app.settings.openRoomSettings("General");
|
||||
|
||||
// Assert that "Show less" details element is rendered
|
||||
@ -34,6 +34,9 @@ test.describe("General room settings tab", () => {
|
||||
// Assert that "Show more" details element is rendered instead of "Show more"
|
||||
await expect(settings.getByText("Show less")).not.toBeVisible();
|
||||
await expect(settings.getByText("Show more")).toBeVisible();
|
||||
|
||||
axe.disableRules("color-contrast"); // XXX: We have some known contrast issues here
|
||||
await expect(axe).toHaveNoViolations();
|
||||
});
|
||||
|
||||
test("long address should not cause dialog to overflow", { tag: "@no-webkit" }, async ({ page, app, user }) => {
|
||||
|
||||
@ -25,7 +25,7 @@ test.describe("Preferences user settings tab", () => {
|
||||
labsFlags: ["feature_new_room_list"],
|
||||
});
|
||||
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user }) => {
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user, axe }) => {
|
||||
await page.setViewportSize({ width: 1024, height: 4000 });
|
||||
const tab = await app.settings.openUserSettings("Preferences");
|
||||
// Assert that the top heading is rendered
|
||||
@ -39,6 +39,8 @@ test.describe("Preferences user settings tab", () => {
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
await expect(axe).toHaveNoViolations();
|
||||
});
|
||||
|
||||
test("should be able to change the app language", { tag: ["@no-firefox", "@no-webkit"] }, async ({ uut, user }) => {
|
||||
|
||||
@ -8,11 +8,13 @@ Please see LICENSE files in the repository root for full details.
|
||||
import { test, expect } from "../../element-web-test";
|
||||
|
||||
test.describe("Quick settings menu", () => {
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user }) => {
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user, axe }) => {
|
||||
await page.getByRole("button", { name: "Quick settings" }).click();
|
||||
// Assert that the top heading is renderedc
|
||||
const settings = page.getByTestId("quick-settings-menu");
|
||||
await expect(settings).toBeVisible();
|
||||
await expect(settings).toMatchScreenshot("quick-settings.png");
|
||||
|
||||
await expect(axe).toHaveNoViolations();
|
||||
});
|
||||
});
|
||||
|
||||
@ -25,7 +25,7 @@ test.describe("Roles & Permissions room settings tab", () => {
|
||||
settings = await app.settings.openRoomSettings("Roles & Permissions");
|
||||
});
|
||||
|
||||
test("should be able to change the role of a user", async ({ page, app, user }) => {
|
||||
test("should be able to change the role of a user", async ({ page, app, user, axe }) => {
|
||||
const privilegedUserSection = settings.locator(".mx_SettingsFieldset").first();
|
||||
const applyButton = privilegedUserSection.getByRole("button", { name: "Apply" });
|
||||
|
||||
@ -55,5 +55,7 @@ test.describe("Roles & Permissions room settings tab", () => {
|
||||
settings = await app.settings.openRoomSettings("Roles & Permissions");
|
||||
combobox = privilegedUserSection.getByRole("combobox", { name: user.userId });
|
||||
await expect(combobox).toHaveValue("50");
|
||||
|
||||
await expect(axe).toHaveNoViolations();
|
||||
});
|
||||
});
|
||||
|
||||
@ -32,12 +32,14 @@ test.describe("Security user settings tab", () => {
|
||||
});
|
||||
|
||||
test.describe("AnalyticsLearnMoreDialog", () => {
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user }) => {
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user, axe }) => {
|
||||
const tab = await app.settings.openUserSettings("Security");
|
||||
await tab.getByRole("button", { name: "Learn more" }).click();
|
||||
await expect(page.locator(".mx_AnalyticsLearnMoreDialog_wrapper .mx_Dialog")).toMatchScreenshot(
|
||||
"Security-user-settings-tab-with-posthog-enable-b5d89-csLearnMoreDialog-should-be-rendered-properly-1.png",
|
||||
);
|
||||
|
||||
await expect(axe).toHaveNoViolations();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -24,7 +24,6 @@ import type { IConfigOptions } from "../src/IConfigOptions";
|
||||
import { type Credentials } from "./plugins/homeserver";
|
||||
import { ElementAppPage } from "./pages/ElementAppPage";
|
||||
import { Crypto } from "./pages/crypto";
|
||||
import { Toasts } from "./pages/toasts";
|
||||
import { Bot, type CreateBotOpts } from "./pages/bot";
|
||||
import { Webserver } from "./plugins/webserver";
|
||||
import { type WorkerOptions, type Services, test as base } from "./services";
|
||||
@ -52,7 +51,6 @@ export interface TestFixtures extends BaseTestFixtures {
|
||||
|
||||
crypto: Crypto;
|
||||
room?: { roomId: string };
|
||||
toasts: Toasts;
|
||||
uut?: Locator; // Unit Under Test, useful place to refer a prepared locator
|
||||
botCreateOpts: CreateBotOpts;
|
||||
bot: Bot;
|
||||
@ -92,9 +90,6 @@ export const test = base.extend<TestFixtures>({
|
||||
crypto: async ({ page, homeserver, request }, use) => {
|
||||
await use(new Crypto(page, homeserver, request));
|
||||
},
|
||||
toasts: async ({ page }, use) => {
|
||||
await use(new Toasts(page));
|
||||
},
|
||||
|
||||
botCreateOpts: {},
|
||||
bot: async ({ page, homeserver, botCreateOpts, user }, use, testInfo) => {
|
||||
|
||||
@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import type { Page, Request, Route } from "@playwright/test";
|
||||
import type { Page, Request, Route, Disposable } from "@playwright/test";
|
||||
import type { Client } from "./client";
|
||||
|
||||
/**
|
||||
@ -16,7 +16,7 @@ import type { Client } from "./client";
|
||||
*/
|
||||
export class Network {
|
||||
private isOffline = false;
|
||||
private setupPromise?: Promise<void>;
|
||||
private setupPromise?: Promise<Disposable>;
|
||||
|
||||
constructor(
|
||||
private page: Page,
|
||||
|
||||
@ -1,53 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 { type Page, expect, type Locator } from "@playwright/test";
|
||||
|
||||
export class Toasts {
|
||||
public constructor(private readonly page: Page) {}
|
||||
|
||||
/**
|
||||
* Assert that a toast with the given title exists, and return it
|
||||
*
|
||||
* @param expectedTitle - Expected title of the toast
|
||||
* @param timeout Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`.
|
||||
* @returns the Locator for the matching toast
|
||||
*/
|
||||
public async getToast(expectedTitle: string, timeout?: number): Promise<Locator> {
|
||||
const toast = this.page.locator(".mx_Toast_toast", { hasText: expectedTitle }).first();
|
||||
await expect(toast).toBeVisible({ timeout });
|
||||
return toast;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that no toasts exist
|
||||
*/
|
||||
public async assertNoToasts(): Promise<void> {
|
||||
await expect(this.page.locator(".mx_Toast_toast")).not.toBeVisible();
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept a toast with the given title, only works for the first toast in the stack
|
||||
*
|
||||
* @param expectedTitle - Expected title of the toast
|
||||
*/
|
||||
public async acceptToast(expectedTitle: string): Promise<void> {
|
||||
const toast = await this.getToast(expectedTitle);
|
||||
await toast.locator('.mx_Toast_buttons button[data-kind="primary"]').click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject a toast with the given title, only works for the first toast in the stack
|
||||
*
|
||||
* @param expectedTitle - Expected title of the toast
|
||||
*/
|
||||
public async rejectToast(expectedTitle: string): Promise<void> {
|
||||
const toast = await this.getToast(expectedTitle);
|
||||
await toast.locator('.mx_Toast_buttons button[data-kind="secondary"]').click();
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 956 KiB |
|
Before Width: | Height: | Size: 981 KiB After Width: | Height: | Size: 982 KiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |