Compare commits
42 Commits
t3chguy/pl
...
robin/reve
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63d32efb27 | ||
|
|
f05df80b46 | ||
|
|
9a109cdce8 | ||
|
|
a0ab88943b | ||
|
|
ad01218942 | ||
|
|
e1e4d26154 | ||
|
|
84c614676d | ||
|
|
29d9e98111 | ||
|
|
9f5f898ed8 | ||
|
|
78251a3a8a | ||
|
|
1b077c53f5 | ||
|
|
68828a2326 | ||
|
|
af8d93f58a | ||
|
|
c0a097867e | ||
|
|
0b13e57518 | ||
|
|
8615b411b2 | ||
|
|
3d31376b1d | ||
|
|
43e5124cd4 | ||
|
|
19674cca08 | ||
|
|
6ca6cb0fbe | ||
|
|
d92fc5a595 | ||
|
|
b9d411eecc | ||
|
|
3da6619bcf | ||
|
|
f33e7c9782 | ||
|
|
1ebae09834 | ||
|
|
56eafc908e | ||
|
|
1644169ff3 | ||
|
|
cf895b4296 | ||
|
|
e9d4f39e9d | ||
|
|
7c0ec21365 | ||
|
|
72df9c9076 | ||
|
|
e42ee727b4 | ||
|
|
7d30413178 | ||
|
|
8884e77ce3 | ||
|
|
58f812ffe6 | ||
|
|
ef1597ff2d | ||
|
|
e5ca7954c8 | ||
|
|
13913ba8b2 | ||
|
|
03a1b48e1f | ||
|
|
ef3bf59656 | ||
|
|
7a3202b537 | ||
|
|
dbce48b23d |
@@ -1,5 +1,5 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: ["matrix-org"],
|
plugins: ["matrix-org", "eslint-plugin-react-compiler"],
|
||||||
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,6 +170,8 @@ 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: [
|
||||||
{
|
{
|
||||||
@@ -262,6 +264,7 @@ 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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
18
.github/CODEOWNERS
vendored
@@ -3,13 +3,17 @@
|
|||||||
/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
|
||||||
|
|||||||
1
.github/workflows/deploy.yml
vendored
@@ -96,3 +96,4 @@ 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
|
||||||
|
|||||||
2
.github/workflows/dockerhub.yaml
vendored
@@ -51,7 +51,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
id: build-and-push
|
id: build-and-push
|
||||||
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6
|
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
|
|||||||
16
.github/workflows/end-to-end-tests.yaml
vendored
@@ -114,6 +114,8 @@ 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
|
||||||
@@ -122,6 +124,10 @@ 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:
|
||||||
@@ -166,22 +172,12 @@ jobs:
|
|||||||
|
|
||||||
# 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
|
||||||
env:
|
|
||||||
PWTEST_PROFILE_DIR: profile
|
|
||||||
run: |
|
run: |
|
||||||
yarn playwright test \
|
yarn playwright test \
|
||||||
--shard "${{ matrix.runner }}/${{ needs.build.outputs.num-runners }}" \
|
--shard "${{ matrix.runner }}/${{ needs.build.outputs.num-runners }}" \
|
||||||
--project="${{ matrix.project }}" \
|
--project="${{ matrix.project }}" \
|
||||||
${{ (github.event_name == 'pull_request' && matrix.runAllTests == false ) && '--grep-invert @mergequeue' || '' }}
|
${{ (github.event_name == 'pull_request' && matrix.runAllTests == false ) && '--grep-invert @mergequeue' || '' }}
|
||||||
|
|
||||||
- name: Upload profile
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: profile-${{ matrix.project }}-${{ matrix.runner }}
|
|
||||||
path: profile
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
- name: Upload blob report to GitHub Actions Artifacts
|
- name: Upload blob report to GitHub Actions Artifacts
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|||||||
2
.github/workflows/tests.yml
vendored
@@ -104,7 +104,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Skip SonarCloud in merge queue
|
- name: Skip SonarCloud in merge queue
|
||||||
if: github.event_name == 'merge_group' || inputs.disable_coverage == 'true'
|
if: github.event_name == 'merge_group' || inputs.disable_coverage == 'true'
|
||||||
uses: guibranco/github-status-action-v2@56cd38caf0615dd03f49d42ed301f1469911ac61
|
uses: guibranco/github-status-action-v2@ecd54a02cf761e85a8fb328fe937710fd4227cda
|
||||||
with:
|
with:
|
||||||
authToken: ${{ secrets.GITHUB_TOKEN }}
|
authToken: ${{ secrets.GITHUB_TOKEN }}
|
||||||
state: success
|
state: success
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ 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,11 +66,11 @@ 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 `synapseConfigOptions` as such:
|
in different configurations. You can specify `synapseConfig` as such:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
test.use({
|
test.use({
|
||||||
synapseConfigOptions: {
|
synapseConfig: {
|
||||||
// The config options to pass to the Synapse instance
|
// The config options to pass to the Synapse instance
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,11 +8,13 @@
|
|||||||
|
|
||||||
#### develop
|
#### develop
|
||||||
|
|
||||||
The develop branch holds the very latest and greatest code we have to offer, as such it may be less stable. It corresponds to the develop.element.io CD platform.
|
The develop branch holds the very latest and greatest code we have to offer, as such it may be less stable.
|
||||||
|
It is auto-deployed on every commit to element-web or matrix-js-sdk to develop.element.io via GitHub Actions `build_develop.yml`.
|
||||||
|
|
||||||
#### staging
|
#### staging
|
||||||
|
|
||||||
The staging branch corresponds to the very latest release regardless of whether it is an RC or not. Deployed to staging.element.io manually.
|
The staging branch corresponds to the very latest release regardless of whether it is an RC or not. Deployed to staging.element.io manually.
|
||||||
|
It is auto-deployed on every release of element-web to staging.element.io via GitHub Actions `deploy.yml`.
|
||||||
|
|
||||||
#### master
|
#### master
|
||||||
|
|
||||||
@@ -215,7 +217,7 @@ We ship Element Web to dockerhub, `*.element.io`, and packages.element.io.
|
|||||||
We ship Element Desktop to packages.element.io.
|
We ship Element Desktop to packages.element.io.
|
||||||
|
|
||||||
- [ ] Check that element-web has shipped to dockerhub
|
- [ ] Check that element-web has shipped to dockerhub
|
||||||
- [ ] Deploy staging.element.io. [See docs.](https://handbook.element.io/books/element-web-team/page/deploying-appstagingelementio)
|
- [ ] Check that the staging [deployment](https://github.com/element-hq/element-web/actions/workflows/deploy.yml) has completed successfully
|
||||||
- [ ] Test staging.element.io
|
- [ ] Test staging.element.io
|
||||||
|
|
||||||
For final releases additionally do these steps:
|
For final releases additionally do these steps:
|
||||||
@@ -225,6 +227,9 @@ For final releases additionally do these steps:
|
|||||||
- [ ] Ensure Element Web package has shipped to packages.element.io
|
- [ ] Ensure Element Web package has shipped to packages.element.io
|
||||||
- [ ] Ensure Element Desktop packages have shipped to packages.element.io
|
- [ ] Ensure Element Desktop packages have shipped to packages.element.io
|
||||||
|
|
||||||
|
If you need to roll back a deployment to staging.element.io,
|
||||||
|
you can run the `deploy.yml` automation choosing an older tag which you wish to deploy.
|
||||||
|
|
||||||
# Housekeeping
|
# Housekeeping
|
||||||
|
|
||||||
We have some manual housekeeping to do in order to prepare for the next release.
|
We have some manual housekeeping to do in order to prepare for the next release.
|
||||||
|
|||||||
12
package.json
@@ -74,7 +74,7 @@
|
|||||||
"@types/react-dom": "18.3.5",
|
"@types/react-dom": "18.3.5",
|
||||||
"oidc-client-ts": "3.1.0",
|
"oidc-client-ts": "3.1.0",
|
||||||
"jwt-decode": "4.0.0",
|
"jwt-decode": "4.0.0",
|
||||||
"caniuse-lite": "1.0.30001690",
|
"caniuse-lite": "1.0.30001692",
|
||||||
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
|
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
|
||||||
"wrap-ansi": "npm:wrap-ansi@^7.0.0"
|
"wrap-ansi": "npm:wrap-ansi@^7.0.0"
|
||||||
},
|
},
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
"matrix-encrypt-attachment": "^1.0.3",
|
"matrix-encrypt-attachment": "^1.0.3",
|
||||||
"matrix-events-sdk": "0.0.1",
|
"matrix-events-sdk": "0.0.1",
|
||||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
||||||
"matrix-widget-api": "^1.10.0",
|
"matrix-widget-api": "1.11.0",
|
||||||
"memoize-one": "^6.0.0",
|
"memoize-one": "^6.0.0",
|
||||||
"mime": "^4.0.4",
|
"mime": "^4.0.4",
|
||||||
"oidc-client-ts": "^3.0.1",
|
"oidc-client-ts": "^3.0.1",
|
||||||
@@ -178,7 +178,7 @@
|
|||||||
"@peculiar/webcrypto": "^1.4.3",
|
"@peculiar/webcrypto": "^1.4.3",
|
||||||
"@playwright/test": "^1.40.1",
|
"@playwright/test": "^1.40.1",
|
||||||
"@principalstudio/html-webpack-inject-preload": "^1.2.7",
|
"@principalstudio/html-webpack-inject-preload": "^1.2.7",
|
||||||
"@sentry/webpack-plugin": "^2.7.1",
|
"@sentry/webpack-plugin": "^3.0.0",
|
||||||
"@stylistic/eslint-plugin": "^2.9.0",
|
"@stylistic/eslint-plugin": "^2.9.0",
|
||||||
"@svgr/webpack": "^8.0.0",
|
"@svgr/webpack": "^8.0.0",
|
||||||
"@testcontainers/postgresql": "^10.16.0",
|
"@testcontainers/postgresql": "^10.16.0",
|
||||||
@@ -230,13 +230,14 @@
|
|||||||
"dotenv": "^16.0.2",
|
"dotenv": "^16.0.2",
|
||||||
"eslint": "8.57.1",
|
"eslint": "8.57.1",
|
||||||
"eslint-config-google": "^0.14.0",
|
"eslint-config-google": "^0.14.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint-config-prettier": "^10.0.0",
|
||||||
"eslint-plugin-deprecate": "0.8.5",
|
"eslint-plugin-deprecate": "0.8.5",
|
||||||
"eslint-plugin-import": "^2.25.4",
|
"eslint-plugin-import": "^2.25.4",
|
||||||
"eslint-plugin-jest": "^28.0.0",
|
"eslint-plugin-jest": "^28.0.0",
|
||||||
"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",
|
||||||
@@ -286,13 +287,14 @@
|
|||||||
"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",
|
||||||
"typescript": "5.7.2",
|
"typescript": "5.7.3",
|
||||||
"util": "^0.12.5",
|
"util": "^0.12.5",
|
||||||
"web-streams-polyfill": "^4.0.0",
|
"web-streams-polyfill": "^4.0.0",
|
||||||
"webpack": "^5.89.0",
|
"webpack": "^5.89.0",
|
||||||
"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,19 +8,25 @@ 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";
|
||||||
|
|
||||||
export default defineConfig({
|
const 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"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineConfig<Options>({
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: "Chrome",
|
name: "Chrome",
|
||||||
use: {
|
use: {
|
||||||
...devices["Desktop Chrome"],
|
...chromeProject,
|
||||||
channel: "chromium",
|
|
||||||
permissions: ["clipboard-write", "clipboard-read", "microphone"],
|
|
||||||
launchOptions: {
|
|
||||||
args: ["--use-fake-ui-for-media-stream", "--use-fake-device-for-media-stream", "--mute-audio"],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -48,6 +54,22 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
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 },
|
||||||
|
|||||||
@@ -13,6 +13,14 @@ 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",
|
||||||
@@ -222,8 +230,7 @@ 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 tile.hover();
|
await clickButtonReply(tile);
|
||||||
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");
|
||||||
@@ -251,18 +258,12 @@ 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();
|
await clickButtonReply(tile);
|
||||||
|
|
||||||
// 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");
|
||||||
@@ -270,7 +271,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();
|
await clickButtonReply(tile);
|
||||||
|
|
||||||
// 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");
|
||||||
|
|||||||
@@ -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(/\/#\/room\/#test-room-1:localhost/);
|
await expect(page).toHaveURL(new RegExp(`/#/room/#test-room-1:${user.homeServer}`));
|
||||||
const header = page.locator(".mx_RoomHeader");
|
const header = page.locator(".mx_RoomHeader");
|
||||||
await expect(header).toContainText(name);
|
await expect(header).toContainText(name);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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(
|
||||||
@@ -19,6 +20,7 @@ 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,8 +8,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 { 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,6 +11,7 @@ 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");
|
||||||
@@ -67,6 +68,7 @@ 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,6 +28,8 @@ 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,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
awaitVerifier,
|
awaitVerifier,
|
||||||
checkDeviceIsConnectedKeyBackup,
|
checkDeviceIsConnectedKeyBackup,
|
||||||
checkDeviceIsCrossSigned,
|
checkDeviceIsCrossSigned,
|
||||||
|
createBot,
|
||||||
doTwoWaySasVerification,
|
doTwoWaySasVerification,
|
||||||
logIntoElement,
|
logIntoElement,
|
||||||
waitForVerificationRequest,
|
waitForVerificationRequest,
|
||||||
@@ -28,29 +29,9 @@ 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 }) => {
|
||||||
// Visit the login page of the app, to load the matrix sdk
|
const res = await createBot(page, homeserver, credentials);
|
||||||
await page.goto("/#/login");
|
aliceBotClient = res.botClient;
|
||||||
|
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.
|
||||||
@@ -219,9 +200,10 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
|
|||||||
/* 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();
|
||||||
await expect(
|
// We don't assert the full string as the device name is unset on Synapse but set to the user ID on Dendrite
|
||||||
infoDialog.getByText(`You've successfully verified (${aliceBotClient.credentials.deviceId})!`),
|
await expect(infoDialog.getByText(`You've successfully verified`)).toContainText(
|
||||||
).toBeVisible();
|
`(${aliceBotClient.credentials.deviceId})`,
|
||||||
|
);
|
||||||
await infoDialog.getByRole("button", { name: "Got it" }).click();
|
await infoDialog.getByRole("button", { name: "Got it" }).click();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ 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",
|
||||||
|
|||||||
@@ -8,8 +8,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 { 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);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ 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,
|
||||||
@@ -22,6 +23,46 @@ 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
|
||||||
*
|
*
|
||||||
@@ -372,3 +413,25 @@ 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();
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ 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, {
|
||||||
@@ -31,6 +32,8 @@ 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");
|
||||||
|
|||||||
@@ -9,8 +9,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 { 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"],
|
||||||
@@ -79,6 +81,7 @@ 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,8 +13,10 @@ 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"],
|
||||||
@@ -282,6 +284,7 @@ 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,8 +10,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 { 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"],
|
||||||
|
|||||||
@@ -10,8 +10,12 @@ 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({
|
||||||
@@ -35,12 +39,18 @@ 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(page: Page, app: ElementAppPage, bob: Bot, charlies: Bot[]) {
|
async function setupRoomWithBobAliceAndCharlies(
|
||||||
|
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,
|
||||||
@@ -95,7 +105,13 @@ test.describe("Lazy Loading", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function joinCharliesWhileAliceIsOffline(page: Page, app: ElementAppPage, charlies: Bot[]) {
|
async function joinCharliesWhileAliceIsOffline(
|
||||||
|
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);
|
||||||
@@ -107,19 +123,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 }) => {
|
test("should handle lazy loading properly even when offline", async ({ page, app, bot, user }) => {
|
||||||
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, bot, charly1to5);
|
await setupRoomWithBobAliceAndCharlies(page, app, user, 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, charly6to10);
|
await joinCharliesWhileAliceIsOffline(page, app, user, charly6to10);
|
||||||
await checkMemberList(page, charly6to10);
|
await checkMemberList(page, charly6to10);
|
||||||
|
|
||||||
for (const charly of charlies) {
|
for (const charly of charlies) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ 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";
|
||||||
@@ -113,6 +114,8 @@ test.use({
|
|||||||
|
|
||||||
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,
|
||||||
|
|||||||
@@ -58,6 +58,16 @@ 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" },
|
||||||
@@ -79,9 +89,10 @@ 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(`basic-message-ltr-${direction}displayname.png`, {
|
await expect(msgTile).toMatchScreenshot(
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
`basic-message-ltr-${direction}displayname.png`,
|
||||||
});
|
screenshotOptions(page),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -89,14 +100,17 @@ 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`);
|
await expect(msgTile).toMatchScreenshot(`emote-ltr-${direction}displayname.png`, screenshotOptions());
|
||||||
});
|
});
|
||||||
|
|
||||||
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(`emote-rich-ltr-${direction}displayname.png`);
|
await expect(msgTile).toMatchScreenshot(
|
||||||
|
`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 }) => {
|
||||||
@@ -106,9 +120,10 @@ test.describe("Message rendering", () => {
|
|||||||
|
|
||||||
await editMessage(page, msgTile, "Hello, universe!");
|
await editMessage(page, msgTile, "Hello, universe!");
|
||||||
|
|
||||||
await expect(msgTile).toMatchScreenshot(`edited-message-ltr-${direction}displayname.png`, {
|
await expect(msgTile).toMatchScreenshot(
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
`edited-message-ltr-${direction}displayname.png`,
|
||||||
});
|
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 }) => {
|
||||||
@@ -122,32 +137,37 @@ test.describe("Message rendering", () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
await replyMessage(page, msgTile, "response to multiline message");
|
await replyMessage(page, msgTile, "response to multiline message");
|
||||||
await expect(msgTile).toMatchScreenshot(`reply-message-ltr-${direction}displayname.png`, {
|
await expect(msgTile).toMatchScreenshot(
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
`reply-message-ltr-${direction}displayname.png`,
|
||||||
});
|
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(`basic-message-rtl-${direction}displayname.png`, {
|
await expect(msgTile).toMatchScreenshot(
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
`basic-message-rtl-${direction}displayname.png`,
|
||||||
});
|
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`);
|
await expect(msgTile).toMatchScreenshot(`emote-rtl-${direction}displayname.png`, screenshotOptions());
|
||||||
});
|
});
|
||||||
|
|
||||||
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(`emote-rich-rtl-${direction}displayname.png`);
|
await expect(msgTile).toMatchScreenshot(
|
||||||
|
`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 }) => {
|
||||||
@@ -157,9 +177,10 @@ test.describe("Message rendering", () => {
|
|||||||
|
|
||||||
await editMessage(page, msgTile, "مرحبا بالكون!");
|
await editMessage(page, msgTile, "مرحبا بالكون!");
|
||||||
|
|
||||||
await expect(msgTile).toMatchScreenshot(`edited-message-rtl-${direction}displayname.png`, {
|
await expect(msgTile).toMatchScreenshot(
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
`edited-message-rtl-${direction}displayname.png`,
|
||||||
});
|
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 }) => {
|
||||||
@@ -173,9 +194,10 @@ test.describe("Message rendering", () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
await replyMessage(page, msgTile, "مرحبا بالعالم!");
|
await replyMessage(page, msgTile, "مرحبا بالعالم!");
|
||||||
await expect(msgTile).toMatchScreenshot(`reply-message-trl-${direction}displayname.png`, {
|
await expect(msgTile).toMatchScreenshot(
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
`reply-message-trl-${direction}displayname.png`,
|
||||||
});
|
screenshotOptions(page),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,12 +9,10 @@ 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 ({
|
||||||
|
|||||||
@@ -9,12 +9,15 @@ 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, testInfo) => {
|
||||||
|
|||||||
@@ -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:localhost";
|
const danielleId = `@danielle:${user.homeServer}`;
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,8 +11,11 @@ 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,8 +9,11 @@ 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,8 +9,11 @@ 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,8 +9,11 @@ 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,8 +9,10 @@ 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,8 +9,11 @@ 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,8 +9,11 @@ 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,8 +9,11 @@ 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,8 +9,11 @@ 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 ({
|
||||||
|
|||||||
@@ -9,8 +9,11 @@ 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,8 +9,10 @@ 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,8 +12,10 @@ 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" },
|
||||||
|
|||||||
@@ -9,8 +9,11 @@ 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,8 +9,11 @@ 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,8 +9,11 @@ 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 ({
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
|
|
||||||
import { test, expect } from "../../element-web-test";
|
import { test, expect } from "../../element-web-test";
|
||||||
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({
|
||||||
@@ -23,6 +24,8 @@ 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");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ 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";
|
||||||
@@ -181,6 +182,8 @@ 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");
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ test.describe("Memberlist", () => {
|
|||||||
await app.viewRoomByName(ROOM_NAME);
|
await app.viewRoomByName(ROOM_NAME);
|
||||||
const memberlist = await app.toggleMemberlistPanel();
|
const memberlist = await app.toggleMemberlistPanel();
|
||||||
await expect(memberlist.locator(".mx_MemberTileView")).toHaveCount(4);
|
await expect(memberlist.locator(".mx_MemberTileView")).toHaveCount(4);
|
||||||
await expect(memberlist.getByText("(Invited)")).toHaveCount(1);
|
await expect(memberlist.getByText("Invited")).toHaveCount(1);
|
||||||
await expect(page.locator(".mx_MemberListView")).toMatchScreenshot("with-four-members.png");
|
await expect(page.locator(".mx_MemberListView")).toMatchScreenshot("with-four-members.png");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,30 +38,34 @@ test.describe("RightPanel", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test.describe("in rooms", () => {
|
test.describe("in rooms", () => {
|
||||||
test("should handle long room address and long room name", { tag: "@screenshot" }, async ({ page, app }) => {
|
test(
|
||||||
await app.client.createRoom({ name: ROOM_NAME_LONG });
|
"should handle long room address and long room name",
|
||||||
await viewRoomSummaryByName(page, app, ROOM_NAME_LONG);
|
{ tag: "@screenshot" },
|
||||||
|
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 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,6 +10,7 @@ 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" },
|
||||||
@@ -32,14 +33,14 @@ test.describe("Room Directory", () => {
|
|||||||
await localAddresses.getByRole("textbox").fill("gaming");
|
await localAddresses.getByRole("textbox").fill("gaming");
|
||||||
await expect(page.getByText("This address is available to use")).toBeVisible();
|
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:localhost")).toHaveClass("mx_EditableItem_item");
|
await expect(localAddresses.getByText(`#gaming:${user.homeServer}`)).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:localhost");
|
await expect(publishedAddresses.locator("#canonicalAlias")).toHaveValue(`#gaming:${user.homeServer}`);
|
||||||
const checkbox = publishedAddresses
|
const checkbox = publishedAddresses
|
||||||
.locator(".mx_SettingsFlag", {
|
.locator(".mx_SettingsFlag", {
|
||||||
hasText: "Publish this room to the public in localhost's room directory?",
|
hasText: `Publish this room to the public in ${user.homeServer}'s room directory?`,
|
||||||
})
|
})
|
||||||
.getByRole("switch");
|
.getByRole("switch");
|
||||||
await checkbox.check();
|
await checkbox.check();
|
||||||
@@ -87,7 +88,7 @@ test.describe("Room Directory", () => {
|
|||||||
.getByRole("button", { name: "Join" })
|
.getByRole("button", { name: "Join" })
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
await expect(page).toHaveURL("/#/room/#test1234:localhost");
|
await expect(page).toHaveURL(`/#/room/#test1234:${user.homeServer}`);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -111,6 +111,10 @@ 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,10 +7,13 @@ 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: {
|
||||||
|
|||||||
97
playwright/e2e/settings/encryption-user-tab/index.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
156
playwright/e2e/settings/encryption-user-tab/recovery.spec.ts
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* 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,7 +36,7 @@ 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 }) => {
|
test("long address should not cause dialog to overflow", { tag: "@no-webkit" }, async ({ page, app, user }) => {
|
||||||
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);
|
||||||
@@ -45,7 +45,7 @@ test.describe("General room settings tab", () => {
|
|||||||
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}:localhost`);
|
await expect(settings.locator("#canonicalAlias")).toHaveValue(`#${longString}:${user.homeServer}`);
|
||||||
|
|
||||||
// 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();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ 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();
|
||||||
@@ -50,6 +51,7 @@ 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" },
|
||||||
@@ -82,7 +84,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.getClipboardText()).toEqual("https://matrix.to/#/#lets-have-a-riot:localhost");
|
expect(await app.getClipboard()).toEqual(`https://matrix.to/#/#lets-have-a-riot:${user.homeServer}`);
|
||||||
|
|
||||||
// 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();
|
||||||
@@ -169,13 +171,13 @@ test.describe("Spaces", () => {
|
|||||||
room_alias_name: "space",
|
room_alias_name: "space",
|
||||||
});
|
});
|
||||||
|
|
||||||
const menu = await openSpaceContextMenu(page, app, "#space:localhost");
|
const menu = await openSpaceContextMenu(page, app, `#space:${user.homeServer}`);
|
||||||
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.getClipboardText()).toEqual("https://matrix.to/#/#space:localhost");
|
expect(await app.getClipboard()).toEqual(`https://matrix.to/#/#space:${user.homeServer}`);
|
||||||
// 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,11 +38,13 @@ 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,8 +8,14 @@
|
|||||||
|
|
||||||
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" },
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ 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");
|
||||||
@@ -89,6 +90,7 @@ const test = base.extend<{
|
|||||||
});
|
});
|
||||||
|
|
||||||
test.describe("Spotlight", () => {
|
test.describe("Spotlight", () => {
|
||||||
|
test.skip(isDendrite, "due to a Dendrite bug https://github.com/element-hq/dendrite/issues/3488");
|
||||||
test.use({
|
test.use({
|
||||||
displayName: "Jim",
|
displayName: "Jim",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ 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: {
|
||||||
|
|||||||
@@ -1195,6 +1195,7 @@ 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
|
||||||
|
|||||||
31
playwright/e2e/voip/pstn.spec.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
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, roomId: string, contentUri: string) {
|
async function expectTimelineSticker(page: Page, serverName: string, 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, roomId: string, contentUri: 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(`/localhost/${contentId}`),
|
new RegExp(`/${serverName}/${contentId}`),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ test.describe("Stickers", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
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, room.roomId, contentUri);
|
await expectTimelineSticker(page, user.homeServer, 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, roomId2, contentUri);
|
await expectTimelineSticker(page, user.homeServer, roomId2, contentUri);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should handle a sticker picker widget missing creatorUserId", async ({
|
test("should handle a sticker picker widget missing creatorUserId", async ({
|
||||||
@@ -183,7 +183,7 @@ test.describe("Stickers", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
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, room.roomId, contentUri);
|
await expectTimelineSticker(page, user.homeServer, 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 }) => {
|
||||||
|
|||||||
@@ -24,16 +24,40 @@ 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 Map<string, TestCase[]>();
|
||||||
|
|
||||||
public onTestEnd(test: TestCase): void {
|
public onTestEnd(test: TestCase): void {
|
||||||
const title = `${test.location.file.split("playwright/e2e/")[1]}: ${test.title}`;
|
// Ignores flakes on Dendrite and Pinecone as they have their own flakes we do not track
|
||||||
|
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") {
|
||||||
if (!this.flakes.has(title)) {
|
const timedOutRuns = test.results.filter((result) => result.status === "timedOut");
|
||||||
this.flakes.set(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);
|
||||||
}
|
}
|
||||||
this.flakes.get(title).push(test);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -158,10 +158,6 @@ 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();
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import type {
|
|||||||
ICreateRoomOpts,
|
ICreateRoomOpts,
|
||||||
ISendEventResponse,
|
ISendEventResponse,
|
||||||
MatrixClient,
|
MatrixClient,
|
||||||
Room,
|
|
||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
ReceiptType,
|
ReceiptType,
|
||||||
IRoomDirectoryOptions,
|
IRoomDirectoryOptions,
|
||||||
@@ -178,21 +177,12 @@ 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();
|
||||||
return await client.evaluate(async (cli, options) => {
|
const roomId = await client.evaluate(async (cli, options) => {
|
||||||
const { room_id: roomId } = await cli.createRoom(options);
|
const { room_id: roomId } = await cli.createRoom(options);
|
||||||
if (!cli.getRoom(roomId)) {
|
|
||||||
await 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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return roomId;
|
return roomId;
|
||||||
}, options);
|
}, options);
|
||||||
|
await this.awaitRoomMembership(roomId);
|
||||||
|
return roomId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,34 +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 { DendriteContainer, PineconeContainer } from "../../../testcontainers/dendrite.ts";
|
import { Options } from "../../../services.ts";
|
||||||
import { Fixtures } from "../../../element-web-test.ts";
|
|
||||||
|
|
||||||
export const dendriteHomeserver: Fixtures = {
|
export const isDendrite = ({ homeserverType }: Options): boolean => {
|
||||||
_homeserver: [
|
return homeserverType === "dendrite" || homeserverType === "pinecone";
|
||||||
// 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";
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -46,3 +46,5 @@ 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";
|
||||||
|
|||||||
@@ -54,4 +54,9 @@ 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);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,4 +26,9 @@ export const emailHomeserver: Fixtures = {
|
|||||||
},
|
},
|
||||||
{ scope: "worker" },
|
{ scope: "worker" },
|
||||||
],
|
],
|
||||||
|
|
||||||
|
context: async ({ homeserverType, context }, use, testInfo) => {
|
||||||
|
testInfo.skip(homeserverType !== "synapse", "does not yet support MAS");
|
||||||
|
await use(context);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ export const legacyOAuthHomeserver: Fixtures = {
|
|||||||
},
|
},
|
||||||
{ scope: "worker" },
|
{ scope: "worker" },
|
||||||
],
|
],
|
||||||
context: async ({ context, oAuthServer }, use, testInfo) => {
|
context: async ({ homeserverType, context, oAuthServer }, use, testInfo) => {
|
||||||
|
testInfo.skip(homeserverType !== "synapse", "does not yet support OIDC");
|
||||||
oAuthServer.onTestStarted(testInfo);
|
oAuthServer.onTestStarted(testInfo);
|
||||||
await use(context);
|
await use(context);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -79,4 +79,9 @@ export const masHomeserver: Fixtures = {
|
|||||||
default_server_config: wellKnown,
|
default_server_config: wellKnown,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
context: async ({ homeserverType, context }, use, testInfo) => {
|
||||||
|
testInfo.skip(homeserverType !== "synapse", "does not yet support MAS");
|
||||||
|
await use(context);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import { StartedMatrixAuthenticationServiceContainer } from "./testcontainers/ma
|
|||||||
import { HomeserverContainer, StartedHomeserverContainer } from "./testcontainers/HomeserverContainer.ts";
|
import { HomeserverContainer, StartedHomeserverContainer } from "./testcontainers/HomeserverContainer.ts";
|
||||||
import { MailhogContainer, StartedMailhogContainer } from "./testcontainers/mailhog.ts";
|
import { MailhogContainer, StartedMailhogContainer } from "./testcontainers/mailhog.ts";
|
||||||
import { OAuthServer } from "./plugins/oauth_server";
|
import { OAuthServer } from "./plugins/oauth_server";
|
||||||
|
import { DendriteContainer, PineconeContainer } from "./testcontainers/dendrite.ts";
|
||||||
|
import { HomeserverType } from "./plugins/homeserver";
|
||||||
|
|
||||||
export interface TestFixtures {
|
export interface TestFixtures {
|
||||||
mailhogClient: mailhog.API;
|
mailhogClient: mailhog.API;
|
||||||
@@ -37,7 +39,9 @@ export interface Services {
|
|||||||
oAuthServer?: OAuthServer;
|
oAuthServer?: OAuthServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Options {}
|
export interface Options {
|
||||||
|
homeserverType: HomeserverType;
|
||||||
|
}
|
||||||
|
|
||||||
export const test = base.extend<TestFixtures, Services & Options>({
|
export const test = base.extend<TestFixtures, Services & Options>({
|
||||||
logger: [
|
logger: [
|
||||||
@@ -104,21 +108,35 @@ export const test = base.extend<TestFixtures, Services & Options>({
|
|||||||
},
|
},
|
||||||
|
|
||||||
synapseConfig: [{}, { scope: "worker" }],
|
synapseConfig: [{}, { scope: "worker" }],
|
||||||
|
homeserverType: ["synapse", { option: true, scope: "worker" }],
|
||||||
_homeserver: [
|
_homeserver: [
|
||||||
// eslint-disable-next-line no-empty-pattern
|
async ({ homeserverType }, use) => {
|
||||||
async ({}, use) => {
|
let container: HomeserverContainer<any>;
|
||||||
const container = new SynapseContainer();
|
switch (homeserverType) {
|
||||||
|
case "synapse":
|
||||||
|
container = new SynapseContainer();
|
||||||
|
break;
|
||||||
|
case "dendrite":
|
||||||
|
container = new DendriteContainer();
|
||||||
|
break;
|
||||||
|
case "pinecone":
|
||||||
|
container = new PineconeContainer();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
await use(container);
|
await use(container);
|
||||||
},
|
},
|
||||||
{ scope: "worker" },
|
{ scope: "worker" },
|
||||||
],
|
],
|
||||||
homeserver: [
|
homeserver: [
|
||||||
async ({ logger, network, _homeserver: homeserver, synapseConfig, mas }, use) => {
|
async ({ homeserverType, logger, network, _homeserver: homeserver, synapseConfig, mas }, use) => {
|
||||||
|
if (homeserver instanceof SynapseContainer) {
|
||||||
|
homeserver.withConfig(synapseConfig);
|
||||||
|
}
|
||||||
const container = await homeserver
|
const container = await homeserver
|
||||||
.withNetwork(network)
|
.withNetwork(network)
|
||||||
.withNetworkAliases("homeserver")
|
.withNetworkAliases("homeserver")
|
||||||
.withLogConsumer(logger.getConsumer("synapse"))
|
.withLogConsumer(logger.getConsumer(homeserverType))
|
||||||
.withConfig(synapseConfig)
|
|
||||||
.withMatrixAuthenticationService(mas)
|
.withMatrixAuthenticationService(mas)
|
||||||
.start();
|
.start();
|
||||||
|
|
||||||
@@ -137,7 +155,15 @@ export const test = base.extend<TestFixtures, Services & Options>({
|
|||||||
{ scope: "worker" },
|
{ scope: "worker" },
|
||||||
],
|
],
|
||||||
|
|
||||||
context: async ({ logger, context, request, homeserver, mailhogClient }, use, testInfo) => {
|
context: async (
|
||||||
|
{ homeserverType, synapseConfig, logger, context, request, _homeserver, homeserver },
|
||||||
|
use,
|
||||||
|
testInfo,
|
||||||
|
) => {
|
||||||
|
testInfo.skip(
|
||||||
|
!(_homeserver instanceof SynapseContainer) && Object.keys(synapseConfig).length > 0,
|
||||||
|
`Test specifies Synapse config options so is unsupported with ${homeserverType}`,
|
||||||
|
);
|
||||||
homeserver.setRequest(request);
|
homeserver.setRequest(request);
|
||||||
await logger.onTestStarted(context);
|
await logger.onTestStarted(context);
|
||||||
await use(context);
|
await use(context);
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 239 KiB After Width: | Height: | Size: 239 KiB |
BIN
playwright/snapshots/voip/pstn.spec.ts/dialpad-linux.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
playwright/snapshots/voip/pstn.spec.ts/dialpad-trigger-linux.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
@@ -236,8 +236,9 @@ export class DendriteContainer extends GenericContainer implements HomeserverCon
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dendrite does not support MAS at this time
|
||||||
public withMatrixAuthenticationService(mas?: StartedMatrixAuthenticationServiceContainer): this {
|
public withMatrixAuthenticationService(mas?: StartedMatrixAuthenticationServiceContainer): this {
|
||||||
throw new Error("Dendrite does not support MAS.");
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async start(): Promise<StartedDendriteContainer> {
|
public override async start(): Promise<StartedDendriteContainer> {
|
||||||
@@ -264,4 +265,10 @@ export class PineconeContainer extends DendriteContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Surprisingly, Dendrite implements the same register user Synapse Admin API, so we can just extend it
|
// Surprisingly, Dendrite implements the same register user Synapse Admin API, so we can just extend it
|
||||||
export class StartedDendriteContainer extends StartedSynapseContainer {}
|
export class StartedDendriteContainer extends StartedSynapseContainer {
|
||||||
|
protected async deletePublicRooms(): Promise<void> {
|
||||||
|
// Dendrite does not support admin users managing the room directory
|
||||||
|
// https://github.com/element-hq/dendrite/blob/main/clientapi/routing/directory.go#L365
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { HomeserverContainer, StartedHomeserverContainer } from "./HomeserverCon
|
|||||||
import { StartedMatrixAuthenticationServiceContainer } from "./mas.ts";
|
import { StartedMatrixAuthenticationServiceContainer } from "./mas.ts";
|
||||||
import { Api, ClientServerApi, Verb } from "../plugins/utils/api.ts";
|
import { Api, ClientServerApi, Verb } from "../plugins/utils/api.ts";
|
||||||
|
|
||||||
const TAG = "develop@sha256:7be2e00da62dfbb2bad071c6d408fecb1fabf740a538d08768b9b3e0a8c45350";
|
const TAG = "develop@sha256:3594fba0d21ad44f407225baed4be0542da8abcb6e1a7e2e16d3be35c278a7cb";
|
||||||
|
|
||||||
const DEFAULT_CONFIG = {
|
const DEFAULT_CONFIG = {
|
||||||
server_name: "localhost",
|
server_name: "localhost",
|
||||||
|
|||||||
@@ -596,7 +596,9 @@ legend {
|
|||||||
.mx_Dialog
|
.mx_Dialog
|
||||||
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
|
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
|
||||||
.mx_UserProfileSettings button
|
.mx_UserProfileSettings button
|
||||||
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button),
|
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
|
||||||
|
.mx_EncryptionUserSettingsTab button
|
||||||
|
),
|
||||||
.mx_Dialog input[type="submit"],
|
.mx_Dialog input[type="submit"],
|
||||||
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton),
|
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton),
|
||||||
.mx_Dialog_buttons input[type="submit"] {
|
.mx_Dialog_buttons input[type="submit"] {
|
||||||
@@ -616,8 +618,8 @@ legend {
|
|||||||
.mx_Dialog
|
.mx_Dialog
|
||||||
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
|
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
|
||||||
.mx_UserProfileSettings button
|
.mx_UserProfileSettings button
|
||||||
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(
|
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
|
||||||
.mx_ShareDialog button
|
.mx_EncryptionUserSettingsTab button
|
||||||
):last-child {
|
):last-child {
|
||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
}
|
}
|
||||||
@@ -625,7 +627,9 @@ legend {
|
|||||||
.mx_Dialog
|
.mx_Dialog
|
||||||
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
|
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
|
||||||
.mx_UserProfileSettings button
|
.mx_UserProfileSettings button
|
||||||
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):focus,
|
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
|
||||||
|
.mx_EncryptionUserSettingsTab button
|
||||||
|
):focus,
|
||||||
.mx_Dialog input[type="submit"]:focus,
|
.mx_Dialog input[type="submit"]:focus,
|
||||||
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):focus,
|
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):focus,
|
||||||
.mx_Dialog_buttons input[type="submit"]:focus {
|
.mx_Dialog_buttons input[type="submit"]:focus {
|
||||||
@@ -637,7 +641,9 @@ legend {
|
|||||||
.mx_Dialog_buttons
|
.mx_Dialog_buttons
|
||||||
button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not(
|
button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not(
|
||||||
.mx_UserProfileSettings button
|
.mx_UserProfileSettings button
|
||||||
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button),
|
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
|
||||||
|
.mx_EncryptionUserSettingsTab button
|
||||||
|
),
|
||||||
.mx_Dialog_buttons input[type="submit"].mx_Dialog_primary {
|
.mx_Dialog_buttons input[type="submit"].mx_Dialog_primary {
|
||||||
color: var(--cpd-color-text-on-solid-primary);
|
color: var(--cpd-color-text-on-solid-primary);
|
||||||
background-color: var(--cpd-color-bg-action-primary-rest);
|
background-color: var(--cpd-color-bg-action-primary-rest);
|
||||||
@@ -650,7 +656,7 @@ legend {
|
|||||||
.mx_Dialog_buttons
|
.mx_Dialog_buttons
|
||||||
button.danger:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not(.mx_UserProfileSettings button):not(
|
button.danger:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not(.mx_UserProfileSettings button):not(
|
||||||
.mx_ThemeChoicePanel_CustomTheme button
|
.mx_ThemeChoicePanel_CustomTheme button
|
||||||
):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button),
|
):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(.mx_EncryptionUserSettingsTab button),
|
||||||
.mx_Dialog_buttons input[type="submit"].danger {
|
.mx_Dialog_buttons input[type="submit"].danger {
|
||||||
background-color: var(--cpd-color-bg-critical-primary);
|
background-color: var(--cpd-color-bg-critical-primary);
|
||||||
border: solid 1px var(--cpd-color-bg-critical-primary);
|
border: solid 1px var(--cpd-color-bg-critical-primary);
|
||||||
@@ -666,7 +672,9 @@ legend {
|
|||||||
.mx_Dialog
|
.mx_Dialog
|
||||||
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
|
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
|
||||||
.mx_UserProfileSettings button
|
.mx_UserProfileSettings button
|
||||||
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):disabled,
|
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
|
||||||
|
.mx_EncryptionUserSettingsTab button
|
||||||
|
):disabled,
|
||||||
.mx_Dialog input[type="submit"]:disabled,
|
.mx_Dialog input[type="submit"]:disabled,
|
||||||
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):disabled,
|
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):disabled,
|
||||||
.mx_Dialog_buttons input[type="submit"]:disabled {
|
.mx_Dialog_buttons input[type="submit"]:disabled {
|
||||||
|
|||||||
@@ -283,6 +283,7 @@
|
|||||||
@import "./views/rooms/_EventTile.pcss";
|
@import "./views/rooms/_EventTile.pcss";
|
||||||
@import "./views/rooms/_HistoryTile.pcss";
|
@import "./views/rooms/_HistoryTile.pcss";
|
||||||
@import "./views/rooms/_IRCLayout.pcss";
|
@import "./views/rooms/_IRCLayout.pcss";
|
||||||
|
@import "./views/rooms/_InvitedIconView.pcss";
|
||||||
@import "./views/rooms/_JumpToBottomButton.pcss";
|
@import "./views/rooms/_JumpToBottomButton.pcss";
|
||||||
@import "./views/rooms/_LinkPreviewGroup.pcss";
|
@import "./views/rooms/_LinkPreviewGroup.pcss";
|
||||||
@import "./views/rooms/_LinkPreviewWidget.pcss";
|
@import "./views/rooms/_LinkPreviewWidget.pcss";
|
||||||
@@ -347,10 +348,14 @@
|
|||||||
@import "./views/settings/_SetIdServer.pcss";
|
@import "./views/settings/_SetIdServer.pcss";
|
||||||
@import "./views/settings/_SetIntegrationManager.pcss";
|
@import "./views/settings/_SetIntegrationManager.pcss";
|
||||||
@import "./views/settings/_SettingsFieldset.pcss";
|
@import "./views/settings/_SettingsFieldset.pcss";
|
||||||
|
@import "./views/settings/_SettingsHeader.pcss";
|
||||||
|
@import "./views/settings/_SettingsSubheader.pcss";
|
||||||
@import "./views/settings/_SpellCheckLanguages.pcss";
|
@import "./views/settings/_SpellCheckLanguages.pcss";
|
||||||
@import "./views/settings/_ThemeChoicePanel.pcss";
|
@import "./views/settings/_ThemeChoicePanel.pcss";
|
||||||
@import "./views/settings/_UpdateCheckButton.pcss";
|
@import "./views/settings/_UpdateCheckButton.pcss";
|
||||||
@import "./views/settings/_UserProfileSettings.pcss";
|
@import "./views/settings/_UserProfileSettings.pcss";
|
||||||
|
@import "./views/settings/encryption/_ChangeRecoveryKey.pcss";
|
||||||
|
@import "./views/settings/encryption/_EncryptionCard.pcss";
|
||||||
@import "./views/settings/tabs/_SettingsBanner.pcss";
|
@import "./views/settings/tabs/_SettingsBanner.pcss";
|
||||||
@import "./views/settings/tabs/_SettingsIndent.pcss";
|
@import "./views/settings/tabs/_SettingsIndent.pcss";
|
||||||
@import "./views/settings/tabs/_SettingsSection.pcss";
|
@import "./views/settings/tabs/_SettingsSection.pcss";
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
.mx_DisambiguatedProfile_mxid {
|
.mx_DisambiguatedProfile_mxid {
|
||||||
margin-inline-start: 0;
|
margin-inline-start: 0;
|
||||||
font: var(--cpd-font-body-sm-regular);
|
font: var(--cpd-font-body-sm-regular);
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
span:not(.mx_DisambiguatedProfile_mxid) {
|
span:not(.mx_DisambiguatedProfile_mxid) {
|
||||||
|
|||||||
10
res/css/views/rooms/_InvitedIconView.pcss
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_InvitedIconView {
|
||||||
|
color: var(--cpd-color-icon-tertiary);
|
||||||
|
}
|
||||||
@@ -14,4 +14,10 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
.mx_MemberListView_container {
|
.mx_MemberListView_container {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_MemberListView_separator {
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
border-top: 2px solid var(--cpd-color-bg-subtle-primary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,11 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MemberTileView_user_label {
|
.mx_MemberTileView_userLabel {
|
||||||
font: var(--cpd-font-body-sm-regular);
|
font: var(--cpd-font-body-sm-regular);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
color: var(--cpd-color-text-secondary);
|
||||||
|
margin-left: var(--cpd-space-4x);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MemberTileView_avatar {
|
.mx_MemberTileView_avatar {
|
||||||
@@ -41,18 +43,4 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
height: 32px;
|
height: 32px;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_E2EIconView {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_E2EIconView_warning {
|
|
||||||
color: var(--cpd-color-icon-critical-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_E2EIconView_verified {
|
|
||||||
color: var(--cpd-color-icon-success-primary);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||