Merge branch 'develop' into midhun/new-memberlist

This commit is contained in:
R Midhun Suresh
2024-12-16 21:32:46 +05:30
876 changed files with 10347 additions and 21754 deletions

View File

@@ -42,6 +42,10 @@ module.exports = {
name: "setImmediate",
message: "Use setTimeout instead.",
},
{
name: "Buffer",
message: "Buffer is not available in the web.",
},
],
"import/no-duplicates": ["error"],
@@ -117,10 +121,6 @@ module.exports = {
"!matrix-js-sdk/src/extensible_events_v1/PollResponseEvent",
"!matrix-js-sdk/src/extensible_events_v1/PollEndEvent",
"!matrix-js-sdk/src/extensible_events_v1/InvalidEventError",
"!matrix-js-sdk/src/crypto",
"!matrix-js-sdk/src/crypto/keybackup",
"!matrix-js-sdk/src/crypto/deviceinfo",
"!matrix-js-sdk/src/crypto/dehydration",
"!matrix-js-sdk/src/oidc",
"!matrix-js-sdk/src/oidc/discovery",
"!matrix-js-sdk/src/oidc/authorize",
@@ -259,6 +259,9 @@ module.exports = {
additionalTestBlockFunctions: ["beforeAll", "beforeEach", "oldBackendOnly"],
},
],
// These are fine in tests
"no-restricted-globals": "off",
},
},
{

1
.github/CODEOWNERS vendored
View File

@@ -13,6 +13,7 @@
# Ignore translations as those will be updated by GHA for Localazy download
/src/i18n/strings
/src/i18n/strings/en_EN.json @element-hq/element-web-reviewers
# Ignore the synapse plugin as this is updated by GHA for docker image updating
/playwright/plugins/homeserver/synapse/index.ts

View File

@@ -0,0 +1,38 @@
name: Upload release assets
description: Uploads assets to an existing release and optionally signs them
inputs:
tag:
description: GitHub release tag to fetch assets from.
required: true
out-file-path:
description: Path to where the webapp should be extracted to.
required: true
runs:
using: composite
steps:
- name: Download release tarball
uses: robinraju/release-downloader@a96f54c1b5f5e09e47d9504526e96febd949d4c2 # v1
with:
tag: ${{ inputs.tag }}
fileName: element-*.tar.gz*
out-file-path: ${{ runner.temp }}/download-verify-element-tarball
- name: Verify tarball
shell: bash
run: gpg --verify element-*.tar.gz.asc element-*.tar.gz
working-directory: ${{ runner.temp }}/download-verify-element-tarball
- name: Extract tarball
shell: bash
run: |
mkdir webapp
tar xvzf element-*.tar.gz -C webapp --strip-components=1
working-directory: ${{ runner.temp }}/download-verify-element-tarball
- name: Move webapp to out-file-path
shell: bash
run: mv ${{ runner.temp }}/download-verify-element-tarball/webapp ${{ inputs.out-file-path }}
- name: Clean up temp directory
shell: bash
run: rm -R ${{ runner.temp }}/download-verify-element-tarball

3
.github/labels.yml vendored
View File

@@ -232,6 +232,9 @@
- name: "Z-Flaky-Test"
description: "A test is raising false alarms"
color: "ededed"
- name: "Z-Flaky-Jest-Test"
description: "A Jest test is raising false alarms"
color: "ededed"
- name: "Z-FOSDEM"
description: "Issues in chat.fosdem.org"
color: "ededed"

View File

@@ -7,6 +7,8 @@ on:
branches:
- develop
permissions: {} # We use ELEMENT_BOT_TOKEN instead
jobs:
backport:
name: Backport

View File

@@ -10,6 +10,7 @@ env:
# These must be set for fetchdep.sh to get the right branch
REPOSITORY: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
permissions: {} # No permissions required
jobs:
build:
name: "Build on ${{ matrix.image }}"

View File

@@ -3,6 +3,7 @@ on:
release:
types: [published]
concurrency: ${{ github.workflow }}
permissions: {} # We use ELEMENT_BOT_TOKEN instead
jobs:
build:
name: Build package

View File

@@ -9,6 +9,7 @@ on:
concurrency:
group: ${{ github.repository_owner }}-${{ github.workflow }}-${{ github.ref_name }}
cancel-in-progress: true
permissions: {}
jobs:
build:
name: "Build & Deploy develop.element.io"
@@ -16,6 +17,10 @@ jobs:
if: github.repository == 'element-hq/element-web'
runs-on: ubuntu-24.04
environment: develop
permissions:
checks: read
pages: write
deployments: write
env:
R2_BUCKET: "element-web-develop"
R2_URL: ${{ vars.CF_R2_S3_API }}

92
.github/workflows/deploy.yml vendored Normal file
View File

@@ -0,0 +1,92 @@
# Manual deploy workflow for deploying to app.element.io & staging.element.io
# Runs automatically for staging.element.io when an RC or Release is published
# Note: Does *NOT* run automatically for app.element.io so that it gets tested on staging.element.io beforehand
name: Deploy release
run-name: Deploy ${{ github.ref_name }} to ${{ inputs.site || 'staging.element.io' }}
on:
release:
types: [published]
workflow_dispatch:
inputs:
site:
description: Which site to deploy to
required: true
default: staging.element.io
type: choice
options:
- staging.element.io
- app.element.io
concurrency: ${{ inputs.site || 'staging.element.io' }}
permissions: {}
jobs:
deploy:
name: "Deploy to Cloudflare Pages"
runs-on: ubuntu-24.04
environment: ${{ inputs.site || 'staging.element.io' }}
permissions:
checks: read
deployments: write
env:
SITE: ${{ inputs.site || 'staging.element.io' }}
steps:
- uses: actions/checkout@v4
- name: Load GPG key
run: |
curl https://packages.element.io/element-release-key.gpg | gpg --import
gpg -k "$GPG_FINGERPRINT"
env:
GPG_FINGERPRINT: ${{ vars.GPG_FINGERPRINT }}
- name: Check current version on deployment
id: current_version
run: |
version=$(curl -s https://$SITE/version)
echo "version=${version#v}" >> $GITHUB_OUTPUT
# The current version bundle melding dance is skipped if the version we're deploying is the same
# as then we're just doing a re-deploy of the same version with potentially different configs.
- name: Download current version for its old bundles
id: current_download
if: steps.current_version.outputs.version != github.ref_name
uses: ./.github/actions/download-verify-element-tarball
with:
tag: v${{ steps.current_version.outputs.version }}
out-file-path: _current_version
- name: Download target version
uses: ./.github/actions/download-verify-element-tarball
with:
tag: ${{ github.ref_name }}
out-file-path: _deploy
- name: Merge current bundles into target
if: steps.current_download.outcome == 'success'
run: cp -vnpr _current_version/bundles/* _deploy/bundles/
- name: Copy config
run: cp element.io/app/config.json _deploy/config.json
- name: Populate 404.html
run: echo "404 Not Found" > _deploy/404.html
- name: Populate _headers
run: cp .github/cfp_headers _deploy/_headers
- name: Wait for other steps to succeed
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
with:
ref: ${{ github.sha }}
running-workflow-name: "Deploy to Cloudflare Pages"
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 10
check-regexp: ^((?!SonarCloud|SonarQube|issue|board|label|Release|prepare|GitHub Pages).)*$
- name: Deploy to Cloudflare Pages
uses: cloudflare/pages-action@f0a1cd58cd66095dee69bfa18fa5efd1dde93bca # v1
with:
apiToken: ${{ secrets.CF_PAGES_TOKEN }}
accountId: ${{ secrets.CF_PAGES_ACCOUNT_ID }}
projectName: ${{ env.SITE == 'staging.element.io' && 'element-web-staging' || 'element-web' }}
directory: _deploy
gitHubToken: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -7,14 +7,14 @@ on:
# This job can take a while, and we have usage limits, so just publish develop only twice a day
- cron: "0 7/12 * * *"
concurrency: ${{ github.workflow }}-${{ github.ref_name }}
permissions:
id-token: write # needed for signing the images with GitHub OIDC Token
permissions: {}
jobs:
buildx:
name: Docker Buildx
runs-on: ubuntu-24.04
environment: dockerhub
permissions:
id-token: write # needed for signing the images with GitHub OIDC Token
steps:
- uses: actions/checkout@v4
with:
@@ -39,7 +39,7 @@ jobs:
- name: Docker meta
id: meta
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5
uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5
with:
images: |
vectorim/element-web
@@ -51,7 +51,7 @@ jobs:
- name: Build and push
id: build-and-push
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6
with:
context: .
push: true

View File

@@ -5,10 +5,7 @@ on:
branches: [develop]
workflow_dispatch: {}
permissions:
contents: read
pages: write
id-token: write
permissions: {}
concurrency:
group: "pages"
@@ -100,6 +97,9 @@ jobs:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-24.04
permissions:
pages: write
id-token: write
needs: build
steps:
- name: Deploy to GitHub Pages

View File

@@ -11,6 +11,8 @@ concurrency:
group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.run_id }}
cancel-in-progress: ${{ github.event.workflow_run.event == 'pull_request' }}
permissions: {}
jobs:
report:
if: github.event.workflow_run.conclusion != 'cancelled'
@@ -20,11 +22,12 @@ jobs:
permissions:
statuses: write
deployments: write
actions: read
steps:
- name: Download HTML report
uses: actions/download-artifact@v4
with:
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
name: html-report
path: playwright-report

View File

@@ -33,6 +33,8 @@ env:
# fetchdep.sh needs to know our PR number
PR_NUMBER: ${{ github.event.pull_request.number }}
permissions: {} # No permissions required
jobs:
build:
name: "Build Element-Web"
@@ -81,7 +83,7 @@ jobs:
name: "Run Tests ${{ matrix.runner }}/${{ strategy.job-total }}"
needs: build
if: inputs.skip != true
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
permissions:
actions: read
issues: read
@@ -122,14 +124,18 @@ jobs:
with:
path: |
~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ steps.playwright.outputs.version }}
key: ${{ runner.os }}-playwright-${{ steps.playwright.outputs.version }}-chromium
- name: Install Playwright browsers
- name: Install Playwright browser
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: yarn playwright install --with-deps
run: yarn playwright install --with-deps --no-shell chromium
# We skip tests tagged with @mergequeue when running on PRs, but run them in MQ and everywhere else
- name: Run Playwright tests
run: yarn playwright test --shard ${{ matrix.runner }}/${{ strategy.job-total }}
run: |
yarn playwright test \
--shard "${{ matrix.runner }}/${{ strategy.job-total }}" \
${{ github.event_name == 'pull_request' && '--grep-invert @mergequeue' || '' }}
- name: Upload blob report to GitHub Actions Artifacts
if: always()

View File

@@ -4,6 +4,7 @@
on:
issues:
types: [closed]
permissions: {} # We use ELEMENT_BOT_TOKEN instead
jobs:
tidy:
name: Tidy closed issues

View File

@@ -3,6 +3,7 @@ on:
workflow_dispatch: {}
schedule:
- cron: "0 6 * * 1,3,5" # Every Monday, Wednesday and Friday at 6am UTC
permissions: {} # We use ELEMENT_BOT_TOKEN instead
jobs:
download:
uses: matrix-org/matrix-web-i18n/.github/workflows/localazy_download.yaml@main

View File

@@ -4,6 +4,7 @@ on:
branches: [develop]
paths:
- "src/i18n/strings/en_EN.json"
permissions: {} # No permissions needed
jobs:
upload:
uses: matrix-org/matrix-web-i18n/.github/workflows/localazy_upload.yaml@main

View File

@@ -11,6 +11,9 @@ jobs:
if: github.event.workflow_run.conclusion != 'cancelled' && github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-24.04
environment: Netlify
permissions:
actions: read
deployments: write
steps:
- name: 📝 Create Deployment
uses: bobheadxi/deployments@648679e8e4915b27893bd7dbc35cb504dc915bc8 # v1
@@ -27,7 +30,7 @@ jobs:
- name: 📥 Download artifact
uses: actions/download-artifact@v4
with:
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
name: webapp
path: webapp

View File

@@ -6,6 +6,7 @@ on:
#schedule:
# - cron: "*/10 * * * *"
concurrency: ${{ github.workflow }}
permissions: {} # We use ELEMENT_BOT_TOKEN instead
jobs:
bot:
name: Pending reviews bot

View File

@@ -3,9 +3,12 @@ on:
workflow_dispatch: {}
schedule:
- cron: "0 6 * * *" # Every day at 6am UTC
permissions: {}
jobs:
update:
runs-on: ubuntu-24.04
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v4

View File

@@ -4,8 +4,11 @@ on:
types: [opened, edited, labeled, unlabeled, synchronize]
merge_group:
types: [checks_requested]
permissions: {}
jobs:
action:
uses: matrix-org/matrix-js-sdk/.github/workflows/pull_request.yaml@develop
permissions:
pull-requests: write
secrets:
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}

View File

@@ -2,6 +2,7 @@ name: Pull Request Base Branch
on:
pull_request:
types: [opened, edited, synchronize]
permissions: {} # No permissions required
jobs:
check_base_branch:
name: Check PR base branch

View File

@@ -4,6 +4,9 @@ on:
branches: [staging]
workflow_dispatch: {}
concurrency: ${{ github.workflow }}
permissions: {}
jobs:
draft:
permissions:
contents: write
uses: matrix-org/matrix-js-sdk/.github/workflows/release-drafter-workflow.yml@develop

View File

@@ -4,6 +4,7 @@ on:
push:
branches: [master]
concurrency: ${{ github.repository }}-${{ github.workflow }}
permissions: {} # We use ELEMENT_BOT_TOKEN instead
jobs:
merge:
uses: matrix-org/matrix-js-sdk/.github/workflows/release-gitflow.yml@develop

View File

@@ -11,9 +11,14 @@ on:
- rc
- final
concurrency: ${{ github.workflow }}
permissions: {}
jobs:
release:
uses: matrix-org/matrix-js-sdk/.github/workflows/release-make.yml@develop
permissions:
contents: write
issues: write
pull-requests: read
secrets:
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
@@ -42,6 +47,8 @@ jobs:
name: Post release checks
needs: release
runs-on: ubuntu-24.04
permissions:
checks: read
steps:
- name: Wait for dockerhub
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork

View File

@@ -17,9 +17,25 @@ on:
required: true
type: boolean
default: true
permissions: {} # Uses ELEMENT_BOT_TOKEN instead
jobs:
checks:
name: Sanity checks
strategy:
matrix:
repo:
- matrix-org/matrix-js-sdk
- element-hq/element-web
- element-hq/element-desktop
uses: matrix-org/matrix-js-sdk/.github/workflows/release-checks.yml@develop
secrets:
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
with:
repository: ${{ matrix.repo }}
prepare:
runs-on: ubuntu-24.04
needs: checks
env:
# The order is specified bottom-up to avoid any races for allchange
REPOS: matrix-js-sdk element-web element-desktop

View File

@@ -7,11 +7,16 @@ on:
concurrency:
group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch }}
cancel-in-progress: true
permissions: {}
jobs:
sonarqube:
name: 🩻 SonarQube
if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event != 'merge_group'
uses: matrix-org/matrix-js-sdk/.github/workflows/sonarcloud.yml@develop
permissions:
actions: read
statuses: write
id-token: write # sonar
secrets:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}

