Move Lazy Loading tests from Puppeteer to Cypress (#8982)
* Remove Puppeteer Lazy Loading tests * Remove Puppeteer Lazy Loading tests * Remove Puppeteer Lazy Loading tests * Stash lazy loading cypress tests * Stash lazy loading cypress tests * Update cypress-real-events * Stash offline-less test * Add offline/online'ingpull/28217/head
parent
77d8a242af
commit
42ff9d6dc8
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
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 { SynapseInstance } from "../../plugins/synapsedocker";
|
||||
import { MatrixClient } from "../../global";
|
||||
import Chainable = Cypress.Chainable;
|
||||
|
||||
interface Charly {
|
||||
client: MatrixClient;
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
describe("Lazy Loading", () => {
|
||||
let synapse: SynapseInstance;
|
||||
let bob: MatrixClient;
|
||||
const charlies: Charly[] = [];
|
||||
|
||||
beforeEach(() => {
|
||||
cy.window().then(win => {
|
||||
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
|
||||
});
|
||||
|
||||
cy.startSynapse("default").then(data => {
|
||||
synapse = data;
|
||||
|
||||
cy.initTestUser(synapse, "Alice");
|
||||
|
||||
cy.getBot(synapse, {
|
||||
displayName: "Bob",
|
||||
startClient: false,
|
||||
autoAcceptInvites: false,
|
||||
}).then(_bob => {
|
||||
bob = _bob;
|
||||
});
|
||||
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
const displayName = `Charly #${i}`;
|
||||
cy.getBot(synapse, {
|
||||
displayName,
|
||||
startClient: false,
|
||||
autoAcceptInvites: false,
|
||||
}).then(client => {
|
||||
charlies[i - 1] = { displayName, client };
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cy.stopSynapse(synapse);
|
||||
});
|
||||
|
||||
const name = "Lazy Loading Test";
|
||||
const alias = "#lltest:localhost";
|
||||
const charlyMsg1 = "hi bob!";
|
||||
const charlyMsg2 = "how's it going??";
|
||||
|
||||
function setupRoomWithBobAliceAndCharlies(charlies: Charly[]) {
|
||||
cy.window({ log: false }).then(win => {
|
||||
return cy.wrap(bob.createRoom({
|
||||
name,
|
||||
room_alias_name: "lltest",
|
||||
visibility: win.matrixcs.Visibility.Public,
|
||||
}).then(r => r.room_id), { log: false }).as("roomId");
|
||||
});
|
||||
|
||||
cy.get<string>("@roomId").then(async roomId => {
|
||||
for (const charly of charlies) {
|
||||
await charly.client.joinRoom(alias);
|
||||
}
|
||||
|
||||
for (const charly of charlies) {
|
||||
cy.botSendMessage(charly.client, roomId, charlyMsg1);
|
||||
}
|
||||
for (const charly of charlies) {
|
||||
cy.botSendMessage(charly.client, roomId, charlyMsg2);
|
||||
}
|
||||
|
||||
for (let i = 20; i >= 1; --i) {
|
||||
cy.botSendMessage(bob, roomId, `I will only say this ${i} time(s)!`);
|
||||
}
|
||||
});
|
||||
|
||||
cy.joinRoom(alias);
|
||||
cy.viewRoomByName(name);
|
||||
}
|
||||
|
||||
function checkPaginatedDisplayNames(charlies: Charly[]) {
|
||||
cy.scrollToTop();
|
||||
for (const charly of charlies) {
|
||||
cy.findEventTile(charly.displayName, charlyMsg1).should("exist");
|
||||
cy.findEventTile(charly.displayName, charlyMsg2).should("exist");
|
||||
}
|
||||
}
|
||||
|
||||
function openMemberlist(): void {
|
||||
cy.get('.mx_HeaderButtons [aria-label="Room Info"]').click();
|
||||
cy.get(".mx_RoomSummaryCard").within(() => {
|
||||
cy.get(".mx_RoomSummaryCard_icon_people").click();
|
||||
});
|
||||
}
|
||||
|
||||
function getMembersInMemberlist(): Chainable<JQuery> {
|
||||
return cy.get(".mx_MemberList .mx_EntityTile_name");
|
||||
}
|
||||
|
||||
function checkMemberList(charlies: Charly[]) {
|
||||
getMembersInMemberlist().contains("Alice").should("exist");
|
||||
getMembersInMemberlist().contains("Bob").should("exist");
|
||||
charlies.forEach(charly => {
|
||||
getMembersInMemberlist().contains(charly.displayName).should("exist");
|
||||
});
|
||||
}
|
||||
|
||||
function checkMemberListLacksCharlies(charlies: Charly[]) {
|
||||
charlies.forEach(charly => {
|
||||
getMembersInMemberlist().contains(charly.displayName).should("not.exist");
|
||||
});
|
||||
}
|
||||
|
||||
function joinCharliesWhileAliceIsOffline(charlies: Charly[]) {
|
||||
cy.goOffline();
|
||||
|
||||
cy.get<string>("@roomId").then(async roomId => {
|
||||
for (const charly of charlies) {
|
||||
await charly.client.joinRoom(alias);
|
||||
}
|
||||
for (let i = 20; i >= 1; --i) {
|
||||
cy.botSendMessage(charlies[0].client, roomId, "where is charly?");
|
||||
}
|
||||
});
|
||||
|
||||
cy.goOnline();
|
||||
cy.wait(1000); // Ideally we'd await a /sync here but intercepts step on each other from going offline/online
|
||||
}
|
||||
|
||||
it("should handle lazy loading properly even when offline", () => {
|
||||
const charly1to5 = charlies.slice(0, 5);
|
||||
const charly6to10 = charlies.slice(5);
|
||||
|
||||
// Set up room with alice, bob & charlies 1-5
|
||||
setupRoomWithBobAliceAndCharlies(charly1to5);
|
||||
// Alice should see 2 messages from every charly with the correct display name
|
||||
checkPaginatedDisplayNames(charly1to5);
|
||||
|
||||
openMemberlist();
|
||||
checkMemberList(charly1to5);
|
||||
joinCharliesWhileAliceIsOffline(charly6to10);
|
||||
checkMemberList(charly6to10);
|
||||
|
||||
cy.get<string>("@roomId").then(async roomId => {
|
||||
for (const charly of charlies) {
|
||||
await charly.client.leave(roomId);
|
||||
}
|
||||
});
|
||||
|
||||
checkMemberListLacksCharlies(charlies);
|
||||
});
|
||||
});
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
|||
|
||||
import request from "browser-request";
|
||||
|
||||
import type { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
|
||||
import type { ISendEventResponse, MatrixClient, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { SynapseInstance } from "../plugins/synapsedocker";
|
||||
import Chainable = Cypress.Chainable;
|
||||
|
||||
|
@ -31,10 +31,15 @@ interface CreateBotOpts {
|
|||
* The display name to give to that bot user
|
||||
*/
|
||||
displayName?: string;
|
||||
/**
|
||||
* Whether or not to start the syncing client.
|
||||
*/
|
||||
startClient?: boolean;
|
||||
}
|
||||
|
||||
const defaultCreateBotOptions = {
|
||||
autoAcceptInvites: true,
|
||||
startClient: true,
|
||||
} as CreateBotOpts;
|
||||
|
||||
declare global {
|
||||
|
@ -59,6 +64,13 @@ declare global {
|
|||
* @param roomName Name of the room to join
|
||||
*/
|
||||
botJoinRoomByName(cli: MatrixClient, roomName: string): Chainable<Room>;
|
||||
/**
|
||||
* Send a message as a bot into a room
|
||||
* @param cli The bot's MatrixClient
|
||||
* @param roomId ID of the room to join
|
||||
* @param message the message body to send
|
||||
*/
|
||||
botSendMessage(cli: MatrixClient, roomId: string, message: string): Chainable<ISendEventResponse>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -88,6 +100,10 @@ Cypress.Commands.add("getBot", (synapse: SynapseInstance, opts: CreateBotOpts):
|
|||
});
|
||||
}
|
||||
|
||||
if (!opts.startClient) {
|
||||
return cy.wrap(cli);
|
||||
}
|
||||
|
||||
return cy.wrap(
|
||||
cli.initCrypto()
|
||||
.then(() => cli.setGlobalErrorOnUnknownDevices(false))
|
||||
|
@ -114,3 +130,14 @@ Cypress.Commands.add("botJoinRoomByName", (cli: MatrixClient, roomName: string):
|
|||
|
||||
return cy.wrap(Promise.reject());
|
||||
});
|
||||
|
||||
Cypress.Commands.add("botSendMessage", (
|
||||
cli: MatrixClient,
|
||||
roomId: string,
|
||||
message: string,
|
||||
): Chainable<ISendEventResponse> => {
|
||||
return cy.wrap(cli.sendMessage(roomId, {
|
||||
msgtype: "m.text",
|
||||
body: message,
|
||||
}), { log: false });
|
||||
});
|
||||
|
|
|
@ -124,6 +124,11 @@ declare global {
|
|||
* Boostraps cross-signing.
|
||||
*/
|
||||
bootstrapCrossSigning(): Chainable<void>;
|
||||
/**
|
||||
* Joins the given room by alias or ID
|
||||
* @param roomIdOrAlias the id or alias of the room to join
|
||||
*/
|
||||
joinRoom(roomIdOrAlias: string): Chainable<Room>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -217,3 +222,7 @@ Cypress.Commands.add("bootstrapCrossSigning", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add("joinRoom", (roomIdOrAlias: string): Chainable<Room> => {
|
||||
return cy.getClient().then(cli => cli.joinRoom(roomIdOrAlias));
|
||||
});
|
||||
|
|
|
@ -33,3 +33,5 @@ import "./percy";
|
|||
import "./webserver";
|
||||
import "./views";
|
||||
import "./iframes";
|
||||
import "./timeline";
|
||||
import "./network";
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
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" />
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
// Intercept all /_matrix/ networking requests for the logged in user and fail them
|
||||
goOffline(): void;
|
||||
// Remove intercept on all /_matrix/ networking requests
|
||||
goOnline(): void;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We manage intercepting Matrix APIs here, as fully disabling networking will disconnect
|
||||
// the browser under test from the Cypress runner, so can cause issues.
|
||||
|
||||
Cypress.Commands.add("goOffline", (): void => {
|
||||
cy.log("Going offline");
|
||||
cy.window({ log: false }).then(win => {
|
||||
cy.intercept("**/_matrix/**", {
|
||||
headers: {
|
||||
"Authorization": "Bearer " + win.mxMatrixClientPeg.matrixClient.getAccessToken(),
|
||||
},
|
||||
}, req => {
|
||||
req.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add("goOnline", (): void => {
|
||||
cy.log("Going online");
|
||||
cy.window({ log: false }).then(win => {
|
||||
cy.intercept("**/_matrix/**", {
|
||||
headers: {
|
||||
"Authorization": "Bearer " + win.mxMatrixClientPeg.matrixClient.getAccessToken(),
|
||||
},
|
||||
}, req => {
|
||||
req.continue();
|
||||
});
|
||||
win.dispatchEvent(new Event("online"));
|
||||
});
|
||||
});
|
||||
|
||||
// Needed to make this file a module
|
||||
export { };
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
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 Chainable = Cypress.Chainable;
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
// Scroll to the top of the timeline
|
||||
scrollToTop(): void;
|
||||
// Find the event tile matching the given sender & body
|
||||
findEventTile(sender: string, body: string): Chainable<JQuery>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
sender: string;
|
||||
body: string;
|
||||
encrypted: boolean;
|
||||
continuation: boolean;
|
||||
}
|
||||
|
||||
Cypress.Commands.add("scrollToTop", (): void => {
|
||||
cy.get(".mx_RoomView_timeline .mx_ScrollPanel").scrollTo("top", { duration: 100 }).then(ref => {
|
||||
if (ref.scrollTop() > 0) {
|
||||
return cy.scrollToTop();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add("findEventTile", (sender: string, body: string): Chainable<JQuery> => {
|
||||
// We can't just use a bunch of `.contains` here due to continuations meaning that the events don't
|
||||
// have their own rendered sender displayname so we have to walk the list to keep track of the sender.
|
||||
return cy.get(".mx_RoomView_MessageList .mx_EventTile").then(refs => {
|
||||
let latestSender: string;
|
||||
for (let i = 0; i < refs.length; i++) {
|
||||
const ref = refs.eq(i);
|
||||
const displayName = ref.find(".mx_DisambiguatedProfile_displayName");
|
||||
if (displayName) {
|
||||
latestSender = displayName.text();
|
||||
}
|
||||
|
||||
if (latestSender === sender && ref.find(".mx_EventTile_body").text() === body) {
|
||||
return ref;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Needed to make this file a module
|
||||
export { };
|
|
@ -172,7 +172,7 @@
|
|||
"blob-polyfill": "^6.0.20211015",
|
||||
"chokidar": "^3.5.1",
|
||||
"cypress": "^10.3.0",
|
||||
"cypress-real-events": "^1.7.0",
|
||||
"cypress-real-events": "^1.7.1",
|
||||
"enzyme": "^3.11.0",
|
||||
"enzyme-to-json": "^3.6.2",
|
||||
"eslint": "8.9.0",
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2019 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 request = require('request-promise-native');
|
||||
import * as cheerio from 'cheerio';
|
||||
import * as url from "url";
|
||||
|
||||
export const approveConsent = async function(consentUrl: string): Promise<void> {
|
||||
const body = await request.get(consentUrl);
|
||||
const doc = cheerio.load(body);
|
||||
const v = doc("input[name=v]").val();
|
||||
const u = doc("input[name=u]").val();
|
||||
const h = doc("input[name=h]").val();
|
||||
const formAction = doc("form").attr("action");
|
||||
const absAction = url.resolve(consentUrl, formAction);
|
||||
await request.post(absAction).form({ v, u, h });
|
||||
};
|
|
@ -1,90 +0,0 @@
|
|||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2019 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 request = require('request-promise-native');
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
import { RestSession } from './session';
|
||||
import { RestMultiSession } from './multi';
|
||||
|
||||
export interface Credentials {
|
||||
accessToken: string;
|
||||
homeServer: string;
|
||||
userId: string;
|
||||
deviceId: string;
|
||||
hsUrl: string;
|
||||
}
|
||||
|
||||
export class RestSessionCreator {
|
||||
constructor(private readonly hsUrl: string, private readonly regSecret: string) {}
|
||||
|
||||
public async createSessionRange(usernames: string[], password: string,
|
||||
groupName: string): Promise<RestMultiSession> {
|
||||
const sessionPromises = usernames.map((username) => this.createSession(username, password));
|
||||
const sessions = await Promise.all(sessionPromises);
|
||||
return new RestMultiSession(sessions, groupName);
|
||||
}
|
||||
|
||||
public async createSession(username: string, password: string): Promise<RestSession> {
|
||||
await this.register(username, password);
|
||||
console.log(` * created REST user ${username} ... done`);
|
||||
const authResult = await this.authenticate(username, password);
|
||||
return new RestSession(authResult);
|
||||
}
|
||||
|
||||
private async register(username: string, password: string): Promise<void> {
|
||||
// get a nonce
|
||||
const regUrl = `${this.hsUrl}/_synapse/admin/v1/register`;
|
||||
const nonceResp = await request.get({ uri: regUrl, json: true });
|
||||
|
||||
const mac = crypto.createHmac('sha1', this.regSecret).update(
|
||||
`${nonceResp.nonce}\0${username}\0${password}\0notadmin`,
|
||||
).digest('hex');
|
||||
|
||||
await request.post({
|
||||
uri: regUrl,
|
||||
json: true,
|
||||
body: {
|
||||
nonce: nonceResp.nonce,
|
||||
username,
|
||||
password,
|
||||
mac,
|
||||
admin: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async authenticate(username: string, password: string): Promise<Credentials> {
|
||||
const requestBody = {
|
||||
"type": "m.login.password",
|
||||
"identifier": {
|
||||
"type": "m.id.user",
|
||||
"user": username,
|
||||
},
|
||||
"password": password,
|
||||
};
|
||||
const url = `${this.hsUrl}/_matrix/client/r0/login`;
|
||||
const responseBody = await request.post({ url, json: true, body: requestBody });
|
||||
return {
|
||||
accessToken: responseBody.access_token,
|
||||
homeServer: responseBody.home_server,
|
||||
userId: responseBody.user_id,
|
||||
deviceId: responseBody.device_id,
|
||||
hsUrl: this.hsUrl,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2019 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 { Logger } from '../logger';
|
||||
import { RestSession } from "./session";
|
||||
import { RestRoom } from "./room";
|
||||
|
||||
export class RestMultiSession {
|
||||
readonly log: Logger;
|
||||
|
||||
constructor(public readonly sessions: RestSession[], groupName: string) {
|
||||
this.log = new Logger(groupName);
|
||||
}
|
||||
|
||||
public slice(groupName: string, start: number, end?: number): RestMultiSession {
|
||||
return new RestMultiSession(this.sessions.slice(start, end), groupName);
|
||||
}
|
||||
|
||||
public pop(userName: string): RestSession {
|
||||
const idx = this.sessions.findIndex((s) => s.userName() === userName);
|
||||
if (idx === -1) {
|
||||
throw new Error(`user ${userName} not found`);
|
||||
}
|
||||
const session = this.sessions.splice(idx, 1)[0];
|
||||
return session;
|
||||
}
|
||||
|
||||
public async setDisplayName(fn: (s: RestSession) => string): Promise<void> {
|
||||
this.log.step("set their display name");
|
||||
await Promise.all(this.sessions.map(async (s: RestSession) => {
|
||||
s.log.mute();
|
||||
await s.setDisplayName(fn(s));
|
||||
s.log.unmute();
|
||||
}));
|
||||
this.log.done();
|
||||
}
|
||||
|
||||
public async join(roomIdOrAlias: string): Promise<RestMultiRoom> {
|
||||
this.log.step(`join ${roomIdOrAlias}`);
|
||||
const rooms = await Promise.all(this.sessions.map(async (s) => {
|
||||
s.log.mute();
|
||||
const room = await s.join(roomIdOrAlias);
|
||||
s.log.unmute();
|
||||
return room;
|
||||
}));
|
||||
this.log.done();
|
||||
return new RestMultiRoom(rooms, roomIdOrAlias, this.log);
|
||||
}
|
||||
|
||||
public room(roomIdOrAlias: string): RestMultiRoom {
|
||||
const rooms = this.sessions.map(s => s.room(roomIdOrAlias));
|
||||
return new RestMultiRoom(rooms, roomIdOrAlias, this.log);
|
||||
}
|
||||
}
|
||||
|
||||
class RestMultiRoom {
|
||||
constructor(public readonly rooms: RestRoom[], private readonly roomIdOrAlias: string,
|
||||
private readonly log: Logger) {}
|
||||
|
||||
public async talk(message: string): Promise<void> {
|
||||
this.log.step(`say "${message}" in ${this.roomIdOrAlias}`);
|
||||
await Promise.all(this.rooms.map(async (r: RestRoom) => {
|
||||
r.log.mute();
|
||||
await r.talk(message);
|
||||
r.log.unmute();
|
||||
}));
|
||||
this.log.done();
|
||||
}
|
||||
|
||||
public async leave() {
|
||||
this.log.step(`leave ${this.roomIdOrAlias}`);
|
||||
await Promise.all(this.rooms.map(async (r) => {
|
||||
r.log.mute();
|
||||
await r.leave();
|
||||
r.log.unmute();
|
||||
}));
|
||||
this.log.done();
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2019 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 uuidv4 = require('uuid/v4');
|
||||
|
||||
import { RestSession } from "./session";
|
||||
import { Logger } from "../logger";
|
||||
|
||||
/* no pun intended */
|
||||
export class RestRoom {
|
||||
constructor(readonly session: RestSession, readonly roomId: string, readonly log: Logger) {}
|
||||
|
||||
async talk(message: string): Promise<string> {
|
||||
this.log.step(`says "${message}" in ${this.roomId}`);
|
||||
const txId = uuidv4();
|
||||
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 eventId;
|
||||
}
|
||||
|
||||
async leave(): Promise<void> {
|
||||
this.log.step(`leaves ${this.roomId}`);
|
||||
await this.session.post(`/rooms/${this.roomId}/leave`);
|
||||
this.log.done();
|
||||
}
|
||||
}
|
|
@ -1,138 +0,0 @@
|
|||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
||||
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 request = require('request-promise-native');
|
||||
|
||||
import { Logger } from '../logger';
|
||||
import { RestRoom } from './room';
|
||||
import { approveConsent } from './consent';
|
||||
import { Credentials } from "./creator";
|
||||
|
||||
interface RoomOptions {
|
||||
invite?: string;
|
||||
public?: boolean;
|
||||
topic?: string;
|
||||
dm?: boolean;
|
||||
}
|
||||
|
||||
export class RestSession {
|
||||
private _displayName: string = null;
|
||||
private readonly rooms: Record<string, RestRoom> = {};
|
||||
readonly log: Logger;
|
||||
|
||||
constructor(private readonly credentials: Credentials) {
|
||||
this.log = new Logger(credentials.userId);
|
||||
}
|
||||
|
||||
userId(): string {
|
||||
return this.credentials.userId;
|
||||
}
|
||||
|
||||
userName(): string {
|
||||
return this.credentials.userId.split(":")[0].slice(1);
|
||||
}
|
||||
|
||||
displayName(): string {
|
||||
return this._displayName;
|
||||
}
|
||||
|
||||
async setDisplayName(displayName: string): Promise<void> {
|
||||
this.log.step(`sets their display name to ${displayName}`);
|
||||
this._displayName = displayName;
|
||||
await this.put(`/profile/${this.credentials.userId}/displayname`, {
|
||||
displayname: displayName,
|
||||
});
|
||||
this.log.done();
|
||||
}
|
||||
|
||||
async join(roomIdOrAlias: string): Promise<RestRoom> {
|
||||
this.log.step(`joins ${roomIdOrAlias}`);
|
||||
const roomId = (await this.post(`/join/${encodeURIComponent(roomIdOrAlias)}`)).room_id;
|
||||
this.log.done();
|
||||
const room = new RestRoom(this, roomId, this.log);
|
||||
this.rooms[roomId] = room;
|
||||
this.rooms[roomIdOrAlias] = room;
|
||||
return room;
|
||||
}
|
||||
|
||||
room(roomIdOrAlias: string): RestRoom {
|
||||
if (this.rooms.hasOwnProperty(roomIdOrAlias)) {
|
||||
return this.rooms[roomIdOrAlias];
|
||||
} else {
|
||||
throw new Error(`${this.credentials.userId} is not in ${roomIdOrAlias}`);
|
||||
}
|
||||
}
|
||||
|
||||
async createRoom(name: string, options: RoomOptions): Promise<RestRoom> {
|
||||
this.log.step(`creates room ${name}`);
|
||||
const body = {
|
||||
name,
|
||||
};
|
||||
if (options.invite) {
|
||||
body['invite'] = options.invite;
|
||||
}
|
||||
if (options.public) {
|
||||
body['visibility'] = "public";
|
||||
} else {
|
||||
body['visibility'] = "private";
|
||||
}
|
||||
if (options.dm) {
|
||||
body['is_direct'] = true;
|
||||
}
|
||||
if (options.topic) {
|
||||
body['topic'] = options.topic;
|
||||
}
|
||||
|
||||
const roomId = (await this.post(`/createRoom`, body)).room_id;
|
||||
this.log.done();
|
||||
return new RestRoom(this, roomId, this.log);
|
||||
}
|
||||
|
||||
post(csApiPath: string, body?: any): Promise<any> {
|
||||
return this.request("POST", csApiPath, body);
|
||||
}
|
||||
|
||||
put(csApiPath: string, body?: any): Promise<any> {
|
||||
return this.request("PUT", csApiPath, body);
|
||||
}
|
||||
|
||||
async request(method: string, csApiPath: string, body?: any): Promise<any> {
|
||||
try {
|
||||
return await request({
|
||||
url: `${this.credentials.hsUrl}/_matrix/client/r0${csApiPath}`,
|
||||
method,
|
||||
headers: {
|
||||
"Authorization": `Bearer ${this.credentials.accessToken}`,
|
||||
},
|
||||
json: true,
|
||||
body,
|
||||
});
|
||||
} catch (err) {
|
||||
if (!err.response) {
|
||||
throw err;
|
||||
}
|
||||
const responseBody = err.response.body;
|
||||
if (responseBody.errcode === 'M_CONSENT_NOT_GIVEN') {
|
||||
await approveConsent(responseBody.consent_uri);
|
||||
return this.request(method, csApiPath, body);
|
||||
} else if (responseBody && responseBody.error) {
|
||||
throw new Error(`${method} ${csApiPath}: ${responseBody.error}`);
|
||||
} else {
|
||||
throw new Error(`${method} ${csApiPath}: ${err.response.statusCode}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,18 +15,12 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { range } from './util';
|
||||
import { signup } from './usecases/signup';
|
||||
import { toastScenarios } from './scenarios/toast';
|
||||
import { lazyLoadingScenarios } from './scenarios/lazy-loading';
|
||||
import { e2eEncryptionScenarios } from './scenarios/e2e-encryption';
|
||||
import { ElementSession } from "./session";
|
||||
import { RestSessionCreator } from "./rest/creator";
|
||||
import { RestMultiSession } from "./rest/multi";
|
||||
import { RestSession } from "./rest/session";
|
||||
|
||||
export async function scenario(createSession: (s: string) => Promise<ElementSession>,
|
||||
restCreator: RestSessionCreator): Promise<void> {
|
||||
export async function scenario(createSession: (s: string) => Promise<ElementSession>): Promise<void> {
|
||||
let firstUser = true;
|
||||
async function createUser(username: string) {
|
||||
const session = await createSession(username);
|
||||
|
@ -45,14 +39,4 @@ export async function scenario(createSession: (s: string) => Promise<ElementSess
|
|||
|
||||
await toastScenarios(alice, bob);
|
||||
await e2eEncryptionScenarios(alice, bob);
|
||||
console.log("create REST users:");
|
||||
const charlies = await createRestUsers(restCreator);
|
||||
await lazyLoadingScenarios(alice, bob, charlies);
|
||||
}
|
||||
|
||||
async function createRestUsers(restCreator: RestSessionCreator): Promise<RestMultiSession> {
|
||||
const usernames = range(1, 10).map((i) => `charly-${i}`);
|
||||
const charlies = await restCreator.createSessionRange(usernames, "testtest", "charly-1..10");
|
||||
await charlies.setDisplayName((s: RestSession) => `Charly #${s.userName().split('-')[1]}`);
|
||||
return charlies;
|
||||
}
|
||||
|
|
|
@ -1,127 +0,0 @@
|
|||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2019 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 { delay } from '../util';
|
||||
import { join } from '../usecases/join';
|
||||
import { sendMessage } from '../usecases/send-message';
|
||||
import {
|
||||
checkTimelineContains,
|
||||
scrollToTimelineTop,
|
||||
} from '../usecases/timeline';
|
||||
import { createRoom } from '../usecases/create-room';
|
||||
import { getMembersInMemberlist } from '../usecases/memberlist';
|
||||
import { changeRoomSettings } from '../usecases/room-settings';
|
||||
import { RestMultiSession } from "../rest/multi";
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
export async function lazyLoadingScenarios(alice: ElementSession,
|
||||
bob: ElementSession, charlies: RestMultiSession): Promise<void> {
|
||||
console.log(" creating a room for lazy loading member scenarios:");
|
||||
const charly1to5 = charlies.slice("charly-1..5", 0, 5);
|
||||
const charly6to10 = charlies.slice("charly-6..10", 5);
|
||||
assert(charly1to5.sessions.length == 5);
|
||||
assert(charly6to10.sessions.length == 5);
|
||||
await setupRoomWithBobAliceAndCharlies(alice, bob, charly1to5);
|
||||
await checkPaginatedDisplayNames(alice, charly1to5);
|
||||
await checkMemberList(alice, charly1to5);
|
||||
await joinCharliesWhileAliceIsOffline(alice, charly6to10);
|
||||
await checkMemberList(alice, charly6to10);
|
||||
await charlies.room(alias).leave();
|
||||
await delay(1000);
|
||||
await checkMemberListLacksCharlies(alice, charlies);
|
||||
await checkMemberListLacksCharlies(bob, charlies);
|
||||
}
|
||||
|
||||
const room = "Lazy Loading Test";
|
||||
const alias = "#lltest:localhost";
|
||||
const charlyMsg1 = "hi bob!";
|
||||
const charlyMsg2 = "how's it going??";
|
||||
|
||||
async function setupRoomWithBobAliceAndCharlies(alice: ElementSession, bob: ElementSession,
|
||||
charlies: RestMultiSession): Promise<void> {
|
||||
await createRoom(bob, room);
|
||||
await changeRoomSettings(bob, { directory: true, visibility: "public", alias });
|
||||
// wait for alias to be set by server after clicking "save"
|
||||
// so the charlies can join it.
|
||||
await bob.delay(500);
|
||||
const charlyMembers = await charlies.join(alias);
|
||||
await charlyMembers.talk(charlyMsg1);
|
||||
await charlyMembers.talk(charlyMsg2);
|
||||
bob.log.step("sends 20 messages").mute();
|
||||
for (let i = 20; i >= 1; --i) {
|
||||
await sendMessage(bob, `I will only say this ${i} time(s)!`);
|
||||
}
|
||||
bob.log.unmute().done();
|
||||
await join(alice, alias);
|
||||
}
|
||||
|
||||
async function checkPaginatedDisplayNames(alice: ElementSession, charlies: RestMultiSession): Promise<void> {
|
||||
await scrollToTimelineTop(alice);
|
||||
//alice should see 2 messages from every charly with
|
||||
//the correct display name
|
||||
const expectedMessages = [charlyMsg1, charlyMsg2].reduce((messages, msgText) => {
|
||||
return charlies.sessions.reduce((messages, charly) => {
|
||||
return messages.concat({
|
||||
sender: charly.displayName(),
|
||||
body: msgText,
|
||||
});
|
||||
}, messages);
|
||||
}, []);
|
||||
await checkTimelineContains(alice, expectedMessages, charlies.log.username);
|
||||
}
|
||||
|
||||
async function checkMemberList(alice: ElementSession, charlies: RestMultiSession): Promise<void> {
|
||||
alice.log.step(`checks the memberlist contains herself, bob and ${charlies.log.username}`);
|
||||
const displayNames = (await getMembersInMemberlist(alice)).map((m) => m.displayName);
|
||||
assert(displayNames.includes("alice"));
|
||||
assert(displayNames.includes("bob"));
|
||||
charlies.sessions.forEach((charly) => {
|
||||
assert(displayNames.includes(charly.displayName()),
|
||||
`${charly.displayName()} should be in the member list, ` +
|
||||
`only have ${displayNames}`);
|
||||
});
|
||||
alice.log.done();
|
||||
}
|
||||
|
||||
async function checkMemberListLacksCharlies(session: ElementSession, charlies: RestMultiSession): Promise<void> {
|
||||
session.log.step(`checks the memberlist doesn't contain ${charlies.log.username}`);
|
||||
const displayNames = (await getMembersInMemberlist(session)).map((m) => m.displayName);
|
||||
charlies.sessions.forEach((charly) => {
|
||||
assert(!displayNames.includes(charly.displayName()),
|
||||
`${charly.displayName()} should not be in the member list, ` +
|
||||
`only have ${displayNames}`);
|
||||
});
|
||||
session.log.done();
|
||||
}
|
||||
|
||||
async function joinCharliesWhileAliceIsOffline(alice: ElementSession, charly6to10: RestMultiSession) {
|
||||
await alice.setOffline(true);
|
||||
await delay(1000);
|
||||
const members6to10 = await charly6to10.join(alias);
|
||||
const member6 = members6to10.rooms[0];
|
||||
member6.log.step("sends 20 messages").mute();
|
||||
for (let i = 20; i >= 1; --i) {
|
||||
await member6.talk("where is charly?");
|
||||
}
|
||||
member6.log.unmute().done();
|
||||
const catchupPromise = alice.waitForNextSuccessfulSync();
|
||||
await alice.setOffline(false);
|
||||
await catchupPromise;
|
||||
await delay(2000);
|
||||
}
|
|
@ -118,24 +118,6 @@ export class ElementSession {
|
|||
return await this.page.$$(selector);
|
||||
}
|
||||
|
||||
/** wait for a /sync request started after this call that gets a 200 response */
|
||||
public async waitForNextSuccessfulSync(): Promise<void> {
|
||||
const syncUrls = [];
|
||||
function onRequest(request) {
|
||||
if (request.url().indexOf("/sync") !== -1) {
|
||||
syncUrls.push(request.url());
|
||||
}
|
||||
}
|
||||
|
||||
this.page.on('request', onRequest);
|
||||
|
||||
await this.page.waitForResponse((response) => {
|
||||
return syncUrls.includes(response.request().url()) && response.status() === 200;
|
||||
});
|
||||
|
||||
this.page.off('request', onRequest);
|
||||
}
|
||||
|
||||
public async waitNoSpinner(): Promise<void> {
|
||||
await this.page.waitForSelector(".mx_Spinner", { hidden: true });
|
||||
}
|
||||
|
@ -152,13 +134,6 @@ export class ElementSession {
|
|||
return delay(ms);
|
||||
}
|
||||
|
||||
public async setOffline(enabled: boolean): Promise<void> {
|
||||
const description = enabled ? "offline" : "back online";
|
||||
this.log.step(`goes ${description}`);
|
||||
await this.page.setOfflineMode(enabled);
|
||||
this.log.done();
|
||||
}
|
||||
|
||||
public async close(): Promise<void> {
|
||||
return this.browser.close();
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { strict as assert } from 'assert';
|
||||
import { ElementHandle } from "puppeteer";
|
||||
|
||||
import { openRoomSummaryCard } from "./rightpanel";
|
||||
|
@ -29,46 +28,6 @@ export async function openMemberInfo(session: ElementSession, name: String): Pro
|
|||
await matchingLabel.click();
|
||||
}
|
||||
|
||||
interface Device {
|
||||
id: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export async function verifyDeviceForUser(session: ElementSession, name: string,
|
||||
expectedDevice: Device): Promise<void> {
|
||||
session.log.step(`verifies e2e device for ${name}`);
|
||||
const membersAndNames = await getMembersInMemberlist(session);
|
||||
const matchingLabel = membersAndNames.filter((m) => {
|
||||
return m.displayName === name;
|
||||
}).map((m) => m.label)[0];
|
||||
await matchingLabel.click();
|
||||
// click verify in member info
|
||||
const firstVerifyButton = await session.query(".mx_MemberDeviceInfo_verify");
|
||||
await firstVerifyButton.click();
|
||||
// expect "Verify device" dialog and click "Begin Verification"
|
||||
const dialogHeader = await session.innerText(await session.query(".mx_Dialog .mx_Dialog_title"));
|
||||
assert(dialogHeader, "Verify device");
|
||||
const beginVerificationButton = await session.query(".mx_Dialog .mx_Dialog_primary");
|
||||
await beginVerificationButton.click();
|
||||
// get emoji SAS labels
|
||||
const sasLabelElements = await session.queryAll(
|
||||
".mx_VerificationShowSas .mx_VerificationShowSas_emojiSas .mx_VerificationShowSas_emojiSas_label");
|
||||
const sasLabels = await Promise.all(sasLabelElements.map(e => session.innerText(e)));
|
||||
console.log("my sas labels", sasLabels);
|
||||
|
||||
const dialogCodeFields = await session.queryAll(".mx_QuestionDialog code");
|
||||
assert.strictEqual(dialogCodeFields.length, 2);
|
||||
const deviceId = await session.innerText(dialogCodeFields[0]);
|
||||
const deviceKey = await session.innerText(dialogCodeFields[1]);
|
||||
assert.strictEqual(expectedDevice.id, deviceId);
|
||||
assert.strictEqual(expectedDevice.key, deviceKey);
|
||||
const confirmButton = await session.query(".mx_Dialog_primary");
|
||||
await confirmButton.click();
|
||||
const closeMemberInfo = await session.query(".mx_MemberInfo_cancel");
|
||||
await closeMemberInfo.click();
|
||||
session.log.done();
|
||||
}
|
||||
|
||||
interface MemberName {
|
||||
label: ElementHandle;
|
||||
displayName: string;
|
||||
|
|
|
@ -20,32 +20,6 @@ import { ElementHandle } from "puppeteer";
|
|||
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
export async function scrollToTimelineTop(session: ElementSession): Promise<void> {
|
||||
session.log.step(`scrolls to the top of the timeline`);
|
||||
await session.page.evaluate(() => {
|
||||
return Promise.resolve().then(async () => {
|
||||
let timedOut = false;
|
||||
let timeoutHandle = null;
|
||||
// set scrollTop to 0 in a loop and check every 50ms
|
||||
// if content became available (scrollTop not being 0 anymore),
|
||||
// assume everything is loaded after 3s
|
||||
do {
|
||||
const timelineScrollView = document.querySelector(".mx_RoomView_timeline .mx_ScrollPanel");
|
||||
if (timelineScrollView && timelineScrollView.scrollTop !== 0) {
|
||||
if (timeoutHandle) {
|
||||
clearTimeout(timeoutHandle);
|
||||
}
|
||||
timeoutHandle = setTimeout(() => timedOut = true, 3000);
|
||||
timelineScrollView.scrollTop = 0;
|
||||
} else {
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
}
|
||||
} while (!timedOut);
|
||||
});
|
||||
});
|
||||
session.log.done();
|
||||
}
|
||||
|
||||
interface Message {
|
||||
sender: string;
|
||||
encrypted?: boolean;
|
||||
|
@ -79,41 +53,6 @@ export async function receiveMessage(session: ElementSession, expectedMessage: M
|
|||
session.log.done();
|
||||
}
|
||||
|
||||
export async function checkTimelineContains(session: ElementSession, expectedMessages: Message[],
|
||||
sendersDescription: string): Promise<void> {
|
||||
session.log.step(`checks timeline contains ${expectedMessages.length} ` +
|
||||
`given messages${sendersDescription ? ` from ${sendersDescription}`:""}`);
|
||||
const eventTiles = await getAllEventTiles(session);
|
||||
let timelineMessages: Message[] = await Promise.all(eventTiles.map((eventTile) => {
|
||||
return getMessageFromEventTile(eventTile);
|
||||
}));
|
||||
//filter out tiles that were not messages
|
||||
timelineMessages = timelineMessages.filter((m) => !!m);
|
||||
timelineMessages.reduce((prevSender: string, m) => {
|
||||
if (m.continuation) {
|
||||
m.sender = prevSender;
|
||||
return prevSender;
|
||||
} else {
|
||||
return m.sender;
|
||||
}
|
||||
}, "");
|
||||
|
||||
expectedMessages.forEach((expectedMessage) => {
|
||||
const foundMessage = timelineMessages.find((message) => {
|
||||
return message.sender === expectedMessage.sender &&
|
||||
message.body === expectedMessage.body;
|
||||
});
|
||||
try {
|
||||
assertMessage(foundMessage, expectedMessage);
|
||||
} catch (err) {
|
||||
console.log("timelineMessages", timelineMessages);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
session.log.done();
|
||||
}
|
||||
|
||||
function assertMessage(foundMessage: Message, expectedMessage: Message): void {
|
||||
assert(foundMessage, `message ${JSON.stringify(expectedMessage)} not found in timeline`);
|
||||
assert.equal(foundMessage.body, expectedMessage.body);
|
||||
|
@ -127,10 +66,6 @@ function getLastEventTile(session: ElementSession): Promise<ElementHandle> {
|
|||
return session.query(".mx_EventTile_last");
|
||||
}
|
||||
|
||||
function getAllEventTiles(session: ElementSession): Promise<ElementHandle[]> {
|
||||
return session.queryAll(".mx_RoomView_MessageList .mx_EventTile");
|
||||
}
|
||||
|
||||
async function getMessageFromEventTile(eventTile: ElementHandle): Promise<Message> {
|
||||
const senderElement = await eventTile.$(".mx_DisambiguatedProfile_displayName");
|
||||
const className: string = await (await eventTile.getProperty("className")).jsonValue();
|
||||
|
|
|
@ -20,14 +20,6 @@ import { padEnd } from "lodash";
|
|||
|
||||
import { ElementSession } from "./session";
|
||||
|
||||
export const range = function(start: number, amount: number, step = 1): Array<number> {
|
||||
const r = [];
|
||||
for (let i = 0; i < amount; ++i) {
|
||||
r.push(start + (i * step));
|
||||
}
|
||||
return r;
|
||||
};
|
||||
|
||||
export const delay = function(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
};
|
||||
|
|
|
@ -19,7 +19,6 @@ import { Command } from "commander";
|
|||
|
||||
import { ElementSession } from './src/session';
|
||||
import { scenario } from './src/scenario';
|
||||
import { RestSessionCreator } from './src/rest/creator';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
|
@ -54,12 +53,7 @@ async function runTests() {
|
|||
options['executablePath'] = path;
|
||||
}
|
||||
|
||||
const restCreator = new RestSessionCreator(
|
||||
hsUrl,
|
||||
program.opts().registrationSharedSecret,
|
||||
);
|
||||
|
||||
async function createSession(username) {
|
||||
async function createSession(username: string) {
|
||||
const session = await ElementSession.create(
|
||||
username, options, program.opts().appUrl, hsUrl, program.opts().throttleCpu,
|
||||
);
|
||||
|
@ -69,7 +63,7 @@ async function runTests() {
|
|||
|
||||
let failure = false;
|
||||
try {
|
||||
await scenario(createSession, restCreator);
|
||||
await scenario(createSession);
|
||||
} catch (err) {
|
||||
failure = true;
|
||||
console.log('failure: ', err);
|
||||
|
|
|
@ -3539,7 +3539,7 @@ csstype@^3.0.2:
|
|||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2"
|
||||
integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==
|
||||
|
||||
cypress-real-events@^1.7.0:
|
||||
cypress-real-events@^1.7.1:
|
||||
version "1.7.1"
|
||||
resolved "https://registry.yarnpkg.com/cypress-real-events/-/cypress-real-events-1.7.1.tgz#8f430d67c29ea4f05b9c5b0311780120cbc9b935"
|
||||
integrity sha512-/Bg15RgJ0SYsuXc6lPqH08x19z6j2vmhWN4wXfJqm3z8BTAFiK2MvipZPzxT8Z0jJP0q7kuniWrLIvz/i/8lCQ==
|
||||
|
|
Loading…
Reference in New Issue