Compare commits
577 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bb4a7e9613 | |||
| 1a6b0e22a1 | |||
| 8f9953f419 | |||
| 12df09bd4b | |||
| cd515444a8 | |||
| f4c62abbcd | |||
| 73e1b87075 | |||
| 4b4289e211 | |||
| 7b89d84acb | |||
| 9df7182c0c | |||
| 2d16498fe6 | |||
| 021e222719 | |||
| 9df9fb9428 | |||
| 29411f0ded | |||
| 5fc98d0a36 | |||
| a08c34142f | |||
| 44a2c9936d | |||
| f0eb95495e | |||
| 4437dadef6 | |||
| 193cdff562 | |||
| d01f40bf27 | |||
| 1a87865134 | |||
| 764892bd41 | |||
| fb263ee511 | |||
| 8fa7b5ca2c | |||
| e568ed8aac | |||
| abb014553b | |||
| ae8769e12d | |||
| fd86405338 | |||
| a0195fc4d6 | |||
| ced3c25785 | |||
| 370d2ec7d2 | |||
| 14917f9df5 | |||
| 6423f2d8c0 | |||
| feae8ed8b5 | |||
| 549bdb8cb7 | |||
| e1b62c3370 | |||
| 354a05d89f | |||
| 86ea6bd6b9 | |||
| a054e785ea | |||
| 42009ce6ec | |||
| 356119da79 | |||
| d823d633e3 | |||
| 1f6d1dbc0d | |||
| 12a3abc0d5 | |||
| e90bc4a2f3 | |||
| 0d9f205505 | |||
| ac9ef6c2a2 | |||
| 4c474f5639 | |||
| 3f3ed93b8a | |||
| 133a56da65 | |||
| b06422d848 | |||
| 2c58f82298 | |||
| 384adea5a2 | |||
| cb6c48a493 | |||
| 73b8b51ea5 | |||
| e8db72d81a | |||
| 106b0e09ae | |||
| 8253d64021 | |||
| 9428c10284 | |||
| e58e803368 | |||
| aeaa73adf6 | |||
| 8834438a20 | |||
| 6b67b24254 | |||
| 73d4b63ada | |||
| 1044a95687 | |||
| abae870b83 | |||
| 4afba408c0 | |||
| c3e82cf33b | |||
| 3b1b39f822 | |||
| cf4b8744ab | |||
| aadf760e3c | |||
| 733755abb2 | |||
| 9c09a1b731 | |||
| 30f442208a | |||
| d7f5546294 | |||
| 64d3802efe | |||
| 583eae63f7 | |||
| d4aea25600 | |||
| aa0abdb43a | |||
| 3e63216822 | |||
| 475b89ec46 | |||
| b495628e6f | |||
| ef5fad8d6a | |||
| 93ab87c4a1 | |||
| e8b4b9b6ed | |||
| 421fbe4a52 | |||
| 35b9b12eae | |||
| de4a1e6d35 | |||
| 5475edbbc5 | |||
| dfdd438ae6 | |||
| c106abd721 | |||
| 181b06b3c8 | |||
| b63c3646bd | |||
| 201f5dfd90 | |||
| 80cf9e5b4a | |||
| cc9549da0a | |||
| 9151640d40 | |||
| f615968835 | |||
| 9a8ffbe0bd | |||
| 733c685d5e | |||
| abefefc981 | |||
| 91e9a0058c | |||
| e2197307b4 | |||
| c982a4c89b | |||
| 1670015ef7 | |||
| 5d2c0de9e2 | |||
| 65c744f16c | |||
| a7cd6eac31 | |||
| d46cfe154b | |||
| 8c5ccfe0e1 | |||
| 936c75ff1c | |||
| 2204493094 | |||
| 23b11aaf86 | |||
| dc42c2518e | |||
| 9e47271afe | |||
| 273a891b7b | |||
| eef8cad229 | |||
| 7010f2091c | |||
| 17efc81f0b | |||
| ad2e6d37bc | |||
| e30adf4eb3 | |||
| a210d3c29e | |||
| 1096ca2066 | |||
| 4186b8e8e8 | |||
| 7b9e586c3a | |||
| ec924deaa0 | |||
| b97a0be0fd | |||
| a132b9167d | |||
| b860a3864d | |||
| 4c4bfcde7e | |||
| ca6943cb43 | |||
| 70f26f9142 | |||
| 3fd5718fcd | |||
| 917237fc3c | |||
| b6b0b0009c | |||
| 561dbd32be | |||
| 540e620f40 | |||
| 03e12cbd22 | |||
| 5e80431399 | |||
| a5e09ebb53 | |||
| 60a7a22c7b | |||
| 04dff2d2b6 | |||
| 16f17d9127 | |||
| f5ec194937 | |||
| b4d0c21abf | |||
| 253dcb44dd | |||
| 70e40009a3 | |||
| 1721b69017 | |||
| 6486a6b5ff | |||
| 5ba09a5f90 | |||
| 52061d624b | |||
| f4bd466b02 | |||
| d5602ebea8 | |||
| ec8b7bf109 | |||
| 134ffe3de2 | |||
| 7261b3eef1 | |||
| bbe4280c2c | |||
| 121c2d18e9 | |||
| ce498ef983 | |||
| 725b191ace | |||
| ee404f098b | |||
| b1745318fb | |||
| 8cae8df17f | |||
| 4cc51a6756 | |||
| d197fb4e30 | |||
| 6e9fc9b8fa | |||
| 4beaa52b28 | |||
| 3abf74ef44 | |||
| e0cddf8e4a | |||
| 08dbd78a7c | |||
| dc06d36b9f | |||
| 950b4cc23a | |||
| 1dcc19b4b6 | |||
| edea3fffdf | |||
| 6c1dc7051f | |||
| 7fd837e723 | |||
| e74e146b4c | |||
| 1107ddf695 | |||
| 63dbc2a60b | |||
| 592d7ca6b6 | |||
| 63aa8cad6a | |||
| 5de316b752 | |||
| 811670a08c | |||
| 6b00466a85 | |||
| f12b23a87a | |||
| 2bef316bed | |||
| c05795c1bb | |||
| 2c89396a6f | |||
| e0deef531c | |||
| cffd8cfd70 | |||
| 11fd669c26 | |||
| 6d1cd514e4 | |||
| 46bff1f9e6 | |||
| e53a148da2 | |||
| d5ec23f032 | |||
| b1ed35e9b8 | |||
| 5e140768f0 | |||
| d7a5659180 | |||
| 063e0802f4 | |||
| 2d3e2fcb70 | |||
| 7bbd86ca10 | |||
| f18eb24144 | |||
| f6461ed944 | |||
| b790019ecc | |||
| 0a8ef85f2f | |||
| 4315038346 | |||
| 0391543bbc | |||
| 3e04b24d1e | |||
| e2ffaef7c3 | |||
| 93ca06d6b7 | |||
| f24003d217 | |||
| 51e8573ddc | |||
| b79a34e9fc | |||
| e9dbddfc97 | |||
| 319044f2f9 | |||
| bee4c1cdaf | |||
| 823d43bfce | |||
| f1c0e556ab | |||
| 0f515f581e | |||
| 1974b50213 | |||
| 1e3dc4f9b9 | |||
| 5b44a1cd16 | |||
| 60c65e5649 | |||
| 86d9e32d53 | |||
| 2b3720b4a2 | |||
| 27f6129dab | |||
| 2b3425f0e8 | |||
| a6f0d25525 | |||
| 72de8745e9 | |||
| 00a2e704ec | |||
| 0079270fa5 | |||
| 4e72eb6dcc | |||
| 91d153aca3 | |||
| 520dfffc95 | |||
| f3aef2f1cc | |||
| d42c816558 | |||
| a2ca504fd6 | |||
| 7fc83fa30e | |||
| 196c8a082a | |||
| 82398f7d50 | |||
| 90ab1534c9 | |||
| 3a21741737 | |||
| 4591a72d3a | |||
| a96327b691 | |||
| 28e5008abf | |||
| e322627d3e | |||
| 9ad27f61f2 | |||
| 267b63a10e | |||
| bf69ddf2b4 | |||
| 225fe7c406 | |||
| 6d71191af9 | |||
| fd4bb50a14 | |||
| 4af0608995 | |||
| ccf3fcf4af | |||
| 1a9ce45038 | |||
| 7206db8243 | |||
| d11c2e880b | |||
| 2b688528e3 | |||
| c9d7142267 | |||
| 3ac2b36657 | |||
| 226824890a | |||
| 83d752cfd7 | |||
| 73e2770246 | |||
| a0d4df56c0 | |||
| cd061ee9f5 | |||
| e30d825503 | |||
| e98b9978d7 | |||
| 5a3559a9bb | |||
| 30c27af170 | |||
| d51eb5dfd3 | |||
| a9e76dbb8a | |||
| 4614d2f395 | |||
| a5ec21bd06 | |||
| 5bb1d3d461 | |||
| 30cbbd48b3 | |||
| dd71370223 | |||
| e16626519f | |||
| a604153ece | |||
| 2c73e09907 | |||
| f243b3dd54 | |||
| 82f185700e | |||
| aadeb01f97 | |||
| 9c1f3ba928 | |||
| 31d5d60b4c | |||
| 8169d1c21b | |||
| 5f7b56a89f | |||
| cb27334d3d | |||
| ccadd73953 | |||
| c96f5adcef | |||
| c3966aea82 | |||
| fba0da2eaa | |||
| 582537a879 | |||
| 33fbb47d93 | |||
| 7698c72bbf | |||
| 9233c0c0db | |||
| edfc66fa81 | |||
| 1bd24aeb27 | |||
| 48e262d8f1 | |||
| 0561301d98 | |||
| 1ab1b54e7c | |||
| 23565d2d36 | |||
| a303e13060 | |||
| 384ddb4eab | |||
| db11815589 | |||
| a4b5142837 | |||
| 184c8dc3b3 | |||
| b048f5323c | |||
| de205f9911 | |||
| 5f154c77f9 | |||
| ea6017a26c | |||
| 1bf78ac7c4 | |||
| 839a757d94 | |||
| a8523421da | |||
| 894e684dab | |||
| b12b59a932 | |||
| 174c35b874 | |||
| 1b28a823f3 | |||
| 9b570e5cea | |||
| fec7923af0 | |||
| a7583dbdbc | |||
| 02e36fd5fb | |||
| 9ea40d14a5 | |||
| ac12b9a79a | |||
| 7b4894d444 | |||
| 40f7bf2b81 | |||
| 0126cded9e | |||
| 94aba63a1d | |||
| 84123065a7 | |||
| f35357d3b3 | |||
| 2d3b554649 | |||
| 44d8f89754 | |||
| 64a63bdbb0 | |||
| 8f40ea5c0a | |||
| 3d0ad97667 | |||
| 0d69056e7b | |||
| 65edff067a | |||
| 0d46bc924c | |||
| 4c928d2854 | |||
| 1b11b3d1c0 | |||
| 3b31bbf4a9 | |||
| 0f3938bb54 | |||
| 76b07d736c | |||
| 56662c53e9 | |||
| 8019035578 | |||
| ef180bf89d | |||
| c570936d33 | |||
| 7518d1c068 | |||
| 99ee1f95ad | |||
| 9b0af6c3ab | |||
| 0788c3a207 | |||
| 17066c03fd | |||
| a66fd6e976 | |||
| 44c3d767ce | |||
| c2f4535153 | |||
| 6a4c529083 | |||
| 2612dcd8e0 | |||
| e197f0ec4b | |||
| 4125ebea79 | |||
| e31efda9b4 | |||
| 49ad9c1d9c | |||
| 2af8e3c21f | |||
| 28384f1112 | |||
| 6502ffcbe0 | |||
| 4cfa5b3265 | |||
| 9b8c26ff22 | |||
| 815b80e8f7 | |||
| a1cd21244f | |||
| 3d4b42c44b | |||
| b7385d8936 | |||
| 07ab08a6f9 | |||
| afa2984645 | |||
| ef9732c68f | |||
| ca6443e4a2 | |||
| 81212a3c16 | |||
| 879c5c47f6 | |||
| 74acfb84aa | |||
| 10d73fa0ea | |||
| 4698f68d8a | |||
| 0c0fc60f1c | |||
| 0f6ac0a841 | |||
| 4381032d63 | |||
| 4ee9b05608 | |||
| af3d476a70 | |||
| 97e1462474 | |||
| 200d52a162 | |||
| 6b4f33bee1 | |||
| 04379cc089 | |||
| cd9a21ac93 | |||
| 17f1a54a1f | |||
| 7c40be9054 | |||
| 4930da1e97 | |||
| 051fd6e8fe | |||
| b9a90b2bfc | |||
| d0923c7b76 | |||
| 00f9283262 | |||
| 949e64c7b5 | |||
| c24cfb6311 | |||
| 214f3e20a8 | |||
| 824f262ac9 | |||
| a41a7faa75 | |||
| 79344ffd31 | |||
| c938dbe519 | |||
| b5639c25a4 | |||
| e1f7a798a2 | |||
| b83b5f4ad6 | |||
| 6d0a715ac0 | |||
| 3e31c2387c | |||
| 31d6b73476 | |||
| ecb939a68a | |||
| 24df6be6ce | |||
| b484916ea8 | |||
| 68de37a6cb | |||
| d0c3901a1a | |||
| 595141cf4b | |||
| 519bafd68f | |||
| 92b9896deb | |||
| 885e7c36d9 | |||
| cf869c9454 | |||
| 446830776f | |||
| 9685775528 | |||
| 2cd86f7c3d | |||
| 85ae19a093 | |||
| ac81502c0a | |||
| 44bb9ed71a | |||
| 636cee5dcb | |||
| d3e9d5bd92 | |||
| 4a9559bb16 | |||
| f91670f6b5 | |||
| 6520352453 | |||
| 5360f309f1 | |||
| b4a59ce009 | |||
| 90a51d4487 | |||
| c7f5d91504 | |||
| dd402c834c | |||
| 7a24eecde8 | |||
| f397f0bb7d | |||
| a3c31d82d6 | |||
| 2b1858c436 | |||
| 9c2ca140bb | |||
| 41887c61a9 | |||
| 8fba68db78 | |||
| d1d69fbc56 | |||
| 4df86c4d3f | |||
| 9f14297827 | |||
| 6752197562 | |||
| 0b82316dbe | |||
| 65ced81a8f | |||
| 0b5681bf5b | |||
| cfb8c685d4 | |||
| 24cc2455f9 | |||
| 20098f132b | |||
| 9763807c42 | |||
| 03c2f2cd5f | |||
| 46912a6a67 | |||
| a44fe85209 | |||
| 62b8d1b3ab | |||
| 3ad03e123b | |||
| 9bb434a2aa | |||
| 1c8d368cfe | |||
| a440678ffd | |||
| 75cf1ee738 | |||
| 78bc53ef0a | |||
| a6f851bbdf | |||
| 02833dac5e | |||
| 683dfaef69 | |||
| 2af22fb54f | |||
| e481865aa6 | |||
| fbd7c21ecc | |||
| 433e88670e | |||
| 0d4a45d447 | |||
| e2a89888c2 | |||
| c49ac2677b | |||
| b38f0f2dd2 | |||
| 56d0c5656a | |||
| 89102aa903 | |||
| ead847ec6a | |||
| 66f83a9663 | |||
| 4e8c99dfd1 | |||
| e5bbf7b45d | |||
| cea6abfca5 | |||
| 240045f433 | |||
| 8cf426f0e7 | |||
| 60687b27a6 | |||
| c6a6123826 | |||
| e2ae6f2288 | |||
| 3941f99e17 | |||
| 0eba5281fa | |||
| 3befdeb167 | |||
| 546861811c | |||
| 0a9dffb132 | |||
| 930a5eb8b7 | |||
| 627355e24c | |||
| b480633c15 | |||
| b9f200d349 | |||
| b75c7ee34d | |||
| ba5ce17574 | |||
| 908771235f | |||
| e972c05ccc | |||
| 50fc7bec66 | |||
| b5a7bd9eac | |||
| c9ea5f8f4f | |||
| c94eb986d5 | |||
| 079f3392bb | |||
| eda354505d | |||
| 6d58d9090f | |||
| d2c2437188 | |||
| 5927495c88 | |||
| d7736db1af | |||
| 7157587668 | |||
| 29d1c61d82 | |||
| 4b56b98d20 | |||
| 2b1fb75f44 | |||
| b55feb5a7b | |||
| 03e04dec08 | |||
| ad0a276af2 | |||
| afcf4593bc | |||
| df54bc9a52 | |||
| e8ed2f8f41 | |||
| 737d306f53 | |||
| 586ad25600 | |||
| f0686ce9a6 | |||
| 8ddc8020d7 | |||
| 12463365d6 | |||
| bb972bb084 | |||
| d93c34926b | |||
| f1372747db | |||
| 46a5a594f5 | |||
| 6837bbab67 | |||
| 98cc18fd5f | |||
| 2954e39346 | |||
| e6bdd86c4c | |||
| 3825bd80cd | |||
| df19fbc259 | |||
| 8cfa277cd8 | |||
| 98ae690c6f | |||
| a9b1311cb3 | |||
| 0fa87a2d7e | |||
| 2f1bc2ab78 | |||
| 5d866e86ba | |||
| cae6c42eb4 | |||
| 2fa2805b8d | |||
| c9288b846e | |||
| 0a4706da1e | |||
| 0b12fac200 | |||
| 5f28c50af8 | |||
| ca0b82d6e3 | |||
| ea906ed8a9 | |||
| f004a93e15 | |||
| 3a729d0487 | |||
| 3ecce9efe0 | |||
| 418598caa8 | |||
| 0d32376f8d | |||
| d45edc8c4d | |||
| 3253b73029 | |||
| 96893e8191 | |||
| 1744b18955 | |||
| 30ac522e83 | |||
| e1c29f6ff1 | |||
| 47be004a2c | |||
| dd521f5a21 | |||
| b8633472eb | |||
| af435b4f1a | |||
| 8d05c5329e | |||
| 0a2f311cc8 | |||
| 06fcf783a1 | |||
| 776d07730a | |||
| 2edfce5e7a | |||
| 3a5baec699 | |||
| 3d94420f9f | |||
| b0c81d53d9 | |||
| 505533a5f5 | |||
| 35b33ac893 | |||
| 0e8315493e | |||
| e71f9e327e | |||
| 5bc85e8ae4 | |||
| 3dde93bb69 |
+4
-3
@@ -4,12 +4,13 @@
|
||||
/pnpm-lock.yaml @element-hq/element-web-team
|
||||
|
||||
/apps/web/src/SecurityManager.ts @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/test/SecurityManager-test.ts @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/test/unit-tests/SecurityManager-test.ts @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/src/async-components/views/dialogs/security/ @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/test/unit-tests/async-components/dialogs/security/ @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/src/components/views/dialogs/security/ @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/test/components/views/dialogs/security/ @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/test/unit-tests/components/views/dialogs/security/ @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/src/stores/SetupEncryptionStore.ts @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/test/stores/SetupEncryptionStore-test.ts @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/test/unit-tests/stores/SetupEncryptionStore-test.ts @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/src/components/views/settings/tabs/user/EncryptionUserSettingsTab.tsx @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/src/components/views/settings/encryption/ @element-hq/element-crypto-web-reviewers
|
||||
/apps/web/test/unit-tests/components/views/settings/encryption/ @element-hq/element-crypto-web-reviewers
|
||||
|
||||
@@ -11,7 +11,7 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Download release tarball
|
||||
uses: robinraju/release-downloader@daf26c55d821e836577a15f77d86ddc078948b05 # v1
|
||||
uses: robinraju/release-downloader@28fc21f50d76778e7023361aa1f863e717d3d56f # v1
|
||||
with:
|
||||
tag: ${{ inputs.tag }}
|
||||
fileName: element-*.tar.gz*
|
||||
@@ -31,7 +31,9 @@ runs:
|
||||
|
||||
- name: Move webapp to out-file-path
|
||||
shell: bash
|
||||
run: mv ${{ runner.temp }}/download-verify-element-tarball/webapp ${{ inputs.out-file-path }}
|
||||
run: mv ${{ runner.temp }}/download-verify-element-tarball/webapp "$OUT_PATH"
|
||||
env:
|
||||
OUT_PATH: ${{ inputs.out-file-path }}
|
||||
|
||||
- name: Clean up temp directory
|
||||
shell: bash
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
name: Setup playwright
|
||||
description: Installs playwright browsers and sets up a cache
|
||||
inputs:
|
||||
needs-webkit:
|
||||
description: Whether to install the additional dependencies for webkit
|
||||
required: false
|
||||
default: "false"
|
||||
write-cache:
|
||||
description: Whether to write the cache back
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Calculate cache key
|
||||
id: key
|
||||
run: |
|
||||
PW_VERSION=$(pnpm --silent -- playwright --version | awk '{print $2}')
|
||||
echo "key=${PREFIX}-playwright-${PW_VERSION}" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
env:
|
||||
PREFIX: ${{ runner.os }}-${{ runner.arch }}
|
||||
|
||||
- name: Cache playwright binaries
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
if: inputs.write-cache == 'true'
|
||||
id: cache
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: ${{ steps.key.outputs.key }}
|
||||
|
||||
# When running in merge queue only restore the cache, never write it
|
||||
- name: Restore playwright binaries cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
if: inputs.write-cache != 'true'
|
||||
id: cache-restore
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: ${{ steps.key.outputs.key }}
|
||||
|
||||
- name: Install Playwright browsers
|
||||
if: (steps.cache.outputs.cache-hit || steps.cache-restore.outputs.cache-hit) != 'true'
|
||||
shell: bash
|
||||
run: pnpm playwright install --with-deps
|
||||
|
||||
# Some WebKit dependencies seem to lay outside the cache and will need to be installed separately
|
||||
- name: Install system dependencies for WebKit
|
||||
if: inputs.needs-webkit == 'true' && (steps.cache.outputs.cache-hit || steps.cache-restore.outputs.cache-hit) == 'true'
|
||||
shell: bash
|
||||
run: pnpm playwright install-deps webkit
|
||||
+33
-2
@@ -2,14 +2,45 @@
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["github>matrix-org/renovate-config-element-web"],
|
||||
"postUpdateOptions": ["pnpmDedupe"],
|
||||
"stopUpdatingLabel": "X-Blocked",
|
||||
"packageRules": [
|
||||
{
|
||||
"description": "Group all testcontainers docker digests",
|
||||
"groupName": "testcontainers docker digests",
|
||||
"groupSlug": "testcontainers-docker",
|
||||
"matchDepTypes": ["testcontainers-docker"],
|
||||
"matchPackageNames": ["*"]
|
||||
},
|
||||
{
|
||||
"description": "Separate updates to overrides from other groups",
|
||||
"matchDepTypes": ["pnpm.overrides"],
|
||||
"groupSlug": null
|
||||
},
|
||||
{
|
||||
"description": "Disable any major updates to overrides as this almost always is wrong",
|
||||
"matchDepTypes": ["pnpm.overrides"],
|
||||
"matchUpdateTypes": ["major"],
|
||||
"enabled": false
|
||||
}
|
||||
],
|
||||
"customManagers": [
|
||||
{
|
||||
"description": "Update testcontainers docker digests",
|
||||
"customType": "regex",
|
||||
"datasourceTemplate": "docker",
|
||||
"versioningTemplate": "loose",
|
||||
"description": "Update testcontainers docker digests",
|
||||
"managerFilePatterns": ["**/testcontainers/*.ts"],
|
||||
"matchStrings": ["\\s+\"(?<depName>[^@]+):(?<currentValue>[^@]+)@(?<currentDigest>sha256:[a-f0-9]+)\""]
|
||||
"matchStrings": ["\\s+\"(?<depName>[^@]+):(?<currentValue>[^@]+)@(?<currentDigest>sha256:[a-f0-9]+)\""],
|
||||
"depTypeTemplate": "testcontainers-docker"
|
||||
},
|
||||
{
|
||||
"description": "Update element-desktop hakDependencies",
|
||||
"customType": "jsonata",
|
||||
"managerFilePatterns": ["/(^|/)package\\.json$/"],
|
||||
"fileFormat": "json",
|
||||
"matchStrings": ["hakDependencies.$each(function($v, $k) { { 'packageName': $k, 'currentValue': $v } })"],
|
||||
"datasourceTemplate": "npm",
|
||||
"depTypeTemplate": "hak"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ on:
|
||||
# Privilege escalation necessary to publish to Netlify
|
||||
# 🚨 We must not execute any checked out code here.
|
||||
workflow_run: # zizmor: ignore[dangerous-triggers]
|
||||
workflows: ["End to End Tests"]
|
||||
workflows: ["Build & Test"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
# Produce a build of element-web with this version of react-sdk
|
||||
# and any matching branches of element-web and js-sdk, output it
|
||||
# as an artifact and run end-to-end tests.
|
||||
name: End to End Tests
|
||||
# builds Element Web
|
||||
# runs Playwright tests against the built Element Web
|
||||
# builds Element Desktop using the built Element Web
|
||||
#
|
||||
# Tries to use a matching js-sdk branch for the build.
|
||||
#
|
||||
# Produces a `webapp` artifact
|
||||
# Produces multiple Desktop artifacts
|
||||
# Produces multiple Playwright report artifacts
|
||||
name: Build & Test
|
||||
on:
|
||||
# CRON to run all Projects at 6am UTC
|
||||
schedule:
|
||||
@@ -10,9 +16,8 @@ on:
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
push:
|
||||
branches: [develop, master]
|
||||
repository_dispatch:
|
||||
types: [element-web-notify]
|
||||
# We do not build on push to develop as the merge_group check handles that
|
||||
branches: [staging, master]
|
||||
|
||||
# support triggering from other workflows
|
||||
workflow_call:
|
||||
@@ -35,20 +40,22 @@ concurrency:
|
||||
env:
|
||||
# fetchdep.sh needs to know our PR number
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
# Use 6 runners in the default case, but 4 when running on a schedule where we run all 5 projects (20 runners total)
|
||||
NUM_RUNNERS: ${{ github.event_name == 'schedule' && 4 || 6 }}
|
||||
# Use 4 runners in the default case, but only 1 when running on a schedule where we run all 5 projects
|
||||
NUM_RUNNERS: ${{ github.event_name == 'schedule' && 1 || 4 }}
|
||||
NX_DEFAULT_OUTPUT_STYLE: stream-without-prefixes
|
||||
|
||||
permissions: {} # No permissions required
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: "Build Element-Web"
|
||||
build_ew:
|
||||
name: "Build Element Web"
|
||||
runs-on: ubuntu-24.04
|
||||
if: inputs.skip != true
|
||||
outputs:
|
||||
num-runners: ${{ env.NUM_RUNNERS }}
|
||||
runners-matrix: ${{ steps.runner-vars.outputs.matrix }}
|
||||
# Skip pull_request runs on renovate PRs to speed up CI time, delegating to the full run in merge queue
|
||||
skip: ${{ inputs.skip || (github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')) }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
@@ -57,7 +64,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
cache: "pnpm"
|
||||
node-version: "lts/*"
|
||||
@@ -79,7 +86,7 @@ jobs:
|
||||
run: VERSION=$(scripts/get-version-from-git.sh) pnpm run build
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: webapp
|
||||
path: apps/web/webapp
|
||||
@@ -87,17 +94,17 @@ jobs:
|
||||
|
||||
- name: Calculate runner variables
|
||||
id: runner-vars
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
||||
with:
|
||||
script: |
|
||||
const numRunners = parseInt(process.env.NUM_RUNNERS, 10);
|
||||
const matrix = Array.from({ length: numRunners }, (_, i) => i + 1);
|
||||
core.setOutput("matrix", JSON.stringify(matrix));
|
||||
|
||||
playwright:
|
||||
name: "Run Tests [${{ matrix.project }}] ${{ matrix.runner }}/${{ needs.build.outputs.num-runners }}"
|
||||
needs: build
|
||||
if: inputs.skip != true
|
||||
playwright_ew:
|
||||
name: "Run Tests [${{ matrix.project }}] ${{ matrix.runner }}/${{ needs.build_ew.outputs.num-runners }}"
|
||||
needs: build_ew
|
||||
if: needs.build_ew.outputs.skip == 'false'
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
actions: read
|
||||
@@ -107,7 +114,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Run multiple instances in parallel to speed up the tests
|
||||
runner: ${{ fromJSON(needs.build.outputs.runners-matrix) }}
|
||||
runner: ${{ fromJSON(needs.build_ew.outputs.runners-matrix) }}
|
||||
project:
|
||||
- Chrome
|
||||
- Firefox
|
||||
@@ -139,7 +146,7 @@ jobs:
|
||||
path: apps/web/webapp
|
||||
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: pnpm-lock.yaml
|
||||
@@ -148,105 +155,155 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Get installed Playwright version
|
||||
id: playwright
|
||||
run: echo "version=$(pnpm --silent -- playwright --version | awk '{print $2}')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache playwright binaries
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
id: playwright-cache
|
||||
- name: Setup playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright.outputs.version }}
|
||||
|
||||
- name: Install Playwright browsers
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
||||
working-directory: apps/web
|
||||
run: pnpm playwright install --with-deps --no-shell
|
||||
|
||||
- name: Install system dependencies for WebKit
|
||||
# Some WebKit dependencies seem to lay outside the cache and will need to be installed separately
|
||||
if: matrix.project == 'WebKit' && steps.playwright-cache.outputs.cache-hit == 'true'
|
||||
working-directory: apps/web
|
||||
run: pnpm playwright install-deps webkit
|
||||
needs-webkit: ${{ matrix.project == 'WebKit' }}
|
||||
write-cache: ${{ github.event_name != 'merge_group' }}
|
||||
|
||||
# We skip tests tagged with @mergequeue when running on PRs, but run them in MQ and everywhere else
|
||||
- name: Run Playwright tests
|
||||
working-directory: apps/web
|
||||
run: |
|
||||
pnpm playwright test \
|
||||
pnpm test:playwright \
|
||||
--shard "$SHARD" \
|
||||
--project="${{ matrix.project }}" \
|
||||
${{ (github.event_name == 'pull_request' && matrix.runAllTests == false ) && '--grep-invert @mergequeue' || '' }}
|
||||
env:
|
||||
SHARD: ${{ format('{0}/{1}', matrix.runner, needs.build.outputs.num-runners) }}
|
||||
SHARD: ${{ format('{0}/{1}', matrix.runner, needs.build_ew.outputs.num-runners) }}
|
||||
|
||||
- name: Upload blob report to GitHub Actions Artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: all-blob-reports-${{ matrix.project }}-${{ matrix.runner }}
|
||||
name: blob-report-${{ matrix.project }}-${{ matrix.runner }}
|
||||
path: apps/web/blob-report
|
||||
retention-days: 1
|
||||
if-no-files-found: error
|
||||
|
||||
downstream-modules:
|
||||
name: Downstream Playwright tests [element-modules]
|
||||
needs: build
|
||||
if: inputs.skip != true && github.event_name == 'merge_group'
|
||||
needs: build_ew
|
||||
if: needs.build_ew.outputs.skip == 'false' && github.event_name == 'merge_group'
|
||||
uses: element-hq/element-modules/.github/workflows/reusable-playwright-tests.yml@main # zizmor: ignore[unpinned-uses]
|
||||
with:
|
||||
webapp-artifact: webapp
|
||||
reporter: blob
|
||||
|
||||
prepare_ed:
|
||||
name: "Prepare Element Desktop"
|
||||
uses: ./.github/workflows/build_desktop_prepare.yaml
|
||||
needs: build_ew
|
||||
if: needs.build_ew.outputs.skip == 'false'
|
||||
permissions:
|
||||
contents: read
|
||||
with:
|
||||
config: ${{ (github.event.pull_request.base.ref || github.ref_name) == 'develop' && 'element.io/nightly' || 'element.io/release' }}
|
||||
version: ${{ case((github.event.pull_request.base.ref || github.ref_name) == 'develop' || github.event_name == 'merge_group', 'develop', '') }}
|
||||
webapp-artifact: webapp
|
||||
|
||||
build_ed_windows:
|
||||
needs: prepare_ed
|
||||
name: "Desktop Windows"
|
||||
uses: ./.github/workflows/build_desktop_windows.yaml
|
||||
# Skip Windows builds on PRs, as the Linux amd64 build is enough of a smoke test and includes the screenshot tests
|
||||
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'X-Run-All-Tests')
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x64, ia32, arm64]
|
||||
with:
|
||||
arch: ${{ matrix.arch }}
|
||||
blob_report: true
|
||||
|
||||
build_ed_linux:
|
||||
needs: prepare_ed
|
||||
name: "Desktop Linux"
|
||||
uses: ./.github/workflows/build_desktop_linux.yaml
|
||||
strategy:
|
||||
matrix:
|
||||
sqlcipher: [system, static]
|
||||
arch: [amd64, arm64]
|
||||
runAllTests:
|
||||
- ${{ github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'X-Run-All-Tests') }}
|
||||
exclude:
|
||||
# We ship static sqlcipher builds, so delegate testing the system builds to the merge queue
|
||||
- runAllTests: false
|
||||
sqlcipher: system
|
||||
# Additionally skip arm64 system builds on PRs, as the amd64 test is enough for a smoke test and includes the screenshot tests
|
||||
- runAllTests: false
|
||||
arch: arm64
|
||||
with:
|
||||
sqlcipher: ${{ matrix.sqlcipher }}
|
||||
arch: ${{ matrix.arch }}
|
||||
blob_report: true
|
||||
|
||||
build_ed_macos:
|
||||
needs: prepare_ed
|
||||
name: "Desktop macOS"
|
||||
uses: ./.github/workflows/build_desktop_macos.yaml
|
||||
# Skip macOS builds on PRs, as the Linux amd64 build is enough of a smoke test and includes the screenshot tests
|
||||
# and we have a very low limit of concurrent macos runners (5) across the Github org.
|
||||
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'X-Run-All-Tests')
|
||||
with:
|
||||
blob_report: true
|
||||
|
||||
complete:
|
||||
name: end-to-end-tests
|
||||
needs:
|
||||
- playwright
|
||||
- build_ew
|
||||
- playwright_ew
|
||||
- downstream-modules
|
||||
- prepare_ed
|
||||
- build_ed_windows
|
||||
- build_ed_linux
|
||||
- build_ed_macos
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
if: inputs.skip != true
|
||||
if: needs.build_ew.outputs.skip == 'false'
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: element-hq/element-web
|
||||
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
if: inputs.skip != true
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
if: inputs.skip != true
|
||||
if: needs.build_ew.outputs.skip == 'false'
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
if: needs.build_ew.outputs.skip == 'false'
|
||||
with:
|
||||
cache: "pnpm"
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Install dependencies
|
||||
if: inputs.skip != true
|
||||
if: needs.build_ew.outputs.skip == 'false'
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Download blob reports from GitHub Actions Artifacts
|
||||
if: inputs.skip != true
|
||||
if: needs.build_ew.outputs.skip == 'false'
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
pattern: all-blob-reports-*
|
||||
path: apps/web/all-blob-reports
|
||||
pattern: blob-report-*
|
||||
path: all-blob-reports
|
||||
merge-multiple: true
|
||||
|
||||
- name: Merge into HTML Report
|
||||
if: inputs.skip != true
|
||||
working-directory: apps/web
|
||||
run: pnpm playwright merge-reports --reporter=html,./playwright/flaky-reporter.ts,@element-hq/element-web-playwright-common/lib/stale-screenshot-reporter.js ./all-blob-reports
|
||||
if: needs.build_ew.outputs.skip == 'false'
|
||||
run: |
|
||||
pnpm playwright merge-reports \
|
||||
--config=playwright-merge.config.ts \
|
||||
./all-blob-reports
|
||||
env:
|
||||
# Only pass creds to the flaky-reporter on main branch runs
|
||||
GITHUB_TOKEN: ${{ github.ref_name == 'develop' && secrets.ELEMENT_BOT_TOKEN || '' }}
|
||||
PLAYWRIGHT_HTML_TITLE: ${{ case(github.event_name == 'pull_request', format('EW Playwright Report PR-{0}', env.PR_NUMBER), 'EW Playwright Report') }}
|
||||
PLAYWRIGHT_HTML_TITLE: ${{ case(github.event_name == 'pull_request', format('Playwright Report PR-{0}', env.PR_NUMBER), 'Playwright Report') }}
|
||||
|
||||
# Upload the HTML report even if one of our reporters fails, this can happen when stale screenshots are detected
|
||||
- name: Upload HTML report
|
||||
if: always() && inputs.skip != true
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
if: always() && needs.build_ew.outputs.skip == 'false'
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: html-report
|
||||
path: apps/web/playwright-report
|
||||
path: playwright-report
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
# Disable cache on Windows as it is slower than not caching
|
||||
# https://github.com/actions/setup-node/issues/975
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
run: VERSION=$(scripts/get-version-from-git.sh) pnpm run build
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: webapp-${{ matrix.image }}
|
||||
path: apps/web/webapp
|
||||
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
dpkg-gencontrol -v"$VERSION" -ldebian/tmp/DEBIAN/changelog
|
||||
dpkg-deb -Zxz --root-owner-group --build debian/tmp element-web.deb
|
||||
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: element-web.deb
|
||||
path: apps/web/element-web.deb
|
||||
|
||||
@@ -212,7 +212,7 @@ jobs:
|
||||
|
||||
- name: Stash packages.element.io
|
||||
if: needs.prepare.outputs.deploy == 'false'
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: packages.element.io
|
||||
path: packages.element.io
|
||||
@@ -250,7 +250,7 @@ jobs:
|
||||
|
||||
- name: Stash debs
|
||||
if: needs.prepare.outputs.deploy == 'false' && needs.linux.result == 'success'
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: debs
|
||||
path: |
|
||||
@@ -289,7 +289,7 @@ jobs:
|
||||
id-token: write # This is required for requesting the JWT
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6
|
||||
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6
|
||||
with:
|
||||
role-to-assume: arn:aws:iam::264135176173:role/Push-ElementDesktop-MSI
|
||||
role-session-name: githubaction-run-${{ github.run_id }}
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
name: Build and Test
|
||||
on:
|
||||
pull_request: {}
|
||||
push:
|
||||
branches: [develop, staging, master]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
permissions: {} # No permissions required
|
||||
jobs:
|
||||
fetch:
|
||||
uses: ./.github/workflows/build_desktop_prepare.yaml
|
||||
permissions:
|
||||
contents: read
|
||||
with:
|
||||
config: ${{ (github.event.pull_request.base.ref || github.ref_name) == 'develop' && 'element.io/nightly' || 'element.io/release' }}
|
||||
version: ${{ (github.event.pull_request.base.ref || github.ref_name) == 'develop' && 'develop' || '' }}
|
||||
branch-matching: true
|
||||
|
||||
windows:
|
||||
needs: fetch
|
||||
name: Windows
|
||||
uses: ./.github/workflows/build_desktop_windows.yaml
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x64, ia32, arm64]
|
||||
with:
|
||||
arch: ${{ matrix.arch }}
|
||||
blob_report: true
|
||||
|
||||
linux:
|
||||
needs: fetch
|
||||
name: "Linux (${{ matrix.arch }}) (sqlcipher: ${{ matrix.sqlcipher }})"
|
||||
uses: ./.github/workflows/build_desktop_linux.yaml
|
||||
strategy:
|
||||
matrix:
|
||||
sqlcipher: [system, static]
|
||||
arch: [amd64, arm64]
|
||||
with:
|
||||
sqlcipher: ${{ matrix.sqlcipher }}
|
||||
arch: ${{ matrix.arch }}
|
||||
blob_report: true
|
||||
|
||||
macos:
|
||||
needs: fetch
|
||||
name: macOS
|
||||
uses: ./.github/workflows/build_desktop_macos.yaml
|
||||
with:
|
||||
blob_report: true
|
||||
|
||||
tests-done:
|
||||
needs: [windows, linux, macos]
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ !cancelled() }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
cache: "pnpm"
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Download blob reports from GitHub Actions Artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
pattern: blob-report-*
|
||||
path: apps/desktop/all-blob-reports
|
||||
merge-multiple: true
|
||||
|
||||
- name: Merge into HTML Report
|
||||
working-directory: apps/desktop
|
||||
run: pnpm playwright merge-reports -c ./playwright.config.ts --reporter=html ./all-blob-reports
|
||||
|
||||
- name: Upload HTML report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: html-report
|
||||
path: apps/desktop/playwright-report
|
||||
retention-days: 14
|
||||
|
||||
- if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
|
||||
run: exit 1
|
||||
@@ -28,7 +28,7 @@ on:
|
||||
type: string
|
||||
required: false
|
||||
description: |
|
||||
The name of the prepare artifact to use, defaults to 'webapp'.
|
||||
The name of the prepare artifact to use, defaults to 'desktop-prepare'.
|
||||
The artifact must contain the following:
|
||||
+ webapp.asar - the asar archive of the webapp to embed in the desktop app
|
||||
+ electronVersion - the version of electron to use for cache keying
|
||||
@@ -38,7 +38,7 @@ on:
|
||||
|
||||
The artifact can also contain any additional files which will be applied as overrides to the checkout root before building,
|
||||
for example icons in the `build/` directory to override the app icons.
|
||||
default: "webapp"
|
||||
default: "desktop-prepare"
|
||||
test:
|
||||
type: boolean
|
||||
required: false
|
||||
@@ -73,20 +73,8 @@ jobs:
|
||||
# https://github.com/matrix-org/seshat/issues/135
|
||||
runs-on: ${{ inputs.runs-on || (inputs.arch == 'arm64' && 'ubuntu-22.04-arm' || 'ubuntu-22.04') }}
|
||||
env:
|
||||
HAK_DOCKER_IMAGE: ghcr.io/element-hq/element-web/desktop-build-env
|
||||
HAK_DOCKER_IMAGE: ghcr.io/element-hq/element-web/desktop-build-env:${{ case(github.event_name == 'push', inputs.ref || github.ref_name, github.event_name == 'release', 'staging', 'develop') }}
|
||||
steps:
|
||||
- name: Resolve docker image tag for push
|
||||
if: github.event_name == 'push'
|
||||
run: echo "HAK_DOCKER_IMAGE=$HAK_DOCKER_IMAGE:$REF" >> $GITHUB_ENV
|
||||
env:
|
||||
REF: ${{ inputs.ref || github.ref_name }}
|
||||
- name: Resolve docker image tag for release
|
||||
if: github.event_name == 'release'
|
||||
run: echo "HAK_DOCKER_IMAGE=$HAK_DOCKER_IMAGE:staging" >> $GITHUB_ENV
|
||||
- name: Resolve docker image tag for other triggers
|
||||
if: github.event_name != 'push' && github.event_name != 'release'
|
||||
run: echo "HAK_DOCKER_IMAGE=$HAK_DOCKER_IMAGE:develop" >> $GITHUB_ENV
|
||||
|
||||
- uses: nbucic/variable-mapper@0673f6891a0619ba7c002ecfed0f9f4f39017b6f
|
||||
id: config
|
||||
with:
|
||||
@@ -95,11 +83,9 @@ jobs:
|
||||
map: |
|
||||
{
|
||||
"amd64": {
|
||||
"target": "x86_64-unknown-linux-gnu",
|
||||
"arch": "x86-64"
|
||||
},
|
||||
"arm64": {
|
||||
"target": "aarch64-unknown-linux-gnu",
|
||||
"arch": "aarch64",
|
||||
"build-args": "--arm64"
|
||||
}
|
||||
@@ -118,14 +104,14 @@ jobs:
|
||||
|
||||
- name: Cache .hak
|
||||
id: cache
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ github.ref_name }}-${{ inputs.sqlcipher }}-${{ inputs.arch }}-${{ hashFiles('hakHash', 'electronVersion', 'dockerbuild/*') }}
|
||||
key: ${{ runner.os }}-${{ github.ref_name }}-${{ inputs.sqlcipher }}-${{ inputs.arch }}-${{ hashFiles('apps/desktop/hakHash', 'apps/desktop/electronVersion', 'apps/desktop/dockerbuild/*') }}
|
||||
path: |
|
||||
apps/desktop/.hak
|
||||
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: apps/desktop/.node-version
|
||||
cache: "pnpm"
|
||||
@@ -135,19 +121,19 @@ jobs:
|
||||
|
||||
- name: Install Deps
|
||||
working-directory: apps/desktop
|
||||
run: pnpm install --frozen-lockfile
|
||||
run: "pnpm install --frozen-lockfile --filter element-desktop"
|
||||
|
||||
- name: "Get modified files"
|
||||
id: changed_files
|
||||
if: steps.cache.outputs.cache-hit != 'true' && github.event_name == 'pull_request' && github.repository == 'element-hq/element-web'
|
||||
uses: tj-actions/changed-files@823fcebdb31bb35fdf2229d9f769b400309430d0 # v46
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47
|
||||
with:
|
||||
files: |
|
||||
apps/desktop/dockerbuild/**
|
||||
|
||||
# This allows contributors to test changes to the dockerbuild image within a pull request
|
||||
- name: Build docker image
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
if: steps.changed_files.outputs.any_modified == 'true'
|
||||
with:
|
||||
file: apps/desktop/dockerbuild/Dockerfile
|
||||
@@ -230,7 +216,7 @@ jobs:
|
||||
|
||||
# We exclude *-unpacked as it loses permissions and the tarball contains it with correct permissions
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: ${{ inputs.artifact-prefix }}linux-${{ inputs.arch }}-sqlcipher-${{ inputs.sqlcipher }}
|
||||
path: |
|
||||
|
||||
@@ -37,7 +37,7 @@ on:
|
||||
type: string
|
||||
required: false
|
||||
description: |
|
||||
The name of the prepare artifact to use, defaults to 'webapp'.
|
||||
The name of the prepare artifact to use, defaults to 'desktop-prepare'.
|
||||
The artifact must contain the following:
|
||||
+ webapp.asar - the asar archive of the webapp to embed in the desktop app
|
||||
+ electronVersion - the version of electron to use for cache keying
|
||||
@@ -46,7 +46,7 @@ on:
|
||||
|
||||
The artifact can also contain any additional files which will be applied as overrides to the checkout root before building,
|
||||
for example icons in the `build/` directory to override the app icons.
|
||||
default: "webapp"
|
||||
default: "desktop-prepare"
|
||||
test:
|
||||
type: boolean
|
||||
required: false
|
||||
@@ -90,9 +90,9 @@ jobs:
|
||||
|
||||
- name: Cache .hak
|
||||
id: cache
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ hashFiles('hakHash', 'electronVersion') }}
|
||||
key: ${{ runner.os }}-${{ hashFiles('apps/desktop/hakHash', 'apps/desktop/electronVersion') }}
|
||||
path: |
|
||||
apps/desktop/.hak
|
||||
|
||||
@@ -114,14 +114,14 @@ jobs:
|
||||
- run: sudo pip3 install pyobjc-framework-Quartz
|
||||
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: apps/desktop/.node-version
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install Deps
|
||||
working-directory: apps/desktop
|
||||
run: "pnpm install --frozen-lockfile"
|
||||
run: "pnpm install --frozen-lockfile --filter element-desktop"
|
||||
|
||||
- name: Build Natives
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
@@ -194,7 +194,7 @@ jobs:
|
||||
|
||||
# We exclude mac-universal as the unpacked app takes forever to upload and zip and dmg already contains it
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: ${{ inputs.artifact-prefix }}macos
|
||||
path: |
|
||||
|
||||
@@ -20,11 +20,10 @@ on:
|
||||
required: false
|
||||
default: false
|
||||
description: "Whether the build should be deployed to production"
|
||||
branch-matching:
|
||||
type: boolean
|
||||
webapp-artifact:
|
||||
type: string
|
||||
required: false
|
||||
default: false
|
||||
description: "Whether the branch name should be matched to find the element-web commit"
|
||||
description: "Name of the webapp artifact that should be used, will fetch a relevant build if omitted"
|
||||
secrets:
|
||||
# Required if `nightly` is set
|
||||
CF_R2_ACCESS_KEY_ID:
|
||||
@@ -57,37 +56,36 @@ jobs:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: element-hq/element-web
|
||||
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: apps/desktop/.node-version
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install Deps
|
||||
working-directory: apps/desktop
|
||||
run: "pnpm install --frozen-lockfile"
|
||||
run: "pnpm install --frozen-lockfile --filter element-desktop"
|
||||
|
||||
- name: Fetch Element Web (matching branch)
|
||||
id: branch-matching
|
||||
if: inputs.branch-matching
|
||||
- name: Fetch Element Web (from artifact)
|
||||
if: inputs.webapp-artifact != ''
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
name: ${{ inputs.webapp-artifact }}
|
||||
path: apps/desktop/webapp
|
||||
|
||||
- name: Build webapp.asar (from artifact)
|
||||
if: inputs.webapp-artifact != ''
|
||||
working-directory: apps/desktop
|
||||
continue-on-error: true
|
||||
run: |
|
||||
scripts/branch-match.sh
|
||||
cp "$CONFIG_DIR/config.json" element-web/
|
||||
pnpm --cwd element-web install --frozen-lockfile
|
||||
pnpm --cwd element-web run build
|
||||
mv element-web/webapp .
|
||||
cp -f "$CONFIG_DIR/config.json" webapp/config.json
|
||||
pnpm run asar-webapp
|
||||
env:
|
||||
# These must be set for branch-match.sh to get the right branch
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
CONFIG_DIR: ${{ inputs.config }}
|
||||
|
||||
- name: Fetch Element Web (${{ inputs.version }})
|
||||
if: steps.branch-matching.outcome == 'failure' || steps.branch-matching.outcome == 'skipped'
|
||||
if: inputs.webapp-artifact == ''
|
||||
working-directory: apps/desktop
|
||||
run: pnpm run fetch --noverify -d ${CONFIG} ${VERSION}
|
||||
env:
|
||||
@@ -187,9 +185,9 @@ jobs:
|
||||
env:
|
||||
NIGHTLY_VERSION: ${{ steps.versions.outputs.nightly }}
|
||||
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: webapp
|
||||
name: desktop-prepare
|
||||
retention-days: 1
|
||||
path: |
|
||||
apps/desktop/webapp.asar
|
||||
|
||||
@@ -38,18 +38,17 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
repository: ${{ github.repository == 'element-hq/element-web-pro' && 'element-hq/element-web' || github.repository }}
|
||||
repository: element-hq/element-web
|
||||
persist-credentials: false
|
||||
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: apps/desktop/.node-version
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install Deps
|
||||
working-directory: apps/desktop
|
||||
run: "pnpm install --frozen-lockfile"
|
||||
run: "pnpm install --frozen-lockfile --filter element-desktop"
|
||||
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
@@ -85,25 +84,35 @@ jobs:
|
||||
EXECUTABLE: ${{ steps.executable.outputs.path }}
|
||||
|
||||
- name: Run tests
|
||||
uses: coactions/setup-xvfb@6b00cf1889f4e1d5a48635647013c0508128ee1a
|
||||
timeout-minutes: 20
|
||||
with:
|
||||
run: pnpm -C apps/desktop test --project=${{ inputs.project }} ${{ runner.os != 'Linux' && '--ignore-snapshots' || '' }} ${{ inputs.blob_report == false && '--reporter=html' || '' }} ${{ inputs.args }}
|
||||
shell: bash
|
||||
working-directory: apps/desktop
|
||||
run: |
|
||||
$PREFIX pnpm playwright test \
|
||||
${{ runner.os != 'Linux' && '--ignore-snapshots' || '' }} \
|
||||
${{ inputs.blob_report == false && '--reporter=html' || '' }} \
|
||||
$ARGS
|
||||
env:
|
||||
PREFIX: ${{ runner.os == 'Linux' && 'xvfb-run' || '' }}
|
||||
PW_TAG: ${{ inputs.project }}
|
||||
ELEMENT_DESKTOP_EXECUTABLE: ${{ steps.executable.outputs.path }}
|
||||
ARGS: ${{ inputs.args }}
|
||||
DEBUG: pw:browser
|
||||
|
||||
- name: Upload blob report
|
||||
if: always() && inputs.blob_report
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: blob-report-${{ inputs.artifact }}
|
||||
path: apps/desktop/blob-report
|
||||
retention-days: 1
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload HTML report
|
||||
if: always() && inputs.blob_report == false
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: ${{ inputs.artifact }}-test
|
||||
path: apps/desktop/playwright-report
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
|
||||
@@ -42,7 +42,7 @@ on:
|
||||
type: string
|
||||
required: false
|
||||
description: |
|
||||
The name of the prepare artifact to use, defaults to 'webapp'.
|
||||
The name of the prepare artifact to use, defaults to 'desktop-prepare'.
|
||||
The artifact must contain the following:
|
||||
+ webapp.asar - the asar archive of the webapp to embed in the desktop app
|
||||
+ electronVersion - the version of electron to use for cache keying
|
||||
@@ -52,7 +52,7 @@ on:
|
||||
|
||||
The artifact can also contain any additional files which will be applied as overrides to the checkout root before building,
|
||||
for example icons in the `build/` directory to override the app icons.
|
||||
default: "webapp"
|
||||
default: "desktop-prepare"
|
||||
test:
|
||||
type: boolean
|
||||
required: false
|
||||
@@ -121,9 +121,9 @@ jobs:
|
||||
|
||||
- name: Cache .hak
|
||||
id: cache
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ inputs.arch }}-${{ hashFiles('hakHash', 'electronVersion') }}
|
||||
key: ${{ runner.os }}-${{ inputs.arch }}-${{ hashFiles('apps/desktop/hakHash', 'apps/desktop/electronVersion') }}
|
||||
path: |
|
||||
apps/desktop/.hak
|
||||
|
||||
@@ -153,14 +153,14 @@ jobs:
|
||||
TARGET: ${{ steps.config.outputs.target }}
|
||||
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: apps/desktop/.node-version
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install Deps
|
||||
working-directory: apps/desktop
|
||||
run: "pnpm install --frozen-lockfile"
|
||||
run: "pnpm install --frozen-lockfile --filter element-desktop"
|
||||
|
||||
- name: Insert config snippet
|
||||
if: steps.config.outputs.extra_config != ''
|
||||
@@ -274,7 +274,7 @@ jobs:
|
||||
| ForEach-Object -Process {. $env:SIGNTOOL_PATH verify /pa $_.FullName; if(!$?) { throw }}
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: ${{ inputs.artifact-prefix }}win-${{ inputs.arch }}
|
||||
path: |
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
cache: "pnpm"
|
||||
node-version: "lts/*"
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
- run: mv dist/element-*.tar.gz dist/develop.tar.gz
|
||||
working-directory: apps/web
|
||||
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: webapp
|
||||
path: apps/web/dist/develop.tar.gz
|
||||
@@ -111,7 +111,7 @@ jobs:
|
||||
running-workflow-name: "Build & Deploy develop.element.io"
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
wait-interval: 10
|
||||
check-regexp: ^((?!SonarCloud|SonarQube|issue|board|label|Release|prepare|GitHub Pages|Upload).)*$
|
||||
check-regexp: ^((?!SonarCloud|SonarQube|issue|board|label|Release|prepare|GitHub Pages|Upload|Netlify|Report).)*$
|
||||
|
||||
# We keep the latest develop.tar.gz on R2 instead of relying on the github artifact uploaded earlier
|
||||
# as the expires after 24h and requires auth to download.
|
||||
|
||||
@@ -2,6 +2,13 @@ name: CD # Continuous Delivery
|
||||
on:
|
||||
push:
|
||||
branches: [master, staging, develop]
|
||||
paths:
|
||||
- "**/Dockerfile"
|
||||
- "**/dockerbuild"
|
||||
- "**/docker"
|
||||
- "**/docker-*"
|
||||
- "pnpm-lock.yaml"
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref_name }}
|
||||
|
||||
permissions: {}
|
||||
@@ -24,7 +31,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3
|
||||
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
|
||||
@@ -34,7 +41,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: package.json
|
||||
cache: "pnpm"
|
||||
@@ -43,7 +50,7 @@ jobs:
|
||||
run: "pnpm install --frozen-lockfile"
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3
|
||||
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
|
||||
if: github.event_name != 'pull_request'
|
||||
|
||||
- name: Set up QEMU
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
|
||||
- name: Build and load
|
||||
id: test-build
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
with:
|
||||
context: .
|
||||
file: apps/web/Dockerfile
|
||||
@@ -97,14 +97,14 @@ jobs:
|
||||
latest=${{ contains(github.ref_name, '-rc.') && 'false' || 'auto' }}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
registry: ghcr.io
|
||||
@@ -140,7 +140,7 @@ jobs:
|
||||
services/web-repositories/secret/data/oci.element.io password | OCI_PASSWORD ;
|
||||
|
||||
- name: Login to oci.element.io Registry
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
registry: oci-push.vpn.infra.element.io
|
||||
@@ -149,7 +149,7 @@ jobs:
|
||||
|
||||
- name: Build and push
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
context: .
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
with:
|
||||
package_json_file: package.json
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: pnpm-lock.yaml
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
run: pnpm run docs:build
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4
|
||||
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5
|
||||
with:
|
||||
path: ./docs/.vitepress/dist
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
name: Tidy closed issues
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
||||
id: main
|
||||
with:
|
||||
# PAT needed as the GITHUB_TOKEN won't be able to see cross-references from other orgs (matrix-org)
|
||||
@@ -142,7 +142,7 @@ jobs:
|
||||
});
|
||||
}
|
||||
}
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
||||
name: Close duplicate as Not Planned
|
||||
if: steps.main.outputs.closeAsNotPlanned
|
||||
with:
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Tweaks the behaviour of Merge Queue to skip certain checks
|
||||
name: Merge Queue tweaks
|
||||
on:
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
statuses: write
|
||||
steps:
|
||||
# This is only needed as license/cla at time of writing seems to be extraordinarily flaky
|
||||
# and Github doesn't support conditional checks between PR & merge queue.
|
||||
# This is fine to do as a PR won't make it to merge queue until it has license/cla passing.
|
||||
- name: Skip license/cla on merge queues
|
||||
uses: guibranco/github-status-action-v2@9bfa8773cdbdc6c185747fd43cd7faa9d7c32f09
|
||||
with:
|
||||
authToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
state: success
|
||||
context: license/cla
|
||||
sha: ${{ github.sha }}
|
||||
target_url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
+15
-6
@@ -1,6 +1,16 @@
|
||||
name: Publish shared component npm package
|
||||
name: Publish npm package
|
||||
run-name: Publish ${{ inputs.package }}
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
package:
|
||||
description: Which package to release
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- playwright-common
|
||||
- shared-components
|
||||
- module-api
|
||||
|
||||
concurrency: release
|
||||
jobs:
|
||||
@@ -19,7 +29,7 @@ jobs:
|
||||
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
- name: 🔧 Set up node environment
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
cache: "pnpm"
|
||||
node-version-file: ".node-version"
|
||||
@@ -29,10 +39,9 @@ jobs:
|
||||
- name: Update npm
|
||||
run: npm install -g npm@latest
|
||||
|
||||
# Need to setup element web too as it needs the translations
|
||||
- name: 🛠️ Setup EW
|
||||
- name: 🛠️ Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: 🚀 Publish to npm
|
||||
working-directory: packages/shared-components
|
||||
working-directory: packages/${{ inputs.package }}
|
||||
run: npm publish --access public --provenance
|
||||
@@ -8,7 +8,7 @@ jobs:
|
||||
name: Check PR base branch
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
||||
with:
|
||||
script: |
|
||||
const baseBranch = context.payload.pull_request.base.ref;
|
||||
|
||||
@@ -30,7 +30,8 @@ jobs:
|
||||
asset-path: dist/*.tar.gz
|
||||
expected-asset-count: 3
|
||||
# Desktop has no dist script so we only target web here
|
||||
dir: apps/web
|
||||
dist-dir: apps/web
|
||||
version-dirs: apps/web apps/desktop
|
||||
|
||||
check:
|
||||
name: Post release checks
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
- name: 🔧 Pnpm cache
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
cache: "pnpm"
|
||||
node-version-file: package.json
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
working-directory: packages/shared-components
|
||||
run: pnpm build:storybook
|
||||
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: shared-components-storybook
|
||||
path: packages/shared-components/storybook-static
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
path: storybook-static
|
||||
|
||||
- name: 🚀 Deploy to Cloudflare Pages
|
||||
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3
|
||||
uses: cloudflare/wrangler-action@9acf94ace14e7dc412b076f2c5c20b8ce93c79cd # v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_PAGES_TOKEN }}
|
||||
accountId: ${{ secrets.CF_PAGES_ACCOUNT_ID }}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
# It uploads the received images and diffs to netlify, printing the URLs to the console
|
||||
name: Upload Shared Component Visual Test Diffs
|
||||
on:
|
||||
workflow_run:
|
||||
# Privilege escalation necessary to deploy to Netlify
|
||||
# 🚨 We must not execute any checked out code here.
|
||||
workflow_run: # zizmor: ignore[dangerous-triggers]
|
||||
workflows: ["Shared Component Visual Tests"]
|
||||
types:
|
||||
- completed
|
||||
@@ -23,9 +25,6 @@ jobs:
|
||||
actions: read
|
||||
deployments: write
|
||||
steps:
|
||||
- name: Install tree
|
||||
run: "sudo apt-get install -y tree"
|
||||
|
||||
- name: Download Diffs
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
repository: element-hq/element-web
|
||||
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
cache: "pnpm"
|
||||
node-version: "lts/*"
|
||||
@@ -36,36 +36,25 @@ jobs:
|
||||
working-directory: packages/shared-components
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Get installed Playwright version
|
||||
working-directory: packages/shared-components
|
||||
id: playwright
|
||||
run: echo "version=$(pnpm list @playwright/test --depth=0 --json | jq -r '.[].devDependencies["@playwright/test"].version')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache playwright binaries
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
id: playwright-cache
|
||||
- name: Setup playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright.outputs.version }}-onlyshell
|
||||
|
||||
- name: Install Playwright browsers
|
||||
working-directory: packages/shared-components
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
||||
run: "pnpm playwright install --with-deps --only-shell"
|
||||
write-cache: ${{ github.event_name != 'merge_group' }}
|
||||
|
||||
- name: Run Visual tests
|
||||
working-directory: packages/shared-components
|
||||
run: "pnpm test:storybook --run"
|
||||
|
||||
# Workaround for vis silently adding new baselines if they didn't exist
|
||||
# Can be removed once https://github.com/repobuddy/visual-testing/issues/516 is released
|
||||
- run: |
|
||||
git add -N .
|
||||
git diff --exit-code
|
||||
- name: Detect stale screenshots
|
||||
run: |
|
||||
if diff -rq __baselines__ __results__ | grep "^Only in __baselines__"; then
|
||||
exit 1
|
||||
fi
|
||||
working-directory: packages/shared-components/__vis__/linux
|
||||
|
||||
- name: Upload received images & diffs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: received-images
|
||||
path: packages/shared-components/__vis__/linux
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
name: SonarQube
|
||||
on:
|
||||
workflow_run:
|
||||
# Privilege escalation necessary to call upon SonarCloud
|
||||
# 🚨 We must not execute any checked out code here.
|
||||
workflow_run: # zizmor: ignore[dangerous-triggers]
|
||||
workflows: ["Tests"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
@@ -5,8 +5,6 @@ on:
|
||||
branches: [develop, master]
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
repository_dispatch:
|
||||
types: [element-web-notify]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
@@ -56,7 +54,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
if: matrix.install != ''
|
||||
with:
|
||||
cache: "pnpm"
|
||||
@@ -89,7 +87,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run zizmor
|
||||
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
|
||||
uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
|
||||
|
||||
i18n:
|
||||
strategy:
|
||||
@@ -105,7 +103,7 @@ jobs:
|
||||
voip|element_call
|
||||
error|invalid_json
|
||||
error|misconfigured
|
||||
welcome_to_element
|
||||
welcome|title_element
|
||||
devtools|settings|elementCallUrl
|
||||
labs|sliding_sync_description
|
||||
settings|voip|noise_suppression_description
|
||||
@@ -127,7 +125,9 @@ jobs:
|
||||
# Dummy job to simplify branch protections
|
||||
ci:
|
||||
name: Static Analysis
|
||||
needs: [lint, i18n]
|
||||
needs: [lint, i18n, zizmor]
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- run: echo "Ok"
|
||||
- if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
|
||||
run: exit 1
|
||||
|
||||
+30
-34
@@ -5,8 +5,6 @@ on:
|
||||
types: [checks_requested]
|
||||
push:
|
||||
branches: [develop, master]
|
||||
repository_dispatch:
|
||||
types: [element-web-notify]
|
||||
workflow_call:
|
||||
inputs:
|
||||
disable_coverage:
|
||||
@@ -47,7 +45,7 @@ jobs:
|
||||
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
- name: pnpm cache
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
cache: "pnpm"
|
||||
@@ -58,7 +56,7 @@ jobs:
|
||||
JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }}
|
||||
|
||||
- name: Jest Cache
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
with:
|
||||
path: /tmp/jest_cache
|
||||
key: ${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
@@ -93,7 +91,7 @@ jobs:
|
||||
|
||||
- name: Upload Artifact
|
||||
if: env.ENABLE_COVERAGE == 'true'
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: coverage-${{ matrix.runner }}
|
||||
path: |
|
||||
@@ -102,7 +100,7 @@ jobs:
|
||||
|
||||
complete:
|
||||
name: jest-tests
|
||||
needs: [jest_ew, vitest_sc]
|
||||
needs: [jest_ew, vitest]
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
@@ -122,8 +120,13 @@ jobs:
|
||||
sha: ${{ github.sha }}
|
||||
target_url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
vitest_sc:
|
||||
name: Vitest (Shared Components)
|
||||
vitest:
|
||||
name: Vitest
|
||||
strategy:
|
||||
matrix:
|
||||
package:
|
||||
- shared-components
|
||||
- module-api
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -134,49 +137,42 @@ jobs:
|
||||
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
- name: pnpm cache
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install Shared Component Deps
|
||||
working-directory: "packages/shared-components"
|
||||
- name: Install Deps
|
||||
run: "pnpm install"
|
||||
|
||||
- name: Cache storybook & vitest
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
with:
|
||||
path: |
|
||||
packages/shared-components/node_modules/.cache
|
||||
packages/shared-components/node_modules/.vite/vitest
|
||||
packages/${{ matrix.package }}/node_modules/.cache
|
||||
packages/${{ matrix.package }}/node_modules/.vite/vitest
|
||||
key: ${{ hashFiles('pnpm-lock.yaml') }}
|
||||
|
||||
- name: Get installed Playwright version
|
||||
working-directory: packages/shared-components
|
||||
id: playwright
|
||||
run: echo "version=$(pnpm list @playwright/test --depth=0 --json | jq -r '.[].devDependencies["@playwright/test"].version')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache playwright binaries
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
id: playwright-cache
|
||||
- name: Setup playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
if: matrix.package == 'shared-components'
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright.outputs.version }}-onlyshell
|
||||
|
||||
- name: Install Playwright browsers
|
||||
working-directory: packages/shared-components
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
||||
run: "pnpm playwright install --with-deps --only-shell"
|
||||
write-cache: ${{ github.event_name != 'merge_group' }}
|
||||
|
||||
- name: Run tests
|
||||
working-directory: "packages/shared-components"
|
||||
working-directory: "packages/${{ matrix.package }}"
|
||||
run: pnpm test:unit --coverage=$ENABLE_COVERAGE
|
||||
|
||||
# Dump the disk usage on failure, because this job seems to fail with disk fills sometimes
|
||||
- name: df
|
||||
run: df
|
||||
if: ${{ failure() }}
|
||||
|
||||
- name: Upload Artifact
|
||||
if: env.ENABLE_COVERAGE == 'true'
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: coverage-sharedcomponents
|
||||
name: coverage-${{ matrix.package }}
|
||||
path: |
|
||||
packages/shared-components/coverage
|
||||
!packages/shared-components/coverage/lcov-report
|
||||
packages/${{ matrix.package }}/coverage
|
||||
!packages/${{ matrix.package }}/coverage/lcov-report
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
contains(github.event.issue.labels.*.name, 'A-Rich-Text-Editor') ||
|
||||
contains(github.event.issue.labels.*.name, 'A-Element-Call')
|
||||
steps:
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.addLabels({
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
contains(github.event.issue.labels.*.name, 'good first issue') ||
|
||||
contains(github.event.issue.labels.*.name, 'Hacktoberfest')
|
||||
steps:
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.addLabels({
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
contains(github.event.issue.labels.*.name, 'A-Element-Call')) &&
|
||||
contains(github.event.issue.labels.*.name, 'Z-Labs')
|
||||
steps:
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.removeLabel({
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
cache: "pnpm"
|
||||
node-version: "lts/*"
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
run: "pnpm vendor:jitsi"
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8
|
||||
with:
|
||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
branch: actions/jitsi-update
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
environment: Matrix
|
||||
steps:
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
||||
env:
|
||||
HS_URL: ${{ secrets.BETABOT_HS_URL }}
|
||||
LOBBY_ROOM_ID: ${{ secrets.ROOM_ID }}
|
||||
|
||||
+3
-1
@@ -14,7 +14,8 @@ webpack-stats.json
|
||||
.vscode/
|
||||
.env
|
||||
coverage
|
||||
# Auto-generated file
|
||||
# Auto-generated files
|
||||
*.api.md
|
||||
/apps/web/src/modules.ts
|
||||
/apps/web/src/modules.js
|
||||
src/i18n/strings
|
||||
@@ -49,6 +50,7 @@ CHANGELOG.md
|
||||
/apps/desktop/dist/
|
||||
/apps/desktop/build/
|
||||
/apps/desktop/dockerbuild/
|
||||
/apps/desktop/deploys/
|
||||
/apps/desktop/lib/
|
||||
/apps/desktop/webapp
|
||||
/apps/desktop/playwright/html-report
|
||||
|
||||
@@ -49,6 +49,21 @@ As for your PR description, it should include these things:
|
||||
Please **_do not use force push_** in your PRs. Doing so means we can't see what
|
||||
has changed. We use squash merge to get a "clean" git history.
|
||||
|
||||
### Adding a new feature or enhancement
|
||||
|
||||
To make a great product with a great user experience, all the small efforts need to go in the same direction and be aligned and consistent with each other.
|
||||
|
||||
Before making your contribution, please consider the following:
|
||||
|
||||
- One product can’t do everything well. Element is focusing on private end-to-end encrypted messaging and voice - this can either be for consumers (e.g. friends and family) or for professional teams and organizations. Public forums and other types of chats without E2EE remain supported but are not the primary use case in case UX compromises need to be made.
|
||||
- There are 3 platforms - Web/Desktop, [Android](https://github.com/element-hq/element-x-android) and [iOS](https://github.com/element-hq/element-x-ios). These platforms need to have feature parity and design consistency. For some features, supporting all platforms is a must have, in some cases exceptions can be made to have it on one platform only.
|
||||
- To make sure your idea fits both from a design/solution and use case perspective, please open a new issue (or find an existing issue) in [element-meta](https://github.com/element-hq/element-meta/issues) repository describing the use case and how you plan to tackle it. Do not just describe what feature is missing, explain why the users need it with a couple of real life examples from the field.
|
||||
- In case of an existing issue, please comment that you're planning to contribute. If you create a new issue, please specify that in the issue. In such a case we will try to review the issue ASAP and provide you with initial feedback so you can be confident if and at which conditions your contributions will be accepted.
|
||||
|
||||
Once we know that you want to contribute and have confirmed that the new feature is overall aligned with the product direction, the designers of the core team will help you with the designs and any other type of guidance when it comes to the user experience. We will try to unblock you as quickly as we can, but it may not be instant. Having a clear understanding of the use case and the impact of the feature will help us with the prioritization and faster responses.
|
||||
|
||||
Only once all of the above is met should you open a PR with your proposed changes.
|
||||
|
||||
### Changelogs
|
||||
|
||||
There's no need to manually add Changelog entries: we use information in the
|
||||
|
||||
@@ -1 +1 @@
|
||||
24.14.0
|
||||
24.15.0
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Docker image to facilitate building Element Desktop's native bits using a glibc version (2.31)
|
||||
# with broader compatibility, down to Debian bullseye & Ubuntu focal.
|
||||
|
||||
FROM rust:bullseye@sha256:16950191527a4cb9e0762d9d48b705a6315158e4035e64f7a93ce8656a1b053c
|
||||
FROM rust:bullseye@sha256:949b0903defbfc4e374dc85f947b153859e9ee0104e425cd9a74d94474a9a335
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "node16",
|
||||
"module": "Node16",
|
||||
"esModuleInterop": true,
|
||||
"target": "es2022",
|
||||
"sourceMap": false,
|
||||
|
||||
+21
-20
@@ -28,7 +28,7 @@
|
||||
"mkdirs": "mkdirp packages deploys",
|
||||
"fetch": "pnpm run mkdirs && node scripts/fetch-package.ts",
|
||||
"asar-webapp": "asar p webapp webapp.asar",
|
||||
"start": "pnpm run build:ts && pnpm run build:res && electron .",
|
||||
"start": "nx start",
|
||||
"lint": "pnpm lint:types && pnpm lint:js",
|
||||
"lint:js": "eslint --max-warnings 0 src hak playwright scripts",
|
||||
"lint:js-fix": "eslint --fix --max-warnings 0 src hak playwright scripts && prettier --log-level=warn --write .",
|
||||
@@ -39,22 +39,19 @@
|
||||
"lint:types:hak": "tsc --noEmit -p hak/tsconfig.json",
|
||||
"build:native": "pnpm run hak",
|
||||
"build:native:universal": "pnpm run hak --target x86_64-apple-darwin fetchandbuild && pnpm run hak --target aarch64-apple-darwin fetchandbuild && pnpm run hak --target x86_64-apple-darwin --target aarch64-apple-darwin copyandlink",
|
||||
"build:32": "pnpm run build:ts && pnpm run build:res && electron-builder --ia32",
|
||||
"build:64": "pnpm run build:ts && pnpm run build:res && electron-builder --x64",
|
||||
"build:universal": "pnpm run build:ts && pnpm run build:res && electron-builder --universal",
|
||||
"build": "pnpm run build:ts && pnpm run build:res && electron-builder",
|
||||
"build:ts": "tsc",
|
||||
"build:res": "node scripts/copy-res.ts",
|
||||
"build:32": "nx build --ia32",
|
||||
"build:64": "nx build --x64",
|
||||
"build:universal": "nx build --universal",
|
||||
"build": "nx build --",
|
||||
"docker:setup": "docker build --platform linux/amd64 -t element-desktop-dockerbuild -f dockerbuild/Dockerfile .",
|
||||
"docker:build:native": "scripts/in-docker.sh pnpm run hak",
|
||||
"docker:build": "scripts/in-docker.sh pnpm run build",
|
||||
"docker:install": "scripts/in-docker.sh pnpm install",
|
||||
"clean": "rimraf webapp.asar dist packages deploys lib",
|
||||
"hak": "node scripts/hak/index.ts",
|
||||
"test": "playwright test",
|
||||
"test:open": "pnpm test --ui",
|
||||
"test:screenshots:build": "docker build playwright -t element-desktop-playwright --platform linux/amd64",
|
||||
"test:screenshots:run": "docker run --rm --network host -v $(pwd):/work/element-desktop -v element-desktop-playwright:/work/element-desktop/node_modules -v /var/run/docker.sock:/var/run/docker.sock --platform linux/amd64 -it element-desktop-playwright",
|
||||
"test:playwright": "nx test:playwright --",
|
||||
"test:playwright:open": "nx test:playwright -- --ui",
|
||||
"test:playwright:screenshots": "nx test:playwright:screenshots --",
|
||||
"sane-postinstall": "electron-builder install-app-deps"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -65,13 +62,14 @@
|
||||
"electron-window-state": "^5.0.3",
|
||||
"minimist": "^1.2.6",
|
||||
"png-to-ico": "^3.0.0",
|
||||
"uuid": "^13.0.0"
|
||||
"uuid": "^14.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.10",
|
||||
"@babel/preset-env": "^7.18.10",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@electron/asar": "4.1.0",
|
||||
"@electron/asar": "4.2.0",
|
||||
"@electron/fuses": "^2.1.1",
|
||||
"@playwright/test": "catalog:",
|
||||
"@stylistic/eslint-plugin": "^5.0.0",
|
||||
"@types/auto-launch": "^5.0.1",
|
||||
@@ -81,12 +79,12 @@
|
||||
"@types/pacote": "^11.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||
"@typescript-eslint/parser": "^8.0.0",
|
||||
"app-builder-lib": "26.8.2",
|
||||
"app-builder-lib": "26.9.0",
|
||||
"chokidar": "^5.0.0",
|
||||
"detect-libc": "^2.0.0",
|
||||
"electron": "41.0.3",
|
||||
"electron-builder": "26.8.2",
|
||||
"electron-builder-squirrel-windows": "26.8.2",
|
||||
"electron": "41.2.2",
|
||||
"electron-builder": "26.9.0",
|
||||
"electron-builder-squirrel-windows": "26.9.0",
|
||||
"electron-devtools-installer": "^4.0.0",
|
||||
"eslint": "^8.26.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
@@ -102,10 +100,13 @@
|
||||
"prettier": "^3.0.0",
|
||||
"rimraf": "^6.0.0",
|
||||
"tar": "^7.5.8",
|
||||
"typescript": "5.9.3"
|
||||
"typescript": "6.0.3"
|
||||
},
|
||||
"hakDependencies": {
|
||||
"matrix-seshat": "^4.0.1"
|
||||
"matrix-seshat": "4.2.0"
|
||||
},
|
||||
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be"
|
||||
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319",
|
||||
"nx": {
|
||||
"includedScripts": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,25 +8,9 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import { defineConfig } from "@playwright/test";
|
||||
|
||||
const projects = [
|
||||
"macos",
|
||||
"win-x64",
|
||||
"win-ia32",
|
||||
"win-arm64",
|
||||
"linux-amd64-sqlcipher-system",
|
||||
"linux-amd64-sqlcipher-static",
|
||||
"linux-arm64-sqlcipher-system",
|
||||
"linux-arm64-sqlcipher-static",
|
||||
];
|
||||
|
||||
export default defineConfig({
|
||||
// Allows the GitHub action to specify a project name (OS + arch) for the combined report to make sense
|
||||
// workaround for https://github.com/microsoft/playwright/issues/33521
|
||||
projects: process.env.CI
|
||||
? projects.map((name) => ({
|
||||
name,
|
||||
}))
|
||||
: undefined,
|
||||
projects: [{ name: "Desktop" }],
|
||||
tag: process.env.PW_TAG ? `@${process.env.PW_TAG}` : undefined,
|
||||
use: {
|
||||
viewport: { width: 1280, height: 720 },
|
||||
video: "retain-on-failure",
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
FROM mcr.microsoft.com/playwright:v1.58.2-jammy@sha256:4698a73749c5848d3f5fcd42a2174d172fcad2b2283e087843b115424303a565
|
||||
FROM mcr.microsoft.com/playwright:v1.59.1-jammy@sha256:8a0360d39d1973be506dd59002904a774f6d697d4946c94063b3fd006461c8ff
|
||||
|
||||
WORKDIR /work/element-desktop
|
||||
WORKDIR /work
|
||||
|
||||
RUN apt-get update && apt-get -y install xvfb dbus-x11 && apt-get purge -y --auto-remove && rm -rf /var/lib/apt/lists/*
|
||||
RUN apt-get update && \
|
||||
apt-get -y install xvfb dbus-x11 && \
|
||||
apt-get purge -y --auto-remove && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
corepack enable
|
||||
|
||||
# Create node_modules & dist dirs so that the volumes have the correct permissions
|
||||
RUN mkdir node_modules dist && chown 1000:1000 node_modules dist
|
||||
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0
|
||||
ENV GITHUB_ACTIONS=1
|
||||
ENV DEBUG=pw:browser
|
||||
|
||||
# switch to node user
|
||||
USER 1000:1000
|
||||
|
||||
COPY docker-entrypoint.sh /opt/docker-entrypoint.sh
|
||||
COPY apps/desktop/playwright/docker-entrypoint.sh /opt/docker-entrypoint.sh
|
||||
ENTRYPOINT ["bash", "/opt/docker-entrypoint.sh"]
|
||||
|
||||
@@ -8,11 +8,5 @@ sleep 2
|
||||
|
||||
export DISPLAY=:99
|
||||
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm build -l --dir
|
||||
|
||||
PLAYWRIGHT_HTML_OPEN=never ELEMENT_DESKTOP_EXECUTABLE="./dist/linux-unpacked/element-desktop" \
|
||||
npx playwright test --update-snapshots --reporter line,html "$1"
|
||||
|
||||
# Clean up
|
||||
rm -R core qemu_* || exit 0
|
||||
exec pnpm -C apps/desktop exec playwright test --update-snapshots --reporter line,html "$1"
|
||||
|
||||
@@ -17,14 +17,14 @@ import { PassThrough } from "node:stream";
|
||||
* A PassThrough stream that captures all data written to it.
|
||||
*/
|
||||
class CapturedPassThrough extends PassThrough {
|
||||
private _chunks = [];
|
||||
private _chunks: any[] = [];
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
super.on("data", this.onData);
|
||||
}
|
||||
|
||||
private onData = (chunk): void => {
|
||||
private onData = (chunk: any): void => {
|
||||
this._chunks.push(chunk);
|
||||
};
|
||||
|
||||
@@ -69,7 +69,13 @@ export const test = base.extend<Fixtures>({
|
||||
const args = ["--profile-dir", tmpDir, ...extraArgs];
|
||||
|
||||
if (process.env.GITHUB_ACTIONS) {
|
||||
args.push("--disable-gpu");
|
||||
|
||||
if (process.platform === "linux") {
|
||||
if (process.getuid() === 0) {
|
||||
args.push("--no-sandbox");
|
||||
}
|
||||
|
||||
// GitHub Actions hosted runner lacks dbus and a compatible keyring, so we need to force plaintext storage
|
||||
args.push("--storage-mode", "force-plaintext");
|
||||
} else if (process.platform === "darwin") {
|
||||
|
||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 957 KiB |
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "bundler",
|
||||
"esModuleInterop": true,
|
||||
"target": "es2022",
|
||||
"module": "es2022",
|
||||
"lib": ["es2022", "dom"],
|
||||
"module": "ESNext",
|
||||
"lib": ["es2024", "dom", "dom.iterable"],
|
||||
"strictNullChecks": false,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "app",
|
||||
"projectType": "application",
|
||||
"implicitDependencies": ["element-web"],
|
||||
"root": "apps/desktop",
|
||||
"targets": {
|
||||
"docker:build": {
|
||||
@@ -18,6 +19,65 @@
|
||||
"tags": ["type=ref,event=branch"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"build:ts": {
|
||||
"cache": true,
|
||||
"command": "tsc",
|
||||
"inputs": ["src", "{projectRoot}/tsconfig.json"],
|
||||
"outputs": ["{projectRoot}/lib/*.js", "{projectRoot}/lib/*.d.ts"],
|
||||
"options": { "cwd": "apps/desktop" }
|
||||
},
|
||||
"build:res": {
|
||||
"cache": true,
|
||||
"command": "node scripts/copy-res.ts",
|
||||
"inputs": ["{projectRoot}/i18n"],
|
||||
"outputs": ["{projectRoot}/lib/i18n"],
|
||||
"options": { "cwd": "apps/desktop" }
|
||||
},
|
||||
"build": {
|
||||
"cache": true,
|
||||
"command": "pnpm exec electron-builder",
|
||||
"inputs": [
|
||||
"src",
|
||||
"{projectRoot}/.hak/hakModules",
|
||||
"{projectRoot}/electron-builder.json",
|
||||
"{projectRoot}/webapp.asar"
|
||||
],
|
||||
"outputs": ["{projectRoot}/dist"],
|
||||
"options": { "cwd": "apps/desktop" },
|
||||
"dependsOn": ["build:*"]
|
||||
},
|
||||
"start": {
|
||||
"command": "electron .",
|
||||
"options": { "cwd": "apps/desktop" },
|
||||
"dependsOn": ["build:*"]
|
||||
},
|
||||
"test:playwright": {
|
||||
"command": "playwright test",
|
||||
"options": { "cwd": "apps/desktop" }
|
||||
},
|
||||
"test:playwright:screenshots:build-app": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"commands": [
|
||||
"pnpm run build -l --x64 --dir --publish=never",
|
||||
"pnpm exec electron-fuses write --app ./dist/linux-unpacked/element-desktop EnableNodeCliInspectArguments=on"
|
||||
],
|
||||
"parallel": false,
|
||||
"cwd": "apps/desktop"
|
||||
},
|
||||
"dependsOn": ["build:*"]
|
||||
},
|
||||
"test:playwright:screenshots:build-docker": {
|
||||
"cache": true,
|
||||
"command": "docker build -f playwright/Dockerfile -t element-desktop-playwright --platform linux/amd64 ../..",
|
||||
"inputs": ["{projectRoot}/playwright/Dockerfile", "{projectRoot}/playwright/docker-entrypoint.sh"],
|
||||
"options": { "cwd": "apps/desktop" }
|
||||
},
|
||||
"test:playwright:screenshots": {
|
||||
"command": "docker run --rm --network host -v $(pwd)/../../:/work/ --platform linux/amd64 -it element-desktop-playwright",
|
||||
"options": { "cwd": "apps/desktop" },
|
||||
"dependsOn": ["test:playwright:screenshots:*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script for downloading a branch of element-web matching the branch a PR is contributed from
|
||||
|
||||
set -x
|
||||
|
||||
deforg="element-hq"
|
||||
defrepo="element-web"
|
||||
|
||||
# The PR_NUMBER variable must be set explicitly.
|
||||
default_org_repo=${GITHUB_REPOSITORY:-"$deforg/$defrepo"}
|
||||
PR_ORG=${PR_ORG:-${default_org_repo%%/*}}
|
||||
PR_REPO=${PR_REPO:-${default_org_repo##*/}}
|
||||
|
||||
# A function that clones a branch of a repo based on the org, repo and branch
|
||||
clone() {
|
||||
org=$1
|
||||
repo=$2
|
||||
branch=$3
|
||||
if [ -n "$branch" ]
|
||||
then
|
||||
echo "Trying to use $org/$repo#$branch"
|
||||
# Disable auth prompts: https://serverfault.com/a/665959
|
||||
GIT_TERMINAL_PROMPT=0 git clone https://github.com/$org/$repo.git $repo --branch "$branch" --depth 1 && exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
echo "Getting info about a PR with number $PR_NUMBER"
|
||||
apiEndpoint="https://api.github.com/repos/$PR_ORG/$PR_REPO/pulls/$PR_NUMBER"
|
||||
head=$(curl "$apiEndpoint" | jq -r '.head.label')
|
||||
|
||||
# for forks, $head will be in the format "fork:branch", so we split it by ":"
|
||||
# into an array. On non-forks, this has the effect of splitting into a single
|
||||
# element array given ":" shouldn't appear in the head - it'll just be the
|
||||
# branch name. Based on the results, we clone.
|
||||
BRANCH_ARRAY=(${head//:/ })
|
||||
TRY_ORG=$deforg
|
||||
TRY_BRANCH=${BRANCH_ARRAY[0]}
|
||||
if [[ "$head" == *":"* ]]; then
|
||||
# ... but only match that fork if it's a real fork
|
||||
if [ "${BRANCH_ARRAY[0]}" != "$PR_ORG" ]; then
|
||||
TRY_ORG=${BRANCH_ARRAY[0]}
|
||||
fi
|
||||
TRY_BRANCH=${BRANCH_ARRAY[1]}
|
||||
fi
|
||||
clone "$TRY_ORG" "$defrepo" "$TRY_BRANCH"
|
||||
|
||||
exit 1
|
||||
@@ -22,7 +22,9 @@
|
||||
"about": "关于",
|
||||
"brand_help": "%(brand)s帮助",
|
||||
"help": "帮助",
|
||||
"preferences": "偏好"
|
||||
"no": "否",
|
||||
"preferences": "偏好",
|
||||
"yes": "是"
|
||||
},
|
||||
"confirm_quit": "你确定要退出吗?",
|
||||
"edit_menu": {
|
||||
@@ -30,9 +32,20 @@
|
||||
"speech_start_speaking": "开始讲话",
|
||||
"speech_stop_speaking": "停止讲话"
|
||||
},
|
||||
"eol": {
|
||||
"no_more_updates": "你正在使用的 macOS 版本不受支持。请升级以获取 %(brand)s 更新。",
|
||||
"title": "不受支持的系统",
|
||||
"warning": "你正在使用的 macOS 版本不受支持。请升级系统以确保 %(brand)s 能保持运行。"
|
||||
},
|
||||
"file_menu": {
|
||||
"label": "文件"
|
||||
},
|
||||
"icon_overlay": {
|
||||
"description_error": "错误",
|
||||
"description_notifications": {
|
||||
"other": "你有 %(count)s 条未读通知。"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"hide": "隐藏",
|
||||
"hide_others": "隐藏其他",
|
||||
@@ -49,6 +62,21 @@
|
||||
"save_image_as_error_description": "图片保存失败",
|
||||
"save_image_as_error_title": "图片保存失败"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"backend_changed": "清除数据并重新加载?",
|
||||
"backend_changed_detail": "无法从系统密钥环访问密钥,该密钥似乎已被更改。",
|
||||
"backend_changed_title": "数据库加载失败",
|
||||
"backend_no_encryption": "你的系统支持密钥环,但加密功能不可用。",
|
||||
"backend_no_encryption_detail": "Electron 检测到你的密钥环 %(backend)s 的加密不可用。请确保已安装该密钥环。若已安装请重启设备后重试。也可选择允许 %(brand)s 使用弱加密方式。",
|
||||
"backend_no_encryption_title": "不支持加密",
|
||||
"unsupported_keyring": "你的系统存在不受支持的密钥环,这意味着无法打开数据库。",
|
||||
"unsupported_keyring_detail": "Electron 的密钥环检测未找到受支持的后端。你可以尝试通过命令行参数启动 %(brand)s 来手动配置后端,此操作仅需执行一次。详情请参阅:%(link)s。",
|
||||
"unsupported_keyring_title": "不受支持的系统",
|
||||
"unsupported_keyring_use_basic_text": "使用弱加密",
|
||||
"unsupported_keyring_use_plaintext": "不使用加密"
|
||||
}
|
||||
},
|
||||
"view_menu": {
|
||||
"actual_size": "实际大小",
|
||||
"toggle_developer_tools": "切换开发者工具",
|
||||
|
||||
@@ -56,6 +56,7 @@ module.exports = {
|
||||
{ from: "res/css/views/rooms/_EditMessageComposer.pcss", type: "css" },
|
||||
{ from: "res/css/views/right_panel/_BaseCard.pcss", type: "css" },
|
||||
{ from: "res/css/views/messages/_MessageActionBar.pcss", type: "css" },
|
||||
{ from: "res/css/views/messages/_ThreadActionBar.pcss", type: "css" },
|
||||
{ from: "res/css/views/voip/LegacyCallView/_LegacyCallViewButtons.pcss", type: "css" },
|
||||
{ from: "res/css/views/elements/_ToggleSwitch.pcss", type: "css" },
|
||||
{ from: "res/css/views/settings/tabs/_SettingsTab.pcss", type: "css" },
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
# syntax=docker.io/docker/dockerfile:1.22-labs@sha256:4c116b618ed48404d579b5467127b20986f2a6b29e4b9be2fee841f632db6a86
|
||||
# syntax=docker.io/docker/dockerfile:1.23-labs@sha256:7eca9451d94f9b8ad22e44988b92d595d3e4d65163794237949a8c3413fbed5d
|
||||
# Context must be the root of the monorepo
|
||||
|
||||
# Builder
|
||||
FROM --platform=$BUILDPLATFORM node:24-bullseye@sha256:4bfbd78e049926e4ca595c1798810691ca7bb5aedd829ffd8a78b2ab30689810 AS builder
|
||||
FROM --platform=$BUILDPLATFORM node:24-bullseye@sha256:d2059a9c157c9f70739736979fa3635008bf3ca74560b30930dc181228bc427f AS builder
|
||||
|
||||
# Support custom branch of the js-sdk. This also helps us build images of element-web develop.
|
||||
ARG USE_CUSTOM_SDKS=false
|
||||
@@ -25,7 +25,7 @@ RUN --mount=type=bind,source=.git,target=/src/.git /src/scripts/docker-package.s
|
||||
RUN cp /src/apps/web/config.sample.json /src/apps/web/webapp/config.json
|
||||
|
||||
# App
|
||||
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:4011c42f28e9b54c86b52211598dbc6bcaa520311ddd55f211587cdd71f88a9c
|
||||
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:360465db60105a4cbf5215cd9e5a2ba40ef956978dd94f99707e9674050e38ea
|
||||
|
||||
# Need root user to install packages & manipulate the usr directory
|
||||
USER root
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
**/.pnpm-store
|
||||
**/tsconfig.node.tsbuildinfo
|
||||
**/*.md
|
||||
!**/*.api.md
|
||||
**/*.rst
|
||||
|
||||
.idea/
|
||||
@@ -22,6 +23,8 @@ apps/web/webpack-stats.json
|
||||
apps/web/playwright/
|
||||
apps/web/webapp/
|
||||
apps/web/debian/
|
||||
apps/desktop
|
||||
!apps/desktop/package.json
|
||||
|
||||
packages/shared-components/__vis__/
|
||||
packages/shared-components/storybook-static/
|
||||
|
||||
@@ -46,7 +46,7 @@ const config: Config = {
|
||||
"@vector-im/compound-web": "<rootDir>/../../node_modules/@vector-im/compound-web",
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
"/node_modules/(?!(mime|matrix-js-sdk|uuid|p-retry|is-network-error|react-merge-refs|is-ip|ip-regex|super-regex|function-timeout|time-span|convert-hrtime|clone-regexp|is-regexp|matrix-web-i18n|await-lock|@element-hq/web-shared-components|react-virtuoso|lodash)).+$",
|
||||
"/node_modules/(?!(mime|matrix-js-sdk|uuid|p-retry|is-network-error|react-merge-refs|is-ip|ip-regex|super-regex|function-timeout|time-span|convert-hrtime|clone-regexp|is-regexp|matrix-web-i18n|await-lock|@element-hq/web-shared-components|react-virtuoso|lodash|domutils|domhandler|domelementtype|dom-serializer|entities)).+$",
|
||||
],
|
||||
collectCoverageFrom: [
|
||||
"<rootDir>/src/**/*.{js,ts,tsx}",
|
||||
|
||||
+19
-21
@@ -30,15 +30,15 @@
|
||||
"lint:types": "nx lint:types",
|
||||
"lint:style": "stylelint \"res/css/**/*.pcss\"",
|
||||
"test": "nx test:unit",
|
||||
"test:playwright": "playwright test",
|
||||
"test:playwright:open": "pnpm test:playwright --ui",
|
||||
"test:playwright:screenshots": "playwright-screenshots-experimental pnpm playwright test --update-snapshots --project=Chrome --grep @screenshot",
|
||||
"test:playwright": "nx test:playwright --",
|
||||
"test:playwright:open": "nx test:playwright -- --ui",
|
||||
"test:playwright:screenshots": "nx test:playwright:screenshots --",
|
||||
"coverage": "pnpm test --coverage",
|
||||
"analyse:webpack-bundles": "webpack-bundle-analyzer webpack-stats.json webapp"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@element-hq/element-web-module-api": "catalog:",
|
||||
"@element-hq/element-web-module-api": "workspace:*",
|
||||
"@element-hq/web-shared-components": "workspace:*",
|
||||
"@fontsource/fira-code": "^5",
|
||||
"@fontsource/inter": "catalog:",
|
||||
@@ -63,25 +63,25 @@
|
||||
"css-tree": "^3.0.0",
|
||||
"diff-dom": "^5.0.0",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
"domutils": "^3.2.2",
|
||||
"domutils": "^4.0.0",
|
||||
"emojibase-regex": "^17.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"file-saver": "^2.0.5",
|
||||
"filesize": "11.0.13",
|
||||
"filesize": "11.0.15",
|
||||
"github-markdown-css": "^5.5.1",
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
"highlight.js": "^11.3.1",
|
||||
"html-entities": "^2.0.0",
|
||||
"html-react-parser": "^5.2.2",
|
||||
"html-react-parser": "^6.0.0",
|
||||
"is-ip": "^5.0.0",
|
||||
"js-xxhash": "^5.0.0",
|
||||
"jsrsasign": "^11.0.0",
|
||||
"jszip": "^3.7.0",
|
||||
"katex": "^0.16.0",
|
||||
"lodash": "npm:lodash-es@^4.17.21",
|
||||
"lodash": "npm:lodash-es@4.18.1",
|
||||
"maplibre-gl": "^5.0.0",
|
||||
"matrix-encrypt-attachment": "^1.0.3",
|
||||
"matrix-js-sdk": "41.3.0",
|
||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
||||
"matrix-widget-api": "^1.16.1",
|
||||
"memoize-one": "^6.0.0",
|
||||
"mime": "^4.0.4",
|
||||
@@ -89,7 +89,7 @@
|
||||
"opus-recorder": "^8.0.3",
|
||||
"pako": "^2.0.3",
|
||||
"png-chunks-extract": "^1.0.0",
|
||||
"posthog-js": "1.360.2",
|
||||
"posthog-js": "1.369.3",
|
||||
"qrcode": "1.5.4",
|
||||
"re-resizable": "6.11.2",
|
||||
"react": "catalog:",
|
||||
@@ -101,10 +101,9 @@
|
||||
"react-transition-group": "^4.4.1",
|
||||
"rfc4648": "^1.4.0",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"sanitize-html": "2.17.1",
|
||||
"sanitize-html": "2.17.3",
|
||||
"tar-js": "^0.3.0",
|
||||
"ua-parser-js": "1.0.40",
|
||||
"uuid": "^13.0.0",
|
||||
"what-input": "^5.2.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -125,10 +124,9 @@
|
||||
"@babel/preset-env": "^7.12.11",
|
||||
"@babel/preset-react": "^7.12.10",
|
||||
"@babel/preset-typescript": "^7.12.7",
|
||||
"@casualbot/jest-sonar-reporter": "2.5.0",
|
||||
"@element-hq/element-call-embedded": "0.18.0",
|
||||
"@element-hq/element-web-playwright-common": "catalog:",
|
||||
"@element-hq/element-web-playwright-common-local": "workspace:*",
|
||||
"@casualbot/jest-sonar-reporter": "2.6.0",
|
||||
"@element-hq/element-call-embedded": "0.19.1",
|
||||
"@element-hq/element-web-playwright-common": "workspace:*",
|
||||
"@fetch-mock/jest": "^0.2.20",
|
||||
"@jest/globals": "^30.2.0",
|
||||
"@peculiar/webcrypto": "^1.4.3",
|
||||
@@ -203,20 +201,20 @@
|
||||
"jest-raw-loader": "^1.0.1",
|
||||
"jsqr": "^1.4.0",
|
||||
"matrix-web-i18n": "catalog:",
|
||||
"mini-css-extract-plugin": "2.10.1",
|
||||
"mini-css-extract-plugin": "2.10.2",
|
||||
"modernizr": "^3.12.0",
|
||||
"playwright-core": "catalog:",
|
||||
"postcss": "8.5.8",
|
||||
"postcss": "8.5.10",
|
||||
"postcss-easings": "4.0.0",
|
||||
"postcss-hexrgba": "2.1.0",
|
||||
"postcss-import": "16.1.1",
|
||||
"postcss-loader": "8.2.1",
|
||||
"postcss-mixins": "12.1.2",
|
||||
"postcss-nested": "7.0.2",
|
||||
"postcss-preset-env": "11.2.0",
|
||||
"postcss-preset-env": "11.2.1",
|
||||
"postcss-scss": "4.0.9",
|
||||
"postcss-simple-vars": "7.0.1",
|
||||
"prettier": "3.8.1",
|
||||
"prettier": "3.8.3",
|
||||
"process": "^0.11.10",
|
||||
"raw-loader": "^4.0.2",
|
||||
"semver": "^7.5.2",
|
||||
@@ -246,6 +244,6 @@
|
||||
"engines": {
|
||||
"node": ">=22.18"
|
||||
},
|
||||
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be",
|
||||
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319",
|
||||
"private": true
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ export default defineConfig<{}, WorkerOptions>({
|
||||
trace: "on-first-retry",
|
||||
},
|
||||
webServer: {
|
||||
command: process.env.CI ? "npx serve -p 8080 -L ./webapp" : "pnpm start",
|
||||
command: process.env.CI ? "npx serve -p 8080 -L ./webapp" : "nx --outputStyle stream start",
|
||||
url: `${baseURL}/config.json`,
|
||||
reuseExistingServer: true,
|
||||
timeout: (process.env.CI ? 30 : 120) * 1000,
|
||||
|
||||
@@ -20,7 +20,7 @@ test.use({
|
||||
|
||||
test("Shows the welcome page by default", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await expect(page.getByRole("heading", { name: "Welcome to Element!" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Be in your element" })).toBeVisible();
|
||||
await expect(page.getByRole("link", { name: "Sign in" })).toBeVisible();
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import type { Locator, Page } from "@playwright/test";
|
||||
import { test, expect } from "../../element-web-test";
|
||||
import { test, expect, type ExtendedToMatchScreenshotOptions } from "../../element-web-test";
|
||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||
import { Layout } from "../../../src/settings/enums/Layout";
|
||||
import { type ElementAppPage } from "../../pages/ElementAppPage";
|
||||
@@ -94,7 +94,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
// Assert that rendering of the player settled and the play button is visible before taking a snapshot
|
||||
await checkPlayerVisibility(ircTile);
|
||||
|
||||
const screenshotOptions = {
|
||||
const screenshotOptions: ExtendedToMatchScreenshotOptions = {
|
||||
css: `
|
||||
/* The timestamp is of inconsistent width depending on the time the test runs at */
|
||||
.mx_MessageTimestamp {
|
||||
@@ -120,7 +120,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
};
|
||||
|
||||
// Take a snapshot of mx_EventTile_last on IRC layout
|
||||
screenshotOptions.clip = await page.locator(".mx_EventTile_last").boundingBox();
|
||||
screenshotOptions.clip = (await page.locator(".mx_EventTile_last").boundingBox()) ?? undefined;
|
||||
await scrollToBottomOfTimeline(page);
|
||||
await expect(page).toMatchScreenshot(`${detail.replaceAll(" ", "-")}-irc-layout.png`, screenshotOptions);
|
||||
|
||||
@@ -129,7 +129,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
const groupTile = page.locator(".mx_EventTile_last[data-layout='group']");
|
||||
await groupTile.locator(".mx_MessageTimestamp").click();
|
||||
await checkPlayerVisibility(groupTile);
|
||||
screenshotOptions.clip = await page.locator(".mx_EventTile_last").boundingBox();
|
||||
screenshotOptions.clip = (await page.locator(".mx_EventTile_last").boundingBox()) ?? undefined;
|
||||
await scrollToBottomOfTimeline(page);
|
||||
await expect(page).toMatchScreenshot(`${detail.replaceAll(" ", "-")}-group-layout.png`, screenshotOptions);
|
||||
|
||||
@@ -138,7 +138,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
const bubbleTile = page.locator(".mx_EventTile_last[data-layout='bubble']");
|
||||
await bubbleTile.locator(".mx_MessageTimestamp").click();
|
||||
await checkPlayerVisibility(bubbleTile);
|
||||
screenshotOptions.clip = await page.locator(".mx_EventTile_last").boundingBox();
|
||||
screenshotOptions.clip = (await page.locator(".mx_EventTile_last").boundingBox()) ?? undefined;
|
||||
await scrollToBottomOfTimeline(page);
|
||||
await expect(page).toMatchScreenshot(`${detail.replaceAll(" ", "-")}-bubble-layout.png`, screenshotOptions);
|
||||
};
|
||||
|
||||
@@ -27,6 +27,9 @@ const startDMWithBob = async (page: Page, bob: Bot) => {
|
||||
await page.getByRole("option", { name: bob.credentials.displayName }).click();
|
||||
await expect(page.getByTestId("invite-dialog-input-wrapper").getByText("Bob")).toBeVisible();
|
||||
await page.getByRole("button", { name: "Go" }).click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Start a chat with this new contact?" })).toBeVisible();
|
||||
await page.getByRole("button", { name: "Continue" }).click();
|
||||
};
|
||||
|
||||
const testMessages = async (page: Page, bob: Bot, bobRoomId: string) => {
|
||||
@@ -44,7 +47,7 @@ const bobJoin = async (page: Page, bob: Bot) => {
|
||||
const bobRooms = cli.getRooms();
|
||||
if (!bobRooms.length) {
|
||||
await new Promise<void>((resolve) => {
|
||||
const onMembership = (_event) => {
|
||||
const onMembership = () => {
|
||||
cli.off(window.matrixcs.RoomMemberEvent.Membership, onMembership);
|
||||
resolve();
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import type { EmittedEvents, Preset } from "matrix-js-sdk/src/matrix";
|
||||
import type { Preset, RoomMemberEvent, RoomStateEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { expect, test } from "../../element-web-test";
|
||||
import {
|
||||
createRoom,
|
||||
@@ -122,7 +122,7 @@ test.describe("Cryptography", function () {
|
||||
const roomId = await bob.evaluate(
|
||||
async (client, { alice }) => {
|
||||
const encryptionStatePromise = new Promise<void>((resolve) => {
|
||||
client.on("RoomState.events" as EmittedEvents, (event, _state, _lastStateEvent) => {
|
||||
client.on("RoomState.events" as RoomStateEvent.Events, (event, _state, _lastStateEvent) => {
|
||||
if (event.getType() === "m.room.encryption") {
|
||||
resolve();
|
||||
}
|
||||
@@ -253,11 +253,14 @@ test.describe("Cryptography", function () {
|
||||
|
||||
// invite Alice
|
||||
const inviteAlicePromise = new Promise<void>((resolve) => {
|
||||
client.on("RoomMember.membership" as EmittedEvents, (_event, member, _oldMembership?) => {
|
||||
if (member.userId === alice.userId && member.membership === "invite") {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
client.on(
|
||||
"RoomMember.membership" as RoomMemberEvent.Membership,
|
||||
(_event, member, _oldMembership?) => {
|
||||
if (member.userId === alice.userId && member.membership === "invite") {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
await client.invite(roomId, alice.userId);
|
||||
// wait for the invite to come back so that we encrypt to Alice
|
||||
@@ -271,11 +274,14 @@ test.describe("Cryptography", function () {
|
||||
|
||||
// kick Alice
|
||||
const kickAlicePromise = new Promise<void>((resolve) => {
|
||||
client.on("RoomMember.membership" as EmittedEvents, (_event, member, _oldMembership?) => {
|
||||
if (member.userId === alice.userId && member.membership === "leave") {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
client.on(
|
||||
"RoomMember.membership" as RoomMemberEvent.Membership,
|
||||
(_event, member, _oldMembership?) => {
|
||||
if (member.userId === alice.userId && member.membership === "leave") {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
await client.kick(roomId, alice.userId);
|
||||
await kickAlicePromise;
|
||||
|
||||
@@ -166,13 +166,9 @@ async function getDehydratedDeviceIds(client: Client): Promise<string[]> {
|
||||
return await client.evaluate(async (client) => {
|
||||
const userId = client.getUserId();
|
||||
const devices = await client.getCrypto().getUserDeviceInfo([userId]);
|
||||
return Array.from(
|
||||
devices
|
||||
.get(userId)
|
||||
.values()
|
||||
.filter((d) => d.dehydrated)
|
||||
.map((d) => d.deviceId),
|
||||
);
|
||||
return Array.from(devices.get(userId).values())
|
||||
.filter((d) => d.dehydrated)
|
||||
.map((d) => d.deviceId);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
waitForVerificationRequest,
|
||||
} from "./utils";
|
||||
import { type Bot } from "../../pages/bot";
|
||||
import { Toasts } from "../../pages/toasts.ts";
|
||||
import type { ElementAppPage } from "../../pages/ElementAppPage.ts";
|
||||
|
||||
test.describe("Device verification", { tag: "@no-webkit" }, () => {
|
||||
@@ -82,7 +81,11 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
|
||||
);
|
||||
|
||||
// Regression test for https://github.com/element-hq/element-web/issues/29110
|
||||
test("No toast after verification, even if the secrets take a while to arrive", async ({ page, credentials }) => {
|
||||
test("No toast after verification, even if the secrets take a while to arrive", async ({
|
||||
page,
|
||||
credentials,
|
||||
toasts,
|
||||
}) => {
|
||||
// Before we log in, the bot creates an encrypted room, so that we can test the toast behaviour that only happens
|
||||
// when we are in an encrypted room.
|
||||
await aliceBotClient.createRoom({
|
||||
@@ -121,7 +124,6 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
|
||||
await infoDialog.getByRole("button", { name: "Got it" }).click();
|
||||
|
||||
// There should be no toast (other than the notifications one)
|
||||
const toasts = new Toasts(page);
|
||||
await toasts.rejectToast("Notifications");
|
||||
await toasts.assertNoToasts();
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ test.describe("History sharing", function () {
|
||||
await sendMessageInCurrentRoom(alicePage, "A message from Alice");
|
||||
|
||||
// Send the invite to Bob
|
||||
await aliceElementApp.inviteUserToCurrentRoom(bobCredentials.userId);
|
||||
await aliceElementApp.inviteUserToCurrentRoom(bobCredentials.userId, { confirmUnknownUser: true });
|
||||
|
||||
// Bob accepts the invite
|
||||
await bobPage.getByRole("option", { name: "TestRoom" }).click();
|
||||
@@ -105,7 +105,7 @@ test.describe("History sharing", function () {
|
||||
|
||||
// Alice invites Bob, and Bob accepts
|
||||
const roomId = await aliceElementApp.getCurrentRoomIdFromUrl();
|
||||
await aliceElementApp.inviteUserToCurrentRoom(bobCredentials.userId);
|
||||
await aliceElementApp.inviteUserToCurrentRoom(bobCredentials.userId, { confirmUnknownUser: true });
|
||||
await bobPage.getByRole("option", { name: "TestRoom" }).click();
|
||||
await bobPage.getByRole("button", { name: "Accept" }).click();
|
||||
|
||||
@@ -143,7 +143,7 @@ test.describe("History sharing", function () {
|
||||
await sendMessageInCurrentRoom(bobPage, "Message3: 'shared' visibility, but Bob thinks it is still 'joined'");
|
||||
|
||||
// Alice now invites Charlie
|
||||
await aliceElementApp.inviteUserToCurrentRoom(charlieCredentials.userId);
|
||||
await aliceElementApp.inviteUserToCurrentRoom(charlieCredentials.userId, { confirmUnknownUser: true });
|
||||
await charliePage.getByRole("option", { name: "TestRoom" }).click();
|
||||
await charliePage.getByRole("button", { name: "Accept" }).click();
|
||||
|
||||
|
||||
@@ -43,11 +43,7 @@ test.describe("Key storage out of sync toast", () => {
|
||||
});
|
||||
|
||||
test("should prompt for recovery key if 'enter recovery key' pressed", { tag: "@screenshot" }, async ({ page }) => {
|
||||
// We need to wait for there to be two toasts as the wait below won't work in isolation:
|
||||
// playwright only evaluates the 'first()' call initially, not subsequent times it checks, so
|
||||
// it would always be checking the same toast, even if another one is now the first.
|
||||
await expect(page.getByRole("alert")).toHaveCount(2);
|
||||
await expect(page.getByRole("alert").first()).toMatchScreenshot(
|
||||
await expect(page.getByRole("alert").filter({ hasText: "Your key storage is out of sync." })).toMatchScreenshot(
|
||||
"key-storage-out-of-sync-toast.png",
|
||||
screenshotOptions,
|
||||
);
|
||||
|
||||
@@ -579,8 +579,8 @@ export async function deleteCachedSecrets(page: Page) {
|
||||
await page.evaluate(async () => {
|
||||
const removeCachedSecrets = new Promise((resolve) => {
|
||||
const request = window.indexedDB.open("matrix-js-sdk::matrix-sdk-crypto");
|
||||
request.onsuccess = (event: Event & { target: { result: IDBDatabase } }) => {
|
||||
const db = event.target.result;
|
||||
request.onsuccess = function (this: IDBRequest) {
|
||||
const db = this.result as IDBDatabase;
|
||||
const request = db.transaction("core", "readwrite").objectStore("core").delete("private_identity");
|
||||
request.onsuccess = () => {
|
||||
db.close();
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { test, expect } from "../../element-web-test";
|
||||
import { getSampleFilePath } from "../../sample-files";
|
||||
|
||||
test.describe("Devtools", () => {
|
||||
test.use({
|
||||
displayName: "Alice",
|
||||
});
|
||||
|
||||
test("should allow enabling low bandwidth mode", async ({ page, homeserver, user, app }) => {
|
||||
// Upload a picture
|
||||
const userSettings = await app.settings.openUserSettings("Account");
|
||||
const profileSettings = userSettings.locator(".mx_UserProfileSettings");
|
||||
await profileSettings.getByAltText("Upload").setInputFiles(getSampleFilePath("riot.png"));
|
||||
await app.closeDialog();
|
||||
|
||||
// Create an initial room.
|
||||
const createRoomDialog = await app.openCreateRoomDialog();
|
||||
await createRoomDialog.getByRole("textbox", { name: "Name" }).fill("Test Room");
|
||||
await createRoomDialog.getByRole("button", { name: "Create room" }).click();
|
||||
|
||||
const composer = app.getComposer().locator("[contenteditable]");
|
||||
await composer.fill("/devtools");
|
||||
await composer.press("Enter");
|
||||
const dialog = page.locator(".mx_Dialog");
|
||||
await dialog.getByLabel("Developer mode").check();
|
||||
await dialog.getByLabel("Disable bandwidth-heavy features").click();
|
||||
// Wait for refresh.
|
||||
await page.waitForEvent("domcontentloaded");
|
||||
await app.viewRoomByName("Test Room");
|
||||
|
||||
// This only appears when encryption has been disabled in the client.
|
||||
await expect(page.getByText("The encryption used by this room isn't supported.")).toBeVisible();
|
||||
|
||||
// None of these should be requested.
|
||||
let hasSentTyping = false;
|
||||
let hasRequestedThumbnail = false;
|
||||
await page.route("**/_matrix/client/v3/rooms/*/typing/*", async (route) => {
|
||||
hasSentTyping = true;
|
||||
await route.fulfill({ json: {} });
|
||||
});
|
||||
await page.route("**/_matrix/media/v3/thumbnail/**", async (route) => {
|
||||
hasRequestedThumbnail = true;
|
||||
await route.fulfill({ json: {} });
|
||||
});
|
||||
await page.route("**/_matrix/client/v1/media/thumbnail/**", async (route) => {
|
||||
hasRequestedThumbnail = true;
|
||||
await route.fulfill({ json: {} });
|
||||
});
|
||||
|
||||
await composer.pressSequentially("Provoke typing request", { delay: 5 });
|
||||
expect(hasSentTyping).toEqual(false);
|
||||
expect(hasRequestedThumbnail).toEqual(false);
|
||||
});
|
||||
});
|
||||
@@ -9,6 +9,15 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import { test, expect } from "../../element-web-test";
|
||||
|
||||
/**
|
||||
* CSS which will hide the mxid in the user list of the "unknown users" confirmation dialog. This is useful because the
|
||||
* MXID is not stable and the screenshot tests will otherwise fail.
|
||||
*
|
||||
* Ideally RichItem would give us a way to do this that doesn't involve gnarly CSS.
|
||||
*/
|
||||
const UNKNOWN_IDENTITY_USERS_DIALOG_HIDE_MXID_CSS =
|
||||
'[data-testid="userlist"] li > span:nth-last-child(1) { display: none }';
|
||||
|
||||
test.describe("Invite dialog", function () {
|
||||
test.use({
|
||||
displayName: "Hanako",
|
||||
@@ -62,6 +71,15 @@ test.describe("Invite dialog", function () {
|
||||
// Invite the bot
|
||||
await other.getByRole("button", { name: "Invite" }).click();
|
||||
|
||||
// Expect a confirmation dialog, screenshot, and dismiss
|
||||
await expect(
|
||||
page.locator(".mx_Dialog").getByRole("heading", { name: "Invite new contacts to this room?" }),
|
||||
).toBeVisible();
|
||||
await expect(page.locator(".mx_Dialog")).toMatchScreenshot("confirm-invite-new-contact.png", {
|
||||
css: UNKNOWN_IDENTITY_USERS_DIALOG_HIDE_MXID_CSS,
|
||||
});
|
||||
await page.locator(".mx_Dialog").getByRole("button", { name: "Invite" }).click();
|
||||
|
||||
// Assert that the invite dialog disappears
|
||||
await expect(page.locator(".mx_InviteDialog_other")).not.toBeVisible();
|
||||
|
||||
@@ -104,6 +122,15 @@ test.describe("Invite dialog", function () {
|
||||
// Open a direct message UI
|
||||
await other.getByRole("button", { name: "Go" }).click();
|
||||
|
||||
// Expect a confirmation dialog, screenshot, and dismiss
|
||||
await expect(
|
||||
page.locator(".mx_Dialog").getByRole("heading", { name: "Start a chat with this new contact?" }),
|
||||
).toBeVisible();
|
||||
await expect(page.locator(".mx_Dialog")).toMatchScreenshot("confirm-chat-with-new-contact.png", {
|
||||
css: UNKNOWN_IDENTITY_USERS_DIALOG_HIDE_MXID_CSS,
|
||||
});
|
||||
await page.locator(".mx_Dialog").getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
// Assert that the invite dialog disappears
|
||||
await expect(page.locator(".mx_InviteDialog_other")).not.toBeVisible();
|
||||
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* Copyright 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type Page } from "@playwright/test";
|
||||
|
||||
import { expect, test } from "../../../element-web-test";
|
||||
import { getRoomList, getRoomListHeader, getSectionHeader } from "./utils";
|
||||
|
||||
test.describe("Room list custom sections", () => {
|
||||
test.use({
|
||||
displayName: "Alice",
|
||||
labsFlags: ["feature_new_room_list", "feature_room_list_sections"],
|
||||
botCreateOpts: {
|
||||
displayName: "BotBob",
|
||||
autoAcceptInvites: true,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Create a custom section via the header compose menu and dialog.
|
||||
* @param page
|
||||
* @param sectionName The name of the section to create
|
||||
*/
|
||||
async function createCustomSection(page: Page, sectionName: string): Promise<void> {
|
||||
const composeMenu = getRoomListHeader(page).getByRole("button", { name: "New conversation" });
|
||||
await composeMenu.click();
|
||||
await page.getByRole("menuitem", { name: "New section" }).click();
|
||||
|
||||
// Fill in the section name in the dialog
|
||||
const dialog = page.getByRole("dialog", { name: "Create a section" });
|
||||
await expect(dialog).toBeVisible();
|
||||
await dialog.getByRole("textbox", { name: "Section name" }).fill(sectionName);
|
||||
await dialog.getByRole("button", { name: "Create section" }).click();
|
||||
|
||||
// Wait for the dialog to close
|
||||
await expect(dialog).not.toBeVisible();
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page, app, user }) => {
|
||||
// The notification toast is displayed above the search section
|
||||
await app.closeNotificationToast();
|
||||
|
||||
// Focus the user menu to avoid hover decoration
|
||||
await page.getByRole("button", { name: "User menu" }).focus();
|
||||
});
|
||||
|
||||
test.describe("Section creation", () => {
|
||||
test("should create a custom section via the header compose menu", async ({ page, app }) => {
|
||||
await app.client.createRoom({ name: "my room" });
|
||||
|
||||
await createCustomSection(page, "Work");
|
||||
|
||||
// The custom section header should be visible (even though it is empty)
|
||||
await expect(getSectionHeader(page, "Work")).toBeVisible();
|
||||
// The Chats section should also be visible
|
||||
await expect(getSectionHeader(page, "Chats")).toBeVisible();
|
||||
});
|
||||
|
||||
test("should show 'Section created' toast after creating a section", async ({ page, app }) => {
|
||||
await app.client.createRoom({ name: "my room" });
|
||||
|
||||
await createCustomSection(page, "Personal");
|
||||
|
||||
// The "Section created" toast should appear
|
||||
await expect(page.getByText("Section created")).toBeVisible();
|
||||
});
|
||||
|
||||
test("should create a custom section via the room option menu", async ({ page, app }) => {
|
||||
await app.client.createRoom({ name: "my room" });
|
||||
|
||||
const roomList = getRoomList(page);
|
||||
const roomItem = roomList.getByRole("option", { name: "Open room my room" });
|
||||
await expect(roomItem).toBeVisible();
|
||||
|
||||
// Open the More Options menu
|
||||
await roomItem.hover();
|
||||
await roomItem.getByRole("button", { name: "More Options" }).click();
|
||||
|
||||
// Open the "Move to" submenu
|
||||
await page.getByRole("menuitem", { name: "Move to" }).hover();
|
||||
|
||||
// Click on "New section"
|
||||
await page.getByRole("menuitem", { name: "New section" }).click();
|
||||
|
||||
// Fill in the section name in the dialog
|
||||
const dialog = page.getByRole("dialog", { name: "Create a section" });
|
||||
await expect(dialog).toBeVisible();
|
||||
await dialog.getByRole("textbox", { name: "Section name" }).fill("Projects");
|
||||
await dialog.getByRole("button", { name: "Create section" }).click();
|
||||
|
||||
// Wait for the dialog to close
|
||||
await expect(dialog).not.toBeVisible();
|
||||
|
||||
// The custom section should be created
|
||||
await expect(getSectionHeader(page, "Projects")).toBeVisible();
|
||||
});
|
||||
|
||||
test("should cancel section creation when dialog is dismissed", async ({ page, app }) => {
|
||||
await app.client.createRoom({ name: "my room" });
|
||||
|
||||
const composeMenu = getRoomListHeader(page).getByRole("button", { name: "New conversation" });
|
||||
await composeMenu.click();
|
||||
await page.getByRole("menuitem", { name: "New section" }).click();
|
||||
|
||||
// The dialog should appear
|
||||
const dialog = page.getByRole("dialog", { name: "Create a section" });
|
||||
await expect(dialog).toBeVisible();
|
||||
|
||||
// Cancel the dialog
|
||||
await dialog.getByRole("button", { name: "Cancel" }).click();
|
||||
|
||||
// The dialog should close
|
||||
await expect(dialog).not.toBeVisible();
|
||||
|
||||
// No custom section should be created - should remain a flat list
|
||||
await expect(getSectionHeader(page, "Chats")).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("should create multiple custom sections", async ({ page, app }) => {
|
||||
await app.client.createRoom({ name: "my room" });
|
||||
|
||||
await createCustomSection(page, "Work");
|
||||
await createCustomSection(page, "Personal");
|
||||
|
||||
// Both custom sections should be visible
|
||||
await expect(getSectionHeader(page, "Work")).toBeVisible();
|
||||
await expect(getSectionHeader(page, "Personal")).toBeVisible();
|
||||
await expect(getSectionHeader(page, "Chats")).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Custom section display", () => {
|
||||
test("should show empty custom sections", async ({ page, app }) => {
|
||||
// Create a room so the Chats section has something
|
||||
await app.client.createRoom({ name: "my room" });
|
||||
|
||||
await createCustomSection(page, "Empty Section");
|
||||
|
||||
// The custom section should be visible even with no rooms
|
||||
await expect(getSectionHeader(page, "Empty Section")).toBeVisible();
|
||||
// The room should still be in the Chats section
|
||||
const roomList = getRoomList(page);
|
||||
await expect(roomList.getByRole("row", { name: "Open room my room" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("should display custom sections between Favourites and Chats", async ({ page, app }) => {
|
||||
// Create a favourite room
|
||||
const favouriteId = await app.client.createRoom({ name: "favourite room" });
|
||||
await app.client.evaluate(async (client, roomId) => {
|
||||
await client.setRoomTag(roomId, "m.favourite");
|
||||
}, favouriteId);
|
||||
|
||||
// Create a low priority room
|
||||
const lowPrioId = await app.client.createRoom({ name: "low prio room" });
|
||||
await app.client.evaluate(async (client, roomId) => {
|
||||
await client.setRoomTag(roomId, "m.lowpriority");
|
||||
}, lowPrioId);
|
||||
|
||||
// Create a regular room
|
||||
await app.client.createRoom({ name: "regular room" });
|
||||
|
||||
// Create a custom section
|
||||
await createCustomSection(page, "Work");
|
||||
|
||||
// All section headers should be visible
|
||||
await expect(getSectionHeader(page, "Favourites")).toBeVisible();
|
||||
await expect(getSectionHeader(page, "Work")).toBeVisible();
|
||||
// Should be expanded by default
|
||||
await expect(getSectionHeader(page, "Work")).toHaveAttribute("aria-expanded", "true");
|
||||
await expect(getSectionHeader(page, "Chats")).toBeVisible();
|
||||
await expect(getSectionHeader(page, "Low Priority")).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Adding a room to a custom section", () => {
|
||||
/**
|
||||
* Asserts a room is nested under a specific section using the treegrid aria-level hierarchy.
|
||||
* Section header rows sit at aria-level=1; room rows nested within a section sit at aria-level=2.
|
||||
* Verifies that the closest preceding aria-level=1 row is the expected section header.
|
||||
*/
|
||||
async function assertRoomInSection(page: Page, sectionName: string, roomName: string): Promise<void> {
|
||||
const roomList = getRoomList(page);
|
||||
const roomRow = roomList.getByRole("row", { name: `Open room ${roomName}` });
|
||||
// Room row must be at aria-level=2 (i.e. inside a section)
|
||||
await expect(roomRow).toHaveAttribute("aria-level", "2");
|
||||
// The closest preceding aria-level=1 row must be the expected section header.
|
||||
// XPath preceding:: axis returns nodes before the context in document order; [1] picks the nearest one.
|
||||
const closestSectionHeader = roomRow.locator(`xpath=preceding::*[@role="row" and @aria-level="1"][1]`);
|
||||
await expect(closestSectionHeader).toContainText(sectionName);
|
||||
}
|
||||
|
||||
test("should add a room to a custom section via the More Options menu", async ({ page, app }) => {
|
||||
await app.client.createRoom({ name: "my room" });
|
||||
await createCustomSection(page, "Work");
|
||||
|
||||
const roomList = getRoomList(page);
|
||||
|
||||
// Room starts in Chats section (aria-level=2)
|
||||
const roomItem = roomList.getByRole("row", { name: "Open room my room" });
|
||||
await expect(roomItem).toBeVisible();
|
||||
|
||||
// Open More Options and move to the Work section
|
||||
await roomItem.hover();
|
||||
await roomItem.getByRole("button", { name: "More Options" }).click();
|
||||
await page.getByRole("menuitem", { name: "Move to" }).hover();
|
||||
await page.getByRole("menuitem", { name: "Work" }).click();
|
||||
|
||||
// Room should now be nested under the Work section header (aria-level=1 → aria-level=2)
|
||||
await assertRoomInSection(page, "Work", "my room");
|
||||
});
|
||||
|
||||
test(
|
||||
"should show 'Chat moved' toast when adding a room to a custom section",
|
||||
{ tag: "@screenshot" },
|
||||
async ({ page, app }) => {
|
||||
await app.client.createRoom({ name: "my room" });
|
||||
await createCustomSection(page, "Work");
|
||||
|
||||
const roomList = getRoomList(page);
|
||||
const roomItem = roomList.getByRole("row", { name: "Open room my room" });
|
||||
|
||||
await roomItem.hover();
|
||||
await roomItem.getByRole("button", { name: "More Options" }).click();
|
||||
await page.getByRole("menuitem", { name: "Move to" }).hover();
|
||||
await page.getByRole("menuitem", { name: "Work" }).click();
|
||||
|
||||
// The "Chat moved" toast should appear
|
||||
await expect(page.getByText("Chat moved")).toBeVisible();
|
||||
|
||||
// Remove focus outline from the room item before taking the screenshot
|
||||
await page.getByRole("button", { name: "User menu" }).focus();
|
||||
|
||||
await expect(roomList).toMatchScreenshot("room-list-sections-chat-moved-toast.png");
|
||||
},
|
||||
);
|
||||
|
||||
test("should remove a room from a custom section when toggling the same section", async ({ page, app }) => {
|
||||
await app.client.createRoom({ name: "my room" });
|
||||
await createCustomSection(page, "Work");
|
||||
|
||||
const roomList = getRoomList(page);
|
||||
|
||||
// Move to Work section and verify placement via aria-level
|
||||
let roomItem = roomList.getByRole("row", { name: "Open room my room" });
|
||||
await roomItem.hover();
|
||||
await roomItem.getByRole("button", { name: "More Options" }).click();
|
||||
await page.getByRole("menuitem", { name: "Move to" }).hover();
|
||||
await page.getByRole("menuitem", { name: "Work" }).click();
|
||||
|
||||
await assertRoomInSection(page, "Work", "my room");
|
||||
|
||||
// Toggle off by selecting the same section again
|
||||
roomItem = roomList.getByRole("row", { name: "Open room my room" });
|
||||
await roomItem.hover();
|
||||
await roomItem.getByRole("button", { name: "More Options" }).click();
|
||||
await page.getByRole("menuitem", { name: "Move to" }).hover();
|
||||
await page.getByRole("menuitem", { name: "Work" }).click();
|
||||
|
||||
// Room is back in the Chats section
|
||||
await assertRoomInSection(page, "Chats", "my room");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -6,10 +6,11 @@
|
||||
*/
|
||||
|
||||
import { type Visibility } from "matrix-js-sdk/src/matrix";
|
||||
import { type Locator, type Page } from "@playwright/test";
|
||||
import { type Page } from "@playwright/test";
|
||||
|
||||
import { expect, test } from "../../../element-web-test";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
import { getFilterCollapseButton, getFilterExpandButton, getPrimaryFilters, getRoomOptionsMenu } from "./utils";
|
||||
|
||||
test.describe("Room list filters and sort", () => {
|
||||
test.use({
|
||||
@@ -21,22 +22,6 @@ test.describe("Room list filters and sort", () => {
|
||||
labsFlags: ["feature_new_room_list"],
|
||||
});
|
||||
|
||||
function getPrimaryFilters(page: Page): Locator {
|
||||
return page.getByTestId("primary-filters");
|
||||
}
|
||||
|
||||
function getRoomOptionsMenu(page: Page): Locator {
|
||||
return page.getByRole("button", { name: "Room Options" });
|
||||
}
|
||||
|
||||
function getFilterExpandButton(page: Page): Locator {
|
||||
return getPrimaryFilters(page).getByRole("button", { name: "Expand filter list" });
|
||||
}
|
||||
|
||||
function getFilterCollapseButton(page: Page): Locator {
|
||||
return getPrimaryFilters(page).getByRole("button", { name: "Collapse filter list" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the room list
|
||||
* @param page
|
||||
|
||||
@@ -6,21 +6,13 @@
|
||||
*/
|
||||
|
||||
import { test, expect } from "../../../element-web-test";
|
||||
import type { Page } from "@playwright/test";
|
||||
import { getHeaderSection } from "./utils";
|
||||
|
||||
test.describe("Header section of the room list", () => {
|
||||
test.use({
|
||||
labsFlags: ["feature_new_room_list"],
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the header section of the room list
|
||||
* @param page
|
||||
*/
|
||||
function getHeaderSection(page: Page) {
|
||||
return page.getByTestId("room-list-header");
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page, app, user }) => {
|
||||
// The notification toast is displayed above the search section
|
||||
await app.closeNotificationToast();
|
||||
|
||||
@@ -5,23 +5,14 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type Page } from "@playwright/test";
|
||||
|
||||
import { test, expect } from "../../../element-web-test";
|
||||
import { getRoomListView } from "./utils";
|
||||
|
||||
test.describe("Room list panel", () => {
|
||||
test.use({
|
||||
labsFlags: ["feature_new_room_list"],
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the room list view
|
||||
* @param page
|
||||
*/
|
||||
function getRoomListView(page: Page) {
|
||||
return page.getByRole("navigation", { name: "Room list" });
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page, app, user }) => {
|
||||
// The notification toast is displayed above the search section
|
||||
await app.closeNotificationToast();
|
||||
|
||||
@@ -5,23 +5,14 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type Page } from "@playwright/test";
|
||||
|
||||
import { test, expect } from "../../../element-web-test";
|
||||
import { getSearchSection } from "./utils";
|
||||
|
||||
test.describe("Search section of the room list", () => {
|
||||
test.use({
|
||||
labsFlags: ["feature_new_room_list"],
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the search section of the room list
|
||||
* @param page
|
||||
*/
|
||||
function getSearchSection(page: Page) {
|
||||
return page.getByRole("search");
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page, app, user }) => {
|
||||
// The notification toast is displayed above the search section
|
||||
await app.closeNotificationToast();
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { expect, test } from "../../../element-web-test";
|
||||
import { getPrimaryFilters, getRoomList, getSectionHeader } from "./utils";
|
||||
|
||||
test.describe("Room list sections", () => {
|
||||
test.use({
|
||||
displayName: "Alice",
|
||||
labsFlags: ["feature_new_room_list", "feature_room_list_sections"],
|
||||
botCreateOpts: {
|
||||
displayName: "BotBob",
|
||||
autoAcceptInvites: true,
|
||||
},
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ page, app, user }) => {
|
||||
// The notification toast is displayed above the search section
|
||||
await app.closeNotificationToast();
|
||||
|
||||
// focus the user menu to avoid to have hover decoration
|
||||
await page.getByRole("button", { name: "User menu" }).focus();
|
||||
});
|
||||
|
||||
test.describe("Section rendering", () => {
|
||||
test.beforeEach(async ({ app, user }) => {
|
||||
// Create regular rooms
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await app.client.createRoom({ name: `room${i}` });
|
||||
}
|
||||
});
|
||||
|
||||
test("should render sections with correct rooms in each", { tag: "@screenshot" }, async ({ page, app }) => {
|
||||
// Create a favourite room
|
||||
const favouriteId = await app.client.createRoom({ name: "favourite room" });
|
||||
await app.client.evaluate(async (client, roomId) => {
|
||||
await client.setRoomTag(roomId, "m.favourite");
|
||||
}, favouriteId);
|
||||
|
||||
// Create a low priority room
|
||||
const lowPrioId = await app.client.createRoom({ name: "low prio room" });
|
||||
await app.client.evaluate(async (client, roomId) => {
|
||||
await client.setRoomTag(roomId, "m.lowpriority");
|
||||
}, lowPrioId);
|
||||
|
||||
const roomList = getRoomList(page);
|
||||
|
||||
// All three section headers should be visible
|
||||
await expect(getSectionHeader(page, "Favourites")).toBeVisible();
|
||||
await expect(getSectionHeader(page, "Chats")).toBeVisible();
|
||||
await expect(getSectionHeader(page, "Low Priority")).toBeVisible();
|
||||
|
||||
// Ensure all rooms are visible
|
||||
await expect(roomList.getByRole("row", { name: "Open room favourite room" })).toBeVisible();
|
||||
await expect(roomList.getByRole("row", { name: "Open room low prio room" })).toBeVisible();
|
||||
await expect(roomList.getByRole("row", { name: "Open room room0" })).toBeVisible();
|
||||
|
||||
await expect(roomList).toMatchScreenshot("room-list-sections.png");
|
||||
});
|
||||
|
||||
test("should only show non-empty sections", async ({ page, app }) => {
|
||||
// No low priority rooms created, only regular and favourite rooms
|
||||
const favouriteId = await app.client.createRoom({ name: "favourite room" });
|
||||
await app.client.evaluate(async (client, roomId) => {
|
||||
await client.setRoomTag(roomId, "m.favourite");
|
||||
}, favouriteId);
|
||||
|
||||
// Chats and Favourites sections should still be visible
|
||||
await expect(getSectionHeader(page, "Chats")).toBeVisible();
|
||||
await expect(getSectionHeader(page, "Favourites")).toBeVisible();
|
||||
// Low Priority sections should not be visible
|
||||
await expect(getSectionHeader(page, "Low Priority")).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("should render a flat list when there is only rooms in Chats section", async ({ page, app }) => {
|
||||
// All sections should not be visible
|
||||
await expect(getSectionHeader(page, "Chats")).not.toBeVisible();
|
||||
await expect(getSectionHeader(page, "Favourites")).not.toBeVisible();
|
||||
await expect(getSectionHeader(page, "Low Priority")).not.toBeVisible();
|
||||
// It should be a flat list (using listbox a11y role)
|
||||
await expect(page.getByRole("listbox", { name: "Room list", exact: true })).toBeVisible();
|
||||
await expect(getRoomList(page).getByRole("option", { name: "Open room room0" })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Section collapse and expand", () => {
|
||||
[
|
||||
{ section: "Favourites", roomName: "favourite room", tag: "m.favourite" },
|
||||
{ section: "Low Priority", roomName: "low prio room", tag: "m.lowpriority" },
|
||||
].forEach(({ section, roomName, tag }) => {
|
||||
test(`should collapse and expand the ${section} section`, async ({ page, app }) => {
|
||||
const roomId = await app.client.createRoom({ name: roomName });
|
||||
if (tag) {
|
||||
await app.client.evaluate(
|
||||
async (client, { roomId, tag }) => {
|
||||
await client.setRoomTag(roomId, tag);
|
||||
},
|
||||
{ roomId, tag },
|
||||
);
|
||||
}
|
||||
|
||||
const roomList = getRoomList(page);
|
||||
const sectionHeader = getSectionHeader(page, section);
|
||||
|
||||
// The room should be visible
|
||||
await expect(roomList.getByRole("row", { name: `Open room ${roomName}` })).toBeVisible();
|
||||
|
||||
// Collapse the section
|
||||
await sectionHeader.click();
|
||||
|
||||
// The room should no longer be visible
|
||||
await expect(roomList.getByRole("row", { name: `Open room ${roomName}` })).not.toBeVisible();
|
||||
|
||||
// The section header should still be visible
|
||||
await expect(sectionHeader).toBeVisible();
|
||||
|
||||
// Expand the section again
|
||||
await sectionHeader.click();
|
||||
|
||||
// The room should be visible again
|
||||
await expect(roomList.getByRole("row", { name: `Open room ${roomName}` })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test("should render collapsed section", { tag: "@screenshot" }, async ({ page, app }) => {
|
||||
const favouriteId = await app.client.createRoom({ name: "favourite room" });
|
||||
await app.client.evaluate(async (client, roomId) => {
|
||||
await client.setRoomTag(roomId, "m.favourite");
|
||||
}, favouriteId);
|
||||
|
||||
await app.client.createRoom({ name: "regular room" });
|
||||
|
||||
const roomList = getRoomList(page);
|
||||
|
||||
// Collapse the Favourites section
|
||||
await getSectionHeader(page, "Favourites").click();
|
||||
|
||||
// Verify favourite room is hidden but regular room is still visible
|
||||
await expect(roomList.getByRole("row", { name: "Open room favourite room" })).not.toBeVisible();
|
||||
await expect(roomList.getByRole("row", { name: "Open room regular room" })).toBeVisible();
|
||||
|
||||
await expect(roomList).toMatchScreenshot("room-list-sections-collapsed.png");
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Rooms placement in sections", () => {
|
||||
test("should move a room between sections when tags change", async ({ page, app }) => {
|
||||
await app.client.createRoom({ name: "my room" });
|
||||
|
||||
const roomList = getRoomList(page);
|
||||
|
||||
// Flat list because there is only rooms in the Chats section
|
||||
let roomItem = roomList.getByRole("option", { name: "Open room my room" });
|
||||
await expect(roomItem).toBeVisible();
|
||||
|
||||
// Favourite the room via context menu
|
||||
await roomItem.click({ button: "right" });
|
||||
await page.getByRole("menuitemcheckbox", { name: "Favourited" }).click();
|
||||
|
||||
// The Favourites section header should now be visible and the room should be under it
|
||||
await expect(getSectionHeader(page, "Favourites")).toBeVisible();
|
||||
roomItem = roomList.getByRole("row", { name: "Open room my room" });
|
||||
await expect(roomItem).toBeVisible();
|
||||
|
||||
// Unfavourite the room
|
||||
await roomItem.hover();
|
||||
await roomItem.getByRole("button", { name: "More Options" }).click();
|
||||
await page.getByRole("menuitemcheckbox", { name: "Favourited" }).click();
|
||||
|
||||
// Mark the room as low priority via context menu
|
||||
roomItem = roomList.getByRole("option", { name: "Open room my room" });
|
||||
await roomItem.click({ button: "right" });
|
||||
await page.getByRole("menuitemcheckbox", { name: "Low priority" }).click();
|
||||
|
||||
// The Low Priority section header should now be visible and the room should be under it
|
||||
await expect(getSectionHeader(page, "Low Priority")).toBeVisible();
|
||||
roomItem = roomList.getByRole("row", { name: "Open room my room" });
|
||||
await expect(roomItem).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test("should show unread indicator on section header", async ({ page, app, bot }) => {
|
||||
// Create a favourite room
|
||||
const favouriteId = await app.client.createRoom({ name: "favourite room" });
|
||||
await app.client.evaluate(async (client, roomId) => {
|
||||
await client.setRoomTag(roomId, "m.favourite");
|
||||
}, favouriteId);
|
||||
|
||||
const roomList = getRoomList(page);
|
||||
|
||||
// Invite the bot and have it send a message to generate an unread
|
||||
await app.client.inviteUser(favouriteId, bot.credentials.userId);
|
||||
await bot.joinRoom(favouriteId);
|
||||
await bot.sendMessage(favouriteId, "Hello from bot!");
|
||||
|
||||
let sectionHeader = getSectionHeader(page, "Favourites", true);
|
||||
await expect(sectionHeader).toBeVisible();
|
||||
|
||||
// Open the room to mark it as read
|
||||
await roomList.getByRole("row", { name: "Open room favourite room" }).click();
|
||||
|
||||
// The section should no longer be unread
|
||||
sectionHeader = getSectionHeader(page, "Favourites", false);
|
||||
await expect(sectionHeader).toBeVisible();
|
||||
});
|
||||
|
||||
test.describe("Sections and filters interaction", () => {
|
||||
test("should not show Favourite and Low Priority filters when sections are enabled", async ({ page, app }) => {
|
||||
const primaryFilters = getPrimaryFilters(page);
|
||||
|
||||
// Expand the filter list to see all filters
|
||||
const expandButton = primaryFilters.getByRole("button", { name: "Expand filter list" });
|
||||
await expandButton.click();
|
||||
|
||||
// Favourite and Low Priority filters should NOT be visible since sections handle them
|
||||
await expect(primaryFilters.getByRole("option", { name: "Favourite" })).not.toBeVisible();
|
||||
|
||||
// Other filters should still be present
|
||||
await expect(primaryFilters.getByRole("option", { name: "People" })).toBeVisible();
|
||||
await expect(primaryFilters.getByRole("option", { name: "Rooms" })).toBeVisible();
|
||||
await expect(primaryFilters.getByRole("option", { name: "Unread" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("should maintain sections when a filter is applied", async ({ page, app, bot }) => {
|
||||
// Create a favourite room with unread messages
|
||||
const favouriteId = await app.client.createRoom({ name: "fav with unread" });
|
||||
await app.client.evaluate(async (client, roomId) => {
|
||||
await client.setRoomTag(roomId, "m.favourite");
|
||||
}, favouriteId);
|
||||
await app.client.inviteUser(favouriteId, bot.credentials.userId);
|
||||
await bot.joinRoom(favouriteId);
|
||||
await bot.sendMessage(favouriteId, "Hello from favourite!");
|
||||
|
||||
// Create a regular room with unread messages
|
||||
const regularId = await app.client.createRoom({ name: "regular with unread" });
|
||||
await app.client.inviteUser(regularId, bot.credentials.userId);
|
||||
await bot.joinRoom(regularId);
|
||||
await bot.sendMessage(regularId, "Hello from regular!");
|
||||
|
||||
// Create a room without unread
|
||||
await app.client.createRoom({ name: "no unread room" });
|
||||
|
||||
const roomList = getRoomList(page);
|
||||
const primaryFilters = getPrimaryFilters(page);
|
||||
|
||||
// Apply the Unread filter
|
||||
await primaryFilters.getByRole("option", { name: "Unread" }).click();
|
||||
|
||||
// Only rooms with unreads should be visible
|
||||
await expect(roomList.getByRole("row", { name: "fav with unread" })).toBeVisible();
|
||||
await expect(roomList.getByRole("row", { name: "regular with unread" })).toBeVisible();
|
||||
await expect(roomList.getByRole("row", { name: "no unread room" })).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -10,6 +10,7 @@ import { type Page } from "@playwright/test";
|
||||
import { expect, test } from "../../../element-web-test";
|
||||
import { type Bot } from "../../../pages/bot";
|
||||
import { type ElementAppPage } from "../../../pages/ElementAppPage";
|
||||
import { getRoomList } from "./utils";
|
||||
|
||||
test.describe("Room list", () => {
|
||||
test.use({
|
||||
@@ -20,14 +21,6 @@ test.describe("Room list", () => {
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the room list
|
||||
* @param page
|
||||
*/
|
||||
function getRoomList(page: Page) {
|
||||
return page.getByTestId("room-list");
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page, app, user }) => {
|
||||
// The notification toast is displayed above the search section
|
||||
await app.closeNotificationToast();
|
||||
@@ -328,11 +321,11 @@ test.describe("Room list", () => {
|
||||
|
||||
const roomListView = getRoomList(page);
|
||||
const videoRoom = roomListView.getByRole("option", { name: "video room" });
|
||||
await expect(videoRoom).toHaveAttribute("aria-selected", "true"); // wait for room list update
|
||||
|
||||
// focus the user menu to avoid to have hover decoration
|
||||
await page.getByRole("button", { name: "User menu" }).focus();
|
||||
|
||||
await expect(videoRoom).toBeVisible();
|
||||
await expect(videoRoom).toMatchScreenshot("room-list-item-video.png");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type Locator, type Page } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* Get the room list
|
||||
* @param page
|
||||
*/
|
||||
export function getRoomList(page: Page): Locator {
|
||||
return page.getByTestId("room-list");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the room list header
|
||||
* @param page
|
||||
*/
|
||||
export function getRoomListHeader(page: Page): Locator {
|
||||
return page.getByTestId("room-list-header");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a section header toggle button by section name
|
||||
* @param page
|
||||
* @param sectionName The display name of the section
|
||||
* @param isUnread Whether to look for the unread version of the section header
|
||||
*/
|
||||
export function getSectionHeader(page: Page, sectionName: string, isUnread = false): Locator {
|
||||
return getRoomList(page).getByRole("gridcell", {
|
||||
name: isUnread ? `Toggle ${sectionName} section with unread room(s)` : `Toggle ${sectionName} section`,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the primary filters container
|
||||
* @param page
|
||||
*/
|
||||
export function getPrimaryFilters(page: Page): Locator {
|
||||
return page.getByTestId("primary-filters");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the room options menu button in the room list header
|
||||
* @param page
|
||||
*/
|
||||
export function getRoomOptionsMenu(page: Page): Locator {
|
||||
return page.getByRole("button", { name: "Room Options" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filter list expand button in the room list header
|
||||
* @param page
|
||||
*/
|
||||
export function getFilterExpandButton(page: Page): Locator {
|
||||
return getPrimaryFilters(page).getByRole("button", { name: "Expand filter list" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filter list collapse button in the room list header
|
||||
* @param page
|
||||
*/
|
||||
export function getFilterCollapseButton(page: Page): Locator {
|
||||
return getPrimaryFilters(page).getByRole("button", { name: "Collapse filter list" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the header section of the room list
|
||||
* @param page
|
||||
*/
|
||||
export function getHeaderSection(page: Page) {
|
||||
return page.getByTestId("room-list-header");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the room list view
|
||||
* @param page
|
||||
*/
|
||||
export function getRoomListView(page: Page) {
|
||||
return page.getByRole("navigation", { name: "Room list" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the search section of the room list
|
||||
* @param page
|
||||
*/
|
||||
export function getSearchSection(page: Page) {
|
||||
return page.getByRole("search");
|
||||
}
|
||||
@@ -14,7 +14,7 @@ test.describe("Message links", () => {
|
||||
await use({ roomId });
|
||||
},
|
||||
});
|
||||
for (const link of ["https://example.org", "example.org", "ftp://example.org"]) {
|
||||
for (const link of ["https://example.org", "ftp://example.org"]) {
|
||||
test(`should linkify a regular link '${link}'`, async ({ page, user, app, room }) => {
|
||||
await page.goto(`#/room/${room.roomId}`);
|
||||
// Needs to be unformatted so we test linkifing
|
||||
@@ -24,6 +24,13 @@ test.describe("Message links", () => {
|
||||
await expect(linkElement).toBeVisible();
|
||||
});
|
||||
}
|
||||
test("should NOT linkify a bare domain", async ({ page, user, app, room }) => {
|
||||
await page.goto(`#/room/${room.roomId}`);
|
||||
// Needs to be unformatted so we test linkifing
|
||||
await app.client.sendMessage(room.roomId, `Check out example.org`);
|
||||
const linkElement = page.locator(".mx_EventTile_last").getByRole("link", { name: "example.org" });
|
||||
await expect(linkElement).not.toBeVisible();
|
||||
});
|
||||
test("should linkify a User ID", async ({ page, user, app, room }) => {
|
||||
await page.goto(`#/room/${room.roomId}`);
|
||||
// Needs to be unformatted so we test linkifing
|
||||
|
||||
@@ -14,13 +14,7 @@ test.describe("Topic links", () => {
|
||||
await use({ roomId });
|
||||
},
|
||||
});
|
||||
for (const link of [
|
||||
"https://example.org",
|
||||
"example.org",
|
||||
"ftp://example.org",
|
||||
"#aroom:example.org",
|
||||
"@alice:example.org",
|
||||
]) {
|
||||
for (const link of ["https://example.org", "ftp://example.org", "#aroom:example.org", "@alice:example.org"]) {
|
||||
// Playwright treats '@' as a tag, so replace it to be safe
|
||||
test(`should linkify plaintext '${link.replace("@", "_@")}'`, async ({ page, user, app, room }) => {
|
||||
await app.client.sendStateEvent(
|
||||
|
||||
@@ -126,7 +126,7 @@ test.describe("Login", () => {
|
||||
await page.goto("/");
|
||||
|
||||
// Should give us the welcome page initially
|
||||
await expect(page.getByRole("heading", { name: "Welcome to Element!" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Be in your element" })).toBeVisible();
|
||||
|
||||
// Start the login process
|
||||
await expect(axe).toHaveNoViolations();
|
||||
|
||||
@@ -252,6 +252,7 @@ test.describe("Message url previews", () => {
|
||||
"og:title": "A simple site",
|
||||
"og:description": "And with a brief description",
|
||||
"og:image": mxc,
|
||||
"og:image:alt": "The riot logo",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -148,7 +148,8 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
|
||||
const userId = `alice_${testInfo.testId}`;
|
||||
await registerAccountMas(page, mailpitClient, userId, `${userId}@email.com`, "Pa$sW0rD!");
|
||||
await expect(page.getByText("Welcome")).toBeVisible();
|
||||
// richvdh: This takes several seconds to happen on a dev instance
|
||||
await expect(page.getByText("Welcome")).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Log out
|
||||
await page.getByRole("button", { name: "User menu" }).click();
|
||||
@@ -162,11 +163,14 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
|
||||
// Log in again
|
||||
await page.goto("/#/login");
|
||||
await expect(page.getByText("Sign in")).toBeVisible();
|
||||
await page.getByRole("button", { name: "Continue" }).click();
|
||||
await expect(page.getByText("Continue to Element?")).toBeVisible();
|
||||
await page.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
// We should be being warned that we need to verify (but we can't)
|
||||
await expect(page.getByText("Confirm your digital identity")).toBeVisible();
|
||||
// richvdh: Again, Element takes several seconds to load on a dev instance
|
||||
await expect(page.getByText("Confirm your digital identity")).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// And there should be no way to close this prompt
|
||||
await expect(page.getByRole("button", { name: "Skip verification for now" })).not.toBeVisible();
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import type { JSHandle } from "@playwright/test";
|
||||
import type { MatrixEvent, ISendEventResponse, ReceiptType } from "matrix-js-sdk/src/matrix";
|
||||
import type { MatrixEvent, ISendEventResponse, ReceiptType, RelationType } from "matrix-js-sdk/src/matrix";
|
||||
import { expect } from "../../element-web-test";
|
||||
import { type ElementAppPage } from "../../pages/ElementAppPage";
|
||||
import { type Bot } from "../../pages/bot";
|
||||
@@ -47,7 +47,7 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
||||
getId: () => eventResponse.event_id,
|
||||
threadRootId,
|
||||
getTs: () => 1,
|
||||
isRelation: (relType) => {
|
||||
isRelation: (relType: RelationType) => {
|
||||
return !relType || relType === "m.thread";
|
||||
},
|
||||
} as any as MatrixEvent;
|
||||
|
||||
@@ -48,9 +48,9 @@ test.describe("Room Directory", () => {
|
||||
await app.closeDialog();
|
||||
|
||||
const resp = await bot.publicRooms({});
|
||||
expect(resp.total_room_count_estimate).toEqual(1);
|
||||
expect(resp.chunk).toHaveLength(1);
|
||||
expect(resp.chunk[0].room_id).toEqual(roomId);
|
||||
expect(resp.total_room_count_estimate).toBeGreaterThanOrEqual(1);
|
||||
expect(resp.chunk).toHaveLength(resp.total_room_count_estimate);
|
||||
expect(resp.chunk.find((r) => r.room_id === roomId)).toBeTruthy();
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -57,6 +57,9 @@ test.describe("Create Room", () => {
|
||||
|
||||
await page.getByRole("button", { name: "Go" }).click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Start a chat with this new contact?" })).toBeVisible();
|
||||
await page.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
await expect(page.getByText("Encryption enabled")).toBeVisible();
|
||||
await expect(page.getByText("Send your first message to")).toBeVisible();
|
||||
|
||||
|
||||
@@ -163,6 +163,10 @@ test.describe("Room Status Bar", () => {
|
||||
).toBeVisible();
|
||||
await other.getByRole("option", { name: "Alice" }).click();
|
||||
await other.getByRole("button", { name: "Go" }).click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Start a chat with this new contact?" })).toBeVisible();
|
||||
await page.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
// Send a message to invite the bots
|
||||
const composer = app.getComposerField();
|
||||
await composer.fill("Hello");
|
||||
|
||||
@@ -24,7 +24,7 @@ test.describe("Account user settings tab", () => {
|
||||
},
|
||||
});
|
||||
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ uut, user }) => {
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ uut, user, axe }) => {
|
||||
await expect(uut).toMatchScreenshot("account.png");
|
||||
|
||||
// Assert that the top heading is rendered
|
||||
@@ -70,6 +70,8 @@ test.describe("Account user settings tab", () => {
|
||||
await expect(accountManagementSection.getByRole("button", { name: "Deactivate Account" })).toHaveClass(
|
||||
/mx_AccessibleButton_kind_danger/,
|
||||
);
|
||||
|
||||
await expect(axe).toHaveNoViolations();
|
||||
});
|
||||
|
||||
test("should respond to small screen sizes", { tag: "@screenshot" }, async ({ page, uut }) => {
|
||||
|
||||
+3
-1
@@ -13,7 +13,7 @@ test.describe("Appearance user settings tab", () => {
|
||||
displayName: "Hanako",
|
||||
});
|
||||
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ page, user, app }) => {
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ page, user, app, axe }) => {
|
||||
const tab = await app.settings.openUserSettings("Appearance");
|
||||
|
||||
// Click "Show advanced" link button
|
||||
@@ -23,6 +23,8 @@ test.describe("Appearance user settings tab", () => {
|
||||
await expect(tab.getByRole("button", { name: "Hide advanced" })).toBeVisible();
|
||||
|
||||
await expect(tab).toMatchScreenshot("appearance-tab.png");
|
||||
|
||||
await expect(axe).toHaveNoViolations();
|
||||
});
|
||||
|
||||
test(
|
||||
|
||||
+3
-1
@@ -23,7 +23,7 @@ test.describe("Appearance user settings tab", () => {
|
||||
test(
|
||||
"should be rendered with the light theme selected",
|
||||
{ tag: "@screenshot" },
|
||||
async ({ page, app, util }) => {
|
||||
async ({ page, app, util, axe }) => {
|
||||
// Assert that 'Match system theme' is not checked
|
||||
await expect(util.getMatchSystemThemeSwitch()).not.toBeChecked();
|
||||
|
||||
@@ -34,6 +34,8 @@ test.describe("Appearance user settings tab", () => {
|
||||
await expect(util.getHighContrastTheme()).not.toBeChecked();
|
||||
|
||||
await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-light.png");
|
||||
|
||||
await expect(axe).toHaveNoViolations();
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ test.describe("Device manager", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("should display sessions", async ({ page, app }) => {
|
||||
test("should display sessions", async ({ page, app, axe }) => {
|
||||
await app.settings.openUserSettings("Sessions");
|
||||
const tab = page.locator(".mx_SettingsTab");
|
||||
|
||||
@@ -85,7 +85,7 @@ test.describe("Device manager", () => {
|
||||
// session name updated in details
|
||||
await expect(firstSession.locator(".mx_DeviceDetailHeading h4").getByText(sessionName)).toBeVisible();
|
||||
// and main list item
|
||||
await expect(firstSession.locator(".mx_DeviceTile h4").getByText(sessionName)).toBeVisible();
|
||||
await expect(firstSession.locator(".mx_DeviceTile h3").getByText(sessionName)).toBeVisible();
|
||||
|
||||
// sign out using the device details sign out
|
||||
await firstSession.getByRole("button", { name: "Remove this session" }).click();
|
||||
@@ -96,5 +96,7 @@ test.describe("Device manager", () => {
|
||||
// no other sessions or security recommendations sections when only one session
|
||||
await expect(tab.getByText("Other sessions")).not.toBeVisible();
|
||||
await expect(tab.getByTestId("security-recommendations-section")).not.toBeVisible();
|
||||
|
||||
await expect(axe).toHaveNoViolations();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ test.describe("Advanced section in Encryption tab", () => {
|
||||
await bootstrapCrossSigningForClient(clientHandle, credentials, true);
|
||||
});
|
||||
|
||||
test("should show the encryption details", { tag: "@screenshot" }, async ({ page, app, util }) => {
|
||||
test("should show the encryption details", { tag: "@screenshot" }, async ({ page, app, util, axe }) => {
|
||||
await util.openEncryptionTab();
|
||||
const section = util.getEncryptionDetailsSection();
|
||||
|
||||
@@ -26,6 +26,8 @@ test.describe("Advanced section in Encryption tab", () => {
|
||||
await expect(section).toMatchScreenshot("encryption-details.png", {
|
||||
mask: [section.getByTestId("deviceId"), section.getByTestId("sessionKey")],
|
||||
});
|
||||
|
||||
await expect(axe).toHaveNoViolations();
|
||||
});
|
||||
|
||||
test("should show the import room keys dialog", async ({ page, app, util }) => {
|
||||
|
||||
@@ -33,7 +33,7 @@ test.describe("Other people's devices section in Encryption tab", () => {
|
||||
|
||||
// Create the room and invite bob
|
||||
await createRoom(alicePage, "TestRoom", true);
|
||||
await aliceElementApp.inviteUserToCurrentRoom(bobCredentials.userId);
|
||||
await aliceElementApp.inviteUserToCurrentRoom(bobCredentials.userId, { confirmUnknownUser: true });
|
||||
|
||||
// Bob accepts the invite
|
||||
await bobPage.getByRole("option", { name: "TestRoom" }).click();
|
||||
@@ -72,7 +72,7 @@ test.describe("Other people's devices section in Encryption tab", () => {
|
||||
|
||||
// Create the room and invite bob
|
||||
await createRoom(alicePage, "TestRoom", true);
|
||||
await aliceElementApp.inviteUserToCurrentRoom(bobCredentials.userId);
|
||||
await aliceElementApp.inviteUserToCurrentRoom(bobCredentials.userId, { confirmUnknownUser: true });
|
||||
|
||||
// Bob accepts the invite
|
||||
await bobPage.getByRole("option", { name: "TestRoom" }).click();
|
||||
@@ -115,7 +115,7 @@ test.describe("Other people's devices section in Encryption tab", () => {
|
||||
|
||||
// Create the room and invite bob
|
||||
await createRoom(alicePage, "TestRoom", true);
|
||||
await aliceElementApp.inviteUserToCurrentRoom(bobCredentials.userId);
|
||||
await aliceElementApp.inviteUserToCurrentRoom(bobCredentials.userId, { confirmUnknownUser: true });
|
||||
|
||||
// Bob accepts the invite and dismisses the warnings.
|
||||
await bobPage.getByRole("option", { name: "TestRoom" }).click();
|
||||
@@ -149,7 +149,7 @@ test.describe("Other people's devices section in Encryption tab", () => {
|
||||
|
||||
// Alice creates the room and invite Bob.
|
||||
await createRoom(alicePage, "TestRoom", true);
|
||||
await aliceElementApp.inviteUserToCurrentRoom(bobCredentials.userId);
|
||||
await aliceElementApp.inviteUserToCurrentRoom(bobCredentials.userId, { confirmUnknownUser: true });
|
||||
|
||||
// Bob accepts the invite.
|
||||
await bobPage.getByRole("option", { name: "TestRoom" }).click();
|
||||
@@ -214,7 +214,7 @@ test.describe("Other people's devices section in Encryption tab", () => {
|
||||
|
||||
// Alice creates the room and invite Bob.
|
||||
await createRoom(alicePage, "TestRoom", true);
|
||||
await aliceElementApp.inviteUserToCurrentRoom(bobCredentials.userId);
|
||||
await aliceElementApp.inviteUserToCurrentRoom(bobCredentials.userId, { confirmUnknownUser: true });
|
||||
|
||||
// Bob accepts the invite.
|
||||
await bobPage.getByRole("option", { name: "TestRoom" }).click();
|
||||
|
||||
@@ -20,7 +20,7 @@ test.describe("General room settings tab", () => {
|
||||
await app.viewRoomByName(roomName);
|
||||
});
|
||||
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ page, app }) => {
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ page, app, axe }) => {
|
||||
const settings = await app.settings.openRoomSettings("General");
|
||||
|
||||
// Assert that "Show less" details element is rendered
|
||||
@@ -34,6 +34,9 @@ test.describe("General room settings tab", () => {
|
||||
// Assert that "Show more" details element is rendered instead of "Show more"
|
||||
await expect(settings.getByText("Show less")).not.toBeVisible();
|
||||
await expect(settings.getByText("Show more")).toBeVisible();
|
||||
|
||||
axe.disableRules("color-contrast"); // XXX: We have some known contrast issues here
|
||||
await expect(axe).toHaveNoViolations();
|
||||
});
|
||||
|
||||
test("long address should not cause dialog to overflow", { tag: "@no-webkit" }, async ({ page, app, user }) => {
|
||||
|
||||
@@ -25,7 +25,7 @@ test.describe("Preferences user settings tab", () => {
|
||||
labsFlags: ["feature_new_room_list"],
|
||||
});
|
||||
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user }) => {
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user, axe }) => {
|
||||
await page.setViewportSize({ width: 1024, height: 4000 });
|
||||
const tab = await app.settings.openUserSettings("Preferences");
|
||||
// Assert that the top heading is rendered
|
||||
@@ -39,6 +39,8 @@ test.describe("Preferences user settings tab", () => {
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
await expect(axe).toHaveNoViolations();
|
||||
});
|
||||
|
||||
test("should be able to change the app language", { tag: ["@no-firefox", "@no-webkit"] }, async ({ uut, user }) => {
|
||||
|
||||
@@ -8,11 +8,13 @@ Please see LICENSE files in the repository root for full details.
|
||||
import { test, expect } from "../../element-web-test";
|
||||
|
||||
test.describe("Quick settings menu", () => {
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user }) => {
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user, axe }) => {
|
||||
await page.getByRole("button", { name: "Quick settings" }).click();
|
||||
// Assert that the top heading is renderedc
|
||||
const settings = page.getByTestId("quick-settings-menu");
|
||||
await expect(settings).toBeVisible();
|
||||
await expect(settings).toMatchScreenshot("quick-settings.png");
|
||||
|
||||
await expect(axe).toHaveNoViolations();
|
||||
});
|
||||
});
|
||||
|
||||
+3
-1
@@ -25,7 +25,7 @@ test.describe("Roles & Permissions room settings tab", () => {
|
||||
settings = await app.settings.openRoomSettings("Roles & Permissions");
|
||||
});
|
||||
|
||||
test("should be able to change the role of a user", async ({ page, app, user }) => {
|
||||
test("should be able to change the role of a user", async ({ page, app, user, axe }) => {
|
||||
const privilegedUserSection = settings.locator(".mx_SettingsFieldset").first();
|
||||
const applyButton = privilegedUserSection.getByRole("button", { name: "Apply" });
|
||||
|
||||
@@ -55,5 +55,7 @@ test.describe("Roles & Permissions room settings tab", () => {
|
||||
settings = await app.settings.openRoomSettings("Roles & Permissions");
|
||||
combobox = privilegedUserSection.getByRole("combobox", { name: user.userId });
|
||||
await expect(combobox).toHaveValue("50");
|
||||
|
||||
await expect(axe).toHaveNoViolations();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -32,12 +32,14 @@ test.describe("Security user settings tab", () => {
|
||||
});
|
||||
|
||||
test.describe("AnalyticsLearnMoreDialog", () => {
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user }) => {
|
||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user, axe }) => {
|
||||
const tab = await app.settings.openUserSettings("Security");
|
||||
await tab.getByRole("button", { name: "Learn more" }).click();
|
||||
await expect(page.locator(".mx_AnalyticsLearnMoreDialog_wrapper .mx_Dialog")).toMatchScreenshot(
|
||||
"Security-user-settings-tab-with-posthog-enable-b5d89-csLearnMoreDialog-should-be-rendered-properly-1.png",
|
||||
);
|
||||
|
||||
await expect(axe).toHaveNoViolations();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import type { Locator, Page } from "@playwright/test";
|
||||
import type { ISendEventResponse, EventType, MsgType } from "matrix-js-sdk/src/matrix";
|
||||
import type { ISendEventResponse, EventType, MsgType, IContent } from "matrix-js-sdk/src/matrix";
|
||||
import { test, expect } from "../../element-web-test";
|
||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||
import { Layout } from "../../../src/settings/enums/Layout";
|
||||
@@ -50,11 +50,9 @@ const expectAvatar = async (cli: Client, e: Locator, avatarUrl: string): Promise
|
||||
};
|
||||
|
||||
const sendEvent = async (client: Client, roomId: string, html = false): Promise<ISendEventResponse> => {
|
||||
const content = {
|
||||
const content: IContent = {
|
||||
msgtype: "m.text" as MsgType,
|
||||
body: "Message",
|
||||
format: undefined,
|
||||
formatted_body: undefined,
|
||||
};
|
||||
if (html) {
|
||||
content.format = "org.matrix.custom.html";
|
||||
|
||||
@@ -42,7 +42,7 @@ export async function waitForRoom(
|
||||
return new Promise<Room>((resolve) => {
|
||||
const room = matrixClient.getRoom(roomId);
|
||||
|
||||
if (window[predicateId](room)) {
|
||||
if ((<any>window)[predicateId](room)) {
|
||||
resolve(room);
|
||||
return;
|
||||
}
|
||||
@@ -50,7 +50,7 @@ export async function waitForRoom(
|
||||
function onEvent(ev: MatrixEvent) {
|
||||
if (ev.getRoomId() !== roomId) return;
|
||||
|
||||
if (window[predicateId](room)) {
|
||||
if ((<any>window)[predicateId](room)) {
|
||||
matrixClient.removeListener("event" as ClientEvent, onEvent);
|
||||
resolve(room);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import type { IConfigOptions } from "../src/IConfigOptions";
|
||||
import { type Credentials } from "./plugins/homeserver";
|
||||
import { ElementAppPage } from "./pages/ElementAppPage";
|
||||
import { Crypto } from "./pages/crypto";
|
||||
import { Toasts } from "./pages/toasts";
|
||||
import { Bot, type CreateBotOpts } from "./pages/bot";
|
||||
import { Webserver } from "./plugins/webserver";
|
||||
import { type WorkerOptions, type Services, test as base } from "./services";
|
||||
@@ -52,7 +51,6 @@ export interface TestFixtures extends BaseTestFixtures {
|
||||
|
||||
crypto: Crypto;
|
||||
room?: { roomId: string };
|
||||
toasts: Toasts;
|
||||
uut?: Locator; // Unit Under Test, useful place to refer a prepared locator
|
||||
botCreateOpts: CreateBotOpts;
|
||||
bot: Bot;
|
||||
@@ -92,9 +90,6 @@ export const test = base.extend<TestFixtures>({
|
||||
crypto: async ({ page, homeserver, request }, use) => {
|
||||
await use(new Crypto(page, homeserver, request));
|
||||
},
|
||||
toasts: async ({ page }, use) => {
|
||||
await use(new Toasts(page));
|
||||
},
|
||||
|
||||
botCreateOpts: {},
|
||||
bot: async ({ page, homeserver, botCreateOpts, user }, use, testInfo) => {
|
||||
@@ -112,7 +107,7 @@ export const test = base.extend<TestFixtures>({
|
||||
},
|
||||
});
|
||||
|
||||
interface ExtendedToMatchScreenshotOptions extends ToMatchScreenshotOptions {
|
||||
export interface ExtendedToMatchScreenshotOptions extends ToMatchScreenshotOptions {
|
||||
includeDialogBackground?: boolean;
|
||||
showTooltips?: boolean;
|
||||
timeout?: number;
|
||||
|
||||
@@ -233,15 +233,30 @@ export class ElementAppPage {
|
||||
* Open the room info panel, and use it to send an invite to the given user.
|
||||
*
|
||||
* @param userId - The user to invite to the room.
|
||||
* @param options - Options object
|
||||
*/
|
||||
public async inviteUserToCurrentRoom(userId: string): Promise<void> {
|
||||
public async inviteUserToCurrentRoom(
|
||||
userId: string,
|
||||
options?: {
|
||||
/** If true, expect and acknowledge "Confirm inviting new users" page */
|
||||
confirmUnknownUser?: boolean;
|
||||
},
|
||||
): Promise<void> {
|
||||
const rightPanel = await this.openRoomInfoPanel();
|
||||
await rightPanel.getByRole("menuitem", { name: "Invite" }).click();
|
||||
|
||||
const input = this.page.getByRole("dialog").getByTestId("invite-dialog-input");
|
||||
const dialogLocator = this.page.getByRole("dialog");
|
||||
const input = dialogLocator.getByTestId("invite-dialog-input");
|
||||
await input.fill(userId);
|
||||
await input.press("Enter");
|
||||
await this.page.getByRole("dialog").getByRole("button", { name: "Invite" }).click();
|
||||
await dialogLocator.getByRole("button", { name: "Invite" }).click();
|
||||
|
||||
if (options?.confirmUnknownUser) {
|
||||
await expect(
|
||||
dialogLocator.getByRole("heading", { name: "Invite new contacts to this room?" }),
|
||||
).toBeVisible();
|
||||
await dialogLocator.getByRole("button", { name: "Invite" }).click();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type JSHandle, type Page } from "@playwright/test";
|
||||
import { type PageFunctionOn } from "playwright-core/types/structs";
|
||||
import { type ElementHandle } from "playwright-core";
|
||||
|
||||
import { Network } from "./network";
|
||||
import type {
|
||||
@@ -30,6 +30,34 @@ import type {
|
||||
import type { RoomMessageEventContent } from "matrix-js-sdk/src/types";
|
||||
import { type CredentialsOptionalAccessToken } from "./bot";
|
||||
|
||||
/** Types cribbed from playwright-core/types/structs as they are not importable */
|
||||
export type NoHandles<Arg> = Arg extends JSHandle
|
||||
? never
|
||||
: Arg extends object
|
||||
? { [Key in keyof Arg]: NoHandles<Arg[Key]> }
|
||||
: Arg;
|
||||
export type Unboxed<Arg> =
|
||||
Arg extends ElementHandle<infer T>
|
||||
? T
|
||||
: Arg extends JSHandle<infer T>
|
||||
? T
|
||||
: Arg extends NoHandles<Arg>
|
||||
? Arg
|
||||
: Arg extends [infer A0]
|
||||
? [Unboxed<A0>]
|
||||
: Arg extends [infer A0, infer A1]
|
||||
? [Unboxed<A0>, Unboxed<A1>]
|
||||
: Arg extends [infer A0, infer A1, infer A2]
|
||||
? [Unboxed<A0>, Unboxed<A1>, Unboxed<A2>]
|
||||
: Arg extends [infer A0, infer A1, infer A2, infer A3]
|
||||
? [Unboxed<A0>, Unboxed<A1>, Unboxed<A2>, Unboxed<A3>]
|
||||
: Arg extends Array<infer T>
|
||||
? Array<Unboxed<T>>
|
||||
: Arg extends object
|
||||
? { [Key in keyof Arg]: Unboxed<Arg[Key]> }
|
||||
: Arg;
|
||||
export type PageFunctionOn<On, Arg2, R> = string | ((on: On, arg2: Unboxed<Arg2>) => R | Promise<R>);
|
||||
|
||||
export class Client {
|
||||
public network: Network;
|
||||
protected client: JSHandle<MatrixClient>;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user