Files
element-web/playwright/e2e/spotlight/spotlight.spec.ts
Michael Telatynski dbce48b23d Run Playwright tests against Dendrite & Pinecone periodically (#28888)
* Switch to TestContainers for manging services in Playwright

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

* Iterate

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

* Flip fixture dependency order

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

* Remove mas dep

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

* Iterate

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

* Iterate

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

* Iterate

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

* Iterate

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

* Iterate

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

* Iterate

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

* Update matrix-authentication-service in Playwright tests

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

* delint

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

* Fix SMTP port

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

* Iterate

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

* Comments

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

* Strip ansi from playwright logs to make them more readable

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

* Actually do the update

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

* Iterate

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

* Remove access to homeserver.config.baseUrl field in favour of homeserver.baseUrl

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

* Use sane default_server_config and specify server.invalid in the specific tests which demand it

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

* Fix mas run

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

* break cycle

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

* Iterate

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

* typo

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

* Iterate

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

* Iterate

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

* Iterate

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

* prettier

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

* Wire up basics of dendriteHomeserver

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

* Run Playwright tests against Dendrite & Pinecone periodically

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

* Iterate

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

* Iterate

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

* Iterate

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

* Iterate

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

* Iterate

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

* Iterate

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

* Iterate

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

* Iterate

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

* Iterate

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

* Fix playwright-image-updates.yaml workflow

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

* Add `X-Run-All-Tests` label for running all tests

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

* Ignore failing tests in stale-screenshot-reporter.ts to avoid confusing errors

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

* Iterate

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

* Iterate

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

* Specify Synapse ui_auth.session_timeout only on tests which require it

As Dendrite lacks this configuration option

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

* Iterate

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

* Iterate

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

* Iterate

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

* Iterate

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

* React to MatrixEvent sender/target sentinels being updated for rendering state events

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

* Iterate

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

* Iterate

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

* Remove test code

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

* Iterate

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

* React to sentinel changes in EventListSummary

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

* Iterate

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

* Iterate

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

* Docs

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

* Avoid reusing user1234

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

* Fix stale-screenshot-reporter.ts

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

* Clean up public rooms between tests on reused homeserver

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

* Deflake spotlight when homeserver is reused

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

* Deflake more tests using existing username

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

* Clean mailhog between tests

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

* Fix more flakes

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

* Fix missing _request

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

* Iterate

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

* Iterate

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

* Iterate

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

* Iterate

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

* Fix playwright flaky tests

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

* Wipe mailhog between test runs

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

* Delint

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

* Iterate

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

* delint

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

* Deflake more tests

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

* Iterate

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

* Fix flaky tests

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

* Fix flaky tests

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

* Fix mas config

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

* Fix another flaky test

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

* Fix playwright flakes due to floating promises

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

* Iterate

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

* Iterate

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

* Fix flaky playwright tests

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

* Iterate

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

* Iterate

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

* Iterate

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

* Iterate

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

* Iterate

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

* Iterate

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

* Iterate

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

* Update services.ts

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-01-14 18:00:30 +00:00

401 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.getByRole("group", { name: "People" }).getByText(name)).toBeAttached();
}
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.getByRole("group", { name: "People" })).toContainText(bot2.credentials.displayName);
// 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.getByRole("group", { name: "People" }).first()).toContainText(groupDmName);
// 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");
});
});