Convert end-to-end tests to Typescript (#7206)
This commit is contained in:
parent
5219b6be80
commit
d4813f7a1a
42 changed files with 653 additions and 441 deletions
|
@ -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/
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
2
test/end-to-end-tests/.gitignore
vendored
2
test/end-to-end-tests/.gitignore
vendored
|
@ -2,3 +2,5 @@ node_modules
|
||||||
*.png
|
*.png
|
||||||
element/env
|
element/env
|
||||||
performance-entries.json
|
performance-entries.json
|
||||||
|
lib
|
||||||
|
logs
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
24
test/end-to-end-tests/src/@types/global.d.ts
vendored
Normal file
24
test/end-to-end-tests/src/@types/global.d.ts
vendored
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
};
|
}
|
|
@ -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();
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
}
|
|
@ -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();
|
|
@ -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;
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
137
test/end-to-end-tests/src/rest/session.ts
Normal file
137
test/end-to-end-tests/src/rest/session.ts
Normal 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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
|
@ -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 });
|
||||||
};
|
}
|
|
@ -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);
|
||||||
};
|
}
|
|
@ -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);
|
|
@ -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
|
|
@ -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();
|
||||||
};
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
};
|
}
|
|
@ -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();
|
||||||
};
|
}
|
|
@ -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 };
|
|
|
@ -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 };
|
|
|
@ -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,
|
|
||||||
};
|
|
|
@ -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();
|
||||||
};
|
}
|
|
@ -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();
|
||||||
};
|
}
|
|
@ -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;
|
|
|
@ -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);
|
||||||
};
|
}
|
|
@ -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 };
|
|
|
@ -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");
|
|
@ -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();
|
||||||
};
|
}
|
|
@ -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 };
|
||||||
};
|
}
|
|
@ -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();
|
||||||
};
|
}
|
|
@ -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,
|
|
@ -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 };
|
|
|
@ -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;
|
||||||
};
|
}
|
|
@ -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);
|
|
@ -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 {
|
23
test/end-to-end-tests/tsconfig.json
Normal file
23
test/end-to-end-tests/tsconfig.json
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in a new issue