mirror of https://github.com/vector-im/riot-web
End to end tests for threads (#8267)
parent
ecdc11d3d5
commit
82981e4161
|
@ -4,3 +4,4 @@ element/env
|
|||
performance-entries.json
|
||||
lib
|
||||
logs
|
||||
homeserver.log
|
||||
|
|
|
@ -20,19 +20,19 @@ import uuidv4 = require('uuid/v4');
|
|||
import { RestSession } from "./session";
|
||||
import { Logger } from "../logger";
|
||||
|
||||
/* no pun intented */
|
||||
/* no pun intended */
|
||||
export class RestRoom {
|
||||
constructor(readonly session: RestSession, readonly roomId: string, readonly log: Logger) {}
|
||||
|
||||
async talk(message: string): Promise<void> {
|
||||
async talk(message: string): Promise<string> {
|
||||
this.log.step(`says "${message}" in ${this.roomId}`);
|
||||
const txId = uuidv4();
|
||||
await this.session.put(`/rooms/${this.roomId}/send/m.room.message/${txId}`, {
|
||||
const { event_id: eventId } = await this.session.put(`/rooms/${this.roomId}/send/m.room.message/${txId}`, {
|
||||
"msgtype": "m.text",
|
||||
"body": message,
|
||||
});
|
||||
this.log.done();
|
||||
return txId;
|
||||
return eventId;
|
||||
}
|
||||
|
||||
async leave(): Promise<void> {
|
||||
|
|
|
@ -30,6 +30,8 @@ import { stickerScenarios } from './scenarios/sticker';
|
|||
import { userViewScenarios } from "./scenarios/user-view";
|
||||
import { ssoCustomisationScenarios } from "./scenarios/sso-customisations";
|
||||
import { updateScenarios } from "./scenarios/update";
|
||||
import { threadsScenarios } from "./scenarios/threads";
|
||||
import { enableThreads } from "./usecases/threads";
|
||||
|
||||
export async function scenario(createSession: (s: string) => Promise<ElementSession>,
|
||||
restCreator: RestSessionCreator): Promise<void> {
|
||||
|
@ -48,6 +50,12 @@ export async function scenario(createSession: (s: string) => Promise<ElementSess
|
|||
const alice = await createUser("alice");
|
||||
const bob = await createUser("bob");
|
||||
|
||||
// Enable threads for Alice & Bob before going any further as it requires refreshing the app
|
||||
// which otherwise loses all performance ticks.
|
||||
console.log("Enabling threads: ");
|
||||
await enableThreads(alice);
|
||||
await enableThreads(bob);
|
||||
|
||||
await toastScenarios(alice, bob);
|
||||
await userViewScenarios(alice, bob);
|
||||
await roomDirectoryScenarios(alice, bob);
|
||||
|
@ -55,6 +63,7 @@ export async function scenario(createSession: (s: string) => Promise<ElementSess
|
|||
console.log("create REST users:");
|
||||
const charlies = await createRestUsers(restCreator);
|
||||
await lazyLoadingScenarios(alice, bob, charlies);
|
||||
await threadsScenarios(alice, bob);
|
||||
// do spaces scenarios last as the rest of the alice/bob tests may get confused by spaces
|
||||
await spacesScenarios(alice, bob);
|
||||
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
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 { ElementSession } from "../session";
|
||||
import {
|
||||
assertTimelineThreadSummary,
|
||||
clickTimelineThreadSummary,
|
||||
editThreadMessage,
|
||||
reactThreadMessage,
|
||||
redactThreadMessage,
|
||||
sendThreadMessage,
|
||||
startThread,
|
||||
} from "../usecases/threads";
|
||||
import { sendMessage } from "../usecases/send-message";
|
||||
import {
|
||||
assertThreadListHasUnreadIndicator,
|
||||
clickLatestThreadInThreadListPanel,
|
||||
closeRoomRightPanel,
|
||||
openThreadListPanel,
|
||||
} from "../usecases/rightpanel";
|
||||
|
||||
export async function threadsScenarios(alice: ElementSession, bob: ElementSession): Promise<void> {
|
||||
console.log(" threads tests:");
|
||||
|
||||
// Alice sends message
|
||||
await sendMessage(alice, "Hey bob, what do you think about X?");
|
||||
|
||||
// Bob responds via a thread
|
||||
await startThread(bob, "I think its Y!");
|
||||
|
||||
// Alice sees thread summary and opens thread panel
|
||||
await assertTimelineThreadSummary(alice, "bob", "I think its Y!");
|
||||
await assertTimelineThreadSummary(bob, "bob", "I think its Y!");
|
||||
await clickTimelineThreadSummary(alice);
|
||||
|
||||
// Bob closes right panel
|
||||
await closeRoomRightPanel(bob);
|
||||
|
||||
// Alice responds in thread
|
||||
await sendThreadMessage(alice, "Great!");
|
||||
await assertTimelineThreadSummary(alice, "alice", "Great!");
|
||||
await assertTimelineThreadSummary(bob, "alice", "Great!");
|
||||
|
||||
// Alice reacts to Bob's message instead
|
||||
await reactThreadMessage(alice, "😁");
|
||||
await assertTimelineThreadSummary(alice, "alice", "Great!");
|
||||
await assertTimelineThreadSummary(bob, "alice", "Great!");
|
||||
await redactThreadMessage(alice);
|
||||
await assertTimelineThreadSummary(alice, "bob", "I think its Y!");
|
||||
await assertTimelineThreadSummary(bob, "bob", "I think its Y!");
|
||||
|
||||
// Bob sees notification dot on the thread header icon
|
||||
await assertThreadListHasUnreadIndicator(bob);
|
||||
|
||||
// Bob opens thread list and inspects it
|
||||
await openThreadListPanel(bob);
|
||||
|
||||
// Bob opens thread in right panel via thread list
|
||||
await clickLatestThreadInThreadListPanel(bob);
|
||||
|
||||
// Bob responds to thread
|
||||
await sendThreadMessage(bob, "Testing threads s'more :)");
|
||||
await assertTimelineThreadSummary(alice, "bob", "Testing threads s'more :)");
|
||||
await assertTimelineThreadSummary(bob, "bob", "Testing threads s'more :)");
|
||||
|
||||
// Bob edits thread response
|
||||
await editThreadMessage(bob, "Testing threads some more :)");
|
||||
await assertTimelineThreadSummary(alice, "bob", "Testing threads some more :)");
|
||||
await assertTimelineThreadSummary(bob, "bob", "Testing threads some more :)");
|
||||
}
|
|
@ -131,8 +131,11 @@ export class ElementSession {
|
|||
await input.type(text);
|
||||
}
|
||||
|
||||
public query(selector: string, timeout: number = DEFAULT_TIMEOUT,
|
||||
hidden = false): Promise<puppeteer.ElementHandle> {
|
||||
public query(
|
||||
selector: string,
|
||||
timeout: number = DEFAULT_TIMEOUT,
|
||||
hidden = false,
|
||||
): Promise<puppeteer.ElementHandle> {
|
||||
return this.page.waitForSelector(selector, { visible: true, timeout, hidden });
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,28 @@ limitations under the License.
|
|||
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
export async function closeRoomRightPanel(session: ElementSession): Promise<void> {
|
||||
const button = await session.query(".mx_BaseCard_close");
|
||||
await button.click();
|
||||
}
|
||||
|
||||
export async function openThreadListPanel(session: ElementSession): Promise<void> {
|
||||
await session.query('.mx_RoomHeader .mx_AccessibleButton[aria-label="Threads"]');
|
||||
const button = await session.queryWithoutWaiting('.mx_RoomHeader .mx_AccessibleButton[aria-label="Threads"]' +
|
||||
':not(.mx_RightPanel_headerButton_highlight)');
|
||||
await button?.click();
|
||||
}
|
||||
|
||||
export async function assertThreadListHasUnreadIndicator(session: ElementSession): Promise<void> {
|
||||
await session.query('.mx_RoomHeader .mx_AccessibleButton[aria-label="Threads"] ' +
|
||||
'.mx_RightPanel_headerButton_unreadIndicator');
|
||||
}
|
||||
|
||||
export async function clickLatestThreadInThreadListPanel(session: ElementSession): Promise<void> {
|
||||
const threads = await session.queryAll(".mx_ThreadPanel .mx_EventTile");
|
||||
await threads[threads.length - 1].click();
|
||||
}
|
||||
|
||||
export async function openRoomRightPanel(session: ElementSession): Promise<void> {
|
||||
// block until we have a roomSummaryButton
|
||||
const roomSummaryButton = await session.query('.mx_RoomHeader .mx_AccessibleButton[aria-label="Room Info"]');
|
||||
|
|
|
@ -19,8 +19,12 @@ import { strict as assert } from 'assert';
|
|||
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
export async function signup(session: ElementSession, username: string, password: string,
|
||||
homeserver: string): Promise<void> {
|
||||
export async function signup(
|
||||
session: ElementSession,
|
||||
username: string,
|
||||
password: string,
|
||||
homeserver: string,
|
||||
): Promise<void> {
|
||||
session.log.step("signs up");
|
||||
await session.goto(session.url('/#/register'));
|
||||
// change the homeserver by clicking the advanced section
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
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 { strict as assert } from "assert";
|
||||
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
export async function enableThreads(session: ElementSession): Promise<void> {
|
||||
session.log.step(`enables threads`);
|
||||
await session.page.evaluate(() => {
|
||||
window.localStorage.setItem("mx_seen_feature_thread_experimental", "1"); // inhibit dialog
|
||||
window["mxSettingsStore"].setValue("feature_thread", null, "device", true);
|
||||
});
|
||||
session.log.done();
|
||||
}
|
||||
|
||||
async function clickReplyInThread(session: ElementSession): Promise<void> {
|
||||
const events = await session.queryAll(".mx_EventTile_line");
|
||||
const event = events[events.length - 1];
|
||||
await event.hover();
|
||||
const button = await event.$(".mx_MessageActionBar_threadButton");
|
||||
await button.click();
|
||||
}
|
||||
|
||||
export async function sendThreadMessage(session: ElementSession, message: string): Promise<void> {
|
||||
session.log.step(`sends thread response "${message}"`);
|
||||
const composer = await session.query(".mx_ThreadView .mx_BasicMessageComposer_input");
|
||||
await composer.click();
|
||||
await composer.type(message);
|
||||
|
||||
const text = await session.innerText(composer);
|
||||
assert.equal(text.trim(), message.trim());
|
||||
await composer.press("Enter");
|
||||
// wait for the message to appear sent
|
||||
await session.query(".mx_ThreadView .mx_EventTile_last:not(.mx_EventTile_sending)");
|
||||
session.log.done();
|
||||
}
|
||||
|
||||
export async function editThreadMessage(session: ElementSession, message: string): Promise<void> {
|
||||
session.log.step(`edits thread response "${message}"`);
|
||||
const events = await session.queryAll(".mx_EventTile_line");
|
||||
const event = events[events.length - 1];
|
||||
await event.hover();
|
||||
const button = await event.$(".mx_MessageActionBar_editButton");
|
||||
await button.click();
|
||||
|
||||
const composer = await session.query(".mx_ThreadView .mx_EditMessageComposer .mx_BasicMessageComposer_input");
|
||||
await composer.click({ clickCount: 3 });
|
||||
await composer.type(message);
|
||||
|
||||
const text = await session.innerText(composer);
|
||||
assert.equal(text.trim(), message.trim());
|
||||
await composer.press("Enter");
|
||||
// wait for the edit to appear sent
|
||||
await session.query(".mx_ThreadView .mx_EventTile_last:not(.mx_EventTile_sending)");
|
||||
session.log.done();
|
||||
}
|
||||
|
||||
export async function redactThreadMessage(session: ElementSession): Promise<void> {
|
||||
session.log.startGroup(`redacts latest thread response`);
|
||||
|
||||
const events = await session.queryAll(".mx_ThreadView .mx_EventTile_line");
|
||||
const event = events[events.length - 1];
|
||||
await event.hover();
|
||||
|
||||
session.log.step(`clicks the ... button`);
|
||||
let button = await event.$('.mx_MessageActionBar [aria-label="Options"]');
|
||||
await button.click();
|
||||
session.log.done();
|
||||
|
||||
session.log.step(`clicks the remove option`);
|
||||
button = await session.query('.mx_IconizedContextMenu_item[aria-label="Remove"]');
|
||||
await button.click();
|
||||
session.log.done();
|
||||
|
||||
session.log.step(`confirms in the dialog`);
|
||||
button = await session.query(".mx_Dialog_primary");
|
||||
await button.click();
|
||||
session.log.done();
|
||||
|
||||
await session.query(".mx_ThreadView .mx_RedactedBody");
|
||||
|
||||
session.log.endGroup();
|
||||
}
|
||||
|
||||
export async function reactThreadMessage(session: ElementSession, reaction: string): Promise<void> {
|
||||
session.log.startGroup(`reacts to latest thread response`);
|
||||
|
||||
const events = await session.queryAll(".mx_ThreadView .mx_EventTile_line");
|
||||
const event = events[events.length - 1];
|
||||
await event.hover();
|
||||
|
||||
session.log.step(`clicks the reaction button`);
|
||||
let button = await event.$('.mx_MessageActionBar [aria-label="React"]');
|
||||
await button.click();
|
||||
session.log.done();
|
||||
|
||||
session.log.step(`selects reaction`);
|
||||
button = await session.query(`.mx_EmojiPicker_item_wrapper[aria-label=${reaction}]`);
|
||||
await button.click;
|
||||
session.log.done();
|
||||
|
||||
session.log.step(`clicks away`);
|
||||
button = await session.query(".mx_ContextualMenu_background");
|
||||
await button.click();
|
||||
session.log.done();
|
||||
|
||||
session.log.endGroup();
|
||||
}
|
||||
|
||||
export async function startThread(session: ElementSession, response: string): Promise<void> {
|
||||
session.log.startGroup(`creates thread on latest message`);
|
||||
|
||||
await clickReplyInThread(session);
|
||||
await sendThreadMessage(session, response);
|
||||
|
||||
session.log.endGroup();
|
||||
}
|
||||
|
||||
export async function assertTimelineThreadSummary(
|
||||
session: ElementSession,
|
||||
sender: string,
|
||||
content: string,
|
||||
): Promise<void> {
|
||||
session.log.step("asserts the timeline thread summary is as expected");
|
||||
const summaries = await session.queryAll(".mx_MainSplit_timeline .mx_ThreadInfo");
|
||||
const summary = summaries[summaries.length - 1];
|
||||
assert.equal(await session.innerText(await summary.$(".mx_ThreadInfo_sender")), sender);
|
||||
assert.equal(await session.innerText(await summary.$(".mx_ThreadInfo_content")), content);
|
||||
session.log.done();
|
||||
}
|
||||
|
||||
export async function clickTimelineThreadSummary(session: ElementSession): Promise<void> {
|
||||
session.log.step(`clicks the latest thread summary in the timeline`);
|
||||
|
||||
const summaries = await session.queryAll(".mx_MainSplit_timeline .mx_ThreadInfo");
|
||||
await summaries[summaries.length - 1].click();
|
||||
|
||||
session.log.done();
|
||||
}
|
Loading…
Reference in New Issue