Stabilize playwright tests by making the left panel constant width (#33451)
* Move css injection to panel.ts - Inject css on event * Upgrade package * Fix playwright tests * Remove RR instead of masking it * Fix wrong import * Fix playwright tests * Fix more playwright tests * Catch error and add comment
@@ -20,6 +20,7 @@ test.describe("Composer", () => {
|
|||||||
botCreateOpts: {
|
botCreateOpts: {
|
||||||
displayName: "Bob",
|
displayName: "Bob",
|
||||||
},
|
},
|
||||||
|
lockLeftPanelWidth: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
test.use({
|
test.use({
|
||||||
@@ -185,6 +186,7 @@ test.describe("Composer", () => {
|
|||||||
await app.viewRoomByName("Bob");
|
await app.viewRoomByName("Bob");
|
||||||
|
|
||||||
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||||
|
await composer.click();
|
||||||
await composer.pressSequentially("@bob");
|
await composer.pressSequentially("@bob");
|
||||||
|
|
||||||
// Note that we include the user ID here as the room tile is also an 'option' role
|
// Note that we include the user ID here as the room tile is also an 'option' role
|
||||||
|
|||||||
@@ -216,10 +216,16 @@ test.describe("Composer", () => {
|
|||||||
await expect(page.locator(".mx_ImageBody")).toBeVisible();
|
await expect(page.locator(".mx_ImageBody")).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("renders in narrow viewports", { tag: "@screenshot" }, async ({ page, bot, app }) => {
|
test.describe(() => {
|
||||||
// Shrink the viewport
|
test.use({
|
||||||
await page.setViewportSize({ width: 750, height: 1080 });
|
lockLeftPanelWidth: false,
|
||||||
await expect(page.locator(".mx_MessageComposer_wrapper")).toMatchScreenshot("narrow.png");
|
});
|
||||||
|
|
||||||
|
test("renders in narrow viewports", { tag: "@screenshot" }, async ({ page, bot, app }) => {
|
||||||
|
// Shrink the viewport
|
||||||
|
await page.setViewportSize({ width: 750, height: 1080 });
|
||||||
|
await expect(page.locator(".mx_MessageComposer_wrapper")).toMatchScreenshot("narrow.png");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe("when Control+Enter is required to send", () => {
|
test.describe("when Control+Enter is required to send", () => {
|
||||||
|
|||||||
@@ -61,13 +61,18 @@ test.describe("History sharing", function () {
|
|||||||
// Bob should now be able to decrypt the event
|
// Bob should now be able to decrypt the event
|
||||||
await expect(bobPage.getByText("A message from Alice")).toBeVisible();
|
await expect(bobPage.getByText("A message from Alice")).toBeVisible();
|
||||||
|
|
||||||
// Exclude message timestamps and RR avatars from the screenshot. Bob sometimes sees Alice's RR on the
|
// Mask message timestamps and exclude RR avatars from the screenshot. Bob sometimes sees Alice's RR on the
|
||||||
// previous event, which is surprising but not what we're testing here.
|
// previous event, which is surprising but not what we're testing here.
|
||||||
const mask = [bobPage.locator(".mx_MessageTimestamp"), bobPage.locator(".mx_ReadReceiptGroup_container")];
|
const mask = [bobPage.locator(".mx_MessageTimestamp")];
|
||||||
await expect(bobPage.locator(".mx_RoomView_timeline")).toMatchScreenshot(
|
await expect(bobPage.locator(".mx_RoomView_timeline")).toMatchScreenshot(
|
||||||
"shared-history-invite-accepted.png",
|
"shared-history-invite-accepted.png",
|
||||||
{
|
{
|
||||||
mask,
|
mask,
|
||||||
|
css: `
|
||||||
|
.mx_ReadReceiptGroup_container {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import type { Locator, Page } from "playwright-core";
|
|||||||
test.describe("Collapsible Room list", () => {
|
test.describe("Collapsible Room list", () => {
|
||||||
test.use({
|
test.use({
|
||||||
displayName: "Alice",
|
displayName: "Alice",
|
||||||
|
lockLeftPanelWidth: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
test.beforeEach(async ({ page, app, user }) => {
|
test.beforeEach(async ({ page, app, user }) => {
|
||||||
|
|||||||
@@ -36,9 +36,12 @@ test.describe("Room list panel", () => {
|
|||||||
await expect(roomListView).toMatchScreenshot("room-list-panel.png");
|
await expect(roomListView).toMatchScreenshot("room-list-panel.png");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should respond to small screen sizes", { tag: "@screenshot" }, async ({ page }) => {
|
test.describe("small screen", () => {
|
||||||
await page.setViewportSize({ width: 575, height: 600 });
|
test.use({ lockLeftPanelWidth: false });
|
||||||
const roomListPanel = getRoomListView(page);
|
test("should respond to small screen sizes", { tag: "@screenshot" }, async ({ page }) => {
|
||||||
await expect(roomListPanel).toMatchScreenshot("room-list-panel-smallscreen.png");
|
await page.setViewportSize({ width: 575, height: 600 });
|
||||||
|
const roomListPanel = getRoomListView(page);
|
||||||
|
await expect(roomListPanel).toMatchScreenshot("room-list-panel-smallscreen.png");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 168 KiB |
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* 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-core";
|
||||||
|
import { test as base } from "./services.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the width the panel by default.
|
||||||
|
* Rounding this number to a whole number would mean updating a whole
|
||||||
|
* bunch of screenshots.
|
||||||
|
*/
|
||||||
|
const LEFT_PANEL_WIDTH = "369.6875px";
|
||||||
|
|
||||||
|
export const test = base.extend<{
|
||||||
|
/**
|
||||||
|
* Whether the left panel should have its width fixed.
|
||||||
|
* This is done because the library that we use for rendering collapsible
|
||||||
|
* panels uses math to calculate the width which can sometimes leads to +/-1px
|
||||||
|
* difference. While this does not matter to the user, it can lead to screenshot
|
||||||
|
* tests failing.
|
||||||
|
* Defaults to true, should be set to false via {@link base.use} when you want to test the collapse
|
||||||
|
* behaviour.
|
||||||
|
*/
|
||||||
|
lockLeftPanelWidth: boolean;
|
||||||
|
}>({
|
||||||
|
lockLeftPanelWidth: true,
|
||||||
|
page: async ({ lockLeftPanelWidth, page }, use) => {
|
||||||
|
const listener = async (page: Page) => {
|
||||||
|
try {
|
||||||
|
await page.addStyleTag({
|
||||||
|
content: `
|
||||||
|
#left-panel {
|
||||||
|
flex: 0 0 ${LEFT_PANEL_WIDTH} !important;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to add style tag to stabilize left panel", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (lockLeftPanelWidth) page.on("load", listener);
|
||||||
|
await use(page);
|
||||||
|
if (lockLeftPanelWidth) page.off("load", listener);
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -12,7 +12,7 @@ import { sample, uniqueId } from "lodash-es";
|
|||||||
// We want to avoid using `mergeTests` in index.ts because it drops useful type
|
// We want to avoid using `mergeTests` in index.ts because it drops useful type
|
||||||
// information about the fixtures. Instead, we add `services` into our fixture
|
// information about the fixtures. Instead, we add `services` into our fixture
|
||||||
// suite by using its `test` as a base, so that there is a linear hierarchy.
|
// suite by using its `test` as a base, so that there is a linear hierarchy.
|
||||||
import { test as base } from "./services.js";
|
import { test as base } from "./panel.js";
|
||||||
import { type Credentials } from "../utils/api.js";
|
import { type Credentials } from "../utils/api.js";
|
||||||
|
|
||||||
/** Adds an initScript to the given page which will populate localStorage appropriately so that Element will use the given credentials. */
|
/** Adds an initScript to the given page which will populate localStorage appropriately so that Element will use the given credentials. */
|
||||||
|
|||||||
@@ -84,18 +84,8 @@ export const test = base.extend<TestFixtures>({
|
|||||||
config: async ({}, use) => use({}),
|
config: async ({}, use) => use({}),
|
||||||
labsFlags: async ({}, use) => use([]),
|
labsFlags: async ({}, use) => use([]),
|
||||||
disablePresence: async ({}, use) => use(false),
|
disablePresence: async ({}, use) => use(false),
|
||||||
lockLeftPanelWidth: true,
|
page: async ({ homeserver, context, page, config, labsFlags, disablePresence }, use) => {
|
||||||
page: async ({ homeserver, context, page, config, labsFlags, disablePresence, lockLeftPanelWidth }, use) => {
|
|
||||||
await routeConfigJson(context, homeserver.baseUrl, config, labsFlags, disablePresence);
|
await routeConfigJson(context, homeserver.baseUrl, config, labsFlags, disablePresence);
|
||||||
if (lockLeftPanelWidth) {
|
|
||||||
await page.addStyleTag({
|
|
||||||
content: `
|
|
||||||
#left-panel {
|
|
||||||
flex: 0 0 369.6875px !important;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await use(page);
|
await use(page);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
"matrix-web-i18n": "catalog:",
|
"matrix-web-i18n": "catalog:",
|
||||||
"react-blurhash": "^0.3.0",
|
"react-blurhash": "^0.3.0",
|
||||||
"react-merge-refs": "^3.0.2",
|
"react-merge-refs": "^3.0.2",
|
||||||
"react-resizable-panels": "4.7.2",
|
"react-resizable-panels": "4.9.0",
|
||||||
"react-virtuoso": "^4.14.0",
|
"react-virtuoso": "^4.14.0",
|
||||||
"temporal-polyfill": "^0.3.0"
|
"temporal-polyfill": "^0.3.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1080,8 +1080,8 @@ importers:
|
|||||||
specifier: ^3.0.2
|
specifier: ^3.0.2
|
||||||
version: 3.0.2(react@19.2.6)
|
version: 3.0.2(react@19.2.6)
|
||||||
react-resizable-panels:
|
react-resizable-panels:
|
||||||
specifier: 4.7.2
|
specifier: 4.9.0
|
||||||
version: 4.7.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
version: 4.9.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
||||||
react-virtuoso:
|
react-virtuoso:
|
||||||
specifier: ^4.14.0
|
specifier: ^4.14.0
|
||||||
version: 4.18.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
version: 4.18.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
||||||
@@ -11498,8 +11498,8 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
react-resizable-panels@4.7.2:
|
react-resizable-panels@4.9.0:
|
||||||
resolution: {integrity: sha512-1L2vyeBG96hp7N6x6rzYXJ8EjYiDiffMsqj3cd+T9aOKwscvuyCn2CuZ5q3PoUSTIJUM6Q5DgXH1bdDe6uvh2w==}
|
resolution: {integrity: sha512-sEl+hA6y9/kxa0aPlrUC+G1lcShAf/PiIjoeC8kWXxa53RfAVplVCIxEl01Nwa4L2iRa5JXBXq1/mI8ch6qOZQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^18.0.0 || ^19.0.0
|
react: ^18.0.0 || ^19.0.0
|
||||||
react-dom: ^18.0.0 || ^19.0.0
|
react-dom: ^18.0.0 || ^19.0.0
|
||||||
@@ -25455,7 +25455,7 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.14
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
react-resizable-panels@4.7.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
|
react-resizable-panels@4.9.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.2.6
|
react: 19.2.6
|
||||||
react-dom: 19.2.6(react@19.2.6)
|
react-dom: 19.2.6(react@19.2.6)
|
||||||
|
|||||||