diff --git a/cypress/e2e/audio-player/audio-player.spec.ts b/cypress/e2e/audio-player/audio-player.spec.ts deleted file mode 100644 index 30470716c9..0000000000 --- a/cypress/e2e/audio-player/audio-player.spec.ts +++ /dev/null @@ -1,387 +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 { SettingLevel } from "../../../src/settings/SettingLevel"; -import { Layout } from "../../../src/settings/enums/Layout"; - -describe("Audio player", () => { - let homeserver: HomeserverInstance; - const TEST_USER = "Hanako"; - - const percyCSS = - // FIXME: hide mx_SeekBar because flaky - see https://github.com/vector-im/element-web/issues/24898 - ".mx_SeekBar, " + - // Exclude various components from the snapshot, for consistency - ".mx_JumpToBottomButton, " + - ".mx_MessageTimestamp, .mx_MessagePanel_myReadMarker { visibility: hidden !important; }"; - - 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(() => { - // Find and click primary "Upload" button - 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"); - // wait for the tile to finish loading - cy.get(".mx_AudioPlayer_mediaName").should("exist"); - }; - - /** - * Take snapshots of mx_EventTile_last on each layout, outputting log for reference/debugging. - * @param detail The Percy snapshot name. Used for outputting logs too. - * @param monospace This changes the font used to render the UI from a default one to a monospace one. - * Set to false by default. Note that the font applied to Percy snapshots can be different from the test result - * on your local environment. - */ - const takeSnapshots = (detail: string, monospace = false) => { - // Check that the audio player is rendered and its button becomes visible - const checkPlayerVisibility = () => { - // Assert that the audio player and media information are visible - cy.get(".mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container .mx_AudioPlayer_mediaInfo").within( - () => { - cy.contains(".mx_AudioPlayer_mediaName", ".ogg").should("be.visible"); // extension - cy.contains(".mx_AudioPlayer_byline", "00:01").should("be.visible"); - cy.contains(".mx_AudioPlayer_byline", "(3.56 KB)").should("be.visible"); // actual size - }, - ); - - // Assert that the play button can be found and is visible - cy.findByRole("button", { name: "Play" }).should("be.visible"); - - if (monospace) { - // Assert that the monospace timer is visible - cy.get("[role='timer']").should("have.css", "font-family", '"monospace"').should("be.visible"); - } - }; - - /** - * Define snapshot widths of selected EventTile, on which the audio player is rendered - * - * 50px (magic number): narrow enough EventTile to be compressed to check a11y - * 267px: EventTile on IRC and modern/group layout, on which the player is rendered in its full width - * 285px: EventTile on bubble layout, on which the player is rendered in its full width - */ - const snapshotWidthsIRC = [50, 267]; - const snapshotWidthsGroup = snapshotWidthsIRC; - const snapshotWidthsBubble = [50, 285]; - - if (monospace) { - // Enable system font and monospace setting - cy.setSettingValue("useSystemFont", null, SettingLevel.DEVICE, true); - cy.setSettingValue("systemFont", null, SettingLevel.DEVICE, "monospace"); - } - - // Check the status of the seek bar - // TODO: check if visible - currently checking its visibility on a compressed EventTile returns an error - cy.get(".mx_AudioPlayer_seek input[type='range']").should("exist"); - - // Enable IRC layout - cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC); - - cy.get(".mx_EventTile_last[data-layout='irc']").within(() => { - // Click the event timestamp to highlight EventTile in case it is not visible - cy.get(".mx_MessageTimestamp").click(); - - // Assert that rendering of the player settled and the play button is visible before taking a snapshot - checkPlayerVisibility(); - }); - - // Take a snapshot of mx_EventTile_last on IRC layout - cy.get(".mx_EventTile_last").percySnapshotElement(detail + " on IRC layout", { - percyCSS, - widths: snapshotWidthsIRC, - }); - - // Output a log - cy.log("Took a snapshot of " + detail + " on IRC layout"); - - // Take a snapshot on modern/group layout - cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Group); - cy.get(".mx_EventTile_last[data-layout='group']").within(() => { - cy.get(".mx_MessageTimestamp").click(); - checkPlayerVisibility(); - }); - cy.get(".mx_EventTile_last").percySnapshotElement(detail + " on modern/group layout", { - percyCSS, - widths: snapshotWidthsGroup, - }); - cy.log("Took a snapshot of " + detail + " on modern/group layout"); - - // Take a snapshot on bubble layout - cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Bubble); - cy.get(".mx_EventTile_last[data-layout='bubble']").within(() => { - cy.get(".mx_MessageTimestamp").click(); - checkPlayerVisibility(); - }); - cy.get(".mx_EventTile_last").percySnapshotElement(detail + " on bubble layout", { - percyCSS, - widths: snapshotWidthsBubble, - }); - cy.log("Took a snapshot of " + detail + " on bubble layout"); - }; - - beforeEach(() => { - cy.startHomeserver("default").then((data) => { - homeserver = data; - cy.initTestUser(homeserver, TEST_USER); - }); - - cy.createRoom({ name: "Test Room" }).viewRoomByName("Test Room"); - - // Wait until configuration is finished - cy.get(".mx_GenericEventListSummary[data-layout='group'] .mx_GenericEventListSummary_summary").within(() => { - cy.findByText(TEST_USER + " created and configured the room.").should("exist"); - }); - }); - - afterEach(() => { - cy.stopHomeserver(homeserver); - }); - - it("should be correctly rendered - light theme", () => { - uploadFile("cypress/fixtures/1sec-long-name-audio-file.ogg"); - - takeSnapshots("Selected EventTile of audio player (light theme)"); - }); - - it("should be correctly rendered - light theme with monospace font", () => { - uploadFile("cypress/fixtures/1sec-long-name-audio-file.ogg"); - - // Disabled because flaky - see https://github.com/vector-im/element-web/issues/24881 - //takeSnapshots("Selected EventTile of audio player (light theme, monospace font)", true); // Enable monospace - }); - - it("should be correctly rendered - high contrast theme", () => { - // Disable system theme in case ThemeWatcher enables the theme automatically, - // so that the high contrast theme can be enabled - cy.setSettingValue("use_system_theme", null, SettingLevel.DEVICE, false); - - // Enable high contrast manually - cy.openUserSettings("Appearance") - .findByTestId("mx_ThemeChoicePanel") - .findByLabelText("Use high contrast") - .click({ force: true }); // force click because the size of the checkbox is zero - - cy.closeDialog(); - - uploadFile("cypress/fixtures/1sec-long-name-audio-file.ogg"); - - // Disabled because flaky - see https://github.com/vector-im/element-web/issues/24881 - //takeSnapshots("Selected EventTile of audio player (high contrast)"); - }); - - it("should be correctly rendered - dark theme", () => { - // Enable dark theme - cy.setSettingValue("theme", null, SettingLevel.ACCOUNT, "dark"); - - uploadFile("cypress/fixtures/1sec-long-name-audio-file.ogg"); - - takeSnapshots("Selected EventTile of audio player (dark theme)"); - }); - - it("should play an audio file", () => { - uploadFile("cypress/fixtures/1sec.ogg"); - - // Assert that the audio player is rendered - cy.get(".mx_EventTile_last .mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container").within(() => { - // Assert that the counter is zero before clicking the play button - cy.contains(".mx_AudioPlayer_seek [role='timer']", "00:00").should("exist"); - - // Find and click "Play" button, the wait is to make the test less flaky - cy.findByRole("button", { name: "Play" }).should("exist"); - cy.wait(500).findByRole("button", { name: "Play" }).click(); - - // Assert that "Pause" button can be found - 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 "Play" button can be found - cy.findByRole("button", { name: "Play" }).should("exist"); - }); - }); - - it("should support downloading an audio file", () => { - uploadFile("cypress/fixtures/1sec.ogg"); - - // Find and click "Download" button on MessageActionBar - cy.get(".mx_EventTile_last").realHover().findByRole("button", { name: "Download" }).click(); - - // Assert that the file was downloaded - cy.readFile("cypress/downloads/1sec.ogg").should("exist"); - }); - - it("should support replying to audio file with another audio file", () => { - uploadFile("cypress/fixtures/1sec.ogg"); - - // Assert the audio player is rendered - cy.get(".mx_EventTile_last .mx_AudioPlayer_container").should("exist"); - - // Find and click "Reply" button on MessageActionBar - cy.get(".mx_EventTile_last").realHover().findByRole("button", { name: "Reply" }).click(); - - // Reply to the player with another audio file - uploadFile("cypress/fixtures/1sec.ogg"); - - cy.get(".mx_EventTile_last").within(() => { - // Assert that the audio player is rendered - cy.get(".mx_AudioPlayer_container").should("exist"); - - // Assert that replied audio file is rendered as file button inside ReplyChain - cy.get(".mx_ReplyChain_wrapper .mx_MFileBody_info[role='button']").within(() => { - // Assert that the file button has file name - cy.get(".mx_MFileBody_info_filename").should("exist"); - }); - }); - - // Disabled because flaky - see https://github.com/vector-im/element-web/issues/24881 - //takeSnapshots("Selected EventTile of audio player with a reply"); - }); - - it("should support creating a reply chain with multiple audio files", () => { - // Note: "mx_ReplyChain" element is used not only for replies which - // create a reply chain, but also for a single reply without a replied - // message. This test checks whether a reply chain which consists of - // multiple audio file replies is rendered properly. - - // Find and click "Reply" button - const clickButtonReply = () => { - cy.get(".mx_EventTile_last").realHover().findByRole("button", { name: "Reply" }).click(); - }; - - uploadFile("cypress/fixtures/upload-first.ogg"); - - // Assert that the audio player is rendered - cy.get(".mx_EventTile_last .mx_AudioPlayer_container").should("exist"); - - clickButtonReply(); - - // Reply to the player with another audio file - uploadFile("cypress/fixtures/upload-second.ogg"); - - // Assert that the audio player is rendered - cy.get(".mx_EventTile_last .mx_AudioPlayer_container").should("exist"); - - clickButtonReply(); - - // Reply to the player with yet another audio file to create a reply chain - uploadFile("cypress/fixtures/upload-third.ogg"); - - cy.get(".mx_EventTile_last").within(() => { - // Assert that the audio player is rendered - cy.get(".mx_AudioPlayer_container").should("exist"); - - // Assert that there are two "mx_ReplyChain" elements - cy.get(".mx_ReplyChain").should("have.length", 2); - - // Assert that one line contains the user name - cy.get(".mx_ReplyChain .mx_ReplyTile_sender").within(() => { - cy.findByText(TEST_USER); - }); - - // Assert that the other line contains the file button - cy.get(".mx_ReplyChain .mx_MFileBody").should("exist"); - - // Click "In reply to" - cy.contains(".mx_ReplyChain .mx_ReplyChain_show", "In reply to").click(); - - cy.get("blockquote.mx_ReplyChain:first-of-type").within(() => { - // Assert that "In reply to" has disappeared - cy.findByText("In reply to").should("not.exist"); - - // Assert that audio file on the first row is rendered as file button - cy.get(".mx_MFileBody_info[role='button']").within(() => { - // Assert that the file button contains the name of the file sent at first - cy.contains(".mx_MFileBody_info_filename", "upload-first.ogg"); - }); - }); - }); - - // Take snapshots - takeSnapshots("Selected EventTile of audio player with a reply chain"); - }); - - it("should be rendered, play, and support replying on a thread", () => { - uploadFile("cypress/fixtures/1sec-long-name-audio-file.ogg"); - - // On the main timeline - cy.get(".mx_RoomView_MessageList").within(() => { - // Assert the audio player is rendered - cy.get(".mx_EventTile_last .mx_AudioPlayer_container").should("exist"); - - // Find and click "Reply in thread" button - cy.get(".mx_EventTile_last").realHover().findByRole("button", { name: "Reply in thread" }).click(); - }); - - // On a thread - cy.get(".mx_ThreadView").within(() => { - cy.get(".mx_EventTile_last").within(() => { - // Assert that the player is correctly rendered on a thread - cy.get(".mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container").within(() => { - // Assert that the counter is zero before clicking the play button - cy.contains(".mx_AudioPlayer_seek [role='timer']", "00:00").should("exist"); - - // Find and click "Play" button, the wait is to make the test less flaky - cy.findByRole("button", { name: "Play" }).should("exist"); - cy.wait(500).findByRole("button", { name: "Play" }).click(); - - // Assert that "Pause" button can be found - 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 "Play" button can be found - cy.findByRole("button", { name: "Play" }).should("exist").should("not.have.attr", "disabled"); - }); - }); - - // Find and click "Reply" button - // - // Calling cy.get(".mx_EventTile_last") again here is a workaround for - // https://github.com/matrix-org/matrix-js-sdk/issues/3394: the event tile may have been re-mounted while - // the audio was playing. - cy.get(".mx_EventTile_last").realHover().findByRole("button", { name: "Reply" }).click(); - - cy.get(".mx_MessageComposer--compact").within(() => { - // Assert that the reply preview is rendered on the message composer - cy.get(".mx_ReplyPreview").within(() => { - // Assert that the reply preview contains audio ReplyTile the file info button - cy.get(".mx_ReplyTile_audio .mx_MFileBody_info[role='button']").should("exist"); - }); - - // Select :smile: emoji and send it - cy.findByTestId("basicmessagecomposer").type(":smile:"); - cy.get(".mx_Autocomplete_Completion[aria-selected='true']").click(); - cy.findByTestId("basicmessagecomposer").type("{enter}"); - }); - - cy.get(".mx_EventTile_last").within(() => { - // Assert that the file name is rendered on the file button - cy.get(".mx_ReplyTile_audio .mx_MFileBody_info[role='button']").should("exist"); - }); - }); - }); -}); diff --git a/playwright/e2e/audio-player/audio-player.spec.ts b/playwright/e2e/audio-player/audio-player.spec.ts new file mode 100644 index 0000000000..f7aaf1fb46 --- /dev/null +++ b/playwright/e2e/audio-player/audio-player.spec.ts @@ -0,0 +1,351 @@ +/* +Copyright 2023 Suguru Hirahara +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 { Locator, Page } from "@playwright/test"; +import { test, expect } from "../../element-web-test"; +import { SettingLevel } from "../../../src/settings/SettingLevel"; +import { Layout } from "../../../src/settings/enums/Layout"; +import { ElementAppPage } from "../../pages/ElementAppPage"; + +test.describe("Audio player", () => { + test.use({ + displayName: "Hanako", + }); + + const uploadFile = async (page: Page, file: string) => { + // Upload a file from the message composer + await page.locator(".mx_MessageComposer_actions input[type='file']").setInputFiles(file); + + // Find and click primary "Upload" button + 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(); + // wait for the tile to finish loading + await expect( + page + .locator(".mx_AudioPlayer_mediaName") + .last() + .filter({ hasText: file.split("/").at(-1) }), + ).toBeVisible(); + }; + + /** + * Take snapshots of mx_EventTile_last on each layout, outputting log for reference/debugging. + * @param detail The snapshot name. Used for outputting logs too. + * @param monospace This changes the font used to render the UI from a default one to Inconsolata. Set to false by default. + */ + const takeSnapshots = async (page: Page, app: ElementAppPage, detail: string, monospace = false) => { + // Check that the audio player is rendered and its button becomes visible + const checkPlayerVisibility = async (locator: Locator) => { + // Assert that the audio player and media information are visible + const mediaInfo = locator.locator( + ".mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container .mx_AudioPlayer_mediaInfo", + ); + await expect(mediaInfo.locator(".mx_AudioPlayer_mediaName", { hasText: ".ogg" })).toBeVisible(); // extension + 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 play button can be found and is visible + await expect(locator.getByRole("button", { name: "Play" })).toBeVisible(); + + if (monospace) { + // Assert that the monospace timer is visible + await expect(locator.locator("[role='timer']")).toHaveCSS("font-family", "Inconsolata"); + } + }; + + if (monospace) { + // Enable system font and monospace setting + await app.settings.setValue("useBundledEmojiFont", null, SettingLevel.DEVICE, false); + await app.settings.setValue("useSystemFont", null, SettingLevel.DEVICE, true); + await app.settings.setValue("systemFont", null, SettingLevel.DEVICE, "Inconsolata"); + } + + // Check the status of the seek bar + expect(await page.locator(".mx_AudioPlayer_seek input[type='range']").count()).toBeGreaterThan(0); + + // Enable IRC layout + await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC); + + const ircTile = page.locator(".mx_EventTile_last[data-layout='irc']"); + // Click the event timestamp to highlight EventTile in case it is not visible + await ircTile.locator(".mx_MessageTimestamp").click(); + // Assert that rendering of the player settled and the play button is visible before taking a snapshot + await checkPlayerVisibility(ircTile); + + const screenshotOptions = { + css: ` + /* The timestamp is of inconsistent width depending on the time the test runs at */ + .mx_MessageTimestamp { + display: none !important; + } + `, + mask: [page.locator(".mx_AudioPlayer_seek")], + }; + + // Take a snapshot of mx_EventTile_last on IRC layout + await expect(page.locator(".mx_EventTile_last")).toMatchScreenshot( + `${detail.replaceAll(" ", "-")}-irc-layout.png`, + screenshotOptions, + ); + + // Take a snapshot on modern/group layout + await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Group); + const groupTile = page.locator(".mx_EventTile_last[data-layout='group']"); + await groupTile.locator(".mx_MessageTimestamp").click(); + await checkPlayerVisibility(groupTile); + await expect(page.locator(".mx_EventTile_last")).toMatchScreenshot( + `${detail.replaceAll(" ", "-")}-group-layout.png`, + screenshotOptions, + ); + + // Take a snapshot on bubble layout + await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Bubble); + const bubbleTile = page.locator(".mx_EventTile_last[data-layout='bubble']"); + await bubbleTile.locator(".mx_MessageTimestamp").click(); + await checkPlayerVisibility(bubbleTile); + await expect(page.locator(".mx_EventTile_last")).toMatchScreenshot( + `${detail.replaceAll(" ", "-")}-bubble-layout.png`, + screenshotOptions, + ); + }; + + test.beforeEach(async ({ page, app, user }) => { + await app.client.createRoom({ name: "Test Room" }); + await app.viewRoomByName("Test Room"); + + // Wait until configuration is finished + await expect( + page + .locator(".mx_GenericEventListSummary[data-layout='group'] .mx_GenericEventListSummary_summary") + .getByText(`${user.displayName} created and configured the room.`), + ).toBeVisible(); + }); + + test("should be correctly rendered - light theme", async ({ page, app }) => { + await uploadFile(page, "cypress/fixtures/1sec-long-name-audio-file.ogg"); + await takeSnapshots(page, app, "Selected EventTile of audio player (light theme)"); + }); + + test("should be correctly rendered - light theme with monospace font", async ({ page, app }) => { + await uploadFile(page, "cypress/fixtures/1sec-long-name-audio-file.ogg"); + + await takeSnapshots(page, app, "Selected EventTile of audio player (light theme, monospace font)", true); // Enable monospace + }); + + test("should be correctly rendered - high contrast theme", async ({ page, app }) => { + // Disable system theme in case ThemeWatcher enables the theme automatically, + // so that the high contrast theme can be enabled + await app.settings.setValue("use_system_theme", null, SettingLevel.DEVICE, false); + + // Enable high contrast manually + const settings = await app.settings.openUserSettings("Appearance"); + await settings.getByTestId("mx_ThemeChoicePanel").getByText("Use high contrast").click(); + + await app.closeDialog(); + + await uploadFile(page, "cypress/fixtures/1sec-long-name-audio-file.ogg"); + + await takeSnapshots(page, app, "Selected EventTile of audio player (high contrast)"); + }); + + test("should be correctly rendered - dark theme", async ({ page, app }) => { + // Enable dark theme + await app.settings.setValue("theme", null, SettingLevel.ACCOUNT, "dark"); + + await uploadFile(page, "cypress/fixtures/1sec-long-name-audio-file.ogg"); + + await takeSnapshots(page, app, "Selected EventTile of audio player (dark theme)"); + }); + + test("should play an audio file", async ({ page, app }) => { + await uploadFile(page, "cypress/fixtures/1sec.ogg"); + + // Assert that the audio player is rendered + const container = page.locator(".mx_EventTile_last .mx_AudioPlayer_container"); + // Assert that the counter is zero before clicking the play button + await expect(container.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible(); + + // Find and click "Play" button, the wait is to make the test less flaky + await expect(container.getByRole("button", { name: "Play" })).toBeVisible(); + await container.getByRole("button", { name: "Play" }).click(); + + // Assert that "Pause" button can be found + await expect(container.getByRole("button", { name: "Pause" })).toBeVisible(); + + // Assert that the timer is reset when the audio file finished playing + await expect(container.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible(); + + // Assert that "Play" button can be found + await expect(container.getByRole("button", { name: "Play" })).toBeVisible(); + }); + + test("should support downloading an audio file", async ({ page, app }) => { + await uploadFile(page, "cypress/fixtures/1sec.ogg"); + + const downloadPromise = page.waitForEvent("download"); + + // Find and click "Download" button on MessageActionBar + const tile = page.locator(".mx_EventTile_last"); + await tile.hover(); + await tile.getByRole("button", { name: "Download" }).click(); + + // Assert that the file was downloaded + const download = await downloadPromise; + expect(download.suggestedFilename()).toBe("1sec.ogg"); + }); + + test("should support replying to audio file with another audio file", async ({ page, app }) => { + await uploadFile(page, "cypress/fixtures/1sec.ogg"); + + // Assert the audio player is rendered + await expect(page.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible(); + + // Find and click "Reply" button on MessageActionBar + const tile = page.locator(".mx_EventTile_last"); + await tile.hover(); + await tile.getByRole("button", { name: "Reply", exact: true }).click(); + + // Reply to the player with another audio file + await uploadFile(page, "cypress/fixtures/1sec.ogg"); + + // Assert that the audio player is rendered + await expect(tile.locator(".mx_AudioPlayer_container")).toBeVisible(); + + // Assert that replied audio file is rendered as file button inside ReplyChain + const button = tile.locator(".mx_ReplyChain_wrapper .mx_MFileBody_info[role='button']"); + // Assert that the file button has file name + await expect(button.locator(".mx_MFileBody_info_filename")).toBeVisible(); + + await takeSnapshots(page, app, "Selected EventTile of audio player with a reply"); + }); + + test("should support creating a reply chain with multiple audio files", async ({ page, app, user }) => { + // Note: "mx_ReplyChain" element is used not only for replies which + // create a reply chain, but also for a single reply without a replied + // message. This test checks whether a reply chain which consists of + // multiple audio file replies is rendered properly. + + const tile = page.locator(".mx_EventTile_last"); + + // Find and click "Reply" button + const clickButtonReply = async () => { + await tile.hover(); + await tile.getByRole("button", { name: "Reply", exact: true }).click(); + }; + + await uploadFile(page, "cypress/fixtures/upload-first.ogg"); + + // Assert that the audio player is rendered + await expect(page.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible(); + + await clickButtonReply(); + + // Reply to the player with another audio file + await uploadFile(page, "cypress/fixtures/upload-second.ogg"); + + // Assert that the audio player is rendered + await expect(page.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible(); + + await clickButtonReply(); + + // Reply to the player with yet another audio file to create a reply chain + await uploadFile(page, "cypress/fixtures/upload-third.ogg"); + + // Assert that the audio player is rendered + await expect(tile.locator(".mx_AudioPlayer_container")).toBeVisible(); + + // Assert that there are two "mx_ReplyChain" elements + await expect(tile.locator(".mx_ReplyChain")).toHaveCount(2); + + // Assert that one line contains the user name + await expect(tile.locator(".mx_ReplyChain .mx_ReplyTile_sender").getByText(user.displayName)).toBeVisible(); + + // Assert that the other line contains the file button + await expect(tile.locator(".mx_ReplyChain .mx_MFileBody")).toBeVisible(); + + // Click "In reply to" + await tile.locator(".mx_ReplyChain .mx_ReplyChain_show", { hasText: "In reply to" }).click(); + + const replyChain = tile.locator(".mx_ReplyChain:first-of-type"); + // Assert that "In reply to" has disappeared + await expect(replyChain.getByText("In reply to")).not.toBeVisible(); + + // Assert that the file button contains the name of the file sent at first + await expect( + replyChain + .locator(".mx_MFileBody_info[role='button']") + .locator(".mx_MFileBody_info_filename", { hasText: "upload-first.ogg" }), + ).toBeVisible(); + + // Take snapshots + await takeSnapshots(page, app, "Selected EventTile of audio player with a reply chain"); + }); + + test("should be rendered, play, and support replying on a thread", async ({ page, app }) => { + await uploadFile(page, "cypress/fixtures/1sec-long-name-audio-file.ogg"); + + // On the main timeline + const messageList = page.locator(".mx_RoomView_MessageList"); + // Assert the audio player is rendered + await expect(messageList.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible(); + // Find and click "Reply in thread" button + await messageList.locator(".mx_EventTile_last").hover(); + await messageList.locator(".mx_EventTile_last").getByRole("button", { name: "Reply in thread" }).click(); + + // On a thread + const thread = page.locator(".mx_ThreadView"); + const threadTile = thread.locator(".mx_EventTile_last"); + const audioPlayer = threadTile.locator(".mx_AudioPlayer_container"); + + // Assert that the counter is zero before clicking the play button + await expect(audioPlayer.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible(); + + // Find and click "Play" button, the wait is to make the test less flaky + await expect(audioPlayer.getByRole("button", { name: "Play" })).toBeVisible(); + await audioPlayer.getByRole("button", { name: "Play" }).click(); + + // Assert that "Pause" button can be found + await expect(audioPlayer.getByRole("button", { name: "Pause" })).toBeVisible(); + + // Assert that the timer is reset when the audio file finished playing + await expect(audioPlayer.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible(); + + // Assert that "Play" button can be found + await expect(audioPlayer.getByRole("button", { name: "Play" })).not.toBeDisabled(); + + // Find and click "Reply" button + await threadTile.hover(); + await threadTile.getByRole("button", { name: "Reply", exact: true }).click(); + + const composer = thread.locator(".mx_MessageComposer--compact"); + // Assert that the reply preview contains audio ReplyTile the file info button + await expect( + composer.locator(".mx_ReplyPreview .mx_ReplyTile_audio .mx_MFileBody_info[role='button']"), + ).toBeVisible(); + + // Select :smile: emoji and send it + await composer.getByTestId("basicmessagecomposer").fill(":smile:"); + await composer.locator(".mx_Autocomplete_Completion[aria-selected='true']").click(); + await composer.getByTestId("basicmessagecomposer").press("Enter"); + + // Assert that the file name is rendered on the file button + await expect(threadTile.locator(".mx_ReplyTile_audio .mx_MFileBody_info[role='button']")).toBeVisible(); + }); +}); diff --git a/playwright/element-web-test.ts b/playwright/element-web-test.ts index 52bd31c0ce..ee78be2d06 100644 --- a/playwright/element-web-test.ts +++ b/playwright/element-web-test.ts @@ -211,7 +211,17 @@ export const test = base.extend< }); export const expect = baseExpect.extend({ - async toMatchScreenshot(this: ExpectMatcherState, receiver: Page | Locator, ...args) { + async toMatchScreenshot( + this: ExpectMatcherState, + receiver: Page | Locator, + name?: `${string}.png`, + options?: { + mask?: Array; + omitBackground?: boolean; + timeout?: number; + css?: string; + }, + ) { const page = "page" in receiver ? receiver.page() : receiver; // We add a custom style tag before taking screenshots @@ -233,10 +243,11 @@ export const expect = baseExpect.extend({ .mx_ReplyChain { border-left-color: var(--cpd-color-blue-1200) !important; } + ${options?.css ?? ""} `, })) as ElementHandle; - await baseExpect(receiver).toHaveScreenshot(...args); + await baseExpect(receiver).toHaveScreenshot(name, options); await style.evaluate((tag) => tag.remove()); return { pass: true, message: () => "", name: "toMatchScreenshot" }; diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--dark-theme--bubble-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--dark-theme--bubble-layout-linux.png new file mode 100644 index 0000000000..1a6148c9c1 Binary files /dev/null and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--dark-theme--bubble-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--dark-theme--group-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--dark-theme--group-layout-linux.png new file mode 100644 index 0000000000..6d074f2773 Binary files /dev/null and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--dark-theme--group-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--dark-theme--irc-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--dark-theme--irc-layout-linux.png new file mode 100644 index 0000000000..98ce66178e Binary files /dev/null and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--dark-theme--irc-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--high-contrast--bubble-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--high-contrast--bubble-layout-linux.png new file mode 100644 index 0000000000..7e22dea5df Binary files /dev/null and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--high-contrast--bubble-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--high-contrast--group-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--high-contrast--group-layout-linux.png new file mode 100644 index 0000000000..655baf15a2 Binary files /dev/null and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--high-contrast--group-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--high-contrast--irc-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--high-contrast--irc-layout-linux.png new file mode 100644 index 0000000000..f230fe06da Binary files /dev/null and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--high-contrast--irc-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--bubble-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--bubble-layout-linux.png new file mode 100644 index 0000000000..6968d3176d Binary files /dev/null and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--bubble-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--group-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--group-layout-linux.png new file mode 100644 index 0000000000..2e661cac95 Binary files /dev/null and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--group-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--irc-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--irc-layout-linux.png new file mode 100644 index 0000000000..0c474b2c5c Binary files /dev/null and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--irc-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--monospace-font--bubble-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--monospace-font--bubble-layout-linux.png new file mode 100644 index 0000000000..1d8b5771d4 Binary files /dev/null and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--monospace-font--bubble-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--monospace-font--group-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--monospace-font--group-layout-linux.png new file mode 100644 index 0000000000..a59f8fbf17 Binary files /dev/null and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--monospace-font--group-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--monospace-font--irc-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--monospace-font--irc-layout-linux.png new file mode 100644 index 0000000000..4b74eb5745 Binary files /dev/null and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--monospace-font--irc-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-bubble-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-bubble-layout-linux.png new file mode 100644 index 0000000000..6a486d591f Binary files /dev/null and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-bubble-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-bubble-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-bubble-layout-linux.png new file mode 100644 index 0000000000..22b7b56d27 Binary files /dev/null and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-bubble-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-group-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-group-layout-linux.png new file mode 100644 index 0000000000..8592436e75 Binary files /dev/null and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-group-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-irc-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-irc-layout-linux.png new file mode 100644 index 0000000000..5ec4e38282 Binary files /dev/null and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-irc-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-group-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-group-layout-linux.png new file mode 100644 index 0000000000..00a570e933 Binary files /dev/null and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-group-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-irc-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-irc-layout-linux.png new file mode 100644 index 0000000000..a3bbd87162 Binary files /dev/null and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-irc-layout-linux.png differ