Migrate spaces.spec.ts from Cypress to Playwright (#11986)

* Remove old percy media query CSS

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

* Stabilise soft_logout.spec.ts

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

* Update screenshots using `toMatchScreenshot` assertion with CSS overrides

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

* Fix accidentally commented test

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

* Migrate spaces.spec.ts from Cypress to Playwright

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

* Delint

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

* Add screenshots

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

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
pull/28217/head
Michael Telatynski 2023-12-04 11:56:48 +00:00 committed by GitHub
parent d7c682d05e
commit eb6101cc1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 324 additions and 380 deletions

View File

@ -1,347 +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 type { MatrixClient, Preset, ICreateRoomOpts } from "matrix-js-sdk/src/matrix";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import Chainable = Cypress.Chainable;
import { UserCredentials } from "../../support/login";
function openSpaceCreateMenu(): Chainable<JQuery> {
cy.findByRole("button", { name: "Create a space" }).click();
return cy.get(".mx_SpaceCreateMenu_wrapper .mx_ContextualMenu");
}
function openSpaceContextMenu(spaceName: string): Chainable<JQuery> {
cy.getSpacePanelButton(spaceName).rightclick();
return cy.get(".mx_SpacePanel_contextMenu");
}
function spaceCreateOptions(spaceName: string, roomIds: string[] = []): ICreateRoomOpts {
return {
creation_content: {
type: "m.space",
},
initial_state: [
{
type: "m.room.name",
content: {
name: spaceName,
},
},
...roomIds.map(spaceChildInitialState),
],
};
}
function spaceChildInitialState(roomId: string): ICreateRoomOpts["initial_state"]["0"] {
return {
type: "m.space.child",
state_key: roomId,
content: {
via: [roomId.split(":")[1]],
},
};
}
describe("Spaces", () => {
let homeserver: HomeserverInstance;
let user: UserCredentials;
beforeEach(() => {
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(homeserver, "Sue").then((_user) => {
user = _user;
cy.mockClipboard();
});
});
});
afterEach(() => {
cy.stopHomeserver(homeserver);
});
it("should allow user to create public space", () => {
openSpaceCreateMenu();
cy.get("#mx_ContextualMenu_Container").percySnapshotElement("Space create menu");
cy.get(".mx_SpaceCreateMenu_wrapper .mx_ContextualMenu").within(() => {
// Regex pattern due to strings of "mx_SpaceCreateMenuType_public"
cy.findByRole("button", { name: /Public/ }).click();
cy.get('.mx_SpaceBasicSettings_avatarContainer input[type="file"]').selectFile(
"cypress/fixtures/riot.png",
{ force: true },
);
cy.findByRole("textbox", { name: "Name" }).type("Let's have a Riot");
cy.findByRole("textbox", { name: "Address" }).should("have.value", "lets-have-a-riot");
cy.findByRole("textbox", { name: "Description" }).type("This is a space to reminisce Riot.im!");
cy.findByRole("button", { name: "Create" }).click();
});
// Create the default General & Random rooms, as well as a custom "Jokes" room
cy.findByPlaceholderText("General").should("exist");
cy.findByPlaceholderText("Random").should("exist");
cy.findByPlaceholderText("Support").type("Jokes");
cy.findByRole("button", { name: "Continue" }).click();
// Copy matrix.to link
// Regex pattern due to strings of "mx_SpacePublicShare_shareButton"
cy.findByRole("button", { name: /Share invite link/ }).realClick();
cy.getClipboardText().should("eq", "https://matrix.to/#/#lets-have-a-riot:localhost");
// Go to space home
cy.findByRole("button", { name: "Go to my first room" }).click();
// Assert rooms exist in the room list
cy.findByRole("treeitem", { name: "General" }).should("exist");
cy.findByRole("treeitem", { name: "Random" }).should("exist");
cy.findByRole("treeitem", { name: "Jokes" }).should("exist");
});
it("should allow user to create private space", () => {
openSpaceCreateMenu().within(() => {
// Regex pattern due to strings of "mx_SpaceCreateMenuType_private"
cy.findByRole("button", { name: /Private/ }).click();
cy.get('.mx_SpaceBasicSettings_avatarContainer input[type="file"]').selectFile(
"cypress/fixtures/riot.png",
{ force: true },
);
cy.findByRole("textbox", { name: "Name" }).type("This is not a Riot");
cy.findByRole("textbox", { name: "Address" }).should("not.exist");
cy.findByRole("textbox", { name: "Description" }).type("This is a private space of mourning Riot.im...");
cy.findByRole("button", { name: "Create" }).click();
});
// Regex pattern due to strings of "mx_SpaceRoomView_privateScope_meAndMyTeammatesButton"
cy.findByRole("button", { name: /Me and my teammates/ }).click();
// Create the default General & Random rooms, as well as a custom "Projects" room
cy.findByPlaceholderText("General").should("exist");
cy.findByPlaceholderText("Random").should("exist");
cy.findByPlaceholderText("Support").type("Projects");
cy.findByRole("button", { name: "Continue" }).click();
cy.get(".mx_SpaceRoomView h1").findByText("Invite your teammates");
cy.get(".mx_SpaceRoomView").percySnapshotElement("Space - 'Invite your teammates' dialog");
cy.findByRole("button", { name: "Skip for now" }).click();
// Assert rooms exist in the room list
cy.findByRole("treeitem", { name: "General" }).should("exist");
cy.findByRole("treeitem", { name: "Random" }).should("exist");
cy.findByRole("treeitem", { name: "Projects" }).should("exist");
// Assert rooms exist in the space explorer
cy.contains(".mx_SpaceHierarchy_list .mx_SpaceHierarchy_roomTile", "General").should("exist");
cy.contains(".mx_SpaceHierarchy_list .mx_SpaceHierarchy_roomTile", "Random").should("exist");
cy.contains(".mx_SpaceHierarchy_list .mx_SpaceHierarchy_roomTile", "Projects").should("exist");
});
it("should allow user to create just-me space", () => {
cy.createRoom({
name: "Sample Room",
});
openSpaceCreateMenu().within(() => {
// Regex pattern due to strings of "mx_SpaceCreateMenuType_private"
cy.findByRole("button", { name: /Private/ }).click();
cy.get('.mx_SpaceBasicSettings_avatarContainer input[type="file"]').selectFile(
"cypress/fixtures/riot.png",
{ force: true },
);
cy.findByRole("textbox", { name: "Address" }).should("not.exist");
cy.findByRole("textbox", { name: "Description" }).type("This is a personal space to mourn Riot.im...");
cy.findByRole("textbox", { name: "Name" }).type("This is my Riot{enter}");
});
// Regex pattern due to of strings of "mx_SpaceRoomView_privateScope_justMeButton"
cy.findByRole("button", { name: /Just me/ }).click();
cy.findByText("Sample Room").click({ force: true }); // force click as checkbox size is zero
// Temporal implementation as multiple elements with the role "button" and name "Add" are found
cy.get(".mx_AddExistingToSpace_footer").within(() => {
cy.findByRole("button", { name: "Add" }).click();
});
cy.get(".mx_SpaceHierarchy_list").within(() => {
// Regex pattern due to the strings of "mx_SpaceHierarchy_roomTile_joined"
cy.findByRole("treeitem", { name: /Sample Room/ }).should("exist");
});
});
it("should allow user to invite another to a space", () => {
let bot: MatrixClient;
cy.getBot(homeserver, { displayName: "BotBob" }).then((_bot) => {
bot = _bot;
});
cy.createSpace({
visibility: "public" as any,
room_alias_name: "space",
}).as("spaceId");
openSpaceContextMenu("#space:localhost").within(() => {
cy.findByRole("menuitem", { name: "Invite" }).click();
});
cy.get(".mx_SpacePublicShare").within(() => {
// Copy link first
// Regex pattern due to strings of "mx_SpacePublicShare_shareButton"
cy.findByRole("button", { name: /Share invite link/ })
.focus()
.realClick();
cy.getClipboardText().should("eq", "https://matrix.to/#/#space:localhost");
// Start Matrix invite flow
// Regex pattern due to strings of "mx_SpacePublicShare_inviteButton"
cy.findByRole("button", { name: /Invite people/ }).click();
});
cy.get(".mx_InviteDialog_other").within(() => {
cy.findByRole("textbox").type(bot.getUserId());
cy.findByRole("button", { name: "Invite" }).click();
});
cy.get(".mx_InviteDialog_other").should("not.exist");
});
it("should show space invites at the top of the space panel", () => {
cy.createSpace({
name: "My Space",
});
cy.getSpacePanelButton("My Space").should("exist");
cy.getBot(homeserver, { displayName: "BotBob" }).then({ timeout: 10000 }, async (bot) => {
const { room_id: roomId } = await bot.createRoom(spaceCreateOptions("Space Space"));
await bot.invite(roomId, user.userId);
});
// Assert that `Space Space` is above `My Space` due to it being an invite
cy.getSpacePanelButton("Space Space")
.should("exist")
.parent()
.next()
.findByRole("button", { name: "My Space" })
.should("exist");
});
it("should include rooms in space home", () => {
cy.createRoom({
name: "Music",
}).as("roomId1");
cy.createRoom({
name: "Gaming",
}).as("roomId2");
const spaceName = "Spacey Mc. Space Space";
cy.all([cy.get<string>("@roomId1"), cy.get<string>("@roomId2")]).then(([roomId1, roomId2]) => {
cy.createSpace({
name: spaceName,
initial_state: [spaceChildInitialState(roomId1), spaceChildInitialState(roomId2)],
}).as("spaceId");
});
cy.get("@spaceId").then(() => {
cy.viewSpaceHomeByName(spaceName);
});
cy.get(".mx_SpaceRoomView .mx_SpaceHierarchy_list").within(() => {
// Regex pattern due to strings in "mx_SpaceHierarchy_roomTile_name"
cy.findByRole("treeitem", { name: /Music/ }).findByRole("button").should("exist");
cy.findByRole("treeitem", { name: /Gaming/ })
.findByRole("button")
.should("exist");
});
});
it("should render subspaces in the space panel only when expanded", () => {
cy.injectAxe();
cy.createSpace({
name: "Child Space",
initial_state: [],
}).then((spaceId) => {
cy.createSpace({
name: "Root Space",
initial_state: [spaceChildInitialState(spaceId)],
}).as("spaceId");
});
// Find collapsed Space panel
cy.findByRole("tree", { name: "Spaces" }).within(() => {
cy.findByRole("button", { name: "Root Space" }).should("exist");
cy.findByRole("button", { name: "Child Space" }).should("not.exist");
});
const axeOptions = {
rules: {
// Disable this check as it triggers on nested roving tab index elements which are in practice fine
"nested-interactive": {
enabled: false,
},
// Disable this check as it wrongly triggers on the room list container which has
// roving tab index elements with automatic scrolling
"scrollable-region-focusable": {
enabled: false,
},
},
};
cy.checkA11y(undefined, axeOptions);
cy.get(".mx_SpacePanel").percySnapshotElement("Space panel collapsed", { widths: [68] });
cy.findByRole("tree", { name: "Spaces" }).within(() => {
// This finds the expand button with the class name "mx_SpaceButton_toggleCollapse". Note there is another
// button with the same name with different class name "mx_SpacePanel_toggleCollapse".
cy.findByRole("button", { name: "Expand" }).realHover().click();
});
cy.get(".mx_SpacePanel:not(.collapsed)").should("exist"); // TODO: replace :not() selector
cy.contains(".mx_SpaceItem", "Root Space")
.should("exist")
.contains(".mx_SpaceItem", "Child Space")
.should("exist");
cy.checkA11y(undefined, axeOptions);
cy.get(".mx_SpacePanel").percySnapshotElement("Space panel expanded", { widths: [258] });
});
it("should not soft crash when joining a room from space hierarchy which has a link in its topic", () => {
cy.getBot(homeserver, { displayName: "BotBob" }).then({ timeout: 10000 }, async (bot) => {
const { room_id: roomId } = await bot.createRoom({
preset: "public_chat" as Preset,
name: "Test Room",
topic: "This is a topic https://github.com/matrix-org/matrix-react-sdk/pull/10060 with a link",
});
const { room_id: spaceId } = await bot.createRoom(spaceCreateOptions("Test Space", [roomId]));
await bot.invite(spaceId, user.userId);
});
cy.getSpacePanelButton("Test Space").should("exist");
cy.wait(500); // without this we can end up clicking too quickly and it ends up having no effect
cy.viewSpaceByName("Test Space");
cy.findByRole("button", { name: "Accept" }).click();
// Regex pattern due to strings in "mx_SpaceHierarchy_roomTile_item"
cy.findByRole("button", { name: /Test Room/ }).realHover();
cy.findByRole("button", { name: "Join" }).should("exist").realHover().click();
cy.findByRole("button", { name: "View", timeout: 5000 }).should("exist").realHover().click();
// Assert we get shown the new room intro, and thus not the soft crash screen
cy.get(".mx_NewRoomIntro").should("exist");
});
});

