Use the new room list by default (#30640)

* Default to new room list and enforce in config for app and develop

* Update jest tests

* Update LandmarkNavigation and e2e test

* Update viewRoomByName helper

* lint

* Update Add -> New Room flow

Keep legacy viewRoomByName until we delete the olds tests.

* Update e2e test to use Add -> Start Chat

* Update screenshots

* Fix viewRoomByName, can't use option as it contains more that just the room name. Using title which should be exact.

* Fix knocking tests

* fix layout.spec.ts and pstn.spec

* timeline snapshots

* Fix spotlight.spec

* TAC spaces and media preview settings

* Fix more screenshots and mark as unread tests

* Fix leftpanel test

* Bugfix for knocking use case. We should check EffectiveMembership when remove rooms from the new room list, so that knocking is handled

* Fix openCreateRoomDialog to new room list specifics to fix create-knock-room.spec.ts

* lint

* Fix Landmark navigation from left panel search to the next landmark

* lint

* Update window-12px-linux.png

* Update apps-drawer-linux.png

* Update sliding sync e2e tests

* Update some screenshots

* Revert change to the space create screenshot

* Use actual screenshot

as focused elements are different when generated locally

* Fix test selectors

* Morfe test screenshot selector / update

* Add test for landmark navigation

* Replace screenshots

* Fix another test that just got added an hour ago

* Not sure why this was changed, doesn't seem necessary

* Disambiguate selector

* Another screenshot that's now changed in width by 1px

* Revert changes to config files

It's being turned on by default so these are unnecessary

* Convert read.unread assertions to new room list

removing support for checking for activity in assertUnread which was
unused.

* Update room list order tests

that feel a bit like they ought to be in room-list rather than read-receipts but whatever

* Fix room titles in read receipts test

---------

Co-authored-by: David Baker <dbkr@users.noreply.github.com>
This commit is contained in:
David Langley
2025-09-16 09:13:05 +01:00
committed by GitHub
parent eaa20a2c9a
commit 3432613195
135 changed files with 451 additions and 697 deletions
@@ -29,7 +29,7 @@ test.describe("Landmark navigation tests", () => {
// Pressing Control+F6 again will focus room search // Pressing Control+F6 again will focus room search
await page.keyboard.press("ControlOrMeta+F6"); await page.keyboard.press("ControlOrMeta+F6");
await expect(page.locator(".mx_RoomSearch")).toBeFocused(); await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
// Pressing Control+F6 again will focus the message composer // Pressing Control+F6 again will focus the message composer
await page.keyboard.press("ControlOrMeta+F6"); await page.keyboard.press("ControlOrMeta+F6");
@@ -44,7 +44,7 @@ test.describe("Landmark navigation tests", () => {
await expect(page.locator(".mx_HomePage")).toBeFocused(); await expect(page.locator(".mx_HomePage")).toBeFocused();
await page.keyboard.press("ControlOrMeta+Shift+F6"); await page.keyboard.press("ControlOrMeta+Shift+F6");
await expect(page.locator(".mx_RoomSearch")).toBeFocused(); await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
await page.keyboard.press("ControlOrMeta+Shift+F6"); await page.keyboard.press("ControlOrMeta+Shift+F6");
await expect(page.locator(".mx_SpaceButton_active")).toBeFocused(); await expect(page.locator(".mx_SpaceButton_active")).toBeFocused();
@@ -75,11 +75,11 @@ test.describe("Landmark navigation tests", () => {
// Pressing Control+F6 again will focus room search // Pressing Control+F6 again will focus room search
await page.keyboard.press("ControlOrMeta+F6"); await page.keyboard.press("ControlOrMeta+F6");
await expect(page.locator(".mx_RoomSearch")).toBeFocused(); await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
// Pressing Control+F6 again will focus the room tile in the room list // Pressing Control+F6 again will focus the room tile in the room list
await page.keyboard.press("ControlOrMeta+F6"); await page.keyboard.press("ControlOrMeta+F6");
await expect(page.locator(".mx_RoomTile_selected")).toBeFocused(); await expect(page.locator(".mx_RoomListItemView_selected")).toBeFocused();
// Pressing Control+F6 again will focus the message composer // Pressing Control+F6 again will focus the message composer
await page.keyboard.press("ControlOrMeta+F6"); await page.keyboard.press("ControlOrMeta+F6");
@@ -94,10 +94,10 @@ test.describe("Landmark navigation tests", () => {
await expect(page.locator(".mx_BasicMessageComposer_input")).toBeFocused(); await expect(page.locator(".mx_BasicMessageComposer_input")).toBeFocused();
await page.keyboard.press("ControlOrMeta+Shift+F6"); await page.keyboard.press("ControlOrMeta+Shift+F6");
await expect(page.locator(".mx_RoomTile_selected")).toBeFocused(); await expect(page.locator(".mx_RoomListItemView_selected")).toBeFocused();
await page.keyboard.press("ControlOrMeta+Shift+F6"); await page.keyboard.press("ControlOrMeta+Shift+F6");
await expect(page.locator(".mx_RoomSearch")).toBeFocused(); await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
await page.keyboard.press("ControlOrMeta+Shift+F6"); await page.keyboard.press("ControlOrMeta+Shift+F6");
await expect(page.locator(".mx_SpaceButton_active")).toBeFocused(); await expect(page.locator(".mx_SpaceButton_active")).toBeFocused();
@@ -131,11 +131,11 @@ test.describe("Landmark navigation tests", () => {
// Pressing Control+F6 again will focus room search // Pressing Control+F6 again will focus room search
await page.keyboard.press("ControlOrMeta+F6"); await page.keyboard.press("ControlOrMeta+F6");
await expect(page.locator(".mx_RoomSearch")).toBeFocused(); await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
// Pressing Control+F6 again will focus the room tile in the room list // Pressing Control+F6 again will focus the room tile in the room list
await page.keyboard.press("ControlOrMeta+F6"); await page.keyboard.press("ControlOrMeta+F6");
await expect(page.locator(".mx_RoomTile")).toBeFocused(); await expect(page.locator(".mx_RoomListItemView")).toBeFocused();
// Pressing Control+F6 again will focus the home section // Pressing Control+F6 again will focus the home section
await page.keyboard.press("ControlOrMeta+F6"); await page.keyboard.press("ControlOrMeta+F6");
@@ -150,10 +150,10 @@ test.describe("Landmark navigation tests", () => {
await expect(page.locator(".mx_HomePage")).toBeFocused(); await expect(page.locator(".mx_HomePage")).toBeFocused();
await page.keyboard.press("ControlOrMeta+Shift+F6"); await page.keyboard.press("ControlOrMeta+Shift+F6");
await expect(page.locator(".mx_RoomTile")).toBeFocused(); await expect(page.locator(".mx_RoomListItemView")).toBeFocused();
await page.keyboard.press("ControlOrMeta+Shift+F6"); await page.keyboard.press("ControlOrMeta+Shift+F6");
await expect(page.locator(".mx_RoomSearch")).toBeFocused(); await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
await page.keyboard.press("ControlOrMeta+Shift+F6"); await page.keyboard.press("ControlOrMeta+Shift+F6");
await expect(page.locator(".mx_SpaceButton_active")).toBeFocused(); await expect(page.locator(".mx_SpaceButton_active")).toBeFocused();
+4 -1
View File
@@ -108,7 +108,10 @@ test.describe("Composer", () => {
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" }); const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
await composer.pressSequentially("@bob"); await composer.pressSequentially("@bob");
await page.getByRole("option", { name: "Bob" }).click();
// Note that we include the user ID here as the room tile is also an 'option' role
// with text 'Bob'
await page.getByRole("option", { name: `Bob ${bot.credentials.userId}` }).click();
await expect(composer.getByText("Bob")).toBeVisible(); await expect(composer.getByText("Bob")).toBeVisible();
await expect(composer).toMatchScreenshot("mention.png"); await expect(composer).toMatchScreenshot("mention.png");
await composer.press("Enter"); await composer.press("Enter");
+2 -2
View File
@@ -49,7 +49,7 @@ test.describe("Encryption state after registration", () => {
"Pa$sW0rD!", "Pa$sW0rD!",
); );
await page.getByRole("button", { name: "Add room" }).click(); await page.getByRole("navigation", { name: "Room list" }).getByRole("button", { name: "Add" }).click();
await page.getByRole("menuitem", { name: "New room" }).click(); await page.getByRole("menuitem", { name: "New room" }).click();
await page.getByRole("textbox", { name: "Name" }).fill("test room"); await page.getByRole("textbox", { name: "Name" }).fill("test room");
await page.getByRole("button", { name: "Create room" }).click(); await page.getByRole("button", { name: "Create room" }).click();
@@ -78,7 +78,7 @@ test.describe("Key backup reset from elsewhere", () => {
await page.getByRole("button", { name: "Continue" }).click(); await page.getByRole("button", { name: "Continue" }).click();
await registerAccountMas(page, mailpitClient, testUsername, `${testUsername}@email.com`, testPassword); await registerAccountMas(page, mailpitClient, testUsername, `${testUsername}@email.com`, testPassword);
await page.getByRole("button", { name: "Add room" }).click(); await page.getByRole("navigation", { name: "Room list" }).getByRole("button", { name: "Add" }).click();
await page.getByRole("menuitem", { name: "New room" }).click(); await page.getByRole("menuitem", { name: "New room" }).click();
await page.getByRole("textbox", { name: "Name" }).fill("test room"); await page.getByRole("textbox", { name: "Name" }).fill("test room");
await page.getByRole("button", { name: "Create room" }).click(); await page.getByRole("button", { name: "Create room" }).click();
+2 -1
View File
@@ -21,7 +21,8 @@ const checkDMRoom = async (page: Page) => {
}; };
const startDMWithBob = async (page: Page, bob: Bot) => { const startDMWithBob = async (page: Page, bob: Bot) => {
await page.locator(".mx_LegacyRoomList").getByRole("button", { name: "Start chat" }).click(); await page.getByRole("navigation", { name: "Room list" }).getByRole("button", { name: "Add" }).click();
await page.getByRole("menuitem", { name: "Start chat" }).click();
await page.getByTestId("invite-dialog-input").fill(bob.credentials.userId); await page.getByTestId("invite-dialog-input").fill(bob.credentials.userId);
await page.locator(".mx_InviteDialog_tile_nameStack_name").getByText("Bob").click(); await page.locator(".mx_InviteDialog_tile_nameStack_name").getByText("Bob").click();
await expect( await expect(
@@ -143,10 +143,7 @@ test.describe("Cryptography", function () {
); );
// Alice accepts the invite // Alice accepts the invite
await expect( await page.getByRole("option", { name: "Test room" }).click();
page.getByRole("group", { name: "Invites" }).locator(".mx_RoomSublist_tiles").getByRole("treeitem"),
).toHaveCount(1);
await page.getByRole("treeitem", { name: "Test room" }).click();
await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click(); await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click();
// Bob sends an encrypted event and an undecryptable event // Bob sends an encrypted event and an undecryptable event
@@ -280,10 +277,7 @@ test.describe("Cryptography", function () {
); );
// Alice accepts the invite // Alice accepts the invite
await expect( await page.getByRole("option", { name: "Test room" }).click();
page.getByRole("group", { name: "Invites" }).locator(".mx_RoomSublist_tiles").getByRole("treeitem"),
).toHaveCount(1);
await page.getByRole("treeitem", { name: "Test room" }).click();
await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click(); await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click();
// wait until we're joined and see the timeline // wait until we're joined and see the timeline
+2 -2
View File
@@ -23,7 +23,7 @@ test.describe("Key storage out of sync toast", () => {
await deleteCachedSecrets(page); await deleteCachedSecrets(page);
// We won't be prompted for crypto setup unless we have an e2e room, so make one // We won't be prompted for crypto setup unless we have an e2e room, so make one
await page.getByRole("button", { name: "Add room" }).click(); await page.getByRole("navigation", { name: "Room list" }).getByRole("button", { name: "Add" }).click();
await page.getByRole("menuitem", { name: "New room" }).click(); await page.getByRole("menuitem", { name: "New room" }).click();
await page.getByRole("textbox", { name: "Name" }).fill("Test room"); await page.getByRole("textbox", { name: "Name" }).fill("Test room");
await page.getByRole("button", { name: "Create room" }).click(); await page.getByRole("button", { name: "Create room" }).click();
@@ -68,7 +68,7 @@ test.describe("'Turn on key storage' toast", () => {
await logIntoElementAndVerify(page, credentials, recoveryKey.encodedPrivateKey); await logIntoElementAndVerify(page, credentials, recoveryKey.encodedPrivateKey);
// We won't be prompted for crypto setup unless we have an e2e room, so make one // We won't be prompted for crypto setup unless we have an e2e room, so make one
await page.getByRole("button", { name: "Add room" }).click(); await page.getByRole("navigation", { name: "Room list" }).getByRole("button", { name: "Add" }).click();
await page.getByRole("menuitem", { name: "New room" }).click(); await page.getByRole("menuitem", { name: "New room" }).click();
await page.getByRole("textbox", { name: "Name" }).fill("Test room"); await page.getByRole("textbox", { name: "Name" }).fill("Test room");
await page.getByRole("button", { name: "Create room" }).click(); await page.getByRole("button", { name: "Create room" }).click();
+2 -2
View File
@@ -438,8 +438,8 @@ export async function sendMessageInCurrentRoom(page: Page, message: string): Pro
* @param isEncrypted - Whether the room should be encrypted * @param isEncrypted - Whether the room should be encrypted
*/ */
export async function createRoom(page: Page, roomName: string, isEncrypted: boolean): Promise<void> { export async function createRoom(page: Page, roomName: string, isEncrypted: boolean): Promise<void> {
await page.getByRole("button", { name: "Add room" }).click(); await page.getByRole("navigation", { name: "Room list" }).getByRole("button", { name: "Add" }).click();
await page.locator(".mx_IconizedContextMenu").getByRole("menuitem", { name: "New room" }).click(); await page.getByRole("menuitem", { name: "New room" }).click();
const dialog = page.locator(".mx_Dialog"); const dialog = page.locator(".mx_Dialog");
+2 -1
View File
@@ -77,7 +77,8 @@ test.describe("Invite dialog", function () {
"should support inviting a user to Direct Messages", "should support inviting a user to Direct Messages",
{ tag: "@screenshot" }, { tag: "@screenshot" },
async ({ page, app, user, bot }) => { async ({ page, app, user, bot }) => {
await page.locator(".mx_LegacyRoomList").getByRole("button", { name: "Start chat" }).click(); await page.getByRole("navigation", { name: "Room list" }).getByRole("button", { name: "Add" }).click();
await page.getByRole("menuitem", { name: "Start chat" }).click();
const other = page.locator(".mx_InviteDialog_other"); const other = page.locator(".mx_InviteDialog_other");
// Assert that the header is rendered // Assert that the header is rendered
+11 -14
View File
@@ -59,7 +59,7 @@ test.describe("Knock Into Room", () => {
// Knocked room should appear in Rooms // Knocked room should appear in Rooms
await expect( await expect(
page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity" }), page.getByTestId("room-list").getByRole("option", { name: "Open room Cybersecurity" }),
).toBeVisible(); ).toBeVisible();
// bot waits for knock request from Alice // bot waits for knock request from Alice
@@ -77,7 +77,7 @@ test.describe("Knock Into Room", () => {
await bot.inviteUser(room.roomId, user.userId); await bot.inviteUser(room.roomId, user.userId);
await expect( await expect(
page.getByRole("group", { name: "Invites" }).getByRole("treeitem", { name: "Cybersecurity" }), page.getByTestId("room-list").getByRole("option", { name: "Open room Cybersecurity" }),
).toBeVisible(); ).toBeVisible();
// Alice have to accept invitation in order to join the room. // Alice have to accept invitation in order to join the room.
@@ -85,7 +85,7 @@ test.describe("Knock Into Room", () => {
await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click(); await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click();
await expect( await expect(
page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity" }), page.getByTestId("room-list").getByRole("option", { name: "Open room Cybersecurity" }),
).toBeVisible(); ).toBeVisible();
await expect(page.getByText("Alice joined the room")).toBeVisible(); await expect(page.getByText("Alice joined the room")).toBeVisible();
@@ -136,7 +136,7 @@ test.describe("Knock Into Room", () => {
// Knocked room should appear in Rooms // Knocked room should appear in Rooms
await expect( await expect(
page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity" }), page.getByTestId("room-list").getByRole("option", { name: "Open room Cybersecurity" }),
).toBeVisible(); ).toBeVisible();
// bot waits for knock request from Alice // bot waits for knock request from Alice
@@ -154,7 +154,7 @@ test.describe("Knock Into Room", () => {
await bot.inviteUser(room.roomId, user.userId); await bot.inviteUser(room.roomId, user.userId);
await expect( await expect(
page.getByRole("group", { name: "Invites" }).getByRole("treeitem", { name: "Cybersecurity" }), page.getByTestId("room-list").getByRole("option", { name: "Open room Cybersecurity" }),
).toBeVisible(); ).toBeVisible();
// Alice have to accept invitation in order to join the room. // Alice have to accept invitation in order to join the room.
@@ -162,7 +162,7 @@ test.describe("Knock Into Room", () => {
await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click(); await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click();
await expect( await expect(
page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity" }), page.getByTestId("room-list").getByRole("option", { name: "Open room Cybersecurity" }),
).toBeVisible(); ).toBeVisible();
await expect(page.getByText("Alice joined the room")).toBeVisible(); await expect(page.getByText("Alice joined the room")).toBeVisible();
@@ -215,14 +215,14 @@ test.describe("Knock Into Room", () => {
await expect(roomPreviewBar.getByRole("heading", { name: "Request to join sent" })).toBeVisible(); await expect(roomPreviewBar.getByRole("heading", { name: "Request to join sent" })).toBeVisible();
// Knocked room should appear in Rooms // Knocked room should appear in Rooms
page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity" }); page.getByTestId("room-list").getByRole("option", { name: "Open room Cybersecurity" });
await roomPreviewBar.getByRole("button", { name: "Cancel request" }).click(); await roomPreviewBar.getByRole("button", { name: "Cancel request" }).click();
await expect(roomPreviewBar.getByRole("heading", { name: "Ask to join Cybersecurity?" })).toBeVisible(); await expect(roomPreviewBar.getByRole("heading", { name: "Ask to join Cybersecurity?" })).toBeVisible();
await expect(roomPreviewBar.getByRole("button", { name: "Request access" })).toBeVisible(); await expect(roomPreviewBar.getByRole("button", { name: "Request access" })).toBeVisible();
await expect( await expect(
page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity" }), page.getByTestId("room-list").getByRole("option", { name: "Open room Cybersecurity" }),
).not.toBeVisible(); ).not.toBeVisible();
}); });
@@ -244,7 +244,7 @@ test.describe("Knock Into Room", () => {
// Knocked room should appear in Rooms // Knocked room should appear in Rooms
await expect( await expect(
page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity" }), page.getByTestId("room-list").getByRole("option", { name: "Open room Cybersecurity" }),
).toBeVisible(); ).toBeVisible();
// bot waits for knock request from Alice // bot waits for knock request from Alice
@@ -262,13 +262,10 @@ test.describe("Knock Into Room", () => {
await bot.kick(room.roomId, user.userId); await bot.kick(room.roomId, user.userId);
// Room should stay in Rooms and have red badge when knock is denied // Room should stay in Rooms and have red badge when knock is denied
await expect(
page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity", exact: true }),
).not.toBeVisible();
await expect( await expect(
page page
.getByRole("group", { name: "Rooms" }) .getByTestId("room-list")
.getByRole("treeitem", { name: "Cybersecurity 1 unread mention." }), .getByRole("option", { name: "Open room Cybersecurity with 1 unread mention." }),
).toBeVisible(); ).toBeVisible();
await expect(roomPreviewBar.getByRole("heading", { name: "You have been denied access" })).toBeVisible(); await expect(roomPreviewBar.getByRole("heading", { name: "You have been denied access" })).toBeVisible();
+1 -1
View File
@@ -17,7 +17,7 @@ test.describe("LeftPanel", () => {
// create rooms and check room names are correct // create rooms and check room names are correct
for (const name of ["Apple", "Pineapple", "Orange"]) { for (const name of ["Apple", "Pineapple", "Orange"]) {
await app.client.createRoom({ name }); await app.client.createRoom({ name });
await expect(page.getByRole("treeitem", { name })).toBeVisible(); await expect(page.getByRole("option", { name: `Open room ${name}` })).toBeVisible();
} }
}); });
}); });
+18 -24
View File
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import type { JSHandle, Page } from "@playwright/test"; import type { JSHandle, Locator, Page } from "@playwright/test";
import type { MatrixEvent, Room, IndexedDBStore, ReceiptType } from "matrix-js-sdk/src/matrix"; import type { MatrixEvent, Room, IndexedDBStore, ReceiptType } from "matrix-js-sdk/src/matrix";
import { test as base, expect } from "../../element-web-test"; import { test as base, expect } from "../../element-web-test";
import { type Bot } from "../../pages/bot"; import { type Bot } from "../../pages/bot";
@@ -428,7 +428,7 @@ class Helpers {
} }
getRoomListTile(label: string) { getRoomListTile(label: string) {
return this.page.getByRole("treeitem", { name: new RegExp("^" + label) }); return this.page.getByRole("option", { name: new RegExp("^Open room " + label) });
} }
/** /**
@@ -446,8 +446,8 @@ class Helpers {
*/ */
async assertRead(room: RoomRef) { async assertRead(room: RoomRef) {
const tile = this.getRoomListTile(room.name); const tile = this.getRoomListTile(room.name);
await expect(tile.locator(".mx_NotificationBadge_dot")).not.toBeVisible(); await expect(tile.getByTestId("notification-decoration")).not.toBeVisible();
await expect(tile.locator(".mx_NotificationBadge_count")).not.toBeVisible(); await expect(tile).not.toHaveAccessibleName(/with \d* unread message/);
} }
/** /**
@@ -463,15 +463,18 @@ class Helpers {
/** /**
* Assert a given room is marked as unread (via the room list tile) * Assert a given room is marked as unread (via the room list tile)
* @param room - the name of the room to check * @param room - the name of the room to check
* @param count - the numeric count to assert, or if "." specified then a bold/dot (no count) state is asserted * @param count - the numeric count to assert
*/ */
async assertUnread(room: RoomRef, count: number | ".") { async assertUnread(room: RoomRef, count: number) {
const tile = this.getRoomListTile(room.name); const tile = this.getRoomListTile(room.name);
if (count === ".") { await expect(tile).toBeVisible();
await expect(tile.locator(".mx_NotificationBadge_dot")).toBeVisible(); await expect(tile).toHaveAccessibleName(/with \d* unread message/);
} else {
await expect(tile.locator(".mx_NotificationBadge_count")).toHaveText(count.toString());
} }
async unreadCountForRoomTile(tile: Locator): Promise<number> {
const accessibleName = await tile.getAttribute("aria-label");
const match = accessibleName?.match(/(\d+)\s+unread message/);
return match ? parseInt(match[1], 10) : 0;
} }
/** /**
@@ -487,7 +490,7 @@ class Helpers {
// .toBeLessThan doesn't have a retry mechanism, so we use .poll // .toBeLessThan doesn't have a retry mechanism, so we use .poll
await expect await expect
.poll(async () => { .poll(async () => {
return parseInt(await tile.locator(".mx_NotificationBadge_count").textContent(), 10); return this.unreadCountForRoomTile(tile);
}) })
.toBeLessThan(lessThan); .toBeLessThan(lessThan);
} }
@@ -505,7 +508,7 @@ class Helpers {
// .toBeGreaterThan doesn't have a retry mechanism, so we use .poll // .toBeGreaterThan doesn't have a retry mechanism, so we use .poll
await expect await expect
.poll(async () => { .poll(async () => {
return parseInt(await tile.locator(".mx_NotificationBadge_count").textContent(), 10); return this.unreadCountForRoomTile(tile);
}) })
.toBeGreaterThan(greaterThan); .toBeGreaterThan(greaterThan);
} }
@@ -596,24 +599,15 @@ class Helpers {
await button.click(); await button.click();
} }
/**
* Toggle the `Show rooms with unread messages first` option for the room list
*/
async toggleRoomUnreadOrder() {
await this.toggleRoomListMenu();
await this.page.getByText("Show rooms with unread messages first").click();
// Close contextual menu
await this.page.locator(".mx_ContextualMenu_background").click();
}
/** /**
* Assert that the room list is ordered as expected * Assert that the room list is ordered as expected
* @param rooms * @param rooms
*/ */
async assertRoomListOrder(rooms: Array<{ name: string }>) { async assertRoomListOrder(rooms: Array<{ name: string }>) {
const roomList = this.page.locator(".mx_RoomTile_title"); const roomListContainer = this.page.getByTestId("room-list");
const roomTiles = roomListContainer.getByRole("option");
for (const [i, room] of rooms.entries()) { for (const [i, room] of rooms.entries()) {
await expect(roomList.nth(i)).toHaveText(room.name); await expect(roomTiles.nth(i)).toHaveAccessibleName(new RegExp(`${room.name}`));
} }
} }
} }
@@ -112,7 +112,7 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
const main3 = await sendMessage(bot); const main3 = await sendMessage(bot);
// (So the room starts off unread) // (So the room starts off unread)
await expect(page.getByLabel(`${otherRoomName} 3 unread messages.`)).toBeVisible(); await expect(page.getByLabel(`${otherRoomName} with 3 unread messages.`)).toBeVisible();
// When we send a threaded receipt for the last event in main // When we send a threaded receipt for the last event in main
// And an unthreaded receipt for an earlier event // And an unthreaded receipt for an earlier event
@@ -147,13 +147,13 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
await sendMessage(bot); await sendMessage(bot);
// (The room starts off unread) // (The room starts off unread)
await expect(page.getByLabel(`${otherRoomName} 3 unread messages.`)).toBeVisible(); await expect(page.getByLabel(`${otherRoomName} with 3 unread messages.`)).toBeVisible();
// When we send a threaded receipt for the second-last event in main // When we send a threaded receipt for the second-last event in main
await sendThreadedReadReceipt(app, main2); await sendThreadedReadReceipt(app, main2);
// Then the room has only one unread // Then the room has only one unread
await expect(page.getByLabel(`${otherRoomName} 1 unread message.`)).toBeVisible(); await expect(page.getByLabel(`${otherRoomName} with 1 unread message.`)).toBeVisible();
}); });
test("Considers room read if there is only a main thread and we have a main receipt", async ({ test("Considers room read if there is only a main thread and we have a main receipt", async ({
@@ -166,7 +166,7 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
await sendMessage(bot); await sendMessage(bot);
const main3 = await sendMessage(bot); const main3 = await sendMessage(bot);
// (The room starts off unread) // (The room starts off unread)
await expect(page.getByLabel(`${otherRoomName} 3 unread messages.`)).toBeVisible(); await expect(page.getByLabel(`${otherRoomName} with 3 unread messages.`)).toBeVisible();
// When we send a threaded receipt for the last event in main // When we send a threaded receipt for the last event in main
await sendThreadedReadReceipt(app, main3); await sendThreadedReadReceipt(app, main3);
@@ -186,7 +186,7 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
const thread1a = await botSendThreadMessage(bot, main1.event_id); const thread1a = await botSendThreadMessage(bot, main1.event_id);
await botSendThreadMessage(bot, main1.event_id); await botSendThreadMessage(bot, main1.event_id);
// 1 unread on the main thread, 2 in the new thread that aren't shown // 1 unread on the main thread, 2 in the new thread that aren't shown
await expect(page.getByLabel(`${otherRoomName} 1 unread message.`)).toBeVisible(); await expect(page.getByLabel(`${otherRoomName} with 1 unread message.`)).toBeVisible();
// When we send receipts for main, and the second-last in the thread // When we send receipts for main, and the second-last in the thread
await sendThreadedReadReceipt(app, main1); await sendThreadedReadReceipt(app, main1);
@@ -203,7 +203,7 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
await botSendThreadMessage(bot, main1.event_id); await botSendThreadMessage(bot, main1.event_id);
const thread1b = await botSendThreadMessage(bot, main1.event_id); const thread1b = await botSendThreadMessage(bot, main1.event_id);
// 1 unread on the main thread, 2 in the new thread which don't show // 1 unread on the main thread, 2 in the new thread which don't show
await expect(page.getByLabel(`${otherRoomName} 1 unread message.`)).toBeVisible(); await expect(page.getByLabel(`${otherRoomName} with 1 unread message.`)).toBeVisible();
// When we send receipts for main, and the last in the thread // When we send receipts for main, and the last in the thread
await sendThreadedReadReceipt(app, main1); await sendThreadedReadReceipt(app, main1);
@@ -226,7 +226,7 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
const thread1a = await botSendThreadMessage(bot, main1.event_id); const thread1a = await botSendThreadMessage(bot, main1.event_id);
await botSendThreadMessage(bot, main1.event_id); await botSendThreadMessage(bot, main1.event_id);
// 1 unread on the main thread, 2 in the new thread which don't count // 1 unread on the main thread, 2 in the new thread which don't count
await expect(page.getByLabel(`${otherRoomName} 1 unread message.`)).toBeVisible(); await expect(page.getByLabel(`${otherRoomName} with 1 unread message.`)).toBeVisible();
// When we send an unthreaded receipt for the second-last in the thread // When we send an unthreaded receipt for the second-last in the thread
await sendUnthreadedReadReceipt(app, thread1a); await sendUnthreadedReadReceipt(app, thread1a);
@@ -251,7 +251,7 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
const thread1b = await botSendThreadMessage(bot, main1.event_id); const thread1b = await botSendThreadMessage(bot, main1.event_id);
await sendMessage(bot); await sendMessage(bot);
// 2 unreads on the main thread, 2 in the new thread which don't count // 2 unreads on the main thread, 2 in the new thread which don't count
await expect(page.getByLabel(`${otherRoomName} 2 unread messages.`)).toBeVisible(); await expect(page.getByLabel(`${otherRoomName} with 2 unread messages.`)).toBeVisible();
// When we send an unthreaded receipt for the last in the thread // When we send an unthreaded receipt for the last in the thread
await sendUnthreadedReadReceipt(app, thread1b); await sendUnthreadedReadReceipt(app, thread1b);
@@ -259,7 +259,7 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
// Then the room has only one unread - the one in the // Then the room has only one unread - the one in the
// main thread, because it is later than the unthreaded // main thread, because it is later than the unthreaded
// receipt. // receipt.
await expect(page.getByLabel(`${otherRoomName} 1 unread message.`)).toBeVisible(); await expect(page.getByLabel(`${otherRoomName} with 1 unread message.`)).toBeVisible();
}); });
/** /**
@@ -291,7 +291,9 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
const uriEncodedLastMessageId = encodeURIComponent(lastMessageId); const uriEncodedLastMessageId = encodeURIComponent(lastMessageId);
// wait until all messages have been received // wait until all messages have been received
await expect(page.getByLabel(`${otherRoomName} ${sendMessageResponses.length} unread messages.`)).toBeVisible(); await expect(
page.getByLabel(`${otherRoomName} with ${sendMessageResponses.length} unread messages.`),
).toBeVisible();
// switch to the room with the messages // switch to the room with the messages
await page.goto(`/#/room/${otherRoomId}`); await page.goto(`/#/room/${otherRoomId}`);
@@ -12,7 +12,7 @@ import { test } from ".";
test.describe("Read receipts", { tag: "@mergequeue" }, () => { test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("Room list order", () => { test.describe("Room list order", () => {
test("Rooms with unread messages appear at the top of room list if 'unread first' is selected", async ({ test("Rooms with unread messages appear at the top of room list with default 'activity' ordering", async ({
roomAlpha: room1, roomAlpha: room1,
roomBeta: room2, roomBeta: room2,
util, util,
@@ -22,15 +22,18 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
await util.goTo(room2); await util.goTo(room2);
// Display the unread first room // Display the unread first room
await util.toggleRoomUnreadOrder();
await util.receiveMessages(room1, ["Msg1"]); await util.receiveMessages(room1, ["Msg1"]);
await page.reload(); await page.reload();
// switch rooms so they can re-order in the list
await util.goTo(room1);
// Room 1 has an unread message and should be displayed first // Room 1 has an unread message and should be displayed first
// (as the default is to sort by activity)
await util.assertRoomListOrder([room1, room2]); await util.assertRoomListOrder([room1, room2]);
}); });
test("Rooms with unread threads appear at the top of room list if 'unread first' is selected", async ({ test("Rooms with unread threads appear at the top of room list with default 'activity' order", async ({
roomAlpha: room1, roomAlpha: room1,
roomBeta: room2, roomBeta: room2,
util, util,
@@ -42,7 +45,6 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
await util.assertRead(room1); await util.assertRead(room1);
// Display the unread first room // Display the unread first room
await util.toggleRoomUnreadOrder();
await util.receiveMessages(room1, [msg.threadedOff("Msg1", "Resp1")]); await util.receiveMessages(room1, [msg.threadedOff("Msg1", "Resp1")]);
await util.saveAndReload(); await util.saveAndReload();
+1 -1
View File
@@ -46,7 +46,7 @@ test.describe("Create Room", () => {
test("should allow us to start a chat and show encryption state", async ({ page, user, app }) => { test("should allow us to start a chat and show encryption state", async ({ page, user, app }) => {
await page.getByRole("button", { name: "Add", exact: true }).click(); await page.getByRole("button", { name: "Add", exact: true }).click();
await page.getByText("Start new chat").click(); await page.getByRole("menuitem", { name: "Start chat" }).click();
await page.getByTestId("invite-dialog-input").fill(user.userId); await page.getByTestId("invite-dialog-input").fill(user.userId);
+9 -4
View File
@@ -15,6 +15,11 @@ import { type ElementAppPage } from "../../pages/ElementAppPage";
test.describe("Room Header", () => { test.describe("Room Header", () => {
test.use({ test.use({
displayName: "Sakura", displayName: "Sakura",
config: {
features: {
feature_new_room_list: false,
},
},
}); });
test.describe("with feature_notifications enabled", () => { test.describe("with feature_notifications enabled", () => {
@@ -23,7 +28,7 @@ test.describe("Room Header", () => {
}); });
test("should render default buttons properly", { tag: "@screenshot" }, async ({ page, app, user }) => { test("should render default buttons properly", { tag: "@screenshot" }, async ({ page, app, user }) => {
await app.client.createRoom({ name: "Test Room" }); await app.client.createRoom({ name: "Test Room" });
await app.viewRoomByName("Test Room"); await app.viewRoomByNameOnOldRoomList("Test Room");
const header = page.locator(".mx_RoomHeader"); const header = page.locator(".mx_RoomHeader");
@@ -61,7 +66,7 @@ test.describe("Room Header", () => {
"officia deserunt mollit anim id est laborum."; "officia deserunt mollit anim id est laborum.";
await app.client.createRoom({ name: LONG_ROOM_NAME }); await app.client.createRoom({ name: LONG_ROOM_NAME });
await app.viewRoomByName(LONG_ROOM_NAME); await app.viewRoomByNameOnOldRoomList(LONG_ROOM_NAME);
const header = page.locator(".mx_RoomHeader"); const header = page.locator(".mx_RoomHeader");
// Wait until the room name is set // Wait until the room name is set
@@ -86,7 +91,7 @@ test.describe("Room Header", () => {
test("should render room header icon correctly", { tag: "@screenshot" }, async ({ page, app, user }) => { test("should render room header icon correctly", { tag: "@screenshot" }, async ({ page, app, user }) => {
await app.client.createRoom({ name: "Test Room", visibility: "public" as Visibility }); await app.client.createRoom({ name: "Test Room", visibility: "public" as Visibility });
await app.viewRoomByName("Test Room"); await app.viewRoomByNameOnOldRoomList("Test Room");
const header = page.locator(".mx_RoomHeader"); const header = page.locator(".mx_RoomHeader");
@@ -106,7 +111,7 @@ test.describe("Room Header", () => {
await page.getByRole("button", { name: "Create video room" }).click(); await page.getByRole("button", { name: "Create video room" }).click();
await app.viewRoomByName("Test video room"); await app.viewRoomByNameOnOldRoomList("Test video room");
}; };
test.describe("and with feature_notifications enabled", () => { test.describe("and with feature_notifications enabled", () => {
@@ -34,7 +34,7 @@ test.describe("Mark as Unread", () => {
await bot.sendMessage(roomId, "I am a robot. Beep."); await bot.sendMessage(roomId, "I am a robot. Beep.");
// Regular notification on new message // Regular notification on new message
await expect(page.getByLabel(TEST_ROOM_NAME + " 1 unread message.")).toBeVisible(); await expect(page.getByLabel(`Open room ${TEST_ROOM_NAME} with 1 unread message.`)).toBeVisible();
await expect(page).toHaveTitle("Element [1]"); await expect(page).toHaveTitle("Element [1]");
await page.goto("/#/room/" + roomId); await page.goto("/#/room/" + roomId);
@@ -48,9 +48,12 @@ test.describe("Mark as Unread", () => {
const roomTile = page.getByLabel(TEST_ROOM_NAME); const roomTile = page.getByLabel(TEST_ROOM_NAME);
await roomTile.focus(); await roomTile.focus();
await roomTile.getByRole("button", { name: "Room options" }).click(); await roomTile.getByRole("button", { name: "More Options" }).click();
await page.getByRole("menuitem", { name: "Mark as unread" }).click(); await page.getByRole("menuitem", { name: "Mark as unread" }).click();
await expect(page.getByLabel(TEST_ROOM_NAME + " Unread messages.")).toBeVisible(); // focus the user menu to avoid to have hover decoration
await page.getByRole("button", { name: "User menu" }).focus();
await expect(roomTile.getByTestId("notification-decoration")).toBeVisible();
}); });
}); });
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import { type Page, type Request } from "@playwright/test"; import { type Locator, type Page, type Request } from "@playwright/test";
import { test as base, expect } from "../../element-web-test"; import { test as base, expect } from "../../element-web-test";
import type { ElementAppPage } from "../../pages/ElementAppPage"; import type { ElementAppPage } from "../../pages/ElementAppPage";
@@ -38,7 +38,7 @@ const test = base.extend<{
test.describe("Sliding Sync", () => { test.describe("Sliding Sync", () => {
const checkOrder = async (wantOrder: string[], page: Page) => { const checkOrder = async (wantOrder: string[], page: Page) => {
await expect(page.getByRole("group", { name: "Rooms" }).locator(".mx_RoomTile_title")).toHaveText(wantOrder); await expect(page.getByTestId("room-list").locator(".mx_RoomListItemView_text")).toHaveText(wantOrder);
}; };
const bumpRoom = async (roomId: string, app: ElementAppPage) => { const bumpRoom = async (roomId: string, app: ElementAppPage) => {
@@ -50,6 +50,18 @@ test.describe("Sliding Sync", () => {
}); });
}; };
function getPrimaryFilters(page: Page): Locator {
return page.getByTestId("primary-filters");
}
function getRoomOptionsMenu(page: Page): Locator {
return page.getByRole("button", { name: "Room Options" });
}
function getFilterExpandButton(page: Page): Locator {
return getPrimaryFilters(page).getByRole("button", { name: "Expand filter list" });
}
test.use({ test.use({
config: { config: {
features: { features: {
@@ -69,20 +81,15 @@ test.describe("Sliding Sync", () => {
// create rooms and check room names are correct // create rooms and check room names are correct
for (const fruit of ["Apple", "Pineapple", "Orange"]) { for (const fruit of ["Apple", "Pineapple", "Orange"]) {
await app.client.createRoom({ name: fruit }); await app.client.createRoom({ name: fruit });
await expect(page.getByRole("treeitem", { name: fruit })).toBeVisible(); await expect(page.getByRole("option", { name: `Open room ${fruit}` })).toBeVisible();
} }
const roomList = page.getByTestId("room-list");
// Check count, 3 fruits + 1 testRoom = 4 // Check count, 3 fruits + 1 testRoom = 4
await expect(page.locator(".mx_RoomSublist_tiles").getByRole("treeitem")).toHaveCount(4); await expect(roomList.getByRole("option")).toHaveCount(4);
await checkOrder(["Orange", "Pineapple", "Apple", "Test Room"], page); await checkOrder(["Orange", "Pineapple", "Apple", "Test Room"], page);
const locator = page.getByRole("group", { name: "Rooms" }).locator(".mx_RoomSublist_headerContainer"); await getRoomOptionsMenu(page).click();
await locator.hover(); await page.getByRole("menuitemradio", { name: "A-Z" }).click();
await locator.getByRole("button", { name: "List options" }).click();
// force click as the radio button's size is zero
await page.getByRole("menuitemradio", { name: "A-Z" }).dispatchEvent("click");
await expect(page.locator(".mx_StyledRadioButton_checked").getByText("A-Z")).toBeVisible();
await checkOrder(["Apple", "Orange", "Pineapple", "Test Room"], page); await checkOrder(["Apple", "Orange", "Pineapple", "Test Room"], page);
}); });
@@ -93,11 +100,11 @@ test.describe("Sliding Sync", () => {
for (const fruit of ["Apple", "Pineapple", "Orange"]) { for (const fruit of ["Apple", "Pineapple", "Orange"]) {
const id = await app.client.createRoom({ name: fruit }); const id = await app.client.createRoom({ name: fruit });
roomIds.push(id); roomIds.push(id);
await expect(page.getByRole("treeitem", { name: fruit })).toBeVisible(); await expect(page.getByRole("option", { name: `Open room ${fruit}` })).toBeVisible();
} }
// Select the Test Room // Select the Test Room
await page.getByRole("treeitem", { name: "Test Room" }).click(); await page.getByRole("option", { name: "Open room Test Room" }).click();
const [apple, pineapple, orange] = roomIds; const [apple, pineapple, orange] = roomIds;
await checkOrder(["Orange", "Pineapple", "Apple", "Test Room"], page); await checkOrder(["Orange", "Pineapple", "Apple", "Test Room"], page);
await bumpRoom(apple, app); await bumpRoom(apple, app);
@@ -116,7 +123,7 @@ test.describe("Sliding Sync", () => {
for (const fruit of ["Apple", "Pineapple", "Orange"]) { for (const fruit of ["Apple", "Pineapple", "Orange"]) {
const id = await app.client.createRoom({ name: fruit }); const id = await app.client.createRoom({ name: fruit });
roomIds.push(id); roomIds.push(id);
await expect(page.getByRole("treeitem", { name: fruit })).toBeVisible(); await expect(page.getByRole("option", { name: `Open room ${fruit}` })).toBeVisible();
} }
// Given a list of Orange, Pineapple, Apple - if Pineapple is active and a message is sent in Apple, the list should // Given a list of Orange, Pineapple, Apple - if Pineapple is active and a message is sent in Apple, the list should
@@ -124,7 +131,7 @@ test.describe("Sliding Sync", () => {
// be Apple, Orange Pineapple - only when you click on a different room do things reshuffle. // be Apple, Orange Pineapple - only when you click on a different room do things reshuffle.
// Select the Pineapple room // Select the Pineapple room
await page.getByRole("treeitem", { name: "Pineapple" }).click(); await page.getByRole("option", { name: "Open room Pineapple" }).click();
await checkOrder(["Orange", "Pineapple", "Apple", "Test Room"], page); await checkOrder(["Orange", "Pineapple", "Apple", "Test Room"], page);
// Move Apple // Move Apple
@@ -132,7 +139,7 @@ test.describe("Sliding Sync", () => {
await checkOrder(["Apple", "Pineapple", "Orange", "Test Room"], page); await checkOrder(["Apple", "Pineapple", "Orange", "Test Room"], page);
// Select the Test Room // Select the Test Room
await page.getByRole("treeitem", { name: "Test Room" }).click(); await page.getByRole("option", { name: "Open room Test Room" }).click();
// the rooms reshuffle to match reality // the rooms reshuffle to match reality
await checkOrder(["Apple", "Orange", "Pineapple", "Test Room"], page); await checkOrder(["Apple", "Orange", "Pineapple", "Test Room"], page);
@@ -142,25 +149,20 @@ test.describe("Sliding Sync", () => {
// send a message in the test room: unread notification count should increment // send a message in the test room: unread notification count should increment
await bob.sendMessage(testRoom.roomId, "Hello World"); await bob.sendMessage(testRoom.roomId, "Hello World");
const treeItemLocator1 = page.getByRole("treeitem", { name: "Test Room 1 unread message." }); const itemLocator1 = page.getByRole("option", { name: "Open room David Langley with 1 unread message." });
await expect(treeItemLocator1.locator(".mx_NotificationBadge_count")).toHaveText("1"); await expect(itemLocator1.getByTestId("notification-decoration")).toHaveText("1");
// await expect(page.locator(".mx_NotificationBadge")).not.toHaveClass("mx_NotificationBadge_highlighted");
await expect(treeItemLocator1.locator(".mx_NotificationBadge")).not.toHaveClass(
/mx_NotificationBadge_highlighted/,
);
// send an @mention: highlight count (red) should be 2. // send an @mention: highlight count (red) should be 2.
await bob.sendMessage(testRoom.roomId, `Hello ${user.displayName}`); await bob.sendMessage(testRoom.roomId, `Hello ${user.displayName}`);
const treeItemLocator2 = page.getByRole("treeitem", { const itemLocator2 = page.getByRole("treeitem", {
name: "Test Room 2 unread messages including mentions.", name: "Open room Test Room 2 unread messages including mentions.",
}); });
await expect(treeItemLocator2.locator(".mx_NotificationBadge_count")).toHaveText("2"); await expect(itemLocator2.getByTestId("notification-decoration")).toHaveText("2");
await expect(treeItemLocator2.locator(".mx_NotificationBadge")).toHaveClass(/mx_NotificationBadge_highlighted/);
// click on the room, the notif counts should disappear // click on the room, the notif counts should disappear
await page.getByRole("treeitem", { name: "Test Room 2 unread messages including mentions." }).click(); await page.getByRole("option", { name: "Open room Test Room 2 unread messages including mentions." }).click();
await expect( await expect(
page.getByRole("treeitem", { name: "Test Room" }).locator("mx_NotificationBadge_count"), page.getByRole("option", { name: "Open room Test Room" }).getByTestId("notification-decoration"),
).not.toBeAttached(); ).not.toBeAttached();
}); });
@@ -175,7 +177,9 @@ test.describe("Sliding Sync", () => {
// wait for this message to arrive, tell by the room list resorting // wait for this message to arrive, tell by the room list resorting
await checkOrder(["Test Room", "Dummy"], page); await checkOrder(["Test Room", "Dummy"], page);
await expect(page.getByRole("treeitem", { name: "Test Room" }).locator(".mx_NotificationBadge")).toBeAttached(); await expect(
page.getByRole("option", { name: "Open room Test Room" }).getByTestId("notification-decoration"),
).toBeAttached();
}); });
test("should update user settings promptly", async ({ page, app }) => { test("should update user settings promptly", async ({ page, app }) => {
@@ -193,7 +197,7 @@ test.describe("Sliding Sync", () => {
for (const fruit of ["Apple", "Pineapple", "Orange"]) { for (const fruit of ["Apple", "Pineapple", "Orange"]) {
const id = await app.client.createRoom({ name: fruit }); const id = await app.client.createRoom({ name: fruit });
roomIds.push(id); roomIds.push(id);
await expect(page.getByRole("treeitem", { name: fruit })).toBeVisible(); await expect(page.getByRole("option", { name: `Open room ${fruit}` })).toBeVisible();
} }
const [roomAId, roomPId] = roomIds; const [roomAId, roomPId] = roomIds;
@@ -206,7 +210,7 @@ test.describe("Sliding Sync", () => {
// Select the Test Room and wait for playwright to get the request // Select the Test Room and wait for playwright to get the request
const [request] = await Promise.all([ const [request] = await Promise.all([
page.waitForRequest(matchRoomSubRequest(roomAId)), page.waitForRequest(matchRoomSubRequest(roomAId)),
page.getByRole("treeitem", { name: "Apple", exact: true }).click(), page.getByRole("option", { name: "Open room Apple", exact: true }).click(),
]); ]);
const roomSubscriptions = request.postDataJSON().room_subscriptions; const roomSubscriptions = request.postDataJSON().room_subscriptions;
expect(roomSubscriptions, "room_subscriptions is object").toBeDefined(); expect(roomSubscriptions, "room_subscriptions is object").toBeDefined();
@@ -214,7 +218,7 @@ test.describe("Sliding Sync", () => {
// Switch to another room and wait for playwright to get the request // Switch to another room and wait for playwright to get the request
await Promise.all([ await Promise.all([
page.waitForRequest(matchRoomSubRequest(roomPId)), page.waitForRequest(matchRoomSubRequest(roomPId)),
page.getByRole("treeitem", { name: "Pineapple", exact: true }).click(), page.getByRole("option", { name: "Open room Pineapple", exact: true }).click(),
]); ]);
}); });
@@ -240,34 +244,29 @@ test.describe("Sliding Sync", () => {
{ roomNames, clientUserId }, { roomNames, clientUserId },
); );
await expect( await getFilterExpandButton(page).click();
page.getByRole("group", { name: "Invites" }).locator(".mx_RoomSublist_tiles").getByRole("treeitem"), const primaryFilters = getPrimaryFilters(page);
).toHaveCount(3); await primaryFilters.getByRole("option", { name: "Invites" }).click();
await expect(page.getByTestId("room-list").getByRole("option")).toHaveCount(3);
// Select the room to join // Select the room to join
await page.getByRole("treeitem", { name: "Room to Join" }).click(); await page.getByRole("option", { name: "Open room Room to Join" }).click();
// Accept the invite // Accept the invite
await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click(); await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click();
await checkOrder(["Room to Join", "Test Room"], page); await checkOrder(["Room to Rescind", "Room to Reject"], page);
// Select the room to reject // Select the room to reject
await page.getByRole("treeitem", { name: "Room to Reject" }).click(); await page.getByRole("option", { name: "Open room Room to Reject" }).click();
// Decline the invite // Decline the invite
await page.locator(".mx_RoomView").getByRole("button", { name: "Decline", exact: true }).click(); await page.locator(".mx_RoomView").getByRole("button", { name: "Decline", exact: true }).click();
await expect( await expect(page.getByTestId("room-list").getByRole("option")).toHaveCount(1);
page.getByRole("group", { name: "Invites" }).locator(".mx_RoomSublist_tiles").getByRole("treeitem"),
).toHaveCount(2);
// check the lists are correct await expect(page.getByRole("option", { name: "Open room Room to Rescind" })).toBeVisible();
await checkOrder(["Room to Join", "Test Room"], page);
const titleLocator = page.getByRole("group", { name: "Invites" }).locator(".mx_RoomTile_title");
await expect(titleLocator).toHaveCount(1);
await expect(titleLocator).toHaveText("Room to Rescind");
// now rescind the invite // now rescind the invite
await bot.evaluate( await bot.evaluate(
@@ -277,10 +276,14 @@ test.describe("Sliding Sync", () => {
{ roomRescind, clientUserId }, { roomRescind, clientUserId },
); );
await page.getByRole("option", { name: "Open room Room to Rescind" }).click();
await page.locator(".mx_RoomView").getByRole("button", { name: "Forget this room", exact: true }).click();
await primaryFilters.getByRole("option", { name: "Invites" }).click();
// Wait for the rescind to take effect and check the joined list once more // Wait for the rescind to take effect and check the joined list once more
await expect( await expect(page.getByTestId("room-list").getByRole("option")).toHaveCount(2);
page.getByRole("group", { name: "Rooms" }).locator(".mx_RoomSublist_tiles").getByRole("treeitem"),
).toHaveCount(2);
await checkOrder(["Room to Join", "Test Room"], page); await checkOrder(["Room to Join", "Test Room"], page);
}); });
@@ -293,19 +296,27 @@ test.describe("Sliding Sync", () => {
await app.client.evaluate(async (client, roomId) => { await app.client.evaluate(async (client, roomId) => {
await client.setRoomTag(roomId, "m.favourite", { order: 0.5 }); await client.setRoomTag(roomId, "m.favourite", { order: 0.5 });
}, roomId); }, roomId);
await expect(page.getByRole("group", { name: "Favourites" }).getByText("Favourite DM")).toBeVisible();
await expect(page.getByRole("group", { name: "People" }).getByText("Favourite DM")).not.toBeAttached(); await getFilterExpandButton(page).click();
const primaryFilters = getPrimaryFilters(page);
await primaryFilters.getByRole("option", { name: "Favourites" }).click();
await expect(page.getByRole("option", { name: "Favourite DM" })).toBeVisible();
await primaryFilters.getByRole("option", { name: "People" }).click();
await expect(page.getByRole("option", { name: "Favourite DM" })).not.toBeAttached();
}); });
// Regression test for a bug in SS mode, but would be useful to have in non-SS mode too. // Regression test for a bug in SS mode, but would be useful to have in non-SS mode too.
// This ensures we are setting RoomViewStore state correctly. // This ensures we are setting RoomViewStore state correctly.
test("should clear the reply to field when swapping rooms", async ({ page, app, testRoom }) => { test("should clear the reply to field when swapping rooms", async ({ page, app, testRoom }) => {
await app.client.createRoom({ name: "Other Room" }); await app.client.createRoom({ name: "Other Room" });
await expect(page.getByRole("treeitem", { name: "Other Room" })).toBeVisible(); await expect(page.getByRole("option", { name: "Open room Other Room" })).toBeVisible();
await app.client.sendMessage(testRoom.roomId, "Hello world"); await app.client.sendMessage(testRoom.roomId, "Hello world");
// select the room // select the room
await page.getByRole("treeitem", { name: "Test Room" }).click(); await page.getByRole("option", { name: "Open room Test Room" }).click();
await expect(page.locator(".mx_ReplyPreview")).not.toBeAttached(); await expect(page.locator(".mx_ReplyPreview")).not.toBeAttached();
@@ -318,13 +329,13 @@ test.describe("Sliding Sync", () => {
await expect(page.locator(".mx_ReplyPreview")).toBeVisible(); await expect(page.locator(".mx_ReplyPreview")).toBeVisible();
// now click Other Room // now click Other Room
await page.getByRole("treeitem", { name: "Other Room" }).click(); await page.getByRole("option", { name: "Open room Other Room" }).click();
// ensure the reply-to disappears // ensure the reply-to disappears
await expect(page.locator(".mx_ReplyPreview")).not.toBeAttached(); await expect(page.locator(".mx_ReplyPreview")).not.toBeAttached();
// click back // click back
await page.getByRole("treeitem", { name: "Test Room" }).click(); await page.getByRole("option", { name: "Open room Test Room" }).click();
// ensure the reply-to reappears // ensure the reply-to reappears
await expect(page.locator(".mx_ReplyPreview")).toBeVisible(); await expect(page.locator(".mx_ReplyPreview")).toBeVisible();
@@ -338,7 +349,7 @@ test.describe("Sliding Sync", () => {
await app.client.sendMessage(testRoom.roomId, "Reply to me"); await app.client.sendMessage(testRoom.roomId, "Reply to me");
// select the room // select the room
await page.getByRole("treeitem", { name: "Test Room" }).click(); await page.getByRole("option", { name: "Open room Test Room" }).click();
await expect(page.locator(".mx_ReplyPreview")).not.toBeAttached(); await expect(page.locator(".mx_ReplyPreview")).not.toBeAttached();
// click reply-to on the Reply to me message // click reply-to on the Reply to me message
+12 -12
View File
@@ -96,9 +96,9 @@ test.describe("Spaces", () => {
await page.getByRole("button", { name: "Go to my first room" }).click(); await page.getByRole("button", { name: "Go to my first room" }).click();
// Assert rooms exist in the room list // Assert rooms exist in the room list
await expect(page.getByRole("treeitem", { name: "General" })).toBeVisible(); await expect(page.getByRole("option", { name: "General" })).toBeVisible();
await expect(page.getByRole("treeitem", { name: "Random" })).toBeVisible(); await expect(page.getByRole("option", { name: "Random" })).toBeVisible();
await expect(page.getByRole("treeitem", { name: "Jokes" })).toBeVisible(); await expect(page.getByRole("option", { name: "Jokes" })).toBeVisible();
}, },
); );
@@ -127,10 +127,10 @@ test.describe("Spaces", () => {
await page.getByRole("button", { name: "Skip for now" }).click(); await page.getByRole("button", { name: "Skip for now" }).click();
// Assert rooms exist in the room list // Assert rooms exist in the room list
const roomList = page.getByRole("tree", { name: "Rooms" }); const roomList = page.getByRole("listbox", { name: "Room list", exact: true });
await expect(roomList.getByRole("treeitem", { name: "General", exact: true })).toBeVisible(); await expect(roomList.getByRole("option", { name: "General" })).toBeVisible();
await expect(roomList.getByRole("treeitem", { name: "Random", exact: true })).toBeVisible(); await expect(roomList.getByRole("option", { name: "Random" })).toBeVisible();
await expect(roomList.getByRole("treeitem", { name: "Projects", exact: true })).toBeVisible(); await expect(roomList.getByRole("option", { name: "Projects" })).toBeVisible();
// Assert rooms exist in the space explorer // Assert rooms exist in the space explorer
await expect( await expect(
@@ -200,7 +200,7 @@ test.describe("Spaces", () => {
await page.getByRole("button", { name: "Skip for now" }).click(); await page.getByRole("button", { name: "Skip for now" }).click();
await page.getByRole("button", { name: "Add room" }).click(); await page.getByRole("main").getByRole("button", { name: "Add" }).click();
await page.getByRole("menuitem", { name: "Add existing room" }).click(); await page.getByRole("menuitem", { name: "Add existing room" }).click();
await page.getByRole("checkbox", { name: "Sample Room" }).click(); await page.getByRole("checkbox", { name: "Sample Room" }).click();
@@ -424,10 +424,10 @@ test.describe("Spaces", () => {
await page.getByRole("button", { name: "Skip for now" }).click(); await page.getByRole("button", { name: "Skip for now" }).click();
// Assert rooms exist in the room list // Assert rooms exist in the room list
const roomList = page.getByRole("tree", { name: "Rooms" }); const roomList = page.getByRole("listbox", { name: "Room list", exact: true });
await expect(roomList.getByRole("treeitem", { name: "General", exact: true })).toBeVisible(); await expect(roomList.getByRole("option", { name: "General" })).toBeVisible();
await expect(roomList.getByRole("treeitem", { name: "Random", exact: true })).toBeVisible(); await expect(roomList.getByRole("option", { name: "Random" })).toBeVisible();
await expect(roomList.getByRole("treeitem", { name: "Projects", exact: true })).toBeVisible(); await expect(roomList.getByRole("option", { name: "Projects" })).toBeVisible();
// Assert rooms exist in the space explorer // Assert rooms exist in the space explorer
await expect( await expect(
@@ -377,7 +377,7 @@ export class Helpers {
* Expand the space panel * Expand the space panel
*/ */
expandSpacePanel() { expandSpacePanel() {
return this.page.getByRole("button", { name: "Expand" }).click(); return this.page.getByRole("navigation", { name: "Spaces" }).getByRole("button", { name: "Expand" }).click();
} }
/** /**
+7 -3
View File
@@ -36,7 +36,7 @@ async function startDM(app: ElementAppPage, page: Page, name: string): Promise<v
await locator.press("Enter"); await locator.press("Enter");
// The DM room is created at this point, this can take a little bit of time // The DM room is created at this point, this can take a little bit of time
await expect(page.locator(".mx_EventTile_body").getByText("Hey!")).toBeAttached({ timeout: 3000 }); await expect(page.locator(".mx_EventTile_body").getByText("Hey!")).toBeAttached({ timeout: 3000 });
await expect(page.getByRole("group", { name: "People" }).getByText(name)).toBeAttached(); await expect(page.getByTestId("room-list").getByRole("option", { name: `Open room ${name}` })).toBeVisible();
} }
type RoomRef = { name: string; roomId: string }; type RoomRef = { name: string; roomId: string };
@@ -266,7 +266,9 @@ test.describe("Spotlight", () => {
// Assert DM exists by checking for the first message and the room being in the room list // Assert DM exists by checking for the first message and the room being in the room list
await expect(page.locator(".mx_EventTile_body").filter({ hasText: "Hey!" })).toBeAttached({ timeout: 3000 }); await expect(page.locator(".mx_EventTile_body").filter({ hasText: "Hey!" })).toBeAttached({ timeout: 3000 });
await expect(page.getByRole("group", { name: "People" })).toContainText(bot2.credentials.displayName); await expect(
page.getByTestId("room-list").getByRole("option", { name: `Open room ${bot2.credentials.displayName}` }),
).toBeVisible();
// Invite BotBob into existing DM with ByteBot // Invite BotBob into existing DM with ByteBot
const dmRooms = await app.client.evaluate((client, userId) => { const dmRooms = await app.client.evaluate((client, userId) => {
@@ -279,7 +281,9 @@ test.describe("Spotlight", () => {
const groupDmName = await app.client.evaluate((client, id) => client.getRoom(id).name, dmRooms[0]); const groupDmName = await app.client.evaluate((client, id) => client.getRoom(id).name, dmRooms[0]);
await app.client.inviteUser(dmRooms[0], bot1.credentials.userId); await app.client.inviteUser(dmRooms[0], bot1.credentials.userId);
await expect(roomHeaderName(page).first()).toContainText(groupDmName); await expect(roomHeaderName(page).first()).toContainText(groupDmName);
await expect(page.getByRole("group", { name: "People" }).first()).toContainText(groupDmName); await expect(
page.getByTestId("room-list").getByRole("option", { name: `Open room ${groupDmName}` }),
).toBeVisible();
// Search for BotBob by id, should return group DM and user // Search for BotBob by id, should return group DM and user
spotlight = await app.openSpotlight(); spotlight = await app.openSpotlight();
@@ -50,9 +50,12 @@ test.describe("Media preview settings", () => {
} }
`, `,
}); });
await expect(
page.getByRole("tree", { name: "Rooms" }).getByRole("treeitem", { name: "Test room" }), const testRoomTile = page
).toMatchScreenshot("invite-room-tree-no-avatar.png"); .getByRole("listbox", { name: "Room list" })
.getByRole("option", { name: "Test room" });
await expect(testRoomTile).toBeVisible();
await expect(testRoomTile).toMatchScreenshot("invite-room-tree-no-avatar.png");
// And then go back to being visible // And then go back to being visible
settings = await app.settings.openUserSettings("Preferences"); settings = await app.settings.openUserSettings("Preferences");
@@ -70,9 +73,7 @@ test.describe("Media preview settings", () => {
} }
`, `,
}); });
await expect( await expect(testRoomTile).toMatchScreenshot("invite-room-tree-with-avatar.png");
page.getByRole("tree", { name: "Rooms" }).getByRole("treeitem", { name: "Test room" }),
).toMatchScreenshot("invite-room-tree-with-avatar.png");
}); });
test("should be able to hide media in rooms globally", async ({ page, app, room, user }) => { test("should be able to hide media in rooms globally", async ({ page, app, room, user }) => {
+1 -1
View File
@@ -24,7 +24,7 @@ test.describe("PSTN", () => {
await toasts.rejectToast("Notifications"); await toasts.rejectToast("Notifications");
await toasts.assertNoToasts(); await toasts.assertNoToasts();
await expect(page.locator(".mx_LeftPanel_filterContainer")).toMatchScreenshot("dialpad-trigger.png"); await expect(page.locator(".mx_RoomListSearch")).toMatchScreenshot("dialpad-trigger.png");
await page.getByLabel("Open dial pad").click(); await page.getByLabel("Open dial pad").click();
await expect(page.locator(".mx_Dialog")).toMatchScreenshot("dialpad.png"); await expect(page.locator(".mx_Dialog")).toMatchScreenshot("dialpad.png");
}); });
+15 -3
View File
@@ -51,9 +51,10 @@ export class ElementAppPage {
/** /**
* Open room creation dialog. * Open room creation dialog.
*/ */
public async openCreateRoomDialog(roomKindname: "New room" | "New video room" = "New room"): Promise<Locator> { public async openCreateRoomDialog(roomKindname: "New room" | "New video room" = "New room"): Promise<Locator> {
await this.page.getByRole("button", { name: "Add room", exact: true }).click(); await this.page.getByRole("navigation", { name: "Room list" }).getByRole("button", { name: "Add" }).click();
await this.page.getByRole("menuitem", { name: roomKindname, exact: true }).click(); await this.page.getByRole("menuitem", { name: roomKindname }).click();
return this.page.locator(".mx_CreateRoomDialog"); return this.page.locator(".mx_CreateRoomDialog");
} }
@@ -70,12 +71,23 @@ export class ElementAppPage {
/** /**
* Opens the given room by name. The room must be visible in the * Opens the given room by name. The room must be visible in the
* room list and the room may contain unread messages.
*
* @param name The exact room name to find and click on/open.
*/
public async viewRoomByName(name: string): Promise<void> {
// We get the room list by test-id which is a listbox and matching title=name
return this.page.getByTestId("room-list").locator(`[title="${name}"]`).first().click();
}
/**
* Opens the given room on the old room list by name. The room must be visible in the
* room list, but the room list may be folded horizontally, and the * room list, but the room list may be folded horizontally, and the
* room may contain unread messages. * room may contain unread messages.
* *
* @param name The exact room name to find and click on/open. * @param name The exact room name to find and click on/open.
*/ */
public async viewRoomByName(name: string): Promise<void> { public async viewRoomByNameOnOldRoomList(name: string): Promise<void> {
// We look for the room inside the room list, which is a tree called Rooms. // We look for the room inside the room list, which is a tree called Rooms.
// //
// There are 3 cases: // There are 3 cases:
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Some files were not shown because too many files have changed in this diff Show More