Convert end-to-end tests to Typescript (#7206)

This commit is contained in:
James Salter 2021-12-06 09:59:06 +11:00 committed by GitHub
parent 5219b6be80
commit d4813f7a1a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 653 additions and 441 deletions

View file

@ -2,3 +2,4 @@ src/component-index.js
test/end-to-end-tests/node_modules/ test/end-to-end-tests/node_modules/
test/end-to-end-tests/element/ test/end-to-end-tests/element/
test/end-to-end-tests/synapse/ test/end-to-end-tests/synapse/
test/end-to-end-tests/lib/

View file

@ -1,8 +1,67 @@
# Update on docker hub with the following commands in the directory of this file: # Update on docker hub with the following commands in the directory of this file:
# docker build -t vectorim/element-web-ci-e2etests-env:latest . # If you're on linux amd64
# docker push vectorim/element-web-ci-e2etests-env:latest # docker build -t vectorim/element-web-ci-e2etests-env:latest .
# If you're on some other platform, you need to cross-compile
# docker buildx build --platform linux/amd64,linux/arm64 --push -t vectorim/element-web-ci-e2etests-env:latest .
# Then:
# docker push vectorim/element-web-ci-e2etests-env:latest
FROM node:14-buster FROM node:14-buster
RUN apt-get update RUN apt-get update
RUN apt-get -y install jq build-essential python3-dev libffi-dev python-pip python-setuptools sqlite3 libssl-dev python-virtualenv libjpeg-dev libxslt1-dev uuid-runtime RUN apt-get -y install \
build-essential \
jq \
libffi-dev \
libjpeg-dev \
libssl-dev \
libxslt1-dev \
python3-dev \
python-pip \
python-setuptools \
python-virtualenv \
sqlite3 \
uuid-runtime
# dependencies for chrome (installed by puppeteer) # dependencies for chrome (installed by puppeteer)
RUN apt-get -y install gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm-dev libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget RUN apt-get -y install \
ca-certificates \
fonts-liberation \
gconf-service \
libappindicator1 \
libasound2 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libc6 \
libcairo2 \
libcups2 \
libdbus-1-3 \
libexpat1 \
libfontconfig1 \
libgbm-dev \
libgcc1 \
libgconf-2-4 \
libgdk-pixbuf2.0-0 \
libglib2.0-0 \
libgtk-3-0 \
libnspr4 \
libnss3 \
libpango-1.0-0 \
libpangocairo-1.0-0 \
libstdc++6 \
libx11-6 \
libx11-xcb1 \
libxcb1 \
libxcomposite1 \
libxcursor1 \
libxdamage1 \
libxext6 \
libxfixes3 \
libxi6 \
libxrandr2 \
libxrender1 \
libxss1 \
libxtst6 \
lsb-release \
wget \
xdg-utils
RUN npm install -g typescript

View file

@ -67,7 +67,11 @@ const EncryptionInfo: React.FC<IProps> = ({
content = <PendingActionSpinner text={text} />; content = <PendingActionSpinner text={text} />;
} else { } else {
content = ( content = (
<AccessibleButton kind="primary" className="mx_UserInfo_wideButton" onClick={onStartVerification}> <AccessibleButton
kind="primary"
className="mx_UserInfo_wideButton mx_UserInfo_startVerification"
onClick={onStartVerification}
>
{ _t("Start Verification") } { _t("Start Verification") }
</AccessibleButton> </AccessibleButton>
); );

View file

@ -2,3 +2,5 @@ node_modules
*.png *.png
element/env element/env
performance-entries.json performance-entries.json
lib
logs

View file

@ -4,7 +4,8 @@
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc -p ./tsconfig.json"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
@ -15,5 +16,8 @@
"request": "^2.88.0", "request": "^2.88.0",
"request-promise-native": "^1.0.7", "request-promise-native": "^1.0.7",
"uuid": "^3.3.2" "uuid": "^3.3.2"
},
"devDependencies": {
"@types/puppeteer": "^5.4.4"
} }
} }

View file

@ -35,5 +35,6 @@ trap 'handle_error' ERR
if [ $has_custom_app -ne "1" ]; then if [ $has_custom_app -ne "1" ]; then
./element/start.sh ./element/start.sh
fi fi
node start.js $@ yarn build
node lib/start.js $@
stop_servers stop_servers

View file

