/* 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 { Download, 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_EmptyState")).toBeVisible(); // Take a snapshot of RightPanel - fix https://github.com/vector-im/element-web/issues/25332 await expect(page.locator(".mx_RightPanel")).toMatchScreenshot("empty.png"); }); test("should list tiles on the panel", async ({ page }) => { // Upload multiple files await uploadFile(page, "playwright/sample-files/riot.png"); // Image await uploadFile(page, "playwright/sample-files/1sec.ogg"); // Audio await uploadFile(page, "playwright/sample-files/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 await expect(filePanelMessageList).toMatchScreenshot("file-tiles-list.png", { // Exclude timestamps & flaky seek bar from snapshot mask: [page.locator(".mx_MessageTimestamp, .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, "playwright/sample-files/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, "playwright/sample-files/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, "playwright/sample-files/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 = new Promise((resolve) => { page.once("download", resolve); }); // 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 await expect(newPage) .toHaveURL(/.+\/_matrix\/media\/\w+\/download\/localhost\/\w+/) .catch(async () => { const download = await downloadPromise; expect(download.suggestedFilename()).toBe("riot.png"); }); }); }); });