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 {