Compare commits

..

198 Commits

Author SHA1 Message Date
David Langley
a415bce803 Fix tests and lint 2025-09-19 15:50:51 +01:00
David Langley
705dcd39f4 Fix html export when feature_jump_to_date is enabled 2025-09-19 15:04:11 +01:00
ElementRobot
c1689ad226 [create-pull-request] automated change (#30825)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-09-19 06:19:18 +00:00
ElementRobot
03129e8f10 [create-pull-request] automated change (#30807)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-09-18 06:19:01 +00:00
David Langley
78cb26201b We no longer need the add_to_project step, as per the step below we now only move the issue to the other column if it is already in the project in the needs info column (#30803) 2025-09-17 13:08:13 +00:00
Florian Duros
38a0d28453 Storybook: load story when language is set (#30799)
* fix: load story when language is set

* refactor: cleaner first render

* fix: use a storybook loader instead of a decorator so set up the language
2025-09-17 12:14:35 +00:00
Will Hunt
b4ba350770 Add new and improved ringtone (#30761)
* Fix racy ringing sound

* Add new ringtone from ECall

* Drop use of promise

* Add a comment.

* lint
2025-09-17 12:02:57 +00:00
David Langley
db2e958823 Disable RTE formatting buttons when the content contains a slash command (#30802)
* Add ability to disable all formatting buttons

* Create hook to check if the content contains a slash command

* Disable the formatting buttons if the message content contains a slash command

* lint

* typo
2025-09-17 11:47:20 +00:00
David Langley
25a8591791 Fix unlabelling of "X-Needs-Info" (#30753)
* Fix broken unlabelled automation

when removing labels if needs info is no longer in the labels we move it from the needs info column to the triage column

* Remove unneeded param

* make action id more specific
2025-09-17 10:56:00 +00:00
ElementRobot
570e263ca1 [create-pull-request] automated change (#30797)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-09-17 06:19:21 +00:00
renovate[bot]
a2e18a61a8 Update all non-major dependencies (#30779)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 17:56:43 +00:00
renovate[bot]
2146ff6f23 Update typescript-eslint monorepo to v8.43.0 (#30782)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 16:03:21 +00:00
renovate[bot]
115e3cd3d8 Update dependency uuid to v13 (#30786)
* Update dependency uuid to v13

* Make jest happier

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Remove redundant @types package

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-09-16 16:49:38 +00:00
Florian Duros
155c0b6a0c Room list: make the filter resize correctly (#30788)
* fix: make the filter resize correctly

* test: update snapshot
2025-09-16 15:55:02 +00:00
Florian Duros
841f12bd46 Avoid flicker of the room list filter on resize (#30787)
* fix: avoid flicker of the room list filter on resize

* test: update existing test to render correctly

* test: add test to avoid flicker regression
2025-09-16 15:52:36 +00:00
renovate[bot]
6f41ac58bc Update dependency @fontsource/inconsolata to v5.2.7 (#30777)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 15:35:17 +00:00
renovate[bot]
902cdb0810 Update Node.js to f8c398a (#30774)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 15:21:38 +00:00
renovate[bot]
4fd752fe29 Update actions/stale action to v10 (#30785)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 14:54:49 +00:00
renovate[bot]
d83a2d2b93 Update actions/setup-node action to v5 (#30784)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 14:54:21 +00:00
renovate[bot]
fb9d5db6ec Update actions/github-script action to v8 (#30783)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 14:54:06 +00:00
renovate[bot]
0ca23af4a9 Update dependency @stylistic/eslint-plugin to v5.3.1 (#30781)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 14:45:40 +00:00
renovate[bot]
5db72f7fab Update dependency @sentry/browser to v10.11.0 (#30780)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 14:43:57 +00:00
renovate[bot]
d193434b30 Update babel monorepo to v7.28.4 (#30775)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 14:43:07 +00:00
renovate[bot]
1bbe4802e4 Update definitelyTyped (#30776)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 14:38:46 +00:00
renovate[bot]
169ae025bf Update dependency @types/react to v19.1.13 (#30778)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 14:38:10 +00:00
renovate[bot]
e666fa33f3 Update nginxinc/nginx-unprivileged:alpine-slim Docker digest to 14b127e (#30773)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 14:37:58 +00:00
Andy Balaam
c9f375a02b Fix #30439: "Forgot recovery key" should go to "reset" (#30771)
* Fix #30439: "Forgot recovery key" should go to "reset"

* Wrap showToast in act() to ensure React render is finished

* Remove duplicated code
2025-09-16 13:29:45 +00:00
Michael Telatynski
81fc054b8b Lint
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-09-16 13:12:43 +01:00
RiotRobot
efbced733a Merge branch 'master' into develop 2025-09-16 11:55:53 +00:00
RiotRobot
87d40ab0e0 v1.11.112 2025-09-16 11:52:32 +00:00
Michael Telatynski
8e9a43d70c Merge commit from fork
* Validate room upgrade relationships properly

Ensures only correctly related rooms are left when leaving the latest version of a room.
Ensures the room list does not wrongly hide rooms which have not yet been upgraded.
Ensures the breadcrumbs store finds the correct latest version of a room for a given stored room.

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-09-16 12:43:49 +01:00
RiotRobot
9a11a80483 Upgrade dependency to matrix-js-sdk@38.2.0 2025-09-16 11:41:51 +00:00
David Baker
ccf0762737 Don't show release announcements while toasts are displayed (#30770)
* Don't show release announcements while toasts are displayed

Otherwise they can clash and look like a mess.

* Dismiss the toast in the e2e test

* Update screenshot
2025-09-16 11:37:10 +00:00
ElementRobot
8d07e797c5 Hold Electron toasts until after the client starts (#30768) (#30769)
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-09-16 12:05:08 +01:00
ElementRobot
4dc087c342 Playwright Docker image updates (#30734)
* [create-pull-request] automated change

* Update screenshot

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-09-16 10:43:31 +00:00
Timo
0783f27f33 Add decline button to call notification toast (use new notification event) (#30729)
* Add decline button to call notification toast (use new notification event)

 - This make EW incompatible with the old style notify events.

Signed-off-by: Timo K <toger5@hotmail.de>

* update styling for call toast

Signed-off-by: Timo K <toger5@hotmail.de>

* skip lobby on join button click / dont skip lobby on toast click

Signed-off-by: Timo K <toger5@hotmail.de>

* dismiss toast on remote decline

Signed-off-by: Timo K <toger5@hotmail.de>

* fixup docstring and event_id

Signed-off-by: Timo K <toger5@hotmail.de>

* Add tests
Signed-off-by: Timo K <toger5@hotmail.de>

* remove unused var

Signed-off-by: Timo K <toger5@hotmail.de>

* test that decline event gets sent

Signed-off-by: Timo K <toger5@hotmail.de>

* make "go to lobby" accessible via keyboard (fix sonar cloud)

Signed-off-by: Timo K <toger5@hotmail.de>

* remove keyboard input

Signed-off-by: Timo K <toger5@hotmail.de>

* fix lint

Signed-off-by: Timo K <toger5@hotmail.de>

* use actual button

Signed-off-by: Timo K <toger5@hotmail.de>

* review style + toggle for join immediately

Signed-off-by: Timo K <toger5@hotmail.de>

* fix `getNotificationEventSendTs`

Signed-off-by: Timo K <toger5@hotmail.de>

* use story component

Signed-off-by: Timo K <toger5@hotmail.de>

* english text

Signed-off-by: Timo K <toger5@hotmail.de>

* dont use legacy toggle

Signed-off-by: Timo K <toger5@hotmail.de>

* fix lint

Signed-off-by: Timo K <toger5@hotmail.de>

* review

Signed-off-by: Timo K <toger5@hotmail.de>

* review (mostly docs)

Signed-off-by: Timo K <toger5@hotmail.de>

---------

Signed-off-by: Timo K <toger5@hotmail.de>
2025-09-16 10:41:44 +00:00
Michael Telatynski
3c13f55b74 Hold Electron toasts until after the client starts (#30768)
* Hold Electron toasts until after the client starts

as otherwise they won't be shown and will be reset out of existence

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update test

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Lint

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-09-16 10:16:12 +00:00
ElementRobot
2e8e6e92cc Add mechanism for Electron to render toasts (#30765) (#30767)
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-09-16 09:41:11 +01:00
David Langley
3432613195 Use the new room list by default (#30640)
* Default to new room list and enforce in config for app and develop

* Update jest tests

* Update LandmarkNavigation and e2e test

* Update viewRoomByName helper

* lint

* Update Add -> New Room flow

Keep legacy viewRoomByName until we delete the olds tests.

* Update e2e test to use Add -> Start Chat

* Update screenshots

* Fix viewRoomByName, can't use option as it contains more that just the room name. Using title which should be exact.

* Fix knocking tests

* fix layout.spec.ts and pstn.spec

* timeline snapshots

* Fix spotlight.spec

* TAC spaces and media preview settings

* Fix more screenshots and mark as unread tests

* Fix leftpanel test

* Bugfix for knocking use case. We should check EffectiveMembership when remove rooms from the new room list, so that knocking is handled

* Fix openCreateRoomDialog to new room list specifics to fix create-knock-room.spec.ts

* lint

* Fix Landmark navigation from left panel search to the next landmark

* lint

* Update window-12px-linux.png

* Update apps-drawer-linux.png

* Update sliding sync e2e tests

* Update some screenshots

* Revert change to the space create screenshot

* Use actual screenshot

as focused elements are different when generated locally

* Fix test selectors

* Morfe test screenshot selector / update

* Add test for landmark navigation

* Replace screenshots

* Fix another test that just got added an hour ago

* Not sure why this was changed, doesn't seem necessary

* Disambiguate selector

* Another screenshot that's now changed in width by 1px

* Revert changes to config files

It's being turned on by default so these are unnecessary

* Convert read.unread assertions to new room list

removing support for checking for activity in assertUnread which was
unused.

* Update room list order tests

that feel a bit like they ought to be in room-list rather than read-receipts but whatever

* Fix room titles in read receipts test

---------

Co-authored-by: David Baker <dbkr@users.noreply.github.com>
2025-09-16 08:13:05 +00:00
Michael Telatynski
eaa20a2c9a Add mechanism for Electron to render toasts (#30765)
* Add mechanism for Electron to render toasts

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-09-15 17:14:37 +00:00
Florian Duros
0747c9f0e8 chore: add storybook a11y plugin (#30763) 2025-09-15 16:10:15 +00:00
Will Hunt
08487aa945 Fix enabling key backup not working if there is an untrusted key backup (#30707)
* Fix enabling key backup not working if there is an untrusted key backup on the server.

* lint

* Add test for trust situations.

* remove conditional

* Update src/components/viewmodels/settings/encryption/KeyStoragePanelViewModel.ts

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

* Update src/components/viewmodels/settings/encryption/KeyStoragePanelViewModel.ts

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

---------

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
2025-09-15 12:18:34 +00:00
Will Hunt
2b5dc7bfd5 Force preload to be false when setting an intent on an Element Call. (#30759)
* force preload

* lnt
2025-09-15 09:54:59 +00:00
Hubert Chathi
9ad239f87f "Verify this device" redesign (#30596)
* add variant of ResetIdentityBody for when the user has no verif. methods

* no longer distinguish between the using having a passphrase or not

* use vertical stack of buttons via EncryptionCard

and update wording

* swap logic order to match rendering order

* use the same dialog when no verification options available

* make it agree with the design more

* allow signing out on initial login

* apply styling changes and remove duplicate elements

* fix and add tests

* add missing snapshot

* Apply suggestions from code review

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

* use a boolean property to disable blurring instead of adding a class

* change string identifiers

* apply changes from review -- simplify logic

* change class name to avoid confusion

---------

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
2025-09-12 18:37:14 +00:00
Will Hunt
1e0cdf7b14 Set Element Call "intents" when starting and answering DM calls. (#30730)
* Start to implement intents for DM calls.

* Refactor and fix intent bugs

* Do not default skipLobby in Element Web

* Remove hacks

* cleanup

* Don't template skipLobby or returnToLobby but inject as required

* Revert "Don't template skipLobby or returnToLobby but inject as required"

This reverts commit 35569f35bb.

* lint

* Fix test

* lint

* Use other intents

* Ensure we test all intents

* lint

* cleanup

* Fix room check

* Update imports

* update test

* Fix RoomViewStore test
2025-09-12 13:00:48 +00:00
Hugh Nimmo-Smith
33d3df24f9 Fix handling of 413 server response when uploading media (#30737) 2025-09-12 10:28:40 +00:00
Will Hunt
21a86a3269 Set call owner (#30750) 2025-09-12 09:37:24 +00:00
David Baker
34450d513a Make landmark navigation work with new room list (#30747)
* Make landmark navigation work with new room list

Split out from https://github.com/element-hq/element-web/pull/30640

* Fix landmark selection to work with either room list

* Add test for landmark navigation

* Add test

* Fix test

* Clear mocks between runs
2025-09-12 09:24:56 +00:00
Florian Duros
b6710d19c0 Prevent voice message from displaying spurious errors (#30736)
* fix: avoid to render `AudioPlayerViewModel` when `MAudioBody` is inherited

* fix: avoid `Playback.prepare` to fail when called twice

* fix: add `decoding` to playback type

* refactor: fix circular deps

* refactor: extract `MockedPlayback` from `AudioPlayerViewModel`

* test: add `MAudioBody` basic test

* test: add tests for `MVoiceMessageBody`

* fix: lint
2025-09-12 08:24:51 +00:00
Florian Duros
7fc0cb242c Align default avatar and fix colors in composer pills (#30739)
* fix: align default avatar in composer pills

* fix: use correct color for avatar in composer pills when there is no image

* test(e2e): add test for cider mention

* chore: fix typo
2025-09-11 13:44:08 +00:00
Michael Telatynski
5edcc4c1c4 Use configured URL for link to desktop app in message search settings (#30742)
* Use configured URL for link to desktop app in message search settings

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update snapshots

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-09-11 13:38:58 +00:00
David Baker
e31042f1b1 Fix history visibility when creating space rooms (#30745)
* Fix history visibility when creating space rooms

This line was here which made history visibility different for space
rooms vs normal rooms, making history world readable for public rooms
and shared from the point of invite (rather than joining) for any other
rooms.

I can't see any reason this makes sense, or why space rooms should
have different history visibility defaults to other rooms. It wasn't
commented. Let's just remove the line and make them consistent.

* Fix import

* Add some tests

to asert that we don't randomly change the options that createRoom
passes to the HS.
2025-09-11 13:26:40 +00:00
Will Hunt
1c1f1435be Check HTML-encoded quotes when handling translations for embedded pages (such as welcome.html) (#30743)
* Check HTML encoding on embedded page

* Add tests

* lint
2025-09-11 10:45:35 +00:00
renovate[bot]
a69ce3f64e Update dependency vite to v7.1.5 [SECURITY] (#30732)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-11 08:59:40 +00:00
Valere Fedronic
cd7f1a0638 Fix local room encryption status always not enabled (#30461)
* Fix local room encryption status always not enabled

* refactor: put back the e2e test after merge

* fix: look at e2eStatus in composer of local room

* doc: add docs to `LocalRoom.isEncryptionEnabled`

* test(e2e): check composer doesn't display unencrypted state

* test: update existing tests

* test(e2e): update existing tests

* refactor: move room encryption check in a dedicated function

* refactor: make `isEncryptionEnabled` cleaner

* test: add tests for `LocalRoom.isEncrypted`

* doc: fix `useIsEncrypted` comment

---------

Co-authored-by: Florian Duros <florian.duros@ormaz.fr>
2025-09-10 15:09:17 +00:00
RiotRobot
8a1fc65beb Reset matrix-js-sdk back to develop branch 2025-09-10 09:28:53 +00:00
RiotRobot
f7d48bb422 Merge branch 'master' into develop 2025-09-10 09:28:38 +00:00
RiotRobot
c7c0e91fdc v1.11.111 2025-09-10 09:24:49 +00:00
David Baker
28ca369a10 Remove 'beta' pill from Element Call option (#30726)
* Remove 'beta' pill from Element Call option

This just removes the 'beta' lebelling: it does not take it out of
labs by default just yet.

* Appease import linter
2025-09-10 08:51:28 +00:00
Richard van der Hoff
dad1bd6834 Fix flaky shields playwright test (#30731)
* Playwright: split `logIntoElement` into two

Split up this helper function, so that rather than being a single function with
an optional argument, it is two separate functions.

* Playwright: fix flaky shields test

Wait for the application to redirect to `/#/home` after completing security, so
that we don't end up racing with it.

Fixes https://github.com/element-hq/element-web/issues/28836
2025-09-09 19:30:48 +00:00
David Langley
dba4ca26e8 Add axe compliance for new room list (#30700)
* Add tests for axe violations for the new room list

* axe doesn't like a ul/li with roles listbox/option. Changing to div/button as we have elsewhere like RoomListitemView.

* Fix RoomListPrimaryFilters test

* Justify the items in the primary filter container

to get the dropdown button on the right again

* Update snapshot

* Make the room list itself focusable

As the comment said, there was no real reason it needed to be, except
that there was because of axe. Probably having the children focusable
would be better, but Virtuoso wraps them in more divs which doesn't
satisfy axe's requirements since those inner divs are not the scrollable
ones. I can't see a better option than this right now.

* Update snapshot

---------

Co-authored-by: David Baker <dbkr@users.noreply.github.com>
2025-09-09 16:54:13 +00:00
RiotRobot
fc06cf1276 Upgrade dependency to matrix-js-sdk@38.1.0 2025-09-09 16:32:22 +00:00
Will Hunt
a73f4f5803 Stop ringing and remove toast if another device answers a RTC call. (#30728)
* Stop ringing if another device answers a call.

* Add test

* fix check
2025-09-09 15:45:42 +00:00
Valere Fedronic
d594ce479c Allow Element Call to recieve/send call decline events (#30694)
* Allow Element Call to recieve/send call decline events

* fix bad copy paste in test
2025-09-09 14:16:19 +00:00
David Baker
733007cb28 Upgrade compound-web for AXE fix (#30724)
* Upgrade compound-web for AXE fix

I give up waiting for renovate to create the PR

* Update snapshots
2025-09-09 10:31:14 +00:00
ElementRobot
f57660ac14 [create-pull-request] automated change (#30723)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-09-09 06:22:05 +00:00
mxandreas
207173db95 Remove outdated recovery setup options from E2EE docs (#30681)
* Deprecate secure_backup_required and secure_backup_setup_methods in docs.

* Wording enhancements.

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

* Use removal, not deprecation for sake of clarity.

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

* Use removal, not deprecation for sake of clarity.

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

* prettier

---------

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
Co-authored-by: Richard van der Hoff <richard@matrix.org>
2025-09-08 18:16:03 +00:00
Richard van der Hoff
d70a3a695e Patch react-sdk-module-api to suppress confusing logs (#30717)
`Default empty createSecretStorageKey() => null` is unhelpful at best, and
indeed all the other logs from this file are redundant. Let's patch them out to
help log analysis.
2025-09-08 17:01:14 +00:00
David Baker
7ccb9355de Mention our dev room in the contributing guide (#30714)
* Mention our dev room in the contributing guide

It was in there, but only in the tests section, relating to how
to write tests. This adds it in the first section.

* Prettier
2025-09-08 15:23:11 +00:00
Will Hunt
6b510a535b Automatically adjust history visibility when making a room private (#30713)
* Refactor StyledRadioButton to provide proper labels.

* Automatically change history settings to members only if room is made private

* Add tests

* lint

* lint further

* Fix clickable buttons

* Revert functional component-ing

* text tweaks

* update snapshots

* Add unit test for history vis changes

* lint

* Update snapshots

* Fix flakes

* lint
2025-09-08 14:54:15 +00:00
Will Hunt
6d05bfc4c5 Add UIFeature to hide public space and room creation (#30708)
* Add settings to hide public room & space creation.

* Add space changes.

* Add room changes.

* lint

* Add playwright tests

* don't specialcase 1 join rule

* Ensure mocks get cleared

* Fixup test

* Add SpaceCreateMenu component unit-tests

* Fixup create room test asserts

* fix import
2025-09-08 13:53:13 +00:00
ElementRobot
9e7f583acc Localazy Download (#30711)
* [create-pull-request] automated change

* Discard changes to src/i18n/strings/en_EN.json

---------

Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-09-08 09:02:55 +00:00
ElementRobot
4f702b70aa Ensure container starts if it is mounted with an empty /modules directory. (#30699) (#30705)
* Set nullglob

* Replace with a if statement (because we're using sh)

* combine if

(cherry picked from commit 1c30bec083)

Co-authored-by: Will Hunt <will@half-shot.uk>
2025-09-05 09:10:59 +00:00
ElementRobot
ef3b9eb9e4 Localazy Download (#30704)
* [create-pull-request] automated change

* Discard changes to src/i18n/strings/en_EN.json

---------

Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-09-05 08:43:45 +00:00
renovate[bot]
1e5e4a04ad Update dependency @vector-im/compound-web to v8.2.3 (#30701)
* Update dependency @vector-im/compound-web to v8.2.3

* Update snapshot, tabIndex is now overridden as intended.

Original commit + comment -> 8086262e04 (diff-1e0f987f11006689ab011e33003e54e364d2089d66fc6c8f613753a2891fb529R118-R120)

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: David Langley <davidl@element.io>
2025-09-05 08:36:21 +00:00
Richard van der Hoff
5534c0dbe9 Remove remaining support for outdated .well-known settings (#30702)
* Remove remaining support for `secure_backup_setup_methods` option

Support for this .well-known setting had been removed everywhere except in a
rather obscure corner of the code. There are many other problems with this area
(https://github.com/element-hq/element-web/issues/29171) but removing support
for the outdated option is an easy step.

* Remove remaining `secure_backup_required` setting support

Again, this setting was only honoured in the obscure "New Recovery Method"
flow.
2025-09-05 08:32:17 +00:00
Will Hunt
1c30bec083 Ensure container starts if it is mounted with an empty /modules directory. (#30699)
* Set nullglob

* Replace with a if statement (because we're using sh)

* combine if
2025-09-04 16:11:50 +00:00
renovate[bot]
1386bc9f5c Update dependency @vector-im/compound-web to v8.2.2 (#30685)
* Update dependency @vector-im/compound-web to v8.2.2

* Update snapshot

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: David Langley <davidl@element.io>
Co-authored-by: David Langley <langley.dave@gmail.com>
2025-09-04 15:55:36 +00:00
David Langley
48c3d91383 We no longer have the release announcement for the TAC, remove the jest tests. (#30698)
* We no longer have the release announcement for the TAC, remove the jest tests.

* lint

* Trigger checks to fix cla check
2025-09-04 15:16:41 +00:00
Florian Duros
9aa617df1b fix: make url in topic in room intro clickable (#30686)
* fix: make url in topic in room intro clickable

* chore: remove extra line

* refactor: use tag instead variable

* test: add topic tests

* fix: update i18n key
2025-09-04 12:28:53 +00:00
Will Hunt
c17d71a90b Block change recovery key button while a change is ongoing. (#30664)
* Block change recovery key button while a change is ongoing.

* Add disable check

* lint

* Ensure we test that spamming the button doesn't break it.

* Mock out modals

* lint

* add two more clicks

* lint

---------

Co-authored-by: Andy Balaam <andy.balaam@matrix.org>
2025-09-04 09:15:08 +00:00
Will Hunt
07c253d11f Add Playwright tests for settings toggles (#30318)
* Add playwright tests

* import pages/ remove duplicate create-room

* Update screenshots

* Fix accessibility for devtools

* Disable region test

* Fixup headers

* remove extra test

* Fix permissions dialog

* fixup tests

* update snapshot

* Update jest tests

* Clear up playwright tests

* update widget screenshot

* Fix wrong snaps from using wrong compound version

* Revert mistaken s/checkbox/switch/
2025-09-04 07:12:24 +00:00
David Baker
cba341f824 Release announcement for new room list (#30675)
* Release announcement for new room list

* Update snapshots

* Update release announcement tests

* worryingly large snapshot update

* Remove the pinned message release anncounement

* Hopefully fix e2e tests

add missing e2e screenshot and remove one for removed test

* Remove unused i18n strings

* Fix screenshot

* Try straight on the quick settings button

* unused import

* update snapshots

* Fix settings location
2025-09-03 15:25:49 +00:00
Florian Duros
09fe9281a5 Hide advanced settings during room creation when UIFeature.advancedSettings=false (#30684)
* fix: hide advanced settings during room creation when UI.advancedSettings=false

* test: add tests
2025-09-03 15:23:40 +00:00
Tulir Asokan
80375db934 Fix m.topic tests (#30663)
For https://github.com/matrix-org/matrix-js-sdk/pull/4984

Signed-off-by: Tulir Asokan <tulir@maunium.net>
2025-09-03 13:55:54 +00:00
ElementRobot
ea4ccda928 Localazy Download (#30653)
* [create-pull-request] automated change

* Update tests

Hold back one source translation due to inconsistency with related keys

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update screenshot

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-09-03 10:30:36 +00:00
renovate[bot]
69d5acb2f3 Update all non-major dependencies (#30668)
* Update all non-major dependencies

* Make knip happy

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Make parseUserAgent happy

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-09-03 09:56:55 +00:00
renovate[bot]
34c2ccebba Update typescript-eslint monorepo to v8.41.0 (#30673)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-03 09:41:35 +00:00
ElementRobot
5deb5097b5 [create-pull-request] automated change (#30679)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-09-03 09:16:00 +00:00
renovate[bot]
eeb14d3b7f Update dependency @testing-library/jest-dom to v6.8.0 (#30670)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-03 09:07:44 +00:00
renovate[bot]
6e88b46f02 Update react monorepo (#30667)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-03 09:07:25 +00:00
renovate[bot]
a50f51257b Update nginxinc/nginx-unprivileged:alpine-slim Docker digest to 0d019e9 (#30665)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-02 14:04:08 +00:00
renovate[bot]
6f71769466 Update Node.js to f7f28d1 (#30666)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-02 14:03:25 +00:00
renovate[bot]
08b9f3685d Update dependency @sentry/browser to v10.8.0 (#30669)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-02 14:02:45 +00:00
renovate[bot]
4a184e3346 Update dependency @types/sdp-transform to v2.15.0 (#30671)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-02 14:02:28 +00:00
renovate[bot]
de265e1ef6 Update actions/upload-pages-artifact action to v4 (#30674)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-02 14:01:57 +00:00
Florian Duros
eb086bd795 A11y: improve accessibility of pinned messages (#30558)
* fix: improve aria role and label on pinned message banner

* fix: change pinned message badge background for contrast

* fix: link pinned message button to content

* test: update tests

* fix: add aria-describedby on pinned message badge

* feat: use `aria-describedby` instead of `aria-description`

* test: update room view snapshot

* test: update snapshot

* fix: put id only textual body upper div

* fix: use lodash uniqueId

* test: update snapshots
2025-09-02 13:03:01 +00:00
RiotRobot
9f15532d12 v1.11.111-rc.0 2025-09-02 13:01:52 +00:00
RiotRobot
71cf19f4b2 Upgrade dependency to matrix-js-sdk@38.1.0-rc.0 2025-09-02 12:55:51 +00:00
Will Hunt
1925132a3c Do not hide media from your own user by default (#29797)
* Always show media from your own user

* Update usages of useMediaVisible

* lint

* Add a test for HideActionButton

* Improve docs

* Document the event

* fixup test

* Allow users to hide their own media if they wish.

* Update tests

* remove a check\

* tweak

* tweak
2025-09-02 12:21:12 +00:00
Michael Telatynski
8fa3d7e4b7 Fix room joining over federation not specifying vias or using aliases (#30641)
* Fix room joining over federation not specifying vias or using aliases

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Be consistent with viaServers

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Simplify modules

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Only consider canAskToJoin on 403 as per spec

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Remove unused helper which I only just added =(

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add test

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-09-02 11:10:10 +00:00
David Baker
1b4a979b6c Fix stable-suffixed MSC4133 support (#30649)
It looked for the ".stable" suffixed feature to work out what URL
to use but not to see whether the server supported it.
2025-09-02 08:41:49 +00:00
R Midhun Suresh
d287ac07a3 Add links to relevant docs in template (#30656) 2025-09-01 16:51:09 +00:00
Bojidar Marinov
8903927e0c Remember whether sidebar is shown for calls when switching rooms (#30262)
* Remember whether sidebar is shown for calls when switching rooms

Stores the sidebar state per-room in LegacyCallHandler, along with other details about calls.

* Hide the Show/Hide Sidebar from the Picture-in-Picture preview

The toggle sidebar button currently does nothing in PIP mode, since PIP mode never shows a sidebar (even when the call is made fullscreen from the PIP preview)

* Add test for Show/Hide Sidebar feature

* Add more tests for LegacyCallView and LegacyCallViewForRoom

Also, fix issue where LegacyCallViewForRoom used roomId and not callId for checking for sidebar state
2025-09-01 14:33:33 +00:00
Will Hunt
4d48d1b2f2 Open the proper integration settings on integrations disabled error (#30538)
* Open the proper integration settings on integrations disabled error.

* Convert to functional component.

* Add test

* update snap
2025-09-01 07:30:49 +00:00
David Baker
f75d41054f Fix i18n of message when a setting is disabled (#30646)
The function was supposed to return an i18ned string but lacked a _t
2025-08-29 09:18:37 +00:00
Richard van der Hoff
701019052c MatrixChat: only start session load once (#30642)
* MatrixChat: only start session load once

When running in development mode, `MatrixChat.componentDidMount` is run twice,
meaning it checks the session lock twice. Sometimes, this means it shows the
"Element is running in another window" page.

The real problem is of course that we're running all this application logic
inside the `MatrixChat` component, but as a quick workaround, we can just
remember that we've started the session load code, and bail out on the second pass.

* Address review comments
2025-08-28 16:09:54 +00:00
renovate[bot]
cf692e751b Update vector-im (compound-web to 8.2.1, design tokens is already at 6.0.0 on develop) (#30373)
* Update vector-im

* Update snapshots

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Make BaseDialog's div keyboard focusable and fix test.

* Update more e2e tests to use switch instead of checkbox

* fix useParticipants incorrectly returning an array when a map is expected

* Try again to fix calParticipants

* try fix RoomHeader tests again by also mocking useParticipants

* Revert "try fix RoomHeader tests again by also mocking useParticipants"

This reverts commit f83093cc44.

* Try with no dependencies

* try mocking useCall rather than just useParticipantCount

* Mock the call store rather than the hook

* Only mock the call object for tests that expect it.

* Revert "Only mock the call object for tests that expect it."

This reverts commit 043d812b1d.

* Revert "Mock the call store rather than the hook"

This reverts commit 644be3155c.

* Revert "try mocking useCall rather than just useParticipantCount"

This reverts commit 92034aaff9.

* Revert "Try with no dependencies"

This reverts commit fb502a68a0.

* Reapply "try fix RoomHeader tests again by also mocking useParticipants"

This reverts commit e456782efd.

* Revert "try fix RoomHeader tests again by also mocking useParticipants"

This reverts commit f83093cc44.

* Revert "Try again to fix calParticipants"

This reverts commit c45ad3063f.

* Revert "fix useParticipants incorrectly returning an array when a map is expected"

This reverts commit e06d76e3f7.

* bump compound-web

* Update snapshots

* Fix bad merge, we don't need the second call to escape. The comment a couple of lines up explains things.

* Trigger build to fix licence/cla check

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: David Langley <davidl@element.io>
2025-08-28 12:24:08 +00:00
David Langley
1a005ad5d2 Mock CallStore.getCall rather than individual hooks like useParticipantCount (#30636) 2025-08-28 08:43:00 +00:00
ElementRobot
42f7bc1d0d [create-pull-request] automated change (#30637)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-08-28 06:18:02 +00:00
David Langley
b7f89db43c ListView should not handle the arrow keys if there is a modifier applied (#30633)
* ListView should not handle the arrow keys if there is a modifier applied.

* lint

* Reduce nesting
2025-08-27 15:48:33 +00:00
RiotRobot
98a04e1812 Reset matrix-js-sdk back to develop branch 2025-08-27 13:49:38 +00:00
RiotRobot
42d726a4ff Merge branch 'master' into develop 2025-08-27 13:49:23 +00:00
RiotRobot
b6f5843028 v1.11.110 2025-08-27 13:45:54 +00:00
RiotRobot
81d054bb99 Upgrade dependency to matrix-js-sdk@38.0.0 2025-08-27 13:34:31 +00:00
David Langley
a1f56ebbf2 Deflake test (#30634) 2025-08-27 13:17:12 +00:00
Richard van der Hoff
a003ebcb35 Fix yarn lint:types when matrix-js-sdk is not yarn linked. (#30612)
* Add missing dependencies on `@types` packages

Because we import the typescript source from matrix-js-sdk rather than the
`.d.ts` files, `tsc` ends up type-checking the js-sdk source. That means that
we need to have the `@types` packages that js-sdk needs.

* Add missing type definitions for `setInterval` and `setTimeout`

Our source assumes that `setTimeout` returns a number, not a
`Timeout`. If we `yarn link` js-sdk, then (somehow) we end up using the
definitions from there, but it's not really correct.

* Configure knip to ignore new deps
2025-08-27 13:13:00 +00:00
David Langley
87b4918d34 Make BaseDialog's div keyboard focusable and fix test. (#30631)
* Make BaseDialog's div keyboard focusable and fix test.

* Less weird test

* Update snapshots

* More snapshots
2025-08-27 12:41:39 +00:00
ElementRobot
c6f47cfd8e [create-pull-request] automated change (#30629)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-08-27 10:36:39 +00:00
AlirezaMrtz
a112dfe1db Fix: Allow triple-click text selection to flow around pills (#30349)
* Fix: Allow triple-click text selection to flow around keyword pills

* Fix: Remove unnecessary align-items property from Pill component

* Change display property of .mx_Pill from inline to inline-block to fix rendering issue in Playwright tests

* Add test for triple-click message selection with pills
2025-08-27 08:06:01 +00:00
Robin
4b4cb896eb Watch for a 'join' action to know when the call is connected (#29492)
Previously we were watching for changes to the room state to know when you become connected to a call. However, the room state might not change if you had a stuck membership event prior to re-joining the call. It's going to be more reliable to watch for the 'join' action that Element Call sends, and use that to track the connection state.
2025-08-27 09:04:36 +01:00
ElementRobot
6a1c0502aa [create-pull-request] automated change (#30628)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-08-27 06:24:04 +00:00
Florian Duros
ea5e525133 Fix: add missing tooltip and aria-label to lock icon next to composer (#30623)
* fix: add missing tooltip and aria-label to lock icon next to composer

* test: update snapshot
2025-08-26 15:29:41 +00:00
David Langley
14d16364db Don't render context menu when scrolling (#30613)
* Don't render context menu when scrolling

* Add test to check context menu is not rendered when scrolling

* Add comment.
2025-08-26 11:12:34 +00:00
ElementRobot
67e0ecc454 [create-pull-request] automated change (#30619)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-08-26 06:19:50 +00:00
R Midhun Suresh
427cddb8e5 MVVM - Introduce the concept of disposables to track event listeners, sub vms and so on (#30475)
* Introduce disposables to track sub vms and event listeners

* Remove old code

* Use disposable in BaseViewModel

* Update vm so that the listener is tracked through disposable

* No-op on dispose call instead of throwing error

* Throw error in trackListener as well

* Fix audio player vm

* Expose isDisposed through base vm

* Dispose AudioPlayerViewModel
2025-08-25 09:19:24 +00:00
Bas Nijholt
df9dfaf16f Fix minor type setting issue in README.md (missing space) (#30565) 2025-08-25 08:55:32 +00:00
ElementRobot
9b5410bad5 [create-pull-request] automated change (#30615)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-08-25 06:28:57 +00:00
ElementRobot
afab82068d [create-pull-request] automated change (#30614)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-08-24 06:19:14 +00:00
Richard van der Hoff
e8c88918cb Show a "progress" dialog while invites are being sent (#30561)
* InviteDialog: show some words and a spinner while invites are being sent

* MultiInviter-test: avoid building unhandled rejected promises

If we don't handle rejected promises, jest gets confused by them. Instead,
let's create them on-demand.

* Open a "progress" dialog while invites are being sent

* Inhibit invite progress dialog when RoomUpgradeWarning dialog is kept open

... otherwise the `RoomUpgradeWarning` dialog disappears during the invites,
and the tests that assert that it is showing the correct thing fail.
 enter the commit message for your changes. Lines starting

* Switch to compound CSS variables instead of old pcss vars

* update playwright screenshots

* Revert "update playwright screenshots"

This reverts commit b0a15d97f3.

* Another go at updating screenshots

* Address review comments

* remove redundant Props
2025-08-22 15:10:42 +00:00
David Langley
c842b615db Move the room list to the new ListView(backed by react-virtuoso) (#30515)
* Move Room List to ListView

- Also remove Space/Enter handing from keyboard navigation we can just leave the default behaviour of those keys and handle via onClick

* Update rooms when the primary filter changes

Otherwise when changing spaces, the filter does not reset until the next update to the RVS is made.

* Fix stickyRow/scrollIntoView when switiching space or changing filters

- Also remove the rest of space/enter keyboard handling use

* Remove the rest of space/enter keyboard handling use

* Remove useCombinedRef and add @radix-ui/react-compose-refs as we already depend on it

- Also remove eact-virtualized dep

* Update RoomList unit test

* Update snapshots and unit tests

* Fix e2e tests

* Remove react-virtualized from tests

* Fix e2e flake

* Update more screenshots

* Fix e2e test case where were should scroll to the top when the active room is no longer in the list

* Move from gitpkg to package-patch

* Update to latest react virtuoso release/api.

Also pass spaceId to the room list and scroll the activeIndex into view when spaceId or primaryFilter change.

* Use listbox/option roles to improve ScreenReader experience

* Change onKeyDown e.stopPropogation to cover context menu

* lint

* Remove unneeded exposure of the listView ref

Also move scrollIntoViewOnChange to useCallback

* Update unit test and snapshot

* Fix e2e tests and update screenshots

* Fix unit test and snapshot

* Update more unit tests

* Fix keyboard shortcuts and e2e test

* Fix another e2e and unit test

* lint

* Improve the naming for RoomResult and the documentation on it's fields meaning.

Also update the login in RoomList to check for any change in filters, this is a bit more future proof for when we introduce multi select than using activePrimaryFilter.

* Put back and fix landmark tests

* Fix test import

* Add comment regarding context object getting rendered.

* onKeyDown should be optional

* Use SpaceKey type on RoomResult

* lint
2025-08-21 14:43:40 +00:00
RiotRobot
aab1fae299 Upgrade dependency to matrix-js-sdk@38.0.0-rc.1 2025-08-21 14:18:45 +00:00
ElementRobot
ef3a6a9429 [create-pull-request] automated change (#30604)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-08-21 09:16:38 +00:00
Michael Telatynski
5c8c39424a Update CODEOWNERS to allow playwright docker image updates 2025-08-21 10:16:14 +01:00
David Langley
4735412c91 Remove onSelectItem and space/enter handing from ListView (#30601)
* Remove onSelectItem and space/enter handing from ListView(And therefore memberlist).)

* remove unused imports

* fix unit test
2025-08-20 16:09:44 +00:00
Quentin Gliech
4b6e5d380e tests: use stable MAS integration in Synapse (#30473)
* tests: use stable MAS integration in Synapse

* Automatically follow MAS main branch

* Update the pinned Synapse container image to latest develop

* Update element-web-playwright-common to 1.4.5

* Fix the typing of the MAS config

* Update playwright-common to 1.4.6

* Use the modern MAS -> Synapse API

* Relax MAS rate limiting

* Revert using the modern API explicitly, it is now the default

* Better adjust the MAS rate limits
2025-08-20 08:45:49 +00:00
renovate[bot]
1f825f11de Update all non-major dependencies (#30591)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-20 08:21:41 +00:00
ElementRobot
646162db4e [create-pull-request] automated change (#30600)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-08-20 06:25:50 +00:00
ElementRobot
2c6f349ce7 [create-pull-request] automated change (#30599)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-08-20 06:18:45 +00:00
renovate[bot]
fffe7a31be Update definitelyTyped (#30585)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 17:38:12 +00:00
renovate[bot]
002e4f6655 Update nginxinc/nginx-unprivileged:alpine-slim Docker digest to ea6c4b8 (#30582)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 17:24:30 +00:00
renovate[bot]
260042b388 Update actions/cache digest to 0400d5f (#30581)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 15:33:30 +00:00
renovate[bot]
6e78d739ac Update actions/download-artifact action to v5 (#30595)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 15:26:09 +00:00
renovate[bot]
78bf5644a0 Update actions/checkout action to v5 (#30594)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 15:26:01 +00:00
renovate[bot]
96cc35a68c Update dependency @testing-library/jest-dom to v6.7.0 (#30593)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 15:25:38 +00:00
renovate[bot]
0f93481266 Update dependency @sentry/browser to v10.5.0 (#30592)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 15:25:03 +00:00
renovate[bot]
433eb23d88 Update typescript-eslint monorepo to v8.39.1 (#30590)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 15:24:40 +00:00
renovate[bot]
3df8293085 Update dependency testcontainers to v11.5.1 (#30589)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 15:24:34 +00:00
renovate[bot]
76674e43b3 Update dependency @types/react to v19.1.10 (#30588)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 15:24:18 +00:00
renovate[bot]
0bc6fa9f6e Update dependency @stylistic/eslint-plugin to v5.2.3 (#30587)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 15:24:06 +00:00
renovate[bot]
fd6e8054a7 Update babel monorepo to v7.28.3 (#30584)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 15:23:47 +00:00
renovate[bot]
de4f72fac0 Update Node.js to 9e34ba5 (#30583)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 15:23:41 +00:00
RiotRobot
f5d6f8f639 v1.11.110-rc.0 2025-08-19 13:20:54 +00:00
RiotRobot
cc20136170 Upgrade dependency to matrix-js-sdk@38.0.0-rc.0 2025-08-19 13:10:27 +00:00
ElementRobot
29f6cc03bd [create-pull-request] automated change (#30577)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-08-19 06:18:18 +00:00
ElementRobot
8f91f8fac5 [create-pull-request] automated change (#30569)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-08-18 06:26:15 +00:00
ElementRobot
8d3ea2b71b [create-pull-request] automated change (#30572)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-08-16 06:17:44 +00:00
fkwp
aa5bdab3ba Element Call widget driver: allow state keys to have a _m.call suffix (#30566)
* Extended string-packing for state keys allowing additonially the `_m.call` suffix

* add comment about unstable prefix
2025-08-15 19:19:28 +00:00
Richard van der Hoff
08ec6166c7 Add some comments in _font-sizes.pcss (#30563)
... to let people know that these things shouldn't be used any more.
2025-08-14 15:28:48 +00:00
Andy Balaam
362c7d2aac Hide recovery key when re-entering it while creating or changing it (#30499)
* Hide recovery key when asked to re-enter it when creating or changing key

* Use align-self to centre the eye icon

Co-authored-by: R Midhun Suresh <hi@midhun.dev>

* Use CSS vars for padding

Co-authored-by: R Midhun Suresh <hi@midhun.dev>

* Use CSS classes to avoid needing the highly specific rule

* Use a Compound variable for border width

* Add classes to snapshots

* Update screenshots

---------

Co-authored-by: R Midhun Suresh <hi@midhun.dev>
2025-08-14 15:02:10 +00:00
Florian Duros
0c498a66b1 A11y: move focus to right panel when opened (#30553)
* fix: move focus to right panel when opened

* test: update snapshot

* test(e2e): update screenshot
2025-08-14 08:59:42 +00:00
ElementRobot
64dfbc5aa5 [create-pull-request] automated change (#30562)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-08-14 06:21:26 +00:00
ElementRobot
12dbe719d7 Localazy Download (#30557)
* [create-pull-request] automated change

* Update static_analysis.yaml

---------

Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-08-13 08:19:46 +00:00
ElementRobot
ee6ce8ac1d [create-pull-request] automated change (#30556)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-08-13 08:00:47 +00:00
Florian Duros
31506ef864 Fix e2e warning icon should be white (#30539)
* fix: e2e warning icon should be white

* tests: add e2e warning screenshot test
2025-08-12 18:26:56 +00:00
Richard van der Hoff
713f524948 Update MultiInviter to take an options object (#30541)
* Move `inviteUsersToRoom` to `RoomUpgrade`

This method is only used in one place, uses only public methods, and is
undocumented. Let's move it to the place where it is used, to simplify the API
for `RoomInvite`.

* Simplify `inviteUsersToRoom`

`inviteMultipleToRoom` basically never throws, so this code was effectively
unreachable.

* Update MultiInviter to take an options object

I'm going to add another option, so an options object is going to be more
flexible.

* Jump through the coverage hoop with another test
2025-08-12 17:41:58 +00:00
Michael Telatynski
e880a866ed Stop using deprecated js-sdk fields (#30552)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-08-12 13:19:50 +00:00
Timo
789dba7b3d Remove NoOneHere disabled reason. (#30524)
* Remove NoOneHere disabled reason.
This was used to prohibit starting calls if the user is alone in the room.
Since there are currently issues with the user count calculation this can disable the button even when not appropriate.

On top of that, there is a reason to start a call if the room was just created and the user is still waiting for the others to join the room to then join the call.

Signed-off-by: Timo K <toger5@hotmail.de>

* some ci fixes

Signed-off-by: Timo K <toger5@hotmail.de>

* fix test snapshots

Signed-off-by: Timo K <toger5@hotmail.de>

* fix test to expect enabled call buttons

* Update snapshot for unit-tests/components/views/rooms/RoomHeader/RoomHeader-test.tsx

---------

Signed-off-by: Timo K <toger5@hotmail.de>
Co-authored-by: fkwp <github-fkwp@w4ve.de>
2025-08-12 10:07:59 +00:00
Florian Duros
76be5ccc9e test(e2e): fix share-by-url flakiness (#30550) 2025-08-12 09:26:44 +00:00
ElementRobot
3b675b83f1 [create-pull-request] automated change (#30546)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-08-12 06:18:44 +00:00
Richard van der Hoff
8bd98aa3fd Move test files into test/unit-tests (#30542)
I think these tests got misfiled. All the other jest tests are under `test/unit-tests`.
2025-08-11 20:55:06 +00:00
Richard van der Hoff
b897006899 Refactor InviteDialog (#30540)
* Remove unreferenced CSS class `mx_InviteDialog_hasFooter`

This is never used in the CSS (or elsewhere), so let's remove it

* Move `consultConnectSection` initialization

Since this only used and set when `kind === InviteKind.CallTransfer`, we can
simplify

* Factor out `title` logic

Move the logic for caclulating the title to a separate method. I want to be
able to reference it from a couple of places, so it will be easier if it is a
separate method.

(We'll actually be inlining it again later in this PR)

* Factor out `renderMainTab` method

Break the big `render` method in half by pulling the `usersSection` out into a
separate method.

* Split out `renderRegularDialog` and `renderCallTransferDialog`

`render` is now almost entirely two separate flows, so let's spit it into two
separate methods. Recommend reviewing this commit with whitespace changes
hidden.

* Inline `getTitle`

This method has served its purpose: we can now inline it again.

* Factor out `renderSuggestions`

Break up `renderMainTab` a bit more: pull out a new method which renders the
"suggestions" bit of the dialog, together with the associated warnings and footer.
2025-08-11 16:49:52 +00:00
Michael Telatynski
01c4ba8893 Fix call permissions check confusion around element call (#30521)
It would previously say no permission if you had no perms for an EC call but had perms for a legacy call.

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-08-11 15:20:35 +00:00
Michael Telatynski
001ed616f6 Fix downloading files with authenticated media API (#30520)
* Fix downloading files with authenticated media API

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update snapshot

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix test

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-08-11 15:20:21 +00:00
Michael Telatynski
2395cb1402 Add ?no_universal_links=true to OIDC url so EX doesn't try to handle it (#29439)
* Add `?no_universal_links=true` to OIDC cb url so EX doesn't try to handle it

This is specific to macOS and only affects cases where auth is attempted in the non-default browser

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Strip no_universal_links after auth

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update MAS

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update playwright-common

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Bump @element-hq/element-web-playwright-common

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-08-11 13:26:11 +00:00
RiotRobot
7951e48291 Reset matrix-js-sdk back to develop branch 2025-08-11 11:30:55 +00:00
RiotRobot
664f79306a Merge branch 'master' into develop 2025-08-11 11:30:39 +00:00
RiotRobot
29e895095f v1.11.109 2025-08-11 11:26:35 +00:00
RiotRobot
0d3a81ee8f Upgrade dependency to matrix-js-sdk@37.13.0 2025-08-11 11:16:00 +00:00
renovate[bot]
26e24624d9 Update all non-major dependencies (#30488)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 10:55:36 +00:00
ElementRobot
e94d690587 Use userId to filter users in non-federated rooms when showing the InviteDialog (#30364) (#30537)
* Use userId to filter users in non-federated rooms.

* a line

* another line

* Add getDomain to Jest test

(cherry picked from commit 700068a558)

Co-authored-by: Will Hunt <will@half-shot.uk>
2025-08-11 10:50:40 +00:00
ElementRobot
4abdb74673 Catch error when encountering invalid m.room.pinned_events event (#30534) (#30536)
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-08-11 11:22:34 +01:00
David Langley
59531ea512 Show a blue lock for unencrypted rooms and hide the grey shield for encrypted rooms (#30440)
* Show a blue lock instead of a grey shield for unencrypted rooms

* Update screenshots and snapshot

* Update snapshots and fix e2e test that used to expect the grey shield

* lint and add tests for shield

* Update more screen shots

* finish unit test for left icon

* Remove unneeded check

* Don't bother adding stray props to E2EIcon for data-testid

* Upate snapshots
2025-08-11 09:35:04 +00:00
Michael Telatynski
4da27eb199 Catch error when encountering invalid m.room.pinned_events event (#30534) 2025-08-11 10:42:46 +01:00
Michael Telatynski
d2e4631a14 Fix line wrap around emoji verification (#30523)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-08-11 09:03:34 +00:00
Michael Telatynski
6ff71480d8 Don't highlight redacted events (#30519)
* Don't highlight redacted events

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add test

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-08-11 08:22:15 +00:00
Will Hunt
700068a558 Use userId to filter users in non-federated rooms when showing the InviteDialog (#30364)
* Use userId to filter users in non-federated rooms.

* a line

* another line

* Add getDomain to Jest test
2025-08-11 08:22:10 +00:00
Michael Telatynski
d5a9b3f4c0 Add support for Module API 1.4 (#30185)
* Add support for Module API 1.3.0

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add missing import

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix import

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Bump module API

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update module API and remove jest stub

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix test mocks

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Improve coverage

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* types

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Coverage

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Coverage

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-08-11 08:15:45 +00:00
ElementRobot
bbb179b6d3 [create-pull-request] automated change (#30532)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-08-11 06:29:24 +00:00
ElementRobot
93095f99db Allow /upgraderoom command without developer mode enabled (#30527) (#30529)
* Allow /upgraderoom command without developer mode enabled

This will make the instructions for upgrading rooms for hydra a lot
more straightforward, so maybe let's do this at least while hydra
upgrades happen.

* Update test to match

* Unused imports

(cherry picked from commit 4d3fde192d)

Co-authored-by: David Baker <dbkr@users.noreply.github.com>
2025-08-08 16:16:38 +00:00
David Baker
4d3fde192d Allow /upgraderoom command without developer mode enabled (#30527)
* Allow /upgraderoom command without developer mode enabled

This will make the instructions for upgrading rooms for hydra a lot
more straightforward, so maybe let's do this at least while hydra
upgrades happen.

* Update test to match

* Unused imports
2025-08-08 15:36:41 +00:00
Michael Telatynski
bcf755d45f Fix matrix.to links not being handled in the app (#30522)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-08-08 15:11:34 +00:00
ElementRobot
adfa43dcbb Support for creator/owner power level (#30525) (#30526)
* Support for creator/owner power level

This just shows them as 'Owner' in the list.

* Add test for owner level

(cherry picked from commit 96dbddcb14)

Co-authored-by: David Baker <dbkr@users.noreply.github.com>
2025-08-08 14:28:52 +00:00
David Baker
96dbddcb14 Support for creator/owner power level (#30525)
* Support for creator/owner power level

This just shows them as 'Owner' in the list.

* Add test for owner level
2025-08-08 13:20:02 +00:00
Fabian Kammel
227c8ff1cd pin github actions by hash (#30501)
Signed-off-by: Fabian Kammel <fabian@kammel.dev>
2025-08-08 08:54:40 +00:00
ElementRobot
619e11a749 [create-pull-request] automated change (#30518)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-08-08 06:23:27 +00:00
RiotRobot
9590e59fd2 v1.11.109-rc.0 2025-08-05 13:09:17 +00:00
RiotRobot
745c12f10d Upgrade dependency to matrix-js-sdk@37.13.0-rc.0 2025-08-05 12:53:23 +00:00
609 changed files with 12113 additions and 7600 deletions

8
.github/CODEOWNERS vendored
View File

@@ -17,9 +17,15 @@
/playwright/e2e/crypto/ @element-hq/element-crypto-web-reviewers
/playwright/e2e/settings/encryption-user-tab/ @element-hq/element-crypto-web-reviewers
/src/models/Call.ts @element-hq/element-call-reviewers
/src/call-types.ts @element-hq/element-call-reviewers
/src/components/views/voip @element-hq/element-call-reviewers
# 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
# Ignore the synapse & mas plugins as this is updated by GHA for docker image updating
/playwright/testcontainers/synapse.ts
/playwright/testcontainers/mas.ts

View File

@@ -2,6 +2,7 @@
## Checklist
- [ ] I have read through [review guidelines](../docs/review.md) and [CONTRIBUTING.md](../CONTRIBUTING.md).
- [ ] Tests written for new code (and old code if feasible).
- [ ] New or updated `public`/`exported` symbols have accurate [TSDoc](https://tsdoc.org/) documentation.
- [ ] Linter and other CI checks pass.

View File

@@ -43,9 +43,9 @@ jobs:
run:
shell: bash
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with:
# Disable cache on Windows as it is slower than not caching
# https://github.com/actions/setup-node/issues/975

View File

@@ -14,7 +14,7 @@ jobs:
R2_URL: ${{ vars.CF_R2_S3_API }}
VERSION: ${{ github.ref_name }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- name: Download package
run: |

View File

@@ -26,9 +26,9 @@ jobs:
R2_URL: ${{ vars.CF_R2_S3_API }}
R2_PUBLIC_URL: "https://element-web-develop.element.io"
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with:
cache: "yarn"
node-version: "lts/*"

View File

@@ -34,7 +34,7 @@ jobs:
env:
SITE: ${{ inputs.site || 'staging.element.io' }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- name: Load GPG key
run: |

View File

@@ -20,7 +20,7 @@ jobs:
env:
TEST_TAG: vectorim/element-web:test
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
fetch-depth: 0 # needed for docker-package to be able to calculate the version

View File

@@ -17,23 +17,23 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Fetch element-desktop
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
repository: element-hq/element-desktop
path: element-desktop
- name: Fetch element-web
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
path: element-web
- name: Fetch matrix-js-sdk
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
repository: matrix-org/matrix-js-sdk
path: matrix-js-sdk
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with:
cache: "yarn"
cache-dependency-path: element-web/yarn.lock
@@ -88,7 +88,7 @@ jobs:
run: mdbook build
- name: Upload artifact
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4
with:
path: ./book

View File

@@ -25,7 +25,7 @@ jobs:
actions: read
steps:
- name: Download HTML report
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}

View File

@@ -50,11 +50,11 @@ jobs:
runners-matrix: ${{ steps.runner-vars.outputs.matrix }}
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
repository: element-hq/element-web
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with:
cache: "yarn"
node-version: "lts/*"
@@ -89,7 +89,7 @@ jobs:
- name: Calculate runner variables
id: runner-vars
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
const numRunners = parseInt(process.env.NUM_RUNNERS, 10);
@@ -129,18 +129,18 @@ jobs:
- runAllTests: false
project: Pinecone
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
persist-credentials: false
repository: element-hq/element-web
- name: 📥 Download artifact
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
with:
name: webapp
path: webapp
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with:
cache: "yarn"
cache-dependency-path: yarn.lock
@@ -154,7 +154,7 @@ jobs:
run: echo "version=$(yarn list --pattern @playwright/test --depth=0 --json --non-interactive --no-progress | jq -r '.data.trees[].name')" >> $GITHUB_OUTPUT
- name: Cache playwright binaries
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4
id: playwright-cache
with:
path: ~/.cache/ms-playwright
@@ -201,13 +201,13 @@ jobs:
if: always()
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
if: inputs.skip != true
with:
persist-credentials: false
repository: element-hq/element-web
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
if: inputs.skip != true
with:
cache: "yarn"
@@ -219,7 +219,7 @@ jobs:
- name: Download blob reports from GitHub Actions Artifacts
if: inputs.skip != true
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
with:
pattern: all-blob-reports-*
path: all-blob-reports

View File

@@ -10,7 +10,7 @@ jobs:
name: Tidy closed issues
runs-on: ubuntu-24.04
steps:
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
id: main
with:
# PAT needed as the GITHUB_TOKEN won't be able to see cross-references from other orgs (matrix-org)
@@ -142,7 +142,7 @@ jobs:
});
}
}
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
name: Close duplicate as Not Planned
if: steps.main.outputs.closeAsNotPlanned
with:

View File

@@ -28,7 +28,7 @@ jobs:
Exercise caution. Use test accounts.
- name: 📥 Download artifact
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}

View File

@@ -16,7 +16,7 @@ jobs:
URL: "https://github.com/pulls?q=is%3Apr+is%3Aopen+repo%3Amatrix-org%2Fmatrix-js-sdk+repo%3Amatrix-org%2Fmatrix-react-sdk+repo%3Aelement-hq%2Felement-web+repo%3Aelement-hq%2Felement-desktop+review-requested%3A%40me+sort%3Aupdated-desc+"
RELEASE_BLOCKERS_URL: "https://github.com/pulls?q=is%3Aopen+repo%3Amatrix-org%2Fmatrix-js-sdk+repo%3Amatrix-org%2Fmatrix-react-sdk+repo%3Aelement-hq%2Felement-web+repo%3Aelement-hq%2Felement-desktop+sort%3Aupdated-desc+label%3AX-Release-Blocker+"
steps:
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
HS_URL: ${{ secrets.BETABOT_HS_URL }}
ROOM_ID: ${{ secrets.ROOM_ID }}

View File

@@ -10,7 +10,7 @@ jobs:
permissions:
pull-requests: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- name: Update synapse image
run: |
@@ -21,6 +21,15 @@ jobs:
env:
IMAGE: ghcr.io/element-hq/synapse:develop
- name: Update MAS image
run: |
docker pull "$IMAGE"
INSPECT=$(docker inspect --format='{{index .RepoDigests 0}}' "$IMAGE")
DIGEST=${INSPECT#*@}
sed -i "s/const TAG.*/const TAG = \"main@$DIGEST\";/" playwright/testcontainers/mas.ts
env:
IMAGE: ghcr.io/element-hq/matrix-authentication-service:main
- name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7

View File

@@ -8,7 +8,7 @@ jobs:
name: Check PR base branch
runs-on: ubuntu-24.04
steps:
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
const baseBranch = context.payload.pull_request.base.ref;

View File

@@ -41,7 +41,7 @@ jobs:
REPOS: matrix-js-sdk element-web element-desktop
steps:
- name: Checkout Element Desktop
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
if: inputs.element-desktop
with:
repository: element-hq/element-desktop
@@ -51,7 +51,7 @@ jobs:
fetch-tags: true
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
- name: Checkout Element Web
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
if: inputs.element-web
with:
repository: element-hq/element-web
@@ -61,7 +61,7 @@ jobs:
fetch-tags: true
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
- name: Checkout Matrix JS SDK
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
if: inputs.matrix-js-sdk
with:
repository: matrix-org/matrix-js-sdk

View File

@@ -27,7 +27,7 @@ jobs:
run: "sudo apt-get install -y tree"
- name: Download Diffs
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}

View File

@@ -21,12 +21,12 @@ jobs:
issues: read
pull-requests: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
persist-credentials: false
repository: element-hq/element-web
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with:
cache: "yarn"
node-version: "lts/*"
@@ -39,7 +39,7 @@ jobs:
run: echo "version=$(yarn list --pattern @playwright/test --depth=0 --json --non-interactive --no-progress | jq -r '.data.trees[].name')" >> $GITHUB_OUTPUT
- name: Cache playwright binaries
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4
id: playwright-cache
with:
path: ~/.cache/ms-playwright

View File

@@ -23,9 +23,9 @@ jobs:
name: "Typescript Syntax Check"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with:
cache: "yarn"
node-version: "lts/*"
@@ -52,12 +52,13 @@ jobs:
error|misconfigured
welcome_to_element
devtools|settings|elementCallUrl
labs|sliding_sync_description
rethemendex_lint:
name: "Rethemendex Check"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- run: ./res/css/rethemendex.sh
@@ -67,9 +68,9 @@ jobs:
name: "ESLint"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with:
cache: "yarn"
node-version: "lts/*"
@@ -85,9 +86,9 @@ jobs:
name: "Style Lint"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with:
cache: "yarn"
node-version: "lts/*"
@@ -103,9 +104,9 @@ jobs:
name: "Workflow Lint"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with:
cache: "yarn"
node-version: "lts/*"
@@ -121,9 +122,9 @@ jobs:
name: "Analyse Dead Code"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with:
cache: "yarn"
node-version: "lts/*"

View File

@@ -39,12 +39,12 @@ jobs:
runner: [1, 2]
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
repository: ${{ inputs.matrix-js-sdk-sha && 'element-hq/element-web' || github.repository }}
- name: Yarn cache
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with:
node-version: "lts/*"
cache: "yarn"
@@ -55,7 +55,7 @@ jobs:
JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }}
- name: Jest Cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4
with:
path: /tmp/jest_cache
key: ${{ hashFiles('**/yarn.lock') }}

View File

@@ -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 }}

View File

@@ -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 }}

View File

@@ -27,7 +27,7 @@ jobs:
contains(github.event.issue.labels.*.name, 'A-Rich-Text-Editor') ||
contains(github.event.issue.labels.*.name, 'A-Element-Call')
steps:
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
github.rest.issues.addLabels({
@@ -44,7 +44,7 @@ jobs:
contains(github.event.issue.labels.*.name, 'good first issue') ||
contains(github.event.issue.labels.*.name, 'Hacktoberfest')
steps:
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
github.rest.issues.addLabels({
@@ -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 }}

View File

@@ -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:

View File

@@ -12,7 +12,7 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
- uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10
with:
operations-per-run: 100

View File

@@ -5,44 +5,25 @@ on:
types: [unlabeled]
permissions: {}
jobs:
Move_Unabeled_Issue_On_Project_Board:
move_no_longer_needs_info_issues:
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') }}
env:
BOARD_NAME: "Issue triage"
OWNER: ${{ github.repository_owner }}
REPO: ${{ github.event.repository.name }}
ISSUE: ${{ github.event.issue.number }}
!contains(github.event.issue.labels.*.name, 'X-Needs-Info')
steps:
- name: Check if issue is already in "${{ env.BOARD_NAME }}"
run: |
json=$(curl -s -H 'Content-Type: application/json' -H "Authorization: bearer ${{ secrets.GITHUB_TOKEN }}" -X POST -d '{"query": "query($issue: Int!, $owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { issue(number: $issue) { projectCards { nodes { project { name } isArchived } } } } } ", "variables" : "{ \"issue\": '${ISSUE}', \"owner\": \"'${OWNER}'\", \"repo\": \"'${REPO}'\" }" }' https://api.github.com/graphql)
if echo $json | jq '.data.repository.issue.projectCards.nodes | length'; then
if [[ $(echo $json | jq '.data.repository.issue.projectCards.nodes[0].project.name') =~ "${BOARD_NAME}" ]]; then
if [[ $(echo $json | jq '.data.repository.issue.projectCards.nodes[0].isArchived') == 'true' ]]; then
echo "Issue is already in Project '$BOARD_NAME', but is archived - skipping workflow";
echo "SKIP_ACTION=true" >> $GITHUB_ENV
else
echo "Issue is already in Project '$BOARD_NAME', proceeding";
echo "ALREADY_IN_BOARD=true" >> $GITHUB_ENV
fi
else
echo "Issue is not in project '$BOARD_NAME', cancelling this workflow"
echo "ALREADY_IN_BOARD=false" >> $GITHUB_ENV
fi
fi
- name: Move issue
uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36
if: ${{ env.ALREADY_IN_BOARD == 'true' && env.SKIP_ACTION != 'true' }}
- id: set_fields
uses: nipe0324/update-project-v2-item-field@c4af58452d1c5a788c1ea4f20e073fa722ec4a6b #v2.0.2
with:
project: Issue triage
column: Triaged
repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
project-url: ${{ env.PROJECT_URL }}
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
skip-update-script: |
const isIssue = item.type === 'ISSUE'
const status = item.fieldValues['Status']
return !isIssue || status !== 'Needs info'
field-name: Status
field-value: "Triaged"
env:
PROJECT_URL: https://github.com/orgs/element-hq/projects/120
remove_Z-Labs_label:
name: Remove Z-Labs label when features behind labs flags are removed
@@ -62,7 +43,7 @@ jobs:
contains(github.event.issue.labels.*.name, 'A-Element-Call')) &&
contains(github.event.issue.labels.*.name, 'Z-Labs')
steps:
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
github.rest.issues.removeLabel({

View File

@@ -9,9 +9,9 @@ jobs:
update:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with:
cache: "yarn"
node-version: "lts/*"

View File

@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-24.04
environment: Matrix
steps:
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
HS_URL: ${{ secrets.BETABOT_HS_URL }}
LOBBY_ROOM_ID: ${{ secrets.ROOM_ID }}

View File

@@ -13,7 +13,7 @@ import { mergeConfig } from "vite";
const config: StorybookConfig = {
stories: ["../src/shared-components/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
staticDirs: ["../webapp"],
addons: ["@storybook/addon-docs", "@storybook/addon-designs"],
addons: ["@storybook/addon-docs", "@storybook/addon-designs", "@storybook/addon-a11y"],
framework: "@storybook/react-vite",
core: {
disableTelemetry: true,

View File

@@ -1,12 +1,11 @@
import type { ArgTypes, Preview, Decorator } from "@storybook/react-vite";
import { addons } from "storybook/preview-api";
import type { ArgTypes, Preview, Decorator, ReactRenderer, StrictArgs } from "@storybook/react-vite";
import "../res/css/shared.pcss";
import "./preview.css";
import React, { useLayoutEffect } from "react";
import { FORCE_RE_RENDER } from "storybook/internal/core-events";
import { setLanguage } from "../src/shared-components/utils/i18n";
import { TooltipProvider } from "@vector-im/compound-web";
import { StoryContext } from "storybook/internal/csf";
export const globalTypes = {
theme: {
@@ -59,29 +58,9 @@ const withThemeProvider: Decorator = (Story, context) => {
);
};
const LanguageSwitcher: React.FC<{
language: string;
}> = ({ language }) => {
useLayoutEffect(() => {
const changeLanguage = async (language: string) => {
await setLanguage(language);
// Force the component to re-render to apply the new language
addons.getChannel().emit(FORCE_RE_RENDER);
};
changeLanguage(language);
}, [language]);
return null;
};
export const withLanguageProvider: Decorator = (Story, context) => {
return (
<>
<LanguageSwitcher language={context.globals.language} />
<Story />
</>
);
};
async function languageLoader(context: StoryContext<ReactRenderer, StrictArgs>): Promise<void> {
await setLanguage(context.globals.language);
}
const withTooltipProvider: Decorator = (Story) => {
return (
@@ -93,14 +72,22 @@ const withTooltipProvider: Decorator = (Story) => {
const preview: Preview = {
tags: ["autodocs"],
decorators: [withThemeProvider, withLanguageProvider, withTooltipProvider],
decorators: [withThemeProvider, withTooltipProvider],
parameters: {
options: {
storySort: {
method: "alphabetical",
},
},
a11y: {
/*
* Configure test behavior
* See: https://storybook.js.org/docs/next/writing-tests/accessibility-testing#test-behavior
*/
test: "error",
},
},
loaders: [languageLoader],
};
export default preview;

View File

@@ -1,3 +1,90 @@
Changes in [1.11.112](https://github.com/element-hq/element-web/releases/tag/v1.11.112) (2025-09-16)
====================================================================================================
Fix [CVE-2025-59161](https://www.cve.org/CVERecord?id=CVE-2025-59161) / [GHSA-m6c8-98f4-75rr](https://github.com/element-hq/element-web/security/advisories/GHSA-m6c8-98f4-75rr)
Changes in [1.11.111](https://github.com/element-hq/element-web/releases/tag/v1.11.111) (2025-09-10)
====================================================================================================
## ✨ Features
* Do not hide media from your own user by default ([#29797](https://github.com/element-hq/element-web/pull/29797)). Contributed by @Half-Shot.
* Remember whether sidebar is shown for calls when switching rooms ([#30262](https://github.com/element-hq/element-web/pull/30262)). Contributed by @bojidar-bg.
* Open the proper integration settings on integrations disabled error ([#30538](https://github.com/element-hq/element-web/pull/30538)). Contributed by @Half-Shot.
* Show a "progress" dialog while invites are being sent ([#30561](https://github.com/element-hq/element-web/pull/30561)). Contributed by @richvdh.
* Move the room list to the new ListView(backed by react-virtuoso) ([#30515](https://github.com/element-hq/element-web/pull/30515)). Contributed by @langleyd.
## 🐛 Bug Fixes
* [Backport staging] Ensure container starts if it is mounted with an empty /modules directory. ([#30705](https://github.com/element-hq/element-web/pull/30705)). Contributed by @RiotRobot.
* Fix room joining over federation not specifying vias or using aliases ([#30641](https://github.com/element-hq/element-web/pull/30641)). Contributed by @t3chguy.
* Fix stable-suffixed MSC4133 support ([#30649](https://github.com/element-hq/element-web/pull/30649)). Contributed by @dbkr.
* Fix i18n of message when a setting is disabled ([#30646](https://github.com/element-hq/element-web/pull/30646)). Contributed by @dbkr.
* ListView should not handle the arrow keys if there is a modifier applied ([#30633](https://github.com/element-hq/element-web/pull/30633)). Contributed by @langleyd.
* Make BaseDialog's div keyboard focusable and fix test. ([#30631](https://github.com/element-hq/element-web/pull/30631)). Contributed by @langleyd.
* Fix: Allow triple-click text selection to flow around pills ([#30349](https://github.com/element-hq/element-web/pull/30349)). Contributed by @AlirezaMrtz.
* Watch for a 'join' action to know when the call is connected ([#29492](https://github.com/element-hq/element-web/pull/29492)). Contributed by @robintown.
* Fix: add missing tooltip and aria-label to lock icon next to composer ([#30623](https://github.com/element-hq/element-web/pull/30623)). Contributed by @florianduros.
* Don't render context menu when scrolling ([#30613](https://github.com/element-hq/element-web/pull/30613)). Contributed by @langleyd.
Changes in [1.11.110](https://github.com/element-hq/element-web/releases/tag/v1.11.110) (2025-08-27)
====================================================================================================
## ✨ Features
* Hide recovery key when re-entering it while creating or changing it ([#30499](https://github.com/element-hq/element-web/pull/30499)). Contributed by @andybalaam.
* Add `?no_universal_links=true` to OIDC url so EX doesn't try to handle it ([#29439](https://github.com/element-hq/element-web/pull/29439)). Contributed by @t3chguy.
* Show a blue lock for unencrypted rooms and hide the grey shield for encrypted rooms ([#30440](https://github.com/element-hq/element-web/pull/30440)). Contributed by @langleyd.
* Add support for Module API 1.4 ([#30185](https://github.com/element-hq/element-web/pull/30185)). Contributed by @t3chguy.
* MVVM - Introduce some helpers for snapshot management ([#30398](https://github.com/element-hq/element-web/pull/30398)). Contributed by @MidhunSureshR.
## 🐛 Bug Fixes
* A11y: move focus to right panel when opened ([#30553](https://github.com/element-hq/element-web/pull/30553)). Contributed by @florianduros.
* Fix e2e warning icon should be white ([#30539](https://github.com/element-hq/element-web/pull/30539)). Contributed by @florianduros.
* Remove NoOneHere disabled reason. ([#30524](https://github.com/element-hq/element-web/pull/30524)). Contributed by @toger5.
* Fix downloading files with authenticated media API ([#30520](https://github.com/element-hq/element-web/pull/30520)). Contributed by @t3chguy.
* Fix call permissions check confusion around element call ([#30521](https://github.com/element-hq/element-web/pull/30521)). Contributed by @t3chguy.
* Fix line wrap around emoji verification ([#30523](https://github.com/element-hq/element-web/pull/30523)). Contributed by @t3chguy.
* Don't highlight redacted events ([#30519](https://github.com/element-hq/element-web/pull/30519)). Contributed by @t3chguy.
* Fix matrix.to links not being handled in the app ([#30522](https://github.com/element-hq/element-web/pull/30522)). Contributed by @t3chguy.
* Fix issue of new room list taking up the full width ([#30459](https://github.com/element-hq/element-web/pull/30459)). Contributed by @langleyd.
* Fix widget persistence in React development mode ([#30509](https://github.com/element-hq/element-web/pull/30509)). Contributed by @robintown.
* Fix widget initialization in React development mode ([#30463](https://github.com/element-hq/element-web/pull/30463)). Contributed by @robintown.
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

View File

@@ -2,6 +2,11 @@
Everyone is welcome to contribute code to Element Web, provided that they are willing to license their contributions to Element under a [Contributor License Agreement](https://cla-assistant.io/element-hq/element-web) (CLA). This ensures that their contribution will be made available under an OSI-approved open-source license, currently licensed under Affero General Public License v3 (AGPLv3) or General Public License v3 (GPLv3) at your choice.
If you're contributing, or thinking about contributing, please come & chat to
us in our development room, [#element-dev](https://matrix.to/#/#element-dev:matrix.org).
This is the best place to ask questions about the code, how to work on the project
or whether a change is likely to be accepted.
## How to contribute
The preferred and easiest way to contribute changes to the project is to fork

View File

@@ -1,7 +1,7 @@
# syntax=docker.io/docker/dockerfile:1.17-labs@sha256:9187104f31e3a002a8a6a3209ea1f937fb7486c093cbbde1e14b0fa0d7e4f1b5
# syntax=docker.io/docker/dockerfile:1.18-labs@sha256:79cdc14e1c220efb546ad14a8ebc816e3277cd72d27195ced5bebdd226dd1025
# Builder
FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:2d63e0f812d023c4c764e83d7e30dc94949304443ebc372d5c445e63a5ae49c1 AS builder
FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:f8c398a3ad2612293e8827915c056ed0f5cc708b0f676274bb6c732e3c10f93d 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:e61b77b27c8f3124fad6d19e894ca5b603bcaf6a34a2df035511299dfa6fad35
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:14b127ed799301a21a1798516443c675237120c76b9a738d43c5e4747de4b1c9
# Need root user to install packages & manipulate the usr directory
USER root

View File

@@ -27,7 +27,7 @@ Element has several tiers of support for different environments:
- Best effort
- Definition:
- Issues **accepted**, regressions **do not block** the release
- The wider Element Products(including Element Call and the Enterprise Server Suite) do still not officially support these browsers.
- The wider Element Products (including Element Call and the Enterprise Server Suite) do still not officially support these browsers.
- The element web project and its contributors should keep the client functioning and gracefully degrade where other sibling features (E.g. Element Call) may not function.
- Last major release of Firefox ESR and Chrome/Edge Extended Stable
- Community Supported

View File

@@ -14,10 +14,9 @@ entrypoint_log() {
mkdir -p /tmp/element-web-config
cp /app/config*.json /tmp/element-web-config/
# If there are modules to be loaded
if [ -d "/modules" ]; then
# If the module directory exists AND the module directory has modules in it
if [ -d "/modules" ] && [ "$( ls -A '/modules' )" ]; then
cd /modules
for MODULE in *
do
# If the module has a package.json, use its main field as the entrypoint

View File

@@ -585,6 +585,8 @@ Currently, the following UI feature flags are supported:
- `UIFeature.BulkUnverifiedSessionsReminder` - Display popup reminders to verify or remove unverified sessions. Defaults
to true.
- `UIFeature.locationSharing` - Whether or not location sharing menus will be shown.
- `UIFeature.allowCreatingPublicRooms` - Whether or not public rooms can be created.
- `UIFeature.allowCreatingPublicSpaces` - Whether or not public spaces can be created.
## Undocumented / developer options

View File

@@ -38,45 +38,20 @@ When `force_disable` is true:
Note: If the server is configured to forcibly enable encryption for some or all rooms,
this behaviour will be overridden.
# Secure backup
# Setting up recovery
By default, Element strongly encourages (but does not require) users to set up
Secure Backup so that cross-signing identity key and message keys can be
recovered in case of a disaster where you lose access to all active devices.
recovery so that you can access history on your new devices as well as retain access to your message history and cryptographic identity when you lose all of your devices.
## Requiring secure backup
## Removal of old settings
To require Secure Backup to be configured before Element can be used, set the
following on your homeserver's `/.well-known/matrix/client` config:
Support for the configuration options `secure_backup_required` and `secure_backup_setup_methods`
in the `/.well-known/matrix/client` config has been removed.
```json
{
"io.element.e2ee": {
"secure_backup_required": true
}
}
```
## Preferring setup methods
By default, Element offers users a choice of a random key or user-chosen
passphrase when setting up Secure Backup. If a homeserver admin would like to
only offer one of these, you can signal this via the
`/.well-known/matrix/client` config, for example:
```json
{
"io.element.e2ee": {
"secure_backup_setup_methods": ["passphrase"]
}
}
```
The field `secure_backup_setup_methods` is an array listing the methods the
client should display. Supported values currently include `key` and
`passphrase`. If the `secure_backup_setup_methods` field is not present or
exists but does not contain any supported methods, Element will fallback to the
default value of: `["key", "passphrase"]`.
Setting up recovery is now always recommended to all users by showing a one-off toast and a
permanent red dot on the _Encryption_ tab in the _Settings_ dialog. When creating a new
recovery key, the UI only supports auto-generated keys. Using an existing (custom) passphrase
still works, but is not exposed in the UI when setting up recovery.
# Compatibility

View File

@@ -41,7 +41,7 @@ const config: Config = {
"recorderWorkletFactory": "<rootDir>/__mocks__/empty.js",
"^fetch-mock$": "<rootDir>/node_modules/fetch-mock",
},
transformIgnorePatterns: ["/node_modules/(?!(mime|matrix-js-sdk)).+$"],
transformIgnorePatterns: ["/node_modules/(?!(mime|matrix-js-sdk|uuid|p-retry|is-network-error)).+$"],
collectCoverageFrom: [
"<rootDir>/src/**/*.{js,ts,tsx}",
// getSessionLock is piped into a different JS context via stringification, and the coverage functionality is

11
knip.ts
View File

@@ -2,7 +2,6 @@ import { KnipConfig } from "knip";
export default {
entry: [
"src/vector/index.ts",
"src/serviceworker/index.ts",
"src/workers/*.worker.ts",
"src/utils/exportUtils/exportJS.js",
@@ -12,8 +11,6 @@ export default {
"res/decoder-ring/**",
"res/jitsi_external_api.min.js",
"docs/**",
// Used by jest
"__mocks__/maplibre-gl.js",
],
project: ["**/*.{js,ts,jsx,tsx}"],
ignore: [
@@ -42,10 +39,18 @@ 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
"jq",
"wait-on",
],
ignoreExportsUsedInFile: true,
} satisfies KnipConfig;

View File

@@ -1,6 +1,6 @@
{
"name": "element-web",
"version": "1.11.108",
"version": "1.11.112",
"description": "Element: the future of secure communication",
"author": "New Vector Ltd.",
"repository": {
@@ -75,8 +75,8 @@
"resolutions": {
"**/pretty-format/react-is": "19.1.1",
"@playwright/test": "1.54.2",
"@types/react": "19.1.9",
"@types/react-dom": "19.1.7",
"@types/react": "19.1.13",
"@types/react-dom": "19.1.9",
"oidc-client-ts": "3.3.0",
"jwt-decode": "4.0.0",
"caniuse-lite": "1.0.30001724",
@@ -96,7 +96,6 @@
"@matrix-org/spec": "^1.7.0",
"@sentry/browser": "^10.0.0",
"@types/png-chunks-extract": "^1.0.2",
"@types/react-virtualized": "^9.21.30",
"@vector-im/compound-design-tokens": "^6.0.0",
"@vector-im/compound-web": "^8.1.2",
"@vector-im/matrix-wysiwyg": "2.39.0",
@@ -143,7 +142,7 @@
"opus-recorder": "^8.0.3",
"pako": "^2.0.3",
"png-chunks-extract": "^1.0.0",
"posthog-js": "1.257.0",
"posthog-js": "1.265.1",
"qrcode": "1.5.4",
"re-resizable": "6.11.2",
"react": "^19.0.0",
@@ -153,15 +152,14 @@
"react-focus-lock": "^2.5.1",
"react-string-replace": "^1.1.1",
"react-transition-group": "^4.4.1",
"react-virtualized": "^9.22.5",
"react-virtuoso": "^4.12.6",
"react-virtuoso": "^4.14.0",
"rfc4648": "^1.4.0",
"sanitize-filename": "^1.6.3",
"sanitize-html": "2.17.0",
"tar-js": "^0.3.0",
"temporal-polyfill": "^0.3.0",
"ua-parser-js": "^1.0.2",
"uuid": "^11.0.0",
"ua-parser-js": "1.0.40",
"uuid": "^13.0.0",
"what-input": "^5.2.10"
},
"devDependencies": {
@@ -186,13 +184,14 @@
"@babel/preset-typescript": "^7.12.7",
"@babel/runtime": "^7.12.5",
"@casualbot/jest-sonar-reporter": "2.2.7",
"@element-hq/element-call-embedded": "0.14.1",
"@element-hq/element-web-playwright-common": "^1.4.4",
"@element-hq/element-call-embedded": "0.15.0",
"@element-hq/element-web-playwright-common": "^1.4.6",
"@peculiar/webcrypto": "^1.4.3",
"@playwright/test": "^1.50.1",
"@principalstudio/html-webpack-inject-preload": "^1.2.7",
"@rrweb/types": "^2.0.0-alpha.18",
"@sentry/webpack-plugin": "^4.0.0",
"@storybook/addon-a11y": "^9.0.18",
"@storybook/addon-designs": "^10.0.1",
"@storybook/addon-docs": "^9.0.12",
"@storybook/icons": "^1.4.0",
@@ -205,6 +204,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",
@@ -223,15 +223,15 @@
"@types/node-fetch": "^2.6.2",
"@types/pako": "^2.0.0",
"@types/qrcode": "^1.3.5",
"@types/react": "19.1.9",
"@types/react": "19.1.13",
"@types/react-beautiful-dnd": "^13.0.0",
"@types/react-dom": "19.1.7",
"@types/react-dom": "19.1.9",
"@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",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^8.19.0",
"@typescript-eslint/parser": "^8.19.0",
"babel-jest": "^29.0.0",

View File

@@ -11,3 +11,42 @@ index 917a7fc..a2710c6 100644
didOkOrSubmit: boolean;
model: M;
}>;
diff --git a/node_modules/@matrix-org/react-sdk-module-api/lib/lifecycles/CryptoSetupExtensions.js b/node_modules/@matrix-org/react-sdk-module-api/lib/lifecycles/CryptoSetupExtensions.js
index 5d422ed..b823add 100644
--- a/node_modules/@matrix-org/react-sdk-module-api/lib/lifecycles/CryptoSetupExtensions.js
+++ b/node_modules/@matrix-org/react-sdk-module-api/lib/lifecycles/CryptoSetupExtensions.js
@@ -124,34 +124,28 @@ var DefaultCryptoSetupExtensions = /*#__PURE__*/function (_CryptoSetupExtension)
(0, _createClass2["default"])(DefaultCryptoSetupExtensions, [{
key: "examineLoginResponse",
value: function examineLoginResponse(response, credentials) {
- console.log("Default empty examineLoginResponse() => void");
}
}, {
key: "persistCredentials",
value: function persistCredentials(credentials) {
- console.log("Default empty persistCredentials() => void");
}
}, {
key: "getSecretStorageKey",
value: function getSecretStorageKey() {
- console.log("Default empty getSecretStorageKey() => null");
return null;
}
}, {
key: "createSecretStorageKey",
value: function createSecretStorageKey() {
- console.log("Default empty createSecretStorageKey() => null");
return null;
}
}, {
key: "catchAccessSecretStorageError",
value: function catchAccessSecretStorageError(e) {
- console.log("Default catchAccessSecretStorageError() => void");
}
}, {
key: "setupEncryptionNeeded",
value: function setupEncryptionNeeded(args) {
- console.log("Default setupEncryptionNeeded() => false");
return false;
}
}, {

View File

@@ -29,7 +29,7 @@ test.describe("Landmark navigation tests", () => {
// Pressing Control+F6 again will focus room search
await page.keyboard.press("ControlOrMeta+F6");
await expect(page.locator(".mx_RoomSearch")).toBeFocused();
await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
// Pressing Control+F6 again will focus the message composer
await page.keyboard.press("ControlOrMeta+F6");
@@ -44,7 +44,7 @@ test.describe("Landmark navigation tests", () => {
await expect(page.locator(".mx_HomePage")).toBeFocused();
await page.keyboard.press("ControlOrMeta+Shift+F6");
await expect(page.locator(".mx_RoomSearch")).toBeFocused();
await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
await page.keyboard.press("ControlOrMeta+Shift+F6");
await expect(page.locator(".mx_SpaceButton_active")).toBeFocused();
@@ -75,11 +75,11 @@ test.describe("Landmark navigation tests", () => {
// Pressing Control+F6 again will focus room search
await page.keyboard.press("ControlOrMeta+F6");
await expect(page.locator(".mx_RoomSearch")).toBeFocused();
await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
// Pressing Control+F6 again will focus the room tile in the room list
await page.keyboard.press("ControlOrMeta+F6");
await expect(page.locator(".mx_RoomTile_selected")).toBeFocused();
await expect(page.locator(".mx_RoomListItemView_selected")).toBeFocused();
// Pressing Control+F6 again will focus the message composer
await page.keyboard.press("ControlOrMeta+F6");
@@ -94,10 +94,10 @@ test.describe("Landmark navigation tests", () => {
await expect(page.locator(".mx_BasicMessageComposer_input")).toBeFocused();
await page.keyboard.press("ControlOrMeta+Shift+F6");
await expect(page.locator(".mx_RoomTile_selected")).toBeFocused();
await expect(page.locator(".mx_RoomListItemView_selected")).toBeFocused();
await page.keyboard.press("ControlOrMeta+Shift+F6");
await expect(page.locator(".mx_RoomSearch")).toBeFocused();
await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
await page.keyboard.press("ControlOrMeta+Shift+F6");
await expect(page.locator(".mx_SpaceButton_active")).toBeFocused();
@@ -131,11 +131,11 @@ test.describe("Landmark navigation tests", () => {
// Pressing Control+F6 again will focus room search
await page.keyboard.press("ControlOrMeta+F6");
await expect(page.locator(".mx_RoomSearch")).toBeFocused();
await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
// Pressing Control+F6 again will focus the room tile in the room list
await page.keyboard.press("ControlOrMeta+F6");
await expect(page.locator(".mx_RoomTile")).toBeFocused();
await expect(page.locator(".mx_RoomListItemView")).toBeFocused();
// Pressing Control+F6 again will focus the home section
await page.keyboard.press("ControlOrMeta+F6");
@@ -150,10 +150,10 @@ test.describe("Landmark navigation tests", () => {
await expect(page.locator(".mx_HomePage")).toBeFocused();
await page.keyboard.press("ControlOrMeta+Shift+F6");
await expect(page.locator(".mx_RoomTile")).toBeFocused();
await expect(page.locator(".mx_RoomListItemView")).toBeFocused();
await page.keyboard.press("ControlOrMeta+Shift+F6");
await expect(page.locator(".mx_RoomSearch")).toBeFocused();
await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
await page.keyboard.press("ControlOrMeta+Shift+F6");
await expect(page.locator(".mx_SpaceButton_active")).toBeFocused();

View File

@@ -14,6 +14,9 @@ const CtrlOrMeta = process.platform === "darwin" ? "Meta" : "Control";
test.describe("Composer", () => {
test.use({
displayName: "Janet",
botCreateOpts: {
displayName: "Bob",
},
});
test.use({
@@ -94,5 +97,25 @@ test.describe("Composer", () => {
).toBeVisible();
});
});
test("can send mention", { tag: "@screenshot" }, async ({ page, bot, app }) => {
// Set up a private room so we have another user to mention
await app.client.createRoom({
is_direct: true,
invite: [bot.credentials.userId],
});
await app.viewRoomByName("Bob");
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
await composer.pressSequentially("@bob");
// Note that we include the user ID here as the room tile is also an 'option' role
// with text 'Bob'
await page.getByRole("option", { name: `Bob ${bot.credentials.userId}` }).click();
await expect(composer.getByText("Bob")).toBeVisible();
await expect(composer).toMatchScreenshot("mention.png");
await composer.press("Enter");
await expect(page.locator(".mx_EventTile_body", { hasText: "Bob" })).toBeVisible();
});
});
});

View File

@@ -1,34 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2022, 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { test, expect } from "../../element-web-test";
test.describe("Create Room", () => {
test.use({ displayName: "Jim" });
test("should allow us to create a public room with name, topic & address set", async ({ page, user, app }) => {
const name = "Test room 1";
const topic = "This room is dedicated to this test and this test only!";
const dialog = await app.openCreateRoomDialog();
// Fill name & topic
await dialog.getByRole("textbox", { name: "Name" }).fill(name);
await dialog.getByRole("textbox", { name: "Topic" }).fill(topic);
// Change room to public
await dialog.getByRole("button", { name: "Room visibility" }).click();
await dialog.getByRole("option", { name: "Public room" }).click();
// Fill room address
await dialog.getByRole("textbox", { name: "Room address" }).fill("test-room-1");
// Submit
await dialog.getByRole("button", { name: "Create room" }).click();
await expect(page).toHaveURL(new RegExp(`/#/room/#test-room-1:${user.homeServer}`));
const header = page.locator(".mx_RoomHeader");
await expect(header).toContainText(name);
});
});

View File

@@ -49,7 +49,7 @@ test.describe("Encryption state after registration", () => {
"Pa$sW0rD!",
);
await page.getByRole("button", { name: "Add room" }).click();
await page.getByRole("navigation", { name: "Room list" }).getByRole("button", { name: "Add" }).click();
await page.getByRole("menuitem", { name: "New room" }).click();
await page.getByRole("textbox", { name: "Name" }).fill("test room");
await page.getByRole("button", { name: "Create room" }).click();
@@ -78,7 +78,7 @@ test.describe("Key backup reset from elsewhere", () => {
await page.getByRole("button", { name: "Continue" }).click();
await registerAccountMas(page, mailpitClient, testUsername, `${testUsername}@email.com`, testPassword);
await page.getByRole("button", { name: "Add room" }).click();
await page.getByRole("navigation", { name: "Room list" }).getByRole("button", { name: "Add" }).click();
await page.getByRole("menuitem", { name: "New room" }).click();
await page.getByRole("textbox", { name: "Name" }).fill("test room");
await page.getByRole("button", { name: "Create room" }).click();

View File

@@ -21,7 +21,8 @@ const checkDMRoom = async (page: Page) => {
};
const startDMWithBob = async (page: Page, bob: Bot) => {
await page.locator(".mx_LegacyRoomList").getByRole("button", { name: "Start chat" }).click();
await page.getByRole("navigation", { name: "Room list" }).getByRole("button", { name: "Add" }).click();
await page.getByRole("menuitem", { name: "Start chat" }).click();
await page.getByTestId("invite-dialog-input").fill(bob.credentials.userId);
await page.locator(".mx_InviteDialog_tile_nameStack_name").getByText("Bob").click();
await expect(
@@ -154,11 +155,12 @@ test.describe("Cryptography", function () {
await app.client.bootstrapCrossSigning(aliceCredentials);
await startDMWithBob(page, bob);
// send first message
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).fill("Hey!");
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).press("Enter");
await page.getByRole("textbox", { name: "Send a message…" }).fill("Hey!");
await page.getByRole("textbox", { name: "Send a 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);

View File

@@ -143,10 +143,7 @@ test.describe("Cryptography", function () {
);
// Alice accepts the invite
await expect(
page.getByRole("group", { name: "Invites" }).locator(".mx_RoomSublist_tiles").getByRole("treeitem"),
).toHaveCount(1);
await page.getByRole("treeitem", { name: "Test room" }).click();
await page.getByRole("option", { name: "Test room" }).click();
await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click();
// Bob sends an encrypted event and an undecryptable event
@@ -280,10 +277,7 @@ test.describe("Cryptography", function () {
);
// Alice accepts the invite
await expect(
page.getByRole("group", { name: "Invites" }).locator(".mx_RoomSublist_tiles").getByRole("treeitem"),
).toHaveCount(1);
await page.getByRole("treeitem", { name: "Test room" }).click();
await page.getByRole("option", { name: "Test room" }).click();
await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click();
// wait until we're joined and see the timeline

View File

@@ -38,7 +38,7 @@ test.describe("Dehydration", () => {
// Reset the identity key
const settings = await app.settings.openUserSettings("Encryption");
await settings.getByRole("button", { name: "Verify this device" }).click();
await page.getByRole("button", { name: "Proceed with reset" }).click();
await page.getByRole("button", { name: "Can't confirm?" }).click();
await page.getByRole("button", { name: "Continue" }).click();
// Set up recovery
@@ -106,7 +106,7 @@ test.describe("Dehydration", () => {
await logIntoElement(page, credentials);
// Oh no, we forgot our recovery key - reset our identity
await page.locator(".mx_AuthPage").getByRole("button", { name: "Reset all" }).click();
await page.locator(".mx_AuthPage").getByRole("button", { name: "Can't confirm" }).click();
await expect(
page.getByRole("heading", { name: "Are you sure you want to reset your identity?" }),
).toBeVisible();

View File

@@ -36,13 +36,13 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
expectedBackupVersion = res.expectedBackupVersion;
});
// Click the "Verify with another device" button, and have the bot client auto-accept it.
// Click the "Use another device" button, and have the bot client auto-accept it.
async function initiateAliceVerificationRequest(page: Page): Promise<JSHandle<VerificationRequest>> {
// alice bot waits for verification request
const promiseVerificationRequest = waitForVerificationRequest(aliceBotClient);
// Click on "Verify with another device"
await page.locator(".mx_AuthPage").getByRole("button", { name: "Verify with another device" }).click();
// Click on "Use another device"
await page.locator(".mx_AuthPage").getByRole("button", { name: "Use another device" }).click();
// alice bot responds yes to verification request from alice
return promiseVerificationRequest;
@@ -203,7 +203,7 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
/** Helper for the three tests above which verify by recovery key */
async function enterRecoveryKeyAndCheckVerified(page: Page, app: ElementAppPage, recoveryKey: string) {
await page.getByRole("button", { name: "Verify with Recovery Key or Phrase" }).click();
await page.getByRole("button", { name: "Use recovery key" }).click();
// Enter the recovery key
const dialog = page.locator(".mx_Dialog");

View File

@@ -14,7 +14,7 @@ import {
createSecondBotDevice,
createSharedRoomWithUser,
enableKeyBackup,
logIntoElement,
logIntoElementAndVerify,
logOutOfElement,
verify,
waitForDevices,
@@ -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,
@@ -195,7 +195,7 @@ test.describe("Cryptography", function () {
window.localStorage.clear();
});
await page.reload();
await logIntoElement(page, aliceCredentials, securityKey);
await logIntoElementAndVerify(page, aliceCredentials, securityKey);
/* go back to the test room and find Bob's message again */
await app.viewRoomById(testRoomId);

View File

@@ -8,7 +8,7 @@
import { type GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api";
import { test, expect } from "../../element-web-test";
import { createBot, deleteCachedSecrets, disableKeyBackup, logIntoElement } from "./utils";
import { createBot, deleteCachedSecrets, disableKeyBackup, logIntoElementAndVerify } from "./utils";
import { type Bot } from "../../pages/bot";
test.describe("Key storage out of sync toast", () => {
@@ -18,12 +18,12 @@ test.describe("Key storage out of sync toast", () => {
const res = await createBot(page, homeserver, credentials);
recoveryKey = res.recoveryKey;
await logIntoElement(page, credentials, recoveryKey.encodedPrivateKey);
await logIntoElementAndVerify(page, credentials, recoveryKey.encodedPrivateKey);
await deleteCachedSecrets(page);
// We won't be prompted for crypto setup unless we have an e2e room, so make one
await page.getByRole("button", { name: "Add room" }).click();
await page.getByRole("navigation", { name: "Room list" }).getByRole("button", { name: "Add" }).click();
await page.getByRole("menuitem", { name: "New room" }).click();
await page.getByRole("textbox", { name: "Name" }).fill("Test room");
await page.getByRole("button", { name: "Create room" }).click();
@@ -65,10 +65,10 @@ test.describe("'Turn on key storage' toast", () => {
const recoveryKey = res.recoveryKey;
botClient = res.botClient;
await logIntoElement(page, credentials, recoveryKey.encodedPrivateKey);
await logIntoElementAndVerify(page, credentials, recoveryKey.encodedPrivateKey);
// We won't be prompted for crypto setup unless we have an e2e room, so make one
await page.getByRole("button", { name: "Add room" }).click();
await page.getByRole("navigation", { name: "Room list" }).getByRole("button", { name: "Add" }).click();
await page.getByRole("menuitem", { name: "New room" }).click();
await page.getByRole("textbox", { name: "Name" }).fill("Test room");
await page.getByRole("button", { name: "Create room" }).click();
@@ -126,7 +126,7 @@ test.describe("'Turn on key storage' toast", () => {
await toast.getByRole("button", { name: "Continue" }).click();
// Then we see the Encryption settings dialog with an option to turn on key storage
await expect(page.getByRole("checkbox", { name: "Allow key storage" })).toBeVisible();
await expect(page.getByRole("switch", { name: "Allow key storage" })).toBeVisible();
// And when we close that
await page.getByRole("button", { name: "Close dialog" }).click();
@@ -153,7 +153,7 @@ test.describe("'Turn on key storage' toast", () => {
await page.getByRole("button", { name: "Go to Settings" }).click();
// Then we see Encryption settings again
await expect(page.getByRole("checkbox", { name: "Allow key storage" })).toBeVisible();
await expect(page.getByRole("switch", { name: "Allow key storage" })).toBeVisible();
// And when we close that, see the toast, click Dismiss, and Yes, Dismiss
await page.getByRole("button", { name: "Close dialog" }).click();

View File

@@ -206,32 +206,42 @@ export async function checkDeviceIsConnectedKeyBackup(
/**
* Fill in the login form in element with the given creds.
*
* If a `securityKey` is given, verifies the new device using the key.
*/
export async function logIntoElement(page: Page, credentials: Credentials, securityKey?: string) {
export async function logIntoElement(page: Page, credentials: Credentials) {
await page.goto("/#/login");
await page.getByRole("textbox", { name: "Username" }).fill(credentials.userId);
await page.getByPlaceholder("Password").fill(credentials.password);
await page.getByRole("button", { name: "Sign in" }).click();
}
// if a securityKey was given, verify the new device
if (securityKey !== undefined) {
await page.locator(".mx_AuthPage").getByRole("button", { name: "Verify with Recovery Key" }).click();
/**
* Fill in the login form in Element with the given creds, and then complete the `CompleteSecurity` step, using the
* given recovery key. (Normally this will verify the new device using the secrets from 4S.)
*
* Afterwards, waits for the application to redirect to the home page.
*/
export async function logIntoElementAndVerify(page: Page, credentials: Credentials, recoveryKey: string) {
await logIntoElement(page, credentials);
const useSecurityKey = page.locator(".mx_Dialog").getByRole("button", { name: "use your Recovery Key" });
// If the user has set a recovery *passphrase*, they'll be prompted for that first and have to click
// through to enter the recovery key which is what we have here. If they haven't, they'll be prompted
// for a recovery key straight away. We click the button if it's there so this works in both cases.
if (await useSecurityKey.isVisible()) {
await useSecurityKey.click();
}
// Fill in the recovery key
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();
await page.locator(".mx_AuthPage").getByRole("button", { name: "Use recovery key" }).click();
const useSecurityKey = page.locator(".mx_Dialog").getByRole("button", { name: "Use recovery key" });
// If the user has set a recovery *passphrase*, they'll be prompted for that first and have to click
// through to enter the recovery key which is what we have here. If they haven't, they'll be prompted
// for a recovery key straight away. We click the button if it's there so this works in both cases.
if (await useSecurityKey.isVisible()) {
await useSecurityKey.click();
}
// Fill in the recovery key
await page.locator(".mx_Dialog").getByTitle("Recovery key").fill(recoveryKey);
await page.getByRole("button", { name: "Continue", disabled: false }).click();
await page.getByRole("button", { name: "Done" }).click();
// The application should now redirect to `/#/home`. Wait for that to happen, otherwise if a test immediately does
// a `viewRoomById` or similar, it could race.
await page.waitForURL("/#/home");
}
/**
@@ -262,7 +272,7 @@ export async function logOutOfElement(page: Page, discardKeys: boolean = false)
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.getByRole("button", { name: "Use recovery key" }).click();
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();
@@ -300,9 +310,9 @@ export async function doTwoWaySasVerification(page: Page, verifier: JSHandle<Ver
export async function enableKeyBackup(app: ElementAppPage): Promise<string> {
const encryptionTab = await app.settings.openUserSettings("Encryption");
const keyStorageToggle = encryptionTab.getByRole("checkbox", { name: "Allow key storage" });
const keyStorageToggle = encryptionTab.getByRole("switch", { name: "Allow key storage" });
if (!(await keyStorageToggle.isChecked())) {
await encryptionTab.getByRole("checkbox", { name: "Allow key storage" }).click();
await encryptionTab.getByRole("switch", { name: "Allow key storage" }).click();
}
await encryptionTab.getByRole("button", { name: "Set up recovery" }).click();
@@ -323,11 +333,11 @@ export async function enableKeyBackup(app: ElementAppPage): Promise<string> {
export async function disableKeyBackup(app: ElementAppPage): Promise<void> {
const encryptionTab = await app.settings.openUserSettings("Encryption");
const keyStorageToggle = encryptionTab.getByRole("checkbox", { name: "Allow key storage" });
const keyStorageToggle = encryptionTab.getByRole("switch", { name: "Allow key storage" });
if (await keyStorageToggle.isChecked()) {
await encryptionTab.getByRole("checkbox", { name: "Allow key storage" }).click();
await encryptionTab.getByRole("switch", { name: "Allow key storage" }).click();
await encryptionTab.getByRole("button", { name: "Delete key storage" }).click();
await encryptionTab.getByRole("checkbox", { name: "Allow key storage" }).isVisible();
await encryptionTab.getByRole("switch", { name: "Allow key storage" }).isVisible();
// Wait for the update to account data to stick
await new Promise((resolve) => setTimeout(resolve, 2000));
@@ -428,8 +438,8 @@ export async function sendMessageInCurrentRoom(page: Page, message: string): Pro
* @param isEncrypted - Whether the room should be encrypted
*/
export async function createRoom(page: Page, roomName: string, isEncrypted: boolean): Promise<void> {
await page.getByRole("button", { name: "Add room" }).click();
await page.locator(".mx_IconizedContextMenu").getByRole("menuitem", { name: "New room" }).click();
await page.getByRole("navigation", { name: "Room list" }).getByRole("button", { name: "Add" }).click();
await page.getByRole("menuitem", { name: "New room" }).click();
const dialog = page.locator(".mx_Dialog");

View File

@@ -0,0 +1,33 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { test, expect } from "../../element-web-test";
test.describe("Devtools", () => {
test.use({
displayName: "Alice",
});
test("should render the devtools", { tag: "@screenshot" }, async ({ page, homeserver, user, app, axe }) => {
await app.client.createRoom({ name: "Test Room" });
await app.viewRoomByName("Test Room");
const composer = app.getComposer().locator("[contenteditable]");
await composer.fill("/devtools");
await composer.press("Enter");
const dialog = page.locator(".mx_Dialog");
await dialog.getByLabel("Developer mode").check();
axe.disableRules("color-contrast"); // XXX: Inheriting colour contrast issues from room view.
await expect(axe).toHaveNoViolations();
await expect(dialog).toMatchScreenshot("devtools-dialog.png", {
css: `.mx_CopyableText {
display: none;
}`,
});
});
});

View File

@@ -0,0 +1,38 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { SettingLevel } from "../../../src/settings/SettingLevel";
import { test, expect } from "../../element-web-test";
test.describe("Room upgrade dialog", () => {
test.use({
displayName: "Alice",
});
test(
"should render the room upgrade dialog",
{ tag: "@screenshot" },
async ({ page, homeserver, user, app, axe }) => {
// Enable developer mode
await app.settings.setValue("developerMode", null, SettingLevel.ACCOUNT, true);
await app.client.createRoom({ name: "Test Room" });
await app.viewRoomByName("Test Room");
const composer = app.getComposer().locator("[contenteditable]");
// Pick a room version that is likely to be supported by all our target homeservers.
await composer.fill("/upgraderoom 5");
await composer.press("Enter");
const dialog = page.locator(".mx_Dialog");
await dialog.getByLabel("Automatically invite members from this room to the new one").check();
axe.disableRules("color-contrast"); // XXX: Inheriting colour contrast issues from room view.
await expect(axe).toHaveNoViolations();
await expect(dialog).toMatchScreenshot("upgrade-room.png");
},
);
});

View File

@@ -0,0 +1,28 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { test, expect } from "../../element-web-test";
test.describe("Decline and block invite dialog", function () {
test.use({
displayName: "Hanako",
});
test(
"should show decline and block dialog for a room",
{ tag: "@screenshot" },
async ({ page, app, user, bot, axe }) => {
await bot.createRoom({ name: "Test Room", invite: [user.userId] });
await app.viewRoomByName("Test Room");
await page.getByRole("button", { name: "Decline and block" }).click();
axe.disableRules("color-contrast"); // XXX: Inheriting colour contrast issues from room view.
await expect(axe).toHaveNoViolations();
await expect(page.locator(".mx_Dialog")).toMatchScreenshot("decline-and-block-invite-empty.png");
},
);
});

View File

@@ -77,7 +77,8 @@ test.describe("Invite dialog", function () {
"should support inviting a user to Direct Messages",
{ tag: "@screenshot" },
async ({ page, app, user, bot }) => {
await page.locator(".mx_LegacyRoomList").getByRole("button", { name: "Start chat" }).click();
await page.getByRole("navigation", { name: "Room list" }).getByRole("button", { name: "Add" }).click();
await page.getByRole("menuitem", { name: "Start chat" }).click();
const other = page.locator(".mx_InviteDialog_other");
// Assert that the header is rendered

View File

@@ -59,7 +59,7 @@ test.describe("Knock Into Room", () => {
// Knocked room should appear in Rooms
await expect(
page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity" }),
page.getByTestId("room-list").getByRole("option", { name: "Open room Cybersecurity" }),
).toBeVisible();
// bot waits for knock request from Alice
@@ -77,7 +77,7 @@ test.describe("Knock Into Room", () => {
await bot.inviteUser(room.roomId, user.userId);
await expect(
page.getByRole("group", { name: "Invites" }).getByRole("treeitem", { name: "Cybersecurity" }),
page.getByTestId("room-list").getByRole("option", { name: "Open room Cybersecurity" }),
).toBeVisible();
// Alice have to accept invitation in order to join the room.
@@ -85,7 +85,7 @@ test.describe("Knock Into Room", () => {
await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click();
await expect(
page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity" }),
page.getByTestId("room-list").getByRole("option", { name: "Open room Cybersecurity" }),
).toBeVisible();
await expect(page.getByText("Alice joined the room")).toBeVisible();
@@ -136,7 +136,7 @@ test.describe("Knock Into Room", () => {
// Knocked room should appear in Rooms
await expect(
page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity" }),
page.getByTestId("room-list").getByRole("option", { name: "Open room Cybersecurity" }),
).toBeVisible();
// bot waits for knock request from Alice
@@ -154,7 +154,7 @@ test.describe("Knock Into Room", () => {
await bot.inviteUser(room.roomId, user.userId);
await expect(
page.getByRole("group", { name: "Invites" }).getByRole("treeitem", { name: "Cybersecurity" }),
page.getByTestId("room-list").getByRole("option", { name: "Open room Cybersecurity" }),
).toBeVisible();
// Alice have to accept invitation in order to join the room.
@@ -162,7 +162,7 @@ test.describe("Knock Into Room", () => {
await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click();
await expect(
page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity" }),
page.getByTestId("room-list").getByRole("option", { name: "Open room Cybersecurity" }),
).toBeVisible();
await expect(page.getByText("Alice joined the room")).toBeVisible();
@@ -215,14 +215,14 @@ test.describe("Knock Into Room", () => {
await expect(roomPreviewBar.getByRole("heading", { name: "Request to join sent" })).toBeVisible();
// Knocked room should appear in Rooms
page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity" });
page.getByTestId("room-list").getByRole("option", { name: "Open room Cybersecurity" });
await roomPreviewBar.getByRole("button", { name: "Cancel request" }).click();
await expect(roomPreviewBar.getByRole("heading", { name: "Ask to join Cybersecurity?" })).toBeVisible();
await expect(roomPreviewBar.getByRole("button", { name: "Request access" })).toBeVisible();
await expect(
page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity" }),
page.getByTestId("room-list").getByRole("option", { name: "Open room Cybersecurity" }),
).not.toBeVisible();
});
@@ -244,7 +244,7 @@ test.describe("Knock Into Room", () => {
// Knocked room should appear in Rooms
await expect(
page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity" }),
page.getByTestId("room-list").getByRole("option", { name: "Open room Cybersecurity" }),
).toBeVisible();
// bot waits for knock request from Alice
@@ -262,13 +262,10 @@ test.describe("Knock Into Room", () => {
await bot.kick(room.roomId, user.userId);
// Room should stay in Rooms and have red badge when knock is denied
await expect(
page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity", exact: true }),
).not.toBeVisible();
await expect(
page
.getByRole("group", { name: "Rooms" })
.getByRole("treeitem", { name: "Cybersecurity 1 unread mention." }),
.getByTestId("room-list")
.getByRole("option", { name: "Open room Cybersecurity with 1 unread mention." }),
).toBeVisible();
await expect(roomPreviewBar.getByRole("heading", { name: "You have been denied access" })).toBeVisible();

View File

@@ -17,7 +17,7 @@ test.describe("LeftPanel", () => {
// create rooms and check room names are correct
for (const name of ["Apple", "Pineapple", "Orange"]) {
await app.client.createRoom({ name });
await expect(page.getByRole("treeitem", { name })).toBeVisible();
await expect(page.getByRole("option", { name: `Open room ${name}` })).toBeVisible();
}
});
});

View File

@@ -68,7 +68,7 @@ test.describe("Room list filters and sort", () => {
So we expect 'Old Room' to show up in the room list.
*/
const roomListView = getRoomList(page);
const oldRoomTile = roomListView.getByRole("gridcell", { name: "Open room Old Room" });
const oldRoomTile = roomListView.getByRole("option", { name: "Open room Old Room" });
await expect(oldRoomTile).toBeVisible();
/*
@@ -139,8 +139,9 @@ test.describe("Room list filters and sort", () => {
// Open the non-favourite room
const roomListView = getRoomList(page);
const tile = roomListView.getByRole("gridcell", { name: "Open room room-non-fav" });
await tile.scrollIntoViewIfNeeded();
const tile = roomListView.getByRole("option", { name: "Open room room-non-fav" });
// item may not be in the DOM using scrollListToBottom rather than scrollIntoViewIfNeeded
await app.scrollListToBottom(roomListView);
await tile.click();
// Enable Favourite filter
@@ -151,7 +152,7 @@ test.describe("Room list filters and sort", () => {
// Ensure the room list is not scrolled
const isScrolledDown = await page
.getByRole("grid", { name: "Room list" })
.getByRole("listbox", { name: "Room list", exact: true })
.evaluate((e) => e.scrollTop !== 0);
expect(isScrolledDown).toStrictEqual(false);
});
@@ -227,37 +228,37 @@ test.describe("Room list filters and sort", () => {
await primaryFilters.getByRole("option", { name: "Unread" }).click();
// only one room should be visible
await expect(roomList.getByRole("gridcell", { name: "unread dm" })).toBeVisible();
await expect(roomList.getByRole("gridcell", { name: "unread room" })).toBeVisible();
await expect.poll(() => roomList.locator("role=gridcell").count()).toBe(4);
await expect(roomList.getByRole("option", { name: "unread dm" })).toBeVisible();
await expect(roomList.getByRole("option", { name: "unread room" })).toBeVisible();
await expect.poll(() => roomList.locator("role=option").count()).toBe(4);
await expect(primaryFilters).toMatchScreenshot("unread-primary-filters.png");
await primaryFilters.getByRole("option", { name: "People" }).click();
await expect(roomList.getByRole("gridcell", { name: "unread dm" })).toBeVisible();
await expect(roomList.getByRole("gridcell", { name: "invited room" })).toBeVisible();
await expect.poll(() => roomList.locator("role=gridcell").count()).toBe(2);
await expect(roomList.getByRole("option", { name: "unread dm" })).toBeVisible();
await expect(roomList.getByRole("option", { name: "invited room" })).toBeVisible();
await expect.poll(() => roomList.locator("role=option").count()).toBe(2);
await primaryFilters.getByRole("option", { name: "Rooms" }).click();
await expect(roomList.getByRole("gridcell", { name: "unread room" })).toBeVisible();
await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible();
await expect(roomList.getByRole("gridcell", { name: "empty room" })).toBeVisible();
await expect(roomList.getByRole("gridcell", { name: "room with mention" })).toBeVisible();
await expect(roomList.getByRole("gridcell", { name: "Low prio room" })).toBeVisible();
await expect.poll(() => roomList.locator("role=gridcell").count()).toBe(5);
await expect(roomList.getByRole("option", { name: "unread room" })).toBeVisible();
await expect(roomList.getByRole("option", { name: "favourite room" })).toBeVisible();
await expect(roomList.getByRole("option", { name: "empty room" })).toBeVisible();
await expect(roomList.getByRole("option", { name: "room with mention" })).toBeVisible();
await expect(roomList.getByRole("option", { name: "Low prio room" })).toBeVisible();
await expect.poll(() => roomList.locator("role=option").count()).toBe(5);
await getFilterExpandButton(page).click();
await primaryFilters.getByRole("option", { name: "Favourite" }).click();
await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible();
await expect.poll(() => roomList.locator("role=gridcell").count()).toBe(1);
await expect(roomList.getByRole("option", { name: "favourite room" })).toBeVisible();
await expect.poll(() => roomList.locator("role=option").count()).toBe(1);
await primaryFilters.getByRole("option", { name: "Mentions" }).click();
await expect(roomList.getByRole("gridcell", { name: "room with mention" })).toBeVisible();
await expect.poll(() => roomList.locator("role=gridcell").count()).toBe(1);
await expect(roomList.getByRole("option", { name: "room with mention" })).toBeVisible();
await expect.poll(() => roomList.locator("role=option").count()).toBe(1);
await primaryFilters.getByRole("option", { name: "Invites" }).click();
await expect(roomList.getByRole("gridcell", { name: "invited room" })).toBeVisible();
await expect.poll(() => roomList.locator("role=gridcell").count()).toBe(1);
await expect(roomList.getByRole("option", { name: "invited room" })).toBeVisible();
await expect.poll(() => roomList.locator("role=option").count()).toBe(1);
await getFilterCollapseButton(page).click();
await expect(primaryFilters.locator("role=option").first()).toHaveText("Invites");
@@ -268,6 +269,7 @@ test.describe("Room list filters and sort", () => {
{ tag: "@screenshot" },
async ({ page, app, bot }) => {
const roomListView = getRoomList(page);
const primaryFilters = getPrimaryFilters(page);
// Let's configure unread dm room so that we only get notification for mentions and keywords
await app.viewRoomById(unReadDmId);
@@ -276,20 +278,20 @@ test.describe("Room list filters and sort", () => {
await app.settings.closeDialog();
// Let's open a room other than unread room or unread dm
await roomListView.getByRole("gridcell", { name: "Open room favourite room" }).click();
await roomListView.getByRole("option", { name: "Open room favourite room" }).click();
// Let's make the bot send a new message in both rooms
await bot.sendMessage(unReadDmId, "Hello!");
await bot.sendMessage(unReadRoomId, "Hello!");
// Let's activate the unread filter now
await page.getByRole("option", { name: "Unread" }).click();
await primaryFilters.getByRole("option", { name: "Unread" }).click();
// Unread filter should only show unread room and not unread dm!
const unreadDm = roomListView.getByRole("gridcell", { name: "Open room unread room" });
const unreadDm = roomListView.getByRole("option", { name: "Open room unread room" });
await expect(unreadDm).toBeVisible();
await expect(unreadDm).toMatchScreenshot("unread-dm.png");
await expect(roomListView.getByRole("gridcell", { name: "Open room unread dm" })).not.toBeVisible();
await expect(roomListView.getByRole("option", { name: "Open room unread dm" })).not.toBeVisible();
},
);
@@ -299,7 +301,7 @@ test.describe("Room list filters and sort", () => {
await getRoomOptionsMenu(page).click();
await page.getByRole("menuitemradio", { name: "A-Z" }).click();
await expect(roomListView.getByRole("gridcell").first()).toHaveText(/empty room/);
await expect(roomListView.getByRole("option").first()).toHaveText(/empty room/);
});
test("should move room to the top on message when sorting by activity", async ({ page, bot }) => {
@@ -307,7 +309,7 @@ test.describe("Room list filters and sort", () => {
await bot.sendMessage(unReadDmId, "Hello!");
await expect(roomListView.getByRole("gridcell").first()).toHaveText(/unread dm/);
await expect(roomListView.getByRole("option").first()).toHaveText(/unread dm/);
});
});

View File

@@ -38,7 +38,7 @@ test.describe("Room list panel", () => {
test("should render the room list panel", { tag: "@screenshot" }, async ({ page, app, user }) => {
const roomListView = getRoomListView(page);
// Wait for the last room to be visible
await expect(roomListView.getByRole("gridcell", { name: "Open room room19" })).toBeVisible();
await expect(roomListView.getByRole("option", { name: "Open room room19" })).toBeVisible();
await expect(roomListView).toMatchScreenshot("room-list-panel.png");
});

View File

@@ -41,33 +41,38 @@ test.describe("Room list", () => {
}
});
test("should render the room list", { tag: "@screenshot" }, async ({ page, app, user }) => {
test("should render the room list", { tag: "@screenshot" }, async ({ page, app, user, axe }) => {
const roomListView = getRoomList(page);
await expect(roomListView.getByRole("gridcell", { name: "Open room room29" })).toBeVisible();
await expect(roomListView.getByRole("option", { name: "Open room room29" })).toBeVisible();
await expect(roomListView).toMatchScreenshot("room-list.png");
// Put focus on the room list
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
await roomListView.getByRole("option", { name: "Open room room29" }).click();
// Scroll to the end of the room list
await app.scrollListToBottom(page.locator(".mx_RoomList_List"));
await app.scrollListToBottom(roomListView);
// scrollListToBottom seems to leave the mouse hovered over the list, move it away.
await page.getByRole("button", { name: "User menu" }).hover();
await expect(axe).toHaveNoViolations();
await expect(roomListView).toMatchScreenshot("room-list-scrolled.png");
});
test("should open the room when it is clicked", async ({ page, app, user }) => {
const roomListView = getRoomList(page);
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
await roomListView.getByRole("option", { name: "Open room room29" }).click();
await expect(page.getByRole("heading", { name: "room29", level: 1 })).toBeVisible();
});
test("should open the context menu", { tag: "@screenshot" }, async ({ page, app, user }) => {
const roomListView = getRoomList(page);
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click({ button: "right" });
await roomListView.getByRole("option", { name: "Open room room29" }).click({ button: "right" });
await expect(page.getByRole("menu", { name: "More Options" })).toBeVisible();
});
test("should open the more options menu", { tag: "@screenshot" }, async ({ page, app, user }) => {
const roomListView = getRoomList(page);
const roomItem = roomListView.getByRole("gridcell", { name: "Open room room29" });
const roomItem = roomListView.getByRole("option", { name: "Open room room29" });
await roomItem.hover();
await expect(roomItem).toMatchScreenshot("room-list-item-hover.png");
@@ -97,7 +102,7 @@ test.describe("Room list", () => {
test("should open the notification options menu", { tag: "@screenshot" }, async ({ page, app, user }) => {
const roomListView = getRoomList(page);
const roomItem = roomListView.getByRole("gridcell", { name: "Open room room29" });
const roomItem = roomListView.getByRole("option", { name: "Open room room29" });
await roomItem.hover();
await expect(roomItem).toMatchScreenshot("room-list-item-hover.png");
@@ -117,10 +122,10 @@ test.describe("Room list", () => {
await expect(roomItem.getByTestId("notification-decoration")).not.toBeVisible();
// Put focus on the room list
await roomListView.getByRole("gridcell", { name: "Open room room28" }).click();
await roomListView.getByRole("option", { name: "Open room room28" }).click();
// Scroll to the end of the room list
await app.scrollListToBottom(page.locator(".mx_RoomList_List"));
await app.scrollListToBottom(roomListView);
// The room decoration should have the muted icon
await expect(roomItem.getByTestId("notification-decoration")).toBeVisible();
@@ -139,25 +144,25 @@ test.describe("Room list", () => {
test("should scroll to the current room", async ({ page, app, user }) => {
const roomListView = getRoomList(page);
// Put focus on the room list
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
await roomListView.getByRole("option", { name: "Open room room29" }).click();
// Scroll to the end of the room list
await app.scrollListToBottom(page.locator(".mx_RoomList_List"));
await app.scrollListToBottom(roomListView);
await expect(roomListView.getByRole("gridcell", { name: "Open room room0" })).toBeVisible();
await roomListView.getByRole("gridcell", { name: "Open room room0" }).click();
await expect(roomListView.getByRole("option", { name: "Open room room0" })).toBeVisible();
await roomListView.getByRole("option", { name: "Open room room0" }).click();
const filters = page.getByRole("listbox", { name: "Room list filters" });
await filters.getByRole("option", { name: "People" }).click();
await expect(roomListView.getByRole("gridcell", { name: "Open room room0" })).not.toBeVisible();
await expect(roomListView.getByRole("option", { name: "Open room room0" })).not.toBeVisible();
await filters.getByRole("option", { name: "People" }).click();
await expect(roomListView.getByRole("gridcell", { name: "Open room room0" })).toBeVisible();
await expect(roomListView.getByRole("option", { name: "Open room room0" })).toBeVisible();
});
test.describe("Shortcuts", () => {
test("should select the next room", async ({ page, app, user }) => {
const roomListView = getRoomList(page);
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
await roomListView.getByRole("option", { name: "Open room room29" }).click();
await page.keyboard.press("Alt+ArrowDown");
await expect(page.getByRole("heading", { name: "room28", level: 1 })).toBeVisible();
@@ -165,7 +170,7 @@ test.describe("Room list", () => {
test("should select the previous room", async ({ page, app, user }) => {
const roomListView = getRoomList(page);
await roomListView.getByRole("gridcell", { name: "Open room room28" }).click();
await roomListView.getByRole("option", { name: "Open room room28" }).click();
await page.keyboard.press("Alt+ArrowUp");
await expect(page.getByRole("heading", { name: "room29", level: 1 })).toBeVisible();
@@ -173,7 +178,7 @@ test.describe("Room list", () => {
test("should select the last room", async ({ page, app, user }) => {
const roomListView = getRoomList(page);
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
await roomListView.getByRole("option", { name: "Open room room29" }).click();
await page.keyboard.press("Alt+ArrowUp");
await expect(page.getByRole("heading", { name: "room0", level: 1 })).toBeVisible();
@@ -187,7 +192,10 @@ test.describe("Room list", () => {
await bot.joinRoom(roomId);
await bot.sendMessage(roomId, "I am a robot. Beep.");
await roomListView.getByRole("gridcell", { name: "Open room room20" }).click();
await roomListView.getByRole("option", { name: "Open room room20" }).click();
// Make sure the room with the unread is visible before we press the keyboard action to select it
await expect(roomListView.getByRole("option", { name: "1 notification" })).toBeVisible();
await page.keyboard.press("Alt+Shift+ArrowDown");
@@ -199,8 +207,8 @@ test.describe("Room list", () => {
test("should navigate to the room list", async ({ page, app, user }) => {
const roomListView = getRoomList(page);
const room29 = roomListView.getByRole("gridcell", { name: "Open room room29" });
const room28 = roomListView.getByRole("gridcell", { name: "Open room room28" });
const room29 = roomListView.getByRole("option", { name: "Open room room29" });
const room28 = roomListView.getByRole("option", { name: "Open room room28" });
// open the room
await room29.click();
@@ -219,7 +227,7 @@ test.describe("Room list", () => {
test("should navigate to the notification menu", async ({ page, app, user }) => {
const roomListView = getRoomList(page);
const room29 = roomListView.getByRole("gridcell", { name: "Open room room29" });
const room29 = roomListView.getByRole("option", { name: "Open room room29" });
const moreButton = room29.getByRole("button", { name: "More options" });
const notificationButton = room29.getByRole("button", { name: "Notification options" });
@@ -258,7 +266,7 @@ test.describe("Room list", () => {
await page.getByRole("button", { name: "User menu" }).focus();
const roomListView = getRoomList(page);
const publicRoom = roomListView.getByRole("gridcell", { name: "public room" });
const publicRoom = roomListView.getByRole("option", { name: "public room" });
await expect(publicRoom).toBeVisible();
await expect(publicRoom).toMatchScreenshot("room-list-item-public.png");
@@ -268,7 +276,7 @@ test.describe("Room list", () => {
// @ts-ignore Visibility enum is not accessible
await app.client.createRoom({ name: "low priority room", visibility: "public" });
const roomListView = getRoomList(page);
const publicRoom = roomListView.getByRole("gridcell", { name: "low priority room" });
const publicRoom = roomListView.getByRole("option", { name: "low priority room" });
// Make room low priority
await publicRoom.hover();
@@ -293,7 +301,7 @@ test.describe("Room list", () => {
await page.getByRole("button", { name: "Create video room" }).click();
const roomListView = getRoomList(page);
const videoRoom = roomListView.getByRole("gridcell", { name: "video room" });
const videoRoom = roomListView.getByRole("option", { name: "video room" });
// focus the user menu to avoid to have hover decoration
await page.getByRole("button", { name: "User menu" }).focus();
@@ -312,7 +320,7 @@ test.describe("Room list", () => {
invite: [user.userId],
is_direct: true,
});
const invitedRoom = roomListView.getByRole("gridcell", { name: "invited room" });
const invitedRoom = roomListView.getByRole("option", { name: "invited room" });
await expect(invitedRoom).toBeVisible();
await expect(invitedRoom).toMatchScreenshot("room-list-item-invited.png");
});
@@ -327,7 +335,7 @@ test.describe("Room list", () => {
await bot.sendMessage(roomId, "I am a robot. Beep.");
await bot.sendMessage(roomId, "I am a robot. Beep.");
const room = roomListView.getByRole("gridcell", { name: "2 notifications" });
const room = roomListView.getByRole("option", { name: "2 notifications" });
await expect(room).toBeVisible();
await expect(room.getByTestId("notification-decoration")).toHaveText("2");
await expect(room).toMatchScreenshot("room-list-item-notification.png");
@@ -358,7 +366,7 @@ test.describe("Room list", () => {
);
await bot.sendMessage(roomId, "I am a robot. Beep.");
const room = roomListView.getByRole("gridcell", { name: "mention" });
const room = roomListView.getByRole("option", { name: "mention" });
await expect(room).toBeVisible();
await expect(room).toMatchScreenshot("room-list-item-mention.png");
});
@@ -379,7 +387,7 @@ test.describe("Room list", () => {
await bot.joinRoom(roomId);
await bot.sendMessage(roomId, "I am a robot. Beep.");
const room = roomListView.getByRole("gridcell", { name: "activity" });
const room = roomListView.getByRole("option", { name: "activity" });
await expect(room.getByText("I am a robot. Beep.")).toBeVisible();
await expect(room).toMatchScreenshot("room-list-item-message-preview.png");
});
@@ -406,7 +414,7 @@ test.describe("Room list", () => {
await app.viewRoomById(otherRoomId);
await bot.sendMessage(roomId, "I am a robot. Beep.");
const room = roomListView.getByRole("gridcell", { name: "activity" });
const room = roomListView.getByRole("option", { name: "activity" });
await expect(room.getByTestId("notification-decoration")).toBeVisible();
await expect(room).toMatchScreenshot("room-list-item-activity.png");
});
@@ -418,7 +426,7 @@ test.describe("Room list", () => {
await app.client.inviteUser(roomId, bot.credentials.userId);
await bot.joinRoom(roomId);
const room = roomListView.getByRole("gridcell", { name: "mark as unread" });
const room = roomListView.getByRole("option", { name: "mark as unread" });
await room.hover();
await room.getByRole("button", { name: "More Options" }).click();
await page.getByRole("menuitem", { name: "mark as unread" }).click();
@@ -441,7 +449,7 @@ test.describe("Room list", () => {
await page.getByText("Off").click();
await app.settings.closeDialog();
const room = roomListView.getByRole("gridcell", { name: "silent" });
const room = roomListView.getByRole("option", { name: "silent" });
await expect(room.getByTestId("notification-decoration")).toBeVisible();
await expect(room).toMatchScreenshot("room-list-item-silent.png");
});

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024, 2025 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -57,4 +57,26 @@ test.describe("Location sharing", { tag: "@no-firefox" }, () => {
await expect(page.locator(".mx_Marker")).toBeVisible();
});
test(
"is prompted for and can consent to live location sharing",
{ tag: "@screenshot" },
async ({ page, user, app, axe }) => {
await app.viewRoomById(await app.client.createRoom({}));
const composerOptions = await app.openMessageComposerOptions();
await composerOptions.getByRole("menuitem", { name: "Location", exact: true }).click();
const menu = page.locator(".mx_LocationShareMenu");
await menu.getByRole("button", { name: "My live location" }).click();
await menu.getByLabel("Enable live location sharing").check();
axe.disableRules([
"color-contrast", // XXX: Inheriting colour contrast issues from room view.
"region", // XXX: ContextMenu managed=false does not provide a role.
]);
await expect(axe).toHaveNoViolations();
await expect(menu).toMatchScreenshot("location-live-share-dialog.png");
},
);
});

View File

@@ -186,7 +186,7 @@ test.describe("Login", () => {
await page.goto("/");
await login(page, homeserver, credentials);
await expect(page.getByRole("heading", { name: "Verify this device", level: 1 })).toBeVisible();
await expect(page.getByRole("heading", { name: "Confirm your identity", level: 2 })).toBeVisible();
await expect(page.getByRole("button", { name: "Skip verification for now" })).toBeVisible();
});
@@ -219,7 +219,7 @@ test.describe("Login", () => {
await page.goto("/");
await login(page, homeserver, credentials);
await expect(page.getByRole("heading", { name: "Verify this device", level: 1 })).toBeVisible();
await expect(page.getByRole("heading", { name: "Confirm your identity", level: 2 })).toBeVisible();
await expect(page.getByRole("button", { name: "Skip verification for now" })).toBeVisible();
});
@@ -254,10 +254,10 @@ test.describe("Login", () => {
await page.goto("/");
await login(page, homeserver, credentials);
const h1 = page.getByRole("heading", { name: "Verify this device", level: 1 });
await expect(h1).toBeVisible();
const h2 = page.getByRole("heading", { name: "Confirm your identity", level: 2 });
await expect(h2).toBeVisible();
await expect(h1.locator(".mx_CompleteSecurity_skip")).toHaveCount(0);
await expect(h2.locator(".mx_CompleteSecurity_skip")).toHaveCount(0);
});
test("Continues to show verification prompt after cancelling device verification", async ({
@@ -274,18 +274,18 @@ test.describe("Login", () => {
// Load the page and see that we are asked to verify
await page.goto("/#/welcome");
await login(page, homeserver, credentials);
let h1 = page.getByRole("heading", { name: "Verify this device", level: 1 });
await expect(h1).toBeVisible();
let h2 = page.getByRole("heading", { name: "Confirm your identity", level: 2 });
await expect(h2).toBeVisible();
// Click "Verify with another device"
await page.getByRole("button", { name: "Verify with another device" }).click();
// Click "Use another device"
await page.getByRole("button", { name: "Use another device" }).click();
// Cancel the new dialog
await page.getByRole("button", { name: "Close dialog" }).click();
// Check that we are still being asked to verify
h1 = page.getByRole("heading", { name: "Verify this device", level: 1 });
await expect(h1).toBeVisible();
h2 = page.getByRole("heading", { name: "Confirm your identity", level: 2 });
await expect(h2).toBeVisible();
});
});
@@ -303,18 +303,18 @@ test.describe("Login", () => {
await page.goto("/");
await login(page, homeserver, credentials);
await expect(page.getByRole("heading", { name: "Verify this device", level: 1 })).toBeVisible();
await expect(page.getByRole("heading", { name: "Confirm your identity", level: 2 })).toBeVisible();
// Start the reset process
await page.getByRole("button", { name: "Proceed with reset" }).click();
await page.getByRole("button", { name: "Can't confirm?" }).click();
// First try cancelling and restarting
await page.getByRole("button", { name: "Cancel" }).click();
await page.getByRole("button", { name: "Proceed with reset" }).click();
await page.getByRole("button", { name: "Can't confirm?" }).click();
// Then click outside the dialog and restart
await page.getByRole("link", { name: "Powered by Matrix" }).click({ force: true });
await page.getByRole("button", { name: "Proceed with reset" }).click();
await page.getByRole("button", { name: "Can't confirm?" }).click();
// Finally we actually continue
await page.getByRole("button", { name: "Continue" }).click();

View File

@@ -95,10 +95,6 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
const result = await mas.manage("kill-sessions", userId);
expect(result.output).toContain("Ended 1 active OAuth 2.0 session");
// Workaround for Synapse's 2 minute cache on MAS token validity
// (https://github.com/element-hq/synapse/pull/18231)
await homeserver.restart();
await page.goto("http://localhost:8080");
await expect(
page.getByText("For security, this session has been signed out. Please sign in again."),
@@ -133,8 +129,8 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
await page.getByRole("button", { name: "Continue" }).click();
await page.getByRole("button", { name: "Continue" }).click();
// We should be in (we see an error because we have no recovery key).
await expect(page.getByText("Unable to verify this device")).toBeVisible();
// We should be in
await expect(page.getByText("Confirm your identity")).toBeVisible();
});
test.describe("with force_verification on", () => {
@@ -166,7 +162,7 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
await page.getByRole("button", { name: "Continue" }).click();
// We should be being warned that we need to verify (but we can't)
await expect(page.getByText("Unable to verify this device")).toBeVisible();
await expect(page.getByText("Confirm your identity")).toBeVisible();
// And there should be no way to close this prompt
await expect(page.getByRole("button", { name: "Skip verification for now" })).not.toBeVisible();
@@ -214,7 +210,7 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
await expect(page.getByRole("button", { name: "Skip verification for now" })).not.toBeVisible();
// When we start verifying with another device
await page.getByRole("button", { name: "Verify with another device" }).click();
await page.getByRole("button", { name: "Use another device" }).click();
// And then cancel it
await page.getByRole("button", { name: "Close dialog" }).click();
@@ -231,7 +227,7 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
* Perform interactive emoji verification for a new device.
*/
async function verifyUsingOtherDevice(deviceToVerifyPage: Page, alreadyVerifiedDevicePage: Page) {
await deviceToVerifyPage.getByRole("button", { name: "Verify with another device" }).click();
await deviceToVerifyPage.getByRole("button", { name: "Use another device" }).click();
await alreadyVerifiedDevicePage.getByRole("button", { name: "Verify session" }).click();
await alreadyVerifiedDevicePage.getByRole("button", { name: "Start" }).click();
await alreadyVerifiedDevicePage.getByRole("button", { name: "They match" }).click();

View File

@@ -100,3 +100,51 @@ test.describe("permalinks", () => {
});
});
});
test.describe("triple-click message selection", () => {
test.use({
displayName: "Alice",
});
test("should select entire message line when triple-clicking on message with pills", async ({
page,
app,
user,
bot,
}) => {
await bot.prepareClient();
const roomId = await app.client.createRoom({ name: "Test Room" });
await app.client.inviteUser(roomId, bot.credentials.userId);
await app.viewRoomByName("Test Room");
// Send a message with user and room pills
await app.client.sendMessage(
roomId,
`Testing triple-click message selection. ` +
`User: ${permalinkPrefix}${bot.credentials.userId}, ` +
`Room: ${permalinkPrefix}${roomId}, ` +
`Message: ${permalinkPrefix}${roomId}/$dummy-event, ` +
`and @room mention.`,
);
const timeline = page.locator(".mx_RoomView_timeline");
const messageTile = timeline.locator(".mx_EventTile").last();
// Triple-click on the message body to select its entire content
const messageBody = messageTile.locator(".mx_EventTile_body");
await messageBody.click({ clickCount: 3 });
// Get the expected text content of the message, including pills
const expectedText = await messageBody.innerText();
// Get the currently selected text from the page
const selectedText = await page.evaluate(() => {
const selection = window.getSelection();
return selection ? selection.toString().trim() : "";
});
// Verify that the selected text exactly matches the message content
expect(selectedText).toBe(expectedText);
});
});

View File

@@ -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 { JSHandle, Page } from "@playwright/test";
import type { JSHandle, Locator, Page } from "@playwright/test";
import type { MatrixEvent, Room, IndexedDBStore, ReceiptType } from "matrix-js-sdk/src/matrix";
import { test as base, expect } from "../../element-web-test";
import { type Bot } from "../../pages/bot";
@@ -428,7 +428,7 @@ class Helpers {
}
getRoomListTile(label: string) {
return this.page.getByRole("treeitem", { name: new RegExp("^" + label) });
return this.page.getByRole("option", { name: new RegExp("^Open room " + label) });
}
/**
@@ -446,8 +446,8 @@ class Helpers {
*/
async assertRead(room: RoomRef) {
const tile = this.getRoomListTile(room.name);
await expect(tile.locator(".mx_NotificationBadge_dot")).not.toBeVisible();
await expect(tile.locator(".mx_NotificationBadge_count")).not.toBeVisible();
await expect(tile.getByTestId("notification-decoration")).not.toBeVisible();
await expect(tile).not.toHaveAccessibleName(/with \d* unread message/);
}
/**
@@ -463,15 +463,18 @@ class Helpers {
/**
* Assert a given room is marked as unread (via the room list tile)
* @param room - the name of the room to check
* @param count - the numeric count to assert, or if "." specified then a bold/dot (no count) state is asserted
* @param count - the numeric count to assert
*/
async assertUnread(room: RoomRef, count: number | ".") {
async assertUnread(room: RoomRef, count: number) {
const tile = this.getRoomListTile(room.name);
if (count === ".") {
await expect(tile.locator(".mx_NotificationBadge_dot")).toBeVisible();
} else {
await expect(tile.locator(".mx_NotificationBadge_count")).toHaveText(count.toString());
}
await expect(tile).toBeVisible();
await expect(tile).toHaveAccessibleName(/with \d* unread message/);
}
async unreadCountForRoomTile(tile: Locator): Promise<number> {
const accessibleName = await tile.getAttribute("aria-label");
const match = accessibleName?.match(/(\d+)\s+unread message/);
return match ? parseInt(match[1], 10) : 0;
}
/**
@@ -487,7 +490,7 @@ class Helpers {
// .toBeLessThan doesn't have a retry mechanism, so we use .poll
await expect
.poll(async () => {
return parseInt(await tile.locator(".mx_NotificationBadge_count").textContent(), 10);
return this.unreadCountForRoomTile(tile);
})
.toBeLessThan(lessThan);
}
@@ -505,7 +508,7 @@ class Helpers {
// .toBeGreaterThan doesn't have a retry mechanism, so we use .poll
await expect
.poll(async () => {
return parseInt(await tile.locator(".mx_NotificationBadge_count").textContent(), 10);
return this.unreadCountForRoomTile(tile);
})
.toBeGreaterThan(greaterThan);
}
@@ -596,24 +599,15 @@ class Helpers {
await button.click();
}
/**
* Toggle the `Show rooms with unread messages first` option for the room list
*/
async toggleRoomUnreadOrder() {
await this.toggleRoomListMenu();
await this.page.getByText("Show rooms with unread messages first").click();
// Close contextual menu
await this.page.locator(".mx_ContextualMenu_background").click();
}
/**
* Assert that the room list is ordered as expected
* @param rooms
*/
async assertRoomListOrder(rooms: Array<{ name: string }>) {
const roomList = this.page.locator(".mx_RoomTile_title");
const roomListContainer = this.page.getByTestId("room-list");
const roomTiles = roomListContainer.getByRole("option");
for (const [i, room] of rooms.entries()) {
await expect(roomList.nth(i)).toHaveText(room.name);
await expect(roomTiles.nth(i)).toHaveAccessibleName(new RegExp(`${room.name}`));
}
}
}

View File

@@ -112,7 +112,7 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
const main3 = await sendMessage(bot);
// (So the room starts off unread)
await expect(page.getByLabel(`${otherRoomName} 3 unread messages.`)).toBeVisible();
await expect(page.getByLabel(`${otherRoomName} with 3 unread messages.`)).toBeVisible();
// When we send a threaded receipt for the last event in main
// And an unthreaded receipt for an earlier event
@@ -147,13 +147,13 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
await sendMessage(bot);
// (The room starts off unread)
await expect(page.getByLabel(`${otherRoomName} 3 unread messages.`)).toBeVisible();
await expect(page.getByLabel(`${otherRoomName} with 3 unread messages.`)).toBeVisible();
// When we send a threaded receipt for the second-last event in main
await sendThreadedReadReceipt(app, main2);
// Then the room has only one unread
await expect(page.getByLabel(`${otherRoomName} 1 unread message.`)).toBeVisible();
await expect(page.getByLabel(`${otherRoomName} with 1 unread message.`)).toBeVisible();
});
test("Considers room read if there is only a main thread and we have a main receipt", async ({
@@ -166,7 +166,7 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
await sendMessage(bot);
const main3 = await sendMessage(bot);
// (The room starts off unread)
await expect(page.getByLabel(`${otherRoomName} 3 unread messages.`)).toBeVisible();
await expect(page.getByLabel(`${otherRoomName} with 3 unread messages.`)).toBeVisible();
// When we send a threaded receipt for the last event in main
await sendThreadedReadReceipt(app, main3);
@@ -186,7 +186,7 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
const thread1a = await botSendThreadMessage(bot, main1.event_id);
await botSendThreadMessage(bot, main1.event_id);
// 1 unread on the main thread, 2 in the new thread that aren't shown
await expect(page.getByLabel(`${otherRoomName} 1 unread message.`)).toBeVisible();
await expect(page.getByLabel(`${otherRoomName} with 1 unread message.`)).toBeVisible();
// When we send receipts for main, and the second-last in the thread
await sendThreadedReadReceipt(app, main1);
@@ -203,7 +203,7 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
await botSendThreadMessage(bot, main1.event_id);
const thread1b = await botSendThreadMessage(bot, main1.event_id);
// 1 unread on the main thread, 2 in the new thread which don't show
await expect(page.getByLabel(`${otherRoomName} 1 unread message.`)).toBeVisible();
await expect(page.getByLabel(`${otherRoomName} with 1 unread message.`)).toBeVisible();
// When we send receipts for main, and the last in the thread
await sendThreadedReadReceipt(app, main1);
@@ -226,7 +226,7 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
const thread1a = await botSendThreadMessage(bot, main1.event_id);
await botSendThreadMessage(bot, main1.event_id);
// 1 unread on the main thread, 2 in the new thread which don't count
await expect(page.getByLabel(`${otherRoomName} 1 unread message.`)).toBeVisible();
await expect(page.getByLabel(`${otherRoomName} with 1 unread message.`)).toBeVisible();
// When we send an unthreaded receipt for the second-last in the thread
await sendUnthreadedReadReceipt(app, thread1a);
@@ -251,7 +251,7 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
const thread1b = await botSendThreadMessage(bot, main1.event_id);
await sendMessage(bot);
// 2 unreads on the main thread, 2 in the new thread which don't count
await expect(page.getByLabel(`${otherRoomName} 2 unread messages.`)).toBeVisible();
await expect(page.getByLabel(`${otherRoomName} with 2 unread messages.`)).toBeVisible();
// When we send an unthreaded receipt for the last in the thread
await sendUnthreadedReadReceipt(app, thread1b);
@@ -259,7 +259,7 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
// Then the room has only one unread - the one in the
// main thread, because it is later than the unthreaded
// receipt.
await expect(page.getByLabel(`${otherRoomName} 1 unread message.`)).toBeVisible();
await expect(page.getByLabel(`${otherRoomName} with 1 unread message.`)).toBeVisible();
});
/**
@@ -291,7 +291,9 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
const uriEncodedLastMessageId = encodeURIComponent(lastMessageId);
// wait until all messages have been received
await expect(page.getByLabel(`${otherRoomName} ${sendMessageResponses.length} unread messages.`)).toBeVisible();
await expect(
page.getByLabel(`${otherRoomName} with ${sendMessageResponses.length} unread messages.`),
).toBeVisible();
// switch to the room with the messages
await page.goto(`/#/room/${otherRoomId}`);

View File

@@ -12,7 +12,7 @@ import { test } from ".";
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 ({
test("Rooms with unread messages appear at the top of room list with default 'activity' ordering", async ({
roomAlpha: room1,
roomBeta: room2,
util,
@@ -22,15 +22,18 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
await util.goTo(room2);
// Display the unread first room
await util.toggleRoomUnreadOrder();
await util.receiveMessages(room1, ["Msg1"]);
await page.reload();
// switch rooms so they can re-order in the list
await util.goTo(room1);
// Room 1 has an unread message and should be displayed first
// (as the default is to sort by activity)
await util.assertRoomListOrder([room1, room2]);
});
test("Rooms with unread threads appear at the top of room list if 'unread first' is selected", async ({
test("Rooms with unread threads appear at the top of room list with default 'activity' order", async ({
roomAlpha: room1,
roomBeta: room2,
util,
@@ -42,7 +45,6 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
await util.assertRead(room1);
// Display the unread first room
await util.toggleRoomUnreadOrder();
await util.receiveMessages(room1, [msg.threadedOff("Msg1", "Resp1")]);
await util.saveAndReload();

View File

@@ -30,9 +30,8 @@ export class Helpers {
/**
* Get the release announcement with the given name.
* @param name
* @private
*/
private getReleaseAnnouncement(name: string) {
public getReleaseAnnouncement(name: string) {
return this.page.getByRole("dialog", { name });
}
@@ -55,16 +54,6 @@ export class Helpers {
assertReleaseAnnouncementIsNotVisible(name: string) {
return expect(this.getReleaseAnnouncement(name)).not.toBeVisible();
}
/**
* Mark the release announcement with the given name as read.
* If the release announcement is not visible, this will throw an error.
* @param name
*/
async markReleaseAnnouncementAsRead(name: string) {
const dialog = this.getReleaseAnnouncement(name);
await dialog.getByRole("button", { name: "Ok" }).click();
}
}
export { expect };

View File

@@ -22,25 +22,28 @@ test.describe("Release announcement", () => {
await app.viewRoomById(roomId);
await use({ roomId });
},
labsFlags: ["feature_new_room_list"],
});
test(
"should display the pinned messages release announcement",
"should display the new room list release announcement",
{ tag: "@screenshot" },
async ({ page, app, room, util }) => {
await app.toggleRoomInfoPanel();
// dismiss the toast so the announcement appears
await page.getByRole("button", { name: "Dismiss" }).click();
const name = "All new pinned messages";
const name = "Chats has a new look!";
// The release announcement should be displayed
await util.assertReleaseAnnouncementIsVisible(name);
// Hide the release announcement
await util.markReleaseAnnouncementAsRead(name);
const dialog = util.getReleaseAnnouncement(name);
await dialog.getByRole("button", { name: "Next" }).click();
await util.assertReleaseAnnouncementIsNotVisible(name);
await page.reload();
await app.toggleRoomInfoPanel();
await expect(page.getByRole("menuitem", { name: "Pinned messages" })).toBeVisible();
await expect(page.getByRole("button", { name: "Room options" })).toBeVisible();
// Check that once the release announcement has been marked as viewed, it does not appear again
await util.assertReleaseAnnouncementIsNotVisible(name);
},

View File

@@ -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";
@@ -189,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");
});
});
});

View File

@@ -0,0 +1,113 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2022, 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { SettingLevel } from "../../../src/settings/SettingLevel";
import { UIFeature } from "../../../src/settings/UIFeature";
import { test, expect } from "../../element-web-test";
const name = "Test room";
const topic = "A decently explanatory topic for a test room.";
test.describe("Create Room", () => {
test.use({ displayName: "Jim" });
test(
"should create a public room with name, topic & address set",
{ tag: "@screenshot" },
async ({ page, user, app, axe }) => {
const dialog = await app.openCreateRoomDialog();
// Fill name & topic
await dialog.getByRole("textbox", { name: "Name" }).fill(name);
await dialog.getByRole("textbox", { name: "Topic" }).fill(topic);
// Change room to public
await dialog.getByRole("button", { name: "Room visibility" }).click();
await dialog.getByRole("option", { name: "Public room" }).click();
// Fill room address
await dialog.getByRole("textbox", { name: "Room address" }).fill("test-create-room-standard");
axe.disableRules("color-contrast"); // XXX: Inheriting colour contrast issues from room view.
await expect(axe).toHaveNoViolations();
// Snapshot it
await expect(dialog).toMatchScreenshot("create-room.png");
// Submit
await dialog.getByRole("button", { name: "Create room" }).click();
await expect(page).toHaveURL(new RegExp(`/#/room/#test-create-room-standard:${user.homeServer}`));
const header = page.locator(".mx_RoomHeader");
await expect(header).toContainText(name);
},
);
test("should allow us to start a chat and show encryption state", async ({ page, user, app }) => {
await page.getByRole("button", { name: "Add", exact: true }).click();
await page.getByRole("menuitem", { name: "Start chat" }).click();
await page.getByTestId("invite-dialog-input").fill(user.userId);
await page.getByRole("button", { name: "Go" }).click();
await expect(page.getByText("Encryption enabled")).toBeVisible();
await expect(page.getByText("Send your first message to")).toBeVisible();
const composer = page.getByRole("region", { name: "Message composer" });
await expect(composer.getByRole("textbox", { name: "Send a message…" })).toBeVisible();
});
test("should create a video room", { tag: "@screenshot" }, async ({ page, user, app }) => {
await app.settings.setValue("feature_video_rooms", null, SettingLevel.DEVICE, true);
const dialog = await app.openCreateRoomDialog("New video room");
// Fill name & topic
await dialog.getByRole("textbox", { name: "Name" }).fill(name);
await dialog.getByRole("textbox", { name: "Topic" }).fill(topic);
// Change room to public
await dialog.getByRole("button", { name: "Room visibility" }).click();
await dialog.getByRole("option", { name: "Public room" }).click();
// Fill room address
await dialog.getByRole("textbox", { name: "Room address" }).fill("test-create-room-video");
// Snapshot it
await expect(dialog).toMatchScreenshot("create-video-room.png");
// Submit
await dialog.getByRole("button", { name: "Create video room" }).click();
await expect(page).toHaveURL(new RegExp(`/#/room/#test-create-room-video:${user.homeServer}`));
const header = page.locator(".mx_RoomHeader");
await expect(header).toContainText(name);
});
test.describe("Should hide public room option if not allowed", () => {
test.use({
config: {
setting_defaults: {
[UIFeature.AllowCreatingPublicRooms]: false,
},
},
});
test("should disallow creating public rooms", { tag: "@screenshot" }, async ({ page, user, app, axe }) => {
const dialog = await app.openCreateRoomDialog();
// Fill name & topic
await dialog.getByRole("textbox", { name: "Name" }).fill(name);
await dialog.getByRole("textbox", { name: "Topic" }).fill(topic);
axe.disableRules("color-contrast"); // XXX: Inheriting colour contrast issues from room view.
await expect(axe).toHaveNoViolations();
// Snapshot it
await expect(dialog).toMatchScreenshot("create-room-no-public.png");
// Submit
await dialog.getByRole("button", { name: "Create room" }).click();
await expect(page).toHaveURL(new RegExp(`/#/room/!.+`));
const header = page.locator(".mx_RoomHeader");
await expect(header).toContainText(name);
});
});
});

View File

@@ -15,6 +15,11 @@ import { type ElementAppPage } from "../../pages/ElementAppPage";
test.describe("Room Header", () => {
test.use({
displayName: "Sakura",
config: {
features: {
feature_new_room_list: false,
},
},
});
test.describe("with feature_notifications enabled", () => {
@@ -23,7 +28,7 @@ test.describe("Room Header", () => {
});
test("should render default buttons properly", { tag: "@screenshot" }, async ({ page, app, user }) => {
await app.client.createRoom({ name: "Test Room" });
await app.viewRoomByName("Test Room");
await app.viewRoomByNameOnOldRoomList("Test Room");
const header = page.locator(".mx_RoomHeader");
@@ -37,11 +42,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();
@@ -64,7 +66,7 @@ test.describe("Room Header", () => {
"officia deserunt mollit anim id est laborum.";
await app.client.createRoom({ name: LONG_ROOM_NAME });
await app.viewRoomByName(LONG_ROOM_NAME);
await app.viewRoomByNameOnOldRoomList(LONG_ROOM_NAME);
const header = page.locator(".mx_RoomHeader");
// Wait until the room name is set
@@ -89,7 +91,7 @@ test.describe("Room Header", () => {
test("should render room header icon correctly", { tag: "@screenshot" }, async ({ page, app, user }) => {
await app.client.createRoom({ name: "Test Room", visibility: "public" as Visibility });
await app.viewRoomByName("Test Room");
await app.viewRoomByNameOnOldRoomList("Test Room");
const header = page.locator(".mx_RoomHeader");
@@ -109,7 +111,7 @@ test.describe("Room Header", () => {
await page.getByRole("button", { name: "Create video room" }).click();
await app.viewRoomByName("Test video room");
await app.viewRoomByNameOnOldRoomList("Test video room");
};
test.describe("and with feature_notifications enabled", () => {

View File

@@ -34,7 +34,7 @@ test.describe("Mark as Unread", () => {
await bot.sendMessage(roomId, "I am a robot. Beep.");
// Regular notification on new message
await expect(page.getByLabel(TEST_ROOM_NAME + " 1 unread message.")).toBeVisible();
await expect(page.getByLabel(`Open room ${TEST_ROOM_NAME} with 1 unread message.`)).toBeVisible();
await expect(page).toHaveTitle("Element [1]");
await page.goto("/#/room/" + roomId);
@@ -48,9 +48,12 @@ test.describe("Mark as Unread", () => {
const roomTile = page.getByLabel(TEST_ROOM_NAME);
await roomTile.focus();
await roomTile.getByRole("button", { name: "Room options" }).click();
await roomTile.getByRole("button", { name: "More Options" }).click();
await page.getByRole("menuitem", { name: "Mark as unread" }).click();
await expect(page.getByLabel(TEST_ROOM_NAME + " Unread messages.")).toBeVisible();
// focus the user menu to avoid to have hover decoration
await page.getByRole("button", { name: "User menu" }).focus();
await expect(roomTile.getByTestId("notification-decoration")).toBeVisible();
});
});

View File

@@ -85,7 +85,7 @@ class Helpers {
* Return the system theme toggle
*/
getMatchSystemThemeCheckbox() {
return this.getThemePanel().getByRole("checkbox", { name: "Match system theme" });
return this.getThemePanel().getByRole("switch", { name: "Match system theme" });
}
/**
@@ -219,7 +219,7 @@ class Helpers {
* Return the compact layout checkbox
*/
getCompactLayoutCheckbox() {
return this.getMessageLayoutPanel().getByRole("checkbox", { name: "Show compact text and messages" });
return this.getMessageLayoutPanel().getByRole("switch", { name: "Show compact text and messages" });
}
/**

View File

@@ -117,7 +117,7 @@ test.describe("Encryption tab", () => {
await verifySession(app, recoveryKey.encodedPrivateKey);
await util.openEncryptionTab();
await page.getByRole("checkbox", { name: "Allow key storage" }).click();
await page.getByRole("switch", { name: "Allow key storage" }).click();
await expect(
page.getByRole("heading", { name: "Are you sure you want to turn off key storage and delete it?" }),
@@ -136,7 +136,7 @@ test.describe("Encryption tab", () => {
await page.getByRole("button", { name: "Delete key storage" }).click();
await expect(page.getByRole("checkbox", { name: "Allow key storage" })).not.toBeChecked();
await expect(page.getByRole("switch", { name: "Allow key storage" })).not.toBeChecked();
for (const prom of deleteRequestPromises) {
const request = await prom;
@@ -160,15 +160,15 @@ test.describe("Encryption tab", () => {
// We will reset our identity
await settings.getByRole("button", { name: "Verify this device" }).click();
await page.getByRole("button", { name: "Proceed with reset" }).click();
await page.getByRole("button", { name: "Can't confirm?" }).click();
// First try cancelling and restarting
await page.getByRole("button", { name: "Cancel" }).click();
await page.getByRole("button", { name: "Proceed with reset" }).click();
await page.getByRole("button", { name: "Can't confirm?" }).click();
// Then click outside the dialog and restart
await page.locator("li").filter({ hasText: "Encryption" }).click({ force: true });
await page.getByRole("button", { name: "Proceed with reset" }).click();
await page.getByRole("button", { name: "Can't confirm?" }).click();
// Finally we actually continue
await page.getByRole("button", { name: "Continue" }).click();

View File

@@ -43,7 +43,7 @@ class Helpers {
*/
async verifyDevice(recoveryKey: GeneratedSecretStorageKey) {
// Select the security phrase
await this.page.getByRole("button", { name: "Verify with Recovery Key" }).click();
await this.page.getByRole("button", { name: "Use recovery key" }).click();
await this.enterRecoveryKey(recoveryKey);
await this.page.getByRole("button", { name: "Done" }).click();
}
@@ -104,7 +104,10 @@ class Helpers {
const clipboardContent = await this.app.getClipboard();
await dialog.getByRole("textbox").fill(clipboardContent);
await dialog.getByRole("button", { name: confirmButtonLabel }).click();
const button = dialog.getByRole("button", { name: confirmButtonLabel });
await button.click();
// Button should disable immediately after clicking.
await expect(button).toBeDisabled();
await expect(this.getEncryptionRecoverySection()).toMatchScreenshot("default-recovery.png");
}
}

View File

@@ -0,0 +1,28 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { test, expect } from "../../../element-web-test";
import { SettingLevel } from "../../../../src/settings/SettingLevel";
test.describe("Notifications 2 tab", () => {
test.use({
displayName: "Alice",
});
test("should display notification settings", { tag: "@screenshot" }, async ({ page, app, user, axe }) => {
await app.settings.setValue("feature_notification_settings2", null, SettingLevel.DEVICE, true);
await page.setViewportSize({ width: 1024, height: 2000 });
const settings = await app.settings.openUserSettings("Notifications");
axe.disableRules("color-contrast"); // XXX: Inheriting colour contrast issues from room view.
await expect(axe).toHaveNoViolations();
await expect(settings).toMatchScreenshot("standard-notifications-2-settings.png", {
// Mask the mxid.
mask: [settings.locator("#mx_NotificationSettings2_MentionCheckbox span")],
});
});
});

View File

@@ -0,0 +1,25 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { test, expect } from "../../../element-web-test";
test.describe("Notifications tab", () => {
test.use({
displayName: "Alice",
});
test("should display notification settings", { tag: "@screenshot" }, async ({ page, app, user, axe }) => {
await page.setViewportSize({ width: 1024, height: 1400 });
const settings = await app.settings.openUserSettings("Notifications");
await settings.getByLabel("Enable notifications for this account").check();
await settings.getByLabel("Enable notifications for this device").check();
axe.disableRules("color-contrast"); // XXX: Inheriting colour contrast issues from room view.
await expect(axe).toHaveNoViolations();
await expect(settings).toMatchScreenshot("standard-notification-settings.png");
});
});

View File

@@ -8,7 +8,7 @@
import { type Locator } from "@playwright/test";
import { test, expect } from "../../element-web-test";
import { test, expect } from "../../../element-web-test";
test.describe("Roles & Permissions room settings tab", () => {
const roomName = "Test room";

View File

@@ -0,0 +1,121 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import { type Locator } from "@playwright/test";
import { test, expect } from "../../../element-web-test";
test.describe("Roles & Permissions room settings tab", () => {
const roomName = "Test room";
test.use({
displayName: "Alice",
});
let settings: Locator;
test.beforeEach(async ({ user, app }) => {
await app.client.createRoom({
name: roomName,
power_level_content_override: {
events: {
// Set the join rules as lower than the history vis to test an edge case.
["m.room.join_rules"]: 80,
["m.room.history_visibility"]: 100,
},
},
});
await app.viewRoomByName(roomName);
settings = await app.settings.openRoomSettings("Security & Privacy");
});
test(
"should be able to toggle on encryption in a room",
{ tag: "@screenshot" },
async ({ page, app, user, axe }) => {
await page.setViewportSize({ width: 1024, height: 1400 });
const encryptedToggle = settings.getByLabel("Encrypted");
await encryptedToggle.click();
// Accept the dialog.
await page.getByRole("button", { name: "Ok " }).click();
await expect(encryptedToggle).toBeChecked();
await expect(encryptedToggle).toBeDisabled();
await settings.getByLabel("Only send messages to verified users.").check();
axe.disableRules("color-contrast"); // XXX: Inheriting colour contrast issues from room view.
await expect(axe).toHaveNoViolations();
await expect(settings).toMatchScreenshot("room-security-settings.png");
},
);
test(
"should automatically adjust history visibility when a room is changed from public to private",
{ tag: "@screenshot" },
async ({ page, app, user, axe }) => {
await page.setViewportSize({ width: 1024, height: 1400 });
const settingsGroupAccess = page.getByRole("group", { name: "Access" });
const settingsGroupHistory = page.getByRole("group", { name: "Who can read history?" });
await settingsGroupAccess.getByText("Public").click();
await settingsGroupHistory.getByText("Anyone").click();
// Test that we have the warning appear.
axe.disableRules("color-contrast"); // XXX: Inheriting colour contrast issues from room view.
await expect(axe).toHaveNoViolations();
await expect(settings).toMatchScreenshot("room-security-settings-world-readable.png");
await settingsGroupAccess.getByText("Private (invite only)").click();
// Element should have automatically set the room to "sharing" history visibility
await expect(
settingsGroupHistory.getByText("Members only (since the point in time of selecting this option)"),
).toBeChecked();
},
);
test(
"should disallow changing from public to private if the user cannot alter history",
{ tag: "@screenshot" },
async ({ page, app, user, bot }) => {
await page.setViewportSize({ width: 1024, height: 1400 });
const settingsGroupAccess = page.getByRole("group", { name: "Access" });
const settingsGroupHistory = page.getByRole("group", { name: "Who can read history?" });
await settingsGroupAccess.getByText("Public").click();
await settingsGroupHistory.getByText("Anyone").click();
// De-op ourselves
await app.settings.switchTab("Roles & Permissions");
// Wait for the permissions list to be visible
await expect(settings.getByRole("heading", { name: "Permissions" })).toBeVisible();
const ourComboBox = settings.getByRole("combobox", { name: user.userId });
await ourComboBox.selectOption("Custom level");
const ourPl = settings.getByRole("spinbutton", { name: user.userId });
await ourPl.fill("80");
await ourPl.blur(); // Shows a warning on
// Accept the de-op
await page.getByRole("button", { name: "Continue" }).click();
await settings.getByRole("button", { name: "Apply", disabled: false }).click();
await app.settings.switchTab("Security & Privacy");
await settingsGroupAccess.getByText("Private (invite only)").click();
// Element should have automatically set the room to "sharing" history visibility
const errorDialog = page.getByRole("heading", { name: "Cannot make room private" });
await expect(errorDialog).toBeVisible();
await errorDialog.getByLabel("OK");
await expect(settingsGroupHistory.getByText("Anyone")).toBeChecked();
},
);
});

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import { type Locator } from "@playwright/test";
import { test, expect } from "../../../element-web-test";
import { SettingLevel } from "../../../../src/settings/SettingLevel";
test.describe("Voice & Video room settings tab", () => {
const roomName = "Test room";
test.use({
displayName: "Alice",
});
let settings: Locator;
test.beforeEach(async ({ user, app, page }) => {
// Execute client actions before setting, as the setting will force a reload.
await app.client.createRoom({ name: roomName });
await app.settings.setValue("feature_group_calls", null, SettingLevel.DEVICE, true);
await app.viewRoomByName(roomName);
settings = await app.settings.openRoomSettings("Voice & Video");
});
test(
"should be able to toggle on Element Call in the room",
{ tag: "@screenshot" },
async ({ page, app, user, axe }) => {
await page.setViewportSize({ width: 1024, height: 1400 });
const callToggle = settings.getByLabel("Enable Element Call as an additional calling option in this room");
await callToggle.check();
axe.disableRules("color-contrast"); // XXX: Inheriting colour contrast issues from room view.
await expect(axe).toHaveNoViolations();
await expect(settings).toMatchScreenshot("room-video-settings.png");
},
);
});

View File

@@ -41,6 +41,18 @@ test.describe("Security user settings tab", () => {
});
});
test("should render the security tab", { tag: "@screenshot" }, async ({ app, page, user }) => {
await page.setViewportSize({ width: 1024, height: 1400 });
const tab = await app.settings.openUserSettings("Security");
await expect(tab).toMatchScreenshot("security-settings-tab.png", {
mask: [
// Contains IM name.
tab.locator("#mx_SetIntegrationManager_BodyText"),
tab.locator("#mx_SetIntegrationManager_ManagerName"),
],
});
});
test("should be able to set an ID server", async ({ app, context, user, page }) => {
const tab = await app.settings.openUserSettings("Security");

View File

@@ -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();

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { type Page, type Request } from "@playwright/test";
import { type Locator, type Page, type Request } from "@playwright/test";
import { test as base, expect } from "../../element-web-test";
import type { ElementAppPage } from "../../pages/ElementAppPage";
@@ -38,7 +38,7 @@ const test = base.extend<{
test.describe("Sliding Sync", () => {
const checkOrder = async (wantOrder: string[], page: Page) => {
await expect(page.getByRole("group", { name: "Rooms" }).locator(".mx_RoomTile_title")).toHaveText(wantOrder);
await expect(page.getByTestId("room-list").locator(".mx_RoomListItemView_text")).toHaveText(wantOrder);
};
const bumpRoom = async (roomId: string, app: ElementAppPage) => {
@@ -50,6 +50,18 @@ test.describe("Sliding Sync", () => {
});
};
function getPrimaryFilters(page: Page): Locator {
return page.getByTestId("primary-filters");
}
function getRoomOptionsMenu(page: Page): Locator {
return page.getByRole("button", { name: "Room Options" });
}
function getFilterExpandButton(page: Page): Locator {
return getPrimaryFilters(page).getByRole("button", { name: "Expand filter list" });
}
test.use({
config: {
features: {
@@ -69,20 +81,15 @@ test.describe("Sliding Sync", () => {
// create rooms and check room names are correct
for (const fruit of ["Apple", "Pineapple", "Orange"]) {
await app.client.createRoom({ name: fruit });
await expect(page.getByRole("treeitem", { name: fruit })).toBeVisible();
await expect(page.getByRole("option", { name: `Open room ${fruit}` })).toBeVisible();
}
const roomList = page.getByTestId("room-list");
// Check count, 3 fruits + 1 testRoom = 4
await expect(page.locator(".mx_RoomSublist_tiles").getByRole("treeitem")).toHaveCount(4);
await expect(roomList.getByRole("option")).toHaveCount(4);
await checkOrder(["Orange", "Pineapple", "Apple", "Test Room"], page);
const locator = page.getByRole("group", { name: "Rooms" }).locator(".mx_RoomSublist_headerContainer");
await locator.hover();
await locator.getByRole("button", { name: "List options" }).click();
// force click as the radio button's size is zero
await page.getByRole("menuitemradio", { name: "A-Z" }).dispatchEvent("click");
await expect(page.locator(".mx_StyledRadioButton_checked").getByText("A-Z")).toBeVisible();
await getRoomOptionsMenu(page).click();
await page.getByRole("menuitemradio", { name: "A-Z" }).click();
await checkOrder(["Apple", "Orange", "Pineapple", "Test Room"], page);
});
@@ -93,11 +100,11 @@ test.describe("Sliding Sync", () => {
for (const fruit of ["Apple", "Pineapple", "Orange"]) {
const id = await app.client.createRoom({ name: fruit });
roomIds.push(id);
await expect(page.getByRole("treeitem", { name: fruit })).toBeVisible();
await expect(page.getByRole("option", { name: `Open room ${fruit}` })).toBeVisible();
}
// Select the Test Room
await page.getByRole("treeitem", { name: "Test Room" }).click();
await page.getByRole("option", { name: "Open room Test Room" }).click();
const [apple, pineapple, orange] = roomIds;
await checkOrder(["Orange", "Pineapple", "Apple", "Test Room"], page);
await bumpRoom(apple, app);
@@ -116,7 +123,7 @@ test.describe("Sliding Sync", () => {
for (const fruit of ["Apple", "Pineapple", "Orange"]) {
const id = await app.client.createRoom({ name: fruit });
roomIds.push(id);
await expect(page.getByRole("treeitem", { name: fruit })).toBeVisible();
await expect(page.getByRole("option", { name: `Open room ${fruit}` })).toBeVisible();
}
// Given a list of Orange, Pineapple, Apple - if Pineapple is active and a message is sent in Apple, the list should
@@ -124,7 +131,7 @@ test.describe("Sliding Sync", () => {
// be Apple, Orange Pineapple - only when you click on a different room do things reshuffle.
// Select the Pineapple room
await page.getByRole("treeitem", { name: "Pineapple" }).click();
await page.getByRole("option", { name: "Open room Pineapple" }).click();
await checkOrder(["Orange", "Pineapple", "Apple", "Test Room"], page);
// Move Apple
@@ -132,7 +139,7 @@ test.describe("Sliding Sync", () => {
await checkOrder(["Apple", "Pineapple", "Orange", "Test Room"], page);
// Select the Test Room
await page.getByRole("treeitem", { name: "Test Room" }).click();
await page.getByRole("option", { name: "Open room Test Room" }).click();
// the rooms reshuffle to match reality
await checkOrder(["Apple", "Orange", "Pineapple", "Test Room"], page);
@@ -142,25 +149,20 @@ test.describe("Sliding Sync", () => {
// send a message in the test room: unread notification count should increment
await bob.sendMessage(testRoom.roomId, "Hello World");
const treeItemLocator1 = page.getByRole("treeitem", { name: "Test Room 1 unread message." });
await expect(treeItemLocator1.locator(".mx_NotificationBadge_count")).toHaveText("1");
// await expect(page.locator(".mx_NotificationBadge")).not.toHaveClass("mx_NotificationBadge_highlighted");
await expect(treeItemLocator1.locator(".mx_NotificationBadge")).not.toHaveClass(
/mx_NotificationBadge_highlighted/,
);
const itemLocator1 = page.getByRole("option", { name: "Open room David Langley with 1 unread message." });
await expect(itemLocator1.getByTestId("notification-decoration")).toHaveText("1");
// send an @mention: highlight count (red) should be 2.
await bob.sendMessage(testRoom.roomId, `Hello ${user.displayName}`);
const treeItemLocator2 = page.getByRole("treeitem", {
name: "Test Room 2 unread messages including mentions.",
const itemLocator2 = page.getByRole("treeitem", {
name: "Open room Test Room 2 unread messages including mentions.",
});
await expect(treeItemLocator2.locator(".mx_NotificationBadge_count")).toHaveText("2");
await expect(treeItemLocator2.locator(".mx_NotificationBadge")).toHaveClass(/mx_NotificationBadge_highlighted/);
await expect(itemLocator2.getByTestId("notification-decoration")).toHaveText("2");
// click on the room, the notif counts should disappear
await page.getByRole("treeitem", { name: "Test Room 2 unread messages including mentions." }).click();
await page.getByRole("option", { name: "Open room Test Room 2 unread messages including mentions." }).click();
await expect(
page.getByRole("treeitem", { name: "Test Room" }).locator("mx_NotificationBadge_count"),
page.getByRole("option", { name: "Open room Test Room" }).getByTestId("notification-decoration"),
).not.toBeAttached();
});
@@ -175,7 +177,9 @@ test.describe("Sliding Sync", () => {
// wait for this message to arrive, tell by the room list resorting
await checkOrder(["Test Room", "Dummy"], page);
await expect(page.getByRole("treeitem", { name: "Test Room" }).locator(".mx_NotificationBadge")).toBeAttached();
await expect(
page.getByRole("option", { name: "Open room Test Room" }).getByTestId("notification-decoration"),
).toBeAttached();
});
test("should update user settings promptly", async ({ page, app }) => {
@@ -193,7 +197,7 @@ test.describe("Sliding Sync", () => {
for (const fruit of ["Apple", "Pineapple", "Orange"]) {
const id = await app.client.createRoom({ name: fruit });
roomIds.push(id);
await expect(page.getByRole("treeitem", { name: fruit })).toBeVisible();
await expect(page.getByRole("option", { name: `Open room ${fruit}` })).toBeVisible();
}
const [roomAId, roomPId] = roomIds;
@@ -206,7 +210,7 @@ test.describe("Sliding Sync", () => {
// Select the Test Room and wait for playwright to get the request
const [request] = await Promise.all([
page.waitForRequest(matchRoomSubRequest(roomAId)),
page.getByRole("treeitem", { name: "Apple", exact: true }).click(),
page.getByRole("option", { name: "Open room Apple", exact: true }).click(),
]);
const roomSubscriptions = request.postDataJSON().room_subscriptions;
expect(roomSubscriptions, "room_subscriptions is object").toBeDefined();
@@ -214,7 +218,7 @@ test.describe("Sliding Sync", () => {
// Switch to another room and wait for playwright to get the request
await Promise.all([
page.waitForRequest(matchRoomSubRequest(roomPId)),
page.getByRole("treeitem", { name: "Pineapple", exact: true }).click(),
page.getByRole("option", { name: "Open room Pineapple", exact: true }).click(),
]);
});
@@ -240,34 +244,29 @@ test.describe("Sliding Sync", () => {
{ roomNames, clientUserId },
);
await expect(
page.getByRole("group", { name: "Invites" }).locator(".mx_RoomSublist_tiles").getByRole("treeitem"),
).toHaveCount(3);
await getFilterExpandButton(page).click();
const primaryFilters = getPrimaryFilters(page);
await primaryFilters.getByRole("option", { name: "Invites" }).click();
await expect(page.getByTestId("room-list").getByRole("option")).toHaveCount(3);
// Select the room to join
await page.getByRole("treeitem", { name: "Room to Join" }).click();
await page.getByRole("option", { name: "Open room Room to Join" }).click();
// Accept the invite
await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click();
await checkOrder(["Room to Join", "Test Room"], page);
await checkOrder(["Room to Rescind", "Room to Reject"], page);
// Select the room to reject
await page.getByRole("treeitem", { name: "Room to Reject" }).click();
await page.getByRole("option", { name: "Open room Room to Reject" }).click();
// Decline the invite
await page.locator(".mx_RoomView").getByRole("button", { name: "Decline", exact: true }).click();
await expect(
page.getByRole("group", { name: "Invites" }).locator(".mx_RoomSublist_tiles").getByRole("treeitem"),
).toHaveCount(2);
await expect(page.getByTestId("room-list").getByRole("option")).toHaveCount(1);
// check the lists are correct
await checkOrder(["Room to Join", "Test Room"], page);
const titleLocator = page.getByRole("group", { name: "Invites" }).locator(".mx_RoomTile_title");
await expect(titleLocator).toHaveCount(1);
await expect(titleLocator).toHaveText("Room to Rescind");
await expect(page.getByRole("option", { name: "Open room Room to Rescind" })).toBeVisible();
// now rescind the invite
await bot.evaluate(
@@ -277,10 +276,14 @@ test.describe("Sliding Sync", () => {
{ roomRescind, clientUserId },
);
await page.getByRole("option", { name: "Open room Room to Rescind" }).click();
await page.locator(".mx_RoomView").getByRole("button", { name: "Forget this room", exact: true }).click();
await primaryFilters.getByRole("option", { name: "Invites" }).click();
// Wait for the rescind to take effect and check the joined list once more
await expect(
page.getByRole("group", { name: "Rooms" }).locator(".mx_RoomSublist_tiles").getByRole("treeitem"),
).toHaveCount(2);
await expect(page.getByTestId("room-list").getByRole("option")).toHaveCount(2);
await checkOrder(["Room to Join", "Test Room"], page);
});
@@ -293,19 +296,27 @@ test.describe("Sliding Sync", () => {
await app.client.evaluate(async (client, roomId) => {
await client.setRoomTag(roomId, "m.favourite", { order: 0.5 });
}, roomId);
await expect(page.getByRole("group", { name: "Favourites" }).getByText("Favourite DM")).toBeVisible();
await expect(page.getByRole("group", { name: "People" }).getByText("Favourite DM")).not.toBeAttached();
await getFilterExpandButton(page).click();
const primaryFilters = getPrimaryFilters(page);
await primaryFilters.getByRole("option", { name: "Favourites" }).click();
await expect(page.getByRole("option", { name: "Favourite DM" })).toBeVisible();
await primaryFilters.getByRole("option", { name: "People" }).click();
await expect(page.getByRole("option", { name: "Favourite DM" })).not.toBeAttached();
});
// Regression test for a bug in SS mode, but would be useful to have in non-SS mode too.
// This ensures we are setting RoomViewStore state correctly.
test("should clear the reply to field when swapping rooms", async ({ page, app, testRoom }) => {
await app.client.createRoom({ name: "Other Room" });
await expect(page.getByRole("treeitem", { name: "Other Room" })).toBeVisible();
await expect(page.getByRole("option", { name: "Open room Other Room" })).toBeVisible();
await app.client.sendMessage(testRoom.roomId, "Hello world");
// select the room
await page.getByRole("treeitem", { name: "Test Room" }).click();
await page.getByRole("option", { name: "Open room Test Room" }).click();
await expect(page.locator(".mx_ReplyPreview")).not.toBeAttached();
@@ -318,13 +329,13 @@ test.describe("Sliding Sync", () => {
await expect(page.locator(".mx_ReplyPreview")).toBeVisible();
// now click Other Room
await page.getByRole("treeitem", { name: "Other Room" }).click();
await page.getByRole("option", { name: "Open room Other Room" }).click();
// ensure the reply-to disappears
await expect(page.locator(".mx_ReplyPreview")).not.toBeAttached();
// click back
await page.getByRole("treeitem", { name: "Test Room" }).click();
await page.getByRole("option", { name: "Open room Test Room" }).click();
// ensure the reply-to reappears
await expect(page.locator(".mx_ReplyPreview")).toBeVisible();
@@ -338,7 +349,7 @@ test.describe("Sliding Sync", () => {
await app.client.sendMessage(testRoom.roomId, "Reply to me");
// select the room
await page.getByRole("treeitem", { name: "Test Room" }).click();
await page.getByRole("option", { name: "Open room Test Room" }).click();
await expect(page.locator(".mx_ReplyPreview")).not.toBeAttached();
// click reply-to on the Reply to me message

View File

@@ -11,6 +11,7 @@ import { test, expect } from "../../element-web-test";
import type { Preset, ICreateRoomOpts } from "matrix-js-sdk/src/matrix";
import { type ElementAppPage } from "../../pages/ElementAppPage";
import { isDendrite } from "../../plugins/homeserver/dendrite";
import { UIFeature } from "../../../src/settings/UIFeature";
async function openSpaceCreateMenu(page: Page): Promise<Locator> {
await page.getByRole("button", { name: "Create a space" }).click();
@@ -95,9 +96,9 @@ test.describe("Spaces", () => {
await page.getByRole("button", { name: "Go to my first room" }).click();
// Assert rooms exist in the room list
await expect(page.getByRole("treeitem", { name: "General" })).toBeVisible();
await expect(page.getByRole("treeitem", { name: "Random" })).toBeVisible();
await expect(page.getByRole("treeitem", { name: "Jokes" })).toBeVisible();
await expect(page.getByRole("option", { name: "General" })).toBeVisible();
await expect(page.getByRole("option", { name: "Random" })).toBeVisible();
await expect(page.getByRole("option", { name: "Jokes" })).toBeVisible();
},
);
@@ -126,10 +127,10 @@ test.describe("Spaces", () => {
await page.getByRole("button", { name: "Skip for now" }).click();
// Assert rooms exist in the room list
const roomList = page.getByRole("tree", { name: "Rooms" });
await expect(roomList.getByRole("treeitem", { name: "General", exact: true })).toBeVisible();
await expect(roomList.getByRole("treeitem", { name: "Random", exact: true })).toBeVisible();
await expect(roomList.getByRole("treeitem", { name: "Projects", exact: true })).toBeVisible();
const roomList = page.getByRole("listbox", { name: "Room list", exact: true });
await expect(roomList.getByRole("option", { name: "General" })).toBeVisible();
await expect(roomList.getByRole("option", { name: "Random" })).toBeVisible();
await expect(roomList.getByRole("option", { name: "Projects" })).toBeVisible();
// Assert rooms exist in the space explorer
await expect(
@@ -199,7 +200,7 @@ test.describe("Spaces", () => {
await page.getByRole("button", { name: "Skip for now" }).click();
await page.getByRole("button", { name: "Add room" }).click();
await page.getByRole("main").getByRole("button", { name: "Add" }).click();
await page.getByRole("menuitem", { name: "Add existing room" }).click();
await page.getByRole("checkbox", { name: "Sample Room" }).click();
@@ -376,4 +377,68 @@ test.describe("Spaces", () => {
await app.viewSpaceByName("Root Space");
await expect(page.locator(".mx_SpaceRoomView")).toMatchScreenshot("space-room-view.png");
});
test("should render spaces visibility settings", { tag: "@screenshot" }, async ({ page, app, user, axe }) => {
await app.client.createSpace({
name: "My Space",
});
await app.viewSpaceByName("My space");
await page.getByLabel("Settings", { exact: true }).click();
await app.settings.switchTab("Visibility");
axe.disableRules("color-contrast"); // XXX: Inheriting colour contrast issues from room view.
await expect(axe).toHaveNoViolations();
await expect(page.locator("#mx_tabpanel_SPACE_VISIBILITY_TAB")).toMatchScreenshot(
"space-visibility-settings.png",
);
});
test.describe("Should hide public spaces option if not allowed", () => {
test.use({
config: {
setting_defaults: {
[UIFeature.AllowCreatingPublicSpaces]: false,
},
},
});
test("should disallow creating public rooms", { tag: "@screenshot" }, async ({ page, user, app }) => {
const menu = await openSpaceCreateMenu(page);
await menu
.locator('.mx_SpaceBasicSettings_avatarContainer input[type="file"]')
.setInputFiles("playwright/sample-files/riot.png");
await menu.getByRole("textbox", { name: "Name" }).fill("This is a private space");
await expect(menu.getByRole("textbox", { name: "Address" })).not.toBeVisible();
await menu
.getByRole("textbox", { name: "Description" })
.fill("This is a private space because we can't make public ones");
await menu.getByRole("button", { name: "Create" }).click();
await page.getByRole("button", { name: "Me and my teammates" }).click();
// Create the default General & Random rooms, as well as a custom "Projects" room
await expect(page.getByPlaceholder("General")).toBeVisible();
await expect(page.getByPlaceholder("Random")).toBeVisible();
await page.getByPlaceholder("Support").fill("Projects");
await page.getByRole("button", { name: "Continue" }).click();
await page.getByRole("button", { name: "Skip for now" }).click();
// Assert rooms exist in the room list
const roomList = page.getByRole("listbox", { name: "Room list", exact: true });
await expect(roomList.getByRole("option", { name: "General" })).toBeVisible();
await expect(roomList.getByRole("option", { name: "Random" })).toBeVisible();
await expect(roomList.getByRole("option", { name: "Projects" })).toBeVisible();
// Assert rooms exist in the space explorer
await expect(
page.locator(".mx_SpaceHierarchy_list .mx_SpaceHierarchy_roomTile", { hasText: "General" }),
).toBeVisible();
await expect(
page.locator(".mx_SpaceHierarchy_list .mx_SpaceHierarchy_roomTile", { hasText: "Random" }),
).toBeVisible();
await expect(
page.locator(".mx_SpaceHierarchy_list .mx_SpaceHierarchy_roomTile", { hasText: "Projects" }),
).toBeVisible();
});
});
});

View File

@@ -377,7 +377,7 @@ export class Helpers {
* Expand the space panel
*/
expandSpacePanel() {
return this.page.getByRole("button", { name: "Expand" }).click();
return this.page.getByRole("navigation", { name: "Spaces" }).getByRole("button", { name: "Expand" }).click();
}
/**

View File

@@ -30,13 +30,13 @@ 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 an unencrypted message…" });
const locator = page.getByRole("textbox", { name: "Send a message…" });
await expect(locator).toBeFocused();
await locator.fill("Hey!");
await locator.press("Enter");
// The DM room is created at this point, this can take a little bit of time
await expect(page.locator(".mx_EventTile_body").getByText("Hey!")).toBeAttached({ timeout: 3000 });
await expect(page.getByRole("group", { name: "People" }).getByText(name)).toBeAttached();
await expect(page.getByTestId("room-list").getByRole("option", { name: `Open room ${name}` })).toBeVisible();
}
type RoomRef = { name: string; roomId: string };
@@ -260,13 +260,15 @@ 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 an unencrypted message…" });
const locator = page.getByRole("textbox", { name: "Send a message…" });
await locator.fill("Hey!");
await locator.press("Enter");
// Assert DM exists by checking for the first message and the room being in the room list
await expect(page.locator(".mx_EventTile_body").filter({ hasText: "Hey!" })).toBeAttached({ timeout: 3000 });
await expect(page.getByRole("group", { name: "People" })).toContainText(bot2.credentials.displayName);
await expect(
page.getByTestId("room-list").getByRole("option", { name: `Open room ${bot2.credentials.displayName}` }),
).toBeVisible();
// Invite BotBob into existing DM with ByteBot
const dmRooms = await app.client.evaluate((client, userId) => {
@@ -279,7 +281,9 @@ test.describe("Spotlight", () => {
const groupDmName = await app.client.evaluate((client, id) => client.getRoom(id).name, dmRooms[0]);
await app.client.inviteUser(dmRooms[0], bot1.credentials.userId);
await expect(roomHeaderName(page).first()).toContainText(groupDmName);
await expect(page.getByRole("group", { name: "People" }).first()).toContainText(groupDmName);
await expect(
page.getByTestId("room-list").getByRole("option", { name: `Open room ${groupDmName}` }),
).toBeVisible();
// Search for BotBob by id, should return group DM and user
spotlight = await app.openSpotlight();

View File

@@ -50,9 +50,12 @@ test.describe("Media preview settings", () => {
}
`,
});
await expect(
page.getByRole("tree", { name: "Rooms" }).getByRole("treeitem", { name: "Test room" }),
).toMatchScreenshot("invite-room-tree-no-avatar.png");
const testRoomTile = page
.getByRole("listbox", { name: "Room list" })
.getByRole("option", { name: "Test room" });
await expect(testRoomTile).toBeVisible();
await expect(testRoomTile).toMatchScreenshot("invite-room-tree-no-avatar.png");
// And then go back to being visible
settings = await app.settings.openUserSettings("Preferences");
@@ -70,9 +73,7 @@ test.describe("Media preview settings", () => {
}
`,
});
await expect(
page.getByRole("tree", { name: "Rooms" }).getByRole("treeitem", { name: "Test room" }),
).toMatchScreenshot("invite-room-tree-with-avatar.png");
await expect(testRoomTile).toMatchScreenshot("invite-room-tree-with-avatar.png");
});
test("should be able to hide media in rooms globally", async ({ page, app, room, user }) => {

View File

@@ -908,23 +908,37 @@ test.describe("Timeline", () => {
});
});
test("should be able to hide an image", { tag: "@screenshot" }, async ({ page, app, room, context }) => {
await app.viewRoomById(room.roomId);
await sendImage(app.client, room.roomId, NEW_AVATAR);
await app.timeline.scrollToBottom();
const imgTile = page.locator(".mx_MImageBody").first();
await expect(imgTile).toBeVisible();
await imgTile.hover();
await page.getByRole("button", { name: "Hide" }).click();
test(
"should be able to hide an image",
{ tag: "@screenshot" },
async ({ page, app, homeserver, room, context }) => {
await app.viewRoomById(room.roomId);
// Check that the image is now hidden.
await expect(page.getByRole("button", { name: "Show image" })).toBeVisible();
});
const bot = new Bot(page, homeserver, {});
await bot.prepareClient();
await app.client.inviteUser(room.roomId, bot.credentials.userId);
test("should be able to hide a video", async ({ page, app, room, context }) => {
await sendImage(bot, room.roomId, NEW_AVATAR);
await app.timeline.scrollToBottom();
const imgTile = page.locator(".mx_MImageBody").first();
await expect(imgTile).toBeVisible();
await imgTile.hover();
await page.getByRole("button", { name: "Hide" }).click();
// Check that the image is now hidden.
await expect(page.getByRole("button", { name: "Show image" })).toBeVisible();
},
);
test("should be able to hide a video", async ({ page, app, homeserver, room, context }) => {
await app.viewRoomById(room.roomId);
const upload = await app.client.uploadContent(VIDEO_FILE, { name: "bbb.webm", type: "video/webm" });
await app.client.sendEvent(room.roomId, null, "m.room.message" as EventType, {
const bot = new Bot(page, homeserver, {});
await bot.prepareClient();
await app.client.inviteUser(room.roomId, bot.credentials.userId);
const upload = await bot.uploadContent(VIDEO_FILE, { name: "bbb.webm", type: "video/webm" });
await bot.sendEvent(room.roomId, null, "m.room.message" as EventType, {
msgtype: "m.video" as MsgType,
body: "bbb.webm",
url: upload.content_uri,

View File

@@ -24,7 +24,7 @@ test.describe("PSTN", () => {
await toasts.rejectToast("Notifications");
await toasts.assertNoToasts();
await expect(page.locator(".mx_LeftPanel_filterContainer")).toMatchScreenshot("dialpad-trigger.png");
await expect(page.locator(".mx_RoomListSearch")).toMatchScreenshot("dialpad-trigger.png");
await page.getByLabel("Open dial pad").click();
await expect(page.locator(".mx_Dialog")).toMatchScreenshot("dialpad.png");
});

View File

@@ -0,0 +1,98 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { test, expect } from "../../element-web-test";
const DEMO_WIDGET_ID = "demo-widget-id";
const DEMO_WIDGET_NAME = "Demo Widget";
const DEMO_WIDGET_TYPE = "demo";
const ROOM_NAME = "Demo";
const DEMO_WIDGET_HTML = `
<html lang="en">
<head>
<title>Demo Widget</title>
<script>
let sendEventCount = 0
window.onmessage = ev => {
if (ev.data.action === 'capabilities') {
window.parent.postMessage(Object.assign({
response: {
capabilities: [
"org.matrix.msc2762.timeline:*",
"org.matrix.msc2762.receive.state_event:m.room.topic",
"org.matrix.msc2762.send.event:net.widget_echo"
]
},
}, ev.data), '*');
}
};
</script>
</head>
</html>
`;
test.describe("Widger permissions dialog", () => {
test.use({
displayName: "Mike",
});
let demoWidgetUrl: string;
test.beforeEach(async ({ webserver }) => {
demoWidgetUrl = webserver.start(DEMO_WIDGET_HTML);
});
test(
"should be updated if user is re-invited into the room with updated state event",
{ tag: "@screenshot" },
async ({ page, app, user, axe }) => {
const roomId = await app.client.createRoom({
name: ROOM_NAME,
});
// setup widget via state event
await app.client.sendStateEvent(
roomId,
"im.vector.modular.widgets",
{
id: DEMO_WIDGET_ID,
creatorUserId: "somebody",
type: DEMO_WIDGET_TYPE,
name: DEMO_WIDGET_NAME,
url: demoWidgetUrl,
},
DEMO_WIDGET_ID,
);
// set initial layout
await app.client.sendStateEvent(
roomId,
"io.element.widgets.layout",
{
widgets: {
[DEMO_WIDGET_ID]: {
container: "top",
index: 1,
width: 100,
height: 0,
},
},
},
"",
);
// open the room
await app.viewRoomByName(ROOM_NAME);
axe.disableRules("color-contrast"); // XXX: Inheriting colour contrast issues from room view.
await expect(axe).toHaveNoViolations();
await expect(page.locator(".mx_WidgetCapabilitiesPromptDialog")).toMatchScreenshot(
"widget-capabilites-prompt.png",
);
},
);
});

View File

@@ -51,9 +51,10 @@ export class ElementAppPage {
/**
* Open room creation dialog.
*/
public async openCreateRoomDialog(): Promise<Locator> {
await this.page.getByRole("button", { name: "Add room", exact: true }).click();
await this.page.getByRole("menuitem", { name: "New room", exact: true }).click();
public async openCreateRoomDialog(roomKindname: "New room" | "New video room" = "New room"): Promise<Locator> {
await this.page.getByRole("navigation", { name: "Room list" }).getByRole("button", { name: "Add" }).click();
await this.page.getByRole("menuitem", { name: roomKindname }).click();
return this.page.locator(".mx_CreateRoomDialog");
}
@@ -70,12 +71,23 @@ export class ElementAppPage {
/**
* Opens the given room by name. The room must be visible in the
* room list and the room may contain unread messages.
*
* @param name The exact room name to find and click on/open.
*/
public async viewRoomByName(name: string): Promise<void> {
// We get the room list by test-id which is a listbox and matching title=name
return this.page.getByTestId("room-list").locator(`[title="${name}"]`).first().click();
}
/**
* Opens the given room on the old room list by name. The room must be visible in the
* room list, but the room list may be folded horizontally, and the
* room may contain unread messages.
*
* @param name The exact room name to find and click on/open.
*/
public async viewRoomByName(name: string): Promise<void> {
public async viewRoomByNameOnOldRoomList(name: string): Promise<void> {
// We look for the room inside the room list, which is a tree called Rooms.
//
// There are 3 cases:

View File

@@ -43,7 +43,7 @@ export class Settings {
* @param {*} value The new value of the setting, may be null.
* @return {Promise} Resolves when the setting has been changed.
*/
public async setValue(settingName: string, roomId: string, level: SettingLevel, value: any): Promise<void> {
public async setValue(settingName: string, roomId: string | null, level: SettingLevel, value: any): Promise<void> {
return this.page.evaluate<
Promise<void>,
{

View File

@@ -1,38 +1,49 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024-2025 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { MatrixAuthenticationServiceContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers";
import { MatrixAuthenticationServiceContainer } from "../../../testcontainers/mas.ts";
import { type Fixtures } from "../../../element-web-test.ts";
export const masHomeserver: Fixtures = {
mas: [
async ({ _homeserver: homeserver, logger, network, postgres, mailpit }, use) => {
const config = {
clients: [
{
client_id: "0000000000000000000SYNAPSE",
client_auth_method: "client_secret_basic",
client_secret: "SomeRandomSecret",
},
],
matrix: {
homeserver: "localhost",
secret: "AnotherRandomSecret",
endpoint: "http://homeserver:8008",
},
};
const secret = "AnotherRandomSecret";
const limits = { burst: 10, per_second: 10 };
const container = await new MatrixAuthenticationServiceContainer(postgres)
.withNetwork(network)
.withNetworkAliases("mas")
.withLogConsumer(logger.getConsumer("mas"))
.withConfig(config)
.withConfig({
matrix: {
kind: "synapse",
homeserver: "localhost",
secret,
endpoint: "http://homeserver:8008",
},
rate_limiting: {
login: {
per_ip: limits,
per_account: limits,
},
registration: limits,
email_authentication: {
per_ip: limits,
per_address: limits,
emails_per_session: limits,
attempt_per_session: limits,
},
account_recovery: {
per_ip: limits,
per_address: limits,
},
},
})
.start();
homeserver.withConfig({
@@ -40,16 +51,10 @@ export const masHomeserver: Fixtures = {
enable_registration_without_verification: undefined,
disable_msisdn_registration: undefined,
password_config: undefined,
experimental_features: {
msc3861: {
enabled: true,
issuer: `http://mas:8080/`,
introspection_endpoint: "http://mas:8080/oauth2/introspect",
client_id: config.clients[0].client_id,
client_auth_method: config.clients[0].client_auth_method,
client_secret: config.clients[0].client_secret,
admin_token: config.matrix.secret,
},
matrix_authentication_service: {
enabled: true,
endpoint: "http://mas:8080/",
secret,
},
});
@@ -59,28 +64,6 @@ export const masHomeserver: Fixtures = {
{ scope: "worker" },
],
config: async ({ homeserver, context, mas }, use) => {
const issuer = `${mas.baseUrl}/`;
const wellKnown = {
"m.homeserver": {
base_url: homeserver.baseUrl,
},
"org.matrix.msc2965.authentication": {
issuer,
account: `${issuer}account`,
},
};
// Ensure org.matrix.msc2965.authentication is in well-known
await context.route("https://localhost/.well-known/matrix/client", async (route) => {
await route.fulfill({ json: wellKnown });
});
await use({
default_server_config: wellKnown,
});
},
context: async ({ homeserverType, context }, use, testInfo) => {
testInfo.skip(homeserverType !== "synapse", "does not yet support MAS");
await use(context);

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

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