View File

@@ -16,6 +16,8 @@ env:
REPOSITORY: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
permissions: {} # No permissions required
jobs:
ts_lint:
name: "Typescript Syntax Check"
@@ -37,6 +39,8 @@ jobs:
i18n_lint:
name: "i18n Check"
uses: matrix-org/matrix-web-i18n/.github/workflows/i18n_check.yml@main
permissions:
pull-requests: read
with:
hardcoded-words: "Element"
allowed-hardcoded-keys: |

View File

@@ -8,6 +8,9 @@ on:
- develop
paths:
- .github/labels.yml
permissions: {} # We use ELEMENT_BOT_TOKEN instead
jobs:
sync-labels:
uses: element-hq/element-meta/.github/workflows/sync-labels.yml@develop

View File

@@ -26,6 +26,8 @@ env:
# fetchdep.sh needs to know our PR number
PR_NUMBER: ${{ github.event.pull_request.number }}
permissions: {}
jobs:
jest:
name: Jest
@@ -94,13 +96,15 @@ jobs:
needs: jest
if: always()
runs-on: ubuntu-24.04
permissions:
statuses: write
steps:
- if: needs.jest.result != 'skipped' && needs.jest.result != 'success'
run: exit 1
- name: Skip SonarCloud in merge queue
if: github.event_name == 'merge_group' || inputs.disable_coverage == 'true'
uses: Sibz/github-status-action@faaa4d96fecf273bd762985e0e7f9f933c774918 # v1
uses: guibranco/github-status-action-v2@d469d49426f5a7b8a1fbcac20ad274d3e4892321
with:
authToken: ${{ secrets.GITHUB_TOKEN }}
state: success

View File

@@ -4,6 +4,8 @@ on:
issues:
types: [assigned]
permissions: {} # We use ELEMENT_BOT_TOKEN instead
jobs:
web-app-team:
runs-on: ubuntu-24.04

View File

@@ -4,6 +4,8 @@ on:
issues:
types: [opened]
permissions: {} # We use ELEMENT_BOT_TOKEN instead
jobs:
automate-project-columns:
runs-on: ubuntu-24.04

View File

@@ -8,6 +8,8 @@ on:
ELEMENT_BOT_TOKEN:
required: true
permissions: {} # We use ELEMENT_BOT_TOKEN instead
jobs:
apply_Z-Labs_label:
name: Add Z-Labs label for features behind labs flags

View File

@@ -3,6 +3,7 @@ on:
pull_request_target:
types: [review_requested]
permissions: {} # Uses ELEMENT_BOT_TOKEN instead
jobs:
add_design_pr_to_project:
name: Move PRs asking for design review to the design board

View File

@@ -2,6 +2,7 @@ name: Close stale flaky issues
on:
schedule:
- cron: "30 1 * * *"
permissions: {}
jobs:
close:
runs-on: ubuntu-24.04

View File

@@ -3,11 +3,13 @@ name: Move unlabelled from needs info columns to triaged
on:
issues:
types: [unlabeled]
permissions: {}
jobs:
Move_Unabeled_Issue_On_Project_Board:
name: Move no longer X-Needs-Info issues to Triaged
runs-on: ubuntu-24.04
permissions:
repository-projects: read
if: >
${{
!contains(github.event.issue.labels.*.name, 'X-Needs-Info') }}

View File

@@ -4,6 +4,7 @@ on:
workflow_dispatch: {}
schedule:
- cron: "0 3 * * 0" # 3am every Sunday
permissions: {} # We use ELEMENT_BOT_TOKEN instead
jobs:
update:
runs-on: ubuntu-24.04

View File

@@ -15,6 +15,7 @@ on:
required: true
type: string
concurrency: ${{ github.workflow }}
permissions: {} # No permissions required
jobs:
bot:
name: Release topic update

View File

