Use homeserver in a worker-scoped fixture (#28848)
* Use homeserver in a worker-scoped fixture Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Switch to TestContainers for manging services in Playwright Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Flip fixture dependency order Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Remove mas dep Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update matrix-authentication-service in Playwright tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * delint Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix SMTP port Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Comments Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Strip ansi from playwright logs to make them more readable Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Actually do the update Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Remove access to homeserver.config.baseUrl field in favour of homeserver.baseUrl Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Use sane default_server_config and specify server.invalid in the specific tests which demand it Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix mas run Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * break cycle Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * typo Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * prettier Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Wire up basics of dendriteHomeserver Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix types Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Discard changes to playwright/e2e/settings/device-management.spec.ts * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix bad merge Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
@@ -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 a user an 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,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
import { type Page } from "@playwright/test";
|
||||
|
||||
import { test, expect } from "../../element-web-test";
|
||||
import { registerAccountMas } from "../oidc";
|
||||
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) {
|
||||
await expect(page.locator(".mx_SecureBackupPanel_statusList tr:nth-child(5) td")).toHaveText(
|
||||
@@ -22,85 +18,6 @@ async function expectBackupVersionToBe(page: Page, version: string) {
|
||||
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 a user an synthesizes an existing login,
|
||||
// which is faster but leaves us without crypto set up.
|
||||
test.describe("Encryption state after registration", () => {
|
||||
test.use(masHomeserver);
|
||||
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.use(masHomeserver);
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Backups", () => {
|
||||
test.use({
|
||||
displayName: "Hanako",
|
||||
|
||||
@@ -19,31 +19,31 @@ function getMemberTileByName(page: Page, name: string): Locator {
|
||||
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.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) => {
|
||||
// Create a backup (which will create SSSS, and dehydrated device)
|
||||
|
||||
|
||||
@@ -16,20 +16,21 @@ const username = "user1234";
|
||||
const password = "oETo7MPf0o";
|
||||
const email = "user@nowhere.dummy";
|
||||
|
||||
test.describe("Forgot Password", () => {
|
||||
test.skip(isDendrite, "not yet wired up");
|
||||
test.use(emailHomeserver);
|
||||
test.use({
|
||||
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(emailHomeserver);
|
||||
test.use({
|
||||
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.describe("Forgot Password", () => {
|
||||
test.skip(isDendrite, "not yet wired up");
|
||||
|
||||
test("renders properly", { tag: "@screenshot" }, async ({ page, homeserver }) => {
|
||||
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 { consentHomeserver } from "../../plugins/homeserver/synapse/consentHomeserver.ts";
|
||||
|
||||
test.describe("Consent", () => {
|
||||
test.use(consentHomeserver);
|
||||
test.use({
|
||||
displayName: "Bob",
|
||||
});
|
||||
test.use(consentHomeserver);
|
||||
test.use({
|
||||
displayName: "Bob",
|
||||
});
|
||||
|
||||
test.describe("Consent", () => {
|
||||
test("should prompt the user to consent to terms when server deems it necessary", async ({
|
||||
context,
|
||||
page,
|
||||
|
||||
@@ -9,13 +9,11 @@ Please see LICENSE files in the repository root for full details.
|
||||
import { Page } from "playwright-core";
|
||||
|
||||
import { expect, test } from "../../element-web-test";
|
||||
import { doTokenRegistration } from "./utils";
|
||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
||||
import { selectHomeserver } from "../utils";
|
||||
import { Credentials, HomeserverInstance } from "../../plugins/homeserver";
|
||||
import { consentHomeserver } from "../../plugins/homeserver/synapse/consentHomeserver.ts";
|
||||
import { legacyOAuthHomeserver } from "../../plugins/homeserver/synapse/legacyOAuthHomeserver.ts";
|
||||
|
||||
// This test requires fixed credentials for the device signing keys below to work
|
||||
const username = "user1234";
|
||||
const password = "p4s5W0rD";
|
||||
|
||||
@@ -70,38 +68,42 @@ 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 selectHomeserver(page, homeserver.baseUrl);
|
||||
|
||||
await page.getByRole("textbox", { name: "Username" }).fill(username);
|
||||
await page.getByPlaceholder("Password").fill(password);
|
||||
await page.getByRole("textbox", { name: "Username" }).fill(credentials.username);
|
||||
await page.getByPlaceholder("Password").fill(credentials.password);
|
||||
await page.getByRole("button", { name: "Sign in" }).click();
|
||||
}
|
||||
|
||||
test.describe("Login", () => {
|
||||
test.use({
|
||||
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(consentHomeserver);
|
||||
test.use({
|
||||
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",
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
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}`);
|
||||
|
||||
test.describe("Password login", () => {
|
||||
test.use(consentHomeserver);
|
||||
|
||||
let creds: Credentials;
|
||||
|
||||
test.beforeEach(async ({ homeserver }) => {
|
||||
creds = await homeserver.registerUser(username, password);
|
||||
await use({
|
||||
...credentials,
|
||||
displayName,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
test.describe("Login", () => {
|
||||
test.describe("Password login", () => {
|
||||
test("Loads the welcome page by default; then logs in with an existing account and lands on the home screen", async ({
|
||||
credentials,
|
||||
page,
|
||||
homeserver,
|
||||
checkA11y,
|
||||
@@ -135,16 +137,16 @@ test.describe("Login", () => {
|
||||
// cy.percySnapshot("Login");
|
||||
await checkA11y();
|
||||
|
||||
await page.getByRole("textbox", { name: "Username" }).fill(username);
|
||||
await page.getByPlaceholder("Password").fill(password);
|
||||
await page.getByRole("textbox", { name: "Username" }).fill(credentials.username);
|
||||
await page.getByPlaceholder("Password").fill(credentials.password);
|
||||
await page.getByRole("button", { name: "Sign in" }).click();
|
||||
|
||||
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 login(page, homeserver);
|
||||
await login(page, homeserver, credentials);
|
||||
|
||||
await expect(page).toHaveURL(/\/#\/room\/!room:id$/);
|
||||
await expect(page.getByRole("button", { name: "Join the discussion" })).toBeVisible();
|
||||
@@ -155,9 +157,10 @@ test.describe("Login", () => {
|
||||
page,
|
||||
homeserver,
|
||||
request,
|
||||
credentials,
|
||||
}) => {
|
||||
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,
|
||||
});
|
||||
if (res.status() / 100 !== 2) {
|
||||
@@ -166,7 +169,7 @@ test.describe("Login", () => {
|
||||
expect(res.status() / 100).toEqual(2);
|
||||
|
||||
await page.goto("/");
|
||||
await login(page, homeserver);
|
||||
await login(page, homeserver, credentials);
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Verify this device", level: 1 })).toBeVisible();
|
||||
|
||||
@@ -184,10 +187,14 @@ test.describe("Login", () => {
|
||||
page,
|
||||
homeserver,
|
||||
request,
|
||||
credentials,
|
||||
}) => {
|
||||
const res = await request.post(
|
||||
`${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) {
|
||||
console.log("Uploading dummy keys failed", await res.json());
|
||||
@@ -195,7 +202,7 @@ test.describe("Login", () => {
|
||||
expect(res.status() / 100).toEqual(2);
|
||||
|
||||
await page.goto("/");
|
||||
await login(page, homeserver);
|
||||
await login(page, homeserver, credentials);
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Verify this device", level: 1 })).toBeVisible();
|
||||
|
||||
@@ -214,11 +221,15 @@ test.describe("Login", () => {
|
||||
page,
|
||||
homeserver,
|
||||
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(
|
||||
`${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) {
|
||||
console.log("Uploading dummy keys failed", await res.json());
|
||||
@@ -226,9 +237,9 @@ test.describe("Login", () => {
|
||||
expect(res.status() / 100).toEqual(2);
|
||||
|
||||
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.locator(".mx_CompleteSecurity_skip")).toHaveCount(0);
|
||||
@@ -237,25 +248,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.use(consentHomeserver);
|
||||
|
||||
test("should go to login page on logout", async ({ page, user }) => {
|
||||
await page.getByRole("button", { name: "User menu" }).click();
|
||||
await expect(page.getByText(user.displayName, { exact: true })).toBeVisible();
|
||||
@@ -267,29 +260,4 @@ test.describe("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,122 +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.
|
||||
*/
|
||||
|
||||
import { Page } from "@playwright/test";
|
||||
|
||||
import { test, expect } from "../../element-web-test";
|
||||
import { doTokenRegistration } from "./utils";
|
||||
import { Credentials } from "../../plugins/homeserver";
|
||||
import { legacyOAuthHomeserver } from "../../plugins/homeserver/synapse/legacyOAuthHomeserver.ts";
|
||||
import { interceptRequestsWithSoftLogout } from "./utils";
|
||||
|
||||
test.describe("Soft logout", () => {
|
||||
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({
|
||||
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.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.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();
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Intercept calls to /sync and have them fail with a soft-logout
|
||||
*
|
||||
* Any further requests to /sync with the same access token are blocked.
|
||||
*/
|
||||
async function interceptRequestsWithSoftLogout(page: Page, user: Credentials): Promise<void> {
|
||||
await page.route("**/_matrix/client/*/sync*", async (route, req) => {
|
||||
const accessToken = await req.headerValue("Authorization");
|
||||
test.describe("Soft logout 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");
|
||||
|
||||
// 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();
|
||||
// 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();
|
||||
});
|
||||
|
||||
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({});
|
||||
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();
|
||||
});
|
||||
|
||||
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("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
|
||||
await expect(page.locator(".mx_ServerPickerDialog")).toHaveCount(0);
|
||||
|
||||
@@ -56,5 +56,44 @@ export async function doTokenRegistration(
|
||||
homeServer: window.mxMatrixClientPeg.get().getHomeserverUrl(),
|
||||
password: null,
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ import { ElementAppPage } from "../../pages/ElementAppPage.ts";
|
||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
||||
import { masHomeserver } from "../../plugins/homeserver/synapse/masHomeserver.ts";
|
||||
|
||||
test.use(masHomeserver);
|
||||
test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
test.use(masHomeserver);
|
||||
test.skip(isDendrite, "does not yet support MAS");
|
||||
test.slow(); // trace recording takes a while here
|
||||
|
||||
|
||||
@@ -10,21 +10,22 @@ import { test, expect } from "../../element-web-test";
|
||||
import { emailHomeserver } from "../../plugins/homeserver/synapse/emailHomeserver.ts";
|
||||
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.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 }) => {
|
||||
await page.goto("/#/register");
|
||||
|
||||
@@ -9,20 +9,20 @@ Please see LICENSE files in the repository root for full details.
|
||||
import { test, expect } from "../../element-web-test";
|
||||
import { consentHomeserver } from "../../plugins/homeserver/synapse/consentHomeserver.ts";
|
||||
|
||||
test.describe("Registration", () => {
|
||||
test.use(consentHomeserver);
|
||||
test.use({
|
||||
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(consentHomeserver);
|
||||
test.use({
|
||||
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.describe("Registration", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/#/register");
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ import { test as base, expect } from "../../../element-web-test";
|
||||
import { Bot } from "../../../pages/bot";
|
||||
import { Client } from "../../../pages/client";
|
||||
import { ElementAppPage } from "../../../pages/ElementAppPage";
|
||||
import { Credentials } from "../../../plugins/homeserver";
|
||||
|
||||
type RoomRef = { name: string; roomId: string };
|
||||
|
||||
@@ -336,12 +337,14 @@ export class Helpers {
|
||||
* @param room1
|
||||
* @param room2
|
||||
* @param msg - MessageBuilder
|
||||
* @param user - the user to mention in the first message
|
||||
* @param hasMention - whether to include a mention in the first message
|
||||
*/
|
||||
async populateThreads(
|
||||
room1: { name: string; roomId: string },
|
||||
room2: { name: string; roomId: string },
|
||||
msg: MessageBuilder,
|
||||
user: Credentials,
|
||||
hasMention = true,
|
||||
) {
|
||||
if (hasMention) {
|
||||
@@ -350,9 +353,9 @@ export class Helpers {
|
||||
msg.threadedOff("Msg1", {
|
||||
"body": "User",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": "<a href='https://matrix.to/#/@user:localhost'>User</a>",
|
||||
"formatted_body": `<a href="https://matrix.to/#/${user.userId}">User</a>`,
|
||||
"m.mentions": {
|
||||
user_ids: ["@user:localhost"],
|
||||
user_ids: [user.userId],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
@@ -46,16 +46,21 @@ test.describe("Threads Activity Centre", { tag: "@no-firefox" }, () => {
|
||||
await util.assertNotificationTac();
|
||||
});
|
||||
|
||||
test("should show a highlight indicator when there is a mention in a thread", async ({ room1, util, msg }) => {
|
||||
test("should show a highlight indicator when there is a mention in a thread", async ({
|
||||
room1,
|
||||
util,
|
||||
msg,
|
||||
user,
|
||||
}) => {
|
||||
await util.goTo(room1);
|
||||
await util.receiveMessages(room1, [
|
||||
"Msg1",
|
||||
msg.threadedOff("Msg1", {
|
||||
"body": "User",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": "<a href='https://matrix.to/#/@user:localhost'>User</a>",
|
||||
"formatted_body": `<a href="https://matrix.to/#/${user.userId}">User</a>`,
|
||||
"m.mentions": {
|
||||
user_ids: ["@user:localhost"],
|
||||
user_ids: [user.userId],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
@@ -64,26 +69,30 @@ test.describe("Threads Activity Centre", { tag: "@no-firefox" }, () => {
|
||||
await util.assertHighlightIndicator();
|
||||
});
|
||||
|
||||
test("should show the rooms with unread threads", { tag: "@screenshot" }, async ({ room1, room2, util, msg }) => {
|
||||
test(
|
||||
"should show the rooms with unread threads",
|
||||
{ tag: "@screenshot" },
|
||||
async ({ room1, room2, util, msg, user }) => {
|
||||
await util.goTo(room2);
|
||||
await util.populateThreads(room1, room2, msg, user);
|
||||
// The indicator should be shown
|
||||
await util.assertHighlightIndicator();
|
||||
|
||||
// Verify that we have the expected rooms in the TAC
|
||||
await util.openTac();
|
||||
await util.assertRoomsInTac([
|
||||
{ room: room2.name, notificationLevel: "highlight" },
|
||||
{ room: room1.name, notificationLevel: "notification" },
|
||||
]);
|
||||
|
||||
// Verify that we don't have a visual regression
|
||||
await expect(util.getTacPanel()).toMatchScreenshot("tac-panel-mix-unread.png");
|
||||
},
|
||||
);
|
||||
|
||||
test("should update with a thread is read", { tag: "@screenshot" }, async ({ room1, room2, util, msg, user }) => {
|
||||
await util.goTo(room2);
|
||||
await util.populateThreads(room1, room2, msg);
|
||||
// The indicator should be shown
|
||||
await util.assertHighlightIndicator();
|
||||
|
||||
// Verify that we have the expected rooms in the TAC
|
||||
await util.openTac();
|
||||
await util.assertRoomsInTac([
|
||||
{ room: room2.name, notificationLevel: "highlight" },
|
||||
{ room: room1.name, notificationLevel: "notification" },
|
||||
]);
|
||||
|
||||
// Verify that we don't have a visual regression
|
||||
await expect(util.getTacPanel()).toMatchScreenshot("tac-panel-mix-unread.png");
|
||||
});
|
||||
|
||||
test("should update with a thread is read", { tag: "@screenshot" }, async ({ room1, room2, util, msg }) => {
|
||||
await util.goTo(room2);
|
||||
await util.populateThreads(room1, room2, msg);
|
||||
await util.populateThreads(room1, room2, msg, user);
|
||||
|
||||
// Click on the first room in TAC
|
||||
await util.openTac();
|
||||
@@ -104,9 +113,9 @@ test.describe("Threads Activity Centre", { tag: "@no-firefox" }, () => {
|
||||
await expect(util.getTacPanel()).toMatchScreenshot("tac-panel-notification-unread.png");
|
||||
});
|
||||
|
||||
test("should order by recency after notification level", async ({ room1, room2, util, msg }) => {
|
||||
test("should order by recency after notification level", async ({ room1, room2, util, msg, user }) => {
|
||||
await util.goTo(room2);
|
||||
await util.populateThreads(room1, room2, msg, false);
|
||||
await util.populateThreads(room1, room2, msg, user, false);
|
||||
|
||||
await util.openTac();
|
||||
await util.assertRoomsInTac([
|
||||
|
||||
@@ -137,12 +137,12 @@ export const test = base.extend<Fixtures>({
|
||||
},
|
||||
|
||||
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 password = _.uniqueId("password_");
|
||||
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 @user:localhost with displayname ${displayName}`);
|
||||
|
||||
await use({
|
||||
|
||||
@@ -6,31 +6,34 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { Fixtures, PlaywrightTestArgs } from "@playwright/test";
|
||||
import { Fixtures } from "@playwright/test";
|
||||
|
||||
import { Fixtures as BaseFixtures } from "../../../element-web-test.ts";
|
||||
import { DendriteContainer, PineconeContainer } from "../../../testcontainers/dendrite.ts";
|
||||
import { Services } from "../../../services.ts";
|
||||
|
||||
type Fixture = PlaywrightTestArgs & Services & BaseFixtures;
|
||||
export const dendriteHomeserver: Fixtures<Fixture, {}, Fixture> = {
|
||||
_homeserver: async ({ request }, use) => {
|
||||
const container =
|
||||
process.env["PLAYWRIGHT_HOMESERVER"] === "dendrite"
|
||||
? new DendriteContainer(request)
|
||||
: new PineconeContainer(request);
|
||||
await use(container);
|
||||
},
|
||||
homeserver: async ({ logger, network, _homeserver: homeserver }, use) => {
|
||||
const container = await homeserver
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("homeserver")
|
||||
.withLogConsumer(logger.getConsumer("dendrite"))
|
||||
.start();
|
||||
export const dendriteHomeserver: Fixtures<{}, Services> = {
|
||||
_homeserver: [
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
async ({}, use) => {
|
||||
const container =
|
||||
process.env["PLAYWRIGHT_HOMESERVER"] === "dendrite" ? new DendriteContainer() : new PineconeContainer();
|
||||
await use(container);
|
||||
},
|
||||
{ scope: "worker" },
|
||||
],
|
||||
homeserver: [
|
||||
async ({ logger, network, _homeserver: homeserver }, use) => {
|
||||
const container = await homeserver
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("homeserver")
|
||||
.withLogConsumer(logger.getConsumer("dendrite"))
|
||||
.start();
|
||||
|
||||
await use(container);
|
||||
await container.stop();
|
||||
},
|
||||
await use(container);
|
||||
await container.stop();
|
||||
},
|
||||
{ scope: "worker" },
|
||||
],
|
||||
};
|
||||
|
||||
export function isDendrite(): boolean {
|
||||
|
||||
@@ -41,4 +41,5 @@ export interface Credentials {
|
||||
homeServer: string;
|
||||
password: string | null; // null for password-less users
|
||||
displayName?: string;
|
||||
username: string; // the localpart of the userId
|
||||
}
|
||||
|
||||
@@ -10,47 +10,50 @@ import { Fixtures } from "@playwright/test";
|
||||
|
||||
import { Services } from "../../../services.ts";
|
||||
|
||||
export const consentHomeserver: Fixtures<Services, {}, Services> = {
|
||||
_homeserver: async ({ _homeserver: container, mailhog }, use) => {
|
||||
container
|
||||
.withCopyDirectoriesToContainer([
|
||||
{ source: "playwright/plugins/homeserver/synapse/res", target: "/data/res" },
|
||||
])
|
||||
.withConfig({
|
||||
email: {
|
||||
enable_notifs: false,
|
||||
smtp_host: "mailhog",
|
||||
smtp_port: 1025,
|
||||
smtp_user: "username",
|
||||
smtp_pass: "password",
|
||||
require_transport_security: false,
|
||||
notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>",
|
||||
app_name: "Matrix",
|
||||
notif_template_html: "notif_mail.html",
|
||||
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",
|
||||
export const consentHomeserver: Fixtures<{}, Services> = {
|
||||
_homeserver: [
|
||||
async ({ _homeserver: container, mailhog }, use) => {
|
||||
container
|
||||
.withCopyDirectoriesToContainer([
|
||||
{ source: "playwright/plugins/homeserver/synapse/res", target: "/data/res" },
|
||||
])
|
||||
.withConfig({
|
||||
email: {
|
||||
enable_notifs: false,
|
||||
smtp_host: "mailhog",
|
||||
smtp_port: 1025,
|
||||
smtp_user: "username",
|
||||
smtp_pass: "password",
|
||||
require_transport_security: false,
|
||||
notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>",
|
||||
app_name: "Matrix",
|
||||
notif_template_html: "notif_mail.html",
|
||||
notif_template_text: "notif_mail.txt",
|
||||
notif_for_new_users: true,
|
||||
client_base_url: "http://localhost/element",
|
||||
},
|
||||
send_server_notice_to_guests: true,
|
||||
block_events_error:
|
||||
"To continue using this homeserver you must review and agree to the terms and conditions at %(consent_uri)s",
|
||||
require_at_registration: true,
|
||||
},
|
||||
server_notices: {
|
||||
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);
|
||||
},
|
||||
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,
|
||||
block_events_error:
|
||||
"To continue using this homeserver you must review and agree to the terms and conditions at %(consent_uri)s",
|
||||
require_at_registration: true,
|
||||
},
|
||||
server_notices: {
|
||||
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" },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -10,19 +10,22 @@ import { Fixtures } from "@playwright/test";
|
||||
|
||||
import { Services } from "../../../services.ts";
|
||||
|
||||
export const emailHomeserver: Fixtures<Services, {}, Services> = {
|
||||
_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);
|
||||
},
|
||||
export const emailHomeserver: Fixtures<{}, Services> = {
|
||||
_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" },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -12,37 +12,40 @@ import { TestContainers } from "testcontainers";
|
||||
import { Services } from "../../../services.ts";
|
||||
import { OAuthServer } from "../../oauth_server";
|
||||
|
||||
export const legacyOAuthHomeserver: Fixtures<Services, {}, Services> = {
|
||||
_homeserver: async ({ _homeserver: container }, use) => {
|
||||
const server = new OAuthServer();
|
||||
const port = server.start();
|
||||
export const legacyOAuthHomeserver: Fixtures<{}, Services> = {
|
||||
_homeserver: [
|
||||
async ({ _homeserver: container }, use) => {
|
||||
const server = new OAuthServer();
|
||||
const port = server.start();
|
||||
|
||||
await TestContainers.exposeHostPorts(port);
|
||||
container.withConfig({
|
||||
oidc_providers: [
|
||||
{
|
||||
idp_id: "test",
|
||||
idp_name: "OAuth test",
|
||||
issuer: `http://localhost:${port}/oauth`,
|
||||
authorization_endpoint: `http://localhost:${port}/oauth/auth.html`,
|
||||
// the token endpoint receives requests from synapse,
|
||||
// rather than the webapp, so needs to escape the docker container.
|
||||
token_endpoint: `http://host.testcontainers.internal:${port}/oauth/token`,
|
||||
userinfo_endpoint: `http://host.testcontainers.internal:${port}/oauth/userinfo`,
|
||||
client_id: "synapse",
|
||||
discover: false,
|
||||
scopes: ["profile"],
|
||||
skip_verification: true,
|
||||
client_auth_method: "none",
|
||||
user_mapping_provider: {
|
||||
config: {
|
||||
display_name_template: "{{ user.name }}",
|
||||
await TestContainers.exposeHostPorts(port);
|
||||
container.withConfig({
|
||||
oidc_providers: [
|
||||
{
|
||||
idp_id: "test",
|
||||
idp_name: "OAuth test",
|
||||
issuer: `http://localhost:${port}/oauth`,
|
||||
authorization_endpoint: `http://localhost:${port}/oauth/auth.html`,
|
||||
// the token endpoint receives requests from synapse,
|
||||
// rather than the webapp, so needs to escape the docker container.
|
||||
token_endpoint: `http://host.testcontainers.internal:${port}/oauth/token`,
|
||||
userinfo_endpoint: `http://host.testcontainers.internal:${port}/oauth/userinfo`,
|
||||
client_id: "synapse",
|
||||
discover: false,
|
||||
scopes: ["profile"],
|
||||
skip_verification: true,
|
||||
client_auth_method: "none",
|
||||
user_mapping_provider: {
|
||||
config: {
|
||||
display_name_template: "{{ user.name }}",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
await use(container);
|
||||
server.stop();
|
||||
},
|
||||
],
|
||||
});
|
||||
await use(container);
|
||||
server.stop();
|
||||
},
|
||||
{ scope: "worker" },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -12,52 +12,55 @@ import { Services } from "../../../services.ts";
|
||||
import { Fixtures as BaseFixtures } from "../../../element-web-test.ts";
|
||||
import { MatrixAuthenticationServiceContainer } from "../../../testcontainers/mas.ts";
|
||||
|
||||
type Fixture = PlaywrightTestArgs & Services & BaseFixtures;
|
||||
export const masHomeserver: Fixtures<Fixture, {}, Fixture> = {
|
||||
mas: async ({ _homeserver: homeserver, logger, network, postgres, mailhog }, use) => {
|
||||
const config = {
|
||||
clients: [
|
||||
{
|
||||
client_id: "0000000000000000000SYNAPSE",
|
||||
client_auth_method: "client_secret_basic",
|
||||
client_secret: "SomeRandomSecret",
|
||||
type Fixture = PlaywrightTestArgs & BaseFixtures;
|
||||
export const masHomeserver: Fixtures<Fixture, Services, Fixture> = {
|
||||
mas: [
|
||||
async ({ _homeserver: homeserver, logger, network, postgres, mailhog }, use) => {
|
||||
const config = {
|
||||
clients: [
|
||||
{
|
||||
client_id: "0000000000000000000SYNAPSE",
|
||||
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)
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("mas")
|
||||
.withLogConsumer(logger.getConsumer("mas"))
|
||||
.withConfig(config)
|
||||
.start();
|
||||
const container = await new MatrixAuthenticationServiceContainer(postgres)
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("mas")
|
||||
.withLogConsumer(logger.getConsumer("mas"))
|
||||
.withConfig(config)
|
||||
.start();
|
||||
|
||||
homeserver.withConfig({
|
||||
enable_registration: undefined,
|
||||
enable_registration_without_verification: undefined,
|
||||
disable_msisdn_registration: undefined,
|
||||
password_config: undefined,
|
||||
experimental_features: {
|
||||
msc3861: {
|
||||
enabled: true,
|
||||
issuer: `http://mas:8080/`,
|
||||
introspection_endpoint: "http://mas:8080/oauth2/introspect",
|
||||
client_id: config.clients[0].client_id,
|
||||
client_auth_method: config.clients[0].client_auth_method,
|
||||
client_secret: config.clients[0].client_secret,
|
||||
admin_token: config.matrix.secret,
|
||||
homeserver.withConfig({
|
||||
enable_registration: undefined,
|
||||
enable_registration_without_verification: undefined,
|
||||
disable_msisdn_registration: undefined,
|
||||
password_config: undefined,
|
||||
experimental_features: {
|
||||
msc3861: {
|
||||
enabled: true,
|
||||
issuer: `http://mas:8080/`,
|
||||
introspection_endpoint: "http://mas:8080/oauth2/introspect",
|
||||
client_id: config.clients[0].client_id,
|
||||
client_auth_method: config.clients[0].client_auth_method,
|
||||
client_secret: config.clients[0].client_secret,
|
||||
admin_token: config.matrix.secret,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await use(container);
|
||||
await container.stop();
|
||||
},
|
||||
await use(container);
|
||||
await container.stop();
|
||||
},
|
||||
{ scope: "worker" },
|
||||
],
|
||||
|
||||
config: async ({ homeserver, context, mas }, use) => {
|
||||
const issuer = `${mas.baseUrl}/`;
|
||||
|
||||
@@ -30,80 +30,111 @@ export interface Services {
|
||||
mas?: StartedMatrixAuthenticationServiceContainer;
|
||||
}
|
||||
|
||||
export const test = base.extend<Services>({
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
logger: async ({}, use, testInfo) => {
|
||||
const logger = new ContainerLogger();
|
||||
await use(logger);
|
||||
export const test = base.extend<{}, Services>({
|
||||
logger: [
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
async ({}, use) => {
|
||||
const logger = new ContainerLogger();
|
||||
await use(logger);
|
||||
},
|
||||
{ scope: "worker" },
|
||||
],
|
||||
network: [
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
async ({}, use) => {
|
||||
const network = await new Network().start();
|
||||
await use(network);
|
||||
await network.stop();
|
||||
},
|
||||
{ scope: "worker" },
|
||||
],
|
||||
postgres: [
|
||||
async ({ logger, network }, use) => {
|
||||
const container = await new PostgreSqlContainer()
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("postgres")
|
||||
.withLogConsumer(logger.getConsumer("postgres"))
|
||||
.withTmpFs({
|
||||
"/dev/shm/pgdata/data": "",
|
||||
})
|
||||
.withEnvironment({
|
||||
PG_DATA: "/dev/shm/pgdata/data",
|
||||
})
|
||||
.withCommand([
|
||||
"-c",
|
||||
"shared_buffers=128MB",
|
||||
"-c",
|
||||
`fsync=off`,
|
||||
"-c",
|
||||
`synchronous_commit=off`,
|
||||
"-c",
|
||||
"full_page_writes=off",
|
||||
])
|
||||
.start();
|
||||
await use(container);
|
||||
await container.stop();
|
||||
},
|
||||
{ scope: "worker" },
|
||||
],
|
||||
|
||||
mailhog: [
|
||||
async ({ logger, network }, use) => {
|
||||
const container = await new GenericContainer("mailhog/mailhog:latest")
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("mailhog")
|
||||
.withExposedPorts(8025)
|
||||
.withLogConsumer(logger.getConsumer("mailhog"))
|
||||
.withWaitStrategy(Wait.forListeningPorts())
|
||||
.start();
|
||||
await use(container);
|
||||
await container.stop();
|
||||
},
|
||||
{ scope: "worker" },
|
||||
],
|
||||
mailhogClient: [
|
||||
async ({ mailhog: container }, use) => {
|
||||
await use(mailhog({ host: container.getHost(), port: container.getMappedPort(8025) }));
|
||||
},
|
||||
{ scope: "worker" },
|
||||
],
|
||||
|
||||
synapseConfigOptions: [{}, { option: true, scope: "worker" }],
|
||||
_homeserver: [
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
async ({}, use) => {
|
||||
const container = new SynapseContainer();
|
||||
await use(container);
|
||||
},
|
||||
{ scope: "worker" },
|
||||
],
|
||||
homeserver: [
|
||||
async ({ logger, network, _homeserver: homeserver, synapseConfigOptions, mas }, use) => {
|
||||
const container = await homeserver
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("homeserver")
|
||||
.withLogConsumer(logger.getConsumer("synapse"))
|
||||
.withConfig(synapseConfigOptions)
|
||||
.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 ({ logger, context, request, homeserver }, use, testInfo) => {
|
||||
homeserver.setRequest(request);
|
||||
await logger.testStarted(testInfo);
|
||||
await use(context);
|
||||
await logger.testFinished(testInfo);
|
||||
},
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
network: async ({}, use) => {
|
||||
const network = await new Network().start();
|
||||
await use(network);
|
||||
await network.stop();
|
||||
},
|
||||
postgres: async ({ logger, network }, use) => {
|
||||
const container = await new PostgreSqlContainer()
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("postgres")
|
||||
.withLogConsumer(logger.getConsumer("postgres"))
|
||||
.withTmpFs({
|
||||
"/dev/shm/pgdata/data": "",
|
||||
})
|
||||
.withEnvironment({
|
||||
PG_DATA: "/dev/shm/pgdata/data",
|
||||
})
|
||||
.withCommand([
|
||||
"-c",
|
||||
"shared_buffers=128MB",
|
||||
"-c",
|
||||
`fsync=off`,
|
||||
"-c",
|
||||
`synchronous_commit=off`,
|
||||
"-c",
|
||||
"full_page_writes=off",
|
||||
])
|
||||
.start();
|
||||
await use(container);
|
||||
await container.stop();
|
||||
},
|
||||
|
||||
mailhog: async ({ logger, network }, use) => {
|
||||
const container = await new GenericContainer("mailhog/mailhog:latest")
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("mailhog")
|
||||
.withExposedPorts(8025)
|
||||
.withLogConsumer(logger.getConsumer("mailhog"))
|
||||
.withWaitStrategy(Wait.forListeningPorts())
|
||||
.start();
|
||||
await use(container);
|
||||
await container.stop();
|
||||
},
|
||||
mailhogClient: async ({ mailhog: container }, use) => {
|
||||
await use(mailhog({ host: container.getHost(), port: container.getMappedPort(8025) }));
|
||||
},
|
||||
|
||||
synapseConfigOptions: [{}, { option: true }],
|
||||
_homeserver: async ({ request }, use) => {
|
||||
const container = new SynapseContainer(request);
|
||||
await use(container);
|
||||
},
|
||||
homeserver: async ({ logger, network, _homeserver: homeserver, synapseConfigOptions, mas }, use) => {
|
||||
const container = await homeserver
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("homeserver")
|
||||
.withLogConsumer(logger.getConsumer("synapse"))
|
||||
.withConfig(synapseConfigOptions)
|
||||
.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 { APIRequestContext } from "@playwright/test";
|
||||
|
||||
import { StartedSynapseContainer } from "./synapse.ts";
|
||||
import { HomeserverInstance } from "../plugins/homeserver";
|
||||
@@ -16,4 +17,6 @@ export interface HomeserverContainer<Config> extends GenericContainer {
|
||||
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 { APIRequestContext } from "@playwright/test";
|
||||
import * as YAML from "yaml";
|
||||
import { set } from "lodash";
|
||||
|
||||
@@ -208,11 +207,7 @@ const DEFAULT_CONFIG = {
|
||||
export class DendriteContainer extends GenericContainer implements HomeserverContainer<typeof DEFAULT_CONFIG> {
|
||||
private config: typeof DEFAULT_CONFIG;
|
||||
|
||||
constructor(
|
||||
private request: APIRequestContext,
|
||||
image = "matrixdotorg/dendrite-monolith:main",
|
||||
binary = "/usr/bin/dendrite",
|
||||
) {
|
||||
constructor(image = "matrixdotorg/dendrite-monolith:main", binary = "/usr/bin/dendrite") {
|
||||
super(image);
|
||||
|
||||
this.config = deepCopy(DEFAULT_CONFIG);
|
||||
@@ -254,13 +249,12 @@ export class DendriteContainer extends GenericContainer implements HomeserverCon
|
||||
container,
|
||||
`http://${container.getHost()}:${container.getMappedPort(8008)}`,
|
||||
this.config.client_api.registration_shared_secret,
|
||||
this.request,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class PineconeContainer extends DendriteContainer {
|
||||
constructor(request: APIRequestContext) {
|
||||
super(request, "matrixdotorg/dendrite-demo-pinecone:main", "/usr/bin/dendrite-demo-pinecone");
|
||||
constructor() {
|
||||
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> {
|
||||
private config: typeof DEFAULT_CONFIG;
|
||||
|
||||
constructor(private readonly request: APIRequestContext) {
|
||||
constructor() {
|
||||
super(`ghcr.io/element-hq/synapse:${TAG}`);
|
||||
|
||||
this.config = deepCopy(DEFAULT_CONFIG);
|
||||
@@ -221,23 +221,26 @@ export class SynapseContainer extends GenericContainer implements HomeserverCont
|
||||
await super.start(),
|
||||
`http://localhost:${port}`,
|
||||
this.config.registration_shared_secret,
|
||||
this.request,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class StartedSynapseContainer extends AbstractStartedContainer implements StartedHomeserverContainer {
|
||||
private adminToken?: string;
|
||||
private request?: APIRequestContext;
|
||||
|
||||
constructor(
|
||||
container: StartedTestContainer,
|
||||
public readonly baseUrl: string,
|
||||
private readonly registrationSharedSecret: string,
|
||||
private readonly request: APIRequestContext,
|
||||
) {
|
||||
super(container);
|
||||
}
|
||||
|
||||
public setRequest(request: APIRequestContext): void {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
private async registerUserInternal(
|
||||
username: string,
|
||||
password: string,
|
||||
@@ -273,6 +276,7 @@ export class StartedSynapseContainer extends AbstractStartedContainer implements
|
||||
deviceId: data.device_id,
|
||||
password,
|
||||
displayName,
|
||||
username,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -300,6 +304,7 @@ export class StartedSynapseContainer extends AbstractStartedContainer implements
|
||||
userId: json.user_id,
|
||||
deviceId: json.device_id,
|
||||
homeServer: json.home_server,
|
||||
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) {
|
||||
if (testInfo.status !== "passed") {
|
||||
for (const container in this.logs) {
|
||||
|
||||