diff --git a/playwright/e2e/crypto/device-verification.spec.ts b/playwright/e2e/crypto/device-verification.spec.ts index 2300b382b8..eb43d4dc78 100644 --- a/playwright/e2e/crypto/device-verification.spec.ts +++ b/playwright/e2e/crypto/device-verification.spec.ts @@ -130,53 +130,68 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => { await page.unrouteAll({ behavior: "ignoreErrors" }); }); - test("Verify device with QR code during login", async ({ page, app, credentials, homeserver }) => { - // A mode 0x02 verification: "self-verifying in which the current device does not yet trust the master key" - await logIntoElement(page, credentials); + test( + "Verify device with QR code during login", + { tag: "@screenshot" }, + async ({ page, app, credentials, homeserver }) => { + // A mode 0x02 verification: "self-verifying in which the current device does not yet trust the master key" + await logIntoElement(page, credentials); - // Launch the verification request between alice and the bot - const verificationRequest = await initiateAliceVerificationRequest(page); + // Launch the verification request between alice and the bot + const verificationRequest = await initiateAliceVerificationRequest(page); - const infoDialog = page.locator(".mx_InfoDialog"); - // feed the QR code into the verification request. - const qrData = await readQrCode(infoDialog); - const verifier = await verificationRequest.evaluateHandle( - (request, qrData) => request.scanQRCode(new Uint8ClampedArray(qrData)), - [...qrData], - ); + const infoDialog = page.locator(".mx_InfoDialog"); + // feed the QR code into the verification request. + const qrData = await readQrCode(infoDialog); + await expect(page.locator(".mx_Dialog")).toMatchScreenshot("qr-code.png", { + mask: [infoDialog.locator("img")], + }); + const verifier = await verificationRequest.evaluateHandle( + (request, qrData) => request.scanQRCode(new Uint8ClampedArray(qrData)), + [...qrData], + ); - // Confirm that the bot user scanned successfully - await expect(infoDialog.getByText("Confirm that you see a green shield on your other device")).toBeVisible(); - await infoDialog.getByRole("button", { name: "Yes, I see a green shield" }).click(); - await infoDialog.getByRole("button", { name: "Got it" }).click(); + // Confirm that the bot user scanned successfully + await expect( + infoDialog.getByText("Confirm that you see a green shield on your other device"), + ).toBeVisible(); + await expect(page.locator(".mx_Dialog")).toMatchScreenshot("confirm-green-shield.png"); + await infoDialog.getByRole("button", { name: "Yes, I see a green shield" }).click(); + await expect(page.locator(".mx_Dialog")).toMatchScreenshot("got-it.png"); + await infoDialog.getByRole("button", { name: "Got it" }).click(); - // wait for the bot to see we have finished - await verifier.evaluate((verifier) => verifier.verify()); + // wait for the bot to see we have finished + await verifier.evaluate((verifier) => verifier.verify()); - // the bot uploads the signatures asynchronously, so wait for that to happen - await page.waitForTimeout(1000); + // the bot uploads the signatures asynchronously, so wait for that to happen + await page.waitForTimeout(1000); - // our device should trust the bot device - await app.client.evaluate(async (cli, aliceBotCredentials) => { - const deviceStatus = await cli - .getCrypto()! - .getDeviceVerificationStatus(aliceBotCredentials.userId, aliceBotCredentials.deviceId); - if (!deviceStatus.isVerified()) { - throw new Error("Bot device was not verified after QR code verification"); - } - }, aliceBotClient.credentials); + // our device should trust the bot device + await app.client.evaluate(async (cli, aliceBotCredentials) => { + const deviceStatus = await cli + .getCrypto()! + .getDeviceVerificationStatus(aliceBotCredentials.userId, aliceBotCredentials.deviceId); + if (!deviceStatus.isVerified()) { + throw new Error("Bot device was not verified after QR code verification"); + } + }, aliceBotClient.credentials); - // Check that our device is now cross-signed - await checkDeviceIsCrossSigned(app); + // Check that our device is now cross-signed + await checkDeviceIsCrossSigned(app); - // Check that the current device is connected to key backup - await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true); - }); + // Check that the current device is connected to key backup + await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true); + }, + ); - test("Verify device with Security Phrase during login", async ({ page, app, credentials, homeserver }) => { - await logIntoElement(page, credentials); - await enterRecoveryKeyAndCheckVerified(page, app, "new passphrase"); - }); + test( + "Verify device with Security Phrase during login", + { tag: "@screenshot" }, + async ({ page, app, credentials, homeserver }) => { + await logIntoElement(page, credentials); + await enterRecoveryKeyAndCheckVerified(page, app, "new passphrase", true); + }, + ); test("Verify device with Recovery Key during login", async ({ page, app, credentials, homeserver }) => { const recoveryKey = (await aliceBotClient.getRecoveryKey()).encodedPrivateKey; @@ -226,7 +241,12 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => { }); /** Helper for the three tests above which verify by recovery key */ - async function enterRecoveryKeyAndCheckVerified(page: Page, app: ElementAppPage, recoveryKey: string) { + async function enterRecoveryKeyAndCheckVerified( + page: Page, + app: ElementAppPage, + recoveryKey: string, + screenshot = false, + ) { await page.getByRole("button", { name: "Use recovery key" }).click(); // Enter the recovery key @@ -234,8 +254,12 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => { // We use `pressSequentially` here to make sure that the FocusLock isn't causing us any problems // (cf https://github.com/element-hq/element-web/issues/30089) await dialog.getByTitle("Recovery key").pressSequentially(recoveryKey); + if (screenshot) { + await expect(page.locator(".mx_Dialog").filter({ hasText: "Enter your recovery key" })).toMatchScreenshot( + "recovery-key.png", + ); + } await dialog.getByRole("button", { name: "Continue", disabled: false }).click(); - await page.getByRole("button", { name: "Done" }).click(); // Check that our device is now cross-signed diff --git a/playwright/snapshots/crypto/device-verification.spec.ts/confirm-green-shield-linux.png b/playwright/snapshots/crypto/device-verification.spec.ts/confirm-green-shield-linux.png new file mode 100644 index 0000000000..23c295c2f6 Binary files /dev/null and b/playwright/snapshots/crypto/device-verification.spec.ts/confirm-green-shield-linux.png differ diff --git a/playwright/snapshots/crypto/device-verification.spec.ts/got-it-linux.png b/playwright/snapshots/crypto/device-verification.spec.ts/got-it-linux.png new file mode 100644 index 0000000000..0d705053b3 Binary files /dev/null and b/playwright/snapshots/crypto/device-verification.spec.ts/got-it-linux.png differ diff --git a/playwright/snapshots/crypto/device-verification.spec.ts/qr-code-linux.png b/playwright/snapshots/crypto/device-verification.spec.ts/qr-code-linux.png new file mode 100644 index 0000000000..23094f7b8b Binary files /dev/null and b/playwright/snapshots/crypto/device-verification.spec.ts/qr-code-linux.png differ diff --git a/playwright/snapshots/crypto/device-verification.spec.ts/recovery-key-linux.png b/playwright/snapshots/crypto/device-verification.spec.ts/recovery-key-linux.png new file mode 100644 index 0000000000..7683872ec8 Binary files /dev/null and b/playwright/snapshots/crypto/device-verification.spec.ts/recovery-key-linux.png differ diff --git a/res/css/structures/auth/_CompleteSecurity.pcss b/res/css/structures/auth/_CompleteSecurity.pcss index f0bca3f39c..6776681f63 100644 --- a/res/css/structures/auth/_CompleteSecurity.pcss +++ b/res/css/structures/auth/_CompleteSecurity.pcss @@ -15,14 +15,14 @@ Please see LICENSE files in the repository root for full details. width: 24px; height: 24px; margin-right: 4px; - position: relative; + display: inline-block; } .mx_CompleteSecurity_heroIcon { width: 128px; height: 128px; - position: relative; margin: 0 auto; + display: inline-block; } .mx_CompleteSecurity_skip { diff --git a/src/components/structures/auth/CompleteSecurity.tsx b/src/components/structures/auth/CompleteSecurity.tsx index 07a4852895..ed3b3d9a20 100644 --- a/src/components/structures/auth/CompleteSecurity.tsx +++ b/src/components/structures/auth/CompleteSecurity.tsx @@ -10,12 +10,14 @@ import React from "react"; import { Glass } from "@vector-im/compound-web"; import { _t } from "../../../languageHandler"; -import { SetupEncryptionStore, Phase } from "../../../stores/SetupEncryptionStore"; +import { Phase, SetupEncryptionStore } from "../../../stores/SetupEncryptionStore"; import SetupEncryptionBody from "./SetupEncryptionBody"; import AccessibleButton from "../../views/elements/AccessibleButton"; import CompleteSecurityBody from "../../views/auth/CompleteSecurityBody"; import AuthPage from "../../views/auth/AuthPage"; import SdkConfig from "../../../SdkConfig"; +import E2EIcon from "../../views/rooms/E2EIcon.tsx"; +import { E2EStatus } from "../../../utils/ShieldUtils.ts"; interface IProps { onFinished: () => void; @@ -67,13 +69,13 @@ export default class CompleteSecurity extends React.Component { } else if (phase === Phase.Intro) { // We don't specify an icon nor title since `SetupEncryptionBody` provides its own } else if (phase === Phase.Done) { - icon = ; + icon = ; title = _t("encryption|verification|after_new_login|device_verified"); } else if (phase === Phase.ConfirmSkip) { - icon = ; + icon = ; title = _t("common|are_you_sure"); } else if (phase === Phase.Busy) { - icon = ; + icon = ; title = _t("encryption|verification|after_new_login|verify_this_device"); } else if (phase === Phase.Finished) { // SetupEncryptionBody will take care of calling onFinished, we don't need to do anything diff --git a/src/components/structures/auth/SetupEncryptionBody.tsx b/src/components/structures/auth/SetupEncryptionBody.tsx index 5098265e67..22b7a1adb5 100644 --- a/src/components/structures/auth/SetupEncryptionBody.tsx +++ b/src/components/structures/auth/SetupEncryptionBody.tsx @@ -27,6 +27,8 @@ import { EncryptionCardButtons } from "../../views/settings/encryption/Encryptio import { EncryptionCardEmphasisedContent } from "../../views/settings/encryption/EncryptionCardEmphasisedContent"; import ExternalLink from "../../views/elements/ExternalLink"; import dispatcher from "../../../dispatcher/dispatcher"; +import E2EIcon from "../../views/rooms/E2EIcon.tsx"; +import { E2EStatus } from "../../../utils/ShieldUtils.ts"; interface IProps { onFinished: () => void; @@ -228,7 +230,7 @@ export default class SetupEncryptionBody extends React.Component } return (
-
+ {message}