Merge branch 'develop' of https://github.com/vector-im/element-web into t3chguy/playwright-homeservers
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> # Conflicts: # .github/workflows/end-to-end-tests.yaml # playwright/e2e/crypto/backups.spec.ts # playwright/e2e/login/login-consent.spec.ts # playwright/e2e/login/soft_logout.spec.ts # playwright/e2e/oidc/oidc-native.spec.ts # playwright/e2e/register/register.spec.ts # playwright/e2e/spaces/threads-activity-centre/threadsActivityCentre.spec.ts # playwright/element-web-test.ts # playwright/plugins/homeserver/dendrite/index.ts # playwright/plugins/homeserver/synapse/consentHomeserver.ts # playwright/plugins/homeserver/synapse/emailHomeserver.ts # playwright/plugins/homeserver/synapse/legacyOAuthHomeserver.ts # playwright/plugins/homeserver/synapse/masHomeserver.ts # playwright/services.ts # playwright/testcontainers/synapse.tst3chguy/playwright-homeservers
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024 New Vector Ltd.
|
||||||
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { test, expect } from "../../element-web-test";
|
||||||
|
import { registerAccountMas } from "../oidc";
|
||||||
|
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
||||||
|
import { TestClientServerAPI } from "../csAPI";
|
||||||
|
import { masHomeserver } from "../../plugins/homeserver/synapse/masHomeserver.ts";
|
||||||
|
|
||||||
|
// These tests register an account with MAS because then we go through the "normal" registration flow
|
||||||
|
// and crypto gets set up. Using the 'user' fixture create a user and synthesizes an existing login,
|
||||||
|
// which is faster but leaves us without crypto set up.
|
||||||
|
test.use(masHomeserver);
|
||||||
|
test.describe("Encryption state after registration", () => {
|
||||||
|
test.skip(isDendrite, "does not yet support MAS");
|
||||||
|
|
||||||
|
test("Key backup is enabled by default", async ({ page, mailhogClient, app }) => {
|
||||||
|
await page.goto("/#/login");
|
||||||
|
await page.getByRole("button", { name: "Continue" }).click();
|
||||||
|
await registerAccountMas(page, mailhogClient, "alice", "alice@email.com", "Pa$sW0rD!");
|
||||||
|
|
||||||
|
await app.settings.openUserSettings("Security & Privacy");
|
||||||
|
await expect(page.getByText("This session is backing up your keys.")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("user is prompted to set up recovery", async ({ page, mailhogClient, app }) => {
|
||||||
|
await page.goto("/#/login");
|
||||||
|
await page.getByRole("button", { name: "Continue" }).click();
|
||||||
|
await registerAccountMas(page, mailhogClient, "alice", "alice@email.com", "Pa$sW0rD!");
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: "Add room" }).click();
|
||||||
|
await page.getByRole("menuitem", { name: "New room" }).click();
|
||||||
|
await page.getByRole("textbox", { name: "Name" }).fill("test room");
|
||||||
|
await page.getByRole("button", { name: "Create room" }).click();
|
||||||
|
|
||||||
|
await expect(page.getByRole("heading", { name: "Set up recovery" })).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("Key backup reset from elsewhere", () => {
|
||||||
|
test.skip(isDendrite, "does not yet support MAS");
|
||||||
|
|
||||||
|
test("Key backup is disabled when reset from elsewhere", async ({ page, mailhogClient, request, homeserver }) => {
|
||||||
|
const testUsername = "alice";
|
||||||
|
const testPassword = "Pa$sW0rD!";
|
||||||
|
|
||||||
|
// there's a delay before keys are uploaded so the error doesn't appear immediately: use a fake
|
||||||
|
// clock so we can skip the delay
|
||||||
|
await page.clock.install();
|
||||||
|
|
||||||
|
await page.goto("/#/login");
|
||||||
|
await page.getByRole("button", { name: "Continue" }).click();
|
||||||
|
await registerAccountMas(page, mailhogClient, testUsername, "alice@email.com", testPassword);
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: "Add room" }).click();
|
||||||
|
await page.getByRole("menuitem", { name: "New room" }).click();
|
||||||
|
await page.getByRole("textbox", { name: "Name" }).fill("test room");
|
||||||
|
await page.getByRole("button", { name: "Create room" }).click();
|
||||||
|
|
||||||
|
// @ts-ignore - this runs in the browser scope where mxMatrixClientPeg is a thing. Here, it is not.
|
||||||
|
const accessToken = await page.evaluate(() => mxMatrixClientPeg.get().getAccessToken());
|
||||||
|
|
||||||
|
const csAPI = new TestClientServerAPI(request, homeserver, accessToken);
|
||||||
|
|
||||||
|
const backupInfo = await csAPI.getCurrentBackupInfo();
|
||||||
|
|
||||||
|
await csAPI.deleteBackupVersion(backupInfo.version);
|
||||||
|
|
||||||
|
await page.getByRole("textbox", { name: "Send an encrypted message…" }).fill("/discardsession");
|
||||||
|
await page.getByRole("button", { name: "Send message" }).click();
|
||||||
|
|
||||||
|
await page.getByRole("textbox", { name: "Send an encrypted message…" }).fill("Message with broken key backup");
|
||||||
|
await page.getByRole("button", { name: "Send message" }).click();
|
||||||
|
|
||||||
|
// Should be the message we sent plus the room creation event
|
||||||
|
await expect(page.locator(".mx_EventTile")).toHaveCount(2);
|
||||||
|
await expect(
|
||||||
|
page.locator(".mx_RoomView_MessageList > .mx_EventTile_last .mx_EventTile_receiptSent"),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Wait for it to try uploading the key
|
||||||
|
await page.clock.fastForward(20000);
|
||||||
|
|
||||||
|
await expect(page.getByRole("heading", { level: 1, name: "New Recovery Method" })).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
|
@ -9,10 +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 { registerAccountMas } from "../oidc";
|
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
||||||
import { TestClientServerAPI } from "../csAPI";
|
|
||||||
import { masHomeserver } from "../../plugins/homeserver/synapse/masHomeserver.ts";
|
|
||||||
|
|
||||||
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(
|
||||||
|
@ -22,83 +19,6 @@ async function expectBackupVersionToBe(page: Page, version: string) {
|
||||||
await expect(page.locator(".mx_SecureBackupPanel_statusList tr:nth-child(6) td")).toHaveText(version);
|
await expect(page.locator(".mx_SecureBackupPanel_statusList tr:nth-child(6) td")).toHaveText(version);
|
||||||
}
|
}
|
||||||
|
|
||||||
// These tests register an account with MAS because then we go through the "normal" registration flow
|
|
||||||
// and crypto gets set up. Using the 'user' fixture create a user and synthesizes an existing login,
|
|
||||||
// which is faster but leaves us without crypto set up.
|
|
||||||
test.describe("Encryption state after registration", () => {
|
|
||||||
test.use(masHomeserver);
|
|
||||||
|
|
||||||
test("Key backup is enabled by default", async ({ page, mailhogClient, app }) => {
|
|
||||||
await page.goto("/#/login");
|
|
||||||
await page.getByRole("button", { name: "Continue" }).click();
|
|
||||||
await registerAccountMas(page, mailhogClient, "alice", "alice@email.com", "Pa$sW0rD!");
|
|
||||||
|
|
||||||
await app.settings.openUserSettings("Security & Privacy");
|
|
||||||
await expect(page.getByText("This session is backing up your keys.")).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("user is prompted to set up recovery", async ({ page, mailhogClient, app }) => {
|
|
||||||
await page.goto("/#/login");
|
|
||||||
await page.getByRole("button", { name: "Continue" }).click();
|
|
||||||
await registerAccountMas(page, mailhogClient, "alice", "alice@email.com", "Pa$sW0rD!");
|
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Add room" }).click();
|
|
||||||
await page.getByRole("menuitem", { name: "New room" }).click();
|
|
||||||
await page.getByRole("textbox", { name: "Name" }).fill("test room");
|
|
||||||
await page.getByRole("button", { name: "Create room" }).click();
|
|
||||||
|
|
||||||
await expect(page.getByRole("heading", { name: "Set up recovery" })).toBeVisible();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Key backup reset from elsewhere", () => {
|
|
||||||
test.use(masHomeserver);
|
|
||||||
|
|
||||||
test("Key backup is disabled when reset from elsewhere", async ({ page, mailhogClient, request, homeserver }) => {
|
|
||||||
const testUsername = "alice";
|
|
||||||
const testPassword = "Pa$sW0rD!";
|
|
||||||
|
|
||||||
// there's a delay before keys are uploaded so the error doesn't appear immediately: use a fake
|
|
||||||
// clock so we can skip the delay
|
|
||||||
await page.clock.install();
|
|
||||||
|
|
||||||
await page.goto("/#/login");
|
|
||||||
await page.getByRole("button", { name: "Continue" }).click();
|
|
||||||
await registerAccountMas(page, mailhogClient, testUsername, "alice@email.com", testPassword);
|
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Add room" }).click();
|
|
||||||
await page.getByRole("menuitem", { name: "New room" }).click();
|
|
||||||
await page.getByRole("textbox", { name: "Name" }).fill("test room");
|
|
||||||
await page.getByRole("button", { name: "Create room" }).click();
|
|
||||||
|
|
||||||
// @ts-ignore - this runs in the browser scope where mxMatrixClientPeg is a thing. Here, it is not.
|
|
||||||
const accessToken = await page.evaluate(() => mxMatrixClientPeg.get().getAccessToken());
|
|
||||||
|
|
||||||
const csAPI = new TestClientServerAPI(request, homeserver, accessToken);
|
|
||||||
|
|
||||||
const backupInfo = await csAPI.getCurrentBackupInfo();
|
|
||||||
|
|
||||||
await csAPI.deleteBackupVersion(backupInfo.version);
|
|
||||||
|
|
||||||
await page.getByRole("textbox", { name: "Send an encrypted message…" }).fill("/discardsession");
|
|
||||||
await page.getByRole("button", { name: "Send message" }).click();
|
|
||||||
|
|
||||||
await page.getByRole("textbox", { name: "Send an encrypted message…" }).fill("Message with broken key backup");
|
|
||||||
await page.getByRole("button", { name: "Send message" }).click();
|
|
||||||
|
|
||||||
// Should be the message we sent plus the room creation event
|
|
||||||
await expect(page.locator(".mx_EventTile")).toHaveCount(2);
|
|
||||||
await expect(
|
|
||||||
page.locator(".mx_RoomView_MessageList > .mx_EventTile_last .mx_EventTile_receiptSent"),
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
// Wait for it to try uploading the key
|
|
||||||
await page.clock.fastForward(20000);
|
|
||||||
|
|
||||||
await expect(page.getByRole("heading", { level: 1, name: "New Recovery Method" })).toBeVisible();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Backups", () => {
|
test.describe("Backups", () => {
|
||||||
test.skip(isDendrite, "Dendrite lacks support for MSC3967 so requires additional auth here");
|
test.skip(isDendrite, "Dendrite lacks support for MSC3967 so requires additional auth here");
|
||||||
test.use({
|
test.use({
|
||||||
|
|
|
@ -19,31 +19,31 @@ function getMemberTileByName(page: Page, name: string): Locator {
|
||||||
return page.locator(`.mx_EntityTile, [title="${name}"]`);
|
return page.locator(`.mx_EntityTile, [title="${name}"]`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test.use({
|
||||||
|
displayName: NAME,
|
||||||
|
synapseConfigOptions: {
|
||||||
|
experimental_features: {
|
||||||
|
msc2697_enabled: false,
|
||||||
|
msc3814_enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
config: async ({ config, context }, use) => {
|
||||||
|
const wellKnown = {
|
||||||
|
...config.default_server_config,
|
||||||
|
"org.matrix.msc3814": true,
|
||||||
|
};
|
||||||
|
|
||||||
|
await context.route("https://localhost/.well-known/matrix/client", async (route) => {
|
||||||
|
await route.fulfill({ json: wellKnown });
|
||||||
|
});
|
||||||
|
|
||||||
|
await use(config);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
test.describe("Dehydration", () => {
|
test.describe("Dehydration", () => {
|
||||||
test.skip(isDendrite, "does not yet support dehydration v2");
|
test.skip(isDendrite, "does not yet support dehydration v2");
|
||||||
|
|
||||||
test.use({
|
|
||||||
displayName: NAME,
|
|
||||||
synapseConfigOptions: {
|
|
||||||
experimental_features: {
|
|
||||||
msc2697_enabled: false,
|
|
||||||
msc3814_enabled: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
config: async ({ config, context }, use) => {
|
|
||||||
const wellKnown = {
|
|
||||||
...config.default_server_config,
|
|
||||||
"org.matrix.msc3814": true,
|
|
||||||
};
|
|
||||||
|
|
||||||
await context.route("https://localhost/.well-known/matrix/client", async (route) => {
|
|
||||||
await route.fulfill({ json: wellKnown });
|
|
||||||
});
|
|
||||||
|
|
||||||
await use(config);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Create dehydrated device", async ({ page, user, app }, workerInfo) => {
|
test("Create dehydrated device", async ({ page, user, app }, workerInfo) => {
|
||||||
// Create a backup (which will create SSSS, and dehydrated device)
|
// Create a backup (which will create SSSS, and dehydrated device)
|
||||||
|
|
||||||
|
|
|
@ -16,20 +16,21 @@ const username = "user1234";
|
||||||
const password = "oETo7MPf0o";
|
const password = "oETo7MPf0o";
|
||||||
const email = "user@nowhere.dummy";
|
const email = "user@nowhere.dummy";
|
||||||
|
|
||||||
test.describe("Forgot Password", () => {
|
test.use(emailHomeserver);
|
||||||
test.skip(isDendrite, "not yet wired up");
|
test.use({
|
||||||
test.use(emailHomeserver);
|
config: {
|
||||||
test.use({
|
// The only thing that we really *need* (otherwise Element refuses to load) is a default homeserver.
|
||||||
config: {
|
// We point that to a guaranteed-invalid domain.
|
||||||
// The only thing that we really *need* (otherwise Element refuses to load) is a default homeserver.
|
default_server_config: {
|
||||||
// We point that to a guaranteed-invalid domain.
|
"m.homeserver": {
|
||||||
default_server_config: {
|
base_url: "https://server.invalid",
|
||||||
"m.homeserver": {
|
|
||||||
base_url: "https://server.invalid",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("Forgot Password", () => {
|
||||||
|
test.skip(isDendrite, "not yet wired up");
|
||||||
|
|
||||||
test("renders properly", { tag: "@screenshot" }, async ({ page, homeserver }) => {
|
test("renders properly", { tag: "@screenshot" }, async ({ page, homeserver }) => {
|
||||||
await page.goto("/");
|
await page.goto("/");
|
||||||
|
|
|
@ -9,12 +9,12 @@ 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";
|
||||||
|
|
||||||
test.describe("Consent", () => {
|
test.use(consentHomeserver);
|
||||||
test.use(consentHomeserver);
|
test.use({
|
||||||
test.use({
|
displayName: "Bob",
|
||||||
displayName: "Bob",
|
});
|
||||||
});
|
|
||||||
|
|
||||||
|
test.describe("Consent", () => {
|
||||||
test("should prompt the user to consent to terms when server deems it necessary", async ({
|
test("should prompt the user to consent to terms when server deems it necessary", async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
|
|
|
@ -9,13 +9,12 @@ Please see LICENSE files in the repository root for full details.
|
||||||
import { Page } from "playwright-core";
|
import { Page } from "playwright-core";
|
||||||
|
|
||||||
import { expect, test } from "../../element-web-test";
|
import { expect, test } from "../../element-web-test";
|
||||||
import { doTokenRegistration } from "./utils";
|
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
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 { legacyOAuthHomeserver } from "../../plugins/homeserver/synapse/legacyOAuthHomeserver.ts";
|
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
||||||
|
|
||||||
|
// This test requires fixed credentials for the device signing keys below to work
|
||||||
const username = "user1234";
|
const username = "user1234";
|
||||||
const password = "p4s5W0rD";
|
const password = "p4s5W0rD";
|
||||||
|
|
||||||
|
@ -70,39 +69,44 @@ const DEVICE_SIGNING_KEYS_BODY = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async function login(page: Page, homeserver: HomeserverInstance) {
|
async function login(page: Page, homeserver: HomeserverInstance, credentials: Credentials) {
|
||||||
await page.getByRole("link", { name: "Sign in" }).click();
|
await page.getByRole("link", { name: "Sign in" }).click();
|
||||||
await selectHomeserver(page, homeserver.baseUrl);
|
await selectHomeserver(page, homeserver.baseUrl);
|
||||||
|
|
||||||
await page.getByRole("textbox", { name: "Username" }).fill(username);
|
await page.getByRole("textbox", { name: "Username" }).fill(credentials.username);
|
||||||
await page.getByPlaceholder("Password").fill(password);
|
await page.getByPlaceholder("Password").fill(credentials.password);
|
||||||
await page.getByRole("button", { name: "Sign in" }).click();
|
await page.getByRole("button", { name: "Sign in" }).click();
|
||||||
}
|
}
|
||||||
|
|
||||||
test.describe("Login", () => {
|
test.use(consentHomeserver);
|
||||||
test.use({
|
test.use({
|
||||||
config: {
|
config: {
|
||||||
// The only thing that we really *need* (otherwise Element refuses to load) is a default homeserver.
|
// The only thing that we really *need* (otherwise Element refuses to load) is a default homeserver.
|
||||||
// We point that to a guaranteed-invalid domain.
|
// We point that to a guaranteed-invalid domain.
|
||||||
default_server_config: {
|
default_server_config: {
|
||||||
"m.homeserver": {
|
"m.homeserver": {
|
||||||
base_url: "https://server.invalid",
|
base_url: "https://server.invalid",
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
credentials: async ({ context, homeserver }, use) => {
|
||||||
|
const displayName = "Dave";
|
||||||
|
const credentials = await homeserver.registerUser(username, password, displayName);
|
||||||
|
console.log(`Registered test user @user:localhost with displayname ${displayName}`);
|
||||||
|
|
||||||
|
await use({
|
||||||
|
...credentials,
|
||||||
|
displayName,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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.skip(isDendrite, "Dendrite lacks support for MSC3967 so requires additional auth here");
|
||||||
test.use(consentHomeserver);
|
|
||||||
|
|
||||||
let creds: Credentials;
|
|
||||||
|
|
||||||
test.beforeEach(async ({ homeserver }) => {
|
|
||||||
creds = await homeserver.registerUser(username, password);
|
|
||||||
});
|
|
||||||
|
|
||||||
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,
|
||||||
page,
|
page,
|
||||||
homeserver,
|
homeserver,
|
||||||
checkA11y,
|
checkA11y,
|
||||||
|
@ -136,16 +140,16 @@ test.describe("Login", () => {
|
||||||
// cy.percySnapshot("Login");
|
// cy.percySnapshot("Login");
|
||||||
await checkA11y();
|
await checkA11y();
|
||||||
|
|
||||||
await page.getByRole("textbox", { name: "Username" }).fill(username);
|
await page.getByRole("textbox", { name: "Username" }).fill(credentials.username);
|
||||||
await page.getByPlaceholder("Password").fill(password);
|
await page.getByPlaceholder("Password").fill(credentials.password);
|
||||||
await page.getByRole("button", { name: "Sign in" }).click();
|
await page.getByRole("button", { name: "Sign in" }).click();
|
||||||
|
|
||||||
await expect(page).toHaveURL(/\/#\/home$/);
|
await expect(page).toHaveURL(/\/#\/home$/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Follows the original link after login", async ({ page, homeserver }) => {
|
test("Follows the original link after login", async ({ page, homeserver, credentials }) => {
|
||||||
await page.goto("/#/room/!room:id"); // should redirect to the welcome page
|
await page.goto("/#/room/!room:id"); // should redirect to the welcome page
|
||||||
await login(page, homeserver);
|
await login(page, homeserver, credentials);
|
||||||
|
|
||||||
await expect(page).toHaveURL(/\/#\/room\/!room:id$/);
|
await expect(page).toHaveURL(/\/#\/room\/!room:id$/);
|
||||||
await expect(page.getByRole("button", { name: "Join the discussion" })).toBeVisible();
|
await expect(page.getByRole("button", { name: "Join the discussion" })).toBeVisible();
|
||||||
|
@ -156,9 +160,10 @@ test.describe("Login", () => {
|
||||||
page,
|
page,
|
||||||
homeserver,
|
homeserver,
|
||||||
request,
|
request,
|
||||||
|
credentials,
|
||||||
}) => {
|
}) => {
|
||||||
const res = await request.post(`${homeserver.baseUrl}/_matrix/client/v3/keys/device_signing/upload`, {
|
const res = await request.post(`${homeserver.baseUrl}/_matrix/client/v3/keys/device_signing/upload`, {
|
||||||
headers: { Authorization: `Bearer ${creds.accessToken}` },
|
headers: { Authorization: `Bearer ${credentials.accessToken}` },
|
||||||
data: DEVICE_SIGNING_KEYS_BODY,
|
data: DEVICE_SIGNING_KEYS_BODY,
|
||||||
});
|
});
|
||||||
if (res.status() / 100 !== 2) {
|
if (res.status() / 100 !== 2) {
|
||||||
|
@ -167,7 +172,7 @@ test.describe("Login", () => {
|
||||||
expect(res.status() / 100).toEqual(2);
|
expect(res.status() / 100).toEqual(2);
|
||||||
|
|
||||||
await page.goto("/");
|
await page.goto("/");
|
||||||
await login(page, homeserver);
|
await login(page, homeserver, credentials);
|
||||||
|
|
||||||
await expect(page.getByRole("heading", { name: "Verify this device", level: 1 })).toBeVisible();
|
await expect(page.getByRole("heading", { name: "Verify this device", level: 1 })).toBeVisible();
|
||||||
|
|
||||||
|
@ -185,10 +190,14 @@ test.describe("Login", () => {
|
||||||
page,
|
page,
|
||||||
homeserver,
|
homeserver,
|
||||||
request,
|
request,
|
||||||
|
credentials,
|
||||||
}) => {
|
}) => {
|
||||||
const res = await request.post(
|
const res = await request.post(
|
||||||
`${homeserver.baseUrl}/_matrix/client/v3/keys/device_signing/upload`,
|
`${homeserver.baseUrl}/_matrix/client/v3/keys/device_signing/upload`,
|
||||||
{ headers: { Authorization: `Bearer ${creds.accessToken}` }, data: DEVICE_SIGNING_KEYS_BODY },
|
{
|
||||||
|
headers: { Authorization: `Bearer ${credentials.accessToken}` },
|
||||||
|
data: DEVICE_SIGNING_KEYS_BODY,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
if (res.status() / 100 !== 2) {
|
if (res.status() / 100 !== 2) {
|
||||||
console.log("Uploading dummy keys failed", await res.json());
|
console.log("Uploading dummy keys failed", await res.json());
|
||||||
|
@ -196,7 +205,7 @@ test.describe("Login", () => {
|
||||||
expect(res.status() / 100).toEqual(2);
|
expect(res.status() / 100).toEqual(2);
|
||||||
|
|
||||||
await page.goto("/");
|
await page.goto("/");
|
||||||
await login(page, homeserver);
|
await login(page, homeserver, credentials);
|
||||||
|
|
||||||
await expect(page.getByRole("heading", { name: "Verify this device", level: 1 })).toBeVisible();
|
await expect(page.getByRole("heading", { name: "Verify this device", level: 1 })).toBeVisible();
|
||||||
|
|
||||||
|
@ -215,11 +224,15 @@ test.describe("Login", () => {
|
||||||
page,
|
page,
|
||||||
homeserver,
|
homeserver,
|
||||||
request,
|
request,
|
||||||
|
credentials,
|
||||||
}) => {
|
}) => {
|
||||||
console.log(`uid ${creds.userId} body`, DEVICE_SIGNING_KEYS_BODY);
|
console.log(`uid ${credentials.userId} body`, DEVICE_SIGNING_KEYS_BODY);
|
||||||
const res = await request.post(
|
const res = await request.post(
|
||||||
`${homeserver.baseUrl}/_matrix/client/v3/keys/device_signing/upload`,
|
`${homeserver.baseUrl}/_matrix/client/v3/keys/device_signing/upload`,
|
||||||
{ headers: { Authorization: `Bearer ${creds.accessToken}` }, data: DEVICE_SIGNING_KEYS_BODY },
|
{
|
||||||
|
headers: { Authorization: `Bearer ${credentials.accessToken}` },
|
||||||
|
data: DEVICE_SIGNING_KEYS_BODY,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
if (res.status() / 100 !== 2) {
|
if (res.status() / 100 !== 2) {
|
||||||
console.log("Uploading dummy keys failed", await res.json());
|
console.log("Uploading dummy keys failed", await res.json());
|
||||||
|
@ -227,9 +240,9 @@ test.describe("Login", () => {
|
||||||
expect(res.status() / 100).toEqual(2);
|
expect(res.status() / 100).toEqual(2);
|
||||||
|
|
||||||
await page.goto("/");
|
await page.goto("/");
|
||||||
await login(page, homeserver);
|
await login(page, homeserver, credentials);
|
||||||
|
|
||||||
const h1 = await page.getByRole("heading", { name: "Verify this device", level: 1 });
|
const h1 = page.getByRole("heading", { name: "Verify this device", level: 1 });
|
||||||
await expect(h1).toBeVisible();
|
await expect(h1).toBeVisible();
|
||||||
|
|
||||||
await expect(h1.locator(".mx_CompleteSecurity_skip")).toHaveCount(0);
|
await expect(h1.locator(".mx_CompleteSecurity_skip")).toHaveCount(0);
|
||||||
|
@ -238,25 +251,7 @@ test.describe("Login", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// tests for old-style SSO login, in which we exchange tokens with Synapse, and Synapse talks to an auth server
|
|
||||||
test.describe("SSO login", () => {
|
|
||||||
test.skip(isDendrite, "does not yet support SSO");
|
|
||||||
test.use(legacyOAuthHomeserver);
|
|
||||||
|
|
||||||
test("logs in with SSO and lands on the home screen", async ({ page, homeserver }) => {
|
|
||||||
// If this test fails with a screen showing "Timeout connecting to remote server", it is most likely due to
|
|
||||||
// your firewall settings: Synapse is unable to reach the OIDC server.
|
|
||||||
//
|
|
||||||
// If you are using ufw, try something like:
|
|
||||||
// sudo ufw allow in on docker0
|
|
||||||
//
|
|
||||||
await doTokenRegistration(page, homeserver);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("logout", () => {
|
test.describe("logout", () => {
|
||||||
test.use(consentHomeserver);
|
|
||||||
|
|
||||||
test("should go to login page on logout", async ({ page, user }) => {
|
test("should go to login page on logout", async ({ page, user }) => {
|
||||||
await page.getByRole("button", { name: "User menu" }).click();
|
await page.getByRole("button", { name: "User menu" }).click();
|
||||||
await expect(page.getByText(user.displayName, { exact: true })).toBeVisible();
|
await expect(page.getByText(user.displayName, { exact: true })).toBeVisible();
|
||||||
|
@ -268,29 +263,4 @@ test.describe("Login", () => {
|
||||||
await expect(page).toHaveURL(/\/#\/login$/);
|
await expect(page).toHaveURL(/\/#\/login$/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe("logout with logout_redirect_url", () => {
|
|
||||||
test.use(consentHomeserver);
|
|
||||||
test.use({
|
|
||||||
config: {
|
|
||||||
// We redirect to decoder-ring because it's a predictable page that isn't Element itself.
|
|
||||||
// We could use example.org, matrix.org, or something else, however this puts dependency of external
|
|
||||||
// infrastructure on our tests. In the same vein, we don't really want to figure out how to ship a
|
|
||||||
// `test-landing.html` page when running with an uncontrolled Element (via `yarn start`).
|
|
||||||
// Using the decoder-ring is just as fine, and we can search for strategic names.
|
|
||||||
logout_redirect_url: "/decoder-ring/",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should respect logout_redirect_url", async ({ page, user }) => {
|
|
||||||
await page.getByRole("button", { name: "User menu" }).click();
|
|
||||||
await expect(page.getByText(user.displayName, { exact: true })).toBeVisible();
|
|
||||||
|
|
||||||
// give a change for the outstanding requests queue to settle before logging out
|
|
||||||
await page.waitForTimeout(2000);
|
|
||||||
|
|
||||||
await page.locator(".mx_UserMenu_contextMenu").getByRole("menuitem", { name: "Sign out" }).click();
|
|
||||||
await expect(page).toHaveURL(/\/decoder-ring\/$/);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024 New Vector Ltd.
|
||||||
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { test } from "../../element-web-test";
|
||||||
|
import { doTokenRegistration } from "./utils";
|
||||||
|
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
||||||
|
import { legacyOAuthHomeserver } from "../../plugins/homeserver/synapse/legacyOAuthHomeserver.ts";
|
||||||
|
|
||||||
|
test.use(legacyOAuthHomeserver);
|
||||||
|
|
||||||
|
// tests for old-style SSO login, in which we exchange tokens with Synapse, and Synapse talks to an auth server
|
||||||
|
test.describe("SSO login", () => {
|
||||||
|
test.skip(isDendrite, "does not yet support SSO");
|
||||||
|
|
||||||
|
test("logs in with SSO and lands on the home screen", async ({ page, homeserver }) => {
|
||||||
|
// If this test fails with a screen showing "Timeout connecting to remote server", it is most likely due to
|
||||||
|
// your firewall settings: Synapse is unable to reach the OIDC server.
|
||||||
|
//
|
||||||
|
// If you are using ufw, try something like:
|
||||||
|
// sudo ufw allow in on docker0
|
||||||
|
//
|
||||||
|
await doTokenRegistration(page, homeserver);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024 New Vector Ltd.
|
||||||
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { expect, test } from "../../element-web-test";
|
||||||
|
import { consentHomeserver } from "../../plugins/homeserver/synapse/consentHomeserver.ts";
|
||||||
|
|
||||||
|
test.use(consentHomeserver);
|
||||||
|
test.use({
|
||||||
|
config: {
|
||||||
|
// We redirect to decoder-ring because it's a predictable page that isn't Element itself.
|
||||||
|
// We could use example.org, matrix.org, or something else, however this puts dependency of external
|
||||||
|
// infrastructure on our tests. In the same vein, we don't really want to figure out how to ship a
|
||||||
|
// `test-landing.html` page when running with an uncontrolled Element (via `yarn start`).
|
||||||
|
// Using the decoder-ring is just as fine, and we can search for strategic names.
|
||||||
|
logout_redirect_url: "/decoder-ring/",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("logout with logout_redirect_url", () => {
|
||||||
|
test("should respect logout_redirect_url", async ({ page, user }) => {
|
||||||
|
await page.getByRole("button", { name: "User menu" }).click();
|
||||||
|
await expect(page.getByText(user.displayName, { exact: true })).toBeVisible();
|
||||||
|
|
||||||
|
// give a change for the outstanding requests queue to settle before logging out
|
||||||
|
await page.waitForTimeout(2000);
|
||||||
|
|
||||||
|
await page.locator(".mx_UserMenu_contextMenu").getByRole("menuitem", { name: "Sign out" }).click();
|
||||||
|
await expect(page).toHaveURL(/\/decoder-ring\/$/);
|
||||||
|
});
|
||||||
|
});
|
|
@ -6,124 +6,38 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Page } from "@playwright/test";
|
|
||||||
|
|
||||||
import { test, expect } from "../../element-web-test";
|
import { test, expect } from "../../element-web-test";
|
||||||
import { doTokenRegistration } from "./utils";
|
import { interceptRequestsWithSoftLogout } from "./utils";
|
||||||
import { Credentials } from "../../plugins/homeserver";
|
|
||||||
import { legacyOAuthHomeserver } from "../../plugins/homeserver/synapse/legacyOAuthHomeserver.ts";
|
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
|
||||||
|
|
||||||
test.describe("Soft logout", () => {
|
test.use({
|
||||||
test.use({
|
displayName: "Alice",
|
||||||
displayName: "Alice",
|
config: {
|
||||||
config: {
|
// The only thing that we really *need* (otherwise Element refuses to load) is a default homeserver.
|
||||||
// The only thing that we really *need* (otherwise Element refuses to load) is a default homeserver.
|
// We point that to a guaranteed-invalid domain.
|
||||||
// We point that to a guaranteed-invalid domain.
|
default_server_config: {
|
||||||
default_server_config: {
|
"m.homeserver": {
|
||||||
"m.homeserver": {
|
base_url: "https://server.invalid",
|
||||||
base_url: "https://server.invalid",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
|
||||||
test.describe("with password user", () => {
|
|
||||||
test("shows the soft-logout page when a request fails, and allows a re-login", async ({ page, user }) => {
|
|
||||||
await interceptRequestsWithSoftLogout(page, user);
|
|
||||||
await expect(page.getByText("You're signed out")).toBeVisible();
|
|
||||||
await page.getByPlaceholder("Password").fill(user.password);
|
|
||||||
await page.getByPlaceholder("Password").press("Enter");
|
|
||||||
|
|
||||||
// back to the welcome page
|
|
||||||
await expect(page).toHaveURL(/\/#\/home/);
|
|
||||||
await expect(
|
|
||||||
page.getByRole("heading", { name: "Now, let's help you get started", exact: true }),
|
|
||||||
).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("still shows the soft-logout page when the page is reloaded after a soft-logout", async ({
|
|
||||||
page,
|
|
||||||
user,
|
|
||||||
}) => {
|
|
||||||
await interceptRequestsWithSoftLogout(page, user);
|
|
||||||
await expect(page.getByText("You're signed out")).toBeVisible();
|
|
||||||
await page.reload();
|
|
||||||
await expect(page.getByText("You're signed out")).toBeVisible();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("with SSO user", () => {
|
|
||||||
test.skip(isDendrite, "does not yet support SSO");
|
|
||||||
test.use(legacyOAuthHomeserver);
|
|
||||||
test.use({
|
|
||||||
user: async ({ page, homeserver }, use) => {
|
|
||||||
const user = await doTokenRegistration(page, homeserver);
|
|
||||||
|
|
||||||
// Eventually, we should end up at the home screen.
|
|
||||||
await expect(page).toHaveURL(/\/#\/home$/);
|
|
||||||
await expect(page.getByRole("heading", { name: "Welcome Alice", exact: true })).toBeVisible();
|
|
||||||
|
|
||||||
await use(user);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
test("shows the soft-logout page when a request fails, and allows a re-login", async ({ page, user }) => {
|
|
||||||
await expect(page.getByRole("heading", { name: "Welcome Alice", exact: true })).toBeVisible();
|
|
||||||
|
|
||||||
await interceptRequestsWithSoftLogout(page, user);
|
|
||||||
|
|
||||||
await expect(page.getByText("You're signed out")).toBeVisible();
|
|
||||||
await page.getByRole("button", { name: "Continue with OAuth test" }).click();
|
|
||||||
|
|
||||||
// click the submit button
|
|
||||||
await page.getByRole("button", { name: "Submit" }).click();
|
|
||||||
|
|
||||||
// Synapse prompts us to grant permission to Element
|
|
||||||
await expect(page.getByRole("heading", { name: "Continue to your account" })).toBeVisible();
|
|
||||||
await page.getByRole("link", { name: "Continue" }).click();
|
|
||||||
|
|
||||||
// back to the welcome page
|
|
||||||
await expect(page).toHaveURL(/\/#\/home$/);
|
|
||||||
await expect(page.getByRole("heading", { name: "Welcome Alice", exact: true })).toBeVisible();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
test.describe("Soft logout with password user", () => {
|
||||||
* Intercept calls to /sync and have them fail with a soft-logout
|
test("shows the soft-logout page when a request fails, and allows a re-login", async ({ page, user }) => {
|
||||||
*
|
await interceptRequestsWithSoftLogout(page, user);
|
||||||
* Any further requests to /sync with the same access token are blocked.
|
await expect(page.getByText("You're signed out")).toBeVisible();
|
||||||
*/
|
await page.getByPlaceholder("Password").fill(user.password);
|
||||||
async function interceptRequestsWithSoftLogout(page: Page, user: Credentials): Promise<void> {
|
await page.getByPlaceholder("Password").press("Enter");
|
||||||
await page.route("**/_matrix/client/*/sync*", async (route, req) => {
|
|
||||||
const accessToken = await req.headerValue("Authorization");
|
|
||||||
|
|
||||||
// now, if the access token on this request matches the expired one, block it
|
// back to the welcome page
|
||||||
if (accessToken === `Bearer ${user.accessToken}`) {
|
await expect(page).toHaveURL(/\/#\/home/);
|
||||||
console.log("Intercepting request with soft-logged-out access token");
|
await expect(page.getByRole("heading", { name: "Now, let's help you get started", exact: true })).toBeVisible();
|
||||||
await route.fulfill({
|
|
||||||
status: 401,
|
|
||||||
json: {
|
|
||||||
errcode: "M_UNKNOWN_TOKEN",
|
|
||||||
error: "Soft logout",
|
|
||||||
soft_logout: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise, pass through as normal
|
|
||||||
await route.continue();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const promise = page.waitForResponse((resp) => resp.url().includes("/sync") && resp.status() === 401);
|
test("still shows the soft-logout page when the page is reloaded after a soft-logout", async ({ page, user }) => {
|
||||||
|
await interceptRequestsWithSoftLogout(page, user);
|
||||||
// do something to make the active /sync return: create a new room
|
await expect(page.getByText("You're signed out")).toBeVisible();
|
||||||
await page.evaluate(() => {
|
await page.reload();
|
||||||
// don't wait for this to complete: it probably won't, because of the broken sync
|
await expect(page.getByText("You're signed out")).toBeVisible();
|
||||||
window.mxMatrixClientPeg.get().createRoom({});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
await promise;
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024 New Vector Ltd.
|
||||||
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { test, expect } from "../../element-web-test";
|
||||||
|
import { doTokenRegistration, interceptRequestsWithSoftLogout } from "./utils";
|
||||||
|
import { legacyOAuthHomeserver } from "../../plugins/homeserver/synapse/legacyOAuthHomeserver.ts";
|
||||||
|
|
||||||
|
test.use({
|
||||||
|
displayName: "Alice",
|
||||||
|
config: {
|
||||||
|
// The only thing that we really *need* (otherwise Element refuses to load) is a default homeserver.
|
||||||
|
// We point that to a guaranteed-invalid domain.
|
||||||
|
default_server_config: {
|
||||||
|
"m.homeserver": {
|
||||||
|
base_url: "https://server.invalid",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test.use(legacyOAuthHomeserver);
|
||||||
|
test.describe("Soft logout with SSO user", () => {
|
||||||
|
test.use({
|
||||||
|
user: async ({ page, homeserver }, use) => {
|
||||||
|
const user = await doTokenRegistration(page, homeserver);
|
||||||
|
|
||||||
|
// Eventually, we should end up at the home screen.
|
||||||
|
await expect(page).toHaveURL(/\/#\/home$/);
|
||||||
|
await expect(page.getByRole("heading", { name: "Welcome Alice", exact: true })).toBeVisible();
|
||||||
|
|
||||||
|
await use(user);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test("shows the soft-logout page when a request fails, and allows a re-login", async ({ page, user }) => {
|
||||||
|
await expect(page.getByRole("heading", { name: "Welcome Alice", exact: true })).toBeVisible();
|
||||||
|
|
||||||
|
await interceptRequestsWithSoftLogout(page, user);
|
||||||
|
|
||||||
|
await expect(page.getByText("You're signed out")).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Continue with OAuth test" }).click();
|
||||||
|
|
||||||
|
// click the submit button
|
||||||
|
await page.getByRole("button", { name: "Submit" }).click();
|
||||||
|
|
||||||
|
// Synapse prompts us to grant permission to Element
|
||||||
|
await expect(page.getByRole("heading", { name: "Continue to your account" })).toBeVisible();
|
||||||
|
await page.getByRole("link", { name: "Continue" }).click();
|
||||||
|
|
||||||
|
// back to the welcome page
|
||||||
|
await expect(page).toHaveURL(/\/#\/home$/);
|
||||||
|
await expect(page.getByRole("heading", { name: "Welcome Alice", exact: true })).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
|
@ -20,7 +20,7 @@ export async function doTokenRegistration(
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Edit" }).click();
|
await page.getByRole("button", { name: "Edit" }).click();
|
||||||
await page.getByRole("textbox", { name: "Other homeserver" }).fill(homeserver.baseUrl);
|
await page.getByRole("textbox", { name: "Other homeserver" }).fill(homeserver.baseUrl);
|
||||||
await page.getByRole("button", { name: "Continue" }).click();
|
await page.getByRole("button", { name: "Continue", exact: true }).click();
|
||||||
// wait for the dialog to go away
|
// wait for the dialog to go away
|
||||||
await expect(page.locator(".mx_ServerPickerDialog")).toHaveCount(0);
|
await expect(page.locator(".mx_ServerPickerDialog")).toHaveCount(0);
|
||||||
|
|
||||||
|
@ -56,5 +56,44 @@ export async function doTokenRegistration(
|
||||||
homeServer: window.mxMatrixClientPeg.get().getHomeserverUrl(),
|
homeServer: window.mxMatrixClientPeg.get().getHomeserverUrl(),
|
||||||
password: null,
|
password: null,
|
||||||
displayName: "Alice",
|
displayName: "Alice",
|
||||||
|
username: window.mxMatrixClientPeg.get().getUserIdLocalpart(),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intercept calls to /sync and have them fail with a soft-logout
|
||||||
|
*
|
||||||
|
* Any further requests to /sync with the same access token are blocked.
|
||||||
|
*/
|
||||||
|
export async function interceptRequestsWithSoftLogout(page: Page, user: Credentials): Promise<void> {
|
||||||
|
await page.route("**/_matrix/client/*/sync*", async (route, req) => {
|
||||||
|
const accessToken = await req.headerValue("Authorization");
|
||||||
|
|
||||||
|
// now, if the access token on this request matches the expired one, block it
|
||||||
|
if (accessToken === `Bearer ${user.accessToken}`) {
|
||||||
|
console.log("Intercepting request with soft-logged-out access token");
|
||||||
|
await route.fulfill({
|
||||||
|
status: 401,
|
||||||
|
json: {
|
||||||
|
errcode: "M_UNKNOWN_TOKEN",
|
||||||
|
error: "Soft logout",
|
||||||
|
soft_logout: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, pass through as normal
|
||||||
|
await route.continue();
|
||||||
|
});
|
||||||
|
|
||||||
|
const promise = page.waitForResponse((resp) => resp.url().includes("/sync") && resp.status() === 401);
|
||||||
|
|
||||||
|
// do something to make the active /sync return: create a new room
|
||||||
|
await page.evaluate(() => {
|
||||||
|
// don't wait for this to complete: it probably won't, because of the broken sync
|
||||||
|
window.mxMatrixClientPeg.get().createRoom({});
|
||||||
|
});
|
||||||
|
|
||||||
|
await promise;
|
||||||
|
}
|
||||||
|
|
|
@ -11,8 +11,8 @@ import { registerAccountMas } from ".";
|
||||||
import { ElementAppPage } from "../../pages/ElementAppPage.ts";
|
import { ElementAppPage } from "../../pages/ElementAppPage.ts";
|
||||||
import { masHomeserver } from "../../plugins/homeserver/synapse/masHomeserver.ts";
|
import { masHomeserver } from "../../plugins/homeserver/synapse/masHomeserver.ts";
|
||||||
|
|
||||||
|
test.use(masHomeserver);
|
||||||
test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||||
test.use(masHomeserver);
|
|
||||||
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 ({ context, page, homeserver, mailhogClient, mas }) => {
|
test("can register the oauth2 client and an account", async ({ context, page, homeserver, mailhogClient, mas }) => {
|
||||||
|
|
|
@ -10,21 +10,22 @@ import { test, expect } from "../../element-web-test";
|
||||||
import { emailHomeserver } from "../../plugins/homeserver/synapse/emailHomeserver.ts";
|
import { emailHomeserver } from "../../plugins/homeserver/synapse/emailHomeserver.ts";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
||||||
|
|
||||||
|
test.use(emailHomeserver);
|
||||||
|
test.use({
|
||||||
|
config: ({ config }, use) =>
|
||||||
|
use({
|
||||||
|
...config,
|
||||||
|
default_server_config: {
|
||||||
|
...config.default_server_config,
|
||||||
|
"m.identity_server": {
|
||||||
|
base_url: "https://server.invalid",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
test.describe("Email Registration", async () => {
|
test.describe("Email Registration", async () => {
|
||||||
test.skip(isDendrite, "not yet wired up");
|
test.skip(isDendrite, "not yet wired up");
|
||||||
test.use(emailHomeserver);
|
|
||||||
test.use({
|
|
||||||
config: ({ config }, use) =>
|
|
||||||
use({
|
|
||||||
...config,
|
|
||||||
default_server_config: {
|
|
||||||
...config.default_server_config,
|
|
||||||
"m.identity_server": {
|
|
||||||
base_url: "https://server.invalid",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
test.beforeEach(async ({ homeserver, page }) => {
|
test.beforeEach(async ({ homeserver, page }) => {
|
||||||
await page.goto("/#/register");
|
await page.goto("/#/register");
|
||||||
|
|
|
@ -10,20 +10,21 @@ 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";
|
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
||||||
|
|
||||||
test.describe("Registration", () => {
|
test.use(consentHomeserver);
|
||||||
test.skip(isDendrite, "Dendrite lacks support for MSC3967 so requires additional auth here");
|
test.use({
|
||||||
test.use(consentHomeserver);
|
config: {
|
||||||
test.use({
|
// The only thing that we really *need* (otherwise Element refuses to load) is a default homeserver.
|
||||||
config: {
|
// We point that to a guaranteed-invalid domain.
|
||||||
// The only thing that we really *need* (otherwise Element refuses to load) is a default homeserver.
|
default_server_config: {
|
||||||
// We point that to a guaranteed-invalid domain.
|
"m.homeserver": {
|
||||||
default_server_config: {
|
base_url: "https://server.invalid",
|
||||||
"m.homeserver": {
|
|
||||||
base_url: "https://server.invalid",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
|
@ -376,37 +376,42 @@ test.describe("Sliding Sync", () => {
|
||||||
roomIds.push(id);
|
roomIds.push(id);
|
||||||
await expect(page.getByRole("treeitem", { name: fruit })).toBeVisible();
|
await expect(page.getByRole("treeitem", { name: fruit })).toBeVisible();
|
||||||
}
|
}
|
||||||
const [roomAId, roomPId] = roomIds;
|
const [roomAId, roomPId, roomOId] = roomIds;
|
||||||
|
|
||||||
const assertUnsubExists = (request: Request, subRoomId: string, unsubRoomId: string) => {
|
const matchRoomSubRequest = (subRoomId: string) => (request: Request) => {
|
||||||
|
if (!request.url().includes("/sync")) return false;
|
||||||
const body = request.postDataJSON();
|
const body = request.postDataJSON();
|
||||||
// There may be a request without a txn_id, ignore it, as there won't be any subscription changes
|
return body.txn_id && body.room_subscriptions?.[subRoomId];
|
||||||
if (body.txn_id === undefined) {
|
};
|
||||||
return;
|
const matchRoomUnsubRequest = (unsubRoomId: string) => (request: Request) => {
|
||||||
}
|
if (!request.url().includes("/sync")) return false;
|
||||||
expect(body.unsubscribe_rooms).toEqual([unsubRoomId]);
|
const body = request.postDataJSON();
|
||||||
expect(body.room_subscriptions).not.toHaveProperty(unsubRoomId);
|
return (
|
||||||
expect(body.room_subscriptions).toHaveProperty(subRoomId);
|
body.txn_id && body.unsubscribe_rooms?.includes(unsubRoomId) && !body.room_subscriptions?.[unsubRoomId]
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
let promise = page.waitForRequest(/sync/);
|
// Select the Test Room and wait for playwright to get the request
|
||||||
|
const [request] = await Promise.all([
|
||||||
// Select the Test Room
|
page.waitForRequest(matchRoomSubRequest(roomAId)),
|
||||||
await page.getByRole("treeitem", { name: "Apple", exact: true }).click();
|
page.getByRole("treeitem", { name: "Apple", exact: true }).click(),
|
||||||
|
]);
|
||||||
// and wait for playwright to get the request
|
const roomSubscriptions = request.postDataJSON().room_subscriptions;
|
||||||
const roomSubscriptions = (await promise).postDataJSON().room_subscriptions;
|
|
||||||
expect(roomSubscriptions, "room_subscriptions is object").toBeDefined();
|
expect(roomSubscriptions, "room_subscriptions is object").toBeDefined();
|
||||||
|
|
||||||
// Switch to another room
|
// Switch to another room and wait for playwright to get the request
|
||||||
promise = page.waitForRequest(/sync/);
|
await Promise.all([
|
||||||
await page.getByRole("treeitem", { name: "Pineapple", exact: true }).click();
|
page.waitForRequest(matchRoomSubRequest(roomPId)),
|
||||||
assertUnsubExists(await promise, roomPId, roomAId);
|
page.waitForRequest(matchRoomUnsubRequest(roomAId)),
|
||||||
|
page.getByRole("treeitem", { name: "Pineapple", exact: true }).click(),
|
||||||
|
]);
|
||||||
|
|
||||||
// And switch to even another room
|
// And switch to even another room and wait for playwright to get the request
|
||||||
promise = page.waitForRequest(/sync/);
|
await Promise.all([
|
||||||
await page.getByRole("treeitem", { name: "Apple", exact: true }).click();
|
page.waitForRequest(matchRoomSubRequest(roomOId)),
|
||||||
assertUnsubExists(await promise, roomPId, roomAId);
|
page.waitForRequest(matchRoomUnsubRequest(roomPId)),
|
||||||
|
page.getByRole("treeitem", { name: "Orange", exact: true }).click(),
|
||||||
|
]);
|
||||||
|
|
||||||
// TODO: Add tests for encrypted rooms
|
// TODO: Add tests for encrypted rooms
|
||||||
});
|
});
|
||||||
|
|
|
@ -334,17 +334,17 @@ export class Helpers {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populate the rooms with messages and threads
|
* Populate the rooms with messages and threads
|
||||||
* @param user the user sending the messages
|
|
||||||
* @param room1
|
* @param room1
|
||||||
* @param room2
|
* @param room2
|
||||||
* @param msg - MessageBuilder
|
* @param msg - MessageBuilder
|
||||||
|
* @param user - the user to mention in the first message
|
||||||
* @param hasMention - whether to include a mention in the first message
|
* @param hasMention - whether to include a mention in the first message
|
||||||
*/
|
*/
|
||||||
async populateThreads(
|
async populateThreads(
|
||||||
user: Credentials,
|
|
||||||
room1: { name: string; roomId: string },
|
room1: { name: string; roomId: string },
|
||||||
room2: { name: string; roomId: string },
|
room2: { name: string; roomId: string },
|
||||||
msg: MessageBuilder,
|
msg: MessageBuilder,
|
||||||
|
user: Credentials,
|
||||||
hasMention = true,
|
hasMention = true,
|
||||||
) {
|
) {
|
||||||
if (hasMention) {
|
if (hasMention) {
|
||||||
|
|
|
@ -15,6 +15,7 @@ test.describe("Threads Activity Centre", { tag: "@no-firefox" }, () => {
|
||||||
isDendrite,
|
isDendrite,
|
||||||
"due to Dendrite lacking full threads support https://github.com/element-hq/dendrite/issues/3283",
|
"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" },
|
||||||
|
@ -79,7 +80,7 @@ test.describe("Threads Activity Centre", { tag: "@no-firefox" }, () => {
|
||||||
{ tag: "@screenshot" },
|
{ tag: "@screenshot" },
|
||||||
async ({ room1, room2, util, msg, user }) => {
|
async ({ room1, room2, util, msg, user }) => {
|
||||||
await util.goTo(room2);
|
await util.goTo(room2);
|
||||||
await util.populateThreads(user, room1, room2, msg);
|
await util.populateThreads(room1, room2, msg, user);
|
||||||
// The indicator should be shown
|
// The indicator should be shown
|
||||||
await util.assertHighlightIndicator();
|
await util.assertHighlightIndicator();
|
||||||
|
|
||||||
|
@ -97,7 +98,7 @@ test.describe("Threads Activity Centre", { tag: "@no-firefox" }, () => {
|
||||||
|
|
||||||
test("should update with a thread is read", { tag: "@screenshot" }, async ({ room1, room2, util, msg, user }) => {
|
test("should update with a thread is read", { tag: "@screenshot" }, async ({ room1, room2, util, msg, user }) => {
|
||||||
await util.goTo(room2);
|
await util.goTo(room2);
|
||||||
await util.populateThreads(user, room1, room2, msg);
|
await util.populateThreads(room1, room2, msg, user);
|
||||||
|
|
||||||
// Click on the first room in TAC
|
// Click on the first room in TAC
|
||||||
await util.openTac();
|
await util.openTac();
|
||||||
|
@ -120,7 +121,7 @@ test.describe("Threads Activity Centre", { tag: "@no-firefox" }, () => {
|
||||||
|
|
||||||
test("should order by recency after notification level", async ({ room1, room2, util, msg, user }) => {
|
test("should order by recency after notification level", async ({ room1, room2, util, msg, user }) => {
|
||||||
await util.goTo(room2);
|
await util.goTo(room2);
|
||||||
await util.populateThreads(user, room1, room2, msg, false);
|
await util.populateThreads(room1, room2, msg, user, false);
|
||||||
|
|
||||||
await util.openTac();
|
await util.openTac();
|
||||||
await util.assertRoomsInTac([
|
await util.assertRoomsInTac([
|
||||||
|
|
|
@ -6,7 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { expect as baseExpect, Locator, Page, ExpectMatcherState, ElementHandle } from "@playwright/test";
|
import {
|
||||||
|
expect as baseExpect,
|
||||||
|
Locator,
|
||||||
|
Page,
|
||||||
|
ExpectMatcherState,
|
||||||
|
ElementHandle,
|
||||||
|
PlaywrightTestArgs,
|
||||||
|
Fixtures as _Fixtures,
|
||||||
|
} from "@playwright/test";
|
||||||
import { sanitizeForFilePath } from "playwright-core/lib/utils";
|
import { sanitizeForFilePath } from "playwright-core/lib/utils";
|
||||||
import AxeBuilder from "@axe-core/playwright";
|
import AxeBuilder from "@axe-core/playwright";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
@ -19,7 +27,7 @@ import { Crypto } from "./pages/crypto";
|
||||||
import { Toasts } from "./pages/toasts";
|
import { Toasts } from "./pages/toasts";
|
||||||
import { Bot, CreateBotOpts } from "./pages/bot";
|
import { Bot, CreateBotOpts } from "./pages/bot";
|
||||||
import { Webserver } from "./plugins/webserver";
|
import { Webserver } from "./plugins/webserver";
|
||||||
import { test as base } from "./services.ts";
|
import { Options, Services, test as base } from "./services.ts";
|
||||||
|
|
||||||
// Enable experimental service worker support
|
// Enable experimental service worker support
|
||||||
// See https://playwright.dev/docs/service-workers-experimental#how-to-enable
|
// See https://playwright.dev/docs/service-workers-experimental#how-to-enable
|
||||||
|
@ -45,7 +53,7 @@ interface CredentialsWithDisplayName extends Credentials {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Fixtures {
|
export interface TestFixtures {
|
||||||
axe: AxeBuilder;
|
axe: AxeBuilder;
|
||||||
checkA11y: () => Promise<void>;
|
checkA11y: () => Promise<void>;
|
||||||
|
|
||||||
|
@ -101,7 +109,10 @@ export interface Fixtures {
|
||||||
webserver: Webserver;
|
webserver: Webserver;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const test = base.extend<Fixtures>({
|
type CombinedTestFixtures = PlaywrightTestArgs & TestFixtures;
|
||||||
|
export type Fixtures = _Fixtures<CombinedTestFixtures, Services & Options, CombinedTestFixtures>;
|
||||||
|
|
||||||
|
export const test = base.extend<TestFixtures>({
|
||||||
context: async ({ context }, use, testInfo) => {
|
context: async ({ context }, use, testInfo) => {
|
||||||
// We skip tests instead of using grep-invert to still surface the counts in the html report
|
// We skip tests instead of using grep-invert to still surface the counts in the html report
|
||||||
test.skip(
|
test.skip(
|
||||||
|
@ -137,12 +148,12 @@ export const test = base.extend<Fixtures>({
|
||||||
},
|
},
|
||||||
|
|
||||||
displayName: undefined,
|
displayName: undefined,
|
||||||
credentials: async ({ homeserver, displayName: testDisplayName }, use) => {
|
credentials: async ({ context, homeserver, displayName: testDisplayName }, use, testInfo) => {
|
||||||
const names = ["Alice", "Bob", "Charlie", "Daniel", "Eve", "Frank", "Grace", "Hannah", "Isaac", "Judy"];
|
const names = ["Alice", "Bob", "Charlie", "Daniel", "Eve", "Frank", "Grace", "Hannah", "Isaac", "Judy"];
|
||||||
const password = _.uniqueId("password_");
|
const password = _.uniqueId("password_");
|
||||||
const displayName = testDisplayName ?? _.sample(names)!;
|
const displayName = testDisplayName ?? _.sample(names)!;
|
||||||
|
|
||||||
const credentials = await homeserver.registerUser("user", password, displayName);
|
const credentials = await homeserver.registerUser(`user_${testInfo.testId}`, password, displayName);
|
||||||
console.log(`Registered test user ${credentials.userId} with displayname ${displayName}`);
|
console.log(`Registered test user ${credentials.userId} with displayname ${displayName}`);
|
||||||
|
|
||||||
await use({
|
await use({
|
||||||
|
@ -167,6 +178,7 @@ export const test = base.extend<Fixtures>({
|
||||||
window.localStorage.setItem(
|
window.localStorage.setItem(
|
||||||
"mx_local_settings",
|
"mx_local_settings",
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
// Retain any other settings which may have already been set
|
||||||
...JSON.parse(window.localStorage.getItem("mx_local_settings") || "{}"),
|
...JSON.parse(window.localStorage.getItem("mx_local_settings") || "{}"),
|
||||||
// Ensure the language is set to a consistent value
|
// Ensure the language is set to a consistent value
|
||||||
language: "en",
|
language: "en",
|
||||||
|
|
|
@ -41,6 +41,7 @@ export interface Credentials {
|
||||||
homeServer: string;
|
homeServer: string;
|
||||||
password: string | null; // null for password-less users
|
password: string | null; // null for password-less users
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
|
username: string; // the localpart of the userId
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HomeserverType = "synapse" | "dendrite" | "pinecone";
|
export type HomeserverType = "synapse" | "dendrite" | "pinecone";
|
||||||
|
|
|
@ -6,53 +6,57 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Fixtures } from "@playwright/test";
|
import { Fixtures } from "../../../element-web-test.ts";
|
||||||
|
|
||||||
import { Options, Services } from "../../../services.ts";
|
export const consentHomeserver: Fixtures = {
|
||||||
|
_homeserver: [
|
||||||
export const consentHomeserver: Fixtures<Services & Options, {}, Services & Options> = {
|
async ({ _homeserver: container, mailhog }, use) => {
|
||||||
_homeserver: async ({ homeserverType, _homeserver: container, mailhog }, use, testInfo) => {
|
container
|
||||||
testInfo.skip(homeserverType !== "synapse", "does not yet support MAS");
|
.withCopyDirectoriesToContainer([
|
||||||
|
{ source: "playwright/plugins/homeserver/synapse/res", target: "/data/res" },
|
||||||
container
|
])
|
||||||
.withCopyDirectoriesToContainer([
|
.withConfig({
|
||||||
{ source: "playwright/plugins/homeserver/synapse/res", target: "/data/res" },
|
email: {
|
||||||
])
|
enable_notifs: false,
|
||||||
.withConfig({
|
smtp_host: "mailhog",
|
||||||
email: {
|
smtp_port: 1025,
|
||||||
enable_notifs: false,
|
smtp_user: "username",
|
||||||
smtp_host: "mailhog",
|
smtp_pass: "password",
|
||||||
smtp_port: 1025,
|
require_transport_security: false,
|
||||||
smtp_user: "username",
|
notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>",
|
||||||
smtp_pass: "password",
|
app_name: "Matrix",
|
||||||
require_transport_security: false,
|
notif_template_html: "notif_mail.html",
|
||||||
notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>",
|
notif_template_text: "notif_mail.txt",
|
||||||
app_name: "Matrix",
|
notif_for_new_users: true,
|
||||||
notif_template_html: "notif_mail.html",
|
client_base_url: "http://localhost/element",
|
||||||
notif_template_text: "notif_mail.txt",
|
|
||||||
notif_for_new_users: true,
|
|
||||||
client_base_url: "http://localhost/element",
|
|
||||||
},
|
|
||||||
user_consent: {
|
|
||||||
template_dir: "/data/res/templates/privacy",
|
|
||||||
version: "1.0",
|
|
||||||
server_notice_content: {
|
|
||||||
msgtype: "m.text",
|
|
||||||
body: "To continue using this homeserver you must review and agree to the terms and conditions at %(consent_uri)s",
|
|
||||||
},
|
},
|
||||||
send_server_notice_to_guests: true,
|
user_consent: {
|
||||||
block_events_error:
|
template_dir: "/data/res/templates/privacy",
|
||||||
"To continue using this homeserver you must review and agree to the terms and conditions at %(consent_uri)s",
|
version: "1.0",
|
||||||
require_at_registration: true,
|
server_notice_content: {
|
||||||
},
|
msgtype: "m.text",
|
||||||
server_notices: {
|
body: "To continue using this homeserver you must review and agree to the terms and conditions at %(consent_uri)s",
|
||||||
system_mxid_localpart: "notices",
|
},
|
||||||
system_mxid_display_name: "Server Notices",
|
send_server_notice_to_guests: true,
|
||||||
system_mxid_avatar_url: "mxc://localhost/oumMVlgDnLYFaPVkExemNVVZ",
|
block_events_error:
|
||||||
room_name: "Server Notices",
|
"To continue using this homeserver you must review and agree to the terms and conditions at %(consent_uri)s",
|
||||||
},
|
require_at_registration: true,
|
||||||
})
|
},
|
||||||
.withConfigField("listeners[0].resources[0].names", ["client", "consent"]);
|
server_notices: {
|
||||||
await use(container);
|
system_mxid_localpart: "notices",
|
||||||
|
system_mxid_display_name: "Server Notices",
|
||||||
|
system_mxid_avatar_url: "mxc://localhost/oumMVlgDnLYFaPVkExemNVVZ",
|
||||||
|
room_name: "Server Notices",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.withConfigField("listeners[0].resources[0].names", ["client", "consent"]);
|
||||||
|
await use(container);
|
||||||
|
},
|
||||||
|
{ scope: "worker" },
|
||||||
|
],
|
||||||
|
|
||||||
|
context: async ({ homeserverType, context }, use, testInfo) => {
|
||||||
|
testInfo.skip(homeserverType !== "synapse", "does not yet support MAS");
|
||||||
|
await use(context);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,24 +6,29 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Fixtures } from "@playwright/test";
|
import { Fixtures } from "../../../element-web-test.ts";
|
||||||
|
|
||||||
import { Options, Services } from "../../../services.ts";
|
export const emailHomeserver: Fixtures = {
|
||||||
|
_homeserver: [
|
||||||
|
async ({ _homeserver: container, mailhog }, use) => {
|
||||||
|
container.withConfig({
|
||||||
|
enable_registration_without_verification: undefined,
|
||||||
|
disable_msisdn_registration: undefined,
|
||||||
|
registrations_require_3pid: ["email"],
|
||||||
|
email: {
|
||||||
|
smtp_host: "mailhog",
|
||||||
|
smtp_port: 1025,
|
||||||
|
notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>",
|
||||||
|
app_name: "my_branded_matrix_server",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await use(container);
|
||||||
|
},
|
||||||
|
{ scope: "worker" },
|
||||||
|
],
|
||||||
|
|
||||||
export const emailHomeserver: Fixtures<Services & Options, {}, Services & Options> = {
|
context: async ({ homeserverType, context }, use, testInfo) => {
|
||||||
_homeserver: async ({ homeserverType, _homeserver: container, mailhog }, use, testInfo) => {
|
|
||||||
testInfo.skip(homeserverType !== "synapse", "does not yet support MAS");
|
testInfo.skip(homeserverType !== "synapse", "does not yet support MAS");
|
||||||
container.withConfig({
|
await use(context);
|
||||||
enable_registration_without_verification: undefined,
|
|
||||||
disable_msisdn_registration: undefined,
|
|
||||||
registrations_require_3pid: ["email"],
|
|
||||||
email: {
|
|
||||||
smtp_host: "mailhog",
|
|
||||||
smtp_port: 1025,
|
|
||||||
notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>",
|
|
||||||
app_name: "my_branded_matrix_server",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await use(container);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,44 +6,50 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Fixtures } from "@playwright/test";
|
|
||||||
import { TestContainers } from "testcontainers";
|
import { TestContainers } from "testcontainers";
|
||||||
|
|
||||||
import { Options, Services } from "../../../services.ts";
|
|
||||||
import { OAuthServer } from "../../oauth_server";
|
import { OAuthServer } from "../../oauth_server";
|
||||||
|
import { Fixtures } from "../../../element-web-test.ts";
|
||||||
|
|
||||||
export const legacyOAuthHomeserver: Fixtures<Services & Options, {}, Services & Options> = {
|
export const legacyOAuthHomeserver: Fixtures = {
|
||||||
_homeserver: async ({ homeserverType, _homeserver: container }, use, testInfo) => {
|
_homeserver: [
|
||||||
testInfo.skip(homeserverType !== "synapse", "does not yet support MAS");
|
async ({ _homeserver: container }, use) => {
|
||||||
const server = new OAuthServer();
|
const server = new OAuthServer();
|
||||||
const port = server.start();
|
const port = server.start();
|
||||||
|
|
||||||
await TestContainers.exposeHostPorts(port);
|
await TestContainers.exposeHostPorts(port);
|
||||||
container.withConfig({
|
container.withConfig({
|
||||||
oidc_providers: [
|
oidc_providers: [
|
||||||
{
|
{
|
||||||
idp_id: "test",
|
idp_id: "test",
|
||||||
idp_name: "OAuth test",
|
idp_name: "OAuth test",
|
||||||
issuer: `http://localhost:${port}/oauth`,
|
issuer: `http://localhost:${port}/oauth`,
|
||||||
authorization_endpoint: `http://localhost:${port}/oauth/auth.html`,
|
authorization_endpoint: `http://localhost:${port}/oauth/auth.html`,
|
||||||
// the token endpoint receives requests from synapse,
|
// the token endpoint receives requests from synapse,
|
||||||
// rather than the webapp, so needs to escape the docker container.
|
// rather than the webapp, so needs to escape the docker container.
|
||||||
token_endpoint: `http://host.testcontainers.internal:${port}/oauth/token`,
|
token_endpoint: `http://host.testcontainers.internal:${port}/oauth/token`,
|
||||||
userinfo_endpoint: `http://host.testcontainers.internal:${port}/oauth/userinfo`,
|
userinfo_endpoint: `http://host.testcontainers.internal:${port}/oauth/userinfo`,
|
||||||
client_id: "synapse",
|
client_id: "synapse",
|
||||||
discover: false,
|
discover: false,
|
||||||
scopes: ["profile"],
|
scopes: ["profile"],
|
||||||
skip_verification: true,
|
skip_verification: true,
|
||||||
client_auth_method: "none",
|
client_auth_method: "none",
|
||||||
user_mapping_provider: {
|
user_mapping_provider: {
|
||||||
config: {
|
config: {
|
||||||
display_name_template: "{{ user.name }}",
|
display_name_template: "{{ user.name }}",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
],
|
});
|
||||||
});
|
await use(container);
|
||||||
await use(container);
|
server.stop();
|
||||||
server.stop();
|
},
|
||||||
|
{ scope: "worker" },
|
||||||
|
],
|
||||||
|
|
||||||
|
context: async ({ homeserverType, context }, use, testInfo) => {
|
||||||
|
testInfo.skip(homeserverType !== "synapse", "does not yet support MAS");
|
||||||
|
await use(context);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,60 +6,57 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Fixtures, PlaywrightTestArgs } from "@playwright/test";
|
import { Fixtures } from "../../../element-web-test.ts";
|
||||||
|
|
||||||
import { Options, Services } from "../../../services.ts";
|
|
||||||
import { Fixtures as BaseFixtures } from "../../../element-web-test.ts";
|
|
||||||
import { MatrixAuthenticationServiceContainer } from "../../../testcontainers/mas.ts";
|
import { MatrixAuthenticationServiceContainer } from "../../../testcontainers/mas.ts";
|
||||||
|
|
||||||
type Fixture = PlaywrightTestArgs & Services & BaseFixtures & Options;
|
export const masHomeserver: Fixtures = {
|
||||||
export const masHomeserver: Fixtures<Fixture, {}, Fixture> = {
|
mas: [
|
||||||
mas: async ({ homeserverType, _homeserver: homeserver, logger, network, postgres, mailhog }, use, testInfo) => {
|
async ({ _homeserver: homeserver, logger, network, postgres, mailhog }, use) => {
|
||||||
testInfo.skip(homeserverType !== "synapse", "does not yet support MAS");
|
const config = {
|
||||||
|
clients: [
|
||||||
const config = {
|
{
|
||||||
clients: [
|
client_id: "0000000000000000000SYNAPSE",
|
||||||
{
|
client_auth_method: "client_secret_basic",
|
||||||
client_id: "0000000000000000000SYNAPSE",
|
client_secret: "SomeRandomSecret",
|
||||||
client_auth_method: "client_secret_basic",
|
},
|
||||||
client_secret: "SomeRandomSecret",
|
],
|
||||||
|
matrix: {
|
||||||
|
homeserver: "localhost",
|
||||||
|
secret: "AnotherRandomSecret",
|
||||||
|
endpoint: "http://homeserver:8008",
|
||||||
},
|
},
|
||||||
],
|
};
|
||||||
matrix: {
|
|
||||||
homeserver: "localhost",
|
|
||||||
secret: "AnotherRandomSecret",
|
|
||||||
endpoint: "http://homeserver:8008",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const container = await new MatrixAuthenticationServiceContainer(postgres)
|
const container = await new MatrixAuthenticationServiceContainer(postgres)
|
||||||
.withNetwork(network)
|
.withNetwork(network)
|
||||||
.withNetworkAliases("mas")
|
.withNetworkAliases("mas")
|
||||||
.withLogConsumer(logger.getConsumer("mas"))
|
.withLogConsumer(logger.getConsumer("mas"))
|
||||||
.withConfig(config)
|
.withConfig(config)
|
||||||
.start();
|
.start();
|
||||||
|
|
||||||
homeserver.withConfig({
|
homeserver.withConfig({
|
||||||
enable_registration: undefined,
|
enable_registration: undefined,
|
||||||
enable_registration_without_verification: undefined,
|
enable_registration_without_verification: undefined,
|
||||||
disable_msisdn_registration: undefined,
|
disable_msisdn_registration: undefined,
|
||||||
password_config: undefined,
|
password_config: undefined,
|
||||||
experimental_features: {
|
experimental_features: {
|
||||||
msc3861: {
|
msc3861: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
issuer: `http://mas:8080/`,
|
issuer: `http://mas:8080/`,
|
||||||
introspection_endpoint: "http://mas:8080/oauth2/introspect",
|
introspection_endpoint: "http://mas:8080/oauth2/introspect",
|
||||||
client_id: config.clients[0].client_id,
|
client_id: config.clients[0].client_id,
|
||||||
client_auth_method: config.clients[0].client_auth_method,
|
client_auth_method: config.clients[0].client_auth_method,
|
||||||
client_secret: config.clients[0].client_secret,
|
client_secret: config.clients[0].client_secret,
|
||||||
admin_token: config.matrix.secret,
|
admin_token: config.matrix.secret,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
await use(container);
|
await use(container);
|
||||||
await container.stop();
|
await container.stop();
|
||||||
},
|
},
|
||||||
|
{ scope: "worker" },
|
||||||
|
],
|
||||||
|
|
||||||
config: async ({ homeserver, context, mas }, use) => {
|
config: async ({ homeserver, context, mas }, use) => {
|
||||||
const issuer = `${mas.baseUrl}/`;
|
const issuer = `${mas.baseUrl}/`;
|
||||||
|
@ -82,4 +79,9 @@ export const masHomeserver: Fixtures<Fixture, {}, Fixture> = {
|
||||||
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);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -36,105 +36,132 @@ export interface Options {
|
||||||
homeserverType: HomeserverType;
|
homeserverType: HomeserverType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const test = base.extend<Services & Options>({
|
export const test = base.extend<{}, Services & Options>({
|
||||||
// eslint-disable-next-line no-empty-pattern
|
logger: [
|
||||||
logger: async ({}, use, testInfo) => {
|
// eslint-disable-next-line no-empty-pattern
|
||||||
const logger = new ContainerLogger();
|
async ({}, use, testInfo) => {
|
||||||
await use(logger);
|
const logger = new ContainerLogger();
|
||||||
await logger.testFinished(testInfo);
|
await use(logger);
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line no-empty-pattern
|
{ scope: "worker" },
|
||||||
network: async ({}, use) => {
|
],
|
||||||
const network = await new Network().start();
|
network: [
|
||||||
await use(network);
|
// eslint-disable-next-line no-empty-pattern
|
||||||
await network.stop();
|
async ({}, use) => {
|
||||||
},
|
const network = await new Network().start();
|
||||||
postgres: async ({ logger, network }, use) => {
|
await use(network);
|
||||||
const container = await new PostgreSqlContainer()
|
await network.stop();
|
||||||
.withNetwork(network)
|
},
|
||||||
.withNetworkAliases("postgres")
|
{ scope: "worker" },
|
||||||
.withLogConsumer(logger.getConsumer("postgres"))
|
],
|
||||||
.withTmpFs({
|
postgres: [
|
||||||
"/dev/shm/pgdata/data": "",
|
async ({ logger, network }, use) => {
|
||||||
})
|
const container = await new PostgreSqlContainer()
|
||||||
.withEnvironment({
|
.withNetwork(network)
|
||||||
PG_DATA: "/dev/shm/pgdata/data",
|
.withNetworkAliases("postgres")
|
||||||
})
|
.withLogConsumer(logger.getConsumer("postgres"))
|
||||||
.withCommand([
|
.withTmpFs({
|
||||||
"-c",
|
"/dev/shm/pgdata/data": "",
|
||||||
"shared_buffers=128MB",
|
})
|
||||||
"-c",
|
.withEnvironment({
|
||||||
`fsync=off`,
|
PG_DATA: "/dev/shm/pgdata/data",
|
||||||
"-c",
|
})
|
||||||
`synchronous_commit=off`,
|
.withCommand([
|
||||||
"-c",
|
"-c",
|
||||||
"full_page_writes=off",
|
"shared_buffers=128MB",
|
||||||
])
|
"-c",
|
||||||
.start();
|
`fsync=off`,
|
||||||
await use(container);
|
"-c",
|
||||||
await container.stop();
|
`synchronous_commit=off`,
|
||||||
},
|
"-c",
|
||||||
|
"full_page_writes=off",
|
||||||
|
])
|
||||||
|
.start();
|
||||||
|
await use(container);
|
||||||
|
await container.stop();
|
||||||
|
},
|
||||||
|
{ scope: "worker" },
|
||||||
|
],
|
||||||
|
|
||||||
mailhog: async ({ logger, network }, use) => {
|
mailhog: [
|
||||||
const container = await new GenericContainer("mailhog/mailhog:latest")
|
async ({ logger, network }, use) => {
|
||||||
.withNetwork(network)
|
const container = await new GenericContainer("mailhog/mailhog:latest")
|
||||||
.withNetworkAliases("mailhog")
|
.withNetwork(network)
|
||||||
.withExposedPorts(8025)
|
.withNetworkAliases("mailhog")
|
||||||
.withLogConsumer(logger.getConsumer("mailhog"))
|
.withExposedPorts(8025)
|
||||||
.withWaitStrategy(Wait.forListeningPorts())
|
.withLogConsumer(logger.getConsumer("mailhog"))
|
||||||
.start();
|
.withWaitStrategy(Wait.forListeningPorts())
|
||||||
await use(container);
|
.start();
|
||||||
await container.stop();
|
await use(container);
|
||||||
},
|
await container.stop();
|
||||||
mailhogClient: async ({ mailhog: container }, use) => {
|
},
|
||||||
await use(mailhog({ host: container.getHost(), port: container.getMappedPort(8025) }));
|
{ scope: "worker" },
|
||||||
},
|
],
|
||||||
|
mailhogClient: [
|
||||||
|
async ({ mailhog: container }, use) => {
|
||||||
|
const client = mailhog({ host: container.getHost(), port: container.getMappedPort(8025) });
|
||||||
|
await use(client);
|
||||||
|
},
|
||||||
|
{ scope: "worker" },
|
||||||
|
],
|
||||||
|
|
||||||
synapseConfigOptions: [{}, { option: true }],
|
synapseConfigOptions: [{}, { option: true, scope: "worker" }],
|
||||||
homeserverType: ["synapse", { option: true }],
|
homeserverType: ["synapse", { option: true, scope: "worker" }],
|
||||||
_homeserver: async ({ homeserverType, request }, use) => {
|
_homeserver: [
|
||||||
let container: HomeserverContainer<any>;
|
async ({ homeserverType }, use) => {
|
||||||
switch (homeserverType) {
|
let container: HomeserverContainer<any>;
|
||||||
case "synapse":
|
switch (homeserverType) {
|
||||||
container = new SynapseContainer(request);
|
case "synapse":
|
||||||
break;
|
container = new SynapseContainer();
|
||||||
case "dendrite":
|
break;
|
||||||
container = new DendriteContainer(request);
|
case "dendrite":
|
||||||
break;
|
container = new DendriteContainer();
|
||||||
case "pinecone":
|
break;
|
||||||
container = new PineconeContainer(request);
|
case "pinecone":
|
||||||
break;
|
container = new PineconeContainer();
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
await use(container);
|
await use(container);
|
||||||
},
|
},
|
||||||
homeserver: async (
|
{ scope: "worker" },
|
||||||
{ homeserverType, logger, network, _homeserver: homeserver, synapseConfigOptions, mas },
|
],
|
||||||
use,
|
homeserver: [
|
||||||
testInfo,
|
async ({ homeserverType, logger, network, _homeserver: homeserver, synapseConfigOptions, mas }, use) => {
|
||||||
) => {
|
if (homeserver instanceof SynapseContainer) {
|
||||||
|
homeserver.withConfig(synapseConfigOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = await homeserver
|
||||||
|
.withNetwork(network)
|
||||||
|
.withNetworkAliases("homeserver")
|
||||||
|
.withLogConsumer(logger.getConsumer(homeserverType))
|
||||||
|
.start();
|
||||||
|
|
||||||
|
await use(container);
|
||||||
|
await container.stop();
|
||||||
|
},
|
||||||
|
{ scope: "worker" },
|
||||||
|
],
|
||||||
|
mas: [
|
||||||
|
// eslint-disable-next-line no-empty-pattern
|
||||||
|
async ({}, use) => {
|
||||||
|
// we stub the mas fixture to allow `homeserver` to depend on it to ensure
|
||||||
|
// when it is specified by `masHomeserver` it is started before the homeserver
|
||||||
|
await use(undefined);
|
||||||
|
},
|
||||||
|
{ scope: "worker" },
|
||||||
|
],
|
||||||
|
|
||||||
|
context: async ({ homeserverType, synapseConfigOptions, logger, context, request, homeserver }, use, testInfo) => {
|
||||||
testInfo.skip(
|
testInfo.skip(
|
||||||
!(homeserver instanceof SynapseContainer) && Object.keys(synapseConfigOptions).length > 0,
|
!(homeserver instanceof SynapseContainer) && Object.keys(synapseConfigOptions).length > 0,
|
||||||
`Test specifies Synapse config options so is unsupported with ${homeserverType}`,
|
`Test specifies Synapse config options so is unsupported with ${homeserverType}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (homeserver instanceof SynapseContainer) {
|
homeserver.setRequest(request);
|
||||||
homeserver.withConfig(synapseConfigOptions);
|
await logger.testStarted(testInfo);
|
||||||
}
|
await use(context);
|
||||||
|
await logger.testFinished(testInfo);
|
||||||
const container = await homeserver
|
|
||||||
.withNetwork(network)
|
|
||||||
.withNetworkAliases("homeserver")
|
|
||||||
.withLogConsumer(logger.getConsumer(homeserverType))
|
|
||||||
.start();
|
|
||||||
|
|
||||||
await use(container);
|
|
||||||
await container.stop();
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line no-empty-pattern
|
|
||||||
mas: async ({}, use) => {
|
|
||||||
// we stub the mas fixture to allow `homeserver` to depend on it to ensure
|
|
||||||
// when it is specified by `masHomeserver` it is started before the homeserver
|
|
||||||
await use(undefined);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |
|
@ -6,6 +6,7 @@ Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AbstractStartedContainer, GenericContainer } from "testcontainers";
|
import { AbstractStartedContainer, GenericContainer } from "testcontainers";
|
||||||
|
import { APIRequestContext } from "@playwright/test";
|
||||||
|
|
||||||
import { StartedSynapseContainer } from "./synapse.ts";
|
import { StartedSynapseContainer } from "./synapse.ts";
|
||||||
import { HomeserverInstance } from "../plugins/homeserver";
|
import { HomeserverInstance } from "../plugins/homeserver";
|
||||||
|
@ -16,4 +17,6 @@ export interface HomeserverContainer<Config> extends GenericContainer {
|
||||||
start(): Promise<StartedSynapseContainer>;
|
start(): Promise<StartedSynapseContainer>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StartedHomeserverContainer extends AbstractStartedContainer, HomeserverInstance {}
|
export interface StartedHomeserverContainer extends AbstractStartedContainer, HomeserverInstance {
|
||||||
|
setRequest(request: APIRequestContext): void;
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { GenericContainer, Wait } from "testcontainers";
|
import { GenericContainer, Wait } from "testcontainers";
|
||||||
import { APIRequestContext } from "@playwright/test";
|
|
||||||
import * as YAML from "yaml";
|
import * as YAML from "yaml";
|
||||||
import { set } from "lodash";
|
import { set } from "lodash";
|
||||||
|
|
||||||
|
@ -208,11 +207,7 @@ const DEFAULT_CONFIG = {
|
||||||
export class DendriteContainer extends GenericContainer implements HomeserverContainer<typeof DEFAULT_CONFIG> {
|
export class DendriteContainer extends GenericContainer implements HomeserverContainer<typeof DEFAULT_CONFIG> {
|
||||||
private config: typeof DEFAULT_CONFIG;
|
private config: typeof DEFAULT_CONFIG;
|
||||||
|
|
||||||
constructor(
|
constructor(image = "matrixdotorg/dendrite-monolith:main", binary = "/usr/bin/dendrite") {
|
||||||
private request: APIRequestContext,
|
|
||||||
image = "matrixdotorg/dendrite-monolith:main",
|
|
||||||
binary = "/usr/bin/dendrite",
|
|
||||||
) {
|
|
||||||
super(image);
|
super(image);
|
||||||
|
|
||||||
this.config = deepCopy(DEFAULT_CONFIG);
|
this.config = deepCopy(DEFAULT_CONFIG);
|
||||||
|
@ -254,13 +249,12 @@ export class DendriteContainer extends GenericContainer implements HomeserverCon
|
||||||
container,
|
container,
|
||||||
`http://${container.getHost()}:${container.getMappedPort(8008)}`,
|
`http://${container.getHost()}:${container.getMappedPort(8008)}`,
|
||||||
this.config.client_api.registration_shared_secret,
|
this.config.client_api.registration_shared_secret,
|
||||||
this.request,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PineconeContainer extends DendriteContainer {
|
export class PineconeContainer extends DendriteContainer {
|
||||||
constructor(request: APIRequestContext) {
|
constructor() {
|
||||||
super(request, "matrixdotorg/dendrite-demo-pinecone:main", "/usr/bin/dendrite-demo-pinecone");
|
super("matrixdotorg/dendrite-demo-pinecone:main", "/usr/bin/dendrite-demo-pinecone");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,7 +141,7 @@ export type SynapseConfigOptions = Partial<typeof DEFAULT_CONFIG>;
|
||||||
export class SynapseContainer extends GenericContainer implements HomeserverContainer<typeof DEFAULT_CONFIG> {
|
export class SynapseContainer extends GenericContainer implements HomeserverContainer<typeof DEFAULT_CONFIG> {
|
||||||
private config: typeof DEFAULT_CONFIG;
|
private config: typeof DEFAULT_CONFIG;
|
||||||
|
|
||||||
constructor(private readonly request: APIRequestContext) {
|
constructor() {
|
||||||
super(`ghcr.io/element-hq/synapse:${TAG}`);
|
super(`ghcr.io/element-hq/synapse:${TAG}`);
|
||||||
|
|
||||||
this.config = deepCopy(DEFAULT_CONFIG);
|
this.config = deepCopy(DEFAULT_CONFIG);
|
||||||
|
@ -221,23 +221,26 @@ export class SynapseContainer extends GenericContainer implements HomeserverCont
|
||||||
await super.start(),
|
await super.start(),
|
||||||
`http://localhost:${port}`,
|
`http://localhost:${port}`,
|
||||||
this.config.registration_shared_secret,
|
this.config.registration_shared_secret,
|
||||||
this.request,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class StartedSynapseContainer extends AbstractStartedContainer implements StartedHomeserverContainer {
|
export class StartedSynapseContainer extends AbstractStartedContainer implements StartedHomeserverContainer {
|
||||||
private adminToken?: string;
|
private adminToken?: string;
|
||||||
|
private request?: APIRequestContext;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
container: StartedTestContainer,
|
container: StartedTestContainer,
|
||||||
public readonly baseUrl: string,
|
public readonly baseUrl: string,
|
||||||
private readonly registrationSharedSecret: string,
|
private readonly registrationSharedSecret: string,
|
||||||
private readonly request: APIRequestContext,
|
|
||||||
) {
|
) {
|
||||||
super(container);
|
super(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setRequest(request: APIRequestContext): void {
|
||||||
|
this.request = request;
|
||||||
|
}
|
||||||
|
|
||||||
private async registerUserInternal(
|
private async registerUserInternal(
|
||||||
username: string,
|
username: string,
|
||||||
password: string,
|
password: string,
|
||||||
|
@ -273,6 +276,7 @@ export class StartedSynapseContainer extends AbstractStartedContainer implements
|
||||||
deviceId: data.device_id,
|
deviceId: data.device_id,
|
||||||
password,
|
password,
|
||||||
displayName,
|
displayName,
|
||||||
|
username,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,6 +304,7 @@ export class StartedSynapseContainer extends AbstractStartedContainer implements
|
||||||
userId: json.user_id,
|
userId: json.user_id,
|
||||||
deviceId: json.device_id,
|
deviceId: json.device_id,
|
||||||
homeServer: json.home_server || json.user_id.split(":").slice(1).join(":"),
|
homeServer: json.home_server || json.user_id.split(":").slice(1).join(":"),
|
||||||
|
username: userId.slice(1).split(":")[0],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,12 @@ export class ContainerLogger {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async testStarted(testInfo: TestInfo) {
|
||||||
|
for (const container in this.logs) {
|
||||||
|
this.logs[container] = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async testFinished(testInfo: TestInfo) {
|
public async testFinished(testInfo: TestInfo) {
|
||||||
if (testInfo.status !== "passed") {
|
if (testInfo.status !== "passed") {
|
||||||
for (const container in this.logs) {
|
for (const container in this.logs) {
|
||||||
|
|
|
@ -1276,7 +1276,7 @@
|
||||||
"error_already_invited_space": "Die Person wurde bereits eingeladen",
|
"error_already_invited_space": "Die Person wurde bereits eingeladen",
|
||||||
"error_already_joined_room": "Die Person ist bereits im Raum",
|
"error_already_joined_room": "Die Person ist bereits im Raum",
|
||||||
"error_already_joined_space": "Die Person ist bereits im Space",
|
"error_already_joined_space": "Die Person ist bereits im Space",
|
||||||
"error_bad_state": "Verbannte Nutzer können nicht eingeladen werden.",
|
"error_bad_state": "Gesperrte Benutzer können nicht eingeladen werden.",
|
||||||
"error_dm": "Wir konnten deine Direktnachricht nicht erstellen.",
|
"error_dm": "Wir konnten deine Direktnachricht nicht erstellen.",
|
||||||
"error_find_room": "Beim Einladen der Nutzer lief etwas schief.",
|
"error_find_room": "Beim Einladen der Nutzer lief etwas schief.",
|
||||||
"error_find_user_description": "Folgende Nutzer konnten nicht eingeladen werden, da sie nicht existieren oder ungültig sind: %(csvNames)s",
|
"error_find_user_description": "Folgende Nutzer konnten nicht eingeladen werden, da sie nicht existieren oder ungültig sind: %(csvNames)s",
|
||||||
|
@ -1317,7 +1317,7 @@
|
||||||
"unable_find_profiles_invite_label_default": "Dennoch einladen",
|
"unable_find_profiles_invite_label_default": "Dennoch einladen",
|
||||||
"unable_find_profiles_invite_never_warn_label_default": "Trotzdem einladen und mich nicht mehr warnen",
|
"unable_find_profiles_invite_never_warn_label_default": "Trotzdem einladen und mich nicht mehr warnen",
|
||||||
"unable_find_profiles_title": "Eventuell existieren folgende Benutzer nicht",
|
"unable_find_profiles_title": "Eventuell existieren folgende Benutzer nicht",
|
||||||
"unban_first_title": "Benutzer kann nicht eingeladen werden, solange er nicht entbannt ist"
|
"unban_first_title": "Benutzer kann nicht eingeladen werden, solange die Sperre nicht aufgehoben worden ist."
|
||||||
},
|
},
|
||||||
"inviting_user1_and_user2": "Lade %(user1)s und %(user2)s ein",
|
"inviting_user1_and_user2": "Lade %(user1)s und %(user2)s ein",
|
||||||
"inviting_user_and_n_others": {
|
"inviting_user_and_n_others": {
|
||||||
|
@ -3732,8 +3732,8 @@
|
||||||
"ban_button_room": "Bannen",
|
"ban_button_room": "Bannen",
|
||||||
"ban_button_space": "Bannen",
|
"ban_button_space": "Bannen",
|
||||||
"ban_room_confirm_title": "Aus %(roomName)s verbannen",
|
"ban_room_confirm_title": "Aus %(roomName)s verbannen",
|
||||||
"ban_space_everything": "Überall wo ich die Rechte dazu habe bannen",
|
"ban_space_everything": "Überall sperren, wo ich die Rechte dazu habe",
|
||||||
"ban_space_specific": "In ausgewählten Räumen und Spaces bannen",
|
"ban_space_specific": "Sperre sie in ausgewählten Chatrooms und Spaces",
|
||||||
"count_of_sessions": {
|
"count_of_sessions": {
|
||||||
"other": "%(count)s Sitzungen",
|
"other": "%(count)s Sitzungen",
|
||||||
"one": "%(count)s Sitzung"
|
"one": "%(count)s Sitzung"
|
||||||
|
@ -3798,11 +3798,11 @@
|
||||||
"room_unencrypted_detail": "Nachrichten in verschlüsselten Räumen können nur von dir und vom Empfänger gelesen werden.",
|
"room_unencrypted_detail": "Nachrichten in verschlüsselten Räumen können nur von dir und vom Empfänger gelesen werden.",
|
||||||
"send_message": "Nachricht senden",
|
"send_message": "Nachricht senden",
|
||||||
"share_button": "Profil teilen",
|
"share_button": "Profil teilen",
|
||||||
"unban_button_room": "Entbannen",
|
"unban_button_room": "Sperrung für den Chatroom aufheben",
|
||||||
"unban_button_space": "Entbannen",
|
"unban_button_space": "Sperrung aus Space aufheben",
|
||||||
"unban_room_confirm_title": "Von %(roomName)s entbannen",
|
"unban_room_confirm_title": "Sperrung für %(roomName)s aufheben",
|
||||||
"unban_space_everything": "Überall wo ich die Rechte dazu habe, entbannen",
|
"unban_space_everything": "Die Verbannung überall aufheben wo ich die Rechte dazu habe",
|
||||||
"unban_space_specific": "In ausgewählten Räumen und Spaces entbannen",
|
"unban_space_specific": "In ausgewählten Chatrooms und Spaces die Sperrung für sie aufheben",
|
||||||
"unban_space_warning": "Die Person wird keinen Zutritt zu Bereichen haben, in denen du nicht administrierst.",
|
"unban_space_warning": "Die Person wird keinen Zutritt zu Bereichen haben, in denen du nicht administrierst.",
|
||||||
"unignore_button": "Nicht mehr ignorieren",
|
"unignore_button": "Nicht mehr ignorieren",
|
||||||
"verify_button": "Nutzer verifizieren",
|
"verify_button": "Nutzer verifizieren",
|
||||||
|
|