@ -0,0 +1,24 @@
/*
Copyright 2021 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 "matrix-react-sdk/src/@types/global"; // load matrix-react-sdk's type extensions first
declare global {
interface Window {
mxPerformanceMonitor: any;
mxPerformanceEntryNames: any;
}
}

View file

@ -15,16 +15,20 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
module.exports = class LogBuffer { import { Page, PageEventObject } from "puppeteer";
constructor(page, eventName, eventMapper, reduceAsync=false, initialValue = "") {
export class LogBuffer<EventMapperArg extends Parameters<Parameters<Page['on']>[1]>[0]> {
buffer: string;
constructor(
page: Page,
eventName: keyof PageEventObject,
eventMapper: (arg: EventMapperArg) => Promise<string>,
initialValue = "",
) {
this.buffer = initialValue; this.buffer = initialValue;
page.on(eventName, (arg) => { page.on(eventName, (arg: EventMapperArg) => {
const result = eventMapper(arg); eventMapper(arg).then((r) => this.buffer += r);
if (reduceAsync) {
result.then((r) => this.buffer += r);
} else {
this.buffer += result;
}
}); });
} }
}; }

View file

@ -15,14 +15,13 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
module.exports = class Logger { export class Logger {
constructor(username) { private indent = 0;
this.indent = 0; private muted = false;
this.username = username;
this.muted = false;
}
startGroup(description) { constructor(readonly username: string) {}
public startGroup(description: string): Logger {
if (!this.muted) { if (!this.muted) {
const indent = " ".repeat(this.indent * 2); const indent = " ".repeat(this.indent * 2);
console.log(`${indent} * ${this.username} ${description}:`); console.log(`${indent} * ${this.username} ${description}:`);
@ -31,12 +30,12 @@ module.exports = class Logger {
return this; return this;
} }
endGroup() { public endGroup(): Logger {
this.indent -= 1; this.indent -= 1;
return this; return this;
} }
step(description) { public step(description: string): Logger {
if (!this.muted) { if (!this.muted) {
const indent = " ".repeat(this.indent * 2); const indent = " ".repeat(this.indent * 2);
process.stdout.write(`${indent} * ${this.username} ${description} ... `); process.stdout.write(`${indent} * ${this.username} ${description} ... `);
@ -44,20 +43,20 @@ module.exports = class Logger {
return this; return this;
} }
done(status = "done") { public done(status = "done"): Logger {
if (!this.muted) { if (!this.muted) {
process.stdout.write(status + "\n"); process.stdout.write(status + "\n");
} }
return this; return this;
} }
mute() { public mute(): Logger {
this.muted = true; this.muted = true;
return this; return this;
} }
unmute() { public unmute(): Logger {
this.muted = false; this.muted = false;
return this; return this;
} }
}; }

View file

@ -15,11 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const request = require('request-promise-native'); import request = require('request-promise-native');
const cheerio = require('cheerio'); import * as cheerio from 'cheerio';
const url = require("url"); import * as url from "url";
module.exports.approveConsent = async function(consentUrl) { export const approveConsent = async function(consentUrl: string): Promise<void> {
const body = await request.get(consentUrl); const body = await request.get(consentUrl);
const doc = cheerio.load(body); const doc = cheerio.load(body);
const v = doc("input[name=v]").val(); const v = doc("input[name=v]").val();

View file

@ -15,12 +15,17 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const { exec } = require('child_process'); import { exec } from 'child_process';
const request = require('request-promise-native'); import request = require('request-promise-native');
const RestSession = require('./session'); import { RestSession } from './session';
const RestMultiSession = require('./multi'); import { RestMultiSession } from './multi';
function execAsync(command, options) { interface ExecResult {
stdout: string;
stderr: string;
}
function execAsync(command: string, options: Parameters<typeof exec>[1]): Promise<ExecResult> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
exec(command, options, (error, stdout, stderr) => { exec(command, options, (error, stdout, stderr) => {
if (error) { if (error) {
@ -32,27 +37,32 @@ function execAsync(command, options) {
}); });
} }
module.exports = class RestSessionCreator { export interface Credentials {
constructor(synapseSubdir, hsUrl, cwd) { accessToken: string;
this.synapseSubdir = synapseSubdir; homeServer: string;
this.hsUrl = hsUrl; userId: string;
this.cwd = cwd; deviceId: string;
} hsUrl: string;
}
async createSessionRange(usernames, password, groupName) { export class RestSessionCreator {
constructor(private readonly synapseSubdir: string, private readonly hsUrl: string, private readonly cwd: string) {}
public async createSessionRange(usernames: string[], password: string,
groupName: string): Promise<RestMultiSession> {
const sessionPromises = usernames.map((username) => this.createSession(username, password)); const sessionPromises = usernames.map((username) => this.createSession(username, password));
const sessions = await Promise.all(sessionPromises); const sessions = await Promise.all(sessionPromises);
return new RestMultiSession(sessions, groupName); return new RestMultiSession(sessions, groupName);
} }
async createSession(username, password) { public async createSession(username: string, password: string): Promise<RestSession> {
await this._register(username, password); await this.register(username, password);
console.log(` * created REST user ${username} ... done`); console.log(` * created REST user ${username} ... done`);
const authResult = await this._authenticate(username, password); const authResult = await this.authenticate(username, password);
return new RestSession(authResult); return new RestSession(authResult);
} }
async _register(username, password) { private async register(username: string, password: string): Promise<void> {
const registerArgs = [ const registerArgs = [
'-c homeserver.yaml', '-c homeserver.yaml',
`-u ${username}`, `-u ${username}`,
@ -70,7 +80,7 @@ module.exports = class RestSessionCreator {
await execAsync(allCmds, { cwd: this.cwd, encoding: 'utf-8' }); await execAsync(allCmds, { cwd: this.cwd, encoding: 'utf-8' });
} }
async _authenticate(username, password) { private async authenticate(username: string, password: string): Promise<Credentials> {
const requestBody = { const requestBody = {
"type": "m.login.password", "type": "m.login.password",
"identifier": { "identifier": {
@ -89,4 +99,4 @@ module.exports = class RestSessionCreator {
hsUrl: this.hsUrl, hsUrl: this.hsUrl,
}; };
} }
}; }

View file

@ -15,19 +15,22 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const Logger = require('../logger'); import { Logger } from '../logger';
import { RestSession } from "./session";
import { RestRoom } from "./room";
module.exports = class RestMultiSession { export class RestMultiSession {
constructor(sessions, groupName) { readonly log: Logger;
constructor(public readonly sessions: RestSession[], groupName: string) {
this.log = new Logger(groupName); this.log = new Logger(groupName);
this.sessions = sessions;
} }
slice(groupName, start, end) { public slice(groupName: string, start: number, end?: number): RestMultiSession {
return new RestMultiSession(this.sessions.slice(start, end), groupName); return new RestMultiSession(this.sessions.slice(start, end), groupName);
} }
pop(userName) { public pop(userName: string): RestSession {
const idx = this.sessions.findIndex((s) => s.userName() === userName); const idx = this.sessions.findIndex((s) => s.userName() === userName);
if (idx === -1) { if (idx === -1) {
throw new Error(`user ${userName} not found`); throw new Error(`user ${userName} not found`);
@ -36,9 +39,9 @@ module.exports = class RestMultiSession {
return session; return session;
} }
async setDisplayName(fn) { public async setDisplayName(fn: (s: RestSession) => string): Promise<void> {
this.log.step("set their display name"); this.log.step("set their display name");
await Promise.all(this.sessions.map(async (s) => { await Promise.all(this.sessions.map(async (s: RestSession) => {
s.log.mute(); s.log.mute();
await s.setDisplayName(fn(s)); await s.setDisplayName(fn(s));
s.log.unmute(); s.log.unmute();
@ -46,7 +49,7 @@ module.exports = class RestMultiSession {
this.log.done(); this.log.done();
} }
async join(roomIdOrAlias) { public async join(roomIdOrAlias: string): Promise<RestMultiRoom> {
this.log.step(`join ${roomIdOrAlias}`); this.log.step(`join ${roomIdOrAlias}`);
const rooms = await Promise.all(this.sessions.map(async (s) => { const rooms = await Promise.all(this.sessions.map(async (s) => {
s.log.mute(); s.log.mute();
@ -58,22 +61,19 @@ module.exports = class RestMultiSession {
return new RestMultiRoom(rooms, roomIdOrAlias, this.log); return new RestMultiRoom(rooms, roomIdOrAlias, this.log);
} }
room(roomIdOrAlias) { public room(roomIdOrAlias: string): RestMultiRoom {
const rooms = this.sessions.map(s => s.room(roomIdOrAlias)); const rooms = this.sessions.map(s => s.room(roomIdOrAlias));
return new RestMultiRoom(rooms, roomIdOrAlias, this.log); return new RestMultiRoom(rooms, roomIdOrAlias, this.log);
} }
}; }
class RestMultiRoom { class RestMultiRoom {
constructor(rooms, roomIdOrAlias, log) { constructor(public readonly rooms: RestRoom[], private readonly roomIdOrAlias: string,
this.rooms = rooms; private readonly log: Logger) {}
this.roomIdOrAlias = roomIdOrAlias;
this.log = log;
}
async talk(message) { public async talk(message: string): Promise<void> {
this.log.step(`say "${message}" in ${this.roomIdOrAlias}`); this.log.step(`say "${message}" in ${this.roomIdOrAlias}`);
await Promise.all(this.rooms.map(async (r) => { await Promise.all(this.rooms.map(async (r: RestRoom) => {
r.log.mute(); r.log.mute();
await r.talk(message); await r.talk(message);
r.log.unmute(); r.log.unmute();
@ -81,7 +81,7 @@ class RestMultiRoom {
this.log.done(); this.log.done();
} }
async leave() { public async leave() {
this.log.step(`leave ${this.roomIdOrAlias}`); this.log.step(`leave ${this.roomIdOrAlias}`);
await Promise.all(this.rooms.map(async (r) => { await Promise.all(this.rooms.map(async (r) => {
r.log.mute(); r.log.mute();

View file

@ -15,20 +15,18 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const uuidv4 = require('uuid/v4'); import uuidv4 = require('uuid/v4');
import { RestSession } from "./session";
import { Logger } from "../logger";
/* no pun intented */ /* no pun intented */
module.exports = class RestRoom { export class RestRoom {
constructor(session, roomId, log) { constructor(readonly session: RestSession, readonly roomId: string, readonly log: Logger) {}
this.session = session;
this._roomId = roomId;
this.log = log;
}
async talk(message) { async talk(message: string): Promise<void> {
this.log.step(`says "${message}" in ${this._roomId}`); this.log.step(`says "${message}" in ${this.roomId}`);
const txId = uuidv4(); const txId = uuidv4();
await this.session._put(`/rooms/${this._roomId}/send/m.room.message/${txId}`, { await this.session.put(`/rooms/${this.roomId}/send/m.room.message/${txId}`, {
"msgtype": "m.text", "msgtype": "m.text",
"body": message, "body": message,
}); });
@ -36,13 +34,9 @@ module.exports = class RestRoom {
return txId; return txId;
} }
async leave() { async leave(): Promise<void> {
this.log.step(`leaves ${this._roomId}`); this.log.step(`leaves ${this.roomId}`);
await this.session._post(`/rooms/${this._roomId}/leave`); await this.session.post(`/rooms/${this.roomId}/leave`);
this.log.done(); this.log.done();
} }
}
roomId() {
return this._roomId;
}
};

View file

@ -1,126 +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.
*/
const request = require('request-promise-native');
const Logger = require('../logger');
const RestRoom = require('./room');
const { approveConsent } = require('./consent');
module.exports = class RestSession {
constructor(credentials) {
this.log = new Logger(credentials.userId);
this._credentials = credentials;
this._displayName = null;
this._rooms = {};
}
userId() {
return this._credentials.userId;
}
userName() {
return this._credentials.userId.split(":")[0].substr(1);
}
displayName() {
return this._displayName;
}
async setDisplayName(displayName) {
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) {
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) {
if (this._rooms.hasOwnProperty(roomIdOrAlias)) {
return this._rooms[roomIdOrAlias];
} else {
throw new Error(`${this._credentials.userId} is not in ${roomIdOrAlias}`);
}
}
async createRoom(name, options) {
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, body) {
return this._request("POST", csApiPath, body);
}
_put(csApiPath, body) {
return this._request("PUT", csApiPath, body);
}
async _request(method, csApiPath, body) {
try {
const responseBody = await request({
url: `${this._credentials.hsUrl}/_matrix/client/r0${csApiPath}`,
method,
headers: {
"Authorization": `Bearer ${this._credentials.accessToken}`,
},
json: true,
body,
});
return responseBody;
} catch (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}`);
}
}
}
};

View file

@ -0,0 +1,137 @@
/*
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].substr(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}`);
}
}
}
}

View file