@@ -1,7 +1,7 @@
module.exports = {
extends: ["stylelint-config-standard"],
customSyntax: "postcss-scss",
plugins: ["stylelint-scss"],
plugins: ["stylelint-scss", "stylelint-value-no-unknown-custom-properties"],
rules: {
"comment-empty-line-before": null,
"declaration-empty-line-before": null,
@@ -46,5 +46,33 @@ module.exports = {
"number-max-precision": null,
"no-invalid-double-slash-comments": true,
"media-feature-range-notation": null,
"csstools/value-no-unknown-custom-properties": [
true,
{
importFrom: [
{ from: "res/css/_common.pcss", type: "css" },
{ from: "res/themes/light/css/_light.pcss", type: "css" },
// Right now our styles share vars all over the place, this is not ideal but acceptable for now
{ from: "res/css/views/rooms/_EventTile.pcss", type: "css" },
{ from: "res/css/views/rooms/_IRCLayout.pcss", type: "css" },
{ from: "res/css/views/rooms/_EventBubbleTile.pcss", type: "css" },
{ from: "res/css/views/rooms/_ReadReceiptGroup.pcss", type: "css" },
{ from: "res/css/views/rooms/_EditMessageComposer.pcss", type: "css" },
{ from: "res/css/views/right_panel/_BaseCard.pcss", type: "css" },
{ from: "res/css/views/messages/_MessageTimestamp.pcss", type: "css" },
{ from: "res/css/views/messages/_EventTileBubble.pcss", type: "css" },
{ from: "res/css/views/messages/_MessageActionBar.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" },
{ from: "res/css/structures/_RoomView.pcss", type: "css" },
// Compound vars
"node_modules/@vector-im/compound-design-tokens/assets/web/css/cpd-common-base.css",
"node_modules/@vector-im/compound-design-tokens/assets/web/css/cpd-common-semantic.css",
"node_modules/@vector-im/compound-design-tokens/assets/web/css/cpd-theme-light-base-mq.css",
"node_modules/@vector-im/compound-design-tokens/assets/web/css/cpd-theme-light-semantic-mq.css",
],
},
],
},
};

View File

@@ -1,3 +1,53 @@
Changes in [1.11.87](https://github.com/element-hq/element-web/releases/tag/v1.11.87) (2024-12-03)
==================================================================================================
## ✨ Features
* Send and respect MSC4230 is\_animated flag ([#28513](https://github.com/element-hq/element-web/pull/28513)). Contributed by @t3chguy.
* Display a warning when an unverified user's identity changes ([#28211](https://github.com/element-hq/element-web/pull/28211)). Contributed by @uhoreg.
* Swap out Twitter link for Mastodon on auth footer ([#28508](https://github.com/element-hq/element-web/pull/28508)). Contributed by @t3chguy.
* Consider `org.matrix.msc3417.call` as video room in create room dialog ([#28497](https://github.com/element-hq/element-web/pull/28497)). Contributed by @t3chguy.
* Standardise icons using Compound Design Tokens ([#28217](https://github.com/element-hq/element-web/pull/28217)). Contributed by @t3chguy.
* Start sending stable `m.marked_unread` events ([#28478](https://github.com/element-hq/element-web/pull/28478)). Contributed by @tulir.
* Upgrade to compound-design-tokens v2 ([#28471](https://github.com/element-hq/element-web/pull/28471)). Contributed by @t3chguy.
* Standardise icons using Compound Design Tokens ([#28286](https://github.com/element-hq/element-web/pull/28286)). Contributed by @t3chguy.
* Remove reply fallbacks as per merged MSC2781 ([#28406](https://github.com/element-hq/element-web/pull/28406)). Contributed by @t3chguy.
* Use React Suspense when rendering async modals ([#28386](https://github.com/element-hq/element-web/pull/28386)). Contributed by @t3chguy.
## 🐛 Bug Fixes
* Add spinner when room encryption is loading in room settings ([#28535](https://github.com/element-hq/element-web/pull/28535)). Contributed by @florianduros.
* Fix getOidcCallbackUrl for Element Desktop ([#28521](https://github.com/element-hq/element-web/pull/28521)). Contributed by @t3chguy.
* Filter out redacted poll votes to avoid crashing the Poll widget ([#28498](https://github.com/element-hq/element-web/pull/28498)). Contributed by @t3chguy.
* Fix force tab complete not working since switching to React 18 createRoot API ([#28505](https://github.com/element-hq/element-web/pull/28505)). Contributed by @t3chguy.
* Fix media captions in bubble layout ([#28480](https://github.com/element-hq/element-web/pull/28480)). Contributed by @tulir.
* Reset cross-signing before backup when resetting both ([#28402](https://github.com/element-hq/element-web/pull/28402)). Contributed by @uhoreg.
* Listen to events so that encryption icon updates when status changes ([#28407](https://github.com/element-hq/element-web/pull/28407)). Contributed by @uhoreg.
* Check that the file the user chose has a MIME type of `image/*` ([#28467](https://github.com/element-hq/element-web/pull/28467)). Contributed by @t3chguy.
* Fix download button size in message action bar ([#28472](https://github.com/element-hq/element-web/pull/28472)). Contributed by @t3chguy.
* Allow tab completing users in brackets ([#28460](https://github.com/element-hq/element-web/pull/28460)). Contributed by @t3chguy.
* Fix React 18 strict mode breaking spotlight dialog ([#28452](https://github.com/element-hq/element-web/pull/28452)). Contributed by @MidhunSureshR.
Changes in [1.11.86](https://github.com/element-hq/element-web/releases/tag/v1.11.86) (2024-11-19)
==================================================================================================
## ✨ Features
* Deduplicate icons using Compound Design Tokens ([#28419](https://github.com/element-hq/element-web/pull/28419)). Contributed by @t3chguy.
* Let widget driver send error details ([#28357](https://github.com/element-hq/element-web/pull/28357)). Contributed by @AndrewFerr.
* Deduplicate icons using Compound Design Tokens ([#28381](https://github.com/element-hq/element-web/pull/28381)). Contributed by @t3chguy.
* Auto approvoce `io.element.call.reaction` capability for element call widgets ([#28401](https://github.com/element-hq/element-web/pull/28401)). Contributed by @toger5.
* Show message type prefix in thread root \& reply previews ([#28361](https://github.com/element-hq/element-web/pull/28361)). Contributed by @t3chguy.
* Support sending encrypted to device messages from widgets ([#28315](https://github.com/element-hq/element-web/pull/28315)). Contributed by @hughns.
## 🐛 Bug Fixes
* Feed events to widgets as they are decrypted (even if out of order) ([#28376](https://github.com/element-hq/element-web/pull/28376)). Contributed by @robintown.
* Handle authenticated media when downloading from ImageView ([#28379](https://github.com/element-hq/element-web/pull/28379)). Contributed by @t3chguy.
* Ignore `m.3pid_changes` for Identity service 3PID changes ([#28375](https://github.com/element-hq/element-web/pull/28375)). Contributed by @t3chguy.
* Fix markdown escaping wrongly passing html through ([#28363](https://github.com/element-hq/element-web/pull/28363)). Contributed by @t3chguy.
* Remove "Upgrade your encryption" flow in `CreateSecretStorageDialog` ([#28290](https://github.com/element-hq/element-web/pull/28290)). Contributed by @florianduros.
Changes in [1.11.85](https://github.com/element-hq/element-web/releases/tag/v1.11.85) (2024-11-12)
==================================================================================================
# Security

View File

@@ -1,6 +0,0 @@
// Stub out FontManager for tests as it doesn't validate anything we don't already know given
// our fixed test environment and it requires the installation of node-canvas.
module.exports = {
fixupColorFonts: () => Promise.resolve(),
};

View File

@@ -592,4 +592,3 @@ The following are undocumented or intended for developer use only.
2. `sync_timeline_limit`
3. `dangerously_allow_unsafe_and_insecure_passwords`
4. `latex_maths_delims`: An optional setting to override the default delimiters used for maths parsing. See https://github.com/matrix-org/matrix-react-sdk/pull/5939 for details. Only used when `feature_latex_maths` is enabled.
5. `voice_broadcast.chunk_length`: Target chunk length in seconds for the Voice Broadcast feature currently under development.

View File

@@ -217,3 +217,10 @@ instead of the native `toHaveScreenshot`.
If you are running Linux and are unfortunate that the screenshots are not rendering identically,
you may wish to specify `--ignore-snapshots` and rely on Docker to render them for you.
## Test Tags
We use test tags to categorise tests for running subsets more efficiently.
- `@mergequeue`: Tests that are slow or flaky and cover areas of the app we update seldom, should not be run on every PR commit but will be run in the Merge Queue.
- `@screenshot`: Tests that use `toMatchScreenshot` to speed up a run of `test:playwright:screenshots`. A test with this tag must not also have the `@mergequeue` tag as this would cause false positives in the stale screenshot detection.

View File

@@ -32,7 +32,6 @@ const config: Config = {
"decoderWorker\\.min\\.wasm": "<rootDir>/__mocks__/empty.js",
"waveWorker\\.min\\.js": "<rootDir>/__mocks__/empty.js",
"context-filter-polyfill": "<rootDir>/__mocks__/empty.js",
"FontManager.ts": "<rootDir>/__mocks__/FontManager.js",
"workers/(.+)Factory": "<rootDir>/__mocks__/workerFactoryMock.js",
"^!!raw-loader!.*": "jest-raw-loader",
"recorderWorkletFactory": "<rootDir>/__mocks__/empty.js",

View File

@@ -1,6 +1,6 @@
{
"name": "element-web",
"version": "1.11.85",
"version": "1.11.87",
"description": "A feature-rich client for Matrix.org",
"author": "New Vector Ltd.",
"repository": {
@@ -64,7 +64,7 @@
"test:playwright:open": "yarn test:playwright --ui",
"test:playwright:screenshots": "yarn test:playwright:screenshots:build && yarn test:playwright:screenshots:run",
"test:playwright:screenshots:build": "docker build playwright -t element-web-playwright",
"test:playwright:screenshots:run": "docker run --rm --network host -e BASE_URL -e CI -v $(pwd):/work/ -v $(node -e 'console.log(require(`path`).dirname(require.resolve(`matrix-js-sdk/package.json`)))'):/work/node_modules/matrix-js-sdk -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/:/tmp/ -it element-web-playwright",
"test:playwright:screenshots:run": "docker run --rm --network host -e BASE_URL -e CI -v $(pwd):/work/ -v $(node -e 'console.log(require(`path`).dirname(require.resolve(`matrix-js-sdk/package.json`)))'):/work/node_modules/matrix-js-sdk -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/:/tmp/ -it element-web-playwright --grep @screenshot",
"coverage": "yarn test --coverage",
"analyse:unused-exports": "ts-node ./scripts/analyse_unused_exports.ts",
"analyse:webpack-bundles": "webpack-bundle-analyzer webpack-stats.json webapp",
@@ -73,12 +73,14 @@
"resolutions": {
"oidc-client-ts": "3.1.0",
"jwt-decode": "4.0.0",
"caniuse-lite": "1.0.30001679",
"caniuse-lite": "1.0.30001684",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
"wrap-ansi": "npm:wrap-ansi@^7.0.0"
},
"dependencies": {
"@babel/runtime": "^7.12.5",
"@fontsource/inconsolata": "^5",
"@fontsource/inter": "^5",
"@formatjs/intl-segmenter": "^11.5.7",
"@matrix-org/analytics-events": "^0.29.0",
"@matrix-org/emojibase-bindings": "^1.3.3",
@@ -86,7 +88,7 @@
"@matrix-org/spec": "^1.7.0",
"@sentry/browser": "^8.0.0",
"@vector-im/compound-design-tokens": "^2.1.0",
"@vector-im/compound-web": "^7.4.0",
"@vector-im/compound-web": "^7.5.0",
"@vector-im/matrix-wysiwyg": "2.37.13",
"@zxcvbn-ts/core": "^3.0.4",
"@zxcvbn-ts/language-common": "^3.0.4",
@@ -114,10 +116,10 @@
"jsrsasign": "^11.0.0",
"jszip": "^3.7.0",
"katex": "^0.16.0",
"linkify-element": "4.1.3",
"linkify-react": "4.1.3",
"linkify-string": "4.1.3",
"linkifyjs": "4.1.3",
"linkify-element": "4.2.0",
"linkify-react": "4.2.0",
"linkify-string": "4.2.0",
"linkifyjs": "4.2.0",
"lodash": "^4.17.21",
"maplibre-gl": "^4.0.0",
"matrix-encrypt-attachment": "^1.0.3",
@@ -216,7 +218,6 @@
"babel-loader": "^9.0.0",
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
"blob-polyfill": "^9.0.0",
"buffer": "^6.0.3",
"chokidar": "^4.0.0",
"concurrently": "^9.0.0",
"copy-webpack-plugin": "^12.0.0",
@@ -270,14 +271,16 @@
"postcss-preset-env": "^10.0.0",
"postcss-scss": "^4.0.4",
"postcss-simple-vars": "^7.0.1",
"prettier": "3.3.3",
"prettier": "3.4.2",
"process": "^0.11.10",
"raw-loader": "^4.0.2",
"rimraf": "^6.0.0",
"semver": "^7.5.2",
"source-map-loader": "^5.0.0",
"stylelint": "^16.1.0",
"stylelint-config-standard": "^36.0.0",
"stylelint-scss": "^6.0.0",
"stylelint-value-no-unknown-custom-properties": "^6.0.1",
"terser-webpack-plugin": "^5.3.9",
"ts-node": "^10.9.1",
"ts-prune": "^0.10.3",
@@ -298,6 +301,5 @@
},
"engines": {
"node": ">=20.0.0"
},
"packageManager": "yarn@1.22.19+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447"
}
}

View File

@@ -6,11 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import { defineConfig } from "@playwright/test";
import { defineConfig, devices } from "@playwright/test";
const baseURL = process.env["BASE_URL"] ?? "http://localhost:8080";
export default defineConfig({
projects: [{ name: "Chrome", use: { ...devices["Desktop Chrome"], channel: "chromium" } }],
use: {
viewport: { width: 1280, height: 720 },
ignoreHTTPSErrors: true,

View File

@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/playwright:v1.48.2-jammy
FROM mcr.microsoft.com/playwright:v1.49.1-noble
WORKDIR /work

View File

@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import { test, expect } from "../../element-web-test";
test(`shows error page if browser lacks Intl support`, async ({ page }) => {
test(`shows error page if browser lacks Intl support`, { tag: "@screenshot" }, async ({ page }) => {
await page.addInitScript({ content: `delete window.Intl;` });
await page.goto("/");
@@ -21,7 +21,7 @@ test(`shows error page if browser lacks Intl support`, async ({ page }) => {
await expect(page).toMatchScreenshot("unsupported-browser.png");
});
test(`shows error page if browser lacks WebAssembly support`, async ({ page }) => {
test(`shows error page if browser lacks WebAssembly support`, { tag: "@screenshot" }, async ({ page }) => {
await page.addInitScript({ content: `delete window.WebAssembly;` });
await page.goto("/");

View File

@@ -134,18 +134,22 @@ test.describe("Audio player", () => {
).toBeVisible();
});
test("should be correctly rendered - light theme", async ({ page, app }) => {
test("should be correctly rendered - light theme", { tag: "@screenshot" }, async ({ page, app }) => {
await uploadFile(page, "playwright/sample-files/1sec-long-name-audio-file.ogg");
await takeSnapshots(page, app, "Selected EventTile of audio player (light theme)");
});
test("should be correctly rendered - light theme with monospace font", async ({ page, app }) => {
test(
"should be correctly rendered - light theme with monospace font",
{ tag: "@screenshot" },
async ({ page, app }) => {
await uploadFile(page, "playwright/sample-files/1sec-long-name-audio-file.ogg");
await takeSnapshots(page, app, "Selected EventTile of audio player (light theme, monospace font)", true); // Enable monospace
});
},
);
test("should be correctly rendered - high contrast theme", async ({ page, app }) => {
test("should be correctly rendered - high contrast theme", { tag: "@screenshot" }, async ({ page, app }) => {
// Disable system theme in case ThemeWatcher enables the theme automatically,
// so that the high contrast theme can be enabled
await app.settings.setValue("use_system_theme", null, SettingLevel.DEVICE, false);
@@ -161,7 +165,7 @@ test.describe("Audio player", () => {
await takeSnapshots(page, app, "Selected EventTile of audio player (high contrast)");
});
test("should be correctly rendered - dark theme", async ({ page, app }) => {
test("should be correctly rendered - dark theme", { tag: "@screenshot" }, async ({ page, app }) => {
// Enable dark theme
await app.settings.setValue("theme", null, SettingLevel.ACCOUNT, "dark");
@@ -207,7 +211,10 @@ test.describe("Audio player", () => {
expect(download.suggestedFilename()).toBe("1sec.ogg");
});
test("should support replying to audio file with another audio file", async ({ page, app }) => {
test(
"should support replying to audio file with another audio file",
{ tag: "@screenshot" },
async ({ page, app }) => {
await uploadFile(page, "playwright/sample-files/1sec.ogg");
// Assert the audio player is rendered
@@ -230,9 +237,13 @@ test.describe("Audio player", () => {
await expect(button.locator(".mx_MFileBody_info_filename")).toBeVisible();
await takeSnapshots(page, app, "Selected EventTile of audio player with a reply");
});
},
);
test("should support creating a reply chain with multiple audio files", async ({ page, app, user }) => {
test(
"should support creating a reply chain with multiple audio files",
{ tag: "@screenshot" },
async ({ page, app, user }) => {
// Note: "mx_ReplyChain" element is used not only for replies which
// create a reply chain, but also for a single reply without a replied
// message. This test checks whether a reply chain which consists of
@@ -293,7 +304,8 @@ test.describe("Audio player", () => {
// Take snapshots
await takeSnapshots(page, app, "Selected EventTile of audio player with a reply chain");
});
},
);
test("should be rendered, play, and support replying on a thread", async ({ page, app }) => {
await uploadFile(page, "playwright/sample-files/1sec-long-name-audio-file.ogg");

View File

@@ -89,7 +89,10 @@ test.describe("HTML Export", () => {
},
});
test("should export html successfully and match screenshot", async ({ page, app, room }) => {
test(
"should export html successfully and match screenshot",
{ tag: "@screenshot" },
async ({ page, app, room }) => {
// Set a fixed time rather than masking off the line with the time in it: we don't need to worry
// about the width changing and we can actually test this line looks correct.
page.clock.setSystemTime(new Date("2024-01-01T00:00:00Z"));
@@ -127,5 +130,6 @@ test.describe("HTML Export", () => {
page.locator(".mx_MessageTimestamp"),
],
});
});
},
);
});

View File

@@ -204,12 +204,10 @@ test.describe("Cryptography", function () {
await expect(page.locator(".mx_Dialog")).toHaveCount(1);
});
test("creating a DM should work, being e2e-encrypted / user verification", async ({
page,
app,
bot: bob,
user: aliceCredentials,
}) => {
test(
"creating a DM should work, being e2e-encrypted / user verification",
{ tag: "@screenshot" },
async ({ page, app, bot: bob, user: aliceCredentials }) => {
await app.client.bootstrapCrossSigning(aliceCredentials);
await startDMWithBob(page, bob);
// send first message
@@ -227,7 +225,8 @@ test.describe("Cryptography", function () {
// Take a snapshot of RoomSummaryCard with a verified E2EE icon
await expect(page.locator(".mx_RightPanel")).toMatchScreenshot("RoomSummaryCard-with-verified-e2ee.png");
});
},
);
test("should allow verification when there is no existing DM", async ({
page,

View File

@@ -67,6 +67,9 @@ test.describe("Cryptography", function () {
await page.locator(".mx_AuthPage").getByRole("button", { name: "I'll verify later" }).click();
await app.viewRoomByName("Test room");
// In this case, the call to cryptoApi.isEncryptionEnabledInRoom is taking a long time to resolve
await page.waitForTimeout(1000);
// There should be two historical events in the timeline
const tiles = await page.locator(".mx_EventTile").all();
expect(tiles.length).toBeGreaterThanOrEqual(2);

View File

@@ -102,7 +102,7 @@ test.describe("Device verification", () => {
// feed the QR code into the verification request.
const qrData = await readQrCode(infoDialog);
const verifier = await verificationRequest.evaluateHandle(
(request, qrData) => request.scanQRCode(new Uint8Array(qrData)),
(request, qrData) => request.scanQRCode(new Uint8ClampedArray(qrData)),
[...qrData],
);

View File

@@ -6,6 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import { Locator } from "@playwright/test";
import { expect, test } from "../../element-web-test";
import {
autoJoin,
@@ -16,6 +18,8 @@ import {
logOutOfElement,
verify,
} from "./utils";
import { bootstrapCrossSigningForClient } from "../../pages/client.ts";
import { ElementAppPage } from "../../pages/ElementAppPage.ts";
test.describe("Cryptography", function () {
test.use({
@@ -276,6 +280,15 @@ test.describe("Cryptography", function () {
bot: bob,
homeserver,
}) => {
// Workaround for https://github.com/element-hq/element-web/issues/28640:
// make sure that Alice has seen Bob's identity before she goes offline. We do this by opening
// his user info.
await app.toggleRoomInfoPanel();
const rightPanel = page.locator(".mx_RightPanel");
await rightPanel.getByRole("menuitem", { name: "People" }).click();
await rightPanel.getByRole("button", { name: bob.credentials!.userId }).click();
await expect(rightPanel.locator(".mx_UserInfo_devices")).toContainText("1 session");
// Our app is blocked from syncing while Bob sends his messages.
await app.client.network.goOffline();
@@ -305,7 +318,50 @@ test.describe("Cryptography", function () {
);
const penultimate = page.locator(".mx_EventTile").filter({ hasText: "test encrypted from verified" });
await expect(penultimate.locator(".mx_EventTile_e2eIcon")).not.toBeVisible();
await assertNoE2EIcon(penultimate, app);
});
test("should show correct shields on events sent by users with changed identity", async ({
page,
app,
bot: bob,
homeserver,
}) => {
// Verify Bob
await verify(app, bob);
// Bob logs in a new device and resets cross-signing
const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob);
await bootstrapCrossSigningForClient(await bobSecondDevice.prepareClient(), bob.credentials, true);
/* should show an error for a message from a previously verified device */
await bobSecondDevice.sendMessage(testRoomId, "test encrypted from user that was previously verified");
const last = page.locator(".mx_EventTile_last");
await expect(last).toContainText("test encrypted from user that was previously verified");
const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon");
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
await lastE2eIcon.focus();
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
"Sender's verified identity has changed",
);
});
});
});
/**
* Check that the given message doesn't have an E2E warning icon.
*
* If it does, throw an error.
*/
async function assertNoE2EIcon(messageLocator: Locator, app: ElementAppPage) {
// Make sure the message itself exists, before we check if it has any icons
await messageLocator.waitFor();
const e2eIcon = messageLocator.locator(".mx_EventTile_e2eIcon");
if ((await e2eIcon.count()) > 0) {
// uh-oh, there is an e2e icon. Let's find out what it's about so that we can throw a helpful error.
await e2eIcon.focus();
const tooltip = await app.getTooltipForElement(e2eIcon);
throw new Error(`Found an unexpected e2eIcon with tooltip '${await tooltip.textContent()}'`);
}
}

View File

@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import { type Preset, type Visibility } from "matrix-js-sdk/src/matrix";
import type { Page } from "@playwright/test";
import { test, expect } from "../../element-web-test";
import { doTwoWaySasVerification, awaitVerifier } from "./utils";
import { Client } from "../../pages/client";
@@ -38,6 +39,8 @@ test.describe("User verification", () => {
toasts,
room: { roomId: dmRoomId },
}) => {
await waitForDeviceKeys(page);
// once Alice has joined, Bob starts the verification
const bobVerificationRequest = await bob.evaluateHandle(
async (client, { dmRoomId, aliceCredentials }) => {
@@ -87,6 +90,8 @@ test.describe("User verification", () => {
toasts,
room: { roomId: dmRoomId },
}) => {
await waitForDeviceKeys(page);
// once Alice has joined, Bob starts the verification
const bobVerificationRequest = await bob.evaluateHandle(
async (client, { dmRoomId, aliceCredentials }) => {
@@ -149,3 +154,15 @@ async function createDMRoom(client: Client, userId: string): Promise<string> {
],
});
}
/**
* Wait until we get the other user's device keys.
* In newer rust-crypto versions, the verification request will be ignored if we
* don't have the sender's device keys.
*/
async function waitForDeviceKeys(page: Page): Promise<void> {
await expect(page.getByRole("button", { name: "Avatar" })).toBeVisible();
const avatar = await page.getByRole("button", { name: "Avatar" });
await avatar.click();
await expect(page.getByText("1 session")).toBeVisible();
}

View File

@@ -66,7 +66,10 @@ test.describe("Editing", () => {
botCreateOpts: { displayName: "Bob" },
});
test("should render and interact with the message edit history dialog", async ({ page, user, app, room }) => {
test(
"should render and interact with the message edit history dialog",
{ tag: "@screenshot" },
async ({ page, user, app, room }) => {
// Click the "Remove" button on the message edit history dialog
const clickButtonRemove = async (locator: Locator) => {
const eventTileLine = locator.locator(".mx_EventTile_line");
@@ -185,7 +188,8 @@ test.describe("Editing", () => {
.locator(".mx_RoomView_MessageList")
.locator(".mx_EventTile_last .mx_RedactedBody", { hasText: "Message deleted" }),
).toBeVisible();
});
},
);
test("should render 'View Source' button in developer mode on the message edit history dialog", async ({
page,

View File

@@ -25,7 +25,7 @@ test.describe("Image Upload", () => {
).toBeVisible();
});
test("should show image preview when uploading an image", async ({ page, app }) => {
test("should show image preview when uploading an image", { tag: "@screenshot" }, async ({ page, app }) => {
await page
.locator(".mx_MessageComposer_actions input[type='file']")
.setInputFiles("playwright/sample-files/riot.png");

View File

@@ -26,7 +26,7 @@ test.describe("Forgot Password", () => {
}),
});
test("renders properly", async ({ page, homeserver }) => {
test("renders properly", { tag: "@screenshot" }, async ({ page, homeserver }) => {
await page.goto("/");
await page.getByRole("link", { name: "Sign in" }).click();
@@ -39,7 +39,7 @@ test.describe("Forgot Password", () => {
await expect(page.getByRole("main")).toMatchScreenshot("forgot-password.png");
});
test("renders email verification dialog properly", async ({ page, homeserver }) => {
test("renders email verification dialog properly", { tag: "@screenshot" }, async ({ page, homeserver }) => {
const user = await homeserver.registerUser(username, password);
await homeserver.setThreepid(user.userId, "email", email);

View File

@@ -19,7 +19,7 @@ test.describe("Invite dialog", function () {
const botName = "BotAlice";
test("should support inviting a user to a room", async ({ page, app, user, bot }) => {
test("should support inviting a user to a room", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
// Create and view a room
await app.client.createRoom({ name: "Test Room" });
await app.viewRoomByName("Test Room");
@@ -73,12 +73,17 @@ test.describe("Invite dialog", function () {
await expect(page.getByText(`${botName} joined the room`)).toBeVisible();
});
test("should support inviting a user to Direct Messages", async ({ page, app, user, bot }) => {
test(
"should support inviting a user to Direct Messages",
{ tag: "@screenshot" },
async ({ page, app, user, bot }) => {
await page.locator(".mx_RoomList").getByRole("button", { name: "Start chat" }).click();
const other = page.locator(".mx_InviteDialog_other");
// Assert that the header is rendered
await expect(other.locator(".mx_Dialog_header .mx_Dialog_title").getByText("Direct Messages")).toBeVisible();
await expect(
other.locator(".mx_Dialog_header .mx_Dialog_title").getByText("Direct Messages"),
).toBeVisible();
// Assert that the bar is rendered
await expect(other.locator(".mx_InviteDialog_addressBar")).toBeVisible();
@@ -88,7 +93,9 @@ test.describe("Invite dialog", function () {
await other.getByTestId("invite-dialog-input").fill(bot.credentials.userId);
await expect(other.locator(".mx_InviteDialog_tile_nameStack").getByText(bot.credentials.userId)).toBeVisible();
await expect(
other.locator(".mx_InviteDialog_tile_nameStack").getByText(bot.credentials.userId),
).toBeVisible();
await other.locator(".mx_InviteDialog_tile_nameStack").getByText(botName).click();
await expect(
@@ -108,7 +115,10 @@ test.describe("Invite dialog", function () {
// TODO: implement the test on room-header.spec.ts
const roomHeader = page.locator(".mx_RoomHeader");
await roomHeader.locator(".mx_RoomHeader_heading").hover();
await expect(roomHeader.locator(".mx_RoomHeader_heading")).toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
await expect(roomHeader.locator(".mx_RoomHeader_heading")).toHaveCSS(
"background-color",
"rgba(0, 0, 0, 0)",
);
// Send a message to invite the bots
const composer = app.getComposer().locator("[contenteditable]");
@@ -120,5 +130,6 @@ test.describe("Invite dialog", function () {
// Assert that the message is displayed at the bottom
await expect(page.locator(".mx_EventTile_last").getByText("Hello")).toBeVisible();
});
},
);
});

View File

@@ -63,7 +63,7 @@ test.describe("Message rendering", () => {
{ direction: "ltr", displayName: "Quentin" },
{ direction: "rtl", displayName: "كوينتين" },
].forEach(({ direction, displayName }) => {
test.describe(`with ${direction} display name`, () => {
test.describe(`with ${direction} display name`, { tag: "@screenshot" }, () => {
test.use({
displayName,
room: async ({ user, app }, use) => {
@@ -72,14 +72,18 @@ test.describe("Message rendering", () => {
},
});
test("should render a basic LTR text message", async ({ page, user, app, room }) => {
test(
"should render a basic LTR text message",
{ tag: "@screenshot" },
async ({ page, user, app, room }) => {
await page.goto(`#/room/${room.roomId}`);
const msgTile = await sendMessage(page, "Hello, world!");
await expect(msgTile).toMatchScreenshot(`basic-message-ltr-${direction}displayname.png`, {
mask: [page.locator(".mx_MessageTimestamp")],
});
});
},
);
test("should render an LTR emote", async ({ page, user, app, room }) => {
await page.goto(`#/room/${room.roomId}`);

View File

@@ -24,7 +24,7 @@ test.describe("permalinks", () => {
displayName: "Alice",
});
test("shoud render permalinks as expected", async ({ page, app, user, homeserver }) => {
test("shoud render permalinks as expected", { tag: "@screenshot" }, async ({ page, app, user, homeserver }) => {
const bob = new Bot(page, homeserver, { displayName: "Bob" });
const charlotte = new Bot(page, homeserver, { displayName: "Charlotte" });
await bob.prepareClient();

View File

@@ -129,6 +129,7 @@ export class Helpers {
const timelineMessage = this.page.locator(".mx_MTextBody", { hasText: message });
await timelineMessage.click({ button: "right" });
await this.page.getByRole("menuitem", { name: "Pin", exact: true }).click();
await this.assertMessageInBanner(message);
}
/**

View File

@@ -10,20 +10,22 @@ import { test } from "./index";
import { expect } from "../../element-web-test";
test.describe("Pinned messages", () => {
test("should show the empty state when there are no pinned messages", async ({ page, app, room1, util }) => {
test(
"should show the empty state when there are no pinned messages",
{ tag: "@screenshot" },
async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.openRoomInfo();
await util.assertPinnedCountInRoomInfo(0);
await util.openPinnedMessagesList();
await util.assertEmptyPinnedMessagesList();
});
},
);
test("should pin one message and to have the pinned message badge in the timeline", async ({
page,
app,
room1,
util,
}) => {
test(
"should pin one message and to have the pinned message badge in the timeline",
{ tag: "@screenshot" },
async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.receiveMessages(room1, ["Msg1"]);
await util.pinMessages(["Msg1"]);
@@ -38,7 +40,8 @@ test.describe("Pinned messages", () => {
}
`,
});
});
},
);
test("should pin messages and show them in the room info panel", async ({ page, app, room1, util }) => {
await util.goTo(room1);
@@ -73,7 +76,7 @@ test.describe("Pinned messages", () => {
await util.assertPinnedCountInRoomInfo(2);
});
test("should unpin all messages", async ({ page, app, room1, util }) => {
test("should unpin all messages", { tag: "@screenshot" }, async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.receiveMessages(room1, ["Msg1", "Msg2", "Msg3", "Msg4"]);
await util.pinMessages(["Msg1", "Msg2", "Msg4"]);
@@ -98,7 +101,7 @@ test.describe("Pinned messages", () => {
await util.assertPinnedCountInRoomInfo(0);
});
test("should display one message in the banner", async ({ page, app, room1, util }) => {
test("should display one message in the banner", { tag: "@screenshot" }, async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.receiveMessages(room1, ["Msg1"]);
await util.pinMessages(["Msg1"]);
@@ -106,7 +109,7 @@ test.describe("Pinned messages", () => {
await expect(util.getBanner()).toMatchScreenshot("pinned-message-banner-1-Msg1.png");
});
test("should display 2 messages in the banner", async ({ page, app, room1, util }) => {
test("should display 2 messages in the banner", { tag: "@screenshot" }, async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.receiveMessages(room1, ["Msg1", "Msg2"]);
await util.pinMessages(["Msg1", "Msg2"]);
@@ -123,7 +126,7 @@ test.describe("Pinned messages", () => {
await expect(util.getBanner()).toMatchScreenshot("pinned-message-banner-2-Msg2.png");
});
test("should display 4 messages in the banner", async ({ page, app, room1, util }) => {
test("should display 4 messages in the banner", { tag: "@screenshot" }, async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.receiveMessages(room1, ["Msg1", "Msg2", "Msg3", "Msg4"]);
await util.pinMessages(["Msg1", "Msg2", "Msg3", "Msg4"]);

View File

@@ -93,7 +93,7 @@ test.describe("Polls", () => {
});
});
test("should be creatable and votable", async ({ page, app, bot, user }) => {
test("should be creatable and votable", { tag: "@screenshot" }, async ({ page, app, bot, user }) => {
const roomId: string = await app.client.createRoom({});
await app.client.inviteUser(roomId, bot.credentials.userId);
await page.goto("/#/room/" + roomId);
@@ -219,7 +219,10 @@ test.describe("Polls", () => {
await expect(page.locator(".mx_ErrorDialog")).toBeAttached();
});
test("should be displayed correctly in thread panel", async ({ page, app, user, bot, homeserver }) => {
test(
"should be displayed correctly in thread panel",
{ tag: "@screenshot" },
async ({ page, app, user, bot, homeserver }) => {
const botCharlie = new Bot(page, homeserver, { displayName: "BotCharlie" });
await botCharlie.prepareClient();
@@ -229,7 +232,9 @@ test.describe("Polls", () => {
await page.goto("/#/room/" + roomId);
// wait until the bots joined
await expect(page.getByText("BotBob and one other were invited and joined")).toBeAttached({ timeout: 10000 });
await expect(page.getByText("BotBob and one other were invited and joined")).toBeAttached({
timeout: 10000,
});
const locator = await app.openMessageComposerOptions();
await locator.getByRole("menuitem", { name: "Poll" }).click();
@@ -274,22 +279,30 @@ test.describe("Polls", () => {
// and thread view
await expect(
page.locator(".mx_ThreadView .mx_MPollBody_totalVotes").getByText("2 votes cast. Vote to see the results"),
page
.locator(".mx_ThreadView .mx_MPollBody_totalVotes")
.getByText("2 votes cast. Vote to see the results"),
).toBeAttached();
// Take snapshots of poll on ThreadView
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Bubble);
await expect(page.locator(".mx_ThreadView .mx_EventTile[data-layout='bubble']").first()).toBeVisible();
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot("ThreadView_with_a_poll_on_bubble_layout.png", {
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
"ThreadView_with_a_poll_on_bubble_layout.png",
{
mask: [page.locator(".mx_MessageTimestamp")],
});
},
);
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Group);
await expect(page.locator(".mx_ThreadView .mx_EventTile[data-layout='group']").first()).toBeVisible();
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot("ThreadView_with_a_poll_on_group_layout.png", {
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
"ThreadView_with_a_poll_on_group_layout.png",
{
mask: [page.locator(".mx_MessageTimestamp")],
});
},
);
const roomViewLocator = page.locator(".mx_RoomView_body");
// vote 'Maybe' in the main timeline poll
@@ -321,5 +334,6 @@ test.describe("Polls", () => {
// and in thread view tile
await expectVoteCounts(page.locator(".mx_ThreadView"));
});
},
);
});

View File

@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
import { test } from ".";
test.describe("Read receipts", () => {
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("editing messages", () => {
test.describe("in threads", () => {
test("An edit of a threaded message makes the room unread", async ({

View File

@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
import { test } from ".";
test.describe("Read receipts", () => {
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("editing messages", () => {
test.describe("in the main timeline", () => {
test("Editing a message leaves a room read", async ({ roomAlpha: room1, roomBeta: room2, util, msg }) => {

View File

@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
import { test } from ".";
test.describe("Read receipts", () => {
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("editing messages", () => {
test.describe("thread roots", () => {
test("An edit of a thread root leaves the room read", async ({

View File

@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
import { customEvent, many, test } from ".";
test.describe("Read receipts", () => {
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("Ignored events", () => {
test("If all events after receipt are unimportant, the room is read", async ({
roomAlpha: room1,

View File

@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
import { test } from ".";
test.describe("Read receipts", () => {
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("Message ordering", () => {
test.describe("in the main timeline", () => {
test.fixme(

View File

@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
import { test } from ".";
test.describe("Read receipts", () => {
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("messages with missing referents", () => {
test.fixme(
"A message in an unknown thread is not visible and the room is read",

View File

@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
import { many, test } from ".";
test.describe("Read receipts", () => {
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("new messages", () => {
test.describe("in threads", () => {
test("Receiving a message makes a room unread", async ({

View File

@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
import { many, test } from ".";
test.describe("Read receipts", () => {
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("new messages", () => {
test.describe("in the main timeline", () => {
test("Receiving a message makes a room unread", async ({

View File

@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
import { many, test } from ".";
test.describe("Read receipts", () => {
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("new messages", () => {
test.describe("thread roots", () => {
test("Reading a thread root does not mark the thread as read", async ({

View File

@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
import { test } from ".";
test.describe("Read receipts", () => {
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("Notifications", () => {
test.describe("in the main timeline", () => {
test.fixme("A new message that mentions me shows a notification", () => {});

View File

@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
import { test, expect } from ".";
test.describe("Read receipts", () => {
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("reactions", () => {
test.describe("in threads", () => {
test("A reaction to a threaded message does not make the room unread", async ({

View File

@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
import { test } from ".";
test.describe("Read receipts", () => {
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("reactions", () => {
test.describe("in the main timeline", () => {
test("Receiving a reaction to a message does not make a room unread", async ({

View File

@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
import { test } from ".";
test.describe("Read receipts", () => {
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("reactions", () => {
test.describe("thread roots", () => {
test("A reaction to a thread root does not make the room unread", async ({

View File

@@ -13,7 +13,7 @@ import { ElementAppPage } from "../../pages/ElementAppPage";
import { Bot } from "../../pages/bot";
import { test } from ".";
test.describe("Read receipts", () => {
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.use({
displayName: "Mae",
botCreateOpts: { displayName: "Other User" },

View File

@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
import { test } from ".";
test.describe("Read receipts", () => {
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("redactions", () => {
test.describe("in threads", () => {
test("Redacting the threaded message pointed to by my receipt leaves the room read", async ({

View File

@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
import { test } from ".";
test.describe("Read receipts", () => {
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("redactions", () => {
test.describe("in the main timeline", () => {
test("Redacting the message pointed to by my receipt leaves the room read", async ({

View File

@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
import { test } from ".";
test.describe("Read receipts", () => {
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("redactions", () => {
test.describe("thread roots", () => {
test("Redacting a thread root after it was read leaves the room read", async ({

View File

@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
import { test } from ".";
test.describe("Read receipts", () => {
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("Room list order", () => {
test("Rooms with unread messages appear at the top of room list if 'unread first' is selected", async ({
roomAlpha: room1,

View File

@@ -38,12 +38,10 @@ test.describe("Email Registration", async () => {
await page.goto("/#/register");
});
test("registers an account and lands on the use case selection screen", async ({
page,
mailhog,
request,
checkA11y,
}) => {
test(
"registers an account and lands on the use case selection screen",
{ tag: "@screenshot" },
async ({ page, mailhog, request, checkA11y }) => {
await expect(page.getByRole("textbox", { name: "Username" })).toBeVisible();
// Hide the server text as it contains the randomly allocated Homeserver port
const screenshotOptions = { mask: [page.locator(".mx_ServerPicker_server")] };
@@ -67,5 +65,6 @@ test.describe("Email Registration", async () => {
await request.get(emailLink); // "Click" the link in the email
await expect(page.locator(".mx_UseCaseSelection_skip")).toBeVisible();
});
},
);
});

View File

@@ -15,7 +15,10 @@ test.describe("Registration", () => {
await page.goto("/#/register");
});
test("registers an account and lands on the home screen", async ({ homeserver, page, checkA11y, crypto }) => {
test(
"registers an account and lands on the home screen",
{ tag: "@screenshot" },
async ({ homeserver, page, checkA11y, crypto }) => {
await page.getByRole("button", { name: "Edit", exact: true }).click();
await expect(page.getByRole("button", { name: "Continue", exact: true })).toBeVisible();
@@ -29,7 +32,10 @@ test.describe("Registration", () => {
await expect(page.getByRole("textbox", { name: "Username", exact: true })).toBeVisible();
// Hide the server text as it contains the randomly allocated Homeserver port
const screenshotOptions = { mask: [page.locator(".mx_ServerPicker_server")], includeDialogBackground: true };
const screenshotOptions = {
mask: [page.locator(".mx_ServerPicker_server")],
includeDialogBackground: true,
};
await expect(page).toMatchScreenshot("registration.png", screenshotOptions);
await checkA11y();
@@ -68,13 +74,14 @@ test.describe("Registration", () => {
await page.getByRole("button", { name: "User menu", exact: true }).click();
await page.getByRole("menuitem", { name: "All settings", exact: true }).click();
await page.getByRole("tab", { name: "Sessions", exact: true }).click();
await expect(page.getByTestId("current-session-section").getByTestId("device-metadata-isVerified")).toHaveText(
"Verified",
);
await expect(
page.getByTestId("current-session-section").getByTestId("device-metadata-isVerified"),
).toHaveText("Verified");
// check that cross-signing keys have been uploaded.
await crypto.assertDeviceIsCrossSigned();
});
},
);
test("should require username to fulfil requirements and be available", async ({ homeserver, page }) => {
await page.getByRole("button", { name: "Edit", exact: true }).click();

View File

@@ -18,7 +18,7 @@ test.describe("Release announcement", () => {
labsFlags: ["threadsActivityCentre"],
});
test("should display the release announcement process", async ({ page, app, util }) => {
test("should display the release announcement process", { tag: "@screenshot" }, async ({ page, app, util }) => {
// The TAC release announcement should be displayed
await util.assertReleaseAnnouncementIsVisible("Threads Activity Centre");
// Hide the release announcement

View File

@@ -40,7 +40,7 @@ test.describe("FilePanel", () => {
});
test.describe("render", () => {
test("should render empty state", async ({ page }) => {
test("should render empty state", { tag: "@screenshot" }, async ({ page }) => {
// Wait until the information about the empty state is rendered
await expect(page.locator(".mx_EmptyState")).toBeVisible();
@@ -48,7 +48,7 @@ test.describe("FilePanel", () => {
await expect(page.locator(".mx_RightPanel")).toMatchScreenshot("empty.png");
});
test("should list tiles on the panel", async ({ page }) => {
test("should list tiles on the panel", { tag: "@screenshot" }, async ({ page }) => {
// Upload multiple files
await uploadFile(page, "playwright/sample-files/riot.png"); // Image
await uploadFile(page, "playwright/sample-files/1sec.ogg"); // Audio

View File

@@ -21,7 +21,7 @@ test.describe("NotificationPanel", () => {
await app.client.createRoom({ name: ROOM_NAME });
});
test("should render empty state", async ({ page, app }) => {
test("should render empty state", { tag: "@screenshot" }, async ({ page, app }) => {
await app.viewRoomByName(ROOM_NAME);
await page.getByRole("button", { name: "Notifications" }).click();

View File

@@ -38,7 +38,7 @@ test.describe("RightPanel", () => {
});
test.describe("in rooms", () => {
test("should handle long room address and long room name", async ({ page, app }) => {
test("should handle long room address and long room name", { tag: "@screenshot" }, async ({ page, app }) => {
await app.client.createRoom({ name: ROOM_NAME_LONG });
await viewRoomSummaryByName(page, app, ROOM_NAME_LONG);

View File

@@ -47,7 +47,10 @@ test.describe("Room Directory", () => {
expect(resp.chunk[0].room_id).toEqual(roomId);
});
test("should allow finding published rooms in directory", async ({ page, app, user, bot }) => {
test(
"should allow finding published rooms in directory",
{ tag: "@screenshot" },
async ({ page, app, user, bot }) => {
const name = "This is a public room";
await bot.createRoom({
visibility: "public" as Visibility,
@@ -60,7 +63,9 @@ test.describe("Room Directory", () => {
const dialog = page.locator(".mx_SpotlightDialog");
await dialog.getByRole("textbox", { name: "Search" }).fill("Unknown Room");
await expect(
dialog.getByText("If you can't find the room you're looking for, ask for an invite or create a new room."),
dialog.getByText(
"If you can't find the room you're looking for, ask for an invite or create a new room.",
),
).toHaveClass("mx_SpotlightDialog_otherSearches_messageSearchText");
await expect(page.locator(".mx_Dialog")).toMatchScreenshot("filtered-no-results.png");
@@ -76,5 +81,6 @@ test.describe("Room Directory", () => {
.click();
await expect(page).toHaveURL("/#/room/#test1234:localhost");
});
},
);
});

View File

@@ -20,7 +20,7 @@ test.describe("Room Header", () => {
test.use({
labsFlags: ["feature_notifications"],
});
test("should render default buttons properly", async ({ page, app, user }) => {
test("should render default buttons properly", { tag: "@screenshot" }, async ({ page, app, user }) => {
await app.client.createRoom({ name: "Test Room" });
await app.viewRoomByName("Test Room");
@@ -51,7 +51,10 @@ test.describe("Room Header", () => {
await expect(header).toMatchScreenshot("room-header.png");
});
test("should render a very long room name without collapsing the buttons", async ({ page, app, user }) => {
test(
"should render a very long room name without collapsing the buttons",
{ tag: "@screenshot" },
async ({ page, app, user }) => {
const LONG_ROOM_NAME =
"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore " +
"et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut " +
@@ -78,7 +81,8 @@ test.describe("Room Header", () => {
}
await expect(header).toMatchScreenshot("room-header-long-name.png");
});
},
);
});
test.describe("with a video room", () => {
@@ -99,7 +103,10 @@ test.describe("Room Header", () => {
test.describe("and with feature_notifications enabled", () => {
test.use({ labsFlags: ["feature_video_rooms", "feature_notifications"] });
test("should render buttons for chat, room info, threads and facepile", async ({ page, app, user }) => {
test(
"should render buttons for chat, room info, threads and facepile",
{ tag: "@screenshot" },
async ({ page, app, user }) => {
await createVideoRoom(page, app);
const header = page.locator(".mx_RoomHeader");
@@ -122,7 +129,8 @@ test.describe("Room Header", () => {
await expect(header.getByRole("button")).toHaveCount(7);
await expect(header).toMatchScreenshot("room-header-video-room.png");
});
},
);
});
test("should render a working chat button which opens the timeline on a right panel", async ({

View File

@@ -23,7 +23,7 @@ test.describe("Account user settings tab", () => {
},
});
test("should be rendered properly", async ({ uut, user }) => {
test("should be rendered properly", { tag: "@screenshot" }, async ({ uut, user }) => {
await expect(uut).toMatchScreenshot("account.png");
// Assert that the top heading is rendered
@@ -71,7 +71,7 @@ test.describe("Account user settings tab", () => {
);
});
test("should respond to small screen sizes", async ({ page, uut }) => {
test("should respond to small screen sizes", { tag: "@screenshot" }, async ({ page, uut }) => {
await page.setViewportSize({ width: 700, height: 600 });
await expect(uut).toMatchScreenshot("account-smallscreen.png");
});

View File

@@ -13,7 +13,7 @@ test.describe("Appearance user settings tab", () => {
displayName: "Hanako",
});
test("should be rendered properly", async ({ page, user, app }) => {
test("should be rendered properly", { tag: "@screenshot" }, async ({ page, user, app }) => {
const tab = await app.settings.openUserSettings("Appearance");
// Click "Show advanced" link button
@@ -25,7 +25,10 @@ test.describe("Appearance user settings tab", () => {
await expect(tab).toMatchScreenshot("appearance-tab.png");
});
test("should support changing font size by using the font size dropdown", async ({ page, app, user }) => {
test(
"should support changing font size by using the font size dropdown",
{ tag: "@screenshot" },
async ({ page, app, user }) => {
await app.settings.openUserSettings("Appearance");
const tab = page.getByTestId("mx_AppearanceUserSettingsTab");
@@ -37,7 +40,8 @@ test.describe("Appearance user settings tab", () => {
await fontDropdown.getByLabel("Font size").selectOption({ value: "-4" });
await expect(page).toMatchScreenshot("window-12px.png", { includeDialogBackground: true });
});
},
);
test("should support enabling system font", async ({ page, app, user }) => {
await app.settings.openUserSettings("Appearance");

View File

@@ -20,7 +20,10 @@ test.describe("Appearance user settings tab", () => {
await util.openAppearanceTab();
});
test("should change the message layout from modern to bubble", async ({ page, app, user, util }) => {
test(
"should change the message layout from modern to bubble",
{ tag: "@screenshot" },
async ({ page, app, user, util }) => {
await util.assertScreenshot(util.getMessageLayoutPanel(), "message-layout-panel-modern.png");
await util.getBubbleLayout().click();
@@ -33,7 +36,8 @@ test.describe("Appearance user settings tab", () => {
// Assert that the room layout is set to bubble layout
await util.assertBubbleLayout();
await util.assertScreenshot(util.getMessageLayoutPanel(), "message-layout-panel-bubble.png");
});
},
);
test("should enable compact layout when the modern layout is selected", async ({ page, app, user, util }) => {
await expect(util.getCompactLayoutCheckbox()).not.toBeChecked();

View File

@@ -20,7 +20,10 @@ test.describe("Appearance user settings tab", () => {
await util.openAppearanceTab();
});
test("should be rendered with the light theme selected", async ({ page, app, util }) => {
test(
"should be rendered with the light theme selected",
{ tag: "@screenshot" },
async ({ page, app, util }) => {
// Assert that 'Match system theme' is not checked
await expect(util.getMatchSystemThemeCheckbox()).not.toBeChecked();
@@ -31,9 +34,13 @@ test.describe("Appearance user settings tab", () => {
await expect(util.getHighContrastTheme()).not.toBeChecked();
await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-light.png");
});
},
);
test("should disable the themes when the system theme is clicked", async ({ page, app, util }) => {
test(
"should disable the themes when the system theme is clicked",
{ tag: "@screenshot" },
async ({ page, app, util }) => {
await util.getMatchSystemThemeCheckbox().click();
// Assert that the themes are disabled
@@ -42,9 +49,10 @@ test.describe("Appearance user settings tab", () => {
await expect(util.getHighContrastTheme()).toBeDisabled();
await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-match-system-enabled.png");
});
},
);
test("should change the theme to dark", async ({ page, app, util }) => {
test("should change the theme to dark", { tag: "@screenshot" }, async ({ page, app, util }) => {
// Assert that the light theme is selected
await expect(util.getLightTheme()).toBeChecked();
@@ -63,11 +71,14 @@ test.describe("Appearance user settings tab", () => {
labsFlags: ["feature_custom_themes"],
});
test("should render the custom theme section", async ({ page, app, util }) => {
test("should render the custom theme section", { tag: "@screenshot" }, async ({ page, app, util }) => {
await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-custom-theme.png");
});
test("should be able to add and remove a custom theme", async ({ page, app, util }) => {
test(
"should be able to add and remove a custom theme",
{ tag: "@screenshot" },
async ({ page, app, util }) => {
await util.addCustomTheme();
await expect(util.getCustomTheme()).not.toBeChecked();
@@ -75,7 +86,8 @@ test.describe("Appearance user settings tab", () => {
await util.removeCustomTheme();
await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-custom-theme-removed.png");
});
},
);
});
});
});

View File

@@ -20,7 +20,7 @@ test.describe("General room settings tab", () => {
await app.viewRoomByName(roomName);
});
test("should be rendered properly", async ({ page, app }) => {
test("should be rendered properly", { tag: "@screenshot" }, async ({ page, app }) => {
const settings = await app.settings.openRoomSettings("General");
// Assert that "Show less" details element is rendered

View File

@@ -23,7 +23,7 @@ test.describe("Preferences user settings tab", () => {
},
});
test("should be rendered properly", async ({ app, page, user }) => {
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user }) => {
page.setViewportSize({ width: 1024, height: 3300 });
const tab = await app.settings.openUserSettings("Preferences");
// Assert that the top heading is rendered

View File

@@ -36,7 +36,7 @@ test.describe("Security user settings tab", () => {
});
test.describe("AnalyticsLearnMoreDialog", () => {
test("should be rendered properly", async ({ app, page }) => {
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page }) => {
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(

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/
import { test, expect } from "../../element-web-test";
test.describe("Share dialog", () => {
test.use({
displayName: "Alice",
room: async ({ app, user, bot }, use) => {
const roomId = await app.client.createRoom({ name: "Alice room" });
await use({ roomId });
},
});
test("should share a room", { tag: "@screenshot" }, async ({ page, app, room }) => {
await app.viewRoomById(room.roomId);
await app.toggleRoomInfoPanel();
await page.getByRole("menuitem", { name: "Copy link" }).click();
const dialog = page.getByRole("dialog", { name: "Share room" });
await expect(dialog.getByText(`https://matrix.to/#/${room.roomId}`)).toBeVisible();
expect(dialog).toMatchScreenshot("share-dialog-room.png", {
// QRCode and url changes at every run
mask: [page.locator(".mx_QRCode"), page.locator(".mx_ShareDialog_top > span")],
});
});
test("should share a room member", { tag: "@screenshot" }, async ({ page, app, room, user }) => {
await app.viewRoomById(room.roomId);
await app.client.sendMessage(room.roomId, { body: "hello", msgtype: "m.text" });
const rightPanel = await app.toggleRoomInfoPanel();
await rightPanel.getByRole("menuitem", { name: "People" }).click();
await rightPanel.getByRole("button", { name: `${user.userId} (power 100)` }).click();
await rightPanel.getByRole("button", { name: "Share profile" }).click();
const dialog = page.getByRole("dialog", { name: "Share User" });
await expect(dialog.getByText(`https://matrix.to/#/${user.userId}`)).toBeVisible();
expect(dialog).toMatchScreenshot("share-dialog-user.png", {
// QRCode changes at every run
mask: [page.locator(".mx_QRCode")],
});
});
test("should share an event", { tag: "@screenshot" }, async ({ page, app, room }) => {
await app.viewRoomById(room.roomId);
await app.client.sendMessage(room.roomId, { body: "hello", msgtype: "m.text" });
const timelineMessage = page.locator(".mx_MTextBody", { hasText: "hello" });
await timelineMessage.hover();
await page.getByRole("button", { name: "Options", exact: true }).click();
await page.getByRole("menuitem", { name: "Share" }).click();
const dialog = page.getByRole("dialog", { name: "Share Room Message" });
await expect(dialog.getByRole("checkbox", { name: "Link to selected message" })).toBeChecked();
expect(dialog).toMatchScreenshot("share-dialog-event.png", {
// QRCode and url changes at every run
mask: [page.locator(".mx_QRCode"), page.locator(".mx_ShareDialog_top > span")],
});
await dialog.getByRole("checkbox", { name: "Link to selected message" }).click();
await expect(dialog.getByRole("checkbox", { name: "Link to selected message" })).not.toBeChecked();
});
});

View File

@@ -55,7 +55,7 @@ test.describe("Spaces", () => {
botCreateOpts: { displayName: "BotBob" },
});
test("should allow user to create public space", async ({ page, app, user }) => {
test("should allow user to create public space", { tag: "@screenshot" }, async ({ page, app, user }) => {
const contextMenu = await openSpaceCreateMenu(page);
await expect(contextMenu).toMatchScreenshot("space-create-menu.png");
@@ -88,7 +88,7 @@ test.describe("Spaces", () => {
await expect(page.getByRole("treeitem", { name: "Jokes" })).toBeVisible();
});
test("should allow user to create private space", async ({ page, app, user }) => {
test("should allow user to create private space", { tag: "@screenshot" }, async ({ page, app, user }) => {
const menu = await openSpaceCreateMenu(page);
await menu.getByRole("button", { name: "Private" }).click();
@@ -216,13 +216,10 @@ test.describe("Spaces", () => {
await expect(hierarchyList.getByRole("treeitem", { name: "Gaming" }).getByRole("button")).toBeVisible();
});
test("should render subspaces in the space panel only when expanded", async ({
page,
app,
user,
axe,
checkA11y,
}) => {
test(
"should render subspaces in the space panel only when expanded",
{ tag: "@screenshot" },
async ({ page, app, user, axe, checkA11y }) => {
axe.disableRules([
// Disable this check as it triggers on nested roving tab index elements which are in practice fine
"nested-interactive",
@@ -258,7 +255,8 @@ test.describe("Spaces", () => {
await checkA11y();
await expect(page.locator(".mx_SpacePanel")).toMatchScreenshot("space-panel-expanded.png");
});
},
);
test("should not soft crash when joining a room from space hierarchy which has a link in its topic", async ({
page,

View File

@@ -276,7 +276,7 @@ export class Helpers {
* Assert that the threads activity centre button has no indicator
*/
async assertNoTacIndicator() {
// Assert by checkng neither of the known indicators are visible first. This will wait
// Assert by checking neither of the known indicators are visible first. This will wait
// if it takes a little time to disappear, but the screenshot comparison won't.
await expect(this.getTacButton().locator("[data-indicator='success']")).not.toBeVisible();
await expect(this.getTacButton().locator("[data-indicator='critical']")).not.toBeVisible();
@@ -376,7 +376,7 @@ export class Helpers {
* Clicks the button to mark all threads as read in the current room
*/
clickMarkAllThreadsRead() {
return this.page.getByLabel("Mark all as read").click();
return this.page.locator("#thread-panel").getByRole("button", { name: "Mark all as read" }).click();
}
}

View File

@@ -16,16 +16,18 @@ test.describe("Threads Activity Centre", () => {
labsFlags: ["threadsActivityCentre"],
});
test("should have the button correctly aligned and displayed in the space panel when expanded", async ({
util,
}) => {
test(
"should have the button correctly aligned and displayed in the space panel when expanded",
{ tag: "@screenshot" },
async ({ util }) => {
// Open the space panel
await util.expandSpacePanel();
// The buttons in the space panel should be aligned when expanded
await expect(util.getSpacePanel()).toMatchScreenshot("tac-button-expanded.png");
});
},
);
test("should not show indicator when there is no thread", async ({ room1, util }) => {
test("should not show indicator when there is no thread", { tag: "@screenshot" }, async ({ room1, util }) => {
// No indicator should be shown
await util.assertNoTacIndicator();
@@ -62,7 +64,7 @@ test.describe("Threads Activity Centre", () => {
await util.assertHighlightIndicator();
});
test("should show the rooms with unread threads", async ({ room1, room2, util, msg }) => {
test("should show the rooms with unread threads", { tag: "@screenshot" }, async ({ room1, room2, util, msg }) => {
await util.goTo(room2);
await util.populateThreads(room1, room2, msg);
// The indicator should be shown
@@ -79,7 +81,7 @@ test.describe("Threads Activity Centre", () => {
await expect(util.getTacPanel()).toMatchScreenshot("tac-panel-mix-unread.png");
});
test("should update with a thread is read", async ({ room1, room2, util, msg }) => {
test("should update with a thread is read", { tag: "@screenshot" }, async ({ room1, room2, util, msg }) => {
await util.goTo(room2);
await util.populateThreads(room1, room2, msg);
@@ -128,7 +130,7 @@ test.describe("Threads Activity Centre", () => {
await expect(page.locator(".mx_SpotlightDialog")).not.toBeVisible();
});
test("should have the correct hover state", async ({ util, page }) => {
test("should have the correct hover state", { tag: "@screenshot" }, async ({ util, page }) => {
await util.hoverTacButton();
await expect(util.getSpacePanel()).toMatchScreenshot("tac-hovered.png");
@@ -138,7 +140,7 @@ test.describe("Threads Activity Centre", () => {
await expect(util.getSpacePanel()).toMatchScreenshot("tac-hovered-expanded.png");
});
test("should mark all threads as read", async ({ room1, room2, util, msg, page }) => {
test("should mark all threads as read", { tag: "@screenshot" }, async ({ room1, room2, util, msg, page }) => {
await util.receiveMessages(room1, ["Msg1", msg.threadedOff("Msg1", "Resp1")]);
await util.assertNotificationTac();
@@ -146,7 +148,7 @@ test.describe("Threads Activity Centre", () => {
await util.openTac();
await util.clickRoomInTac(room1.name);
util.clickMarkAllThreadsRead();
await util.clickMarkAllThreadsRead();
await util.assertNoTacIndicator();
});

Some files were not shown because too many files have changed in this diff Show More