Convert spotlight tests to playwright (#12033)

* Convert tests

* Use existing code

* Remove code from settings

* Change names

* Change comment

* Update comment
This commit is contained in:
R Midhun Suresh 2023-12-18 11:34:15 +05:30 committed by GitHub
parent 7508e62eda
commit d1562befc4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 408 additions and 460 deletions

View file

@ -1,458 +0,0 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/// <reference types="cypress" />
import { MatrixClient } from "../../global";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import Chainable = Cypress.Chainable;
import Loggable = Cypress.Loggable;
import Timeoutable = Cypress.Timeoutable;
import Withinable = Cypress.Withinable;
import Shadow = Cypress.Shadow;
import { Filter } from "../../support/settings";
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface Chainable {
roomHeaderName(
options?: Partial<Loggable & Timeoutable & Withinable & Shadow>,
): Chainable<JQuery<HTMLElement>>;
startDM(name: string): Chainable<void>;
}
}
}
Cypress.Commands.add(
"roomHeaderName",
(options?: Partial<Loggable & Timeoutable & Withinable & Shadow>): Chainable<JQuery<HTMLElement>> => {
return cy.get(".mx_LegacyRoomHeader_nametext", options);
},
);
Cypress.Commands.add("startDM", (name: string) => {
cy.openSpotlightDialog().within(() => {
cy.spotlightFilter(Filter.People);
cy.spotlightSearch().clear().type(name);
cy.wait(1000); // wait for the dialog code to settle
cy.get(".mx_Spinner").should("not.exist");
cy.spotlightResults().should("have.length", 1);
cy.spotlightResults().eq(0).should("contain", name);
cy.spotlightResults().eq(0).click();
});
// send first message to start DM
cy.findByRole("textbox", { name: "Send a message…" }).should("have.focus").type("Hey!{enter}");
// The DM room is created at this point, this can take a little bit of time
cy.get(".mx_EventTile_body", { timeout: 30000 }).findByText("Hey!");
cy.findByRole("group", { name: "People" }).findByText(name);
});
describe("Spotlight", () => {
let homeserver: HomeserverInstance;
const bot1Name = "BotBob";
let bot1: MatrixClient;
const bot2Name = "ByteBot";
let bot2: MatrixClient;
const room1Name = "247";
let room1Id: string;
const room2Name = "Lounge";
let room2Id: string;
const room3Name = "Public";
let room3Id: string;
beforeEach(() => {
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(homeserver, "Jim")
.then(() =>
cy.getBot(homeserver, { displayName: bot1Name }).then((_bot1) => {
bot1 = _bot1;
}),
)
.then(() =>
cy.getBot(homeserver, { displayName: bot2Name }).then((_bot2) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
bot2 = _bot2;
}),
)
.then(() =>
cy.window({ log: false }).then(({ matrixcs: { Visibility } }) => {
cy.createRoom({ name: room1Name, visibility: Visibility.Public }).then(async (_room1Id) => {
room1Id = _room1Id;
await bot1.joinRoom(room1Id);
});
bot2.createRoom({ name: room2Name, visibility: Visibility.Public }).then(
({ room_id: _room2Id }) => {
room2Id = _room2Id;
bot2.invite(room2Id, bot1.getUserId());
},
);
bot2.createRoom({
name: room3Name,
visibility: Visibility.Public,
initial_state: [
{
type: "m.room.history_visibility",
state_key: "",
content: {
history_visibility: "world_readable",
},
},
],
}).then(({ room_id: _room3Id }) => {
room3Id = _room3Id;
bot2.invite(room3Id, bot1.getUserId());
});
}),
)
.then(() => {
cy.visit("/#/room/" + room1Id);
cy.get(".mx_RoomSublist_skeletonUI").should("not.exist");
});
});
// wait for the room to have the right name
cy.get(".mx_LegacyRoomHeader").within(() => {
cy.findByText(room1Name);
});
});
afterEach(() => {
cy.visit("/#/home");
cy.stopHomeserver(homeserver);
});
it("should be able to add and remove filters via keyboard", () => {
cy.openSpotlightDialog().within(() => {
cy.wait(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)
cy.get("#mx_SpotlightDialog_button_explorePublicSpaces").should("have.attr", "aria-selected", "true");
// hitting enter should enable the public rooms filter
cy.spotlightSearch().type("{enter}");
cy.get(".mx_SpotlightDialog_filter").should("contain", "Public spaces");
cy.spotlightSearch().type("{backspace}");
cy.get(".mx_SpotlightDialog_filter").should("not.exist");
cy.wait(200); // Again, wait to settle so keypresses arrive correctly
cy.spotlightSearch().type("{downArrow}");
cy.get("#mx_SpotlightDialog_button_explorePublicRooms").should("have.attr", "aria-selected", "true");
cy.spotlightSearch().type("{enter}");
cy.get(".mx_SpotlightDialog_filter").should("contain", "Public rooms");
cy.spotlightSearch().type("{backspace}");
cy.get(".mx_SpotlightDialog_filter").should("not.exist");
});
});
it("should find joined rooms", () => {
cy.openSpotlightDialog()
.within(() => {
cy.wait(500); // Wait for dialog to settle
cy.spotlightSearch().clear().type(room1Name);
cy.spotlightResults().should("have.length", 1);
cy.spotlightResults().eq(0).should("contain", room1Name);
cy.spotlightResults().eq(0).click();
cy.url().should("contain", room1Id);
})
.then(() => {
cy.roomHeaderName().should("contain", room1Name);
});
});
it("should find known public rooms", () => {
cy.openSpotlightDialog()
.within(() => {
cy.wait(500); // Wait for dialog to settle
cy.spotlightFilter(Filter.PublicRooms);
cy.spotlightSearch().clear().type(room1Name);
cy.spotlightResults().should("have.length", 1);
cy.spotlightResults().eq(0).should("contain", room1Name);
cy.spotlightResults().eq(0).should("contain", "View");
cy.spotlightResults().eq(0).click();
cy.url().should("contain", room1Id);
})
.then(() => {
cy.roomHeaderName().should("contain", room1Name);
});
});
it("should find unknown public rooms", () => {
cy.openSpotlightDialog()
.within(() => {
cy.wait(500); // Wait for dialog to settle
cy.spotlightFilter(Filter.PublicRooms);
cy.spotlightSearch().clear().type(room2Name);
cy.spotlightResults().should("have.length", 1);
cy.spotlightResults().eq(0).should("contain", room2Name);
cy.spotlightResults().eq(0).should("contain", "Join");
cy.spotlightResults().eq(0).click();
cy.url().should("contain", room2Id);
})
.then(() => {
cy.get(".mx_RoomView_MessageList").should("have.length", 1);
cy.roomHeaderName().should("contain", room2Name);
});
});
it("should find unknown public world readable rooms", () => {
cy.openSpotlightDialog()
.within(() => {
cy.wait(500); // Wait for dialog to settle
cy.spotlightFilter(Filter.PublicRooms);
cy.spotlightSearch().clear().type(room3Name);
cy.spotlightResults().should("have.length", 1);
cy.spotlightResults().eq(0).should("contain", room3Name);
cy.spotlightResults().eq(0).should("contain", "View");
cy.spotlightResults().eq(0).click();
cy.url().should("contain", room3Id);
})
.then(() => {
cy.findByRole("button", { name: "Join the discussion" }).click();
cy.roomHeaderName().should("contain", room3Name);
});
});
// TODO: We currently cant test finding rooms on other homeservers/other protocols
// We obviously dont have federation or bridges in cypress tests
it.skip("should find unknown public rooms on other homeservers", () => {
cy.openSpotlightDialog()
.within(() => {
cy.wait(500); // Wait for dialog to settle
cy.spotlightFilter(Filter.PublicRooms);
cy.spotlightSearch().clear().type(room3Name);
cy.get("[aria-haspopup=true][role=button]").click();
})
.then(() => {
cy.contains(".mx_GenericDropdownMenu_Option--header", "matrix.org")
.next("[role=menuitemradio]")
.click();
cy.wait(3_600_000);
})
.then(() =>
cy.spotlightDialog().within(() => {
cy.wait(500); // Wait for dialog to settle
cy.spotlightResults().should("have.length", 1);
cy.spotlightResults().eq(0).should("contain", room3Name);
cy.spotlightResults().eq(0).should("contain", room3Id);
}),
);
});
it("should find known people", () => {
cy.openSpotlightDialog()
.within(() => {
cy.wait(500); // Wait for dialog to settle
cy.spotlightFilter(Filter.People);
cy.spotlightSearch().clear().type(bot1Name);
cy.spotlightResults().should("have.length", 1);
cy.spotlightResults().eq(0).should("contain", bot1Name);
cy.spotlightResults().eq(0).click();
})
.then(() => {
cy.roomHeaderName().should("contain", bot1Name);
});
});
/**
* 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
*/
it.skip("should find unknown people", () => {
cy.openSpotlightDialog()
.within(() => {
cy.wait(500); // Wait for dialog to settle
cy.spotlightFilter(Filter.People);
cy.spotlightSearch().clear().type(bot2Name);
cy.spotlightResults().should("have.length", 1);
cy.spotlightResults().eq(0).should("contain", bot2Name);
cy.spotlightResults().eq(0).click();
})
.then(() => {
cy.roomHeaderName().should("contain", bot2Name);
});
});
it("should find group DMs by usernames or user ids", () => {
// First we want to share a room with both bots to ensure weve got their usernames cached
cy.inviteUser(room1Id, bot2.getUserId());
// Starting a DM with ByteBot (will be turned into a group dm later)
cy.openSpotlightDialog().within(() => {
cy.wait(500); // Wait for dialog to settle
cy.spotlightFilter(Filter.People);
cy.spotlightSearch().clear().type(bot2Name);
cy.spotlightResults().should("have.length", 1);
cy.spotlightResults().eq(0).should("contain", bot2Name);
cy.spotlightResults().eq(0).click();
});
// Send first message to actually start DM
cy.roomHeaderName().should("contain", bot2Name);
cy.findByRole("textbox", { name: "Send a message…" }).type("Hey!{enter}");
// Assert DM exists by checking for the first message and the room being in the room list
cy.contains(".mx_EventTile_body", "Hey!", { timeout: 30000 });
cy.findByRole("group", { name: "People" }).should("contain", bot2Name);
// Invite BotBob into existing DM with ByteBot
cy.getDmRooms(bot2.getUserId())
.should("have.length", 1)
.then((dmRooms) => cy.getClient().then((client) => client.getRoom(dmRooms[0])))
.then((groupDm) => {
cy.inviteUser(groupDm.roomId, bot1.getUserId());
cy.roomHeaderName().should(($element) => expect($element.get(0).innerText).contains(groupDm.name));
cy.findByRole("group", { name: "People" }).should(($element) =>
expect($element.get(0).innerText).contains(groupDm.name),
);
// Search for BotBob by id, should return group DM and user
cy.openSpotlightDialog().within(() => {
cy.spotlightFilter(Filter.People);
cy.spotlightSearch().clear().type(bot1.getUserId());
cy.wait(1000); // wait for the dialog code to settle
cy.spotlightResults().should("have.length", 2);
cy.contains(
".mx_SpotlightDialog_section.mx_SpotlightDialog_results .mx_SpotlightDialog_option",
groupDm.name,
);
});
// Search for ByteBot by id, should return group DM and user
cy.openSpotlightDialog().within(() => {
cy.spotlightFilter(Filter.People);
cy.spotlightSearch().clear().type(bot2.getUserId());
cy.wait(1000); // wait for the dialog code to settle
cy.spotlightResults().should("have.length", 2);
cy.contains(
".mx_SpotlightDialog_section.mx_SpotlightDialog_results .mx_SpotlightDialog_option",
groupDm.name,
);
});
});
});
// Test against https://github.com/vector-im/element-web/issues/22851
it("should show each person result only once", () => {
cy.openSpotlightDialog().within(() => {
cy.wait(500); // Wait for dialog to settle
cy.spotlightFilter(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++) {
cy.log("Iteration: " + i);
cy.spotlightSearch().clear().type(bot1.getUserId());
cy.wait(1000); // wait for the dialog code to settle
cy.spotlightResults().should("have.length", 1);
cy.spotlightResults().eq(0).should("contain", bot1.getUserId());
}
});
});
it("should allow opening group chat dialog", () => {
cy.openSpotlightDialog()
.within(() => {
cy.wait(500); // Wait for dialog to settle
cy.spotlightFilter(Filter.People);
cy.spotlightSearch().clear().type(bot2Name);
cy.wait(3000); // wait for the dialog code to settle
cy.spotlightResults().should("have.length", 1);
cy.spotlightResults().eq(0).should("contain", bot2Name);
cy.get(".mx_SpotlightDialog_startGroupChat").should("contain", "Start a group chat");
cy.get(".mx_SpotlightDialog_startGroupChat").click();
})
.then(() => {
cy.findByRole("dialog").should("contain", "Direct Messages");
});
});
it("should close spotlight after starting a DM", () => {
cy.startDM(bot1Name);
cy.get(".mx_SpotlightDialog").should("have.length", 0);
});
it("should show the same user only once", () => {
cy.startDM(bot1Name);
cy.visit("/#/home");
cy.openSpotlightDialog().within(() => {
cy.wait(500); // Wait for dialog to settle
cy.spotlightFilter(Filter.People);
cy.spotlightSearch().clear().type(bot1Name);
cy.wait(3000); // wait for the dialog code to settle
cy.get(".mx_Spinner").should("not.exist");
cy.spotlightResults().should("have.length", 1);
});
});
it("should be able to navigate results via keyboard", () => {
cy.openSpotlightDialog().within(() => {
cy.wait(500); // Wait for dialog to settle
cy.spotlightFilter(Filter.People);
cy.spotlightSearch().clear().type("b");
// our debouncing logic only starts the search after a short timeout,
// so we wait a few milliseconds.
cy.wait(1000);
cy.get(".mx_Spinner")
.should("not.exist")
.then(() => {
cy.wait(500); // Wait to settle again
cy.spotlightResults()
.should("have.length", 2)
.then(() => {
cy.spotlightResults().eq(0).should("have.attr", "aria-selected", "true");
cy.spotlightResults().eq(1).should("have.attr", "aria-selected", "false");
});
cy.spotlightSearch()
.type("{downArrow}")
.then(() => {
cy.spotlightResults().eq(0).should("have.attr", "aria-selected", "false");
cy.spotlightResults().eq(1).should("have.attr", "aria-selected", "true");
});
cy.spotlightSearch()
.type("{downArrow}")
.then(() => {
cy.spotlightResults().eq(0).should("have.attr", "aria-selected", "false");
cy.spotlightResults().eq(1).should("have.attr", "aria-selected", "false");
});
cy.spotlightSearch()
.type("{upArrow}")
.then(() => {
cy.spotlightResults().eq(0).should("have.attr", "aria-selected", "false");
cy.spotlightResults().eq(1).should("have.attr", "aria-selected", "true");
});
cy.spotlightSearch()
.type("{upArrow}")
.then(() => {
cy.spotlightResults().eq(0).should("have.attr", "aria-selected", "true");
cy.spotlightResults().eq(1).should("have.attr", "aria-selected", "false");
});
});
});
});
});

