Fix e2e icons in CompleteSecurity & SetupEncryptionBody (#31521)

* Fix e2e icons in CompleteSecurity & SetupEncryptionBody

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Prevent screenshot clash between tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski
2025-12-11 16:37:49 +00:00
committed by GitHub
parent 362e34513d
commit 7b024f956d
8 changed files with 75 additions and 47 deletions

View File

@@ -130,53 +130,68 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
await page.unrouteAll({ behavior: "ignoreErrors" }); await page.unrouteAll({ behavior: "ignoreErrors" });
}); });
test("Verify device with QR code during login", async ({ page, app, credentials, homeserver }) => { test(
// A mode 0x02 verification: "self-verifying in which the current device does not yet trust the master key" "Verify device with QR code during login",
await logIntoElement(page, credentials); { 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 // Launch the verification request between alice and the bot
const verificationRequest = await initiateAliceVerificationRequest(page); const verificationRequest = await initiateAliceVerificationRequest(page);
const infoDialog = page.locator(".mx_InfoDialog"); const infoDialog = page.locator(".mx_InfoDialog");
// feed the QR code into the verification request. // feed the QR code into the verification request.
const qrData = await readQrCode(infoDialog); const qrData = await readQrCode(infoDialog);
const verifier = await verificationRequest.evaluateHandle( await expect(page.locator(".mx_Dialog")).toMatchScreenshot("qr-code.png", {
(request, qrData) => request.scanQRCode(new Uint8ClampedArray(qrData)), mask: [infoDialog.locator("img")],
[...qrData], });
); const verifier = await verificationRequest.evaluateHandle(
(request, qrData) => request.scanQRCode(new Uint8ClampedArray(qrData)),
[...qrData],
);
// Confirm that the bot user scanned successfully // 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(
await infoDialog.getByRole("button", { name: "Yes, I see a green shield" }).click(); infoDialog.getByText("Confirm that you see a green shield on your other device"),
await infoDialog.getByRole("button", { name: "Got it" }).click(); ).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 // wait for the bot to see we have finished
await verifier.evaluate((verifier) => verifier.verify()); await verifier.evaluate((verifier) => verifier.verify());
// the bot uploads the signatures asynchronously, so wait for that to happen // the bot uploads the signatures asynchronously, so wait for that to happen
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
// our device should trust the bot device // our device should trust the bot device
await app.client.evaluate(async (cli, aliceBotCredentials) => { await app.client.evaluate(async (cli, aliceBotCredentials) => {
const deviceStatus = await cli const deviceStatus = await cli
.getCrypto()! .getCrypto()!
.getDeviceVerificationStatus(aliceBotCredentials.userId, aliceBotCredentials.deviceId); .getDeviceVerificationStatus(aliceBotCredentials.userId, aliceBotCredentials.deviceId);
if (!deviceStatus.isVerified()) { if (!deviceStatus.isVerified()) {
throw new Error("Bot device was not verified after QR code verification"); throw new Error("Bot device was not verified after QR code verification");
} }
}, aliceBotClient.credentials); }, aliceBotClient.credentials);
// Check that our device is now cross-signed // Check that our device is now cross-signed
await checkDeviceIsCrossSigned(app); await checkDeviceIsCrossSigned(app);
// Check that the current device is connected to key backup // Check that the current device is connected to key backup
await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true); await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true);
}); },
);
test("Verify device with Security Phrase during login", async ({ page, app, credentials, homeserver }) => { test(
await logIntoElement(page, credentials); "Verify device with Security Phrase during login",
await enterRecoveryKeyAndCheckVerified(page, app, "new passphrase"); { 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 }) => { test("Verify device with Recovery Key during login", async ({ page, app, credentials, homeserver }) => {
const recoveryKey = (await aliceBotClient.getRecoveryKey()).encodedPrivateKey; 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 */ /** 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(); await page.getByRole("button", { name: "Use recovery key" }).click();
// Enter the recovery key // 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 // 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) // (cf https://github.com/element-hq/element-web/issues/30089)
await dialog.getByTitle("Recovery key").pressSequentially(recoveryKey); 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 dialog.getByRole("button", { name: "Continue", disabled: false }).click();
await page.getByRole("button", { name: "Done" }).click(); await page.getByRole("button", { name: "Done" }).click();
// Check that our device is now cross-signed // Check that our device is now cross-signed

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -15,14 +15,14 @@ Please see LICENSE files in the repository root for full details.
width: 24px; width: 24px;
height: 24px; height: 24px;
margin-right: 4px; margin-right: 4px;
position: relative; display: inline-block;
} }
.mx_CompleteSecurity_heroIcon { .mx_CompleteSecurity_heroIcon {
width: 128px; width: 128px;
height: 128px; height: 128px;
position: relative;
margin: 0 auto; margin: 0 auto;
display: inline-block;
} }
.mx_CompleteSecurity_skip { .mx_CompleteSecurity_skip {

View File

@@ -10,12 +10,14 @@ import React from "react";
import { Glass } from "@vector-im/compound-web"; import { Glass } from "@vector-im/compound-web";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import { SetupEncryptionStore, Phase } from "../../../stores/SetupEncryptionStore"; import { Phase, SetupEncryptionStore } from "../../../stores/SetupEncryptionStore";
import SetupEncryptionBody from "./SetupEncryptionBody"; import SetupEncryptionBody from "./SetupEncryptionBody";
import AccessibleButton from "../../views/elements/AccessibleButton"; import AccessibleButton from "../../views/elements/AccessibleButton";
import CompleteSecurityBody from "../../views/auth/CompleteSecurityBody"; import CompleteSecurityBody from "../../views/auth/CompleteSecurityBody";
import AuthPage from "../../views/auth/AuthPage"; import AuthPage from "../../views/auth/AuthPage";
import SdkConfig from "../../../SdkConfig"; import SdkConfig from "../../../SdkConfig";
import E2EIcon from "../../views/rooms/E2EIcon.tsx";
import { E2EStatus } from "../../../utils/ShieldUtils.ts";
interface IProps { interface IProps {
onFinished: () => void; onFinished: () => void;
@@ -67,13 +69,13 @@ export default class CompleteSecurity extends React.Component<IProps, IState> {
} else if (phase === Phase.Intro) { } else if (phase === Phase.Intro) {
// We don't specify an icon nor title since `SetupEncryptionBody` provides its own // We don't specify an icon nor title since `SetupEncryptionBody` provides its own
} else if (phase === Phase.Done) { } else if (phase === Phase.Done) {
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_verified" />; icon = <E2EIcon className="mx_CompleteSecurity_headerIcon" status={E2EStatus.Verified} hideTooltip />;
title = _t("encryption|verification|after_new_login|device_verified"); title = _t("encryption|verification|after_new_login|device_verified");
} else if (phase === Phase.ConfirmSkip) { } else if (phase === Phase.ConfirmSkip) {
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning" />; icon = <E2EIcon className="mx_CompleteSecurity_headerIcon" status={E2EStatus.Warning} hideTooltip />;
title = _t("common|are_you_sure"); title = _t("common|are_you_sure");
} else if (phase === Phase.Busy) { } else if (phase === Phase.Busy) {
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning" />; icon = <E2EIcon className="mx_CompleteSecurity_headerIcon" status={E2EStatus.Warning} hideTooltip />;
title = _t("encryption|verification|after_new_login|verify_this_device"); title = _t("encryption|verification|after_new_login|verify_this_device");
} else if (phase === Phase.Finished) { } else if (phase === Phase.Finished) {
// SetupEncryptionBody will take care of calling onFinished, we don't need to do anything // SetupEncryptionBody will take care of calling onFinished, we don't need to do anything

View File

@@ -27,6 +27,8 @@ import { EncryptionCardButtons } from "../../views/settings/encryption/Encryptio
import { EncryptionCardEmphasisedContent } from "../../views/settings/encryption/EncryptionCardEmphasisedContent"; import { EncryptionCardEmphasisedContent } from "../../views/settings/encryption/EncryptionCardEmphasisedContent";
import ExternalLink from "../../views/elements/ExternalLink"; import ExternalLink from "../../views/elements/ExternalLink";
import dispatcher from "../../../dispatcher/dispatcher"; import dispatcher from "../../../dispatcher/dispatcher";
import E2EIcon from "../../views/rooms/E2EIcon.tsx";
import { E2EStatus } from "../../../utils/ShieldUtils.ts";
interface IProps { interface IProps {
onFinished: () => void; onFinished: () => void;
@@ -228,7 +230,7 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
} }
return ( return (
<div> <div>
<div className="mx_CompleteSecurity_heroIcon mx_E2EIcon_verified" /> <E2EIcon className="mx_CompleteSecurity_heroIcon" status={E2EStatus.Verified} hideTooltip />
{message} {message}
<div className="mx_CompleteSecurity_actionRow"> <div className="mx_CompleteSecurity_actionRow">
<AccessibleButton kind="primary" onClick={this.onDoneClick}> <AccessibleButton kind="primary" onClick={this.onDoneClick}>