Playwright tests for file uploads (#33319)
* Refactor tests to use helper method for composer uploads. * Add drag and drop tests * lint * Add commentary * fixup test * More precise selector
This commit is contained in:
@@ -11,7 +11,7 @@ import type { Locator, Page } from "@playwright/test";
|
||||
import { test, expect, type ExtendedToMatchScreenshotOptions } from "../../element-web-test";
|
||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||
import { Layout } from "../../../src/settings/enums/Layout";
|
||||
import { type ElementAppPage } from "../../pages/ElementAppPage";
|
||||
import type { ElementAppPage } from "../../pages/ElementAppPage";
|
||||
import { getSampleFilePath } from "../../sample-files";
|
||||
|
||||
// Find and click "Reply" button
|
||||
@@ -29,22 +29,17 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
displayName: "Hanako",
|
||||
});
|
||||
|
||||
const uploadFile = async (page: Page, sampleFile: string) => {
|
||||
const uploadFile = async (app: ElementAppPage, sampleFile: string) => {
|
||||
// Upload a file from the message composer
|
||||
await page
|
||||
.locator(".mx_MessageComposer_actions input[type='file']")
|
||||
.setInputFiles(getSampleFilePath(sampleFile));
|
||||
|
||||
// Find and click primary "Upload" button
|
||||
await page.locator(".mx_Dialog").getByRole("button", { name: "Upload" }).click();
|
||||
await app.composerUploadFiles("room", getSampleFilePath(sampleFile));
|
||||
|
||||
// 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").getByRole("status")).toHaveAccessibleName(
|
||||
await expect(app.page.locator(".mx_RoomView_statusArea_expanded")).not.toBeVisible();
|
||||
await expect(app.page.locator(".mx_EventTile.mx_EventTile_last").getByRole("status")).toHaveAccessibleName(
|
||||
"Your message was sent",
|
||||
);
|
||||
// wait for the tile to finish loading
|
||||
await expect(page.getByTestId("audio-player-name").last().filter({ hasText: sampleFile })).toBeVisible();
|
||||
await expect(app.page.getByTestId("audio-player-name").last().filter({ hasText: sampleFile })).toBeVisible();
|
||||
};
|
||||
|
||||
const scrollToBottomOfTimeline = async (page: Page) => {
|
||||
@@ -157,7 +152,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
});
|
||||
|
||||
test("should be correctly rendered - light theme", { tag: "@screenshot" }, async ({ page, app }) => {
|
||||
await uploadFile(page, "1sec-long-name-audio-file.ogg");
|
||||
await uploadFile(app, "1sec-long-name-audio-file.ogg");
|
||||
await takeSnapshots(page, app, "Selected EventTile of audio player (light theme)");
|
||||
});
|
||||
|
||||
@@ -165,7 +160,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
"should be correctly rendered - light theme with monospace font",
|
||||
{ tag: "@screenshot" },
|
||||
async ({ page, app }) => {
|
||||
await uploadFile(page, "1sec-long-name-audio-file.ogg");
|
||||
await uploadFile(app, "1sec-long-name-audio-file.ogg");
|
||||
|
||||
await takeSnapshots(page, app, "Selected EventTile of audio player (light theme, monospace font)", true); // Enable monospace
|
||||
},
|
||||
@@ -182,7 +177,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
|
||||
await app.closeDialog();
|
||||
|
||||
await uploadFile(page, "1sec-long-name-audio-file.ogg");
|
||||
await uploadFile(app, "1sec-long-name-audio-file.ogg");
|
||||
|
||||
await takeSnapshots(page, app, "Selected EventTile of audio player (high contrast)");
|
||||
});
|
||||
@@ -191,13 +186,13 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
// Enable dark theme
|
||||
await app.settings.setValue("theme", null, SettingLevel.ACCOUNT, "dark");
|
||||
|
||||
await uploadFile(page, "1sec-long-name-audio-file.ogg");
|
||||
await uploadFile(app, "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, "1sec.ogg");
|
||||
await uploadFile(app, "1sec.ogg");
|
||||
|
||||
// Assert that the audio player is rendered
|
||||
const container = page.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" });
|
||||
@@ -219,7 +214,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
});
|
||||
|
||||
test("should support downloading an audio file", async ({ page, app }) => {
|
||||
await uploadFile(page, "1sec.ogg");
|
||||
await uploadFile(app, "1sec.ogg");
|
||||
|
||||
const downloadPromise = page.waitForEvent("download");
|
||||
|
||||
@@ -237,7 +232,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
"should support replying to audio file with another audio file",
|
||||
{ tag: "@screenshot" },
|
||||
async ({ page, app }) => {
|
||||
await uploadFile(page, "1sec.ogg");
|
||||
await uploadFile(app, "1sec.ogg");
|
||||
|
||||
// Assert the audio player is rendered
|
||||
await expect(page.getByRole("region", { name: "Audio player" })).toBeVisible();
|
||||
@@ -247,7 +242,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
await clickButtonReply(tile);
|
||||
|
||||
// Reply to the player with another audio file
|
||||
await uploadFile(page, "1sec.ogg");
|
||||
await uploadFile(app, "1sec.ogg");
|
||||
|
||||
// Assert that the audio player is rendered
|
||||
await expect(tile.getByRole("region", { name: "Audio player" })).toBeVisible();
|
||||
@@ -272,7 +267,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
|
||||
const tile = page.locator(".mx_EventTile_last");
|
||||
|
||||
await uploadFile(page, "upload-first.ogg");
|
||||
await uploadFile(app, "upload-first.ogg");
|
||||
|
||||
// Assert that the audio player is rendered
|
||||
await expect(
|
||||
@@ -282,7 +277,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
await clickButtonReply(tile);
|
||||
|
||||
// Reply to the player with another audio file
|
||||
await uploadFile(page, "upload-second.ogg");
|
||||
await uploadFile(app, "upload-second.ogg");
|
||||
|
||||
// Assert that the audio player is rendered
|
||||
await expect(
|
||||
@@ -292,7 +287,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
await clickButtonReply(tile);
|
||||
|
||||
// Reply to the player with yet another audio file to create a reply chain
|
||||
await uploadFile(page, "upload-third.ogg");
|
||||
await uploadFile(app, "upload-third.ogg");
|
||||
|
||||
// Assert that the audio player is rendered
|
||||
await expect(tile.getByRole("region", { name: "Audio player" })).toBeVisible();
|
||||
@@ -324,7 +319,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
);
|
||||
|
||||
test("should be rendered, play, and support replying on a thread", async ({ page, app }) => {
|
||||
await uploadFile(page, "1sec-long-name-audio-file.ogg");
|
||||
await uploadFile(app, "1sec-long-name-audio-file.ogg");
|
||||
|
||||
// On the main timeline
|
||||
const messageList = page.locator(".mx_RoomView_MessageList");
|
||||
|
||||
@@ -27,12 +27,17 @@ test.describe("Image Upload", () => {
|
||||
});
|
||||
|
||||
test("should show image preview when uploading an image", { tag: "@screenshot" }, async ({ page, app }) => {
|
||||
await page
|
||||
.locator(".mx_MessageComposer_actions input[type='file']")
|
||||
.setInputFiles(getSampleFilePath("riot.png"));
|
||||
await app.setComposerInputFiles("room", getSampleFilePath("riot.png"));
|
||||
|
||||
await expect(page.getByRole("button", { name: "Upload" })).toBeEnabled();
|
||||
await expect(page.getByRole("button", { name: "Close dialog" })).toBeEnabled();
|
||||
await expect(page.locator(".mx_Dialog")).toMatchScreenshot("image-upload-preview.png");
|
||||
});
|
||||
|
||||
test("should allow upload via drag and drop", { tag: "@screenshot" }, async ({ page, app }) => {
|
||||
await app.composerDragAndUploadFiles("room", getSampleFilePath("riot.png"), "image/png");
|
||||
await app.timeline.scrollToBottom();
|
||||
const imgTile = page.locator(".mx_MImageBody").first();
|
||||
await expect(imgTile).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,26 +5,21 @@ Copyright 2023 Suguru Hirahara
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type Page } from "@playwright/test";
|
||||
|
||||
import { test, expect } from "../../element-web-test";
|
||||
import { viewRoomSummaryByName } from "./utils";
|
||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
||||
import { getSampleFilePath } from "../../sample-files";
|
||||
import type { ElementAppPage } from "../../pages/ElementAppPage";
|
||||
|
||||
const ROOM_NAME = "Test room";
|
||||
const NAME = "Alice";
|
||||
|
||||
async function uploadFile(page: Page, sampleFile: string) {
|
||||
async function uploadFile(app: ElementAppPage, sampleFile: string) {
|
||||
// Upload a file from the message composer
|
||||
await page.locator(".mx_MessageComposer_actions input[type='file']").setInputFiles(getSampleFilePath(sampleFile));
|
||||
|
||||
await page.locator(".mx_Dialog").getByRole("button", { name: "Upload" }).click();
|
||||
|
||||
await app.composerUploadFiles("room", getSampleFilePath(sampleFile));
|
||||
// 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").getByRole("status")).toHaveAccessibleName(
|
||||
await expect(app.page.locator(".mx_RoomView_statusArea_expanded")).not.toBeVisible();
|
||||
await expect(app.page.locator(".mx_EventTile.mx_EventTile_last").getByRole("status")).toHaveAccessibleName(
|
||||
"Your message was sent",
|
||||
);
|
||||
}
|
||||
@@ -52,11 +47,11 @@ test.describe("FilePanel", () => {
|
||||
await expect(page.locator(".mx_RightPanel")).toMatchScreenshot("empty.png");
|
||||
});
|
||||
|
||||
test("should list tiles on the panel", { tag: "@screenshot" }, async ({ page }) => {
|
||||
test("should list tiles on the panel", { tag: "@screenshot" }, async ({ page, app }) => {
|
||||
// Upload multiple files
|
||||
await uploadFile(page, "riot.png"); // Image
|
||||
await uploadFile(page, "1sec.ogg"); // Audio
|
||||
await uploadFile(page, "matrix-org-client-versions.json"); // JSON
|
||||
await uploadFile(app, "riot.png"); // Image
|
||||
await uploadFile(app, "1sec.ogg"); // Audio
|
||||
await uploadFile(app, "matrix-org-client-versions.json"); // JSON
|
||||
|
||||
const roomViewBody = page.locator(".mx_RoomView_body");
|
||||
// Assert that all of the file were uploaded and rendered
|
||||
@@ -136,9 +131,9 @@ test.describe("FilePanel", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("should render the audio player and play the audio file on the panel", async ({ page }) => {
|
||||
test("should render the audio player and play the audio file on the panel", async ({ page, app }) => {
|
||||
// Upload an image file
|
||||
await uploadFile(page, "1sec.ogg");
|
||||
await uploadFile(app, "1sec.ogg");
|
||||
|
||||
const audioBody = page.getByTestId("right-panel").getByRole("region", { name: "Audio player" });
|
||||
|
||||
@@ -167,11 +162,11 @@ test.describe("FilePanel", () => {
|
||||
await expect(audioBody.getByRole("button", { name: "Play" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("should render file size in kibibytes on a file tile", async ({ page }) => {
|
||||
test("should render file size in kibibytes on a file tile", async ({ page, app }) => {
|
||||
const size = "1.12 KB"; // actual file size in kibibytes (1024 bytes)
|
||||
|
||||
// Upload a file
|
||||
await uploadFile(page, "matrix-org-client-versions.json");
|
||||
await uploadFile(app, "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)
|
||||
@@ -183,9 +178,9 @@ test.describe("FilePanel", () => {
|
||||
test.describe("download", () => {
|
||||
test.skip(isDendrite, "due to a Dendrite sending Content-Disposition inline");
|
||||
|
||||
test("should download an image via the link on the panel", async ({ page, context }) => {
|
||||
test("should download an image via the link on the panel", async ({ page, app, context }) => {
|
||||
// Upload an image file
|
||||
await uploadFile(page, "riot.png");
|
||||
await uploadFile(app, "riot.png");
|
||||
|
||||
// Detect the image file on the panel
|
||||
const imageBody = page.locator(
|
||||
|
||||
@@ -9,6 +9,7 @@ import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||
import { Layout } from "../../../src/settings/enums/Layout";
|
||||
import { test, expect } from "../../element-web-test";
|
||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
||||
import { getSampleFilePath } from "../../sample-files";
|
||||
|
||||
test.describe("Threads", () => {
|
||||
test.skip(isDendrite, "due to a Dendrite bug https://github.com/element-hq/dendrite/issues/3489");
|
||||
@@ -360,6 +361,50 @@ test.describe("Threads", () => {
|
||||
await app.getComposer(true).getByRole("button", { name: "Send voice message" }).click();
|
||||
await expect(page.locator(".mx_ThreadView .mx_MVoiceMessageBody")).toHaveCount(1);
|
||||
});
|
||||
test("can send files", async ({ page, app, user }) => {
|
||||
// Increase right-panel size, so that files fit
|
||||
await page.evaluate(() => {
|
||||
window.localStorage.setItem("mx_rhs_size", "600");
|
||||
});
|
||||
|
||||
const roomId = await app.client.createRoom({});
|
||||
await page.goto("/#/room/" + roomId);
|
||||
|
||||
// Send message
|
||||
const locator = page.locator(".mx_RoomView_body");
|
||||
await locator.getByRole("textbox", { name: "Send an unencrypted message…" }).fill("Hello Mr. Bot");
|
||||
await locator.getByRole("textbox", { name: "Send an unencrypted message…" }).press("Enter");
|
||||
// Create thread
|
||||
const locator2 = locator.locator(".mx_EventTile[data-scroll-tokens]").filter({ hasText: "Hello Mr. Bot" });
|
||||
await locator2.hover();
|
||||
await locator2.getByRole("button", { name: "Reply in thread" }).click();
|
||||
|
||||
await expect(page.locator(".mx_ThreadView_timelinePanelWrapper")).toHaveCount(1);
|
||||
await app.composerUploadFiles("thread", getSampleFilePath("riot.png"));
|
||||
await expect(page.locator(".mx_ThreadView .mx_EventTile_image")).toHaveCount(1);
|
||||
});
|
||||
test("can send files via drag&drop", async ({ page, app, user }) => {
|
||||
// Increase right-panel size, so that files fit
|
||||
await page.evaluate(() => {
|
||||
window.localStorage.setItem("mx_rhs_size", "600");
|
||||
});
|
||||
|
||||
const roomId = await app.client.createRoom({});
|
||||
await page.goto("/#/room/" + roomId);
|
||||
|
||||
// Send message
|
||||
const locator = page.locator(".mx_RoomView_body");
|
||||
await locator.getByRole("textbox", { name: "Send an unencrypted message…" }).fill("Hello Mr. Bot");
|
||||
await locator.getByRole("textbox", { name: "Send an unencrypted message…" }).press("Enter");
|
||||
// Create thread
|
||||
const locator2 = locator.locator(".mx_EventTile[data-scroll-tokens]").filter({ hasText: "Hello Mr. Bot" });
|
||||
await locator2.hover();
|
||||
await locator2.getByRole("button", { name: "Reply in thread" }).click();
|
||||
|
||||
await expect(page.locator(".mx_ThreadView_timelinePanelWrapper")).toHaveCount(1);
|
||||
await app.composerDragAndUploadFiles("thread", getSampleFilePath("riot.png"), "image/png");
|
||||
await expect(page.locator(".mx_ThreadView .mx_EventTile_image")).toHaveCount(1);
|
||||
});
|
||||
});
|
||||
|
||||
test(
|
||||
|
||||
Reference in New Issue
Block a user