From 83f0650ed421e6b2e42f767d73360f1406ce3071 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 28 Nov 2023 13:52:09 +0000
Subject: [PATCH] Migrate most of editing.spec.ts from Cypress to Playwright
(#11947)
* Migrate location.spec.ts from Cypress to Playwright
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Migrate location.spec.ts from Cypress to Playwright
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Add screenshot
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Deflake
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---------
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
cypress/e2e/editing/editing.spec.ts | 257 +--------------
playwright/e2e/editing/editing.spec.ts | 292 ++++++++++++++++++
playwright/element-web-test.ts | 1 +
playwright/global.d.ts | 5 +
playwright/pages/ElementAppPage.ts | 61 +++-
.../message-edit-history-dialog-linux.png | Bin 0 -> 8442 bytes
6 files changed, 361 insertions(+), 255 deletions(-)
create mode 100644 playwright/e2e/editing/editing.spec.ts
create mode 100644 playwright/snapshots/editing/editing.spec.ts/message-edit-history-dialog-linux.png
diff --git a/cypress/e2e/editing/editing.spec.ts b/cypress/e2e/editing/editing.spec.ts
index 8695531a60..113da3421a 100644
--- a/cypress/e2e/editing/editing.spec.ts
+++ b/cypress/e2e/editing/editing.spec.ts
@@ -16,17 +16,8 @@ limitations under the License.
///
-import type { EventType, MsgType, ISendEventResponse, IContent } from "matrix-js-sdk/src/matrix";
-import { SettingLevel } from "../../../src/settings/SettingLevel";
+import type { MsgType, IContent } from "matrix-js-sdk/src/matrix";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
-import Chainable = Cypress.Chainable;
-
-const sendEvent = (roomId: string): Chainable => {
- return cy.sendEvent(roomId, null, "m.room.message" as EventType, {
- msgtype: "m.text" as MsgType,
- body: "Message",
- });
-};
/** generate a message event which will take up some room on the page. */
function mkPadding(n: number): IContent {
@@ -40,37 +31,13 @@ function mkPadding(n: number): IContent {
describe("Editing", () => {
let homeserver: HomeserverInstance;
- let roomId: string;
-
- // Edit "Message"
- const editLastMessage = (edit: string) => {
- cy.get(".mx_EventTile_last").realHover().findByRole("button", { name: "Edit" }).click();
- cy.findByRole("textbox", { name: "Edit message" }).type(`{selectAll}{del}${edit}{enter}`);
- };
-
- const clickEditedMessage = (edited: string) => {
- // Assert that the message was edited
- cy.contains(".mx_EventTile", edited)
- .should("exist")
- .within(() => {
- // Click to display the message edit history dialog
- cy.contains(".mx_EventTile_edited", "(edited)").click();
- });
- };
-
- const clickButtonViewSource = () => {
- // Assert that "View Source" button is rendered and click it
- cy.get(".mx_EventTile .mx_EventTile_line").realHover().findByRole("button", { name: "View Source" }).click();
- };
beforeEach(() => {
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(homeserver, "Edith").then(() => {
- cy.createRoom({ name: "Test room" }).then((_room1Id) => {
- roomId = _room1Id;
- }),
- cy.injectAxe();
+ cy.createRoom({ name: "Test room" });
+ cy.injectAxe();
});
});
});
@@ -79,224 +46,6 @@ describe("Editing", () => {
cy.stopHomeserver(homeserver);
});
- it("should render and interact with the message edit history dialog", () => {
- // Click the "Remove" button on the message edit history dialog
- const clickButtonRemove = () => {
- cy.get(".mx_EventTile_line").realHover().findByRole("button", { name: "Remove" }).click();
- };
-
- cy.visit("/#/room/" + roomId);
-
- // Send "Message"
- sendEvent(roomId);
-
- cy.get(".mx_RoomView_MessageList").within(() => {
- // Edit "Message" to "Massage"
- editLastMessage("Massage");
-
- // Assert that the edit label is visible
- cy.get(".mx_EventTile_edited").should("be.visible");
-
- clickEditedMessage("Massage");
- });
-
- cy.get(".mx_Dialog").within(() => {
- // Assert that the message edit history dialog is rendered
- cy.get(".mx_MessageEditHistoryDialog").within(() => {
- // Assert CSS styles which are difficult or cannot be detected with snapshots are applied as expected
- cy.get("li").should("have.css", "clear", "both");
- cy.get(".mx_EventTile .mx_MessageTimestamp")
- .should("have.css", "position", "absolute")
- .should("have.css", "inset-inline-start", "0px")
- .should("have.css", "text-align", "center");
- // Assert that monospace characters can fill the content line as expected
- cy.get(".mx_EventTile .mx_EventTile_content").should("have.css", "margin-inline-end", "0px");
-
- // Assert that zero block start padding is applied to mx_EventTile as expected
- // See: .mx_EventTile on _EventTile.pcss
- cy.get(".mx_EventTile").should("have.css", "padding-block-start", "0px");
-
- // Assert that the date separator is rendered at the top
- cy.get("li:nth-child(1) .mx_TimelineSeparator").within(() => {
- cy.get("h2").within(() => {
- cy.findByText("today").should("have.css", "text-transform", "capitalize");
- });
- });
-
- // Assert that the edited message is rendered under the date separator
- cy.get("li:nth-child(2) .mx_EventTile").within(() => {
- // Assert that the edited message body consists of both deleted character and inserted character
- // Above the first "e" of "Message" was replaced with "a"
- cy.get(".mx_EventTile_content .mx_EventTile_body").should("have.text", "Meassage");
-
- cy.get(".mx_EventTile_content .mx_EventTile_body").within(() => {
- cy.get(".mx_EditHistoryMessage_deletion").within(() => {
- cy.findByText("e");
- });
- cy.get(".mx_EditHistoryMessage_insertion").within(() => {
- cy.findByText("a");
- });
- });
- });
-
- // Assert that the original message is rendered at the bottom
- cy.get("li:nth-child(3) .mx_EventTile").within(() => {
- cy.get(".mx_EventTile_content .mx_EventTile_body").within(() => {
- cy.findByText("Message");
- });
- });
- });
- });
-
- // Exclude timestamps from a snapshot
- const percyCSS = ".mx_MessageTimestamp { visibility: hidden !important; }";
-
- // Take a snapshot of the dialog
- cy.get(".mx_Dialog_wrapper").percySnapshotElement("Message edit history dialog", { percyCSS });
-
- cy.get(".mx_Dialog").within(() => {
- cy.get(".mx_MessageEditHistoryDialog li:nth-child(2) .mx_EventTile").within(() => {
- cy.get(".mx_EventTile_content .mx_EventTile_body").should("have.text", "Meassage");
-
- // Click the "Remove" button again
- clickButtonRemove();
- });
-
- // Do nothing and close the dialog to confirm that the message edit history dialog is rendered
- cy.get(".mx_TextInputDialog").closeDialog();
-
- // Assert that the message edit history dialog is rendered again after it was closed
- cy.get(".mx_MessageEditHistoryDialog li:nth-child(2) .mx_EventTile").within(() => {
- cy.get(".mx_EventTile_content .mx_EventTile_body").should("have.text", "Meassage");
-
- // Click the "Remove" button again
- clickButtonRemove();
- });
-
- // This time remove the message really
- cy.get(".mx_TextInputDialog").within(() => {
- cy.findByRole("textbox", { name: "Reason (optional)" }).type("This is a test."); // Reason
- cy.findByRole("button", { name: "Remove" }).click();
- });
-
- // Assert that the message edit history dialog is rendered again
- cy.get(".mx_MessageEditHistoryDialog").within(() => {
- // Assert that the date is rendered
- cy.get("li:nth-child(1) .mx_TimelineSeparator").within(() => {
- cy.get("h2").within(() => {
- cy.findByText("today").should("have.css", "text-transform", "capitalize");
- });
- });
-
- // Assert that the original message is rendered under the date on the dialog
- cy.get("li:nth-child(2) .mx_EventTile").within(() => {
- cy.get(".mx_EventTile_content .mx_EventTile_body").within(() => {
- cy.findByText("Message");
- });
- });
-
- // Assert that the edited message is gone
- cy.contains(".mx_EventTile_content .mx_EventTile_body", "Meassage").should("not.exist");
-
- cy.closeDialog();
- });
- });
-
- // Assert that the main timeline is rendered
- cy.get(".mx_RoomView_MessageList").within(() => {
- cy.get(".mx_EventTile_last .mx_RedactedBody").within(() => {
- // Assert that the placeholder is rendered
- cy.findByText("Message deleted");
- });
- });
- });
-
- it("should render 'View Source' button in developer mode on the message edit history dialog", () => {
- cy.visit("/#/room/" + roomId);
-
- // Send "Message"
- sendEvent(roomId);
-
- cy.get(".mx_RoomView_MessageList").within(() => {
- // Edit "Message" to "Massage"
- editLastMessage("Massage");
-
- // Assert that the edit label is visible
- cy.get(".mx_EventTile_edited").should("be.visible");
-
- clickEditedMessage("Massage");
- });
-
- cy.get(".mx_Dialog").within(() => {
- // Assert that the original message is rendered
- cy.get(".mx_MessageEditHistoryDialog li:nth-child(3)").within(() => {
- // Assert that "View Source" is not rendered
- cy.get(".mx_EventTile .mx_EventTile_line")
- .realHover()
- .findByRole("button", { name: "View Source" })
- .should("not.exist");
- });
-
- cy.closeDialog();
- });
-
- // Enable developer mode
- cy.setSettingValue("developerMode", null, SettingLevel.ACCOUNT, true);
-
- cy.get(".mx_RoomView_MessageList").within(() => {
- clickEditedMessage("Massage");
- });
-
- cy.get(".mx_Dialog").within(() => {
- // Assert that the edited message is rendered
- cy.get(".mx_MessageEditHistoryDialog li:nth-child(2)").within(() => {
- // Assert that "Remove" button for the original message is rendered
- cy.get(".mx_EventTile .mx_EventTile_line").realHover().findByRole("button", { name: "Remove" });
-
- clickButtonViewSource();
- });
-
- // Assert that view source dialog is rendered and close the dialog
- cy.get(".mx_ViewSource").closeDialog();
-
- // Assert that the original message is rendered
- cy.get(".mx_MessageEditHistoryDialog li:nth-child(3)").within(() => {
- // Assert that "Remove" button for the original message does not exist
- cy.get(".mx_EventTile .mx_EventTile_line")
- .realHover()
- .findByRole("button", { name: "Remove" })
- .should("not.exist");
-
- clickButtonViewSource();
- });
-
- // Assert that view source dialog is rendered and close the dialog
- cy.get(".mx_ViewSource").closeDialog();
- });
- });
-
- it("should close the composer when clicking save after making a change and undoing it", () => {
- cy.visit("/#/room/" + roomId);
-
- sendEvent(roomId);
-
- // Edit message
- cy.get(".mx_RoomView_body .mx_EventTile").within(() => {
- cy.findByText("Message");
- cy.get(".mx_EventTile_line").realHover().findByRole("button", { name: "Edit" }).click().checkA11y();
- cy.get(".mx_EventTile_line")
- .findByRole("textbox", { name: "Edit message" })
- .type("Foo{backspace}{backspace}{backspace}{enter}")
- .checkA11y();
- });
- cy.get(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]").within(() => {
- cy.findByText("Message");
- });
-
- // Assert that the edit composer has gone away
- cy.findByRole("textbox", { name: "Edit message" }).should("not.exist");
- });
-
it("should correctly display events which are edited, where we lack the edit event", () => {
// This tests the behaviour when a message has been edited some time after it has been sent, and we
// jump back in room history to view the event, but do not have the actual edit event.
diff --git a/playwright/e2e/editing/editing.spec.ts b/playwright/e2e/editing/editing.spec.ts
new file mode 100644
index 0000000000..f05f6f3382
--- /dev/null
+++ b/playwright/e2e/editing/editing.spec.ts
@@ -0,0 +1,292 @@
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { Locator, Page } from "@playwright/test";
+
+import type { EventType, MsgType, ISendEventResponse } from "matrix-js-sdk/src/matrix";
+import { test, expect } from "../../element-web-test";
+import { ElementAppPage } from "../../pages/ElementAppPage";
+import { SettingLevel } from "../../../src/settings/SettingLevel";
+
+const sendEvent = async (app: ElementAppPage, roomId: string): Promise => {
+ return app.sendEvent(roomId, null, "m.room.message" as EventType, {
+ msgtype: "m.text" as MsgType,
+ body: "Message",
+ });
+};
+
+test.describe("Editing", () => {
+ // Edit "Message"
+ const editLastMessage = async (page: Page, edit: string) => {
+ const eventTile = page.locator(".mx_RoomView_MessageList .mx_EventTile_last");
+ await eventTile.hover();
+ await eventTile.getByRole("button", { name: "Edit" }).click();
+
+ const textbox = page.getByRole("textbox", { name: "Edit message" });
+ await textbox.fill(edit);
+ await textbox.press("Enter");
+ };
+
+ const clickEditedMessage = async (page: Page, edited: string) => {
+ // Assert that the message was edited
+ const eventTile = page.locator(".mx_EventTile", { hasText: edited });
+ await expect(eventTile).toBeVisible();
+ // Click to display the message edit history dialog
+ await eventTile.getByText("(edited)").click();
+ };
+
+ const clickButtonViewSource = async (locator: Locator) => {
+ const eventTile = locator.locator(".mx_EventTile_line");
+ await eventTile.hover();
+ // Assert that "View Source" button is rendered and click it
+ await eventTile.getByRole("button", { name: "View Source" }).click();
+ };
+
+ test.use({
+ displayName: "Edith",
+ room: async ({ user, app }, use) => {
+ const roomId = await app.createRoom({ name: "Test room" });
+ await use({ roomId });
+ },
+ });
+
+ test("should render and interact with the message edit history dialog", async ({ page, user, app, room }) => {
+ // Click the "Remove" button on the message edit history dialog
+ const clickButtonRemove = async (locator: Locator) => {
+ const eventTileLine = locator.locator(".mx_EventTile_line");
+ await eventTileLine.hover();
+ await eventTileLine.getByRole("button", { name: "Remove" }).click();
+ };
+
+ await page.goto(`#/room/${room.roomId}`);
+
+ // Send "Message"
+ await sendEvent(app, room.roomId);
+
+ // Edit "Message" to "Massage"
+ await editLastMessage(page, "Massage");
+
+ // Assert that the edit label is visible
+ await expect(page.locator(".mx_EventTile_edited")).toBeVisible();
+
+ await clickEditedMessage(page, "Massage");
+
+ // Assert that the message edit history dialog is rendered
+ const dialog = page.getByRole("dialog");
+ const li = dialog.getByRole("listitem").last();
+ // Assert CSS styles which are difficult or cannot be detected with snapshots are applied as expected
+ await expect(li).toHaveCSS("clear", "both");
+
+ const timestamp = li.locator(".mx_EventTile .mx_MessageTimestamp");
+ await expect(timestamp).toHaveCSS("position", "absolute");
+ await expect(timestamp).toHaveCSS("inset-inline-start", "0px");
+ await expect(timestamp).toHaveCSS("text-align", "center");
+
+ // Assert that monospace characters can fill the content line as expected
+ await expect(li.locator(".mx_EventTile .mx_EventTile_content")).toHaveCSS("margin-inline-end", "0px");
+
+ // Assert that zero block start padding is applied to mx_EventTile as expected
+ // See: .mx_EventTile on _EventTile.pcss
+ await expect(li.locator(".mx_EventTile")).toHaveCSS("padding-block-start", "0px");
+
+ // Assert that the date separator is rendered at the top
+ await expect(dialog.getByRole("listitem").first().locator("h2", { hasText: "today" })).toHaveCSS(
+ "text-transform",
+ "capitalize",
+ );
+
+ {
+ // Assert that the edited message is rendered under the date separator
+ const tile = dialog.locator("li:nth-child(2) .mx_EventTile");
+ // Assert that the edited message body consists of both deleted character and inserted character
+ // Above the first "e" of "Message" was replaced with "a"
+ await expect(tile.locator(".mx_EventTile_body")).toHaveText("Meassage");
+
+ const body = tile.locator(".mx_EventTile_content .mx_EventTile_body");
+ await expect(body.locator(".mx_EditHistoryMessage_deletion").getByText("e")).toBeVisible();
+ await expect(body.locator(".mx_EditHistoryMessage_insertion").getByText("a")).toBeVisible();
+ }
+
+ // Assert that the original message is rendered at the bottom
+ await expect(
+ dialog
+ .locator("li:nth-child(3) .mx_EventTile")
+ .locator(".mx_EventTile_content .mx_EventTile_body", { hasText: "Message" }),
+ ).toBeVisible();
+
+ // Take a snapshot of the dialog
+ await expect(dialog).toHaveScreenshot("message-edit-history-dialog.png", {
+ mask: [page.locator(".mx_MessageTimestamp")],
+ });
+
+ {
+ const tile = dialog.locator("li:nth-child(2) .mx_EventTile");
+ await expect(tile.locator(".mx_EventTile_body")).toHaveText("Meassage");
+ // Click the "Remove" button again
+ await clickButtonRemove(tile);
+ }
+
+ // Do nothing and close the dialog to confirm that the message edit history dialog is rendered
+ await app.closeDialog();
+
+ {
+ // Assert that the message edit history dialog is rendered again after it was closed
+ const tile = dialog.locator("li:nth-child(2) .mx_EventTile");
+ await expect(tile.locator(".mx_EventTile_body")).toHaveText("Meassage");
+ // Click the "Remove" button again
+ await clickButtonRemove(tile);
+ }
+
+ // This time remove the message really
+ const textInputDialog = page.locator(".mx_TextInputDialog");
+ await textInputDialog.getByRole("textbox", { name: "Reason (optional)" }).fill("This is a test."); // Reason
+ await textInputDialog.getByRole("button", { name: "Remove" }).click();
+
+ // Assert that the message edit history dialog is rendered again
+ const messageEditHistoryDialog = page.locator(".mx_MessageEditHistoryDialog");
+ // Assert that the date is rendered
+ await expect(
+ messageEditHistoryDialog.getByRole("listitem").first().locator("h2", { hasText: "today" }),
+ ).toHaveCSS("text-transform", "capitalize");
+
+ // Assert that the original message is rendered under the date on the dialog
+ await expect(
+ messageEditHistoryDialog
+ .locator("li:nth-child(2) .mx_EventTile")
+ .locator(".mx_EventTile_content .mx_EventTile_body", { hasText: "Message" }),
+ ).toBeVisible();
+
+ // Assert that the edited message is gone
+ await expect(
+ messageEditHistoryDialog.locator(".mx_EventTile_content .mx_EventTile_body", { hasText: "Meassage" }),
+ ).not.toBeVisible();
+
+ await app.closeDialog();
+
+ // Assert that the redaction placeholder is rendered
+ await expect(
+ page
+ .locator(".mx_RoomView_MessageList")
+ .locator(".mx_EventTile_last .mx_RedactedBody", { hasText: "Message deleted" }),
+ ).toBeVisible();
+ });
+
+ test("should render 'View Source' button in developer mode on the message edit history dialog", async ({
+ page,
+ user,
+ app,
+ room,
+ }) => {
+ await page.goto(`#/room/${room.roomId}`);
+
+ // Send "Message"
+ await sendEvent(app, room.roomId);
+
+ // Edit "Message" to "Massage"
+ await editLastMessage(page, "Massage");
+
+ // Assert that the edit label is visible
+ await expect(page.locator(".mx_EventTile_edited")).toBeVisible();
+
+ await clickEditedMessage(page, "Massage");
+
+ {
+ const dialog = page.getByRole("dialog");
+ // Assert that the original message is rendered
+ const li = dialog.locator("li:nth-child(3)");
+ // Assert that "View Source" is not rendered
+ const eventLine = li.locator(".mx_EventTile_line");
+ await eventLine.hover();
+ await expect(eventLine.getByRole("button", { name: "View Source" })).not.toBeVisible();
+ }
+
+ await app.closeDialog();
+
+ // Enable developer mode
+ await app.setSettingValue("developerMode", null, SettingLevel.ACCOUNT, true);
+
+ await clickEditedMessage(page, "Massage");
+
+ {
+ const dialog = page.getByRole("dialog");
+ {
+ // Assert that the edited message is rendered
+ const li = dialog.locator("li:nth-child(2)");
+ // Assert that "Remove" button for the original message is rendered
+ const line = li.locator(".mx_EventTile_line");
+ await line.hover();
+ await expect(line.getByRole("button", { name: "Remove" })).toBeVisible();
+ await clickButtonViewSource(li);
+ }
+
+ // Assert that view source dialog is rendered and close the dialog
+ await app.closeDialog();
+
+ {
+ // Assert that the original message is rendered
+ const li = dialog.locator("li:nth-child(3)");
+ // Assert that "Remove" button for the original message does not exist
+ const line = li.locator(".mx_EventTile_line");
+ await line.hover();
+ await expect(line.getByRole("button", { name: "Remove" })).not.toBeVisible();
+
+ await clickButtonViewSource(li);
+ }
+
+ // Assert that view source dialog is rendered and close the dialog
+ await app.closeDialog();
+ }
+ });
+
+ test("should close the composer when clicking save after making a change and undoing it", async ({
+ page,
+ user,
+ app,
+ room,
+ axe,
+ checkA11y,
+ }) => {
+ axe.disableRules("color-contrast"); // XXX: We have some known contrast issues here
+ axe.exclude(".mx_Tooltip_visible"); // XXX: this is fine but would be good to fix
+
+ await page.goto(`#/room/${room.roomId}`);
+
+ await sendEvent(app, room.roomId);
+
+ {
+ // Edit message
+ const tile = page.locator(".mx_RoomView_body .mx_EventTile").last();
+ await expect(tile.getByText("Message", { exact: true })).toBeVisible();
+ const line = tile.locator(".mx_EventTile_line");
+ await line.hover();
+ await line.getByRole("button", { name: "Edit" }).click();
+ await checkA11y();
+ const editComposer = page.getByRole("textbox", { name: "Edit message" });
+ await editComposer.pressSequentially("Foo");
+ await editComposer.press("Backspace");
+ await editComposer.press("Backspace");
+ await editComposer.press("Backspace");
+ await editComposer.press("Enter");
+ await checkA11y();
+ }
+ await expect(
+ page.locator(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", { hasText: "Message" }),
+ ).toBeVisible();
+
+ // Assert that the edit composer has gone away
+ await expect(page.getByRole("textbox", { name: "Edit message" })).not.toBeVisible();
+ });
+});
diff --git a/playwright/element-web-test.ts b/playwright/element-web-test.ts
index 28956919b0..042af50a56 100644
--- a/playwright/element-web-test.ts
+++ b/playwright/element-web-test.ts
@@ -64,6 +64,7 @@ export const test = base.extend<
app: ElementAppPage;
mailhog?: { api: mailhog.API; instance: Instance };
crypto: Crypto;
+ room?: { roomId: string };
toasts: Toasts;
}
>({
diff --git a/playwright/global.d.ts b/playwright/global.d.ts
index 784c09cee4..8b4a280153 100644
--- a/playwright/global.d.ts
+++ b/playwright/global.d.ts
@@ -16,10 +16,15 @@ limitations under the License.
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type SettingLevel } from "../src/settings/SettingLevel";
+
declare global {
interface Window {
mxMatrixClientPeg: {
get(): MatrixClient;
};
+ mxSettingsStore: {
+ setValue(settingName: string, roomId: string | null, level: SettingLevel, value: any): Promise;
+ };
}
}
diff --git a/playwright/pages/ElementAppPage.ts b/playwright/pages/ElementAppPage.ts
index a984ba0d00..359c0a54b8 100644
--- a/playwright/pages/ElementAppPage.ts
+++ b/playwright/pages/ElementAppPage.ts
@@ -15,11 +15,42 @@ limitations under the License.
*/
import { type Locator, type Page } from "@playwright/test";
-import { type ICreateRoomOpts } from "matrix-js-sdk/src/matrix";
+
+import type { IContent, ICreateRoomOpts, ISendEventResponse } from "matrix-js-sdk/src/matrix";
+import type { SettingLevel } from "../../src/settings/SettingLevel";
export class ElementAppPage {
public constructor(private readonly page: Page) {}
+ /**
+ * Sets the value for a setting. The room ID is optional if the
+ * setting is not being set for a particular room, otherwise it
+ * should be supplied. The value may be null to indicate that the
+ * level should no longer have an override.
+ * @param {string} settingName The name of the setting to change.
+ * @param {String} roomId The room ID to change the value in, may be
+ * null.
+ * @param {SettingLevel} level The level to change the value at.
+ * @param {*} value The new value of the setting, may be null.
+ * @return {Promise} Resolves when the setting has been changed.
+ */
+ public async setSettingValue(settingName: string, roomId: string, level: SettingLevel, value: any): Promise {
+ return this.page.evaluate<
+ Promise,
+ {
+ settingName: string;
+ roomId: string | null;
+ level: SettingLevel;
+ value: any;
+ }
+ >(
+ ({ settingName, roomId, level, value }) => {
+ return window.mxSettingsStore.setValue(settingName, roomId, level, value);
+ },
+ { settingName, roomId, level, value },
+ );
+ }
+
/**
* Open the top left user menu, returning a Locator to the resulting context menu.
*/
@@ -100,4 +131,32 @@ export class ElementAppPage {
await composer.getByRole("button", { name: "More options", exact: true }).click();
return this.page.getByRole("menu");
}
+
+ /**
+ * @param {string} roomId
+ * @param {string} threadId
+ * @param {string} eventType
+ * @param {Object} content
+ */
+ public async sendEvent(
+ roomId: string,
+ threadId: string | null,
+ eventType: string,
+ content: IContent,
+ ): Promise {
+ return this.page.evaluate<
+ Promise,
+ {
+ roomId: string;
+ threadId: string | null;
+ eventType: string;
+ content: IContent;
+ }
+ >(
+ async ({ roomId, threadId, eventType, content }) => {
+ return window.mxMatrixClientPeg.get().sendEvent(roomId, threadId, eventType, content);
+ },
+ { roomId, threadId, eventType, content },
+ );
+ }
}
diff --git a/playwright/snapshots/editing/editing.spec.ts/message-edit-history-dialog-linux.png b/playwright/snapshots/editing/editing.spec.ts/message-edit-history-dialog-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..0135b0a66e8bbe7735a0aca278bf5a780d13b2ea
GIT binary patch
literal 8442
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
Nz!cQv^Pii&{~v}^LKOf2
literal 0
HcmV?d00001