View File

@ -39,27 +39,6 @@ declare global {
* @param id
*/
viewRoomById(id: string): void;
/**
* Returns the space panel space button based on a name. The space
* must be visible in the space panel
* @param name The space name to find
*/
getSpacePanelButton(name: string): Chainable<JQuery<HTMLElement>>;
/**
* Opens the given space home by name. The space must be visible in
* the space list.
* @param name The space name to find and click on/open.
*/
viewSpaceHomeByName(name: string): Chainable<JQuery<HTMLElement>>;
/**
* Opens the given space by name. The space must be visible in the
* space list.
* @param name The space name to find and click on/open.
*/
viewSpaceByName(name: string): Chainable<JQuery<HTMLElement>>;
}
}
}
@ -85,17 +64,5 @@ Cypress.Commands.add("viewRoomById", (id: string): void => {
cy.visit(`/#/room/${id}`);
});
Cypress.Commands.add("getSpacePanelButton", (name: string): Chainable<JQuery<HTMLElement>> => {
return cy.findByRole("button", { name: name }).should("have.class", "mx_SpaceButton");
});
Cypress.Commands.add("viewSpaceByName", (name: string): Chainable<JQuery<HTMLElement>> => {
return cy.getSpacePanelButton(name).click();
});
Cypress.Commands.add("viewSpaceHomeByName", (name: string): Chainable<JQuery<HTMLElement>> => {
return cy.getSpacePanelButton(name).dblclick();
});
// Needed to make this file a module
export {};