View file

@ -0,0 +1,393 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { test, 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";
function roomHeaderName(page: Page): Locator {
return page.locator(".mx_LegacyRoomHeader_nametext");
}
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();
}
test.describe("Spotlight", () => {
const bot1Name = "BotBob";
let bot1: Bot;
const bot2Name = "ByteBot";
let bot2: Bot;
const room1Name = "247";
let room1Id: string;
const room2Name = "Lounge";
let room2Id: string;
const room3Name = "Public";
let room3Id: string;
test.use({
displayName: "Jim",
});
test.beforeEach(async ({ page, homeserver, app, user }) => {
bot1 = new Bot(page, homeserver, { displayName: bot1Name, autoAcceptInvites: true });
bot2 = new Bot(page, homeserver, { displayName: bot2Name, autoAcceptInvites: true });
const Visibility = await page.evaluate(() => (window as any).matrixcs.Visibility);
room1Id = await app.client.createRoom({ name: room1Name, visibility: Visibility.Public });
await bot1.joinRoom(room1Id);
const bot1UserId = await bot1.evaluate((client) => client.getUserId());
room2Id = await bot2.createRoom({ name: room2Name, visibility: Visibility.Public });
await bot2.inviteUser(room2Id, bot1UserId);
room3Id = await bot2.createRoom({
name: room3Name,
visibility: Visibility.Public,
initial_state: [
{
type: "m.room.history_visibility",
state_key: "",
content: {
history_visibility: "world_readable",
},
},
],
});
await bot2.inviteUser(room3Id, bot1UserId);
await page.goto("/#/room/" + room1Id);
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 }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.search(room1Name);
const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
await expect(resultLocator.first()).toContainText(room1Name);
await resultLocator.first().click();
expect(page.url()).toContain(room1Id);
await expect(roomHeaderName(page)).toContainText(room1Name);
});
test("should find known public rooms", async ({ page, app }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.PublicRooms);
await spotlight.search(room1Name);
const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
await expect(resultLocator.first()).toContainText(room1Name);
await expect(resultLocator.first()).toContainText("View");
await resultLocator.first().click();
expect(page.url()).toContain(room1Id);
await expect(roomHeaderName(page)).toContainText(room1Name);
});
test("should find unknown public rooms", async ({ page, app }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.PublicRooms);
await spotlight.search(room2Name);
const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
await expect(resultLocator.first()).toContainText(room2Name);
await expect(resultLocator.first()).toContainText("Join");
await resultLocator.first().click();
expect(page.url()).toContain(room2Id);
await expect(page.locator(".mx_RoomView_MessageList")).toHaveCount(1);
await expect(roomHeaderName(page)).toContainText(room2Name);
});
test("should find unknown public world readable rooms", async ({ page, app }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.PublicRooms);
await spotlight.search(room3Name);
const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
await expect(resultLocator.first()).toContainText(room3Name);
await expect(resultLocator.first()).toContainText("View");
await resultLocator.first().click();
expect(page.url()).toContain(room3Id);
await page.getByRole("button", { name: "Join the discussion" }).click();
await expect(roomHeaderName(page)).toHaveText(room3Name);
});
// 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 }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.PublicRooms);
await spotlight.search(room3Name);
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(room3Name);
await expect(resultLocator.first()).toContainText(room3Id);
});
test("should find known people", 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(bot1Name);
const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
await expect(resultLocator.first()).toContainText(bot1Name);
await resultLocator.first().click();
await expect(roomHeaderName(page)).toHaveText(bot1Name);
});
/**
* 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.skip("should find unknown people", 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(bot2Name);
const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
await expect(resultLocator.first()).toContainText(bot2Name);
await resultLocator.first().click();
await expect(roomHeaderName(page)).toHaveText(bot2Name);
});
test("should find group DMs by usernames or user ids", async ({ page, app }) => {
// First we want to share a room with both bots to ensure weve got their usernames cached
const bot2UserId = await bot2.evaluate((client) => client.getUserId());
await app.client.inviteUser(room1Id, bot2UserId);
// 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(bot2Name);
let resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
await expect(resultLocator.first()).toContainText(bot2Name);
await resultLocator.first().click();
// Send first message to actually start DM
await expect(roomHeaderName(page)).toHaveText(bot2Name);
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(bot2Name);
// Invite BotBob into existing DM with ByteBot
const dmRooms = await app.client.evaluate((client, userId) => {
const map = client.getAccountData("m.direct")?.getContent<Record<string, string[]>>();
return map[userId] ?? [];
}, bot2UserId);
expect(dmRooms).toHaveLength(1);
const groupDmName = await app.client.evaluate((client, id) => client.getRoom(id).name, dmRooms[0]);
const bot1UserId = await bot1.evaluate((client) => client.getUserId());
await app.client.inviteUser(dmRooms[0], bot1UserId);
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(bot1UserId);
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(bot2UserId);
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 }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.People);
const bot1UserId = await bot1.evaluate((client) => client.getUserId());
// 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(bot1UserId);
await page.waitForTimeout(1000); // wait for the dialog to settle
const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
await expect(resultLocator.first()).toContainText(bot1UserId);
}
});
test("should allow opening group chat dialog", 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(bot2Name);
await page.waitForTimeout(3000); // wait for the dialog to settle
const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
await expect(resultLocator.first()).toContainText(bot2Name);
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 }) => {
await startDM(app, page, bot1Name);
await expect(page.locator(".mx_SpotlightDialog")).toHaveCount(0);
});
test("should show the same user only once", async ({ page, app }) => {
await startDM(app, page, bot1Name);
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(bot1Name);
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");
});
});

View file

@ -28,8 +28,13 @@ export class Spotlight {
constructor(private page: Page) {} constructor(private page: Page) {}
public async open() { public async open() {
await this.page.keyboard.press(`${CommandOrControl}+KeyK`);
this.root = this.page.locator('[role=dialog][aria-label="Search Dialog"]'); this.root = this.page.locator('[role=dialog][aria-label="Search Dialog"]');
const isSpotlightAlreadyOpen = !!(await this.root.count());
if (isSpotlightAlreadyOpen) {
// Close dialog if it is already open
await this.page.keyboard.press(`${CommandOrControl}+KeyK`);
}
await this.page.keyboard.press(`${CommandOrControl}+KeyK`);
} }
public async filter(filter: Filter) { public async filter(filter: Filter) {
@ -49,10 +54,18 @@ export class Spotlight {
} }
public async search(query: string) { public async search(query: string) {
await this.root.locator(".mx_SpotlightDialog_searchBox").getByRole("textbox", { name: "Search" }).fill(query); await this.searchBox.getByRole("textbox", { name: "Search" }).fill(query);
}
public get searchBox() {
return this.root.locator(".mx_SpotlightDialog_searchBox");
} }
public get results() { public get results() {
return this.root.locator(".mx_SpotlightDialog_section.mx_SpotlightDialog_results .mx_SpotlightDialog_option"); return this.root.locator(".mx_SpotlightDialog_section.mx_SpotlightDialog_results .mx_SpotlightDialog_option");
} }
public get dialog() {
return this.root;
}
} }