mirror of
https://github.com/element-hq/element-web.git
synced 2025-12-11 01:40:42 +00:00
Compare commits
1 Commits
t3chguy/pr
...
t3chguy/ca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d82dc2e06 |
@@ -1,5 +1,5 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: ["matrix-org", "eslint-plugin-react-compiler"],
|
plugins: ["matrix-org"],
|
||||||
extends: ["plugin:matrix-org/babel", "plugin:matrix-org/react", "plugin:matrix-org/a11y"],
|
extends: ["plugin:matrix-org/babel", "plugin:matrix-org/react", "plugin:matrix-org/a11y"],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: ["./tsconfig.json"],
|
project: ["./tsconfig.json"],
|
||||||
@@ -170,8 +170,6 @@ module.exports = {
|
|||||||
"jsx-a11y/role-supports-aria-props": "off",
|
"jsx-a11y/role-supports-aria-props": "off",
|
||||||
|
|
||||||
"matrix-org/require-copyright-header": "error",
|
"matrix-org/require-copyright-header": "error",
|
||||||
|
|
||||||
"react-compiler/react-compiler": "error",
|
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
@@ -264,7 +262,6 @@ module.exports = {
|
|||||||
|
|
||||||
// These are fine in tests
|
// These are fine in tests
|
||||||
"no-restricted-globals": "off",
|
"no-restricted-globals": "off",
|
||||||
"react-compiler/react-compiler": "off",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -274,7 +271,6 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
"react-hooks/rules-of-hooks": ["off"],
|
"react-hooks/rules-of-hooks": ["off"],
|
||||||
"@typescript-eslint/no-floating-promises": ["error"],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
18
.github/CODEOWNERS
vendored
18
.github/CODEOWNERS
vendored
@@ -3,17 +3,13 @@
|
|||||||
/package.json @element-hq/element-web-team
|
/package.json @element-hq/element-web-team
|
||||||
/yarn.lock @element-hq/element-web-team
|
/yarn.lock @element-hq/element-web-team
|
||||||
|
|
||||||
/src/SecurityManager.ts @element-hq/element-crypto-web-reviewers
|
/src/SecurityManager.ts @element-hq/element-crypto-web-reviewers
|
||||||
/test/SecurityManager-test.ts @element-hq/element-crypto-web-reviewers
|
/test/SecurityManager-test.ts @element-hq/element-crypto-web-reviewers
|
||||||
/src/async-components/views/dialogs/security/ @element-hq/element-crypto-web-reviewers
|
/src/async-components/views/dialogs/security/ @element-hq/element-crypto-web-reviewers
|
||||||
/src/components/views/dialogs/security/ @element-hq/element-crypto-web-reviewers
|
/src/components/views/dialogs/security/ @element-hq/element-crypto-web-reviewers
|
||||||
/test/components/views/dialogs/security/ @element-hq/element-crypto-web-reviewers
|
/test/components/views/dialogs/security/ @element-hq/element-crypto-web-reviewers
|
||||||
/src/stores/SetupEncryptionStore.ts @element-hq/element-crypto-web-reviewers
|
/src/stores/SetupEncryptionStore.ts @element-hq/element-crypto-web-reviewers
|
||||||
/test/stores/SetupEncryptionStore-test.ts @element-hq/element-crypto-web-reviewers
|
/test/stores/SetupEncryptionStore-test.ts @element-hq/element-crypto-web-reviewers
|
||||||
/src/components/views/settings/tabs/user/EncryptionUserSettingsTab.tsx @element-hq/element-crypto-web-reviewers
|
|
||||||
/src/src/components/views/settings/encryption/ @element-hq/element-crypto-web-reviewers
|
|
||||||
/test/unit-tests/components/views/settings/encryption/ @element-hq/element-crypto-web-reviewers
|
|
||||||
/playwright/e2e/settings/encryption-user-tab/ @element-hq/element-crypto-web-reviewers
|
|
||||||
|
|
||||||
# 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
|
||||||
|
|||||||
9
.github/labels.yml
vendored
9
.github/labels.yml
vendored
@@ -235,15 +235,6 @@
|
|||||||
- name: "Z-Flaky-Test"
|
- name: "Z-Flaky-Test"
|
||||||
description: "A test is raising false alarms"
|
description: "A test is raising false alarms"
|
||||||
color: "ededed"
|
color: "ededed"
|
||||||
- name: "Z-Flaky-Test-Chrome"
|
|
||||||
description: "Flaky playwright test in Chrome"
|
|
||||||
color: "ededed"
|
|
||||||
- name: "Z-Flaky-Test-Firefox"
|
|
||||||
description: "Flaky playwright test in Firefox"
|
|
||||||
color: "ededed"
|
|
||||||
- name: "Z-Flaky-Test-Webkit"
|
|
||||||
description: "Flaky playwright test in Webkit"
|
|
||||||
color: "ededed"
|
|
||||||
- name: "Z-Flaky-Jest-Test"
|
- name: "Z-Flaky-Jest-Test"
|
||||||
description: "A Jest test is raising false alarms"
|
description: "A Jest test is raising false alarms"
|
||||||
color: "ededed"
|
color: "ededed"
|
||||||
|
|||||||
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@@ -27,17 +27,10 @@ jobs:
|
|||||||
- macos-14
|
- macos-14
|
||||||
isDevelop:
|
isDevelop:
|
||||||
- ${{ github.event_name == 'push' && github.ref_name == 'develop' }}
|
- ${{ github.event_name == 'push' && github.ref_name == 'develop' }}
|
||||||
isPullRequest:
|
|
||||||
- ${{ github.event_name == 'pull_request' }}
|
|
||||||
# Skip the ubuntu-24.04 build for the develop branch as the dedicated CD build_develop workflow handles that
|
# Skip the ubuntu-24.04 build for the develop branch as the dedicated CD build_develop workflow handles that
|
||||||
# Skip the non-linux builds for pull requests as Windows is awfully slow, so run in merge queue only
|
|
||||||
exclude:
|
exclude:
|
||||||
- isDevelop: true
|
- isDevelop: true
|
||||||
image: ubuntu-24.04
|
image: ubuntu-24.04
|
||||||
- isPullRequest: true
|
|
||||||
image: windows-2022
|
|
||||||
- isPullRequest: true
|
|
||||||
image: macos-14
|
|
||||||
runs-on: ${{ matrix.image }}
|
runs-on: ${{ matrix.image }}
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
|
|||||||
1
.github/workflows/deploy.yml
vendored
1
.github/workflows/deploy.yml
vendored
@@ -96,4 +96,3 @@ jobs:
|
|||||||
projectName: ${{ env.SITE == 'staging.element.io' && 'element-web-staging' || 'element-web' }}
|
projectName: ${{ env.SITE == 'staging.element.io' && 'element-web-staging' || 'element-web' }}
|
||||||
directory: _deploy
|
directory: _deploy
|
||||||
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||||
branch: main
|
|
||||||
|
|||||||
21
.github/workflows/end-to-end-tests.yaml
vendored
21
.github/workflows/end-to-end-tests.yaml
vendored
@@ -48,6 +48,7 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
num-runners: ${{ env.NUM_RUNNERS }}
|
num-runners: ${{ env.NUM_RUNNERS }}
|
||||||
runners-matrix: ${{ steps.runner-vars.outputs.matrix }}
|
runners-matrix: ${{ steps.runner-vars.outputs.matrix }}
|
||||||
|
docker-cache-key: ${{ steps.runner-vars.outputs.docker-cache-key }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -80,6 +81,12 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
yarn build
|
yarn build
|
||||||
|
|
||||||
|
# Heuristic for calculating a cache key which is based on all images we pass to testcontainers
|
||||||
|
- name: Calculate docker cache key
|
||||||
|
run: |
|
||||||
|
grep -hr "Container(\"" --exclude-dir=snapshots --exclude-dir=sample-files playwright >> _docker_cache_key
|
||||||
|
grep -hr -C1 "super(" playwright/testcontainers/ >> _docker_cache_key
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
@@ -90,11 +97,14 @@ jobs:
|
|||||||
- name: Calculate runner variables
|
- name: Calculate runner variables
|
||||||
id: runner-vars
|
id: runner-vars
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v7
|
||||||
|
env:
|
||||||
|
DOCKER_CACHE_KEY: ${{ hashFiles('_docker_cache_key') }}
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const numRunners = parseInt(process.env.NUM_RUNNERS, 10);
|
const numRunners = parseInt(process.env.NUM_RUNNERS, 10);
|
||||||
const matrix = Array.from({ length: numRunners }, (_, i) => i + 1);
|
const matrix = Array.from({ length: numRunners }, (_, i) => i + 1);
|
||||||
core.setOutput("matrix", JSON.stringify(matrix));
|
core.setOutput("matrix", JSON.stringify(matrix));
|
||||||
|
core.setOutput("docker-cache-key", process.env.DOCKER_CACHE_KEY);
|
||||||
|
|
||||||
playwright:
|
playwright:
|
||||||
name: "Run Tests [${{ matrix.project }}] ${{ matrix.runner }}/${{ needs.build.outputs.num-runners }}"
|
name: "Run Tests [${{ matrix.project }}] ${{ matrix.runner }}/${{ needs.build.outputs.num-runners }}"
|
||||||
@@ -114,8 +124,6 @@ jobs:
|
|||||||
- Chrome
|
- Chrome
|
||||||
- Firefox
|
- Firefox
|
||||||
- WebKit
|
- WebKit
|
||||||
- Dendrite
|
|
||||||
- Pinecone
|
|
||||||
runAllTests:
|
runAllTests:
|
||||||
- ${{ github.event_name == 'schedule' || contains(github.event.pull_request.labels.*.name, 'X-Run-All-Tests') }}
|
- ${{ github.event_name == 'schedule' || contains(github.event.pull_request.labels.*.name, 'X-Run-All-Tests') }}
|
||||||
# Skip the Firefox & Safari runs unless this was a cron trigger or PR has X-Run-All-Tests label
|
# Skip the Firefox & Safari runs unless this was a cron trigger or PR has X-Run-All-Tests label
|
||||||
@@ -124,10 +132,6 @@ jobs:
|
|||||||
project: Firefox
|
project: Firefox
|
||||||
- runAllTests: false
|
- runAllTests: false
|
||||||
project: WebKit
|
project: WebKit
|
||||||
- runAllTests: false
|
|
||||||
project: Dendrite
|
|
||||||
- runAllTests: false
|
|
||||||
project: Pinecone
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -170,6 +174,11 @@ jobs:
|
|||||||
if: matrix.project == 'WebKit' && steps.playwright-cache.outputs.cache-hit == 'true'
|
if: matrix.project == 'WebKit' && steps.playwright-cache.outputs.cache-hit == 'true'
|
||||||
run: yarn playwright install-deps webkit
|
run: yarn playwright install-deps webkit
|
||||||
|
|
||||||
|
- name: Docker image cache
|
||||||
|
uses: ScribeMD/docker-cache@fb28c93772363301b8d0a6072ce850224b73f74e # 0.5.0
|
||||||
|
with:
|
||||||
|
key: ${{ needs.build.outputs.docker-cache-key }}
|
||||||
|
|
||||||
# We skip tests tagged with @mergequeue when running on PRs, but run them in MQ and everywhere else
|
# We skip tests tagged with @mergequeue when running on PRs, but run them in MQ and everywhere else
|
||||||
- name: Run Playwright tests
|
- name: Run Playwright tests
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ jobs:
|
|||||||
docker pull "$IMAGE"
|
docker pull "$IMAGE"
|
||||||
INSPECT=$(docker inspect --format='{{index .RepoDigests 0}}' "$IMAGE")
|
INSPECT=$(docker inspect --format='{{index .RepoDigests 0}}' "$IMAGE")
|
||||||
DIGEST=${INSPECT#*@}
|
DIGEST=${INSPECT#*@}
|
||||||
sed -i "s/const TAG.*/const TAG = \"develop@$DIGEST\";/" playwright/testcontainers/synapse.ts
|
sed -i "s,`$IMAGE.*`,`$IMAGE@$DIGEST`," playwright/testcontainers/synapse.ts
|
||||||
env:
|
env:
|
||||||
IMAGE: ghcr.io/element-hq/synapse:develop
|
IMAGE: ghcr.io/element-hq/synapse:develop
|
||||||
|
|
||||||
|
|||||||
6
.github/workflows/static_analysis.yaml
vendored
6
.github/workflows/static_analysis.yaml
vendored
@@ -132,3 +132,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Run linter
|
- name: Run linter
|
||||||
run: "yarn run lint:knip"
|
run: "yarn run lint:knip"
|
||||||
|
|
||||||
|
- name: Install Deps
|
||||||
|
run: "scripts/layered.sh"
|
||||||
|
|
||||||
|
- name: Dead Code Analysis
|
||||||
|
run: "yarn run analyse:unused-exports"
|
||||||
|
|||||||
18
CHANGELOG.md
18
CHANGELOG.md
@@ -1,21 +1,3 @@
|
|||||||
Changes in [1.11.90](https://github.com/element-hq/element-web/releases/tag/v1.11.90) (2025-01-14)
|
|
||||||
==================================================================================================
|
|
||||||
## ✨ Features
|
|
||||||
|
|
||||||
* Docker: run as non-root ([#28849](https://github.com/element-hq/element-web/pull/28849)). Contributed by @richvdh.
|
|
||||||
* Docker: allow configuration of HTTP listen port via env var ([#28840](https://github.com/element-hq/element-web/pull/28840)). Contributed by @richvdh.
|
|
||||||
* Update matrix-wysiwyg to consume WASM asset ([#28838](https://github.com/element-hq/element-web/pull/28838)). Contributed by @t3chguy.
|
|
||||||
* OIDC settings tweaks ([#28787](https://github.com/element-hq/element-web/pull/28787)). Contributed by @t3chguy.
|
|
||||||
* Delabs native OIDC support ([#28615](https://github.com/element-hq/element-web/pull/28615)). Contributed by @t3chguy.
|
|
||||||
* Move room header info button to right-most position ([#28754](https://github.com/element-hq/element-web/pull/28754)). Contributed by @t3chguy.
|
|
||||||
* Enable key backup by default ([#28691](https://github.com/element-hq/element-web/pull/28691)). Contributed by @dbkr.
|
|
||||||
|
|
||||||
## 🐛 Bug Fixes
|
|
||||||
|
|
||||||
* Fix building the automations mermaid diagram ([#28881](https://github.com/element-hq/element-web/pull/28881)). Contributed by @dbkr.
|
|
||||||
* Playwright: wait for the network listener on the postgres db ([#28808](https://github.com/element-hq/element-web/pull/28808)). Contributed by @dbkr.
|
|
||||||
|
|
||||||
|
|
||||||
Changes in [1.11.89](https://github.com/element-hq/element-web/releases/tag/v1.11.89) (2024-12-18)
|
Changes in [1.11.89](https://github.com/element-hq/element-web/releases/tag/v1.11.89) (2024-12-18)
|
||||||
==================================================================================================
|
==================================================================================================
|
||||||
This is a patch release to fix a bug which could prevent loading stored crypto state from storage, and also to fix URL previews when switching back to a room.
|
This is a patch release to fix a bug which could prevent loading stored crypto state from storage, and also to fix URL previews when switching back to a room.
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ class MockMap extends EventEmitter {
|
|||||||
setCenter = jest.fn();
|
setCenter = jest.fn();
|
||||||
setStyle = jest.fn();
|
setStyle = jest.fn();
|
||||||
fitBounds = jest.fn();
|
fitBounds = jest.fn();
|
||||||
remove = jest.fn();
|
|
||||||
}
|
}
|
||||||
const MockMapInstance = new MockMap();
|
const MockMapInstance = new MockMap();
|
||||||
|
|
||||||
|
|||||||
@@ -66,20 +66,17 @@ as is typical for Playwright tests. Likewise, tests live in `playwright/e2e`.
|
|||||||
of Synapse/Dendrite. These servers are what Element-web runs against in the tests.
|
of Synapse/Dendrite. These servers are what Element-web runs against in the tests.
|
||||||
|
|
||||||
Synapse can be launched with different configurations in order to test element
|
Synapse can be launched with different configurations in order to test element
|
||||||
in different configurations. You can specify `synapseConfig` as such:
|
in different configurations. You can specify `synapseConfigOptions` as such:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
test.use({
|
test.use({
|
||||||
synapseConfig: {
|
synapseConfigOptions: {
|
||||||
// The config options to pass to the Synapse instance
|
// The config options to pass to the Synapse instance
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
The appropriate homeserver will be launched by the Playwright worker and reused for all tests which match the worker configuration.
|
The appropriate homeserver will be launched by the Playwright worker and reused for all tests which match the worker configuration.
|
||||||
Due to homeservers being reused between tests, please use unique names for any rooms put into the room directory as
|
|
||||||
they may be visible from other tests, the suggested approach is to use `testInfo.testId` within the name or lodash's uniqueId.
|
|
||||||
We remove public rooms from the room directory between tests but deleting users doesn't have a homeserver agnostic solution.
|
|
||||||
The logs from testcontainers will be attached to any reports output from Playwright.
|
The logs from testcontainers will be attached to any reports output from Playwright.
|
||||||
|
|
||||||
## Writing Tests
|
## Writing Tests
|
||||||
|
|||||||
13
knip.ts
13
knip.ts
@@ -10,13 +10,13 @@ export default {
|
|||||||
"playwright/**",
|
"playwright/**",
|
||||||
"test/**",
|
"test/**",
|
||||||
"res/decoder-ring/**",
|
"res/decoder-ring/**",
|
||||||
"res/jitsi_external_api.min.js",
|
|
||||||
"docs/**",
|
|
||||||
// Used by jest
|
|
||||||
"__mocks__/maplibre-gl.js",
|
|
||||||
],
|
],
|
||||||
project: ["**/*.{js,ts,jsx,tsx}"],
|
project: ["**/*.{js,ts,jsx,tsx}"],
|
||||||
ignore: [
|
ignore: [
|
||||||
|
"docs/**",
|
||||||
|
"res/jitsi_external_api.min.js",
|
||||||
|
// Used by jest
|
||||||
|
"__mocks__/maplibre-gl.js",
|
||||||
// Keep for now
|
// Keep for now
|
||||||
"src/hooks/useLocalStorageState.ts",
|
"src/hooks/useLocalStorageState.ts",
|
||||||
"src/components/views/elements/InfoTooltip.tsx",
|
"src/components/views/elements/InfoTooltip.tsx",
|
||||||
@@ -37,8 +37,13 @@ export default {
|
|||||||
// False positive
|
// False positive
|
||||||
"sw.js",
|
"sw.js",
|
||||||
// Used by webpack
|
// Used by webpack
|
||||||
|
"buffer",
|
||||||
"process",
|
"process",
|
||||||
"util",
|
"util",
|
||||||
|
// Used by workflows
|
||||||
|
"ts-prune",
|
||||||
|
// Required due to bug in bloom-filters https://github.com/Callidon/bloom-filters/issues/75
|
||||||
|
"@types/seedrandom",
|
||||||
],
|
],
|
||||||
ignoreBinaries: [
|
ignoreBinaries: [
|
||||||
// Used in scripts & workflows
|
// Used in scripts & workflows
|
||||||
|
|||||||
12
package.json
12
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "element-web",
|
"name": "element-web",
|
||||||
"version": "1.11.90",
|
"version": "1.11.89",
|
||||||
"description": "A feature-rich client for Matrix.org",
|
"description": "A feature-rich client for Matrix.org",
|
||||||
"author": "New Vector Ltd.",
|
"author": "New Vector Ltd.",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -66,6 +66,7 @@
|
|||||||
"test:playwright:screenshots:build": "docker build playwright -t element-web-playwright",
|
"test:playwright:screenshots:build": "docker build playwright -t element-web-playwright",
|
||||||
"test:playwright:screenshots:run": "docker run --rm --network host -e BASE_URL -e CI -v $(pwd):/work/ -v $(node -e 'console.log(require(`path`).dirname(require.resolve(`matrix-js-sdk/package.json`)))'):/work/node_modules/matrix-js-sdk -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/:/tmp/ -it element-web-playwright --grep @screenshot --project=Chrome",
|
"test:playwright:screenshots:run": "docker run --rm --network host -e BASE_URL -e CI -v $(pwd):/work/ -v $(node -e 'console.log(require(`path`).dirname(require.resolve(`matrix-js-sdk/package.json`)))'):/work/node_modules/matrix-js-sdk -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/:/tmp/ -it element-web-playwright --grep @screenshot --project=Chrome",
|
||||||
"coverage": "yarn test --coverage",
|
"coverage": "yarn test --coverage",
|
||||||
|
"analyse:unused-exports": "ts-node ./scripts/analyse_unused_exports.ts",
|
||||||
"analyse:webpack-bundles": "webpack-bundle-analyzer webpack-stats.json webapp",
|
"analyse:webpack-bundles": "webpack-bundle-analyzer webpack-stats.json webapp",
|
||||||
"update:jitsi": "curl -s https://meet.element.io/libs/external_api.min.js > ./res/jitsi_external_api.min.js"
|
"update:jitsi": "curl -s https://meet.element.io/libs/external_api.min.js > ./res/jitsi_external_api.min.js"
|
||||||
},
|
},
|
||||||
@@ -89,7 +90,6 @@
|
|||||||
"@matrix-org/spec": "^1.7.0",
|
"@matrix-org/spec": "^1.7.0",
|
||||||
"@sentry/browser": "^8.0.0",
|
"@sentry/browser": "^8.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": "^2.1.0",
|
"@vector-im/compound-design-tokens": "^2.1.0",
|
||||||
"@vector-im/compound-web": "^7.5.0",
|
"@vector-im/compound-web": "^7.5.0",
|
||||||
"@vector-im/matrix-wysiwyg": "2.38.0",
|
"@vector-im/matrix-wysiwyg": "2.38.0",
|
||||||
@@ -144,7 +144,6 @@
|
|||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-focus-lock": "^2.5.1",
|
"react-focus-lock": "^2.5.1",
|
||||||
"react-transition-group": "^4.4.1",
|
"react-transition-group": "^4.4.1",
|
||||||
"react-virtualized": "^9.22.5",
|
|
||||||
"rfc4648": "^1.4.0",
|
"rfc4648": "^1.4.0",
|
||||||
"sanitize-filename": "^1.6.3",
|
"sanitize-filename": "^1.6.3",
|
||||||
"sanitize-html": "2.14.0",
|
"sanitize-html": "2.14.0",
|
||||||
@@ -152,7 +151,9 @@
|
|||||||
"temporal-polyfill": "^0.2.5",
|
"temporal-polyfill": "^0.2.5",
|
||||||
"ua-parser-js": "^1.0.2",
|
"ua-parser-js": "^1.0.2",
|
||||||
"uuid": "^11.0.0",
|
"uuid": "^11.0.0",
|
||||||
"what-input": "^5.2.10"
|
"what-input": "^5.2.10",
|
||||||
|
"@types/react-virtualized": "^9.21.30",
|
||||||
|
"react-virtualized": "^9.22.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@action-validator/cli": "^0.6.0",
|
"@action-validator/cli": "^0.6.0",
|
||||||
@@ -237,7 +238,6 @@
|
|||||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||||
"eslint-plugin-matrix-org": "^2.0.2",
|
"eslint-plugin-matrix-org": "^2.0.2",
|
||||||
"eslint-plugin-react": "^7.28.0",
|
"eslint-plugin-react": "^7.28.0",
|
||||||
"eslint-plugin-react-compiler": "^19.0.0-beta-df7b47d-20241124",
|
|
||||||
"eslint-plugin-react-hooks": "^5.0.0",
|
"eslint-plugin-react-hooks": "^5.0.0",
|
||||||
"eslint-plugin-unicorn": "^56.0.0",
|
"eslint-plugin-unicorn": "^56.0.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
@@ -287,6 +287,7 @@
|
|||||||
"terser-webpack-plugin": "^5.3.9",
|
"terser-webpack-plugin": "^5.3.9",
|
||||||
"testcontainers": "^10.16.0",
|
"testcontainers": "^10.16.0",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
|
"ts-prune": "^0.10.3",
|
||||||
"typescript": "5.7.2",
|
"typescript": "5.7.2",
|
||||||
"util": "^0.12.5",
|
"util": "^0.12.5",
|
||||||
"web-streams-polyfill": "^4.0.0",
|
"web-streams-polyfill": "^4.0.0",
|
||||||
@@ -294,7 +295,6 @@
|
|||||||
"webpack-bundle-analyzer": "^4.8.0",
|
"webpack-bundle-analyzer": "^4.8.0",
|
||||||
"webpack-cli": "^6.0.0",
|
"webpack-cli": "^6.0.0",
|
||||||
"webpack-dev-server": "^5.0.0",
|
"webpack-dev-server": "^5.0.0",
|
||||||
"webpack-retry-chunk-load-plugin": "^3.1.1",
|
|
||||||
"webpack-version-file-plugin": "^0.5.0",
|
"webpack-version-file-plugin": "^0.5.0",
|
||||||
"yaml": "^2.3.3"
|
"yaml": "^2.3.3"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,25 +8,19 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
|
|
||||||
import { defineConfig, devices } from "@playwright/test";
|
import { defineConfig, devices } from "@playwright/test";
|
||||||
|
|
||||||
import { Options } from "./playwright/services";
|
|
||||||
|
|
||||||
const baseURL = process.env["BASE_URL"] ?? "http://localhost:8080";
|
const baseURL = process.env["BASE_URL"] ?? "http://localhost:8080";
|
||||||
|
|
||||||
const chromeProject = {
|
export default defineConfig({
|
||||||
...devices["Desktop Chrome"],
|
|
||||||
channel: "chromium",
|
|
||||||
permissions: ["clipboard-write", "clipboard-read", "microphone"],
|
|
||||||
launchOptions: {
|
|
||||||
args: ["--use-fake-ui-for-media-stream", "--use-fake-device-for-media-stream", "--mute-audio"],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default defineConfig<Options>({
|
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: "Chrome",
|
name: "Chrome",
|
||||||
use: {
|
use: {
|
||||||
...chromeProject,
|
...devices["Desktop Chrome"],
|
||||||
|
channel: "chromium",
|
||||||
|
permissions: ["clipboard-write", "clipboard-read", "microphone"],
|
||||||
|
launchOptions: {
|
||||||
|
args: ["--use-fake-ui-for-media-stream", "--use-fake-device-for-media-stream", "--mute-audio"],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -54,22 +48,6 @@ export default defineConfig<Options>({
|
|||||||
},
|
},
|
||||||
ignoreSnapshots: true,
|
ignoreSnapshots: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Dendrite",
|
|
||||||
use: {
|
|
||||||
...chromeProject,
|
|
||||||
homeserverType: "dendrite",
|
|
||||||
},
|
|
||||||
ignoreSnapshots: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Pinecone",
|
|
||||||
use: {
|
|
||||||
...chromeProject,
|
|
||||||
homeserverType: "pinecone",
|
|
||||||
},
|
|
||||||
ignoreSnapshots: true,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
use: {
|
use: {
|
||||||
viewport: { width: 1280, height: 720 },
|
viewport: { width: 1280, height: 720 },
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ test.describe("Landmark navigation tests", () => {
|
|||||||
await expect(page.getByText("Bob joined the room")).toBeVisible();
|
await expect(page.getByText("Bob joined the room")).toBeVisible();
|
||||||
|
|
||||||
// Close the room
|
// Close the room
|
||||||
await page.goto("/#/home");
|
page.goto("/#/home");
|
||||||
|
|
||||||
// Pressing Control+F6 will first focus the space button
|
// Pressing Control+F6 will first focus the space button
|
||||||
await page.keyboard.press("ControlOrMeta+F6");
|
await page.keyboard.press("ControlOrMeta+F6");
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
import { expect, test } from "../../element-web-test";
|
import { expect, test } from "../../element-web-test";
|
||||||
|
|
||||||
test.use({
|
test.use({
|
||||||
synapseConfig: {
|
synapseConfigOptions: {
|
||||||
allow_guest_access: true,
|
allow_guest_access: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,14 +13,6 @@ import { SettingLevel } from "../../../src/settings/SettingLevel";
|
|||||||
import { Layout } from "../../../src/settings/enums/Layout";
|
import { Layout } from "../../../src/settings/enums/Layout";
|
||||||
import { ElementAppPage } from "../../pages/ElementAppPage";
|
import { ElementAppPage } from "../../pages/ElementAppPage";
|
||||||
|
|
||||||
// Find and click "Reply" button
|
|
||||||
const clickButtonReply = async (tile: Locator) => {
|
|
||||||
await expect(async () => {
|
|
||||||
await tile.hover();
|
|
||||||
await tile.getByRole("button", { name: "Reply", exact: true }).click();
|
|
||||||
}).toPass();
|
|
||||||
};
|
|
||||||
|
|
||||||
test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||||
test.use({
|
test.use({
|
||||||
displayName: "Hanako",
|
displayName: "Hanako",
|
||||||
@@ -230,7 +222,8 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
|
|
||||||
// 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");
|
||||||
await clickButtonReply(tile);
|
await tile.hover();
|
||||||
|
await tile.getByRole("button", { name: "Reply", exact: true }).click();
|
||||||
|
|
||||||
// Reply to the player with another audio file
|
// Reply to the player with another audio file
|
||||||
await uploadFile(page, "playwright/sample-files/1sec.ogg");
|
await uploadFile(page, "playwright/sample-files/1sec.ogg");
|
||||||
@@ -258,12 +251,18 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
|
|
||||||
const tile = page.locator(".mx_EventTile_last");
|
const tile = page.locator(".mx_EventTile_last");
|
||||||
|
|
||||||
|
// Find and click "Reply" button
|
||||||
|
const clickButtonReply = async () => {
|
||||||
|
await tile.hover();
|
||||||
|
await tile.getByRole("button", { name: "Reply", exact: true }).click();
|
||||||
|
};
|
||||||
|
|
||||||
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 .mx_AudioPlayer_container")).toBeVisible();
|
||||||
|
|
||||||
await clickButtonReply(tile);
|
await clickButtonReply();
|
||||||
|
|
||||||
// Reply to the player with another audio file
|
// Reply to the player with another audio file
|
||||||
await uploadFile(page, "playwright/sample-files/upload-second.ogg");
|
await uploadFile(page, "playwright/sample-files/upload-second.ogg");
|
||||||
@@ -271,7 +270,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
// 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 .mx_AudioPlayer_container")).toBeVisible();
|
||||||
|
|
||||||
await clickButtonReply(tile);
|
await clickButtonReply();
|
||||||
|
|
||||||
// Reply to the player with yet another audio file to create a reply chain
|
// Reply to the player with yet another audio file to create a reply chain
|
||||||
await uploadFile(page, "playwright/sample-files/upload-third.ogg");
|
await uploadFile(page, "playwright/sample-files/upload-third.ogg");
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ test.describe("HTML Export", () => {
|
|||||||
async ({ page, app, room }) => {
|
async ({ page, app, room }) => {
|
||||||
// Set a fixed time rather than masking off the line with the time in it: we don't need to worry
|
// Set a fixed time rather than masking off the line with the time in it: we don't need to worry
|
||||||
// about the width changing and we can actually test this line looks correct.
|
// about the width changing and we can actually test this line looks correct.
|
||||||
await page.clock.setSystemTime(new Date("2024-01-01T00:00:00Z"));
|
page.clock.setSystemTime(new Date("2024-01-01T00:00:00Z"));
|
||||||
|
|
||||||
// Send a bunch of messages to populate the room
|
// Send a bunch of messages to populate the room
|
||||||
for (let i = 1; i < 10; i++) {
|
for (let i = 1; i < 10; i++) {
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ test.describe("Composer", () => {
|
|||||||
// Type another
|
// Type another
|
||||||
await page.locator("div[contenteditable=true]").pressSequentially("my message 1");
|
await page.locator("div[contenteditable=true]").pressSequentially("my message 1");
|
||||||
// Send message
|
// Send message
|
||||||
await page.locator("div[contenteditable=true]").press("Enter");
|
page.locator("div[contenteditable=true]").press("Enter");
|
||||||
// It was sent
|
// It was sent
|
||||||
await expect(page.locator(".mx_EventTile_last .mx_EventTile_body").getByText("my message 1")).toBeVisible();
|
await expect(page.locator(".mx_EventTile_last .mx_EventTile_body").getByText("my message 1")).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ test.describe("Create Room", () => {
|
|||||||
// Submit
|
// Submit
|
||||||
await dialog.getByRole("button", { name: "Create room" }).click();
|
await dialog.getByRole("button", { name: "Create room" }).click();
|
||||||
|
|
||||||
await expect(page).toHaveURL(new RegExp(`/#/room/#test-room-1:${user.homeServer}`));
|
await expect(page).toHaveURL(/\/#\/room\/#test-room-1:localhost/);
|
||||||
const header = page.locator(".mx_RoomHeader");
|
const header = page.locator(".mx_RoomHeader");
|
||||||
await expect(header).toContainText(name);
|
await expect(header).toContainText(name);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,25 +13,25 @@ import { TestClientServerAPI } from "../csAPI";
|
|||||||
import { masHomeserver } from "../../plugins/homeserver/synapse/masHomeserver.ts";
|
import { masHomeserver } from "../../plugins/homeserver/synapse/masHomeserver.ts";
|
||||||
|
|
||||||
// These tests register an account with MAS because then we go through the "normal" registration flow
|
// These tests register an account with MAS because then we go through the "normal" registration flow
|
||||||
// and crypto gets set up. Using the 'user' fixture create a user and synthesizes an existing login,
|
// and crypto gets set up. Using the 'user' fixture create a a user an synthesizes an existing login,
|
||||||
// which is faster but leaves us without crypto set up.
|
// which is faster but leaves us without crypto set up.
|
||||||
test.use(masHomeserver);
|
test.use(masHomeserver);
|
||||||
test.describe("Encryption state after registration", () => {
|
test.describe("Encryption state after registration", () => {
|
||||||
test.skip(isDendrite, "does not yet support MAS");
|
test.skip(isDendrite, "does not yet support MAS");
|
||||||
|
|
||||||
test("Key backup is enabled by default", async ({ page, mailhogClient, app }, testInfo) => {
|
test("Key backup is enabled by default", async ({ page, mailhogClient, app }) => {
|
||||||
await page.goto("/#/login");
|
await page.goto("/#/login");
|
||||||
await page.getByRole("button", { name: "Continue" }).click();
|
await page.getByRole("button", { name: "Continue" }).click();
|
||||||
await registerAccountMas(page, mailhogClient, `alice_${testInfo.testId}`, "alice@email.com", "Pa$sW0rD!");
|
await registerAccountMas(page, mailhogClient, "alice", "alice@email.com", "Pa$sW0rD!");
|
||||||
|
|
||||||
await app.settings.openUserSettings("Security & Privacy");
|
await app.settings.openUserSettings("Security & Privacy");
|
||||||
await expect(page.getByText("This session is backing up your keys.")).toBeVisible();
|
await expect(page.getByText("This session is backing up your keys.")).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("user is prompted to set up recovery", async ({ page, mailhogClient, app }, testInfo) => {
|
test("user is prompted to set up recovery", async ({ page, mailhogClient, app }) => {
|
||||||
await page.goto("/#/login");
|
await page.goto("/#/login");
|
||||||
await page.getByRole("button", { name: "Continue" }).click();
|
await page.getByRole("button", { name: "Continue" }).click();
|
||||||
await registerAccountMas(page, mailhogClient, `alice_${testInfo.testId}`, "alice@email.com", "Pa$sW0rD!");
|
await registerAccountMas(page, mailhogClient, "alice", "alice@email.com", "Pa$sW0rD!");
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Add room" }).click();
|
await page.getByRole("button", { name: "Add room" }).click();
|
||||||
await page.getByRole("menuitem", { name: "New room" }).click();
|
await page.getByRole("menuitem", { name: "New room" }).click();
|
||||||
@@ -45,13 +45,8 @@ test.describe("Encryption state after registration", () => {
|
|||||||
test.describe("Key backup reset from elsewhere", () => {
|
test.describe("Key backup reset from elsewhere", () => {
|
||||||
test.skip(isDendrite, "does not yet support MAS");
|
test.skip(isDendrite, "does not yet support MAS");
|
||||||
|
|
||||||
test("Key backup is disabled when reset from elsewhere", async ({
|
test("Key backup is disabled when reset from elsewhere", async ({ page, mailhogClient, request, homeserver }) => {
|
||||||
page,
|
const testUsername = "alice";
|
||||||
mailhogClient,
|
|
||||||
request,
|
|
||||||
homeserver,
|
|
||||||
}, testInfo) => {
|
|
||||||
const testUsername = `alice_${testInfo.testId}`;
|
|
||||||
const testPassword = "Pa$sW0rD!";
|
const testPassword = "Pa$sW0rD!";
|
||||||
|
|
||||||
// there's a delay before keys are uploaded so the error doesn't appear immediately: use a fake
|
// there's a delay before keys are uploaded so the error doesn't appear immediately: use a fake
|
||||||
@@ -67,7 +62,8 @@ test.describe("Key backup reset from elsewhere", () => {
|
|||||||
await page.getByRole("textbox", { name: "Name" }).fill("test room");
|
await page.getByRole("textbox", { name: "Name" }).fill("test room");
|
||||||
await page.getByRole("button", { name: "Create room" }).click();
|
await page.getByRole("button", { name: "Create room" }).click();
|
||||||
|
|
||||||
const accessToken = await page.evaluate(() => window.mxMatrixClientPeg.get().getAccessToken());
|
// @ts-ignore - this runs in the browser scope where mxMatrixClientPeg is a thing. Here, it is not.
|
||||||
|
const accessToken = await page.evaluate(() => mxMatrixClientPeg.get().getAccessToken());
|
||||||
|
|
||||||
const csAPI = new TestClientServerAPI(request, homeserver, accessToken);
|
const csAPI = new TestClientServerAPI(request, homeserver, accessToken);
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
import { 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 { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
async function expectBackupVersionToBe(page: Page, version: string) {
|
async function expectBackupVersionToBe(page: Page, version: string) {
|
||||||
await expect(page.locator(".mx_SecureBackupPanel_statusList tr:nth-child(5) td")).toHaveText(
|
await expect(page.locator(".mx_SecureBackupPanel_statusList tr:nth-child(5) td")).toHaveText(
|
||||||
@@ -20,7 +19,6 @@ async function expectBackupVersionToBe(page: Page, version: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test.describe("Backups", () => {
|
test.describe("Backups", () => {
|
||||||
test.skip(isDendrite, "Dendrite lacks support for MSC3967 so requires additional auth here");
|
|
||||||
test.use({
|
test.use({
|
||||||
displayName: "Hanako",
|
displayName: "Hanako",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,10 +8,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
|
|
||||||
import { test, expect } from "../../element-web-test";
|
import { test, expect } from "../../element-web-test";
|
||||||
import { logIntoElement } from "./utils";
|
import { logIntoElement } from "./utils";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Complete security", () => {
|
test.describe("Complete security", () => {
|
||||||
test.skip(isDendrite, "Dendrite lacks support for MSC3967 so requires additional auth here");
|
|
||||||
test.use({
|
test.use({
|
||||||
displayName: "Jeff",
|
displayName: "Jeff",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { expect, test } from "../../element-web-test";
|
|||||||
import { autoJoin, copyAndContinue, createSharedRoomWithUser, enableKeyBackup, verify } from "./utils";
|
import { autoJoin, copyAndContinue, createSharedRoomWithUser, enableKeyBackup, verify } from "./utils";
|
||||||
import { Bot } from "../../pages/bot";
|
import { Bot } from "../../pages/bot";
|
||||||
import { ElementAppPage } from "../../pages/ElementAppPage";
|
import { ElementAppPage } from "../../pages/ElementAppPage";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
const checkDMRoom = async (page: Page) => {
|
const checkDMRoom = async (page: Page) => {
|
||||||
const body = page.locator(".mx_RoomView_body");
|
const body = page.locator(".mx_RoomView_body");
|
||||||
@@ -68,7 +67,6 @@ const bobJoin = async (page: Page, bob: Bot) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
test.describe("Cryptography", function () {
|
test.describe("Cryptography", function () {
|
||||||
test.skip(isDendrite, "Dendrite lacks support for MSC3967 so requires additional auth here");
|
|
||||||
test.use({
|
test.use({
|
||||||
displayName: "Alice",
|
displayName: "Alice",
|
||||||
botCreateOpts: {
|
botCreateOpts: {
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ test.describe("Cryptography", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test.describe("decryption failure messages", () => {
|
test.describe("decryption failure messages", () => {
|
||||||
test.skip(isDendrite, "Dendrite lacks support for MSC3967 so requires additional auth here");
|
|
||||||
|
|
||||||
test("should handle device-relative historical messages", async ({
|
test("should handle device-relative historical messages", async ({
|
||||||
homeserver,
|
homeserver,
|
||||||
page,
|
page,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ function getMemberTileByName(page: Page, name: string): Locator {
|
|||||||
|
|
||||||
test.use({
|
test.use({
|
||||||
displayName: NAME,
|
displayName: NAME,
|
||||||
synapseConfig: {
|
synapseConfigOptions: {
|
||||||
experimental_features: {
|
experimental_features: {
|
||||||
msc2697_enabled: false,
|
msc2697_enabled: false,
|
||||||
msc3814_enabled: true,
|
msc3814_enabled: true,
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import {
|
|||||||
awaitVerifier,
|
awaitVerifier,
|
||||||
checkDeviceIsConnectedKeyBackup,
|
checkDeviceIsConnectedKeyBackup,
|
||||||
checkDeviceIsCrossSigned,
|
checkDeviceIsCrossSigned,
|
||||||
createBot,
|
|
||||||
doTwoWaySasVerification,
|
doTwoWaySasVerification,
|
||||||
logIntoElement,
|
logIntoElement,
|
||||||
waitForVerificationRequest,
|
waitForVerificationRequest,
|
||||||
@@ -29,9 +28,29 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
|
|||||||
let expectedBackupVersion: string;
|
let expectedBackupVersion: string;
|
||||||
|
|
||||||
test.beforeEach(async ({ page, homeserver, credentials }) => {
|
test.beforeEach(async ({ page, homeserver, credentials }) => {
|
||||||
const res = await createBot(page, homeserver, credentials);
|
// Visit the login page of the app, to load the matrix sdk
|
||||||
aliceBotClient = res.botClient;
|
await page.goto("/#/login");
|
||||||
expectedBackupVersion = res.expectedBackupVersion;
|
|
||||||
|
// wait for the page to load
|
||||||
|
await page.waitForSelector(".mx_AuthPage", { timeout: 30000 });
|
||||||
|
|
||||||
|
// Create a new device for alice
|
||||||
|
aliceBotClient = new Bot(page, homeserver, {
|
||||||
|
bootstrapCrossSigning: true,
|
||||||
|
bootstrapSecretStorage: true,
|
||||||
|
});
|
||||||
|
aliceBotClient.setCredentials(credentials);
|
||||||
|
|
||||||
|
// Backup is prepared in the background. Poll until it is ready.
|
||||||
|
const botClientHandle = await aliceBotClient.prepareClient();
|
||||||
|
await expect
|
||||||
|
.poll(async () => {
|
||||||
|
expectedBackupVersion = await botClientHandle.evaluate((cli) =>
|
||||||
|
cli.getCrypto()!.getActiveSessionBackupVersion(),
|
||||||
|
);
|
||||||
|
return expectedBackupVersion;
|
||||||
|
})
|
||||||
|
.not.toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Click the "Verify with another device" button, and have the bot client auto-accept it.
|
// Click the "Verify with another device" button, and have the bot client auto-accept it.
|
||||||
@@ -193,17 +212,16 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
|
|||||||
/* on the bot side, wait for the verifier to exist ... */
|
/* on the bot side, wait for the verifier to exist ... */
|
||||||
const verifier = await awaitVerifier(botVerificationRequest);
|
const verifier = await awaitVerifier(botVerificationRequest);
|
||||||
// ... confirm ...
|
// ... confirm ...
|
||||||
void botVerificationRequest.evaluate((verificationRequest) => verificationRequest.verifier.verify());
|
botVerificationRequest.evaluate((verificationRequest) => verificationRequest.verifier.verify());
|
||||||
// ... and then check the emoji match
|
// ... and then check the emoji match
|
||||||
await doTwoWaySasVerification(page, verifier);
|
await doTwoWaySasVerification(page, verifier);
|
||||||
|
|
||||||
/* And we're all done! */
|
/* And we're all done! */
|
||||||
const infoDialog = page.locator(".mx_InfoDialog");
|
const infoDialog = page.locator(".mx_InfoDialog");
|
||||||
await infoDialog.getByRole("button", { name: "They match" }).click();
|
await infoDialog.getByRole("button", { name: "They match" }).click();
|
||||||
// We don't assert the full string as the device name is unset on Synapse but set to the user ID on Dendrite
|
await expect(
|
||||||
await expect(infoDialog.getByText(`You've successfully verified`)).toContainText(
|
infoDialog.getByText(`You've successfully verified (${aliceBotClient.credentials.deviceId})!`),
|
||||||
`(${aliceBotClient.credentials.deviceId})`,
|
).toBeVisible();
|
||||||
);
|
|
||||||
await infoDialog.getByRole("button", { name: "Got it" }).click();
|
await infoDialog.getByRole("button", { name: "Got it" }).click();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -66,9 +66,6 @@ test.describe("Cryptography", function () {
|
|||||||
// Bob has a second, not cross-signed, device
|
// Bob has a second, not cross-signed, device
|
||||||
const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob);
|
const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob);
|
||||||
|
|
||||||
// Dismiss the toast nagging us to set up recovery otherwise it gets in the way of clicking the room list
|
|
||||||
await page.getByRole("button", { name: "Not now" }).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",
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { bootstrapCrossSigningForClient } from "../../pages/client.ts";
|
|||||||
|
|
||||||
/** Tests for the "invisible crypto" behaviour -- i.e., when the "exclude insecure devices" setting is enabled */
|
/** Tests for the "invisible crypto" behaviour -- i.e., when the "exclude insecure devices" setting is enabled */
|
||||||
test.describe("Invisible cryptography", () => {
|
test.describe("Invisible cryptography", () => {
|
||||||
test.slow();
|
|
||||||
test.use({
|
test.use({
|
||||||
displayName: "Alice",
|
displayName: "Alice",
|
||||||
botCreateOpts: { displayName: "Bob" },
|
botCreateOpts: { displayName: "Bob" },
|
||||||
|
|||||||
@@ -8,10 +8,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
|
|
||||||
import { test, expect } from "../../element-web-test";
|
import { test, expect } from "../../element-web-test";
|
||||||
import { createRoom, enableKeyBackup, logIntoElement, sendMessageInCurrentRoom } from "./utils";
|
import { createRoom, enableKeyBackup, logIntoElement, sendMessageInCurrentRoom } from "./utils";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Logout tests", () => {
|
test.describe("Logout tests", () => {
|
||||||
test.skip(isDendrite, "Dendrite lacks support for MSC3967 so requires additional auth here");
|
|
||||||
test.beforeEach(async ({ page, homeserver, credentials }) => {
|
test.beforeEach(async ({ page, homeserver, credentials }) => {
|
||||||
await logIntoElement(page, credentials);
|
await logIntoElement(page, credentials);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ test.describe("User verification", () => {
|
|||||||
/* on the bot side, wait for the verifier to exist ... */
|
/* on the bot side, wait for the verifier to exist ... */
|
||||||
const botVerifier = await awaitVerifier(bobVerificationRequest);
|
const botVerifier = await awaitVerifier(bobVerificationRequest);
|
||||||
// ... confirm ...
|
// ... confirm ...
|
||||||
void botVerifier.evaluate((verifier) => verifier.verify());
|
botVerifier.evaluate((verifier) => verifier.verify());
|
||||||
// ... and then check the emoji match
|
// ... and then check the emoji match
|
||||||
await doTwoWaySasVerification(page, botVerifier);
|
await doTwoWaySasVerification(page, botVerifier);
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import type { ICreateRoomOpts, MatrixClient } from "matrix-js-sdk/src/matrix";
|
|||||||
import type {
|
import type {
|
||||||
CryptoEvent,
|
CryptoEvent,
|
||||||
EmojiMapping,
|
EmojiMapping,
|
||||||
GeneratedSecretStorageKey,
|
|
||||||
ShowSasCallbacks,
|
ShowSasCallbacks,
|
||||||
VerificationRequest,
|
VerificationRequest,
|
||||||
Verifier,
|
Verifier,
|
||||||
@@ -23,46 +22,6 @@ import { Client } from "../../pages/client";
|
|||||||
import { ElementAppPage } from "../../pages/ElementAppPage";
|
import { ElementAppPage } from "../../pages/ElementAppPage";
|
||||||
import { Bot } from "../../pages/bot";
|
import { Bot } from "../../pages/bot";
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a bot client using the supplied credentials, and wait for the key backup to be ready.
|
|
||||||
* @param page - the playwright `page` fixture
|
|
||||||
* @param homeserver - the homeserver to use
|
|
||||||
* @param credentials - the credentials to use for the bot client
|
|
||||||
*/
|
|
||||||
export async function createBot(
|
|
||||||
page: Page,
|
|
||||||
homeserver: HomeserverInstance,
|
|
||||||
credentials: Credentials,
|
|
||||||
): Promise<{ botClient: Bot; recoveryKey: GeneratedSecretStorageKey; expectedBackupVersion: string }> {
|
|
||||||
// Visit the login page of the app, to load the matrix sdk
|
|
||||||
await page.goto("/#/login");
|
|
||||||
|
|
||||||
// wait for the page to load
|
|
||||||
await page.waitForSelector(".mx_AuthPage", { timeout: 30000 });
|
|
||||||
|
|
||||||
// Create a new bot client
|
|
||||||
const botClient = new Bot(page, homeserver, {
|
|
||||||
bootstrapCrossSigning: true,
|
|
||||||
bootstrapSecretStorage: true,
|
|
||||||
});
|
|
||||||
botClient.setCredentials(credentials);
|
|
||||||
// Backup is prepared in the background. Poll until it is ready.
|
|
||||||
const botClientHandle = await botClient.prepareClient();
|
|
||||||
let expectedBackupVersion: string;
|
|
||||||
await expect
|
|
||||||
.poll(async () => {
|
|
||||||
expectedBackupVersion = await botClientHandle.evaluate((cli) =>
|
|
||||||
cli.getCrypto()!.getActiveSessionBackupVersion(),
|
|
||||||
);
|
|
||||||
return expectedBackupVersion;
|
|
||||||
})
|
|
||||||
.not.toBe(null);
|
|
||||||
|
|
||||||
const recoveryKey = await botClient.getRecoveryKey();
|
|
||||||
|
|
||||||
return { botClient, recoveryKey, expectedBackupVersion };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* wait for the given client to receive an incoming verification request, and automatically accept it
|
* wait for the given client to receive an incoming verification request, and automatically accept it
|
||||||
*
|
*
|
||||||
@@ -100,7 +59,7 @@ export function handleSasVerification(verifier: JSHandle<Verifier>): Promise<Emo
|
|||||||
return new Promise<EmojiMapping[]>((resolve) => {
|
return new Promise<EmojiMapping[]>((resolve) => {
|
||||||
const onShowSas = (event: ShowSasCallbacks) => {
|
const onShowSas = (event: ShowSasCallbacks) => {
|
||||||
verifier.off("show_sas" as VerifierEvent, onShowSas);
|
verifier.off("show_sas" as VerifierEvent, onShowSas);
|
||||||
void event.confirm();
|
event.confirm();
|
||||||
resolve(event.sas.emoji);
|
resolve(event.sas.emoji);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -354,7 +313,7 @@ export async function autoJoin(client: Client) {
|
|||||||
await client.evaluate((cli) => {
|
await client.evaluate((cli) => {
|
||||||
cli.on(window.matrixcs.RoomMemberEvent.Membership, (event, member) => {
|
cli.on(window.matrixcs.RoomMemberEvent.Membership, (event, member) => {
|
||||||
if (member.membership === "invite" && member.userId === cli.getUserId()) {
|
if (member.membership === "invite" && member.userId === cli.getUserId()) {
|
||||||
void cli.joinRoom(member.roomId);
|
cli.joinRoom(member.roomId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -413,25 +372,3 @@ export async function createSecondBotDevice(page: Page, homeserver: HomeserverIn
|
|||||||
await bobSecondDevice.prepareClient();
|
await bobSecondDevice.prepareClient();
|
||||||
return bobSecondDevice;
|
return bobSecondDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the cached secrets from the indexedDB
|
|
||||||
* This is a workaround to simulate the case where the secrets are not cached.
|
|
||||||
*/
|
|
||||||
export async function deleteCachedSecrets(page: Page) {
|
|
||||||
await page.evaluate(async () => {
|
|
||||||
const removeCachedSecrets = new Promise((resolve) => {
|
|
||||||
const request = window.indexedDB.open("matrix-js-sdk::matrix-sdk-crypto");
|
|
||||||
request.onsuccess = (event: Event & { target: { result: IDBDatabase } }) => {
|
|
||||||
const db = event.target.result;
|
|
||||||
const request = db.transaction("core", "readwrite").objectStore("core").delete("private_identity");
|
|
||||||
request.onsuccess = () => {
|
|
||||||
db.close();
|
|
||||||
resolve(undefined);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
});
|
|
||||||
await removeCachedSecrets;
|
|
||||||
});
|
|
||||||
await page.reload();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,24 +9,24 @@ import { APIRequestContext } from "playwright-core";
|
|||||||
import { KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
|
import { KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
|
||||||
|
|
||||||
import { HomeserverInstance } from "../plugins/homeserver";
|
import { HomeserverInstance } from "../plugins/homeserver";
|
||||||
import { ClientServerApi } from "../plugins/utils/api.ts";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A small subset of the Client-Server API used to manipulate the state of the
|
* A small subset of the Client-Server API used to manipulate the state of the
|
||||||
* account on the homeserver independently of the client under test.
|
* account on the homeserver independently of the client under test.
|
||||||
*/
|
*/
|
||||||
export class TestClientServerAPI extends ClientServerApi {
|
export class TestClientServerAPI {
|
||||||
public constructor(
|
public constructor(
|
||||||
request: APIRequestContext,
|
private request: APIRequestContext,
|
||||||
homeserver: HomeserverInstance,
|
private homeserver: HomeserverInstance,
|
||||||
private accessToken: string,
|
private accessToken: string,
|
||||||
) {
|
) {}
|
||||||
super(homeserver.baseUrl);
|
|
||||||
this.setRequest(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getCurrentBackupInfo(): Promise<KeyBackupInfo | null> {
|
public async getCurrentBackupInfo(): Promise<KeyBackupInfo | null> {
|
||||||
return this.request("GET", `/v3/room_keys/version`, this.accessToken);
|
const res = await this.request.get(`${this.homeserver.baseUrl}/_matrix/client/v3/room_keys/version`, {
|
||||||
|
headers: { Authorization: `Bearer ${this.accessToken}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
return await res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,6 +34,15 @@ export class TestClientServerAPI extends ClientServerApi {
|
|||||||
* @param version The version to delete
|
* @param version The version to delete
|
||||||
*/
|
*/
|
||||||
public async deleteBackupVersion(version: string): Promise<void> {
|
public async deleteBackupVersion(version: string): Promise<void> {
|
||||||
await this.request("DELETE", `/v3/room_keys/version/${version}`, this.accessToken);
|
const res = await this.request.delete(
|
||||||
|
`${this.homeserver.baseUrl}/_matrix/client/v3/room_keys/version/${version}`,
|
||||||
|
{
|
||||||
|
headers: { Authorization: `Bearer ${this.accessToken}` },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`Failed to delete backup version: ${res.status}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import type { EventType, IContent, ISendEventResponse, MsgType, Visibility } fro
|
|||||||
import { expect, test } from "../../element-web-test";
|
import { expect, test } from "../../element-web-test";
|
||||||
import { ElementAppPage } from "../../pages/ElementAppPage";
|
import { ElementAppPage } from "../../pages/ElementAppPage";
|
||||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
async function sendEvent(app: ElementAppPage, roomId: string): Promise<ISendEventResponse> {
|
async function sendEvent(app: ElementAppPage, roomId: string): Promise<ISendEventResponse> {
|
||||||
return app.client.sendEvent(roomId, null, "m.room.message" as EventType, {
|
return app.client.sendEvent(roomId, null, "m.room.message" as EventType, {
|
||||||
@@ -32,8 +31,6 @@ function mkPadding(n: number): IContent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test.describe("Editing", () => {
|
test.describe("Editing", () => {
|
||||||
test.skip(isDendrite, "due to a Dendrite bug https://github.com/element-hq/dendrite/issues/3488");
|
|
||||||
|
|
||||||
// Edit "Message"
|
// Edit "Message"
|
||||||
const editLastMessage = async (page: Page, edit: string) => {
|
const editLastMessage = async (page: Page, edit: string) => {
|
||||||
const eventTile = page.locator(".mx_RoomView_MessageList .mx_EventTile_last");
|
const eventTile = page.locator(".mx_RoomView_MessageList .mx_EventTile_last");
|
||||||
|
|||||||
@@ -6,25 +6,16 @@ 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 { expect, test as base } from "../../element-web-test";
|
import { expect, test } from "../../element-web-test";
|
||||||
import { selectHomeserver } from "../utils";
|
import { selectHomeserver } from "../utils";
|
||||||
import { emailHomeserver } from "../../plugins/homeserver/synapse/emailHomeserver.ts";
|
import { emailHomeserver } from "../../plugins/homeserver/synapse/emailHomeserver.ts";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
||||||
import { Credentials } from "../../plugins/homeserver";
|
|
||||||
|
|
||||||
|
const username = "user1234";
|
||||||
|
// this has to be password-like enough to please zxcvbn. Needless to say it's just from pwgen.
|
||||||
|
const password = "oETo7MPf0o";
|
||||||
const email = "user@nowhere.dummy";
|
const email = "user@nowhere.dummy";
|
||||||
|
|
||||||
const test = base.extend<{ credentials: Pick<Credentials, "username" | "password"> }>({
|
|
||||||
// eslint-disable-next-line no-empty-pattern
|
|
||||||
credentials: async ({}, use, testInfo) => {
|
|
||||||
await use({
|
|
||||||
username: `user_${testInfo.testId}`,
|
|
||||||
// this has to be password-like enough to please zxcvbn. Needless to say it's just from pwgen.
|
|
||||||
password: "oETo7MPf0o",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
test.use(emailHomeserver);
|
test.use(emailHomeserver);
|
||||||
test.use({
|
test.use({
|
||||||
config: {
|
config: {
|
||||||
@@ -54,35 +45,31 @@ test.describe("Forgot Password", () => {
|
|||||||
await expect(page.getByRole("main")).toMatchScreenshot("forgot-password.png");
|
await expect(page.getByRole("main")).toMatchScreenshot("forgot-password.png");
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test("renders email verification dialog properly", { tag: "@screenshot" }, async ({ page, homeserver }) => {
|
||||||
"renders email verification dialog properly",
|
const user = await homeserver.registerUser(username, password);
|
||||||
{ tag: "@screenshot" },
|
|
||||||
async ({ page, homeserver, credentials }) => {
|
|
||||||
const user = await homeserver.registerUser(credentials.username, credentials.password);
|
|
||||||
|
|
||||||
await homeserver.setThreepid(user.userId, "email", email);
|
await homeserver.setThreepid(user.userId, "email", email);
|
||||||
|
|
||||||
await page.goto("/");
|
await page.goto("/");
|
||||||
|
|
||||||
await page.getByRole("link", { name: "Sign in" }).click();
|
await page.getByRole("link", { name: "Sign in" }).click();
|
||||||
await selectHomeserver(page, homeserver.baseUrl);
|
await selectHomeserver(page, homeserver.baseUrl);
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Forgot password?" }).click();
|
await page.getByRole("button", { name: "Forgot password?" }).click();
|
||||||
|
|
||||||
await page.getByRole("textbox", { name: "Email address" }).fill(email);
|
await page.getByRole("textbox", { name: "Email address" }).fill(email);
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Send email" }).click();
|
await page.getByRole("button", { name: "Send email" }).click();
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Next" }).click();
|
await page.getByRole("button", { name: "Next" }).click();
|
||||||
|
|
||||||
await page.getByRole("textbox", { name: "New Password", exact: true }).fill(credentials.password);
|
await page.getByRole("textbox", { name: "New Password", exact: true }).fill(password);
|
||||||
await page.getByRole("textbox", { name: "Confirm new password", exact: true }).fill(credentials.password);
|
await page.getByRole("textbox", { name: "Confirm new password", exact: true }).fill(password);
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Reset password" }).click();
|
await page.getByRole("button", { name: "Reset password" }).click();
|
||||||
|
|
||||||
await expect(page.getByRole("button", { name: "Resend" })).toBeInViewport();
|
await expect(page.getByRole("button", { name: "Resend" })).toBeInViewport();
|
||||||
|
|
||||||
await expect(page.locator(".mx_Dialog")).toMatchScreenshot("forgot-password-verify-email.png");
|
await expect(page.locator(".mx_Dialog")).toMatchScreenshot("forgot-password-verify-email.png");
|
||||||
},
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -69,13 +69,29 @@ async function sendActionFromIntegrationManager(
|
|||||||
await iframe.getByRole("button", { name: "Press to send action" }).click();
|
await iframe.getByRole("button", { name: "Press to send action" }).click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function clickUntilGone(page: Page, selector: string, attempt = 0) {
|
||||||
|
if (attempt === 11) {
|
||||||
|
throw new Error("clickUntilGone attempt count exceeded");
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.locator(selector).last().click();
|
||||||
|
|
||||||
|
const count = await page.locator(selector).count();
|
||||||
|
if (count > 0) {
|
||||||
|
return clickUntilGone(page, selector, ++attempt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function expectKickedMessage(page: Page, shouldExist: boolean) {
|
async function expectKickedMessage(page: Page, shouldExist: boolean) {
|
||||||
await expect(async () => {
|
// Expand any event summaries, we can't use a click multiple here because clicking one might de-render others
|
||||||
await page.locator(".mx_GenericEventListSummary_toggle[aria-expanded=false]").last().click();
|
// This is quite horrible but seems the most stable way of clicking 0-N buttons,
|
||||||
await expect(page.getByText(`${USER_DISPLAY_NAME} removed ${BOT_DISPLAY_NAME}: ${KICK_REASON}`)).toBeVisible({
|
// one at a time with a full re-evaluation after each click
|
||||||
visible: shouldExist,
|
await clickUntilGone(page, ".mx_GenericEventListSummary_toggle[aria-expanded=false]");
|
||||||
});
|
|
||||||
}).toPass();
|
// Check for the event message (or lack thereof)
|
||||||
|
await expect(page.getByText(`${USER_DISPLAY_NAME} removed ${BOT_DISPLAY_NAME}: ${KICK_REASON}`)).toBeVisible({
|
||||||
|
visible: shouldExist,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
test.describe("Integration Manager: Kick", () => {
|
test.describe("Integration Manager: Kick", () => {
|
||||||
|
|||||||
@@ -9,10 +9,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
import { test, expect } from "../../element-web-test";
|
import { test, expect } from "../../element-web-test";
|
||||||
import { waitForRoom } from "../utils";
|
import { waitForRoom } from "../utils";
|
||||||
import { Filter } from "../../pages/Spotlight";
|
import { Filter } from "../../pages/Spotlight";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Create Knock Room", () => {
|
test.describe("Create Knock Room", () => {
|
||||||
test.skip(isDendrite, "Dendrite does not have support for knocking");
|
|
||||||
test.use({
|
test.use({
|
||||||
displayName: "Alice",
|
displayName: "Alice",
|
||||||
labsFlags: ["feature_ask_to_join"],
|
labsFlags: ["feature_ask_to_join"],
|
||||||
@@ -81,7 +79,6 @@ test.describe("Create Knock Room", () => {
|
|||||||
|
|
||||||
const spotlightDialog = await app.openSpotlight();
|
const spotlightDialog = await app.openSpotlight();
|
||||||
await spotlightDialog.filter(Filter.PublicRooms);
|
await spotlightDialog.filter(Filter.PublicRooms);
|
||||||
await spotlightDialog.search("Cyber");
|
|
||||||
await expect(spotlightDialog.results.nth(0)).toContainText("Cybersecurity");
|
await expect(spotlightDialog.results.nth(0)).toContainText("Cybersecurity");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,10 +13,8 @@ import { type Visibility } from "matrix-js-sdk/src/matrix";
|
|||||||
import { test, expect } from "../../element-web-test";
|
import { test, expect } from "../../element-web-test";
|
||||||
import { waitForRoom } from "../utils";
|
import { waitForRoom } from "../utils";
|
||||||
import { Filter } from "../../pages/Spotlight";
|
import { Filter } from "../../pages/Spotlight";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Knock Into Room", () => {
|
test.describe("Knock Into Room", () => {
|
||||||
test.skip(isDendrite, "Dendrite does not have support for knocking");
|
|
||||||
test.use({
|
test.use({
|
||||||
displayName: "Alice",
|
displayName: "Alice",
|
||||||
labsFlags: ["feature_ask_to_join"],
|
labsFlags: ["feature_ask_to_join"],
|
||||||
@@ -284,7 +282,6 @@ test.describe("Knock Into Room", () => {
|
|||||||
|
|
||||||
const spotlightDialog = await app.openSpotlight();
|
const spotlightDialog = await app.openSpotlight();
|
||||||
await spotlightDialog.filter(Filter.PublicRooms);
|
await spotlightDialog.filter(Filter.PublicRooms);
|
||||||
await spotlightDialog.search("Cyber");
|
|
||||||
await expect(spotlightDialog.results.nth(0)).toContainText("Cybersecurity");
|
await expect(spotlightDialog.results.nth(0)).toContainText("Cybersecurity");
|
||||||
await spotlightDialog.results.nth(0).click();
|
await spotlightDialog.results.nth(0).click();
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
|
|
||||||
import { test, expect } from "../../element-web-test";
|
import { test, expect } from "../../element-web-test";
|
||||||
import { waitForRoom } from "../utils";
|
import { waitForRoom } from "../utils";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Manage Knocks", () => {
|
test.describe("Manage Knocks", () => {
|
||||||
test.skip(isDendrite, "Dendrite does not have support for knocking");
|
|
||||||
test.use({
|
test.use({
|
||||||
displayName: "Alice",
|
displayName: "Alice",
|
||||||
labsFlags: ["feature_ask_to_join"],
|
labsFlags: ["feature_ask_to_join"],
|
||||||
@@ -52,7 +50,7 @@ test.describe("Manage Knocks", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("should deny knock using bar", async ({ page, app, bot, room }) => {
|
test("should deny knock using bar", async ({ page, app, bot, room }) => {
|
||||||
await bot.knockRoom(room.roomId);
|
bot.knockRoom(room.roomId);
|
||||||
|
|
||||||
const roomKnocksBar = page.locator(".mx_RoomKnocksBar");
|
const roomKnocksBar = page.locator(".mx_RoomKnocksBar");
|
||||||
await expect(roomKnocksBar.getByRole("heading", { name: "Asking to join" })).toBeVisible();
|
await expect(roomKnocksBar.getByRole("heading", { name: "Asking to join" })).toBeVisible();
|
||||||
|
|||||||
@@ -10,12 +10,8 @@ import { Bot } from "../../pages/bot";
|
|||||||
import type { Locator, Page } from "@playwright/test";
|
import type { Locator, Page } from "@playwright/test";
|
||||||
import type { ElementAppPage } from "../../pages/ElementAppPage";
|
import type { ElementAppPage } from "../../pages/ElementAppPage";
|
||||||
import { test, expect } from "../../element-web-test";
|
import { test, expect } from "../../element-web-test";
|
||||||
import { Credentials } from "../../plugins/homeserver";
|
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Lazy Loading", () => {
|
test.describe("Lazy Loading", () => {
|
||||||
test.skip(isDendrite, "due to a Dendrite bug https://github.com/element-hq/dendrite/issues/3488");
|
|
||||||
|
|
||||||
const charlies: Bot[] = [];
|
const charlies: Bot[] = [];
|
||||||
|
|
||||||
test.use({
|
test.use({
|
||||||
@@ -39,18 +35,12 @@ test.describe("Lazy Loading", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const name = "Lazy Loading Test";
|
const name = "Lazy Loading Test";
|
||||||
|
const alias = "#lltest:localhost";
|
||||||
const charlyMsg1 = "hi bob!";
|
const charlyMsg1 = "hi bob!";
|
||||||
const charlyMsg2 = "how's it going??";
|
const charlyMsg2 = "how's it going??";
|
||||||
let roomId: string;
|
let roomId: string;
|
||||||
|
|
||||||
async function setupRoomWithBobAliceAndCharlies(
|
async function setupRoomWithBobAliceAndCharlies(page: Page, app: ElementAppPage, bob: Bot, charlies: Bot[]) {
|
||||||
page: Page,
|
|
||||||
app: ElementAppPage,
|
|
||||||
user: Credentials,
|
|
||||||
bob: Bot,
|
|
||||||
charlies: Bot[],
|
|
||||||
) {
|
|
||||||
const alias = `#lltest:${user.homeServer}`;
|
|
||||||
const visibility = await page.evaluate(() => (window as any).matrixcs.Visibility.Public);
|
const visibility = await page.evaluate(() => (window as any).matrixcs.Visibility.Public);
|
||||||
roomId = await bob.createRoom({
|
roomId = await bob.createRoom({
|
||||||
name,
|
name,
|
||||||
@@ -105,13 +95,7 @@ test.describe("Lazy Loading", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function joinCharliesWhileAliceIsOffline(
|
async function joinCharliesWhileAliceIsOffline(page: Page, app: ElementAppPage, charlies: Bot[]) {
|
||||||
page: Page,
|
|
||||||
app: ElementAppPage,
|
|
||||||
user: Credentials,
|
|
||||||
charlies: Bot[],
|
|
||||||
) {
|
|
||||||
const alias = `#lltest:${user.homeServer}`;
|
|
||||||
await app.client.network.goOffline();
|
await app.client.network.goOffline();
|
||||||
for (const charly of charlies) {
|
for (const charly of charlies) {
|
||||||
await charly.joinRoom(alias);
|
await charly.joinRoom(alias);
|
||||||
@@ -123,19 +107,19 @@ test.describe("Lazy Loading", () => {
|
|||||||
await app.client.waitForNextSync();
|
await app.client.waitForNextSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
test("should handle lazy loading properly even when offline", async ({ page, app, bot, user }) => {
|
test("should handle lazy loading properly even when offline", async ({ page, app, bot }) => {
|
||||||
test.slow();
|
test.slow();
|
||||||
const charly1to5 = charlies.slice(0, 5);
|
const charly1to5 = charlies.slice(0, 5);
|
||||||
const charly6to10 = charlies.slice(5);
|
const charly6to10 = charlies.slice(5);
|
||||||
|
|
||||||
// Set up room with alice, bob & charlies 1-5
|
// Set up room with alice, bob & charlies 1-5
|
||||||
await setupRoomWithBobAliceAndCharlies(page, app, user, bot, charly1to5);
|
await setupRoomWithBobAliceAndCharlies(page, app, bot, charly1to5);
|
||||||
// Alice should see 2 messages from every charly with the correct display name
|
// Alice should see 2 messages from every charly with the correct display name
|
||||||
await checkPaginatedDisplayNames(app, charly1to5);
|
await checkPaginatedDisplayNames(app, charly1to5);
|
||||||
|
|
||||||
await openMemberlist(app);
|
await openMemberlist(app);
|
||||||
await checkMemberList(page, charly1to5);
|
await checkMemberList(page, charly1to5);
|
||||||
await joinCharliesWhileAliceIsOffline(page, app, user, charly6to10);
|
await joinCharliesWhileAliceIsOffline(page, app, charly6to10);
|
||||||
await checkMemberList(page, charly6to10);
|
await checkMemberList(page, charly6to10);
|
||||||
|
|
||||||
for (const charly of charlies) {
|
for (const charly of charlies) {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { expect, test } from "../../element-web-test";
|
|||||||
import { selectHomeserver } from "../utils";
|
import { selectHomeserver } from "../utils";
|
||||||
import { Credentials, HomeserverInstance } from "../../plugins/homeserver";
|
import { Credentials, HomeserverInstance } from "../../plugins/homeserver";
|
||||||
import { consentHomeserver } from "../../plugins/homeserver/synapse/consentHomeserver.ts";
|
import { consentHomeserver } from "../../plugins/homeserver/synapse/consentHomeserver.ts";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
// This test requires fixed credentials for the device signing keys below to work
|
// This test requires fixed credentials for the device signing keys below to work
|
||||||
const username = "user1234";
|
const username = "user1234";
|
||||||
@@ -78,9 +77,6 @@ async function login(page: Page, homeserver: HomeserverInstance, credentials: Cr
|
|||||||
await page.getByRole("button", { name: "Sign in" }).click();
|
await page.getByRole("button", { name: "Sign in" }).click();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This test suite uses the same userId for all tests in the suite
|
|
||||||
// due to DEVICE_SIGNING_KEYS_BODY being specific to that userId,
|
|
||||||
// so we restart the Synapse container to make it forget everything.
|
|
||||||
test.use(consentHomeserver);
|
test.use(consentHomeserver);
|
||||||
test.use({
|
test.use({
|
||||||
config: {
|
config: {
|
||||||
@@ -92,11 +88,6 @@ test.use({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
context: async ({ context, homeserver }, use) => {
|
|
||||||
// Restart the homeserver to wipe its in-memory db so we can reuse the same user ID without cross-signing prompts
|
|
||||||
await homeserver.restart();
|
|
||||||
await use(context);
|
|
||||||
},
|
|
||||||
credentials: async ({ context, homeserver }, use) => {
|
credentials: async ({ context, homeserver }, use) => {
|
||||||
const displayName = "Dave";
|
const displayName = "Dave";
|
||||||
const credentials = await homeserver.registerUser(username, password, displayName);
|
const credentials = await homeserver.registerUser(username, password, displayName);
|
||||||
@@ -106,16 +97,11 @@ test.use({
|
|||||||
...credentials,
|
...credentials,
|
||||||
displayName,
|
displayName,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Restart the homeserver to wipe its in-memory db so we can reuse the same user ID without cross-signing prompts
|
|
||||||
await homeserver.restart();
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe("Login", () => {
|
test.describe("Login", () => {
|
||||||
test.describe("Password login", () => {
|
test.describe("Password login", () => {
|
||||||
test.skip(isDendrite, "Dendrite lacks support for MSC3967 so requires additional auth here");
|
|
||||||
|
|
||||||
test("Loads the welcome page by default; then logs in with an existing account and lands on the home screen", async ({
|
test("Loads the welcome page by default; then logs in with an existing account and lands on the home screen", async ({
|
||||||
credentials,
|
credentials,
|
||||||
page,
|
page,
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ test.use(legacyOAuthHomeserver);
|
|||||||
test.describe("SSO login", () => {
|
test.describe("SSO login", () => {
|
||||||
test.skip(isDendrite, "does not yet support SSO");
|
test.skip(isDendrite, "does not yet support SSO");
|
||||||
|
|
||||||
test("logs in with SSO and lands on the home screen", async ({ page, homeserver }, testInfo) => {
|
test("logs in with SSO and lands on the home screen", async ({ page, homeserver }) => {
|
||||||
// If this test fails with a screen showing "Timeout connecting to remote server", it is most likely due to
|
// If this test fails with a screen showing "Timeout connecting to remote server", it is most likely due to
|
||||||
// your firewall settings: Synapse is unable to reach the OIDC server.
|
// your firewall settings: Synapse is unable to reach the OIDC server.
|
||||||
//
|
//
|
||||||
// If you are using ufw, try something like:
|
// If you are using ufw, try something like:
|
||||||
// sudo ufw allow in on docker0
|
// sudo ufw allow in on docker0
|
||||||
//
|
//
|
||||||
await doTokenRegistration(page, homeserver, testInfo);
|
await doTokenRegistration(page, homeserver);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ test.use({
|
|||||||
test.use(legacyOAuthHomeserver);
|
test.use(legacyOAuthHomeserver);
|
||||||
test.describe("Soft logout with SSO user", () => {
|
test.describe("Soft logout with SSO user", () => {
|
||||||
test.use({
|
test.use({
|
||||||
user: async ({ page, homeserver }, use, testInfo) => {
|
user: async ({ page, homeserver }, use) => {
|
||||||
const user = await doTokenRegistration(page, homeserver, testInfo);
|
const user = await doTokenRegistration(page, homeserver);
|
||||||
|
|
||||||
// Eventually, we should end up at the home screen.
|
// Eventually, we should end up at the home screen.
|
||||||
await expect(page).toHaveURL(/\/#\/home$/);
|
await expect(page).toHaveURL(/\/#\/home$/);
|
||||||
|
|||||||
@@ -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 { Page, expect, TestInfo } from "@playwright/test";
|
import { Page, expect } from "@playwright/test";
|
||||||
|
|
||||||
import { Credentials, HomeserverInstance } from "../../plugins/homeserver";
|
import { Credentials, HomeserverInstance } from "../../plugins/homeserver";
|
||||||
|
|
||||||
@@ -15,7 +15,6 @@ import { Credentials, HomeserverInstance } from "../../plugins/homeserver";
|
|||||||
export async function doTokenRegistration(
|
export async function doTokenRegistration(
|
||||||
page: Page,
|
page: Page,
|
||||||
homeserver: HomeserverInstance,
|
homeserver: HomeserverInstance,
|
||||||
testInfo: TestInfo,
|
|
||||||
): Promise<Credentials & { displayName: string }> {
|
): Promise<Credentials & { displayName: string }> {
|
||||||
await page.goto("/#/login");
|
await page.goto("/#/login");
|
||||||
|
|
||||||
@@ -36,7 +35,7 @@ export async function doTokenRegistration(
|
|||||||
|
|
||||||
// Synapse prompts us to pick a user ID
|
// Synapse prompts us to pick a user ID
|
||||||
await expect(page.getByRole("heading", { name: "Create your account" })).toBeVisible();
|
await expect(page.getByRole("heading", { name: "Create your account" })).toBeVisible();
|
||||||
await page.getByRole("textbox", { name: "Username (required)" }).fill(`alice_${testInfo.testId}`);
|
await page.getByRole("textbox", { name: "Username (required)" }).fill("alice");
|
||||||
|
|
||||||
// wait for username validation to start, and complete
|
// wait for username validation to start, and complete
|
||||||
await expect(page.locator("#field-username-output")).toHaveText("");
|
await expect(page.locator("#field-username-output")).toHaveText("");
|
||||||
@@ -93,7 +92,7 @@ export async function interceptRequestsWithSoftLogout(page: Page, user: Credenti
|
|||||||
// do something to make the active /sync return: create a new room
|
// do something to make the active /sync return: create a new room
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
// don't wait for this to complete: it probably won't, because of the broken sync
|
// don't wait for this to complete: it probably won't, because of the broken sync
|
||||||
void window.mxMatrixClientPeg.get().createRoom({});
|
window.mxMatrixClientPeg.get().createRoom({});
|
||||||
});
|
});
|
||||||
|
|
||||||
await promise;
|
await promise;
|
||||||
|
|||||||
@@ -58,16 +58,6 @@ async function editMessage(page: Page, message: Locator, newMsg: string): Promis
|
|||||||
await editComposer.press("Enter");
|
await editComposer.press("Enter");
|
||||||
}
|
}
|
||||||
|
|
||||||
const screenshotOptions = (page?: Page) => ({
|
|
||||||
mask: page ? [page.locator(".mx_MessageTimestamp")] : undefined,
|
|
||||||
// Hide the jump to bottom button in the timeline to avoid flakiness
|
|
||||||
css: `
|
|
||||||
.mx_JumpToBottomButton {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Message rendering", () => {
|
test.describe("Message rendering", () => {
|
||||||
[
|
[
|
||||||
{ direction: "ltr", displayName: "Quentin" },
|
{ direction: "ltr", displayName: "Quentin" },
|
||||||
@@ -89,10 +79,9 @@ test.describe("Message rendering", () => {
|
|||||||
await page.goto(`#/room/${room.roomId}`);
|
await page.goto(`#/room/${room.roomId}`);
|
||||||
|
|
||||||
const msgTile = await sendMessage(page, "Hello, world!");
|
const msgTile = await sendMessage(page, "Hello, world!");
|
||||||
await expect(msgTile).toMatchScreenshot(
|
await expect(msgTile).toMatchScreenshot(`basic-message-ltr-${direction}displayname.png`, {
|
||||||
`basic-message-ltr-${direction}displayname.png`,
|
mask: [page.locator(".mx_MessageTimestamp")],
|
||||||
screenshotOptions(page),
|
});
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -100,17 +89,14 @@ test.describe("Message rendering", () => {
|
|||||||
await page.goto(`#/room/${room.roomId}`);
|
await page.goto(`#/room/${room.roomId}`);
|
||||||
|
|
||||||
const msgTile = await sendMessage(page, "/me lays an egg");
|
const msgTile = await sendMessage(page, "/me lays an egg");
|
||||||
await expect(msgTile).toMatchScreenshot(`emote-ltr-${direction}displayname.png`, screenshotOptions());
|
await expect(msgTile).toMatchScreenshot(`emote-ltr-${direction}displayname.png`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should render an LTR rich text emote", async ({ page, user, app, room }) => {
|
test("should render an LTR rich text emote", async ({ page, user, app, room }) => {
|
||||||
await page.goto(`#/room/${room.roomId}`);
|
await page.goto(`#/room/${room.roomId}`);
|
||||||
|
|
||||||
const msgTile = await sendMessage(page, "/me lays a *free range* egg");
|
const msgTile = await sendMessage(page, "/me lays a *free range* egg");
|
||||||
await expect(msgTile).toMatchScreenshot(
|
await expect(msgTile).toMatchScreenshot(`emote-rich-ltr-${direction}displayname.png`);
|
||||||
`emote-rich-ltr-${direction}displayname.png`,
|
|
||||||
screenshotOptions(),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should render an edited LTR message", async ({ page, user, app, room }) => {
|
test("should render an edited LTR message", async ({ page, user, app, room }) => {
|
||||||
@@ -120,10 +106,9 @@ test.describe("Message rendering", () => {
|
|||||||
|
|
||||||
await editMessage(page, msgTile, "Hello, universe!");
|
await editMessage(page, msgTile, "Hello, universe!");
|
||||||
|
|
||||||
await expect(msgTile).toMatchScreenshot(
|
await expect(msgTile).toMatchScreenshot(`edited-message-ltr-${direction}displayname.png`, {
|
||||||
`edited-message-ltr-${direction}displayname.png`,
|
mask: [page.locator(".mx_MessageTimestamp")],
|
||||||
screenshotOptions(page),
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should render a reply of a LTR message", async ({ page, user, app, room }) => {
|
test("should render a reply of a LTR message", async ({ page, user, app, room }) => {
|
||||||
@@ -137,37 +122,32 @@ test.describe("Message rendering", () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
await replyMessage(page, msgTile, "response to multiline message");
|
await replyMessage(page, msgTile, "response to multiline message");
|
||||||
await expect(msgTile).toMatchScreenshot(
|
await expect(msgTile).toMatchScreenshot(`reply-message-ltr-${direction}displayname.png`, {
|
||||||
`reply-message-ltr-${direction}displayname.png`,
|
mask: [page.locator(".mx_MessageTimestamp")],
|
||||||
screenshotOptions(page),
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should render a basic RTL text message", async ({ page, user, app, room }) => {
|
test("should render a basic RTL text message", async ({ page, user, app, room }) => {
|
||||||
await page.goto(`#/room/${room.roomId}`);
|
await page.goto(`#/room/${room.roomId}`);
|
||||||
|
|
||||||
const msgTile = await sendMessage(page, "مرحبا بالعالم!");
|
const msgTile = await sendMessage(page, "مرحبا بالعالم!");
|
||||||
await expect(msgTile).toMatchScreenshot(
|
await expect(msgTile).toMatchScreenshot(`basic-message-rtl-${direction}displayname.png`, {
|
||||||
`basic-message-rtl-${direction}displayname.png`,
|
mask: [page.locator(".mx_MessageTimestamp")],
|
||||||
screenshotOptions(page),
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should render an RTL emote", async ({ page, user, app, room }) => {
|
test("should render an RTL emote", async ({ page, user, app, room }) => {
|
||||||
await page.goto(`#/room/${room.roomId}`);
|
await page.goto(`#/room/${room.roomId}`);
|
||||||
|
|
||||||
const msgTile = await sendMessage(page, "/me يضع بيضة");
|
const msgTile = await sendMessage(page, "/me يضع بيضة");
|
||||||
await expect(msgTile).toMatchScreenshot(`emote-rtl-${direction}displayname.png`, screenshotOptions());
|
await expect(msgTile).toMatchScreenshot(`emote-rtl-${direction}displayname.png`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should render a richtext RTL emote", async ({ page, user, app, room }) => {
|
test("should render a richtext RTL emote", async ({ page, user, app, room }) => {
|
||||||
await page.goto(`#/room/${room.roomId}`);
|
await page.goto(`#/room/${room.roomId}`);
|
||||||
|
|
||||||
const msgTile = await sendMessage(page, "/me أضع بيضة *حرة النطاق*");
|
const msgTile = await sendMessage(page, "/me أضع بيضة *حرة النطاق*");
|
||||||
await expect(msgTile).toMatchScreenshot(
|
await expect(msgTile).toMatchScreenshot(`emote-rich-rtl-${direction}displayname.png`);
|
||||||
`emote-rich-rtl-${direction}displayname.png`,
|
|
||||||
screenshotOptions(),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should render an edited RTL message", async ({ page, user, app, room }) => {
|
test("should render an edited RTL message", async ({ page, user, app, room }) => {
|
||||||
@@ -177,10 +157,9 @@ test.describe("Message rendering", () => {
|
|||||||
|
|
||||||
await editMessage(page, msgTile, "مرحبا بالكون!");
|
await editMessage(page, msgTile, "مرحبا بالكون!");
|
||||||
|
|
||||||
await expect(msgTile).toMatchScreenshot(
|
await expect(msgTile).toMatchScreenshot(`edited-message-rtl-${direction}displayname.png`, {
|
||||||
`edited-message-rtl-${direction}displayname.png`,
|
mask: [page.locator(".mx_MessageTimestamp")],
|
||||||
screenshotOptions(page),
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should render a reply of a RTL message", async ({ page, user, app, room }) => {
|
test("should render a reply of a RTL message", async ({ page, user, app, room }) => {
|
||||||
@@ -194,10 +173,9 @@ test.describe("Message rendering", () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
await replyMessage(page, msgTile, "مرحبا بالعالم!");
|
await replyMessage(page, msgTile, "مرحبا بالعالم!");
|
||||||
await expect(msgTile).toMatchScreenshot(
|
await expect(msgTile).toMatchScreenshot(`reply-message-trl-${direction}displayname.png`, {
|
||||||
`reply-message-trl-${direction}displayname.png`,
|
mask: [page.locator(".mx_MessageTimestamp")],
|
||||||
screenshotOptions(page),
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export async function registerAccountMas(
|
|||||||
expect(messages.items).toHaveLength(1);
|
expect(messages.items).toHaveLength(1);
|
||||||
}).toPass();
|
}).toPass();
|
||||||
expect(messages.items[0].to).toEqual(`${username} <${email}>`);
|
expect(messages.items[0].to).toEqual(`${username} <${email}>`);
|
||||||
const [, code] = messages.items[0].text.match(/Your verification code to confirm this email address is: (\d{6})/);
|
const [code] = messages.items[0].text.match(/(\d{6})/);
|
||||||
|
|
||||||
await page.getByRole("textbox", { name: "6-digit code" }).fill(code);
|
await page.getByRole("textbox", { name: "6-digit code" }).fill(code);
|
||||||
await page.getByRole("button", { name: "Continue" }).click();
|
await page.getByRole("button", { name: "Continue" }).click();
|
||||||
|
|||||||
@@ -9,21 +9,15 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
import { test, expect } from "../../element-web-test.ts";
|
import { test, expect } from "../../element-web-test.ts";
|
||||||
import { registerAccountMas } from ".";
|
import { registerAccountMas } from ".";
|
||||||
import { ElementAppPage } from "../../pages/ElementAppPage.ts";
|
import { ElementAppPage } from "../../pages/ElementAppPage.ts";
|
||||||
|
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
||||||
import { masHomeserver } from "../../plugins/homeserver/synapse/masHomeserver.ts";
|
import { masHomeserver } from "../../plugins/homeserver/synapse/masHomeserver.ts";
|
||||||
|
|
||||||
test.use(masHomeserver);
|
test.use(masHomeserver);
|
||||||
test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||||
|
test.skip(isDendrite, "does not yet support MAS");
|
||||||
test.slow(); // trace recording takes a while here
|
test.slow(); // trace recording takes a while here
|
||||||
|
|
||||||
test("can register the oauth2 client and an account", async ({
|
test("can register the oauth2 client and an account", async ({ context, page, homeserver, mailhogClient, mas }) => {
|
||||||
context,
|
|
||||||
page,
|
|
||||||
homeserver,
|
|
||||||
mailhogClient,
|
|
||||||
mas,
|
|
||||||
}, testInfo) => {
|
|
||||||
await page.clock.install();
|
|
||||||
|
|
||||||
const tokenUri = `${mas.baseUrl}/oauth2/token`;
|
const tokenUri = `${mas.baseUrl}/oauth2/token`;
|
||||||
const tokenApiPromise = page.waitForRequest(
|
const tokenApiPromise = page.waitForRequest(
|
||||||
(request) => request.url() === tokenUri && request.postDataJSON()["grant_type"] === "authorization_code",
|
(request) => request.url() === tokenUri && request.postDataJSON()["grant_type"] === "authorization_code",
|
||||||
@@ -31,14 +25,11 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
|
|
||||||
await page.goto("/#/login");
|
await page.goto("/#/login");
|
||||||
await page.getByRole("button", { name: "Continue" }).click();
|
await page.getByRole("button", { name: "Continue" }).click();
|
||||||
|
await registerAccountMas(page, mailhogClient, "alice", "alice@email.com", "Pa$sW0rD!");
|
||||||
const userId = `alice_${testInfo.testId}`;
|
|
||||||
await registerAccountMas(page, mailhogClient, userId, "alice@email.com", "Pa$sW0rD!");
|
|
||||||
|
|
||||||
// Eventually, we should end up at the home screen.
|
// Eventually, we should end up at the home screen.
|
||||||
await expect(page).toHaveURL(/\/#\/home$/, { timeout: 10000 });
|
await expect(page).toHaveURL(/\/#\/home$/, { timeout: 10000 });
|
||||||
await expect(page.getByRole("heading", { name: `Welcome ${userId}`, exact: true })).toBeVisible();
|
await expect(page.getByRole("heading", { name: "Welcome alice", exact: true })).toBeVisible();
|
||||||
await page.clock.runFor(20000); // run the timer so we see the token request
|
|
||||||
|
|
||||||
const tokenApiRequest = await tokenApiPromise;
|
const tokenApiRequest = await tokenApiPromise;
|
||||||
expect(tokenApiRequest.postDataJSON()["grant_type"]).toBe("authorization_code");
|
expect(tokenApiRequest.postDataJSON()["grant_type"]).toBe("authorization_code");
|
||||||
|
|||||||
@@ -9,19 +9,16 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
|
|
||||||
import { test as base, expect } from "../../element-web-test";
|
import { test as base, expect } from "../../element-web-test";
|
||||||
import { Credentials } from "../../plugins/homeserver";
|
import { Credentials } from "../../plugins/homeserver";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
const test = base.extend<{
|
const test = base.extend<{
|
||||||
user2?: Credentials;
|
user2?: Credentials;
|
||||||
}>({});
|
}>({});
|
||||||
|
|
||||||
test.describe("1:1 chat room", () => {
|
test.describe("1:1 chat room", () => {
|
||||||
test.skip(isDendrite, "due to a Dendrite bug https://github.com/element-hq/dendrite/issues/3492");
|
|
||||||
|
|
||||||
test.use({
|
test.use({
|
||||||
displayName: "Jeff",
|
displayName: "Jeff",
|
||||||
user2: async ({ homeserver }, use, testInfo) => {
|
user2: async ({ homeserver }, use) => {
|
||||||
const credentials = await homeserver.registerUser(`user2_${testInfo.testId}`, "p4s5W0rD", "Timmy");
|
const credentials = await homeserver.registerUser("user1234", "p4s5W0rD", "Timmy");
|
||||||
await use(credentials);
|
await use(credentials);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ test.describe("permalinks", () => {
|
|||||||
await charlotte.prepareClient();
|
await charlotte.prepareClient();
|
||||||
|
|
||||||
// We don't use a bot for danielle as we want a stable MXID.
|
// We don't use a bot for danielle as we want a stable MXID.
|
||||||
const danielleId = `@danielle:${user.homeServer}`;
|
const danielleId = "@danielle:localhost";
|
||||||
|
|
||||||
const room1Id = await app.client.createRoom({ name: room1Name });
|
const room1Id = await app.client.createRoom({ name: room1Name });
|
||||||
const room2Id = await app.client.createRoom({ name: room2Name });
|
const room2Id = await app.client.createRoom({ name: room2Name });
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ test.describe("Pinned messages", () => {
|
|||||||
mask: [tile.locator(".mx_MessageTimestamp")],
|
mask: [tile.locator(".mx_MessageTimestamp")],
|
||||||
// Hide the jump to bottom button in the timeline to avoid flakiness
|
// Hide the jump to bottom button in the timeline to avoid flakiness
|
||||||
css: `
|
css: `
|
||||||
.mx_JumpToBottomButton {
|
.mx_JumpToBottomButton {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ test.describe("Poll history", () => {
|
|||||||
|
|
||||||
await expect(dialog.getByText(pollParams2.title)).toBeAttached();
|
await expect(dialog.getByText(pollParams2.title)).toBeAttached();
|
||||||
await expect(dialog.getByText(pollParams1.title)).toBeAttached();
|
await expect(dialog.getByText(pollParams1.title)).toBeAttached();
|
||||||
await dialog.getByText("Active polls").click();
|
dialog.getByText("Active polls").click();
|
||||||
|
|
||||||
// no more active polls
|
// no more active polls
|
||||||
await expect(page.getByText("There are no active polls in this room")).toBeAttached();
|
await expect(page.getByText("There are no active polls in this room")).toBeAttached();
|
||||||
|
|||||||
@@ -11,11 +11,8 @@ import { Bot } from "../../pages/bot";
|
|||||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||||
import { Layout } from "../../../src/settings/enums/Layout";
|
import { Layout } from "../../../src/settings/enums/Layout";
|
||||||
import type { Locator, Page } from "@playwright/test";
|
import type { Locator, Page } from "@playwright/test";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Polls", () => {
|
test.describe("Polls", () => {
|
||||||
test.skip(isDendrite, "due to a Dendrite bug https://github.com/element-hq/dendrite/issues/3492");
|
|
||||||
|
|
||||||
type CreatePollOptions = {
|
type CreatePollOptions = {
|
||||||
title: string;
|
title: string;
|
||||||
options: string[];
|
options: string[];
|
||||||
|
|||||||
@@ -9,11 +9,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
/* See readme.md for tips on writing these tests. */
|
/* See readme.md for tips on writing these tests. */
|
||||||
|
|
||||||
import { test } from ".";
|
import { test } from ".";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
||||||
test.skip(isDendrite, "due to Dendrite bug https://github.com/element-hq/dendrite/issues/2970");
|
|
||||||
|
|
||||||
test.describe("editing messages", () => {
|
test.describe("editing messages", () => {
|
||||||
test.describe("in threads", () => {
|
test.describe("in threads", () => {
|
||||||
test("An edit of a threaded message makes the room unread", async ({
|
test("An edit of a threaded message makes the room unread", async ({
|
||||||
|
|||||||
@@ -9,11 +9,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
/* See readme.md for tips on writing these tests. */
|
/* See readme.md for tips on writing these tests. */
|
||||||
|
|
||||||
import { test } from ".";
|
import { test } from ".";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
||||||
test.skip(isDendrite, "due to Dendrite bug https://github.com/element-hq/dendrite/issues/2970");
|
|
||||||
|
|
||||||
test.describe("editing messages", () => {
|
test.describe("editing messages", () => {
|
||||||
test.describe("in the main timeline", () => {
|
test.describe("in the main timeline", () => {
|
||||||
test("Editing a message leaves a room read", async ({ roomAlpha: room1, roomBeta: room2, util, msg }) => {
|
test("Editing a message leaves a room read", async ({ roomAlpha: room1, roomBeta: room2, util, msg }) => {
|
||||||
|
|||||||
@@ -9,11 +9,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
/* See readme.md for tips on writing these tests. */
|
/* See readme.md for tips on writing these tests. */
|
||||||
|
|
||||||
import { test } from ".";
|
import { test } from ".";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
||||||
test.skip(isDendrite, "due to Dendrite bug https://github.com/element-hq/dendrite/issues/2970");
|
|
||||||
|
|
||||||
test.describe("editing messages", () => {
|
test.describe("editing messages", () => {
|
||||||
test.describe("thread roots", () => {
|
test.describe("thread roots", () => {
|
||||||
test("An edit of a thread root leaves the room read", async ({
|
test("An edit of a thread root leaves the room read", async ({
|
||||||
|
|||||||
@@ -9,10 +9,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
/* See readme.md for tips on writing these tests. */
|
/* See readme.md for tips on writing these tests. */
|
||||||
|
|
||||||
import { customEvent, many, test } from ".";
|
import { customEvent, many, test } from ".";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
||||||
test.skip(isDendrite, "due to Dendrite bug https://github.com/element-hq/dendrite/issues/2970");
|
|
||||||
test.slow();
|
test.slow();
|
||||||
|
|
||||||
test.describe("Ignored events", () => {
|
test.describe("Ignored events", () => {
|
||||||
|
|||||||
@@ -9,11 +9,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
/* See readme.md for tips on writing these tests. */
|
/* See readme.md for tips on writing these tests. */
|
||||||
|
|
||||||
import { many, test } from ".";
|
import { many, test } from ".";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
||||||
test.skip(isDendrite, "due to Dendrite bug https://github.com/element-hq/dendrite/issues/2970");
|
|
||||||
|
|
||||||
test.describe("new messages", () => {
|
test.describe("new messages", () => {
|
||||||
test.describe("in threads", () => {
|
test.describe("in threads", () => {
|
||||||
test("Receiving a message makes a room unread", async ({
|
test("Receiving a message makes a room unread", async ({
|
||||||
|
|||||||
@@ -9,11 +9,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
/* See readme.md for tips on writing these tests. */
|
/* See readme.md for tips on writing these tests. */
|
||||||
|
|
||||||
import { many, test } from ".";
|
import { many, test } from ".";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
||||||
test.skip(isDendrite, "due to Dendrite bug https://github.com/element-hq/dendrite/issues/2970");
|
|
||||||
|
|
||||||
test.describe("new messages", () => {
|
test.describe("new messages", () => {
|
||||||
test.describe("in the main timeline", () => {
|
test.describe("in the main timeline", () => {
|
||||||
test("Receiving a message makes a room unread", async ({
|
test("Receiving a message makes a room unread", async ({
|
||||||
|
|||||||
@@ -9,11 +9,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
/* See readme.md for tips on writing these tests. */
|
/* See readme.md for tips on writing these tests. */
|
||||||
|
|
||||||
import { many, test } from ".";
|
import { many, test } from ".";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
||||||
test.skip(isDendrite, "due to Dendrite bug https://github.com/element-hq/dendrite/issues/2970");
|
|
||||||
|
|
||||||
test.describe("new messages", () => {
|
test.describe("new messages", () => {
|
||||||
test.describe("thread roots", () => {
|
test.describe("thread roots", () => {
|
||||||
test("Reading a thread root does not mark the thread as read", async ({
|
test("Reading a thread root does not mark the thread as read", async ({
|
||||||
|
|||||||
@@ -9,11 +9,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
/* See readme.md for tips on writing these tests. */
|
/* See readme.md for tips on writing these tests. */
|
||||||
|
|
||||||
import { test, expect } from ".";
|
import { test, expect } from ".";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
||||||
test.skip(isDendrite, "due to Dendrite bug https://github.com/element-hq/dendrite/issues/2970");
|
|
||||||
|
|
||||||
test.describe("reactions", () => {
|
test.describe("reactions", () => {
|
||||||
test.describe("in threads", () => {
|
test.describe("in threads", () => {
|
||||||
test("A reaction to a threaded message does not make the room unread", async ({
|
test("A reaction to a threaded message does not make the room unread", async ({
|
||||||
@@ -73,7 +70,11 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
|||||||
// Given a thread exists and I have marked it as read
|
// Given a thread exists and I have marked it as read
|
||||||
await util.goTo(room1);
|
await util.goTo(room1);
|
||||||
await util.assertRead(room2);
|
await util.assertRead(room2);
|
||||||
await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Reply1")]);
|
await util.receiveMessages(room2, [
|
||||||
|
"Msg1",
|
||||||
|
msg.threadedOff("Msg1", "Reply1"),
|
||||||
|
msg.reactionTo("Reply1", "🪿"),
|
||||||
|
]);
|
||||||
await util.assertUnread(room2, 1);
|
await util.assertUnread(room2, 1);
|
||||||
await util.markAsRead(room2);
|
await util.markAsRead(room2);
|
||||||
await util.assertRead(room2);
|
await util.assertRead(room2);
|
||||||
|
|||||||
@@ -9,11 +9,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
/* See readme.md for tips on writing these tests. */
|
/* See readme.md for tips on writing these tests. */
|
||||||
|
|
||||||
import { test } from ".";
|
import { test } from ".";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
||||||
test.skip(isDendrite, "due to Dendrite bug https://github.com/element-hq/dendrite/issues/2970");
|
|
||||||
|
|
||||||
test.describe("reactions", () => {
|
test.describe("reactions", () => {
|
||||||
test.describe("in the main timeline", () => {
|
test.describe("in the main timeline", () => {
|
||||||
test("Receiving a reaction to a message does not make a room unread", async ({
|
test("Receiving a reaction to a message does not make a room unread", async ({
|
||||||
|
|||||||
@@ -9,10 +9,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
/* See readme.md for tips on writing these tests. */
|
/* See readme.md for tips on writing these tests. */
|
||||||
|
|
||||||
import { test } from ".";
|
import { test } from ".";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
||||||
test.skip(isDendrite, "due to Dendrite bug https://github.com/element-hq/dendrite/issues/2970");
|
|
||||||
test.describe("reactions", () => {
|
test.describe("reactions", () => {
|
||||||
test.describe("thread roots", () => {
|
test.describe("thread roots", () => {
|
||||||
test("A reaction to a thread root does not make the room unread", async ({
|
test("A reaction to a thread root does not make the room unread", async ({
|
||||||
|
|||||||
@@ -12,10 +12,8 @@ import { expect } from "../../element-web-test";
|
|||||||
import { ElementAppPage } from "../../pages/ElementAppPage";
|
import { ElementAppPage } from "../../pages/ElementAppPage";
|
||||||
import { Bot } from "../../pages/bot";
|
import { Bot } from "../../pages/bot";
|
||||||
import { test } from ".";
|
import { test } from ".";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
||||||
test.skip(isDendrite, "due to Dendrite bug https://github.com/element-hq/dendrite/issues/2970");
|
|
||||||
test.use({
|
test.use({
|
||||||
displayName: "Mae",
|
displayName: "Mae",
|
||||||
botCreateOpts: { displayName: "Other User" },
|
botCreateOpts: { displayName: "Other User" },
|
||||||
@@ -102,7 +100,12 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
|||||||
await page.goto(`/#/room/${selectedRoomId}`);
|
await page.goto(`/#/room/${selectedRoomId}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("With sync accumulator, considers main thread and unthreaded receipts #24629", async ({ page, app, bot }) => {
|
// Disabled due to flakiness: https://github.com/element-hq/element-web/issues/26895
|
||||||
|
test.skip("With sync accumulator, considers main thread and unthreaded receipts #24629", async ({
|
||||||
|
page,
|
||||||
|
app,
|
||||||
|
bot,
|
||||||
|
}) => {
|
||||||
// Details are in https://github.com/vector-im/element-web/issues/24629
|
// Details are in https://github.com/vector-im/element-web/issues/24629
|
||||||
// This proves we've fixed one of the "stuck unreads" issues.
|
// This proves we've fixed one of the "stuck unreads" issues.
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
/* See readme.md for tips on writing these tests. */
|
/* See readme.md for tips on writing these tests. */
|
||||||
|
|
||||||
import { test } from ".";
|
import { test } from ".";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
||||||
test.skip(isDendrite, "due to Dendrite bug https://github.com/element-hq/dendrite/issues/2970");
|
|
||||||
|
|
||||||
test.describe("redactions", () => {
|
test.describe("redactions", () => {
|
||||||
test.describe("in threads", () => {
|
test.describe("in threads", () => {
|
||||||
test("Redacting the threaded message pointed to by my receipt leaves the room read", async ({
|
test("Redacting the threaded message pointed to by my receipt leaves the room read", async ({
|
||||||
|
|||||||
@@ -9,11 +9,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
/* See readme.md for tips on writing these tests. */
|
/* See readme.md for tips on writing these tests. */
|
||||||
|
|
||||||
import { test } from ".";
|
import { test } from ".";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
||||||
test.skip(isDendrite, "due to Dendrite bug https://github.com/element-hq/dendrite/issues/2970");
|
|
||||||
|
|
||||||
test.describe("redactions", () => {
|
test.describe("redactions", () => {
|
||||||
test.describe("in the main timeline", () => {
|
test.describe("in the main timeline", () => {
|
||||||
test("Redacting the message pointed to by my receipt leaves the room read", async ({
|
test("Redacting the message pointed to by my receipt leaves the room read", async ({
|
||||||
|
|||||||
@@ -9,11 +9,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
/* See readme.md for tips on writing these tests. */
|
/* See readme.md for tips on writing these tests. */
|
||||||
|
|
||||||
import { test } from ".";
|
import { test } from ".";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
|
||||||
test.skip(isDendrite, "due to Dendrite bug https://github.com/element-hq/dendrite/issues/2970");
|
|
||||||
|
|
||||||
test.describe("redactions", () => {
|
test.describe("redactions", () => {
|
||||||
test.describe("thread roots", () => {
|
test.describe("thread roots", () => {
|
||||||
test("Redacting a thread root after it was read leaves the room read", async ({
|
test("Redacting a thread root after it was read leaves the room read", async ({
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ test.describe("Email Registration", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test(
|
||||||
"registers an account and lands on the home page",
|
"registers an account and lands on the use case selection screen",
|
||||||
{ tag: "@screenshot" },
|
{ tag: "@screenshot" },
|
||||||
async ({ page, mailhogClient, request, checkA11y }) => {
|
async ({ page, mailhogClient, request, checkA11y }) => {
|
||||||
await expect(page.getByRole("textbox", { name: "Username" })).toBeVisible();
|
await expect(page.getByRole("textbox", { name: "Username" })).toBeVisible();
|
||||||
@@ -57,7 +57,7 @@ test.describe("Email Registration", async () => {
|
|||||||
const [emailLink] = messages.items[0].text.match(/http.+/);
|
const [emailLink] = messages.items[0].text.match(/http.+/);
|
||||||
await request.get(emailLink); // "Click" the link in the email
|
await request.get(emailLink); // "Click" the link in the email
|
||||||
|
|
||||||
await expect(page.getByText("Welcome alice")).toBeVisible();
|
await expect(page.locator(".mx_UseCaseSelection_skip")).toBeVisible();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
|
|
||||||
import { test, expect } from "../../element-web-test";
|
import { test, expect } from "../../element-web-test";
|
||||||
import { consentHomeserver } from "../../plugins/homeserver/synapse/consentHomeserver.ts";
|
import { consentHomeserver } from "../../plugins/homeserver/synapse/consentHomeserver.ts";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.use(consentHomeserver);
|
test.use(consentHomeserver);
|
||||||
test.use({
|
test.use({
|
||||||
@@ -24,8 +23,6 @@ test.use({
|
|||||||
});
|
});
|
||||||
|
|
||||||
test.describe("Registration", () => {
|
test.describe("Registration", () => {
|
||||||
test.skip(isDendrite, "Dendrite lacks support for MSC3967 so requires additional auth here");
|
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto("/#/register");
|
await page.goto("/#/register");
|
||||||
});
|
});
|
||||||
@@ -74,6 +71,12 @@ test.describe("Registration", () => {
|
|||||||
await expect(termsPolicy.getByLabel("Privacy Policy")).toBeVisible();
|
await expect(termsPolicy.getByLabel("Privacy Policy")).toBeVisible();
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Accept", exact: true }).click();
|
await page.getByRole("button", { name: "Accept", exact: true }).click();
|
||||||
|
|
||||||
|
await expect(page.locator(".mx_UseCaseSelection_skip")).toBeVisible();
|
||||||
|
await expect(page).toMatchScreenshot("use-case-selection.png", screenshotOptions);
|
||||||
|
await checkA11y();
|
||||||
|
await page.getByRole("button", { name: "Skip", exact: true }).click();
|
||||||
|
|
||||||
await expect(page).toHaveURL(/\/#\/home$/);
|
await expect(page).toHaveURL(/\/#\/home$/);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { Download, 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";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
const ROOM_NAME = "Test room";
|
const ROOM_NAME = "Test room";
|
||||||
const NAME = "Alice";
|
const NAME = "Alice";
|
||||||
@@ -182,8 +181,6 @@ test.describe("FilePanel", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test.describe("download", () => {
|
test.describe("download", () => {
|
||||||
test.skip(isDendrite, "due to a Dendrite sending Content-Disposition inline");
|
|
||||||
|
|
||||||
test("should download an image via the link on the panel", async ({ page, context }) => {
|
test("should download an image via the link on the panel", async ({ page, context }) => {
|
||||||
// Upload an image file
|
// Upload an image file
|
||||||
await uploadFile(page, "playwright/sample-files/riot.png");
|
await uploadFile(page, "playwright/sample-files/riot.png");
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const ROOM_NAME = "Test room";
|
|||||||
const NAME = "Alice";
|
const NAME = "Alice";
|
||||||
|
|
||||||
test.use({
|
test.use({
|
||||||
synapseConfig: {
|
synapseConfigOptions: {
|
||||||
presence: {
|
presence: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
include_offline_users_on_sync: false,
|
include_offline_users_on_sync: false,
|
||||||
|
|||||||
@@ -38,34 +38,29 @@ test.describe("RightPanel", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test.describe("in rooms", () => {
|
test.describe("in rooms", () => {
|
||||||
test(
|
test("should handle long room address and long room name", { tag: "@screenshot" }, async ({ page, app }) => {
|
||||||
"should handle long room address and long room name",
|
await app.client.createRoom({ name: ROOM_NAME_LONG });
|
||||||
{ tag: "@screenshot" },
|
await viewRoomSummaryByName(page, app, ROOM_NAME_LONG);
|
||||||
async ({ page, app, user }) => {
|
|
||||||
await app.client.createRoom({ name: ROOM_NAME_LONG });
|
|
||||||
await viewRoomSummaryByName(page, app, ROOM_NAME_LONG);
|
|
||||||
|
|
||||||
await app.settings.openRoomSettings();
|
await app.settings.openRoomSettings();
|
||||||
|
|
||||||
// Set a local room address
|
// Set a local room address
|
||||||
const localAddresses = page.locator(".mx_SettingsFieldset", { hasText: "Local Addresses" });
|
const localAddresses = page.locator(".mx_SettingsFieldset", { hasText: "Local Addresses" });
|
||||||
await localAddresses.getByRole("textbox").fill(ROOM_ADDRESS_LONG);
|
await localAddresses.getByRole("textbox").fill(ROOM_ADDRESS_LONG);
|
||||||
await expect(page.getByText("This address is available to use")).toBeVisible();
|
await localAddresses.getByRole("button", { name: "Add" }).click();
|
||||||
await localAddresses.getByRole("button", { name: "Add" }).click();
|
await expect(localAddresses.getByText(`#${ROOM_ADDRESS_LONG}:localhost`)).toHaveClass(
|
||||||
await expect(localAddresses.getByText(`#${ROOM_ADDRESS_LONG}:${user.homeServer}`)).toHaveClass(
|
"mx_EditableItem_item",
|
||||||
"mx_EditableItem_item",
|
);
|
||||||
);
|
|
||||||
|
|
||||||
await app.closeDialog();
|
await app.closeDialog();
|
||||||
|
|
||||||
// Close and reopen the right panel to render the room address
|
// Close and reopen the right panel to render the room address
|
||||||
await app.toggleRoomInfoPanel();
|
await app.toggleRoomInfoPanel();
|
||||||
await expect(page.locator(".mx_RightPanel")).not.toBeVisible();
|
await expect(page.locator(".mx_RightPanel")).not.toBeVisible();
|
||||||
await app.toggleRoomInfoPanel();
|
await app.toggleRoomInfoPanel();
|
||||||
|
|
||||||
await expect(page.locator(".mx_RightPanel")).toMatchScreenshot("with-name-and-address.png");
|
await expect(page.locator(".mx_RightPanel")).toMatchScreenshot("with-name-and-address.png");
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
test("should handle clicking add widgets", async ({ page, app }) => {
|
test("should handle clicking add widgets", async ({ page, app }) => {
|
||||||
await viewRoomSummaryByName(page, app, ROOM_NAME);
|
await viewRoomSummaryByName(page, app, ROOM_NAME);
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import type { Preset, Visibility } from "matrix-js-sdk/src/matrix";
|
|||||||
import { test, expect } from "../../element-web-test";
|
import { test, expect } from "../../element-web-test";
|
||||||
|
|
||||||
test.describe("Room Directory", () => {
|
test.describe("Room Directory", () => {
|
||||||
test.skip(({ homeserverType }) => homeserverType === "pinecone", "Pinecone's /publicRooms API takes forever");
|
|
||||||
test.use({
|
test.use({
|
||||||
displayName: "Ray",
|
displayName: "Ray",
|
||||||
botCreateOpts: { displayName: "Paul" },
|
botCreateOpts: { displayName: "Paul" },
|
||||||
@@ -31,16 +30,15 @@ test.describe("Room Directory", () => {
|
|||||||
// First add a local address `gaming`
|
// First add a local address `gaming`
|
||||||
const localAddresses = page.locator(".mx_SettingsFieldset", { hasText: "Local Addresses" });
|
const localAddresses = page.locator(".mx_SettingsFieldset", { hasText: "Local Addresses" });
|
||||||
await localAddresses.getByRole("textbox").fill("gaming");
|
await localAddresses.getByRole("textbox").fill("gaming");
|
||||||
await expect(page.getByText("This address is available to use")).toBeVisible();
|
|
||||||
await localAddresses.getByRole("button", { name: "Add" }).click();
|
await localAddresses.getByRole("button", { name: "Add" }).click();
|
||||||
await expect(localAddresses.getByText(`#gaming:${user.homeServer}`)).toHaveClass("mx_EditableItem_item");
|
await expect(localAddresses.getByText("#gaming:localhost")).toHaveClass("mx_EditableItem_item");
|
||||||
|
|
||||||
// Publish into the public rooms directory
|
// Publish into the public rooms directory
|
||||||
const publishedAddresses = page.locator(".mx_SettingsFieldset", { hasText: "Published Addresses" });
|
const publishedAddresses = page.locator(".mx_SettingsFieldset", { hasText: "Published Addresses" });
|
||||||
await expect(publishedAddresses.locator("#canonicalAlias")).toHaveValue(`#gaming:${user.homeServer}`);
|
await expect(publishedAddresses.locator("#canonicalAlias")).toHaveValue("#gaming:localhost");
|
||||||
const checkbox = publishedAddresses
|
const checkbox = publishedAddresses
|
||||||
.locator(".mx_SettingsFlag", {
|
.locator(".mx_SettingsFlag", {
|
||||||
hasText: `Publish this room to the public in ${user.homeServer}'s room directory?`,
|
hasText: "Publish this room to the public in localhost's room directory?",
|
||||||
})
|
})
|
||||||
.getByRole("switch");
|
.getByRole("switch");
|
||||||
await checkbox.check();
|
await checkbox.check();
|
||||||
@@ -88,7 +86,7 @@ test.describe("Room Directory", () => {
|
|||||||
.getByRole("button", { name: "Join" })
|
.getByRole("button", { name: "Join" })
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
await expect(page).toHaveURL(`/#/room/#test1234:${user.homeServer}`);
|
await expect(page).toHaveURL("/#/room/#test1234:localhost");
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -111,10 +111,6 @@ test.describe("Room Header", () => {
|
|||||||
async ({ page, app, user }) => {
|
async ({ page, app, user }) => {
|
||||||
await createVideoRoom(page, app);
|
await createVideoRoom(page, app);
|
||||||
|
|
||||||
// Dismiss a toast that is otherwise in the way (it's the other
|
|
||||||
// side but there's no need to have it in the screenshot)
|
|
||||||
await page.getByRole("button", { name: "Later" }).click();
|
|
||||||
|
|
||||||
const header = page.locator(".mx_RoomHeader");
|
const header = page.locator(".mx_RoomHeader");
|
||||||
|
|
||||||
// There's two room info button - the header itself and the i button
|
// There's two room info button - the header itself and the i button
|
||||||
|
|||||||
@@ -7,13 +7,10 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { test, expect } from "../../element-web-test";
|
import { test, expect } from "../../element-web-test";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
const TEST_ROOM_NAME = "The mark unread test room";
|
const TEST_ROOM_NAME = "The mark unread test room";
|
||||||
|
|
||||||
test.describe("Mark as Unread", () => {
|
test.describe("Mark as Unread", () => {
|
||||||
test.skip(isDendrite, "due to Dendrite bug https://github.com/element-hq/dendrite/issues/2970");
|
|
||||||
|
|
||||||
test.use({
|
test.use({
|
||||||
displayName: "Tom",
|
displayName: "Tom",
|
||||||
botCreateOpts: {
|
botCreateOpts: {
|
||||||
@@ -51,6 +48,6 @@ test.describe("Mark as Unread", () => {
|
|||||||
await roomTile.getByRole("button", { name: "Room options" }).click();
|
await roomTile.getByRole("button", { name: "Room options" }).click();
|
||||||
await page.getByRole("menuitem", { name: "Mark as unread" }).click();
|
await page.getByRole("menuitem", { name: "Mark as unread" }).click();
|
||||||
|
|
||||||
await expect(page.getByLabel(TEST_ROOM_NAME + " Unread messages.")).toBeVisible();
|
expect(page.getByLabel(TEST_ROOM_NAME + " Unread messages.")).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -34,14 +34,14 @@ test.describe("Account user settings tab", () => {
|
|||||||
await expect(profile.getByRole("textbox", { name: "Display Name" })).toHaveValue(USER_NAME);
|
await expect(profile.getByRole("textbox", { name: "Display Name" })).toHaveValue(USER_NAME);
|
||||||
|
|
||||||
// Assert that a userId is rendered
|
// Assert that a userId is rendered
|
||||||
await expect(uut.getByLabel("Username")).toHaveText(user.userId);
|
expect(uut.getByLabel("Username")).toHaveText(user.userId);
|
||||||
|
|
||||||
// Wait until spinners disappear
|
// Wait until spinners disappear
|
||||||
await expect(uut.getByTestId("accountSection").locator(".mx_Spinner")).not.toBeVisible();
|
await expect(uut.getByTestId("accountSection").locator(".mx_Spinner")).not.toBeVisible();
|
||||||
await expect(uut.getByTestId("discoverySection").locator(".mx_Spinner")).not.toBeVisible();
|
await expect(uut.getByTestId("discoverySection").locator(".mx_Spinner")).not.toBeVisible();
|
||||||
|
|
||||||
const accountSection = uut.getByTestId("accountSection");
|
const accountSection = uut.getByTestId("accountSection");
|
||||||
await accountSection.scrollIntoViewIfNeeded();
|
accountSection.scrollIntoViewIfNeeded();
|
||||||
// Assert that input areas for changing a password exists
|
// Assert that input areas for changing a password exists
|
||||||
await expect(accountSection.getByLabel("Current password")).toBeVisible();
|
await expect(accountSection.getByLabel("Current password")).toBeVisible();
|
||||||
await expect(accountSection.getByLabel("New Password")).toBeVisible();
|
await expect(accountSection.getByLabel("New Password")).toBeVisible();
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Page } from "@playwright/test";
|
|
||||||
import { GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api";
|
|
||||||
|
|
||||||
import { ElementAppPage } from "../../../pages/ElementAppPage";
|
|
||||||
import { test as base, expect } from "../../../element-web-test";
|
|
||||||
export { expect };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up for the encryption tab test
|
|
||||||
*/
|
|
||||||
export const test = base.extend<{
|
|
||||||
util: Helpers;
|
|
||||||
}>({
|
|
||||||
util: async ({ page, app, bot }, use) => {
|
|
||||||
await use(new Helpers(page, app));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
class Helpers {
|
|
||||||
constructor(
|
|
||||||
private page: Page,
|
|
||||||
private app: ElementAppPage,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open the encryption tab
|
|
||||||
*/
|
|
||||||
openEncryptionTab() {
|
|
||||||
return this.app.settings.openUserSettings("Encryption");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Go through the device verification flow using the recovery key.
|
|
||||||
*/
|
|
||||||
async verifyDevice(recoveryKey: GeneratedSecretStorageKey) {
|
|
||||||
// Select the security phrase
|
|
||||||
await this.page.getByRole("button", { name: "Verify with Security Key or Phrase" }).click();
|
|
||||||
await this.enterRecoveryKey(recoveryKey);
|
|
||||||
await this.page.getByRole("button", { name: "Done" }).click();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fill the recovery key in the dialog
|
|
||||||
* @param recoveryKey
|
|
||||||
*/
|
|
||||||
async enterRecoveryKey(recoveryKey: GeneratedSecretStorageKey) {
|
|
||||||
// Select to use recovery key
|
|
||||||
await this.page.getByRole("button", { name: "use your Security Key" }).click();
|
|
||||||
|
|
||||||
// Fill the recovery key
|
|
||||||
const dialog = this.page.locator(".mx_Dialog");
|
|
||||||
await dialog.getByRole("textbox").fill(recoveryKey.encodedPrivateKey);
|
|
||||||
await dialog.getByRole("button", { name: "Continue" }).click();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the encryption tab content
|
|
||||||
*/
|
|
||||||
getEncryptionTabContent() {
|
|
||||||
return this.page.getByTestId("encryptionTab");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the default key id of the secret storage to `null`
|
|
||||||
*/
|
|
||||||
async removeSecretStorageDefaultKeyId() {
|
|
||||||
const client = await this.app.client.prepareClient();
|
|
||||||
await client.evaluate(async (client) => {
|
|
||||||
await client.secretStorage.setDefaultKeyId(null);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the security key from the clipboard and fill in the input field
|
|
||||||
* Then click on the finish button
|
|
||||||
* @param title - The title of the dialog
|
|
||||||
* @param confirmButtonLabel - The label of the confirm button
|
|
||||||
* @param screenshot
|
|
||||||
*/
|
|
||||||
async confirmRecoveryKey(title: string, confirmButtonLabel: string, screenshot: `${string}.png`) {
|
|
||||||
const dialog = this.getEncryptionTabContent();
|
|
||||||
await expect(dialog.getByText(title, { exact: true })).toBeVisible();
|
|
||||||
await expect(dialog).toMatchScreenshot(screenshot);
|
|
||||||
|
|
||||||
const clipboardContent = await this.app.getClipboard();
|
|
||||||
await dialog.getByRole("textbox").fill(clipboardContent);
|
|
||||||
await dialog.getByRole("button", { name: confirmButtonLabel }).click();
|
|
||||||
await expect(dialog).toMatchScreenshot("default-recovery.png");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api";
|
|
||||||
|
|
||||||
import { test, expect } from ".";
|
|
||||||
import {
|
|
||||||
checkDeviceIsConnectedKeyBackup,
|
|
||||||
checkDeviceIsCrossSigned,
|
|
||||||
createBot,
|
|
||||||
deleteCachedSecrets,
|
|
||||||
verifySession,
|
|
||||||
} from "../../crypto/utils";
|
|
||||||
|
|
||||||
test.describe("Recovery section in Encryption tab", () => {
|
|
||||||
test.use({
|
|
||||||
displayName: "Alice",
|
|
||||||
});
|
|
||||||
|
|
||||||
let recoveryKey: GeneratedSecretStorageKey;
|
|
||||||
let expectedBackupVersion: string;
|
|
||||||
|
|
||||||
test.beforeEach(async ({ page, homeserver, credentials }) => {
|
|
||||||
const res = await createBot(page, homeserver, credentials);
|
|
||||||
recoveryKey = res.recoveryKey;
|
|
||||||
expectedBackupVersion = res.expectedBackupVersion;
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should verify the device", { tag: "@screenshot" }, async ({ page, app, util }) => {
|
|
||||||
const dialog = await util.openEncryptionTab();
|
|
||||||
|
|
||||||
// The user's device is in an unverified state, therefore the only option available to them here is to verify it
|
|
||||||
const verifyButton = dialog.getByRole("button", { name: "Verify this device" });
|
|
||||||
await expect(verifyButton).toBeVisible();
|
|
||||||
await expect(util.getEncryptionTabContent()).toMatchScreenshot("verify-device-encryption-tab.png");
|
|
||||||
await verifyButton.click();
|
|
||||||
|
|
||||||
await util.verifyDevice(recoveryKey);
|
|
||||||
await expect(util.getEncryptionTabContent()).toMatchScreenshot("default-recovery.png");
|
|
||||||
|
|
||||||
// Check that our device is now cross-signed
|
|
||||||
await checkDeviceIsCrossSigned(app);
|
|
||||||
|
|
||||||
// Check that the current device is connected to key backup
|
|
||||||
// The backup decryption key should be in cache also, as we got it directly from the 4S
|
|
||||||
await app.closeDialog();
|
|
||||||
await checkDeviceIsConnectedKeyBackup(page, expectedBackupVersion, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test(
|
|
||||||
"should change the recovery key",
|
|
||||||
{ tag: ["@screenshot", "@no-webkit"] },
|
|
||||||
async ({ page, app, homeserver, credentials, util, context }) => {
|
|
||||||
await verifySession(app, "new passphrase");
|
|
||||||
const dialog = await util.openEncryptionTab();
|
|
||||||
|
|
||||||
// The user can only change the recovery key
|
|
||||||
const changeButton = dialog.getByRole("button", { name: "Change recovery key" });
|
|
||||||
await expect(changeButton).toBeVisible();
|
|
||||||
await expect(util.getEncryptionTabContent()).toMatchScreenshot("default-recovery.png");
|
|
||||||
await changeButton.click();
|
|
||||||
|
|
||||||
// Display the new recovery key and click on the copy button
|
|
||||||
await expect(dialog.getByText("Change recovery key?")).toBeVisible();
|
|
||||||
await expect(util.getEncryptionTabContent()).toMatchScreenshot("change-key-1-encryption-tab.png", {
|
|
||||||
mask: [dialog.getByTestId("recoveryKey")],
|
|
||||||
});
|
|
||||||
await dialog.getByRole("button", { name: "Copy" }).click();
|
|
||||||
await dialog.getByRole("button", { name: "Continue" }).click();
|
|
||||||
|
|
||||||
// Confirm the recovery key
|
|
||||||
await util.confirmRecoveryKey(
|
|
||||||
"Enter your new recovery key",
|
|
||||||
"Confirm new recovery key",
|
|
||||||
"change-key-2-encryption-tab.png",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
test("should setup the recovery key", { tag: ["@screenshot", "@no-webkit"] }, async ({ page, app, util }) => {
|
|
||||||
await verifySession(app, "new passphrase");
|
|
||||||
await util.removeSecretStorageDefaultKeyId();
|
|
||||||
|
|
||||||
// The key backup is deleted and the user needs to set it up
|
|
||||||
const dialog = await util.openEncryptionTab();
|
|
||||||
const setupButton = dialog.getByRole("button", { name: "Set up recovery" });
|
|
||||||
await expect(setupButton).toBeVisible();
|
|
||||||
await expect(util.getEncryptionTabContent()).toMatchScreenshot("set-up-recovery.png");
|
|
||||||
await setupButton.click();
|
|
||||||
|
|
||||||
// Display an informative panel about the recovery key
|
|
||||||
await expect(dialog.getByRole("heading", { name: "Set up recovery" })).toBeVisible();
|
|
||||||
await expect(util.getEncryptionTabContent()).toMatchScreenshot("set-up-key-1-encryption-tab.png");
|
|
||||||
await dialog.getByRole("button", { name: "Continue" }).click();
|
|
||||||
|
|
||||||
// Display the new recovery key and click on the copy button
|
|
||||||
await expect(dialog.getByText("Save your recovery key somewhere safe")).toBeVisible();
|
|
||||||
await expect(util.getEncryptionTabContent()).toMatchScreenshot("set-up-key-2-encryption-tab.png", {
|
|
||||||
mask: [dialog.getByTestId("recoveryKey")],
|
|
||||||
});
|
|
||||||
await dialog.getByRole("button", { name: "Copy" }).click();
|
|
||||||
await dialog.getByRole("button", { name: "Continue" }).click();
|
|
||||||
|
|
||||||
// Confirm the recovery key
|
|
||||||
await util.confirmRecoveryKey(
|
|
||||||
"Enter your recovery key to confirm",
|
|
||||||
"Finish set up",
|
|
||||||
"set-up-key-3-encryption-tab.png",
|
|
||||||
);
|
|
||||||
|
|
||||||
// The recovery key is now set up and the user can change it
|
|
||||||
await expect(dialog.getByRole("button", { name: "Change recovery key" })).toBeVisible();
|
|
||||||
|
|
||||||
await app.closeDialog();
|
|
||||||
// Check that the current device is connected to key backup and the backup version is the expected one
|
|
||||||
await checkDeviceIsConnectedKeyBackup(page, "1", true);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test what happens if the cross-signing secrets are in secret storage but are not cached in the local DB.
|
|
||||||
//
|
|
||||||
// This can happen if we verified another device and secret-gossiping failed, or the other device itself lacked the secrets.
|
|
||||||
// We simulate this case by deleting the cached secrets in the indexedDB.
|
|
||||||
test(
|
|
||||||
"should enter the recovery key when the secrets are not cached",
|
|
||||||
{ tag: "@screenshot" },
|
|
||||||
async ({ page, app, util }) => {
|
|
||||||
await verifySession(app, "new passphrase");
|
|
||||||
// We need to delete the cached secrets
|
|
||||||
await deleteCachedSecrets(page);
|
|
||||||
|
|
||||||
await util.openEncryptionTab();
|
|
||||||
// We ask the user to enter the recovery key
|
|
||||||
const dialog = util.getEncryptionTabContent();
|
|
||||||
const enterKeyButton = dialog.getByRole("button", { name: "Enter recovery key" });
|
|
||||||
await expect(enterKeyButton).toBeVisible();
|
|
||||||
await expect(dialog).toMatchScreenshot("out-of-sync-recovery.png");
|
|
||||||
await enterKeyButton.click();
|
|
||||||
|
|
||||||
// Fill the recovery key
|
|
||||||
await util.enterRecoveryKey(recoveryKey);
|
|
||||||
await expect(dialog).toMatchScreenshot("default-recovery.png");
|
|
||||||
|
|
||||||
// Check that our device is now cross-signed
|
|
||||||
await checkDeviceIsCrossSigned(app);
|
|
||||||
|
|
||||||
// Check that the current device is connected to key backup
|
|
||||||
// The backup decryption key should be in cache also, as we got it directly from the 4S
|
|
||||||
await app.closeDialog();
|
|
||||||
await checkDeviceIsConnectedKeyBackup(page, expectedBackupVersion, true);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@@ -36,16 +36,15 @@ test.describe("General room settings tab", () => {
|
|||||||
await expect(settings.getByText("Show more")).toBeVisible();
|
await expect(settings.getByText("Show more")).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("long address should not cause dialog to overflow", { tag: "@no-webkit" }, async ({ page, app, user }) => {
|
test("long address should not cause dialog to overflow", { tag: "@no-webkit" }, async ({ page, app }) => {
|
||||||
const settings = await app.settings.openRoomSettings("General");
|
const settings = await app.settings.openRoomSettings("General");
|
||||||
// 1. Set the room-address to be a really long string
|
// 1. Set the room-address to be a really long string
|
||||||
const longString = "abcasdhjasjhdaj1jh1asdhasjdhajsdhjavhjksd".repeat(4);
|
const longString = "abcasdhjasjhdaj1jh1asdhasjdhajsdhjavhjksd".repeat(4);
|
||||||
await settings.locator("#roomAliases input[label='Room address']").fill(longString);
|
await settings.locator("#roomAliases input[label='Room address']").fill(longString);
|
||||||
await expect(page.getByText("This address is available to use")).toBeVisible();
|
|
||||||
await settings.locator("#roomAliases").getByText("Add", { exact: true }).click();
|
await settings.locator("#roomAliases").getByText("Add", { exact: true }).click();
|
||||||
|
|
||||||
// 2. wait for the new setting to apply ...
|
// 2. wait for the new setting to apply ...
|
||||||
await expect(settings.locator("#canonicalAlias")).toHaveValue(`#${longString}:${user.homeServer}`);
|
await expect(settings.locator("#canonicalAlias")).toHaveValue(`#${longString}:localhost`);
|
||||||
|
|
||||||
// 3. Check if the dialog overflows
|
// 3. Check if the dialog overflows
|
||||||
const dialogBoundingBox = await page.locator(".mx_Dialog").boundingBox();
|
const dialogBoundingBox = await page.locator(".mx_Dialog").boundingBox();
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ test.describe("Preferences user settings tab", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user }) => {
|
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user }) => {
|
||||||
await page.setViewportSize({ width: 1024, height: 3300 });
|
page.setViewportSize({ width: 1024, height: 3300 });
|
||||||
const tab = await app.settings.openUserSettings("Preferences");
|
const tab = await app.settings.openUserSettings("Preferences");
|
||||||
// Assert that the top heading is rendered
|
// Assert that the top heading is rendered
|
||||||
await expect(tab.getByRole("heading", { name: "Preferences" })).toBeVisible();
|
await expect(tab.getByRole("heading", { name: "Preferences" })).toBeVisible();
|
||||||
@@ -61,7 +61,7 @@ test.describe("Preferences user settings tab", () => {
|
|||||||
// Click the button to display the dropdown menu
|
// Click the button to display the dropdown menu
|
||||||
await timezoneInput.getByRole("button", { name: "Set timezone" }).click();
|
await timezoneInput.getByRole("button", { name: "Set timezone" }).click();
|
||||||
// Select a different value
|
// Select a different value
|
||||||
await timezoneInput.getByRole("option", { name: /Africa\/Abidjan/ }).click();
|
timezoneInput.getByRole("option", { name: /Africa\/Abidjan/ }).click();
|
||||||
// Check the new value
|
// Check the new value
|
||||||
await expect(timezoneValue.getByText("Africa/Abidjan")).toBeVisible();
|
await expect(timezoneValue.getByText("Africa/Abidjan")).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ test.describe("Share dialog", () => {
|
|||||||
|
|
||||||
const dialog = page.getByRole("dialog", { name: "Share room" });
|
const dialog = page.getByRole("dialog", { name: "Share room" });
|
||||||
await expect(dialog.getByText(`https://matrix.to/#/${room.roomId}`)).toBeVisible();
|
await expect(dialog.getByText(`https://matrix.to/#/${room.roomId}`)).toBeVisible();
|
||||||
await expect(dialog).toMatchScreenshot("share-dialog-room.png", {
|
expect(dialog).toMatchScreenshot("share-dialog-room.png", {
|
||||||
// QRCode and url changes at every run
|
// QRCode and url changes at every run
|
||||||
mask: [page.locator(".mx_QRCode"), page.locator(".mx_ShareDialog_top > span")],
|
mask: [page.locator(".mx_QRCode"), page.locator(".mx_ShareDialog_top > span")],
|
||||||
});
|
});
|
||||||
@@ -40,7 +40,7 @@ test.describe("Share dialog", () => {
|
|||||||
|
|
||||||
const dialog = page.getByRole("dialog", { name: "Share User" });
|
const dialog = page.getByRole("dialog", { name: "Share User" });
|
||||||
await expect(dialog.getByText(`https://matrix.to/#/${user.userId}`)).toBeVisible();
|
await expect(dialog.getByText(`https://matrix.to/#/${user.userId}`)).toBeVisible();
|
||||||
await expect(dialog).toMatchScreenshot("share-dialog-user.png", {
|
expect(dialog).toMatchScreenshot("share-dialog-user.png", {
|
||||||
// QRCode changes at every run
|
// QRCode changes at every run
|
||||||
mask: [page.locator(".mx_QRCode")],
|
mask: [page.locator(".mx_QRCode")],
|
||||||
});
|
});
|
||||||
@@ -57,7 +57,7 @@ test.describe("Share dialog", () => {
|
|||||||
|
|
||||||
const dialog = page.getByRole("dialog", { name: "Share Room Message" });
|
const dialog = page.getByRole("dialog", { name: "Share Room Message" });
|
||||||
await expect(dialog.getByRole("checkbox", { name: "Link to selected message" })).toBeChecked();
|
await expect(dialog.getByRole("checkbox", { name: "Link to selected message" })).toBeChecked();
|
||||||
await expect(dialog).toMatchScreenshot("share-dialog-event.png", {
|
expect(dialog).toMatchScreenshot("share-dialog-event.png", {
|
||||||
// QRCode and url changes at every run
|
// QRCode and url changes at every run
|
||||||
mask: [page.locator(".mx_QRCode"), page.locator(".mx_ShareDialog_top > span")],
|
mask: [page.locator(".mx_QRCode"), page.locator(".mx_ShareDialog_top > span")],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ test.describe("Sliding Sync", () => {
|
|||||||
await page.getByRole("menuitemradio", { name: "A-Z" }).dispatchEvent("click");
|
await page.getByRole("menuitemradio", { name: "A-Z" }).dispatchEvent("click");
|
||||||
await expect(page.locator(".mx_StyledRadioButton_checked").getByText("A-Z")).toBeVisible();
|
await expect(page.locator(".mx_StyledRadioButton_checked").getByText("A-Z")).toBeVisible();
|
||||||
|
|
||||||
|
await page.pause();
|
||||||
await checkOrder(["Apple", "Orange", "Pineapple", "Test Room"], page);
|
await checkOrder(["Apple", "Orange", "Pineapple", "Test Room"], page);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -275,7 +276,7 @@ test.describe("Sliding Sync", () => {
|
|||||||
// now rescind the invite
|
// now rescind the invite
|
||||||
await bot.evaluate(
|
await bot.evaluate(
|
||||||
async (client, { roomRescind, clientUserId }) => {
|
async (client, { roomRescind, clientUserId }) => {
|
||||||
await client.kick(roomRescind, clientUserId);
|
client.kick(roomRescind, clientUserId);
|
||||||
},
|
},
|
||||||
{ roomRescind, clientUserId },
|
{ roomRescind, clientUserId },
|
||||||
);
|
);
|
||||||
@@ -294,7 +295,7 @@ test.describe("Sliding Sync", () => {
|
|||||||
is_direct: true,
|
is_direct: true,
|
||||||
});
|
});
|
||||||
await app.client.evaluate(async (client, roomId) => {
|
await app.client.evaluate(async (client, roomId) => {
|
||||||
await client.setRoomTag(roomId, "m.favourite", { order: 0.5 });
|
client.setRoomTag(roomId, "m.favourite", { order: 0.5 });
|
||||||
}, roomId);
|
}, roomId);
|
||||||
await expect(page.getByRole("group", { name: "Favourites" }).getByText("Favourite DM")).toBeVisible();
|
await expect(page.getByRole("group", { name: "Favourites" }).getByText("Favourite DM")).toBeVisible();
|
||||||
await expect(page.getByRole("group", { name: "People" }).getByText("Favourite DM")).not.toBeAttached();
|
await expect(page.getByRole("group", { name: "People" }).getByText("Favourite DM")).not.toBeAttached();
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import type { Locator, Page } from "@playwright/test";
|
|||||||
import { test, expect } from "../../element-web-test";
|
import { test, expect } from "../../element-web-test";
|
||||||
import type { Preset, ICreateRoomOpts } from "matrix-js-sdk/src/matrix";
|
import type { Preset, ICreateRoomOpts } from "matrix-js-sdk/src/matrix";
|
||||||
import { ElementAppPage } from "../../pages/ElementAppPage";
|
import { ElementAppPage } from "../../pages/ElementAppPage";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
async function openSpaceCreateMenu(page: Page): Promise<Locator> {
|
async function openSpaceCreateMenu(page: Page): Promise<Locator> {
|
||||||
await page.getByRole("button", { name: "Create a space" }).click();
|
await page.getByRole("button", { name: "Create a space" }).click();
|
||||||
@@ -51,7 +50,6 @@ function spaceChildInitialState(roomId: string): ICreateRoomOpts["initial_state"
|
|||||||
}
|
}
|
||||||
|
|
||||||
test.describe("Spaces", () => {
|
test.describe("Spaces", () => {
|
||||||
test.skip(isDendrite, "due to a Dendrite bug https://github.com/element-hq/dendrite/issues/3488");
|
|
||||||
test.use({
|
test.use({
|
||||||
displayName: "Sue",
|
displayName: "Sue",
|
||||||
botCreateOpts: { displayName: "BotBob" },
|
botCreateOpts: { displayName: "BotBob" },
|
||||||
@@ -84,7 +82,7 @@ test.describe("Spaces", () => {
|
|||||||
|
|
||||||
// Copy matrix.to link
|
// Copy matrix.to link
|
||||||
await page.getByRole("button", { name: "Share invite link" }).click();
|
await page.getByRole("button", { name: "Share invite link" }).click();
|
||||||
expect(await app.getClipboard()).toEqual(`https://matrix.to/#/#lets-have-a-riot:${user.homeServer}`);
|
expect(await app.getClipboardText()).toEqual("https://matrix.to/#/#lets-have-a-riot:localhost");
|
||||||
|
|
||||||
// Go to space home
|
// Go to space home
|
||||||
await page.getByRole("button", { name: "Go to my first room" }).click();
|
await page.getByRole("button", { name: "Go to my first room" }).click();
|
||||||
@@ -171,13 +169,13 @@ test.describe("Spaces", () => {
|
|||||||
room_alias_name: "space",
|
room_alias_name: "space",
|
||||||
});
|
});
|
||||||
|
|
||||||
const menu = await openSpaceContextMenu(page, app, `#space:${user.homeServer}`);
|
const menu = await openSpaceContextMenu(page, app, "#space:localhost");
|
||||||
await menu.getByRole("menuitem", { name: "Invite" }).click();
|
await menu.getByRole("menuitem", { name: "Invite" }).click();
|
||||||
|
|
||||||
const shareDialog = page.locator(".mx_SpacePublicShare");
|
const shareDialog = page.locator(".mx_SpacePublicShare");
|
||||||
// Copy link first
|
// Copy link first
|
||||||
await shareDialog.getByRole("button", { name: "Share invite link" }).click();
|
await shareDialog.getByRole("button", { name: "Share invite link" }).click();
|
||||||
expect(await app.getClipboard()).toEqual(`https://matrix.to/#/#space:${user.homeServer}`);
|
expect(await app.getClipboardText()).toEqual("https://matrix.to/#/#space:localhost");
|
||||||
// Start Matrix invite flow
|
// Start Matrix invite flow
|
||||||
await shareDialog.getByRole("button", { name: "Invite people" }).click();
|
await shareDialog.getByRole("button", { name: "Invite people" }).click();
|
||||||
|
|
||||||
|
|||||||
@@ -38,13 +38,11 @@ export const test = base.extend<{
|
|||||||
room1Name: "Room 1",
|
room1Name: "Room 1",
|
||||||
room1: async ({ room1Name: name, app, user, bot }, use) => {
|
room1: async ({ room1Name: name, app, user, bot }, use) => {
|
||||||
const roomId = await app.client.createRoom({ name, invite: [bot.credentials.userId] });
|
const roomId = await app.client.createRoom({ name, invite: [bot.credentials.userId] });
|
||||||
await bot.awaitRoomMembership(roomId);
|
|
||||||
await use({ name, roomId });
|
await use({ name, roomId });
|
||||||
},
|
},
|
||||||
room2Name: "Room 2",
|
room2Name: "Room 2",
|
||||||
room2: async ({ room2Name: name, app, user, bot }, use) => {
|
room2: async ({ room2Name: name, app, user, bot }, use) => {
|
||||||
const roomId = await app.client.createRoom({ name, invite: [bot.credentials.userId] });
|
const roomId = await app.client.createRoom({ name, invite: [bot.credentials.userId] });
|
||||||
await bot.awaitRoomMembership(roomId);
|
|
||||||
await use({ name, roomId });
|
await use({ name, roomId });
|
||||||
},
|
},
|
||||||
msg: async ({ page, app, util }, use) => {
|
msg: async ({ page, app, util }, use) => {
|
||||||
|
|||||||
@@ -8,14 +8,8 @@
|
|||||||
|
|
||||||
import { expect, test } from ".";
|
import { expect, test } from ".";
|
||||||
import { CommandOrControl } from "../../utils";
|
import { CommandOrControl } from "../../utils";
|
||||||
import { isDendrite } from "../../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Threads Activity Centre", { tag: "@no-firefox" }, () => {
|
test.describe("Threads Activity Centre", { tag: "@no-firefox" }, () => {
|
||||||
test.skip(
|
|
||||||
isDendrite,
|
|
||||||
"due to Dendrite lacking full threads support https://github.com/element-hq/dendrite/issues/3283",
|
|
||||||
);
|
|
||||||
|
|
||||||
test.use({
|
test.use({
|
||||||
displayName: "Alice",
|
displayName: "Alice",
|
||||||
botCreateOpts: { displayName: "Other User" },
|
botCreateOpts: { displayName: "Other User" },
|
||||||
|
|||||||
@@ -6,13 +6,12 @@ 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 { AccountDataEvents, Visibility } from "matrix-js-sdk/src/matrix";
|
import type { AccountDataEvents } from "matrix-js-sdk/src/matrix";
|
||||||
import { test as base, expect } from "../../element-web-test";
|
import { test, expect } from "../../element-web-test";
|
||||||
import { Filter } from "../../pages/Spotlight";
|
import { Filter } from "../../pages/Spotlight";
|
||||||
import { Bot } from "../../pages/bot";
|
import { Bot } from "../../pages/bot";
|
||||||
import type { Locator, Page } from "@playwright/test";
|
import type { Locator, Page } from "@playwright/test";
|
||||||
import type { ElementAppPage } from "../../pages/ElementAppPage";
|
import type { ElementAppPage } from "../../pages/ElementAppPage";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
function roomHeaderName(page: Page): Locator {
|
function roomHeaderName(page: Page): Locator {
|
||||||
return page.locator(".mx_RoomHeader_heading");
|
return page.locator(".mx_RoomHeader_heading");
|
||||||
@@ -39,37 +38,41 @@ async function startDM(app: ElementAppPage, page: Page, name: string): Promise<v
|
|||||||
await expect(page.getByRole("group", { name: "People" }).getByText(name)).toBeAttached();
|
await expect(page.getByRole("group", { name: "People" }).getByText(name)).toBeAttached();
|
||||||
}
|
}
|
||||||
|
|
||||||
type RoomRef = { name: string; roomId: string };
|
test.describe("Spotlight", () => {
|
||||||
const test = base.extend<{
|
const bot1Name = "BotBob";
|
||||||
bot1: Bot;
|
let bot1: Bot;
|
||||||
bot2: Bot;
|
|
||||||
room1: RoomRef;
|
const bot2Name = "ByteBot";
|
||||||
room2: RoomRef;
|
let bot2: Bot;
|
||||||
room3: RoomRef;
|
|
||||||
}>({
|
const room1Name = "247";
|
||||||
bot1: async ({ page, homeserver }, use, testInfo) => {
|
let room1Id: string;
|
||||||
const bot = new Bot(page, homeserver, { displayName: `BotBob_${testInfo.testId}`, autoAcceptInvites: true });
|
|
||||||
await use(bot);
|
const room2Name = "Lounge";
|
||||||
},
|
let room2Id: string;
|
||||||
bot2: async ({ page, homeserver }, use, testInfo) => {
|
|
||||||
const bot = new Bot(page, homeserver, { displayName: `ByteBot_${testInfo.testId}`, autoAcceptInvites: true });
|
const room3Name = "Public";
|
||||||
await use(bot);
|
let room3Id: string;
|
||||||
},
|
|
||||||
room1: async ({ app }, use) => {
|
test.use({
|
||||||
const name = "247";
|
displayName: "Jim",
|
||||||
const roomId = await app.client.createRoom({ name, visibility: "public" as Visibility });
|
});
|
||||||
await use({ name, roomId });
|
|
||||||
},
|
test.beforeEach(async ({ page, homeserver, app, user }) => {
|
||||||
room2: async ({ bot2 }, use) => {
|
bot1 = new Bot(page, homeserver, { displayName: bot1Name, autoAcceptInvites: true });
|
||||||
const name = "Lounge";
|
bot2 = new Bot(page, homeserver, { displayName: bot2Name, autoAcceptInvites: true });
|
||||||
const roomId = await bot2.createRoom({ name, visibility: "public" as Visibility });
|
const Visibility = await page.evaluate(() => (window as any).matrixcs.Visibility);
|
||||||
await use({ name, roomId });
|
|
||||||
},
|
room1Id = await app.client.createRoom({ name: room1Name, visibility: Visibility.Public });
|
||||||
room3: async ({ bot2 }, use) => {
|
|
||||||
const name = "Public";
|
await bot1.joinRoom(room1Id);
|
||||||
const roomId = await bot2.createRoom({
|
const bot1UserId = await bot1.evaluate((client) => client.getUserId());
|
||||||
name,
|
room2Id = await bot2.createRoom({ name: room2Name, visibility: Visibility.Public });
|
||||||
visibility: "public" as Visibility,
|
await bot2.inviteUser(room2Id, bot1UserId);
|
||||||
|
|
||||||
|
room3Id = await bot2.createRoom({
|
||||||
|
name: room3Name,
|
||||||
|
visibility: Visibility.Public,
|
||||||
initial_state: [
|
initial_state: [
|
||||||
{
|
{
|
||||||
type: "m.room.history_visibility",
|
type: "m.room.history_visibility",
|
||||||
@@ -80,27 +83,9 @@ const test = base.extend<{
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
await use({ name, roomId });
|
await bot2.inviteUser(room3Id, bot1UserId);
|
||||||
},
|
|
||||||
context: async ({ context, homeserver }, use) => {
|
|
||||||
// Restart the homeserver to wipe its in-memory db so we can reuse the same user ID without cross-signing prompts
|
|
||||||
await homeserver.restart();
|
|
||||||
await use(context);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Spotlight", () => {
|
await page.goto("/#/room/" + room1Id);
|
||||||
test.skip(isDendrite, "due to a Dendrite bug https://github.com/element-hq/dendrite/issues/3488");
|
|
||||||
test.use({
|
|
||||||
displayName: "Jim",
|
|
||||||
});
|
|
||||||
|
|
||||||
test.beforeEach(async ({ page, user, bot1, bot2, room1, room2, room3 }) => {
|
|
||||||
await bot1.joinRoom(room1.roomId);
|
|
||||||
await bot2.inviteUser(room2.roomId, bot1.credentials.userId);
|
|
||||||
await bot2.inviteUser(room3.roomId, bot1.credentials.userId);
|
|
||||||
|
|
||||||
await page.goto(`/#/room/${room1.roomId}`);
|
|
||||||
await expect(page.locator(".mx_RoomSublist_skeletonUI")).not.toBeAttached();
|
await expect(page.locator(".mx_RoomSublist_skeletonUI")).not.toBeAttached();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -132,69 +117,69 @@ test.describe("Spotlight", () => {
|
|||||||
await expect(spotlight.dialog.locator(".mx_SpotlightDialog_filter")).not.toBeAttached();
|
await expect(spotlight.dialog.locator(".mx_SpotlightDialog_filter")).not.toBeAttached();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should find joined rooms", async ({ page, app, room1 }) => {
|
test("should find joined rooms", async ({ page, app }) => {
|
||||||
const spotlight = await app.openSpotlight();
|
const spotlight = await app.openSpotlight();
|
||||||
await page.waitForTimeout(500); // wait for the dialog to settle
|
await page.waitForTimeout(500); // wait for the dialog to settle
|
||||||
await spotlight.search(room1.name);
|
await spotlight.search(room1Name);
|
||||||
const resultLocator = spotlight.results;
|
const resultLocator = spotlight.results;
|
||||||
await expect(resultLocator).toHaveCount(1);
|
await expect(resultLocator).toHaveCount(1);
|
||||||
await expect(resultLocator.first()).toContainText(room1.name);
|
await expect(resultLocator.first()).toContainText(room1Name);
|
||||||
await resultLocator.first().click();
|
await resultLocator.first().click();
|
||||||
await expect(page).toHaveURL(new RegExp(`#/room/${room1.roomId}`));
|
await expect(page).toHaveURL(new RegExp(`#/room/${room1Id}`));
|
||||||
await expect(roomHeaderName(page)).toContainText(room1.name);
|
await expect(roomHeaderName(page)).toContainText(room1Name);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should find known public rooms", async ({ page, app, room1 }) => {
|
test("should find known public rooms", async ({ page, app }) => {
|
||||||
const spotlight = await app.openSpotlight();
|
const spotlight = await app.openSpotlight();
|
||||||
await page.waitForTimeout(500); // wait for the dialog to settle
|
await page.waitForTimeout(500); // wait for the dialog to settle
|
||||||
await spotlight.filter(Filter.PublicRooms);
|
await spotlight.filter(Filter.PublicRooms);
|
||||||
await spotlight.search(room1.name);
|
await spotlight.search(room1Name);
|
||||||
const resultLocator = spotlight.results;
|
const resultLocator = spotlight.results;
|
||||||
await expect(resultLocator).toHaveCount(1);
|
await expect(resultLocator).toHaveCount(1);
|
||||||
await expect(resultLocator.first()).toContainText(room1.name);
|
await expect(resultLocator.first()).toContainText(room1Name);
|
||||||
await expect(resultLocator.first()).toContainText("View");
|
await expect(resultLocator.first()).toContainText("View");
|
||||||
await resultLocator.first().click();
|
await resultLocator.first().click();
|
||||||
await expect(page).toHaveURL(new RegExp(`#/room/${room1.roomId}`));
|
await expect(page).toHaveURL(new RegExp(`#/room/${room1Id}`));
|
||||||
await expect(roomHeaderName(page)).toContainText(room1.name);
|
await expect(roomHeaderName(page)).toContainText(room1Name);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should find unknown public rooms", async ({ page, app, room2 }) => {
|
test("should find unknown public rooms", async ({ page, app }) => {
|
||||||
const spotlight = await app.openSpotlight();
|
const spotlight = await app.openSpotlight();
|
||||||
await page.waitForTimeout(500); // wait for the dialog to settle
|
await page.waitForTimeout(500); // wait for the dialog to settle
|
||||||
await spotlight.filter(Filter.PublicRooms);
|
await spotlight.filter(Filter.PublicRooms);
|
||||||
await spotlight.search(room2.name);
|
await spotlight.search(room2Name);
|
||||||
const resultLocator = spotlight.results;
|
const resultLocator = spotlight.results;
|
||||||
await expect(resultLocator).toHaveCount(1);
|
await expect(resultLocator).toHaveCount(1);
|
||||||
await expect(resultLocator.first()).toContainText(room2.name);
|
await expect(resultLocator.first()).toContainText(room2Name);
|
||||||
await expect(resultLocator.first()).toContainText("Join");
|
await expect(resultLocator.first()).toContainText("Join");
|
||||||
await resultLocator.first().click();
|
await resultLocator.first().click();
|
||||||
await expect(page).toHaveURL(new RegExp(`#/room/${room2.roomId}`));
|
await expect(page).toHaveURL(new RegExp(`#/room/${room2Id}`));
|
||||||
await expect(page.locator(".mx_RoomView_MessageList")).toHaveCount(1);
|
await expect(page.locator(".mx_RoomView_MessageList")).toHaveCount(1);
|
||||||
await expect(roomHeaderName(page)).toContainText(room2.name);
|
await expect(roomHeaderName(page)).toContainText(room2Name);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should find unknown public world readable rooms", async ({ page, app, room3 }) => {
|
test("should find unknown public world readable rooms", async ({ page, app }) => {
|
||||||
const spotlight = await app.openSpotlight();
|
const spotlight = await app.openSpotlight();
|
||||||
await page.waitForTimeout(500); // wait for the dialog to settle
|
await page.waitForTimeout(500); // wait for the dialog to settle
|
||||||
await spotlight.filter(Filter.PublicRooms);
|
await spotlight.filter(Filter.PublicRooms);
|
||||||
await spotlight.search(room3.name);
|
await spotlight.search(room3Name);
|
||||||
const resultLocator = spotlight.results;
|
const resultLocator = spotlight.results;
|
||||||
await expect(resultLocator).toHaveCount(1);
|
await expect(resultLocator).toHaveCount(1);
|
||||||
await expect(resultLocator.first()).toContainText(room3.name);
|
await expect(resultLocator.first()).toContainText(room3Name);
|
||||||
await expect(resultLocator.first()).toContainText("View");
|
await expect(resultLocator.first()).toContainText("View");
|
||||||
await resultLocator.first().click();
|
await resultLocator.first().click();
|
||||||
await expect(page).toHaveURL(new RegExp(`#/room/${room3.roomId}`));
|
await expect(page).toHaveURL(new RegExp(`#/room/${room3Id}`));
|
||||||
await page.getByRole("button", { name: "Join the discussion" }).click();
|
await page.getByRole("button", { name: "Join the discussion" }).click();
|
||||||
await expect(roomHeaderName(page)).toHaveText(room3.name);
|
await expect(roomHeaderName(page)).toHaveText(room3Name);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: We currently can’t test finding rooms on other homeservers/other protocols
|
// TODO: We currently can’t test finding rooms on other homeservers/other protocols
|
||||||
// We obviously don’t have federation or bridges in local e2e tests
|
// We obviously don’t have federation or bridges in local e2e tests
|
||||||
test.skip("should find unknown public rooms on other homeservers", async ({ page, app, room3 }) => {
|
test.skip("should find unknown public rooms on other homeservers", async ({ page, app }) => {
|
||||||
const spotlight = await app.openSpotlight();
|
const spotlight = await app.openSpotlight();
|
||||||
await page.waitForTimeout(500); // wait for the dialog to settle
|
await page.waitForTimeout(500); // wait for the dialog to settle
|
||||||
await spotlight.filter(Filter.PublicRooms);
|
await spotlight.filter(Filter.PublicRooms);
|
||||||
await spotlight.search(room3.name);
|
await spotlight.search(room3Name);
|
||||||
await page.locator("[aria-haspopup=true][role=button]").click();
|
await page.locator("[aria-haspopup=true][role=button]").click();
|
||||||
|
|
||||||
await page
|
await page
|
||||||
@@ -209,20 +194,20 @@ test.describe("Spotlight", () => {
|
|||||||
|
|
||||||
const resultLocator = spotlight.results;
|
const resultLocator = spotlight.results;
|
||||||
await expect(resultLocator).toHaveCount(1);
|
await expect(resultLocator).toHaveCount(1);
|
||||||
await expect(resultLocator.first()).toContainText(room3.name);
|
await expect(resultLocator.first()).toContainText(room3Name);
|
||||||
await expect(resultLocator.first()).toContainText(room3.roomId);
|
await expect(resultLocator.first()).toContainText(room3Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should find known people", async ({ page, app, bot1 }) => {
|
test("should find known people", async ({ page, app }) => {
|
||||||
const spotlight = await app.openSpotlight();
|
const spotlight = await app.openSpotlight();
|
||||||
await page.waitForTimeout(500); // wait for the dialog to settle
|
await page.waitForTimeout(500); // wait for the dialog to settle
|
||||||
await spotlight.filter(Filter.People);
|
await spotlight.filter(Filter.People);
|
||||||
await spotlight.search(bot1.credentials.displayName);
|
await spotlight.search(bot1Name);
|
||||||
const resultLocator = spotlight.results;
|
const resultLocator = spotlight.results;
|
||||||
await expect(resultLocator).toHaveCount(1);
|
await expect(resultLocator).toHaveCount(1);
|
||||||
await expect(resultLocator.first()).toContainText(bot1.credentials.displayName);
|
await expect(resultLocator.first()).toContainText(bot1Name);
|
||||||
await resultLocator.first().click();
|
await resultLocator.first().click();
|
||||||
await expect(roomHeaderName(page)).toHaveText(bot1.credentials.displayName);
|
await expect(roomHeaderName(page)).toHaveText(bot1Name);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -232,41 +217,42 @@ test.describe("Spotlight", () => {
|
|||||||
*
|
*
|
||||||
* https://github.com/matrix-org/synapse/issues/16472
|
* https://github.com/matrix-org/synapse/issues/16472
|
||||||
*/
|
*/
|
||||||
test("should find unknown people", async ({ page, app, bot2 }) => {
|
test("should find unknown people", async ({ page, app }) => {
|
||||||
const spotlight = await app.openSpotlight();
|
const spotlight = await app.openSpotlight();
|
||||||
await page.waitForTimeout(500); // wait for the dialog to settle
|
await page.waitForTimeout(500); // wait for the dialog to settle
|
||||||
await spotlight.filter(Filter.People);
|
await spotlight.filter(Filter.People);
|
||||||
await spotlight.search(bot2.credentials.displayName);
|
await spotlight.search(bot2Name);
|
||||||
const resultLocator = spotlight.results;
|
const resultLocator = spotlight.results;
|
||||||
await expect(resultLocator).toHaveCount(1);
|
await expect(resultLocator).toHaveCount(1);
|
||||||
await expect(resultLocator.first()).toContainText(bot2.credentials.displayName);
|
await expect(resultLocator.first()).toContainText(bot2Name);
|
||||||
await resultLocator.first().click();
|
await resultLocator.first().click();
|
||||||
await expect(roomHeaderName(page)).toHaveText(bot2.credentials.displayName);
|
await expect(roomHeaderName(page)).toHaveText(bot2Name);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should find group DMs by usernames or user ids", async ({ page, app, bot1, bot2, room1 }) => {
|
test("should find group DMs by usernames or user ids", async ({ page, app }) => {
|
||||||
// First we want to share a room with both bots to ensure we’ve got their usernames cached
|
// First we want to share a room with both bots to ensure we’ve got their usernames cached
|
||||||
await app.client.inviteUser(room1.roomId, bot2.credentials.userId);
|
const bot2UserId = await bot2.evaluate((client) => client.getUserId());
|
||||||
|
await app.client.inviteUser(room1Id, bot2UserId);
|
||||||
|
|
||||||
// Starting a DM with ByteBot (will be turned into a group dm later)
|
// Starting a DM with ByteBot (will be turned into a group dm later)
|
||||||
let spotlight = await app.openSpotlight();
|
let spotlight = await app.openSpotlight();
|
||||||
await page.waitForTimeout(500); // wait for the dialog to settle
|
await page.waitForTimeout(500); // wait for the dialog to settle
|
||||||
await spotlight.filter(Filter.People);
|
await spotlight.filter(Filter.People);
|
||||||
await spotlight.search(bot2.credentials.displayName);
|
await spotlight.search(bot2Name);
|
||||||
let resultLocator = spotlight.results;
|
let resultLocator = spotlight.results;
|
||||||
await expect(resultLocator).toHaveCount(1);
|
await expect(resultLocator).toHaveCount(1);
|
||||||
await expect(resultLocator.first()).toContainText(bot2.credentials.displayName);
|
await expect(resultLocator.first()).toContainText(bot2Name);
|
||||||
await resultLocator.first().click();
|
await resultLocator.first().click();
|
||||||
|
|
||||||
// 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(bot2Name);
|
||||||
const locator = page.getByRole("textbox", { name: "Send a message…" });
|
const locator = page.getByRole("textbox", { name: "Send a message…" });
|
||||||
await locator.fill("Hey!");
|
await locator.fill("Hey!");
|
||||||
await locator.press("Enter");
|
await locator.press("Enter");
|
||||||
|
|
||||||
// Assert DM exists by checking for the first message and the room being in the room list
|
// Assert DM exists by checking for the first message and the room being in the room list
|
||||||
await expect(page.locator(".mx_EventTile_body").filter({ hasText: "Hey!" })).toBeAttached({ timeout: 3000 });
|
await expect(page.locator(".mx_EventTile_body").filter({ hasText: "Hey!" })).toBeAttached({ timeout: 3000 });
|
||||||
await expect(page.getByRole("group", { name: "People" })).toContainText(bot2.credentials.displayName);
|
await expect(page.getByRole("group", { name: "People" })).toContainText(bot2Name);
|
||||||
|
|
||||||
// Invite BotBob into existing DM with ByteBot
|
// Invite BotBob into existing DM with ByteBot
|
||||||
const dmRooms = await app.client.evaluate((client, userId) => {
|
const dmRooms = await app.client.evaluate((client, userId) => {
|
||||||
@@ -274,17 +260,18 @@ test.describe("Spotlight", () => {
|
|||||||
.getAccountData("m.direct" as keyof AccountDataEvents)
|
.getAccountData("m.direct" as keyof AccountDataEvents)
|
||||||
?.getContent<Record<string, string[]>>();
|
?.getContent<Record<string, string[]>>();
|
||||||
return map[userId] ?? [];
|
return map[userId] ?? [];
|
||||||
}, bot2.credentials.userId);
|
}, bot2UserId);
|
||||||
expect(dmRooms).toHaveLength(1);
|
expect(dmRooms).toHaveLength(1);
|
||||||
const groupDmName = await app.client.evaluate((client, id) => client.getRoom(id).name, dmRooms[0]);
|
const groupDmName = await app.client.evaluate((client, id) => client.getRoom(id).name, dmRooms[0]);
|
||||||
await app.client.inviteUser(dmRooms[0], bot1.credentials.userId);
|
const bot1UserId = await bot1.evaluate((client) => client.getUserId());
|
||||||
|
await app.client.inviteUser(dmRooms[0], bot1UserId);
|
||||||
await expect(roomHeaderName(page).first()).toContainText(groupDmName);
|
await expect(roomHeaderName(page).first()).toContainText(groupDmName);
|
||||||
await expect(page.getByRole("group", { name: "People" }).first()).toContainText(groupDmName);
|
await expect(page.getByRole("group", { name: "People" }).first()).toContainText(groupDmName);
|
||||||
|
|
||||||
// Search for BotBob by id, should return group DM and user
|
// Search for BotBob by id, should return group DM and user
|
||||||
spotlight = await app.openSpotlight();
|
spotlight = await app.openSpotlight();
|
||||||
await spotlight.filter(Filter.People);
|
await spotlight.filter(Filter.People);
|
||||||
await spotlight.search(bot1.credentials.userId);
|
await spotlight.search(bot1UserId);
|
||||||
await page.waitForTimeout(1000); // wait for the dialog to settle
|
await page.waitForTimeout(1000); // wait for the dialog to settle
|
||||||
resultLocator = spotlight.results;
|
resultLocator = spotlight.results;
|
||||||
await expect(resultLocator).toHaveCount(2);
|
await expect(resultLocator).toHaveCount(2);
|
||||||
@@ -297,7 +284,7 @@ test.describe("Spotlight", () => {
|
|||||||
// Search for ByteBot by id, should return group DM and user
|
// Search for ByteBot by id, should return group DM and user
|
||||||
spotlight = await app.openSpotlight();
|
spotlight = await app.openSpotlight();
|
||||||
await spotlight.filter(Filter.People);
|
await spotlight.filter(Filter.People);
|
||||||
await spotlight.search(bot2.credentials.userId);
|
await spotlight.search(bot2UserId);
|
||||||
await page.waitForTimeout(1000); // wait for the dialog to settle
|
await page.waitForTimeout(1000); // wait for the dialog to settle
|
||||||
resultLocator = spotlight.results;
|
resultLocator = spotlight.results;
|
||||||
await expect(resultLocator).toHaveCount(2);
|
await expect(resultLocator).toHaveCount(2);
|
||||||
@@ -310,10 +297,11 @@ test.describe("Spotlight", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Test against https://github.com/vector-im/element-web/issues/22851
|
// Test against https://github.com/vector-im/element-web/issues/22851
|
||||||
test("should show each person result only once", async ({ page, app, bot1 }) => {
|
test("should show each person result only once", async ({ page, app }) => {
|
||||||
const spotlight = await app.openSpotlight();
|
const spotlight = await app.openSpotlight();
|
||||||
await page.waitForTimeout(500); // wait for the dialog to settle
|
await page.waitForTimeout(500); // wait for the dialog to settle
|
||||||
await spotlight.filter(Filter.People);
|
await spotlight.filter(Filter.People);
|
||||||
|
const bot1UserId = await bot1.evaluate((client) => client.getUserId());
|
||||||
|
|
||||||
// 2 rounds of search to simulate the bug conditions. Specifically, the first search
|
// 2 rounds of search to simulate the bug conditions. Specifically, the first search
|
||||||
// should have 1 result (not 2) and the second search should also have 1 result (instead
|
// should have 1 result (not 2) and the second search should also have 1 result (instead
|
||||||
@@ -322,24 +310,24 @@ test.describe("Spotlight", () => {
|
|||||||
// We search for user ID to trigger the profile lookup within the dialog.
|
// We search for user ID to trigger the profile lookup within the dialog.
|
||||||
for (let i = 0; i < 2; i++) {
|
for (let i = 0; i < 2; i++) {
|
||||||
console.log("Iteration: " + i);
|
console.log("Iteration: " + i);
|
||||||
await spotlight.search(bot1.credentials.userId);
|
await spotlight.search(bot1UserId);
|
||||||
await page.waitForTimeout(1000); // wait for the dialog to settle
|
await page.waitForTimeout(1000); // wait for the dialog to settle
|
||||||
const resultLocator = spotlight.results;
|
const resultLocator = spotlight.results;
|
||||||
await expect(resultLocator).toHaveCount(1);
|
await expect(resultLocator).toHaveCount(1);
|
||||||
await expect(resultLocator.first()).toContainText(bot1.credentials.userId);
|
await expect(resultLocator.first()).toContainText(bot1UserId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should allow opening group chat dialog", async ({ page, app, bot2 }) => {
|
test("should allow opening group chat dialog", async ({ page, app }) => {
|
||||||
const spotlight = await app.openSpotlight();
|
const spotlight = await app.openSpotlight();
|
||||||
await page.waitForTimeout(500); // wait for the dialog to settle
|
await page.waitForTimeout(500); // wait for the dialog to settle
|
||||||
await spotlight.filter(Filter.People);
|
await spotlight.filter(Filter.People);
|
||||||
await spotlight.search(bot2.credentials.displayName);
|
await spotlight.search(bot2Name);
|
||||||
await page.waitForTimeout(3000); // wait for the dialog to settle
|
await page.waitForTimeout(3000); // wait for the dialog to settle
|
||||||
|
|
||||||
const resultLocator = spotlight.results;
|
const resultLocator = spotlight.results;
|
||||||
await expect(resultLocator).toHaveCount(1);
|
await expect(resultLocator).toHaveCount(1);
|
||||||
await expect(resultLocator.first()).toContainText(bot2.credentials.displayName);
|
await expect(resultLocator.first()).toContainText(bot2Name);
|
||||||
|
|
||||||
await expect(spotlight.dialog.locator(".mx_SpotlightDialog_startGroupChat")).toContainText(
|
await expect(spotlight.dialog.locator(".mx_SpotlightDialog_startGroupChat")).toContainText(
|
||||||
"Start a group chat",
|
"Start a group chat",
|
||||||
@@ -348,18 +336,18 @@ test.describe("Spotlight", () => {
|
|||||||
await expect(page.getByRole("dialog")).toContainText("Direct Messages");
|
await expect(page.getByRole("dialog")).toContainText("Direct Messages");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should close spotlight after starting a DM", async ({ page, app, bot1 }) => {
|
test("should close spotlight after starting a DM", async ({ page, app }) => {
|
||||||
await startDM(app, page, bot1.credentials.displayName);
|
await startDM(app, page, bot1Name);
|
||||||
await expect(page.locator(".mx_SpotlightDialog")).toHaveCount(0);
|
await expect(page.locator(".mx_SpotlightDialog")).toHaveCount(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should show the same user only once", async ({ page, app, bot1 }) => {
|
test("should show the same user only once", async ({ page, app }) => {
|
||||||
await startDM(app, page, bot1.credentials.displayName);
|
await startDM(app, page, bot1Name);
|
||||||
await page.goto("/#/home");
|
await page.goto("/#/home");
|
||||||
const spotlight = await app.openSpotlight();
|
const spotlight = await app.openSpotlight();
|
||||||
await page.waitForTimeout(500); // wait for the dialog to settle
|
await page.waitForTimeout(500); // wait for the dialog to settle
|
||||||
await spotlight.filter(Filter.People);
|
await spotlight.filter(Filter.People);
|
||||||
await spotlight.search(bot1.credentials.displayName);
|
await spotlight.search(bot1Name);
|
||||||
await page.waitForTimeout(3000); // wait for the dialog to settle
|
await page.waitForTimeout(3000); // wait for the dialog to settle
|
||||||
await expect(spotlight.dialog.locator(".mx_Spinner")).not.toBeAttached();
|
await expect(spotlight.dialog.locator(".mx_Spinner")).not.toBeAttached();
|
||||||
const resultLocator = spotlight.results;
|
const resultLocator = spotlight.results;
|
||||||
|
|||||||
@@ -8,10 +8,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||||
import { Layout } from "../../../src/settings/enums/Layout";
|
import { Layout } from "../../../src/settings/enums/Layout";
|
||||||
import { test, expect } from "../../element-web-test";
|
import { test, expect } from "../../element-web-test";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Threads", () => {
|
test.describe("Threads", () => {
|
||||||
test.skip(isDendrite, "due to a Dendrite bug https://github.com/element-hq/dendrite/issues/3489");
|
|
||||||
test.use({
|
test.use({
|
||||||
displayName: "Tom",
|
displayName: "Tom",
|
||||||
botCreateOpts: {
|
botCreateOpts: {
|
||||||
@@ -26,7 +24,8 @@ test.describe("Threads", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should be usable for a conversation", { tag: "@screenshot" }, async ({ page, app, bot }) => {
|
// Flaky: https://github.com/vector-im/element-web/issues/26452
|
||||||
|
test.skip("should be usable for a conversation", { tag: "@screenshot" }, async ({ page, app, bot }) => {
|
||||||
const roomId = await app.client.createRoom({});
|
const roomId = await app.client.createRoom({});
|
||||||
await app.client.inviteUser(roomId, bot.credentials.userId);
|
await app.client.inviteUser(roomId, bot.credentials.userId);
|
||||||
await bot.joinRoom(roomId);
|
await bot.joinRoom(roomId);
|
||||||
@@ -77,7 +76,7 @@ test.describe("Threads", () => {
|
|||||||
mask: mask,
|
mask: mask,
|
||||||
});
|
});
|
||||||
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Bubble);
|
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Bubble);
|
||||||
await expect(page.locator(".mx_ThreadView .mx_EventTile[data-layout='bubble']")).toHaveCount(2);
|
await expect(page.locator(".mx_ThreadView .mx_EventTile[data-layout='bubble']")).toBeVisible();
|
||||||
|
|
||||||
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot("Initial_ThreadView_on_bubble_layout.png", {
|
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot("Initial_ThreadView_on_bubble_layout.png", {
|
||||||
mask: mask,
|
mask: mask,
|
||||||
@@ -137,8 +136,8 @@ test.describe("Threads", () => {
|
|||||||
await page.getByRole("gridcell", { name: "👋" }).click();
|
await page.getByRole("gridcell", { name: "👋" }).click();
|
||||||
|
|
||||||
locator = page.locator(".mx_ThreadView");
|
locator = page.locator(".mx_ThreadView");
|
||||||
// Make sure the CSS style for spacing is applied to mx_EventTile_footer on group/modern layout
|
// Make sure the CSS style for spacing is applied to mx_ReactionsRow on group/modern layout
|
||||||
await expect(locator.locator(".mx_EventTile[data-layout=group] .mx_EventTile_footer")).toHaveCSS(
|
await expect(locator.locator(".mx_EventTile[data-layout=group] .mx_ReactionsRow")).toHaveCSS(
|
||||||
"margin-inline-start",
|
"margin-inline-start",
|
||||||
ThreadViewGroupSpacingStart,
|
ThreadViewGroupSpacingStart,
|
||||||
);
|
);
|
||||||
@@ -165,7 +164,7 @@ test.describe("Threads", () => {
|
|||||||
locator = page.locator(
|
locator = page.locator(
|
||||||
".mx_ThreadView .mx_GenericEventListSummary[data-layout=bubble] .mx_EventTile_info.mx_EventTile_last",
|
".mx_ThreadView .mx_GenericEventListSummary[data-layout=bubble] .mx_EventTile_info.mx_EventTile_last",
|
||||||
);
|
);
|
||||||
await expect(locator.locator(".mx_EventTile_line .mx_EventTile_content"))
|
expect(locator.locator(".mx_EventTile_line .mx_EventTile_content"))
|
||||||
// 76px: ThreadViewGroupSpacingStart + 14px + 6px
|
// 76px: ThreadViewGroupSpacingStart + 14px + 6px
|
||||||
// 14px: avatar width
|
// 14px: avatar width
|
||||||
// See: _EventTile.pcss
|
// See: _EventTile.pcss
|
||||||
@@ -203,14 +202,12 @@ test.describe("Threads", () => {
|
|||||||
await locator.click();
|
await locator.click();
|
||||||
|
|
||||||
// Wait until the response is redacted
|
// Wait until the response is redacted
|
||||||
// XXX: one would expect this redaction to be shown in the thread the message was in, but due to redactions
|
await expect(
|
||||||
// stripping the thread_id, it is instead shown in the main timeline
|
page.locator(".mx_ThreadView").locator(".mx_EventTile_last .mx_EventTile_receiptSent"),
|
||||||
await expect(page.locator(".mx_MainSplit_timeline").locator(".mx_EventTile_last")).toContainText(
|
).toBeVisible();
|
||||||
"Message deleted",
|
|
||||||
);
|
|
||||||
|
|
||||||
// Take snapshots in group layout and bubble layout (IRC layout is not available on ThreadView)
|
// Take snapshots in group layout and bubble layout (IRC layout is not available on ThreadView)
|
||||||
await expect(page.locator(".mx_ThreadView .mx_EventTile[data-layout='group']")).toHaveCount(2);
|
await expect(page.locator(".mx_ThreadView .mx_EventTile[data-layout='group']")).toBeVisible();
|
||||||
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
|
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
|
||||||
"ThreadView_with_redacted_messages_on_group_layout.png",
|
"ThreadView_with_redacted_messages_on_group_layout.png",
|
||||||
{
|
{
|
||||||
@@ -218,7 +215,7 @@ test.describe("Threads", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Bubble);
|
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Bubble);
|
||||||
await expect(page.locator(".mx_ThreadView .mx_EventTile[data-layout='bubble']")).toHaveCount(2);
|
await expect(page.locator(".mx_ThreadView .mx_EventTile[data-layout='bubble']")).toBeVisible();
|
||||||
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
|
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
|
||||||
"ThreadView_with_redacted_messages_on_bubble_layout.png",
|
"ThreadView_with_redacted_messages_on_bubble_layout.png",
|
||||||
{
|
{
|
||||||
@@ -236,8 +233,8 @@ test.describe("Threads", () => {
|
|||||||
|
|
||||||
// User closes right panel after clicking back to thread list
|
// User closes right panel after clicking back to thread list
|
||||||
locator = page.locator(".mx_ThreadPanel");
|
locator = page.locator(".mx_ThreadPanel");
|
||||||
await locator.getByRole("button", { name: "Threads" }).click();
|
locator.getByRole("button", { name: "Threads" }).click();
|
||||||
await locator.getByRole("button", { name: "Close" }).click();
|
locator.getByRole("button", { name: "Close" }).click();
|
||||||
|
|
||||||
// Bot responds to thread
|
// Bot responds to thread
|
||||||
await bot.sendMessage(roomId, "How are things?", threadId);
|
await bot.sendMessage(roomId, "How are things?", threadId);
|
||||||
@@ -246,8 +243,9 @@ test.describe("Threads", () => {
|
|||||||
await expect(locator.locator(".mx_ThreadSummary_sender").getByText("BotBob")).toBeAttached();
|
await expect(locator.locator(".mx_ThreadSummary_sender").getByText("BotBob")).toBeAttached();
|
||||||
await expect(locator.locator(".mx_ThreadSummary_content").getByText("How are things?")).toBeAttached();
|
await expect(locator.locator(".mx_ThreadSummary_content").getByText("How are things?")).toBeAttached();
|
||||||
|
|
||||||
locator = page.getByRole("banner").getByRole("button", { name: "Threads" });
|
locator = page.getByRole("button", { name: "Threads" });
|
||||||
await expect(locator).toHaveAttribute("data-indicator", "success"); // User asserts thread list unread indicator
|
await expect(locator).toHaveAttribute("data-indicator", "default"); // User asserts thread list unread indicator
|
||||||
|
// await expect(locator).toHaveClass(/mx_LegacyRoomHeader_button--unread/);
|
||||||
await locator.click(); // User opens thread list
|
await locator.click(); // User opens thread list
|
||||||
|
|
||||||
// User asserts thread with correct root & latest events & unread dot
|
// User asserts thread with correct root & latest events & unread dot
|
||||||
@@ -275,18 +273,20 @@ test.describe("Threads", () => {
|
|||||||
await expect(locator.getByText("Great!")).toBeAttached();
|
await expect(locator.getByText("Great!")).toBeAttached();
|
||||||
await locator.locator(".mx_EventTile_line").hover();
|
await locator.locator(".mx_EventTile_line").hover();
|
||||||
await locator.locator(".mx_EventTile_line").getByRole("button", { name: "Edit" }).click();
|
await locator.locator(".mx_EventTile_line").getByRole("button", { name: "Edit" }).click();
|
||||||
await locator.getByRole("textbox").pressSequentially(" How about yourself?"); // fill would overwrite the original text
|
await locator.getByRole("textbox").fill(" How about yourself?{enter}");
|
||||||
await locator.getByRole("textbox").press("Enter");
|
await locator.getByRole("textbox").press("Enter");
|
||||||
|
|
||||||
locator = page.locator(".mx_RoomView_body .mx_ThreadSummary");
|
locator = page.locator(".mx_RoomView_body .mx_ThreadSummary");
|
||||||
await expect(locator.locator(".mx_ThreadSummary_sender").getByText("Tom")).toBeAttached();
|
await expect(locator.locator(".mx_ThreadSummary_sender").getByText("Tom")).toBeAttached();
|
||||||
await expect(locator.locator(".mx_ThreadSummary_content")).toHaveText("Great! How about yourself?");
|
await expect(
|
||||||
|
locator.locator(".mx_ThreadSummary_content").getByText("Great! How about yourself?"),
|
||||||
|
).toBeAttached();
|
||||||
|
|
||||||
// User closes right panel
|
// User closes right panel
|
||||||
await page.locator(".mx_ThreadPanel").getByRole("button", { name: "Close" }).click();
|
await page.locator(".mx_ThreadPanel").getByRole("button", { name: "Close" }).click();
|
||||||
|
|
||||||
// Bot responds to thread and saves the id of their message to @eventId
|
// Bot responds to thread and saves the id of their message to @eventId
|
||||||
const { event_id: eventId } = await bot.sendMessage(roomId, "I'm very good thanks", threadId);
|
const { event_id: eventId } = await bot.sendMessage(roomId, threadId, "I'm very good thanks");
|
||||||
|
|
||||||
// User asserts
|
// User asserts
|
||||||
locator = page.locator(".mx_RoomView_body .mx_ThreadSummary");
|
locator = page.locator(".mx_RoomView_body .mx_ThreadSummary");
|
||||||
@@ -344,7 +344,7 @@ test.describe("Threads", () => {
|
|||||||
|
|
||||||
await expect(page.locator(".mx_ThreadView_timelinePanelWrapper")).toHaveCount(1);
|
await expect(page.locator(".mx_ThreadView_timelinePanelWrapper")).toHaveCount(1);
|
||||||
|
|
||||||
await (await app.openMessageComposerOptions(true)).getByRole("menuitem", { name: "Voice Message" }).click();
|
(await app.openMessageComposerOptions(true)).getByRole("menuitem", { name: "Voice Message" }).click();
|
||||||
await page.waitForTimeout(3000);
|
await page.waitForTimeout(3000);
|
||||||
await app.getComposer(true).getByRole("button", { name: "Send voice message" }).click();
|
await app.getComposer(true).getByRole("button", { name: "Send voice message" }).click();
|
||||||
await expect(page.locator(".mx_ThreadView .mx_MVoiceMessageBody")).toHaveCount(1);
|
await expect(page.locator(".mx_ThreadView .mx_MVoiceMessageBody")).toHaveCount(1);
|
||||||
|
|||||||
@@ -590,6 +590,10 @@ test.describe("Timeline", () => {
|
|||||||
"should set inline start padding to a hidden event line",
|
"should set inline start padding to a hidden event line",
|
||||||
{ tag: "@screenshot" },
|
{ tag: "@screenshot" },
|
||||||
async ({ page, app, room }) => {
|
async ({ page, app, room }) => {
|
||||||
|
test.skip(
|
||||||
|
true,
|
||||||
|
"Disabled due to screenshot test being flaky - https://github.com/element-hq/element-web/issues/26890",
|
||||||
|
);
|
||||||
await sendEvent(app.client, room.roomId);
|
await sendEvent(app.client, room.roomId);
|
||||||
await page.goto(`/#/room/${room.roomId}`);
|
await page.goto(`/#/room/${room.roomId}`);
|
||||||
await app.settings.setValue("showHiddenEventsInTimeline", null, SettingLevel.DEVICE, true);
|
await app.settings.setValue("showHiddenEventsInTimeline", null, SettingLevel.DEVICE, true);
|
||||||
@@ -603,12 +607,7 @@ test.describe("Timeline", () => {
|
|||||||
await messageEdit(page);
|
await messageEdit(page);
|
||||||
|
|
||||||
// Click timestamp to highlight hidden event line
|
// Click timestamp to highlight hidden event line
|
||||||
const timestamp = page.locator(".mx_RoomView_body .mx_EventTile_info a", {
|
await page.locator(".mx_RoomView_body .mx_EventTile_info .mx_MessageTimestamp").click();
|
||||||
has: page.locator(".mx_MessageTimestamp"),
|
|
||||||
});
|
|
||||||
// wait for the remote echo otherwise we get an error modal due to a 404 on the /event/ API
|
|
||||||
await expect(timestamp).not.toHaveAttribute("href", /~!/);
|
|
||||||
await timestamp.locator(".mx_MessageTimestamp").click();
|
|
||||||
|
|
||||||
// should not add inline start padding to a hidden event line on IRC layout
|
// should not add inline start padding to a hidden event line on IRC layout
|
||||||
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
||||||
@@ -1195,7 +1194,6 @@ test.describe("Timeline", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await sendImage(app.client, room.roomId, NEW_AVATAR);
|
await sendImage(app.client, room.roomId, NEW_AVATAR);
|
||||||
await app.timeline.scrollToBottom();
|
|
||||||
await expect(page.locator(".mx_MImageBody").first()).toBeVisible();
|
await expect(page.locator(".mx_MImageBody").first()).toBeVisible();
|
||||||
|
|
||||||
// Exclude timestamp and read marker from snapshot
|
// Exclude timestamp and read marker from snapshot
|
||||||
|
|||||||
79
playwright/e2e/user-onboarding/user-onboarding-new.spec.ts
Normal file
79
playwright/e2e/user-onboarding/user-onboarding-new.spec.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 New Vector Ltd.
|
||||||
|
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { test, expect } from "../../element-web-test";
|
||||||
|
|
||||||
|
test.describe("User Onboarding (new user)", () => {
|
||||||
|
test.use({
|
||||||
|
displayName: "Jane Doe",
|
||||||
|
});
|
||||||
|
|
||||||
|
// This first beforeEach happens before the `user` fixture runs
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.addInitScript(() => {
|
||||||
|
window.localStorage.setItem("mx_registration_time", "1656633601");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page, user }) => {
|
||||||
|
await expect(page.locator(".mx_UserOnboardingPage")).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: "Welcome" })).toBeVisible();
|
||||||
|
await expect(page.locator(".mx_UserOnboardingList")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("page is shown and preference exists", { tag: "@screenshot" }, async ({ page, app }) => {
|
||||||
|
await expect(page.locator(".mx_UserOnboardingPage")).toMatchScreenshot(
|
||||||
|
"User-Onboarding-new-user-page-is-shown-and-preference-exists-1.png",
|
||||||
|
);
|
||||||
|
await app.settings.openUserSettings("Preferences");
|
||||||
|
await expect(page.getByText("Show shortcut to welcome checklist above the room list")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("app download dialog", { tag: "@screenshot" }, async ({ page }) => {
|
||||||
|
await page.getByRole("button", { name: "Download apps" }).click();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("dialog").getByRole("heading", { level: 1, name: "Download Element" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.locator(".mx_Dialog")).toMatchScreenshot(
|
||||||
|
"User-Onboarding-new-user-app-download-dialog-1.png",
|
||||||
|
{
|
||||||
|
// Set a constant bg behind the modal to ensure screenshot stability
|
||||||
|
css: `
|
||||||
|
.mx_AppDownloadDialog_wrapper {
|
||||||
|
background: black;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("using find friends action should increase progress", async ({ page, homeserver }) => {
|
||||||
|
const bot = await homeserver.registerUser("botbob", "password", "BotBob");
|
||||||
|
|
||||||
|
const oldProgress = parseFloat(await page.getByRole("progressbar").getAttribute("value"));
|
||||||
|
await page.getByRole("button", { name: "Find friends" }).click();
|
||||||
|
await page.locator(".mx_InviteDialog_editor").getByRole("textbox").fill(bot.userId);
|
||||||
|
await page.getByRole("button", { name: "Go" }).click();
|
||||||
|
await expect(page.locator(".mx_InviteDialog_buttonAndSpinner")).not.toBeVisible();
|
||||||
|
|
||||||
|
const message = "Hi!";
|
||||||
|
const composer = page.getByRole("textbox", { name: "Send a message…" });
|
||||||
|
await composer.fill(`${message}`);
|
||||||
|
await composer.press("Enter");
|
||||||
|
await expect(page.locator(".mx_MTextBody.mx_EventTile_content", { hasText: message })).toBeVisible();
|
||||||
|
|
||||||
|
await page.goto("/#/home");
|
||||||
|
await expect(page.locator(".mx_UserOnboardingPage")).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: "Welcome" })).toBeVisible();
|
||||||
|
await expect(page.locator(".mx_UserOnboardingList")).toBeVisible();
|
||||||
|
|
||||||
|
await page.waitForTimeout(500); // await progress bar animation
|
||||||
|
const progress = parseFloat(await page.getByRole("progressbar").getAttribute("value"));
|
||||||
|
expect(progress).toBeGreaterThan(oldProgress);
|
||||||
|
});
|
||||||
|
});
|
||||||
28
playwright/e2e/user-onboarding/user-onboarding-old.spec.ts
Normal file
28
playwright/e2e/user-onboarding/user-onboarding-old.spec.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 New Vector Ltd.
|
||||||
|
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { test, expect } from "../../element-web-test";
|
||||||
|
|
||||||
|
test.describe("User Onboarding (old user)", () => {
|
||||||
|
test.use({
|
||||||
|
displayName: "Jane Doe",
|
||||||
|
});
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.addInitScript(() => {
|
||||||
|
window.localStorage.setItem("mx_registration_time", "2");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("page and preference are hidden", async ({ page, user, app }) => {
|
||||||
|
await expect(page.locator(".mx_UserOnboardingPage")).not.toBeVisible();
|
||||||
|
await expect(page.locator(".mx_UserOnboardingButton")).not.toBeVisible();
|
||||||
|
await app.settings.openUserSettings("Preferences");
|
||||||
|
await expect(page.getByText("Show shortcut to welcome checklist above the room list")).not.toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2025 New Vector Ltd.
|
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { test, expect } from "../../element-web-test";
|
|
||||||
|
|
||||||
test.describe("PSTN", () => {
|
|
||||||
test.beforeEach(async ({ page }) => {
|
|
||||||
// Mock the third party protocols endpoint to look like the HS has PSTN support
|
|
||||||
await page.route("**/_matrix/client/v3/thirdparty/protocols", async (route) => {
|
|
||||||
await route.fulfill({
|
|
||||||
status: 200,
|
|
||||||
json: {
|
|
||||||
"im.vector.protocol.pstn": {},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should render dialpad as expected", { tag: "@screenshot" }, async ({ page, user, toasts }) => {
|
|
||||||
await toasts.rejectToast("Notifications");
|
|
||||||
await toasts.assertNoToasts();
|
|
||||||
|
|
||||||
await expect(page.locator(".mx_LeftPanel_filterContainer")).toMatchScreenshot("dialpad-trigger.png");
|
|
||||||
await page.getByLabel("Open dial pad").click();
|
|
||||||
await expect(page.locator(".mx_Dialog")).toMatchScreenshot("dialpad.png");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -88,7 +88,7 @@ async function sendStickerFromPicker(page: Page) {
|
|||||||
await expect(page.locator(".mx_AppTileFullWidth#stickers")).not.toBeVisible();
|
await expect(page.locator(".mx_AppTileFullWidth#stickers")).not.toBeVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function expectTimelineSticker(page: Page, serverName: string, roomId: string, contentUri: string) {
|
async function expectTimelineSticker(page: Page, roomId: string, contentUri: string) {
|
||||||
const contentId = contentUri.split("/").slice(-1)[0];
|
const contentId = contentUri.split("/").slice(-1)[0];
|
||||||
// Make sure it's in the right room
|
// Make sure it's in the right room
|
||||||
await expect(page.locator(".mx_EventTile_sticker > a")).toHaveAttribute("href", new RegExp(`/${roomId}/`));
|
await expect(page.locator(".mx_EventTile_sticker > a")).toHaveAttribute("href", new RegExp(`/${roomId}/`));
|
||||||
@@ -98,7 +98,7 @@ async function expectTimelineSticker(page: Page, serverName: string, roomId: str
|
|||||||
// download URL.
|
// download URL.
|
||||||
await expect(page.locator(`img[alt="${STICKER_NAME}"]`)).toHaveAttribute(
|
await expect(page.locator(`img[alt="${STICKER_NAME}"]`)).toHaveAttribute(
|
||||||
"src",
|
"src",
|
||||||
new RegExp(`/${serverName}/${contentId}`),
|
new RegExp(`/localhost/${contentId}`),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,13 +150,13 @@ test.describe("Stickers", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
const { content_uri: contentUri } = await app.client.uploadContent(STICKER_IMAGE, { type: "image/png" });
|
const { content_uri: contentUri } = await app.client.uploadContent(STICKER_IMAGE, { type: "image/png" });
|
||||||
const widgetHtml = getWidgetHtml(contentUri, "image/png");
|
const widgetHtml = getWidgetHtml(contentUri, "image/png");
|
||||||
stickerPickerUrl = webserver.start(widgetHtml);
|
stickerPickerUrl = webserver.start(widgetHtml);
|
||||||
await setWidgetAccountData(app, user, stickerPickerUrl);
|
setWidgetAccountData(app, user, stickerPickerUrl);
|
||||||
|
|
||||||
await app.viewRoomByName(ROOM_NAME_1);
|
await app.viewRoomByName(ROOM_NAME_1);
|
||||||
await expect(page).toHaveURL(`/#/room/${room.roomId}`);
|
await expect(page).toHaveURL(`/#/room/${room.roomId}`);
|
||||||
await openStickerPicker(app);
|
await openStickerPicker(app);
|
||||||
await sendStickerFromPicker(page);
|
await sendStickerFromPicker(page);
|
||||||
await expectTimelineSticker(page, user.homeServer, room.roomId, contentUri);
|
await expectTimelineSticker(page, room.roomId, contentUri);
|
||||||
|
|
||||||
// Ensure that when we switch to a different room that the sticker
|
// Ensure that when we switch to a different room that the sticker
|
||||||
// goes to the right place
|
// goes to the right place
|
||||||
@@ -164,7 +164,7 @@ test.describe("Stickers", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
await expect(page).toHaveURL(`/#/room/${roomId2}`);
|
await expect(page).toHaveURL(`/#/room/${roomId2}`);
|
||||||
await openStickerPicker(app);
|
await openStickerPicker(app);
|
||||||
await sendStickerFromPicker(page);
|
await sendStickerFromPicker(page);
|
||||||
await expectTimelineSticker(page, user.homeServer, roomId2, contentUri);
|
await expectTimelineSticker(page, roomId2, contentUri);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should handle a sticker picker widget missing creatorUserId", async ({
|
test("should handle a sticker picker widget missing creatorUserId", async ({
|
||||||
@@ -177,13 +177,13 @@ test.describe("Stickers", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
const { content_uri: contentUri } = await app.client.uploadContent(STICKER_IMAGE, { type: "image/png" });
|
const { content_uri: contentUri } = await app.client.uploadContent(STICKER_IMAGE, { type: "image/png" });
|
||||||
const widgetHtml = getWidgetHtml(contentUri, "image/png");
|
const widgetHtml = getWidgetHtml(contentUri, "image/png");
|
||||||
stickerPickerUrl = webserver.start(widgetHtml);
|
stickerPickerUrl = webserver.start(widgetHtml);
|
||||||
await setWidgetAccountData(app, user, stickerPickerUrl, false);
|
setWidgetAccountData(app, user, stickerPickerUrl, false);
|
||||||
|
|
||||||
await app.viewRoomByName(ROOM_NAME_1);
|
await app.viewRoomByName(ROOM_NAME_1);
|
||||||
await expect(page).toHaveURL(`/#/room/${room.roomId}`);
|
await expect(page).toHaveURL(`/#/room/${room.roomId}`);
|
||||||
await openStickerPicker(app);
|
await openStickerPicker(app);
|
||||||
await sendStickerFromPicker(page);
|
await sendStickerFromPicker(page);
|
||||||
await expectTimelineSticker(page, user.homeServer, room.roomId, contentUri);
|
await expectTimelineSticker(page, room.roomId, contentUri);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should render invalid mimetype as a file", async ({ webserver, page, app, user, room }) => {
|
test("should render invalid mimetype as a file", async ({ webserver, page, app, user, room }) => {
|
||||||
@@ -192,7 +192,7 @@ test.describe("Stickers", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
});
|
});
|
||||||
const widgetHtml = getWidgetHtml(contentUri, "application/octet-stream");
|
const widgetHtml = getWidgetHtml(contentUri, "application/octet-stream");
|
||||||
stickerPickerUrl = webserver.start(widgetHtml);
|
stickerPickerUrl = webserver.start(widgetHtml);
|
||||||
await setWidgetAccountData(app, user, stickerPickerUrl);
|
setWidgetAccountData(app, user, stickerPickerUrl);
|
||||||
|
|
||||||
await app.viewRoomByName(ROOM_NAME_1);
|
await app.viewRoomByName(ROOM_NAME_1);
|
||||||
await expect(page).toHaveURL(`/#/room/${room.roomId}`);
|
await expect(page).toHaveURL(`/#/room/${room.roomId}`);
|
||||||
|
|||||||
@@ -6,15 +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 {
|
import { expect as baseExpect, Locator, Page, ExpectMatcherState, ElementHandle } from "@playwright/test";
|
||||||
expect as baseExpect,
|
|
||||||
Locator,
|
|
||||||
Page,
|
|
||||||
ExpectMatcherState,
|
|
||||||
ElementHandle,
|
|
||||||
PlaywrightTestArgs,
|
|
||||||
Fixtures as _Fixtures,
|
|
||||||
} from "@playwright/test";
|
|
||||||
import { sanitizeForFilePath } from "playwright-core/lib/utils";
|
import { sanitizeForFilePath } from "playwright-core/lib/utils";
|
||||||
import AxeBuilder from "@axe-core/playwright";
|
import AxeBuilder from "@axe-core/playwright";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
@@ -27,7 +19,7 @@ import { Crypto } from "./pages/crypto";
|
|||||||
import { Toasts } from "./pages/toasts";
|
import { Toasts } from "./pages/toasts";
|
||||||
import { Bot, CreateBotOpts } from "./pages/bot";
|
import { Bot, CreateBotOpts } from "./pages/bot";
|
||||||
import { Webserver } from "./plugins/webserver";
|
import { Webserver } from "./plugins/webserver";
|
||||||
import { Options, Services, test as base } from "./services.ts";
|
import { test as base } from "./services.ts";
|
||||||
|
|
||||||
// Enable experimental service worker support
|
// Enable experimental service worker support
|
||||||
// See https://playwright.dev/docs/service-workers-experimental#how-to-enable
|
// See https://playwright.dev/docs/service-workers-experimental#how-to-enable
|
||||||
@@ -49,11 +41,11 @@ const CONFIG_JSON: Partial<IConfigOptions> = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface CredentialsWithDisplayName extends Credentials {
|
interface CredentialsWithDisplayName extends Credentials {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TestFixtures {
|
export interface Fixtures {
|
||||||
axe: AxeBuilder;
|
axe: AxeBuilder;
|
||||||
checkA11y: () => Promise<void>;
|
checkA11y: () => Promise<void>;
|
||||||
|
|
||||||
@@ -110,9 +102,7 @@ export interface TestFixtures {
|
|||||||
disablePresence: boolean;
|
disablePresence: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type CombinedTestFixtures = PlaywrightTestArgs & TestFixtures;
|
export const test = base.extend<Fixtures>({
|
||||||
export type Fixtures = _Fixtures<CombinedTestFixtures, Services & Options, CombinedTestFixtures>;
|
|
||||||
export const test = base.extend<TestFixtures>({
|
|
||||||
context: async ({ context }, use, testInfo) => {
|
context: async ({ context }, use, testInfo) => {
|
||||||
// We skip tests instead of using grep-invert to still surface the counts in the html report
|
// We skip tests instead of using grep-invert to still surface the counts in the html report
|
||||||
test.skip(
|
test.skip(
|
||||||
@@ -160,7 +150,7 @@ export const test = base.extend<TestFixtures>({
|
|||||||
const displayName = testDisplayName ?? _.sample(names)!;
|
const displayName = testDisplayName ?? _.sample(names)!;
|
||||||
|
|
||||||
const credentials = await homeserver.registerUser(`user_${testInfo.testId}`, password, displayName);
|
const credentials = await homeserver.registerUser(`user_${testInfo.testId}`, password, displayName);
|
||||||
console.log(`Registered test user ${credentials.userId} with displayname ${displayName}`);
|
console.log(`Registered test user @user:localhost with displayname ${displayName}`);
|
||||||
|
|
||||||
await use({
|
await use({
|
||||||
...credentials,
|
...credentials,
|
||||||
|
|||||||
@@ -24,40 +24,13 @@ type PaginationLinks = {
|
|||||||
first?: string;
|
first?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// We see quite a few test flakes which are caused by the app exploding
|
|
||||||
// so we have some magic strings we check the logs for to better track the flake with its cause
|
|
||||||
const SPECIAL_CASES = {
|
|
||||||
"ChunkLoadError": "ChunkLoadError",
|
|
||||||
"Unreachable code should not be executed": "Rust crypto panic",
|
|
||||||
"Out of bounds memory access": "Rust crypto memory error",
|
|
||||||
};
|
|
||||||
|
|
||||||
class FlakyReporter implements Reporter {
|
class FlakyReporter implements Reporter {
|
||||||
private flakes = new Map<string, TestCase[]>();
|
private flakes = new Set<string>();
|
||||||
|
|
||||||
public onTestEnd(test: TestCase): void {
|
public onTestEnd(test: TestCase): void {
|
||||||
// Ignores flakes on Dendrite and Pinecone as they have their own flakes we do not track
|
const title = `${test.location.file.split("playwright/e2e/")[1]}: ${test.title}`;
|
||||||
if (["Dendrite", "Pinecone"].includes(test.parent.project()?.name)) return;
|
|
||||||
let failures = [`${test.location.file.split("playwright/e2e/")[1]}: ${test.title}`];
|
|
||||||
if (test.outcome() === "flaky") {
|
if (test.outcome() === "flaky") {
|
||||||
const timedOutRuns = test.results.filter((result) => result.status === "timedOut");
|
this.flakes.add(title);
|
||||||
const pageLogs = timedOutRuns.flatMap((result) =>
|
|
||||||
result.attachments.filter((attachment) => attachment.name.startsWith("page-")),
|
|
||||||
);
|
|
||||||
// If a test failed due to a systemic fault then the test is not flaky, the app is, record it as such.
|
|
||||||
const specialCases = Object.keys(SPECIAL_CASES).filter((log) =>
|
|
||||||
pageLogs.some((attachment) => attachment.name.startsWith("page-") && attachment.body.includes(log)),
|
|
||||||
);
|
|
||||||
if (specialCases.length > 0) {
|
|
||||||
failures = specialCases.map((specialCase) => SPECIAL_CASES[specialCase]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const title of failures) {
|
|
||||||
if (!this.flakes.has(title)) {
|
|
||||||
this.flakes.set(title, []);
|
|
||||||
}
|
|
||||||
this.flakes.get(title).push(test);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,14 +97,12 @@ class FlakyReporter implements Reporter {
|
|||||||
if (!GITHUB_TOKEN) return;
|
if (!GITHUB_TOKEN) return;
|
||||||
|
|
||||||
const issues = await this.getAllIssues();
|
const issues = await this.getAllIssues();
|
||||||
for (const [flake, results] of this.flakes) {
|
for (const flake of this.flakes) {
|
||||||
const title = ISSUE_TITLE_PREFIX + "`" + flake + "`";
|
const title = ISSUE_TITLE_PREFIX + "`" + flake + "`";
|
||||||
const existingIssue = issues.find((issue) => issue.title === title);
|
const existingIssue = issues.find((issue) => issue.title === title);
|
||||||
const headers = { Authorization: `Bearer ${GITHUB_TOKEN}` };
|
const headers = { Authorization: `Bearer ${GITHUB_TOKEN}` };
|
||||||
const body = `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}`;
|
const body = `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}`;
|
||||||
|
|
||||||
const labels = [LABEL, ...results.map((test) => `${LABEL}-${test.parent.project()?.name}`)];
|
|
||||||
|
|
||||||
if (existingIssue) {
|
if (existingIssue) {
|
||||||
console.log(`Found issue ${existingIssue.number} for ${flake}, adding comment...`);
|
console.log(`Found issue ${existingIssue.number} for ${flake}, adding comment...`);
|
||||||
// Ensure that the test is open
|
// Ensure that the test is open
|
||||||
@@ -140,11 +111,6 @@ class FlakyReporter implements Reporter {
|
|||||||
headers,
|
headers,
|
||||||
body: JSON.stringify({ state: "open" }),
|
body: JSON.stringify({ state: "open" }),
|
||||||
});
|
});
|
||||||
await fetch(`${existingIssue.url}/labels`, {
|
|
||||||
method: "POST",
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ labels }),
|
|
||||||
});
|
|
||||||
await fetch(`${existingIssue.url}/comments`, {
|
await fetch(`${existingIssue.url}/comments`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers,
|
headers,
|
||||||
@@ -158,7 +124,7 @@ class FlakyReporter implements Reporter {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
title,
|
title,
|
||||||
body,
|
body,
|
||||||
labels: [...labels],
|
labels: [LABEL],
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2024 New Vector Ltd.
|
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { BrowserContext, Page, TestInfo } from "@playwright/test";
|
|
||||||
import { Readable } from "stream";
|
|
||||||
import stripAnsi from "strip-ansi";
|
|
||||||
|
|
||||||
export class Logger {
|
|
||||||
private pages: Page[] = [];
|
|
||||||
private logs: Record<string, string> = {};
|
|
||||||
|
|
||||||
public getConsumer(container: string) {
|
|
||||||
this.logs[container] = "";
|
|
||||||
return (stream: Readable) => {
|
|
||||||
stream.on("data", (chunk) => {
|
|
||||||
this.logs[container] += chunk.toString();
|
|
||||||
});
|
|
||||||
stream.on("err", (chunk) => {
|
|
||||||
this.logs[container] += "ERR " + chunk.toString();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async onTestStarted(context: BrowserContext) {
|
|
||||||
this.pages = [];
|
|
||||||
for (const id in this.logs) {
|
|
||||||
if (id.startsWith("page-")) {
|
|
||||||
delete this.logs[id];
|
|
||||||
} else {
|
|
||||||
this.logs[id] = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context.on("console", (msg) => {
|
|
||||||
const page = msg.page();
|
|
||||||
let pageIdx = this.pages.indexOf(page);
|
|
||||||
if (pageIdx === -1) {
|
|
||||||
this.pages.push(page);
|
|
||||||
pageIdx = this.pages.length - 1;
|
|
||||||
this.logs[`page-${pageIdx}`] = `Console logs for page with URL: ${page.url()}\n\n`;
|
|
||||||
}
|
|
||||||
const type = msg.type();
|
|
||||||
const text = msg.text();
|
|
||||||
this.logs[`page-${pageIdx}`] += `${type}: ${text}\n`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async onTestFinished(testInfo: TestInfo) {
|
|
||||||
if (testInfo.status !== "passed") {
|
|
||||||
for (const id in this.logs) {
|
|
||||||
if (!this.logs[id]) continue;
|
|
||||||
await testInfo.attach(id, {
|
|
||||||
body: stripAnsi(this.logs[id]),
|
|
||||||
contentType: "text/plain",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -158,6 +158,10 @@ export class ElementAppPage {
|
|||||||
return button.click();
|
return button.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getClipboardText(): Promise<string> {
|
||||||
|
return this.page.evaluate("navigator.clipboard.readText()");
|
||||||
|
}
|
||||||
|
|
||||||
public async openSpotlight(): Promise<Spotlight> {
|
public async openSpotlight(): Promise<Spotlight> {
|
||||||
const spotlight = new Spotlight(this.page);
|
const spotlight = new Spotlight(this.page);
|
||||||
await spotlight.open();
|
await spotlight.open();
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export class Bot extends Client {
|
|||||||
return logger as unknown as Logger;
|
return logger as unknown as Logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
const logger = getLogger(`bot ${credentials.userId}`);
|
const logger = getLogger(`cypress bot ${credentials.userId}`);
|
||||||
|
|
||||||
const keys = {};
|
const keys = {};
|
||||||
|
|
||||||
@@ -171,7 +171,7 @@ export class Bot extends Client {
|
|||||||
if (opts.autoAcceptInvites) {
|
if (opts.autoAcceptInvites) {
|
||||||
cli.on(window.matrixcs.RoomMemberEvent.Membership, (event, member) => {
|
cli.on(window.matrixcs.RoomMemberEvent.Membership, (event, member) => {
|
||||||
if (member.membership === "invite" && member.userId === cli.getUserId()) {
|
if (member.membership === "invite" && member.userId === cli.getUserId()) {
|
||||||
void cli.joinRoom(member.roomId);
|
cli.joinRoom(member.roomId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import type {
|
|||||||
ICreateRoomOpts,
|
ICreateRoomOpts,
|
||||||
ISendEventResponse,
|
ISendEventResponse,
|
||||||
MatrixClient,
|
MatrixClient,
|
||||||
|
Room,
|
||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
ReceiptType,
|
ReceiptType,
|
||||||
IRoomDirectoryOptions,
|
IRoomDirectoryOptions,
|
||||||
@@ -177,12 +178,22 @@ export class Client {
|
|||||||
*/
|
*/
|
||||||
public async createRoom(options: ICreateRoomOpts): Promise<string> {
|
public async createRoom(options: ICreateRoomOpts): Promise<string> {
|
||||||
const client = await this.prepareClient();
|
const client = await this.prepareClient();
|
||||||
const roomId = await client.evaluate(async (cli, options) => {
|
return await client.evaluate(async (cli, options) => {
|
||||||
|
const roomPromise = new Promise<void>((resolve) => {
|
||||||
|
const onRoom = (room: Room) => {
|
||||||
|
if (room.roomId === roomId) {
|
||||||
|
cli.off(window.matrixcs.ClientEvent.Room, onRoom);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
cli.on(window.matrixcs.ClientEvent.Room, onRoom);
|
||||||
|
});
|
||||||
const { room_id: roomId } = await cli.createRoom(options);
|
const { room_id: roomId } = await cli.createRoom(options);
|
||||||
|
if (!cli.getRoom(roomId)) {
|
||||||
|
await roomPromise;
|
||||||
|
}
|
||||||
return roomId;
|
return roomId;
|
||||||
}, options);
|
}, options);
|
||||||
await this.awaitRoomMembership(roomId);
|
|
||||||
return roomId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,8 +6,36 @@ 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 { Options } from "../../../services.ts";
|
import { Fixtures } from "@playwright/test";
|
||||||
|
|
||||||
export const isDendrite = ({ homeserverType }: Options): boolean => {
|
import { DendriteContainer, PineconeContainer } from "../../../testcontainers/dendrite.ts";
|
||||||
return homeserverType === "dendrite" || homeserverType === "pinecone";
|
import { Services } from "../../../services.ts";
|
||||||
|
|
||||||
|
export const dendriteHomeserver: Fixtures<{}, Services> = {
|
||||||
|
_homeserver: [
|
||||||
|
// eslint-disable-next-line no-empty-pattern
|
||||||
|
async ({}, use) => {
|
||||||
|
const container =
|
||||||
|
process.env["PLAYWRIGHT_HOMESERVER"] === "dendrite" ? new DendriteContainer() : new PineconeContainer();
|
||||||
|
await use(container);
|
||||||
|
},
|
||||||
|
{ scope: "worker" },
|
||||||
|
],
|
||||||
|
homeserver: [
|
||||||
|
async ({ logger, network, _homeserver: homeserver }, use) => {
|
||||||
|
const container = await homeserver
|
||||||
|
.withNetwork(network)
|
||||||
|
.withNetworkAliases("homeserver")
|
||||||
|
.withLogConsumer(logger.getConsumer("dendrite"))
|
||||||
|
.start();
|
||||||
|
|
||||||
|
await use(container);
|
||||||
|
await container.stop();
|
||||||
|
},
|
||||||
|
{ scope: "worker" },
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function isDendrite(): boolean {
|
||||||
|
return process.env["PLAYWRIGHT_HOMESERVER"] === "dendrite" || process.env["PLAYWRIGHT_HOMESERVER"] === "pinecone";
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,11 +6,8 @@ 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 { ClientServerApi } from "../utils/api.ts";
|
|
||||||
|
|
||||||
export interface HomeserverInstance {
|
export interface HomeserverInstance {
|
||||||
readonly baseUrl: string;
|
readonly baseUrl: string;
|
||||||
readonly csApi: ClientServerApi;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a user on the given Homeserver using the shared registration secret.
|
* Register a user on the given Homeserver using the shared registration secret.
|
||||||
@@ -46,5 +43,3 @@ export interface Credentials {
|
|||||||
displayName?: string;
|
displayName?: string;
|
||||||
username: string; // the localpart of the userId
|
username: string; // the localpart of the userId
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HomeserverType = "synapse" | "dendrite" | "pinecone";
|
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ 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 { Fixtures } from "../../../element-web-test.ts";
|
import { Fixtures } from "@playwright/test";
|
||||||
|
|
||||||
export const consentHomeserver: Fixtures = {
|
import { Services } from "../../../services.ts";
|
||||||
|
|
||||||
|
export const consentHomeserver: Fixtures<{}, Services> = {
|
||||||
_homeserver: [
|
_homeserver: [
|
||||||
async ({ _homeserver: container, mailhog }, use) => {
|
async ({ _homeserver: container, mailhog }, use) => {
|
||||||
container
|
container
|
||||||
@@ -54,9 +56,4 @@ export const consentHomeserver: Fixtures = {
|
|||||||
},
|
},
|
||||||
{ scope: "worker" },
|
{ scope: "worker" },
|
||||||
],
|
],
|
||||||
|
|
||||||
context: async ({ homeserverType, context }, use, testInfo) => {
|
|
||||||
testInfo.skip(homeserverType !== "synapse", "does not yet support MAS");
|
|
||||||
await use(context);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user