Compare commits
99 Commits
robin/depr
...
rav/fix_li
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
489b4afe23 | ||
|
|
8ac9bfb4ab | ||
|
|
bcb43fc1eb | ||
|
|
0c498a66b1 | ||
|
|
64dfbc5aa5 | ||
|
|
12dbe719d7 | ||
|
|
ee6ce8ac1d | ||
|
|
31506ef864 | ||
|
|
713f524948 | ||
|
|
e880a866ed | ||
|
|
789dba7b3d | ||
|
|
76be5ccc9e | ||
|
|
3b675b83f1 | ||
|
|
8bd98aa3fd | ||
|
|
b897006899 | ||
|
|
01c4ba8893 | ||
|
|
001ed616f6 | ||
|
|
2395cb1402 | ||
|
|
7951e48291 | ||
|
|
664f79306a | ||
|
|
29e895095f | ||
|
|
0d3a81ee8f | ||
|
|
26e24624d9 | ||
|
|
e94d690587 | ||
|
|
4abdb74673 | ||
|
|
59531ea512 | ||
|
|
4da27eb199 | ||
|
|
d2e4631a14 | ||
|
|
6ff71480d8 | ||
|
|
700068a558 | ||
|
|
d5a9b3f4c0 | ||
|
|
bbb179b6d3 | ||
|
|
93095f99db | ||
|
|
4d3fde192d | ||
|
|
bcf755d45f | ||
|
|
adfa43dcbb | ||
|
|
96dbddcb14 | ||
|
|
227c8ff1cd | ||
|
|
619e11a749 | ||
|
|
bdfdf5fc49 | ||
|
|
cc094f4b56 | ||
|
|
2d0facd47b | ||
|
|
c53b17d291 | ||
|
|
8086262e04 | ||
|
|
f9a0a626a6 | ||
|
|
d7f54355ac | ||
|
|
a668216e20 | ||
|
|
1cadf1a82e | ||
|
|
ee37734cfc | ||
|
|
15f1291cbc | ||
|
|
8a550cf3f6 | ||
|
|
9c911d5c59 | ||
|
|
6fca4d106e | ||
|
|
24f923feac | ||
|
|
9be2b973d0 | ||
|
|
d837d2f62d | ||
|
|
f2379878cd | ||
|
|
261d073f6d | ||
|
|
401fc63eb0 | ||
|
|
51c4506431 | ||
|
|
1de27b265b | ||
|
|
db9514760d | ||
|
|
4b8f404bb3 | ||
|
|
e10b1f9222 | ||
|
|
ff87df4825 | ||
|
|
c1a163cbc9 | ||
|
|
9590e59fd2 | ||
|
|
1e6f9dd096 | ||
|
|
745c12f10d | ||
|
|
6a8493c6eb | ||
|
|
12927cc4a7 | ||
|
|
814f4a85df | ||
|
|
475504d33b | ||
|
|
7faee3d1b7 | ||
|
|
30e7567064 | ||
|
|
2250f5e6a2 | ||
|
|
e43b696461 | ||
|
|
bf98ede4fa | ||
|
|
cc0ece9837 | ||
|
|
ab6ef2fa85 | ||
|
|
c79c8c836b | ||
|
|
3f0dcaa64c | ||
|
|
652e891663 | ||
|
|
7eb5a29cf0 | ||
|
|
1b38624fd8 | ||
|
|
d98533025a | ||
|
|
c3e5367e45 | ||
|
|
1e15a322a5 | ||
|
|
452996eacf | ||
|
|
ee120f2fa9 | ||
|
|
94aa51dc57 | ||
|
|
e19d3dcd44 | ||
|
|
5a4b5418cc | ||
|
|
d1f62317ba | ||
|
|
9232a220dc | ||
|
|
45a2fd9d63 | ||
|
|
db5c69e228 | ||
|
|
acb3d31a07 | ||
|
|
9136332f42 |
19
.github/workflows/docker.yaml
vendored
@@ -37,14 +37,14 @@ jobs:
|
||||
install: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3
|
||||
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@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
registry: ghcr.io
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
images: |
|
||||
@@ -139,3 +139,16 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
repository: vectorim/element-web
|
||||
|
||||
- name: Repository Dispatch
|
||||
uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
repository: element-hq/element-web-pro
|
||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
event-type: image-built
|
||||
# Stable way to determine the :version
|
||||
client-payload: |-
|
||||
{
|
||||
"base-ref": "${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}"
|
||||
}
|
||||
|
||||
1
.github/workflows/static_analysis.yaml
vendored
@@ -52,6 +52,7 @@ jobs:
|
||||
error|misconfigured
|
||||
welcome_to_element
|
||||
devtools|settings|elementCallUrl
|
||||
labs|sliding_sync_description
|
||||
|
||||
rethemendex_lint:
|
||||
name: "Rethemendex Check"
|
||||
|
||||
2
.github/workflows/triage-assigned.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
contains(github.event.issue.assignees.*.login, 'dbkr') ||
|
||||
contains(github.event.issue.assignees.*.login, 'MidhunSureshR')
|
||||
steps:
|
||||
- uses: actions/add-to-project@main
|
||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/67
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
2
.github/workflows/triage-incoming.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
automate-project-columns:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/add-to-project@main
|
||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/120
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
12
.github/workflows/triage-labelled.yml
vendored
@@ -112,7 +112,7 @@ jobs:
|
||||
contains(github.event.issue.labels.*.name, 'O-Frequent') ||
|
||||
contains(github.event.issue.labels.*.name, 'A11y'))
|
||||
steps:
|
||||
- uses: actions/add-to-project@main
|
||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/18
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
@@ -123,7 +123,7 @@ jobs:
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'X-Needs-Product')
|
||||
steps:
|
||||
- uses: actions/add-to-project@main
|
||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/28
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
@@ -134,7 +134,7 @@ jobs:
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'A-New-Search-Experience')
|
||||
steps:
|
||||
- uses: actions/add-to-project@main
|
||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/48
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
@@ -145,7 +145,7 @@ jobs:
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'Team: VoIP')
|
||||
steps:
|
||||
- uses: actions/add-to-project@main
|
||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/41
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
@@ -156,7 +156,7 @@ jobs:
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'Team: Crypto')
|
||||
steps:
|
||||
- uses: actions/add-to-project@main
|
||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/76
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
@@ -172,7 +172,7 @@ jobs:
|
||||
contains(github.event.issue.labels.*.name, 'A-Testing') ||
|
||||
contains(github.event.issue.labels.*.name, 'Z-Flaky-Test')
|
||||
steps:
|
||||
- uses: actions/add-to-project@main
|
||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/101
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
name: Move PRs asking for design review to the design board
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: octokit/graphql-action@v2.x
|
||||
- uses: octokit/graphql-action@8ad880e4d437783ea2ab17010324de1075228110 # v2.3.2
|
||||
id: find_team_members
|
||||
with:
|
||||
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
fi
|
||||
env:
|
||||
TEAM: "design"
|
||||
- uses: octokit/graphql-action@v2.x
|
||||
- uses: octokit/graphql-action@8ad880e4d437783ea2ab17010324de1075228110 # v2.3.2
|
||||
id: add_to_project
|
||||
if: steps.any_matching_reviewers.outputs.match == 'true'
|
||||
with:
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
name: Move PRs asking for design review to the design board
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: octokit/graphql-action@v2.x
|
||||
- uses: octokit/graphql-action@8ad880e4d437783ea2ab17010324de1075228110 # v2.3.2
|
||||
id: find_team_members
|
||||
with:
|
||||
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
fi
|
||||
env:
|
||||
TEAM: "product"
|
||||
- uses: octokit/graphql-action@v2.x
|
||||
- uses: octokit/graphql-action@8ad880e4d437783ea2ab17010324de1075228110 # v2.3.2
|
||||
id: add_to_project
|
||||
if: steps.any_matching_reviewers.outputs.match == 'true'
|
||||
with:
|
||||
|
||||
@@ -70,5 +70,13 @@ module.exports = {
|
||||
],
|
||||
},
|
||||
],
|
||||
"property-no-deprecated": [
|
||||
true,
|
||||
{
|
||||
ignoreProperties: ["-webkit-box-orient", "word-wrap"],
|
||||
},
|
||||
],
|
||||
"nesting-selector-no-missing-scoping-root": null,
|
||||
"no-invalid-position-declaration": null,
|
||||
},
|
||||
};
|
||||
|
||||
59
CHANGELOG.md
@@ -1,3 +1,62 @@
|
||||
Changes in [1.11.109](https://github.com/element-hq/element-web/releases/tag/v1.11.109) (2025-08-11)
|
||||
====================================================================================================
|
||||
This release supports the upcoming v12 ("hydra") Matrix room version and is necessary to view and participate in these rooms.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
* [Backport staging] Allow /upgraderoom command without developer mode enabled ([#30529](https://github.com/element-hq/element-web/pull/30529)). Contributed by @RiotRobot.
|
||||
* [Backport staging] Support for creator/owner power level ([#30526](https://github.com/element-hq/element-web/pull/30526)). Contributed by @RiotRobot.
|
||||
* New room list: change icon and label of menu item for to start a DM ([#30470](https://github.com/element-hq/element-web/pull/30470)). Contributed by @florianduros.
|
||||
* Implement the member list with virtuoso ([#29869](https://github.com/element-hq/element-web/pull/29869)). Contributed by @langleyd.
|
||||
* Add labs option for history sharing on invite ([#30313](https://github.com/element-hq/element-web/pull/30313)). Contributed by @richvdh.
|
||||
* Bump wysiwyg to 2.39.0 adding support for pasting rich text content in the Rich Text Edtior ([#30421](https://github.com/element-hq/element-web/pull/30421)). Contributed by @langleyd.
|
||||
* Support `EventShieldReason.MISMATCHED_SENDER` ([#30403](https://github.com/element-hq/element-web/pull/30403)). Contributed by @richvdh.
|
||||
* Change unencrypted and public pills to blue ([#30399](https://github.com/element-hq/element-web/pull/30399)). Contributed by @florianduros.
|
||||
* Change color of public room icon ([#30390](https://github.com/element-hq/element-web/pull/30390)). Contributed by @florianduros.
|
||||
* Script for updating storybook screenshots ([#30340](https://github.com/element-hq/element-web/pull/30340)). Contributed by @dbkr.
|
||||
* Add toggle to hide empty state in devtools ([#30352](https://github.com/element-hq/element-web/pull/30352)). Contributed by @toger5.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* [Backport staging] Use userId to filter users in non-federated rooms when showing the InviteDialog ([#30537](https://github.com/element-hq/element-web/pull/30537)). Contributed by @RiotRobot.
|
||||
* [Backport staging] Catch error when encountering invalid m.room.pinned\_events event ([#30536](https://github.com/element-hq/element-web/pull/30536)). Contributed by @RiotRobot.
|
||||
* Update for compatibility with v12 rooms ([#30452](https://github.com/element-hq/element-web/pull/30452)). Contributed by @dbkr.
|
||||
* New room list: fix tooltip on presence ([#30474](https://github.com/element-hq/element-web/pull/30474)). Contributed by @florianduros.
|
||||
* New room list: add tooltip for presence and room status ([#30472](https://github.com/element-hq/element-web/pull/30472)). Contributed by @florianduros.
|
||||
* Fix: Clicking on an item in the member list causes it to scroll to the top rather than show the profile view ([#30455](https://github.com/element-hq/element-web/pull/30455)). Contributed by @langleyd.
|
||||
* Put the 'decrypting' tooltip back ([#30446](https://github.com/element-hq/element-web/pull/30446)). Contributed by @dbkr.
|
||||
* Use server name explicitly for via. ([#30362](https://github.com/element-hq/element-web/pull/30362)). Contributed by @Half-Shot.
|
||||
* fix: replace hardcoded string in poll history dialog ([#30402](https://github.com/element-hq/element-web/pull/30402)). Contributed by @florianduros.
|
||||
* fix: replace hardcoded string on qr code back button ([#30401](https://github.com/element-hq/element-web/pull/30401)). Contributed by @florianduros.
|
||||
* Fix color of icon button with outline ([#30361](https://github.com/element-hq/element-web/pull/30361)). Contributed by @florianduros.
|
||||
|
||||
|
||||
Changes in [1.11.108](https://github.com/element-hq/element-web/releases/tag/v1.11.108) (2025-07-30)
|
||||
====================================================================================================
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* [Backport staging] Fix downloaded attachments not being decrypted ([#30434](https://github.com/element-hq/element-web/pull/30434)). Contributed by @RiotRobot.
|
||||
|
||||
|
||||
Changes in [1.11.107](https://github.com/element-hq/element-web/releases/tag/v1.11.107) (2025-07-29)
|
||||
====================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Message preview should show tooltip with the full message on hover ([#30265](https://github.com/element-hq/element-web/pull/30265)). Contributed by @MidhunSureshR.
|
||||
* Support rendering notification badges on platforms that do their own icon overlays ([#30315](https://github.com/element-hq/element-web/pull/30315)). Contributed by @Half-Shot.
|
||||
* Add SubscriptionViewModel base class ([#30297](https://github.com/element-hq/element-web/pull/30297)). Contributed by @dbkr.
|
||||
* Enhancement: Save image on CTRL+S ([#30330](https://github.com/element-hq/element-web/pull/30330)). Contributed by @ioalexander.
|
||||
* Add quote functionality to MessageContextMenu (#29893) ([#30323](https://github.com/element-hq/element-web/pull/30323)). Contributed by @AlirezaMrtz.
|
||||
* Initial structure for shared component views ([#30216](https://github.com/element-hq/element-web/pull/30216)). Contributed by @dbkr.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* [Backport staging] Fix e2e shield being invisible in white mode for encrypted room ([#30411](https://github.com/element-hq/element-web/pull/30411)). Contributed by @RiotRobot.
|
||||
* Force ED titlebar color for new room list ([#30332](https://github.com/element-hq/element-web/pull/30332)). Contributed by @florianduros.
|
||||
* Add a background color to left panel for macos titlebar in element desktop ([#30328](https://github.com/element-hq/element-web/pull/30328)). Contributed by @florianduros.
|
||||
* Fix: Prevent page refresh on Enter key in right panel member search ([#30312](https://github.com/element-hq/element-web/pull/30312)). Contributed by @AlirezaMrtz.
|
||||
|
||||
|
||||
Changes in [1.11.106](https://github.com/element-hq/element-web/releases/tag/v1.11.106) (2025-07-15)
|
||||
====================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# syntax=docker.io/docker/dockerfile:1.17-labs@sha256:9187104f31e3a002a8a6a3209ea1f937fb7486c093cbbde1e14b0fa0d7e4f1b5
|
||||
|
||||
# Builder
|
||||
FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:a80324457a2c8d09c83ff9edf2bdf71f378d3288de920e68a358bd3c484b8c4a AS builder
|
||||
FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:2d63e0f812d023c4c764e83d7e30dc94949304443ebc372d5c445e63a5ae49c1 AS builder
|
||||
|
||||
# Support custom branch of the js-sdk. This also helps us build images of element-web develop.
|
||||
ARG USE_CUSTOM_SDKS=false
|
||||
@@ -19,7 +19,7 @@ RUN /src/scripts/docker-package.sh
|
||||
RUN cp /src/config.sample.json /src/webapp/config.json
|
||||
|
||||
# App
|
||||
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:86df552d36eb24c45d3f5becf6423bd056a3fd235d7085fe3d5ea28ba89a8232
|
||||
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:e61b77b27c8f3124fad6d19e894ca5b603bcaf6a34a2df035511299dfa6fad35
|
||||
|
||||
# Need root user to install packages & manipulate the usr directory
|
||||
USER root
|
||||
|
||||
@@ -40,8 +40,6 @@ const config: Config = {
|
||||
"^!!raw-loader!.*": "jest-raw-loader",
|
||||
"recorderWorkletFactory": "<rootDir>/__mocks__/empty.js",
|
||||
"^fetch-mock$": "<rootDir>/node_modules/fetch-mock",
|
||||
// Requires ESM which is incompatible with our current Jest setup
|
||||
"^@element-hq/element-web-module-api$": "<rootDir>/__mocks__/empty.js",
|
||||
},
|
||||
transformIgnorePatterns: ["/node_modules/(?!(mime|matrix-js-sdk)).+$"],
|
||||
collectCoverageFrom: [
|
||||
|
||||
7
knip.ts
@@ -42,6 +42,13 @@ export default {
|
||||
"util",
|
||||
// Embedded into webapp
|
||||
"@element-hq/element-call-embedded",
|
||||
|
||||
// Used by matrix-js-sdk, which means we have to include them as a
|
||||
// dependency so that // we can run `tsc` (since we import the typescript
|
||||
// source of js-sdk, rather than the transpiled and annotated JS like you
|
||||
// would with a normal library).
|
||||
"@types/content-type",
|
||||
"@types/sdp-transform",
|
||||
],
|
||||
ignoreBinaries: [
|
||||
// Used in scripts & workflows
|
||||
|
||||
41
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "element-web",
|
||||
"version": "1.11.106",
|
||||
"version": "1.11.109",
|
||||
"description": "Element: the future of secure communication",
|
||||
"author": "New Vector Ltd.",
|
||||
"repository": {
|
||||
@@ -73,10 +73,10 @@
|
||||
"test:storybook:update": "playwright-screenshots --entrypoint yarn --with-node-modules && playwright-screenshots --entrypoint /work/node_modules/.bin/test-storybook --with-node-modules --url http://host.docker.internal:6007/ --updateSnapshot"
|
||||
},
|
||||
"resolutions": {
|
||||
"**/pretty-format/react-is": "19.1.0",
|
||||
"@playwright/test": "1.54.1",
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"**/pretty-format/react-is": "19.1.1",
|
||||
"@playwright/test": "1.54.2",
|
||||
"@types/react": "19.1.9",
|
||||
"@types/react-dom": "19.1.7",
|
||||
"oidc-client-ts": "3.3.0",
|
||||
"jwt-decode": "4.0.0",
|
||||
"caniuse-lite": "1.0.30001724",
|
||||
@@ -86,7 +86,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@element-hq/element-web-module-api": "1.3.0",
|
||||
"@element-hq/element-web-module-api": "1.4.1",
|
||||
"@fontsource/inconsolata": "^5",
|
||||
"@fontsource/inter": "^5",
|
||||
"@formatjs/intl-segmenter": "^11.5.7",
|
||||
@@ -94,10 +94,10 @@
|
||||
"@matrix-org/emojibase-bindings": "^1.3.4",
|
||||
"@matrix-org/react-sdk-module-api": "^2.4.0",
|
||||
"@matrix-org/spec": "^1.7.0",
|
||||
"@sentry/browser": "^9.0.0",
|
||||
"@sentry/browser": "^10.0.0",
|
||||
"@types/png-chunks-extract": "^1.0.2",
|
||||
"@types/react-virtualized": "^9.21.30",
|
||||
"@vector-im/compound-design-tokens": "^5.0.0",
|
||||
"@vector-im/compound-design-tokens": "^6.0.0",
|
||||
"@vector-im/compound-web": "^8.1.2",
|
||||
"@vector-im/matrix-wysiwyg": "2.39.0",
|
||||
"@zxcvbn-ts/core": "^3.0.4",
|
||||
@@ -117,7 +117,7 @@
|
||||
"emojibase-regex": "15.3.2",
|
||||
"escape-html": "^1.0.3",
|
||||
"file-saver": "^2.0.5",
|
||||
"filesize": "10.1.6",
|
||||
"filesize": "11.0.2",
|
||||
"github-markdown-css": "^5.5.1",
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
"highlight.js": "^11.3.1",
|
||||
@@ -128,9 +128,9 @@
|
||||
"jsrsasign": "^11.0.0",
|
||||
"jszip": "^3.7.0",
|
||||
"katex": "^0.16.0",
|
||||
"linkify-react": "4.3.1",
|
||||
"linkify-string": "4.3.1",
|
||||
"linkifyjs": "4.3.1",
|
||||
"linkify-react": "4.3.2",
|
||||
"linkify-string": "4.3.2",
|
||||
"linkifyjs": "4.3.2",
|
||||
"lodash": "^4.17.21",
|
||||
"maplibre-gl": "^5.0.0",
|
||||
"matrix-encrypt-attachment": "^1.0.3",
|
||||
@@ -143,7 +143,7 @@
|
||||
"opus-recorder": "^8.0.3",
|
||||
"pako": "^2.0.3",
|
||||
"png-chunks-extract": "^1.0.0",
|
||||
"posthog-js": "1.257.0",
|
||||
"posthog-js": "1.259.0",
|
||||
"qrcode": "1.5.4",
|
||||
"re-resizable": "6.11.2",
|
||||
"react": "^19.0.0",
|
||||
@@ -154,6 +154,7 @@
|
||||
"react-string-replace": "^1.1.1",
|
||||
"react-transition-group": "^4.4.1",
|
||||
"react-virtualized": "^9.22.5",
|
||||
"react-virtuoso": "^4.12.6",
|
||||
"rfc4648": "^1.4.0",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"sanitize-html": "2.17.0",
|
||||
@@ -185,8 +186,8 @@
|
||||
"@babel/preset-typescript": "^7.12.7",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@casualbot/jest-sonar-reporter": "2.2.7",
|
||||
"@element-hq/element-call-embedded": "0.13.1",
|
||||
"@element-hq/element-web-playwright-common": "^1.4.3",
|
||||
"@element-hq/element-call-embedded": "0.14.1",
|
||||
"@element-hq/element-web-playwright-common": "^1.4.4",
|
||||
"@peculiar/webcrypto": "^1.4.3",
|
||||
"@playwright/test": "^1.50.1",
|
||||
"@principalstudio/html-webpack-inject-preload": "^1.2.7",
|
||||
@@ -204,6 +205,7 @@
|
||||
"@testing-library/react": "^16.0.0",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/commonmark": "^0.27.4",
|
||||
"@types/content-type": "^1.1.9",
|
||||
"@types/counterpart": "^0.18.1",
|
||||
"@types/css-tree": "^2.3.8",
|
||||
"@types/diff-match-patch": "^1.0.32",
|
||||
@@ -222,11 +224,12 @@
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"@types/pako": "^2.0.0",
|
||||
"@types/qrcode": "^1.3.5",
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react": "19.1.9",
|
||||
"@types/react-beautiful-dnd": "^13.0.0",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"@types/react-dom": "19.1.7",
|
||||
"@types/react-transition-group": "^4.4.0",
|
||||
"@types/sanitize-html": "2.16.0",
|
||||
"@types/sdp-transform": "^2.4.10",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/tar-js": "^0.3.5",
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
@@ -299,8 +302,8 @@
|
||||
"semver": "^7.5.2",
|
||||
"source-map-loader": "^5.0.0",
|
||||
"storybook": "^9.0.12",
|
||||
"stylelint": "^16.13.0",
|
||||
"stylelint-config-standard": "^38.0.0",
|
||||
"stylelint": "^16.23.0",
|
||||
"stylelint-config-standard": "^39.0.0",
|
||||
"stylelint-scss": "^6.0.0",
|
||||
"stylelint-value-no-unknown-custom-properties": "^6.0.1",
|
||||
"terser-webpack-plugin": "^5.3.9",
|
||||
|
||||
@@ -19,6 +19,7 @@ const clickButtonReply = async (tile: Locator) => {
|
||||
await tile.hover();
|
||||
await tile.getByRole("button", { name: "Reply", exact: true }).click();
|
||||
}).toPass();
|
||||
await expect(tile.page().getByText("Replying", { exact: true })).toBeVisible();
|
||||
};
|
||||
|
||||
test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
@@ -39,7 +40,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
// wait for the tile to finish loading
|
||||
await expect(
|
||||
page
|
||||
.locator(".mx_AudioPlayer_mediaName")
|
||||
.getByTestId("audio-player-name")
|
||||
.last()
|
||||
.filter({ hasText: file.split("/").at(-1) }),
|
||||
).toBeVisible();
|
||||
@@ -54,12 +55,10 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
// Check that the audio player is rendered and its button becomes visible
|
||||
const checkPlayerVisibility = async (locator: Locator) => {
|
||||
// Assert that the audio player and media information are visible
|
||||
const mediaInfo = locator.locator(
|
||||
".mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container .mx_AudioPlayer_mediaInfo",
|
||||
);
|
||||
await expect(mediaInfo.locator(".mx_AudioPlayer_mediaName", { hasText: ".ogg" })).toBeVisible(); // extension
|
||||
await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "00:01" })).toBeVisible();
|
||||
await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "(3.56 KB)" })).toBeVisible(); // actual size
|
||||
const mediaInfo = locator.getByRole("region", { name: "Audio player" });
|
||||
await expect(mediaInfo.getByText(".ogg")).toBeVisible(); // extension
|
||||
await expect(mediaInfo.getByRole("time")).toHaveText("00:01"); // duration
|
||||
await expect(mediaInfo.getByText("(3.56 KB)")).toBeVisible(); // actual size;
|
||||
|
||||
// Assert that the play button can be found and is visible
|
||||
await expect(locator.getByRole("button", { name: "Play" })).toBeVisible();
|
||||
@@ -78,7 +77,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
}
|
||||
|
||||
// Check the status of the seek bar
|
||||
expect(await page.locator(".mx_AudioPlayer_seek input[type='range']").count()).toBeGreaterThan(0);
|
||||
expect(await page.getByRole("region", { name: "Audio player" }).getByRole("slider").count()).toBeGreaterThan(0);
|
||||
|
||||
// Enable IRC layout
|
||||
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
||||
@@ -100,7 +99,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
display: none !important;
|
||||
}
|
||||
`,
|
||||
mask: [page.locator(".mx_AudioPlayer_seek")],
|
||||
mask: [page.getByTestId("audio-player-seek")],
|
||||
};
|
||||
|
||||
// Take a snapshot of mx_EventTile_last on IRC layout
|
||||
@@ -186,9 +185,9 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
await uploadFile(page, "playwright/sample-files/1sec.ogg");
|
||||
|
||||
// Assert that the audio player is rendered
|
||||
const container = page.locator(".mx_EventTile_last .mx_AudioPlayer_container");
|
||||
const container = page.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" });
|
||||
// Assert that the counter is zero before clicking the play button
|
||||
await expect(container.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
|
||||
await expect(container.getByRole("timer")).toHaveText("00:00");
|
||||
|
||||
// Find and click "Play" button, the wait is to make the test less flaky
|
||||
await expect(container.getByRole("button", { name: "Play" })).toBeVisible();
|
||||
@@ -198,7 +197,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
await expect(container.getByRole("button", { name: "Pause" })).toBeVisible();
|
||||
|
||||
// Assert that the timer is reset when the audio file finished playing
|
||||
await expect(container.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
|
||||
await expect(container.getByRole("timer")).toHaveText("00:00");
|
||||
|
||||
// Assert that "Play" button can be found
|
||||
await expect(container.getByRole("button", { name: "Play" })).toBeVisible();
|
||||
@@ -226,7 +225,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
await uploadFile(page, "playwright/sample-files/1sec.ogg");
|
||||
|
||||
// Assert the audio player is rendered
|
||||
await expect(page.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible();
|
||||
await expect(page.getByRole("region", { name: "Audio player" })).toBeVisible();
|
||||
|
||||
// Find and click "Reply" button on MessageActionBar
|
||||
const tile = page.locator(".mx_EventTile_last");
|
||||
@@ -236,7 +235,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
await uploadFile(page, "playwright/sample-files/1sec.ogg");
|
||||
|
||||
// Assert that the audio player is rendered
|
||||
await expect(tile.locator(".mx_AudioPlayer_container")).toBeVisible();
|
||||
await expect(tile.getByRole("region", { name: "Audio player" })).toBeVisible();
|
||||
|
||||
// Assert that replied audio file is rendered as file button inside ReplyChain
|
||||
const button = tile.locator(".mx_ReplyChain_wrapper .mx_MFileBody_info[role='button']");
|
||||
@@ -261,7 +260,9 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
await uploadFile(page, "playwright/sample-files/upload-first.ogg");
|
||||
|
||||
// Assert that the audio player is rendered
|
||||
await expect(page.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible();
|
||||
await expect(
|
||||
page.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" }),
|
||||
).toBeVisible();
|
||||
|
||||
await clickButtonReply(tile);
|
||||
|
||||
@@ -269,7 +270,9 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
await uploadFile(page, "playwright/sample-files/upload-second.ogg");
|
||||
|
||||
// Assert that the audio player is rendered
|
||||
await expect(page.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible();
|
||||
await expect(
|
||||
page.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" }),
|
||||
).toBeVisible();
|
||||
|
||||
await clickButtonReply(tile);
|
||||
|
||||
@@ -277,7 +280,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
await uploadFile(page, "playwright/sample-files/upload-third.ogg");
|
||||
|
||||
// Assert that the audio player is rendered
|
||||
await expect(tile.locator(".mx_AudioPlayer_container")).toBeVisible();
|
||||
await expect(tile.getByRole("region", { name: "Audio player" })).toBeVisible();
|
||||
|
||||
// Assert that there are two "mx_ReplyChain" elements
|
||||
await expect(tile.locator(".mx_ReplyChain")).toHaveCount(2);
|
||||
@@ -313,7 +316,9 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
// On the main timeline
|
||||
const messageList = page.locator(".mx_RoomView_MessageList");
|
||||
// Assert the audio player is rendered
|
||||
await expect(messageList.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible();
|
||||
await expect(
|
||||
messageList.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" }),
|
||||
).toBeVisible();
|
||||
// Find and click "Reply in thread" button
|
||||
await messageList.locator(".mx_EventTile_last").hover();
|
||||
await messageList.locator(".mx_EventTile_last").getByRole("button", { name: "Reply in thread" }).click();
|
||||
@@ -321,10 +326,10 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
// On a thread
|
||||
const thread = page.locator(".mx_ThreadView");
|
||||
const threadTile = thread.locator(".mx_EventTile_last");
|
||||
const audioPlayer = threadTile.locator(".mx_AudioPlayer_container");
|
||||
const audioPlayer = threadTile.getByRole("region", { name: "Audio player" });
|
||||
|
||||
// Assert that the counter is zero before clicking the play button
|
||||
await expect(audioPlayer.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
|
||||
await expect(audioPlayer.getByRole("timer")).toHaveText("00:00");
|
||||
|
||||
// Find and click "Play" button, the wait is to make the test less flaky
|
||||
await expect(audioPlayer.getByRole("button", { name: "Play" })).toBeVisible();
|
||||
@@ -334,7 +339,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
await expect(audioPlayer.getByRole("button", { name: "Pause" })).toBeVisible();
|
||||
|
||||
// Assert that the timer is reset when the audio file finished playing
|
||||
await expect(audioPlayer.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
|
||||
await expect(audioPlayer.getByRole("timer")).toHaveText("00:00");
|
||||
|
||||
// Assert that "Play" button can be found
|
||||
await expect(audioPlayer.getByRole("button", { name: "Play" })).not.toBeDisabled();
|
||||
|
||||
@@ -28,7 +28,7 @@ test.describe("Composer", () => {
|
||||
|
||||
test.describe("CIDER", () => {
|
||||
test("sends a message when you click send or press Enter", async ({ page }) => {
|
||||
const composer = page.getByRole("textbox", { name: "Send a message…" });
|
||||
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
|
||||
// Type a message
|
||||
await composer.pressSequentially("my message 0");
|
||||
@@ -52,7 +52,7 @@ test.describe("Composer", () => {
|
||||
});
|
||||
|
||||
test("can write formatted text", async ({ page }) => {
|
||||
const composer = page.getByRole("textbox", { name: "Send a message…" });
|
||||
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
|
||||
await composer.pressSequentially("my bold");
|
||||
await composer.press(`${CtrlOrMeta}+KeyB`);
|
||||
@@ -68,7 +68,7 @@ test.describe("Composer", () => {
|
||||
await page.getByTestId("mx_EmojiPicker").locator(".mx_EmojiPicker_item", { hasText: "😇" }).click();
|
||||
|
||||
await page.locator(".mx_ContextualMenu_background").click(); // Close emoji picker
|
||||
await page.getByRole("textbox", { name: "Send a message…" }).press("Enter"); // Send message
|
||||
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).press("Enter"); // Send message
|
||||
|
||||
await expect(page.locator(".mx_EventTile_body", { hasText: "😇" })).toBeVisible();
|
||||
});
|
||||
@@ -79,7 +79,7 @@ test.describe("Composer", () => {
|
||||
});
|
||||
|
||||
test("only sends when you press Control+Enter", async ({ page }) => {
|
||||
const composer = page.getByRole("textbox", { name: "Send a message…" });
|
||||
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
// Type a message and press Enter
|
||||
await composer.pressSequentially("my message 3");
|
||||
await composer.press("Enter");
|
||||
|
||||
@@ -91,10 +91,10 @@ test.describe("Key backup reset from elsewhere", () => {
|
||||
|
||||
await csAPI.deleteBackupVersion(backupInfo.version);
|
||||
|
||||
await page.getByRole("textbox", { name: "Send an encrypted message…" }).fill("/discardsession");
|
||||
await page.getByRole("textbox", { name: "Send a message…" }).fill("/discardsession");
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
|
||||
await page.getByRole("textbox", { name: "Send an encrypted message…" }).fill("Message with broken key backup");
|
||||
await page.getByRole("textbox", { name: "Send a message…" }).fill("Message with broken key backup");
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
|
||||
// Should be the message we sent plus the room creation event
|
||||
|
||||
@@ -154,11 +154,12 @@ test.describe("Cryptography", function () {
|
||||
await app.client.bootstrapCrossSigning(aliceCredentials);
|
||||
await startDMWithBob(page, bob);
|
||||
// send first message
|
||||
await page.getByRole("textbox", { name: "Send a message…" }).fill("Hey!");
|
||||
await page.getByRole("textbox", { name: "Send a message…" }).press("Enter");
|
||||
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).fill("Hey!");
|
||||
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).press("Enter");
|
||||
await checkDMRoom(page);
|
||||
const bobRoomId = await bobJoin(page, bob);
|
||||
await expect(page.locator(".mx_MessageComposer_e2eIcon")).toMatchScreenshot("composer-e2e-icon-normal.png");
|
||||
// We no longer show the grey badge in the composer, check that it is not there.
|
||||
await expect(page.locator(".mx_MessageComposer_e2eIcon")).toHaveCount(0);
|
||||
|
||||
await testMessages(page, bob, bobRoomId);
|
||||
await verify(app, bob);
|
||||
|
||||
@@ -124,6 +124,10 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
|
||||
const toasts = new Toasts(page);
|
||||
await toasts.rejectToast("Notifications");
|
||||
await toasts.assertNoToasts();
|
||||
|
||||
// There may still be a `/sendToDevice/m.secret.request` in flight, which will later throw an error and cause
|
||||
// a *subsequent* test to fail. Tell playwright to ignore any errors resulting from in-flight routes.
|
||||
await page.unrouteAll({ behavior: "ignoreErrors" });
|
||||
});
|
||||
|
||||
test("Verify device with QR code during login", async ({ page, app, credentials, homeserver }) => {
|
||||
@@ -205,7 +209,7 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
|
||||
const dialog = page.locator(".mx_Dialog");
|
||||
// We use `pressSequentially` here to make sure that the FocusLock isn't causing us any problems
|
||||
// (cf https://github.com/element-hq/element-web/issues/30089)
|
||||
await dialog.locator("textarea").pressSequentially(recoveryKey);
|
||||
await dialog.getByTitle("Recovery key").pressSequentially(recoveryKey);
|
||||
await dialog.getByRole("button", { name: "Continue", disabled: false }).click();
|
||||
|
||||
await page.getByRole("button", { name: "Done" }).click();
|
||||
|
||||
@@ -58,108 +58,108 @@ test.describe("Cryptography", function () {
|
||||
await app.client.network.setupRoute();
|
||||
});
|
||||
|
||||
test("should show the correct shield on e2e events", async ({
|
||||
page,
|
||||
app,
|
||||
bot: bob,
|
||||
homeserver,
|
||||
}, workerInfo) => {
|
||||
// Bob has a second, not cross-signed, device
|
||||
const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob);
|
||||
test(
|
||||
"should show the correct shield on e2e events",
|
||||
{ tag: "@screenshot" },
|
||||
async ({ page, app, bot: bob, homeserver }, workerInfo) => {
|
||||
// Bob has a second, not cross-signed, device
|
||||
const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob);
|
||||
|
||||
// Dismiss the toasts nagging us, otherwise they get in the way of clicking the room list
|
||||
await page.getByRole("button", { name: "Dismiss" }).click();
|
||||
await page.getByRole("button", { name: "Yes, dismiss" }).click();
|
||||
// Dismiss the toasts nagging us, otherwise they get in the way of clicking the room list
|
||||
await page.getByRole("button", { name: "Dismiss" }).click();
|
||||
await page.getByRole("button", { name: "Yes, dismiss" }).click();
|
||||
|
||||
await bob.sendEvent(testRoomId, null, "m.room.encrypted", {
|
||||
algorithm: "m.megolm.v1.aes-sha2",
|
||||
ciphertext: "the bird is in the hand",
|
||||
});
|
||||
await bob.sendEvent(testRoomId, null, "m.room.encrypted", {
|
||||
algorithm: "m.megolm.v1.aes-sha2",
|
||||
ciphertext: "the bird is in the hand",
|
||||
});
|
||||
|
||||
const last = page.locator(".mx_EventTile_last");
|
||||
await expect(last).toContainText("Unable to decrypt message");
|
||||
const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon");
|
||||
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_decryption_failure/);
|
||||
await lastE2eIcon.focus();
|
||||
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
|
||||
"This message could not be decrypted",
|
||||
);
|
||||
const last = page.locator(".mx_EventTile_last");
|
||||
await expect(last).toContainText("Unable to decrypt message");
|
||||
const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon");
|
||||
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_decryption_failure/);
|
||||
await lastE2eIcon.focus();
|
||||
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
|
||||
"This message could not be decrypted",
|
||||
);
|
||||
|
||||
/* Should show a red padlock for an unencrypted message in an e2e room */
|
||||
await bob.evaluate(
|
||||
(cli, testRoomId) =>
|
||||
cli.http.authedRequest(
|
||||
window.matrixcs.Method.Put,
|
||||
`/rooms/${encodeURIComponent(testRoomId)}/send/m.room.message/test_txn_1`,
|
||||
undefined,
|
||||
{
|
||||
msgtype: "m.text",
|
||||
body: "test unencrypted",
|
||||
},
|
||||
),
|
||||
testRoomId,
|
||||
);
|
||||
/* Should show a red padlock for an unencrypted message in an e2e room */
|
||||
await bob.evaluate(
|
||||
(cli, testRoomId) =>
|
||||
cli.http.authedRequest(
|
||||
window.matrixcs.Method.Put,
|
||||
`/rooms/${encodeURIComponent(testRoomId)}/send/m.room.message/test_txn_1`,
|
||||
undefined,
|
||||
{
|
||||
msgtype: "m.text",
|
||||
body: "test unencrypted",
|
||||
},
|
||||
),
|
||||
testRoomId,
|
||||
);
|
||||
|
||||
await expect(last).toContainText("test unencrypted");
|
||||
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
|
||||
await lastE2eIcon.focus();
|
||||
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText("Not encrypted");
|
||||
await expect(last).toContainText("test unencrypted");
|
||||
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
|
||||
await expect(lastE2eIcon).toMatchScreenshot("event-shield-warning.png");
|
||||
await lastE2eIcon.focus();
|
||||
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText("Not encrypted");
|
||||
|
||||
/* Should show no padlock for an unverified user */
|
||||
// bob sends a valid event
|
||||
await bob.sendMessage(testRoomId, "test encrypted 1");
|
||||
/* Should show no padlock for an unverified user */
|
||||
// bob sends a valid event
|
||||
await bob.sendMessage(testRoomId, "test encrypted 1");
|
||||
|
||||
// the message should appear, decrypted, with no warning, but also no "verified"
|
||||
const lastTile = page.locator(".mx_EventTile_last");
|
||||
const lastTileE2eIcon = lastTile.locator(".mx_EventTile_e2eIcon");
|
||||
await expect(lastTile).toContainText("test encrypted 1");
|
||||
// no e2e icon
|
||||
await expect(lastTileE2eIcon).not.toBeVisible();
|
||||
// the message should appear, decrypted, with no warning, but also no "verified"
|
||||
const lastTile = page.locator(".mx_EventTile_last");
|
||||
const lastTileE2eIcon = lastTile.locator(".mx_EventTile_e2eIcon");
|
||||
await expect(lastTile).toContainText("test encrypted 1");
|
||||
// no e2e icon
|
||||
await expect(lastTileE2eIcon).not.toBeVisible();
|
||||
|
||||
/* Now verify Bob */
|
||||
await verify(app, bob);
|
||||
/* Now verify Bob */
|
||||
await verify(app, bob);
|
||||
|
||||
/* Existing message should be updated when user is verified. */
|
||||
await expect(last).toContainText("test encrypted 1");
|
||||
// still no e2e icon
|
||||
await expect(last.locator(".mx_EventTile_e2eIcon")).not.toBeVisible();
|
||||
/* Existing message should be updated when user is verified. */
|
||||
await expect(last).toContainText("test encrypted 1");
|
||||
// still no e2e icon
|
||||
await expect(last.locator(".mx_EventTile_e2eIcon")).not.toBeVisible();
|
||||
|
||||
/* should show no padlock, and be verified, for a message from a verified device */
|
||||
await bob.sendMessage(testRoomId, "test encrypted 2");
|
||||
/* should show no padlock, and be verified, for a message from a verified device */
|
||||
await bob.sendMessage(testRoomId, "test encrypted 2");
|
||||
|
||||
await expect(lastTile).toContainText("test encrypted 2");
|
||||
// no e2e icon
|
||||
await expect(lastTileE2eIcon).not.toBeVisible();
|
||||
await expect(lastTile).toContainText("test encrypted 2");
|
||||
// no e2e icon
|
||||
await expect(lastTileE2eIcon).not.toBeVisible();
|
||||
|
||||
/* should show red padlock for a message from an unverified device */
|
||||
await bobSecondDevice.sendMessage(testRoomId, "test encrypted from unverified");
|
||||
await expect(lastTile).toContainText("test encrypted from unverified");
|
||||
await expect(lastTileE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
|
||||
await lastTileE2eIcon.focus();
|
||||
await expect(await app.getTooltipForElement(lastTileE2eIcon)).toContainText(
|
||||
"Encrypted by a device not verified by its owner.",
|
||||
);
|
||||
/* should show red padlock for a message from an unverified device */
|
||||
await bobSecondDevice.sendMessage(testRoomId, "test encrypted from unverified");
|
||||
await expect(lastTile).toContainText("test encrypted from unverified");
|
||||
await expect(lastTileE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
|
||||
await lastTileE2eIcon.focus();
|
||||
await expect(await app.getTooltipForElement(lastTileE2eIcon)).toContainText(
|
||||
"Encrypted by a device not verified by its owner.",
|
||||
);
|
||||
|
||||
/* Should show a red padlock for a message from an unverified device.
|
||||
* Rust crypto remembers the verification state of the sending device, so it will know that the device was
|
||||
* unverified, even if it gets deleted. */
|
||||
// bob deletes his second device
|
||||
await bobSecondDevice.evaluate((cli) => cli.logout(true));
|
||||
/* Should show a red padlock for a message from an unverified device.
|
||||
* Rust crypto remembers the verification state of the sending device, so it will know that the device was
|
||||
* unverified, even if it gets deleted. */
|
||||
// bob deletes his second device
|
||||
await bobSecondDevice.evaluate((cli) => cli.logout(true));
|
||||
|
||||
// wait for the logout to propagate.
|
||||
await waitForDevices(app, bob.credentials.userId, 1);
|
||||
// wait for the logout to propagate.
|
||||
await waitForDevices(app, bob.credentials.userId, 1);
|
||||
|
||||
// close and reopen the room, to get the shield to update.
|
||||
await app.viewRoomByName("Bob");
|
||||
await app.viewRoomByName("TestRoom");
|
||||
// close and reopen the room, to get the shield to update.
|
||||
await app.viewRoomByName("Bob");
|
||||
await app.viewRoomByName("TestRoom");
|
||||
|
||||
await expect(last).toContainText("test encrypted from unverified");
|
||||
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
|
||||
await lastE2eIcon.focus();
|
||||
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
|
||||
"Encrypted by a device not verified by its owner.",
|
||||
);
|
||||
});
|
||||
await expect(last).toContainText("test encrypted from unverified");
|
||||
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
|
||||
await lastE2eIcon.focus();
|
||||
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
|
||||
"Encrypted by a device not verified by its owner.",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("Should show a grey padlock for a key restored from backup", async ({
|
||||
page,
|
||||
|
||||
@@ -228,7 +228,7 @@ export async function logIntoElement(page: Page, credentials: Credentials, secur
|
||||
await useSecurityKey.click();
|
||||
}
|
||||
// Fill in the recovery key
|
||||
await page.locator(".mx_Dialog").locator("textarea").fill(securityKey);
|
||||
await page.locator(".mx_Dialog").getByTitle("Recovery key").fill(securityKey);
|
||||
await page.getByRole("button", { name: "Continue", disabled: false }).click();
|
||||
await page.getByRole("button", { name: "Done" }).click();
|
||||
}
|
||||
@@ -263,7 +263,7 @@ export async function verifySession(app: ElementAppPage, securityKey: string) {
|
||||
const settings = await app.settings.openUserSettings("Encryption");
|
||||
await settings.getByRole("button", { name: "Verify this device" }).click();
|
||||
await app.page.getByRole("button", { name: "Verify with Recovery Key" }).click();
|
||||
await app.page.locator(".mx_Dialog").locator("textarea").fill(securityKey);
|
||||
await app.page.locator(".mx_Dialog").getByTitle("Recovery key").fill(securityKey);
|
||||
await app.page.getByRole("button", { name: "Continue", disabled: false }).click();
|
||||
await app.page.getByRole("button", { name: "Done" }).click();
|
||||
await app.settings.closeDialog();
|
||||
|
||||
@@ -30,6 +30,10 @@ test.describe("Lazy Loading", () => {
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ page, homeserver, user, bot, app }) => {
|
||||
// The charlies were running off the bottom of the screen.
|
||||
// We no longer overscan the member list so the result is they are not in the dom.
|
||||
// Increase the viewport size to ensure they are.
|
||||
await page.setViewportSize({ width: 1000, height: 1000 });
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
const displayName = `Charly #${i}`;
|
||||
const bot = new Bot(page, homeserver, { displayName, startClient: false, autoAcceptInvites: false });
|
||||
|
||||
@@ -35,8 +35,8 @@ test.describe("Header section of the room list", () => {
|
||||
|
||||
await expect(page.getByRole("menu")).toMatchScreenshot("room-list-header-compose-menu.png");
|
||||
|
||||
// New message should open the direct messages dialog
|
||||
await page.getByRole("menuitem", { name: "New message" }).click();
|
||||
// Start chat should open the direct messages dialog
|
||||
await page.getByRole("menuitem", { name: "Start chat" }).click();
|
||||
await expect(page.getByRole("heading", { name: "Direct Messages" })).toBeVisible();
|
||||
await app.closeDialog();
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import { type Locator, type Page } from "@playwright/test";
|
||||
import { test, expect } from "../../element-web-test";
|
||||
|
||||
async function sendMessage(page: Page, message: string): Promise<Locator> {
|
||||
await page.getByRole("textbox", { name: "Send a message…" }).fill(message);
|
||||
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).fill(message);
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
|
||||
const msgTile = page.locator(".mx_EventTile_last");
|
||||
@@ -22,7 +22,7 @@ async function sendMessage(page: Page, message: string): Promise<Locator> {
|
||||
}
|
||||
|
||||
async function sendMultilineMessages(page: Page, messages: string[]) {
|
||||
await page.getByRole("textbox", { name: "Send a message…" }).focus();
|
||||
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).focus();
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
await page.keyboard.type(messages[i]);
|
||||
if (i < messages.length - 1) await page.keyboard.press("Shift+Enter");
|
||||
@@ -40,7 +40,7 @@ async function replyMessage(page: Page, message: Locator, replyMessage: string):
|
||||
await line.hover();
|
||||
await line.getByRole("button", { name: "Reply", exact: true }).click();
|
||||
|
||||
await page.getByRole("textbox", { name: "Send a reply…" }).fill(replyMessage);
|
||||
await page.getByRole("textbox", { name: "Send an unencrypted reply…" }).fill(replyMessage);
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
|
||||
const msgTile = page.locator(".mx_EventTile_last");
|
||||
|
||||
@@ -29,7 +29,7 @@ test.describe("Pills", () => {
|
||||
|
||||
// send a message using the built-in room mention functionality (autocomplete)
|
||||
await page
|
||||
.getByRole("textbox", { name: "Send a message…" })
|
||||
.getByRole("textbox", { name: "Send an unencrypted message…" })
|
||||
.pressSequentially(`Hello world! Join here: #${targetLocalpart.substring(0, 3)}`);
|
||||
await page.locator(".mx_Autocomplete_Completion_title").click();
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
|
||||
@@ -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 Download, type Page } from "@playwright/test";
|
||||
import { type Page } from "@playwright/test";
|
||||
|
||||
import { test, expect } from "../../element-web-test";
|
||||
import { viewRoomSummaryByName } from "./utils";
|
||||
@@ -63,9 +63,7 @@ test.describe("FilePanel", () => {
|
||||
await expect(roomViewBody.locator(".mx_EventTile[data-layout='group'] img[alt='riot.png']")).toBeVisible();
|
||||
|
||||
// Assert that the audio player is rendered
|
||||
await expect(
|
||||
roomViewBody.locator(".mx_EventTile[data-layout='group'] .mx_AudioPlayer_container"),
|
||||
).toBeVisible();
|
||||
await expect(roomViewBody.getByRole("region", { name: "Audio player" })).toBeVisible();
|
||||
|
||||
// Assert that the file button exists
|
||||
await expect(
|
||||
@@ -97,9 +95,7 @@ test.describe("FilePanel", () => {
|
||||
await expect(image.locator("img[alt='riot.png']")).toBeVisible();
|
||||
|
||||
// Detect the audio file
|
||||
const audio = filePanelMessageList.locator(
|
||||
".mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container",
|
||||
);
|
||||
const audio = filePanelMessageList.getByRole("region", { name: "Audio player" });
|
||||
// Assert that the play button is rendered
|
||||
await expect(audio.getByRole("button", { name: "Play" })).toBeVisible();
|
||||
|
||||
@@ -130,7 +126,7 @@ test.describe("FilePanel", () => {
|
||||
// Take a snapshot of file tiles list on FilePanel
|
||||
await expect(filePanelMessageList).toMatchScreenshot("file-tiles-list.png", {
|
||||
// Exclude timestamps & flaky seek bar from snapshot
|
||||
mask: [page.locator(".mx_MessageTimestamp, .mx_AudioPlayer_seek")],
|
||||
mask: [page.locator(".mx_MessageTimestamp"), page.getByTestId("audio-player-seek")],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -138,21 +134,19 @@ test.describe("FilePanel", () => {
|
||||
// Upload an image file
|
||||
await uploadFile(page, "playwright/sample-files/1sec.ogg");
|
||||
|
||||
const audioBody = page.locator(
|
||||
".mx_FilePanel .mx_RoomView_MessageList .mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container",
|
||||
);
|
||||
const audioBody = page.getByTestId("right-panel").getByRole("region", { name: "Audio player" });
|
||||
|
||||
// Assert that the audio player is rendered
|
||||
// Assert that the audio file information is rendered
|
||||
const mediaInfo = audioBody.locator(".mx_AudioPlayer_mediaInfo");
|
||||
await expect(mediaInfo.locator(".mx_AudioPlayer_mediaName").getByText("1sec.ogg")).toBeVisible();
|
||||
await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "00:01" })).toBeVisible();
|
||||
await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "(3.56 KB)" })).toBeVisible(); // actual size
|
||||
// Assert that the audio file information is rendered;
|
||||
await expect(audioBody.getByText("1sec.ogg")).toBeVisible(); // extension
|
||||
await expect(audioBody.getByRole("time")).toHaveText("00:01"); // duration
|
||||
await expect(audioBody.getByText("(3.56 KB)")).toBeVisible(); // actual size;
|
||||
|
||||
// Assert that the duration counter is 00:01 before clicking the play button
|
||||
await expect(audioBody.locator(".mx_AudioPlayer_mediaInfo time", { hasText: "00:01" })).toBeVisible();
|
||||
await expect(audioBody.getByRole("time")).toHaveText("00:01");
|
||||
|
||||
// Assert that the counter is zero before clicking the play button
|
||||
await expect(audioBody.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
|
||||
await expect(audioBody.getByRole("timer")).toHaveText("00:00");
|
||||
|
||||
// Click the play button
|
||||
await audioBody.getByRole("button", { name: "Play" }).click();
|
||||
@@ -161,7 +155,7 @@ test.describe("FilePanel", () => {
|
||||
await expect(audioBody.getByRole("button", { name: "Pause" })).toBeVisible();
|
||||
|
||||
// Assert that the timer is reset when the audio file finished playing
|
||||
await expect(audioBody.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
|
||||
await expect(audioBody.getByRole("timer")).toHaveText("00:00");
|
||||
|
||||
// Assert that the play button is rendered
|
||||
await expect(audioBody.getByRole("button", { name: "Play" })).toBeVisible();
|
||||
@@ -195,23 +189,13 @@ test.describe("FilePanel", () => {
|
||||
|
||||
const link = imageBody.locator(".mx_MFileBody_download a");
|
||||
|
||||
const newPagePromise = context.waitForEvent("page");
|
||||
|
||||
const downloadPromise = new Promise<Download>((resolve) => {
|
||||
page.once("download", resolve);
|
||||
});
|
||||
const downloadPromise = page.waitForEvent("download");
|
||||
|
||||
// Click the anchor link (not the image itself)
|
||||
await link.click();
|
||||
|
||||
const newPage = await newPagePromise;
|
||||
// XXX: Clicking the link opens the image in a new tab on some browsers rather than downloading
|
||||
await expect(newPage)
|
||||
.toHaveURL(/.+\/_matrix\/media\/\w+\/download\/localhost\/\w+/)
|
||||
.catch(async () => {
|
||||
const download = await downloadPromise;
|
||||
expect(download.suggestedFilename()).toBe("riot.png");
|
||||
});
|
||||
const download = await downloadPromise;
|
||||
expect(download.suggestedFilename()).toBe("riot.png");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,32 @@ import { Bot } from "../../pages/bot";
|
||||
const ROOM_NAME = "Test room";
|
||||
const NAME = "Alice";
|
||||
|
||||
async function setupRoomWithMembers(
|
||||
app: any,
|
||||
page: any,
|
||||
homeserver: any,
|
||||
roomName: string,
|
||||
memberNames: string[],
|
||||
): Promise<string> {
|
||||
const visibility = await page.evaluate(() => (window as any).matrixcs.Visibility.Public);
|
||||
const id = await app.client.createRoom({ name: roomName, visibility });
|
||||
const bots: Bot[] = [];
|
||||
|
||||
for (let i = 0; i < memberNames.length; i++) {
|
||||
const displayName = memberNames[i];
|
||||
const bot = new Bot(page, homeserver, { displayName, startClient: false, autoAcceptInvites: false });
|
||||
if (displayName === "Susan") {
|
||||
await bot.prepareClient();
|
||||
await app.client.inviteUser(id, bot.credentials?.userId);
|
||||
} else {
|
||||
await bot.joinRoom(id);
|
||||
}
|
||||
bots.push(bot);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
test.use({
|
||||
synapseConfig: {
|
||||
presence: {
|
||||
@@ -25,17 +51,8 @@ test.use({
|
||||
test.describe("Memberlist", () => {
|
||||
test.beforeEach(async ({ app, user, page, homeserver }, testInfo) => {
|
||||
testInfo.setTimeout(testInfo.timeout + 30_000);
|
||||
const id = await app.client.createRoom({ name: ROOM_NAME });
|
||||
const newBots: Bot[] = [];
|
||||
const names = ["Bob", "Bob", "Susan"];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const displayName = names[i];
|
||||
const autoAcceptInvites = displayName !== "Susan";
|
||||
const bot = new Bot(page, homeserver, { displayName, startClient: true, autoAcceptInvites });
|
||||
await bot.prepareClient();
|
||||
await app.client.inviteUser(id, bot.credentials?.userId);
|
||||
newBots.push(bot);
|
||||
}
|
||||
await setupRoomWithMembers(app, page, homeserver, ROOM_NAME, names);
|
||||
});
|
||||
|
||||
test("Renders correctly", { tag: "@screenshot" }, async ({ page, app }) => {
|
||||
@@ -45,4 +62,37 @@ test.describe("Memberlist", () => {
|
||||
await expect(memberlist.getByText("Invited")).toHaveCount(1);
|
||||
await expect(page.locator(".mx_MemberListView")).toMatchScreenshot("with-four-members.png");
|
||||
});
|
||||
|
||||
test("should handle scroll and click to view member profile", async ({ page, app, homeserver }) => {
|
||||
// Create a room with many members to enable scrolling
|
||||
const memberNames = Array.from({ length: 15 }, (_, i) => `Member${i.toString()}`);
|
||||
await setupRoomWithMembers(app, page, homeserver, "Large Room", memberNames);
|
||||
|
||||
// Navigate to the room and open member list
|
||||
await app.viewRoomByName("Large Room");
|
||||
|
||||
const memberlist = await app.toggleMemberlistPanel();
|
||||
|
||||
// Get the scrollable container
|
||||
const memberListContainer = memberlist.locator(".mx_AutoHideScrollbar");
|
||||
|
||||
// Scroll down to the bottom of the member list
|
||||
await app.scrollListToBottom(memberListContainer);
|
||||
|
||||
// Wait for the target member to be visible after scrolling
|
||||
const targetName = "Member14";
|
||||
const targetMember = memberlist.locator(".mx_MemberTileView_name").filter({ hasText: targetName });
|
||||
await targetMember.waitFor({ state: "visible" });
|
||||
|
||||
// Verify Alice is not visible at this point
|
||||
await expect(memberlist.locator(".mx_MemberTileView_name").filter({ hasText: "Alice" })).toHaveCount(0);
|
||||
|
||||
// Click on a member near the bottom of the list
|
||||
await expect(targetMember).toBeVisible();
|
||||
await targetMember.click();
|
||||
|
||||
// Verify that the user info screen is shown and hasn't scrolled back to top
|
||||
await expect(page.locator(".mx_UserInfo")).toBeVisible();
|
||||
await expect(page.locator(".mx_UserInfo_profile").getByText(targetName)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,11 +37,8 @@ test.describe("Room Header", () => {
|
||||
await expect(header.locator(".mx_FacePile")).toBeVisible();
|
||||
|
||||
// There should be both a voice and a video call button
|
||||
// but they'll be disabled
|
||||
const callButtons = header.getByRole("button", { name: "There's no one here to call" });
|
||||
await expect(callButtons).toHaveCount(2);
|
||||
await expect(callButtons.first()).toBeVisible();
|
||||
await expect(callButtons.last()).toBeVisible();
|
||||
await expect(header.getByRole("button", { name: "Video call" })).toBeVisible();
|
||||
await expect(header.getByRole("button", { name: "Voice call" })).toBeVisible();
|
||||
|
||||
await expect(header.getByRole("button", { name: "Threads" })).toBeVisible();
|
||||
await expect(header.getByRole("button", { name: "Notifications" })).toBeVisible();
|
||||
|
||||
@@ -18,13 +18,14 @@ test.describe("share from URL", () => {
|
||||
|
||||
test("should share message when users navigates to share URL", async ({ page, user, room, app }) => {
|
||||
await page.goto("/#/share?msg=Hello+world");
|
||||
const dialog = page.getByRole("dialog", { name: "Forward message" });
|
||||
// The forward message dialog doesn't update as new infomation arrives via sync, which means sometimes
|
||||
// this is just says, "Empty room". For the same reason, we can't reliably write a test for loading the
|
||||
// app straight away with a /#/share url as the room doesn't appear until the client syncs.]
|
||||
// Ideally we should fix the forward dialog to update and eliminate races, until then, there is only one
|
||||
// room so we click the first button.
|
||||
await page.getByRole("listitem" /*, { name: "A test room" }*/).getByRole("button", { name: "Send" }).click();
|
||||
await page.keyboard.press("Escape");
|
||||
await dialog.getByRole("listitem" /*, { name: "A test room" }*/).getByRole("button", { name: "Send" }).click();
|
||||
await dialog.getByRole("button", { name: "Close" }).click();
|
||||
await app.viewRoomByName("A test room");
|
||||
const lastMessage = page.locator(".mx_RoomView_MessageList .mx_EventTile_last");
|
||||
await expect(lastMessage).toBeVisible();
|
||||
|
||||
@@ -35,7 +35,7 @@ test.describe("Share dialog", () => {
|
||||
|
||||
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("option", { name: user.displayName }).click();
|
||||
await rightPanel.getByRole("button", { name: "Share profile" }).click();
|
||||
|
||||
const dialog = page.getByRole("dialog", { name: "Share User" });
|
||||
|
||||
@@ -23,7 +23,7 @@ async function openSpaceContextMenu(page: Page, app: ElementAppPage, spaceName:
|
||||
return page.locator(".mx_SpacePanel_contextMenu");
|
||||
}
|
||||
|
||||
function spaceCreateOptions(spaceName: string, roomIds: string[] = []): ICreateRoomOpts {
|
||||
function spaceCreateOptions(serverName: string, spaceName: string, roomIds: string[] = []): ICreateRoomOpts {
|
||||
return {
|
||||
creation_content: {
|
||||
type: "m.space",
|
||||
@@ -35,17 +35,21 @@ function spaceCreateOptions(spaceName: string, roomIds: string[] = []): ICreateR
|
||||
name: spaceName,
|
||||
},
|
||||
},
|
||||
...roomIds.map((r) => spaceChildInitialState(r)),
|
||||
...roomIds.map((r) => spaceChildInitialState(serverName, r)),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function spaceChildInitialState(roomId: string, order?: string): ICreateRoomOpts["initial_state"]["0"] {
|
||||
function spaceChildInitialState(
|
||||
serverName: string,
|
||||
roomId: string,
|
||||
order?: string,
|
||||
): ICreateRoomOpts["initial_state"]["0"] {
|
||||
return {
|
||||
type: "m.space.child",
|
||||
state_key: roomId,
|
||||
content: {
|
||||
via: [roomId.split(":")[1]],
|
||||
via: [serverName],
|
||||
order,
|
||||
},
|
||||
};
|
||||
@@ -240,7 +244,7 @@ test.describe("Spaces", () => {
|
||||
});
|
||||
await expect(await app.getSpacePanelButton("My Space")).toBeVisible();
|
||||
|
||||
const roomId = await bot.createRoom(spaceCreateOptions("Space Space"));
|
||||
const roomId = await bot.createRoom(spaceCreateOptions(user.homeServer, "Space Space"));
|
||||
await bot.inviteUser(roomId, user.userId);
|
||||
|
||||
// Assert that `Space Space` is above `My Space` due to it being an invite
|
||||
@@ -260,7 +264,10 @@ test.describe("Spaces", () => {
|
||||
const spaceName = "Spacey Mc. Space Space";
|
||||
await app.client.createSpace({
|
||||
name: spaceName,
|
||||
initial_state: [spaceChildInitialState(roomId1), spaceChildInitialState(roomId2)],
|
||||
initial_state: [
|
||||
spaceChildInitialState(user.homeServer, roomId1),
|
||||
spaceChildInitialState(user.homeServer, roomId2),
|
||||
],
|
||||
});
|
||||
|
||||
await app.viewSpaceHomeByName(spaceName);
|
||||
@@ -287,7 +294,7 @@ test.describe("Spaces", () => {
|
||||
});
|
||||
await app.client.createSpace({
|
||||
name: "Root Space",
|
||||
initial_state: [spaceChildInitialState(childSpaceId)],
|
||||
initial_state: [spaceChildInitialState(user.homeServer, childSpaceId)],
|
||||
});
|
||||
|
||||
// Find collapsed Space panel
|
||||
@@ -323,7 +330,7 @@ test.describe("Spaces", () => {
|
||||
name: "Test Room",
|
||||
topic: "This is a topic https://github.com/matrix-org/matrix-react-sdk/pull/10060 with a link",
|
||||
});
|
||||
const spaceId = await bot.createRoom(spaceCreateOptions("Test Space", [roomId]));
|
||||
const spaceId = await bot.createRoom(spaceCreateOptions(user.homeServer, "Test Space", [roomId]));
|
||||
await bot.inviteUser(spaceId, user.userId);
|
||||
|
||||
await expect(await app.getSpacePanelButton("Test Space")).toBeVisible();
|
||||
@@ -361,9 +368,9 @@ test.describe("Spaces", () => {
|
||||
await app.client.createSpace({
|
||||
name: "Root Space",
|
||||
initial_state: [
|
||||
spaceChildInitialState(childSpaceId1, "a"),
|
||||
spaceChildInitialState(childSpaceId2, "b"),
|
||||
spaceChildInitialState(childSpaceId3, "c"),
|
||||
spaceChildInitialState(user.homeServer, childSpaceId1, "a"),
|
||||
spaceChildInitialState(user.homeServer, childSpaceId2, "b"),
|
||||
spaceChildInitialState(user.homeServer, childSpaceId3, "c"),
|
||||
],
|
||||
});
|
||||
await app.viewSpaceByName("Root Space");
|
||||
|
||||
@@ -30,7 +30,7 @@ async function startDM(app: ElementAppPage, page: Page, name: string): Promise<v
|
||||
await result.first().click();
|
||||
|
||||
// send first message to start DM
|
||||
const locator = page.getByRole("textbox", { name: "Send a message…" });
|
||||
const locator = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
await expect(locator).toBeFocused();
|
||||
await locator.fill("Hey!");
|
||||
await locator.press("Enter");
|
||||
@@ -260,7 +260,7 @@ test.describe("Spotlight", () => {
|
||||
|
||||
// Send first message to actually start DM
|
||||
await expect(roomHeaderName(page)).toHaveText(bot2.credentials.displayName);
|
||||
const locator = page.getByRole("textbox", { name: "Send a message…" });
|
||||
const locator = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
await locator.fill("Hey!");
|
||||
await locator.press("Enter");
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ test.describe("Threads", () => {
|
||||
|
||||
const roomViewLocator = page.locator(".mx_RoomView_body");
|
||||
// User sends message
|
||||
const textbox = roomViewLocator.getByRole("textbox", { name: "Send a message…" });
|
||||
const textbox = roomViewLocator.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
await textbox.fill("Hello Mr. Bot");
|
||||
await textbox.press("Enter");
|
||||
|
||||
@@ -108,7 +108,7 @@ test.describe("Threads", () => {
|
||||
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Group);
|
||||
|
||||
// User responds in thread
|
||||
locator = page.locator(".mx_ThreadView").getByRole("textbox", { name: "Send a message…" });
|
||||
locator = page.locator(".mx_ThreadView").getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
await locator.fill("Test");
|
||||
await locator.press("Enter");
|
||||
|
||||
@@ -262,7 +262,7 @@ test.describe("Threads", () => {
|
||||
await locator.locator(".mx_EventTile_line").click();
|
||||
|
||||
// User responds & asserts
|
||||
locator = page.locator(".mx_ThreadView").getByRole("textbox", { name: "Send a message…" });
|
||||
locator = page.locator(".mx_ThreadView").getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
await locator.fill("Great!");
|
||||
await locator.press("Enter");
|
||||
|
||||
@@ -335,8 +335,8 @@ test.describe("Threads", () => {
|
||||
|
||||
// Send message
|
||||
const locator = page.locator(".mx_RoomView_body");
|
||||
await locator.getByRole("textbox", { name: "Send a message…" }).fill("Hello Mr. Bot");
|
||||
await locator.getByRole("textbox", { name: "Send a message…" }).press("Enter");
|
||||
await locator.getByRole("textbox", { name: "Send an unencrypted message…" }).fill("Hello Mr. Bot");
|
||||
await locator.getByRole("textbox", { name: "Send an unencrypted message…" }).press("Enter");
|
||||
// Create thread
|
||||
const locator2 = locator.locator(".mx_EventTile[data-scroll-tokens]").filter({ hasText: "Hello Mr. Bot" });
|
||||
await locator2.hover();
|
||||
@@ -366,7 +366,7 @@ test.describe("Threads", () => {
|
||||
|
||||
let locator = page.locator(".mx_RoomView_body");
|
||||
// User sends message
|
||||
let textbox = locator.getByRole("textbox", { name: "Send a message…" });
|
||||
let textbox = locator.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
await textbox.fill("Hello Mr. Bot");
|
||||
await textbox.press("Enter");
|
||||
// Wait for message to send, get its ID and save as @threadId
|
||||
@@ -395,7 +395,7 @@ test.describe("Threads", () => {
|
||||
locator = page.locator(".mx_ThreadView");
|
||||
await locator.locator(".mx_EventTile_last").hover();
|
||||
await locator.locator(".mx_EventTile_last").getByRole("button", { name: "Reply" }).click();
|
||||
textbox = locator.getByRole("textbox", { name: "Reply to thread…" });
|
||||
textbox = locator.getByRole("textbox", { name: "Reply to unencrypted thread…" });
|
||||
await textbox.fill("Please come here");
|
||||
await textbox.press("Enter");
|
||||
// Wait until the reply is sent
|
||||
@@ -414,7 +414,7 @@ test.describe("Threads", () => {
|
||||
|
||||
// Send message
|
||||
let locator = page.locator(".mx_RoomView_body");
|
||||
let textbox = locator.getByRole("textbox", { name: "Send a message…" });
|
||||
let textbox = locator.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
await textbox.fill("Hello Mr. Bot");
|
||||
await textbox.press("Enter");
|
||||
// Create thread
|
||||
@@ -425,7 +425,7 @@ test.describe("Threads", () => {
|
||||
|
||||
// Send message to thread
|
||||
locator = page.locator(".mx_ThreadPanel");
|
||||
textbox = locator.getByRole("textbox", { name: "Send a message…" });
|
||||
textbox = locator.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
await textbox.fill("Hello Mr. User");
|
||||
await textbox.press("Enter");
|
||||
await expect(locator.locator(".mx_EventTile_last").getByText("Hello Mr. User")).toBeAttached();
|
||||
@@ -456,7 +456,7 @@ test.describe("Threads", () => {
|
||||
*/
|
||||
const sendMessage = async (message: string) => {
|
||||
const messageComposer = page.getByRole("region", { name: "Message composer" });
|
||||
const textbox = messageComposer.getByRole("textbox", { name: "Send a message…" });
|
||||
const textbox = messageComposer.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
await textbox.fill(message);
|
||||
await textbox.press("Enter");
|
||||
};
|
||||
@@ -478,7 +478,7 @@ test.describe("Threads", () => {
|
||||
|
||||
// Send a message in the thread
|
||||
const threadPanel = page.locator(".mx_ThreadPanel");
|
||||
const textbox = threadPanel.getByRole("textbox", { name: "Send a message…" });
|
||||
const textbox = threadPanel.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
await textbox.fill(threadMessage);
|
||||
await textbox.press("Enter");
|
||||
await expect(threadPanel.locator(".mx_EventTile_last").getByText(threadMessage)).toBeVisible();
|
||||
|
||||
@@ -461,11 +461,11 @@ test.describe("Timeline", () => {
|
||||
// Send a emote
|
||||
await page
|
||||
.locator(".mx_RoomView_body")
|
||||
.getByRole("textbox", { name: "Send a message…" })
|
||||
.getByRole("textbox", { name: "Send an unencrypted message…" })
|
||||
.fill("/me says hello to Mr. Bot");
|
||||
await page
|
||||
.locator(".mx_RoomView_body")
|
||||
.getByRole("textbox", { name: "Send a message…" })
|
||||
.getByRole("textbox", { name: "Send an unencrypted message…" })
|
||||
.press("Enter");
|
||||
// Check inline start margin of its avatar
|
||||
// Here --right-padding is for the avatar on the message line
|
||||
|
||||
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 268 B |
|
After Width: | Height: | Size: 406 B |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 19 KiB |
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import { SynapseContainer as BaseSynapseContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers";
|
||||
|
||||
const TAG = "develop@sha256:8c2d9a93dd209a79d3e5e50cd18addfe52d80bea0ffe48a5d3e15836032eeb9d";
|
||||
const TAG = "develop@sha256:f614f4bf0e43aa580a6ecf6ac5a0cad89be3c8830491cb12e75088b7797851db";
|
||||
|
||||
/**
|
||||
* SynapseContainer which freezes the docker digest to stabilise tests,
|
||||
|
||||
@@ -95,7 +95,6 @@
|
||||
@import "./structures/auth/_Registration.pcss";
|
||||
@import "./structures/auth/_SessionLockStolenView.pcss";
|
||||
@import "./structures/auth/_SetupEncryptionBody.pcss";
|
||||
@import "./views/audio_messages/_AudioPlayer.pcss";
|
||||
@import "./views/audio_messages/_PlayPauseButton.pcss";
|
||||
@import "./views/audio_messages/_PlaybackContainer.pcss";
|
||||
@import "./views/audio_messages/_SeekBar.pcss";
|
||||
|
||||