diff --git a/playwright/e2e/crypto/decryption-failure-messages.spec.ts b/playwright/e2e/crypto/decryption-failure-messages.spec.ts index f36b445660..e0343a8005 100644 --- a/playwright/e2e/crypto/decryption-failure-messages.spec.ts +++ b/playwright/e2e/crypto/decryption-failure-messages.spec.ts @@ -30,76 +30,80 @@ test.describe("Cryptography", function () { test.describe("decryption failure messages", () => { test.skip(isDendrite, "Dendrite lacks support for MSC3967 so requires additional auth here"); - test("should handle device-relative historical messages", async ({ - homeserver, - page, - app, - credentials, - user, - }) => { - test.setTimeout(60000); + test( + "should handle device-relative historical messages", + { tag: "@screenshot" }, + async ({ homeserver, page, app, credentials, user }) => { + test.setTimeout(60000); - // Start with a logged-in session, without key backup, and send a message. - await createRoom(page, "Test room", true); - await sendMessageInCurrentRoom(page, "test test"); + // Start with a logged-in session, without key backup, and send a message. + await createRoom(page, "Test room", true); + await sendMessageInCurrentRoom(page, "test test"); - // Log out, discarding the key for the sent message. - await logOutOfElement(page, true); + // Log out, discarding the key for the sent message. + await logOutOfElement(page, true); - // Log in again, and see how the message looks. - await logIntoElement(page, credentials); - await app.viewRoomByName("Test room"); - const lastTile = page.locator(".mx_EventTile").last(); - await expect(lastTile).toContainText("Historical messages are not available on this device"); - await expect(lastTile.locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName( - "This message could not be decrypted", - ); - await expect(lastTile).toMatchScreenshot("history-not-available.png"); - - // Now, we set up key backup, and then send another message. - const secretStorageKey = await enableKeyBackup(app); - await app.viewRoomByName("Test room"); - await sendMessageInCurrentRoom(page, "test2 test2"); - - // Workaround for https://github.com/element-hq/element-web/issues/27267. It can take up to 10 seconds for - // the key to be backed up. - await page.waitForTimeout(10000); - - // Finally, log out again, and back in, skipping verification for now, and see what we see. - await logOutOfElement(page); - await logIntoElement(page, credentials); - await page.locator(".mx_AuthPage").getByRole("button", { name: "Skip verification for now" }).click(); - await page.locator(".mx_AuthPage").getByRole("button", { name: "I'll verify later" }).click(); - await app.viewRoomByName("Test room"); - - // In this case, the call to cryptoApi.isEncryptionEnabledInRoom is taking a long time to resolve - await page.waitForTimeout(1000); - - // There should be two historical events in the timeline - const tiles = await page.locator(".mx_EventTile").all(); - expect(tiles.length).toBeGreaterThanOrEqual(2); - // look at the last two tiles only - for (const tile of tiles.slice(-2)) { - await expect(tile).toContainText("You need to verify this device for access to historical messages"); - await expect(tile.locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName( + // Log in again, and see how the message looks. + await logIntoElement(page, credentials); + await app.viewRoomByName("Test room"); + const lastTile = page.locator(".mx_EventTile").last(); + await expect(lastTile).toContainText("Historical messages are not available on this device"); + await expect(lastTile.locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName( "This message could not be decrypted", ); - } + await expect(lastTile).toMatchScreenshot("history-not-available.png", { + mask: [page.locator(".mx_MessageTimestamp")], + }); - // Now verify our device (setting up key backup), and check what happens - await verifySession(app, secretStorageKey); - const tilesAfterVerify = (await page.locator(".mx_EventTile").all()).slice(-2); + // Now, we set up key backup, and then send another message. + const secretStorageKey = await enableKeyBackup(app); + await app.viewRoomByName("Test room"); + await sendMessageInCurrentRoom(page, "test2 test2"); - // The first message still cannot be decrypted, because it was never backed up. It's now a regular UTD though. - await expect(tilesAfterVerify[0]).toContainText("Unable to decrypt message"); - await expect(tilesAfterVerify[0].locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName( - "This message could not be decrypted", - ); + // Workaround for https://github.com/element-hq/element-web/issues/27267. It can take up to 10 seconds for + // the key to be backed up. + await page.waitForTimeout(10000); - // The second message should now be decrypted, with a grey shield - await expect(tilesAfterVerify[1]).toContainText("test2 test2"); - await expect(tilesAfterVerify[1].locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName("TODO"); - }); + // Finally, log out again, and back in, skipping verification for now, and see what we see. + await logOutOfElement(page); + await logIntoElement(page, credentials); + await page.locator(".mx_AuthPage").getByRole("button", { name: "Skip verification for now" }).click(); + await page.locator(".mx_AuthPage").getByRole("button", { name: "I'll verify later" }).click(); + await app.viewRoomByName("Test room"); + + // In this case, the call to cryptoApi.isEncryptionEnabledInRoom is taking a long time to resolve + await page.waitForTimeout(1000); + + // There should be two historical events in the timeline + const tiles = await page.locator(".mx_EventTile").all(); + expect(tiles.length).toBeGreaterThanOrEqual(2); + // look at the last two tiles only + for (const tile of tiles.slice(-2)) { + await expect(tile).toContainText( + "You need to verify this device for access to historical messages", + ); + await expect(tile.locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName( + "This message could not be decrypted", + ); + } + + // Now verify our device (setting up key backup), and check what happens + await verifySession(app, secretStorageKey); + const tilesAfterVerify = (await page.locator(".mx_EventTile").all()).slice(-2); + + // The first message still cannot be decrypted, because it was never backed up. It's now a regular UTD though. + await expect(tilesAfterVerify[0]).toContainText("Unable to decrypt message"); + await expect(tilesAfterVerify[0].locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName( + "This message could not be decrypted", + ); + + // The second message should now be decrypted, with a grey shield + await expect(tilesAfterVerify[1]).toContainText("test2 test2"); + await expect(tilesAfterVerify[1].locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName( + "The authenticity of this encrypted message can't be guaranteed on this device.", + ); + }, + ); test.describe("non-joined historical messages", () => { test.skip(isDendrite, "does not yet support membership on events"); diff --git a/playwright/e2e/crypto/event-shields.spec.ts b/playwright/e2e/crypto/event-shields.spec.ts index eb77bb3a0c..688b81b5b1 100644 --- a/playwright/e2e/crypto/event-shields.spec.ts +++ b/playwright/e2e/crypto/event-shields.spec.ts @@ -150,55 +150,53 @@ test.describe("Cryptography", function () { }, ); - test("Should show a grey padlock for a key restored from backup", async ({ - page, - app, - bot: bob, - homeserver, - user: aliceCredentials, - }) => { - test.slow(); - const securityKey = await enableKeyBackup(app); + test( + "Should show a grey padlock for a key restored from backup", + { tag: "@screenshot" }, + async ({ page, app, bot: bob, homeserver, user: aliceCredentials }) => { + test.slow(); + const securityKey = await enableKeyBackup(app); - // bob sends a valid event - await bob.sendMessage(testRoomId, "test encrypted 1"); + // bob sends a valid event + await bob.sendMessage(testRoomId, "test encrypted 1"); - const lastTile = page.locator(".mx_EventTile_last"); - const lastTileE2eIcon = lastTile.locator(".mx_EventTile_e2eIcon"); - await expect(lastTile).toContainText("test encrypted 1"); - // no e2e icon - await expect(lastTileE2eIcon).not.toBeVisible(); + const lastTile = page.locator(".mx_EventTile_last"); + const lastTileE2eIcon = lastTile.locator(".mx_EventTile_e2eIcon"); + await expect(lastTile).toContainText("test encrypted 1"); + // no e2e icon + await expect(lastTileE2eIcon).not.toBeVisible(); - // Workaround for https://github.com/element-hq/element-web/issues/27267. It can take up to 10 seconds for - // the key to be backed up. - await page.waitForTimeout(10000); + // Workaround for https://github.com/element-hq/element-web/issues/27267. It can take up to 10 seconds for + // the key to be backed up. + await page.waitForTimeout(10000); - /* log out, and back in */ - await logOutOfElement(page); - // Reload to work around a Rust crypto bug where it can hold onto the indexeddb even after logout - // https://github.com/element-hq/element-web/issues/25779 - await page.addInitScript(() => { - // When we reload, the initScript created by the `user`/`pageWithCredentials` fixtures - // will re-inject the original credentials into localStorage, which we don't want. - // To work around, we add a second initScript which will clear localStorage again. - window.localStorage.clear(); - }); - await page.reload(); - await logIntoElementAndVerify(page, aliceCredentials, securityKey); + /* log out, and back in */ + await logOutOfElement(page); + // Reload to work around a Rust crypto bug where it can hold onto the indexeddb even after logout + // https://github.com/element-hq/element-web/issues/25779 + await page.addInitScript(() => { + // When we reload, the initScript created by the `user`/`pageWithCredentials` fixtures + // will re-inject the original credentials into localStorage, which we don't want. + // To work around, we add a second initScript which will clear localStorage again. + window.localStorage.clear(); + }); + await page.reload(); + await logIntoElementAndVerify(page, aliceCredentials, securityKey); - /* go back to the test room and find Bob's message again */ - await app.viewRoomById(testRoomId); - await expect(lastTile).toContainText("test encrypted 1"); - // The gray shield would be a Compound info icon. The red shield would be a Compound error solid icon. - // No shield would have no div mx_EventTile_e2eIcon at all. - // The key is coming from backup, so it is not anymore possible to establish if the claimed device - // creator of this key is authentic. The tooltip should be "The authenticity of this encrypted message can't be guaranteed on this device." - // It is not "Encrypted by an unknown or deleted device." even if the claimed device is actually deleted. - await expect(lastTileE2eIcon).toHaveAccessibleName( - "The authenticity of this encrypted message can't be guaranteed on this device.", - ); - await expect(lastTileE2eIcon).toMatchScreenshot("event-shield-authenticity.png"); - }); + /* go back to the test room and find Bob's message again */ + await app.viewRoomById(testRoomId); + await expect(lastTile).toContainText("test encrypted 1"); + // The gray shield would be a Compound info icon. The red shield would be a Compound error solid icon. + // No shield would have no div mx_EventTile_e2eIcon at all. + // The key is coming from backup, so it is not anymore possible to establish if the claimed device + // creator of this key is authentic. The tooltip should be "The authenticity of this encrypted message can't be guaranteed on this device." + // It is not "Encrypted by an unknown or deleted device." even if the claimed device is actually deleted. + await expect(lastTileE2eIcon).toHaveAccessibleName( + "The authenticity of this encrypted message can't be guaranteed on this device.", + ); + await expect(lastTileE2eIcon).toMatchScreenshot("event-shield-authenticity.png"); + }, + ); test("should show the correct shield on edited e2e events", async ({ page, app, bot: bob, homeserver }) => { // bob has a second, not cross-signed, device @@ -249,67 +247,65 @@ test.describe("Cryptography", function () { ).not.toBeVisible(); }); - test("should show correct shields on events sent by devices which have since been deleted", async ({ - page, - app, - bot: bob, - homeserver, - }) => { - // Workaround for https://github.com/element-hq/element-web/issues/28640: - // make sure that Alice has seen Bob's identity before she goes offline. We do this by opening - // his user info. - await waitForDevices(app, bob.credentials.userId, 1); + test( + "should show correct shields on events sent by devices which have since been deleted", + { tag: "@screenshot" }, + async ({ page, app, bot: bob, homeserver }) => { + // Workaround for https://github.com/element-hq/element-web/issues/28640: + // make sure that Alice has seen Bob's identity before she goes offline. We do this by opening + // his user info. + await waitForDevices(app, bob.credentials.userId, 1); - // Our app is blocked from syncing while Bob sends his messages. - await app.client.network.goOffline(); + // Our app is blocked from syncing while Bob sends his messages. + await app.client.network.goOffline(); - // Bob sends a message from his verified device - await bob.sendMessage(testRoomId, "test encrypted from verified"); + // Bob sends a message from his verified device + await bob.sendMessage(testRoomId, "test encrypted from verified"); - // And one from a second, not cross-signed, device - const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob); - await bobSecondDevice.waitForNextSync(); // make sure the client knows the room is encrypted - await bobSecondDevice.sendMessage(testRoomId, "test encrypted from unverified"); + // And one from a second, not cross-signed, device + const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob); + await bobSecondDevice.waitForNextSync(); // make sure the client knows the room is encrypted + await bobSecondDevice.sendMessage(testRoomId, "test encrypted from unverified"); - // ... and then logs out both devices. - await bob.evaluate((cli) => cli.logout(true)); - await bobSecondDevice.evaluate((cli) => cli.logout(true)); + // ... and then logs out both devices. + await bob.evaluate((cli) => cli.logout(true)); + await bobSecondDevice.evaluate((cli) => cli.logout(true)); - // Let our app start syncing again - await app.client.network.goOnline(); + // Let our app start syncing again + await app.client.network.goOnline(); - // Wait for the messages to arrive. It can take quite a while for the sync to wake up. - const last = page.locator(".mx_EventTile_last"); - await expect(last).toContainText("test encrypted from unverified", { timeout: 20000 }); - const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon"); - await expect(lastE2eIcon).toHaveAccessibleName("Encrypted by a device not verified by its owner."); - await expect(lastE2eIcon).toMatchScreenshot("event-shield-not-verified.png"); + // Wait for the messages to arrive. It can take quite a while for the sync to wake up. + const last = page.locator(".mx_EventTile_last"); + await expect(last).toContainText("test encrypted from unverified", { timeout: 20000 }); + const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon"); + await expect(lastE2eIcon).toHaveAccessibleName("Encrypted by a device not verified by its owner."); + await expect(lastE2eIcon).toMatchScreenshot("event-shield-not-verified.png"); - const penultimate = page.locator(".mx_EventTile").filter({ hasText: "test encrypted from verified" }); - await assertNoE2EIcon(penultimate, app); - }); + const penultimate = page.locator(".mx_EventTile").filter({ hasText: "test encrypted from verified" }); + await assertNoE2EIcon(penultimate, app); + }, + ); - test("should show correct shields on events sent by users with changed identity", async ({ - page, - app, - bot: bob, - homeserver, - }) => { - // Verify Bob - await verify(app, bob); + test( + "should show correct shields on events sent by users with changed identity", + { tag: "@screenshot" }, + async ({ page, app, bot: bob, homeserver }) => { + // Verify Bob + await verify(app, bob); - // Bob logs in a new device and resets cross-signing - const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob); - await bootstrapCrossSigningForClient(await bobSecondDevice.prepareClient(), bob.credentials, true); + // Bob logs in a new device and resets cross-signing + const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob); + await bootstrapCrossSigningForClient(await bobSecondDevice.prepareClient(), bob.credentials, true); - /* should show an error for a message from a previously verified device */ - await bobSecondDevice.sendMessage(testRoomId, "test encrypted from user that was previously verified"); - const last = page.locator(".mx_EventTile_last"); - await expect(last).toContainText("test encrypted from user that was previously verified"); - const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon"); - await expect(lastE2eIcon).toHaveAccessibleName("Sender's verified identity was reset"); - await expect(lastE2eIcon).toMatchScreenshot("event-shield-identity-reset.png"); - }); + /* should show an error for a message from a previously verified device */ + await bobSecondDevice.sendMessage(testRoomId, "test encrypted from user that was previously verified"); + const last = page.locator(".mx_EventTile_last"); + await expect(last).toContainText("test encrypted from user that was previously verified"); + const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon"); + await expect(lastE2eIcon).toHaveAccessibleName("Sender's verified identity was reset"); + await expect(lastE2eIcon).toMatchScreenshot("event-shield-identity-reset.png"); + }, + ); }); }); diff --git a/playwright/snapshots/crypto/decryption-failure-messages.spec.ts/history-not-available-linux.png b/playwright/snapshots/crypto/decryption-failure-messages.spec.ts/history-not-available-linux.png new file mode 100644 index 0000000000..351e2c5d02 Binary files /dev/null and b/playwright/snapshots/crypto/decryption-failure-messages.spec.ts/history-not-available-linux.png differ diff --git a/playwright/snapshots/crypto/event-shields.spec.ts/event-shield-authenticity-linux.png b/playwright/snapshots/crypto/event-shields.spec.ts/event-shield-authenticity-linux.png new file mode 100644 index 0000000000..5980ff3ca0 Binary files /dev/null and b/playwright/snapshots/crypto/event-shields.spec.ts/event-shield-authenticity-linux.png differ diff --git a/playwright/snapshots/crypto/event-shields.spec.ts/event-shield-identity-reset-linux.png b/playwright/snapshots/crypto/event-shields.spec.ts/event-shield-identity-reset-linux.png new file mode 100644 index 0000000000..3499e8f704 Binary files /dev/null and b/playwright/snapshots/crypto/event-shields.spec.ts/event-shield-identity-reset-linux.png differ