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 { test, expect, type ExtendedToMatchScreenshotOptions } from "../../element-web-test";
|
||||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||||
import { Layout } from "../../../src/settings/enums/Layout";
|
import { Layout } from "../../../src/settings/enums/Layout";
|
||||||
import { type ElementAppPage } from "../../pages/ElementAppPage";
|
import type { ElementAppPage } from "../../pages/ElementAppPage";
|
||||||
import { getSampleFilePath } from "../../sample-files";
|
import { getSampleFilePath } from "../../sample-files";
|
||||||
|
|
||||||
// Find and click "Reply" button
|
// Find and click "Reply" button
|
||||||
@@ -29,22 +29,17 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
displayName: "Hanako",
|
displayName: "Hanako",
|
||||||
});
|
});
|
||||||
|
|
||||||
const uploadFile = async (page: Page, sampleFile: string) => {
|
const uploadFile = async (app: ElementAppPage, sampleFile: string) => {
|
||||||
// Upload a file from the message composer
|
// Upload a file from the message composer
|
||||||
await page
|
await app.composerUploadFiles("room", getSampleFilePath(sampleFile));
|
||||||
.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();
|
|
||||||
|
|
||||||
// Wait until the file is sent
|
// Wait until the file is sent
|
||||||
await expect(page.locator(".mx_RoomView_statusArea_expanded")).not.toBeVisible();
|
await expect(app.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_EventTile.mx_EventTile_last").getByRole("status")).toHaveAccessibleName(
|
||||||
"Your message was sent",
|
"Your message was sent",
|
||||||
);
|
);
|
||||||
// wait for the tile to finish loading
|
// 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) => {
|
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 }) => {
|
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)");
|
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",
|
"should be correctly rendered - light theme with monospace font",
|
||||||
{ tag: "@screenshot" },
|
{ tag: "@screenshot" },
|
||||||
async ({ page, app }) => {
|
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
|
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 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)");
|
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
|
// Enable dark theme
|
||||||
await app.settings.setValue("theme", null, SettingLevel.ACCOUNT, "dark");
|
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)");
|
await takeSnapshots(page, app, "Selected EventTile of audio player (dark theme)");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should play an audio file", async ({ page, app }) => {
|
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
|
// Assert that the audio player is rendered
|
||||||
const container = page.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" });
|
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 }) => {
|
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");
|
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",
|
"should support replying to audio file with another audio file",
|
||||||
{ tag: "@screenshot" },
|
{ tag: "@screenshot" },
|
||||||
async ({ page, app }) => {
|
async ({ page, app }) => {
|
||||||
await uploadFile(page, "1sec.ogg");
|
await uploadFile(app, "1sec.ogg");
|
||||||
|
|
||||||
// Assert the audio player is rendered
|
// Assert the audio player is rendered
|
||||||
await expect(page.getByRole("region", { name: "Audio player" })).toBeVisible();
|
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);
|
await clickButtonReply(tile);
|
||||||
|
|
||||||
// Reply to the player with another audio file
|
// 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
|
// Assert that the audio player is rendered
|
||||||
await expect(tile.getByRole("region", { name: "Audio player" })).toBeVisible();
|
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");
|
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
|
// Assert that the audio player is rendered
|
||||||
await expect(
|
await expect(
|
||||||
@@ -282,7 +277,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
await clickButtonReply(tile);
|
await clickButtonReply(tile);
|
||||||
|
|
||||||
// Reply to the player with another audio file
|
// 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
|
// Assert that the audio player is rendered
|
||||||
await expect(
|
await expect(
|
||||||
@@ -292,7 +287,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
await clickButtonReply(tile);
|
await clickButtonReply(tile);
|
||||||
|
|
||||||
// Reply to the player with yet another audio file to create a reply chain
|
// 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
|
// Assert that the audio player is rendered
|
||||||
await expect(tile.getByRole("region", { name: "Audio player" })).toBeVisible();
|
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 }) => {
|
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
|
// On the main timeline
|
||||||
const messageList = page.locator(".mx_RoomView_MessageList");
|
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 }) => {
|
test("should show image preview when uploading an image", { tag: "@screenshot" }, async ({ page, app }) => {
|
||||||
await page
|
await app.setComposerInputFiles("room", getSampleFilePath("riot.png"));
|
||||||
.locator(".mx_MessageComposer_actions input[type='file']")
|
|
||||||
.setInputFiles(getSampleFilePath("riot.png"));
|
|
||||||
|
|
||||||
await expect(page.getByRole("button", { name: "Upload" })).toBeEnabled();
|
await expect(page.getByRole("button", { name: "Upload" })).toBeEnabled();
|
||||||
await expect(page.getByRole("button", { name: "Close dialog" })).toBeEnabled();
|
await expect(page.getByRole("button", { name: "Close dialog" })).toBeEnabled();
|
||||||
await expect(page.locator(".mx_Dialog")).toMatchScreenshot("image-upload-preview.png");
|
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
|
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.
|
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 { test, expect } from "../../element-web-test";
|
||||||
import { viewRoomSummaryByName } from "./utils";
|
import { viewRoomSummaryByName } from "./utils";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
||||||
import { getSampleFilePath } from "../../sample-files";
|
import { getSampleFilePath } from "../../sample-files";
|
||||||
|
import type { ElementAppPage } from "../../pages/ElementAppPage";
|
||||||
|
|
||||||
const ROOM_NAME = "Test room";
|
const ROOM_NAME = "Test room";
|
||||||
const NAME = "Alice";
|
const NAME = "Alice";
|
||||||
|
|
||||||
async function uploadFile(page: Page, sampleFile: string) {
|
async function uploadFile(app: ElementAppPage, sampleFile: string) {
|
||||||
// Upload a file from the message composer
|
// Upload a file from the message composer
|
||||||
await page.locator(".mx_MessageComposer_actions input[type='file']").setInputFiles(getSampleFilePath(sampleFile));
|
await app.composerUploadFiles("room", getSampleFilePath(sampleFile));
|
||||||
|
|
||||||
await page.locator(".mx_Dialog").getByRole("button", { name: "Upload" }).click();
|
|
||||||
|
|
||||||
// Wait until the file is sent
|
// Wait until the file is sent
|
||||||
await expect(page.locator(".mx_RoomView_statusArea_expanded")).not.toBeVisible();
|
await expect(app.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_EventTile.mx_EventTile_last").getByRole("status")).toHaveAccessibleName(
|
||||||
"Your message was sent",
|
"Your message was sent",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -52,11 +47,11 @@ test.describe("FilePanel", () => {
|
|||||||
await expect(page.locator(".mx_RightPanel")).toMatchScreenshot("empty.png");
|
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
|
// Upload multiple files
|
||||||
await uploadFile(page, "riot.png"); // Image
|
await uploadFile(app, "riot.png"); // Image
|
||||||
await uploadFile(page, "1sec.ogg"); // Audio
|
await uploadFile(app, "1sec.ogg"); // Audio
|
||||||
await uploadFile(page, "matrix-org-client-versions.json"); // JSON
|
await uploadFile(app, "matrix-org-client-versions.json"); // JSON
|
||||||
|
|
||||||
const roomViewBody = page.locator(".mx_RoomView_body");
|
const roomViewBody = page.locator(".mx_RoomView_body");
|
||||||
// Assert that all of the file were uploaded and rendered
|
// 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
|
// 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" });
|
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();
|
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)
|
const size = "1.12 KB"; // actual file size in kibibytes (1024 bytes)
|
||||||
|
|
||||||
// Upload a file
|
// 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");
|
const tile = page.locator(".mx_FilePanel .mx_EventTile");
|
||||||
// Assert that the file size is displayed in kibibytes, not kilobytes (1000 bytes)
|
// Assert that the file size is displayed in kibibytes, not kilobytes (1000 bytes)
|
||||||
@@ -183,9 +178,9 @@ test.describe("FilePanel", () => {
|
|||||||
test.describe("download", () => {
|
test.describe("download", () => {
|
||||||
test.skip(isDendrite, "due to a Dendrite sending Content-Disposition inline");
|
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
|
// Upload an image file
|
||||||
await uploadFile(page, "riot.png");
|
await uploadFile(app, "riot.png");
|
||||||
|
|
||||||
// Detect the image file on the panel
|
// Detect the image file on the panel
|
||||||
const imageBody = page.locator(
|
const imageBody = page.locator(
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { SettingLevel } from "../../../src/settings/SettingLevel";
|
|||||||
import { Layout } from "../../../src/settings/enums/Layout";
|
import { Layout } from "../../../src/settings/enums/Layout";
|
||||||
import { test, expect } from "../../element-web-test";
|
import { test, expect } from "../../element-web-test";
|
||||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
||||||
|
import { getSampleFilePath } from "../../sample-files";
|
||||||
|
|
||||||
test.describe("Threads", () => {
|
test.describe("Threads", () => {
|
||||||
test.skip(isDendrite, "due to a Dendrite bug https://github.com/element-hq/dendrite/issues/3489");
|
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 app.getComposer(true).getByRole("button", { name: "Send voice message" }).click();
|
||||||
await expect(page.locator(".mx_ThreadView .mx_MVoiceMessageBody")).toHaveCount(1);
|
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(
|
test(
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { type Locator, type Page, expect } from "@playwright/test";
|
import { type Locator, type Page, expect } from "@playwright/test";
|
||||||
|
import { readFile } from "node:fs/promises";
|
||||||
|
import { basename } from "node:path";
|
||||||
|
|
||||||
import { Settings } from "./settings";
|
import { Settings } from "./settings";
|
||||||
import { Client } from "./client";
|
import { Client } from "./client";
|
||||||
@@ -156,6 +158,66 @@ export class ElementAppPage {
|
|||||||
return this.page.getByRole("menu");
|
return this.page.getByRole("menu");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the files on the composers file input, causing it to open the file
|
||||||
|
* upload dialog.
|
||||||
|
* @param location Should the main room input or the thread view input be used.
|
||||||
|
*/
|
||||||
|
public setComposerInputFiles(
|
||||||
|
location: "room" | "thread",
|
||||||
|
...params: Parameters<Locator["setInputFiles"]>
|
||||||
|
): ReturnType<Locator["setInputFiles"]> {
|
||||||
|
const input = this.page
|
||||||
|
.locator(location === "room" ? ".mx_RoomView_body" : ".mx_RightPanel")
|
||||||
|
.getByRole("region", { name: "Message composer" })
|
||||||
|
.locator("input[type='file']");
|
||||||
|
return input.setInputFiles(...params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the files on the composers file input, causing it to open the file
|
||||||
|
* upload dialog, and then automaticlly submits the dialog that pops up which
|
||||||
|
* causes the file to be uploaded.
|
||||||
|
* @param location Should the main room input or the thread view input be used.
|
||||||
|
*/
|
||||||
|
public async composerUploadFiles(
|
||||||
|
location: "room" | "thread",
|
||||||
|
...params: Parameters<Locator["setInputFiles"]>
|
||||||
|
): Promise<void> {
|
||||||
|
await this.setComposerInputFiles(location, ...params);
|
||||||
|
await this.page.locator(".mx_Dialog").getByRole("button", { name: "Upload" }).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drags a "file" into the specified composer and automatically uploads it.
|
||||||
|
* @param location Should the drop target the main room or the thread.
|
||||||
|
* @param path The path to the sample file so it can be read.
|
||||||
|
* @param type The mimetype of the file.
|
||||||
|
*/
|
||||||
|
public async composerDragAndUploadFiles(location: "room" | "thread", path: string, type: string): Promise<void> {
|
||||||
|
// Based on https://github.com/microsoft/playwright/issues/10667#issuecomment-2742123424
|
||||||
|
// This read a file, encodes it into base64 and then sends it along to the page to be treated
|
||||||
|
// as a DataTransfer (the mechanism for drag and dropped files).
|
||||||
|
const buffer = await readFile(path);
|
||||||
|
const name = basename(path);
|
||||||
|
|
||||||
|
const dataTransfer = await this.page.evaluateHandle(
|
||||||
|
async ([buffer, name, type]) => {
|
||||||
|
const dt = new DataTransfer();
|
||||||
|
const file = new File([Uint8Array.fromBase64(buffer)], name, {
|
||||||
|
type,
|
||||||
|
});
|
||||||
|
dt.items.add(file);
|
||||||
|
return dt;
|
||||||
|
},
|
||||||
|
[buffer.toString("base64"), name, type],
|
||||||
|
);
|
||||||
|
await this.page.dispatchEvent(location === "room" ? ".mx_RoomView_body" : ".mx_ThreadPanel", "drop", {
|
||||||
|
dataTransfer,
|
||||||
|
});
|
||||||
|
await this.page.locator(".mx_Dialog").getByRole("button", { name: "Upload" }).click();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the space panel space button based on a name. The space
|
* Returns the space panel space button based on a name. The space
|
||||||
* must be visible in the space panel
|
* must be visible in the space panel
|
||||||
|
|||||||
Reference in New Issue
Block a user