@ -14,15 +14,20 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const { range } = require('./util'); import { range } from './util';
const signup = require('./usecases/signup'); import { signup } from './usecases/signup';
const toastScenarios = require('./scenarios/toast'); import { toastScenarios } from './scenarios/toast';
const roomDirectoryScenarios = require('./scenarios/directory'); import { roomDirectoryScenarios } from './scenarios/directory';
const lazyLoadingScenarios = require('./scenarios/lazy-loading'); import { lazyLoadingScenarios } from './scenarios/lazy-loading';
const e2eEncryptionScenarios = require('./scenarios/e2e-encryption'); import { e2eEncryptionScenarios } from './scenarios/e2e-encryption';
const spacesScenarios = require('./scenarios/spaces'); import { ElementSession } from "./session";
import { RestSessionCreator } from "./rest/creator";
import { RestMultiSession } from "./rest/multi";
import { spacesScenarios } from './scenarios/spaces';
import { RestSession } from "./rest/session";
module.exports = async function scenario(createSession, restCreator) { export async function scenario(createSession: (s: string) => Promise<ElementSession>,
restCreator: RestSessionCreator): Promise<void> {
let firstUser = true; let firstUser = true;
async function createUser(username) { async function createUser(username) {
const session = await createSession(username); const session = await createSession(username);
@ -46,11 +51,11 @@ module.exports = async function scenario(createSession, restCreator) {
await lazyLoadingScenarios(alice, bob, charlies); await lazyLoadingScenarios(alice, bob, charlies);
// do spaces scenarios last as the rest of the tests may get confused by spaces // do spaces scenarios last as the rest of the tests may get confused by spaces
await spacesScenarios(alice, bob); await spacesScenarios(alice, bob);
}; }
async function createRestUsers(restCreator) { async function createRestUsers(restCreator: RestSessionCreator): Promise<RestMultiSession> {
const usernames = range(1, 10).map((i) => `charly-${i}`); const usernames = range(1, 10).map((i) => `charly-${i}`);
const charlies = await restCreator.createSessionRange(usernames, "testtest", "charly-1..10"); const charlies = await restCreator.createSessionRange(usernames, "testtest", "charly-1..10");
await charlies.setDisplayName((s) => `Charly #${s.userName().split('-')[1]}`); await charlies.setDisplayName((s: RestSession) => `Charly #${s.userName().split('-')[1]}`);
return charlies; return charlies;
} }

View file

@ -15,13 +15,14 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const join = require('../usecases/join'); import { join } from '../usecases/join';
const sendMessage = require('../usecases/send-message'); import { sendMessage } from '../usecases/send-message';
const { receiveMessage } = require('../usecases/timeline'); import { receiveMessage } from '../usecases/timeline';
const { createRoom } = require('../usecases/create-room'); import { createRoom } from '../usecases/create-room';
const { changeRoomSettings } = require('../usecases/room-settings'); import { changeRoomSettings } from '../usecases/room-settings';
import { ElementSession } from "../session";
module.exports = async function roomDirectoryScenarios(alice, bob) { export async function roomDirectoryScenarios(alice: ElementSession, bob: ElementSession) {
console.log(" creating a public room and join through directory:"); console.log(" creating a public room and join through directory:");
const room = 'test'; const room = 'test';
await createRoom(alice, room); await createRoom(alice, room);
@ -33,4 +34,4 @@ module.exports = async function roomDirectoryScenarios(alice, bob) {
const aliceMessage = "hi Bob, welcome!"; const aliceMessage = "hi Bob, welcome!";
await sendMessage(alice, aliceMessage); await sendMessage(alice, aliceMessage);
await receiveMessage(bob, { sender: "alice", body: aliceMessage }); await receiveMessage(bob, { sender: "alice", body: aliceMessage });
}; }

View file

@ -15,22 +15,24 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const sendMessage = require('../usecases/send-message'); import { ElementSession } from "../session";
const acceptInvite = require('../usecases/accept-invite');
const { receiveMessage } = require('../usecases/timeline');
const { createDm } = require('../usecases/create-room');
const { checkRoomSettings } = require('../usecases/room-settings');
const { startSasVerification, acceptSasVerification } = require('../usecases/verify');
const { setupSecureBackup } = require('../usecases/security');
const assert = require('assert');
const { measureStart, measureStop } = require('../util');
module.exports = async function e2eEncryptionScenarios(alice, bob) { import { sendMessage } from '../usecases/send-message';
import { acceptInvite } from '../usecases/accept-invite';
import { receiveMessage } from '../usecases/timeline';
import { createDm } from '../usecases/create-room';
import { checkRoomSettings } from '../usecases/room-settings';
import { startSasVerification, acceptSasVerification } from '../usecases/verify';
import { setupSecureBackup } from '../usecases/security';
import { strict as assert } from 'assert';
import { measureStart, measureStop } from '../util';
export async function e2eEncryptionScenarios(alice: ElementSession, bob: ElementSession) {
console.log(" creating an e2e encrypted DM and join through invite:"); console.log(" creating an e2e encrypted DM and join through invite:");
await createDm(bob, ['@alice:localhost']); await createDm(bob, ['@alice:localhost']);
await checkRoomSettings(bob, { encryption: true }); // for sanity, should be e2e-by-default await checkRoomSettings(bob, { encryption: true }); // for sanity, should be e2e-by-default
await acceptInvite(alice, 'bob'); await acceptInvite(alice, 'bob');
// do sas verifcation // do sas verification
bob.log.step(`starts SAS verification with ${alice.username}`); bob.log.step(`starts SAS verification with ${alice.username}`);
await measureStart(bob, "mx_VerifyE2EEUser"); await measureStart(bob, "mx_VerifyE2EEUser");
const bobSasPromise = startSasVerification(bob, alice.username); const bobSasPromise = startSasVerification(bob, alice.username);
@ -48,4 +50,4 @@ module.exports = async function e2eEncryptionScenarios(alice, bob) {
await sendMessage(bob, bobMessage); await sendMessage(bob, bobMessage);
await receiveMessage(alice, { sender: "bob", body: bobMessage, encrypted: true }); await receiveMessage(alice, { sender: "bob", body: bobMessage, encrypted: true });
await setupSecureBackup(alice); await setupSecureBackup(alice);
}; }

View file

@ -15,24 +15,27 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const { delay } = require('../util'); import { delay } from '../util';
const join = require('../usecases/join'); import { join } from '../usecases/join';
const sendMessage = require('../usecases/send-message'); import { sendMessage } from '../usecases/send-message';
const { import {
checkTimelineContains, checkTimelineContains,
scrollToTimelineTop, scrollToTimelineTop,
} = require('../usecases/timeline'); } from '../usecases/timeline';
const { createRoom } = require('../usecases/create-room'); import { createRoom } from '../usecases/create-room';
const { getMembersInMemberlist } = require('../usecases/memberlist'); import { getMembersInMemberlist } from '../usecases/memberlist';
const { changeRoomSettings } = require('../usecases/room-settings'); import { changeRoomSettings } from '../usecases/room-settings';
const assert = require('assert'); import { strict as assert } from 'assert';
import { RestMultiSession } from "../rest/multi";
import { ElementSession } from "../session";
module.exports = async function lazyLoadingScenarios(alice, bob, charlies) { export async function lazyLoadingScenarios(alice: ElementSession,
bob: ElementSession, charlies: RestMultiSession): Promise<void> {
console.log(" creating a room for lazy loading member scenarios:"); console.log(" creating a room for lazy loading member scenarios:");
const charly1to5 = charlies.slice("charly-1..5", 0, 5); const charly1to5 = charlies.slice("charly-1..5", 0, 5);
const charly6to10 = charlies.slice("charly-6..10", 5); const charly6to10 = charlies.slice("charly-6..10", 5);
assert(charly1to5.sessions.length, 5); assert(charly1to5.sessions.length == 5);
assert(charly6to10.sessions.length, 5); assert(charly6to10.sessions.length == 5);
await setupRoomWithBobAliceAndCharlies(alice, bob, charly1to5); await setupRoomWithBobAliceAndCharlies(alice, bob, charly1to5);
await checkPaginatedDisplayNames(alice, charly1to5); await checkPaginatedDisplayNames(alice, charly1to5);
await checkMemberList(alice, charly1to5); await checkMemberList(alice, charly1to5);
@ -42,14 +45,15 @@ module.exports = async function lazyLoadingScenarios(alice, bob, charlies) {
await delay(1000); await delay(1000);
await checkMemberListLacksCharlies(alice, charlies); await checkMemberListLacksCharlies(alice, charlies);
await checkMemberListLacksCharlies(bob, charlies); await checkMemberListLacksCharlies(bob, charlies);
}; }
const room = "Lazy Loading Test"; const room = "Lazy Loading Test";
const alias = "#lltest:localhost"; const alias = "#lltest:localhost";
const charlyMsg1 = "hi bob!"; const charlyMsg1 = "hi bob!";
const charlyMsg2 = "how's it going??"; const charlyMsg2 = "how's it going??";
async function setupRoomWithBobAliceAndCharlies(alice, bob, charlies) { async function setupRoomWithBobAliceAndCharlies(alice: ElementSession, bob: ElementSession,
charlies: RestMultiSession): Promise<void> {
await createRoom(bob, room); await createRoom(bob, room);
await changeRoomSettings(bob, { directory: true, visibility: "public", alias }); await changeRoomSettings(bob, { directory: true, visibility: "public", alias });
// wait for alias to be set by server after clicking "save" // wait for alias to be set by server after clicking "save"
@ -66,7 +70,7 @@ async function setupRoomWithBobAliceAndCharlies(alice, bob, charlies) {
await join(alice, alias); await join(alice, alias);
} }
async function checkPaginatedDisplayNames(alice, charlies) { async function checkPaginatedDisplayNames(alice: ElementSession, charlies: RestMultiSession): Promise<void> {
await scrollToTimelineTop(alice); await scrollToTimelineTop(alice);
//alice should see 2 messages from every charly with //alice should see 2 messages from every charly with
//the correct display name //the correct display name
@ -81,7 +85,7 @@ async function checkPaginatedDisplayNames(alice, charlies) {
await checkTimelineContains(alice, expectedMessages, charlies.log.username); await checkTimelineContains(alice, expectedMessages, charlies.log.username);
} }
async function checkMemberList(alice, charlies) { async function checkMemberList(alice: ElementSession, charlies: RestMultiSession): Promise<void> {
alice.log.step(`checks the memberlist contains herself, bob and ${charlies.log.username}`); alice.log.step(`checks the memberlist contains herself, bob and ${charlies.log.username}`);
const displayNames = (await getMembersInMemberlist(alice)).map((m) => m.displayName); const displayNames = (await getMembersInMemberlist(alice)).map((m) => m.displayName);
assert(displayNames.includes("alice")); assert(displayNames.includes("alice"));
@ -94,7 +98,7 @@ async function checkMemberList(alice, charlies) {
alice.log.done(); alice.log.done();
} }
async function checkMemberListLacksCharlies(session, charlies) { async function checkMemberListLacksCharlies(session: ElementSession, charlies: RestMultiSession): Promise<void> {
session.log.step(`checks the memberlist doesn't contain ${charlies.log.username}`); session.log.step(`checks the memberlist doesn't contain ${charlies.log.username}`);
const displayNames = (await getMembersInMemberlist(session)).map((m) => m.displayName); const displayNames = (await getMembersInMemberlist(session)).map((m) => m.displayName);
charlies.sessions.forEach((charly) => { charlies.sessions.forEach((charly) => {
@ -105,7 +109,7 @@ async function checkMemberListLacksCharlies(session, charlies) {
session.log.done(); session.log.done();
} }
async function joinCharliesWhileAliceIsOffline(alice, charly6to10) { async function joinCharliesWhileAliceIsOffline(alice: ElementSession, charly6to10: RestMultiSession) {
await alice.setOffline(true); await alice.setOffline(true);
await delay(1000); await delay(1000);
const members6to10 = await charly6to10.join(alias); const members6to10 = await charly6to10.join(alias);

View file

@ -1,3 +1,5 @@
import { ElementSession } from "../session";
/* /*
Copyright 2021 The Matrix.org Foundation C.I.C. Copyright 2021 The Matrix.org Foundation C.I.C.
@ -14,18 +16,18 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const { createSpace, inviteSpace } = require("../usecases/create-space"); import { createSpace, inviteSpace } from "../usecases/create-space";
module.exports = async function spacesScenarios(alice, bob) { export async function spacesScenarios(alice: ElementSession, bob: ElementSession): Promise<void> {
console.log(" creating a space for spaces scenarios:"); console.log(" creating a space for spaces scenarios:");
await alice.delay(1000); // wait for dialogs to close await alice.delay(1000); // wait for dialogs to close
await setupSpaceUsingAliceAndInviteBob(alice, bob); await setupSpaceUsingAliceAndInviteBob(alice, bob);
}; }
const space = "Test Space"; const space = "Test Space";
async function setupSpaceUsingAliceAndInviteBob(alice, bob) { async function setupSpaceUsingAliceAndInviteBob(alice: ElementSession, bob: ElementSession): Promise<void> {
await createSpace(alice, space); await createSpace(alice, space);
await inviteSpace(alice, space, "@bob:localhost"); await inviteSpace(alice, space, "@bob:localhost");
await bob.query(`.mx_SpaceButton[aria-label="${space}"]`); // assert invite received await bob.query(`.mx_SpaceButton[aria-label="${space}"]`); // assert invite received

View file

@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const { assertNoToasts, acceptToast, rejectToast } = require("../usecases/toasts"); import { assertNoToasts, acceptToast, rejectToast } from "../usecases/toasts";
import { ElementSession } from "../session";
module.exports = async function toastScenarios(alice, bob) { export async function toastScenarios(alice: ElementSession, bob: ElementSession): Promise<void> {
console.log(" checking and clearing toasts:"); console.log(" checking and clearing toasts:");
alice.log.startGroup(`clears toasts`); alice.log.startGroup(`clears toasts`);
@ -48,4 +49,4 @@ module.exports = async function toastScenarios(alice, bob) {
await assertNoToasts(bob); await assertNoToasts(bob);
bob.log.done(); bob.log.done();
bob.log.endGroup(); bob.log.endGroup();
}; }

View file

@ -15,30 +15,37 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const puppeteer = require('puppeteer'); import * as puppeteer from 'puppeteer';
const Logger = require('./logger'); import { Logger } from './logger';
const LogBuffer = require('./logbuffer'); import { LogBuffer } from './logbuffer';
const { delay } = require('./util'); import { delay } from './util';
const DEFAULT_TIMEOUT = 20000; const DEFAULT_TIMEOUT = 20000;
module.exports = class ElementSession { interface XHRLogger {
constructor(browser, page, username, elementServer, hsUrl) { logs: () => string;
this.browser = browser; }
this.page = page;
this.hsUrl = hsUrl; export class ElementSession {
this.elementServer = elementServer; readonly consoleLog: LogBuffer<puppeteer.ConsoleMessage>;
this.username = username; readonly networkLog: LogBuffer<puppeteer.HTTPRequest>;
this.consoleLog = new LogBuffer(page, "console", (msg) => `${msg.text()}\n`); readonly log: Logger;
this.networkLog = new LogBuffer(page, "requestfinished", async (req) => {
const type = req.resourceType(); constructor(readonly browser: puppeteer.Browser, readonly page: puppeteer.Page, readonly username: string,
const response = await req.response(); readonly elementServer: string, readonly hsUrl: string) {
return `${type} ${response.status()} ${req.method()} ${req.url()} \n`; this.consoleLog = new LogBuffer(page, "console",
}, true); async (msg: puppeteer.ConsoleMessage) => Promise.resolve(`${msg.text()}\n`));
this.networkLog = new LogBuffer(page,
"requestfinished", async (req: puppeteer.HTTPRequest) => {
const type = req.resourceType();
const response = await req.response();
return `${type} ${response.status()} ${req.method()} ${req.url()} \n`;
});
this.log = new Logger(this.username); this.log = new Logger(this.username);
} }
static async create(username, puppeteerOptions, elementServer, hsUrl, throttleCpuFactor = 1) { public static async create(username: string, puppeteerOptions: Parameters<typeof puppeteer.launch>[0],
elementServer: string, hsUrl: string, throttleCpuFactor = 1): Promise<ElementSession> {
const browser = await puppeteer.launch(puppeteerOptions); const browser = await puppeteer.launch(puppeteerOptions);
const page = await browser.newPage(); const page = await browser.newPage();
await page.setViewport({ await page.setViewport({
@ -53,7 +60,7 @@ module.exports = class ElementSession {
return new ElementSession(browser, page, username, elementServer, hsUrl); return new ElementSession(browser, page, username, elementServer, hsUrl);
} }
async tryGetInnertext(selector) { public async tryGetInnertext(selector: string): Promise<string> {
const field = await this.page.$(selector); const field = await this.page.$(selector);
if (field != null) { if (field != null) {
const textHandle = await field.getProperty('innerText'); const textHandle = await field.getProperty('innerText');
@ -62,32 +69,32 @@ module.exports = class ElementSession {
return null; return null;
} }
async getElementProperty(handle, property) { public async getElementProperty(handle: puppeteer.ElementHandle, property: string): Promise<string> {
const propHandle = await handle.getProperty(property); const propHandle = await handle.getProperty(property);
return await propHandle.jsonValue(); return await propHandle.jsonValue();
} }
innerText(field) { public innerText(field: puppeteer.ElementHandle): Promise<string> {
return this.getElementProperty(field, 'innerText'); return this.getElementProperty(field, 'innerText');
} }
getOuterHTML(field) { public getOuterHTML(field: puppeteer.ElementHandle): Promise<string> {
return this.getElementProperty(field, 'outerHTML'); return this.getElementProperty(field, 'outerHTML');
} }
isChecked(field) { public isChecked(field: puppeteer.ElementHandle): Promise<string> {
return this.getElementProperty(field, 'checked'); return this.getElementProperty(field, 'checked');
} }
consoleLogs() { public consoleLogs(): string {
return this.consoleLog.buffer; return this.consoleLog.buffer;
} }
networkLogs() { public networkLogs(): string {
return this.networkLog.buffer; return this.networkLog.buffer;
} }
logXHRRequests() { public logXHRRequests(): XHRLogger {
let buffer = ""; let buffer = "";
this.page.on('requestfinished', async (req) => { this.page.on('requestfinished', async (req) => {
const type = req.resourceType(); const type = req.resourceType();
@ -106,11 +113,11 @@ module.exports = class ElementSession {
}; };
} }
async printElements(label, elements) { public async printElements(label: string, elements: puppeteer.ElementHandle[] ): Promise<void> {
console.log(label, await Promise.all(elements.map(this.getOuterHTML))); console.log(label, await Promise.all(elements.map(this.getOuterHTML)));
} }
async replaceInputText(input, text) { public async replaceInputText(input: puppeteer.ElementHandle, text: string): Promise<void> {
// click 3 times to select all text // click 3 times to select all text
await input.click({ clickCount: 3 }); await input.click({ clickCount: 3 });
// waiting here solves not having selected all the text by the 3x click above, // waiting here solves not having selected all the text by the 3x click above,
@ -122,21 +129,22 @@ module.exports = class ElementSession {
await input.type(text); await input.type(text);
} }
query(selector, timeout = DEFAULT_TIMEOUT, hidden = false) { public query(selector: string, timeout: number = DEFAULT_TIMEOUT,
hidden = false): Promise<puppeteer.ElementHandle> {
return this.page.waitForSelector(selector, { visible: true, timeout, hidden }); return this.page.waitForSelector(selector, { visible: true, timeout, hidden });
} }
async queryAll(selector) { public async queryAll(selector: string): Promise<puppeteer.ElementHandle[]> {
const timeout = DEFAULT_TIMEOUT; const timeout = DEFAULT_TIMEOUT;
await this.query(selector, timeout); await this.query(selector, timeout);
return await this.page.$$(selector); return await this.page.$$(selector);
} }
waitForReload() { public waitForReload(): Promise<void> {
const timeout = DEFAULT_TIMEOUT; const timeout = DEFAULT_TIMEOUT;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const timeoutHandle = setTimeout(() => { const timeoutHandle = setTimeout(() => {
this.browser.removeEventListener('domcontentloaded', callback); this.page.off('domcontentloaded', callback);
reject(new Error(`timeout of ${timeout}ms for waitForReload elapsed`)); reject(new Error(`timeout of ${timeout}ms for waitForReload elapsed`));
}, timeout); }, timeout);
@ -149,11 +157,11 @@ module.exports = class ElementSession {
}); });
} }
waitForNewPage() { public waitForNewPage(): Promise<void> {
const timeout = DEFAULT_TIMEOUT; const timeout = DEFAULT_TIMEOUT;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const timeoutHandle = setTimeout(() => { const timeoutHandle = setTimeout(() => {
this.browser.removeListener('targetcreated', callback); this.browser.off('targetcreated', callback);
reject(new Error(`timeout of ${timeout}ms for waitForNewPage elapsed`)); reject(new Error(`timeout of ${timeout}ms for waitForNewPage elapsed`));
}, timeout); }, timeout);
@ -161,7 +169,7 @@ module.exports = class ElementSession {
if (target.type() !== 'page') { if (target.type() !== 'page') {
return; return;
} }
this.browser.removeListener('targetcreated', callback); this.browser.off('targetcreated', callback);
clearTimeout(timeoutHandle); clearTimeout(timeoutHandle);
const page = await target.page(); const page = await target.page();
resolve(page); resolve(page);
@ -172,7 +180,7 @@ module.exports = class ElementSession {
} }
/** wait for a /sync request started after this call that gets a 200 response */ /** wait for a /sync request started after this call that gets a 200 response */
async waitForNextSuccessfulSync() { public async waitForNextSuccessfulSync(): Promise<void> {
const syncUrls = []; const syncUrls = [];
function onRequest(request) { function onRequest(request) {
if (request.url().indexOf("/sync") !== -1) { if (request.url().indexOf("/sync") !== -1) {
@ -186,33 +194,33 @@ module.exports = class ElementSession {
return syncUrls.includes(response.request().url()) && response.status() === 200; return syncUrls.includes(response.request().url()) && response.status() === 200;
}); });
this.page.removeListener('request', onRequest); this.page.off('request', onRequest);
} }
goto(url) { public goto(url: string): Promise<puppeteer.HTTPResponse> {
return this.page.goto(url); return this.page.goto(url);
} }
url(path) { public url(path: string): string {
return this.elementServer + path; return this.elementServer + path;
} }
delay(ms) { public delay(ms: number) {
return delay(ms); return delay(ms);
} }
async setOffline(enabled) { public async setOffline(enabled: boolean): Promise<void> {
const description = enabled ? "offline" : "back online"; const description = enabled ? "offline" : "back online";
this.log.step(`goes ${description}`); this.log.step(`goes ${description}`);
await this.page.setOfflineMode(enabled); await this.page.setOfflineMode(enabled);
this.log.done(); this.log.done();
} }
async close() { public async close(): Promise<void> {
return this.browser.close(); return this.browser.close();
} }
async poll(callback, interval = 100) { public async poll(callback: () => Promise<boolean>, interval = 100): Promise<boolean> {
const timeout = DEFAULT_TIMEOUT; const timeout = DEFAULT_TIMEOUT;
let waited = 0; let waited = 0;
while (waited < timeout) { while (waited < timeout) {
@ -224,4 +232,4 @@ module.exports = class ElementSession {
} }
return false; return false;
} }
}; }

View file

@ -15,9 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const { findSublist } = require("./create-room"); import { findSublist } from "./create-room";
import { ElementSession } from "../session";
module.exports = async function acceptInvite(session, name) { export async function acceptInvite(session: ElementSession, name: string): Promise<void> {
session.log.step(`accepts "${name}" invite`); session.log.step(`accepts "${name}" invite`);
const inviteSublist = await findSublist(session, "invites"); const inviteSublist = await findSublist(session, "invites");
const invitesHandles = await inviteSublist.$$(".mx_RoomTile_name"); const invitesHandles = await inviteSublist.$$(".mx_RoomTile_name");
@ -35,4 +36,4 @@ module.exports = async function acceptInvite(session, name) {
await acceptInvitationLink.click(); await acceptInvitationLink.click();
session.log.done(); session.log.done();
}; }

View file

@ -15,18 +15,20 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const { measureStart, measureStop } = require('../util'); import { measureStart, measureStop } from '../util';
import { ElementSession } from "../session";
import * as puppeteer from "puppeteer";
async function openRoomDirectory(session) { export async function openRoomDirectory(session: ElementSession): Promise<void> {
const roomDirectoryButton = await session.query('.mx_LeftPanel_exploreButton'); const roomDirectoryButton = await session.query('.mx_LeftPanel_exploreButton');
await roomDirectoryButton.click(); await roomDirectoryButton.click();
} }
async function findSublist(session, name) { export async function findSublist(session: ElementSession, name: string): Promise<puppeteer.ElementHandle> {
return await session.query(`.mx_RoomSublist[aria-label="${name}" i]`); return await session.query(`.mx_RoomSublist[aria-label="${name}" i]`);
} }
async function createRoom(session, roomName, encrypted=false) { export async function createRoom(session: ElementSession, roomName: string, encrypted = false): Promise<void> {
session.log.step(`creates room "${roomName}"`); session.log.step(`creates room "${roomName}"`);
const roomsSublist = await findSublist(session, "rooms"); const roomsSublist = await findSublist(session, "rooms");
@ -51,7 +53,7 @@ async function createRoom(session, roomName, encrypted=false) {
session.log.done(); session.log.done();
} }
async function createDm(session, invitees) { export async function createDm(session: ElementSession, invitees: string[]): Promise<void> {
session.log.step(`creates DM with ${JSON.stringify(invitees)}`); session.log.step(`creates DM with ${JSON.stringify(invitees)}`);
await measureStart(session, "mx_CreateDM"); await measureStart(session, "mx_CreateDM");
@ -83,5 +85,3 @@ async function createDm(session, invitees) {
await measureStop(session, "mx_CreateDM"); await measureStop(session, "mx_CreateDM");
} }
module.exports = { openRoomDirectory, findSublist, createRoom, createDm };

View file

@ -14,12 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
async function openSpaceCreateMenu(session) { import { ElementSession } from "../session";
export async function openSpaceCreateMenu(session: ElementSession): Promise<void> {
const spaceCreateButton = await session.query('.mx_SpaceButton_new'); const spaceCreateButton = await session.query('.mx_SpaceButton_new');
await spaceCreateButton.click(); await spaceCreateButton.click();
} }
async function createSpace(session, name, isPublic = false) { export async function createSpace(session: ElementSession, name: string, isPublic = false): Promise<void> {
session.log.step(`creates space "${name}"`); session.log.step(`creates space "${name}"`);
await openSpaceCreateMenu(session); await openSpaceCreateMenu(session);
@ -50,7 +52,7 @@ async function createSpace(session, name, isPublic = false) {
session.log.done(); session.log.done();
} }
async function inviteSpace(session, spaceName, userId) { export async function inviteSpace(session: ElementSession, spaceName: string, userId: string): Promise<void> {
session.log.step(`invites "${userId}" to space "${spaceName}"`); session.log.step(`invites "${userId}" to space "${spaceName}"`);
const spaceButton = await session.query(`.mx_SpaceButton[aria-label="${spaceName}"]`); const spaceButton = await session.query(`.mx_SpaceButton[aria-label="${spaceName}"]`);
@ -76,5 +78,3 @@ async function inviteSpace(session, spaceName, userId) {
await confirmButton.click(); await confirmButton.click();
session.log.done(); session.log.done();
} }
module.exports = { openSpaceCreateMenu, createSpace, inviteSpace };

View file

@ -15,22 +15,23 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const assert = require('assert'); import { strict as assert } from 'assert';
import { ElementSession } from "../session";
async function assertDialog(session, expectedTitle) { export async function assertDialog(session: ElementSession, expectedTitle: string): Promise<void> {
const titleElement = await session.query(".mx_Dialog .mx_Dialog_title"); const titleElement = await session.query(".mx_Dialog .mx_Dialog_title");
const dialogHeader = await session.innerText(titleElement); const dialogHeader = await session.innerText(titleElement);
assert.equal(dialogHeader, expectedTitle); assert.equal(dialogHeader, expectedTitle);
} }
async function acceptDialog(session, expectedTitle) { export async function acceptDialog(session: ElementSession, expectedTitle: string): Promise<void> {
const foundDialog = await acceptDialogMaybe(session, expectedTitle); const foundDialog = await acceptDialogMaybe(session, expectedTitle);
if (!foundDialog) { if (!foundDialog) {
throw new Error("could not find a dialog"); throw new Error("could not find a dialog");
} }
} }
async function acceptDialogMaybe(session, expectedTitle) { export async function acceptDialogMaybe(session: ElementSession, expectedTitle: string): Promise<boolean> {
let primaryButton = null; let primaryButton = null;
try { try {
primaryButton = await session.query(".mx_Dialog .mx_Dialog_primary"); primaryButton = await session.query(".mx_Dialog .mx_Dialog_primary");
@ -43,9 +44,3 @@ async function acceptDialogMaybe(session, expectedTitle) {
await primaryButton.click(); await primaryButton.click();
return true; return true;
} }
module.exports = {
assertDialog,
acceptDialog,
acceptDialogMaybe,
};

View file

@ -15,7 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
module.exports = async function invite(session, userId) { import { ElementSession } from "../session";
export async function invite(session: ElementSession, userId: string): Promise<void> {
session.log.step(`invites "${userId}" to room`); session.log.step(`invites "${userId}" to room`);
await session.delay(1000); await session.delay(1000);
const memberPanelButton = await session.query(".mx_RightPanel_membersButton"); const memberPanelButton = await session.query(".mx_RightPanel_membersButton");
@ -38,4 +40,4 @@ module.exports = async function invite(session, userId) {
const confirmButton = await session.query(".mx_InviteDialog_goButton"); const confirmButton = await session.query(".mx_InviteDialog_goButton");
await confirmButton.click(); await confirmButton.click();
session.log.done(); session.log.done();
}; }

View file

@ -15,10 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const { openRoomDirectory } = require('./create-room'); import { openRoomDirectory } from './create-room';
const { measureStart, measureStop } = require('../util'); import { measureStart, measureStop } from '../util';
import { ElementSession } from "../session";
module.exports = async function join(session, roomName) { export async function join(session: ElementSession, roomName: string): Promise<void> {
session.log.step(`joins room "${roomName}"`); session.log.step(`joins room "${roomName}"`);
await measureStart(session, "mx_JoinRoom"); await measureStart(session, "mx_JoinRoom");
await openRoomDirectory(session); await openRoomDirectory(session);
@ -30,4 +31,4 @@ module.exports = async function join(session, roomName) {
await session.query('.mx_MessageComposer'); await session.query('.mx_MessageComposer');
await measureStop(session, "mx_JoinRoom"); await measureStop(session, "mx_JoinRoom");
session.log.done(); session.log.done();
}; }

View file

@ -15,10 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const assert = require('assert'); import { strict as assert } from 'assert';
const { openRoomSummaryCard } = require("./rightpanel"); import { openRoomSummaryCard } from "./rightpanel";
import { ElementSession } from "../session";
import { ElementHandle } from "puppeteer";
async function openMemberInfo(session, name) { export async function openMemberInfo(session: ElementSession, name: String): Promise<void> {
const membersAndNames = await getMembersInMemberlist(session); const membersAndNames = await getMembersInMemberlist(session);
const matchingLabel = membersAndNames.filter((m) => { const matchingLabel = membersAndNames.filter((m) => {
return m.displayName === name; return m.displayName === name;
@ -26,9 +28,13 @@ async function openMemberInfo(session, name) {
await matchingLabel.click(); await matchingLabel.click();
} }
module.exports.openMemberInfo = openMemberInfo; interface Device {
id: string;
key: string;
}
module.exports.verifyDeviceForUser = async function(session, name, expectedDevice) { export async function verifyDeviceForUser(session: ElementSession, name: string,
expectedDevice: Device): Promise<void> {
session.log.step(`verifies e2e device for ${name}`); session.log.step(`verifies e2e device for ${name}`);
const membersAndNames = await getMembersInMemberlist(session); const membersAndNames = await getMembersInMemberlist(session);
const matchingLabel = membersAndNames.filter((m) => { const matchingLabel = membersAndNames.filter((m) => {
@ -50,19 +56,24 @@ module.exports.verifyDeviceForUser = async function(session, name, expectedDevic
console.log("my sas labels", sasLabels); console.log("my sas labels", sasLabels);
const dialogCodeFields = await session.queryAll(".mx_QuestionDialog code"); const dialogCodeFields = await session.queryAll(".mx_QuestionDialog code");
assert.equal(dialogCodeFields.length, 2); assert.strictEqual(dialogCodeFields.length, 2);
const deviceId = await session.innerText(dialogCodeFields[0]); const deviceId = await session.innerText(dialogCodeFields[0]);
const deviceKey = await session.innerText(dialogCodeFields[1]); const deviceKey = await session.innerText(dialogCodeFields[1]);
assert.equal(expectedDevice.id, deviceId); assert.strictEqual(expectedDevice.id, deviceId);
assert.equal(expectedDevice.key, deviceKey); assert.strictEqual(expectedDevice.key, deviceKey);
const confirmButton = await session.query(".mx_Dialog_primary"); const confirmButton = await session.query(".mx_Dialog_primary");
await confirmButton.click(); await confirmButton.click();
const closeMemberInfo = await session.query(".mx_MemberInfo_cancel"); const closeMemberInfo = await session.query(".mx_MemberInfo_cancel");
await closeMemberInfo.click(); await closeMemberInfo.click();
session.log.done(); session.log.done();
}; }
async function getMembersInMemberlist(session) { interface MemberName {
label: ElementHandle;
displayName: string;
}
export async function getMembersInMemberlist(session: ElementSession): Promise<MemberName[]> {
await openRoomSummaryCard(session); await openRoomSummaryCard(session);
const memberPanelButton = await session.query(".mx_RoomSummaryCard_icon_people"); const memberPanelButton = await session.query(".mx_RoomSummaryCard_icon_people");
// We are back at the room summary card // We are back at the room summary card
@ -73,5 +84,3 @@ async function getMembersInMemberlist(session) {
return { label: el, displayName: await session.innerText(el) }; return { label: el, displayName: await session.innerText(el) };
})); }));
} }
module.exports.getMembersInMemberlist = getMembersInMemberlist;

View file

@ -14,7 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
module.exports.openRoomRightPanel = async function(session) { import { ElementSession } from "../session";
export async function openRoomRightPanel(session: ElementSession): Promise<void> {
try { try {
await session.query('.mx_RoomHeader .mx_RightPanel_headerButton_highlight[aria-label="Room Info"]'); await session.query('.mx_RoomHeader .mx_RightPanel_headerButton_highlight[aria-label="Room Info"]');
} catch (e) { } catch (e) {
@ -22,9 +24,9 @@ module.exports.openRoomRightPanel = async function(session) {
const roomSummaryButton = await session.query('.mx_RoomHeader .mx_AccessibleButton[aria-label="Room Info"]'); const roomSummaryButton = await session.query('.mx_RoomHeader .mx_AccessibleButton[aria-label="Room Info"]');
await roomSummaryButton.click(); await roomSummaryButton.click();
} }
}; }
module.exports.goBackToRoomSummaryCard = async function(session) { export async function goBackToRoomSummaryCard(session: ElementSession): Promise<void> {
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
try { try {
const backButton = await session.query(".mx_BaseCard_back", 500); const backButton = await session.query(".mx_BaseCard_back", 500);
@ -39,9 +41,9 @@ module.exports.goBackToRoomSummaryCard = async function(session) {
} }
} }
} }
}; }
module.exports.openRoomSummaryCard = async function(session) { export async function openRoomSummaryCard(session: ElementSession) {
await module.exports.openRoomRightPanel(session); await openRoomRightPanel(session);
await module.exports.goBackToRoomSummaryCard(session); await goBackToRoomSummaryCard(session);
}; }

View file

@ -15,11 +15,13 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const assert = require('assert'); import { strict as assert } from 'assert';
const { openRoomSummaryCard } = require("./rightpanel"); import { openRoomSummaryCard } from "./rightpanel";
const { acceptDialog } = require('./dialog'); import { acceptDialog } from './dialog';
import { ElementSession } from "../session";
import { ElementHandle } from "puppeteer";
async function setSettingsToggle(session, toggle, enabled) { export async function setSettingsToggle(session: ElementSession, toggle: ElementHandle, enabled): Promise<boolean> {
const className = await session.getElementProperty(toggle, "className"); const className = await session.getElementProperty(toggle, "className");
const checked = className.includes("mx_ToggleSwitch_on"); const checked = className.includes("mx_ToggleSwitch_on");
if (checked !== enabled) { if (checked !== enabled) {
@ -31,7 +33,8 @@ async function setSettingsToggle(session, toggle, enabled) {
} }
} }
async function checkSettingsToggle(session, toggle, shouldBeEnabled) { export async function checkSettingsToggle(session: ElementSession,
toggle: ElementHandle, shouldBeEnabled: boolean): Promise<void> {
const className = await session.getElementProperty(toggle, "className"); const className = await session.getElementProperty(toggle, "className");
const checked = className.includes("mx_ToggleSwitch_on"); const checked = className.includes("mx_ToggleSwitch_on");
if (checked === shouldBeEnabled) { if (checked === shouldBeEnabled) {
@ -42,7 +45,11 @@ async function checkSettingsToggle(session, toggle, shouldBeEnabled) {
} }
} }
async function findTabs(session) { interface Tabs {
securityTabButton: ElementHandle;
}
async function findTabs(session: ElementSession): Promise<Tabs> {
/// XXX delay is needed here, possibly because the header is being rerendered /// XXX delay is needed here, possibly because the header is being rerendered
/// click doesn't do anything otherwise /// click doesn't do anything otherwise
await session.delay(1000); await session.delay(1000);
@ -60,7 +67,14 @@ async function findTabs(session) {
return { securityTabButton }; return { securityTabButton };
} }
async function checkRoomSettings(session, expectedSettings) { interface Settings {
encryption: boolean;
directory?: boolean;
alias?: string;
visibility?: string;
}
export async function checkRoomSettings(session: ElementSession, expectedSettings: Settings): Promise<void> {
session.log.startGroup(`checks the room settings`); session.log.startGroup(`checks the room settings`);
const { securityTabButton } = await findTabs(session); const { securityTabButton } = await findTabs(session);
@ -76,7 +90,7 @@ async function checkRoomSettings(session, expectedSettings) {
session.log.step(`checks for local alias of ${expectedSettings.alias}`); session.log.step(`checks for local alias of ${expectedSettings.alias}`);
const summary = await session.query(".mx_RoomSettingsDialog .mx_AliasSettings summary"); const summary = await session.query(".mx_RoomSettingsDialog .mx_AliasSettings summary");
await summary.click(); await summary.click();
const localAliases = await session.query('.mx_RoomSettingsDialog .mx_AliasSettings .mx_EditableItem_item'); const localAliases = await session.queryAll('.mx_RoomSettingsDialog .mx_AliasSettings .mx_EditableItem_item');
const localAliasTexts = await Promise.all(localAliases.map(a => session.innerText(a))); const localAliasTexts = await Promise.all(localAliases.map(a => session.innerText(a)));
if (localAliasTexts.find(a => a.includes(expectedSettings.alias))) { if (localAliasTexts.find(a => a.includes(expectedSettings.alias))) {
session.log.done("present"); session.log.done("present");
@ -85,7 +99,7 @@ async function checkRoomSettings(session, expectedSettings) {
} }
} }
securityTabButton.click(); await securityTabButton.click();
await session.delay(500); await session.delay(500);
const securitySwitches = await session.queryAll(".mx_RoomSettingsDialog .mx_ToggleSwitch"); const securitySwitches = await session.queryAll(".mx_RoomSettingsDialog .mx_ToggleSwitch");
const e2eEncryptionToggle = securitySwitches[0]; const e2eEncryptionToggle = securitySwitches[0];
@ -122,7 +136,7 @@ async function checkRoomSettings(session, expectedSettings) {
session.log.endGroup(); session.log.endGroup();
} }
async function changeRoomSettings(session, settings) { export async function changeRoomSettings(session, settings) {
session.log.startGroup(`changes the room settings`); session.log.startGroup(`changes the room settings`);
const { securityTabButton } = await findTabs(session); const { securityTabButton } = await findTabs(session);
@ -179,5 +193,3 @@ async function changeRoomSettings(session, settings) {
session.log.endGroup(); session.log.endGroup();
} }
module.exports = { checkRoomSettings, changeRoomSettings };

View file

@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const { acceptToast } = require("./toasts"); import { acceptToast } from "./toasts";
import { ElementSession } from "../session";
async function setupSecureBackup(session) { export async function setupSecureBackup(session: ElementSession): Promise<void> {
session.log.step("sets up Secure Backup"); session.log.step("sets up Secure Backup");
await acceptToast(session, "Set up Secure Backup"); await acceptToast(session, "Set up Secure Backup");

View file

@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const assert = require('assert'); import { strict as assert } from 'assert';
import { ElementSession } from "../session";
module.exports = async function sendMessage(session, message) { export async function sendMessage(session: ElementSession, message: string): Promise<void> {
session.log.step(`writes "${message}" in room`); session.log.step(`writes "${message}" in room`);
// this selector needs to be the element that has contenteditable=true, // this selector needs to be the element that has contenteditable=true,
// not any if its parents, otherwise it behaves flaky at best. // not any if its parents, otherwise it behaves flaky at best.
@ -31,4 +32,4 @@ module.exports = async function sendMessage(session, message) {
// wait for the message to appear sent // wait for the message to appear sent
await session.query(".mx_EventTile_last:not(.mx_EventTile_sending)"); await session.query(".mx_EventTile_last:not(.mx_EventTile_sending)");
session.log.done(); session.log.done();
}; }

View file

@ -15,9 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const assert = require('assert'); import { strict as assert } from 'assert';
import { ElementSession } from "../session";
async function openSettings(session, section) { export async function openSettings(session: ElementSession, section: string): Promise<void> {
const menuButton = await session.query(".mx_UserMenu"); const menuButton = await session.query(".mx_UserMenu");
await menuButton.click(); await menuButton.click();
const settingsItem = await session.query(".mx_UserMenu_iconSettings"); const settingsItem = await session.query(".mx_UserMenu_iconSettings");
@ -29,7 +30,7 @@ async function openSettings(session, section) {
} }
} }
module.exports.enableLazyLoading = async function(session) { export async function enableLazyLoading(session: ElementSession): Promise<void> {
session.log.step(`enables lazy loading of members in the lab settings`); session.log.step(`enables lazy loading of members in the lab settings`);
const settingsButton = await session.query('.mx_BottomLeftMenu_settings'); const settingsButton = await session.query('.mx_BottomLeftMenu_settings');
await settingsButton.click(); await settingsButton.click();
@ -39,17 +40,22 @@ module.exports.enableLazyLoading = async function(session) {
const closeButton = await session.query(".mx_RoomHeader_cancelButton"); const closeButton = await session.query(".mx_RoomHeader_cancelButton");
await closeButton.click(); await closeButton.click();
session.log.done(); session.log.done();
}; }
module.exports.getE2EDeviceFromSettings = async function(session) { interface E2EDevice {
id: string;
key: string;
}
export async function getE2EDeviceFromSettings(session: ElementSession): Promise<E2EDevice> {
session.log.step(`gets e2e device/key from settings`); session.log.step(`gets e2e device/key from settings`);
await openSettings(session, "security"); await openSettings(session, "security");
const deviceAndKey = await session.queryAll(".mx_SettingsTab_section .mx_CryptographyPanel code"); const deviceAndKey = await session.queryAll(".mx_SettingsTab_section .mx_CryptographyPanel code");
assert.equal(deviceAndKey.length, 2); assert.equal(deviceAndKey.length, 2);
const id = await (await deviceAndKey[0].getProperty("innerText")).jsonValue(); const id: string = await (await deviceAndKey[0].getProperty("innerText")).jsonValue();
const key = await (await deviceAndKey[1].getProperty("innerText")).jsonValue(); const key: string = await (await deviceAndKey[1].getProperty("innerText")).jsonValue();
const closeButton = await session.query(".mx_UserSettingsDialog .mx_Dialog_cancelButton"); const closeButton = await session.query(".mx_UserSettingsDialog .mx_Dialog_cancelButton");
await closeButton.click(); await closeButton.click();
session.log.done(); session.log.done();
return { id, key }; return { id, key };
}; }

View file

@ -15,9 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const assert = require('assert'); import { strict as assert } from 'assert';
import { ElementSession } from "../session";
module.exports = async function signup(session, username, password, homeserver) { export async function signup(session: ElementSession, username: string, password: string,
homeserver: string): Promise<void> {
session.log.step("signs up"); session.log.step("signs up");
await session.goto(session.url('/#/register')); await session.goto(session.url('/#/register'));
// change the homeserver by clicking the advanced section // change the homeserver by clicking the advanced section
@ -79,4 +81,4 @@ module.exports = async function signup(session, username, password, homeserver)
}); });
assert(foundHomeUrl); assert(foundHomeUrl);
session.log.done(); session.log.done();
}; }

View file

@ -15,9 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const assert = require('assert'); import { strict as assert } from 'assert';
import { ElementSession } from "../session";
import { ElementHandle } from "puppeteer";
module.exports.scrollToTimelineTop = async function(session) { export async function scrollToTimelineTop(session: ElementSession): Promise<void> {
session.log.step(`scrolls to the top of the timeline`); session.log.step(`scrolls to the top of the timeline`);
await session.page.evaluate(() => { await session.page.evaluate(() => {
return Promise.resolve().then(async () => { return Promise.resolve().then(async () => {
@ -41,14 +43,21 @@ module.exports.scrollToTimelineTop = async function(session) {
}); });
}); });
session.log.done(); session.log.done();
}; }
module.exports.receiveMessage = async function(session, expectedMessage) { interface Message {
sender: string;
encrypted?: boolean;
body: string;
continuation?: boolean;
}
export async function receiveMessage(session: ElementSession, expectedMessage: Message): Promise<void> {
session.log.step(`receives message "${expectedMessage.body}" from ${expectedMessage.sender}`); session.log.step(`receives message "${expectedMessage.body}" from ${expectedMessage.sender}`);
// wait for a response to come in that contains the message // wait for a response to come in that contains the message
// crude, but effective // crude, but effective
async function getLastMessage() { async function getLastMessage(): Promise<Message> {
const lastTile = await getLastEventTile(session); const lastTile = await getLastEventTile(session);
return getMessageFromEventTile(lastTile); return getMessageFromEventTile(lastTile);
} }
@ -67,25 +76,26 @@ module.exports.receiveMessage = async function(session, expectedMessage) {
}); });
assertMessage(lastMessage, expectedMessage); assertMessage(lastMessage, expectedMessage);
session.log.done(); session.log.done();
}; }
module.exports.checkTimelineContains = async function(session, expectedMessages, sendersDescription) { export async function checkTimelineContains(session: ElementSession, expectedMessages: Message[],
sendersDescription: string): Promise<void> {
session.log.step(`checks timeline contains ${expectedMessages.length} ` + session.log.step(`checks timeline contains ${expectedMessages.length} ` +
`given messages${sendersDescription ? ` from ${sendersDescription}`:""}`); `given messages${sendersDescription ? ` from ${sendersDescription}`:""}`);
const eventTiles = await getAllEventTiles(session); const eventTiles = await getAllEventTiles(session);
let timelineMessages = await Promise.all(eventTiles.map((eventTile) => { let timelineMessages: Message[] = await Promise.all(eventTiles.map((eventTile) => {
return getMessageFromEventTile(eventTile); return getMessageFromEventTile(eventTile);
})); }));
//filter out tiles that were not messages //filter out tiles that were not messages
timelineMessages = timelineMessages.filter((m) => !!m); timelineMessages = timelineMessages.filter((m) => !!m);
timelineMessages.reduce((prevSender, m) => { timelineMessages.reduce((prevSender: string, m) => {
if (m.continuation) { if (m.continuation) {
m.sender = prevSender; m.sender = prevSender;
return prevSender; return prevSender;
} else { } else {
return m.sender; return m.sender;
} }
}); }, "");
expectedMessages.forEach((expectedMessage) => { expectedMessages.forEach((expectedMessage) => {
const foundMessage = timelineMessages.find((message) => { const foundMessage = timelineMessages.find((message) => {
@ -101,9 +111,9 @@ module.exports.checkTimelineContains = async function(session, expectedMessages,
}); });
session.log.done(); session.log.done();
}; }
function assertMessage(foundMessage, expectedMessage) { function assertMessage(foundMessage: Message, expectedMessage: Message): void {
assert(foundMessage, `message ${JSON.stringify(expectedMessage)} not found in timeline`); assert(foundMessage, `message ${JSON.stringify(expectedMessage)} not found in timeline`);
assert.equal(foundMessage.body, expectedMessage.body); assert.equal(foundMessage.body, expectedMessage.body);
assert.equal(foundMessage.sender, expectedMessage.sender); assert.equal(foundMessage.sender, expectedMessage.sender);
@ -112,17 +122,17 @@ function assertMessage(foundMessage, expectedMessage) {
} }
} }
function getLastEventTile(session) { function getLastEventTile(session: ElementSession): Promise<ElementHandle> {
return session.query(".mx_EventTile_last"); return session.query(".mx_EventTile_last");
} }
function getAllEventTiles(session) { function getAllEventTiles(session: ElementSession): Promise<ElementHandle[]> {
return session.queryAll(".mx_RoomView_MessageList .mx_EventTile"); return session.queryAll(".mx_RoomView_MessageList .mx_EventTile");
} }
async function getMessageFromEventTile(eventTile) { async function getMessageFromEventTile(eventTile: ElementHandle): Promise<Message> {
const senderElement = await eventTile.$(".mx_SenderProfile_displayName"); const senderElement = await eventTile.$(".mx_SenderProfile_displayName");
const className = await (await eventTile.getProperty("className")).jsonValue(); const className: string = await (await eventTile.getProperty("className")).jsonValue();
const classNames = className.split(" "); const classNames = className.split(" ");
const bodyElement = await eventTile.$(".mx_EventTile_body"); const bodyElement = await eventTile.$(".mx_EventTile_body");
let sender = null; let sender = null;
@ -132,7 +142,7 @@ async function getMessageFromEventTile(eventTile) {
if (!bodyElement) { if (!bodyElement) {
return null; return null;
} }
const body = await(await bodyElement.getProperty("innerText")).jsonValue(); const body: string = await(await bodyElement.getProperty("innerText")).jsonValue();
return { return {
sender, sender,

View file

@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const assert = require('assert'); import { strict as assert } from 'assert';
import { ElementSession } from "../session";
async function assertNoToasts(session) { export async function assertNoToasts(session: ElementSession): Promise<void> {
try { try {
await session.query('.mx_Toast_toast', 1000, true); await session.query('.mx_Toast_toast', 1000, true);
} catch (e) { } catch (e) {
@ -26,22 +27,20 @@ async function assertNoToasts(session) {
} }
} }
async function assertToast(session, expectedTitle) { export async function assertToast(session: ElementSession, expectedTitle: string): Promise<void> {
const h2Element = await session.query('.mx_Toast_title h2'); const h2Element = await session.query('.mx_Toast_title h2');
const toastTitle = await session.innerText(h2Element); const toastTitle = await session.innerText(h2Element);
assert.equal(toastTitle, expectedTitle); assert.equal(toastTitle, expectedTitle);
} }
async function acceptToast(session, expectedTitle) { export async function acceptToast(session: ElementSession, expectedTitle: string): Promise<void> {
await assertToast(session, expectedTitle); await assertToast(session, expectedTitle);
const btn = await session.query('.mx_Toast_buttons .mx_AccessibleButton_kind_primary'); const btn = await session.query('.mx_Toast_buttons .mx_AccessibleButton_kind_primary');
await btn.click(); await btn.click();
} }
async function rejectToast(session, expectedTitle) { export async function rejectToast(session: ElementSession, expectedTitle: string): Promise<void> {
await assertToast(session, expectedTitle); await assertToast(session, expectedTitle);
const btn = await session.query('.mx_Toast_buttons .mx_AccessibleButton_kind_danger_outline'); const btn = await session.query('.mx_Toast_buttons .mx_AccessibleButton_kind_danger_outline');
await btn.click(); await btn.click();
} }
module.exports = { assertNoToasts, assertToast, acceptToast, rejectToast };

View file

@ -15,10 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const assert = require('assert'); import { strict as assert } from 'assert';
const { openMemberInfo } = require("./memberlist"); import { openMemberInfo } from "./memberlist";
import { ElementSession } from "../session";
async function startVerification(session, name) { export async function startVerification(session: ElementSession, name: string): Promise<void> {
session.log.step("opens their opponent's profile and starts verification"); session.log.step("opens their opponent's profile and starts verification");
await openMemberInfo(session, name); await openMemberInfo(session, name);
// click verify in member info // click verify in member info
@ -29,22 +30,22 @@ async function startVerification(session, name) {
await session.delay(1000); await session.delay(1000);
// click 'start verification' // click 'start verification'
const startVerifyButton = await session.query('.mx_UserInfo_container .mx_AccessibleButton_kind_primary'); const startVerifyButton = await session.query('.mx_UserInfo_container .mx_UserInfo_startVerification');
await startVerifyButton.click(); await startVerifyButton.click();
session.log.done(); session.log.done();
} }
async function getSasCodes(session) { async function getSasCodes(session: ElementSession): Promise<string[]> {
const sasLabelElements = await session.queryAll( const sasLabelElements = await session.queryAll(
".mx_VerificationShowSas .mx_VerificationShowSas_emojiSas .mx_VerificationShowSas_emojiSas_label"); ".mx_VerificationShowSas .mx_VerificationShowSas_emojiSas .mx_VerificationShowSas_emojiSas_label");
const sasLabels = await Promise.all(sasLabelElements.map(e => session.innerText(e))); const sasLabels = await Promise.all(sasLabelElements.map(e => session.innerText(e)));
return sasLabels; return sasLabels;
} }
async function doSasVerification(session) { async function doSasVerification(session: ElementSession): Promise<string[]> {
session.log.step("hunts for the emoji to yell at their opponent"); session.log.step("hunts for the emoji to yell at their opponent");
const sasCodes = await getSasCodes(session); const sasCodes = await getSasCodes(session);
session.log.done(sasCodes); session.log.done(sasCodes.join("\n"));
// Assume they match // Assume they match
session.log.step("assumes the emoji match"); session.log.step("assumes the emoji match");
@ -74,7 +75,7 @@ async function doSasVerification(session) {
return sasCodes; return sasCodes;
} }
module.exports.startSasVerification = async function(session, name) { export async function startSasVerification(session: ElementSession, name: string): Promise<string[]> {
session.log.startGroup("starts verification"); session.log.startGroup("starts verification");
await startVerification(session, name); await startVerification(session, name);
@ -84,9 +85,9 @@ module.exports.startSasVerification = async function(session, name) {
const sasCodes = await doSasVerification(session); const sasCodes = await doSasVerification(session);
session.log.endGroup(); session.log.endGroup();
return sasCodes; return sasCodes;
}; }
module.exports.acceptSasVerification = async function(session, name) { export async function acceptSasVerification(session: ElementSession, name: string): Promise<string[]> {
session.log.startGroup("accepts verification"); session.log.startGroup("accepts verification");
const requestToast = await session.query('.mx_Toast_icon_verification'); const requestToast = await session.query('.mx_Toast_icon_verification');
@ -110,4 +111,4 @@ module.exports.acceptSasVerification = async function(session, name) {
const sasCodes = await doSasVerification(session); const sasCodes = await doSasVerification(session);
session.log.endGroup(); session.log.endGroup();
return sasCodes; return sasCodes;
}; }

View file

@ -15,7 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
module.exports.range = function(start, amount, step = 1) { import { ElementSession } from "./session";
export const range = function(start: number, amount: number, step = 1): Array<number> {
const r = []; const r = [];
for (let i = 0; i < amount; ++i) { for (let i = 0; i < amount; ++i) {
r.push(start + (i * step)); r.push(start + (i * step));
@ -23,17 +25,17 @@ module.exports.range = function(start, amount, step = 1) {
return r; return r;
}; };
module.exports.delay = function(ms) { export const delay = function(ms): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
}; };
module.exports.measureStart = function(session, name) { export const measureStart = function(session: ElementSession, name: string): Promise<void> {
return session.page.evaluate(_name => { return session.page.evaluate(_name => {
window.mxPerformanceMonitor.start(_name); window.mxPerformanceMonitor.start(_name);
}, name); }, name);
}; };
module.exports.measureStop = function(session, name) { export const measureStop = function(session: ElementSession, name: string): Promise<void> {
return session.page.evaluate(_name => { return session.page.evaluate(_name => {
window.mxPerformanceMonitor.stop(_name); window.mxPerformanceMonitor.stop(_name);
}, name); }, name);

View file

@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const ElementSession = require('./src/session'); import { ElementSession } from './src/session';
const scenario = require('./src/scenario'); import { scenario } from './src/scenario';
const RestSessionCreator = require('./src/rest/creator'); import { RestSessionCreator } from './src/rest/creator';
const fs = require("fs"); import * as fs from "fs";
const program = require('commander'); import program = require('commander');
program program
.option('--no-logs', "don't output logs, document html on error", false) .option('--no-logs', "don't output logs, document html on error", false)
.option('--app-url [url]', "url to test", "http://localhost:5000") .option('--app-url [url]', "url to test", "http://localhost:5000")
@ -47,11 +47,11 @@ async function runTests() {
if (process.env.CHROME_PATH) { if (process.env.CHROME_PATH) {
const path = process.env.CHROME_PATH; const path = process.env.CHROME_PATH;
console.log(`(using external chrome/chromium at ${path}, make sure it's compatible with puppeteer)`); console.log(`(using external chrome/chromium at ${path}, make sure it's compatible with puppeteer)`);
options.executablePath = path; options['executablePath'] = path;
} }
const restCreator = new RestSessionCreator( const restCreator = new RestSessionCreator(
'synapse/installations/consent/env/bin', '../synapse/installations/consent/env/bin',
hsUrl, hsUrl,
__dirname, __dirname,
); );
@ -84,7 +84,7 @@ async function runTests() {
await Promise.all(sessions.map(async (session) => { await Promise.all(sessions.map(async (session) => {
// Collecting all performance monitoring data before closing the session // Collecting all performance monitoring data before closing the session
const measurements = await session.page.evaluate(() => { const measurements = await session.page.evaluate(() => {
let measurements = []; let measurements;
window.mxPerformanceMonitor.addPerformanceDataCallback({ window.mxPerformanceMonitor.addPerformanceDataCallback({
entryNames: [ entryNames: [
window.mxPerformanceEntryNames.REGISTER, window.mxPerformanceEntryNames.REGISTER,
@ -106,7 +106,9 @@ async function runTests() {
performanceEntries = JSON.parse(measurements); performanceEntries = JSON.parse(measurements);
return session.close(); return session.close();
})); }));
fs.writeFileSync(`performance-entries.json`, JSON.stringify(performanceEntries)); if (performanceEntries) {
fs.writeFileSync(`performance-entries.json`, JSON.stringify(performanceEntries));
}
if (failure) { if (failure) {
process.exit(-1); process.exit(-1);
} else { } else {

View file

@ -0,0 +1,23 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"resolveJsonModule": true,
"module": "commonjs",
"moduleResolution": "node",
"target": "es2016",
"noImplicitAny": false,
"sourceMap": false,
"outDir": "./lib",
"declaration": true,
"lib": [
"es2019",
"dom",
"dom.iterable"
],
},
"include": [
"./src/**/*.ts",
"start.ts"
]
}

View file

@ -7,6 +7,13 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-11.12.1.tgz#d90123f6c61fdf2f7cddd286ddae891586dd3488" resolved "https://registry.yarnpkg.com/@types/node/-/node-11.12.1.tgz#d90123f6c61fdf2f7cddd286ddae891586dd3488"
integrity sha512-sKDlqv6COJrR7ar0+GqqhrXQDzQlMcqMnF2iEU6m9hLo8kxozoAGUazwPyELHlRVmjsbvlnGXjnzyptSXVmceA== integrity sha512-sKDlqv6COJrR7ar0+GqqhrXQDzQlMcqMnF2iEU6m9hLo8kxozoAGUazwPyELHlRVmjsbvlnGXjnzyptSXVmceA==
"@types/puppeteer@^5.4.4":
version "5.4.4"
resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-5.4.4.tgz#e92abeccc4f46207c3e1b38934a1246be080ccd0"
integrity sha512-3Nau+qi69CN55VwZb0ATtdUAlYlqOOQ3OfQfq0Hqgc4JMFXiQT/XInlwQ9g6LbicDslE6loIFsXFklGh5XmI6Q==
dependencies:
"@types/node" "*"
"@types/yauzl@^2.9.1": "@types/yauzl@^2.9.1":
version "2.9.1" version "2.9.1"
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af" resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af"