Compare commits
134 Commits
hs/add-rep
...
hs/user-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f16ac7589 | ||
|
|
67e0ecc454 | ||
|
|
427cddb8e5 | ||
|
|
df9dfaf16f | ||
|
|
9b5410bad5 | ||
|
|
afab82068d | ||
|
|
e8c88918cb | ||
|
|
c842b615db | ||
|
|
ef3a6a9429 | ||
|
|
5c8c39424a | ||
|
|
4735412c91 | ||
|
|
4b6e5d380e | ||
|
|
1f825f11de | ||
|
|
646162db4e | ||
|
|
2c6f349ce7 | ||
|
|
fffe7a31be | ||
|
|
002e4f6655 | ||
|
|
260042b388 | ||
|
|
6e78d739ac | ||
|
|
78bf5644a0 | ||
|
|
96cc35a68c | ||
|
|
0f93481266 | ||
|
|
433eb23d88 | ||
|
|
3df8293085 | ||
|
|
76674e43b3 | ||
|
|
0bc6fa9f6e | ||
|
|
fd6e8054a7 | ||
|
|
de4f72fac0 | ||
|
|
29f6cc03bd | ||
|
|
8f91f8fac5 | ||
|
|
8d3ea2b71b | ||
|
|
aa5bdab3ba | ||
|
|
08ec6166c7 | ||
|
|
362c7d2aac | ||
|
|
0c498a66b1 | ||
|
|
64dfbc5aa5 | ||
|
|
12dbe719d7 | ||
|
|
ee6ce8ac1d | ||
|
|
31506ef864 | ||
|
|
713f524948 | ||
|
|
e880a866ed | ||
|
|
789dba7b3d | ||
|
|
76be5ccc9e | ||
|
|
3b675b83f1 | ||
|
|
8bd98aa3fd | ||
|
|
b897006899 | ||
|
|
01c4ba8893 | ||
|
|
001ed616f6 | ||
|
|
2395cb1402 | ||
|
|
7951e48291 | ||
|
|
664f79306a | ||
|
|
29e895095f | ||
|
|
0d3a81ee8f | ||
|
|
26e24624d9 | ||
|
|
e94d690587 | ||
|
|
4abdb74673 | ||
|
|
59531ea512 | ||
|
|
4da27eb199 | ||
|
|
d2e4631a14 | ||
|
|
6ff71480d8 | ||
|
|
700068a558 | ||
|
|
d5a9b3f4c0 | ||
|
|
bbb179b6d3 | ||
|
|
93095f99db | ||
|
|
4d3fde192d | ||
|
|
bcf755d45f | ||
|
|
adfa43dcbb | ||
|
|
96dbddcb14 | ||
|
|
227c8ff1cd | ||
|
|
619e11a749 | ||
|
|
bdfdf5fc49 | ||
|
|
cc094f4b56 | ||
|
|
2d0facd47b | ||
|
|
c53b17d291 | ||
|
|
8086262e04 | ||
|
|
f9a0a626a6 | ||
|
|
d7f54355ac | ||
|
|
a668216e20 | ||
|
|
1cadf1a82e | ||
|
|
ee37734cfc | ||
|
|
15f1291cbc | ||
|
|
8a550cf3f6 | ||
|
|
9c911d5c59 | ||
|
|
6fca4d106e | ||
|
|
24f923feac | ||
|
|
9be2b973d0 | ||
|
|
d837d2f62d | ||
|
|
f2379878cd | ||
|
|
261d073f6d | ||
|
|
401fc63eb0 | ||
|
|
51c4506431 | ||
|
|
1de27b265b | ||
|
|
db9514760d | ||
|
|
4b8f404bb3 | ||
|
|
e10b1f9222 | ||
|
|
ff87df4825 | ||
|
|
c1a163cbc9 | ||
|
|
9590e59fd2 | ||
|
|
1e6f9dd096 | ||
|
|
745c12f10d | ||
|
|
6a8493c6eb | ||
|
|
12927cc4a7 | ||
|
|
814f4a85df | ||
|
|
475504d33b | ||
|
|
7faee3d1b7 | ||
|
|
30e7567064 | ||
|
|
2250f5e6a2 | ||
|
|
e43b696461 | ||
|
|
bf98ede4fa | ||
|
|
cc0ece9837 | ||
|
|
ab6ef2fa85 | ||
|
|
c79c8c836b | ||
|
|
3f0dcaa64c | ||
|
|
652e891663 | ||
|
|
7eb5a29cf0 | ||
|
|
1b38624fd8 | ||
|
|
d98533025a | ||
|
|
c3e5367e45 | ||
|
|
1e15a322a5 | ||
|
|
452996eacf | ||
|
|
ee120f2fa9 | ||
|
|
94aa51dc57 | ||
|
|
e19d3dcd44 | ||
|
|
5a4b5418cc | ||
|
|
d1f62317ba | ||
|
|
9232a220dc | ||
|
|
45a2fd9d63 | ||
|
|
7e40e3697f | ||
|
|
beaabd5b44 | ||
|
|
db5c69e228 | ||
|
|
a23a2c03d3 | ||
|
|
c2c040dd42 | ||
|
|
acb3d31a07 | ||
|
|
9136332f42 |
3
.github/CODEOWNERS
vendored
@@ -20,6 +20,7 @@
|
|||||||
# Ignore translations as those will be updated by GHA for Localazy download
|
# Ignore translations as those will be updated by GHA for Localazy download
|
||||||
/src/i18n/strings
|
/src/i18n/strings
|
||||||
/src/i18n/strings/en_EN.json @element-hq/element-web-reviewers
|
/src/i18n/strings/en_EN.json @element-hq/element-web-reviewers
|
||||||
# Ignore the synapse plugin as this is updated by GHA for docker image updating
|
# Ignore the synapse & mas plugins as this is updated by GHA for docker image updating
|
||||||
/playwright/testcontainers/synapse.ts
|
/playwright/testcontainers/synapse.ts
|
||||||
|
/playwright/testcontainers/mas.ts
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/build.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
|
||||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
.github/workflows/build_debian.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
R2_URL: ${{ vars.CF_R2_S3_API }}
|
R2_URL: ${{ vars.CF_R2_S3_API }}
|
||||||
VERSION: ${{ github.ref_name }}
|
VERSION: ${{ github.ref_name }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
|
||||||
- name: Download package
|
- name: Download package
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
2
.github/workflows/build_develop.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
|||||||
R2_URL: ${{ vars.CF_R2_S3_API }}
|
R2_URL: ${{ vars.CF_R2_S3_API }}
|
||||||
R2_PUBLIC_URL: "https://element-web-develop.element.io"
|
R2_PUBLIC_URL: "https://element-web-develop.element.io"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
|
||||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
.github/workflows/deploy.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
SITE: ${{ inputs.site || 'staging.element.io' }}
|
SITE: ${{ inputs.site || 'staging.element.io' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
|
||||||
- name: Load GPG key
|
- name: Load GPG key
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
8
.github/workflows/docker.yaml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
TEST_TAG: vectorim/element-web:test
|
TEST_TAG: vectorim/element-web:test
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # needed for docker-package to be able to calculate the version
|
fetch-depth: 0 # needed for docker-package to be able to calculate the version
|
||||||
|
|
||||||
@@ -37,14 +37,14 @@ jobs:
|
|||||||
install: true
|
install: true
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
@@ -96,7 +96,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
|
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
|
|||||||
6
.github/workflows/docs.yml
vendored
@@ -17,18 +17,18 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Fetch element-desktop
|
- name: Fetch element-desktop
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
repository: element-hq/element-desktop
|
repository: element-hq/element-desktop
|
||||||
path: element-desktop
|
path: element-desktop
|
||||||
|
|
||||||
- name: Fetch element-web
|
- name: Fetch element-web
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
path: element-web
|
path: element-web
|
||||||
|
|
||||||
- name: Fetch matrix-js-sdk
|
- name: Fetch matrix-js-sdk
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
repository: matrix-org/matrix-js-sdk
|
repository: matrix-org/matrix-js-sdk
|
||||||
path: matrix-js-sdk
|
path: matrix-js-sdk
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
actions: read
|
actions: read
|
||||||
steps:
|
steps:
|
||||||
- name: Download HTML report
|
- name: Download HTML report
|
||||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run-id: ${{ github.event.workflow_run.id }}
|
run-id: ${{ github.event.workflow_run.id }}
|
||||||
|
|||||||
12
.github/workflows/end-to-end-tests.yaml
vendored
@@ -50,7 +50,7 @@ jobs:
|
|||||||
runners-matrix: ${{ steps.runner-vars.outputs.matrix }}
|
runners-matrix: ${{ steps.runner-vars.outputs.matrix }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
repository: element-hq/element-web
|
repository: element-hq/element-web
|
||||||
|
|
||||||
@@ -129,13 +129,13 @@ jobs:
|
|||||||
- runAllTests: false
|
- runAllTests: false
|
||||||
project: Pinecone
|
project: Pinecone
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
repository: element-hq/element-web
|
repository: element-hq/element-web
|
||||||
|
|
||||||
- name: 📥 Download artifact
|
- name: 📥 Download artifact
|
||||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
|
||||||
with:
|
with:
|
||||||
name: webapp
|
name: webapp
|
||||||
path: webapp
|
path: webapp
|
||||||
@@ -154,7 +154,7 @@ jobs:
|
|||||||
run: echo "version=$(yarn list --pattern @playwright/test --depth=0 --json --non-interactive --no-progress | jq -r '.data.trees[].name')" >> $GITHUB_OUTPUT
|
run: echo "version=$(yarn list --pattern @playwright/test --depth=0 --json --non-interactive --no-progress | jq -r '.data.trees[].name')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Cache playwright binaries
|
- name: Cache playwright binaries
|
||||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
|
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4
|
||||||
id: playwright-cache
|
id: playwright-cache
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/ms-playwright
|
path: ~/.cache/ms-playwright
|
||||||
@@ -201,7 +201,7 @@ jobs:
|
|||||||
if: always()
|
if: always()
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
if: inputs.skip != true
|
if: inputs.skip != true
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
@@ -219,7 +219,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Download blob reports from GitHub Actions Artifacts
|
- name: Download blob reports from GitHub Actions Artifacts
|
||||||
if: inputs.skip != true
|
if: inputs.skip != true
|
||||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
|
||||||
with:
|
with:
|
||||||
pattern: all-blob-reports-*
|
pattern: all-blob-reports-*
|
||||||
path: all-blob-reports
|
path: all-blob-reports
|
||||||
|
|||||||
2
.github/workflows/netlify.yaml
vendored
@@ -28,7 +28,7 @@ jobs:
|
|||||||
Exercise caution. Use test accounts.
|
Exercise caution. Use test accounts.
|
||||||
|
|
||||||
- name: 📥 Download artifact
|
- name: 📥 Download artifact
|
||||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run-id: ${{ github.event.workflow_run.id }}
|
run-id: ${{ github.event.workflow_run.id }}
|
||||||
|
|||||||
11
.github/workflows/playwright-image-updates.yaml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
|
||||||
- name: Update synapse image
|
- name: Update synapse image
|
||||||
run: |
|
run: |
|
||||||
@@ -21,6 +21,15 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
IMAGE: ghcr.io/element-hq/synapse:develop
|
IMAGE: ghcr.io/element-hq/synapse:develop
|
||||||
|
|
||||||
|
- name: Update MAS image
|
||||||
|
run: |
|
||||||
|
docker pull "$IMAGE"
|
||||||
|
INSPECT=$(docker inspect --format='{{index .RepoDigests 0}}' "$IMAGE")
|
||||||
|
DIGEST=${INSPECT#*@}
|
||||||
|
sed -i "s/const TAG.*/const TAG = \"main@$DIGEST\";/" playwright/testcontainers/mas.ts
|
||||||
|
env:
|
||||||
|
IMAGE: ghcr.io/element-hq/matrix-authentication-service:main
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
id: cpr
|
id: cpr
|
||||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7
|
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7
|
||||||
|
|||||||
6
.github/workflows/release_prepare.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
|||||||
REPOS: matrix-js-sdk element-web element-desktop
|
REPOS: matrix-js-sdk element-web element-desktop
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Element Desktop
|
- name: Checkout Element Desktop
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
if: inputs.element-desktop
|
if: inputs.element-desktop
|
||||||
with:
|
with:
|
||||||
repository: element-hq/element-desktop
|
repository: element-hq/element-desktop
|
||||||
@@ -51,7 +51,7 @@ jobs:
|
|||||||
fetch-tags: true
|
fetch-tags: true
|
||||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
- name: Checkout Element Web
|
- name: Checkout Element Web
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
if: inputs.element-web
|
if: inputs.element-web
|
||||||
with:
|
with:
|
||||||
repository: element-hq/element-web
|
repository: element-hq/element-web
|
||||||
@@ -61,7 +61,7 @@ jobs:
|
|||||||
fetch-tags: true
|
fetch-tags: true
|
||||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
- name: Checkout Matrix JS SDK
|
- name: Checkout Matrix JS SDK
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
if: inputs.matrix-js-sdk
|
if: inputs.matrix-js-sdk
|
||||||
with:
|
with:
|
||||||
repository: matrix-org/matrix-js-sdk
|
repository: matrix-org/matrix-js-sdk
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ jobs:
|
|||||||
run: "sudo apt-get install -y tree"
|
run: "sudo apt-get install -y tree"
|
||||||
|
|
||||||
- name: Download Diffs
|
- name: Download Diffs
|
||||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run-id: ${{ github.event.workflow_run.id }}
|
run-id: ${{ github.event.workflow_run.id }}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
issues: read
|
issues: read
|
||||||
pull-requests: read
|
pull-requests: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
repository: element-hq/element-web
|
repository: element-hq/element-web
|
||||||
@@ -39,7 +39,7 @@ jobs:
|
|||||||
run: echo "version=$(yarn list --pattern @playwright/test --depth=0 --json --non-interactive --no-progress | jq -r '.data.trees[].name')" >> $GITHUB_OUTPUT
|
run: echo "version=$(yarn list --pattern @playwright/test --depth=0 --json --non-interactive --no-progress | jq -r '.data.trees[].name')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Cache playwright binaries
|
- name: Cache playwright binaries
|
||||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
|
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4
|
||||||
id: playwright-cache
|
id: playwright-cache
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/ms-playwright
|
path: ~/.cache/ms-playwright
|
||||||
|
|||||||
13
.github/workflows/static_analysis.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
name: "Typescript Syntax Check"
|
name: "Typescript Syntax Check"
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
|
||||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
@@ -52,12 +52,13 @@ jobs:
|
|||||||
error|misconfigured
|
error|misconfigured
|
||||||
welcome_to_element
|
welcome_to_element
|
||||||
devtools|settings|elementCallUrl
|
devtools|settings|elementCallUrl
|
||||||
|
labs|sliding_sync_description
|
||||||
|
|
||||||
rethemendex_lint:
|
rethemendex_lint:
|
||||||
name: "Rethemendex Check"
|
name: "Rethemendex Check"
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
|
||||||
- run: ./res/css/rethemendex.sh
|
- run: ./res/css/rethemendex.sh
|
||||||
|
|
||||||
@@ -67,7 +68,7 @@ jobs:
|
|||||||
name: "ESLint"
|
name: "ESLint"
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
|
||||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
@@ -85,7 +86,7 @@ jobs:
|
|||||||
name: "Style Lint"
|
name: "Style Lint"
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
|
||||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
@@ -103,7 +104,7 @@ jobs:
|
|||||||
name: "Workflow Lint"
|
name: "Workflow Lint"
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
|
||||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
@@ -121,7 +122,7 @@ jobs:
|
|||||||
name: "Analyse Dead Code"
|
name: "Analyse Dead Code"
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
|
||||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
4
.github/workflows/tests.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
|||||||
runner: [1, 2]
|
runner: [1, 2]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
repository: ${{ inputs.matrix-js-sdk-sha && 'element-hq/element-web' || github.repository }}
|
repository: ${{ inputs.matrix-js-sdk-sha && 'element-hq/element-web' || github.repository }}
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ jobs:
|
|||||||
JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }}
|
JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }}
|
||||||
|
|
||||||
- name: Jest Cache
|
- name: Jest Cache
|
||||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
|
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4
|
||||||
with:
|
with:
|
||||||
path: /tmp/jest_cache
|
path: /tmp/jest_cache
|
||||||
key: ${{ hashFiles('**/yarn.lock') }}
|
key: ${{ hashFiles('**/yarn.lock') }}
|
||||||
|
|||||||
2
.github/workflows/triage-assigned.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
contains(github.event.issue.assignees.*.login, 'dbkr') ||
|
contains(github.event.issue.assignees.*.login, 'dbkr') ||
|
||||||
contains(github.event.issue.assignees.*.login, 'MidhunSureshR')
|
contains(github.event.issue.assignees.*.login, 'MidhunSureshR')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/add-to-project@main
|
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||||
with:
|
with:
|
||||||
project-url: https://github.com/orgs/element-hq/projects/67
|
project-url: https://github.com/orgs/element-hq/projects/67
|
||||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
|
|||||||
2
.github/workflows/triage-incoming.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
automate-project-columns:
|
automate-project-columns:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/add-to-project@main
|
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||||
with:
|
with:
|
||||||
project-url: https://github.com/orgs/element-hq/projects/120
|
project-url: https://github.com/orgs/element-hq/projects/120
|
||||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
|
|||||||
12
.github/workflows/triage-labelled.yml
vendored
@@ -112,7 +112,7 @@ jobs:
|
|||||||
contains(github.event.issue.labels.*.name, 'O-Frequent') ||
|
contains(github.event.issue.labels.*.name, 'O-Frequent') ||
|
||||||
contains(github.event.issue.labels.*.name, 'A11y'))
|
contains(github.event.issue.labels.*.name, 'A11y'))
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/add-to-project@main
|
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||||
with:
|
with:
|
||||||
project-url: https://github.com/orgs/element-hq/projects/18
|
project-url: https://github.com/orgs/element-hq/projects/18
|
||||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
@@ -123,7 +123,7 @@ jobs:
|
|||||||
if: >
|
if: >
|
||||||
contains(github.event.issue.labels.*.name, 'X-Needs-Product')
|
contains(github.event.issue.labels.*.name, 'X-Needs-Product')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/add-to-project@main
|
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||||
with:
|
with:
|
||||||
project-url: https://github.com/orgs/element-hq/projects/28
|
project-url: https://github.com/orgs/element-hq/projects/28
|
||||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
@@ -134,7 +134,7 @@ jobs:
|
|||||||
if: >
|
if: >
|
||||||
contains(github.event.issue.labels.*.name, 'A-New-Search-Experience')
|
contains(github.event.issue.labels.*.name, 'A-New-Search-Experience')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/add-to-project@main
|
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||||
with:
|
with:
|
||||||
project-url: https://github.com/orgs/element-hq/projects/48
|
project-url: https://github.com/orgs/element-hq/projects/48
|
||||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
@@ -145,7 +145,7 @@ jobs:
|
|||||||
if: >
|
if: >
|
||||||
contains(github.event.issue.labels.*.name, 'Team: VoIP')
|
contains(github.event.issue.labels.*.name, 'Team: VoIP')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/add-to-project@main
|
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||||
with:
|
with:
|
||||||
project-url: https://github.com/orgs/element-hq/projects/41
|
project-url: https://github.com/orgs/element-hq/projects/41
|
||||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
@@ -156,7 +156,7 @@ jobs:
|
|||||||
if: >
|
if: >
|
||||||
contains(github.event.issue.labels.*.name, 'Team: Crypto')
|
contains(github.event.issue.labels.*.name, 'Team: Crypto')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/add-to-project@main
|
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||||
with:
|
with:
|
||||||
project-url: https://github.com/orgs/element-hq/projects/76
|
project-url: https://github.com/orgs/element-hq/projects/76
|
||||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
@@ -172,7 +172,7 @@ jobs:
|
|||||||
contains(github.event.issue.labels.*.name, 'A-Testing') ||
|
contains(github.event.issue.labels.*.name, 'A-Testing') ||
|
||||||
contains(github.event.issue.labels.*.name, 'Z-Flaky-Test')
|
contains(github.event.issue.labels.*.name, 'Z-Flaky-Test')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/add-to-project@main
|
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||||
with:
|
with:
|
||||||
project-url: https://github.com/orgs/element-hq/projects/101
|
project-url: https://github.com/orgs/element-hq/projects/101
|
||||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ jobs:
|
|||||||
name: Move PRs asking for design review to the design board
|
name: Move PRs asking for design review to the design board
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: octokit/graphql-action@v2.x
|
- uses: octokit/graphql-action@8ad880e4d437783ea2ab17010324de1075228110 # v2.3.2
|
||||||
id: find_team_members
|
id: find_team_members
|
||||||
with:
|
with:
|
||||||
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
env:
|
env:
|
||||||
TEAM: "design"
|
TEAM: "design"
|
||||||
- uses: octokit/graphql-action@v2.x
|
- uses: octokit/graphql-action@8ad880e4d437783ea2ab17010324de1075228110 # v2.3.2
|
||||||
id: add_to_project
|
id: add_to_project
|
||||||
if: steps.any_matching_reviewers.outputs.match == 'true'
|
if: steps.any_matching_reviewers.outputs.match == 'true'
|
||||||
with:
|
with:
|
||||||
@@ -76,7 +76,7 @@ jobs:
|
|||||||
name: Move PRs asking for design review to the design board
|
name: Move PRs asking for design review to the design board
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: octokit/graphql-action@v2.x
|
- uses: octokit/graphql-action@8ad880e4d437783ea2ab17010324de1075228110 # v2.3.2
|
||||||
id: find_team_members
|
id: find_team_members
|
||||||
with:
|
with:
|
||||||
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||||
@@ -119,7 +119,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
env:
|
env:
|
||||||
TEAM: "product"
|
TEAM: "product"
|
||||||
- uses: octokit/graphql-action@v2.x
|
- uses: octokit/graphql-action@8ad880e4d437783ea2ab17010324de1075228110 # v2.3.2
|
||||||
id: add_to_project
|
id: add_to_project
|
||||||
if: steps.any_matching_reviewers.outputs.match == 'true'
|
if: steps.any_matching_reviewers.outputs.match == 'true'
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
.github/workflows/update-jitsi.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
update:
|
update:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
|
||||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -70,5 +70,13 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"property-no-deprecated": [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
ignoreProperties: ["-webkit-box-orient", "word-wrap"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"nesting-selector-no-missing-scoping-root": null,
|
||||||
|
"no-invalid-position-declaration": null,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
59
CHANGELOG.md
@@ -1,3 +1,62 @@
|
|||||||
|
Changes in [1.11.109](https://github.com/element-hq/element-web/releases/tag/v1.11.109) (2025-08-11)
|
||||||
|
====================================================================================================
|
||||||
|
This release supports the upcoming v12 ("hydra") Matrix room version and is necessary to view and participate in these rooms.
|
||||||
|
|
||||||
|
## ✨ Features
|
||||||
|
|
||||||
|
* [Backport staging] Allow /upgraderoom command without developer mode enabled ([#30529](https://github.com/element-hq/element-web/pull/30529)). Contributed by @RiotRobot.
|
||||||
|
* [Backport staging] Support for creator/owner power level ([#30526](https://github.com/element-hq/element-web/pull/30526)). Contributed by @RiotRobot.
|
||||||
|
* New room list: change icon and label of menu item for to start a DM ([#30470](https://github.com/element-hq/element-web/pull/30470)). Contributed by @florianduros.
|
||||||
|
* Implement the member list with virtuoso ([#29869](https://github.com/element-hq/element-web/pull/29869)). Contributed by @langleyd.
|
||||||
|
* Add labs option for history sharing on invite ([#30313](https://github.com/element-hq/element-web/pull/30313)). Contributed by @richvdh.
|
||||||
|
* Bump wysiwyg to 2.39.0 adding support for pasting rich text content in the Rich Text Edtior ([#30421](https://github.com/element-hq/element-web/pull/30421)). Contributed by @langleyd.
|
||||||
|
* Support `EventShieldReason.MISMATCHED_SENDER` ([#30403](https://github.com/element-hq/element-web/pull/30403)). Contributed by @richvdh.
|
||||||
|
* Change unencrypted and public pills to blue ([#30399](https://github.com/element-hq/element-web/pull/30399)). Contributed by @florianduros.
|
||||||
|
* Change color of public room icon ([#30390](https://github.com/element-hq/element-web/pull/30390)). Contributed by @florianduros.
|
||||||
|
* Script for updating storybook screenshots ([#30340](https://github.com/element-hq/element-web/pull/30340)). Contributed by @dbkr.
|
||||||
|
* Add toggle to hide empty state in devtools ([#30352](https://github.com/element-hq/element-web/pull/30352)). Contributed by @toger5.
|
||||||
|
|
||||||
|
## 🐛 Bug Fixes
|
||||||
|
|
||||||
|
* [Backport staging] Use userId to filter users in non-federated rooms when showing the InviteDialog ([#30537](https://github.com/element-hq/element-web/pull/30537)). Contributed by @RiotRobot.
|
||||||
|
* [Backport staging] Catch error when encountering invalid m.room.pinned\_events event ([#30536](https://github.com/element-hq/element-web/pull/30536)). Contributed by @RiotRobot.
|
||||||
|
* Update for compatibility with v12 rooms ([#30452](https://github.com/element-hq/element-web/pull/30452)). Contributed by @dbkr.
|
||||||
|
* New room list: fix tooltip on presence ([#30474](https://github.com/element-hq/element-web/pull/30474)). Contributed by @florianduros.
|
||||||
|
* New room list: add tooltip for presence and room status ([#30472](https://github.com/element-hq/element-web/pull/30472)). Contributed by @florianduros.
|
||||||
|
* Fix: Clicking on an item in the member list causes it to scroll to the top rather than show the profile view ([#30455](https://github.com/element-hq/element-web/pull/30455)). Contributed by @langleyd.
|
||||||
|
* Put the 'decrypting' tooltip back ([#30446](https://github.com/element-hq/element-web/pull/30446)). Contributed by @dbkr.
|
||||||
|
* Use server name explicitly for via. ([#30362](https://github.com/element-hq/element-web/pull/30362)). Contributed by @Half-Shot.
|
||||||
|
* fix: replace hardcoded string in poll history dialog ([#30402](https://github.com/element-hq/element-web/pull/30402)). Contributed by @florianduros.
|
||||||
|
* fix: replace hardcoded string on qr code back button ([#30401](https://github.com/element-hq/element-web/pull/30401)). Contributed by @florianduros.
|
||||||
|
* Fix color of icon button with outline ([#30361](https://github.com/element-hq/element-web/pull/30361)). Contributed by @florianduros.
|
||||||
|
|
||||||
|
|
||||||
|
Changes in [1.11.108](https://github.com/element-hq/element-web/releases/tag/v1.11.108) (2025-07-30)
|
||||||
|
====================================================================================================
|
||||||
|
## 🐛 Bug Fixes
|
||||||
|
|
||||||
|
* [Backport staging] Fix downloaded attachments not being decrypted ([#30434](https://github.com/element-hq/element-web/pull/30434)). Contributed by @RiotRobot.
|
||||||
|
|
||||||
|
|
||||||
|
Changes in [1.11.107](https://github.com/element-hq/element-web/releases/tag/v1.11.107) (2025-07-29)
|
||||||
|
====================================================================================================
|
||||||
|
## ✨ Features
|
||||||
|
|
||||||
|
* Message preview should show tooltip with the full message on hover ([#30265](https://github.com/element-hq/element-web/pull/30265)). Contributed by @MidhunSureshR.
|
||||||
|
* Support rendering notification badges on platforms that do their own icon overlays ([#30315](https://github.com/element-hq/element-web/pull/30315)). Contributed by @Half-Shot.
|
||||||
|
* Add SubscriptionViewModel base class ([#30297](https://github.com/element-hq/element-web/pull/30297)). Contributed by @dbkr.
|
||||||
|
* Enhancement: Save image on CTRL+S ([#30330](https://github.com/element-hq/element-web/pull/30330)). Contributed by @ioalexander.
|
||||||
|
* Add quote functionality to MessageContextMenu (#29893) ([#30323](https://github.com/element-hq/element-web/pull/30323)). Contributed by @AlirezaMrtz.
|
||||||
|
* Initial structure for shared component views ([#30216](https://github.com/element-hq/element-web/pull/30216)). Contributed by @dbkr.
|
||||||
|
|
||||||
|
## 🐛 Bug Fixes
|
||||||
|
|
||||||
|
* [Backport staging] Fix e2e shield being invisible in white mode for encrypted room ([#30411](https://github.com/element-hq/element-web/pull/30411)). Contributed by @RiotRobot.
|
||||||
|
* Force ED titlebar color for new room list ([#30332](https://github.com/element-hq/element-web/pull/30332)). Contributed by @florianduros.
|
||||||
|
* Add a background color to left panel for macos titlebar in element desktop ([#30328](https://github.com/element-hq/element-web/pull/30328)). Contributed by @florianduros.
|
||||||
|
* Fix: Prevent page refresh on Enter key in right panel member search ([#30312](https://github.com/element-hq/element-web/pull/30312)). Contributed by @AlirezaMrtz.
|
||||||
|
|
||||||
|
|
||||||
Changes in [1.11.106](https://github.com/element-hq/element-web/releases/tag/v1.11.106) (2025-07-15)
|
Changes in [1.11.106](https://github.com/element-hq/element-web/releases/tag/v1.11.106) (2025-07-15)
|
||||||
====================================================================================================
|
====================================================================================================
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# syntax=docker.io/docker/dockerfile:1.17-labs@sha256:9187104f31e3a002a8a6a3209ea1f937fb7486c093cbbde1e14b0fa0d7e4f1b5
|
# syntax=docker.io/docker/dockerfile:1.17-labs@sha256:9187104f31e3a002a8a6a3209ea1f937fb7486c093cbbde1e14b0fa0d7e4f1b5
|
||||||
|
|
||||||
# Builder
|
# Builder
|
||||||
FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:a80324457a2c8d09c83ff9edf2bdf71f378d3288de920e68a358bd3c484b8c4a AS builder
|
FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:9e34ba52e1f3c31ed9bd4d0bcf784f5909db17cda61c220e29c8d7a8ebfb402e AS builder
|
||||||
|
|
||||||
# Support custom branch of the js-sdk. This also helps us build images of element-web develop.
|
# Support custom branch of the js-sdk. This also helps us build images of element-web develop.
|
||||||
ARG USE_CUSTOM_SDKS=false
|
ARG USE_CUSTOM_SDKS=false
|
||||||
@@ -19,7 +19,7 @@ RUN /src/scripts/docker-package.sh
|
|||||||
RUN cp /src/config.sample.json /src/webapp/config.json
|
RUN cp /src/config.sample.json /src/webapp/config.json
|
||||||
|
|
||||||
# App
|
# App
|
||||||
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:86df552d36eb24c45d3f5becf6423bd056a3fd235d7085fe3d5ea28ba89a8232
|
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:ea6c4b8b568824ea94cd1fabd47e1c4e7c0c04744f344a3793f7e9c8ac3a3636
|
||||||
|
|
||||||
# Need root user to install packages & manipulate the usr directory
|
# Need root user to install packages & manipulate the usr directory
|
||||||
USER root
|
USER root
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ Element has several tiers of support for different environments:
|
|||||||
- Best effort
|
- Best effort
|
||||||
- Definition:
|
- Definition:
|
||||||
- Issues **accepted**, regressions **do not block** the release
|
- Issues **accepted**, regressions **do not block** the release
|
||||||
- The wider Element Products(including Element Call and the Enterprise Server Suite) do still not officially support these browsers.
|
- The wider Element Products (including Element Call and the Enterprise Server Suite) do still not officially support these browsers.
|
||||||
- The element web project and its contributors should keep the client functioning and gracefully degrade where other sibling features (E.g. Element Call) may not function.
|
- The element web project and its contributors should keep the client functioning and gracefully degrade where other sibling features (E.g. Element Call) may not function.
|
||||||
- Last major release of Firefox ESR and Chrome/Edge Extended Stable
|
- Last major release of Firefox ESR and Chrome/Edge Extended Stable
|
||||||
- Community Supported
|
- Community Supported
|
||||||
|
|||||||
@@ -40,8 +40,6 @@ const config: Config = {
|
|||||||
"^!!raw-loader!.*": "jest-raw-loader",
|
"^!!raw-loader!.*": "jest-raw-loader",
|
||||||
"recorderWorkletFactory": "<rootDir>/__mocks__/empty.js",
|
"recorderWorkletFactory": "<rootDir>/__mocks__/empty.js",
|
||||||
"^fetch-mock$": "<rootDir>/node_modules/fetch-mock",
|
"^fetch-mock$": "<rootDir>/node_modules/fetch-mock",
|
||||||
// Requires ESM which is incompatible with our current Jest setup
|
|
||||||
"^@element-hq/element-web-module-api$": "<rootDir>/__mocks__/empty.js",
|
|
||||||
},
|
},
|
||||||
transformIgnorePatterns: ["/node_modules/(?!(mime|matrix-js-sdk)).+$"],
|
transformIgnorePatterns: ["/node_modules/(?!(mime|matrix-js-sdk)).+$"],
|
||||||
collectCoverageFrom: [
|
collectCoverageFrom: [
|
||||||
|
|||||||
43
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "element-web",
|
"name": "element-web",
|
||||||
"version": "1.11.106",
|
"version": "1.11.109",
|
||||||
"description": "Element: the future of secure communication",
|
"description": "Element: the future of secure communication",
|
||||||
"author": "New Vector Ltd.",
|
"author": "New Vector Ltd.",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -73,10 +73,10 @@
|
|||||||
"test:storybook:update": "playwright-screenshots --entrypoint yarn --with-node-modules && playwright-screenshots --entrypoint /work/node_modules/.bin/test-storybook --with-node-modules --url http://host.docker.internal:6007/ --updateSnapshot"
|
"test:storybook:update": "playwright-screenshots --entrypoint yarn --with-node-modules && playwright-screenshots --entrypoint /work/node_modules/.bin/test-storybook --with-node-modules --url http://host.docker.internal:6007/ --updateSnapshot"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"**/pretty-format/react-is": "19.1.0",
|
"**/pretty-format/react-is": "19.1.1",
|
||||||
"@playwright/test": "1.54.1",
|
"@playwright/test": "1.54.2",
|
||||||
"@types/react": "19.1.8",
|
"@types/react": "19.1.10",
|
||||||
"@types/react-dom": "19.1.6",
|
"@types/react-dom": "19.1.7",
|
||||||
"oidc-client-ts": "3.3.0",
|
"oidc-client-ts": "3.3.0",
|
||||||
"jwt-decode": "4.0.0",
|
"jwt-decode": "4.0.0",
|
||||||
"caniuse-lite": "1.0.30001724",
|
"caniuse-lite": "1.0.30001724",
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
"@element-hq/element-web-module-api": "1.3.0",
|
"@element-hq/element-web-module-api": "1.4.1",
|
||||||
"@fontsource/inconsolata": "^5",
|
"@fontsource/inconsolata": "^5",
|
||||||
"@fontsource/inter": "^5",
|
"@fontsource/inter": "^5",
|
||||||
"@formatjs/intl-segmenter": "^11.5.7",
|
"@formatjs/intl-segmenter": "^11.5.7",
|
||||||
@@ -94,12 +94,11 @@
|
|||||||
"@matrix-org/emojibase-bindings": "^1.3.4",
|
"@matrix-org/emojibase-bindings": "^1.3.4",
|
||||||
"@matrix-org/react-sdk-module-api": "^2.4.0",
|
"@matrix-org/react-sdk-module-api": "^2.4.0",
|
||||||
"@matrix-org/spec": "^1.7.0",
|
"@matrix-org/spec": "^1.7.0",
|
||||||
"@sentry/browser": "^9.0.0",
|
"@sentry/browser": "^10.0.0",
|
||||||
"@types/png-chunks-extract": "^1.0.2",
|
"@types/png-chunks-extract": "^1.0.2",
|
||||||
"@types/react-virtualized": "^9.21.30",
|
"@vector-im/compound-design-tokens": "^6.0.0",
|
||||||
"@vector-im/compound-design-tokens": "^5.0.0",
|
|
||||||
"@vector-im/compound-web": "^8.1.2",
|
"@vector-im/compound-web": "^8.1.2",
|
||||||
"@vector-im/matrix-wysiwyg": "2.38.4",
|
"@vector-im/matrix-wysiwyg": "2.39.0",
|
||||||
"@zxcvbn-ts/core": "^3.0.4",
|
"@zxcvbn-ts/core": "^3.0.4",
|
||||||
"@zxcvbn-ts/language-common": "^3.0.4",
|
"@zxcvbn-ts/language-common": "^3.0.4",
|
||||||
"@zxcvbn-ts/language-en": "^3.0.2",
|
"@zxcvbn-ts/language-en": "^3.0.2",
|
||||||
@@ -117,7 +116,7 @@
|
|||||||
"emojibase-regex": "15.3.2",
|
"emojibase-regex": "15.3.2",
|
||||||
"escape-html": "^1.0.3",
|
"escape-html": "^1.0.3",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"filesize": "10.1.6",
|
"filesize": "11.0.2",
|
||||||
"github-markdown-css": "^5.5.1",
|
"github-markdown-css": "^5.5.1",
|
||||||
"glob-to-regexp": "^0.4.1",
|
"glob-to-regexp": "^0.4.1",
|
||||||
"highlight.js": "^11.3.1",
|
"highlight.js": "^11.3.1",
|
||||||
@@ -128,9 +127,9 @@
|
|||||||
"jsrsasign": "^11.0.0",
|
"jsrsasign": "^11.0.0",
|
||||||
"jszip": "^3.7.0",
|
"jszip": "^3.7.0",
|
||||||
"katex": "^0.16.0",
|
"katex": "^0.16.0",
|
||||||
"linkify-react": "4.3.1",
|
"linkify-react": "4.3.2",
|
||||||
"linkify-string": "4.3.1",
|
"linkify-string": "4.3.2",
|
||||||
"linkifyjs": "4.3.1",
|
"linkifyjs": "4.3.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"maplibre-gl": "^5.0.0",
|
"maplibre-gl": "^5.0.0",
|
||||||
"matrix-encrypt-attachment": "^1.0.3",
|
"matrix-encrypt-attachment": "^1.0.3",
|
||||||
@@ -143,7 +142,7 @@
|
|||||||
"opus-recorder": "^8.0.3",
|
"opus-recorder": "^8.0.3",
|
||||||
"pako": "^2.0.3",
|
"pako": "^2.0.3",
|
||||||
"png-chunks-extract": "^1.0.0",
|
"png-chunks-extract": "^1.0.0",
|
||||||
"posthog-js": "1.257.0",
|
"posthog-js": "1.260.1",
|
||||||
"qrcode": "1.5.4",
|
"qrcode": "1.5.4",
|
||||||
"re-resizable": "6.11.2",
|
"re-resizable": "6.11.2",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
@@ -153,7 +152,7 @@
|
|||||||
"react-focus-lock": "^2.5.1",
|
"react-focus-lock": "^2.5.1",
|
||||||
"react-string-replace": "^1.1.1",
|
"react-string-replace": "^1.1.1",
|
||||||
"react-transition-group": "^4.4.1",
|
"react-transition-group": "^4.4.1",
|
||||||
"react-virtualized": "^9.22.5",
|
"react-virtuoso": "^4.14.0",
|
||||||
"rfc4648": "^1.4.0",
|
"rfc4648": "^1.4.0",
|
||||||
"sanitize-filename": "^1.6.3",
|
"sanitize-filename": "^1.6.3",
|
||||||
"sanitize-html": "2.17.0",
|
"sanitize-html": "2.17.0",
|
||||||
@@ -185,8 +184,8 @@
|
|||||||
"@babel/preset-typescript": "^7.12.7",
|
"@babel/preset-typescript": "^7.12.7",
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
"@casualbot/jest-sonar-reporter": "2.2.7",
|
"@casualbot/jest-sonar-reporter": "2.2.7",
|
||||||
"@element-hq/element-call-embedded": "0.13.1",
|
"@element-hq/element-call-embedded": "0.14.1",
|
||||||
"@element-hq/element-web-playwright-common": "^1.4.3",
|
"@element-hq/element-web-playwright-common": "^1.4.6",
|
||||||
"@peculiar/webcrypto": "^1.4.3",
|
"@peculiar/webcrypto": "^1.4.3",
|
||||||
"@playwright/test": "^1.50.1",
|
"@playwright/test": "^1.50.1",
|
||||||
"@principalstudio/html-webpack-inject-preload": "^1.2.7",
|
"@principalstudio/html-webpack-inject-preload": "^1.2.7",
|
||||||
@@ -222,9 +221,9 @@
|
|||||||
"@types/node-fetch": "^2.6.2",
|
"@types/node-fetch": "^2.6.2",
|
||||||
"@types/pako": "^2.0.0",
|
"@types/pako": "^2.0.0",
|
||||||
"@types/qrcode": "^1.3.5",
|
"@types/qrcode": "^1.3.5",
|
||||||
"@types/react": "19.1.8",
|
"@types/react": "19.1.10",
|
||||||
"@types/react-beautiful-dnd": "^13.0.0",
|
"@types/react-beautiful-dnd": "^13.0.0",
|
||||||
"@types/react-dom": "19.1.6",
|
"@types/react-dom": "19.1.7",
|
||||||
"@types/react-transition-group": "^4.4.0",
|
"@types/react-transition-group": "^4.4.0",
|
||||||
"@types/sanitize-html": "2.16.0",
|
"@types/sanitize-html": "2.16.0",
|
||||||
"@types/semver": "^7.5.8",
|
"@types/semver": "^7.5.8",
|
||||||
@@ -299,8 +298,8 @@
|
|||||||
"semver": "^7.5.2",
|
"semver": "^7.5.2",
|
||||||
"source-map-loader": "^5.0.0",
|
"source-map-loader": "^5.0.0",
|
||||||
"storybook": "^9.0.12",
|
"storybook": "^9.0.12",
|
||||||
"stylelint": "^16.13.0",
|
"stylelint": "^16.23.0",
|
||||||
"stylelint-config-standard": "^38.0.0",
|
"stylelint-config-standard": "^39.0.0",
|
||||||
"stylelint-scss": "^6.0.0",
|
"stylelint-scss": "^6.0.0",
|
||||||
"stylelint-value-no-unknown-custom-properties": "^6.0.1",
|
"stylelint-value-no-unknown-custom-properties": "^6.0.1",
|
||||||
"terser-webpack-plugin": "^5.3.9",
|
"terser-webpack-plugin": "^5.3.9",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const clickButtonReply = async (tile: Locator) => {
|
|||||||
await tile.hover();
|
await tile.hover();
|
||||||
await tile.getByRole("button", { name: "Reply", exact: true }).click();
|
await tile.getByRole("button", { name: "Reply", exact: true }).click();
|
||||||
}).toPass();
|
}).toPass();
|
||||||
|
await expect(tile.page().getByText("Replying", { exact: true })).toBeVisible();
|
||||||
};
|
};
|
||||||
|
|
||||||
test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||||
@@ -39,7 +40,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
// wait for the tile to finish loading
|
// wait for the tile to finish loading
|
||||||
await expect(
|
await expect(
|
||||||
page
|
page
|
||||||
.locator(".mx_AudioPlayer_mediaName")
|
.getByTestId("audio-player-name")
|
||||||
.last()
|
.last()
|
||||||
.filter({ hasText: file.split("/").at(-1) }),
|
.filter({ hasText: file.split("/").at(-1) }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
@@ -54,12 +55,10 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
// Check that the audio player is rendered and its button becomes visible
|
// Check that the audio player is rendered and its button becomes visible
|
||||||
const checkPlayerVisibility = async (locator: Locator) => {
|
const checkPlayerVisibility = async (locator: Locator) => {
|
||||||
// Assert that the audio player and media information are visible
|
// Assert that the audio player and media information are visible
|
||||||
const mediaInfo = locator.locator(
|
const mediaInfo = locator.getByRole("region", { name: "Audio player" });
|
||||||
".mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container .mx_AudioPlayer_mediaInfo",
|
await expect(mediaInfo.getByText(".ogg")).toBeVisible(); // extension
|
||||||
);
|
await expect(mediaInfo.getByRole("time")).toHaveText("00:01"); // duration
|
||||||
await expect(mediaInfo.locator(".mx_AudioPlayer_mediaName", { hasText: ".ogg" })).toBeVisible(); // extension
|
await expect(mediaInfo.getByText("(3.56 KB)")).toBeVisible(); // actual size;
|
||||||
await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "00:01" })).toBeVisible();
|
|
||||||
await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "(3.56 KB)" })).toBeVisible(); // actual size
|
|
||||||
|
|
||||||
// Assert that the play button can be found and is visible
|
// Assert that the play button can be found and is visible
|
||||||
await expect(locator.getByRole("button", { name: "Play" })).toBeVisible();
|
await expect(locator.getByRole("button", { name: "Play" })).toBeVisible();
|
||||||
@@ -78,7 +77,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check the status of the seek bar
|
// Check the status of the seek bar
|
||||||
expect(await page.locator(".mx_AudioPlayer_seek input[type='range']").count()).toBeGreaterThan(0);
|
expect(await page.getByRole("region", { name: "Audio player" }).getByRole("slider").count()).toBeGreaterThan(0);
|
||||||
|
|
||||||
// Enable IRC layout
|
// Enable IRC layout
|
||||||
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
||||||
@@ -100,7 +99,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
mask: [page.locator(".mx_AudioPlayer_seek")],
|
mask: [page.getByTestId("audio-player-seek")],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Take a snapshot of mx_EventTile_last on IRC layout
|
// Take a snapshot of mx_EventTile_last on IRC layout
|
||||||
@@ -186,9 +185,9 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
await uploadFile(page, "playwright/sample-files/1sec.ogg");
|
await uploadFile(page, "playwright/sample-files/1sec.ogg");
|
||||||
|
|
||||||
// Assert that the audio player is rendered
|
// Assert that the audio player is rendered
|
||||||
const container = page.locator(".mx_EventTile_last .mx_AudioPlayer_container");
|
const container = page.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" });
|
||||||
// Assert that the counter is zero before clicking the play button
|
// Assert that the counter is zero before clicking the play button
|
||||||
await expect(container.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
|
await expect(container.getByRole("timer")).toHaveText("00:00");
|
||||||
|
|
||||||
// Find and click "Play" button, the wait is to make the test less flaky
|
// Find and click "Play" button, the wait is to make the test less flaky
|
||||||
await expect(container.getByRole("button", { name: "Play" })).toBeVisible();
|
await expect(container.getByRole("button", { name: "Play" })).toBeVisible();
|
||||||
@@ -198,7 +197,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
await expect(container.getByRole("button", { name: "Pause" })).toBeVisible();
|
await expect(container.getByRole("button", { name: "Pause" })).toBeVisible();
|
||||||
|
|
||||||
// Assert that the timer is reset when the audio file finished playing
|
// Assert that the timer is reset when the audio file finished playing
|
||||||
await expect(container.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
|
await expect(container.getByRole("timer")).toHaveText("00:00");
|
||||||
|
|
||||||
// Assert that "Play" button can be found
|
// Assert that "Play" button can be found
|
||||||
await expect(container.getByRole("button", { name: "Play" })).toBeVisible();
|
await expect(container.getByRole("button", { name: "Play" })).toBeVisible();
|
||||||
@@ -226,7 +225,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
await uploadFile(page, "playwright/sample-files/1sec.ogg");
|
await uploadFile(page, "playwright/sample-files/1sec.ogg");
|
||||||
|
|
||||||
// Assert the audio player is rendered
|
// Assert the audio player is rendered
|
||||||
await expect(page.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible();
|
await expect(page.getByRole("region", { name: "Audio player" })).toBeVisible();
|
||||||
|
|
||||||
// Find and click "Reply" button on MessageActionBar
|
// Find and click "Reply" button on MessageActionBar
|
||||||
const tile = page.locator(".mx_EventTile_last");
|
const tile = page.locator(".mx_EventTile_last");
|
||||||
@@ -236,7 +235,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
await uploadFile(page, "playwright/sample-files/1sec.ogg");
|
await uploadFile(page, "playwright/sample-files/1sec.ogg");
|
||||||
|
|
||||||
// Assert that the audio player is rendered
|
// Assert that the audio player is rendered
|
||||||
await expect(tile.locator(".mx_AudioPlayer_container")).toBeVisible();
|
await expect(tile.getByRole("region", { name: "Audio player" })).toBeVisible();
|
||||||
|
|
||||||
// Assert that replied audio file is rendered as file button inside ReplyChain
|
// Assert that replied audio file is rendered as file button inside ReplyChain
|
||||||
const button = tile.locator(".mx_ReplyChain_wrapper .mx_MFileBody_info[role='button']");
|
const button = tile.locator(".mx_ReplyChain_wrapper .mx_MFileBody_info[role='button']");
|
||||||
@@ -261,7 +260,9 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
await uploadFile(page, "playwright/sample-files/upload-first.ogg");
|
await uploadFile(page, "playwright/sample-files/upload-first.ogg");
|
||||||
|
|
||||||
// Assert that the audio player is rendered
|
// Assert that the audio player is rendered
|
||||||
await expect(page.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible();
|
await expect(
|
||||||
|
page.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
await clickButtonReply(tile);
|
await clickButtonReply(tile);
|
||||||
|
|
||||||
@@ -269,7 +270,9 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
await uploadFile(page, "playwright/sample-files/upload-second.ogg");
|
await uploadFile(page, "playwright/sample-files/upload-second.ogg");
|
||||||
|
|
||||||
// Assert that the audio player is rendered
|
// Assert that the audio player is rendered
|
||||||
await expect(page.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible();
|
await expect(
|
||||||
|
page.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
await clickButtonReply(tile);
|
await clickButtonReply(tile);
|
||||||
|
|
||||||
@@ -277,7 +280,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
await uploadFile(page, "playwright/sample-files/upload-third.ogg");
|
await uploadFile(page, "playwright/sample-files/upload-third.ogg");
|
||||||
|
|
||||||
// Assert that the audio player is rendered
|
// Assert that the audio player is rendered
|
||||||
await expect(tile.locator(".mx_AudioPlayer_container")).toBeVisible();
|
await expect(tile.getByRole("region", { name: "Audio player" })).toBeVisible();
|
||||||
|
|
||||||
// Assert that there are two "mx_ReplyChain" elements
|
// Assert that there are two "mx_ReplyChain" elements
|
||||||
await expect(tile.locator(".mx_ReplyChain")).toHaveCount(2);
|
await expect(tile.locator(".mx_ReplyChain")).toHaveCount(2);
|
||||||
@@ -313,7 +316,9 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
// On the main timeline
|
// On the main timeline
|
||||||
const messageList = page.locator(".mx_RoomView_MessageList");
|
const messageList = page.locator(".mx_RoomView_MessageList");
|
||||||
// Assert the audio player is rendered
|
// Assert the audio player is rendered
|
||||||
await expect(messageList.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible();
|
await expect(
|
||||||
|
messageList.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" }),
|
||||||
|
).toBeVisible();
|
||||||
// Find and click "Reply in thread" button
|
// Find and click "Reply in thread" button
|
||||||
await messageList.locator(".mx_EventTile_last").hover();
|
await messageList.locator(".mx_EventTile_last").hover();
|
||||||
await messageList.locator(".mx_EventTile_last").getByRole("button", { name: "Reply in thread" }).click();
|
await messageList.locator(".mx_EventTile_last").getByRole("button", { name: "Reply in thread" }).click();
|
||||||
@@ -321,10 +326,10 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
// On a thread
|
// On a thread
|
||||||
const thread = page.locator(".mx_ThreadView");
|
const thread = page.locator(".mx_ThreadView");
|
||||||
const threadTile = thread.locator(".mx_EventTile_last");
|
const threadTile = thread.locator(".mx_EventTile_last");
|
||||||
const audioPlayer = threadTile.locator(".mx_AudioPlayer_container");
|
const audioPlayer = threadTile.getByRole("region", { name: "Audio player" });
|
||||||
|
|
||||||
// Assert that the counter is zero before clicking the play button
|
// Assert that the counter is zero before clicking the play button
|
||||||
await expect(audioPlayer.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
|
await expect(audioPlayer.getByRole("timer")).toHaveText("00:00");
|
||||||
|
|
||||||
// Find and click "Play" button, the wait is to make the test less flaky
|
// Find and click "Play" button, the wait is to make the test less flaky
|
||||||
await expect(audioPlayer.getByRole("button", { name: "Play" })).toBeVisible();
|
await expect(audioPlayer.getByRole("button", { name: "Play" })).toBeVisible();
|
||||||
@@ -334,7 +339,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
await expect(audioPlayer.getByRole("button", { name: "Pause" })).toBeVisible();
|
await expect(audioPlayer.getByRole("button", { name: "Pause" })).toBeVisible();
|
||||||
|
|
||||||
// Assert that the timer is reset when the audio file finished playing
|
// Assert that the timer is reset when the audio file finished playing
|
||||||
await expect(audioPlayer.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
|
await expect(audioPlayer.getByRole("timer")).toHaveText("00:00");
|
||||||
|
|
||||||
// Assert that "Play" button can be found
|
// Assert that "Play" button can be found
|
||||||
await expect(audioPlayer.getByRole("button", { name: "Play" })).not.toBeDisabled();
|
await expect(audioPlayer.getByRole("button", { name: "Play" })).not.toBeDisabled();
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ test.describe("Composer", () => {
|
|||||||
|
|
||||||
test.describe("CIDER", () => {
|
test.describe("CIDER", () => {
|
||||||
test("sends a message when you click send or press Enter", async ({ page }) => {
|
test("sends a message when you click send or press Enter", async ({ page }) => {
|
||||||
const composer = page.getByRole("textbox", { name: "Send a message…" });
|
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||||
|
|
||||||
// Type a message
|
// Type a message
|
||||||
await composer.pressSequentially("my message 0");
|
await composer.pressSequentially("my message 0");
|
||||||
@@ -52,7 +52,7 @@ test.describe("Composer", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("can write formatted text", async ({ page }) => {
|
test("can write formatted text", async ({ page }) => {
|
||||||
const composer = page.getByRole("textbox", { name: "Send a message…" });
|
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||||
|
|
||||||
await composer.pressSequentially("my bold");
|
await composer.pressSequentially("my bold");
|
||||||
await composer.press(`${CtrlOrMeta}+KeyB`);
|
await composer.press(`${CtrlOrMeta}+KeyB`);
|
||||||
@@ -68,7 +68,7 @@ test.describe("Composer", () => {
|
|||||||
await page.getByTestId("mx_EmojiPicker").locator(".mx_EmojiPicker_item", { hasText: "😇" }).click();
|
await page.getByTestId("mx_EmojiPicker").locator(".mx_EmojiPicker_item", { hasText: "😇" }).click();
|
||||||
|
|
||||||
await page.locator(".mx_ContextualMenu_background").click(); // Close emoji picker
|
await page.locator(".mx_ContextualMenu_background").click(); // Close emoji picker
|
||||||
await page.getByRole("textbox", { name: "Send a message…" }).press("Enter"); // Send message
|
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).press("Enter"); // Send message
|
||||||
|
|
||||||
await expect(page.locator(".mx_EventTile_body", { hasText: "😇" })).toBeVisible();
|
await expect(page.locator(".mx_EventTile_body", { hasText: "😇" })).toBeVisible();
|
||||||
});
|
});
|
||||||
@@ -79,7 +79,7 @@ test.describe("Composer", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("only sends when you press Control+Enter", async ({ page }) => {
|
test("only sends when you press Control+Enter", async ({ page }) => {
|
||||||
const composer = page.getByRole("textbox", { name: "Send a message…" });
|
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||||
// Type a message and press Enter
|
// Type a message and press Enter
|
||||||
await composer.pressSequentially("my message 3");
|
await composer.pressSequentially("my message 3");
|
||||||
await composer.press("Enter");
|
await composer.press("Enter");
|
||||||
|
|||||||
@@ -91,10 +91,10 @@ test.describe("Key backup reset from elsewhere", () => {
|
|||||||
|
|
||||||
await csAPI.deleteBackupVersion(backupInfo.version);
|
await csAPI.deleteBackupVersion(backupInfo.version);
|
||||||
|
|
||||||
await page.getByRole("textbox", { name: "Send an encrypted message…" }).fill("/discardsession");
|
await page.getByRole("textbox", { name: "Send a message…" }).fill("/discardsession");
|
||||||
await page.getByRole("button", { name: "Send message" }).click();
|
await page.getByRole("button", { name: "Send message" }).click();
|
||||||
|
|
||||||
await page.getByRole("textbox", { name: "Send an encrypted message…" }).fill("Message with broken key backup");
|
await page.getByRole("textbox", { name: "Send a message…" }).fill("Message with broken key backup");
|
||||||
await page.getByRole("button", { name: "Send message" }).click();
|
await page.getByRole("button", { name: "Send message" }).click();
|
||||||
|
|
||||||
// Should be the message we sent plus the room creation event
|
// Should be the message we sent plus the room creation event
|
||||||
|
|||||||
@@ -154,11 +154,12 @@ test.describe("Cryptography", function () {
|
|||||||
await app.client.bootstrapCrossSigning(aliceCredentials);
|
await app.client.bootstrapCrossSigning(aliceCredentials);
|
||||||
await startDMWithBob(page, bob);
|
await startDMWithBob(page, bob);
|
||||||
// send first message
|
// send first message
|
||||||
await page.getByRole("textbox", { name: "Send a message…" }).fill("Hey!");
|
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).fill("Hey!");
|
||||||
await page.getByRole("textbox", { name: "Send a message…" }).press("Enter");
|
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).press("Enter");
|
||||||
await checkDMRoom(page);
|
await checkDMRoom(page);
|
||||||
const bobRoomId = await bobJoin(page, bob);
|
const bobRoomId = await bobJoin(page, bob);
|
||||||
await expect(page.locator(".mx_MessageComposer_e2eIcon")).toMatchScreenshot("composer-e2e-icon-normal.png");
|
// We no longer show the grey badge in the composer, check that it is not there.
|
||||||
|
await expect(page.locator(".mx_MessageComposer_e2eIcon")).toHaveCount(0);
|
||||||
|
|
||||||
await testMessages(page, bob, bobRoomId);
|
await testMessages(page, bob, bobRoomId);
|
||||||
await verify(app, bob);
|
await verify(app, bob);
|
||||||
|
|||||||
@@ -124,6 +124,10 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
|
|||||||
const toasts = new Toasts(page);
|
const toasts = new Toasts(page);
|
||||||
await toasts.rejectToast("Notifications");
|
await toasts.rejectToast("Notifications");
|
||||||
await toasts.assertNoToasts();
|
await toasts.assertNoToasts();
|
||||||
|
|
||||||
|
// There may still be a `/sendToDevice/m.secret.request` in flight, which will later throw an error and cause
|
||||||
|
// a *subsequent* test to fail. Tell playwright to ignore any errors resulting from in-flight routes.
|
||||||
|
await page.unrouteAll({ behavior: "ignoreErrors" });
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Verify device with QR code during login", async ({ page, app, credentials, homeserver }) => {
|
test("Verify device with QR code during login", async ({ page, app, credentials, homeserver }) => {
|
||||||
@@ -205,7 +209,7 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
|
|||||||
const dialog = page.locator(".mx_Dialog");
|
const dialog = page.locator(".mx_Dialog");
|
||||||
// We use `pressSequentially` here to make sure that the FocusLock isn't causing us any problems
|
// We use `pressSequentially` here to make sure that the FocusLock isn't causing us any problems
|
||||||
// (cf https://github.com/element-hq/element-web/issues/30089)
|
// (cf https://github.com/element-hq/element-web/issues/30089)
|
||||||
await dialog.locator("textarea").pressSequentially(recoveryKey);
|
await dialog.getByTitle("Recovery key").pressSequentially(recoveryKey);
|
||||||
await dialog.getByRole("button", { name: "Continue", disabled: false }).click();
|
await dialog.getByRole("button", { name: "Continue", disabled: false }).click();
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Done" }).click();
|
await page.getByRole("button", { name: "Done" }).click();
|
||||||
|
|||||||
@@ -58,108 +58,108 @@ test.describe("Cryptography", function () {
|
|||||||
await app.client.network.setupRoute();
|
await app.client.network.setupRoute();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should show the correct shield on e2e events", async ({
|
test(
|
||||||
page,
|
"should show the correct shield on e2e events",
|
||||||
app,
|
{ tag: "@screenshot" },
|
||||||
bot: bob,
|
async ({ page, app, bot: bob, homeserver }, workerInfo) => {
|
||||||
homeserver,
|
// Bob has a second, not cross-signed, device
|
||||||
}, workerInfo) => {
|
const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob);
|
||||||
// Bob has a second, not cross-signed, device
|
|
||||||
const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob);
|
|
||||||
|
|
||||||
// Dismiss the toasts nagging us, otherwise they get in the way of clicking the room list
|
// Dismiss the toasts nagging us, otherwise they get in the way of clicking the room list
|
||||||
await page.getByRole("button", { name: "Dismiss" }).click();
|
await page.getByRole("button", { name: "Dismiss" }).click();
|
||||||
await page.getByRole("button", { name: "Yes, dismiss" }).click();
|
await page.getByRole("button", { name: "Yes, dismiss" }).click();
|
||||||
|
|
||||||
await bob.sendEvent(testRoomId, null, "m.room.encrypted", {
|
await bob.sendEvent(testRoomId, null, "m.room.encrypted", {
|
||||||
algorithm: "m.megolm.v1.aes-sha2",
|
algorithm: "m.megolm.v1.aes-sha2",
|
||||||
ciphertext: "the bird is in the hand",
|
ciphertext: "the bird is in the hand",
|
||||||
});
|
});
|
||||||
|
|
||||||
const last = page.locator(".mx_EventTile_last");
|
const last = page.locator(".mx_EventTile_last");
|
||||||
await expect(last).toContainText("Unable to decrypt message");
|
await expect(last).toContainText("Unable to decrypt message");
|
||||||
const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon");
|
const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon");
|
||||||
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_decryption_failure/);
|
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_decryption_failure/);
|
||||||
await lastE2eIcon.focus();
|
await lastE2eIcon.focus();
|
||||||
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
|
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
|
||||||
"This message could not be decrypted",
|
"This message could not be decrypted",
|
||||||
);
|
);
|
||||||
|
|
||||||
/* Should show a red padlock for an unencrypted message in an e2e room */
|
/* Should show a red padlock for an unencrypted message in an e2e room */
|
||||||
await bob.evaluate(
|
await bob.evaluate(
|
||||||
(cli, testRoomId) =>
|
(cli, testRoomId) =>
|
||||||
cli.http.authedRequest(
|
cli.http.authedRequest(
|
||||||
window.matrixcs.Method.Put,
|
window.matrixcs.Method.Put,
|
||||||
`/rooms/${encodeURIComponent(testRoomId)}/send/m.room.message/test_txn_1`,
|
`/rooms/${encodeURIComponent(testRoomId)}/send/m.room.message/test_txn_1`,
|
||||||
undefined,
|
undefined,
|
||||||
{
|
{
|
||||||
msgtype: "m.text",
|
msgtype: "m.text",
|
||||||
body: "test unencrypted",
|
body: "test unencrypted",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
testRoomId,
|
testRoomId,
|
||||||
);
|
);
|
||||||
|
|
||||||
await expect(last).toContainText("test unencrypted");
|
await expect(last).toContainText("test unencrypted");
|
||||||
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
|
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
|
||||||
await lastE2eIcon.focus();
|
await expect(lastE2eIcon).toMatchScreenshot("event-shield-warning.png");
|
||||||
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText("Not encrypted");
|
await lastE2eIcon.focus();
|
||||||
|
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText("Not encrypted");
|
||||||
|
|
||||||
/* Should show no padlock for an unverified user */
|
/* Should show no padlock for an unverified user */
|
||||||
// bob sends a valid event
|
// bob sends a valid event
|
||||||
await bob.sendMessage(testRoomId, "test encrypted 1");
|
await bob.sendMessage(testRoomId, "test encrypted 1");
|
||||||
|
|
||||||
// the message should appear, decrypted, with no warning, but also no "verified"
|
// the message should appear, decrypted, with no warning, but also no "verified"
|
||||||
const lastTile = page.locator(".mx_EventTile_last");
|
const lastTile = page.locator(".mx_EventTile_last");
|
||||||
const lastTileE2eIcon = lastTile.locator(".mx_EventTile_e2eIcon");
|
const lastTileE2eIcon = lastTile.locator(".mx_EventTile_e2eIcon");
|
||||||
await expect(lastTile).toContainText("test encrypted 1");
|
await expect(lastTile).toContainText("test encrypted 1");
|
||||||
// no e2e icon
|
// no e2e icon
|
||||||
await expect(lastTileE2eIcon).not.toBeVisible();
|
await expect(lastTileE2eIcon).not.toBeVisible();
|
||||||
|
|
||||||
/* Now verify Bob */
|
/* Now verify Bob */
|
||||||
await verify(app, bob);
|
await verify(app, bob);
|
||||||
|
|
||||||
/* Existing message should be updated when user is verified. */
|
/* Existing message should be updated when user is verified. */
|
||||||
await expect(last).toContainText("test encrypted 1");
|
await expect(last).toContainText("test encrypted 1");
|
||||||
// still no e2e icon
|
// still no e2e icon
|
||||||
await expect(last.locator(".mx_EventTile_e2eIcon")).not.toBeVisible();
|
await expect(last.locator(".mx_EventTile_e2eIcon")).not.toBeVisible();
|
||||||
|
|
||||||
/* should show no padlock, and be verified, for a message from a verified device */
|
/* should show no padlock, and be verified, for a message from a verified device */
|
||||||
await bob.sendMessage(testRoomId, "test encrypted 2");
|
await bob.sendMessage(testRoomId, "test encrypted 2");
|
||||||
|
|
||||||
await expect(lastTile).toContainText("test encrypted 2");
|
await expect(lastTile).toContainText("test encrypted 2");
|
||||||
// no e2e icon
|
// no e2e icon
|
||||||
await expect(lastTileE2eIcon).not.toBeVisible();
|
await expect(lastTileE2eIcon).not.toBeVisible();
|
||||||
|
|
||||||
/* should show red padlock for a message from an unverified device */
|
/* should show red padlock for a message from an unverified device */
|
||||||
await bobSecondDevice.sendMessage(testRoomId, "test encrypted from unverified");
|
await bobSecondDevice.sendMessage(testRoomId, "test encrypted from unverified");
|
||||||
await expect(lastTile).toContainText("test encrypted from unverified");
|
await expect(lastTile).toContainText("test encrypted from unverified");
|
||||||
await expect(lastTileE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
|
await expect(lastTileE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
|
||||||
await lastTileE2eIcon.focus();
|
await lastTileE2eIcon.focus();
|
||||||
await expect(await app.getTooltipForElement(lastTileE2eIcon)).toContainText(
|
await expect(await app.getTooltipForElement(lastTileE2eIcon)).toContainText(
|
||||||
"Encrypted by a device not verified by its owner.",
|
"Encrypted by a device not verified by its owner.",
|
||||||
);
|
);
|
||||||
|
|
||||||
/* Should show a red padlock for a message from an unverified device.
|
/* Should show a red padlock for a message from an unverified device.
|
||||||
* Rust crypto remembers the verification state of the sending device, so it will know that the device was
|
* Rust crypto remembers the verification state of the sending device, so it will know that the device was
|
||||||
* unverified, even if it gets deleted. */
|
* unverified, even if it gets deleted. */
|
||||||
// bob deletes his second device
|
// bob deletes his second device
|
||||||
await bobSecondDevice.evaluate((cli) => cli.logout(true));
|
await bobSecondDevice.evaluate((cli) => cli.logout(true));
|
||||||
|
|
||||||
// wait for the logout to propagate.
|
// wait for the logout to propagate.
|
||||||
await waitForDevices(app, bob.credentials.userId, 1);
|
await waitForDevices(app, bob.credentials.userId, 1);
|
||||||
|
|
||||||
// close and reopen the room, to get the shield to update.
|
// close and reopen the room, to get the shield to update.
|
||||||
await app.viewRoomByName("Bob");
|
await app.viewRoomByName("Bob");
|
||||||
await app.viewRoomByName("TestRoom");
|
await app.viewRoomByName("TestRoom");
|
||||||
|
|
||||||
await expect(last).toContainText("test encrypted from unverified");
|
await expect(last).toContainText("test encrypted from unverified");
|
||||||
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
|
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
|
||||||
await lastE2eIcon.focus();
|
await lastE2eIcon.focus();
|
||||||
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
|
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
|
||||||
"Encrypted by a device not verified by its owner.",
|
"Encrypted by a device not verified by its owner.",
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test("Should show a grey padlock for a key restored from backup", async ({
|
test("Should show a grey padlock for a key restored from backup", async ({
|
||||||
page,
|
page,
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ export async function logIntoElement(page: Page, credentials: Credentials, secur
|
|||||||
await useSecurityKey.click();
|
await useSecurityKey.click();
|
||||||
}
|
}
|
||||||
// Fill in the recovery key
|
// Fill in the recovery key
|
||||||
await page.locator(".mx_Dialog").locator("textarea").fill(securityKey);
|
await page.locator(".mx_Dialog").getByTitle("Recovery key").fill(securityKey);
|
||||||
await page.getByRole("button", { name: "Continue", disabled: false }).click();
|
await page.getByRole("button", { name: "Continue", disabled: false }).click();
|
||||||
await page.getByRole("button", { name: "Done" }).click();
|
await page.getByRole("button", { name: "Done" }).click();
|
||||||
}
|
}
|
||||||
@@ -263,7 +263,7 @@ export async function verifySession(app: ElementAppPage, securityKey: string) {
|
|||||||
const settings = await app.settings.openUserSettings("Encryption");
|
const settings = await app.settings.openUserSettings("Encryption");
|
||||||
await settings.getByRole("button", { name: "Verify this device" }).click();
|
await settings.getByRole("button", { name: "Verify this device" }).click();
|
||||||
await app.page.getByRole("button", { name: "Verify with Recovery Key" }).click();
|
await app.page.getByRole("button", { name: "Verify with Recovery Key" }).click();
|
||||||
await app.page.locator(".mx_Dialog").locator("textarea").fill(securityKey);
|
await app.page.locator(".mx_Dialog").getByTitle("Recovery key").fill(securityKey);
|
||||||
await app.page.getByRole("button", { name: "Continue", disabled: false }).click();
|
await app.page.getByRole("button", { name: "Continue", disabled: false }).click();
|
||||||
await app.page.getByRole("button", { name: "Done" }).click();
|
await app.page.getByRole("button", { name: "Done" }).click();
|
||||||
await app.settings.closeDialog();
|
await app.settings.closeDialog();
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ test.describe("Lazy Loading", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test.beforeEach(async ({ page, homeserver, user, bot, app }) => {
|
test.beforeEach(async ({ page, homeserver, user, bot, app }) => {
|
||||||
|
// The charlies were running off the bottom of the screen.
|
||||||
|
// We no longer overscan the member list so the result is they are not in the dom.
|
||||||
|
// Increase the viewport size to ensure they are.
|
||||||
|
await page.setViewportSize({ width: 1000, height: 1000 });
|
||||||
for (let i = 1; i <= 10; i++) {
|
for (let i = 1; i <= 10; i++) {
|
||||||
const displayName = `Charly #${i}`;
|
const displayName = `Charly #${i}`;
|
||||||
const bot = new Bot(page, homeserver, { displayName, startClient: false, autoAcceptInvites: false });
|
const bot = new Bot(page, homeserver, { displayName, startClient: false, autoAcceptInvites: false });
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ test.describe("Room list filters and sort", () => {
|
|||||||
So we expect 'Old Room' to show up in the room list.
|
So we expect 'Old Room' to show up in the room list.
|
||||||
*/
|
*/
|
||||||
const roomListView = getRoomList(page);
|
const roomListView = getRoomList(page);
|
||||||
const oldRoomTile = roomListView.getByRole("gridcell", { name: "Open room Old Room" });
|
const oldRoomTile = roomListView.getByRole("option", { name: "Open room Old Room" });
|
||||||
await expect(oldRoomTile).toBeVisible();
|
await expect(oldRoomTile).toBeVisible();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -139,8 +139,9 @@ test.describe("Room list filters and sort", () => {
|
|||||||
|
|
||||||
// Open the non-favourite room
|
// Open the non-favourite room
|
||||||
const roomListView = getRoomList(page);
|
const roomListView = getRoomList(page);
|
||||||
const tile = roomListView.getByRole("gridcell", { name: "Open room room-non-fav" });
|
const tile = roomListView.getByRole("option", { name: "Open room room-non-fav" });
|
||||||
await tile.scrollIntoViewIfNeeded();
|
// item may not be in the DOM using scrollListToBottom rather than scrollIntoViewIfNeeded
|
||||||
|
await app.scrollListToBottom(roomListView);
|
||||||
await tile.click();
|
await tile.click();
|
||||||
|
|
||||||
// Enable Favourite filter
|
// Enable Favourite filter
|
||||||
@@ -151,7 +152,7 @@ test.describe("Room list filters and sort", () => {
|
|||||||
|
|
||||||
// Ensure the room list is not scrolled
|
// Ensure the room list is not scrolled
|
||||||
const isScrolledDown = await page
|
const isScrolledDown = await page
|
||||||
.getByRole("grid", { name: "Room list" })
|
.getByRole("listbox", { name: "Room list", exact: true })
|
||||||
.evaluate((e) => e.scrollTop !== 0);
|
.evaluate((e) => e.scrollTop !== 0);
|
||||||
expect(isScrolledDown).toStrictEqual(false);
|
expect(isScrolledDown).toStrictEqual(false);
|
||||||
});
|
});
|
||||||
@@ -227,37 +228,37 @@ test.describe("Room list filters and sort", () => {
|
|||||||
|
|
||||||
await primaryFilters.getByRole("option", { name: "Unread" }).click();
|
await primaryFilters.getByRole("option", { name: "Unread" }).click();
|
||||||
// only one room should be visible
|
// only one room should be visible
|
||||||
await expect(roomList.getByRole("gridcell", { name: "unread dm" })).toBeVisible();
|
await expect(roomList.getByRole("option", { name: "unread dm" })).toBeVisible();
|
||||||
await expect(roomList.getByRole("gridcell", { name: "unread room" })).toBeVisible();
|
await expect(roomList.getByRole("option", { name: "unread room" })).toBeVisible();
|
||||||
await expect.poll(() => roomList.locator("role=gridcell").count()).toBe(4);
|
await expect.poll(() => roomList.locator("role=option").count()).toBe(4);
|
||||||
await expect(primaryFilters).toMatchScreenshot("unread-primary-filters.png");
|
await expect(primaryFilters).toMatchScreenshot("unread-primary-filters.png");
|
||||||
|
|
||||||
await primaryFilters.getByRole("option", { name: "People" }).click();
|
await primaryFilters.getByRole("option", { name: "People" }).click();
|
||||||
await expect(roomList.getByRole("gridcell", { name: "unread dm" })).toBeVisible();
|
await expect(roomList.getByRole("option", { name: "unread dm" })).toBeVisible();
|
||||||
await expect(roomList.getByRole("gridcell", { name: "invited room" })).toBeVisible();
|
await expect(roomList.getByRole("option", { name: "invited room" })).toBeVisible();
|
||||||
await expect.poll(() => roomList.locator("role=gridcell").count()).toBe(2);
|
await expect.poll(() => roomList.locator("role=option").count()).toBe(2);
|
||||||
|
|
||||||
await primaryFilters.getByRole("option", { name: "Rooms" }).click();
|
await primaryFilters.getByRole("option", { name: "Rooms" }).click();
|
||||||
await expect(roomList.getByRole("gridcell", { name: "unread room" })).toBeVisible();
|
await expect(roomList.getByRole("option", { name: "unread room" })).toBeVisible();
|
||||||
await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible();
|
await expect(roomList.getByRole("option", { name: "favourite room" })).toBeVisible();
|
||||||
await expect(roomList.getByRole("gridcell", { name: "empty room" })).toBeVisible();
|
await expect(roomList.getByRole("option", { name: "empty room" })).toBeVisible();
|
||||||
await expect(roomList.getByRole("gridcell", { name: "room with mention" })).toBeVisible();
|
await expect(roomList.getByRole("option", { name: "room with mention" })).toBeVisible();
|
||||||
await expect(roomList.getByRole("gridcell", { name: "Low prio room" })).toBeVisible();
|
await expect(roomList.getByRole("option", { name: "Low prio room" })).toBeVisible();
|
||||||
await expect.poll(() => roomList.locator("role=gridcell").count()).toBe(5);
|
await expect.poll(() => roomList.locator("role=option").count()).toBe(5);
|
||||||
|
|
||||||
await getFilterExpandButton(page).click();
|
await getFilterExpandButton(page).click();
|
||||||
|
|
||||||
await primaryFilters.getByRole("option", { name: "Favourite" }).click();
|
await primaryFilters.getByRole("option", { name: "Favourite" }).click();
|
||||||
await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible();
|
await expect(roomList.getByRole("option", { name: "favourite room" })).toBeVisible();
|
||||||
await expect.poll(() => roomList.locator("role=gridcell").count()).toBe(1);
|
await expect.poll(() => roomList.locator("role=option").count()).toBe(1);
|
||||||
|
|
||||||
await primaryFilters.getByRole("option", { name: "Mentions" }).click();
|
await primaryFilters.getByRole("option", { name: "Mentions" }).click();
|
||||||
await expect(roomList.getByRole("gridcell", { name: "room with mention" })).toBeVisible();
|
await expect(roomList.getByRole("option", { name: "room with mention" })).toBeVisible();
|
||||||
await expect.poll(() => roomList.locator("role=gridcell").count()).toBe(1);
|
await expect.poll(() => roomList.locator("role=option").count()).toBe(1);
|
||||||
|
|
||||||
await primaryFilters.getByRole("option", { name: "Invites" }).click();
|
await primaryFilters.getByRole("option", { name: "Invites" }).click();
|
||||||
await expect(roomList.getByRole("gridcell", { name: "invited room" })).toBeVisible();
|
await expect(roomList.getByRole("option", { name: "invited room" })).toBeVisible();
|
||||||
await expect.poll(() => roomList.locator("role=gridcell").count()).toBe(1);
|
await expect.poll(() => roomList.locator("role=option").count()).toBe(1);
|
||||||
|
|
||||||
await getFilterCollapseButton(page).click();
|
await getFilterCollapseButton(page).click();
|
||||||
await expect(primaryFilters.locator("role=option").first()).toHaveText("Invites");
|
await expect(primaryFilters.locator("role=option").first()).toHaveText("Invites");
|
||||||
@@ -268,6 +269,7 @@ test.describe("Room list filters and sort", () => {
|
|||||||
{ tag: "@screenshot" },
|
{ tag: "@screenshot" },
|
||||||
async ({ page, app, bot }) => {
|
async ({ page, app, bot }) => {
|
||||||
const roomListView = getRoomList(page);
|
const roomListView = getRoomList(page);
|
||||||
|
const primaryFilters = getPrimaryFilters(page);
|
||||||
|
|
||||||
// Let's configure unread dm room so that we only get notification for mentions and keywords
|
// Let's configure unread dm room so that we only get notification for mentions and keywords
|
||||||
await app.viewRoomById(unReadDmId);
|
await app.viewRoomById(unReadDmId);
|
||||||
@@ -276,20 +278,20 @@ test.describe("Room list filters and sort", () => {
|
|||||||
await app.settings.closeDialog();
|
await app.settings.closeDialog();
|
||||||
|
|
||||||
// Let's open a room other than unread room or unread dm
|
// Let's open a room other than unread room or unread dm
|
||||||
await roomListView.getByRole("gridcell", { name: "Open room favourite room" }).click();
|
await roomListView.getByRole("option", { name: "Open room favourite room" }).click();
|
||||||
|
|
||||||
// Let's make the bot send a new message in both rooms
|
// Let's make the bot send a new message in both rooms
|
||||||
await bot.sendMessage(unReadDmId, "Hello!");
|
await bot.sendMessage(unReadDmId, "Hello!");
|
||||||
await bot.sendMessage(unReadRoomId, "Hello!");
|
await bot.sendMessage(unReadRoomId, "Hello!");
|
||||||
|
|
||||||
// Let's activate the unread filter now
|
// Let's activate the unread filter now
|
||||||
await page.getByRole("option", { name: "Unread" }).click();
|
await primaryFilters.getByRole("option", { name: "Unread" }).click();
|
||||||
|
|
||||||
// Unread filter should only show unread room and not unread dm!
|
// Unread filter should only show unread room and not unread dm!
|
||||||
const unreadDm = roomListView.getByRole("gridcell", { name: "Open room unread room" });
|
const unreadDm = roomListView.getByRole("option", { name: "Open room unread room" });
|
||||||
await expect(unreadDm).toBeVisible();
|
await expect(unreadDm).toBeVisible();
|
||||||
await expect(unreadDm).toMatchScreenshot("unread-dm.png");
|
await expect(unreadDm).toMatchScreenshot("unread-dm.png");
|
||||||
await expect(roomListView.getByRole("gridcell", { name: "Open room unread dm" })).not.toBeVisible();
|
await expect(roomListView.getByRole("option", { name: "Open room unread dm" })).not.toBeVisible();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -299,7 +301,7 @@ test.describe("Room list filters and sort", () => {
|
|||||||
await getRoomOptionsMenu(page).click();
|
await getRoomOptionsMenu(page).click();
|
||||||
await page.getByRole("menuitemradio", { name: "A-Z" }).click();
|
await page.getByRole("menuitemradio", { name: "A-Z" }).click();
|
||||||
|
|
||||||
await expect(roomListView.getByRole("gridcell").first()).toHaveText(/empty room/);
|
await expect(roomListView.getByRole("option").first()).toHaveText(/empty room/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should move room to the top on message when sorting by activity", async ({ page, bot }) => {
|
test("should move room to the top on message when sorting by activity", async ({ page, bot }) => {
|
||||||
@@ -307,7 +309,7 @@ test.describe("Room list filters and sort", () => {
|
|||||||
|
|
||||||
await bot.sendMessage(unReadDmId, "Hello!");
|
await bot.sendMessage(unReadDmId, "Hello!");
|
||||||
|
|
||||||
await expect(roomListView.getByRole("gridcell").first()).toHaveText(/unread dm/);
|
await expect(roomListView.getByRole("option").first()).toHaveText(/unread dm/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ test.describe("Header section of the room list", () => {
|
|||||||
|
|
||||||
await expect(page.getByRole("menu")).toMatchScreenshot("room-list-header-compose-menu.png");
|
await expect(page.getByRole("menu")).toMatchScreenshot("room-list-header-compose-menu.png");
|
||||||
|
|
||||||
// New message should open the direct messages dialog
|
// Start chat should open the direct messages dialog
|
||||||
await page.getByRole("menuitem", { name: "New message" }).click();
|
await page.getByRole("menuitem", { name: "Start chat" }).click();
|
||||||
await expect(page.getByRole("heading", { name: "Direct Messages" })).toBeVisible();
|
await expect(page.getByRole("heading", { name: "Direct Messages" })).toBeVisible();
|
||||||
await app.closeDialog();
|
await app.closeDialog();
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ test.describe("Room list panel", () => {
|
|||||||
test("should render the room list panel", { tag: "@screenshot" }, async ({ page, app, user }) => {
|
test("should render the room list panel", { tag: "@screenshot" }, async ({ page, app, user }) => {
|
||||||
const roomListView = getRoomListView(page);
|
const roomListView = getRoomListView(page);
|
||||||
// Wait for the last room to be visible
|
// Wait for the last room to be visible
|
||||||
await expect(roomListView.getByRole("gridcell", { name: "Open room room19" })).toBeVisible();
|
await expect(roomListView.getByRole("option", { name: "Open room room19" })).toBeVisible();
|
||||||
await expect(roomListView).toMatchScreenshot("room-list-panel.png");
|
await expect(roomListView).toMatchScreenshot("room-list-panel.png");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -43,31 +43,35 @@ test.describe("Room list", () => {
|
|||||||
|
|
||||||
test("should render the room list", { tag: "@screenshot" }, async ({ page, app, user }) => {
|
test("should render the room list", { tag: "@screenshot" }, async ({ page, app, user }) => {
|
||||||
const roomListView = getRoomList(page);
|
const roomListView = getRoomList(page);
|
||||||
await expect(roomListView.getByRole("gridcell", { name: "Open room room29" })).toBeVisible();
|
await expect(roomListView.getByRole("option", { name: "Open room room29" })).toBeVisible();
|
||||||
await expect(roomListView).toMatchScreenshot("room-list.png");
|
await expect(roomListView).toMatchScreenshot("room-list.png");
|
||||||
|
|
||||||
// Put focus on the room list
|
// Put focus on the room list
|
||||||
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
|
await roomListView.getByRole("option", { name: "Open room room29" }).click();
|
||||||
// Scroll to the end of the room list
|
// Scroll to the end of the room list
|
||||||
await app.scrollListToBottom(page.locator(".mx_RoomList_List"));
|
await app.scrollListToBottom(roomListView);
|
||||||
|
|
||||||
|
// scrollListToBottom seems to leave the mouse hovered over the list, move it away.
|
||||||
|
await page.getByRole("button", { name: "User menu" }).hover();
|
||||||
|
|
||||||
await expect(roomListView).toMatchScreenshot("room-list-scrolled.png");
|
await expect(roomListView).toMatchScreenshot("room-list-scrolled.png");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should open the room when it is clicked", async ({ page, app, user }) => {
|
test("should open the room when it is clicked", async ({ page, app, user }) => {
|
||||||
const roomListView = getRoomList(page);
|
const roomListView = getRoomList(page);
|
||||||
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
|
await roomListView.getByRole("option", { name: "Open room room29" }).click();
|
||||||
await expect(page.getByRole("heading", { name: "room29", level: 1 })).toBeVisible();
|
await expect(page.getByRole("heading", { name: "room29", level: 1 })).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should open the context menu", { tag: "@screenshot" }, async ({ page, app, user }) => {
|
test("should open the context menu", { tag: "@screenshot" }, async ({ page, app, user }) => {
|
||||||
const roomListView = getRoomList(page);
|
const roomListView = getRoomList(page);
|
||||||
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click({ button: "right" });
|
await roomListView.getByRole("option", { name: "Open room room29" }).click({ button: "right" });
|
||||||
await expect(page.getByRole("menu", { name: "More Options" })).toBeVisible();
|
await expect(page.getByRole("menu", { name: "More Options" })).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should open the more options menu", { tag: "@screenshot" }, async ({ page, app, user }) => {
|
test("should open the more options menu", { tag: "@screenshot" }, async ({ page, app, user }) => {
|
||||||
const roomListView = getRoomList(page);
|
const roomListView = getRoomList(page);
|
||||||
const roomItem = roomListView.getByRole("gridcell", { name: "Open room room29" });
|
const roomItem = roomListView.getByRole("option", { name: "Open room room29" });
|
||||||
await roomItem.hover();
|
await roomItem.hover();
|
||||||
|
|
||||||
await expect(roomItem).toMatchScreenshot("room-list-item-hover.png");
|
await expect(roomItem).toMatchScreenshot("room-list-item-hover.png");
|
||||||
@@ -97,7 +101,7 @@ test.describe("Room list", () => {
|
|||||||
test("should open the notification options menu", { tag: "@screenshot" }, async ({ page, app, user }) => {
|
test("should open the notification options menu", { tag: "@screenshot" }, async ({ page, app, user }) => {
|
||||||
const roomListView = getRoomList(page);
|
const roomListView = getRoomList(page);
|
||||||
|
|
||||||
const roomItem = roomListView.getByRole("gridcell", { name: "Open room room29" });
|
const roomItem = roomListView.getByRole("option", { name: "Open room room29" });
|
||||||
await roomItem.hover();
|
await roomItem.hover();
|
||||||
|
|
||||||
await expect(roomItem).toMatchScreenshot("room-list-item-hover.png");
|
await expect(roomItem).toMatchScreenshot("room-list-item-hover.png");
|
||||||
@@ -117,10 +121,10 @@ test.describe("Room list", () => {
|
|||||||
await expect(roomItem.getByTestId("notification-decoration")).not.toBeVisible();
|
await expect(roomItem.getByTestId("notification-decoration")).not.toBeVisible();
|
||||||
|
|
||||||
// Put focus on the room list
|
// Put focus on the room list
|
||||||
await roomListView.getByRole("gridcell", { name: "Open room room28" }).click();
|
await roomListView.getByRole("option", { name: "Open room room28" }).click();
|
||||||
|
|
||||||
// Scroll to the end of the room list
|
// Scroll to the end of the room list
|
||||||
await app.scrollListToBottom(page.locator(".mx_RoomList_List"));
|
await app.scrollListToBottom(roomListView);
|
||||||
|
|
||||||
// The room decoration should have the muted icon
|
// The room decoration should have the muted icon
|
||||||
await expect(roomItem.getByTestId("notification-decoration")).toBeVisible();
|
await expect(roomItem.getByTestId("notification-decoration")).toBeVisible();
|
||||||
@@ -139,25 +143,25 @@ test.describe("Room list", () => {
|
|||||||
test("should scroll to the current room", async ({ page, app, user }) => {
|
test("should scroll to the current room", async ({ page, app, user }) => {
|
||||||
const roomListView = getRoomList(page);
|
const roomListView = getRoomList(page);
|
||||||
// Put focus on the room list
|
// Put focus on the room list
|
||||||
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
|
await roomListView.getByRole("option", { name: "Open room room29" }).click();
|
||||||
// Scroll to the end of the room list
|
// Scroll to the end of the room list
|
||||||
await app.scrollListToBottom(page.locator(".mx_RoomList_List"));
|
await app.scrollListToBottom(roomListView);
|
||||||
|
|
||||||
await expect(roomListView.getByRole("gridcell", { name: "Open room room0" })).toBeVisible();
|
await expect(roomListView.getByRole("option", { name: "Open room room0" })).toBeVisible();
|
||||||
await roomListView.getByRole("gridcell", { name: "Open room room0" }).click();
|
await roomListView.getByRole("option", { name: "Open room room0" }).click();
|
||||||
|
|
||||||
const filters = page.getByRole("listbox", { name: "Room list filters" });
|
const filters = page.getByRole("listbox", { name: "Room list filters" });
|
||||||
await filters.getByRole("option", { name: "People" }).click();
|
await filters.getByRole("option", { name: "People" }).click();
|
||||||
await expect(roomListView.getByRole("gridcell", { name: "Open room room0" })).not.toBeVisible();
|
await expect(roomListView.getByRole("option", { name: "Open room room0" })).not.toBeVisible();
|
||||||
|
|
||||||
await filters.getByRole("option", { name: "People" }).click();
|
await filters.getByRole("option", { name: "People" }).click();
|
||||||
await expect(roomListView.getByRole("gridcell", { name: "Open room room0" })).toBeVisible();
|
await expect(roomListView.getByRole("option", { name: "Open room room0" })).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe("Shortcuts", () => {
|
test.describe("Shortcuts", () => {
|
||||||
test("should select the next room", async ({ page, app, user }) => {
|
test("should select the next room", async ({ page, app, user }) => {
|
||||||
const roomListView = getRoomList(page);
|
const roomListView = getRoomList(page);
|
||||||
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
|
await roomListView.getByRole("option", { name: "Open room room29" }).click();
|
||||||
await page.keyboard.press("Alt+ArrowDown");
|
await page.keyboard.press("Alt+ArrowDown");
|
||||||
|
|
||||||
await expect(page.getByRole("heading", { name: "room28", level: 1 })).toBeVisible();
|
await expect(page.getByRole("heading", { name: "room28", level: 1 })).toBeVisible();
|
||||||
@@ -165,7 +169,7 @@ test.describe("Room list", () => {
|
|||||||
|
|
||||||
test("should select the previous room", async ({ page, app, user }) => {
|
test("should select the previous room", async ({ page, app, user }) => {
|
||||||
const roomListView = getRoomList(page);
|
const roomListView = getRoomList(page);
|
||||||
await roomListView.getByRole("gridcell", { name: "Open room room28" }).click();
|
await roomListView.getByRole("option", { name: "Open room room28" }).click();
|
||||||
await page.keyboard.press("Alt+ArrowUp");
|
await page.keyboard.press("Alt+ArrowUp");
|
||||||
|
|
||||||
await expect(page.getByRole("heading", { name: "room29", level: 1 })).toBeVisible();
|
await expect(page.getByRole("heading", { name: "room29", level: 1 })).toBeVisible();
|
||||||
@@ -173,7 +177,7 @@ test.describe("Room list", () => {
|
|||||||
|
|
||||||
test("should select the last room", async ({ page, app, user }) => {
|
test("should select the last room", async ({ page, app, user }) => {
|
||||||
const roomListView = getRoomList(page);
|
const roomListView = getRoomList(page);
|
||||||
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
|
await roomListView.getByRole("option", { name: "Open room room29" }).click();
|
||||||
await page.keyboard.press("Alt+ArrowUp");
|
await page.keyboard.press("Alt+ArrowUp");
|
||||||
|
|
||||||
await expect(page.getByRole("heading", { name: "room0", level: 1 })).toBeVisible();
|
await expect(page.getByRole("heading", { name: "room0", level: 1 })).toBeVisible();
|
||||||
@@ -187,7 +191,7 @@ test.describe("Room list", () => {
|
|||||||
await bot.joinRoom(roomId);
|
await bot.joinRoom(roomId);
|
||||||
await bot.sendMessage(roomId, "I am a robot. Beep.");
|
await bot.sendMessage(roomId, "I am a robot. Beep.");
|
||||||
|
|
||||||
await roomListView.getByRole("gridcell", { name: "Open room room20" }).click();
|
await roomListView.getByRole("option", { name: "Open room room20" }).click();
|
||||||
|
|
||||||
await page.keyboard.press("Alt+Shift+ArrowDown");
|
await page.keyboard.press("Alt+Shift+ArrowDown");
|
||||||
|
|
||||||
@@ -199,8 +203,8 @@ test.describe("Room list", () => {
|
|||||||
test("should navigate to the room list", async ({ page, app, user }) => {
|
test("should navigate to the room list", async ({ page, app, user }) => {
|
||||||
const roomListView = getRoomList(page);
|
const roomListView = getRoomList(page);
|
||||||
|
|
||||||
const room29 = roomListView.getByRole("gridcell", { name: "Open room room29" });
|
const room29 = roomListView.getByRole("option", { name: "Open room room29" });
|
||||||
const room28 = roomListView.getByRole("gridcell", { name: "Open room room28" });
|
const room28 = roomListView.getByRole("option", { name: "Open room room28" });
|
||||||
|
|
||||||
// open the room
|
// open the room
|
||||||
await room29.click();
|
await room29.click();
|
||||||
@@ -219,7 +223,7 @@ test.describe("Room list", () => {
|
|||||||
|
|
||||||
test("should navigate to the notification menu", async ({ page, app, user }) => {
|
test("should navigate to the notification menu", async ({ page, app, user }) => {
|
||||||
const roomListView = getRoomList(page);
|
const roomListView = getRoomList(page);
|
||||||
const room29 = roomListView.getByRole("gridcell", { name: "Open room room29" });
|
const room29 = roomListView.getByRole("option", { name: "Open room room29" });
|
||||||
const moreButton = room29.getByRole("button", { name: "More options" });
|
const moreButton = room29.getByRole("button", { name: "More options" });
|
||||||
const notificationButton = room29.getByRole("button", { name: "Notification options" });
|
const notificationButton = room29.getByRole("button", { name: "Notification options" });
|
||||||
|
|
||||||
@@ -258,7 +262,7 @@ test.describe("Room list", () => {
|
|||||||
await page.getByRole("button", { name: "User menu" }).focus();
|
await page.getByRole("button", { name: "User menu" }).focus();
|
||||||
|
|
||||||
const roomListView = getRoomList(page);
|
const roomListView = getRoomList(page);
|
||||||
const publicRoom = roomListView.getByRole("gridcell", { name: "public room" });
|
const publicRoom = roomListView.getByRole("option", { name: "public room" });
|
||||||
|
|
||||||
await expect(publicRoom).toBeVisible();
|
await expect(publicRoom).toBeVisible();
|
||||||
await expect(publicRoom).toMatchScreenshot("room-list-item-public.png");
|
await expect(publicRoom).toMatchScreenshot("room-list-item-public.png");
|
||||||
@@ -268,7 +272,7 @@ test.describe("Room list", () => {
|
|||||||
// @ts-ignore Visibility enum is not accessible
|
// @ts-ignore Visibility enum is not accessible
|
||||||
await app.client.createRoom({ name: "low priority room", visibility: "public" });
|
await app.client.createRoom({ name: "low priority room", visibility: "public" });
|
||||||
const roomListView = getRoomList(page);
|
const roomListView = getRoomList(page);
|
||||||
const publicRoom = roomListView.getByRole("gridcell", { name: "low priority room" });
|
const publicRoom = roomListView.getByRole("option", { name: "low priority room" });
|
||||||
|
|
||||||
// Make room low priority
|
// Make room low priority
|
||||||
await publicRoom.hover();
|
await publicRoom.hover();
|
||||||
@@ -293,7 +297,7 @@ test.describe("Room list", () => {
|
|||||||
await page.getByRole("button", { name: "Create video room" }).click();
|
await page.getByRole("button", { name: "Create video room" }).click();
|
||||||
|
|
||||||
const roomListView = getRoomList(page);
|
const roomListView = getRoomList(page);
|
||||||
const videoRoom = roomListView.getByRole("gridcell", { name: "video room" });
|
const videoRoom = roomListView.getByRole("option", { name: "video room" });
|
||||||
|
|
||||||
// focus the user menu to avoid to have hover decoration
|
// focus the user menu to avoid to have hover decoration
|
||||||
await page.getByRole("button", { name: "User menu" }).focus();
|
await page.getByRole("button", { name: "User menu" }).focus();
|
||||||
@@ -312,7 +316,7 @@ test.describe("Room list", () => {
|
|||||||
invite: [user.userId],
|
invite: [user.userId],
|
||||||
is_direct: true,
|
is_direct: true,
|
||||||
});
|
});
|
||||||
const invitedRoom = roomListView.getByRole("gridcell", { name: "invited room" });
|
const invitedRoom = roomListView.getByRole("option", { name: "invited room" });
|
||||||
await expect(invitedRoom).toBeVisible();
|
await expect(invitedRoom).toBeVisible();
|
||||||
await expect(invitedRoom).toMatchScreenshot("room-list-item-invited.png");
|
await expect(invitedRoom).toMatchScreenshot("room-list-item-invited.png");
|
||||||
});
|
});
|
||||||
@@ -327,7 +331,7 @@ test.describe("Room list", () => {
|
|||||||
await bot.sendMessage(roomId, "I am a robot. Beep.");
|
await bot.sendMessage(roomId, "I am a robot. Beep.");
|
||||||
await bot.sendMessage(roomId, "I am a robot. Beep.");
|
await bot.sendMessage(roomId, "I am a robot. Beep.");
|
||||||
|
|
||||||
const room = roomListView.getByRole("gridcell", { name: "2 notifications" });
|
const room = roomListView.getByRole("option", { name: "2 notifications" });
|
||||||
await expect(room).toBeVisible();
|
await expect(room).toBeVisible();
|
||||||
await expect(room.getByTestId("notification-decoration")).toHaveText("2");
|
await expect(room.getByTestId("notification-decoration")).toHaveText("2");
|
||||||
await expect(room).toMatchScreenshot("room-list-item-notification.png");
|
await expect(room).toMatchScreenshot("room-list-item-notification.png");
|
||||||
@@ -358,7 +362,7 @@ test.describe("Room list", () => {
|
|||||||
);
|
);
|
||||||
await bot.sendMessage(roomId, "I am a robot. Beep.");
|
await bot.sendMessage(roomId, "I am a robot. Beep.");
|
||||||
|
|
||||||
const room = roomListView.getByRole("gridcell", { name: "mention" });
|
const room = roomListView.getByRole("option", { name: "mention" });
|
||||||
await expect(room).toBeVisible();
|
await expect(room).toBeVisible();
|
||||||
await expect(room).toMatchScreenshot("room-list-item-mention.png");
|
await expect(room).toMatchScreenshot("room-list-item-mention.png");
|
||||||
});
|
});
|
||||||
@@ -379,7 +383,7 @@ test.describe("Room list", () => {
|
|||||||
await bot.joinRoom(roomId);
|
await bot.joinRoom(roomId);
|
||||||
await bot.sendMessage(roomId, "I am a robot. Beep.");
|
await bot.sendMessage(roomId, "I am a robot. Beep.");
|
||||||
|
|
||||||
const room = roomListView.getByRole("gridcell", { name: "activity" });
|
const room = roomListView.getByRole("option", { name: "activity" });
|
||||||
await expect(room.getByText("I am a robot. Beep.")).toBeVisible();
|
await expect(room.getByText("I am a robot. Beep.")).toBeVisible();
|
||||||
await expect(room).toMatchScreenshot("room-list-item-message-preview.png");
|
await expect(room).toMatchScreenshot("room-list-item-message-preview.png");
|
||||||
});
|
});
|
||||||
@@ -406,7 +410,7 @@ test.describe("Room list", () => {
|
|||||||
await app.viewRoomById(otherRoomId);
|
await app.viewRoomById(otherRoomId);
|
||||||
await bot.sendMessage(roomId, "I am a robot. Beep.");
|
await bot.sendMessage(roomId, "I am a robot. Beep.");
|
||||||
|
|
||||||
const room = roomListView.getByRole("gridcell", { name: "activity" });
|
const room = roomListView.getByRole("option", { name: "activity" });
|
||||||
await expect(room.getByTestId("notification-decoration")).toBeVisible();
|
await expect(room.getByTestId("notification-decoration")).toBeVisible();
|
||||||
await expect(room).toMatchScreenshot("room-list-item-activity.png");
|
await expect(room).toMatchScreenshot("room-list-item-activity.png");
|
||||||
});
|
});
|
||||||
@@ -418,7 +422,7 @@ test.describe("Room list", () => {
|
|||||||
await app.client.inviteUser(roomId, bot.credentials.userId);
|
await app.client.inviteUser(roomId, bot.credentials.userId);
|
||||||
await bot.joinRoom(roomId);
|
await bot.joinRoom(roomId);
|
||||||
|
|
||||||
const room = roomListView.getByRole("gridcell", { name: "mark as unread" });
|
const room = roomListView.getByRole("option", { name: "mark as unread" });
|
||||||
await room.hover();
|
await room.hover();
|
||||||
await room.getByRole("button", { name: "More Options" }).click();
|
await room.getByRole("button", { name: "More Options" }).click();
|
||||||
await page.getByRole("menuitem", { name: "mark as unread" }).click();
|
await page.getByRole("menuitem", { name: "mark as unread" }).click();
|
||||||
@@ -441,7 +445,7 @@ test.describe("Room list", () => {
|
|||||||
await page.getByText("Off").click();
|
await page.getByText("Off").click();
|
||||||
await app.settings.closeDialog();
|
await app.settings.closeDialog();
|
||||||
|
|
||||||
const room = roomListView.getByRole("gridcell", { name: "silent" });
|
const room = roomListView.getByRole("option", { name: "silent" });
|
||||||
await expect(room.getByTestId("notification-decoration")).toBeVisible();
|
await expect(room.getByTestId("notification-decoration")).toBeVisible();
|
||||||
await expect(room).toMatchScreenshot("room-list-item-silent.png");
|
await expect(room).toMatchScreenshot("room-list-item-silent.png");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { type Locator, type Page } from "@playwright/test";
|
|||||||
import { test, expect } from "../../element-web-test";
|
import { test, expect } from "../../element-web-test";
|
||||||
|
|
||||||
async function sendMessage(page: Page, message: string): Promise<Locator> {
|
async function sendMessage(page: Page, message: string): Promise<Locator> {
|
||||||
await page.getByRole("textbox", { name: "Send a message…" }).fill(message);
|
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).fill(message);
|
||||||
await page.getByRole("button", { name: "Send message" }).click();
|
await page.getByRole("button", { name: "Send message" }).click();
|
||||||
|
|
||||||
const msgTile = page.locator(".mx_EventTile_last");
|
const msgTile = page.locator(".mx_EventTile_last");
|
||||||
@@ -22,7 +22,7 @@ async function sendMessage(page: Page, message: string): Promise<Locator> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function sendMultilineMessages(page: Page, messages: string[]) {
|
async function sendMultilineMessages(page: Page, messages: string[]) {
|
||||||
await page.getByRole("textbox", { name: "Send a message…" }).focus();
|
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).focus();
|
||||||
for (let i = 0; i < messages.length; i++) {
|
for (let i = 0; i < messages.length; i++) {
|
||||||
await page.keyboard.type(messages[i]);
|
await page.keyboard.type(messages[i]);
|
||||||
if (i < messages.length - 1) await page.keyboard.press("Shift+Enter");
|
if (i < messages.length - 1) await page.keyboard.press("Shift+Enter");
|
||||||
@@ -40,7 +40,7 @@ async function replyMessage(page: Page, message: Locator, replyMessage: string):
|
|||||||
await line.hover();
|
await line.hover();
|
||||||
await line.getByRole("button", { name: "Reply", exact: true }).click();
|
await line.getByRole("button", { name: "Reply", exact: true }).click();
|
||||||
|
|
||||||
await page.getByRole("textbox", { name: "Send a reply…" }).fill(replyMessage);
|
await page.getByRole("textbox", { name: "Send an unencrypted reply…" }).fill(replyMessage);
|
||||||
await page.getByRole("button", { name: "Send message" }).click();
|
await page.getByRole("button", { name: "Send message" }).click();
|
||||||
|
|
||||||
const msgTile = page.locator(".mx_EventTile_last");
|
const msgTile = page.locator(".mx_EventTile_last");
|
||||||
|
|||||||
@@ -95,10 +95,6 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
const result = await mas.manage("kill-sessions", userId);
|
const result = await mas.manage("kill-sessions", userId);
|
||||||
expect(result.output).toContain("Ended 1 active OAuth 2.0 session");
|
expect(result.output).toContain("Ended 1 active OAuth 2.0 session");
|
||||||
|
|
||||||
// Workaround for Synapse's 2 minute cache on MAS token validity
|
|
||||||
// (https://github.com/element-hq/synapse/pull/18231)
|
|
||||||
await homeserver.restart();
|
|
||||||
|
|
||||||
await page.goto("http://localhost:8080");
|
await page.goto("http://localhost:8080");
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText("For security, this session has been signed out. Please sign in again."),
|
page.getByText("For security, this session has been signed out. Please sign in again."),
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ test.describe("Pills", () => {
|
|||||||
|
|
||||||
// send a message using the built-in room mention functionality (autocomplete)
|
// send a message using the built-in room mention functionality (autocomplete)
|
||||||
await page
|
await page
|
||||||
.getByRole("textbox", { name: "Send a message…" })
|
.getByRole("textbox", { name: "Send an unencrypted message…" })
|
||||||
.pressSequentially(`Hello world! Join here: #${targetLocalpart.substring(0, 3)}`);
|
.pressSequentially(`Hello world! Join here: #${targetLocalpart.substring(0, 3)}`);
|
||||||
await page.locator(".mx_Autocomplete_Completion_title").click();
|
await page.locator(".mx_Autocomplete_Completion_title").click();
|
||||||
await page.getByRole("button", { name: "Send message" }).click();
|
await page.getByRole("button", { name: "Send message" }).click();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
|||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type Download, type Page } from "@playwright/test";
|
import { type Page } from "@playwright/test";
|
||||||
|
|
||||||
import { test, expect } from "../../element-web-test";
|
import { test, expect } from "../../element-web-test";
|
||||||
import { viewRoomSummaryByName } from "./utils";
|
import { viewRoomSummaryByName } from "./utils";
|
||||||
@@ -63,9 +63,7 @@ test.describe("FilePanel", () => {
|
|||||||
await expect(roomViewBody.locator(".mx_EventTile[data-layout='group'] img[alt='riot.png']")).toBeVisible();
|
await expect(roomViewBody.locator(".mx_EventTile[data-layout='group'] img[alt='riot.png']")).toBeVisible();
|
||||||
|
|
||||||
// Assert that the audio player is rendered
|
// Assert that the audio player is rendered
|
||||||
await expect(
|
await expect(roomViewBody.getByRole("region", { name: "Audio player" })).toBeVisible();
|
||||||
roomViewBody.locator(".mx_EventTile[data-layout='group'] .mx_AudioPlayer_container"),
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
// Assert that the file button exists
|
// Assert that the file button exists
|
||||||
await expect(
|
await expect(
|
||||||
@@ -97,9 +95,7 @@ test.describe("FilePanel", () => {
|
|||||||
await expect(image.locator("img[alt='riot.png']")).toBeVisible();
|
await expect(image.locator("img[alt='riot.png']")).toBeVisible();
|
||||||
|
|
||||||
// Detect the audio file
|
// Detect the audio file
|
||||||
const audio = filePanelMessageList.locator(
|
const audio = filePanelMessageList.getByRole("region", { name: "Audio player" });
|
||||||
".mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container",
|
|
||||||
);
|
|
||||||
// Assert that the play button is rendered
|
// Assert that the play button is rendered
|
||||||
await expect(audio.getByRole("button", { name: "Play" })).toBeVisible();
|
await expect(audio.getByRole("button", { name: "Play" })).toBeVisible();
|
||||||
|
|
||||||
@@ -130,7 +126,7 @@ test.describe("FilePanel", () => {
|
|||||||
// Take a snapshot of file tiles list on FilePanel
|
// Take a snapshot of file tiles list on FilePanel
|
||||||
await expect(filePanelMessageList).toMatchScreenshot("file-tiles-list.png", {
|
await expect(filePanelMessageList).toMatchScreenshot("file-tiles-list.png", {
|
||||||
// Exclude timestamps & flaky seek bar from snapshot
|
// Exclude timestamps & flaky seek bar from snapshot
|
||||||
mask: [page.locator(".mx_MessageTimestamp, .mx_AudioPlayer_seek")],
|
mask: [page.locator(".mx_MessageTimestamp"), page.getByTestId("audio-player-seek")],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -138,21 +134,19 @@ test.describe("FilePanel", () => {
|
|||||||
// Upload an image file
|
// Upload an image file
|
||||||
await uploadFile(page, "playwright/sample-files/1sec.ogg");
|
await uploadFile(page, "playwright/sample-files/1sec.ogg");
|
||||||
|
|
||||||
const audioBody = page.locator(
|
const audioBody = page.getByTestId("right-panel").getByRole("region", { name: "Audio player" });
|
||||||
".mx_FilePanel .mx_RoomView_MessageList .mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container",
|
|
||||||
);
|
|
||||||
// Assert that the audio player is rendered
|
// Assert that the audio player is rendered
|
||||||
// Assert that the audio file information is rendered
|
// Assert that the audio file information is rendered;
|
||||||
const mediaInfo = audioBody.locator(".mx_AudioPlayer_mediaInfo");
|
await expect(audioBody.getByText("1sec.ogg")).toBeVisible(); // extension
|
||||||
await expect(mediaInfo.locator(".mx_AudioPlayer_mediaName").getByText("1sec.ogg")).toBeVisible();
|
await expect(audioBody.getByRole("time")).toHaveText("00:01"); // duration
|
||||||
await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "00:01" })).toBeVisible();
|
await expect(audioBody.getByText("(3.56 KB)")).toBeVisible(); // actual size;
|
||||||
await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "(3.56 KB)" })).toBeVisible(); // actual size
|
|
||||||
|
|
||||||
// Assert that the duration counter is 00:01 before clicking the play button
|
// Assert that the duration counter is 00:01 before clicking the play button
|
||||||
await expect(audioBody.locator(".mx_AudioPlayer_mediaInfo time", { hasText: "00:01" })).toBeVisible();
|
await expect(audioBody.getByRole("time")).toHaveText("00:01");
|
||||||
|
|
||||||
// Assert that the counter is zero before clicking the play button
|
// Assert that the counter is zero before clicking the play button
|
||||||
await expect(audioBody.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
|
await expect(audioBody.getByRole("timer")).toHaveText("00:00");
|
||||||
|
|
||||||
// Click the play button
|
// Click the play button
|
||||||
await audioBody.getByRole("button", { name: "Play" }).click();
|
await audioBody.getByRole("button", { name: "Play" }).click();
|
||||||
@@ -161,7 +155,7 @@ test.describe("FilePanel", () => {
|
|||||||
await expect(audioBody.getByRole("button", { name: "Pause" })).toBeVisible();
|
await expect(audioBody.getByRole("button", { name: "Pause" })).toBeVisible();
|
||||||
|
|
||||||
// Assert that the timer is reset when the audio file finished playing
|
// Assert that the timer is reset when the audio file finished playing
|
||||||
await expect(audioBody.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
|
await expect(audioBody.getByRole("timer")).toHaveText("00:00");
|
||||||
|
|
||||||
// Assert that the play button is rendered
|
// Assert that the play button is rendered
|
||||||
await expect(audioBody.getByRole("button", { name: "Play" })).toBeVisible();
|
await expect(audioBody.getByRole("button", { name: "Play" })).toBeVisible();
|
||||||
@@ -195,23 +189,13 @@ test.describe("FilePanel", () => {
|
|||||||
|
|
||||||
const link = imageBody.locator(".mx_MFileBody_download a");
|
const link = imageBody.locator(".mx_MFileBody_download a");
|
||||||
|
|
||||||
const newPagePromise = context.waitForEvent("page");
|
const downloadPromise = page.waitForEvent("download");
|
||||||
|
|
||||||
const downloadPromise = new Promise<Download>((resolve) => {
|
|
||||||
page.once("download", resolve);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Click the anchor link (not the image itself)
|
// Click the anchor link (not the image itself)
|
||||||
await link.click();
|
await link.click();
|
||||||
|
|
||||||
const newPage = await newPagePromise;
|
const download = await downloadPromise;
|
||||||
// XXX: Clicking the link opens the image in a new tab on some browsers rather than downloading
|
expect(download.suggestedFilename()).toBe("riot.png");
|
||||||
await expect(newPage)
|
|
||||||
.toHaveURL(/.+\/_matrix\/media\/\w+\/download\/localhost\/\w+/)
|
|
||||||
.catch(async () => {
|
|
||||||
const download = await downloadPromise;
|
|
||||||
expect(download.suggestedFilename()).toBe("riot.png");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,6 +11,32 @@ import { Bot } from "../../pages/bot";
|
|||||||
const ROOM_NAME = "Test room";
|
const ROOM_NAME = "Test room";
|
||||||
const NAME = "Alice";
|
const NAME = "Alice";
|
||||||
|
|
||||||
|
async function setupRoomWithMembers(
|
||||||
|
app: any,
|
||||||
|
page: any,
|
||||||
|
homeserver: any,
|
||||||
|
roomName: string,
|
||||||
|
memberNames: string[],
|
||||||
|
): Promise<string> {
|
||||||
|
const visibility = await page.evaluate(() => (window as any).matrixcs.Visibility.Public);
|
||||||
|
const id = await app.client.createRoom({ name: roomName, visibility });
|
||||||
|
const bots: Bot[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < memberNames.length; i++) {
|
||||||
|
const displayName = memberNames[i];
|
||||||
|
const bot = new Bot(page, homeserver, { displayName, startClient: false, autoAcceptInvites: false });
|
||||||
|
if (displayName === "Susan") {
|
||||||
|
await bot.prepareClient();
|
||||||
|
await app.client.inviteUser(id, bot.credentials?.userId);
|
||||||
|
} else {
|
||||||
|
await bot.joinRoom(id);
|
||||||
|
}
|
||||||
|
bots.push(bot);
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
test.use({
|
test.use({
|
||||||
synapseConfig: {
|
synapseConfig: {
|
||||||
presence: {
|
presence: {
|
||||||
@@ -25,17 +51,8 @@ test.use({
|
|||||||
test.describe("Memberlist", () => {
|
test.describe("Memberlist", () => {
|
||||||
test.beforeEach(async ({ app, user, page, homeserver }, testInfo) => {
|
test.beforeEach(async ({ app, user, page, homeserver }, testInfo) => {
|
||||||
testInfo.setTimeout(testInfo.timeout + 30_000);
|
testInfo.setTimeout(testInfo.timeout + 30_000);
|
||||||
const id = await app.client.createRoom({ name: ROOM_NAME });
|
|
||||||
const newBots: Bot[] = [];
|
|
||||||
const names = ["Bob", "Bob", "Susan"];
|
const names = ["Bob", "Bob", "Susan"];
|
||||||
for (let i = 0; i < 3; i++) {
|
await setupRoomWithMembers(app, page, homeserver, ROOM_NAME, names);
|
||||||
const displayName = names[i];
|
|
||||||
const autoAcceptInvites = displayName !== "Susan";
|
|
||||||
const bot = new Bot(page, homeserver, { displayName, startClient: true, autoAcceptInvites });
|
|
||||||
await bot.prepareClient();
|
|
||||||
await app.client.inviteUser(id, bot.credentials?.userId);
|
|
||||||
newBots.push(bot);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Renders correctly", { tag: "@screenshot" }, async ({ page, app }) => {
|
test("Renders correctly", { tag: "@screenshot" }, async ({ page, app }) => {
|
||||||
@@ -45,4 +62,37 @@ test.describe("Memberlist", () => {
|
|||||||
await expect(memberlist.getByText("Invited")).toHaveCount(1);
|
await expect(memberlist.getByText("Invited")).toHaveCount(1);
|
||||||
await expect(page.locator(".mx_MemberListView")).toMatchScreenshot("with-four-members.png");
|
await expect(page.locator(".mx_MemberListView")).toMatchScreenshot("with-four-members.png");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("should handle scroll and click to view member profile", async ({ page, app, homeserver }) => {
|
||||||
|
// Create a room with many members to enable scrolling
|
||||||
|
const memberNames = Array.from({ length: 15 }, (_, i) => `Member${i.toString()}`);
|
||||||
|
await setupRoomWithMembers(app, page, homeserver, "Large Room", memberNames);
|
||||||
|
|
||||||
|
// Navigate to the room and open member list
|
||||||
|
await app.viewRoomByName("Large Room");
|
||||||
|
|
||||||
|
const memberlist = await app.toggleMemberlistPanel();
|
||||||
|
|
||||||
|
// Get the scrollable container
|
||||||
|
const memberListContainer = memberlist.locator(".mx_AutoHideScrollbar");
|
||||||
|
|
||||||
|
// Scroll down to the bottom of the member list
|
||||||
|
await app.scrollListToBottom(memberListContainer);
|
||||||
|
|
||||||
|
// Wait for the target member to be visible after scrolling
|
||||||
|
const targetName = "Member14";
|
||||||
|
const targetMember = memberlist.locator(".mx_MemberTileView_name").filter({ hasText: targetName });
|
||||||
|
await targetMember.waitFor({ state: "visible" });
|
||||||
|
|
||||||
|
// Verify Alice is not visible at this point
|
||||||
|
await expect(memberlist.locator(".mx_MemberTileView_name").filter({ hasText: "Alice" })).toHaveCount(0);
|
||||||
|
|
||||||
|
// Click on a member near the bottom of the list
|
||||||
|
await expect(targetMember).toBeVisible();
|
||||||
|
await targetMember.click();
|
||||||
|
|
||||||
|
// Verify that the user info screen is shown and hasn't scrolled back to top
|
||||||
|
await expect(page.locator(".mx_UserInfo")).toBeVisible();
|
||||||
|
await expect(page.locator(".mx_UserInfo_profile").getByText(targetName)).toBeVisible();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,11 +37,8 @@ test.describe("Room Header", () => {
|
|||||||
await expect(header.locator(".mx_FacePile")).toBeVisible();
|
await expect(header.locator(".mx_FacePile")).toBeVisible();
|
||||||
|
|
||||||
// There should be both a voice and a video call button
|
// There should be both a voice and a video call button
|
||||||
// but they'll be disabled
|
await expect(header.getByRole("button", { name: "Video call" })).toBeVisible();
|
||||||
const callButtons = header.getByRole("button", { name: "There's no one here to call" });
|
await expect(header.getByRole("button", { name: "Voice call" })).toBeVisible();
|
||||||
await expect(callButtons).toHaveCount(2);
|
|
||||||
await expect(callButtons.first()).toBeVisible();
|
|
||||||
await expect(callButtons.last()).toBeVisible();
|
|
||||||
|
|
||||||
await expect(header.getByRole("button", { name: "Threads" })).toBeVisible();
|
await expect(header.getByRole("button", { name: "Threads" })).toBeVisible();
|
||||||
await expect(header.getByRole("button", { name: "Notifications" })).toBeVisible();
|
await expect(header.getByRole("button", { name: "Notifications" })).toBeVisible();
|
||||||
|
|||||||
@@ -18,13 +18,14 @@ test.describe("share from URL", () => {
|
|||||||
|
|
||||||
test("should share message when users navigates to share URL", async ({ page, user, room, app }) => {
|
test("should share message when users navigates to share URL", async ({ page, user, room, app }) => {
|
||||||
await page.goto("/#/share?msg=Hello+world");
|
await page.goto("/#/share?msg=Hello+world");
|
||||||
|
const dialog = page.getByRole("dialog", { name: "Forward message" });
|
||||||
// The forward message dialog doesn't update as new infomation arrives via sync, which means sometimes
|
// The forward message dialog doesn't update as new infomation arrives via sync, which means sometimes
|
||||||
// this is just says, "Empty room". For the same reason, we can't reliably write a test for loading the
|
// this is just says, "Empty room". For the same reason, we can't reliably write a test for loading the
|
||||||
// app straight away with a /#/share url as the room doesn't appear until the client syncs.]
|
// app straight away with a /#/share url as the room doesn't appear until the client syncs.]
|
||||||
// Ideally we should fix the forward dialog to update and eliminate races, until then, there is only one
|
// Ideally we should fix the forward dialog to update and eliminate races, until then, there is only one
|
||||||
// room so we click the first button.
|
// room so we click the first button.
|
||||||
await page.getByRole("listitem" /*, { name: "A test room" }*/).getByRole("button", { name: "Send" }).click();
|
await dialog.getByRole("listitem" /*, { name: "A test room" }*/).getByRole("button", { name: "Send" }).click();
|
||||||
await page.keyboard.press("Escape");
|
await dialog.getByRole("button", { name: "Close" }).click();
|
||||||
await app.viewRoomByName("A test room");
|
await app.viewRoomByName("A test room");
|
||||||
const lastMessage = page.locator(".mx_RoomView_MessageList .mx_EventTile_last");
|
const lastMessage = page.locator(".mx_RoomView_MessageList .mx_EventTile_last");
|
||||||
await expect(lastMessage).toBeVisible();
|
await expect(lastMessage).toBeVisible();
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ test.describe("Share dialog", () => {
|
|||||||
|
|
||||||
const rightPanel = await app.toggleRoomInfoPanel();
|
const rightPanel = await app.toggleRoomInfoPanel();
|
||||||
await rightPanel.getByRole("menuitem", { name: "People" }).click();
|
await rightPanel.getByRole("menuitem", { name: "People" }).click();
|
||||||
await rightPanel.getByRole("button", { name: `${user.userId} (power 100)` }).click();
|
await rightPanel.getByRole("option", { name: user.displayName }).click();
|
||||||
await rightPanel.getByRole("button", { name: "Share profile" }).click();
|
await rightPanel.getByRole("button", { name: "Share profile" }).click();
|
||||||
|
|
||||||
const dialog = page.getByRole("dialog", { name: "Share User" });
|
const dialog = page.getByRole("dialog", { name: "Share User" });
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ async function openSpaceContextMenu(page: Page, app: ElementAppPage, spaceName:
|
|||||||
return page.locator(".mx_SpacePanel_contextMenu");
|
return page.locator(".mx_SpacePanel_contextMenu");
|
||||||
}
|
}
|
||||||
|
|
||||||
function spaceCreateOptions(spaceName: string, roomIds: string[] = []): ICreateRoomOpts {
|
function spaceCreateOptions(serverName: string, spaceName: string, roomIds: string[] = []): ICreateRoomOpts {
|
||||||
return {
|
return {
|
||||||
creation_content: {
|
creation_content: {
|
||||||
type: "m.space",
|
type: "m.space",
|
||||||
@@ -35,17 +35,21 @@ function spaceCreateOptions(spaceName: string, roomIds: string[] = []): ICreateR
|
|||||||
name: spaceName,
|
name: spaceName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
...roomIds.map((r) => spaceChildInitialState(r)),
|
...roomIds.map((r) => spaceChildInitialState(serverName, r)),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function spaceChildInitialState(roomId: string, order?: string): ICreateRoomOpts["initial_state"]["0"] {
|
function spaceChildInitialState(
|
||||||
|
serverName: string,
|
||||||
|
roomId: string,
|
||||||
|
order?: string,
|
||||||
|
): ICreateRoomOpts["initial_state"]["0"] {
|
||||||
return {
|
return {
|
||||||
type: "m.space.child",
|
type: "m.space.child",
|
||||||
state_key: roomId,
|
state_key: roomId,
|
||||||
content: {
|
content: {
|
||||||
via: [roomId.split(":")[1]],
|
via: [serverName],
|
||||||
order,
|
order,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -240,7 +244,7 @@ test.describe("Spaces", () => {
|
|||||||
});
|
});
|
||||||
await expect(await app.getSpacePanelButton("My Space")).toBeVisible();
|
await expect(await app.getSpacePanelButton("My Space")).toBeVisible();
|
||||||
|
|
||||||
const roomId = await bot.createRoom(spaceCreateOptions("Space Space"));
|
const roomId = await bot.createRoom(spaceCreateOptions(user.homeServer, "Space Space"));
|
||||||
await bot.inviteUser(roomId, user.userId);
|
await bot.inviteUser(roomId, user.userId);
|
||||||
|
|
||||||
// Assert that `Space Space` is above `My Space` due to it being an invite
|
// Assert that `Space Space` is above `My Space` due to it being an invite
|
||||||
@@ -260,7 +264,10 @@ test.describe("Spaces", () => {
|
|||||||
const spaceName = "Spacey Mc. Space Space";
|
const spaceName = "Spacey Mc. Space Space";
|
||||||
await app.client.createSpace({
|
await app.client.createSpace({
|
||||||
name: spaceName,
|
name: spaceName,
|
||||||
initial_state: [spaceChildInitialState(roomId1), spaceChildInitialState(roomId2)],
|
initial_state: [
|
||||||
|
spaceChildInitialState(user.homeServer, roomId1),
|
||||||
|
spaceChildInitialState(user.homeServer, roomId2),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
await app.viewSpaceHomeByName(spaceName);
|
await app.viewSpaceHomeByName(spaceName);
|
||||||
@@ -287,7 +294,7 @@ test.describe("Spaces", () => {
|
|||||||
});
|
});
|
||||||
await app.client.createSpace({
|
await app.client.createSpace({
|
||||||
name: "Root Space",
|
name: "Root Space",
|
||||||
initial_state: [spaceChildInitialState(childSpaceId)],
|
initial_state: [spaceChildInitialState(user.homeServer, childSpaceId)],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Find collapsed Space panel
|
// Find collapsed Space panel
|
||||||
@@ -323,7 +330,7 @@ test.describe("Spaces", () => {
|
|||||||
name: "Test Room",
|
name: "Test Room",
|
||||||
topic: "This is a topic https://github.com/matrix-org/matrix-react-sdk/pull/10060 with a link",
|
topic: "This is a topic https://github.com/matrix-org/matrix-react-sdk/pull/10060 with a link",
|
||||||
});
|
});
|
||||||
const spaceId = await bot.createRoom(spaceCreateOptions("Test Space", [roomId]));
|
const spaceId = await bot.createRoom(spaceCreateOptions(user.homeServer, "Test Space", [roomId]));
|
||||||
await bot.inviteUser(spaceId, user.userId);
|
await bot.inviteUser(spaceId, user.userId);
|
||||||
|
|
||||||
await expect(await app.getSpacePanelButton("Test Space")).toBeVisible();
|
await expect(await app.getSpacePanelButton("Test Space")).toBeVisible();
|
||||||
@@ -361,9 +368,9 @@ test.describe("Spaces", () => {
|
|||||||
await app.client.createSpace({
|
await app.client.createSpace({
|
||||||
name: "Root Space",
|
name: "Root Space",
|
||||||
initial_state: [
|
initial_state: [
|
||||||
spaceChildInitialState(childSpaceId1, "a"),
|
spaceChildInitialState(user.homeServer, childSpaceId1, "a"),
|
||||||
spaceChildInitialState(childSpaceId2, "b"),
|
spaceChildInitialState(user.homeServer, childSpaceId2, "b"),
|
||||||
spaceChildInitialState(childSpaceId3, "c"),
|
spaceChildInitialState(user.homeServer, childSpaceId3, "c"),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
await app.viewSpaceByName("Root Space");
|
await app.viewSpaceByName("Root Space");
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ async function startDM(app: ElementAppPage, page: Page, name: string): Promise<v
|
|||||||
await result.first().click();
|
await result.first().click();
|
||||||
|
|
||||||
// send first message to start DM
|
// send first message to start DM
|
||||||
const locator = page.getByRole("textbox", { name: "Send a message…" });
|
const locator = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||||
await expect(locator).toBeFocused();
|
await expect(locator).toBeFocused();
|
||||||
await locator.fill("Hey!");
|
await locator.fill("Hey!");
|
||||||
await locator.press("Enter");
|
await locator.press("Enter");
|
||||||
@@ -260,7 +260,7 @@ test.describe("Spotlight", () => {
|
|||||||
|
|
||||||
// Send first message to actually start DM
|
// Send first message to actually start DM
|
||||||
await expect(roomHeaderName(page)).toHaveText(bot2.credentials.displayName);
|
await expect(roomHeaderName(page)).toHaveText(bot2.credentials.displayName);
|
||||||
const locator = page.getByRole("textbox", { name: "Send a message…" });
|
const locator = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||||
await locator.fill("Hey!");
|
await locator.fill("Hey!");
|
||||||
await locator.press("Enter");
|
await locator.press("Enter");
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ test.describe("Threads", () => {
|
|||||||
|
|
||||||
const roomViewLocator = page.locator(".mx_RoomView_body");
|
const roomViewLocator = page.locator(".mx_RoomView_body");
|
||||||
// User sends message
|
// User sends message
|
||||||
const textbox = roomViewLocator.getByRole("textbox", { name: "Send a message…" });
|
const textbox = roomViewLocator.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||||
await textbox.fill("Hello Mr. Bot");
|
await textbox.fill("Hello Mr. Bot");
|
||||||
await textbox.press("Enter");
|
await textbox.press("Enter");
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ test.describe("Threads", () => {
|
|||||||
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Group);
|
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Group);
|
||||||
|
|
||||||
// User responds in thread
|
// User responds in thread
|
||||||
locator = page.locator(".mx_ThreadView").getByRole("textbox", { name: "Send a message…" });
|
locator = page.locator(".mx_ThreadView").getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||||
await locator.fill("Test");
|
await locator.fill("Test");
|
||||||
await locator.press("Enter");
|
await locator.press("Enter");
|
||||||
|
|
||||||
@@ -262,7 +262,7 @@ test.describe("Threads", () => {
|
|||||||
await locator.locator(".mx_EventTile_line").click();
|
await locator.locator(".mx_EventTile_line").click();
|
||||||
|
|
||||||
// User responds & asserts
|
// User responds & asserts
|
||||||
locator = page.locator(".mx_ThreadView").getByRole("textbox", { name: "Send a message…" });
|
locator = page.locator(".mx_ThreadView").getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||||
await locator.fill("Great!");
|
await locator.fill("Great!");
|
||||||
await locator.press("Enter");
|
await locator.press("Enter");
|
||||||
|
|
||||||
@@ -335,8 +335,8 @@ test.describe("Threads", () => {
|
|||||||
|
|
||||||
// Send message
|
// Send message
|
||||||
const locator = page.locator(".mx_RoomView_body");
|
const locator = page.locator(".mx_RoomView_body");
|
||||||
await locator.getByRole("textbox", { name: "Send a message…" }).fill("Hello Mr. Bot");
|
await locator.getByRole("textbox", { name: "Send an unencrypted message…" }).fill("Hello Mr. Bot");
|
||||||
await locator.getByRole("textbox", { name: "Send a message…" }).press("Enter");
|
await locator.getByRole("textbox", { name: "Send an unencrypted message…" }).press("Enter");
|
||||||
// Create thread
|
// Create thread
|
||||||
const locator2 = locator.locator(".mx_EventTile[data-scroll-tokens]").filter({ hasText: "Hello Mr. Bot" });
|
const locator2 = locator.locator(".mx_EventTile[data-scroll-tokens]").filter({ hasText: "Hello Mr. Bot" });
|
||||||
await locator2.hover();
|
await locator2.hover();
|
||||||
@@ -366,7 +366,7 @@ test.describe("Threads", () => {
|
|||||||
|
|
||||||
let locator = page.locator(".mx_RoomView_body");
|
let locator = page.locator(".mx_RoomView_body");
|
||||||
// User sends message
|
// User sends message
|
||||||
let textbox = locator.getByRole("textbox", { name: "Send a message…" });
|
let textbox = locator.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||||
await textbox.fill("Hello Mr. Bot");
|
await textbox.fill("Hello Mr. Bot");
|
||||||
await textbox.press("Enter");
|
await textbox.press("Enter");
|
||||||
// Wait for message to send, get its ID and save as @threadId
|
// Wait for message to send, get its ID and save as @threadId
|
||||||
@@ -395,7 +395,7 @@ test.describe("Threads", () => {
|
|||||||
locator = page.locator(".mx_ThreadView");
|
locator = page.locator(".mx_ThreadView");
|
||||||
await locator.locator(".mx_EventTile_last").hover();
|
await locator.locator(".mx_EventTile_last").hover();
|
||||||
await locator.locator(".mx_EventTile_last").getByRole("button", { name: "Reply" }).click();
|
await locator.locator(".mx_EventTile_last").getByRole("button", { name: "Reply" }).click();
|
||||||
textbox = locator.getByRole("textbox", { name: "Reply to thread…" });
|
textbox = locator.getByRole("textbox", { name: "Reply to unencrypted thread…" });
|
||||||
await textbox.fill("Please come here");
|
await textbox.fill("Please come here");
|
||||||
await textbox.press("Enter");
|
await textbox.press("Enter");
|
||||||
// Wait until the reply is sent
|
// Wait until the reply is sent
|
||||||
@@ -414,7 +414,7 @@ test.describe("Threads", () => {
|
|||||||
|
|
||||||
// Send message
|
// Send message
|
||||||
let locator = page.locator(".mx_RoomView_body");
|
let locator = page.locator(".mx_RoomView_body");
|
||||||
let textbox = locator.getByRole("textbox", { name: "Send a message…" });
|
let textbox = locator.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||||
await textbox.fill("Hello Mr. Bot");
|
await textbox.fill("Hello Mr. Bot");
|
||||||
await textbox.press("Enter");
|
await textbox.press("Enter");
|
||||||
// Create thread
|
// Create thread
|
||||||
@@ -425,7 +425,7 @@ test.describe("Threads", () => {
|
|||||||
|
|
||||||
// Send message to thread
|
// Send message to thread
|
||||||
locator = page.locator(".mx_ThreadPanel");
|
locator = page.locator(".mx_ThreadPanel");
|
||||||
textbox = locator.getByRole("textbox", { name: "Send a message…" });
|
textbox = locator.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||||
await textbox.fill("Hello Mr. User");
|
await textbox.fill("Hello Mr. User");
|
||||||
await textbox.press("Enter");
|
await textbox.press("Enter");
|
||||||
await expect(locator.locator(".mx_EventTile_last").getByText("Hello Mr. User")).toBeAttached();
|
await expect(locator.locator(".mx_EventTile_last").getByText("Hello Mr. User")).toBeAttached();
|
||||||
@@ -456,7 +456,7 @@ test.describe("Threads", () => {
|
|||||||
*/
|
*/
|
||||||
const sendMessage = async (message: string) => {
|
const sendMessage = async (message: string) => {
|
||||||
const messageComposer = page.getByRole("region", { name: "Message composer" });
|
const messageComposer = page.getByRole("region", { name: "Message composer" });
|
||||||
const textbox = messageComposer.getByRole("textbox", { name: "Send a message…" });
|
const textbox = messageComposer.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||||
await textbox.fill(message);
|
await textbox.fill(message);
|
||||||
await textbox.press("Enter");
|
await textbox.press("Enter");
|
||||||
};
|
};
|
||||||
@@ -478,7 +478,7 @@ test.describe("Threads", () => {
|
|||||||
|
|
||||||
// Send a message in the thread
|
// Send a message in the thread
|
||||||
const threadPanel = page.locator(".mx_ThreadPanel");
|
const threadPanel = page.locator(".mx_ThreadPanel");
|
||||||
const textbox = threadPanel.getByRole("textbox", { name: "Send a message…" });
|
const textbox = threadPanel.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||||
await textbox.fill(threadMessage);
|
await textbox.fill(threadMessage);
|
||||||
await textbox.press("Enter");
|
await textbox.press("Enter");
|
||||||
await expect(threadPanel.locator(".mx_EventTile_last").getByText(threadMessage)).toBeVisible();
|
await expect(threadPanel.locator(".mx_EventTile_last").getByText(threadMessage)).toBeVisible();
|
||||||
|
|||||||
@@ -461,11 +461,11 @@ test.describe("Timeline", () => {
|
|||||||
// Send a emote
|
// Send a emote
|
||||||
await page
|
await page
|
||||||
.locator(".mx_RoomView_body")
|
.locator(".mx_RoomView_body")
|
||||||
.getByRole("textbox", { name: "Send a message…" })
|
.getByRole("textbox", { name: "Send an unencrypted message…" })
|
||||||
.fill("/me says hello to Mr. Bot");
|
.fill("/me says hello to Mr. Bot");
|
||||||
await page
|
await page
|
||||||
.locator(".mx_RoomView_body")
|
.locator(".mx_RoomView_body")
|
||||||
.getByRole("textbox", { name: "Send a message…" })
|
.getByRole("textbox", { name: "Send an unencrypted message…" })
|
||||||
.press("Enter");
|
.press("Enter");
|
||||||
// Check inline start margin of its avatar
|
// Check inline start margin of its avatar
|
||||||
// Here --right-padding is for the avatar on the message line
|
// Here --right-padding is for the avatar on the message line
|
||||||
|
|||||||
@@ -1,38 +1,49 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2024 New Vector Ltd.
|
Copyright 2024-2025 New Vector Ltd.
|
||||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
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.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MatrixAuthenticationServiceContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers";
|
import { MatrixAuthenticationServiceContainer } from "../../../testcontainers/mas.ts";
|
||||||
|
|
||||||
import { type Fixtures } from "../../../element-web-test.ts";
|
import { type Fixtures } from "../../../element-web-test.ts";
|
||||||
|
|
||||||
export const masHomeserver: Fixtures = {
|
export const masHomeserver: Fixtures = {
|
||||||
mas: [
|
mas: [
|
||||||
async ({ _homeserver: homeserver, logger, network, postgres, mailpit }, use) => {
|
async ({ _homeserver: homeserver, logger, network, postgres, mailpit }, use) => {
|
||||||
const config = {
|
const secret = "AnotherRandomSecret";
|
||||||
clients: [
|
|
||||||
{
|
|
||||||
client_id: "0000000000000000000SYNAPSE",
|
|
||||||
client_auth_method: "client_secret_basic",
|
|
||||||
client_secret: "SomeRandomSecret",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
matrix: {
|
|
||||||
homeserver: "localhost",
|
|
||||||
secret: "AnotherRandomSecret",
|
|
||||||
endpoint: "http://homeserver:8008",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const limits = { burst: 10, per_second: 10 };
|
||||||
const container = await new MatrixAuthenticationServiceContainer(postgres)
|
const container = await new MatrixAuthenticationServiceContainer(postgres)
|
||||||
.withNetwork(network)
|
.withNetwork(network)
|
||||||
.withNetworkAliases("mas")
|
.withNetworkAliases("mas")
|
||||||
.withLogConsumer(logger.getConsumer("mas"))
|
.withLogConsumer(logger.getConsumer("mas"))
|
||||||
.withConfig(config)
|
.withConfig({
|
||||||
|
matrix: {
|
||||||
|
kind: "synapse",
|
||||||
|
homeserver: "localhost",
|
||||||
|
secret,
|
||||||
|
endpoint: "http://homeserver:8008",
|
||||||
|
},
|
||||||
|
rate_limiting: {
|
||||||
|
login: {
|
||||||
|
per_ip: limits,
|
||||||
|
per_account: limits,
|
||||||
|
},
|
||||||
|
registration: limits,
|
||||||
|
email_authentication: {
|
||||||
|
per_ip: limits,
|
||||||
|
per_address: limits,
|
||||||
|
emails_per_session: limits,
|
||||||
|
attempt_per_session: limits,
|
||||||
|
},
|
||||||
|
account_recovery: {
|
||||||
|
per_ip: limits,
|
||||||
|
per_address: limits,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
.start();
|
.start();
|
||||||
|
|
||||||
homeserver.withConfig({
|
homeserver.withConfig({
|
||||||
@@ -40,16 +51,10 @@ export const masHomeserver: Fixtures = {
|
|||||||
enable_registration_without_verification: undefined,
|
enable_registration_without_verification: undefined,
|
||||||
disable_msisdn_registration: undefined,
|
disable_msisdn_registration: undefined,
|
||||||
password_config: undefined,
|
password_config: undefined,
|
||||||
experimental_features: {
|
matrix_authentication_service: {
|
||||||
msc3861: {
|
enabled: true,
|
||||||
enabled: true,
|
endpoint: "http://mas:8080/",
|
||||||
issuer: `http://mas:8080/`,
|
secret,
|
||||||
introspection_endpoint: "http://mas:8080/oauth2/introspect",
|
|
||||||
client_id: config.clients[0].client_id,
|
|
||||||
client_auth_method: config.clients[0].client_auth_method,
|
|
||||||
client_secret: config.clients[0].client_secret,
|
|
||||||
admin_token: config.matrix.secret,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -59,28 +64,6 @@ export const masHomeserver: Fixtures = {
|
|||||||
{ scope: "worker" },
|
{ scope: "worker" },
|
||||||
],
|
],
|
||||||
|
|
||||||
config: async ({ homeserver, context, mas }, use) => {
|
|
||||||
const issuer = `${mas.baseUrl}/`;
|
|
||||||
const wellKnown = {
|
|
||||||
"m.homeserver": {
|
|
||||||
base_url: homeserver.baseUrl,
|
|
||||||
},
|
|
||||||
"org.matrix.msc2965.authentication": {
|
|
||||||
issuer,
|
|
||||||
account: `${issuer}account`,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ensure org.matrix.msc2965.authentication is in well-known
|
|
||||||
await context.route("https://localhost/.well-known/matrix/client", async (route) => {
|
|
||||||
await route.fulfill({ json: wellKnown });
|
|
||||||
});
|
|
||||||
|
|
||||||
await use({
|
|
||||||
default_server_config: wellKnown,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
context: async ({ homeserverType, context }, use, testInfo) => {
|
context: async ({ homeserverType, context }, use, testInfo) => {
|
||||||
testInfo.skip(homeserverType !== "synapse", "does not yet support MAS");
|
testInfo.skip(homeserverType !== "synapse", "does not yet support MAS");
|
||||||
await use(context);
|
await use(context);
|
||||||
|
|||||||
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 268 B |
|
After Width: | Height: | Size: 406 B |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 74 KiB |