diff --git a/cypress/e2e/right-panel/file-panel.spec.ts b/cypress/e2e/right-panel/file-panel.spec.ts
deleted file mode 100644
index d2ada56108..0000000000
--- a/cypress/e2e/right-panel/file-panel.spec.ts
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
-Copyright 2023 Suguru Hirahara
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-///
-
-import { HomeserverInstance } from "../../plugins/utils/homeserver";
-import Chainable = Cypress.Chainable;
-
-const ROOM_NAME = "Test room";
-const NAME = "Alice";
-
-const viewRoomSummaryByName = (name: string): Chainable> => {
- cy.viewRoomByName(name);
- cy.findByRole("button", { name: "Room info" }).click();
- return checkRoomSummaryCard(name);
-};
-
-const checkRoomSummaryCard = (name: string): Chainable> => {
- cy.get(".mx_RoomSummaryCard").should("have.length", 1);
- return cy.get(".mx_RoomSummaryCard").should("contain", name);
-};
-
-const uploadFile = (file: string) => {
- // Upload a file from the message composer
- cy.get(".mx_MessageComposer_actions input[type='file']").selectFile(file, { force: true });
-
- cy.get(".mx_Dialog").within(() => {
- cy.findByRole("button", { name: "Upload" }).click();
- });
-
- // Wait until the file is sent
- cy.get(".mx_RoomView_statusArea_expanded").should("not.exist");
- cy.get(".mx_EventTile.mx_EventTile_last .mx_EventTile_receiptSent").should("exist");
-};
-
-describe("FilePanel", () => {
- let homeserver: HomeserverInstance;
-
- beforeEach(() => {
- cy.startHomeserver("default").then((data) => {
- homeserver = data;
- cy.initTestUser(homeserver, NAME).then(() =>
- cy.window({ log: false }).then(() => {
- cy.createRoom({ name: ROOM_NAME });
- }),
- );
- });
-
- // Open the file panel
- viewRoomSummaryByName(ROOM_NAME);
- cy.findByRole("menuitem", { name: "Files" }).click();
- cy.get(".mx_FilePanel").should("have.length", 1);
- });
-
- afterEach(() => {
- cy.stopHomeserver(homeserver);
- });
-
- describe("render", () => {
- it("should render empty state", () => {
- // Wait until the information about the empty state is rendered
- cy.get(".mx_FilePanel_empty").should("exist");
-
- // Take a snapshot of RightPanel - fix https://github.com/vector-im/element-web/issues/25332
- cy.get(".mx_RightPanel").percySnapshotElement("File Panel - empty", {
- widths: [264], // Emulate the UI. The value is based on minWidth specified on MainSplit.tsx
- });
- });
-
- it("should list tiles on the panel", () => {
- // Upload multiple files
- uploadFile("cypress/fixtures/riot.png"); // Image
- uploadFile("cypress/fixtures/1sec.ogg"); // Audio
- uploadFile("cypress/fixtures/matrix-org-client-versions.json"); // JSON
-
- cy.get(".mx_RoomView_body").within(() => {
- // Assert that all of the file were uploaded and rendered
- cy.get(".mx_EventTile[data-layout='group']").should("have.length", 3);
-
- // Assert that the image exists and has the alt string
- cy.get(".mx_EventTile[data-layout='group'] img[alt='riot.png']").should("exist");
-
- // Assert that the audio player is rendered
- cy.get(".mx_EventTile[data-layout='group'] .mx_AudioPlayer_container").should("exist");
-
- // Assert that the file button exists
- cy.contains(".mx_EventTile_last[data-layout='group'] .mx_MFileBody", ".json").should("exist");
- });
-
- // Assert that the file panel is opened inside mx_RightPanel and visible
- cy.get(".mx_RightPanel .mx_FilePanel").should("be.visible");
-
- cy.get(".mx_FilePanel").within(() => {
- cy.get(".mx_RoomView_MessageList").within(() => {
- // Assert that data-layout attribute is not applied to file tiles on the panel
- cy.get(".mx_EventTile[data-layout]").should("not.exist");
-
- // Assert that all of the file tiles are rendered
- cy.get(".mx_EventTile").should("have.length", 3);
-
- // Assert that the download links are rendered
- cy.get(".mx_MFileBody_download").should("have.length", 3);
-
- // Assert that the sender of the files is rendered on all of the tiles
- cy.findAllByText(NAME).should("have.length", 3);
-
- // Detect the image file
- cy.get(".mx_EventTile_mediaLine.mx_EventTile_image").within(() => {
- // Assert that the image is specified as thumbnail and has the alt string
- cy.get(".mx_MImageBody").within(() => {
- cy.get("img[class='mx_MImageBody_thumbnail']").should("exist");
- cy.get("img[alt='riot.png']").should("exist");
- });
- });
-
- // Detect the audio file
- cy.get(".mx_EventTile_mediaLine .mx_MAudioBody").within(() => {
- // Assert that the audio player is rendered
- cy.get(".mx_AudioPlayer_container").within(() => {
- // Assert that the play button is rendered
- cy.findByRole("button", { name: "Play" }).should("exist");
- });
- });
-
- // Detect the JSON file
- // Assert that the tile is rendered as a button
- cy.get(".mx_EventTile_mediaLine .mx_MFileBody .mx_MFileBody_info[role='button']").within(() => {
- // Assert that the file name is rendered inside the button with ellipsis
- cy.get(".mx_MFileBody_info_filename").within(() => {
- cy.findByText(/matrix.*?\.json/);
- });
- });
- });
- });
-
- // Make the viewport tall enough to display all of the file tiles on FilePanel
- cy.viewport(660, 1000);
-
- cy.get(".mx_FilePanel").within(() => {
- // In case the panel is scrollable on the resized viewport
- cy.get(".mx_ScrollPanel").scrollTo("bottom", { ensureScrollable: false });
-
- // Assert that the value for flexbox is applied
- cy.get(".mx_ScrollPanel .mx_RoomView_MessageList").should("have.css", "justify-content", "flex-end");
-
- // Assert that all of the file tiles are visible before taking a snapshot
- cy.get(".mx_RoomView_MessageList").within(() => {
- cy.get(".mx_MImageBody").should("be.visible"); // top
- cy.get(".mx_MAudioBody").should("be.visible"); // middle
- cy.get(".mx_EventTile_last").within(() => {
- // bottom
- cy.get(".mx_EventTile_senderDetails").within(() => {
- cy.get(".mx_DisambiguatedProfile").should("be.visible");
- cy.get(".mx_MessageTimestamp").should("be.visible");
- });
- });
- });
- });
-
- // Exclude timestamps and read markers from snapshot
- // FIXME: hide mx_SeekBar because flaky - see https://github.com/vector-im/element-web/issues/24897
- // Remove this once https://github.com/vector-im/element-web/issues/24898 is fixed.
- const percyCSS =
- ".mx_MessageTimestamp, .mx_MessagePanel_myReadMarker, .mx_SeekBar { visibility: hidden !important; }";
-
- // Take a snapshot of file tiles list on FilePanel
- cy.get(".mx_FilePanel .mx_RoomView_MessageList").percySnapshotElement("File tiles list on FilePanel", {
- percyCSS,
- widths: [250], // magic number, should be around the default width
- });
- });
-
- // https://github.com/vector-im/element-web/issues/26045
- it.skip("should render the audio player and play the audio file on the panel", () => {
- // Upload an image file
- uploadFile("cypress/fixtures/1sec.ogg");
-
- cy.get(".mx_FilePanel").within(() => {
- cy.get(".mx_RoomView_MessageList").within(() => {
- // Detect the audio file
- cy.get(".mx_EventTile_mediaLine .mx_MAudioBody").within(() => {
- // Assert that the audio player is rendered
- cy.get(".mx_AudioPlayer_container").within(() => {
- // Assert that the audio file information is rendered
- cy.get(".mx_AudioPlayer_mediaInfo").within(() => {
- cy.get(".mx_AudioPlayer_mediaName").within(() => {
- cy.findByText("1sec.ogg");
- });
- cy.contains(".mx_AudioPlayer_byline", "00:01").should("exist");
- cy.contains(".mx_AudioPlayer_byline", "(3.56 KB)").should("exist"); // actual size
- });
-
- // Assert that the duration counter is 00:01 before clicking the play button
- cy.contains(".mx_AudioPlayer_mediaInfo time", "00:01").should("exist");
-
- // Assert that the counter is zero before clicking the play button
- cy.contains(".mx_AudioPlayer_seek [role='timer']", "00:00").should("exist");
-
- // Click the play button
- cy.wait(500);
- cy.findByRole("button", { name: "Play" }).click();
-
- // Assert that the pause button is rendered
- cy.findByRole("button", { name: "Pause" }).should("exist");
-
- // Assert that the timer is reset when the audio file finished playing
- cy.contains(".mx_AudioPlayer_seek [role='timer']", "00:00").should("exist");
-
- // Assert that the play button is rendered
- cy.findByRole("button", { name: "Play" }).should("exist");
- });
- });
- });
- });
- });
-
- it("should render file size in kibibytes on a file tile", () => {
- const size = "1.12 KB"; // actual file size in kibibytes (1024 bytes)
-
- // Upload a file
- uploadFile("cypress/fixtures/matrix-org-client-versions.json");
-
- cy.get(".mx_FilePanel .mx_EventTile").within(() => {
- // Assert that the file size is displayed in kibibytes, not kilobytes (1000 bytes)
- // See: https://github.com/vector-im/element-web/issues/24866
- cy.contains(".mx_MFileBody_info_filename", size).should("exist");
- cy.get(".mx_MFileBody_download").within(() => {
- cy.contains("a", size).should("exist");
- cy.contains(".mx_MImageBody_size", size).should("exist");
- });
- });
- });
- });
-
- describe("download", () => {
- it("should download an image via the link on the panel", () => {
- // Upload an image file
- uploadFile("cypress/fixtures/riot.png");
-
- cy.get(".mx_FilePanel").within(() => {
- cy.get(".mx_RoomView_MessageList").within(() => {
- // Detect the image file on the panel
- cy.get(".mx_EventTile_mediaLine.mx_EventTile_image .mx_MImageBody").within(() => {
- // Click the anchor link (not the image itself)
- cy.get(".mx_MFileBody_download a").click();
- cy.readFile("cypress/downloads/riot.png").should("exist");
- });
- });
- });
- });
- });
-});
diff --git a/cypress/e2e/right-panel/notification-panel.spec.ts b/cypress/e2e/right-panel/notification-panel.spec.ts
deleted file mode 100644
index 75a80abaf3..0000000000
--- a/cypress/e2e/right-panel/notification-panel.spec.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
-Copyright 2023 Suguru Hirahara
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-///
-
-import { HomeserverInstance } from "../../plugins/utils/homeserver";
-
-const ROOM_NAME = "Test room";
-const NAME = "Alice";
-
-describe("NotificationPanel", () => {
- let homeserver: HomeserverInstance;
-
- beforeEach(() => {
- cy.startHomeserver("default").then((data) => {
- homeserver = data;
- cy.initTestUser(homeserver, NAME).then(() => {
- cy.createRoom({ name: ROOM_NAME });
- });
- });
- });
-
- afterEach(() => {
- cy.stopHomeserver(homeserver);
- });
-
- it("should render empty state", () => {
- cy.enableLabsFeature("feature_notifications");
- cy.viewRoomByName(ROOM_NAME);
- cy.findByRole("button", { name: "Notifications" }).click();
-
- // Wait until the information about the empty state is rendered
- cy.get(".mx_NotificationPanel_empty").should("exist");
-
- // Take a snapshot of RightPanel
- cy.get(".mx_RightPanel").percySnapshotElement("Notification Panel - empty", {
- widths: [264], // Emulate the UI. The value is based on minWidth specified on MainSplit.tsx
- });
- });
-});
diff --git a/cypress/e2e/right-panel/right-panel.spec.ts b/cypress/e2e/right-panel/right-panel.spec.ts
deleted file mode 100644
index 4784e7f838..0000000000
--- a/cypress/e2e/right-panel/right-panel.spec.ts
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
-Copyright 2022 The Matrix.org Foundation C.I.C.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-///
-
-import { HomeserverInstance } from "../../plugins/utils/homeserver";
-import Chainable = Cypress.Chainable;
-
-const ROOM_NAME = "Test room";
-const ROOM_NAME_LONG =
- "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore " +
- "et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut " +
- "aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum " +
- "dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui " +
- "officia deserunt mollit anim id est laborum.";
-const SPACE_NAME = "Test space";
-const NAME = "Alice";
-const ROOM_ADDRESS_LONG =
- "loremIpsumDolorSitAmetConsecteturAdipisicingElitSedDoEiusmodTemporIncididuntUtLaboreEtDoloreMagnaAliqua";
-
-const getMemberTileByName = (name: string): Chainable> => {
- return cy.get(`.mx_EntityTile, [title="${name}"]`);
-};
-
-const viewRoomSummaryByName = (name: string): Chainable> => {
- cy.viewRoomByName(name);
- cy.findByRole("button", { name: "Room info" }).click();
- return checkRoomSummaryCard(name);
-};
-
-const checkRoomSummaryCard = (name: string): Chainable> => {
- cy.get(".mx_RoomSummaryCard").should("have.length", 1);
- return cy.get(".mx_RoomSummaryCard").should("contain", name);
-};
-
-describe("RightPanel", () => {
- let homeserver: HomeserverInstance;
-
- beforeEach(() => {
- cy.startHomeserver("default").then((data) => {
- homeserver = data;
- cy.initTestUser(homeserver, NAME).then(() =>
- cy.window({ log: false }).then(() => {
- cy.createRoom({ name: ROOM_NAME });
- cy.createSpace({ name: SPACE_NAME });
- }),
- );
- });
- });
-
- afterEach(() => {
- cy.stopHomeserver(homeserver);
- });
-
- describe("in rooms", () => {
- it("should handle long room address and long room name", () => {
- cy.createRoom({ name: ROOM_NAME_LONG });
- viewRoomSummaryByName(ROOM_NAME_LONG);
-
- cy.openRoomSettings();
-
- // Set a local room address
- cy.contains(".mx_SettingsFieldset", "Local Addresses").within(() => {
- cy.findByRole("textbox").type(ROOM_ADDRESS_LONG);
- cy.findByRole("button", { name: "Add" }).click();
- cy.findByText(`#${ROOM_ADDRESS_LONG}:localhost`)
- .should("have.class", "mx_EditableItem_item")
- .should("exist");
- });
-
- cy.closeDialog();
-
- // Close and reopen the right panel to render the room address
- cy.findByRole("button", { name: "Room info" }).click();
- cy.get(".mx_RightPanel").should("not.exist");
- cy.findByRole("button", { name: "Room info" }).click();
-
- cy.get(".mx_RightPanel").percySnapshotElement("RoomSummaryCard - with a room name and a local address", {
- widths: [264], // Emulate the UI. The value is based on minWidth specified on MainSplit.tsx
- });
- });
-
- it("should handle clicking add widgets", () => {
- viewRoomSummaryByName(ROOM_NAME);
-
- cy.findByRole("button", { name: "Add widgets, bridges & bots" }).click();
- cy.get(".mx_IntegrationManager").should("have.length", 1);
- });
-
- it("should handle viewing export chat", () => {
- viewRoomSummaryByName(ROOM_NAME);
-
- cy.findByRole("menuitem", { name: "Export Chat" }).click();
- cy.get(".mx_ExportDialog").should("have.length", 1);
- });
-
- it("should handle viewing share room", () => {
- viewRoomSummaryByName(ROOM_NAME);
-
- cy.findByRole("menuitem", { name: "Copy link" }).click();
- cy.get(".mx_ShareDialog").should("have.length", 1);
- });
-
- it("should handle viewing room settings", () => {
- viewRoomSummaryByName(ROOM_NAME);
-
- cy.findByRole("menuitem", { name: "Settings" }).click();
- cy.get(".mx_RoomSettingsDialog").should("have.length", 1);
- cy.get(".mx_Dialog_title").within(() => {
- cy.findByText("Room Settings - " + ROOM_NAME).should("exist");
- });
- });
-
- it("should handle viewing files", () => {
- viewRoomSummaryByName(ROOM_NAME);
-
- cy.findByRole("menuitem", { name: "Files" }).click();
- cy.get(".mx_FilePanel").should("have.length", 1);
- cy.get(".mx_FilePanel_empty").should("have.length", 1);
-
- cy.findByRole("button", { name: "Room information" }).click();
- checkRoomSummaryCard(ROOM_NAME);
- });
-
- it("should handle viewing room member", () => {
- viewRoomSummaryByName(ROOM_NAME);
-
- cy.findByRole("menuitem", { name: "People" }).click();
- cy.get(".mx_MemberList").should("have.length", 1);
-
- getMemberTileByName(NAME).click();
- cy.get(".mx_UserInfo").should("have.length", 1);
- cy.get(".mx_UserInfo_profile").within(() => {
- cy.findByText(NAME);
- });
-
- cy.findByRole("button", { name: "Room members" }).click();
- cy.get(".mx_MemberList").should("have.length", 1);
-
- cy.findByRole("button", { name: "Room information" }).click();
- checkRoomSummaryCard(ROOM_NAME);
- });
- });
-
- describe("in spaces", () => {
- it("should handle viewing space member", () => {
- cy.viewSpaceHomeByName(SPACE_NAME);
-
- cy.get(".mx_RoomInfoLine_private").within(() => {
- // \d represents the number of the space members
- cy.findByRole("button", { name: /\d member/ }).click();
- });
- cy.get(".mx_MemberList").should("have.length", 1);
- cy.get(".mx_SpaceScopeHeader").within(() => {
- cy.findByText(SPACE_NAME);
- });
-
- getMemberTileByName(NAME).click();
- cy.get(".mx_UserInfo").should("have.length", 1);
- cy.get(".mx_UserInfo_profile").within(() => {
- cy.findByText(NAME);
- });
- cy.get(".mx_SpaceScopeHeader").within(() => {
- cy.findByText(SPACE_NAME);
- });
-
- cy.findByRole("button", { name: "Back" }).click();
- cy.get(".mx_MemberList").should("have.length", 1);
- });
- });
-});
diff --git a/playwright/e2e/login/consent.spec.ts b/playwright/e2e/login/consent.spec.ts
index 6e0c9df3db..c24a71f8a8 100644
--- a/playwright/e2e/login/consent.spec.ts
+++ b/playwright/e2e/login/consent.spec.ts
@@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { Page } from "@playwright/test";
-
import { test, expect } from "../../element-web-test";
test.describe("Consent", () => {
@@ -32,7 +30,7 @@ test.describe("Consent", () => {
}) => {
// Attempt to create a room using the js-sdk which should return an error with `M_CONSENT_NOT_GIVEN`
await app.client.createRoom({}).catch(() => {});
- const newPagePromise = new Promise((resolve) => context.once("page", resolve));
+ const newPagePromise = context.waitForEvent("page");
const dialog = page.locator(".mx_QuestionDialog");
// Accept terms & conditions
diff --git a/playwright/e2e/right-panel/file-panel.spec.ts b/playwright/e2e/right-panel/file-panel.spec.ts
new file mode 100644
index 0000000000..8fa6315d4d
--- /dev/null
+++ b/playwright/e2e/right-panel/file-panel.spec.ts
@@ -0,0 +1,227 @@
+/*
+Copyright 2023 Suguru Hirahara
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { type Page } from "@playwright/test";
+
+import { test, expect } from "../../element-web-test";
+import { viewRoomSummaryByName } from "./utils";
+
+const ROOM_NAME = "Test room";
+const NAME = "Alice";
+
+async function uploadFile(page: Page, file: string) {
+ // Upload a file from the message composer
+ await page.locator(".mx_MessageComposer_actions input[type='file']").setInputFiles(file);
+
+ await page.locator(".mx_Dialog").getByRole("button", { name: "Upload" }).click();
+
+ // Wait until the file is sent
+ await expect(page.locator(".mx_RoomView_statusArea_expanded")).not.toBeVisible();
+ await expect(page.locator(".mx_EventTile.mx_EventTile_last .mx_EventTile_receiptSent")).toBeVisible();
+}
+
+test.describe("FilePanel", () => {
+ test.use({
+ displayName: NAME,
+ });
+
+ test.beforeEach(async ({ page, user, app }) => {
+ await app.client.createRoom({ name: ROOM_NAME });
+
+ // Open the file panel
+ await viewRoomSummaryByName(page, app, ROOM_NAME);
+ await page.getByRole("menuitem", { name: "Files" }).click();
+ await expect(page.locator(".mx_FilePanel")).toBeVisible();
+ });
+
+ test.describe("render", () => {
+ test("should render empty state", async ({ page }) => {
+ // Wait until the information about the empty state is rendered
+ await expect(page.locator(".mx_FilePanel_empty")).toBeVisible();
+
+ // Take a snapshot of RightPanel - fix https://github.com/vector-im/element-web/issues/25332
+ await expect(page.locator(".mx_RightPanel")).toHaveScreenshot("empty.png");
+ });
+
+ test("should list tiles on the panel", async ({ page }) => {
+ // Upload multiple files
+ await uploadFile(page, "cypress/fixtures/riot.png"); // Image
+ await uploadFile(page, "cypress/fixtures/1sec.ogg"); // Audio
+ await uploadFile(page, "cypress/fixtures/matrix-org-client-versions.json"); // JSON
+
+ const roomViewBody = page.locator(".mx_RoomView_body");
+ // Assert that all of the file were uploaded and rendered
+ await expect(roomViewBody.locator(".mx_EventTile[data-layout='group']")).toHaveCount(3);
+
+ // Assert that the image exists and has the alt string
+ await expect(roomViewBody.locator(".mx_EventTile[data-layout='group'] img[alt='riot.png']")).toBeVisible();
+
+ // Assert that the audio player is rendered
+ await expect(
+ roomViewBody.locator(".mx_EventTile[data-layout='group'] .mx_AudioPlayer_container"),
+ ).toBeVisible();
+
+ // Assert that the file button exists
+ await expect(
+ roomViewBody.locator(".mx_EventTile_last[data-layout='group'] .mx_MFileBody", { hasText: ".json" }),
+ ).toBeVisible();
+
+ const filePanel = page.locator(".mx_FilePanel");
+ // Assert that the file panel is opened inside mx_RightPanel and visible
+ await expect(filePanel).toBeVisible();
+
+ const filePanelMessageList = filePanel.locator(".mx_RoomView_MessageList");
+
+ // Assert that data-layout attribute is not applied to file tiles on the panel
+ await expect(filePanelMessageList.locator(".mx_EventTile[data-layout]")).not.toBeVisible();
+
+ // Assert that all of the file tiles are rendered
+ await expect(filePanelMessageList.locator(".mx_EventTile")).toHaveCount(3);
+
+ // Assert that the download links are rendered
+ await expect(filePanelMessageList.locator(".mx_MFileBody_download")).toHaveCount(3);
+
+ // Assert that the sender of the files is rendered on all of the tiles
+ await expect(filePanelMessageList.getByText(NAME)).toHaveCount(3);
+
+ // Detect the image file
+ const image = filePanelMessageList.locator(".mx_EventTile_mediaLine.mx_EventTile_image .mx_MImageBody");
+ // Assert that the image is specified as thumbnail and has the alt string
+ await expect(image.locator("img[class='mx_MImageBody_thumbnail']")).toBeVisible();
+ await expect(image.locator("img[alt='riot.png']")).toBeVisible();
+
+ // Detect the audio file
+ const audio = filePanelMessageList.locator(
+ ".mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container",
+ );
+ // Assert that the play button is rendered
+ await expect(audio.getByRole("button", { name: "Play" })).toBeVisible();
+
+ // Detect the JSON file
+ // Assert that the tile is rendered as a button
+ const file = filePanelMessageList.locator(
+ ".mx_EventTile_mediaLine .mx_MFileBody .mx_MFileBody_info[role='button'] .mx_MFileBody_info_filename",
+ );
+ // Assert that the file name is rendered inside the button with ellipsis
+ await expect(file.getByText(/matrix.*?\.json/)).toBeVisible();
+
+ // Make the viewport tall enough to display all of the file tiles on FilePanel
+ await page.setViewportSize({ width: 800, height: 1000 });
+
+ // In case the panel is scrollable on the resized viewport
+ // Assert that the value for flexbox is applied
+ await expect(filePanel.locator(".mx_ScrollPanel .mx_RoomView_MessageList")).toHaveCSS(
+ "justify-content",
+ "flex-end",
+ );
+ // Assert that all of the file tiles are visible before taking a snapshot
+ await expect(filePanelMessageList.locator(".mx_MImageBody")).toBeVisible(); // top
+ await expect(filePanelMessageList.locator(".mx_MAudioBody")).toBeVisible(); // middle
+ const senderDetails = filePanelMessageList.locator(".mx_EventTile_last .mx_EventTile_senderDetails");
+ await expect(senderDetails.locator(".mx_DisambiguatedProfile")).toBeVisible();
+ await expect(senderDetails.locator(".mx_MessageTimestamp")).toBeVisible();
+
+ // Take a snapshot of file tiles list on FilePanel
+ // XXX: We remove the RM as masking it in different locations causes a false positive
+ await page.evaluate(() => {
+ document.querySelectorAll(".mx_MessagePanel_myReadMarker").forEach((e) => e.remove());
+ });
+ await expect(filePanelMessageList).toHaveScreenshot("file-tiles-list.png", {
+ // Exclude timestamps, profiles, avatars & flaky seek bar from snapshot
+ mask: [
+ page.locator(
+ ".mx_MessageTimestamp, .mx_DisambiguatedProfile, .mx_BaseAvatar, .mx_AudioPlayer_seek",
+ ),
+ ],
+ });
+ });
+
+ test("should render the audio player and play the audio file on the panel", async ({ page }) => {
+ // Upload an image file
+ await uploadFile(page, "cypress/fixtures/1sec.ogg");
+
+ const audioBody = page.locator(
+ ".mx_FilePanel .mx_RoomView_MessageList .mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container",
+ );
+ // Assert that the audio player is rendered
+ // Assert that the audio file information is rendered
+ const mediaInfo = audioBody.locator(".mx_AudioPlayer_mediaInfo");
+ await expect(mediaInfo.locator(".mx_AudioPlayer_mediaName").getByText("1sec.ogg")).toBeVisible();
+ await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "00:01" })).toBeVisible();
+ await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "(3.56 KB)" })).toBeVisible(); // actual size
+
+ // Assert that the duration counter is 00:01 before clicking the play button
+ await expect(audioBody.locator(".mx_AudioPlayer_mediaInfo time", { hasText: "00:01" })).toBeVisible();
+
+ // Assert that the counter is zero before clicking the play button
+ await expect(audioBody.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
+
+ // Click the play button
+ await audioBody.getByRole("button", { name: "Play" }).click();
+
+ // Assert that the pause button is rendered
+ await expect(audioBody.getByRole("button", { name: "Pause" })).toBeVisible();
+
+ // Assert that the timer is reset when the audio file finished playing
+ await expect(audioBody.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
+
+ // Assert that the play button is rendered
+ await expect(audioBody.getByRole("button", { name: "Play" })).toBeVisible();
+ });
+
+ test("should render file size in kibibytes on a file tile", async ({ page }) => {
+ const size = "1.12 KB"; // actual file size in kibibytes (1024 bytes)
+
+ // Upload a file
+ await uploadFile(page, "cypress/fixtures/matrix-org-client-versions.json");
+
+ const tile = page.locator(".mx_FilePanel .mx_EventTile");
+ // Assert that the file size is displayed in kibibytes, not kilobytes (1000 bytes)
+ // See: https://github.com/vector-im/element-web/issues/24866
+ await expect(tile.locator(".mx_MFileBody_info_filename", { hasText: size })).toBeVisible();
+ await expect(tile.locator(".mx_MFileBody_download a", { hasText: size })).toBeVisible();
+ await expect(tile.locator(".mx_MFileBody_download .mx_MImageBody_size", { hasText: size })).toBeVisible();
+ });
+ });
+
+ test.describe("download", () => {
+ test("should download an image via the link on the panel", async ({ page, context }) => {
+ // Upload an image file
+ await uploadFile(page, "cypress/fixtures/riot.png");
+
+ // Detect the image file on the panel
+ const imageBody = page.locator(
+ ".mx_FilePanel .mx_RoomView_MessageList .mx_EventTile_mediaLine.mx_EventTile_image .mx_MImageBody",
+ );
+
+ const link = imageBody.locator(".mx_MFileBody_download a");
+
+ const newPagePromise = context.waitForEvent("page");
+ // const downloadPromise = page.waitForEvent("download");
+
+ // Click the anchor link (not the image itself)
+ await link.click();
+
+ const newPage = await newPagePromise;
+ // XXX: Clicking the link opens the image in a new tab on some browsers rather than downloading, so handle that case
+ await expect(newPage).toHaveURL(/.+\/_matrix\/media\/\w+\/download\/localhost\/\w+/);
+ // .catch(async () => {
+ // const download = await downloadPromise;
+ // expect(download.suggestedFilename()).toBe("riot.png");
+ // });
+ });
+ });
+});
diff --git a/playwright/e2e/right-panel/notification-panel.spec.ts b/playwright/e2e/right-panel/notification-panel.spec.ts
new file mode 100644
index 0000000000..18e95beeb2
--- /dev/null
+++ b/playwright/e2e/right-panel/notification-panel.spec.ts
@@ -0,0 +1,43 @@
+/*
+Copyright 2023 Suguru Hirahara
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { test, expect } from "../../element-web-test";
+
+const ROOM_NAME = "Test room";
+const NAME = "Alice";
+
+test.describe("NotificationPanel", () => {
+ test.use({
+ displayName: NAME,
+ });
+
+ test.beforeEach(async ({ app, user }) => {
+ await app.client.createRoom({ name: ROOM_NAME });
+ });
+
+ test("should render empty state", async ({ page, app }) => {
+ await app.labs.enableLabsFeature("feature_notifications");
+ await app.viewRoomByName(ROOM_NAME);
+
+ await page.getByRole("button", { name: "Notifications" }).click();
+
+ // Wait until the information about the empty state is rendered
+ await expect(page.locator(".mx_NotificationPanel_empty")).toBeVisible();
+
+ // Take a snapshot of RightPanel
+ await expect(page.locator(".mx_RightPanel")).toHaveScreenshot("empty.png");
+ });
+});
diff --git a/playwright/e2e/right-panel/right-panel.spec.ts b/playwright/e2e/right-panel/right-panel.spec.ts
new file mode 100644
index 0000000000..c47d381f40
--- /dev/null
+++ b/playwright/e2e/right-panel/right-panel.spec.ts
@@ -0,0 +1,154 @@
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { Locator, type Page } from "@playwright/test";
+
+import { test, expect } from "../../element-web-test";
+import { checkRoomSummaryCard, viewRoomSummaryByName } from "./utils";
+
+const ROOM_NAME = "Test room";
+const ROOM_NAME_LONG =
+ "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore " +
+ "et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut " +
+ "aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum " +
+ "dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui " +
+ "officia deserunt mollit anim id est laborum.";
+const SPACE_NAME = "Test space";
+const NAME = "Alice";
+const ROOM_ADDRESS_LONG =
+ "loremIpsumDolorSitAmetConsecteturAdipisicingElitSedDoEiusmodTemporIncididuntUtLaboreEtDoloreMagnaAliqua";
+
+function getMemberTileByName(page: Page, name: string): Locator {
+ return page.locator(`.mx_EntityTile, [title="${name}"]`);
+}
+
+test.describe("RightPanel", () => {
+ test.use({
+ displayName: NAME,
+ });
+
+ test.beforeEach(async ({ app, user }) => {
+ await app.client.createRoom({ name: ROOM_NAME });
+ await app.client.createSpace({ name: SPACE_NAME });
+ });
+
+ test.describe("in rooms", () => {
+ test("should handle long room address and long room name", async ({ page, app }) => {
+ await app.client.createRoom({ name: ROOM_NAME_LONG });
+ await viewRoomSummaryByName(page, app, ROOM_NAME_LONG);
+
+ await app.settings.openRoomSettings();
+
+ // Set a local room address
+ const localAddresses = page.locator(".mx_SettingsFieldset", { hasText: "Local Addresses" });
+ await localAddresses.getByRole("textbox").fill(ROOM_ADDRESS_LONG);
+ await localAddresses.getByRole("button", { name: "Add" }).click();
+ await expect(localAddresses.getByText(`#${ROOM_ADDRESS_LONG}:localhost`)).toHaveClass(
+ "mx_EditableItem_item",
+ );
+
+ await app.closeDialog();
+
+ // Close and reopen the right panel to render the room address
+ await page.getByRole("button", { name: "Room info" }).click();
+ await expect(page.locator(".mx_RightPanel")).not.toBeVisible();
+ await page.getByRole("button", { name: "Room info" }).click();
+
+ await expect(page.locator(".mx_RightPanel")).toHaveScreenshot("with-name-and-address.png", {
+ mask: [page.locator(".mx_BaseAvatar")],
+ });
+ });
+
+ test("should handle clicking add widgets", async ({ page, app }) => {
+ await viewRoomSummaryByName(page, app, ROOM_NAME);
+
+ await page.getByRole("button", { name: "Add widgets, bridges & bots" }).click();
+ await expect(page.locator(".mx_IntegrationManager")).toBeVisible();
+ });
+
+ test("should handle viewing export chat", async ({ page, app }) => {
+ await viewRoomSummaryByName(page, app, ROOM_NAME);
+
+ await page.getByRole("menuitem", { name: "Export Chat" }).click();
+ await expect(page.locator(".mx_ExportDialog")).toBeVisible();
+ });
+
+ test("should handle viewing share room", async ({ page, app }) => {
+ await viewRoomSummaryByName(page, app, ROOM_NAME);
+
+ await page.getByRole("menuitem", { name: "Copy link" }).click();
+ await expect(page.locator(".mx_ShareDialog")).toBeVisible();
+ });
+
+ test("should handle viewing room settings", async ({ page, app }) => {
+ await viewRoomSummaryByName(page, app, ROOM_NAME);
+
+ await page.getByRole("menuitem", { name: "Settings" }).click();
+ await expect(page.locator(".mx_RoomSettingsDialog")).toBeVisible();
+ await expect(page.locator(".mx_Dialog_title").getByText("Room Settings - " + ROOM_NAME)).toBeVisible();
+ });
+
+ test("should handle viewing files", async ({ page, app }) => {
+ await viewRoomSummaryByName(page, app, ROOM_NAME);
+
+ await page.getByRole("menuitem", { name: "Files" }).click();
+ await expect(page.locator(".mx_FilePanel")).toBeVisible();
+ await expect(page.locator(".mx_FilePanel_empty")).toBeVisible();
+
+ await page.getByRole("button", { name: "Room information" }).click();
+ await checkRoomSummaryCard(page, ROOM_NAME);
+ });
+
+ test("should handle viewing room member", async ({ page, app }) => {
+ await viewRoomSummaryByName(page, app, ROOM_NAME);
+
+ await page.getByRole("menuitem", { name: "People" }).click();
+ await expect(page.locator(".mx_MemberList")).toBeVisible();
+
+ await getMemberTileByName(page, NAME).click();
+ await expect(page.locator(".mx_UserInfo")).toBeVisible();
+ await expect(page.locator(".mx_UserInfo_profile").getByText(NAME)).toBeVisible();
+
+ await page.getByRole("button", { name: "Room members" }).click();
+ await expect(page.locator(".mx_MemberList")).toBeVisible();
+
+ await page.getByRole("button", { name: "Room information" }).click();
+ await checkRoomSummaryCard(page, ROOM_NAME);
+ });
+ });
+
+ test.describe("in spaces", () => {
+ test("should handle viewing space member", async ({ page, app }) => {
+ await app.viewSpaceHomeByName(SPACE_NAME);
+
+ // \d represents the number of the space members
+ await page
+ .locator(".mx_RoomInfoLine_private")
+ .getByRole("button", { name: /\d member/ })
+ .click();
+ await expect(page.locator(".mx_MemberList")).toBeVisible();
+ await expect(page.locator(".mx_SpaceScopeHeader").getByText(SPACE_NAME)).toBeVisible();
+
+ await getMemberTileByName(page, NAME).click();
+ await expect(page.locator(".mx_UserInfo")).toBeVisible();
+ await expect(page.locator(".mx_UserInfo_profile").getByText(NAME)).toBeVisible();
+ await expect(page.locator(".mx_SpaceScopeHeader").getByText(SPACE_NAME)).toBeVisible();
+
+ await page.getByRole("button", { name: "Back" }).click();
+ await expect(page.locator(".mx_MemberList")).toBeVisible();
+ });
+ });
+});
diff --git a/playwright/e2e/right-panel/utils.ts b/playwright/e2e/right-panel/utils.ts
new file mode 100644
index 0000000000..a8dac8394d
--- /dev/null
+++ b/playwright/e2e/right-panel/utils.ts
@@ -0,0 +1,30 @@
+/*
+Copyright 2023 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { type Page, expect } from "@playwright/test";
+
+import { ElementAppPage } from "../../pages/ElementAppPage";
+
+export async function viewRoomSummaryByName(page: Page, app: ElementAppPage, name: string): Promise {
+ await app.viewRoomByName(name);
+ await page.getByRole("button", { name: "Room info" }).click();
+ return checkRoomSummaryCard(page, name);
+}
+
+export async function checkRoomSummaryCard(page: Page, name: string): Promise {
+ await expect(page.locator(".mx_RoomSummaryCard")).toBeVisible();
+ await expect(page.locator(".mx_RoomSummaryCard")).toContainText(name);
+}
diff --git a/playwright/e2e/settings/appearance-user-settings-tab.spec.ts b/playwright/e2e/settings/appearance-user-settings-tab.spec.ts
index e924bffa01..8363f15e1d 100644
--- a/playwright/e2e/settings/appearance-user-settings-tab.spec.ts
+++ b/playwright/e2e/settings/appearance-user-settings-tab.spec.ts
@@ -33,7 +33,9 @@ test.describe("Appearance user settings tab", () => {
// Assert that "Hide advanced" link button is rendered
await expect(tab.getByRole("button", { name: "Hide advanced" })).toBeVisible();
- await expect(tab).toHaveScreenshot();
+ await expect(tab).toHaveScreenshot("appearance-tab.png", {
+ mask: [tab.locator(".mx_DisambiguatedProfile_displayName, .mx_BaseAvatar")],
+ });
});
test("should support switching layouts", async ({ page, user, app }) => {
diff --git a/playwright/pages/ElementAppPage.ts b/playwright/pages/ElementAppPage.ts
index 76037ff58e..a602aadb87 100644
--- a/playwright/pages/ElementAppPage.ts
+++ b/playwright/pages/ElementAppPage.ts
@@ -14,14 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { type Locator, type Page } from "@playwright/test";
+import { type Locator, type Page, expect } from "@playwright/test";
import { Settings } from "./settings";
import { Client } from "./client";
+import { Labs } from "./labs";
export class ElementAppPage {
public constructor(private readonly page: Page) {}
+ public labs = new Labs(this.page);
public settings = new Settings(this.page);
public client: Client = new Client(this.page);
@@ -94,4 +96,25 @@ export class ElementAppPage {
await composer.getByRole("button", { name: "More options", exact: true }).click();
return this.page.getByRole("menu");
}
+
+ /**
+ * Returns the space panel space button based on a name. The space
+ * must be visible in the space panel
+ * @param name The space name to find
+ */
+ public async getSpacePanelButton(name: string): Promise {
+ const button = this.page.getByRole("button", { name: name });
+ await expect(button).toHaveClass(/mx_SpaceButton/);
+ return button;
+ }
+
+ /**
+ * Opens the given space home by name. The space must be visible in
+ * the space list.
+ * @param name The space name to find and click on/open.
+ */
+ public async viewSpaceHomeByName(name: string): Promise {
+ const button = await this.getSpacePanelButton(name);
+ return button.dblclick();
+ }
}
diff --git a/playwright/pages/client.ts b/playwright/pages/client.ts
index c1e78bf6a2..1b3c7dc092 100644
--- a/playwright/pages/client.ts
+++ b/playwright/pages/client.ts
@@ -116,6 +116,21 @@ export class Client {
}, options);
}
+ /**
+ * Create a space with given options.
+ * @param options the options to apply when creating the space
+ * @return the ID of the newly created space (room)
+ */
+ public async createSpace(options: ICreateRoomOpts): Promise {
+ return this.createRoom({
+ ...options,
+ creation_content: {
+ ...options.creation_content,
+ type: "m.space",
+ },
+ });
+ }
+
/**
* Joins the given room by alias or ID
* @param roomIdOrAlias the id or alias of the room to join
diff --git a/playwright/pages/labs.ts b/playwright/pages/labs.ts
new file mode 100644
index 0000000000..397eb08305
--- /dev/null
+++ b/playwright/pages/labs.ts
@@ -0,0 +1,32 @@
+/*
+Copyright 2023 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { Page } from "playwright-core";
+
+export class Labs {
+ constructor(private page: Page) {}
+
+ /**
+ * Enables a labs feature for an element session.
+ * Has to be called before the session is initialized
+ * @param feature labsFeature to enable (e.g. "feature_spotlight")
+ */
+ public async enableLabsFeature(feature: string): Promise {
+ await this.page.evaluate((feature) => {
+ window.localStorage.setItem(`mx_labs_feature_${feature}`, "true");
+ }, feature);
+ }
+}
diff --git a/playwright/snapshots/right-panel/file-panel.spec.ts/empty-linux.png b/playwright/snapshots/right-panel/file-panel.spec.ts/empty-linux.png
new file mode 100644
index 0000000000..38592f9b67
Binary files /dev/null and b/playwright/snapshots/right-panel/file-panel.spec.ts/empty-linux.png differ
diff --git a/playwright/snapshots/right-panel/file-panel.spec.ts/file-tiles-list-linux.png b/playwright/snapshots/right-panel/file-panel.spec.ts/file-tiles-list-linux.png
new file mode 100644
index 0000000000..53365a6e08
Binary files /dev/null and b/playwright/snapshots/right-panel/file-panel.spec.ts/file-tiles-list-linux.png differ
diff --git a/playwright/snapshots/right-panel/notification-panel.spec.ts/empty-linux.png b/playwright/snapshots/right-panel/notification-panel.spec.ts/empty-linux.png
new file mode 100644
index 0000000000..ee3bd0348b
Binary files /dev/null and b/playwright/snapshots/right-panel/notification-panel.spec.ts/empty-linux.png differ
diff --git a/playwright/snapshots/right-panel/right-panel.spec.ts/with-name-and-address-linux.png b/playwright/snapshots/right-panel/right-panel.spec.ts/with-name-and-address-linux.png
new file mode 100644
index 0000000000..dc10e85cfd
Binary files /dev/null and b/playwright/snapshots/right-panel/right-panel.spec.ts/with-name-and-address-linux.png differ
diff --git a/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/Appearance-user-settings-tab-should-be-rendered-properly-1-darwin.png b/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/Appearance-user-settings-tab-should-be-rendered-properly-1-darwin.png
deleted file mode 100644
index 2ad9f6565d..0000000000
Binary files a/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/Appearance-user-settings-tab-should-be-rendered-properly-1-darwin.png and /dev/null differ
diff --git a/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/Appearance-user-settings-tab-should-be-rendered-properly-1-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/Appearance-user-settings-tab-should-be-rendered-properly-1-linux.png
deleted file mode 100644
index 20ef5f738c..0000000000
Binary files a/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/Appearance-user-settings-tab-should-be-rendered-properly-1-linux.png and /dev/null differ
diff --git a/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png
new file mode 100644
index 0000000000..0da9264e7d
Binary files /dev/null and b/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png differ
diff --git a/res/css/views/avatars/_BaseAvatar.pcss b/res/css/views/avatars/_BaseAvatar.pcss
index c8e2360aaf..d367d2da13 100644
--- a/res/css/views/avatars/_BaseAvatar.pcss
+++ b/res/css/views/avatars/_BaseAvatar.pcss
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-/* Percy screenshot test specific CSS */
+/* Screenshot test specific CSS */
@media only percy {
/* Stick the default room avatar colour, so it doesn't cause a false diff on the screenshot */
.mx_BaseAvatar {