From e92ca4fcd2c43233ab5722a650ec8928393c17fa Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 12 Dec 2023 14:08:36 +0000
Subject: [PATCH] Migrate knock/* from Cypress to Playwright (#12030)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
cypress/e2e/knock/create-knock-room.spec.ts | 136 --------
cypress/e2e/knock/knock-into-room.spec.ts | 318 ------------------
cypress/e2e/knock/manage-knocks.spec.ts | 142 --------
.../e2e/knock/create-knock-room.spec.ts | 92 +++++
playwright/e2e/knock/knock-into-room.spec.ts | 301 +++++++++++++++++
playwright/e2e/knock/manage-knocks.spec.ts | 118 +++++++
playwright/e2e/utils.ts | 66 ++++
playwright/element-web-test.ts | 17 +-
playwright/pages/ElementAppPage.ts | 7 +
playwright/pages/Spotlight.ts | 58 ++++
playwright/pages/client.ts | 67 ++++
11 files changed, 721 insertions(+), 601 deletions(-)
delete mode 100644 cypress/e2e/knock/create-knock-room.spec.ts
delete mode 100644 cypress/e2e/knock/knock-into-room.spec.ts
delete mode 100644 cypress/e2e/knock/manage-knocks.spec.ts
create mode 100644 playwright/e2e/knock/create-knock-room.spec.ts
create mode 100644 playwright/e2e/knock/knock-into-room.spec.ts
create mode 100644 playwright/e2e/knock/manage-knocks.spec.ts
create mode 100644 playwright/e2e/utils.ts
create mode 100644 playwright/pages/Spotlight.ts
diff --git a/cypress/e2e/knock/create-knock-room.spec.ts b/cypress/e2e/knock/create-knock-room.spec.ts
deleted file mode 100644
index dbbcf49492..0000000000
--- a/cypress/e2e/knock/create-knock-room.spec.ts
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
-Copyright 2022-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 { HomeserverInstance } from "../../plugins/utils/homeserver";
-import { waitForRoom } from "../utils";
-import { Filter } from "../../support/settings";
-
-describe("Create Knock Room", () => {
- let homeserver: HomeserverInstance;
-
- beforeEach(() => {
- cy.enableLabsFeature("feature_ask_to_join");
-
- cy.startHomeserver("default").then((data) => {
- homeserver = data;
-
- cy.initTestUser(homeserver, "Alice");
- });
- });
-
- afterEach(() => {
- cy.stopHomeserver(homeserver);
- });
-
- it("should create a knock room", () => {
- cy.openCreateRoomDialog().within(() => {
- cy.findByRole("textbox", { name: "Name" }).type("Cybersecurity");
- cy.findByRole("button", { name: "Room visibility" }).click();
- cy.findByRole("option", { name: "Ask to join" }).click();
-
- cy.findByRole("button", { name: "Create room" }).click();
- });
-
- cy.get(".mx_LegacyRoomHeader").within(() => {
- cy.findByText("Cybersecurity");
- });
-
- cy.hash().then((urlHash) => {
- const roomId = urlHash.replace("#/room/", "");
-
- // Room should have a knock join rule
- cy.window().then(async (win) => {
- await waitForRoom(win, win.mxMatrixClientPeg.get(), roomId, (room) => {
- const events = room.getLiveTimeline().getEvents();
- return events.some(
- (e) => e.getType() === "m.room.join_rules" && e.getContent().join_rule === "knock",
- );
- });
- });
- });
- });
-
- it("should create a room and change a join rule to knock", () => {
- cy.openCreateRoomDialog().within(() => {
- cy.findByRole("textbox", { name: "Name" }).type("Cybersecurity");
-
- cy.findByRole("button", { name: "Create room" }).click();
- });
-
- cy.get(".mx_LegacyRoomHeader").within(() => {
- cy.findByText("Cybersecurity");
- });
-
- cy.hash().then((urlHash) => {
- const roomId = urlHash.replace("#/room/", "");
-
- cy.openRoomSettings("Security & Privacy");
-
- cy.findByRole("group", { name: "Access" }).within(() => {
- cy.findByRole("radio", { name: "Private (invite only)" }).should("be.checked");
- cy.findByRole("radio", { name: "Ask to join" }).check({ force: true });
- });
-
- // Room should have a knock join rule
- cy.window().then(async (win) => {
- await waitForRoom(win, win.mxMatrixClientPeg.get(), roomId, (room) => {
- const events = room.getLiveTimeline().getEvents();
- return events.some(
- (e) => e.getType() === "m.room.join_rules" && e.getContent().join_rule === "knock",
- );
- });
- });
- });
- });
-
- it("should create a public knock room", () => {
- cy.openCreateRoomDialog().within(() => {
- cy.findByRole("textbox", { name: "Name" }).type("Cybersecurity");
- cy.findByRole("button", { name: "Room visibility" }).click();
- cy.findByRole("option", { name: "Ask to join" }).click();
- cy.findByRole("checkbox", { name: "Make this room visible in the public room directory." }).click({
- force: true,
- });
-
- cy.findByRole("button", { name: "Create room" }).click();
- });
-
- cy.get(".mx_LegacyRoomHeader").within(() => {
- cy.findByText("Cybersecurity");
- });
-
- cy.hash().then((urlHash) => {
- const roomId = urlHash.replace("#/room/", "");
-
- // Room should have a knock join rule
- cy.window().then(async (win) => {
- await waitForRoom(win, win.mxMatrixClientPeg.get(), roomId, (room) => {
- const events = room.getLiveTimeline().getEvents();
- return events.some(
- (e) => e.getType() === "m.room.join_rules" && e.getContent().join_rule === "knock",
- );
- });
- });
- });
-
- cy.openSpotlightDialog().within(() => {
- cy.spotlightFilter(Filter.PublicRooms);
- cy.spotlightResults().eq(0).should("contain", "Cybersecurity");
- });
- });
-});
diff --git a/cypress/e2e/knock/knock-into-room.spec.ts b/cypress/e2e/knock/knock-into-room.spec.ts
deleted file mode 100644
index 4d6a0eebe7..0000000000
--- a/cypress/e2e/knock/knock-into-room.spec.ts
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
-Copyright 2023 Mikhail Aheichyk
-Copyright 2023 Nordeck IT + Consulting GmbH.
-
-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 type { MatrixClient } from "matrix-js-sdk/src/matrix";
-import { HomeserverInstance } from "../../plugins/utils/homeserver";
-import { UserCredentials } from "../../support/login";
-import { waitForRoom } from "../utils";
-import { Filter } from "../../support/settings";
-
-describe("Knock Into Room", () => {
- let homeserver: HomeserverInstance;
- let user: UserCredentials;
- let bot: MatrixClient;
-
- let roomId;
-
- beforeEach(() => {
- cy.enableLabsFeature("feature_ask_to_join");
-
- cy.startHomeserver("default").then((data) => {
- homeserver = data;
-
- cy.initTestUser(homeserver, "Alice").then((_user) => {
- user = _user;
- });
-
- cy.getBot(homeserver, { displayName: "Bob" }).then(async (_bot) => {
- bot = _bot;
-
- const { room_id: newRoomId } = await bot.createRoom({
- name: "Cybersecurity",
- initial_state: [
- {
- type: "m.room.join_rules",
- content: {
- join_rule: "knock",
- },
- state_key: "",
- },
- ],
- });
-
- roomId = newRoomId;
- });
- });
- });
-
- afterEach(() => {
- cy.stopHomeserver(homeserver);
- });
-
- it("should knock into the room then knock is approved and user joins the room then user is kicked and joins again", () => {
- cy.viewRoomById(roomId);
-
- cy.get(".mx_RoomPreviewBar").within(() => {
- cy.findByRole("button", { name: "Join the discussion" }).click();
-
- cy.findByRole("heading", { name: "Ask to join?" });
- cy.findByRole("textbox");
- cy.findByRole("button", { name: "Request access" }).click();
-
- cy.findByRole("heading", { name: "Request to join sent" });
- });
-
- // Knocked room should appear in Rooms
- cy.findByRole("group", { name: "Rooms" }).findByRole("treeitem", { name: "Cybersecurity" });
-
- cy.window().then(async (win) => {
- // bot waits for knock request from Alice
- await waitForRoom(win, bot, roomId, (room) => {
- const events = room.getLiveTimeline().getEvents();
- return events.some(
- (e) =>
- e.getType() === "m.room.member" &&
- e.getContent()?.membership === "knock" &&
- e.getContent()?.displayname === "Alice",
- );
- });
-
- // bot invites Alice
- await bot.invite(roomId, user.userId);
- });
-
- cy.findByRole("group", { name: "Invites" }).findByRole("treeitem", { name: "Cybersecurity" });
-
- // Alice have to accept invitation in order to join the room.
- // It will be not needed when homeserver implements auto accept knock requests.
- cy.get(".mx_RoomView").findByRole("button", { name: "Accept" }).click();
-
- cy.findByRole("group", { name: "Rooms" }).findByRole("treeitem", { name: "Cybersecurity" });
-
- cy.findByText("Alice joined the room").should("exist");
-
- cy.window().then(async (win) => {
- // bot kicks Alice
- await bot.kick(roomId, user.userId);
- });
-
- cy.get(".mx_RoomPreviewBar").within(() => {
- cy.findByRole("button", { name: "Re-join" }).click();
-
- cy.findByRole("heading", { name: "Ask to join Cybersecurity?" });
- cy.findByRole("button", { name: "Request access" }).click();
- });
-
- cy.window().then(async (win) => {
- // bot waits for knock request from Alice
- await waitForRoom(win, bot, roomId, (room) => {
- const events = room.getLiveTimeline().getEvents();
- return events.some(
- (e) =>
- e.getType() === "m.room.member" &&
- e.getContent()?.membership === "knock" &&
- e.getContent()?.displayname === "Alice",
- );
- });
-
- // bot invites Alice
- await bot.invite(roomId, user.userId);
- });
-
- // Alice have to accept invitation in order to join the room.
- // It will be not needed when homeserver implements auto accept knock requests.
- cy.get(".mx_RoomView").findByRole("button", { name: "Accept" }).click();
-
- cy.findByText("Alice was invited, joined, was removed, was invited, and joined").should("exist");
- });
-
- it("should knock into the room then knock is approved and user joins the room then user is banned/unbanned and joins again", () => {
- cy.viewRoomById(roomId);
-
- cy.get(".mx_RoomPreviewBar").within(() => {
- cy.findByRole("button", { name: "Join the discussion" }).click();
-
- cy.findByRole("heading", { name: "Ask to join?" });
- cy.findByRole("textbox");
- cy.findByRole("button", { name: "Request access" }).click();
-
- cy.findByRole("heading", { name: "Request to join sent" });
- });
-
- // Knocked room should appear in Rooms
- cy.findByRole("group", { name: "Rooms" }).findByRole("treeitem", { name: "Cybersecurity" });
-
- cy.window().then(async (win) => {
- // bot waits for knock request from Alice
- await waitForRoom(win, bot, roomId, (room) => {
- const events = room.getLiveTimeline().getEvents();
- return events.some(
- (e) =>
- e.getType() === "m.room.member" &&
- e.getContent()?.membership === "knock" &&
- e.getContent()?.displayname === "Alice",
- );
- });
-
- // bot invites Alice
- await bot.invite(roomId, user.userId);
- });
-
- cy.findByRole("group", { name: "Invites" }).findByRole("treeitem", { name: "Cybersecurity" });
-
- // Alice have to accept invitation in order to join the room.
- // It will be not needed when homeserver implements auto accept knock requests.
- cy.get(".mx_RoomView").findByRole("button", { name: "Accept" }).click();
-
- cy.findByRole("group", { name: "Rooms" }).findByRole("treeitem", { name: "Cybersecurity" });
-
- cy.findByText("Alice joined the room").should("exist");
-
- cy.window().then(async (win) => {
- // bot bans Alice
- await bot.ban(roomId, user.userId);
- });
-
- cy.get(".mx_RoomPreviewBar").findByText("You were banned from Cybersecurity by Bob").should("exist");
-
- cy.window().then(async (win) => {
- // bot unbans Alice
- await bot.unban(roomId, user.userId);
- });
-
- cy.get(".mx_RoomPreviewBar").within(() => {
- cy.findByRole("button", { name: "Re-join" }).click();
-
- cy.findByRole("heading", { name: "Ask to join Cybersecurity?" });
- cy.findByRole("button", { name: "Request access" }).click();
- });
-
- cy.window().then(async (win) => {
- // bot waits for knock request from Alice
- await waitForRoom(win, bot, roomId, (room) => {
- const events = room.getLiveTimeline().getEvents();
- return events.some(
- (e) =>
- e.getType() === "m.room.member" &&
- e.getContent()?.membership === "knock" &&
- e.getContent()?.displayname === "Alice",
- );
- });
-
- // bot invites Alice
- await bot.invite(roomId, user.userId);
- });
-
- // Alice have to accept invitation in order to join the room.
- // It will be not needed when homeserver implements auto accept knock requests.
- cy.get(".mx_RoomView").findByRole("button", { name: "Accept" }).click();
-
- cy.findByText("Alice was invited, joined, was banned, was unbanned, was invited, and joined").should("exist");
- });
-
- it("should knock into the room and knock is cancelled by user himself", () => {
- cy.viewRoomById(roomId);
-
- cy.get(".mx_RoomPreviewBar").within(() => {
- cy.findByRole("button", { name: "Join the discussion" }).click();
-
- cy.findByRole("heading", { name: "Ask to join?" });
- cy.findByRole("textbox");
- cy.findByRole("button", { name: "Request access" }).click();
-
- cy.findByRole("heading", { name: "Request to join sent" });
- });
-
- // Knocked room should appear in Rooms
- cy.findByRole("group", { name: "Rooms" }).findByRole("treeitem", { name: "Cybersecurity" });
-
- cy.get(".mx_RoomPreviewBar").within(() => {
- cy.findByRole("button", { name: "Cancel request" }).click();
-
- cy.findByRole("heading", { name: "Ask to join Cybersecurity?" });
- cy.findByRole("button", { name: "Request access" });
- });
-
- cy.findByRole("group", { name: "Historical" }).findByRole("treeitem", { name: "Cybersecurity" });
- });
-
- it("should knock into the room then knock is cancelled by another user and room is forgotten", () => {
- cy.viewRoomById(roomId);
-
- cy.get(".mx_RoomPreviewBar").within(() => {
- cy.findByRole("button", { name: "Join the discussion" }).click();
-
- cy.findByRole("heading", { name: "Ask to join?" });
- cy.findByRole("textbox");
- cy.findByRole("button", { name: "Request access" }).click();
-
- cy.findByRole("heading", { name: "Request to join sent" });
- });
-
- // Knocked room should appear in Rooms
- cy.findByRole("group", { name: "Rooms" }).findByRole("treeitem", { name: "Cybersecurity" });
-
- cy.window().then(async (win) => {
- // bot waits for knock request from Alice
- await waitForRoom(win, bot, roomId, (room) => {
- const events = room.getLiveTimeline().getEvents();
- return events.some(
- (e) =>
- e.getType() === "m.room.member" &&
- e.getContent()?.membership === "knock" &&
- e.getContent()?.displayname === "Alice",
- );
- });
-
- // bot kicks Alice
- await bot.kick(roomId, user.userId);
- });
-
- // Room should stay in Rooms and have red badge when knock is denied
- cy.findByRole("group", { name: "Rooms" }).findByRole("treeitem", { name: "Cybersecurity" }).should("not.exist");
- cy.findByRole("group", { name: "Rooms" }).findByRole("treeitem", { name: "Cybersecurity 1 unread mention." });
-
- cy.get(".mx_RoomPreviewBar").within(() => {
- cy.findByRole("heading", { name: "You have been denied access" });
- cy.findByRole("button", { name: "Forget this room" }).click();
- });
-
- // Room should disappear from the list completely when forgotten
- // Should be enabled when issue is fixed: https://github.com/vector-im/element-web/issues/26195
- // cy.findByRole("treeitem", { name: /Cybersecurity/ }).should("not.exist");
- });
-
- it("should knock into the public knock room via spotlight", () => {
- cy.window().then((win) => {
- bot.setRoomDirectoryVisibility(roomId, win.matrixcs.Visibility.Public);
- });
-
- cy.openSpotlightDialog().within(() => {
- cy.spotlightFilter(Filter.PublicRooms);
- cy.spotlightResults().eq(0).should("contain", "Cybersecurity");
- cy.spotlightResults().eq(0).click();
- });
-
- cy.get(".mx_RoomPreviewBar").within(() => {
- cy.findByRole("heading", { name: "Ask to join?" });
- cy.findByRole("textbox");
- cy.findByRole("button", { name: "Request access" }).click();
-
- cy.findByRole("heading", { name: "Request to join sent" });
- });
- });
-});
diff --git a/cypress/e2e/knock/manage-knocks.spec.ts b/cypress/e2e/knock/manage-knocks.spec.ts
deleted file mode 100644
index f31f206a9b..0000000000
--- a/cypress/e2e/knock/manage-knocks.spec.ts
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
-Copyright 2023 Mikhail Aheichyk
-Copyright 2023 Nordeck IT + Consulting GmbH.
-
-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 type { MatrixClient } from "matrix-js-sdk/src/matrix";
-import { HomeserverInstance } from "../../plugins/utils/homeserver";
-import { waitForRoom } from "../utils";
-
-describe("Manage Knocks", () => {
- let homeserver: HomeserverInstance;
- let bot: MatrixClient;
- let roomId: string;
-
- beforeEach(() => {
- cy.enableLabsFeature("feature_ask_to_join");
-
- cy.startHomeserver("default").then((data) => {
- homeserver = data;
-
- cy.initTestUser(homeserver, "Alice");
-
- cy.createRoom({
- name: "Cybersecurity",
- initial_state: [
- {
- type: "m.room.join_rules",
- content: {
- join_rule: "knock",
- },
- state_key: "",
- },
- ],
- }).then((newRoomId) => {
- roomId = newRoomId;
- cy.viewRoomById(newRoomId);
- });
-
- cy.getBot(homeserver, { displayName: "Bob" }).then(async (_bot) => {
- bot = _bot;
- });
- });
- });
-
- afterEach(() => {
- cy.stopHomeserver(homeserver);
- });
-
- it("should approve knock using bar", () => {
- bot.knockRoom(roomId);
-
- cy.get(".mx_RoomKnocksBar").within(() => {
- cy.findByRole("heading", { name: "Asking to join" });
- cy.findByText(/^Bob/);
- cy.findByRole("button", { name: "Approve" }).click();
- });
-
- cy.get(".mx_RoomKnocksBar").should("not.exist");
-
- cy.findByText("Alice invited Bob");
- });
-
- it("should deny knock using bar", () => {
- bot.knockRoom(roomId);
-
- cy.get(".mx_RoomKnocksBar").within(() => {
- cy.findByRole("heading", { name: "Asking to join" });
- cy.findByText(/^Bob/);
- cy.findByRole("button", { name: "Deny" }).click();
- });
-
- cy.get(".mx_RoomKnocksBar").should("not.exist");
-
- // Should receive Bob's "m.room.member" with "leave" membership when access is denied
- cy.window().then(async (win) => {
- await waitForRoom(win, win.mxMatrixClientPeg.get(), roomId, (room) => {
- const events = room.getLiveTimeline().getEvents();
- return events.some(
- (e) =>
- e.getType() === "m.room.member" &&
- e.getContent()?.membership === "leave" &&
- e.getContent()?.displayname === "Bob",
- );
- });
- });
- });
-
- it("should approve knock using people tab", () => {
- bot.knockRoom(roomId, { reason: "Hello, can I join?" });
-
- cy.openRoomSettings("People");
-
- cy.findByRole("group", { name: "Asking to join" }).within(() => {
- cy.findByText(/^Bob/);
- cy.findByText("Hello, can I join?");
- cy.findByRole("button", { name: "Approve" }).click();
-
- cy.findByText(/^Bob/).should("not.exist");
- });
-
- cy.findByText("Alice invited Bob");
- });
-
- it("should deny knock using people tab", () => {
- bot.knockRoom(roomId, { reason: "Hello, can I join?" });
-
- cy.openRoomSettings("People");
-
- cy.findByRole("group", { name: "Asking to join" }).within(() => {
- cy.findByText(/^Bob/);
- cy.findByText("Hello, can I join?");
- cy.findByRole("button", { name: "Deny" }).click();
-
- cy.findByText(/^Bob/).should("not.exist");
- });
-
- // Should receive Bob's "m.room.member" with "leave" membership when access is denied
- cy.window().then(async (win) => {
- await waitForRoom(win, win.mxMatrixClientPeg.get(), roomId, (room) => {
- const events = room.getLiveTimeline().getEvents();
- return events.some(
- (e) =>
- e.getType() === "m.room.member" &&
- e.getContent()?.membership === "leave" &&
- e.getContent()?.displayname === "Bob",
- );
- });
- });
- });
-});
diff --git a/playwright/e2e/knock/create-knock-room.spec.ts b/playwright/e2e/knock/create-knock-room.spec.ts
new file mode 100644
index 0000000000..8763c0fd6a
--- /dev/null
+++ b/playwright/e2e/knock/create-knock-room.spec.ts
@@ -0,0 +1,92 @@
+/*
+Copyright 2022-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 { waitForRoom } from "../utils";
+import { Filter } from "../../pages/Spotlight";
+
+test.describe("Create Knock Room", () => {
+ test.use({
+ displayName: "Alice",
+ labsFlags: ["feature_ask_to_join"],
+ });
+
+ test("should create a knock room", async ({ page, app, user }) => {
+ const dialog = await app.openCreateRoomDialog();
+ await dialog.getByRole("textbox", { name: "Name" }).fill("Cybersecurity");
+ await dialog.getByRole("button", { name: "Room visibility" }).click();
+ await dialog.getByRole("option", { name: "Ask to join" }).click();
+ await dialog.getByRole("button", { name: "Create room" }).click();
+
+ await expect(page.locator(".mx_LegacyRoomHeader").getByText("Cybersecurity")).toBeVisible();
+
+ const urlHash = await page.evaluate(() => window.location.hash);
+ const roomId = urlHash.replace("#/room/", "");
+
+ // Room should have a knock join rule
+ await waitForRoom(page, app.client, roomId, (room) => {
+ const events = room.getLiveTimeline().getEvents();
+ return events.some((e) => e.getType() === "m.room.join_rules" && e.getContent().join_rule === "knock");
+ });
+ });
+
+ test("should create a room and change a join rule to knock", async ({ page, app, user }) => {
+ const dialog = await app.openCreateRoomDialog();
+ await dialog.getByRole("textbox", { name: "Name" }).fill("Cybersecurity");
+ await dialog.getByRole("button", { name: "Create room" }).click();
+
+ await expect(page.locator(".mx_LegacyRoomHeader").getByText("Cybersecurity")).toBeVisible();
+
+ const urlHash = await page.evaluate(() => window.location.hash);
+ const roomId = urlHash.replace("#/room/", "");
+
+ await app.settings.openRoomSettings("Security & Privacy");
+
+ const settingsGroup = page.getByRole("group", { name: "Access" });
+ await expect(settingsGroup.getByRole("radio", { name: "Private (invite only)" })).toBeChecked();
+ await settingsGroup.getByText("Ask to join").click();
+
+ // Room should have a knock join rule
+ await waitForRoom(page, app.client, roomId, (room) => {
+ const events = room.getLiveTimeline().getEvents();
+ return events.some((e) => e.getType() === "m.room.join_rules" && e.getContent().join_rule === "knock");
+ });
+ });
+
+ test("should create a public knock room", async ({ page, app, user }) => {
+ const dialog = await app.openCreateRoomDialog();
+ await dialog.getByRole("textbox", { name: "Name" }).fill("Cybersecurity");
+ await dialog.getByRole("button", { name: "Room visibility" }).click();
+ await dialog.getByRole("option", { name: "Ask to join" }).click();
+ await dialog.getByText("Make this room visible in the public room directory.").click();
+ await dialog.getByRole("button", { name: "Create room" }).click();
+
+ await expect(page.locator(".mx_LegacyRoomHeader").getByText("Cybersecurity")).toBeVisible();
+
+ const urlHash = await page.evaluate(() => window.location.hash);
+ const roomId = urlHash.replace("#/room/", "");
+
+ // Room should have a knock join rule
+ await waitForRoom(page, app.client, roomId, (room) => {
+ const events = room.getLiveTimeline().getEvents();
+ return events.some((e) => e.getType() === "m.room.join_rules" && e.getContent().join_rule === "knock");
+ });
+
+ const spotlightDialog = await app.openSpotlight();
+ await spotlightDialog.filter(Filter.PublicRooms);
+ await expect(spotlightDialog.results.nth(0)).toContainText("Cybersecurity");
+ });
+});
diff --git a/playwright/e2e/knock/knock-into-room.spec.ts b/playwright/e2e/knock/knock-into-room.spec.ts
new file mode 100644
index 0000000000..21c5a145e3
--- /dev/null
+++ b/playwright/e2e/knock/knock-into-room.spec.ts
@@ -0,0 +1,301 @@
+/*
+Copyright 2023 Mikhail Aheichyk
+Copyright 2023 Nordeck IT + Consulting GmbH.
+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 type { Visibility } from "matrix-js-sdk/src/matrix";
+import { test, expect } from "../../element-web-test";
+import { waitForRoom } from "../utils";
+import { Filter } from "../../pages/Spotlight";
+
+test.describe("Knock Into Room", () => {
+ test.use({
+ displayName: "Alice",
+ labsFlags: ["feature_ask_to_join"],
+ botCreateOpts: {
+ displayName: "Bob",
+ },
+ room: async ({ bot }, use) => {
+ const roomId = await bot.createRoom({
+ name: "Cybersecurity",
+ initial_state: [
+ {
+ type: "m.room.join_rules",
+ content: {
+ join_rule: "knock",
+ },
+ state_key: "",
+ },
+ ],
+ });
+ await use({ roomId });
+ },
+ });
+
+ test("should knock into the room then knock is approved and user joins the room then user is kicked and joins again", async ({
+ page,
+ app,
+ user,
+ bot,
+ room,
+ }) => {
+ await app.viewRoomById(room.roomId);
+
+ const roomPreviewBar = page.locator(".mx_RoomPreviewBar");
+ await roomPreviewBar.getByRole("button", { name: "Join the discussion" }).click();
+ await expect(roomPreviewBar.getByRole("heading", { name: "Ask to join?" })).toBeVisible();
+ await expect(roomPreviewBar.getByRole("textbox")).toBeVisible();
+ await roomPreviewBar.getByRole("button", { name: "Request access" }).click();
+
+ await expect(roomPreviewBar.getByRole("heading", { name: "Request to join sent" })).toBeVisible();
+
+ // Knocked room should appear in Rooms
+ await expect(
+ page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity" }),
+ ).toBeVisible();
+
+ // bot waits for knock request from Alice
+ await waitForRoom(page, bot, room.roomId, (room) => {
+ const events = room.getLiveTimeline().getEvents();
+ return events.some(
+ (e) =>
+ e.getType() === "m.room.member" &&
+ e.getContent()?.membership === "knock" &&
+ e.getContent()?.displayname === "Alice",
+ );
+ });
+
+ // bot invites Alice
+ await bot.inviteUser(room.roomId, user.userId);
+
+ await expect(
+ page.getByRole("group", { name: "Invites" }).getByRole("treeitem", { name: "Cybersecurity" }),
+ ).toBeVisible();
+
+ // Alice have to accept invitation in order to join the room.
+ // It will be not needed when homeserver implements auto accept knock requests.
+ await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click();
+
+ await expect(
+ page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity" }),
+ ).toBeVisible();
+
+ await expect(page.getByText("Alice joined the room")).toBeVisible();
+
+ // bot kicks Alice
+ await bot.kick(room.roomId, user.userId);
+
+ await roomPreviewBar.getByRole("button", { name: "Re-join" }).click();
+ await expect(roomPreviewBar.getByRole("heading", { name: "Ask to join Cybersecurity?" })).toBeVisible();
+ await roomPreviewBar.getByRole("button", { name: "Request access" }).click();
+
+ // bot waits for knock request from Alice
+ await waitForRoom(page, bot, room.roomId, (room) => {
+ const events = room.getLiveTimeline().getEvents();
+ return events.some(
+ (e) =>
+ e.getType() === "m.room.member" &&
+ e.getContent()?.membership === "knock" &&
+ e.getContent()?.displayname === "Alice",
+ );
+ });
+
+ // bot invites Alice
+ await bot.inviteUser(room.roomId, user.userId);
+
+ // Alice have to accept invitation in order to join the room.
+ // It will be not needed when homeserver implements auto accept knock requests.
+ await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click();
+
+ await expect(page.getByText("Alice was invited, joined, was removed, was invited, and joined")).toBeVisible();
+ });
+
+ test("should knock into the room then knock is approved and user joins the room then user is banned/unbanned and joins again", async ({
+ page,
+ app,
+ user,
+ bot,
+ room,
+ }) => {
+ await app.viewRoomById(room.roomId);
+
+ const roomPreviewBar = page.locator(".mx_RoomPreviewBar");
+ await roomPreviewBar.getByRole("button", { name: "Join the discussion" }).click();
+ await expect(roomPreviewBar.getByRole("heading", { name: "Ask to join?" })).toBeVisible();
+ await expect(roomPreviewBar.getByRole("textbox")).toBeVisible();
+ await roomPreviewBar.getByRole("button", { name: "Request access" }).click();
+ await expect(roomPreviewBar.getByRole("heading", { name: "Request to join sent" })).toBeVisible();
+
+ // Knocked room should appear in Rooms
+ await expect(
+ page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity" }),
+ ).toBeVisible();
+
+ // bot waits for knock request from Alice
+ await waitForRoom(page, bot, room.roomId, (room) => {
+ const events = room.getLiveTimeline().getEvents();
+ return events.some(
+ (e) =>
+ e.getType() === "m.room.member" &&
+ e.getContent()?.membership === "knock" &&
+ e.getContent()?.displayname === "Alice",
+ );
+ });
+
+ // bot invites Alice
+ await bot.inviteUser(room.roomId, user.userId);
+
+ await expect(
+ page.getByRole("group", { name: "Invites" }).getByRole("treeitem", { name: "Cybersecurity" }),
+ ).toBeVisible();
+
+ // Alice have to accept invitation in order to join the room.
+ // It will be not needed when homeserver implements auto accept knock requests.
+ await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click();
+
+ await expect(
+ page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity" }),
+ ).toBeVisible();
+
+ await expect(page.getByText("Alice joined the room")).toBeVisible();
+
+ // bot bans Alice
+ await bot.ban(room.roomId, user.userId);
+
+ await expect(
+ page.locator(".mx_RoomPreviewBar").getByText("You were banned from Cybersecurity by Bob"),
+ ).toBeVisible();
+
+ // bot unbans Alice
+ await bot.unban(room.roomId, user.userId);
+
+ await roomPreviewBar.getByRole("button", { name: "Re-join" }).click();
+ await expect(roomPreviewBar.getByRole("heading", { name: "Ask to join Cybersecurity?" })).toBeVisible();
+ await roomPreviewBar.getByRole("button", { name: "Request access" }).click();
+
+ // bot waits for knock request from Alice
+ await waitForRoom(page, bot, room.roomId, (room) => {
+ const events = room.getLiveTimeline().getEvents();
+ return events.some(
+ (e) =>
+ e.getType() === "m.room.member" &&
+ e.getContent()?.membership === "knock" &&
+ e.getContent()?.displayname === "Alice",
+ );
+ });
+
+ // bot invites Alice
+ await bot.inviteUser(room.roomId, user.userId);
+
+ // Alice have to accept invitation in order to join the room.
+ // It will be not needed when homeserver implements auto accept knock requests.
+ await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click();
+
+ await expect(
+ page.getByText("Alice was invited, joined, was banned, was unbanned, was invited, and joined"),
+ ).toBeVisible();
+ });
+
+ test("should knock into the room and knock is cancelled by user himself", async ({ page, app, bot, room }) => {
+ await app.viewRoomById(room.roomId);
+
+ const roomPreviewBar = page.locator(".mx_RoomPreviewBar");
+ await roomPreviewBar.getByRole("button", { name: "Join the discussion" }).click();
+ await expect(roomPreviewBar.getByRole("heading", { name: "Ask to join?" })).toBeVisible();
+ await expect(roomPreviewBar.getByRole("textbox")).toBeVisible();
+ await roomPreviewBar.getByRole("button", { name: "Request access" }).click();
+ await expect(roomPreviewBar.getByRole("heading", { name: "Request to join sent" })).toBeVisible();
+
+ // Knocked room should appear in Rooms
+ page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity" });
+
+ await roomPreviewBar.getByRole("button", { name: "Cancel request" }).click();
+ await expect(roomPreviewBar.getByRole("heading", { name: "Ask to join Cybersecurity?" })).toBeVisible();
+ await expect(roomPreviewBar.getByRole("button", { name: "Request access" })).toBeVisible();
+
+ await expect(
+ page.getByRole("group", { name: "Historical" }).getByRole("treeitem", { name: "Cybersecurity" }),
+ ).toBeVisible();
+ });
+
+ test("should knock into the room then knock is cancelled by another user and room is forgotten", async ({
+ page,
+ app,
+ user,
+ bot,
+ room,
+ }) => {
+ await app.viewRoomById(room.roomId);
+
+ const roomPreviewBar = page.locator(".mx_RoomPreviewBar");
+ await roomPreviewBar.getByRole("button", { name: "Join the discussion" }).click();
+ await expect(roomPreviewBar.getByRole("heading", { name: "Ask to join?" })).toBeVisible();
+ await expect(roomPreviewBar.getByRole("textbox")).toBeVisible();
+ await roomPreviewBar.getByRole("button", { name: "Request access" }).click();
+ await expect(roomPreviewBar.getByRole("heading", { name: "Request to join sent" })).toBeVisible();
+
+ // Knocked room should appear in Rooms
+ await expect(
+ page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity" }),
+ ).toBeVisible();
+
+ // bot waits for knock request from Alice
+ await waitForRoom(page, bot, room.roomId, (room) => {
+ const events = room.getLiveTimeline().getEvents();
+ return events.some(
+ (e) =>
+ e.getType() === "m.room.member" &&
+ e.getContent()?.membership === "knock" &&
+ e.getContent()?.displayname === "Alice",
+ );
+ });
+
+ // bot kicks Alice
+ await bot.kick(room.roomId, user.userId);
+
+ // Room should stay in Rooms and have red badge when knock is denied
+ await expect(
+ page.getByRole("group", { name: "Rooms" }).getByRole("treeitem", { name: "Cybersecurity", exact: true }),
+ ).not.toBeVisible();
+ await expect(
+ page
+ .getByRole("group", { name: "Rooms" })
+ .getByRole("treeitem", { name: "Cybersecurity 1 unread mention." }),
+ ).toBeVisible();
+
+ await expect(roomPreviewBar.getByRole("heading", { name: "You have been denied access" })).toBeVisible();
+ await roomPreviewBar.getByRole("button", { name: "Forget this room" }).click();
+
+ // Room should disappear from the list completely when forgotten
+ // Should be enabled when issue is fixed: https://github.com/vector-im/element-web/issues/26195
+ // await expect(page.getByRole("treeitem", { name: /Cybersecurity/ })).not.toBeVisible();
+ });
+
+ test("should knock into the public knock room via spotlight", async ({ page, app, bot, room }) => {
+ await bot.setRoomDirectoryVisibility(room.roomId, "public" as Visibility);
+
+ const spotlightDialog = await app.openSpotlight();
+ await spotlightDialog.filter(Filter.PublicRooms);
+ await expect(spotlightDialog.results.nth(0)).toContainText("Cybersecurity");
+ await spotlightDialog.results.nth(0).click();
+
+ const roomPreviewBar = page.locator(".mx_RoomPreviewBar");
+ await expect(roomPreviewBar.getByRole("heading", { name: "Ask to join?" })).toBeVisible();
+ await expect(roomPreviewBar.getByRole("textbox")).toBeVisible();
+ await roomPreviewBar.getByRole("button", { name: "Request access" }).click();
+ await expect(roomPreviewBar.getByRole("heading", { name: "Request to join sent" })).toBeVisible();
+ });
+});
diff --git a/playwright/e2e/knock/manage-knocks.spec.ts b/playwright/e2e/knock/manage-knocks.spec.ts
new file mode 100644
index 0000000000..3fb5c68551
--- /dev/null
+++ b/playwright/e2e/knock/manage-knocks.spec.ts
@@ -0,0 +1,118 @@
+/*
+Copyright 2023 Mikhail Aheichyk
+Copyright 2023 Nordeck IT + Consulting GmbH.
+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 { waitForRoom } from "../utils";
+
+test.describe("Manage Knocks", () => {
+ test.use({
+ displayName: "Alice",
+ labsFlags: ["feature_ask_to_join"],
+ botCreateOpts: {
+ displayName: "Bob",
+ },
+ room: async ({ app, user }, use) => {
+ const roomId = await app.client.createRoom({
+ name: "Cybersecurity",
+ initial_state: [
+ {
+ type: "m.room.join_rules",
+ content: {
+ join_rule: "knock",
+ },
+ state_key: "",
+ },
+ ],
+ });
+ await app.viewRoomById(roomId);
+ await use({ roomId });
+ },
+ });
+
+ test("should approve knock using bar", async ({ page, bot, room }) => {
+ await bot.knockRoom(room.roomId);
+
+ const roomKnocksBar = page.locator(".mx_RoomKnocksBar");
+ await expect(roomKnocksBar.getByRole("heading", { name: "Asking to join" })).toBeVisible();
+ await expect(roomKnocksBar.getByText(/^Bob/)).toBeVisible();
+ await roomKnocksBar.getByRole("button", { name: "Approve" }).click();
+
+ await expect(roomKnocksBar).not.toBeVisible();
+
+ await expect(page.getByText("Alice invited Bob")).toBeVisible();
+ });
+
+ test("should deny knock using bar", async ({ page, app, bot, room }) => {
+ bot.knockRoom(room.roomId);
+
+ const roomKnocksBar = page.locator(".mx_RoomKnocksBar");
+ await expect(roomKnocksBar.getByRole("heading", { name: "Asking to join" })).toBeVisible();
+ await expect(roomKnocksBar.getByText(/^Bob/)).toBeVisible();
+ await roomKnocksBar.getByRole("button", { name: "Deny" }).click();
+
+ await expect(roomKnocksBar).not.toBeVisible();
+
+ // Should receive Bob's "m.room.member" with "leave" membership when access is denied
+ await waitForRoom(page, app.client, room.roomId, (room) => {
+ const events = room.getLiveTimeline().getEvents();
+ return events.some(
+ (e) =>
+ e.getType() === "m.room.member" &&
+ e.getContent()?.membership === "leave" &&
+ e.getContent()?.displayname === "Bob",
+ );
+ });
+ });
+
+ test("should approve knock using people tab", async ({ page, app, bot, room }) => {
+ await bot.knockRoom(room.roomId, { reason: "Hello, can I join?" });
+
+ await app.settings.openRoomSettings("People");
+
+ const settingsGroup = page.getByRole("group", { name: "Asking to join" });
+ await expect(settingsGroup.getByText(/^Bob/)).toBeVisible();
+ await expect(settingsGroup.getByText("Hello, can I join?")).toBeVisible();
+ await settingsGroup.getByRole("button", { name: "Approve" }).click();
+ await expect(settingsGroup.getByText(/^Bob/)).not.toBeVisible();
+
+ await expect(page.getByText("Alice invited Bob")).toBeVisible();
+ });
+
+ test("should deny knock using people tab", async ({ page, app, bot, room }) => {
+ await bot.knockRoom(room.roomId, { reason: "Hello, can I join?" });
+
+ await app.settings.openRoomSettings("People");
+
+ const settingsGroup = page.getByRole("group", { name: "Asking to join" });
+ await expect(settingsGroup.getByText(/^Bob/)).toBeVisible();
+ await expect(settingsGroup.getByText("Hello, can I join?")).toBeVisible();
+ await settingsGroup.getByRole("button", { name: "Deny" }).click();
+ await expect(settingsGroup.getByText(/^Bob/)).not.toBeVisible();
+
+ // Should receive Bob's "m.room.member" with "leave" membership when access is denied
+ await waitForRoom(page, app.client, room.roomId, (room) => {
+ const events = room.getLiveTimeline().getEvents();
+ return events.some(
+ (e) =>
+ e.getType() === "m.room.member" &&
+ e.getContent()?.membership === "leave" &&
+ e.getContent()?.displayname === "Bob",
+ );
+ });
+ });
+});
diff --git a/playwright/e2e/utils.ts b/playwright/e2e/utils.ts
new file mode 100644
index 0000000000..30aff64dd8
--- /dev/null
+++ b/playwright/e2e/utils.ts
@@ -0,0 +1,66 @@
+/*
+Copyright 2023 Mikhail Aheichyk
+Copyright 2023 Nordeck IT + Consulting GmbH.
+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 { uniqueId } from "lodash";
+
+import type { Page } from "@playwright/test";
+import type { ClientEvent, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import { Client } from "../pages/client";
+
+/**
+ * Resolves when room state matches predicate.
+ * @param page Page instance
+ * @param client Client instance that can be user or bot
+ * @param roomId room id to find room and check
+ * @param predicate defines condition that is used to check the room state
+ */
+export async function waitForRoom(
+ page: Page,
+ client: Client,
+ roomId: string,
+ predicate: (room: Room) => boolean,
+): Promise {
+ const predicateId = uniqueId("waitForRoom");
+ await page.exposeFunction(predicateId, predicate);
+ await client.evaluateHandle(
+ (matrixClient, { roomId, predicateId }) => {
+ return new Promise((resolve) => {
+ const room = matrixClient.getRoom(roomId);
+
+ if (window[predicateId](room)) {
+ resolve(room);
+ return;
+ }
+
+ function onEvent(ev: MatrixEvent) {
+ if (ev.getRoomId() !== roomId) return;
+
+ if (window[predicateId](room)) {
+ matrixClient.removeListener("event" as ClientEvent, onEvent);
+ resolve(room);
+ }
+ }
+
+ matrixClient.on("event" as ClientEvent, onEvent);
+ });
+ },
+ { roomId, predicateId },
+ );
+}
+
+export const CommandOrControl = process.platform === "darwin" ? "Meta" : "Control";
diff --git a/playwright/element-web-test.ts b/playwright/element-web-test.ts
index 95ab529bb7..ecea296332 100644
--- a/playwright/element-web-test.ts
+++ b/playwright/element-web-test.ts
@@ -81,19 +81,25 @@ export const test = base.extend<
uut?: Locator; // Unit Under Test, useful place to refer a prepared locator
botCreateOpts: CreateBotOpts;
bot: Bot;
+ labsFlags: string[];
webserver: Webserver;
}
>({
cryptoBackend: ["legacy", { option: true }],
config: CONFIG_JSON,
- page: async ({ context, page, config, cryptoBackend }, use) => {
+ page: async ({ context, page, config, cryptoBackend, labsFlags }, use) => {
await context.route(`http://localhost:8080/config.json*`, async (route) => {
const json = { ...CONFIG_JSON, ...config };
+ json["features"] = {
+ ...json["features"],
+ // Enable the lab features
+ ...labsFlags.reduce((obj, flag) => {
+ obj[flag] = true;
+ return obj;
+ }, {}),
+ };
if (cryptoBackend === "rust") {
- json["features"] = {
- ...json["features"],
- feature_rust_crypto: true,
- };
+ json.features.feature_rust_crypto = true;
}
await route.fulfill({ json });
});
@@ -145,6 +151,7 @@ export const test = base.extend<
displayName,
});
},
+ labsFlags: [],
user: async ({ page, homeserver, credentials }, use) => {
await page.addInitScript(
({ baseUrl, credentials }) => {
diff --git a/playwright/pages/ElementAppPage.ts b/playwright/pages/ElementAppPage.ts
index 742acc13f4..4e065a4b17 100644
--- a/playwright/pages/ElementAppPage.ts
+++ b/playwright/pages/ElementAppPage.ts
@@ -19,6 +19,7 @@ import { type Locator, type Page, expect } from "@playwright/test";
import { Settings } from "./settings";
import { Client } from "./client";
import { Labs } from "./labs";
+import { Spotlight } from "./Spotlight";
export class ElementAppPage {
public constructor(public readonly page: Page) {}
@@ -148,4 +149,10 @@ export class ElementAppPage {
public async getClipboardText(): Promise {
return this.page.evaluate("navigator.clipboard.readText()");
}
+
+ public async openSpotlight(): Promise {
+ const spotlight = new Spotlight(this.page);
+ await spotlight.open();
+ return spotlight;
+ }
}
diff --git a/playwright/pages/Spotlight.ts b/playwright/pages/Spotlight.ts
new file mode 100644
index 0000000000..e638ab0aad
--- /dev/null
+++ b/playwright/pages/Spotlight.ts
@@ -0,0 +1,58 @@
+/*
+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 type { Locator, Page } from "@playwright/test";
+import { CommandOrControl } from "../e2e/utils";
+
+export enum Filter {
+ People = "people",
+ PublicRooms = "public_rooms",
+}
+
+export class Spotlight {
+ private root: Locator;
+
+ constructor(private page: Page) {}
+
+ public async open() {
+ await this.page.keyboard.press(`${CommandOrControl}+KeyK`);
+ this.root = this.page.locator('[role=dialog][aria-label="Search Dialog"]');
+ }
+
+ public async filter(filter: Filter) {
+ let selector: string;
+ switch (filter) {
+ case Filter.People:
+ selector = "#mx_SpotlightDialog_button_startChat";
+ break;
+ case Filter.PublicRooms:
+ selector = "#mx_SpotlightDialog_button_explorePublicRooms";
+ break;
+ default:
+ selector = ".mx_SpotlightDialog_filter";
+ break;
+ }
+ await this.root.locator(selector).click();
+ }
+
+ public async search(query: string) {
+ await this.root.locator(".mx_SpotlightDialog_searchBox").getByRole("textbox", { name: "Search" }).fill(query);
+ }
+
+ public get results() {
+ return this.root.locator(".mx_SpotlightDialog_section.mx_SpotlightDialog_results .mx_SpotlightDialog_option");
+ }
+}
diff --git a/playwright/pages/client.ts b/playwright/pages/client.ts
index b3a6cdcaed..fcd0d86e02 100644
--- a/playwright/pages/client.ts
+++ b/playwright/pages/client.ts
@@ -26,6 +26,8 @@ import type {
MatrixEvent,
ReceiptType,
IRoomDirectoryOptions,
+ KnockRoomOpts,
+ Visibility,
} from "matrix-js-sdk/src/matrix";
import { Credentials } from "../plugins/homeserver";
@@ -215,6 +217,56 @@ export class Client {
});
}
+ /**
+ * Knocks the given room.
+ * @param roomId the id of the room to knock
+ * @param opts the options to use when knocking
+ */
+ public async knockRoom(roomId: string, opts?: KnockRoomOpts): Promise {
+ const client = await this.prepareClient();
+ await client.evaluate((client, { roomId, opts }) => client.knockRoom(roomId, opts), { roomId, opts });
+ }
+
+ /**
+ * Kicks the given user from the given room.
+ * @param roomId the id of the room to kick from
+ * @param userId the id of the user to kick
+ * @param reason the reason for the kick
+ */
+ public async kick(roomId: string, userId: string, reason?: string): Promise {
+ const client = await this.prepareClient();
+ await client.evaluate((client, { roomId, userId, reason }) => client.kick(roomId, userId, reason), {
+ roomId,
+ userId,
+ reason,
+ });
+ }
+
+ /**
+ * Bans the given user from the given room.
+ * @param roomId the id of the room to ban from
+ * @param userId the id of the user to ban
+ * @param reason the reason for the ban
+ */
+ public async ban(roomId: string, userId: string, reason?: string): Promise {
+ const client = await this.prepareClient();
+ await client.evaluate((client, { roomId, userId, reason }) => client.ban(roomId, userId, reason), {
+ roomId,
+ userId,
+ reason,
+ });
+ }
+
+ /**
+ * Unban the given user from the given room.
+ * @param roomId the id of the room to unban from
+ * @param userId the id of the user to unban
+ */
+ public async unban(roomId: string, userId: string): Promise {
+ const client = await this.prepareClient();
+ await client.evaluate((client, { roomId, userId }) => client.unban(roomId, userId), { roomId, userId });
+ }
+
/**
* @param {MatrixEvent} event
* @param {ReceiptType} receiptType
@@ -261,4 +313,19 @@ export class Client {
});
}, credentials);
}
+
+ /**
+ * Sets the directory visibility for a room.
+ * @param roomId ID of the room to set the directory visibility for
+ * @param visibility The new visibility for the room
+ */
+ public async setRoomDirectoryVisibility(roomId: string, visibility: Visibility): Promise {
+ const client = await this.prepareClient();
+ return client.evaluate(
+ async (client, { roomId, visibility }) => {
+ await client.setRoomDirectoryVisibility(roomId, visibility);
+ },
+ { roomId, visibility },
+ );
+ }
}