From d4813f7a1aec4fd1d66cb25866a87e3c987d6829 Mon Sep 17 00:00:00 2001 From: James Salter Date: Mon, 6 Dec 2021 09:59:06 +1100 Subject: [PATCH] Convert end-to-end tests to Typescript (#7206) --- .eslintignore | 1 + scripts/ci/Dockerfile | 67 ++++++++- .../views/right_panel/EncryptionInfo.tsx | 6 +- test/end-to-end-tests/.gitignore | 2 + test/end-to-end-tests/package.json | 6 +- test/end-to-end-tests/run.sh | 3 +- test/end-to-end-tests/src/@types/global.d.ts | 24 +++ .../src/{logbuffer.js => logbuffer.ts} | 24 +-- .../src/{logger.js => logger.ts} | 25 ++-- .../src/rest/{consent.js => consent.ts} | 8 +- .../src/rest/{creator.js => creator.ts} | 46 +++--- .../src/rest/{multi.js => multi.ts} | 38 ++--- .../src/rest/{room.js => room.ts} | 30 ++-- test/end-to-end-tests/src/rest/session.js | 126 ---------------- test/end-to-end-tests/src/rest/session.ts | 137 ++++++++++++++++++ .../src/{scenario.js => scenario.ts} | 27 ++-- .../scenarios/{directory.js => directory.ts} | 15 +- .../{e2e-encryption.js => e2e-encryption.ts} | 26 ++-- .../{lazy-loading.js => lazy-loading.ts} | 40 ++--- .../src/scenarios/{spaces.js => spaces.ts} | 10 +- .../src/scenarios/{toast.js => toast.ts} | 7 +- .../src/{session.js => session.ts} | 96 ++++++------ .../{accept-invite.js => accept-invite.ts} | 7 +- .../{create-room.js => create-room.ts} | 14 +- .../{create-space.js => create-space.ts} | 10 +- .../src/usecases/{dialog.js => dialog.ts} | 15 +- .../src/usecases/{invite.js => invite.ts} | 6 +- .../src/usecases/{join.js => join.ts} | 9 +- .../usecases/{memberlist.js => memberlist.ts} | 33 +++-- .../usecases/{rightpanel.js => rightpanel.ts} | 18 ++- .../{room-settings.js => room-settings.ts} | 36 +++-- .../src/usecases/{security.js => security.ts} | 5 +- .../{send-message.js => send-message.ts} | 7 +- .../src/usecases/{settings.js => settings.ts} | 22 ++- .../src/usecases/{signup.js => signup.ts} | 8 +- .../src/usecases/{timeline.js => timeline.ts} | 44 +++--- .../src/usecases/{toasts.js => toasts.ts} | 13 +- .../src/usecases/{verify.js => verify.ts} | 23 +-- .../end-to-end-tests/src/{util.js => util.ts} | 10 +- test/end-to-end-tests/{start.js => start.ts} | 20 +-- test/end-to-end-tests/tsconfig.json | 23 +++ test/end-to-end-tests/yarn.lock | 7 + 42 files changed, 653 insertions(+), 441 deletions(-) create mode 100644 test/end-to-end-tests/src/@types/global.d.ts rename test/end-to-end-tests/src/{logbuffer.js => logbuffer.ts} (59%) rename test/end-to-end-tests/src/{logger.js => logger.ts} (78%) rename test/end-to-end-tests/src/rest/{consent.js => consent.ts} (83%) rename test/end-to-end-tests/src/rest/{creator.js => creator.ts} (67%) rename test/end-to-end-tests/src/rest/{multi.js => multi.ts} (69%) rename test/end-to-end-tests/src/rest/{room.js => room.ts} (58%) delete mode 100644 test/end-to-end-tests/src/rest/session.js create mode 100644 test/end-to-end-tests/src/rest/session.ts rename test/end-to-end-tests/src/{scenario.js => scenario.ts} (65%) rename test/end-to-end-tests/src/scenarios/{directory.js => directory.ts} (74%) rename test/end-to-end-tests/src/scenarios/{e2e-encryption.js => e2e-encryption.ts} (73%) rename test/end-to-end-tests/src/scenarios/{lazy-loading.js => lazy-loading.ts} (75%) rename test/end-to-end-tests/src/scenarios/{spaces.js => spaces.ts} (74%) rename test/end-to-end-tests/src/scenarios/{toast.js => toast.ts} (87%) rename test/end-to-end-tests/src/{session.js => session.ts} (65%) rename test/end-to-end-tests/src/usecases/{accept-invite.js => accept-invite.ts} (88%) rename test/end-to-end-tests/src/usecases/{create-room.js => create-room.ts} (84%) rename test/end-to-end-tests/src/usecases/{create-space.js => create-space.ts} (88%) rename test/end-to-end-tests/src/usecases/{dialog.js => dialog.ts} (76%) rename test/end-to-end-tests/src/usecases/{invite.js => invite.ts} (92%) rename test/end-to-end-tests/src/usecases/{join.js => join.ts} (82%) rename test/end-to-end-tests/src/usecases/{memberlist.js => memberlist.ts} (78%) rename test/end-to-end-tests/src/usecases/{rightpanel.js => rightpanel.ts} (80%) rename test/end-to-end-tests/src/usecases/{room-settings.js => room-settings.ts} (86%) rename test/end-to-end-tests/src/usecases/{security.js => security.ts} (89%) rename test/end-to-end-tests/src/usecases/{send-message.js => send-message.ts} (87%) rename test/end-to-end-tests/src/usecases/{settings.js => settings.ts} (75%) rename test/end-to-end-tests/src/usecases/{signup.js => signup.ts} (94%) rename test/end-to-end-tests/src/usecases/{timeline.js => timeline.ts} (77%) rename test/end-to-end-tests/src/usecases/{toasts.js => toasts.ts} (75%) rename test/end-to-end-tests/src/usecases/{verify.js => verify.ts} (85%) rename test/end-to-end-tests/src/{util.js => util.ts} (73%) rename test/end-to-end-tests/{start.js => start.ts} (91%) create mode 100644 test/end-to-end-tests/tsconfig.json diff --git a/.eslintignore b/.eslintignore index e453170087..b715bcd4f6 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,3 +2,4 @@ src/component-index.js test/end-to-end-tests/node_modules/ test/end-to-end-tests/element/ test/end-to-end-tests/synapse/ +test/end-to-end-tests/lib/ diff --git a/scripts/ci/Dockerfile b/scripts/ci/Dockerfile index 6d33987d8c..447ed09b7c 100644 --- a/scripts/ci/Dockerfile +++ b/scripts/ci/Dockerfile @@ -1,8 +1,67 @@ # Update on docker hub with the following commands in the directory of this file: -# docker build -t vectorim/element-web-ci-e2etests-env:latest . -# docker push vectorim/element-web-ci-e2etests-env:latest +# If you're on linux amd64 +# 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 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) -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 diff --git a/src/components/views/right_panel/EncryptionInfo.tsx b/src/components/views/right_panel/EncryptionInfo.tsx index c9a4b3b84c..293516bf17 100644 --- a/src/components/views/right_panel/EncryptionInfo.tsx +++ b/src/components/views/right_panel/EncryptionInfo.tsx @@ -67,7 +67,11 @@ const EncryptionInfo: React.FC = ({ content = ; } else { content = ( - + { _t("Start Verification") } ); diff --git a/test/end-to-end-tests/.gitignore b/test/end-to-end-tests/.gitignore index 9180d32e90..528c296f93 100644 --- a/test/end-to-end-tests/.gitignore +++ b/test/end-to-end-tests/.gitignore @@ -2,3 +2,5 @@ node_modules *.png element/env performance-entries.json +lib +logs diff --git a/test/end-to-end-tests/package.json b/test/end-to-end-tests/package.json index 6f0c12e195..a4f9c1ef0e 100644 --- a/test/end-to-end-tests/package.json +++ b/test/end-to-end-tests/package.json @@ -4,7 +4,8 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "build": "tsc -p ./tsconfig.json" }, "author": "", "license": "ISC", @@ -15,5 +16,8 @@ "request": "^2.88.0", "request-promise-native": "^1.0.7", "uuid": "^3.3.2" + }, + "devDependencies": { + "@types/puppeteer": "^5.4.4" } } diff --git a/test/end-to-end-tests/run.sh b/test/end-to-end-tests/run.sh index f1aa6729b5..1a9009a886 100755 --- a/test/end-to-end-tests/run.sh +++ b/test/end-to-end-tests/run.sh @@ -35,5 +35,6 @@ trap 'handle_error' ERR if [ $has_custom_app -ne "1" ]; then ./element/start.sh fi -node start.js $@ +yarn build +node lib/start.js $@ stop_servers diff --git a/test/end-to-end-tests/src/@types/global.d.ts b/test/end-to-end-tests/src/@types/global.d.ts new file mode 100644 index 0000000000..6966996f1e --- /dev/null +++ b/test/end-to-end-tests/src/@types/global.d.ts @@ -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; + } +} diff --git a/test/end-to-end-tests/src/logbuffer.js b/test/end-to-end-tests/src/logbuffer.ts similarity index 59% rename from test/end-to-end-tests/src/logbuffer.js rename to test/end-to-end-tests/src/logbuffer.ts index 873363c2ec..441bd0bdce 100644 --- a/test/end-to-end-tests/src/logbuffer.js +++ b/test/end-to-end-tests/src/logbuffer.ts @@ -15,16 +15,20 @@ See the License for the specific language governing permissions and limitations under the License. */ -module.exports = class LogBuffer { - constructor(page, eventName, eventMapper, reduceAsync=false, initialValue = "") { +import { Page, PageEventObject } from "puppeteer"; + +export class LogBuffer[1]>[0]> { + buffer: string; + + constructor( + page: Page, + eventName: keyof PageEventObject, + eventMapper: (arg: EventMapperArg) => Promise, + initialValue = "", + ) { this.buffer = initialValue; - page.on(eventName, (arg) => { - const result = eventMapper(arg); - if (reduceAsync) { - result.then((r) => this.buffer += r); - } else { - this.buffer += result; - } + page.on(eventName, (arg: EventMapperArg) => { + eventMapper(arg).then((r) => this.buffer += r); }); } -}; +} diff --git a/test/end-to-end-tests/src/logger.js b/test/end-to-end-tests/src/logger.ts similarity index 78% rename from test/end-to-end-tests/src/logger.js rename to test/end-to-end-tests/src/logger.ts index f5a338e2c7..99c1acef09 100644 --- a/test/end-to-end-tests/src/logger.js +++ b/test/end-to-end-tests/src/logger.ts @@ -15,14 +15,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -module.exports = class Logger { - constructor(username) { - this.indent = 0; - this.username = username; - this.muted = false; - } +export class Logger { + private indent = 0; + private muted = false; - startGroup(description) { + constructor(readonly username: string) {} + + public startGroup(description: string): Logger { if (!this.muted) { const indent = " ".repeat(this.indent * 2); console.log(`${indent} * ${this.username} ${description}:`); @@ -31,12 +30,12 @@ module.exports = class Logger { return this; } - endGroup() { + public endGroup(): Logger { this.indent -= 1; return this; } - step(description) { + public step(description: string): Logger { if (!this.muted) { const indent = " ".repeat(this.indent * 2); process.stdout.write(`${indent} * ${this.username} ${description} ... `); @@ -44,20 +43,20 @@ module.exports = class Logger { return this; } - done(status = "done") { + public done(status = "done"): Logger { if (!this.muted) { process.stdout.write(status + "\n"); } return this; } - mute() { + public mute(): Logger { this.muted = true; return this; } - unmute() { + public unmute(): Logger { this.muted = false; return this; } -}; +} diff --git a/test/end-to-end-tests/src/rest/consent.js b/test/end-to-end-tests/src/rest/consent.ts similarity index 83% rename from test/end-to-end-tests/src/rest/consent.js rename to test/end-to-end-tests/src/rest/consent.ts index 8be27258d0..8b2e19821c 100644 --- a/test/end-to-end-tests/src/rest/consent.js +++ b/test/end-to-end-tests/src/rest/consent.ts @@ -15,11 +15,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -const request = require('request-promise-native'); -const cheerio = require('cheerio'); -const url = require("url"); +import request = require('request-promise-native'); +import * as cheerio from 'cheerio'; +import * as url from "url"; -module.exports.approveConsent = async function(consentUrl) { +export const approveConsent = async function(consentUrl: string): Promise { const body = await request.get(consentUrl); const doc = cheerio.load(body); const v = doc("input[name=v]").val(); diff --git a/test/end-to-end-tests/src/rest/creator.js b/test/end-to-end-tests/src/rest/creator.ts similarity index 67% rename from test/end-to-end-tests/src/rest/creator.js rename to test/end-to-end-tests/src/rest/creator.ts index f01a325a71..9b68face62 100644 --- a/test/end-to-end-tests/src/rest/creator.js +++ b/test/end-to-end-tests/src/rest/creator.ts @@ -15,12 +15,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -const { exec } = require('child_process'); -const request = require('request-promise-native'); -const RestSession = require('./session'); -const RestMultiSession = require('./multi'); +import { exec } from 'child_process'; +import request = require('request-promise-native'); +import { RestSession } from './session'; +import { RestMultiSession } from './multi'; -function execAsync(command, options) { +interface ExecResult { + stdout: string; + stderr: string; +} + +function execAsync(command: string, options: Parameters[1]): Promise { return new Promise((resolve, reject) => { exec(command, options, (error, stdout, stderr) => { if (error) { @@ -32,27 +37,32 @@ function execAsync(command, options) { }); } -module.exports = class RestSessionCreator { - constructor(synapseSubdir, hsUrl, cwd) { - this.synapseSubdir = synapseSubdir; - this.hsUrl = hsUrl; - this.cwd = cwd; - } +export interface Credentials { + accessToken: string; + homeServer: string; + userId: string; + 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 { const sessionPromises = usernames.map((username) => this.createSession(username, password)); const sessions = await Promise.all(sessionPromises); return new RestMultiSession(sessions, groupName); } - async createSession(username, password) { - await this._register(username, password); + public async createSession(username: string, password: string): Promise { + await this.register(username, password); 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); } - async _register(username, password) { + private async register(username: string, password: string): Promise { const registerArgs = [ '-c homeserver.yaml', `-u ${username}`, @@ -70,7 +80,7 @@ module.exports = class RestSessionCreator { await execAsync(allCmds, { cwd: this.cwd, encoding: 'utf-8' }); } - async _authenticate(username, password) { + private async authenticate(username: string, password: string): Promise { const requestBody = { "type": "m.login.password", "identifier": { @@ -89,4 +99,4 @@ module.exports = class RestSessionCreator { hsUrl: this.hsUrl, }; } -}; +} diff --git a/test/end-to-end-tests/src/rest/multi.js b/test/end-to-end-tests/src/rest/multi.ts similarity index 69% rename from test/end-to-end-tests/src/rest/multi.js rename to test/end-to-end-tests/src/rest/multi.ts index 570879bff7..00f127567f 100644 --- a/test/end-to-end-tests/src/rest/multi.js +++ b/test/end-to-end-tests/src/rest/multi.ts @@ -15,19 +15,22 @@ See the License for the specific language governing permissions and limitations under the License. */ -const Logger = require('../logger'); +import { Logger } from '../logger'; +import { RestSession } from "./session"; +import { RestRoom } from "./room"; -module.exports = class RestMultiSession { - constructor(sessions, groupName) { +export class RestMultiSession { + readonly log: Logger; + + constructor(public readonly sessions: RestSession[], groupName: string) { 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); } - pop(userName) { + public pop(userName: string): RestSession { const idx = this.sessions.findIndex((s) => s.userName() === userName); if (idx === -1) { throw new Error(`user ${userName} not found`); @@ -36,9 +39,9 @@ module.exports = class RestMultiSession { return session; } - async setDisplayName(fn) { + public async setDisplayName(fn: (s: RestSession) => string): Promise { 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(); await s.setDisplayName(fn(s)); s.log.unmute(); @@ -46,7 +49,7 @@ module.exports = class RestMultiSession { this.log.done(); } - async join(roomIdOrAlias) { + public async join(roomIdOrAlias: string): Promise { this.log.step(`join ${roomIdOrAlias}`); const rooms = await Promise.all(this.sessions.map(async (s) => { s.log.mute(); @@ -58,22 +61,19 @@ module.exports = class RestMultiSession { return new RestMultiRoom(rooms, roomIdOrAlias, this.log); } - room(roomIdOrAlias) { + public room(roomIdOrAlias: string): RestMultiRoom { const rooms = this.sessions.map(s => s.room(roomIdOrAlias)); return new RestMultiRoom(rooms, roomIdOrAlias, this.log); } -}; +} class RestMultiRoom { - constructor(rooms, roomIdOrAlias, log) { - this.rooms = rooms; - this.roomIdOrAlias = roomIdOrAlias; - this.log = log; - } + constructor(public readonly rooms: RestRoom[], private readonly roomIdOrAlias: string, + private readonly log: Logger) {} - async talk(message) { + public async talk(message: string): Promise { 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(); await r.talk(message); r.log.unmute(); @@ -81,7 +81,7 @@ class RestMultiRoom { this.log.done(); } - async leave() { + public async leave() { this.log.step(`leave ${this.roomIdOrAlias}`); await Promise.all(this.rooms.map(async (r) => { r.log.mute(); diff --git a/test/end-to-end-tests/src/rest/room.js b/test/end-to-end-tests/src/rest/room.ts similarity index 58% rename from test/end-to-end-tests/src/rest/room.js rename to test/end-to-end-tests/src/rest/room.ts index 94afce1dac..95a349e39f 100644 --- a/test/end-to-end-tests/src/rest/room.js +++ b/test/end-to-end-tests/src/rest/room.ts @@ -15,20 +15,18 @@ See the License for the specific language governing permissions and 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 */ -module.exports = class RestRoom { - constructor(session, roomId, log) { - this.session = session; - this._roomId = roomId; - this.log = log; - } +export class RestRoom { + constructor(readonly session: RestSession, readonly roomId: string, readonly log: Logger) {} - async talk(message) { - this.log.step(`says "${message}" in ${this._roomId}`); + async talk(message: string): Promise { + this.log.step(`says "${message}" in ${this.roomId}`); 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", "body": message, }); @@ -36,13 +34,9 @@ module.exports = class RestRoom { return txId; } - async leave() { - this.log.step(`leaves ${this._roomId}`); - await this.session._post(`/rooms/${this._roomId}/leave`); + async leave(): Promise { + this.log.step(`leaves ${this.roomId}`); + await this.session.post(`/rooms/${this.roomId}/leave`); this.log.done(); } - - roomId() { - return this._roomId; - } -}; +} diff --git a/test/end-to-end-tests/src/rest/session.js b/test/end-to-end-tests/src/rest/session.js deleted file mode 100644 index 3c04592d02..0000000000 --- a/test/end-to-end-tests/src/rest/session.js +++ /dev/null @@ -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}`); - } - } - } -}; diff --git a/test/end-to-end-tests/src/rest/session.ts b/test/end-to-end-tests/src/rest/session.ts new file mode 100644 index 0000000000..5b5dd387ef --- /dev/null +++ b/test/end-to-end-tests/src/rest/session.ts @@ -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 = {}; + 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 { + 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 { + 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 { + 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 { + return this.request("POST", csApiPath, body); + } + + put(csApiPath: string, body?: any): Promise { + return this.request("PUT", csApiPath, body); + } + + async request(method: string, csApiPath: string, body?: any): Promise { + 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}`); + } + } + } +} diff --git a/test/end-to-end-tests/src/scenario.js b/test/end-to-end-tests/src/scenario.ts similarity index 65% rename from test/end-to-end-tests/src/scenario.js rename to test/end-to-end-tests/src/scenario.ts index bf7bad309a..2d550e1970 100644 --- a/test/end-to-end-tests/src/scenario.js +++ b/test/end-to-end-tests/src/scenario.ts @@ -14,15 +14,20 @@ See the License for the specific language governing permissions and limitations under the License. */ -const { range } = require('./util'); -const signup = require('./usecases/signup'); -const toastScenarios = require('./scenarios/toast'); -const roomDirectoryScenarios = require('./scenarios/directory'); -const lazyLoadingScenarios = require('./scenarios/lazy-loading'); -const e2eEncryptionScenarios = require('./scenarios/e2e-encryption'); -const spacesScenarios = require('./scenarios/spaces'); +import { range } from './util'; +import { signup } from './usecases/signup'; +import { toastScenarios } from './scenarios/toast'; +import { roomDirectoryScenarios } from './scenarios/directory'; +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 { spacesScenarios } from './scenarios/spaces'; +import { RestSession } from "./rest/session"; -module.exports = async function scenario(createSession, restCreator) { +export async function scenario(createSession: (s: string) => Promise, + restCreator: RestSessionCreator): Promise { let firstUser = true; async function createUser(username) { const session = await createSession(username); @@ -46,11 +51,11 @@ module.exports = async function scenario(createSession, restCreator) { await lazyLoadingScenarios(alice, bob, charlies); // do spaces scenarios last as the rest of the tests may get confused by spaces await spacesScenarios(alice, bob); -}; +} -async function createRestUsers(restCreator) { +async function createRestUsers(restCreator: RestSessionCreator): Promise { const usernames = range(1, 10).map((i) => `charly-${i}`); 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; } diff --git a/test/end-to-end-tests/src/scenarios/directory.js b/test/end-to-end-tests/src/scenarios/directory.ts similarity index 74% rename from test/end-to-end-tests/src/scenarios/directory.js rename to test/end-to-end-tests/src/scenarios/directory.ts index fffca2b05c..a0568c24d8 100644 --- a/test/end-to-end-tests/src/scenarios/directory.js +++ b/test/end-to-end-tests/src/scenarios/directory.ts @@ -15,13 +15,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -const join = require('../usecases/join'); -const sendMessage = require('../usecases/send-message'); -const { receiveMessage } = require('../usecases/timeline'); -const { createRoom } = require('../usecases/create-room'); -const { changeRoomSettings } = require('../usecases/room-settings'); +import { join } from '../usecases/join'; +import { sendMessage } from '../usecases/send-message'; +import { receiveMessage } from '../usecases/timeline'; +import { createRoom } from '../usecases/create-room'; +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:"); const room = 'test'; await createRoom(alice, room); @@ -33,4 +34,4 @@ module.exports = async function roomDirectoryScenarios(alice, bob) { const aliceMessage = "hi Bob, welcome!"; await sendMessage(alice, aliceMessage); await receiveMessage(bob, { sender: "alice", body: aliceMessage }); -}; +} diff --git a/test/end-to-end-tests/src/scenarios/e2e-encryption.js b/test/end-to-end-tests/src/scenarios/e2e-encryption.ts similarity index 73% rename from test/end-to-end-tests/src/scenarios/e2e-encryption.js rename to test/end-to-end-tests/src/scenarios/e2e-encryption.ts index 23234b85ce..9d77ecc59d 100644 --- a/test/end-to-end-tests/src/scenarios/e2e-encryption.js +++ b/test/end-to-end-tests/src/scenarios/e2e-encryption.ts @@ -15,22 +15,24 @@ See the License for the specific language governing permissions and limitations under the License. */ -const sendMessage = require('../usecases/send-message'); -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'); +import { ElementSession } from "../session"; -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:"); await createDm(bob, ['@alice:localhost']); await checkRoomSettings(bob, { encryption: true }); // for sanity, should be e2e-by-default await acceptInvite(alice, 'bob'); - // do sas verifcation + // do sas verification bob.log.step(`starts SAS verification with ${alice.username}`); await measureStart(bob, "mx_VerifyE2EEUser"); const bobSasPromise = startSasVerification(bob, alice.username); @@ -48,4 +50,4 @@ module.exports = async function e2eEncryptionScenarios(alice, bob) { await sendMessage(bob, bobMessage); await receiveMessage(alice, { sender: "bob", body: bobMessage, encrypted: true }); await setupSecureBackup(alice); -}; +} diff --git a/test/end-to-end-tests/src/scenarios/lazy-loading.js b/test/end-to-end-tests/src/scenarios/lazy-loading.ts similarity index 75% rename from test/end-to-end-tests/src/scenarios/lazy-loading.js rename to test/end-to-end-tests/src/scenarios/lazy-loading.ts index 406f7b24a3..e0bae4d77f 100644 --- a/test/end-to-end-tests/src/scenarios/lazy-loading.js +++ b/test/end-to-end-tests/src/scenarios/lazy-loading.ts @@ -15,24 +15,27 @@ See the License for the specific language governing permissions and limitations under the License. */ -const { delay } = require('../util'); -const join = require('../usecases/join'); -const sendMessage = require('../usecases/send-message'); -const { +import { delay } from '../util'; +import { join } from '../usecases/join'; +import { sendMessage } from '../usecases/send-message'; +import { checkTimelineContains, scrollToTimelineTop, -} = require('../usecases/timeline'); -const { createRoom } = require('../usecases/create-room'); -const { getMembersInMemberlist } = require('../usecases/memberlist'); -const { changeRoomSettings } = require('../usecases/room-settings'); -const assert = require('assert'); +} from '../usecases/timeline'; +import { createRoom } from '../usecases/create-room'; +import { getMembersInMemberlist } from '../usecases/memberlist'; +import { changeRoomSettings } from '../usecases/room-settings'; +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 { 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); + assert(charly1to5.sessions.length == 5); + assert(charly6to10.sessions.length == 5); await setupRoomWithBobAliceAndCharlies(alice, bob, charly1to5); await checkPaginatedDisplayNames(alice, charly1to5); await checkMemberList(alice, charly1to5); @@ -42,14 +45,15 @@ module.exports = async function lazyLoadingScenarios(alice, bob, charlies) { 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, bob, charlies) { +async function setupRoomWithBobAliceAndCharlies(alice: ElementSession, bob: ElementSession, + charlies: RestMultiSession): Promise { await createRoom(bob, room); await changeRoomSettings(bob, { directory: true, visibility: "public", alias }); // 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); } -async function checkPaginatedDisplayNames(alice, charlies) { +async function checkPaginatedDisplayNames(alice: ElementSession, charlies: RestMultiSession): Promise { await scrollToTimelineTop(alice); //alice should see 2 messages from every charly with //the correct display name @@ -81,7 +85,7 @@ async function checkPaginatedDisplayNames(alice, charlies) { await checkTimelineContains(alice, expectedMessages, charlies.log.username); } -async function checkMemberList(alice, charlies) { +async function checkMemberList(alice: ElementSession, charlies: RestMultiSession): Promise { 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")); @@ -94,7 +98,7 @@ async function checkMemberList(alice, charlies) { alice.log.done(); } -async function checkMemberListLacksCharlies(session, charlies) { +async function checkMemberListLacksCharlies(session: ElementSession, charlies: RestMultiSession): Promise { 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) => { @@ -105,7 +109,7 @@ async function checkMemberListLacksCharlies(session, charlies) { session.log.done(); } -async function joinCharliesWhileAliceIsOffline(alice, charly6to10) { +async function joinCharliesWhileAliceIsOffline(alice: ElementSession, charly6to10: RestMultiSession) { await alice.setOffline(true); await delay(1000); const members6to10 = await charly6to10.join(alias); diff --git a/test/end-to-end-tests/src/scenarios/spaces.js b/test/end-to-end-tests/src/scenarios/spaces.ts similarity index 74% rename from test/end-to-end-tests/src/scenarios/spaces.js rename to test/end-to-end-tests/src/scenarios/spaces.ts index 303db65593..ed8358d088 100644 --- a/test/end-to-end-tests/src/scenarios/spaces.js +++ b/test/end-to-end-tests/src/scenarios/spaces.ts @@ -1,3 +1,5 @@ +import { ElementSession } from "../session"; + /* 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. */ -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 { console.log(" creating a space for spaces scenarios:"); await alice.delay(1000); // wait for dialogs to close await setupSpaceUsingAliceAndInviteBob(alice, bob); -}; +} const space = "Test Space"; -async function setupSpaceUsingAliceAndInviteBob(alice, bob) { +async function setupSpaceUsingAliceAndInviteBob(alice: ElementSession, bob: ElementSession): Promise { await createSpace(alice, space); await inviteSpace(alice, space, "@bob:localhost"); await bob.query(`.mx_SpaceButton[aria-label="${space}"]`); // assert invite received diff --git a/test/end-to-end-tests/src/scenarios/toast.js b/test/end-to-end-tests/src/scenarios/toast.ts similarity index 87% rename from test/end-to-end-tests/src/scenarios/toast.js rename to test/end-to-end-tests/src/scenarios/toast.ts index b6142d8c3f..246107c27a 100644 --- a/test/end-to-end-tests/src/scenarios/toast.js +++ b/test/end-to-end-tests/src/scenarios/toast.ts @@ -14,9 +14,10 @@ See the License for the specific language governing permissions and 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 { console.log(" checking and clearing toasts:"); alice.log.startGroup(`clears toasts`); @@ -48,4 +49,4 @@ module.exports = async function toastScenarios(alice, bob) { await assertNoToasts(bob); bob.log.done(); bob.log.endGroup(); -}; +} diff --git a/test/end-to-end-tests/src/session.js b/test/end-to-end-tests/src/session.ts similarity index 65% rename from test/end-to-end-tests/src/session.js rename to test/end-to-end-tests/src/session.ts index f5d20fde28..80a85f6d5f 100644 --- a/test/end-to-end-tests/src/session.js +++ b/test/end-to-end-tests/src/session.ts @@ -15,30 +15,37 @@ See the License for the specific language governing permissions and limitations under the License. */ -const puppeteer = require('puppeteer'); -const Logger = require('./logger'); -const LogBuffer = require('./logbuffer'); -const { delay } = require('./util'); +import * as puppeteer from 'puppeteer'; +import { Logger } from './logger'; +import { LogBuffer } from './logbuffer'; +import { delay } from './util'; const DEFAULT_TIMEOUT = 20000; -module.exports = class ElementSession { - constructor(browser, page, username, elementServer, hsUrl) { - this.browser = browser; - this.page = page; - this.hsUrl = hsUrl; - this.elementServer = elementServer; - this.username = username; - this.consoleLog = new LogBuffer(page, "console", (msg) => `${msg.text()}\n`); - this.networkLog = new LogBuffer(page, "requestfinished", async (req) => { - const type = req.resourceType(); - const response = await req.response(); - return `${type} ${response.status()} ${req.method()} ${req.url()} \n`; - }, true); +interface XHRLogger { + logs: () => string; +} + +export class ElementSession { + readonly consoleLog: LogBuffer; + readonly networkLog: LogBuffer; + readonly log: Logger; + + constructor(readonly browser: puppeteer.Browser, readonly page: puppeteer.Page, readonly username: string, + readonly elementServer: string, readonly hsUrl: string) { + this.consoleLog = new LogBuffer(page, "console", + 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); } - static async create(username, puppeteerOptions, elementServer, hsUrl, throttleCpuFactor = 1) { + public static async create(username: string, puppeteerOptions: Parameters[0], + elementServer: string, hsUrl: string, throttleCpuFactor = 1): Promise { const browser = await puppeteer.launch(puppeteerOptions); const page = await browser.newPage(); await page.setViewport({ @@ -53,7 +60,7 @@ module.exports = class ElementSession { return new ElementSession(browser, page, username, elementServer, hsUrl); } - async tryGetInnertext(selector) { + public async tryGetInnertext(selector: string): Promise { const field = await this.page.$(selector); if (field != null) { const textHandle = await field.getProperty('innerText'); @@ -62,32 +69,32 @@ module.exports = class ElementSession { return null; } - async getElementProperty(handle, property) { + public async getElementProperty(handle: puppeteer.ElementHandle, property: string): Promise { const propHandle = await handle.getProperty(property); return await propHandle.jsonValue(); } - innerText(field) { + public innerText(field: puppeteer.ElementHandle): Promise { return this.getElementProperty(field, 'innerText'); } - getOuterHTML(field) { + public getOuterHTML(field: puppeteer.ElementHandle): Promise { return this.getElementProperty(field, 'outerHTML'); } - isChecked(field) { + public isChecked(field: puppeteer.ElementHandle): Promise { return this.getElementProperty(field, 'checked'); } - consoleLogs() { + public consoleLogs(): string { return this.consoleLog.buffer; } - networkLogs() { + public networkLogs(): string { return this.networkLog.buffer; } - logXHRRequests() { + public logXHRRequests(): XHRLogger { let buffer = ""; this.page.on('requestfinished', async (req) => { 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 { console.log(label, await Promise.all(elements.map(this.getOuterHTML))); } - async replaceInputText(input, text) { + public async replaceInputText(input: puppeteer.ElementHandle, text: string): Promise { // click 3 times to select all text await input.click({ clickCount: 3 }); // 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); } - query(selector, timeout = DEFAULT_TIMEOUT, hidden = false) { + public query(selector: string, timeout: number = DEFAULT_TIMEOUT, + hidden = false): Promise { return this.page.waitForSelector(selector, { visible: true, timeout, hidden }); } - async queryAll(selector) { + public async queryAll(selector: string): Promise { const timeout = DEFAULT_TIMEOUT; await this.query(selector, timeout); return await this.page.$$(selector); } - waitForReload() { + public waitForReload(): Promise { const timeout = DEFAULT_TIMEOUT; return new Promise((resolve, reject) => { const timeoutHandle = setTimeout(() => { - this.browser.removeEventListener('domcontentloaded', callback); + this.page.off('domcontentloaded', callback); reject(new Error(`timeout of ${timeout}ms for waitForReload elapsed`)); }, timeout); @@ -149,11 +157,11 @@ module.exports = class ElementSession { }); } - waitForNewPage() { + public waitForNewPage(): Promise { const timeout = DEFAULT_TIMEOUT; return new Promise((resolve, reject) => { const timeoutHandle = setTimeout(() => { - this.browser.removeListener('targetcreated', callback); + this.browser.off('targetcreated', callback); reject(new Error(`timeout of ${timeout}ms for waitForNewPage elapsed`)); }, timeout); @@ -161,7 +169,7 @@ module.exports = class ElementSession { if (target.type() !== 'page') { return; } - this.browser.removeListener('targetcreated', callback); + this.browser.off('targetcreated', callback); clearTimeout(timeoutHandle); const page = await target.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 */ - async waitForNextSuccessfulSync() { + public async waitForNextSuccessfulSync(): Promise { const syncUrls = []; function onRequest(request) { if (request.url().indexOf("/sync") !== -1) { @@ -186,33 +194,33 @@ module.exports = class ElementSession { 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 { return this.page.goto(url); } - url(path) { + public url(path: string): string { return this.elementServer + path; } - delay(ms) { + public delay(ms: number) { return delay(ms); } - async setOffline(enabled) { + public async setOffline(enabled: boolean): Promise { const description = enabled ? "offline" : "back online"; this.log.step(`goes ${description}`); await this.page.setOfflineMode(enabled); this.log.done(); } - async close() { + public async close(): Promise { return this.browser.close(); } - async poll(callback, interval = 100) { + public async poll(callback: () => Promise, interval = 100): Promise { const timeout = DEFAULT_TIMEOUT; let waited = 0; while (waited < timeout) { @@ -224,4 +232,4 @@ module.exports = class ElementSession { } return false; } -}; +} diff --git a/test/end-to-end-tests/src/usecases/accept-invite.js b/test/end-to-end-tests/src/usecases/accept-invite.ts similarity index 88% rename from test/end-to-end-tests/src/usecases/accept-invite.js rename to test/end-to-end-tests/src/usecases/accept-invite.ts index 50d685dc74..76510af8b9 100644 --- a/test/end-to-end-tests/src/usecases/accept-invite.js +++ b/test/end-to-end-tests/src/usecases/accept-invite.ts @@ -15,9 +15,10 @@ See the License for the specific language governing permissions and 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 { session.log.step(`accepts "${name}" invite`); const inviteSublist = await findSublist(session, "invites"); const invitesHandles = await inviteSublist.$$(".mx_RoomTile_name"); @@ -35,4 +36,4 @@ module.exports = async function acceptInvite(session, name) { await acceptInvitationLink.click(); session.log.done(); -}; +} diff --git a/test/end-to-end-tests/src/usecases/create-room.js b/test/end-to-end-tests/src/usecases/create-room.ts similarity index 84% rename from test/end-to-end-tests/src/usecases/create-room.js rename to test/end-to-end-tests/src/usecases/create-room.ts index b644ad0309..b3e2cecf21 100644 --- a/test/end-to-end-tests/src/usecases/create-room.js +++ b/test/end-to-end-tests/src/usecases/create-room.ts @@ -15,18 +15,20 @@ See the License for the specific language governing permissions and 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 { const roomDirectoryButton = await session.query('.mx_LeftPanel_exploreButton'); await roomDirectoryButton.click(); } -async function findSublist(session, name) { +export async function findSublist(session: ElementSession, name: string): Promise { 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 { session.log.step(`creates room "${roomName}"`); const roomsSublist = await findSublist(session, "rooms"); @@ -51,7 +53,7 @@ async function createRoom(session, roomName, encrypted=false) { session.log.done(); } -async function createDm(session, invitees) { +export async function createDm(session: ElementSession, invitees: string[]): Promise { session.log.step(`creates DM with ${JSON.stringify(invitees)}`); await measureStart(session, "mx_CreateDM"); @@ -83,5 +85,3 @@ async function createDm(session, invitees) { await measureStop(session, "mx_CreateDM"); } - -module.exports = { openRoomDirectory, findSublist, createRoom, createDm }; diff --git a/test/end-to-end-tests/src/usecases/create-space.js b/test/end-to-end-tests/src/usecases/create-space.ts similarity index 88% rename from test/end-to-end-tests/src/usecases/create-space.js rename to test/end-to-end-tests/src/usecases/create-space.ts index 7ce9766050..7d3dad2788 100644 --- a/test/end-to-end-tests/src/usecases/create-space.js +++ b/test/end-to-end-tests/src/usecases/create-space.ts @@ -14,12 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -async function openSpaceCreateMenu(session) { +import { ElementSession } from "../session"; + +export async function openSpaceCreateMenu(session: ElementSession): Promise { const spaceCreateButton = await session.query('.mx_SpaceButton_new'); await spaceCreateButton.click(); } -async function createSpace(session, name, isPublic = false) { +export async function createSpace(session: ElementSession, name: string, isPublic = false): Promise { session.log.step(`creates space "${name}"`); await openSpaceCreateMenu(session); @@ -50,7 +52,7 @@ async function createSpace(session, name, isPublic = false) { session.log.done(); } -async function inviteSpace(session, spaceName, userId) { +export async function inviteSpace(session: ElementSession, spaceName: string, userId: string): Promise { session.log.step(`invites "${userId}" to space "${spaceName}"`); const spaceButton = await session.query(`.mx_SpaceButton[aria-label="${spaceName}"]`); @@ -76,5 +78,3 @@ async function inviteSpace(session, spaceName, userId) { await confirmButton.click(); session.log.done(); } - -module.exports = { openSpaceCreateMenu, createSpace, inviteSpace }; diff --git a/test/end-to-end-tests/src/usecases/dialog.js b/test/end-to-end-tests/src/usecases/dialog.ts similarity index 76% rename from test/end-to-end-tests/src/usecases/dialog.js rename to test/end-to-end-tests/src/usecases/dialog.ts index 15ac50bb18..e88617bdf5 100644 --- a/test/end-to-end-tests/src/usecases/dialog.js +++ b/test/end-to-end-tests/src/usecases/dialog.ts @@ -15,22 +15,23 @@ See the License for the specific language governing permissions and 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 { const titleElement = await session.query(".mx_Dialog .mx_Dialog_title"); const dialogHeader = await session.innerText(titleElement); assert.equal(dialogHeader, expectedTitle); } -async function acceptDialog(session, expectedTitle) { +export async function acceptDialog(session: ElementSession, expectedTitle: string): Promise { const foundDialog = await acceptDialogMaybe(session, expectedTitle); if (!foundDialog) { throw new Error("could not find a dialog"); } } -async function acceptDialogMaybe(session, expectedTitle) { +export async function acceptDialogMaybe(session: ElementSession, expectedTitle: string): Promise { let primaryButton = null; try { primaryButton = await session.query(".mx_Dialog .mx_Dialog_primary"); @@ -43,9 +44,3 @@ async function acceptDialogMaybe(session, expectedTitle) { await primaryButton.click(); return true; } - -module.exports = { - assertDialog, - acceptDialog, - acceptDialogMaybe, -}; diff --git a/test/end-to-end-tests/src/usecases/invite.js b/test/end-to-end-tests/src/usecases/invite.ts similarity index 92% rename from test/end-to-end-tests/src/usecases/invite.js rename to test/end-to-end-tests/src/usecases/invite.ts index 07c9595fe2..8dc5d31f27 100644 --- a/test/end-to-end-tests/src/usecases/invite.js +++ b/test/end-to-end-tests/src/usecases/invite.ts @@ -15,7 +15,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -module.exports = async function invite(session, userId) { +import { ElementSession } from "../session"; + +export async function invite(session: ElementSession, userId: string): Promise { session.log.step(`invites "${userId}" to room`); await session.delay(1000); 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"); await confirmButton.click(); session.log.done(); -}; +} diff --git a/test/end-to-end-tests/src/usecases/join.js b/test/end-to-end-tests/src/usecases/join.ts similarity index 82% rename from test/end-to-end-tests/src/usecases/join.js rename to test/end-to-end-tests/src/usecases/join.ts index 1dff5fc45b..93ce9957f0 100644 --- a/test/end-to-end-tests/src/usecases/join.js +++ b/test/end-to-end-tests/src/usecases/join.ts @@ -15,10 +15,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -const { openRoomDirectory } = require('./create-room'); -const { measureStart, measureStop } = require('../util'); +import { openRoomDirectory } from './create-room'; +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 { session.log.step(`joins room "${roomName}"`); await measureStart(session, "mx_JoinRoom"); await openRoomDirectory(session); @@ -30,4 +31,4 @@ module.exports = async function join(session, roomName) { await session.query('.mx_MessageComposer'); await measureStop(session, "mx_JoinRoom"); session.log.done(); -}; +} diff --git a/test/end-to-end-tests/src/usecases/memberlist.js b/test/end-to-end-tests/src/usecases/memberlist.ts similarity index 78% rename from test/end-to-end-tests/src/usecases/memberlist.js rename to test/end-to-end-tests/src/usecases/memberlist.ts index 26c0c39755..6ac8ea2c6a 100644 --- a/test/end-to-end-tests/src/usecases/memberlist.js +++ b/test/end-to-end-tests/src/usecases/memberlist.ts @@ -15,10 +15,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -const assert = require('assert'); -const { openRoomSummaryCard } = require("./rightpanel"); +import { strict as assert } from 'assert'; +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 { const membersAndNames = await getMembersInMemberlist(session); const matchingLabel = membersAndNames.filter((m) => { return m.displayName === name; @@ -26,9 +28,13 @@ async function openMemberInfo(session, name) { 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 { session.log.step(`verifies e2e device for ${name}`); const membersAndNames = await getMembersInMemberlist(session); const matchingLabel = membersAndNames.filter((m) => { @@ -50,19 +56,24 @@ module.exports.verifyDeviceForUser = async function(session, name, expectedDevic console.log("my sas labels", sasLabels); 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 deviceKey = await session.innerText(dialogCodeFields[1]); - assert.equal(expectedDevice.id, deviceId); - assert.equal(expectedDevice.key, deviceKey); + 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(); -}; +} -async function getMembersInMemberlist(session) { +interface MemberName { + label: ElementHandle; + displayName: string; +} + +export async function getMembersInMemberlist(session: ElementSession): Promise { await openRoomSummaryCard(session); const memberPanelButton = await session.query(".mx_RoomSummaryCard_icon_people"); // We are back at the room summary card @@ -73,5 +84,3 @@ async function getMembersInMemberlist(session) { return { label: el, displayName: await session.innerText(el) }; })); } - -module.exports.getMembersInMemberlist = getMembersInMemberlist; diff --git a/test/end-to-end-tests/src/usecases/rightpanel.js b/test/end-to-end-tests/src/usecases/rightpanel.ts similarity index 80% rename from test/end-to-end-tests/src/usecases/rightpanel.js rename to test/end-to-end-tests/src/usecases/rightpanel.ts index 00a8cde5a6..5a02317880 100644 --- a/test/end-to-end-tests/src/usecases/rightpanel.js +++ b/test/end-to-end-tests/src/usecases/rightpanel.ts @@ -14,7 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -module.exports.openRoomRightPanel = async function(session) { +import { ElementSession } from "../session"; + +export async function openRoomRightPanel(session: ElementSession): Promise { try { await session.query('.mx_RoomHeader .mx_RightPanel_headerButton_highlight[aria-label="Room Info"]'); } 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"]'); await roomSummaryButton.click(); } -}; +} -module.exports.goBackToRoomSummaryCard = async function(session) { +export async function goBackToRoomSummaryCard(session: ElementSession): Promise { for (let i = 0; i < 5; i++) { try { 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) { - await module.exports.openRoomRightPanel(session); - await module.exports.goBackToRoomSummaryCard(session); -}; +export async function openRoomSummaryCard(session: ElementSession) { + await openRoomRightPanel(session); + await goBackToRoomSummaryCard(session); +} diff --git a/test/end-to-end-tests/src/usecases/room-settings.js b/test/end-to-end-tests/src/usecases/room-settings.ts similarity index 86% rename from test/end-to-end-tests/src/usecases/room-settings.js rename to test/end-to-end-tests/src/usecases/room-settings.ts index 83d6fd79a8..8e70627a87 100644 --- a/test/end-to-end-tests/src/usecases/room-settings.js +++ b/test/end-to-end-tests/src/usecases/room-settings.ts @@ -15,11 +15,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -const assert = require('assert'); -const { openRoomSummaryCard } = require("./rightpanel"); -const { acceptDialog } = require('./dialog'); +import { strict as assert } from 'assert'; +import { openRoomSummaryCard } from "./rightpanel"; +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 { const className = await session.getElementProperty(toggle, "className"); const checked = className.includes("mx_ToggleSwitch_on"); 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 { const className = await session.getElementProperty(toggle, "className"); const checked = className.includes("mx_ToggleSwitch_on"); 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 { /// XXX delay is needed here, possibly because the header is being rerendered /// click doesn't do anything otherwise await session.delay(1000); @@ -60,7 +67,14 @@ async function findTabs(session) { 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 { session.log.startGroup(`checks the room settings`); const { securityTabButton } = await findTabs(session); @@ -76,7 +90,7 @@ async function checkRoomSettings(session, expectedSettings) { session.log.step(`checks for local alias of ${expectedSettings.alias}`); const summary = await session.query(".mx_RoomSettingsDialog .mx_AliasSettings summary"); 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))); if (localAliasTexts.find(a => a.includes(expectedSettings.alias))) { session.log.done("present"); @@ -85,7 +99,7 @@ async function checkRoomSettings(session, expectedSettings) { } } - securityTabButton.click(); + await securityTabButton.click(); await session.delay(500); const securitySwitches = await session.queryAll(".mx_RoomSettingsDialog .mx_ToggleSwitch"); const e2eEncryptionToggle = securitySwitches[0]; @@ -122,7 +136,7 @@ async function checkRoomSettings(session, expectedSettings) { session.log.endGroup(); } -async function changeRoomSettings(session, settings) { +export async function changeRoomSettings(session, settings) { session.log.startGroup(`changes the room settings`); const { securityTabButton } = await findTabs(session); @@ -179,5 +193,3 @@ async function changeRoomSettings(session, settings) { session.log.endGroup(); } - -module.exports = { checkRoomSettings, changeRoomSettings }; diff --git a/test/end-to-end-tests/src/usecases/security.js b/test/end-to-end-tests/src/usecases/security.ts similarity index 89% rename from test/end-to-end-tests/src/usecases/security.js rename to test/end-to-end-tests/src/usecases/security.ts index 31540874e9..66c786a94e 100644 --- a/test/end-to-end-tests/src/usecases/security.js +++ b/test/end-to-end-tests/src/usecases/security.ts @@ -14,9 +14,10 @@ See the License for the specific language governing permissions and 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 { session.log.step("sets up Secure Backup"); await acceptToast(session, "Set up Secure Backup"); diff --git a/test/end-to-end-tests/src/usecases/send-message.js b/test/end-to-end-tests/src/usecases/send-message.ts similarity index 87% rename from test/end-to-end-tests/src/usecases/send-message.js rename to test/end-to-end-tests/src/usecases/send-message.ts index 764994420c..ab1dc96d71 100644 --- a/test/end-to-end-tests/src/usecases/send-message.js +++ b/test/end-to-end-tests/src/usecases/send-message.ts @@ -14,9 +14,10 @@ See the License for the specific language governing permissions and 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 { session.log.step(`writes "${message}" in room`); // this selector needs to be the element that has contenteditable=true, // 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 await session.query(".mx_EventTile_last:not(.mx_EventTile_sending)"); session.log.done(); -}; +} diff --git a/test/end-to-end-tests/src/usecases/settings.js b/test/end-to-end-tests/src/usecases/settings.ts similarity index 75% rename from test/end-to-end-tests/src/usecases/settings.js rename to test/end-to-end-tests/src/usecases/settings.ts index 372bdead10..a8e2bfee45 100644 --- a/test/end-to-end-tests/src/usecases/settings.js +++ b/test/end-to-end-tests/src/usecases/settings.ts @@ -15,9 +15,10 @@ See the License for the specific language governing permissions and 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 { const menuButton = await session.query(".mx_UserMenu"); await menuButton.click(); 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 { session.log.step(`enables lazy loading of members in the lab settings`); const settingsButton = await session.query('.mx_BottomLeftMenu_settings'); await settingsButton.click(); @@ -39,17 +40,22 @@ module.exports.enableLazyLoading = async function(session) { const closeButton = await session.query(".mx_RoomHeader_cancelButton"); await closeButton.click(); session.log.done(); -}; +} -module.exports.getE2EDeviceFromSettings = async function(session) { +interface E2EDevice { + id: string; + key: string; +} + +export async function getE2EDeviceFromSettings(session: ElementSession): Promise { session.log.step(`gets e2e device/key from settings`); await openSettings(session, "security"); const deviceAndKey = await session.queryAll(".mx_SettingsTab_section .mx_CryptographyPanel code"); assert.equal(deviceAndKey.length, 2); - const id = await (await deviceAndKey[0].getProperty("innerText")).jsonValue(); - const key = await (await deviceAndKey[1].getProperty("innerText")).jsonValue(); + const id: string = await (await deviceAndKey[0].getProperty("innerText")).jsonValue(); + const key: string = await (await deviceAndKey[1].getProperty("innerText")).jsonValue(); const closeButton = await session.query(".mx_UserSettingsDialog .mx_Dialog_cancelButton"); await closeButton.click(); session.log.done(); return { id, key }; -}; +} diff --git a/test/end-to-end-tests/src/usecases/signup.js b/test/end-to-end-tests/src/usecases/signup.ts similarity index 94% rename from test/end-to-end-tests/src/usecases/signup.js rename to test/end-to-end-tests/src/usecases/signup.ts index a3391e99f3..24282fc60e 100644 --- a/test/end-to-end-tests/src/usecases/signup.js +++ b/test/end-to-end-tests/src/usecases/signup.ts @@ -15,9 +15,11 @@ See the License for the specific language governing permissions and 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 { session.log.step("signs up"); await session.goto(session.url('/#/register')); // change the homeserver by clicking the advanced section @@ -79,4 +81,4 @@ module.exports = async function signup(session, username, password, homeserver) }); assert(foundHomeUrl); session.log.done(); -}; +} diff --git a/test/end-to-end-tests/src/usecases/timeline.js b/test/end-to-end-tests/src/usecases/timeline.ts similarity index 77% rename from test/end-to-end-tests/src/usecases/timeline.js rename to test/end-to-end-tests/src/usecases/timeline.ts index f9d7300ff1..7906eb3c88 100644 --- a/test/end-to-end-tests/src/usecases/timeline.js +++ b/test/end-to-end-tests/src/usecases/timeline.ts @@ -15,9 +15,11 @@ See the License for the specific language governing permissions and 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 { session.log.step(`scrolls to the top of the timeline`); await session.page.evaluate(() => { return Promise.resolve().then(async () => { @@ -41,14 +43,21 @@ module.exports.scrollToTimelineTop = async function(session) { }); }); 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 { session.log.step(`receives message "${expectedMessage.body}" from ${expectedMessage.sender}`); // wait for a response to come in that contains the message // crude, but effective - async function getLastMessage() { + async function getLastMessage(): Promise { const lastTile = await getLastEventTile(session); return getMessageFromEventTile(lastTile); } @@ -67,25 +76,26 @@ module.exports.receiveMessage = async function(session, expectedMessage) { }); assertMessage(lastMessage, expectedMessage); session.log.done(); -}; +} -module.exports.checkTimelineContains = async function(session, expectedMessages, sendersDescription) { +export async function checkTimelineContains(session: ElementSession, expectedMessages: Message[], + sendersDescription: string): Promise { session.log.step(`checks timeline contains ${expectedMessages.length} ` + `given messages${sendersDescription ? ` from ${sendersDescription}`:""}`); 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); })); //filter out tiles that were not messages timelineMessages = timelineMessages.filter((m) => !!m); - timelineMessages.reduce((prevSender, 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) => { @@ -101,9 +111,9 @@ module.exports.checkTimelineContains = async function(session, expectedMessages, }); 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.equal(foundMessage.body, expectedMessage.body); assert.equal(foundMessage.sender, expectedMessage.sender); @@ -112,17 +122,17 @@ function assertMessage(foundMessage, expectedMessage) { } } -function getLastEventTile(session) { +function getLastEventTile(session: ElementSession): Promise { return session.query(".mx_EventTile_last"); } -function getAllEventTiles(session) { +function getAllEventTiles(session: ElementSession): Promise { return session.queryAll(".mx_RoomView_MessageList .mx_EventTile"); } -async function getMessageFromEventTile(eventTile) { +async function getMessageFromEventTile(eventTile: ElementHandle): Promise { 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 bodyElement = await eventTile.$(".mx_EventTile_body"); let sender = null; @@ -132,7 +142,7 @@ async function getMessageFromEventTile(eventTile) { if (!bodyElement) { return null; } - const body = await(await bodyElement.getProperty("innerText")).jsonValue(); + const body: string = await(await bodyElement.getProperty("innerText")).jsonValue(); return { sender, diff --git a/test/end-to-end-tests/src/usecases/toasts.js b/test/end-to-end-tests/src/usecases/toasts.ts similarity index 75% rename from test/end-to-end-tests/src/usecases/toasts.js rename to test/end-to-end-tests/src/usecases/toasts.ts index 8650ff1400..c90f9ef16c 100644 --- a/test/end-to-end-tests/src/usecases/toasts.js +++ b/test/end-to-end-tests/src/usecases/toasts.ts @@ -14,9 +14,10 @@ See the License for the specific language governing permissions and 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 { try { await session.query('.mx_Toast_toast', 1000, true); } catch (e) { @@ -26,22 +27,20 @@ async function assertNoToasts(session) { } } -async function assertToast(session, expectedTitle) { +export async function assertToast(session: ElementSession, expectedTitle: string): Promise { const h2Element = await session.query('.mx_Toast_title h2'); const toastTitle = await session.innerText(h2Element); assert.equal(toastTitle, expectedTitle); } -async function acceptToast(session, expectedTitle) { +export async function acceptToast(session: ElementSession, expectedTitle: string): Promise { await assertToast(session, expectedTitle); const btn = await session.query('.mx_Toast_buttons .mx_AccessibleButton_kind_primary'); await btn.click(); } -async function rejectToast(session, expectedTitle) { +export async function rejectToast(session: ElementSession, expectedTitle: string): Promise { await assertToast(session, expectedTitle); const btn = await session.query('.mx_Toast_buttons .mx_AccessibleButton_kind_danger_outline'); await btn.click(); } - -module.exports = { assertNoToasts, assertToast, acceptToast, rejectToast }; diff --git a/test/end-to-end-tests/src/usecases/verify.js b/test/end-to-end-tests/src/usecases/verify.ts similarity index 85% rename from test/end-to-end-tests/src/usecases/verify.js rename to test/end-to-end-tests/src/usecases/verify.ts index a1f600833d..b0a2e0588d 100644 --- a/test/end-to-end-tests/src/usecases/verify.js +++ b/test/end-to-end-tests/src/usecases/verify.ts @@ -15,10 +15,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -const assert = require('assert'); -const { openMemberInfo } = require("./memberlist"); +import { strict as assert } from 'assert'; +import { openMemberInfo } from "./memberlist"; +import { ElementSession } from "../session"; -async function startVerification(session, name) { +export async function startVerification(session: ElementSession, name: string): Promise { session.log.step("opens their opponent's profile and starts verification"); await openMemberInfo(session, name); // click verify in member info @@ -29,22 +30,22 @@ async function startVerification(session, name) { await session.delay(1000); // 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(); session.log.done(); } -async function getSasCodes(session) { +async function getSasCodes(session: ElementSession): Promise { 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))); return sasLabels; } -async function doSasVerification(session) { +async function doSasVerification(session: ElementSession): Promise { session.log.step("hunts for the emoji to yell at their opponent"); const sasCodes = await getSasCodes(session); - session.log.done(sasCodes); + session.log.done(sasCodes.join("\n")); // Assume they match session.log.step("assumes the emoji match"); @@ -74,7 +75,7 @@ async function doSasVerification(session) { return sasCodes; } -module.exports.startSasVerification = async function(session, name) { +export async function startSasVerification(session: ElementSession, name: string): Promise { session.log.startGroup("starts verification"); await startVerification(session, name); @@ -84,9 +85,9 @@ module.exports.startSasVerification = async function(session, name) { const sasCodes = await doSasVerification(session); session.log.endGroup(); return sasCodes; -}; +} -module.exports.acceptSasVerification = async function(session, name) { +export async function acceptSasVerification(session: ElementSession, name: string): Promise { session.log.startGroup("accepts 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); session.log.endGroup(); return sasCodes; -}; +} diff --git a/test/end-to-end-tests/src/util.js b/test/end-to-end-tests/src/util.ts similarity index 73% rename from test/end-to-end-tests/src/util.js rename to test/end-to-end-tests/src/util.ts index 5abb110df4..5c3c4bc6a2 100644 --- a/test/end-to-end-tests/src/util.js +++ b/test/end-to-end-tests/src/util.ts @@ -15,7 +15,9 @@ See the License for the specific language governing permissions and 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 { const r = []; for (let i = 0; i < amount; ++i) { r.push(start + (i * step)); @@ -23,17 +25,17 @@ module.exports.range = function(start, amount, step = 1) { return r; }; -module.exports.delay = function(ms) { +export const delay = function(ms): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); }; -module.exports.measureStart = function(session, name) { +export const measureStart = function(session: ElementSession, name: string): Promise { return session.page.evaluate(_name => { window.mxPerformanceMonitor.start(_name); }, name); }; -module.exports.measureStop = function(session, name) { +export const measureStop = function(session: ElementSession, name: string): Promise { return session.page.evaluate(_name => { window.mxPerformanceMonitor.stop(_name); }, name); diff --git a/test/end-to-end-tests/start.js b/test/end-to-end-tests/start.ts similarity index 91% rename from test/end-to-end-tests/start.js rename to test/end-to-end-tests/start.ts index f65d6137f4..77013939e6 100644 --- a/test/end-to-end-tests/start.js +++ b/test/end-to-end-tests/start.ts @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -const ElementSession = require('./src/session'); -const scenario = require('./src/scenario'); -const RestSessionCreator = require('./src/rest/creator'); -const fs = require("fs"); +import { ElementSession } from './src/session'; +import { scenario } from './src/scenario'; +import { RestSessionCreator } from './src/rest/creator'; +import * as fs from "fs"; -const program = require('commander'); +import program = require('commander'); program .option('--no-logs', "don't output logs, document html on error", false) .option('--app-url [url]', "url to test", "http://localhost:5000") @@ -47,11 +47,11 @@ async function runTests() { if (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)`); - options.executablePath = path; + options['executablePath'] = path; } const restCreator = new RestSessionCreator( - 'synapse/installations/consent/env/bin', + '../synapse/installations/consent/env/bin', hsUrl, __dirname, ); @@ -84,7 +84,7 @@ async function runTests() { await Promise.all(sessions.map(async (session) => { // Collecting all performance monitoring data before closing the session const measurements = await session.page.evaluate(() => { - let measurements = []; + let measurements; window.mxPerformanceMonitor.addPerformanceDataCallback({ entryNames: [ window.mxPerformanceEntryNames.REGISTER, @@ -106,7 +106,9 @@ async function runTests() { performanceEntries = JSON.parse(measurements); return session.close(); })); - fs.writeFileSync(`performance-entries.json`, JSON.stringify(performanceEntries)); + if (performanceEntries) { + fs.writeFileSync(`performance-entries.json`, JSON.stringify(performanceEntries)); + } if (failure) { process.exit(-1); } else { diff --git a/test/end-to-end-tests/tsconfig.json b/test/end-to-end-tests/tsconfig.json new file mode 100644 index 0000000000..7c818bd866 --- /dev/null +++ b/test/end-to-end-tests/tsconfig.json @@ -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" + ] +} diff --git a/test/end-to-end-tests/yarn.lock b/test/end-to-end-tests/yarn.lock index 985c883ad6..61d0a21532 100644 --- a/test/end-to-end-tests/yarn.lock +++ b/test/end-to-end-tests/yarn.lock @@ -7,6 +7,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-11.12.1.tgz#d90123f6c61fdf2f7cddd286ddae891586dd3488" 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": version "2.9.1" resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af"