Playwright test for dehydrated devices with MAS (#34095)
* Device dehydration tests: use common util for recovery setup We have a utility method for setting up the recovery key, so let's use it. * Factor out shared method for creating encrypted rooms * Playwright test for device dehydration Add a playwright test to check that this stuff actually works. * Factor out `verifyAfterLogin` as a separate function * Playwright test for dehydrated devices with MAS
This commit is contained in:
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2026 Element Creations 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 { expect, test } from "../../element-web-test.ts";
|
||||||
|
import { masHomeserver } from "../../plugins/homeserver/synapse/masHomeserver.ts";
|
||||||
|
import {
|
||||||
|
autoJoin,
|
||||||
|
createSharedEncryptedRoomWithUser,
|
||||||
|
enableKeyBackup,
|
||||||
|
logOutOfElement,
|
||||||
|
verifyAfterLogin,
|
||||||
|
} from "./utils.ts";
|
||||||
|
import { registerAccountMas } from "../oidc";
|
||||||
|
import { Bot } from "../../pages/bot.ts";
|
||||||
|
|
||||||
|
test.use({
|
||||||
|
...masHomeserver,
|
||||||
|
synapseConfig: {
|
||||||
|
experimental_features: {
|
||||||
|
msc2697_enabled: false,
|
||||||
|
msc3814_enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("Device dehydration, on a MAS-enabled homeserver", () => {
|
||||||
|
test("Can read messages sent while logged out", async ({ mailpitClient, homeserver, page, app }, testInfo) => {
|
||||||
|
test.slow();
|
||||||
|
const aliceUserId = `alice_${testInfo.testId}`;
|
||||||
|
const alicePassword = "Pa$sW0rD!";
|
||||||
|
|
||||||
|
const recoveryKey =
|
||||||
|
await test.step("Alice registers and sets up recovery => a dehydrated device is created", async () => {
|
||||||
|
await page.goto("/#/login");
|
||||||
|
await page.getByRole("button", { name: "Continue" }).click();
|
||||||
|
|
||||||
|
await registerAccountMas(page, mailpitClient, aliceUserId, `${aliceUserId}@email.com`, alicePassword);
|
||||||
|
return await enableKeyBackup(app);
|
||||||
|
});
|
||||||
|
|
||||||
|
const [bob, testRoomId] = await test.step("Bob registers and joins a room with Alice", async () => {
|
||||||
|
const bob = new Bot(page, homeserver, { displayName: "Bob" });
|
||||||
|
await autoJoin(bob);
|
||||||
|
|
||||||
|
// Create an encrypted room, and wait for Bob to join it.
|
||||||
|
const testRoomId = await createSharedEncryptedRoomWithUser(app, bob.credentials.userId);
|
||||||
|
|
||||||
|
// Even though Alice has seen Bob's join event, Bob may not have done so yet. Wait for the sync to arrive.
|
||||||
|
await bob.awaitRoomMembership(testRoomId);
|
||||||
|
return [bob, testRoomId];
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step("Alice logs out", async () => {
|
||||||
|
await logOutOfElement(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step("Bob sends a message", async () => {
|
||||||
|
await bob.sendMessage(testRoomId, "test encrypted 1");
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step("Alice logs in again", async () => {
|
||||||
|
await page.getByRole("link", { name: "Sign in" }).click();
|
||||||
|
await page.getByRole("button", { name: "Continue" }).click();
|
||||||
|
|
||||||
|
await expect(page.getByText("Continue to Element?")).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Continue" }).click();
|
||||||
|
|
||||||
|
await verifyAfterLogin(page, recoveryKey);
|
||||||
|
await app.viewRoomById(testRoomId);
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step("Alice can decrypt Bob's message", async () => {
|
||||||
|
await expect(page.getByText("test encrypted 1")).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -14,8 +14,8 @@ import {
|
|||||||
createSharedEncryptedRoomWithUser,
|
createSharedEncryptedRoomWithUser,
|
||||||
enableKeyBackup,
|
enableKeyBackup,
|
||||||
logIntoElement,
|
logIntoElement,
|
||||||
logIntoElementAndVerify,
|
|
||||||
logOutOfElement,
|
logOutOfElement,
|
||||||
|
verifyAfterLogin,
|
||||||
} from "./utils.ts";
|
} from "./utils.ts";
|
||||||
import { type Client } from "../../pages/client.ts";
|
import { type Client } from "../../pages/client.ts";
|
||||||
import { type ElementAppPage } from "../../pages/ElementAppPage.ts";
|
import { type ElementAppPage } from "../../pages/ElementAppPage.ts";
|
||||||
@@ -151,7 +151,8 @@ test.describe("Dehydration", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await test.step("Alice logs back in, and should be able to view Bob's message", async () => {
|
await test.step("Alice logs back in, and should be able to view Bob's message", async () => {
|
||||||
await logIntoElementAndVerify(page, credentials, recoveryKey);
|
await logIntoElement(page, credentials);
|
||||||
|
await verifyAfterLogin(page, recoveryKey);
|
||||||
await app.viewRoomById(testRoomId);
|
await app.viewRoomById(testRoomId);
|
||||||
await expect(page.getByText("test encrypted 1")).toBeVisible();
|
await expect(page.getByText("test encrypted 1")).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,9 +14,10 @@ import {
|
|||||||
createSecondBotDevice,
|
createSecondBotDevice,
|
||||||
createSharedEncryptedRoomWithUser,
|
createSharedEncryptedRoomWithUser,
|
||||||
enableKeyBackup,
|
enableKeyBackup,
|
||||||
logIntoElementAndVerify,
|
logIntoElement,
|
||||||
logOutOfElement,
|
logOutOfElement,
|
||||||
verify,
|
verify,
|
||||||
|
verifyAfterLogin,
|
||||||
waitForDevices,
|
waitForDevices,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
import { bootstrapCrossSigningForClient } from "../../pages/client.ts";
|
import { bootstrapCrossSigningForClient } from "../../pages/client.ts";
|
||||||
@@ -170,7 +171,8 @@ test.describe("Cryptography", function () {
|
|||||||
window.localStorage.clear();
|
window.localStorage.clear();
|
||||||
});
|
});
|
||||||
await page.reload();
|
await page.reload();
|
||||||
await logIntoElementAndVerify(page, aliceCredentials, securityKey);
|
await logIntoElement(page, aliceCredentials);
|
||||||
|
await verifyAfterLogin(page, securityKey);
|
||||||
|
|
||||||
/* go back to the test room and find Bob's message again */
|
/* go back to the test room and find Bob's message again */
|
||||||
await app.viewRoomById(testRoomId);
|
await app.viewRoomById(testRoomId);
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
import { type GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api";
|
import { type GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api";
|
||||||
import { assertNoToasts, getToast, rejectToast } from "@element-hq/element-web-playwright-common";
|
import { assertNoToasts, getToast, rejectToast } from "@element-hq/element-web-playwright-common";
|
||||||
|
|
||||||
import { test, expect } from "../../element-web-test";
|
import { expect, test } from "../../element-web-test";
|
||||||
import { createBot, deleteCachedSecrets, disableKeyBackup, logIntoElement, logIntoElementAndVerify } from "./utils";
|
import { createBot, deleteCachedSecrets, disableKeyBackup, logIntoElement, verifyAfterLogin } from "./utils";
|
||||||
import { type Bot } from "../../pages/bot";
|
import { type Bot } from "../../pages/bot";
|
||||||
|
|
||||||
// Mask the background of the screenshot to avoid failing the test just because some
|
// Mask the background of the screenshot to avoid failing the test just because some
|
||||||
@@ -29,7 +29,8 @@ test.describe("Key storage out of sync toast", () => {
|
|||||||
const res = await createBot(page, homeserver, credentials);
|
const res = await createBot(page, homeserver, credentials);
|
||||||
recoveryKey = res.recoveryKey;
|
recoveryKey = res.recoveryKey;
|
||||||
|
|
||||||
await logIntoElementAndVerify(page, credentials, recoveryKey.encodedPrivateKey);
|
await logIntoElement(page, credentials);
|
||||||
|
await verifyAfterLogin(page, recoveryKey.encodedPrivateKey);
|
||||||
|
|
||||||
await deleteCachedSecrets(page);
|
await deleteCachedSecrets(page);
|
||||||
});
|
});
|
||||||
@@ -71,7 +72,8 @@ test.describe("'Turn on key storage' toast", () => {
|
|||||||
const recoveryKey = res.recoveryKey;
|
const recoveryKey = res.recoveryKey;
|
||||||
botClient = res.botClient;
|
botClient = res.botClient;
|
||||||
|
|
||||||
await logIntoElementAndVerify(page, credentials, recoveryKey.encodedPrivateKey);
|
await logIntoElement(page, credentials);
|
||||||
|
await verifyAfterLogin(page, recoveryKey.encodedPrivateKey);
|
||||||
|
|
||||||
// We won't be prompted for crypto setup unless we have an e2e room, so make one
|
// We won't be prompted for crypto setup unless we have an e2e room, so make one
|
||||||
await page
|
await page
|
||||||
|
|||||||
@@ -216,14 +216,14 @@ export async function logIntoElement(page: Page, credentials: Credentials) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fill in the login form in Element with the given creds, and then complete the `CompleteSecurity` step, using the
|
* Complete the `CompleteSecurity` step that happens after login, using the given recovery key.
|
||||||
* given recovery key. (Normally this will verify the new device using the secrets from 4S.)
|
* (Normally this will verify the new device using the secrets from 4S.)
|
||||||
*
|
*
|
||||||
* Afterwards, waits for the application to redirect to the home page.
|
* Afterwards, waits for the application to redirect to the home page.
|
||||||
|
*
|
||||||
|
* This is normally useful after a call to {@link logIntoElement} or {@link logInAccountMas}.
|
||||||
*/
|
*/
|
||||||
export async function logIntoElementAndVerify(page: Page, credentials: Credentials, recoveryKey: string) {
|
export async function verifyAfterLogin(page: Page, recoveryKey: string) {
|
||||||
await logIntoElement(page, credentials);
|
|
||||||
|
|
||||||
await page.locator(".mx_AuthPage").getByRole("button", { name: "Use recovery key" }).click();
|
await page.locator(".mx_AuthPage").getByRole("button", { name: "Use recovery key" }).click();
|
||||||
|
|
||||||
const useSecurityKey = page.locator(".mx_Dialog").getByRole("button", { name: "Use recovery key" });
|
const useSecurityKey = page.locator(".mx_Dialog").getByRole("button", { name: "Use recovery key" });
|
||||||
|
|||||||
Reference in New Issue
Block a user