Playwright test for device rehydration (#34091)
* 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.
This commit is contained in:
@@ -8,7 +8,15 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import { test, expect } from "../../element-web-test";
|
||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
||||
import { createBot, logIntoElement } from "./utils.ts";
|
||||
import {
|
||||
autoJoin,
|
||||
createBot,
|
||||
createSharedEncryptedRoomWithUser,
|
||||
enableKeyBackup,
|
||||
logIntoElement,
|
||||
logIntoElementAndVerify,
|
||||
logOutOfElement,
|
||||
} from "./utils.ts";
|
||||
import { type Client } from "../../pages/client.ts";
|
||||
import { type ElementAppPage } from "../../pages/ElementAppPage.ts";
|
||||
|
||||
@@ -40,15 +48,10 @@ test.describe("Dehydration", () => {
|
||||
await settings.getByRole("button", { name: "Verify this device" }).click();
|
||||
await page.getByRole("button", { name: "Can't confirm?" }).click();
|
||||
await page.getByRole("button", { name: "Continue" }).click();
|
||||
await app.closeDialog();
|
||||
|
||||
// Set up recovery
|
||||
await page.getByRole("button", { name: "Get recovery key" }).click();
|
||||
await page.getByRole("button", { name: "Continue" }).click();
|
||||
const recoveryKey = await page.getByTestId("recoveryKey").innerText();
|
||||
await page.getByRole("button", { name: "Continue" }).click();
|
||||
await page.getByRole("textbox").fill(recoveryKey);
|
||||
await page.getByRole("button", { name: "Finish set up" }).click();
|
||||
await page.getByRole("button", { name: "Close" }).click();
|
||||
await enableKeyBackup(app);
|
||||
|
||||
await expectDehydratedDeviceEnabled(app);
|
||||
|
||||
@@ -61,28 +64,7 @@ test.describe("Dehydration", () => {
|
||||
|
||||
test("'Get recovery key' creates dehydrated device", async ({ app, credentials, page }) => {
|
||||
await logIntoElement(page, credentials);
|
||||
|
||||
const settingsDialogLocator = await app.settings.openUserSettings("Encryption");
|
||||
await settingsDialogLocator.getByRole("button", { name: "Get recovery key" }).click();
|
||||
|
||||
// First it displays an informative panel about the recovery key
|
||||
await expect(settingsDialogLocator.getByRole("heading", { name: "Get recovery key" })).toBeVisible();
|
||||
await settingsDialogLocator.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
// Next, it displays the new recovery key. We click on the copy button.
|
||||
await expect(settingsDialogLocator.getByText("Save your recovery key somewhere safe")).toBeVisible();
|
||||
await settingsDialogLocator.getByRole("button", { name: "Copy" }).click();
|
||||
const recoveryKey = await app.getClipboard();
|
||||
await settingsDialogLocator.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
await expect(
|
||||
settingsDialogLocator.getByText("Enter your recovery key to confirm", { exact: true }),
|
||||
).toBeVisible();
|
||||
await settingsDialogLocator.getByRole("textbox").fill(recoveryKey);
|
||||
await settingsDialogLocator.getByRole("button", { name: "Finish set up" }).click();
|
||||
|
||||
await app.settings.closeDialog();
|
||||
|
||||
await enableKeyBackup(app);
|
||||
await expectDehydratedDeviceEnabled(app);
|
||||
});
|
||||
|
||||
@@ -115,13 +97,7 @@ test.describe("Dehydration", () => {
|
||||
await page.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
// And set up recovery
|
||||
const settings = await app.settings.openUserSettings("Encryption");
|
||||
await settings.getByRole("button", { name: "Get recovery key" }).click();
|
||||
await settings.getByRole("button", { name: "Continue" }).click();
|
||||
const recoveryKey = await settings.getByTestId("recoveryKey").innerText();
|
||||
await settings.getByRole("button", { name: "Continue" }).click();
|
||||
await settings.getByRole("textbox").fill(recoveryKey);
|
||||
await settings.getByRole("button", { name: "Finish set up" }).click();
|
||||
await enableKeyBackup(app);
|
||||
|
||||
// There should be a brand new dehydrated device
|
||||
await expectDehydratedDeviceEnabled(app);
|
||||
@@ -132,34 +108,54 @@ test.describe("Dehydration", () => {
|
||||
|
||||
// Create a dehydrated device by setting up recovery (see "'Set up
|
||||
// recovery' creates dehydrated device" test above)
|
||||
const settingsDialogLocator = await app.settings.openUserSettings("Encryption");
|
||||
await settingsDialogLocator.getByRole("button", { name: "Get recovery key" }).click();
|
||||
|
||||
// First it displays an informative panel about the recovery key
|
||||
await expect(settingsDialogLocator.getByRole("heading", { name: "Get recovery key" })).toBeVisible();
|
||||
await settingsDialogLocator.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
// Next, it displays the new recovery key. We click on the copy button.
|
||||
await expect(settingsDialogLocator.getByText("Save your recovery key somewhere safe")).toBeVisible();
|
||||
await settingsDialogLocator.getByRole("button", { name: "Copy" }).click();
|
||||
const recoveryKey = await app.getClipboard();
|
||||
await settingsDialogLocator.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
await expect(
|
||||
settingsDialogLocator.getByText("Enter your recovery key to confirm", { exact: true }),
|
||||
).toBeVisible();
|
||||
await settingsDialogLocator.getByRole("textbox").fill(recoveryKey);
|
||||
await settingsDialogLocator.getByRole("button", { name: "Finish set up" }).click();
|
||||
|
||||
await enableKeyBackup(app);
|
||||
await expectDehydratedDeviceEnabled(app);
|
||||
|
||||
// After recovery is set up, we reset our cryptographic identity, which
|
||||
// should drop the dehydrated device.
|
||||
const settingsDialogLocator = await app.settings.openUserSettings("Encryption");
|
||||
await settingsDialogLocator.getByRole("button", { name: "Reset cryptographic identity" }).click();
|
||||
await settingsDialogLocator.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
await expectDehydratedDeviceDisabled(app);
|
||||
});
|
||||
|
||||
test("Can read messages sent while logged out", async ({ page, user: credentials, app, bot: bob }) => {
|
||||
const recoveryKey =
|
||||
await test.step("Alice sets up cross-signing and recovery => a dehydrated device is created", async () => {
|
||||
// Create an identity, then set up recovery, to create a dehydrated device.
|
||||
await app.client.bootstrapCrossSigning(credentials);
|
||||
const recoveryKey = await enableKeyBackup(app);
|
||||
|
||||
await expectDehydratedDeviceEnabled(app);
|
||||
return recoveryKey;
|
||||
});
|
||||
|
||||
const testRoomId = await test.step("Bob and Alice make a shared room", async () => {
|
||||
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 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 back in, and should be able to view Bob's message", async () => {
|
||||
await logIntoElementAndVerify(page, credentials, recoveryKey);
|
||||
await app.viewRoomById(testRoomId);
|
||||
await expect(page.getByText("test encrypted 1")).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function getDehydratedDeviceIds(client: Client): Promise<string[]> {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { expect, test } from "../../element-web-test";
|
||||
import {
|
||||
autoJoin,
|
||||
createSecondBotDevice,
|
||||
createSharedRoomWithUser,
|
||||
createSharedEncryptedRoomWithUser,
|
||||
enableKeyBackup,
|
||||
logIntoElementAndVerify,
|
||||
logOutOfElement,
|
||||
@@ -39,18 +39,7 @@ test.describe("Cryptography", function () {
|
||||
await autoJoin(bob);
|
||||
|
||||
// create an encrypted room, and wait for Bob to join it.
|
||||
testRoomId = await createSharedRoomWithUser(app, bob.credentials.userId, {
|
||||
name: "TestRoom",
|
||||
initial_state: [
|
||||
{
|
||||
type: "m.room.encryption",
|
||||
state_key: "",
|
||||
content: {
|
||||
algorithm: "m.megolm.v1.aes-sha2",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
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);
|
||||
|
||||
@@ -6,7 +6,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { expect, test } from "../../element-web-test";
|
||||
import { autoJoin, createSecondBotDevice, createSharedRoomWithUser, verify } from "./utils";
|
||||
import { autoJoin, createSecondBotDevice, createSharedEncryptedRoomWithUser, verify } from "./utils";
|
||||
import { bootstrapCrossSigningForClient } from "../../pages/client.ts";
|
||||
|
||||
/** Tests for the "invisible crypto" behaviour -- i.e., when the "exclude insecure devices" setting is enabled */
|
||||
@@ -29,18 +29,7 @@ test.describe("Invisible cryptography", () => {
|
||||
await autoJoin(bob);
|
||||
|
||||
// create an encrypted room
|
||||
const testRoomId = await createSharedRoomWithUser(app, bob.credentials.userId, {
|
||||
name: "TestRoom",
|
||||
initial_state: [
|
||||
{
|
||||
type: "m.room.encryption",
|
||||
state_key: "",
|
||||
content: {
|
||||
algorithm: "m.megolm.v1.aes-sha2",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
const testRoomId = await createSharedEncryptedRoomWithUser(app, bob.credentials.userId);
|
||||
|
||||
// Verify Bob
|
||||
await verify(app, bob);
|
||||
|
||||
@@ -406,6 +406,7 @@ export async function copyAndContinue(page: Page) {
|
||||
* @param opts - other options for the createRoom call
|
||||
*
|
||||
* @returns a promise which resolves to the room ID
|
||||
* @see createSharedEncryptedRoomWithUser
|
||||
*/
|
||||
export async function createSharedRoomWithUser(
|
||||
app: ElementAppPage,
|
||||
@@ -423,6 +424,32 @@ export async function createSharedRoomWithUser(
|
||||
return roomId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a shared, encrypted room with the given user, and wait for them to join
|
||||
*
|
||||
* @param other - UserID of the other user
|
||||
* @param opts - other options for the createRoom call
|
||||
*
|
||||
* @returns a promise which resolves to the room ID
|
||||
* @see createSharedRoomWithUser
|
||||
*/
|
||||
export async function createSharedEncryptedRoomWithUser(
|
||||
app: ElementAppPage,
|
||||
other: string,
|
||||
opts: Omit<ICreateRoomOpts, "invite"> = { name: "TestRoom" },
|
||||
): Promise<string> {
|
||||
opts = structuredClone(opts);
|
||||
opts.initial_state ??= [];
|
||||
opts.initial_state.push({
|
||||
type: "m.room.encryption",
|
||||
state_key: "",
|
||||
content: {
|
||||
algorithm: "m.megolm.v1.aes-sha2",
|
||||
},
|
||||
});
|
||||
return createSharedRoomWithUser(app, other, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message in the current room
|
||||
* @param page
|
||||
|
||||
Reference in New Issue
Block a user