Tag screenshot tests to speed up test:playwright:screenshot (#28623)

* Tag screenshot tests to speed up test:playwright:screenshot

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add more tags

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2024-12-04 09:12:43 +00:00 committed by GitHub
parent d0e19d3e03
commit d0d0b8212d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 1441 additions and 1301 deletions

View file

@ -64,7 +64,7 @@
"test:playwright:open": "yarn test:playwright --ui",
"test:playwright:screenshots": "yarn test:playwright:screenshots:build && yarn test:playwright:screenshots:run",
"test:playwright:screenshots:build": "docker build playwright -t element-web-playwright",
"test:playwright:screenshots:run": "docker run --rm --network host -e BASE_URL -e CI -v $(pwd):/work/ -v $(node -e 'console.log(require(`path`).dirname(require.resolve(`matrix-js-sdk/package.json`)))'):/work/node_modules/matrix-js-sdk -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/:/tmp/ -it element-web-playwright",
"test:playwright:screenshots:run": "docker run --rm --network host -e BASE_URL -e CI -v $(pwd):/work/ -v $(node -e 'console.log(require(`path`).dirname(require.resolve(`matrix-js-sdk/package.json`)))'):/work/node_modules/matrix-js-sdk -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/:/tmp/ -it element-web-playwright --grep @screenshot",
"coverage": "yarn test --coverage",
"analyse:unused-exports": "ts-node ./scripts/analyse_unused_exports.ts",
"analyse:webpack-bundles": "webpack-bundle-analyzer webpack-stats.json webapp",

View file

@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import { test, expect } from "../../element-web-test";
test(`shows error page if browser lacks Intl support`, async ({ page }) => {
test(`shows error page if browser lacks Intl support`, { tag: "@screenshot" }, async ({ page }) => {
await page.addInitScript({ content: `delete window.Intl;` });
await page.goto("/");
@ -21,7 +21,7 @@ test(`shows error page if browser lacks Intl support`, async ({ page }) => {
await expect(page).toMatchScreenshot("unsupported-browser.png");
});
test(`shows error page if browser lacks WebAssembly support`, async ({ page }) => {
test(`shows error page if browser lacks WebAssembly support`, { tag: "@screenshot" }, async ({ page }) => {
await page.addInitScript({ content: `delete window.WebAssembly;` });
await page.goto("/");

View file

@ -134,18 +134,22 @@ test.describe("Audio player", () => {
).toBeVisible();
});
test("should be correctly rendered - light theme", async ({ page, app }) => {
test("should be correctly rendered - light theme", { tag: "@screenshot" }, async ({ page, app }) => {
await uploadFile(page, "playwright/sample-files/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 }) => {
test(
"should be correctly rendered - light theme with monospace font",
{ tag: "@screenshot" },
async ({ page, app }) => {
await uploadFile(page, "playwright/sample-files/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 }) => {
test("should be correctly rendered - high contrast theme", { tag: "@screenshot" }, 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);
@ -161,7 +165,7 @@ test.describe("Audio player", () => {
await takeSnapshots(page, app, "Selected EventTile of audio player (high contrast)");
});
test("should be correctly rendered - dark theme", async ({ page, app }) => {
test("should be correctly rendered - dark theme", { tag: "@screenshot" }, async ({ page, app }) => {
// Enable dark theme
await app.settings.setValue("theme", null, SettingLevel.ACCOUNT, "dark");
@ -207,7 +211,10 @@ test.describe("Audio player", () => {
expect(download.suggestedFilename()).toBe("1sec.ogg");
});
test("should support replying to audio file with another audio file", async ({ page, app }) => {
test(
"should support replying to audio file with another audio file",
{ tag: "@screenshot" },
async ({ page, app }) => {
await uploadFile(page, "playwright/sample-files/1sec.ogg");
// Assert the audio player is rendered
@ -230,9 +237,13 @@ test.describe("Audio player", () => {
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 }) => {
test(
"should support creating a reply chain with multiple audio files",
{ tag: "@screenshot" },
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
@ -293,7 +304,8 @@ test.describe("Audio player", () => {
// 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, "playwright/sample-files/1sec-long-name-audio-file.ogg");

View file

@ -89,7 +89,10 @@ test.describe("HTML Export", () => {
},
});
test("should export html successfully and match screenshot", async ({ page, app, room }) => {
test(
"should export html successfully and match screenshot",
{ tag: "@screenshot" },
async ({ page, app, room }) => {
// Set a fixed time rather than masking off the line with the time in it: we don't need to worry
// about the width changing and we can actually test this line looks correct.
page.clock.setSystemTime(new Date("2024-01-01T00:00:00Z"));
@ -127,5 +130,6 @@ test.describe("HTML Export", () => {
page.locator(".mx_MessageTimestamp"),
],
});
});
},
);
});

View file

@ -204,12 +204,10 @@ test.describe("Cryptography", function () {
await expect(page.locator(".mx_Dialog")).toHaveCount(1);
});
test("creating a DM should work, being e2e-encrypted / user verification", async ({
page,
app,
bot: bob,
user: aliceCredentials,
}) => {
test(
"creating a DM should work, being e2e-encrypted / user verification",
{ tag: "@screenshot" },
async ({ page, app, bot: bob, user: aliceCredentials }) => {
await app.client.bootstrapCrossSigning(aliceCredentials);
await startDMWithBob(page, bob);
// send first message
@ -227,7 +225,8 @@ test.describe("Cryptography", function () {
// Take a snapshot of RoomSummaryCard with a verified E2EE icon
await expect(page.locator(".mx_RightPanel")).toMatchScreenshot("RoomSummaryCard-with-verified-e2ee.png");
});
},
);
test("should allow verification when there is no existing DM", async ({
page,

View file

@ -66,7 +66,10 @@ test.describe("Editing", () => {
botCreateOpts: { displayName: "Bob" },
});
test("should render and interact with the message edit history dialog", async ({ page, user, app, room }) => {
test(
"should render and interact with the message edit history dialog",
{ tag: "@screenshot" },
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");
@ -185,7 +188,8 @@ test.describe("Editing", () => {
.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,

View file

@ -25,7 +25,7 @@ test.describe("Image Upload", () => {
).toBeVisible();
});
test("should show image preview when uploading an image", async ({ page, app }) => {
test("should show image preview when uploading an image", { tag: "@screenshot" }, async ({ page, app }) => {
await page
.locator(".mx_MessageComposer_actions input[type='file']")
.setInputFiles("playwright/sample-files/riot.png");

View file

@ -26,7 +26,7 @@ test.describe("Forgot Password", () => {
}),
});
test("renders properly", async ({ page, homeserver }) => {
test("renders properly", { tag: "@screenshot" }, async ({ page, homeserver }) => {
await page.goto("/");
await page.getByRole("link", { name: "Sign in" }).click();
@ -39,7 +39,7 @@ test.describe("Forgot Password", () => {
await expect(page.getByRole("main")).toMatchScreenshot("forgot-password.png");
});
test("renders email verification dialog properly", async ({ page, homeserver }) => {
test("renders email verification dialog properly", { tag: "@screenshot" }, async ({ page, homeserver }) => {
const user = await homeserver.registerUser(username, password);
await homeserver.setThreepid(user.userId, "email", email);

View file

@ -19,7 +19,7 @@ test.describe("Invite dialog", function () {
const botName = "BotAlice";
test("should support inviting a user to a room", async ({ page, app, user, bot }) => {
test("should support inviting a user to a room", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
// Create and view a room
await app.client.createRoom({ name: "Test Room" });
await app.viewRoomByName("Test Room");
@ -73,12 +73,17 @@ test.describe("Invite dialog", function () {
await expect(page.getByText(`${botName} joined the room`)).toBeVisible();
});
test("should support inviting a user to Direct Messages", async ({ page, app, user, bot }) => {
test(
"should support inviting a user to Direct Messages",
{ tag: "@screenshot" },
async ({ page, app, user, bot }) => {
await page.locator(".mx_RoomList").getByRole("button", { name: "Start chat" }).click();
const other = page.locator(".mx_InviteDialog_other");
// Assert that the header is rendered
await expect(other.locator(".mx_Dialog_header .mx_Dialog_title").getByText("Direct Messages")).toBeVisible();
await expect(
other.locator(".mx_Dialog_header .mx_Dialog_title").getByText("Direct Messages"),
).toBeVisible();
// Assert that the bar is rendered
await expect(other.locator(".mx_InviteDialog_addressBar")).toBeVisible();
@ -88,7 +93,9 @@ test.describe("Invite dialog", function () {
await other.getByTestId("invite-dialog-input").fill(bot.credentials.userId);
await expect(other.locator(".mx_InviteDialog_tile_nameStack").getByText(bot.credentials.userId)).toBeVisible();
await expect(
other.locator(".mx_InviteDialog_tile_nameStack").getByText(bot.credentials.userId),
).toBeVisible();
await other.locator(".mx_InviteDialog_tile_nameStack").getByText(botName).click();
await expect(
@ -108,7 +115,10 @@ test.describe("Invite dialog", function () {
// TODO: implement the test on room-header.spec.ts
const roomHeader = page.locator(".mx_RoomHeader");
await roomHeader.locator(".mx_RoomHeader_heading").hover();
await expect(roomHeader.locator(".mx_RoomHeader_heading")).toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
await expect(roomHeader.locator(".mx_RoomHeader_heading")).toHaveCSS(
"background-color",
"rgba(0, 0, 0, 0)",
);
// Send a message to invite the bots
const composer = app.getComposer().locator("[contenteditable]");
@ -120,5 +130,6 @@ test.describe("Invite dialog", function () {
// Assert that the message is displayed at the bottom
await expect(page.locator(".mx_EventTile_last").getByText("Hello")).toBeVisible();
});
},
);
});

View file

@ -63,7 +63,7 @@ test.describe("Message rendering", () => {
{ direction: "ltr", displayName: "Quentin" },
{ direction: "rtl", displayName: "كوينتين" },
].forEach(({ direction, displayName }) => {
test.describe(`with ${direction} display name`, () => {
test.describe(`with ${direction} display name`, { tag: "@screenshot" }, () => {
test.use({
displayName,
room: async ({ user, app }, use) => {
@ -72,14 +72,18 @@ test.describe("Message rendering", () => {
},
});
test("should render a basic LTR text message", async ({ page, user, app, room }) => {
test(
"should render a basic LTR text message",
{ tag: "@screenshot" },
async ({ page, user, app, room }) => {
await page.goto(`#/room/${room.roomId}`);
const msgTile = await sendMessage(page, "Hello, world!");
await expect(msgTile).toMatchScreenshot(`basic-message-ltr-${direction}displayname.png`, {
mask: [page.locator(".mx_MessageTimestamp")],
});
});
},
);
test("should render an LTR emote", async ({ page, user, app, room }) => {
await page.goto(`#/room/${room.roomId}`);

View file

@ -24,7 +24,7 @@ test.describe("permalinks", () => {
displayName: "Alice",
});
test("shoud render permalinks as expected", async ({ page, app, user, homeserver }) => {
test("shoud render permalinks as expected", { tag: "@screenshot" }, async ({ page, app, user, homeserver }) => {
const bob = new Bot(page, homeserver, { displayName: "Bob" });
const charlotte = new Bot(page, homeserver, { displayName: "Charlotte" });
await bob.prepareClient();

View file

@ -10,20 +10,22 @@ import { test } from "./index";
import { expect } from "../../element-web-test";
test.describe("Pinned messages", () => {
test("should show the empty state when there are no pinned messages", async ({ page, app, room1, util }) => {
test(
"should show the empty state when there are no pinned messages",
{ tag: "@screenshot" },
async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.openRoomInfo();
await util.assertPinnedCountInRoomInfo(0);
await util.openPinnedMessagesList();
await util.assertEmptyPinnedMessagesList();
});
},
);
test("should pin one message and to have the pinned message badge in the timeline", async ({
page,
app,
room1,
util,
}) => {
test(
"should pin one message and to have the pinned message badge in the timeline",
{ tag: "@screenshot" },
async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.receiveMessages(room1, ["Msg1"]);
await util.pinMessages(["Msg1"]);
@ -38,7 +40,8 @@ test.describe("Pinned messages", () => {
}
`,
});
});
},
);
test("should pin messages and show them in the room info panel", async ({ page, app, room1, util }) => {
await util.goTo(room1);
@ -73,7 +76,7 @@ test.describe("Pinned messages", () => {
await util.assertPinnedCountInRoomInfo(2);
});
test("should unpin all messages", async ({ page, app, room1, util }) => {
test("should unpin all messages", { tag: "@screenshot" }, async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.receiveMessages(room1, ["Msg1", "Msg2", "Msg3", "Msg4"]);
await util.pinMessages(["Msg1", "Msg2", "Msg4"]);
@ -98,7 +101,7 @@ test.describe("Pinned messages", () => {
await util.assertPinnedCountInRoomInfo(0);
});
test("should display one message in the banner", async ({ page, app, room1, util }) => {
test("should display one message in the banner", { tag: "@screenshot" }, async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.receiveMessages(room1, ["Msg1"]);
await util.pinMessages(["Msg1"]);
@ -106,7 +109,7 @@ test.describe("Pinned messages", () => {
await expect(util.getBanner()).toMatchScreenshot("pinned-message-banner-1-Msg1.png");
});
test("should display 2 messages in the banner", async ({ page, app, room1, util }) => {
test("should display 2 messages in the banner", { tag: "@screenshot" }, async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.receiveMessages(room1, ["Msg1", "Msg2"]);
await util.pinMessages(["Msg1", "Msg2"]);
@ -123,7 +126,7 @@ test.describe("Pinned messages", () => {
await expect(util.getBanner()).toMatchScreenshot("pinned-message-banner-2-Msg2.png");
});
test("should display 4 messages in the banner", async ({ page, app, room1, util }) => {
test("should display 4 messages in the banner", { tag: "@screenshot" }, async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.receiveMessages(room1, ["Msg1", "Msg2", "Msg3", "Msg4"]);
await util.pinMessages(["Msg1", "Msg2", "Msg3", "Msg4"]);

View file

@ -93,7 +93,7 @@ test.describe("Polls", () => {
});
});
test("should be creatable and votable", async ({ page, app, bot, user }) => {
test("should be creatable and votable", { tag: "@screenshot" }, async ({ page, app, bot, user }) => {
const roomId: string = await app.client.createRoom({});
await app.client.inviteUser(roomId, bot.credentials.userId);
await page.goto("/#/room/" + roomId);
@ -219,7 +219,10 @@ test.describe("Polls", () => {
await expect(page.locator(".mx_ErrorDialog")).toBeAttached();
});
test("should be displayed correctly in thread panel", async ({ page, app, user, bot, homeserver }) => {
test(
"should be displayed correctly in thread panel",
{ tag: "@screenshot" },
async ({ page, app, user, bot, homeserver }) => {
const botCharlie = new Bot(page, homeserver, { displayName: "BotCharlie" });
await botCharlie.prepareClient();
@ -229,7 +232,9 @@ test.describe("Polls", () => {
await page.goto("/#/room/" + roomId);
// wait until the bots joined
await expect(page.getByText("BotBob and one other were invited and joined")).toBeAttached({ timeout: 10000 });
await expect(page.getByText("BotBob and one other were invited and joined")).toBeAttached({
timeout: 10000,
});
const locator = await app.openMessageComposerOptions();
await locator.getByRole("menuitem", { name: "Poll" }).click();
@ -274,22 +279,30 @@ test.describe("Polls", () => {
// and thread view
await expect(
page.locator(".mx_ThreadView .mx_MPollBody_totalVotes").getByText("2 votes cast. Vote to see the results"),
page
.locator(".mx_ThreadView .mx_MPollBody_totalVotes")
.getByText("2 votes cast. Vote to see the results"),
).toBeAttached();
// Take snapshots of poll on ThreadView
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Bubble);
await expect(page.locator(".mx_ThreadView .mx_EventTile[data-layout='bubble']").first()).toBeVisible();
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot("ThreadView_with_a_poll_on_bubble_layout.png", {
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
"ThreadView_with_a_poll_on_bubble_layout.png",
{
mask: [page.locator(".mx_MessageTimestamp")],
});
},
);
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Group);
await expect(page.locator(".mx_ThreadView .mx_EventTile[data-layout='group']").first()).toBeVisible();
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot("ThreadView_with_a_poll_on_group_layout.png", {
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
"ThreadView_with_a_poll_on_group_layout.png",
{
mask: [page.locator(".mx_MessageTimestamp")],
});
},
);
const roomViewLocator = page.locator(".mx_RoomView_body");
// vote 'Maybe' in the main timeline poll
@ -321,5 +334,6 @@ test.describe("Polls", () => {
// and in thread view tile
await expectVoteCounts(page.locator(".mx_ThreadView"));
});
},
);
});

View file

@ -38,12 +38,10 @@ test.describe("Email Registration", async () => {
await page.goto("/#/register");
});
test("registers an account and lands on the use case selection screen", async ({
page,
mailhog,
request,
checkA11y,
}) => {
test(
"registers an account and lands on the use case selection screen",
{ tag: "@screenshot" },
async ({ page, mailhog, request, checkA11y }) => {
await expect(page.getByRole("textbox", { name: "Username" })).toBeVisible();
// Hide the server text as it contains the randomly allocated Homeserver port
const screenshotOptions = { mask: [page.locator(".mx_ServerPicker_server")] };
@ -67,5 +65,6 @@ test.describe("Email Registration", async () => {
await request.get(emailLink); // "Click" the link in the email
await expect(page.locator(".mx_UseCaseSelection_skip")).toBeVisible();
});
},
);
});

View file

@ -15,7 +15,10 @@ test.describe("Registration", () => {
await page.goto("/#/register");
});
test("registers an account and lands on the home screen", async ({ homeserver, page, checkA11y, crypto }) => {
test(
"registers an account and lands on the home screen",
{ tag: "@screenshot" },
async ({ homeserver, page, checkA11y, crypto }) => {
await page.getByRole("button", { name: "Edit", exact: true }).click();
await expect(page.getByRole("button", { name: "Continue", exact: true })).toBeVisible();
@ -29,7 +32,10 @@ test.describe("Registration", () => {
await expect(page.getByRole("textbox", { name: "Username", exact: true })).toBeVisible();
// Hide the server text as it contains the randomly allocated Homeserver port
const screenshotOptions = { mask: [page.locator(".mx_ServerPicker_server")], includeDialogBackground: true };
const screenshotOptions = {
mask: [page.locator(".mx_ServerPicker_server")],
includeDialogBackground: true,
};
await expect(page).toMatchScreenshot("registration.png", screenshotOptions);
await checkA11y();
@ -68,13 +74,14 @@ test.describe("Registration", () => {
await page.getByRole("button", { name: "User menu", exact: true }).click();
await page.getByRole("menuitem", { name: "All settings", exact: true }).click();
await page.getByRole("tab", { name: "Sessions", exact: true }).click();
await expect(page.getByTestId("current-session-section").getByTestId("device-metadata-isVerified")).toHaveText(
"Verified",
);
await expect(
page.getByTestId("current-session-section").getByTestId("device-metadata-isVerified"),
).toHaveText("Verified");
// check that cross-signing keys have been uploaded.
await crypto.assertDeviceIsCrossSigned();
});
},
);
test("should require username to fulfil requirements and be available", async ({ homeserver, page }) => {
await page.getByRole("button", { name: "Edit", exact: true }).click();

View file

@ -18,7 +18,7 @@ test.describe("Release announcement", () => {
labsFlags: ["threadsActivityCentre"],
});
test("should display the release announcement process", async ({ page, app, util }) => {
test("should display the release announcement process", { tag: "@screenshot" }, async ({ page, app, util }) => {
// The TAC release announcement should be displayed
await util.assertReleaseAnnouncementIsVisible("Threads Activity Centre");
// Hide the release announcement

View file

@ -40,7 +40,7 @@ test.describe("FilePanel", () => {
});
test.describe("render", () => {
test("should render empty state", async ({ page }) => {
test("should render empty state", { tag: "@screenshot" }, async ({ page }) => {
// Wait until the information about the empty state is rendered
await expect(page.locator(".mx_EmptyState")).toBeVisible();
@ -48,7 +48,7 @@ test.describe("FilePanel", () => {
await expect(page.locator(".mx_RightPanel")).toMatchScreenshot("empty.png");
});
test("should list tiles on the panel", async ({ page }) => {
test("should list tiles on the panel", { tag: "@screenshot" }, async ({ page }) => {
// Upload multiple files
await uploadFile(page, "playwright/sample-files/riot.png"); // Image
await uploadFile(page, "playwright/sample-files/1sec.ogg"); // Audio

View file

@ -21,7 +21,7 @@ test.describe("NotificationPanel", () => {
await app.client.createRoom({ name: ROOM_NAME });
});
test("should render empty state", async ({ page, app }) => {
test("should render empty state", { tag: "@screenshot" }, async ({ page, app }) => {
await app.viewRoomByName(ROOM_NAME);
await page.getByRole("button", { name: "Notifications" }).click();

View file

@ -38,7 +38,7 @@ test.describe("RightPanel", () => {
});
test.describe("in rooms", () => {
test("should handle long room address and long room name", async ({ page, app }) => {
test("should handle long room address and long room name", { tag: "@screenshot" }, async ({ page, app }) => {
await app.client.createRoom({ name: ROOM_NAME_LONG });
await viewRoomSummaryByName(page, app, ROOM_NAME_LONG);

View file

@ -47,7 +47,10 @@ test.describe("Room Directory", () => {
expect(resp.chunk[0].room_id).toEqual(roomId);
});
test("should allow finding published rooms in directory", async ({ page, app, user, bot }) => {
test(
"should allow finding published rooms in directory",
{ tag: "@screenshot" },
async ({ page, app, user, bot }) => {
const name = "This is a public room";
await bot.createRoom({
visibility: "public" as Visibility,
@ -60,7 +63,9 @@ test.describe("Room Directory", () => {
const dialog = page.locator(".mx_SpotlightDialog");
await dialog.getByRole("textbox", { name: "Search" }).fill("Unknown Room");
await expect(
dialog.getByText("If you can't find the room you're looking for, ask for an invite or create a new room."),
dialog.getByText(
"If you can't find the room you're looking for, ask for an invite or create a new room.",
),
).toHaveClass("mx_SpotlightDialog_otherSearches_messageSearchText");
await expect(page.locator(".mx_Dialog")).toMatchScreenshot("filtered-no-results.png");
@ -76,5 +81,6 @@ test.describe("Room Directory", () => {
.click();
await expect(page).toHaveURL("/#/room/#test1234:localhost");
});
},
);
});

View file

@ -20,7 +20,7 @@ test.describe("Room Header", () => {
test.use({
labsFlags: ["feature_notifications"],
});
test("should render default buttons properly", async ({ page, app, user }) => {
test("should render default buttons properly", { tag: "@screenshot" }, async ({ page, app, user }) => {
await app.client.createRoom({ name: "Test Room" });
await app.viewRoomByName("Test Room");
@ -51,7 +51,10 @@ test.describe("Room Header", () => {
await expect(header).toMatchScreenshot("room-header.png");
});
test("should render a very long room name without collapsing the buttons", async ({ page, app, user }) => {
test(
"should render a very long room name without collapsing the buttons",
{ tag: "@screenshot" },
async ({ page, app, user }) => {
const LONG_ROOM_NAME =
"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore " +
"et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut " +
@ -78,7 +81,8 @@ test.describe("Room Header", () => {
}
await expect(header).toMatchScreenshot("room-header-long-name.png");
});
},
);
});
test.describe("with a video room", () => {
@ -99,7 +103,10 @@ test.describe("Room Header", () => {
test.describe("and with feature_notifications enabled", () => {
test.use({ labsFlags: ["feature_video_rooms", "feature_notifications"] });
test("should render buttons for chat, room info, threads and facepile", async ({ page, app, user }) => {
test(
"should render buttons for chat, room info, threads and facepile",
{ tag: "@screenshot" },
async ({ page, app, user }) => {
await createVideoRoom(page, app);
const header = page.locator(".mx_RoomHeader");
@ -122,7 +129,8 @@ test.describe("Room Header", () => {
await expect(header.getByRole("button")).toHaveCount(7);
await expect(header).toMatchScreenshot("room-header-video-room.png");
});
},
);
});
test("should render a working chat button which opens the timeline on a right panel", async ({

View file

@ -23,7 +23,7 @@ test.describe("Account user settings tab", () => {
},
});
test("should be rendered properly", async ({ uut, user }) => {
test("should be rendered properly", { tag: "@screenshot" }, async ({ uut, user }) => {
await expect(uut).toMatchScreenshot("account.png");
// Assert that the top heading is rendered
@ -71,7 +71,7 @@ test.describe("Account user settings tab", () => {
);
});
test("should respond to small screen sizes", async ({ page, uut }) => {
test("should respond to small screen sizes", { tag: "@screenshot" }, async ({ page, uut }) => {
await page.setViewportSize({ width: 700, height: 600 });
await expect(uut).toMatchScreenshot("account-smallscreen.png");
});

View file

@ -13,7 +13,7 @@ test.describe("Appearance user settings tab", () => {
displayName: "Hanako",
});
test("should be rendered properly", async ({ page, user, app }) => {
test("should be rendered properly", { tag: "@screenshot" }, async ({ page, user, app }) => {
const tab = await app.settings.openUserSettings("Appearance");
// Click "Show advanced" link button
@ -25,7 +25,10 @@ test.describe("Appearance user settings tab", () => {
await expect(tab).toMatchScreenshot("appearance-tab.png");
});
test("should support changing font size by using the font size dropdown", async ({ page, app, user }) => {
test(
"should support changing font size by using the font size dropdown",
{ tag: "@screenshot" },
async ({ page, app, user }) => {
await app.settings.openUserSettings("Appearance");
const tab = page.getByTestId("mx_AppearanceUserSettingsTab");
@ -37,7 +40,8 @@ test.describe("Appearance user settings tab", () => {
await fontDropdown.getByLabel("Font size").selectOption({ value: "-4" });
await expect(page).toMatchScreenshot("window-12px.png", { includeDialogBackground: true });
});
},
);
test("should support enabling system font", async ({ page, app, user }) => {
await app.settings.openUserSettings("Appearance");

View file

@ -20,7 +20,10 @@ test.describe("Appearance user settings tab", () => {
await util.openAppearanceTab();
});
test("should change the message layout from modern to bubble", async ({ page, app, user, util }) => {
test(
"should change the message layout from modern to bubble",
{ tag: "@screenshot" },
async ({ page, app, user, util }) => {
await util.assertScreenshot(util.getMessageLayoutPanel(), "message-layout-panel-modern.png");
await util.getBubbleLayout().click();
@ -33,7 +36,8 @@ test.describe("Appearance user settings tab", () => {
// Assert that the room layout is set to bubble layout
await util.assertBubbleLayout();
await util.assertScreenshot(util.getMessageLayoutPanel(), "message-layout-panel-bubble.png");
});
},
);
test("should enable compact layout when the modern layout is selected", async ({ page, app, user, util }) => {
await expect(util.getCompactLayoutCheckbox()).not.toBeChecked();

View file

@ -20,7 +20,10 @@ test.describe("Appearance user settings tab", () => {
await util.openAppearanceTab();
});
test("should be rendered with the light theme selected", async ({ page, app, util }) => {
test(
"should be rendered with the light theme selected",
{ tag: "@screenshot" },
async ({ page, app, util }) => {
// Assert that 'Match system theme' is not checked
await expect(util.getMatchSystemThemeCheckbox()).not.toBeChecked();
@ -31,9 +34,13 @@ test.describe("Appearance user settings tab", () => {
await expect(util.getHighContrastTheme()).not.toBeChecked();
await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-light.png");
});
},
);
test("should disable the themes when the system theme is clicked", async ({ page, app, util }) => {
test(
"should disable the themes when the system theme is clicked",
{ tag: "@screenshot" },
async ({ page, app, util }) => {
await util.getMatchSystemThemeCheckbox().click();
// Assert that the themes are disabled
@ -42,9 +49,10 @@ test.describe("Appearance user settings tab", () => {
await expect(util.getHighContrastTheme()).toBeDisabled();
await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-match-system-enabled.png");
});
},
);
test("should change the theme to dark", async ({ page, app, util }) => {
test("should change the theme to dark", { tag: "@screenshot" }, async ({ page, app, util }) => {
// Assert that the light theme is selected
await expect(util.getLightTheme()).toBeChecked();
@ -63,11 +71,14 @@ test.describe("Appearance user settings tab", () => {
labsFlags: ["feature_custom_themes"],
});
test("should render the custom theme section", async ({ page, app, util }) => {
test("should render the custom theme section", { tag: "@screenshot" }, async ({ page, app, util }) => {
await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-custom-theme.png");
});
test("should be able to add and remove a custom theme", async ({ page, app, util }) => {
test(
"should be able to add and remove a custom theme",
{ tag: "@screenshot" },
async ({ page, app, util }) => {
await util.addCustomTheme();
await expect(util.getCustomTheme()).not.toBeChecked();
@ -75,7 +86,8 @@ test.describe("Appearance user settings tab", () => {
await util.removeCustomTheme();
await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-custom-theme-removed.png");
});
},
);
});
});
});

View file

@ -20,7 +20,7 @@ test.describe("General room settings tab", () => {
await app.viewRoomByName(roomName);
});
test("should be rendered properly", async ({ page, app }) => {
test("should be rendered properly", { tag: "@screenshot" }, async ({ page, app }) => {
const settings = await app.settings.openRoomSettings("General");
// Assert that "Show less" details element is rendered

View file

@ -23,7 +23,7 @@ test.describe("Preferences user settings tab", () => {
},
});
test("should be rendered properly", async ({ app, page, user }) => {
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user }) => {
page.setViewportSize({ width: 1024, height: 3300 });
const tab = await app.settings.openUserSettings("Preferences");
// Assert that the top heading is rendered

View file

@ -36,7 +36,7 @@ test.describe("Security user settings tab", () => {
});
test.describe("AnalyticsLearnMoreDialog", () => {
test("should be rendered properly", async ({ app, page }) => {
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page }) => {
const tab = await app.settings.openUserSettings("Security");
await tab.getByRole("button", { name: "Learn more" }).click();
await expect(page.locator(".mx_AnalyticsLearnMoreDialog_wrapper .mx_Dialog")).toMatchScreenshot(

View file

@ -16,7 +16,7 @@ test.describe("Share dialog", () => {
},
});
test("should share a room", async ({ page, app, room }) => {
test("should share a room", { tag: "@screenshot" }, async ({ page, app, room }) => {
await app.viewRoomById(room.roomId);
await app.toggleRoomInfoPanel();
await page.getByRole("menuitem", { name: "Copy link" }).click();
@ -29,7 +29,7 @@ test.describe("Share dialog", () => {
});
});
test("should share a room member", async ({ page, app, room, user }) => {
test("should share a room member", { tag: "@screenshot" }, async ({ page, app, room, user }) => {
await app.viewRoomById(room.roomId);
await app.client.sendMessage(room.roomId, { body: "hello", msgtype: "m.text" });
@ -46,7 +46,7 @@ test.describe("Share dialog", () => {
});
});
test("should share an event", async ({ page, app, room }) => {
test("should share an event", { tag: "@screenshot" }, async ({ page, app, room }) => {
await app.viewRoomById(room.roomId);
await app.client.sendMessage(room.roomId, { body: "hello", msgtype: "m.text" });

View file

@ -55,7 +55,7 @@ test.describe("Spaces", () => {
botCreateOpts: { displayName: "BotBob" },
});
test("should allow user to create public space", async ({ page, app, user }) => {
test("should allow user to create public space", { tag: "@screenshot" }, async ({ page, app, user }) => {
const contextMenu = await openSpaceCreateMenu(page);
await expect(contextMenu).toMatchScreenshot("space-create-menu.png");
@ -88,7 +88,7 @@ test.describe("Spaces", () => {
await expect(page.getByRole("treeitem", { name: "Jokes" })).toBeVisible();
});
test("should allow user to create private space", async ({ page, app, user }) => {
test("should allow user to create private space", { tag: "@screenshot" }, async ({ page, app, user }) => {
const menu = await openSpaceCreateMenu(page);
await menu.getByRole("button", { name: "Private" }).click();
@ -216,13 +216,10 @@ test.describe("Spaces", () => {
await expect(hierarchyList.getByRole("treeitem", { name: "Gaming" }).getByRole("button")).toBeVisible();
});
test("should render subspaces in the space panel only when expanded", async ({
page,
app,
user,
axe,
checkA11y,
}) => {
test(
"should render subspaces in the space panel only when expanded",
{ tag: "@screenshot" },
async ({ page, app, user, axe, checkA11y }) => {
axe.disableRules([
// Disable this check as it triggers on nested roving tab index elements which are in practice fine
"nested-interactive",
@ -258,7 +255,8 @@ test.describe("Spaces", () => {
await checkA11y();
await expect(page.locator(".mx_SpacePanel")).toMatchScreenshot("space-panel-expanded.png");
});
},
);
test("should not soft crash when joining a room from space hierarchy which has a link in its topic", async ({
page,

View file

@ -16,16 +16,18 @@ test.describe("Threads Activity Centre", () => {
labsFlags: ["threadsActivityCentre"],
});
test("should have the button correctly aligned and displayed in the space panel when expanded", async ({
util,
}) => {
test(
"should have the button correctly aligned and displayed in the space panel when expanded",
{ tag: "@screenshot" },
async ({ util }) => {
// Open the space panel
await util.expandSpacePanel();
// The buttons in the space panel should be aligned when expanded
await expect(util.getSpacePanel()).toMatchScreenshot("tac-button-expanded.png");
});
},
);
test("should not show indicator when there is no thread", async ({ room1, util }) => {
test("should not show indicator when there is no thread", { tag: "@screenshot" }, async ({ room1, util }) => {
// No indicator should be shown
await util.assertNoTacIndicator();
@ -62,7 +64,7 @@ test.describe("Threads Activity Centre", () => {
await util.assertHighlightIndicator();
});
test("should show the rooms with unread threads", async ({ room1, room2, util, msg }) => {
test("should show the rooms with unread threads", { tag: "@screenshot" }, async ({ room1, room2, util, msg }) => {
await util.goTo(room2);
await util.populateThreads(room1, room2, msg);
// The indicator should be shown
@ -79,7 +81,7 @@ test.describe("Threads Activity Centre", () => {
await expect(util.getTacPanel()).toMatchScreenshot("tac-panel-mix-unread.png");
});
test("should update with a thread is read", async ({ room1, room2, util, msg }) => {
test("should update with a thread is read", { tag: "@screenshot" }, async ({ room1, room2, util, msg }) => {
await util.goTo(room2);
await util.populateThreads(room1, room2, msg);
@ -128,7 +130,7 @@ test.describe("Threads Activity Centre", () => {
await expect(page.locator(".mx_SpotlightDialog")).not.toBeVisible();
});
test("should have the correct hover state", async ({ util, page }) => {
test("should have the correct hover state", { tag: "@screenshot" }, async ({ util, page }) => {
await util.hoverTacButton();
await expect(util.getSpacePanel()).toMatchScreenshot("tac-hovered.png");
@ -138,7 +140,7 @@ test.describe("Threads Activity Centre", () => {
await expect(util.getSpacePanel()).toMatchScreenshot("tac-hovered-expanded.png");
});
test("should mark all threads as read", async ({ room1, room2, util, msg, page }) => {
test("should mark all threads as read", { tag: "@screenshot" }, async ({ room1, room2, util, msg, page }) => {
await util.receiveMessages(room1, ["Msg1", msg.threadedOff("Msg1", "Resp1")]);
await util.assertNotificationTac();

View file

@ -25,7 +25,7 @@ test.describe("Threads", () => {
});
// Flaky: https://github.com/vector-im/element-web/issues/26452
test.skip("should be usable for a conversation", async ({ page, app, bot }) => {
test.skip("should be usable for a conversation", { tag: "@screenshot" }, async ({ page, app, bot }) => {
const roomId = await app.client.createRoom({});
await app.client.inviteUser(roomId, bot.credentials.userId);
await bot.joinRoom(roomId);
@ -150,7 +150,7 @@ test.describe("Threads", () => {
).toHaveCSS("padding-inline-start", ThreadViewGroupSpacingStart);
// Take snapshot of group layout (IRC layout is not available on ThreadView)
expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
"ThreadView_with_reaction_and_a_hidden_event_on_group_layout.png",
{
mask: mask,
@ -174,7 +174,7 @@ test.describe("Threads", () => {
.toHaveCSS("margin-inline-start", "0px");
// Take snapshot of bubble layout
expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
"ThreadView_with_reaction_and_a_hidden_event_on_bubble_layout.png",
{
mask: mask,
@ -351,7 +351,10 @@ test.describe("Threads", () => {
});
});
test("should send location and reply to the location on ThreadView", async ({ page, app, bot }) => {
test(
"should send location and reply to the location on ThreadView",
{ tag: "@screenshot" },
async ({ page, app, bot }) => {
const roomId = await app.client.createRoom({});
await app.client.inviteUser(roomId, bot.credentials.userId);
await bot.joinRoom(roomId);
@ -401,7 +404,8 @@ test.describe("Threads", () => {
// Take a snapshot of reply to the shared location
await page.addStyleTag({ content: css });
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot("Reply_to_the_location_on_ThreadView.png");
});
},
);
test("right panel behaves correctly", async ({ page, app, user }) => {
// Create room

View file

@ -137,7 +137,10 @@ test.describe("Timeline", () => {
});
test.describe("configure room", () => {
test("should create and configure a room on IRC layout", async ({ page, app, room }) => {
test(
"should create and configure a room on IRC layout",
{ tag: "@screenshot" },
async ({ page, app, room }) => {
await page.goto(`/#/room/${room.roomId}`);
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
await expect(
@ -151,9 +154,13 @@ test.describe("Timeline", () => {
await expect(page.locator(".mx_TimelineSeparator")).toHaveText("today");
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("configured-room-irc-layout.png");
});
},
);
test("should have an expanded generic event list summary (GELS) on IRC layout", async ({ page, app, room }) => {
test(
"should have an expanded generic event list summary (GELS) on IRC layout",
{ tag: "@screenshot" },
async ({ page, app, room }) => {
await page.goto(`/#/room/${room.roomId}`);
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
@ -179,13 +186,13 @@ test.describe("Timeline", () => {
}
`,
});
});
},
);
test("should have an expanded generic event list summary (GELS) on compact modern/group layout", async ({
page,
app,
room,
}) => {
test(
"should have an expanded generic event list summary (GELS) on compact modern/group layout",
{ tag: "@screenshot" },
async ({ page, app, room }) => {
await page.goto(`/#/room/${room.roomId}`);
// Set compact modern layout
@ -213,13 +220,13 @@ test.describe("Timeline", () => {
}
`,
});
});
},
);
test("should click 'collapse' on the first hovered info event line inside GELS on bubble layout", async ({
page,
app,
room,
}) => {
test(
"should click 'collapse' on the first hovered info event line inside GELS on bubble layout",
{ tag: "@screenshot" },
async ({ page, app, room }) => {
// This test checks clickability of the "Collapse" link button, which had been covered with
// MessageActionBar's safe area - https://github.com/vector-im/element-web/issues/22864
@ -250,7 +257,9 @@ test.describe("Timeline", () => {
});
// Click "collapse" link button on the first hovered info event line
const firstTile = gels.locator(".mx_GenericEventListSummary_unstyledList .mx_EventTile_info:first-of-type");
const firstTile = gels.locator(
".mx_GenericEventListSummary_unstyledList .mx_EventTile_info:first-of-type",
);
await firstTile.hover();
await expect(firstTile.getByRole("toolbar", { name: "Message Actions" })).toBeVisible();
await gels.getByRole("button", { name: "Collapse" }).click();
@ -262,15 +271,13 @@ test.describe("Timeline", () => {
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("collapsed-gels-bubble-layout.png", {
mask: [page.locator(".mx_MessageTimestamp")],
});
});
},
);
test("should add inline start margin to an event line on IRC layout", async ({
page,
app,
room,
axe,
checkA11y,
}) => {
test(
"should add inline start margin to an event line on IRC layout",
{ tag: "@screenshot" },
async ({ page, app, room, axe, checkA11y }) => {
axe.disableRules("color-contrast");
await page.goto(`/#/room/${room.roomId}`);
@ -312,7 +319,8 @@ test.describe("Timeline", () => {
},
);
await checkA11y();
});
},
);
});
test.describe("message displaying", () => {
@ -332,11 +340,10 @@ test.describe("Timeline", () => {
).toBeVisible();
};
test("should align generic event list summary with messages and emote on IRC layout", async ({
page,
app,
room,
}) => {
test(
"should align generic event list summary with messages and emote on IRC layout",
{ tag: "@screenshot" },
async ({ page, app, room }) => {
// This test aims to check:
// 1. Alignment of collapsed GELS (generic event list summary) and messages
// 2. Alignment of expanded GELS and messages
@ -372,10 +379,9 @@ test.describe("Timeline", () => {
// = var(--name-width) + var(--icon-width) + var(--MessageTimestamp-width) + 2 * var(--right-padding)
// = 80 + 14 + 46 + 2 * 5
// = 150px
await expect(page.locator(".mx_GenericEventListSummary[data-layout=irc] > .mx_EventTile_line")).toHaveCSS(
"padding-inline-start",
"150px",
);
await expect(
page.locator(".mx_GenericEventListSummary[data-layout=irc] > .mx_EventTile_line"),
).toHaveCSS("padding-inline-start", "150px");
// Check width and spacing values of elements in .mx_EventTile, which should be equal to 150px
// --right-padding should be applied
for (const locator of await page.locator(".mx_EventTile > a").all()) {
@ -416,10 +422,13 @@ test.describe("Timeline", () => {
page.locator(".mx_EventTile[data-layout=irc].mx_EventTile_info:first-of-type .mx_EventTile_line"),
).toHaveCSS("margin-inline-start", "99px");
// Record alignment of expanded GELS and messages on messagePanel
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("expanded-gels-and-messages-irc-layout.png", {
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot(
"expanded-gels-and-messages-irc-layout.png",
{
// Exclude timestamp from snapshot of mx_MainSplit
mask: [page.locator(".mx_MessageTimestamp")],
});
},
);
// 3. Alignment of expanded GELS and placeholder of deleted message
// Delete the second (last) message
@ -431,15 +440,20 @@ test.describe("Timeline", () => {
await page.locator(".mx_Dialog_buttons").getByRole("button", { name: "Remove" }).click();
// Make sure the dialog was closed and the second (last) message was redacted
await expect(page.locator(".mx_Dialog")).not.toBeVisible();
await expect(page.locator(".mx_GenericEventListSummary .mx_EventTile_last .mx_RedactedBody")).toBeVisible();
await expect(
page.locator(".mx_GenericEventListSummary .mx_EventTile_last .mx_RedactedBody"),
).toBeVisible();
await expect(
page.locator(".mx_GenericEventListSummary .mx_EventTile_last .mx_EventTile_receiptSent"),
).toBeVisible();
// Record alignment of expanded GELS and placeholder of deleted message on messagePanel
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("expanded-gels-redaction-placeholder.png", {
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot(
"expanded-gels-redaction-placeholder.png",
{
// Exclude timestamp from snapshot of mx_MainSplit
mask: [page.locator(".mx_MessageTimestamp")],
});
},
);
// 4. Alignment of expanded GELS, placeholder of deleted message, and emote
// Send a emote
@ -447,7 +461,10 @@ test.describe("Timeline", () => {
.locator(".mx_RoomView_body")
.getByRole("textbox", { name: "Send a message…" })
.fill("/me says hello to Mr. Bot");
await page.locator(".mx_RoomView_body").getByRole("textbox", { name: "Send a message…" }).press("Enter");
await page
.locator(".mx_RoomView_body")
.getByRole("textbox", { name: "Send a message…" })
.press("Enter");
// Check inline start margin of its avatar
// Here --right-padding is for the avatar on the message line
// See: _IRCLayout.pcss
@ -456,15 +473,21 @@ test.describe("Timeline", () => {
// = 80 + 14 + 1 * 5
await expect(page.locator(".mx_EventTile_emote .mx_EventTile_avatar")).toHaveCSS("margin-left", "99px");
// Make sure emote was sent
await expect(page.locator(".mx_EventTile_last.mx_EventTile_emote .mx_EventTile_receiptSent")).toBeVisible();
await expect(
page.locator(".mx_EventTile_last.mx_EventTile_emote .mx_EventTile_receiptSent"),
).toBeVisible();
// Record alignment of expanded GELS, placeholder of deleted message, and emote
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("expanded-gels-emote-irc-layout.png", {
// Exclude timestamp from snapshot of mx_MainSplit
mask: [page.locator(".mx_MessageTimestamp")],
});
});
},
);
test("should render EventTiles on IRC, modern (group), and bubble layout", async ({ page, app, room }) => {
test(
"should render EventTiles on IRC, modern (group), and bubble layout",
{ tag: "@screenshot" },
async ({ page, app, room }) => {
const screenshotOptions = {
// Hide because flaky - See https://github.com/vector-im/element-web/issues/24957
mask: [page.locator(".mx_MessageTimestamp")],
@ -491,7 +514,9 @@ test.describe("Timeline", () => {
await composer.fill("This message has an inline emoji 👒");
await composer.press("Enter");
await expect(page.locator(".mx_RoomView").getByText("This message has an inline emoji 👒")).toBeVisible();
await expect(
page.locator(".mx_RoomView").getByText("This message has an inline emoji 👒"),
).toBeVisible();
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// IRC layout
@ -558,9 +583,13 @@ test.describe("Timeline", () => {
"event-tiles-bubble-layout.png",
screenshotOptions,
);
});
},
);
test("should set inline start padding to a hidden event line", async ({ page, app, room }) => {
test(
"should set inline start padding to a hidden event line",
{ tag: "@screenshot" },
async ({ page, app, room }) => {
test.skip(
true,
"Disabled due to screenshot test being flaky - https://github.com/element-hq/element-web/issues/26890",
@ -612,9 +641,10 @@ test.describe("Timeline", () => {
"hidden-event-line-padding-modern-layout.png",
screenshotOptions,
);
});
},
);
test("should click view source event toggle", async ({ page, app, room }) => {
test("should click view source event toggle", { tag: "@screenshot" }, async ({ page, app, room }) => {
// This test checks:
// 1. clickability of top left of view source event toggle
// 2. clickability of view source toggle on IRC layout
@ -712,7 +742,10 @@ test.describe("Timeline", () => {
).toBeVisible();
});
test("should render url previews", async ({ page, app, room, axe, checkA11y, context }) => {
test(
"should render url previews",
{ tag: "@screenshot" },
async ({ page, app, room, axe, checkA11y, context }) => {
axe.disableRules("color-contrast");
// Element Web uses a Service Worker to rewrite unauthenticated media requests to authenticated ones, but
@ -769,10 +802,14 @@ test.describe("Timeline", () => {
}
`,
});
});
},
);
test.describe("on search results panel", () => {
test("should highlight search result words regardless of formatting", async ({ page, app, room }) => {
test(
"should highlight search result words regardless of formatting",
{ tag: "@screenshot" },
async ({ page, app, room }) => {
await sendEvent(app.client, room.roomId);
await sendEvent(app.client, room.roomId, true);
await page.goto(`/#/room/${room.roomId}`);
@ -792,9 +829,10 @@ test.describe("Timeline", () => {
await expect(page.locator(".mx_RoomView_searchResultsPanel")).toMatchScreenshot(
"highlighted-search-results.png",
);
});
},
);
test("should render a fully opaque textual event", async ({ page, app, room }) => {
test("should render a fully opaque textual event", { tag: "@screenshot" }, async ({ page, app, room }) => {
const stringToSearch = "Message"; // Same with string sent with sendEvent()
await sendEvent(app.client, room.roomId);
@ -918,7 +956,7 @@ test.describe("Timeline", () => {
).toHaveCount(4);
});
test("should display a reply chain", async ({ page, app, room, homeserver }) => {
test("should display a reply chain", { tag: "@screenshot" }, async ({ page, app, room, homeserver }) => {
const reply2 = "Reply again";
await page.goto(`/#/room/${room.roomId}`);
@ -1031,12 +1069,10 @@ test.describe("Timeline", () => {
);
});
test("should send, reply, and display long strings without overflowing", async ({
page,
app,
room,
homeserver,
}) => {
test(
"should send, reply, and display long strings without overflowing",
{ tag: "@screenshot" },
async ({ page, app, room, homeserver }) => {
// Max 256 characters for display name
const LONG_STRING =
"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut " +
@ -1146,7 +1182,8 @@ test.describe("Timeline", () => {
"long-strings-with-reply-bubble-layout.png",
screenshotOptions,
);
});
},
);
async function testImageRendering(page: Page, app: ElementAppPage, room: { roomId: string }) {
await app.viewRoomById(room.roomId);
@ -1176,7 +1213,7 @@ test.describe("Timeline", () => {
);
}
test("should render images in the timeline", async ({ page, app, room, context }) => {
test("should render images in the timeline", { tag: "@screenshot" }, async ({ page, app, room, context }) => {
await testImageRendering(page, app, room);
});
@ -1188,7 +1225,10 @@ test.describe("Timeline", () => {
// In practice, this means this test will *always* succeed because it ends up relying on fallback behaviour tested
// above (unless of course the above tests are also broken).
test.describe("MSC3916 - Authenticated Media", () => {
test("should render authenticated images in the timeline", async ({ page, app, room, context }) => {
test(
"should render authenticated images in the timeline",
{ tag: "@screenshot" },
async ({ page, app, room, context }) => {
// Note: we have to use `context` instead of `page` for routing, otherwise we'll miss Service Worker events.
// See https://playwright.dev/docs/service-workers-experimental#network-events-and-routing
@ -1231,7 +1271,8 @@ test.describe("Timeline", () => {
// We check the same screenshot because there should be no user-visible impact to using authentication.
await testImageRendering(page, app, room);
});
},
);
});
});
});

View file

@ -11,7 +11,7 @@ import { test, expect } from "../../element-web-test";
test.describe("User Menu", () => {
test.use({ displayName: "Jeff" });
test("should contain our name & userId", async ({ page, user }) => {
test("should contain our name & userId", { tag: "@screenshot" }, async ({ page, user }) => {
await page.getByRole("button", { name: "User menu", exact: true }).click();
const menu = page.getByRole("menu");

View file

@ -26,7 +26,7 @@ test.describe("User Onboarding (new user)", () => {
await expect(page.locator(".mx_UserOnboardingList")).toBeVisible();
});
test("page is shown and preference exists", async ({ page, app }) => {
test("page is shown and preference exists", { tag: "@screenshot" }, async ({ page, app }) => {
await expect(page.locator(".mx_UserOnboardingPage")).toMatchScreenshot(
"User-Onboarding-new-user-page-is-shown-and-preference-exists-1.png",
);
@ -34,7 +34,7 @@ test.describe("User Onboarding (new user)", () => {
await expect(page.getByText("Show shortcut to welcome checklist above the room list")).toBeVisible();
});
test("app download dialog", async ({ page }) => {
test("app download dialog", { tag: "@screenshot" }, async ({ page }) => {
await page.getByRole("button", { name: "Download apps" }).click();
await expect(
page.getByRole("dialog").getByRole("heading", { level: 1, name: "Download Element" }),

View file

@ -14,7 +14,7 @@ test.describe("UserView", () => {
botCreateOpts: { displayName: "Usman" },
});
test("should render the user view as expected", async ({ page, homeserver, user, bot }) => {
test("should render the user view as expected", { tag: "@screenshot" }, async ({ page, homeserver, user, bot }) => {
await page.goto(`/#/user/${bot.credentials.userId}`);
const rightPanel = page.locator("#mx_RightPanel");

View file

@ -70,7 +70,7 @@ test.describe("Widget Layout", () => {
await app.viewRoomByName(ROOM_NAME);
});
test("should be set properly", async ({ page }) => {
test("should be set properly", { tag: "@screenshot" }, async ({ page }) => {
await expect(page.locator(".mx_AppsDrawer")).toMatchScreenshot("apps-drawer.png");
});

View file

@ -314,6 +314,10 @@ export const expect = baseExpect.extend({
const testInfo = test.info();
if (!testInfo) throw new Error(`toMatchScreenshot() must be called during the test`);
if (!testInfo.tags.includes("@screenshot")) {
throw new Error("toMatchScreenshot() must be used in a test tagged with @screenshot");
}
const page = "page" in receiver ? receiver.page() : receiver;
let css = `