diff --git a/package.json b/package.json index 012b477c92..eadae62f4b 100644 --- a/package.json +++ b/package.json @@ -186,7 +186,7 @@ "@babel/runtime": "^7.12.5", "@casualbot/jest-sonar-reporter": "2.2.7", "@element-hq/element-call-embedded": "0.16.0", - "@element-hq/element-web-playwright-common": "^1.4.6", + "@element-hq/element-web-playwright-common": "^2.0.0", "@peculiar/webcrypto": "^1.4.3", "@playwright/test": "^1.50.1", "@principalstudio/html-webpack-inject-preload": "^1.2.7", diff --git a/playwright/e2e/crypto/history-sharing.spec.ts b/playwright/e2e/crypto/history-sharing.spec.ts new file mode 100644 index 0000000000..cdaa543bd9 --- /dev/null +++ b/playwright/e2e/crypto/history-sharing.spec.ts @@ -0,0 +1,59 @@ +/* +Copyright 2025 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import { createNewInstance } from "@element-hq/element-web-playwright-common"; + +import { expect, test } from "../../element-web-test"; +import { ElementAppPage } from "../../pages/ElementAppPage"; +import { createRoom, sendMessageInCurrentRoom } from "./utils"; + +test.use({ + displayName: "Alice", + labsFlags: ["feature_share_history_on_invite"], +}); + +/** Tests for MSC4268: encrypted history sharing */ +test.describe("History sharing", function () { + test( + "We should share history when sending invites", + { tag: "@screenshot" }, + async ( + { labsFlags, browser, page: alicePage, user: aliceCredentials, app: aliceElementApp, homeserver }, + testInfo, + ) => { + // In this test, Alice creates an encrypted room and sends an event; + // we then invite Bob, and ensure Bob can see the content. + + await aliceElementApp.client.bootstrapCrossSigning(aliceCredentials); + + // Register a second user, and open it in a second instance of the app + const bobCredentials = await homeserver.registerUser(`user_${testInfo.testId}_bob`, "password", "Bob"); + const bobPage = await createNewInstance(browser, bobCredentials, {}, labsFlags); + const bobElementApp = new ElementAppPage(bobPage); + await bobElementApp.client.bootstrapCrossSigning(bobCredentials); + + // Create the room and send a message + await createRoom(alicePage, "TestRoom", true); + await sendMessageInCurrentRoom(alicePage, "A message from Alice"); + + // Send the invite to Bob + await aliceElementApp.inviteUserToCurrentRoom(bobCredentials.userId); + + // Bob accepts the invite + await bobPage.getByRole("option", { name: "TestRoom" }).click(); + await bobPage.getByRole("button", { name: "Accept" }).click(); + + // Bob should now be able to decrypt the event + await expect(bobPage.getByText("A message from Alice")).toBeVisible(); + + const mask = [bobPage.locator(".mx_MessageTimestamp")]; + await expect(bobPage.locator(".mx_RoomView_body")).toMatchScreenshot("shared-history-invite-accepted.png", { + mask, + }); + }, + ); +}); diff --git a/playwright/e2e/knock/create-knock-room.spec.ts b/playwright/e2e/knock/create-knock-room.spec.ts index e21b30a3c2..d53271ef3c 100644 --- a/playwright/e2e/knock/create-knock-room.spec.ts +++ b/playwright/e2e/knock/create-knock-room.spec.ts @@ -27,8 +27,7 @@ test.describe("Create Knock Room", () => { await expect(page.locator(".mx_RoomHeader").getByText("Cybersecurity")).toBeVisible(); - const urlHash = await page.evaluate(() => window.location.hash); - const roomId = urlHash.replace("#/room/", ""); + const roomId = await app.getCurrentRoomIdFromUrl(); // Room should have a knock join rule await waitForRoom(page, app.client, roomId, (room) => { @@ -44,8 +43,7 @@ test.describe("Create Knock Room", () => { await expect(page.locator(".mx_RoomHeader").getByText("Cybersecurity")).toBeVisible(); - const urlHash = await page.evaluate(() => window.location.hash); - const roomId = urlHash.replace("#/room/", ""); + const roomId = await app.getCurrentRoomIdFromUrl(); await app.settings.openRoomSettings("Security & Privacy"); @@ -70,8 +68,7 @@ test.describe("Create Knock Room", () => { await expect(page.locator(".mx_RoomHeader").getByText("Cybersecurity")).toBeVisible(); - const urlHash = await page.evaluate(() => window.location.hash); - const roomId = urlHash.replace("#/room/", ""); + const roomId = await app.getCurrentRoomIdFromUrl(); // Room should have a knock join rule await waitForRoom(page, app.client, roomId, (room) => { diff --git a/playwright/e2e/login/utils.ts b/playwright/e2e/login/utils.ts index d74300908a..e9ff05bb69 100644 --- a/playwright/e2e/login/utils.ts +++ b/playwright/e2e/login/utils.ts @@ -51,6 +51,7 @@ export async function doTokenRegistration( await expect(page.getByRole("heading", { name: "Welcome Alice", exact: true })).toBeVisible(); return page.evaluate(() => ({ + homeserverBaseUrl: window.mxMatrixClientPeg.get().getHomeserverUrl(), accessToken: window.mxMatrixClientPeg.get().getAccessToken(), userId: window.mxMatrixClientPeg.get().getUserId(), deviceId: window.mxMatrixClientPeg.get().getDeviceId(), diff --git a/playwright/e2e/oidc/oidc-native.spec.ts b/playwright/e2e/oidc/oidc-native.spec.ts index 2fedc0c451..3d80c7d56f 100644 --- a/playwright/e2e/oidc/oidc-native.spec.ts +++ b/playwright/e2e/oidc/oidc-native.spec.ts @@ -6,9 +6,10 @@ 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 { type Config, CONFIG_JSON } from "@element-hq/element-web-playwright-common"; +import { type Config } from "@element-hq/element-web-playwright-common"; import { type Browser, type Page } from "@playwright/test"; import { type StartedHomeserverContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers/HomeserverContainer"; +import { routeConfigJson } from "@element-hq/element-web-playwright-common"; import { test, expect } from "../../element-web-test.ts"; import { logInAccountMas, registerAccountMas } from "."; @@ -242,17 +243,6 @@ async function verifyUsingOtherDevice(deviceToVerifyPage: Page, alreadyVerifiedD */ async function newContext(browser: Browser, config: Partial>, homeserver: StartedHomeserverContainer) { const otherContext = await browser.newContext(); - await otherContext.route(`http://localhost:8080/config.json*`, async (route) => { - const json = { - ...CONFIG_JSON, - ...config, - default_server_config: { - "m.homeserver": { - base_url: homeserver.baseUrl, - }, - }, - }; - await route.fulfill({ json }); - }); + await routeConfigJson(otherContext, homeserver.baseUrl, config); return otherContext; } diff --git a/playwright/pages/ElementAppPage.ts b/playwright/pages/ElementAppPage.ts index 816583fd49..82497317c3 100644 --- a/playwright/pages/ElementAppPage.ts +++ b/playwright/pages/ElementAppPage.ts @@ -69,6 +69,20 @@ export class ElementAppPage { return await this.page.evaluate(() => navigator.clipboard.readText()); } + /** + * Get the room ID from the current URL. + * + * @returns The room ID. + * @throws if the current URL does not contain a room ID. + */ + public async getCurrentRoomIdFromUrl(): Promise { + const urlHash = await this.page.evaluate(() => window.location.hash); + if (!urlHash.startsWith("#/room/")) { + throw new Error("URL hash suggests we are not in a room"); + } + return urlHash.replace("#/room/", ""); + } + /** * Opens the given room by name. The room must be visible in the * room list and the room may contain unread messages. @@ -197,6 +211,21 @@ export class ElementAppPage { return memberlist; } + /** + * Open the room info panel, and use it to send an invite to the given user. + * + * @param userId - The user to invite to the room. + */ + public async inviteUserToCurrentRoom(userId: string): Promise { + await this.toggleRoomInfoPanel(); // TODO skip this if the room info panel is already open + await this.page.getByLabel("Right panel").getByRole("menuitem", { name: "Invite" }).click(); + + const input = this.page.getByRole("dialog").getByTestId("invite-dialog-input"); + await input.fill(userId); + await input.press("Enter"); + await this.page.getByRole("dialog").getByRole("button", { name: "Invite" }).click(); + } + /** * Get a locator for the tooltip associated with an element * @param e The element with the tooltip diff --git a/playwright/plugins/homeserver/index.ts b/playwright/plugins/homeserver/index.ts index 0571cd9615..993853a3a2 100644 --- a/playwright/plugins/homeserver/index.ts +++ b/playwright/plugins/homeserver/index.ts @@ -6,7 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import { type ClientServerApi } from "@element-hq/element-web-playwright-common/lib/utils/api.js"; +import { type ClientServerApi, type Credentials } from "@element-hq/element-web-playwright-common/lib/utils/api.js"; +export { type Credentials } from "@element-hq/element-web-playwright-common/lib/utils/api.js"; export interface HomeserverInstance { readonly baseUrl: string; @@ -37,14 +38,4 @@ export interface HomeserverInstance { setThreepid(userId: string, medium: string, address: string): Promise; } -export interface Credentials { - accessToken: string; - userId: string; - deviceId: string; - homeServer: string; - password: string | null; // null for password-less users - displayName?: string; - username: string; // the localpart of the userId -} - export type HomeserverType = "synapse" | "dendrite" | "pinecone"; diff --git a/playwright/snapshots/crypto/history-sharing.spec.ts/shared-history-invite-accepted-linux.png b/playwright/snapshots/crypto/history-sharing.spec.ts/shared-history-invite-accepted-linux.png new file mode 100644 index 0000000000..9f2d1e23e4 Binary files /dev/null and b/playwright/snapshots/crypto/history-sharing.spec.ts/shared-history-invite-accepted-linux.png differ diff --git a/yarn.lock b/yarn.lock index daafc56d1b..d3d6b43319 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1738,10 +1738,10 @@ resolved "https://registry.yarnpkg.com/@element-hq/element-web-module-api/-/element-web-module-api-1.4.1.tgz#a46526d58985190f9989bf1686ea872687d3c6e1" integrity sha512-A8yaQtX7QoKThzzZVU+VYOFhpiNyppEMuIQijK48RvhVp1nwmy0cTD6u/6Yn64saNwJjtna+Oy+Qzo/TfwwhxQ== -"@element-hq/element-web-playwright-common@^1.4.6": - version "1.4.6" - resolved "https://registry.yarnpkg.com/@element-hq/element-web-playwright-common/-/element-web-playwright-common-1.4.6.tgz#a94d5d4ea94627aec430dd904c43f509a2e6c4b2" - integrity sha512-LJ4V6e6NrF2ikNCsxR93PFwDfcRUTY3b2reXwlFJeo44pj8vTYFxkuJwokibFx6+x1zkXWAIMh/0saTMRUXdSA== +"@element-hq/element-web-playwright-common@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@element-hq/element-web-playwright-common/-/element-web-playwright-common-2.0.0.tgz#30cf741a33c69540b4bc434f5349d0fe900bc611" + integrity sha512-axrWlPzP/OljYq53cefo9hha0SGDDu4HeM+sgevgbZSFSms8LsdMsMCyRyUcVBiGYb1xdKmM3RUsWOw5//eE+A== dependencies: "@axe-core/playwright" "^4.10.1" "@testcontainers/postgresql" "^11.0.0"