2023-11-27 20:11:00 +08:00
|
|
|
/*
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2023-12-01 20:24:49 +08:00
|
|
|
import { type Locator, type Page, expect } from "@playwright/test";
|
2023-11-28 21:52:09 +08:00
|
|
|
|
2023-11-29 21:50:13 +08:00
|
|
|
import { Settings } from "./settings";
|
2023-11-30 18:18:18 +08:00
|
|
|
import { Client } from "./client";
|
2023-12-01 20:24:49 +08:00
|
|
|
import { Labs } from "./labs";
|
2023-12-12 22:08:36 +08:00
|
|
|
import { Spotlight } from "./Spotlight";
|
2023-11-27 20:11:00 +08:00
|
|
|
|
|
|
|
export class ElementAppPage {
|
2023-12-12 16:55:29 +08:00
|
|
|
public constructor(public readonly page: Page) {}
|
2023-11-27 20:11:00 +08:00
|
|
|
|
2023-12-01 20:24:49 +08:00
|
|
|
public labs = new Labs(this.page);
|
2023-11-29 21:50:13 +08:00
|
|
|
public settings = new Settings(this.page);
|
2023-11-30 18:18:18 +08:00
|
|
|
public client: Client = new Client(this.page);
|
2023-11-28 21:52:09 +08:00
|
|
|
|
2023-11-27 20:11:00 +08:00
|
|
|
/**
|
|
|
|
* Open the top left user menu, returning a Locator to the resulting context menu.
|
|
|
|
*/
|
|
|
|
public async openUserMenu(): Promise<Locator> {
|
2023-11-29 21:50:13 +08:00
|
|
|
return this.settings.openUserMenu();
|
2023-11-27 20:11:00 +08:00
|
|
|
}
|
|
|
|
|
2023-11-28 00:24:02 +08:00
|
|
|
/**
|
|
|
|
* Open room creation dialog.
|
|
|
|
*/
|
|
|
|
public async openCreateRoomDialog(): Promise<Locator> {
|
|
|
|
await this.page.getByRole("button", { name: "Add room", exact: true }).click();
|
|
|
|
await this.page.getByRole("menuitem", { name: "New room", exact: true }).click();
|
|
|
|
return this.page.locator(".mx_CreateRoomDialog");
|
|
|
|
}
|
|
|
|
|
2023-11-28 08:37:20 +08:00
|
|
|
/**
|
|
|
|
* Close dialog currently open dialog
|
|
|
|
*/
|
|
|
|
public async closeDialog(): Promise<void> {
|
2023-11-29 21:50:13 +08:00
|
|
|
return this.settings.closeDialog();
|
2023-11-28 08:37:20 +08:00
|
|
|
}
|
|
|
|
|
2023-12-07 01:54:47 +08:00
|
|
|
public async getClipboard(): Promise<string> {
|
|
|
|
return await this.page.evaluate(() => navigator.clipboard.readText());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find an open dialog by its title
|
|
|
|
*/
|
|
|
|
public async getDialogByTitle(title: string, timeout = 5000): Promise<Locator> {
|
|
|
|
const dialog = this.page.locator(".mx_Dialog");
|
|
|
|
await dialog.getByRole("heading", { name: title }).waitFor({ timeout });
|
|
|
|
return dialog;
|
|
|
|
}
|
|
|
|
|
2023-11-29 21:50:13 +08:00
|
|
|
/**
|
|
|
|
* Opens the given room by name. The room must be visible in the
|
|
|
|
* room list, but the room list may be folded horizontally, and the
|
|
|
|
* room may contain unread messages.
|
|
|
|
*
|
|
|
|
* @param name The exact room name to find and click on/open.
|
|
|
|
*/
|
|
|
|
public async viewRoomByName(name: string): Promise<void> {
|
|
|
|
// We look for the room inside the room list, which is a tree called Rooms.
|
|
|
|
//
|
|
|
|
// There are 3 cases:
|
|
|
|
// - the room list is folded:
|
|
|
|
// then the aria-label on the room tile is the name (with nothing extra)
|
|
|
|
// - the room list is unfolder and the room has messages:
|
|
|
|
// then the aria-label contains the unread count, but the title of the
|
|
|
|
// div inside the titleContainer equals the room name
|
|
|
|
// - the room list is unfolded and the room has no messages:
|
|
|
|
// then the aria-label is the name and so is the title of a div
|
|
|
|
//
|
|
|
|
// So by matching EITHER title=name OR aria-label=name we find this exact
|
|
|
|
// room in all three cases.
|
|
|
|
return this.page
|
|
|
|
.getByRole("tree", { name: "Rooms" })
|
|
|
|
.locator(`[title="${name}"],[aria-label="${name}"]`)
|
|
|
|
.first()
|
|
|
|
.click();
|
|
|
|
}
|
|
|
|
|
2023-12-12 16:55:29 +08:00
|
|
|
public async viewRoomById(roomId: string): Promise<void> {
|
|
|
|
await this.page.goto(`/#/room/${roomId}`);
|
|
|
|
}
|
|
|
|
|
2023-11-28 08:37:20 +08:00
|
|
|
/**
|
|
|
|
* Get the composer element
|
|
|
|
* @param isRightPanel whether to select the right panel composer, otherwise the main timeline composer
|
|
|
|
*/
|
2023-12-04 20:01:06 +08:00
|
|
|
public getComposer(isRightPanel?: boolean): Locator {
|
2023-11-28 08:37:20 +08:00
|
|
|
const panelClass = isRightPanel ? ".mx_RightPanel" : ".mx_RoomView_body";
|
|
|
|
return this.page.locator(`${panelClass} .mx_MessageComposer`);
|
|
|
|
}
|
|
|
|
|
2023-12-13 22:59:08 +08:00
|
|
|
/**
|
|
|
|
* Get the composer input field
|
|
|
|
* @param isRightPanel whether to select the right panel composer, otherwise the main timeline composer
|
|
|
|
*/
|
|
|
|
public getComposerField(isRightPanel?: boolean): Locator {
|
|
|
|
return this.getComposer(isRightPanel).locator("[contenteditable]");
|
|
|
|
}
|
|
|
|
|
2023-11-28 08:37:20 +08:00
|
|
|
/**
|
|
|
|
* Open the message composer kebab menu
|
|
|
|
* @param isRightPanel whether to select the right panel composer, otherwise the main timeline composer
|
|
|
|
*/
|
|
|
|
public async openMessageComposerOptions(isRightPanel?: boolean): Promise<Locator> {
|
2023-12-04 20:01:06 +08:00
|
|
|
const composer = this.getComposer(isRightPanel);
|
2023-11-28 08:37:20 +08:00
|
|
|
await composer.getByRole("button", { name: "More options", exact: true }).click();
|
|
|
|
return this.page.getByRole("menu");
|
|
|
|
}
|
2023-12-01 20:24:49 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
public async getSpacePanelButton(name: string): Promise<Locator> {
|
|
|
|
const button = this.page.getByRole("button", { name: name });
|
|
|
|
await expect(button).toHaveClass(/mx_SpaceButton/);
|
|
|
|
return button;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
public async viewSpaceHomeByName(name: string): Promise<void> {
|
|
|
|
const button = await this.getSpacePanelButton(name);
|
|
|
|
return button.dblclick();
|
|
|
|
}
|
2023-12-04 19:56:48 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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()");
|
|
|
|
}
|
2023-12-12 22:08:36 +08:00
|
|
|
|
|
|
|
public async openSpotlight(): Promise<Spotlight> {
|
|
|
|
const spotlight = new Spotlight(this.page);
|
|
|
|
await spotlight.open();
|
|
|
|
return spotlight;
|
|
|
|
}
|
2023-12-13 22:59:08 +08:00
|
|
|
|
|
|
|
public async scrollToBottom(page: Page): Promise<void> {
|
|
|
|
await page
|
|
|
|
.locator(".mx_ScrollPanel")
|
|
|
|
.evaluate((scrollPanel) => scrollPanel.scrollTo(0, scrollPanel.scrollHeight));
|
|
|
|
}
|
2023-11-27 20:11:00 +08:00
|
|
|
}
|