View File

@ -26,6 +26,7 @@ export default defineConfig<TestOptions>({
ignoreHTTPSErrors: true,
video: "retain-on-failure",
baseURL,
permissions: ["clipboard-write", "clipboard-read"],
},
webServer: {
command: process.env.CI ? "npx serve -p 8080 -L ../webapp" : "yarn --cwd ../element-web start",

View File

@ -0,0 +1,296 @@
/*
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.
*/
import type { Locator, Page } from "@playwright/test";
import { test, expect } from "../../element-web-test";
import type { Preset, ICreateRoomOpts } from "matrix-js-sdk/src/matrix";
import { ElementAppPage } from "../../pages/ElementAppPage";
async function openSpaceCreateMenu(page: Page): Promise<Locator> {
await page.getByRole("button", { name: "Create a space" }).click();
return page.locator(".mx_SpaceCreateMenu_wrapper .mx_ContextualMenu");
}
async function openSpaceContextMenu(page: Page, app: ElementAppPage, spaceName: string): Promise<Locator> {
const button = await app.getSpacePanelButton(spaceName);
await button.click({ button: "right" });
return page.locator(".mx_SpacePanel_contextMenu");
}
function spaceCreateOptions(spaceName: string, roomIds: string[] = []): ICreateRoomOpts {
return {
creation_content: {
type: "m.space",
},
initial_state: [
{
type: "m.room.name",
content: {
name: spaceName,
},
},
...roomIds.map(spaceChildInitialState),
],
};
}
function spaceChildInitialState(roomId: string): ICreateRoomOpts["initial_state"]["0"] {
return {
type: "m.space.child",
state_key: roomId,
content: {
via: [roomId.split(":")[1]],
},
};
}
test.describe("Spaces", () => {
test.use({
displayName: "Sue",
botCreateOpts: { displayName: "BotBob" },
});
test("should allow user to create public space", async ({ page, app, user }) => {
const contextMenu = await openSpaceCreateMenu(page);
await expect(contextMenu).toMatchScreenshot("space-create-menu.png");
await contextMenu.getByRole("button", { name: /Public/ }).click();
await contextMenu
.locator('.mx_SpaceBasicSettings_avatarContainer input[type="file"]')
.setInputFiles("cypress/fixtures/riot.png");
await contextMenu.getByRole("textbox", { name: "Name" }).fill("Let's have a Riot");
await expect(contextMenu.getByRole("textbox", { name: "Address" })).toHaveValue("lets-have-a-riot");
await contextMenu.getByRole("textbox", { name: "Description" }).fill("This is a space to reminisce Riot.im!");
await contextMenu.getByRole("button", { name: "Create" }).click();
// Create the default General & Random rooms, as well as a custom "Jokes" room
await expect(page.getByPlaceholder("General")).toBeVisible();
await expect(page.getByPlaceholder("Random")).toBeVisible();
await page.getByPlaceholder("Support").fill("Jokes");
await page.getByRole("button", { name: "Continue" }).click();
// Copy matrix.to link
await page.getByRole("button", { name: "Share invite link" }).click();
expect(await app.getClipboardText()).toEqual("https://matrix.to/#/#lets-have-a-riot:localhost");
// Go to space home
await page.getByRole("button", { name: "Go to my first room" }).click();
// Assert rooms exist in the room list
await expect(page.getByRole("treeitem", { name: "General" })).toBeVisible();
await expect(page.getByRole("treeitem", { name: "Random" })).toBeVisible();
await expect(page.getByRole("treeitem", { name: "Jokes" })).toBeVisible();
});
test("should allow user to create private space", async ({ page, app, user }) => {
const menu = await openSpaceCreateMenu(page);
await menu.getByRole("button", { name: "Private" }).click();
await menu
.locator('.mx_SpaceBasicSettings_avatarContainer input[type="file"]')
.setInputFiles("cypress/fixtures/riot.png");
await menu.getByRole("textbox", { name: "Name" }).fill("This is not a Riot");
await expect(menu.getByRole("textbox", { name: "Address" })).not.toBeVisible();
await menu.getByRole("textbox", { name: "Description" }).fill("This is a private space of mourning Riot.im...");
await menu.getByRole("button", { name: "Create" }).click();
await page.getByRole("button", { name: "Me and my teammates" }).click();
// Create the default General & Random rooms, as well as a custom "Projects" room
await expect(page.getByPlaceholder("General")).toBeVisible();
await expect(page.getByPlaceholder("Random")).toBeVisible();
await page.getByPlaceholder("Support").fill("Projects");
await page.getByRole("button", { name: "Continue" }).click();
await expect(page.locator(".mx_SpaceRoomView h1").getByText("Invite your teammates")).toBeVisible();
await expect(page.locator(".mx_SpaceRoomView")).toMatchScreenshot("invite-teammates-dialog.png");
await page.getByRole("button", { name: "Skip for now" }).click();
// Assert rooms exist in the room list
await expect(page.getByRole("treeitem", { name: "General", exact: true })).toBeVisible();
await expect(page.getByRole("treeitem", { name: "Random", exact: true })).toBeVisible();
await expect(page.getByRole("treeitem", { name: "Projects", exact: true })).toBeVisible();
// Assert rooms exist in the space explorer
await expect(
page.locator(".mx_SpaceHierarchy_list .mx_SpaceHierarchy_roomTile", { hasText: "General" }),
).toBeVisible();
await expect(
page.locator(".mx_SpaceHierarchy_list .mx_SpaceHierarchy_roomTile", { hasText: "Random" }),
).toBeVisible();
await expect(
page.locator(".mx_SpaceHierarchy_list .mx_SpaceHierarchy_roomTile", { hasText: "Projects" }),
).toBeVisible();
});
test("should allow user to create just-me space", async ({ page, app, user }) => {
await app.client.createRoom({
name: "Sample Room",
});
const menu = await openSpaceCreateMenu(page);
await menu.getByRole("button", { name: "Private" }).click();
await menu
.locator('.mx_SpaceBasicSettings_avatarContainer input[type="file"]')
.setInputFiles("cypress/fixtures/riot.png");
await expect(menu.getByRole("textbox", { name: "Address" })).not.toBeVisible();
await menu.getByRole("textbox", { name: "Description" }).fill("This is a personal space to mourn Riot.im...");
await menu.getByRole("textbox", { name: "Name" }).fill("This is my Riot");
await menu.getByRole("textbox", { name: "Name" }).press("Enter");
await page.getByRole("button", { name: "Just me" }).click();
await page.getByText("Sample Room").click({ force: true }); // force click as checkbox size is zero
// Temporal implementation as multiple elements with the role "button" and name "Add" are found
await page.locator(".mx_AddExistingToSpace_footer").getByRole("button", { name: "Add" }).click();
await expect(
page.locator(".mx_SpaceHierarchy_list").getByRole("treeitem", { name: "Sample Room" }),
).toBeVisible();
});
test("should allow user to invite another to a space", async ({ page, app, user, bot }) => {
await app.client.createSpace({
visibility: "public" as any,
room_alias_name: "space",
});
const menu = await openSpaceContextMenu(page, app, "#space:localhost");
await menu.getByRole("menuitem", { name: "Invite" }).click();
const shareDialog = page.locator(".mx_SpacePublicShare");
// Copy link first
await shareDialog.getByRole("button", { name: "Share invite link" }).click();
expect(await app.getClipboardText()).toEqual("https://matrix.to/#/#space:localhost");
// Start Matrix invite flow
await shareDialog.getByRole("button", { name: "Invite people" }).click();
const otherSection = page.locator(".mx_InviteDialog_other");
await otherSection.getByRole("textbox").fill(bot.credentials.userId);
await otherSection.getByRole("button", { name: "Invite" }).click();
await expect(page.locator(".mx_InviteDialog_other")).not.toBeVisible();
});
test("should show space invites at the top of the space panel", async ({ page, app, user, bot }) => {
await app.client.createSpace({
name: "My Space",
});
await expect(await app.getSpacePanelButton("My Space")).toBeVisible();
const roomId = await bot.createRoom(spaceCreateOptions("Space Space"));
await bot.inviteUser(roomId, user.userId);
// Assert that `Space Space` is above `My Space` due to it being an invite
const buttons = page.getByRole("tree", { name: "Spaces" }).locator(".mx_SpaceButton");
await expect(buttons.nth(1)).toHaveAttribute("aria-label", "Space Space");
await expect(buttons.nth(2)).toHaveAttribute("aria-label", "My Space");
});
test("should include rooms in space home", async ({ page, app, user }) => {
const roomId1 = await app.client.createRoom({
name: "Music",
});
const roomId2 = await app.client.createRoom({
name: "Gaming",
});
const spaceName = "Spacey Mc. Space Space";
await app.client.createSpace({
name: spaceName,
initial_state: [spaceChildInitialState(roomId1), spaceChildInitialState(roomId2)],
});
await app.viewSpaceHomeByName(spaceName);
const hierarchyList = page.locator(".mx_SpaceRoomView .mx_SpaceHierarchy_list");
await expect(hierarchyList.getByRole("treeitem", { name: "Music" }).getByRole("button")).toBeVisible();
await expect(hierarchyList.getByRole("treeitem", { name: "Gaming" }).getByRole("button")).toBeVisible();
});
test("should render subspaces in the space panel only when expanded", async ({
page,
app,
user,
axe,
checkA11y,
}) => {
axe.disableRules([
// Disable this check as it triggers on nested roving tab index elements which are in practice fine
"nested-interactive",
// XXX: We have some known contrast issues here
"color-contrast",
]);
const childSpaceId = await app.client.createSpace({
name: "Child Space",
initial_state: [],
});
await app.client.createSpace({
name: "Root Space",
initial_state: [spaceChildInitialState(childSpaceId)],
});
// Find collapsed Space panel
const spaceTree = page.getByRole("tree", { name: "Spaces" });
await expect(spaceTree.getByRole("button", { name: "Root Space" })).toBeVisible();
await expect(spaceTree.getByRole("button", { name: "Child Space" })).not.toBeVisible();
await checkA11y();
await expect(page.locator(".mx_SpacePanel")).toMatchScreenshot("space-panel-collapsed.png");
// This finds the expand button with the class name "mx_SpaceButton_toggleCollapse". Note there is another
// button with the same name with different class name "mx_SpacePanel_toggleCollapse".
await spaceTree.getByRole("button", { name: "Expand" }).click();
await expect(page.locator(".mx_SpacePanel:not(.collapsed)")).toBeVisible(); // TODO: replace :not() selector
const item = page.locator(".mx_SpaceItem", { hasText: "Root Space" });
await expect(item).toBeVisible();
await expect(item.locator(".mx_SpaceItem", { hasText: "Child Space" })).toBeVisible();
await checkA11y();
await expect(page.locator(".mx_SpacePanel")).toMatchScreenshot("space-panel-expanded.png");
});
test("should not soft crash when joining a room from space hierarchy which has a link in its topic", async ({
page,
app,
user,
bot,
}) => {
const roomId = await bot.createRoom({
preset: "public_chat" as Preset,
name: "Test Room",
topic: "This is a topic https://github.com/matrix-org/matrix-react-sdk/pull/10060 with a link",
});
const spaceId = await bot.createRoom(spaceCreateOptions("Test Space", [roomId]));
await bot.inviteUser(spaceId, user.userId);
await expect(await app.getSpacePanelButton("Test Space")).toBeVisible();
await app.viewSpaceByName("Test Space");
await page.getByRole("button", { name: "Accept" }).click();
await page.getByRole("button", { name: "Test Room" }).hover();
await page.getByRole("button", { name: "Join", exact: true }).click();
await page.getByRole("button", { name: "View", exact: true }).click();
// Assert we get shown the new room intro, and thus not the soft crash screen
await expect(page.locator(".mx_NewRoomIntro")).toBeVisible();
});
});

View File

@ -117,4 +117,18 @@ export class ElementAppPage {
const button = await this.getSpacePanelButton(name);
return button.dblclick();
}
/**
* Opens the given space by name. The space must be visible in the
* space list.
* @param name The space name to find and click on/open.
*/
public async viewSpaceByName(name: string): Promise<void> {
const button = await this.getSpacePanelButton(name);
return button.click();
}
public async getClipboardText(): Promise<string> {
return this.page.evaluate("navigator.clipboard.readText()");
}
}

View File

@ -161,4 +161,17 @@ export class Client {
},
);
}
/**
* Invites the given user to the given room.
* @param roomId the id of the room to invite to
* @param userId the id of the user to invite
*/
public async inviteUser(roomId: string, userId: string): Promise<void> {
const client = await this.prepareClient();
await client.evaluate((client, { roomId, userId }) => client.invite(roomId, userId), {
roomId,
userId,
});
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB