Files
element-web/playwright/e2e/spotlight/spotlight.spec.ts
David Langley 3432613195 Use the new room list by default (#30640)
* Default to new room list and enforce in config for app and develop

* Update jest tests

* Update LandmarkNavigation and e2e test

* Update viewRoomByName helper

* lint

* Update Add -> New Room flow

Keep legacy viewRoomByName until we delete the olds tests.

* Update e2e test to use Add -> Start Chat

* Update screenshots

* Fix viewRoomByName, can't use option as it contains more that just the room name. Using title which should be exact.

* Fix knocking tests

* fix layout.spec.ts and pstn.spec

* timeline snapshots

* Fix spotlight.spec

* TAC spaces and media preview settings

* Fix more screenshots and mark as unread tests

* Fix leftpanel test

* Bugfix for knocking use case. We should check EffectiveMembership when remove rooms from the new room list, so that knocking is handled

* Fix openCreateRoomDialog to new room list specifics to fix create-knock-room.spec.ts

* lint

* Fix Landmark navigation from left panel search to the next landmark

* lint

* Update window-12px-linux.png

* Update apps-drawer-linux.png

* Update sliding sync e2e tests

* Update some screenshots

* Revert change to the space create screenshot

* Use actual screenshot

as focused elements are different when generated locally

* Fix test selectors

* Morfe test screenshot selector / update

* Add test for landmark navigation

* Replace screenshots

* Fix another test that just got added an hour ago

* Not sure why this was changed, doesn't seem necessary

* Disambiguate selector

* Another screenshot that's now changed in width by 1px

* Revert changes to config files

It's being turned on by default so these are unnecessary

* Convert read.unread assertions to new room list

removing support for checking for activity in assertUnread which was
unused.

* Update room list order tests

that feel a bit like they ought to be in room-list rather than read-receipts but whatever

* Fix room titles in read receipts test

---------

Co-authored-by: David Baker <dbkr@users.noreply.github.com>
2025-09-16 08:13:05 +00:00

405 lines
19 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
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 { AccountDataEvents, Visibility } from "matrix-js-sdk/src/matrix";
import { test as base, expect } from "../../element-web-test";
import { Filter } from "../../pages/Spotlight";
import { Bot } from "../../pages/bot";
import type { Locator, Page } from "@playwright/test";
import type { ElementAppPage } from "../../pages/ElementAppPage";
import { isDendrite } from "../../plugins/homeserver/dendrite";
function roomHeaderName(page: Page): Locator {
return page.locator(".mx_RoomHeader_heading");
}
async function startDM(app: ElementAppPage, page: Page, name: string): Promise<void> {
const spotlight = await app.openSpotlight();
await spotlight.filter(Filter.People);
await spotlight.search(name);
await page.waitForTimeout(1000); // wait for the dialog code to settle
await expect(spotlight.dialog.locator(".mx_Spinner")).not.toBeAttached();
const result = spotlight.results;
await expect(result).toHaveCount(1);
await expect(result.first()).toContainText(name);
await result.first().click();
// send first message to start DM
const locator = page.getByRole("textbox", { name: "Send a message…" });
await expect(locator).toBeFocused();
await locator.fill("Hey!");
await locator.press("Enter");
// The DM room is created at this point, this can take a little bit of time
await expect(page.locator(".mx_EventTile_body").getByText("Hey!")).toBeAttached({ timeout: 3000 });
await expect(page.getByTestId("room-list").getByRole("option", { name: `Open room ${name}` })).toBeVisible();
}
type RoomRef = { name: string; roomId: string };
const test = base.extend<{
bot1: Bot;
bot2: Bot;
room1: RoomRef;
room2: RoomRef;
room3: RoomRef;
}>({
bot1: async ({ page, homeserver }, use, testInfo) => {
const bot = new Bot(page, homeserver, { displayName: `BotBob_${testInfo.testId}`, autoAcceptInvites: true });
await use(bot);
},
bot2: async ({ page, homeserver }, use, testInfo) => {
const bot = new Bot(page, homeserver, { displayName: `ByteBot_${testInfo.testId}`, autoAcceptInvites: true });
await use(bot);
},
room1: async ({ app }, use) => {
const name = "247";
const roomId = await app.client.createRoom({ name, visibility: "public" as Visibility });
await use({ name, roomId });
},
room2: async ({ bot2 }, use) => {
const name = "Lounge";
const roomId = await bot2.createRoom({ name, visibility: "public" as Visibility });
await use({ name, roomId });
},
room3: async ({ bot2 }, use) => {
const name = "Public";
const roomId = await bot2.createRoom({
name,
visibility: "public" as Visibility,
initial_state: [
{
type: "m.room.history_visibility",
state_key: "",
content: {
history_visibility: "world_readable",
},
},
],
});
await use({ name, roomId });
},
context: async ({ context, homeserver }, use) => {
// Restart the homeserver to wipe its in-memory db so we can reuse the same user ID without cross-signing prompts
await homeserver.restart();
await use(context);
},
});
test.describe("Spotlight", () => {
test.skip(isDendrite, "due to a Dendrite bug https://github.com/element-hq/dendrite/issues/3488");
test.use({
displayName: "Jim",
});
test.beforeEach(async ({ page, user, bot1, bot2, room1, room2, room3 }) => {
await bot1.joinRoom(room1.roomId);
await bot2.inviteUser(room2.roomId, bot1.credentials.userId);
await bot2.inviteUser(room3.roomId, bot1.credentials.userId);
await page.goto(`/#/room/${room1.roomId}`);
await expect(page.locator(".mx_RoomSublist_skeletonUI")).not.toBeAttached();
});
test("should be able to add and remove filters via keyboard", async ({ page, app }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(1000); // wait for the dialog to settle, otherwise our keypresses might race with an update
// initially, public spaces should be highlighted (because there are no other suggestions)
await expect(spotlight.dialog.locator("#mx_SpotlightDialog_button_explorePublicSpaces")).toHaveAttribute(
"aria-selected",
"true",
);
// hitting enter should enable the public rooms filter
await spotlight.searchBox.press("Enter");
await expect(spotlight.dialog.locator(".mx_SpotlightDialog_filter")).toHaveText("Public spaces");
await spotlight.searchBox.press("Backspace");
await expect(spotlight.dialog.locator(".mx_SpotlightDialog_filter")).not.toBeAttached();
await page.waitForTimeout(200); // Again, wait to settle so keypresses arrive correctly
await spotlight.searchBox.press("ArrowDown");
await expect(spotlight.dialog.locator("#mx_SpotlightDialog_button_explorePublicRooms")).toHaveAttribute(
"aria-selected",
"true",
);
await spotlight.searchBox.press("Enter");
await expect(spotlight.dialog.locator(".mx_SpotlightDialog_filter")).toHaveText("Public rooms");
await spotlight.searchBox.press("Backspace");
await expect(spotlight.dialog.locator(".mx_SpotlightDialog_filter")).not.toBeAttached();
});
test("should find joined rooms", async ({ page, app, room1 }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.search(room1.name);
const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
await expect(resultLocator.first()).toContainText(room1.name);
await resultLocator.first().click();
await expect(page).toHaveURL(new RegExp(`#/room/${room1.roomId}`));
await expect(roomHeaderName(page)).toContainText(room1.name);
});
test("should find known public rooms", async ({ page, app, room1 }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.PublicRooms);
await spotlight.search(room1.name);
const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
await expect(resultLocator.first()).toContainText(room1.name);
await expect(resultLocator.first()).toContainText("View");
await resultLocator.first().click();
await expect(page).toHaveURL(new RegExp(`#/room/${room1.roomId}`));
await expect(roomHeaderName(page)).toContainText(room1.name);
});
test("should find unknown public rooms", async ({ page, app, room2 }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.PublicRooms);
await spotlight.search(room2.name);
const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
await expect(resultLocator.first()).toContainText(room2.name);
await expect(resultLocator.first()).toContainText("Join");
await resultLocator.first().click();
await expect(page).toHaveURL(new RegExp(`#/room/${room2.roomId}`));
await expect(page.locator(".mx_RoomView_MessageList")).toHaveCount(1);
await expect(roomHeaderName(page)).toContainText(room2.name);
});
test("should find unknown public world readable rooms", async ({ page, app, room3 }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.PublicRooms);
await spotlight.search(room3.name);
const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
await expect(resultLocator.first()).toContainText(room3.name);
await expect(resultLocator.first()).toContainText("View");
await resultLocator.first().click();
await expect(page).toHaveURL(new RegExp(`#/room/${room3.roomId}`));
await page.getByRole("button", { name: "Join the discussion" }).click();
await expect(roomHeaderName(page)).toHaveText(room3.name);
});
// TODO: We currently cant test finding rooms on other homeservers/other protocols
// We obviously dont have federation or bridges in local e2e tests
test.skip("should find unknown public rooms on other homeservers", async ({ page, app, room3 }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.PublicRooms);
await spotlight.search(room3.name);
await page.locator("[aria-haspopup=true][role=button]").click();
await page
.locator(".mx_GenericDropdownMenu_Option--header")
.filter({ hasText: "matrix.org" })
.locator("..")
.locator("[role=menuitemradio]")
.click();
await page.waitForTimeout(3_600_000);
await page.waitForTimeout(500); // wait for the dialog to settle
const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
await expect(resultLocator.first()).toContainText(room3.name);
await expect(resultLocator.first()).toContainText(room3.roomId);
});
test("should find known people", async ({ page, app, bot1 }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.People);
await spotlight.search(bot1.credentials.displayName);
const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
await expect(resultLocator.first()).toContainText(bot1.credentials.displayName);
await resultLocator.first().click();
await expect(roomHeaderName(page)).toHaveText(bot1.credentials.displayName);
});
/**
* Search sends the correct query to Synapse.
* Synapse doesn't return the user in the result list.
* Waiting for the profile to be available via APIs before the tests didn't help.
*
* https://github.com/matrix-org/synapse/issues/16472
*/
test("should find unknown people", async ({ page, app, bot2 }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.People);
await spotlight.search(bot2.credentials.displayName);
const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
await expect(resultLocator.first()).toContainText(bot2.credentials.displayName);
await resultLocator.first().click();
await expect(roomHeaderName(page)).toHaveText(bot2.credentials.displayName);
});
test("should find group DMs by usernames or user ids", async ({ page, app, bot1, bot2, room1 }) => {
// First we want to share a room with both bots to ensure weve got their usernames cached
await app.client.inviteUser(room1.roomId, bot2.credentials.userId);
// Starting a DM with ByteBot (will be turned into a group dm later)
let spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.People);
await spotlight.search(bot2.credentials.displayName);
let resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
await expect(resultLocator.first()).toContainText(bot2.credentials.displayName);
await resultLocator.first().click();
// Send first message to actually start DM
await expect(roomHeaderName(page)).toHaveText(bot2.credentials.displayName);
const locator = page.getByRole("textbox", { name: "Send a message…" });
await locator.fill("Hey!");
await locator.press("Enter");
// Assert DM exists by checking for the first message and the room being in the room list
await expect(page.locator(".mx_EventTile_body").filter({ hasText: "Hey!" })).toBeAttached({ timeout: 3000 });
await expect(
page.getByTestId("room-list").getByRole("option", { name: `Open room ${bot2.credentials.displayName}` }),
).toBeVisible();
// Invite BotBob into existing DM with ByteBot
const dmRooms = await app.client.evaluate((client, userId) => {
const map = client
.getAccountData("m.direct" as keyof AccountDataEvents)
?.getContent<Record<string, string[]>>();
return map[userId] ?? [];
}, bot2.credentials.userId);
expect(dmRooms).toHaveLength(1);
const groupDmName = await app.client.evaluate((client, id) => client.getRoom(id).name, dmRooms[0]);
await app.client.inviteUser(dmRooms[0], bot1.credentials.userId);
await expect(roomHeaderName(page).first()).toContainText(groupDmName);
await expect(
page.getByTestId("room-list").getByRole("option", { name: `Open room ${groupDmName}` }),
).toBeVisible();
// Search for BotBob by id, should return group DM and user
spotlight = await app.openSpotlight();
await spotlight.filter(Filter.People);
await spotlight.search(bot1.credentials.userId);
await page.waitForTimeout(1000); // wait for the dialog to settle
resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(2);
await expect(
spotlight.dialog
.locator(".mx_SpotlightDialog_section.mx_SpotlightDialog_results .mx_SpotlightDialog_option")
.filter({ hasText: groupDmName }),
).toBeAttached();
// Search for ByteBot by id, should return group DM and user
spotlight = await app.openSpotlight();
await spotlight.filter(Filter.People);
await spotlight.search(bot2.credentials.userId);
await page.waitForTimeout(1000); // wait for the dialog to settle
resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(2);
await expect(
spotlight.dialog
.locator(".mx_SpotlightDialog_section.mx_SpotlightDialog_results .mx_SpotlightDialog_option")
.filter({ hasText: groupDmName })
.last(),
).toBeAttached();
});
// Test against https://github.com/vector-im/element-web/issues/22851
test("should show each person result only once", async ({ page, app, bot1 }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.People);
// 2 rounds of search to simulate the bug conditions. Specifically, the first search
// should have 1 result (not 2) and the second search should also have 1 result (instead
// of the super buggy 3 described by https://github.com/vector-im/element-web/issues/22851)
//
// We search for user ID to trigger the profile lookup within the dialog.
for (let i = 0; i < 2; i++) {
console.log("Iteration: " + i);
await spotlight.search(bot1.credentials.userId);
await page.waitForTimeout(1000); // wait for the dialog to settle
const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
await expect(resultLocator.first()).toContainText(bot1.credentials.userId);
}
});
test("should allow opening group chat dialog", async ({ page, app, bot2 }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.People);
await spotlight.search(bot2.credentials.displayName);
await page.waitForTimeout(3000); // wait for the dialog to settle
const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
await expect(resultLocator.first()).toContainText(bot2.credentials.displayName);
await expect(spotlight.dialog.locator(".mx_SpotlightDialog_startGroupChat")).toContainText(
"Start a group chat",
);
await spotlight.dialog.locator(".mx_SpotlightDialog_startGroupChat").click();
await expect(page.getByRole("dialog")).toContainText("Direct Messages");
});
test("should close spotlight after starting a DM", async ({ page, app, bot1 }) => {
await startDM(app, page, bot1.credentials.displayName);
await expect(page.locator(".mx_SpotlightDialog")).toHaveCount(0);
});
test("should show the same user only once", async ({ page, app, bot1 }) => {
await startDM(app, page, bot1.credentials.displayName);
await page.goto("/#/home");
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.People);
await spotlight.search(bot1.credentials.displayName);
await page.waitForTimeout(3000); // wait for the dialog to settle
await expect(spotlight.dialog.locator(".mx_Spinner")).not.toBeAttached();
const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
});
test("should be able to navigate results via keyboard", async ({ page, app }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.People);
await spotlight.search("b");
let resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(2);
await expect(resultLocator.first()).toHaveAttribute("aria-selected", "true");
await expect(resultLocator.last()).toHaveAttribute("aria-selected", "false");
await spotlight.searchBox.press("ArrowDown");
resultLocator = spotlight.results;
await expect(resultLocator.first()).toHaveAttribute("aria-selected", "false");
await expect(resultLocator.last()).toHaveAttribute("aria-selected", "true");
await spotlight.searchBox.press("ArrowDown");
resultLocator = spotlight.results;
await expect(resultLocator.first()).toHaveAttribute("aria-selected", "false");
await expect(resultLocator.last()).toHaveAttribute("aria-selected", "false");
await spotlight.searchBox.press("ArrowUp");
resultLocator = spotlight.results;
await expect(resultLocator.first()).toHaveAttribute("aria-selected", "false");
await expect(resultLocator.last()).toHaveAttribute("aria-selected", "true");
await spotlight.searchBox.press("ArrowUp");
resultLocator = spotlight.results;
await expect(resultLocator.first()).toHaveAttribute("aria-selected", "true");
await expect(resultLocator.last()).toHaveAttribute("aria-selected", "false");
});
});