Compare commits

...
Sign in to create a new pull request.

104 commits

Author SHA1 Message Date
renovate[bot]
943b817194
Update dependency @babel/preset-react to v7.26.3 (#28714)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-11 20:24:53 +00:00
Florian Duros
2aa72bb40b
Update @vector-im/compound-web to 7.5.0 (#28700)
Co-authored-by: David Baker <dbkr@users.noreply.github.com>
2024-12-11 19:35:59 +00:00
David Baker
a755e399cf
Change to en-US locale for date tests (#28723)
* Change to en-US locale for date tests

As per comment. Fixes the tests.

* Spell locale right

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>
2024-12-11 19:00:24 +00:00
renovate[bot]
8dff758153
Update definitelyTyped (#28704)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-11 15:02:47 +00:00
David Baker
cf3bdbdc7a
Fix Read Receipt Test (#28719)
* Update test snapshot

as the date formatting appears to have gained a comma, and somehow
got through the merge tests on the dependency bump.

* Actually this was the problem
2024-12-11 14:25:56 +00:00
renovate[bot]
ba98c2085d
Update dependency @formatjs/intl-segmenter to v11.7.5 (#28713)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-11 13:13:09 +00:00
David Baker
b330de5d6e
Factor out crypto setup process into a store (#28675)
* Factor out crypto setup process into a store

To make components pure and avoid react 18 dev mode problems due
to components making requests when mounted.

* fix test

* test for the store

* Add comment
2024-12-11 13:10:27 +00:00
renovate[bot]
b86bb5cc2f
Update guibranco/github-status-action-v2 digest to d469d49 (#28702)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-11 02:01:31 +00:00
renovate[bot]
e835cab139
Update all non-major dependencies (#28703)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-10 17:20:18 +00:00
Hugh Nimmo-Smith
6b7c94905f
Allow trusted Element Call widget to send and receive media encryption key to-device messages (#28316) 2024-12-10 12:05:30 +00:00
ElementRobot
a4e8bb3f9a
[create-pull-request] automated change (#28696)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2024-12-10 06:17:13 +00:00
Hubert Chathi
2b4000d47f
Add delay in test to allow Alice to fetch Bob's device keys (#28668)
* add delay in test to allow Alice to fetch Bob's device keys

* wait until we see bob's device, rather than hard-coding a timeout

* Fix comment

Co-authored-by: Florian Duros <florianduros@element.io>

* fix lint

---------

Co-authored-by: Florian Duros <florianduros@element.io>
2024-12-09 21:02:29 +00:00
Michael Telatynski
01304439ee
Make tsc faster again (#28678)
* Stash initial work to bring TSC from over 6 mins to under 1 minute

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

* Stabilise types

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

* Fix incorrect props to AccessibleButton

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

* Swap AccessibleButton element types to match the props they provide

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

* Changed my mind, remove spurious previously ignored props

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>
2024-12-06 17:49:32 +00:00
David Baker
c659afa8db
Rename CreateCrossSigningDialog to InitialCryptoSetupDialog (#28658)
* Rename CreateCrossSigningDialog to InitialCryptoSetup

because it will soon encompass things other than just creating cross
signing.

* Fix name & tests

* Fix import

* Remove code creating key backup

Because this was split out from my key backup by default PR

* Fix comment

* Convert to named export
2024-12-06 10:26:26 +00:00
ElementRobot
9cc5564d50
[create-pull-request] automated change (#28670)
Co-authored-by: t3chguy <t3chguy@users.noreply.github.com>
2024-12-06 09:38:58 +00:00
Michael Telatynski
549300726f
Update CODEOWNERS 2024-12-06 09:18:33 +00:00
ElementRobot
319dab5920
[create-pull-request] automated change (#28669)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2024-12-06 06:17:19 +00:00
renovate[bot]
5c51d179b9
Update linkify to v4.2.0 (#28665)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-05 19:28:39 +00:00
Florian Duros
dbdb23f6bc
Notify to resize the timeline when the pinned message banner is displayed or hidden (#28654) 2024-12-05 16:11:40 +00:00
Florian Duros
5686666ad2
Fix multiple pinned messages flacky tests by waiting the message to be displayed in the banner when pinned. (#28655) 2024-12-05 15:31:16 +00:00
Michael Telatynski
0c4189f2ed
Remove stale webpack configuration (#28649)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-05 14:42:26 +00:00
Michael Telatynski
450cb608ec
Add mergequeue tag to read-receipts tests and skip running them on PR commits (#28648)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-05 14:30:28 +00:00
Michael Telatynski
7e03f38a3b
Switch to React18 useId (#28651)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-05 14:11:03 +00:00
ElementRobot
9bf3d22439
[create-pull-request] automated change (#28646)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2024-12-05 09:08:21 +00:00
Richard van der Hoff
5547101bcc
Playwright: fix (hopefully) flaky shields test (#28641)
* Playwright: improve failure report when an unexpected shield exists

If we discover an E2E shield when we didn't expect one, let's make the error
message more helpful by checking the tooltip.

* Playwright: fix (hopefully) flaky shields test

Wait for our user to fetch the bot's identity before running the test, to work
around a race in the shield logic.

Hopefully, fixes https://github.com/element-hq/element-web/issues/28061
2024-12-04 23:01:58 +00:00
Michael Telatynski
085854b125
Merge pull request #28626 from element-hq/t3chguy/web-friendly-buffers
Remove usages of Buffer so we don't need to ship the polyfill
2024-12-04 22:10:30 +00:00
fkwp
ee24989f49
increase ringing timeout from 10 seconds to 90 seconds (#28630)
* increase ringing timeout from 10 seconds to 90 seconds

* increase ringing timeout from 10 seconds to 90 seconds

* increase max age of incoming notify event to 15 seconds which triggers a call ringingn notification/toast
2024-12-04 21:52:15 +00:00
Florian Duros
5a418f3f19
Fix font & spaces in settings subsection (#28631)
* Fix settings header

* Fix gap between subsection

* Update tests

* Update e2e tests

* Update snapshots
2024-12-04 14:11:02 +00:00
Michael Telatynski
db5b3359c6
Switch to using @fontsource for Inter & Inconsolata (#28540)
* Switch to using @fontsource for Inter & Inconsolata

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

* Remove unused font Open_Sans

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

* Switch to less broken imports

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

* Switch to Ubuntu 24.04

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

* Discard changes to res/themes/dark/css/dark.pcss

* Discard changes to res/themes/legacy-light/css/_fonts.pcss

* Discard changes to res/themes/light-high-contrast/css/light-high-contrast.pcss

* Discard changes to res/themes/light/css/light.pcss

* Discard changes to .github/workflows/end-to-end-tests.yaml

* Set outputDir for fonts

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

* Use headed mode for Playwright

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

* Switch to new Chrome headless mode instead

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

* Cache bust playwright browser install in CI

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

* Update screenshots

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

* Try with 22.04

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

* Update to Ubuntu Noble

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

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-04 11:55:27 +00:00
Michael Telatynski
188f910dc7
Merge remote-tracking branch 'origin/t3chguy/web-friendly-buffers' into t3chguy/web-friendly-buffers 2024-12-04 11:46:56 +00:00
Michael Telatynski
619e41e3a2
Remove usages of Buffer
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-04 11:46:48 +00:00
Michael Telatynski
c1838b34b6
Merge branch 'develop' into t3chguy/web-friendly-buffers 2024-12-04 11:34:13 +00:00
ElementRobot
1d51323451
Localazy Download (#28629)
* [create-pull-request] automated change

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

---------

Co-authored-by: t3chguy <t3chguy@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-04 09:40:33 +00:00
Michael Telatynski
d0d0b8212d
Tag screenshot tests to speed up test:playwright:screenshot (#28623)
* Tag screenshot tests to speed up test:playwright:screenshot

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

* Add more tags

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

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-04 09:12:43 +00:00
Michael Telatynski
974d3c175a
Use specific import for bloom-filters to avoid Buffer dependency
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-04 08:46:25 +00:00
Florian Duros
d0e19d3e03
Fix TAC should mark all threads as read e2e test (#28625) 2024-12-04 08:42:37 +00:00
ElementRobot
b016cf59e9
[create-pull-request] automated change (#28628)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2024-12-04 08:38:52 +00:00
Michael Telatynski
cfdfc4e640
Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-03 18:06:05 +00:00
Michael Telatynski
d0fea745bb
Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-03 17:41:28 +00:00
Michael Telatynski
f3ef9e6602
Update test
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-03 17:38:24 +00:00
Michael Telatynski
af0391b86a
Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-03 17:32:10 +00:00
Michael Telatynski
36108c0c22
Update js-sdk usages around Buffers to avoid needing Buffer polyfill
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-03 17:18:43 +00:00
Florian Duros
d2acce1221
Remove manual device verification which is not supported by the new cryptography stack (#28588)
* Remove call of `MatrixClient.setDeviceVerified`

* Replace usage of deprecated crypto events

* Replace deprecated imports

* Remove legacy button in `UntrustedDeviceDialog`

* Review fixes

* Add tests

* Fix doc
2024-12-03 15:26:37 +00:00
Florian Duros
b72c053d1a
Add Close tooltip to dialog (#28617)
* Add `Close` tooltip to dialog

* Update snapshots
2024-12-03 14:25:06 +00:00
Michael Telatynski
865c5b0e9c
Fix deploy script by normalizing version string
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-03 12:42:23 +00:00
RiotRobot
ce3fa2164f Reset matrix-js-sdk back to develop branch 2024-12-03 12:33:41 +00:00
RiotRobot
60b0e8b237 Merge branch 'master' into develop 2024-12-03 12:33:26 +00:00
ElementRobot
4730352092
[create-pull-request] automated change (#28621)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2024-12-03 08:45:44 +00:00
Florian Duros
0429809c00
New UX for Share dialog (#28598)
* New UX for `ShareDialog`

* Use new named import

* Rewrite tests

* Add e2e tests

* Use `box-sizing` for social buttons

* Update e2e tests
2024-12-02 18:10:17 +00:00
Michael Telatynski
06fa3481df
Remove unused scripts (#28612)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-02 14:05:44 +00:00
Michael Telatynski
e75ff818d3
Fix code block highlighting not working reliably with many code blocks (#28613)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-02 14:03:14 +00:00
Michael Telatynski
2c3e01a31c
Provide a way to activate GIFs via the keyboard for a11y (#28611)
* Provide a way to activate GIFs via the keyboard for a11y

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

* Remove dead code

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

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-02 13:13:18 +00:00
Michael Telatynski
84709df3c9
Remove remaining reply fallbacks code (#28610)
* Remove remaining reply fallbacks code

as MSC2781 has been merged

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

* Update tests

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

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-02 13:13:12 +00:00
Michael Telatynski
00aadf1580
Merge pull request #28538 from element-hq/t3chguy/fix/20721 2024-12-02 11:23:18 +00:00
Michael Telatynski
d8ebc68aa8
Remove abandoned Voice Broadcasts labs flag (#28548)
* Remove abandoned Voice Broadcasts labs flag

Any existing voice broadcasts will be shown as a series of voice messages which will sequence play as normal

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

* Remove dead code

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>
2024-12-02 10:53:27 +00:00
Michael Telatynski
5d72735b1f
Fix release-checks to not use reserved name GITHUB_TOKEN
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-02 10:47:38 +00:00
Michael Telatynski
2099aaa663
Improve coverage
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-02 10:29:04 +00:00
Florian Duros
6d8cbf39f5
Replace MatrixClient.isRoomEncrypted by MatrixClient.CryptoApi.isEncryptionEnabledInRoom in EventTile.tsx (#28510)
* Replace `MatrixClient.isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in `EventTile.tsx`

* Use `roomContext.isRoomEncrypted`
2024-12-02 10:20:13 +00:00
Michael Telatynski
b87437d439
Improve performance of RoomContext in RoomHeader (#28574)
* Improve performance of RoomContext in RoomHeader

This allows a component to subscribe to only part of the RoomContext so they do not need to re-render on every single change

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

* Update tests

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

* Prettier

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

* Iterate

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

* Add comment

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

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-02 09:49:52 +00:00
ElementRobot
8619a22f57
Localazy Download (#28608)
* [create-pull-request] automated change

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

---------

Co-authored-by: t3chguy <t3chguy@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-02 09:49:32 +00:00
renovate[bot]
418f121f96
Update all non-major dependencies (#28556)
* Update all non-major dependencies

* Prettier

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>
2024-12-02 09:39:36 +00:00
Michael Telatynski
351774d3e3
Merge branch 'develop' into t3chguy/fix/20721 2024-12-02 09:22:51 +00:00
Michael Telatynski
70f898c71d
Improve coverage
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-02 09:13:33 +00:00
ElementRobot
4f276c1690
[create-pull-request] automated change (#28600)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2024-11-30 06:20:20 +00:00
Florian Duros
2b4ce627b8
Fix format bar position (#28591) 2024-11-29 11:56:52 +00:00
ElementRobot
d68c5a26af
[create-pull-request] automated change (#28586)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2024-11-29 06:20:43 +00:00
Michael Telatynski
95175caf0c
Remove redundant MSC implementation for io.element.rendezvous (#28583)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-28 08:39:51 +00:00
Florian Duros
08418c16c9
Remove Features.RustCrypto (#28582) 2024-11-27 16:56:20 +00:00
Florian Duros
6798239aa8
Check room encryption earlier (#28579) 2024-11-27 15:22:45 +00:00
Michael Telatynski
9c74110969
Add Modernizr warning when running in non-secure context (#28581)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-27 15:17:25 +00:00
Michael Telatynski
1bee3becfb
Merge branch 'develop' of https://github.com/vector-im/element-web into t3chguy/fix/20721 2024-11-27 14:53:20 +00:00
Michael Telatynski
bfac727307
Apply release blocker checks to cut branches workflow (#28551)
* Apply release blocker checks to cut branches workflow

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

* Iterate

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

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-27 14:35:49 +00:00
Michael Telatynski
8e213c5d34
Add source-map-loader for easier debugging (#28580)
of matrix-widget-api and other libs

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-27 14:16:05 +00:00
Michael Telatynski
c34cbd011d
Merge pull request #28398 from element-hq/dbkr/stateafter 2024-11-27 11:22:41 +00:00
Michael Telatynski
e10ecc9a4d
Merge remote-tracking branch 'origin/dbkr/stateafter' into dbkr/stateafter 2024-11-27 10:47:48 +00:00
Michael Telatynski
3dbcb5efa3
Merge branch 'develop' of https://github.com/vector-im/element-web into dbkr/stateafter
# Conflicts:
#	test/unit-tests/components/structures/RoomView-test.tsx
#	test/unit-tests/components/structures/TimelinePanel-test.tsx
2024-11-27 10:47:35 +00:00
ElementRobot
2b883a8aa0
[create-pull-request] automated change (#28572)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-27 09:40:33 +00:00
renovate[bot]
bb54a0e063
Update typescript-eslint monorepo to v8.15.0 (#28567)
* Update typescript-eslint monorepo to v8.15.0

* Add linter exception

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>
2024-11-27 09:38:26 +00:00
renovate[bot]
af3a9777e8
Update dependency caniuse-lite to v1.0.30001684 (#28559)
* Update dependency caniuse-lite to v1.0.30001684

* Update tests

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>
2024-11-27 09:38:01 +00:00
ElementRobot
61542ff3a8
[create-pull-request] automated change (#28573)
Co-authored-by: t3chguy <t3chguy@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-27 09:37:01 +00:00
Michael Telatynski
ec460326f1
Fix workflow permissions
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-27 09:14:31 +00:00
Florian Duros
af846f8be9
Replace MatrixClient.isRoomEncrypted by MatrixClient.CryptoApi.isEncryptionEnabledInRoom in RoomView (#28278)
* Replace `MatrixClient.isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in RoomView

* Add `isRoomEncrypted` to room

* Update e2eStatus and urlPreview when isRoomEncrypted is computed

* Fix e2e test

* Add tests when user verification change

* Reduced abusive timeout in e2e test
2024-11-26 21:34:32 +00:00
renovate[bot]
de5ddcf6f7
Update guibranco/github-status-action-v2 digest to 66088c4 (#28555)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-26 18:23:13 +00:00
renovate[bot]
8d28dd3784
Update dependency @sentry/browser to v8.40.0 (#28561)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-26 18:14:00 +00:00
renovate[bot]
df7703a4a5
Update linkify to v4.1.4 (#28560)
* Update linkify to v4.1.4

* Fix types

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>
2024-11-26 18:11:39 +00:00
renovate[bot]
fe7ac68478
Update definitelyTyped (#28557)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-26 17:34:29 +00:00
renovate[bot]
bda045fad0
Update dependency @stylistic/eslint-plugin to v2.11.0 (#28562)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-26 17:34:01 +00:00
renovate[bot]
8a7cdaa3ef
Update dependency stylelint-scss to v6.10.0 (#28564)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-26 17:33:45 +00:00
renovate[bot]
7796200562
Update playwright to v1.49.0 (#28566)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-26 17:31:10 +00:00
renovate[bot]
80cd93678a
Update docker (#28554)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-26 17:15:04 +00:00
renovate[bot]
b8c178d133
Update dependency @vector-im/compound-design-tokens to v2.1.0 (#28563)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-26 17:14:41 +00:00
renovate[bot]
5f4d789259
Update dependency @formatjs/intl-segmenter to v11.7.4 (#28558)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-26 17:14:36 +00:00
Hubert Chathi
85711be352
Show the correct shield status in tooltip for more conditions (#28476)
* Add support for new shield codes in JS SDK

* change string used for shield message

* fix test
2024-11-26 16:00:52 +00:00
Michael Telatynski
de820e11fc
Remove Twemoji SBIX font in favour of COLRv0 (#28549)
* Remove Twemoji SBIX font in favour of COLRv0

as it is supported everywhere we need it

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

* Remove unused mock

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

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-26 14:20:15 +00:00
Hubert Chathi
0333cba258
temporarily add a default branch to handle unknown shield codes (#28543) 2024-11-26 14:16:20 +00:00
Michael Telatynski
15bd59b81a
Fix release workflow permissions
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-26 13:41:15 +00:00
Michael Telatynski
a478463b75
Remove duplicates
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-25 17:43:09 +00:00
Michael Telatynski
3f221891f7
Remove space-specific right panel store handling
This is no longer needed as the right panel always corresponds to the currently viewed room/space only.

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-25 17:29:32 +00:00
Michael Telatynski
2dffadc92e
Merge branch 'develop' into dbkr/stateafter 2024-11-20 13:13:11 +00:00
Michael Telatynski
f3d0fc4d68
Merge remote-tracking branch 'origin/dbkr/stateafter' into dbkr/stateafter 2024-11-08 16:42:05 +00:00
Michael Telatynski
0b636aec4b
Improve coverage
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-08 16:41:57 +00:00
Michael Telatynski
cb0d72fc2f
Merge branch 'develop' into dbkr/stateafter 2024-11-08 09:58:00 +00:00
Michael Telatynski
931edd7419
Merge branch 'develop' into dbkr/stateafter 2024-11-06 15:42:28 +00:00
Michael Telatynski
044eaf7eb5
Update calls to addEventToTimeline and addLiveEvents for new signature
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-06 14:55:21 +00:00
720 changed files with 7087 additions and 20409 deletions

View file

@ -42,6 +42,10 @@ module.exports = {
name: "setImmediate", name: "setImmediate",
message: "Use setTimeout instead.", message: "Use setTimeout instead.",
}, },
{
name: "Buffer",
message: "Buffer is not available in the web.",
},
], ],
"import/no-duplicates": ["error"], "import/no-duplicates": ["error"],
@ -255,6 +259,9 @@ module.exports = {
additionalTestBlockFunctions: ["beforeAll", "beforeEach", "oldBackendOnly"], additionalTestBlockFunctions: ["beforeAll", "beforeEach", "oldBackendOnly"],
}, },
], ],
// These are fine in tests
"no-restricted-globals": "off",
}, },
}, },
{ {

1
.github/CODEOWNERS vendored
View file

@ -13,6 +13,7 @@
# Ignore translations as those will be updated by GHA for Localazy download # Ignore translations as those will be updated by GHA for Localazy download
/src/i18n/strings /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 plugin as this is updated by GHA for docker image updating
/playwright/plugins/homeserver/synapse/index.ts /playwright/plugins/homeserver/synapse/index.ts

View file

@ -11,7 +11,6 @@ runs:
using: composite using: composite
steps: steps:
- name: Download release tarball - name: Download release tarball
id: current_download
uses: robinraju/release-downloader@a96f54c1b5f5e09e47d9504526e96febd949d4c2 # v1 uses: robinraju/release-downloader@a96f54c1b5f5e09e47d9504526e96febd949d4c2 # v1
with: with:
tag: ${{ inputs.tag }} tag: ${{ inputs.tag }}

View file

@ -41,7 +41,8 @@ jobs:
- name: Check current version on deployment - name: Check current version on deployment
id: current_version id: current_version
run: | run: |
echo "version=v$(curl -s https://$SITE/version)" >> $GITHUB_OUTPUT version=$(curl -s https://$SITE/version)
echo "version=${version#v}" >> $GITHUB_OUTPUT
# The current version bundle melding dance is skipped if the version we're deploying is the same # The current version bundle melding dance is skipped if the version we're deploying is the same
# as then we're just doing a re-deploy of the same version with potentially different configs. # as then we're just doing a re-deploy of the same version with potentially different configs.
@ -50,7 +51,7 @@ jobs:
if: steps.current_version.outputs.version != github.ref_name if: steps.current_version.outputs.version != github.ref_name
uses: ./.github/actions/download-verify-element-tarball uses: ./.github/actions/download-verify-element-tarball
with: with:
tag: ${{ steps.current_version.outputs.version }} tag: v${{ steps.current_version.outputs.version }}
out-file-path: _current_version out-file-path: _current_version
- name: Download target version - name: Download target version

View file

@ -39,7 +39,7 @@ jobs:
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5 uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5
with: with:
images: | images: |
vectorim/element-web vectorim/element-web
@ -51,7 +51,7 @@ jobs:
- name: Build and push - name: Build and push
id: build-and-push id: build-and-push
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6 uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6
with: with:
context: . context: .
push: true push: true

View file

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

View file

@ -9,6 +9,6 @@ jobs:
action: action:
uses: matrix-org/matrix-js-sdk/.github/workflows/pull_request.yaml@develop uses: matrix-org/matrix-js-sdk/.github/workflows/pull_request.yaml@develop
permissions: permissions:
pull-requests: read pull-requests: write
secrets: secrets:
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}

View file

@ -18,6 +18,7 @@ jobs:
permissions: permissions:
contents: write contents: write
issues: write issues: write
pull-requests: read
secrets: secrets:
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}

View file

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

View file

@ -104,7 +104,7 @@ jobs:
- name: Skip SonarCloud in merge queue - name: Skip SonarCloud in merge queue
if: github.event_name == 'merge_group' || inputs.disable_coverage == 'true' if: github.event_name == 'merge_group' || inputs.disable_coverage == 'true'
uses: guibranco/github-status-action-v2@1f26a0237cd1a57626fbb5a0eb2494c9b8797d07 uses: guibranco/github-status-action-v2@d469d49426f5a7b8a1fbcac20ad274d3e4892321
with: with:
authToken: ${{ secrets.GITHUB_TOKEN }} authToken: ${{ secrets.GITHUB_TOKEN }}
state: success state: success

View file

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

View file

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

View file

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

View file

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

View file

@ -64,7 +64,7 @@
"test:playwright:open": "yarn test:playwright --ui", "test:playwright:open": "yarn test:playwright --ui",
"test:playwright:screenshots": "yarn test:playwright:screenshots:build && yarn test:playwright:screenshots:run", "test:playwright:screenshots": "yarn test:playwright:screenshots:build && yarn test:playwright:screenshots:run",
"test:playwright:screenshots:build": "docker build playwright -t element-web-playwright", "test:playwright:screenshots:build": "docker build playwright -t element-web-playwright",
"test:playwright:screenshots:run": "docker run --rm --network host -e BASE_URL -e CI -v $(pwd):/work/ -v $(node -e 'console.log(require(`path`).dirname(require.resolve(`matrix-js-sdk/package.json`)))'):/work/node_modules/matrix-js-sdk -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/:/tmp/ -it element-web-playwright", "test:playwright:screenshots:run": "docker run --rm --network host -e BASE_URL -e CI -v $(pwd):/work/ -v $(node -e 'console.log(require(`path`).dirname(require.resolve(`matrix-js-sdk/package.json`)))'):/work/node_modules/matrix-js-sdk -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/:/tmp/ -it element-web-playwright --grep @screenshot",
"coverage": "yarn test --coverage", "coverage": "yarn test --coverage",
"analyse:unused-exports": "ts-node ./scripts/analyse_unused_exports.ts", "analyse:unused-exports": "ts-node ./scripts/analyse_unused_exports.ts",
"analyse:webpack-bundles": "webpack-bundle-analyzer webpack-stats.json webapp", "analyse:webpack-bundles": "webpack-bundle-analyzer webpack-stats.json webapp",
@ -73,12 +73,14 @@
"resolutions": { "resolutions": {
"oidc-client-ts": "3.1.0", "oidc-client-ts": "3.1.0",
"jwt-decode": "4.0.0", "jwt-decode": "4.0.0",
"caniuse-lite": "1.0.30001679", "caniuse-lite": "1.0.30001684",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
"wrap-ansi": "npm:wrap-ansi@^7.0.0" "wrap-ansi": "npm:wrap-ansi@^7.0.0"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.12.5",
"@fontsource/inconsolata": "^5",
"@fontsource/inter": "^5",
"@formatjs/intl-segmenter": "^11.5.7", "@formatjs/intl-segmenter": "^11.5.7",
"@matrix-org/analytics-events": "^0.29.0", "@matrix-org/analytics-events": "^0.29.0",
"@matrix-org/emojibase-bindings": "^1.3.3", "@matrix-org/emojibase-bindings": "^1.3.3",
@ -86,7 +88,7 @@
"@matrix-org/spec": "^1.7.0", "@matrix-org/spec": "^1.7.0",
"@sentry/browser": "^8.0.0", "@sentry/browser": "^8.0.0",
"@vector-im/compound-design-tokens": "^2.0.1", "@vector-im/compound-design-tokens": "^2.0.1",
"@vector-im/compound-web": "^7.4.0", "@vector-im/compound-web": "^7.5.0",
"@vector-im/matrix-wysiwyg": "2.37.13", "@vector-im/matrix-wysiwyg": "2.37.13",
"@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/core": "^3.0.4",
"@zxcvbn-ts/language-common": "^3.0.4", "@zxcvbn-ts/language-common": "^3.0.4",
@ -114,15 +116,15 @@
"jsrsasign": "^11.0.0", "jsrsasign": "^11.0.0",
"jszip": "^3.7.0", "jszip": "^3.7.0",
"katex": "^0.16.0", "katex": "^0.16.0",
"linkify-element": "4.1.3", "linkify-element": "4.2.0",
"linkify-react": "4.1.3", "linkify-react": "4.2.0",
"linkify-string": "4.1.3", "linkify-string": "4.2.0",
"linkifyjs": "4.1.3", "linkifyjs": "4.2.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"maplibre-gl": "^4.0.0", "maplibre-gl": "^4.0.0",
"matrix-encrypt-attachment": "^1.0.3", "matrix-encrypt-attachment": "^1.0.3",
"matrix-events-sdk": "0.0.1", "matrix-events-sdk": "0.0.1",
"matrix-js-sdk": "34.13.0", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
"matrix-widget-api": "^1.10.0", "matrix-widget-api": "^1.10.0",
"memoize-one": "^6.0.0", "memoize-one": "^6.0.0",
"mime": "^4.0.4", "mime": "^4.0.4",
@ -214,7 +216,6 @@
"babel-loader": "^9.0.0", "babel-loader": "^9.0.0",
"babel-plugin-jsx-remove-data-test-id": "^3.0.0", "babel-plugin-jsx-remove-data-test-id": "^3.0.0",
"blob-polyfill": "^9.0.0", "blob-polyfill": "^9.0.0",
"buffer": "^6.0.3",
"chokidar": "^4.0.0", "chokidar": "^4.0.0",
"concurrently": "^9.0.0", "concurrently": "^9.0.0",
"copy-webpack-plugin": "^12.0.0", "copy-webpack-plugin": "^12.0.0",
@ -268,11 +269,12 @@
"postcss-preset-env": "^10.0.0", "postcss-preset-env": "^10.0.0",
"postcss-scss": "^4.0.4", "postcss-scss": "^4.0.4",
"postcss-simple-vars": "^7.0.1", "postcss-simple-vars": "^7.0.1",
"prettier": "3.3.3", "prettier": "3.4.2",
"process": "^0.11.10", "process": "^0.11.10",
"raw-loader": "^4.0.2", "raw-loader": "^4.0.2",
"rimraf": "^6.0.0", "rimraf": "^6.0.0",
"semver": "^7.5.2", "semver": "^7.5.2",
"source-map-loader": "^5.0.0",
"stylelint": "^16.1.0", "stylelint": "^16.1.0",
"stylelint-config-standard": "^36.0.0", "stylelint-config-standard": "^36.0.0",
"stylelint-scss": "^6.0.0", "stylelint-scss": "^6.0.0",

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
import { test } from "."; import { test } from ".";
test.describe("Read receipts", () => { test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("Room list order", () => { 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 if 'unread first' is selected", async ({
roomAlpha: room1, roomAlpha: room1,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -36,7 +36,7 @@ test.describe("Security user settings tab", () => {
}); });
test.describe("AnalyticsLearnMoreDialog", () => { test.describe("AnalyticsLearnMoreDialog", () => {
test("should be rendered properly", async ({ app, page }) => { test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page }) => {
const tab = await app.settings.openUserSettings("Security"); const tab = await app.settings.openUserSettings("Security");
await tab.getByRole("button", { name: "Learn more" }).click(); await tab.getByRole("button", { name: "Learn more" }).click();
await expect(page.locator(".mx_AnalyticsLearnMoreDialog_wrapper .mx_Dialog")).toMatchScreenshot( await expect(page.locator(".mx_AnalyticsLearnMoreDialog_wrapper .mx_Dialog")).toMatchScreenshot(

View file

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

View file

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

View file

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

View file

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

View file

@ -25,7 +25,7 @@ test.describe("Threads", () => {
}); });
// Flaky: https://github.com/vector-im/element-web/issues/26452 // Flaky: https://github.com/vector-im/element-web/issues/26452
test.skip("should be usable for a conversation", async ({ page, app, bot }) => { test.skip("should be usable for a conversation", { tag: "@screenshot" }, async ({ page, app, bot }) => {
const roomId = await app.client.createRoom({}); const roomId = await app.client.createRoom({});
await app.client.inviteUser(roomId, bot.credentials.userId); await app.client.inviteUser(roomId, bot.credentials.userId);
await bot.joinRoom(roomId); await bot.joinRoom(roomId);
@ -150,7 +150,7 @@ test.describe("Threads", () => {
).toHaveCSS("padding-inline-start", ThreadViewGroupSpacingStart); ).toHaveCSS("padding-inline-start", ThreadViewGroupSpacingStart);
// Take snapshot of group layout (IRC layout is not available on ThreadView) // Take snapshot of group layout (IRC layout is not available on ThreadView)
expect(page.locator(".mx_ThreadView")).toMatchScreenshot( await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
"ThreadView_with_reaction_and_a_hidden_event_on_group_layout.png", "ThreadView_with_reaction_and_a_hidden_event_on_group_layout.png",
{ {
mask: mask, mask: mask,
@ -174,7 +174,7 @@ test.describe("Threads", () => {
.toHaveCSS("margin-inline-start", "0px"); .toHaveCSS("margin-inline-start", "0px");
// Take snapshot of bubble layout // Take snapshot of bubble layout
expect(page.locator(".mx_ThreadView")).toMatchScreenshot( await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
"ThreadView_with_reaction_and_a_hidden_event_on_bubble_layout.png", "ThreadView_with_reaction_and_a_hidden_event_on_bubble_layout.png",
{ {
mask: mask, mask: mask,
@ -351,7 +351,10 @@ test.describe("Threads", () => {
}); });
}); });
test("should send location and reply to the location on ThreadView", async ({ page, app, bot }) => { test(
"should send location and reply to the location on ThreadView",
{ tag: "@screenshot" },
async ({ page, app, bot }) => {
const roomId = await app.client.createRoom({}); const roomId = await app.client.createRoom({});
await app.client.inviteUser(roomId, bot.credentials.userId); await app.client.inviteUser(roomId, bot.credentials.userId);
await bot.joinRoom(roomId); await bot.joinRoom(roomId);
@ -401,7 +404,8 @@ test.describe("Threads", () => {
// Take a snapshot of reply to the shared location // Take a snapshot of reply to the shared location
await page.addStyleTag({ content: css }); await page.addStyleTag({ content: css });
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot("Reply_to_the_location_on_ThreadView.png"); await expect(page.locator(".mx_ThreadView")).toMatchScreenshot("Reply_to_the_location_on_ThreadView.png");
}); },
);
test("right panel behaves correctly", async ({ page, app, user }) => { test("right panel behaves correctly", async ({ page, app, user }) => {
// Create room // Create room

View file

@ -137,7 +137,10 @@ test.describe("Timeline", () => {
}); });
test.describe("configure room", () => { test.describe("configure room", () => {
test("should create and configure a room on IRC layout", async ({ page, app, room }) => { test(
"should create and configure a room on IRC layout",
{ tag: "@screenshot" },
async ({ page, app, room }) => {
await page.goto(`/#/room/${room.roomId}`); await page.goto(`/#/room/${room.roomId}`);
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC); await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
await expect( await expect(
@ -151,9 +154,13 @@ test.describe("Timeline", () => {
await expect(page.locator(".mx_TimelineSeparator")).toHaveText("today"); await expect(page.locator(".mx_TimelineSeparator")).toHaveText("today");
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("configured-room-irc-layout.png"); await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("configured-room-irc-layout.png");
}); },
);
test("should have an expanded generic event list summary (GELS) on IRC layout", async ({ page, app, room }) => { test(
"should have an expanded generic event list summary (GELS) on IRC layout",
{ tag: "@screenshot" },
async ({ page, app, room }) => {
await page.goto(`/#/room/${room.roomId}`); await page.goto(`/#/room/${room.roomId}`);
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC); await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
@ -179,13 +186,13 @@ test.describe("Timeline", () => {
} }
`, `,
}); });
}); },
);
test("should have an expanded generic event list summary (GELS) on compact modern/group layout", async ({ test(
page, "should have an expanded generic event list summary (GELS) on compact modern/group layout",
app, { tag: "@screenshot" },
room, async ({ page, app, room }) => {
}) => {
await page.goto(`/#/room/${room.roomId}`); await page.goto(`/#/room/${room.roomId}`);
// Set compact modern layout // Set compact modern layout
@ -213,13 +220,13 @@ test.describe("Timeline", () => {
} }
`, `,
}); });
}); },
);
test("should click 'collapse' on the first hovered info event line inside GELS on bubble layout", async ({ test(
page, "should click 'collapse' on the first hovered info event line inside GELS on bubble layout",
app, { tag: "@screenshot" },
room, async ({ page, app, room }) => {
}) => {
// This test checks clickability of the "Collapse" link button, which had been covered with // This test checks clickability of the "Collapse" link button, which had been covered with
// MessageActionBar's safe area - https://github.com/vector-im/element-web/issues/22864 // MessageActionBar's safe area - https://github.com/vector-im/element-web/issues/22864
@ -250,7 +257,9 @@ test.describe("Timeline", () => {
}); });
// Click "collapse" link button on the first hovered info event line // Click "collapse" link button on the first hovered info event line
const firstTile = gels.locator(".mx_GenericEventListSummary_unstyledList .mx_EventTile_info:first-of-type"); const firstTile = gels.locator(
".mx_GenericEventListSummary_unstyledList .mx_EventTile_info:first-of-type",
);
await firstTile.hover(); await firstTile.hover();
await expect(firstTile.getByRole("toolbar", { name: "Message Actions" })).toBeVisible(); await expect(firstTile.getByRole("toolbar", { name: "Message Actions" })).toBeVisible();
await gels.getByRole("button", { name: "Collapse" }).click(); await gels.getByRole("button", { name: "Collapse" }).click();
@ -262,15 +271,13 @@ test.describe("Timeline", () => {
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("collapsed-gels-bubble-layout.png", { await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("collapsed-gels-bubble-layout.png", {
mask: [page.locator(".mx_MessageTimestamp")], mask: [page.locator(".mx_MessageTimestamp")],
}); });
}); },
);
test("should add inline start margin to an event line on IRC layout", async ({ test(
page, "should add inline start margin to an event line on IRC layout",
app, { tag: "@screenshot" },
room, async ({ page, app, room, axe, checkA11y }) => {
axe,
checkA11y,
}) => {
axe.disableRules("color-contrast"); axe.disableRules("color-contrast");
await page.goto(`/#/room/${room.roomId}`); await page.goto(`/#/room/${room.roomId}`);
@ -312,7 +319,8 @@ test.describe("Timeline", () => {
}, },
); );
await checkA11y(); await checkA11y();
}); },
);
}); });
test.describe("message displaying", () => { test.describe("message displaying", () => {
@ -332,11 +340,10 @@ test.describe("Timeline", () => {
).toBeVisible(); ).toBeVisible();
}; };
test("should align generic event list summary with messages and emote on IRC layout", async ({ test(
page, "should align generic event list summary with messages and emote on IRC layout",
app, { tag: "@screenshot" },
room, async ({ page, app, room }) => {
}) => {
// This test aims to check: // This test aims to check:
// 1. Alignment of collapsed GELS (generic event list summary) and messages // 1. Alignment of collapsed GELS (generic event list summary) and messages
// 2. Alignment of expanded GELS and messages // 2. Alignment of expanded GELS and messages
@ -372,10 +379,9 @@ test.describe("Timeline", () => {
// = var(--name-width) + var(--icon-width) + var(--MessageTimestamp-width) + 2 * var(--right-padding) // = var(--name-width) + var(--icon-width) + var(--MessageTimestamp-width) + 2 * var(--right-padding)
// = 80 + 14 + 46 + 2 * 5 // = 80 + 14 + 46 + 2 * 5
// = 150px // = 150px
await expect(page.locator(".mx_GenericEventListSummary[data-layout=irc] > .mx_EventTile_line")).toHaveCSS( await expect(
"padding-inline-start", page.locator(".mx_GenericEventListSummary[data-layout=irc] > .mx_EventTile_line"),
"150px", ).toHaveCSS("padding-inline-start", "150px");
);
// Check width and spacing values of elements in .mx_EventTile, which should be equal to 150px // Check width and spacing values of elements in .mx_EventTile, which should be equal to 150px
// --right-padding should be applied // --right-padding should be applied
for (const locator of await page.locator(".mx_EventTile > a").all()) { for (const locator of await page.locator(".mx_EventTile > a").all()) {
@ -416,10 +422,13 @@ test.describe("Timeline", () => {
page.locator(".mx_EventTile[data-layout=irc].mx_EventTile_info:first-of-type .mx_EventTile_line"), page.locator(".mx_EventTile[data-layout=irc].mx_EventTile_info:first-of-type .mx_EventTile_line"),
).toHaveCSS("margin-inline-start", "99px"); ).toHaveCSS("margin-inline-start", "99px");
// Record alignment of expanded GELS and messages on messagePanel // Record alignment of expanded GELS and messages on messagePanel
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("expanded-gels-and-messages-irc-layout.png", { await expect(page.locator(".mx_MainSplit")).toMatchScreenshot(
"expanded-gels-and-messages-irc-layout.png",
{
// Exclude timestamp from snapshot of mx_MainSplit // Exclude timestamp from snapshot of mx_MainSplit
mask: [page.locator(".mx_MessageTimestamp")], mask: [page.locator(".mx_MessageTimestamp")],
}); },
);
// 3. Alignment of expanded GELS and placeholder of deleted message // 3. Alignment of expanded GELS and placeholder of deleted message
// Delete the second (last) message // Delete the second (last) message
@ -431,15 +440,20 @@ test.describe("Timeline", () => {
await page.locator(".mx_Dialog_buttons").getByRole("button", { name: "Remove" }).click(); await page.locator(".mx_Dialog_buttons").getByRole("button", { name: "Remove" }).click();
// Make sure the dialog was closed and the second (last) message was redacted // Make sure the dialog was closed and the second (last) message was redacted
await expect(page.locator(".mx_Dialog")).not.toBeVisible(); await expect(page.locator(".mx_Dialog")).not.toBeVisible();
await expect(page.locator(".mx_GenericEventListSummary .mx_EventTile_last .mx_RedactedBody")).toBeVisible(); await expect(
page.locator(".mx_GenericEventListSummary .mx_EventTile_last .mx_RedactedBody"),
).toBeVisible();
await expect( await expect(
page.locator(".mx_GenericEventListSummary .mx_EventTile_last .mx_EventTile_receiptSent"), page.locator(".mx_GenericEventListSummary .mx_EventTile_last .mx_EventTile_receiptSent"),
).toBeVisible(); ).toBeVisible();
// Record alignment of expanded GELS and placeholder of deleted message on messagePanel // Record alignment of expanded GELS and placeholder of deleted message on messagePanel
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("expanded-gels-redaction-placeholder.png", { await expect(page.locator(".mx_MainSplit")).toMatchScreenshot(
"expanded-gels-redaction-placeholder.png",
{
// Exclude timestamp from snapshot of mx_MainSplit // Exclude timestamp from snapshot of mx_MainSplit
mask: [page.locator(".mx_MessageTimestamp")], mask: [page.locator(".mx_MessageTimestamp")],
}); },
);
// 4. Alignment of expanded GELS, placeholder of deleted message, and emote // 4. Alignment of expanded GELS, placeholder of deleted message, and emote
// Send a emote // Send a emote
@ -447,7 +461,10 @@ test.describe("Timeline", () => {
.locator(".mx_RoomView_body") .locator(".mx_RoomView_body")
.getByRole("textbox", { name: "Send a message…" }) .getByRole("textbox", { name: "Send a message…" })
.fill("/me says hello to Mr. Bot"); .fill("/me says hello to Mr. Bot");
await page.locator(".mx_RoomView_body").getByRole("textbox", { name: "Send a message…" }).press("Enter"); await page
.locator(".mx_RoomView_body")
.getByRole("textbox", { name: "Send a message…" })
.press("Enter");
// Check inline start margin of its avatar // Check inline start margin of its avatar
// Here --right-padding is for the avatar on the message line // Here --right-padding is for the avatar on the message line
// See: _IRCLayout.pcss // See: _IRCLayout.pcss
@ -456,15 +473,21 @@ test.describe("Timeline", () => {
// = 80 + 14 + 1 * 5 // = 80 + 14 + 1 * 5
await expect(page.locator(".mx_EventTile_emote .mx_EventTile_avatar")).toHaveCSS("margin-left", "99px"); await expect(page.locator(".mx_EventTile_emote .mx_EventTile_avatar")).toHaveCSS("margin-left", "99px");
// Make sure emote was sent // Make sure emote was sent
await expect(page.locator(".mx_EventTile_last.mx_EventTile_emote .mx_EventTile_receiptSent")).toBeVisible(); await expect(
page.locator(".mx_EventTile_last.mx_EventTile_emote .mx_EventTile_receiptSent"),
).toBeVisible();
// Record alignment of expanded GELS, placeholder of deleted message, and emote // Record alignment of expanded GELS, placeholder of deleted message, and emote
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("expanded-gels-emote-irc-layout.png", { await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("expanded-gels-emote-irc-layout.png", {
// Exclude timestamp from snapshot of mx_MainSplit // Exclude timestamp from snapshot of mx_MainSplit
mask: [page.locator(".mx_MessageTimestamp")], mask: [page.locator(".mx_MessageTimestamp")],
}); });
}); },
);
test("should render EventTiles on IRC, modern (group), and bubble layout", async ({ page, app, room }) => { test(
"should render EventTiles on IRC, modern (group), and bubble layout",
{ tag: "@screenshot" },
async ({ page, app, room }) => {
const screenshotOptions = { const screenshotOptions = {
// Hide because flaky - See https://github.com/vector-im/element-web/issues/24957 // Hide because flaky - See https://github.com/vector-im/element-web/issues/24957
mask: [page.locator(".mx_MessageTimestamp")], mask: [page.locator(".mx_MessageTimestamp")],
@ -491,7 +514,9 @@ test.describe("Timeline", () => {
await composer.fill("This message has an inline emoji 👒"); await composer.fill("This message has an inline emoji 👒");
await composer.press("Enter"); await composer.press("Enter");
await expect(page.locator(".mx_RoomView").getByText("This message has an inline emoji 👒")).toBeVisible(); await expect(
page.locator(".mx_RoomView").getByText("This message has an inline emoji 👒"),
).toBeVisible();
//////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////
// IRC layout // IRC layout
@ -558,9 +583,13 @@ test.describe("Timeline", () => {
"event-tiles-bubble-layout.png", "event-tiles-bubble-layout.png",
screenshotOptions, screenshotOptions,
); );
}); },
);
test("should set inline start padding to a hidden event line", async ({ page, app, room }) => { test(
"should set inline start padding to a hidden event line",
{ tag: "@screenshot" },
async ({ page, app, room }) => {
test.skip( test.skip(
true, true,
"Disabled due to screenshot test being flaky - https://github.com/element-hq/element-web/issues/26890", "Disabled due to screenshot test being flaky - https://github.com/element-hq/element-web/issues/26890",
@ -612,9 +641,10 @@ test.describe("Timeline", () => {
"hidden-event-line-padding-modern-layout.png", "hidden-event-line-padding-modern-layout.png",
screenshotOptions, screenshotOptions,
); );
}); },
);
test("should click view source event toggle", async ({ page, app, room }) => { test("should click view source event toggle", { tag: "@screenshot" }, async ({ page, app, room }) => {
// This test checks: // This test checks:
// 1. clickability of top left of view source event toggle // 1. clickability of top left of view source event toggle
// 2. clickability of view source toggle on IRC layout // 2. clickability of view source toggle on IRC layout
@ -712,7 +742,10 @@ test.describe("Timeline", () => {
).toBeVisible(); ).toBeVisible();
}); });
test("should render url previews", async ({ page, app, room, axe, checkA11y, context }) => { test(
"should render url previews",
{ tag: "@screenshot" },
async ({ page, app, room, axe, checkA11y, context }) => {
axe.disableRules("color-contrast"); axe.disableRules("color-contrast");
// Element Web uses a Service Worker to rewrite unauthenticated media requests to authenticated ones, but // Element Web uses a Service Worker to rewrite unauthenticated media requests to authenticated ones, but
@ -769,10 +802,14 @@ test.describe("Timeline", () => {
} }
`, `,
}); });
}); },
);
test.describe("on search results panel", () => { test.describe("on search results panel", () => {
test("should highlight search result words regardless of formatting", async ({ page, app, room }) => { test(
"should highlight search result words regardless of formatting",
{ tag: "@screenshot" },
async ({ page, app, room }) => {
await sendEvent(app.client, room.roomId); await sendEvent(app.client, room.roomId);
await sendEvent(app.client, room.roomId, true); await sendEvent(app.client, room.roomId, true);
await page.goto(`/#/room/${room.roomId}`); await page.goto(`/#/room/${room.roomId}`);
@ -792,9 +829,10 @@ test.describe("Timeline", () => {
await expect(page.locator(".mx_RoomView_searchResultsPanel")).toMatchScreenshot( await expect(page.locator(".mx_RoomView_searchResultsPanel")).toMatchScreenshot(
"highlighted-search-results.png", "highlighted-search-results.png",
); );
}); },
);
test("should render a fully opaque textual event", async ({ page, app, room }) => { test("should render a fully opaque textual event", { tag: "@screenshot" }, async ({ page, app, room }) => {
const stringToSearch = "Message"; // Same with string sent with sendEvent() const stringToSearch = "Message"; // Same with string sent with sendEvent()
await sendEvent(app.client, room.roomId); await sendEvent(app.client, room.roomId);
@ -918,7 +956,7 @@ test.describe("Timeline", () => {
).toHaveCount(4); ).toHaveCount(4);
}); });
test("should display a reply chain", async ({ page, app, room, homeserver }) => { test("should display a reply chain", { tag: "@screenshot" }, async ({ page, app, room, homeserver }) => {
const reply2 = "Reply again"; const reply2 = "Reply again";
await page.goto(`/#/room/${room.roomId}`); await page.goto(`/#/room/${room.roomId}`);
@ -1031,12 +1069,10 @@ test.describe("Timeline", () => {
); );
}); });
test("should send, reply, and display long strings without overflowing", async ({ test(
page, "should send, reply, and display long strings without overflowing",
app, { tag: "@screenshot" },
room, async ({ page, app, room, homeserver }) => {
homeserver,
}) => {
// Max 256 characters for display name // Max 256 characters for display name
const LONG_STRING = const LONG_STRING =
"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut " + "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut " +
@ -1146,7 +1182,8 @@ test.describe("Timeline", () => {
"long-strings-with-reply-bubble-layout.png", "long-strings-with-reply-bubble-layout.png",
screenshotOptions, screenshotOptions,
); );
}); },
);
async function testImageRendering(page: Page, app: ElementAppPage, room: { roomId: string }) { async function testImageRendering(page: Page, app: ElementAppPage, room: { roomId: string }) {
await app.viewRoomById(room.roomId); await app.viewRoomById(room.roomId);
@ -1176,7 +1213,7 @@ test.describe("Timeline", () => {
); );
} }
test("should render images in the timeline", async ({ page, app, room, context }) => { test("should render images in the timeline", { tag: "@screenshot" }, async ({ page, app, room, context }) => {
await testImageRendering(page, app, room); await testImageRendering(page, app, room);
}); });
@ -1188,7 +1225,10 @@ test.describe("Timeline", () => {
// In practice, this means this test will *always* succeed because it ends up relying on fallback behaviour tested // In practice, this means this test will *always* succeed because it ends up relying on fallback behaviour tested
// above (unless of course the above tests are also broken). // above (unless of course the above tests are also broken).
test.describe("MSC3916 - Authenticated Media", () => { test.describe("MSC3916 - Authenticated Media", () => {
test("should render authenticated images in the timeline", async ({ page, app, room, context }) => { test(
"should render authenticated images in the timeline",
{ tag: "@screenshot" },
async ({ page, app, room, context }) => {
// Note: we have to use `context` instead of `page` for routing, otherwise we'll miss Service Worker events. // Note: we have to use `context` instead of `page` for routing, otherwise we'll miss Service Worker events.
// See https://playwright.dev/docs/service-workers-experimental#network-events-and-routing // See https://playwright.dev/docs/service-workers-experimental#network-events-and-routing
@ -1231,7 +1271,8 @@ test.describe("Timeline", () => {
// We check the same screenshot because there should be no user-visible impact to using authentication. // We check the same screenshot because there should be no user-visible impact to using authentication.
await testImageRendering(page, app, room); await testImageRendering(page, app, room);
}); },
);
}); });
}); });
}); });

View file

@ -11,7 +11,7 @@ import { test, expect } from "../../element-web-test";
test.describe("User Menu", () => { test.describe("User Menu", () => {
test.use({ displayName: "Jeff" }); test.use({ displayName: "Jeff" });
test("should contain our name & userId", async ({ page, user }) => { test("should contain our name & userId", { tag: "@screenshot" }, async ({ page, user }) => {
await page.getByRole("button", { name: "User menu", exact: true }).click(); await page.getByRole("button", { name: "User menu", exact: true }).click();
const menu = page.getByRole("menu"); const menu = page.getByRole("menu");

View file

@ -26,7 +26,7 @@ test.describe("User Onboarding (new user)", () => {
await expect(page.locator(".mx_UserOnboardingList")).toBeVisible(); await expect(page.locator(".mx_UserOnboardingList")).toBeVisible();
}); });
test("page is shown and preference exists", async ({ page, app }) => { test("page is shown and preference exists", { tag: "@screenshot" }, async ({ page, app }) => {
await expect(page.locator(".mx_UserOnboardingPage")).toMatchScreenshot( await expect(page.locator(".mx_UserOnboardingPage")).toMatchScreenshot(
"User-Onboarding-new-user-page-is-shown-and-preference-exists-1.png", "User-Onboarding-new-user-page-is-shown-and-preference-exists-1.png",
); );
@ -34,7 +34,7 @@ test.describe("User Onboarding (new user)", () => {
await expect(page.getByText("Show shortcut to welcome checklist above the room list")).toBeVisible(); await expect(page.getByText("Show shortcut to welcome checklist above the room list")).toBeVisible();
}); });
test("app download dialog", async ({ page }) => { test("app download dialog", { tag: "@screenshot" }, async ({ page }) => {
await page.getByRole("button", { name: "Download apps" }).click(); await page.getByRole("button", { name: "Download apps" }).click();
await expect( await expect(
page.getByRole("dialog").getByRole("heading", { level: 1, name: "Download Element" }), page.getByRole("dialog").getByRole("heading", { level: 1, name: "Download Element" }),

View file

@ -14,7 +14,7 @@ test.describe("UserView", () => {
botCreateOpts: { displayName: "Usman" }, botCreateOpts: { displayName: "Usman" },
}); });
test("should render the user view as expected", async ({ page, homeserver, user, bot }) => { test("should render the user view as expected", { tag: "@screenshot" }, async ({ page, homeserver, user, bot }) => {
await page.goto(`/#/user/${bot.credentials.userId}`); await page.goto(`/#/user/${bot.credentials.userId}`);
const rightPanel = page.locator("#mx_RightPanel"); const rightPanel = page.locator("#mx_RightPanel");

View file

@ -70,7 +70,7 @@ test.describe("Widget Layout", () => {
await app.viewRoomByName(ROOM_NAME); await app.viewRoomByName(ROOM_NAME);
}); });
test("should be set properly", async ({ page }) => { test("should be set properly", { tag: "@screenshot" }, async ({ page }) => {
await expect(page.locator(".mx_AppsDrawer")).toMatchScreenshot("apps-drawer.png"); await expect(page.locator(".mx_AppsDrawer")).toMatchScreenshot("apps-drawer.png");
}); });

View file

@ -314,6 +314,10 @@ export const expect = baseExpect.extend({
const testInfo = test.info(); const testInfo = test.info();
if (!testInfo) throw new Error(`toMatchScreenshot() must be called during the test`); if (!testInfo) throw new Error(`toMatchScreenshot() must be called during the test`);
if (!testInfo.tags.includes("@screenshot")) {
throw new Error("toMatchScreenshot() must be used in a test tagged with @screenshot");
}
const page = "page" in receiver ? receiver.page() : receiver; const page = "page" in receiver ? receiver.page() : receiver;
let css = ` let css = `

View file

@ -20,7 +20,7 @@ import { randB64Bytes } from "../../utils/rand";
// Docker tag to use for synapse docker image. // Docker tag to use for synapse docker image.
// We target a specific digest as every now and then a Synapse update will break our CI. // We target a specific digest as every now and then a Synapse update will break our CI.
// This digest is updated by the playwright-image-updates.yaml workflow periodically. // This digest is updated by the playwright-image-updates.yaml workflow periodically.
const DOCKER_TAG = "develop@sha256:e163b15bf4905e4067dece856cca00e6ac8d1d655f4f1307978eee256b3ea775"; const DOCKER_TAG = "develop@sha256:6b82dba715fa7ae641010b4cc5e71edaeb9cc05a50ac5b9e4ff09afa9cd2a80d";
async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise<Omit<HomeserverConfig, "dockerUrl">> { async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise<Omit<HomeserverConfig, "dockerUrl">> {
const templateDir = path.join(__dirname, "templates", opts.template); const templateDir = path.join(__dirname, "templates", opts.template);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

After

Width:  |  Height:  |  Size: 224 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Before After
Before After

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