Make apps/web/playwright comply with strict mode (#34403)

* Make apps/web/playwright comply with strict mode

Otherwise it sometimes fails to compile matrix-js-sdk as strict mode & noImplicitAny both impact how overloads are asserted

* Iterate
This commit is contained in:
Michael Telatynski
2026-07-30 09:07:03 +00:00
committed by GitHub
parent 4a18eac927
commit 42253a7ae5
84 changed files with 413 additions and 388 deletions
@@ -89,13 +89,13 @@ test.describe("Key backup reset from elsewhere", () => {
await page.getByRole("textbox", { name: "Name" }).fill("test room");
await page.getByRole("button", { name: "Create room" }).click();
const accessToken = await page.evaluate(() => window.mxMatrixClientPeg.get().getAccessToken());
const accessToken = await page.evaluate(() => window.mxMatrixClientPeg.get().getAccessToken()!);
const csAPI = new TestClientServerAPI(request, homeserver, accessToken);
const backupInfo = await csAPI.getCurrentBackupInfo();
await csAPI.deleteBackupVersion(backupInfo.version);
await csAPI.deleteBackupVersion(backupInfo!.version);
await page.getByRole("textbox", { name: "Send a message…" }).fill("/discardsession");
await page.getByRole("button", { name: "Send message" }).click();
+11 -11
View File
@@ -25,8 +25,8 @@ const checkDMRoom = async (page: Page) => {
const startDMWithBob = async (page: Page, bob: Bot) => {
await page.getByRole("navigation", { name: "Room list" }).getByRole("button", { name: "New conversation" }).click();
await page.getByRole("menuitem", { name: "Start chat" }).click();
await page.getByTestId("invite-dialog-input").fill(bob.credentials.userId);
await page.getByRole("option", { name: bob.credentials.displayName }).click();
await page.getByTestId("invite-dialog-input").fill(bob.credentials!.userId);
await page.getByRole("option", { name: bob.credentials!.displayName! }).click();
await expect(page.getByTestId("invite-dialog-input-wrapper").getByText("Bob")).toBeVisible();
await page.getByRole("button", { name: "Go" }).click();
@@ -82,14 +82,14 @@ test.describe("Cryptography", function () {
* @param keyType
*/
async function verifyKey(app: ElementAppPage, keyType: "master" | "self_signing" | "user_signing") {
const accountData: { encrypted: Record<string, Record<string, string>> } = await app.client.evaluate(
const accountData = await app.client.evaluate(
(cli, keyType) => cli.getAccountDataFromServer(`m.cross_signing.${keyType}`),
keyType,
);
expect(accountData.encrypted).toBeDefined();
const keys = Object.keys(accountData.encrypted);
const key = accountData.encrypted[keys[0]];
expect(accountData?.encrypted).toBeDefined();
const keys = Object.keys(accountData!.encrypted);
const key = accountData!.encrypted[keys[0]];
expect(key.ciphertext).toBeDefined();
expect(key.iv).toBeDefined();
expect(key.mac).toBeDefined();
@@ -119,9 +119,9 @@ test.describe("Cryptography", function () {
async function fetchMasterKey() {
return await test.step("Fetch master key from server", async () => {
const k = await app.client.evaluate(async (cli) => {
const userId = cli.getUserId();
const userId = cli.getSafeUserId();
const keys = await cli.downloadKeysForUsers([userId]);
return Object.values(keys.master_keys[userId].keys)[0];
return Object.values(keys.master_keys![userId].keys)[0];
});
console.log(`fetchMasterKey: ${k}`);
return k;
@@ -138,7 +138,7 @@ test.describe("Cryptography", function () {
await encryptionTab.getByRole("button", { name: "Continue" }).click();
// Enter the password
await page.getByPlaceholder("Password").fill(aliceCredentials.password);
await page.getByPlaceholder("Password").fill(aliceCredentials.password!);
await page.getByRole("button", { name: "Continue" }).click();
await expect(async () => {
@@ -163,7 +163,7 @@ test.describe("Cryptography", function () {
await encryptionTab.getByRole("button", { name: "Continue" }).click();
// Enter the password
await page.getByPlaceholder("Password").fill(aliceCredentials.password);
await page.getByPlaceholder("Password").fill(aliceCredentials.password!);
await page.getByRole("button", { name: "Continue" }).click();
// Key storage should now be enabled
@@ -209,7 +209,7 @@ test.describe("Cryptography", function () {
await autoJoin(bob);
// we need to have a room with the other user present, so we can open the verification panel
await createSharedRoomWithUser(app, bob.credentials.userId);
await createSharedRoomWithUser(app, bob.credentials!.userId);
await verify(app, bob);
});
});
@@ -46,7 +46,7 @@ test.describe("Device dehydration, on a MAS-enabled homeserver", () => {
await autoJoin(bob);
// Create an encrypted room, and wait for Bob to join it.
const testRoomId = await createSharedEncryptedRoomWithUser(app, bob.credentials.userId);
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);
@@ -77,7 +77,7 @@ test.describe("Dehydration", () => {
// Set up cross-signing and recovery
const { botClient } = await createBot(page, homeserver, credentials);
// ... and dehydration
await botClient.evaluate(async (client) => await client.getCrypto().startDehydration());
await botClient.evaluate(async (client) => await client.getCrypto()!.startDehydration());
const initialDehydratedDeviceIds = await getDehydratedDeviceIds(botClient);
expect(initialDehydratedDeviceIds.length).toBe(1);
@@ -93,7 +93,7 @@ test.describe("Dehydration", () => {
page.getByRole("heading", { name: "Are you sure you want to reset your digital identity?" }),
).toBeVisible();
await page.getByRole("button", { name: "Continue", exact: true }).click();
await page.getByPlaceholder("Password").fill(credentials.password);
await page.getByPlaceholder("Password").fill(credentials.password!);
await page.getByRole("button", { name: "Continue" }).click();
// And set up recovery
@@ -141,7 +141,7 @@ test.describe("Dehydration", () => {
await autoJoin(bob);
// create an encrypted room, and wait for Bob to join it.
const testRoomId = await createSharedEncryptedRoomWithUser(app, bob.credentials.userId);
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);
@@ -171,9 +171,9 @@ test.describe("Dehydration", () => {
async function getDehydratedDeviceIds(client: Client): Promise<string[]> {
return await client.evaluate(async (client) => {
const userId = client.getUserId();
const devices = await client.getCrypto().getUserDeviceInfo([userId]);
return Array.from(devices.get(userId).values())
const userId = client.getSafeUserId();
const devices = await client.getCrypto()!.getUserDeviceInfo([userId]);
return Array.from(devices.get(userId)!.values())
.filter((d) => d.dehydrated)
.map((d) => d.deviceId);
});
@@ -169,8 +169,8 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
await app.client.evaluate(async (cli, aliceBotCredentials) => {
const deviceStatus = await cli
.getCrypto()!
.getDeviceVerificationStatus(aliceBotCredentials.userId, aliceBotCredentials.deviceId);
if (!deviceStatus.isVerified()) {
.getDeviceVerificationStatus(aliceBotCredentials!.userId, aliceBotCredentials!.deviceId);
if (!deviceStatus!.isVerified()) {
throw new Error("Bot device was not verified after QR code verification");
}
}, aliceBotClient.credentials);
@@ -193,14 +193,14 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
);
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!;
await logIntoElement(page, credentials);
await enterRecoveryKeyAndCheckVerified(page, app, recoveryKey);
});
test("Verify device with Recovery Key from settings", async ({ page, app, credentials }) => {
const recoveryKey = (await aliceBotClient.getRecoveryKey()).encodedPrivateKey;
const recoveryKey = (await aliceBotClient.getRecoveryKey()).encodedPrivateKey!;
await logIntoElement(page, credentials);
@@ -277,7 +277,7 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
await authPage.getByRole("button", { name: "I'll verify later" }).click();
await page.waitForSelector(".mx_MatrixChat");
const elementDeviceId = await page.evaluate(() => window.mxMatrixClientPeg.get().getDeviceId());
const elementDeviceId = await page.evaluate(() => window.mxMatrixClientPeg.get().getDeviceId()!);
/* Create an encrypted room so the "Verify this device" toast appears */
await app.client.createRoom({
@@ -301,7 +301,7 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
/* Check the toast for the incoming request */
const toast = await getToast(page, "Verification requested");
// it should contain the device ID of the requesting device
await expect(toast.getByText(`${aliceBotClient.credentials.deviceId} from `)).toBeVisible();
await expect(toast.getByText(`${aliceBotClient.credentials!.deviceId} from `)).toBeVisible();
// Accept
await toast.getByRole("button", { name: "Start verification" }).click();
@@ -311,7 +311,7 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
/* on the bot side, wait for the verifier to exist ... */
const verifier = await awaitVerifier(botVerificationRequest);
// ... confirm ...
void botVerificationRequest.evaluate((verificationRequest) => verificationRequest.verifier.verify());
void botVerificationRequest.evaluate((verificationRequest) => verificationRequest.verifier!.verify());
// ... and then check the emoji match
await doTwoWaySasVerification(page, verifier);
@@ -337,7 +337,7 @@ async function readQrCode(base: Locator) {
>(async (img) => {
// draw the image on a canvas
const myCanvas = new OffscreenCanvas(img.width, img.height);
const ctx = myCanvas.getContext("2d");
const ctx = myCanvas.getContext("2d")!;
ctx.drawImage(img, 0, 0);
// read the image data
@@ -352,5 +352,5 @@ async function readQrCode(base: Locator) {
// now we can decode the QR code.
const result = jsQR(new Uint8ClampedArray(imageData.buffer), imageData.width, imageData.height);
return new Uint8Array(result.binaryData);
return new Uint8Array(result!.binaryData);
}
@@ -40,7 +40,7 @@ test.describe("Cryptography", function () {
await autoJoin(bob);
// create an encrypted room, and wait for Bob to join it.
testRoomId = await createSharedEncryptedRoomWithUser(app, bob.credentials.userId);
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);
@@ -128,7 +128,7 @@ test.describe("Cryptography", function () {
await bobSecondDevice.evaluate((cli) => cli.logout(true));
// wait for the logout to propagate.
await waitForDevices(app, bob.credentials.userId, 1);
await waitForDevices(app, bob.credentials!.userId, 1);
// close and reopen the room, to get the shield to update.
await app.viewRoomByName("Bob");
@@ -245,7 +245,7 @@ test.describe("Cryptography", function () {
// 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);
await waitForDevices(app, bob.credentials!.userId, 1);
// Our app is blocked from syncing while Bob sends his messages.
await app.client.network.goOffline();
@@ -286,7 +286,7 @@ test.describe("Cryptography", function () {
// 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);
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");
@@ -29,14 +29,14 @@ test.describe("Invisible cryptography", () => {
await autoJoin(bob);
// create an encrypted room
const testRoomId = await createSharedEncryptedRoomWithUser(app, bob.credentials.userId);
const testRoomId = await createSharedEncryptedRoomWithUser(app, bob.credentials!.userId);
// 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);
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");
@@ -41,8 +41,8 @@ test.describe("migration", { tag: "@no-webkit" }, function () {
// When the progress bar first loads, it should have a high max (one per megolm session to import), and
// a relatively low value.
const progressBar = page.getByRole("progressbar");
const initialProgress = parseFloat(await progressBar.getAttribute("value"));
const initialMax = parseFloat(await progressBar.getAttribute("max"));
const initialProgress = parseFloat((await progressBar.getAttribute("value"))!);
const initialMax = parseFloat((await progressBar.getAttribute("max"))!);
expect(initialMax).toBeGreaterThan(4000);
expect(initialProgress).toBeGreaterThanOrEqual(0);
expect(initialProgress).toBeLessThanOrEqual(500);
@@ -53,8 +53,8 @@ test.describe("migration", { tag: "@no-webkit" }, function () {
async () => {
const progressBar = page.getByRole("progressbar");
return (
(parseFloat(await progressBar.getAttribute("value")) * 100.0) /
parseFloat(await progressBar.getAttribute("max"))
(parseFloat((await progressBar.getAttribute("value"))!) * 100.0) /
parseFloat((await progressBar.getAttribute("max"))!)
);
},
{ timeout: 60000 },
@@ -30,7 +30,7 @@ test.describe("Key storage out of sync toast", () => {
recoveryKey = res.recoveryKey;
await logIntoElement(page, credentials);
await verifyAfterLogin(page, recoveryKey.encodedPrivateKey);
await verifyAfterLogin(page, recoveryKey.encodedPrivateKey!);
await deleteCachedSecrets(page);
});
@@ -43,7 +43,7 @@ test.describe("Key storage out of sync toast", () => {
await page.getByRole("button", { name: "Enter recovery key" }).click();
await page.getByRole("textbox", { name: "Recovery Key" }).fill(recoveryKey.encodedPrivateKey);
await page.getByRole("textbox", { name: "Recovery Key" }).fill(recoveryKey.encodedPrivateKey!);
await page.getByRole("button", { name: "Continue" }).click();
await expect(page.getByRole("button", { name: "Enter recovery key" })).not.toBeVisible();
@@ -63,7 +63,7 @@ test.describe("Key storage out of sync toast", () => {
});
test.describe("'Turn on key storage' toast", () => {
let botClient: Bot | undefined;
let botClient: Bot;
test.beforeEach(async ({ page, homeserver, credentials }) => {
// Set up all crypto stuff. Key storage defaults to on.
@@ -73,7 +73,7 @@ test.describe("'Turn on key storage' toast", () => {
botClient = res.botClient;
await logIntoElement(page, credentials);
await verifyAfterLogin(page, recoveryKey.encodedPrivateKey);
await verifyAfterLogin(page, recoveryKey.encodedPrivateKey!);
// We won't be prompted for crypto setup unless we have an e2e room, so make one
await page
@@ -39,7 +39,7 @@ test.describe("User verification", () => {
user: aliceCredentials,
room: { roomId: dmRoomId },
}) => {
await waitForDevices(app, bob.credentials.userId, 1);
await waitForDevices(app, bob.credentials!.userId, 1);
await expect(page.getByRole("button", { name: "Avatar" })).toBeVisible();
const avatar = page.getByRole("button", { name: "Avatar" });
await avatar.click();
@@ -47,14 +47,14 @@ test.describe("User verification", () => {
// once Alice has joined, Bob starts the verification
const bobVerificationRequest = await bob.evaluateHandle(
async (client, { dmRoomId, aliceCredentials }) => {
const room = client.getRoom(dmRoomId);
const room = client.getRoom(dmRoomId)!;
while (room.getMember(aliceCredentials.userId)?.membership !== "join") {
await new Promise((resolve) => {
room.once(window.matrixcs.RoomStateEvent.Members, resolve);
});
}
return client.getCrypto().requestVerificationDM(aliceCredentials.userId, dmRoomId);
return client.getCrypto()!.requestVerificationDM(aliceCredentials.userId, dmRoomId);
},
{ dmRoomId, aliceCredentials },
);
@@ -62,7 +62,7 @@ test.describe("User verification", () => {
// there should also be a toast
const toast = await getToast(page, "Verification requested");
// it should contain the details of the requesting user
await expect(toast.getByText(`Bob (${bob.credentials.userId})`)).toBeVisible();
await expect(toast.getByText(`Bob (${bob.credentials!.userId})`)).toBeVisible();
// Accept
await toast.getByRole("button", { name: "Verify User" }).click();
@@ -93,7 +93,7 @@ test.describe("User verification", () => {
user: aliceCredentials,
room: { roomId: dmRoomId },
}) => {
await waitForDevices(app, bob.credentials.userId, 1);
await waitForDevices(app, bob.credentials!.userId, 1);
await expect(page.getByRole("button", { name: "Avatar" })).toBeVisible();
const avatar = page.getByRole("button", { name: "Avatar" });
await avatar.click();
@@ -101,14 +101,14 @@ test.describe("User verification", () => {
// once Alice has joined, Bob starts the verification
const bobVerificationRequest = await bob.evaluateHandle(
async (client, { dmRoomId, aliceCredentials }) => {
const room = client.getRoom(dmRoomId);
const room = client.getRoom(dmRoomId)!;
while (room.getMember(aliceCredentials.userId)?.membership !== "join") {
await new Promise((resolve) => {
room.once(window.matrixcs.RoomStateEvent.Members, resolve);
});
}
return client.getCrypto().requestVerificationDM(aliceCredentials.userId, dmRoomId);
return client.getCrypto()!.requestVerificationDM(aliceCredentials.userId, dmRoomId);
},
{ dmRoomId, aliceCredentials },
);
+18 -16
View File
@@ -51,7 +51,7 @@ export async function createBot(
botClient.setCredentials(credentials);
// Backup is prepared in the background. Poll until it is ready.
const botClientHandle = await botClient.prepareClient();
let expectedBackupVersion: string;
let expectedBackupVersion: string | null;
await expect
.poll(async () => {
expectedBackupVersion = await botClientHandle.evaluate((cli) =>
@@ -63,7 +63,7 @@ export async function createBot(
const recoveryKey = await botClient.getRecoveryKey();
return { botClient, recoveryKey, expectedBackupVersion };
return { botClient, recoveryKey, expectedBackupVersion: expectedBackupVersion! };
}
/**
@@ -98,13 +98,15 @@ export async function waitForVerificationRequest(client: Client): Promise<JSHand
export function handleSasVerification(verifier: JSHandle<Verifier>): Promise<EmojiMapping[]> {
return verifier.evaluate((verifier) => {
const event = verifier.getShowSasCallbacks();
if (event) return event.sas.emoji;
if (event) {
return event.sas.emoji!;
}
return new Promise<EmojiMapping[]>((resolve) => {
const onShowSas = (event: ShowSasCallbacks) => {
verifier.off("show_sas" as VerifierEvent, onShowSas);
void event.confirm();
resolve(event.sas.emoji);
resolve(event.sas.emoji!);
};
verifier.on("show_sas" as VerifierEvent, onShowSas);
@@ -117,24 +119,24 @@ export function handleSasVerification(verifier: JSHandle<Verifier>): Promise<Emo
*/
export async function checkDeviceIsCrossSigned(app: ElementAppPage): Promise<void> {
const { userId, deviceId, keys } = await app.client.evaluate(async (cli: MatrixClient) => {
const deviceId = cli.getDeviceId();
const userId = cli.getUserId();
const deviceId = cli.getDeviceId()!;
const userId = cli.getSafeUserId();
const keys = await cli.downloadKeysForUsers([userId]);
return { userId, deviceId, keys };
});
// there should be three cross-signing keys
expect(keys.master_keys[userId]).toHaveProperty("keys");
expect(keys.self_signing_keys[userId]).toHaveProperty("keys");
expect(keys.user_signing_keys[userId]).toHaveProperty("keys");
expect(keys.master_keys![userId]).toHaveProperty("keys");
expect(keys.self_signing_keys![userId]).toHaveProperty("keys");
expect(keys.user_signing_keys![userId]).toHaveProperty("keys");
// and the device should be signed by the self-signing key
const selfSigningKeyId = Object.keys(keys.self_signing_keys[userId].keys)[0];
const selfSigningKeyId = Object.keys(keys.self_signing_keys![userId].keys)[0];
expect(keys.device_keys[userId][deviceId]).toBeDefined();
const myDeviceSignatures = keys.device_keys[userId][deviceId].signatures[userId];
const myDeviceSignatures = keys.device_keys[userId][deviceId].signatures![userId];
expect(myDeviceSignatures[selfSigningKeyId]).toBeDefined();
}
@@ -190,7 +192,7 @@ export async function checkDeviceIsConnectedKeyBackup(
// We have a key backup
expect(backupInfo).toBeDefined();
// The key backup version is as expected
expect(backupInfo.version).toBe(expectedBackupVersion);
expect(backupInfo!.version).toBe(expectedBackupVersion);
// The active backup version is as expected
expect(activeBackupVersion).toBe(expectedBackupVersion);
// The backup key is stored in 4S
@@ -211,7 +213,7 @@ export async function logIntoElement(page: Page, credentials: Credentials) {
await page.goto("/#/login");
await page.getByRole("textbox", { name: "Username" }).fill(credentials.userId);
await page.getByPlaceholder("Password").fill(credentials.password);
await page.getByPlaceholder("Password").fill(credentials.password!);
await page.getByRole("button", { name: "Sign in" }).click();
}
@@ -379,7 +381,7 @@ export async function completeCreateSecretStorageDialog(
// the step is quite quick, and playwright can miss it, so we can't test for it.
if (opts && Object.hasOwn(opts, "accountPassword")) {
await expect(currentDialogLocator.getByRole("heading", { name: "Setting up keys" })).toBeVisible();
await page.getByPlaceholder("Password").fill(opts!.accountPassword);
await page.getByPlaceholder("Password").fill(opts!.accountPassword!);
await currentDialogLocator.getByRole("button", { name: "Continue" }).click();
}
@@ -595,7 +597,7 @@ export async function createSecondBotDevice(page: Page, homeserver: HomeserverIn
bootstrapSecretStorage: false,
bootstrapCrossSigning: false,
});
bobSecondDevice.setCredentials(await homeserver.loginUser(bob.credentials.userId, bob.credentials.password));
bobSecondDevice.setCredentials(await homeserver.loginUser(bob.credentials!.userId, bob.credentials!.password!));
await bobSecondDevice.prepareClient();
return bobSecondDevice;
}
@@ -638,7 +640,7 @@ export async function waitForDevices(
for (let i = 0; i < 10; ++i) {
const userDeviceMap = await cli.getCrypto()?.getUserDeviceInfo([userId], true);
const deviceMap = userDeviceMap?.get(userId);
if (deviceMap.size === expectedNumberOfDevices) return true;
if (deviceMap?.size === expectedNumberOfDevices) return true;
await new Promise((r) => setTimeout(r, 500));
}
return false;