From e806b0d22ac454be9b4b9bb13a1425bef13b3c49 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 5 Jul 2018 11:55:45 +0100 Subject: [PATCH 001/221] initial commit --- package.json | 15 +++++ title.test.js | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 package.json create mode 100644 title.test.js diff --git a/package.json b/package.json new file mode 100644 index 0000000000..fa67de0069 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "e2e-tests", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "jest": "^23.2.0", + "puppeteer": "^1.5.0" + } +} diff --git a/title.test.js b/title.test.js new file mode 100644 index 0000000000..d416e889f0 --- /dev/null +++ b/title.test.js @@ -0,0 +1,162 @@ +const puppeteer = require('puppeteer'); +const riotserver = 'http://localhost:8080'; +const homeserver = 'http://localhost:8008'; +let browser = null; + +jest.setTimeout(10000); + +async function try_get_innertext(page, selector) { + const field = await page.$(selector); + if (field != null) { + const text_handle = await field.getProperty('innerText'); + return await text_handle.jsonValue(); + } + return null; +} + +async function new_page() { + const page = await browser.newPage(); + await page.setViewport({ + width: 1280, + height: 800 + }); + return page; +} + +function log_console(page) { + let buffer = ""; + page.on('console', msg => { + buffer += msg.text() + '\n'; + }); + return { + logs() { + return buffer; + } + } +} + +function log_xhr_requests(page) { + let buffer = ""; + page.on('request', req => { + const type = req.resourceType(); + if (type === 'xhr' || type === 'fetch') { + buffer += `${req.method()} ${req.url()} \n`; + if (req.method() === "POST") { + buffer += " Post data: " + req.postData(); + } + } + }); + return { + logs() { + return buffer; + } + } +} + +function rnd_int(max) { + return Math.ceil(Math.random()*max); +} + +function riot_url(path) { + return riotserver + path; +} + +function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function get_outer_html(element_handle) { + const html_handle = await element_handle.getProperty('outerHTML'); + return await html_handle.jsonValue(); +} + +async function print_elements(label, elements) { + console.log(label, await Promise.all(elements.map(get_outer_html))); +} + +async function replace_input_text(input, text) { + // click 3 times to select all text + await input.click({clickCount: 3}); + // then remove it with backspace + await input.press('Backspace'); + // and type the new text + await input.type(text); +} + +beforeAll(async () => { + browser = await puppeteer.launch(); +}); + +afterAll(() => { + return browser.close(); +}) + +test('test page loads', async () => { + const page = await browser.newPage(); + await page.goto(riot_url('/')); + const title = await page.title(); + expect(title).toBe("Riot"); +}); + +test('test signup', async () => { + const page = await new_page(); + const console_logs = log_console(page); + const xhr_logs = log_xhr_requests(page); + await page.goto(riot_url('/#/register')); + //click 'Custom server' radio button + await page.waitForSelector('#advanced', {visible: true, timeout: 500}); + await page.click('#advanced'); + + const username = 'bruno-' + rnd_int(10000); + const password = 'testtest'; + //fill out form + await page.waitForSelector('.mx_ServerConfig', {visible: true, timeout: 500}); + const login_fields = await page.$$('.mx_Login_field'); + expect(login_fields.length).toBe(7); + const username_field = login_fields[2]; + const password_field = login_fields[3]; + const password_repeat_field = login_fields[4]; + const hsurl_field = login_fields[5]; + await replace_input_text(username_field, username); + await replace_input_text(password_field, password); + await replace_input_text(password_repeat_field, password); + await replace_input_text(hsurl_field, homeserver); + //wait over a second because Registration/ServerConfig have a 1000ms + //delay to internally set the homeserver url + //see Registration::render and ServerConfig::props::delayTimeMs + await delay(1200); + /// focus on the button to make sure error validation + /// has happened before checking the form is good to go + const register_button = await page.$('.mx_Login_submit'); + await register_button.focus(); + //check no errors + const error_text = await try_get_innertext(page, '.mx_Login_error'); + expect(error_text).toBeFalsy(); + //submit form + await page.screenshot({path: "beforesubmit.png", fullPage: true}); + await register_button.click(); + + //confirm dialog saying you cant log back in without e-mail + await page.waitForSelector('.mx_QuestionDialog', {visible: true, timeout: 500}); + const continue_button = await page.$('.mx_QuestionDialog button.mx_Dialog_primary'); + print_elements('continue_button', [continue_button]); + await continue_button.click(); + //wait for registration to finish so the hash gets set + //onhashchange better? + await delay(1000); +/* + await page.screenshot({path: "afterlogin.png", fullPage: true}); + console.log('browser console logs:'); + console.log(console_logs.logs()); + console.log('xhr logs:'); + console.log(xhr_logs.logs()); +*/ + + + //print_elements('page', await page.$('#matrixchat')); +// await navigation_promise; + + //await page.waitForSelector('.mx_MatrixChat', {visible: true, timeout: 3000}); + const url = page.url(); + expect(url).toBe(riot_url('/#/home')); +}); From 956beaf1f4a1bf4c4ffcbc7c1a9bdefb587d764d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 5 Jul 2018 11:56:19 +0100 Subject: [PATCH 002/221] add initial README --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000000..349e0294b7 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# Matrix React Web App End-to-End tests + +This repository contains tests for the matrix-react-sdk web app. The tests fire up a headless chrome and simulate user interaction (end-to-end). Note that end-to-end has little to do with the end-to-end encryption matrix supports, just that we test the full stack, going from user interaction to expected DOM in the browser. + +## Current tests + - test riot loads (check title) + - signup with custom homeserver + +## Roadmap +- get rid of jest, as a test framework won't be helpful to have a continuous flow going from one use case to another (think: do login, create a room, invite a user, ...). a test framework usually assumes the tests are semi-indepedent. +- better error reporting (show console.log, XHR requests, partial DOM, screenshot) on error +- cleanup helper methods +- avoid delay when waiting for location.hash to change +- more tests! +- setup installing & running riot and synapse as part of the tests +- look into CI(Travis) integration + +## How to run + +### Setup + + - install dependencies with `npm install` + - have riot-web running on `localhost:8080` + - have a local synapse running at `localhost:8008` + +### Run tests + - run tests with `./node_modules/jest/bin/jest.js` \ No newline at end of file From a240c5617c416034f4d544d00c157eef684c7605 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 6 Jul 2018 11:40:42 +0100 Subject: [PATCH 003/221] Update roadmap --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 349e0294b7..fdbd0caf3b 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ This repository contains tests for the matrix-react-sdk web app. The tests fire - get rid of jest, as a test framework won't be helpful to have a continuous flow going from one use case to another (think: do login, create a room, invite a user, ...). a test framework usually assumes the tests are semi-indepedent. - better error reporting (show console.log, XHR requests, partial DOM, screenshot) on error - cleanup helper methods +- add more css id's/classes to riot web to make css selectors in test less brittle. - avoid delay when waiting for location.hash to change - more tests! - setup installing & running riot and synapse as part of the tests @@ -24,4 +25,4 @@ This repository contains tests for the matrix-react-sdk web app. The tests fire - have a local synapse running at `localhost:8008` ### Run tests - - run tests with `./node_modules/jest/bin/jest.js` \ No newline at end of file + - run tests with `./node_modules/jest/bin/jest.js` From f43d53c0201b434a0f61417d3b19ed8cc1832681 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 9 Jul 2018 12:41:24 +0200 Subject: [PATCH 004/221] Update roadmap --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fdbd0caf3b..a76301d5a2 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,9 @@ This repository contains tests for the matrix-react-sdk web app. The tests fire - add more css id's/classes to riot web to make css selectors in test less brittle. - avoid delay when waiting for location.hash to change - more tests! -- setup installing & running riot and synapse as part of the tests +- setup installing & running riot and synapse as part of the tests. + - Run 2 synapse instances to test federation use cases. + - start synapse with clean config/database on every test run - look into CI(Travis) integration ## How to run From 9921573076c26ad4b6f72a04793e2f9f0186fe10 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 9 Jul 2018 16:50:49 +0200 Subject: [PATCH 005/221] add license and copyright notice --- title.test.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/title.test.js b/title.test.js index d416e889f0..9c10452230 100644 --- a/title.test.js +++ b/title.test.js @@ -1,3 +1,19 @@ +/* +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 puppeteer = require('puppeteer'); const riotserver = 'http://localhost:8080'; const homeserver = 'http://localhost:8008'; From 5429bfde11de378b6a564a1eb45f55a1eef40461 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 9 Jul 2018 17:06:17 +0200 Subject: [PATCH 006/221] move helpers to other module --- helpers.js | 110 +++++++++++++++++++++++++++++++++++++++++++++++ title.test.js | 117 +++++++++----------------------------------------- 2 files changed, 130 insertions(+), 97 deletions(-) create mode 100644 helpers.js diff --git a/helpers.js b/helpers.js new file mode 100644 index 0000000000..bd0035f13d --- /dev/null +++ b/helpers.js @@ -0,0 +1,110 @@ +/* +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. +*/ + +// puppeteer helpers + +async function try_get_innertext(page, selector) { + const field = await page.$(selector); + if (field != null) { + const text_handle = await field.getProperty('innerText'); + return await text_handle.jsonValue(); + } + return null; +} + +async function new_page() { + const page = await browser.newPage(); + await page.setViewport({ + width: 1280, + height: 800 + }); + return page; +} + +function log_console(page) { + let buffer = ""; + page.on('console', msg => { + buffer += msg.text() + '\n'; + }); + return { + logs() { + return buffer; + } + } +} + +function log_xhr_requests(page) { + let buffer = ""; + page.on('request', req => { + const type = req.resourceType(); + if (type === 'xhr' || type === 'fetch') { + buffer += `${req.method()} ${req.url()} \n`; + if (req.method() === "POST") { + buffer += " Post data: " + req.postData(); + } + } + }); + return { + logs() { + return buffer; + } + } +} + +async function get_outer_html(element_handle) { + const html_handle = await element_handle.getProperty('outerHTML'); + return await html_handle.jsonValue(); +} + +async function print_elements(label, elements) { + console.log(label, await Promise.all(elements.map(get_outer_html))); +} + +async function replace_input_text(input, text) { + // click 3 times to select all text + await input.click({clickCount: 3}); + // then remove it with backspace + await input.press('Backspace'); + // and type the new text + await input.type(text); +} + +// other helpers + +function rnd_int(max) { + return Math.ceil(Math.random()*max); +} + +function riot_url(path) { + return riotserver + path; +} + +function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +module.exports = { + try_get_innertext, + new_page, + log_console, + log_xhr_requests, + get_outer_html, + print_elements, + replace_input_text, + rnd_int, + riot_url, + delay, +} \ No newline at end of file diff --git a/title.test.js b/title.test.js index 9c10452230..0bbb8d56ac 100644 --- a/title.test.js +++ b/title.test.js @@ -15,92 +15,15 @@ limitations under the License. */ const puppeteer = require('puppeteer'); -const riotserver = 'http://localhost:8080'; -const homeserver = 'http://localhost:8008'; -let browser = null; +const helpers = require('./helpers'); +global.riotserver = 'http://localhost:8080'; +global.homeserver = 'http://localhost:8008'; +global.browser = null; jest.setTimeout(10000); -async function try_get_innertext(page, selector) { - const field = await page.$(selector); - if (field != null) { - const text_handle = await field.getProperty('innerText'); - return await text_handle.jsonValue(); - } - return null; -} - -async function new_page() { - const page = await browser.newPage(); - await page.setViewport({ - width: 1280, - height: 800 - }); - return page; -} - -function log_console(page) { - let buffer = ""; - page.on('console', msg => { - buffer += msg.text() + '\n'; - }); - return { - logs() { - return buffer; - } - } -} - -function log_xhr_requests(page) { - let buffer = ""; - page.on('request', req => { - const type = req.resourceType(); - if (type === 'xhr' || type === 'fetch') { - buffer += `${req.method()} ${req.url()} \n`; - if (req.method() === "POST") { - buffer += " Post data: " + req.postData(); - } - } - }); - return { - logs() { - return buffer; - } - } -} - -function rnd_int(max) { - return Math.ceil(Math.random()*max); -} - -function riot_url(path) { - return riotserver + path; -} - -function delay(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -async function get_outer_html(element_handle) { - const html_handle = await element_handle.getProperty('outerHTML'); - return await html_handle.jsonValue(); -} - -async function print_elements(label, elements) { - console.log(label, await Promise.all(elements.map(get_outer_html))); -} - -async function replace_input_text(input, text) { - // click 3 times to select all text - await input.click({clickCount: 3}); - // then remove it with backspace - await input.press('Backspace'); - // and type the new text - await input.type(text); -} - beforeAll(async () => { - browser = await puppeteer.launch(); + global.browser = await puppeteer.launch(); }); afterAll(() => { @@ -109,21 +32,21 @@ afterAll(() => { test('test page loads', async () => { const page = await browser.newPage(); - await page.goto(riot_url('/')); + await page.goto(helpers.riot_url('/')); const title = await page.title(); expect(title).toBe("Riot"); }); test('test signup', async () => { - const page = await new_page(); - const console_logs = log_console(page); - const xhr_logs = log_xhr_requests(page); - await page.goto(riot_url('/#/register')); + const page = await helpers.new_page(); + const console_logs = helpers.log_console(page); + const xhr_logs = helpers.log_xhr_requests(page); + await page.goto(helpers.riot_url('/#/register')); //click 'Custom server' radio button await page.waitForSelector('#advanced', {visible: true, timeout: 500}); await page.click('#advanced'); - const username = 'bruno-' + rnd_int(10000); + const username = 'bruno-' + helpers.rnd_int(10000); const password = 'testtest'; //fill out form await page.waitForSelector('.mx_ServerConfig', {visible: true, timeout: 500}); @@ -133,20 +56,20 @@ test('test signup', async () => { const password_field = login_fields[3]; const password_repeat_field = login_fields[4]; const hsurl_field = login_fields[5]; - await replace_input_text(username_field, username); - await replace_input_text(password_field, password); - await replace_input_text(password_repeat_field, password); - await replace_input_text(hsurl_field, homeserver); + await helpers.replace_input_text(username_field, username); + await helpers.replace_input_text(password_field, password); + await helpers.replace_input_text(password_repeat_field, password); + await helpers.replace_input_text(hsurl_field, homeserver); //wait over a second because Registration/ServerConfig have a 1000ms //delay to internally set the homeserver url //see Registration::render and ServerConfig::props::delayTimeMs - await delay(1200); + await helpers.delay(1200); /// focus on the button to make sure error validation /// has happened before checking the form is good to go const register_button = await page.$('.mx_Login_submit'); await register_button.focus(); //check no errors - const error_text = await try_get_innertext(page, '.mx_Login_error'); + const error_text = await helpers.try_get_innertext(page, '.mx_Login_error'); expect(error_text).toBeFalsy(); //submit form await page.screenshot({path: "beforesubmit.png", fullPage: true}); @@ -155,11 +78,11 @@ test('test signup', async () => { //confirm dialog saying you cant log back in without e-mail await page.waitForSelector('.mx_QuestionDialog', {visible: true, timeout: 500}); const continue_button = await page.$('.mx_QuestionDialog button.mx_Dialog_primary'); - print_elements('continue_button', [continue_button]); + await helpers.print_elements('continue_button', [continue_button]); await continue_button.click(); //wait for registration to finish so the hash gets set //onhashchange better? - await delay(1000); + await helpers.delay(1000); /* await page.screenshot({path: "afterlogin.png", fullPage: true}); console.log('browser console logs:'); @@ -174,5 +97,5 @@ test('test signup', async () => { //await page.waitForSelector('.mx_MatrixChat', {visible: true, timeout: 3000}); const url = page.url(); - expect(url).toBe(riot_url('/#/home')); + expect(url).toBe(helpers.riot_url('/#/home')); }); From 473af6ff6212947e918a5e02dd484d1b7e2ed8ef Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 9 Jul 2018 17:07:03 +0200 Subject: [PATCH 007/221] add ignore file --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..8a48fb815d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +package-lock.json +*.png \ No newline at end of file From 61ac9898470acb045aaaf44dbfaf5a744d555d13 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 9 Jul 2018 17:08:02 +0200 Subject: [PATCH 008/221] add code style --- code_style.md | 184 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 code_style.md diff --git a/code_style.md b/code_style.md new file mode 100644 index 0000000000..2cac303e54 --- /dev/null +++ b/code_style.md @@ -0,0 +1,184 @@ +Matrix JavaScript/ECMAScript Style Guide +======================================== + +The intention of this guide is to make Matrix's JavaScript codebase clean, +consistent with other popular JavaScript styles and consistent with the rest of +the Matrix codebase. For reference, the Matrix Python style guide can be found +at https://github.com/matrix-org/synapse/blob/master/docs/code_style.rst + +This document reflects how we would like Matrix JavaScript code to look, with +acknowledgement that a significant amount of code is written to older +standards. + +Write applications in modern ECMAScript and use a transpiler where necessary to +target older platforms. When writing library code, consider carefully whether +to write in ES5 to allow all JavaScript application to use the code directly or +writing in modern ECMAScript and using a transpile step to generate the file +that applications can then include. There are significant benefits in being +able to use modern ECMAScript, although the tooling for doing so can be awkward +for library code, especially with regard to translating source maps and line +number throgh from the original code to the final application. + +General Style +------------- +- 4 spaces to indent, for consistency with Matrix Python. +- 120 columns per line, but try to keep JavaScript code around the 80 column mark. + Inline JSX in particular can be nicer with more columns per line. +- No trailing whitespace at end of lines. +- Don't indent empty lines. +- One newline at the end of the file. +- Unix newlines, never `\r` +- Indent similar to our python code: break up long lines at logical boundaries, + more than one argument on a line is OK +- Use semicolons, for consistency with node. +- UpperCamelCase for class and type names +- lowerCamelCase for functions and variables. +- Single line ternary operators are fine. +- UPPER_CAMEL_CASE for constants +- Single quotes for strings by default, for consistency with most JavaScript styles: + + ```javascript + "bad" // Bad + 'good' // Good + ``` +- Use parentheses or `` ` `` instead of `\` for line continuation where ever possible +- Open braces on the same line (consistent with Node): + + ```javascript + if (x) { + console.log("I am a fish"); // Good + } + + if (x) + { + console.log("I am a fish"); // Bad + } + ``` +- Spaces after `if`, `for`, `else` etc, no space around the condition: + + ```javascript + if (x) { + console.log("I am a fish"); // Good + } + + if(x) { + console.log("I am a fish"); // Bad + } + + if ( x ) { + console.log("I am a fish"); // Bad + } + ``` +- No new line before else, catch, finally, etc: + + ```javascript + if (x) { + console.log("I am a fish"); + } else { + console.log("I am a chimp"); // Good + } + + if (x) { + console.log("I am a fish"); + } + else { + console.log("I am a chimp"); // Bad + } + ``` +- Declare one variable per var statement (consistent with Node). Unless they + are simple and closely related. If you put the next declaration on a new line, + treat yourself to another `var`: + + ```javascript + const key = "foo", + comparator = function(x, y) { + return x - y; + }; // Bad + + const key = "foo"; + const comparator = function(x, y) { + return x - y; + }; // Good + + let x = 0, y = 0; // Fine + + let x = 0; + let y = 0; // Also fine + ``` +- A single line `if` is fine, all others have braces. This prevents errors when adding to the code.: + + ```javascript + if (x) return true; // Fine + + if (x) { + return true; // Also fine + } + + if (x) + return true; // Not fine + ``` +- Terminate all multi-line lists, object literals, imports and ideally function calls with commas (if using a transpiler). Note that trailing function commas require explicit configuration in babel at time of writing: + + ```javascript + var mascots = [ + "Patrick", + "Shirley", + "Colin", + "Susan", + "Sir Arthur David" // Bad + ]; + + var mascots = [ + "Patrick", + "Shirley", + "Colin", + "Susan", + "Sir Arthur David", // Good + ]; + ``` +- Use `null`, `undefined` etc consistently with node: + Boolean variables and functions should always be either true or false. Don't set it to 0 unless it's supposed to be a number. + When something is intentionally missing or removed, set it to null. + If returning a boolean, type coerce: + + ```javascript + function hasThings() { + return !!length; // bad + return new Boolean(length); // REALLY bad + return Boolean(length); // good + } + ``` + Don't set things to undefined. Reserve that value to mean "not yet set to anything." + Boolean objects are verboten. +- Use JSDoc + +ECMAScript +---------- +- Use `const` unless you need a re-assignable variable. This ensures things you don't want to be re-assigned can't be. +- Be careful migrating files to newer syntax. + - Don't mix `require` and `import` in the same file. Either stick to the old style or change them all. + - Likewise, don't mix things like class properties and `MyClass.prototype.MY_CONSTANT = 42;` + - Be careful mixing arrow functions and regular functions, eg. if one function in a promise chain is an + arrow function, they probably all should be. +- Apart from that, newer ES features should be used whenever the author deems them to be appropriate. +- Flow annotations are welcome and encouraged. + +React +----- +- Use React.createClass rather than ES6 classes for components, as the boilerplate is way too heavy on ES6 currently. ES7 might improve it. +- Pull out functions in props to the class, generally as specific event handlers: + + ```jsx + // Bad + {doStuff();}}> // Equally bad + // Better + // Best, if onFooClick would do anything other than directly calling doStuff + ``` + + Not doing so is acceptable in a single case; in function-refs: + + ```jsx + this.component = self}> + ``` +- Think about whether your component really needs state: are you duplicating + information in component state that could be derived from the model? From b76c3a1842fb0b1eff7debf57a3df36774550e51 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 9 Jul 2018 17:43:21 +0200 Subject: [PATCH 009/221] don't use jest and just run test code sequentially since a lot of tests will be interdepent and need to happen in order, it seems easier to not use a test runner enforcing tests to be semi-independent and instead just run the code and have some logging code to see where a problem occurs --- title.test.js => index.js | 57 ++++++++++++----- package.json | 1 - start.js | 126 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+), 17 deletions(-) rename title.test.js => index.js (77%) create mode 100644 start.js diff --git a/title.test.js b/index.js similarity index 77% rename from title.test.js rename to index.js index 0bbb8d56ac..a47a51252f 100644 --- a/title.test.js +++ b/index.js @@ -16,28 +16,53 @@ limitations under the License. const puppeteer = require('puppeteer'); const helpers = require('./helpers'); +const assert = require('assert'); + global.riotserver = 'http://localhost:8080'; global.homeserver = 'http://localhost:8008'; global.browser = null; -jest.setTimeout(10000); +async function run_tests() { + await start_session(); -beforeAll(async () => { + process.stdout.write(`* testing riot loads ... `); + await test_title(); + process.stdout.write('done\n'); + + const username = 'bruno-' + helpers.rnd_int(10000); + const password = 'testtest'; + process.stdout.write(`* signing up as ${username} ... `); + await do_signup(username, password, homeserver); + process.stdout.write('done\n'); + await end_session(); +} + +async function start_session() { global.browser = await puppeteer.launch(); -}); +} -afterAll(() => { +function end_session() { return browser.close(); -}) +} -test('test page loads', async () => { +function on_success() { + console.log('all tests finished successfully'); +} + +function on_failure(err) { + console.log('failure: ', err); + process.exit(-1); +} + + +async function test_title() { const page = await browser.newPage(); await page.goto(helpers.riot_url('/')); const title = await page.title(); - expect(title).toBe("Riot"); -}); + assert.strictEqual(title, "Riot"); +}; -test('test signup', async () => { +async function do_signup(username, password, homeserver) { const page = await helpers.new_page(); const console_logs = helpers.log_console(page); const xhr_logs = helpers.log_xhr_requests(page); @@ -46,12 +71,10 @@ test('test signup', async () => { await page.waitForSelector('#advanced', {visible: true, timeout: 500}); await page.click('#advanced'); - const username = 'bruno-' + helpers.rnd_int(10000); - const password = 'testtest'; //fill out form await page.waitForSelector('.mx_ServerConfig', {visible: true, timeout: 500}); const login_fields = await page.$$('.mx_Login_field'); - expect(login_fields.length).toBe(7); + assert.strictEqual(login_fields.length, 7); const username_field = login_fields[2]; const password_field = login_fields[3]; const password_repeat_field = login_fields[4]; @@ -70,7 +93,7 @@ test('test signup', async () => { await register_button.focus(); //check no errors const error_text = await helpers.try_get_innertext(page, '.mx_Login_error'); - expect(error_text).toBeFalsy(); + assert.strictEqual(!!error_text, false); //submit form await page.screenshot({path: "beforesubmit.png", fullPage: true}); await register_button.click(); @@ -78,7 +101,7 @@ test('test signup', async () => { //confirm dialog saying you cant log back in without e-mail await page.waitForSelector('.mx_QuestionDialog', {visible: true, timeout: 500}); const continue_button = await page.$('.mx_QuestionDialog button.mx_Dialog_primary'); - await helpers.print_elements('continue_button', [continue_button]); + //await helpers.print_elements('continue_button', [continue_button]); await continue_button.click(); //wait for registration to finish so the hash gets set //onhashchange better? @@ -97,5 +120,7 @@ test('test signup', async () => { //await page.waitForSelector('.mx_MatrixChat', {visible: true, timeout: 3000}); const url = page.url(); - expect(url).toBe(helpers.riot_url('/#/home')); -}); + assert.strictEqual(url, helpers.riot_url('/#/home')); +}; + +run_tests().then(on_success, on_failure); \ No newline at end of file diff --git a/package.json b/package.json index fa67de0069..48644df401 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,6 @@ "author": "", "license": "ISC", "dependencies": { - "jest": "^23.2.0", "puppeteer": "^1.5.0" } } diff --git a/start.js b/start.js new file mode 100644 index 0000000000..a47a51252f --- /dev/null +++ b/start.js @@ -0,0 +1,126 @@ +/* +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 puppeteer = require('puppeteer'); +const helpers = require('./helpers'); +const assert = require('assert'); + +global.riotserver = 'http://localhost:8080'; +global.homeserver = 'http://localhost:8008'; +global.browser = null; + +async function run_tests() { + await start_session(); + + process.stdout.write(`* testing riot loads ... `); + await test_title(); + process.stdout.write('done\n'); + + const username = 'bruno-' + helpers.rnd_int(10000); + const password = 'testtest'; + process.stdout.write(`* signing up as ${username} ... `); + await do_signup(username, password, homeserver); + process.stdout.write('done\n'); + await end_session(); +} + +async function start_session() { + global.browser = await puppeteer.launch(); +} + +function end_session() { + return browser.close(); +} + +function on_success() { + console.log('all tests finished successfully'); +} + +function on_failure(err) { + console.log('failure: ', err); + process.exit(-1); +} + + +async function test_title() { + const page = await browser.newPage(); + await page.goto(helpers.riot_url('/')); + const title = await page.title(); + assert.strictEqual(title, "Riot"); +}; + +async function do_signup(username, password, homeserver) { + const page = await helpers.new_page(); + const console_logs = helpers.log_console(page); + const xhr_logs = helpers.log_xhr_requests(page); + await page.goto(helpers.riot_url('/#/register')); + //click 'Custom server' radio button + await page.waitForSelector('#advanced', {visible: true, timeout: 500}); + await page.click('#advanced'); + + //fill out form + await page.waitForSelector('.mx_ServerConfig', {visible: true, timeout: 500}); + const login_fields = await page.$$('.mx_Login_field'); + assert.strictEqual(login_fields.length, 7); + const username_field = login_fields[2]; + const password_field = login_fields[3]; + const password_repeat_field = login_fields[4]; + const hsurl_field = login_fields[5]; + await helpers.replace_input_text(username_field, username); + await helpers.replace_input_text(password_field, password); + await helpers.replace_input_text(password_repeat_field, password); + await helpers.replace_input_text(hsurl_field, homeserver); + //wait over a second because Registration/ServerConfig have a 1000ms + //delay to internally set the homeserver url + //see Registration::render and ServerConfig::props::delayTimeMs + await helpers.delay(1200); + /// focus on the button to make sure error validation + /// has happened before checking the form is good to go + const register_button = await page.$('.mx_Login_submit'); + await register_button.focus(); + //check no errors + const error_text = await helpers.try_get_innertext(page, '.mx_Login_error'); + assert.strictEqual(!!error_text, false); + //submit form + await page.screenshot({path: "beforesubmit.png", fullPage: true}); + await register_button.click(); + + //confirm dialog saying you cant log back in without e-mail + await page.waitForSelector('.mx_QuestionDialog', {visible: true, timeout: 500}); + const continue_button = await page.$('.mx_QuestionDialog button.mx_Dialog_primary'); + //await helpers.print_elements('continue_button', [continue_button]); + await continue_button.click(); + //wait for registration to finish so the hash gets set + //onhashchange better? + await helpers.delay(1000); +/* + await page.screenshot({path: "afterlogin.png", fullPage: true}); + console.log('browser console logs:'); + console.log(console_logs.logs()); + console.log('xhr logs:'); + console.log(xhr_logs.logs()); +*/ + + + //print_elements('page', await page.$('#matrixchat')); +// await navigation_promise; + + //await page.waitForSelector('.mx_MatrixChat', {visible: true, timeout: 3000}); + const url = page.url(); + assert.strictEqual(url, helpers.riot_url('/#/home')); +}; + +run_tests().then(on_success, on_failure); \ No newline at end of file From 5c4f92952f44b1efb83f309d10f0322603fbe926 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 9 Jul 2018 17:51:02 +0200 Subject: [PATCH 010/221] move tests to separate file --- README.md | 2 +- start.js | 71 ++----------------------------------- tests/loads.js | 25 +++++++++++++ index.js => tests/signup.js | 53 ++------------------------- 4 files changed, 31 insertions(+), 120 deletions(-) create mode 100644 tests/loads.js rename index.js => tests/signup.js (72%) diff --git a/README.md b/README.md index a76301d5a2..c56a47fb49 100644 --- a/README.md +++ b/README.md @@ -27,4 +27,4 @@ This repository contains tests for the matrix-react-sdk web app. The tests fire - have a local synapse running at `localhost:8008` ### Run tests - - run tests with `./node_modules/jest/bin/jest.js` + - run tests with `node start.js` diff --git a/start.js b/start.js index a47a51252f..9ba9cc0e55 100644 --- a/start.js +++ b/start.js @@ -17,6 +17,8 @@ limitations under the License. const puppeteer = require('puppeteer'); const helpers = require('./helpers'); const assert = require('assert'); +const do_signup = require('./tests/signup'); +const test_title = require('./tests/loads'); global.riotserver = 'http://localhost:8080'; global.homeserver = 'http://localhost:8008'; @@ -54,73 +56,4 @@ function on_failure(err) { process.exit(-1); } - -async function test_title() { - const page = await browser.newPage(); - await page.goto(helpers.riot_url('/')); - const title = await page.title(); - assert.strictEqual(title, "Riot"); -}; - -async function do_signup(username, password, homeserver) { - const page = await helpers.new_page(); - const console_logs = helpers.log_console(page); - const xhr_logs = helpers.log_xhr_requests(page); - await page.goto(helpers.riot_url('/#/register')); - //click 'Custom server' radio button - await page.waitForSelector('#advanced', {visible: true, timeout: 500}); - await page.click('#advanced'); - - //fill out form - await page.waitForSelector('.mx_ServerConfig', {visible: true, timeout: 500}); - const login_fields = await page.$$('.mx_Login_field'); - assert.strictEqual(login_fields.length, 7); - const username_field = login_fields[2]; - const password_field = login_fields[3]; - const password_repeat_field = login_fields[4]; - const hsurl_field = login_fields[5]; - await helpers.replace_input_text(username_field, username); - await helpers.replace_input_text(password_field, password); - await helpers.replace_input_text(password_repeat_field, password); - await helpers.replace_input_text(hsurl_field, homeserver); - //wait over a second because Registration/ServerConfig have a 1000ms - //delay to internally set the homeserver url - //see Registration::render and ServerConfig::props::delayTimeMs - await helpers.delay(1200); - /// focus on the button to make sure error validation - /// has happened before checking the form is good to go - const register_button = await page.$('.mx_Login_submit'); - await register_button.focus(); - //check no errors - const error_text = await helpers.try_get_innertext(page, '.mx_Login_error'); - assert.strictEqual(!!error_text, false); - //submit form - await page.screenshot({path: "beforesubmit.png", fullPage: true}); - await register_button.click(); - - //confirm dialog saying you cant log back in without e-mail - await page.waitForSelector('.mx_QuestionDialog', {visible: true, timeout: 500}); - const continue_button = await page.$('.mx_QuestionDialog button.mx_Dialog_primary'); - //await helpers.print_elements('continue_button', [continue_button]); - await continue_button.click(); - //wait for registration to finish so the hash gets set - //onhashchange better? - await helpers.delay(1000); -/* - await page.screenshot({path: "afterlogin.png", fullPage: true}); - console.log('browser console logs:'); - console.log(console_logs.logs()); - console.log('xhr logs:'); - console.log(xhr_logs.logs()); -*/ - - - //print_elements('page', await page.$('#matrixchat')); -// await navigation_promise; - - //await page.waitForSelector('.mx_MatrixChat', {visible: true, timeout: 3000}); - const url = page.url(); - assert.strictEqual(url, helpers.riot_url('/#/home')); -}; - run_tests().then(on_success, on_failure); \ No newline at end of file diff --git a/tests/loads.js b/tests/loads.js new file mode 100644 index 0000000000..7136b934db --- /dev/null +++ b/tests/loads.js @@ -0,0 +1,25 @@ +/* +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 helpers = require('../helpers'); +const assert = require('assert'); + +module.exports = async function test_title() { + const page = await browser.newPage(); + await page.goto(helpers.riot_url('/')); + const title = await page.title(); + assert.strictEqual(title, "Riot"); +}; \ No newline at end of file diff --git a/index.js b/tests/signup.js similarity index 72% rename from index.js rename to tests/signup.js index a47a51252f..b3443bd3ec 100644 --- a/index.js +++ b/tests/signup.js @@ -14,55 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -const puppeteer = require('puppeteer'); -const helpers = require('./helpers'); +const helpers = require('../helpers'); const assert = require('assert'); -global.riotserver = 'http://localhost:8080'; -global.homeserver = 'http://localhost:8008'; -global.browser = null; - -async function run_tests() { - await start_session(); - - process.stdout.write(`* testing riot loads ... `); - await test_title(); - process.stdout.write('done\n'); - - const username = 'bruno-' + helpers.rnd_int(10000); - const password = 'testtest'; - process.stdout.write(`* signing up as ${username} ... `); - await do_signup(username, password, homeserver); - process.stdout.write('done\n'); - await end_session(); -} - -async function start_session() { - global.browser = await puppeteer.launch(); -} - -function end_session() { - return browser.close(); -} - -function on_success() { - console.log('all tests finished successfully'); -} - -function on_failure(err) { - console.log('failure: ', err); - process.exit(-1); -} - - -async function test_title() { - const page = await browser.newPage(); - await page.goto(helpers.riot_url('/')); - const title = await page.title(); - assert.strictEqual(title, "Riot"); -}; - -async function do_signup(username, password, homeserver) { +module.exports = async function do_signup(username, password, homeserver) { const page = await helpers.new_page(); const console_logs = helpers.log_console(page); const xhr_logs = helpers.log_xhr_requests(page); @@ -121,6 +76,4 @@ async function do_signup(username, password, homeserver) { //await page.waitForSelector('.mx_MatrixChat', {visible: true, timeout: 3000}); const url = page.url(); assert.strictEqual(url, helpers.riot_url('/#/home')); -}; - -run_tests().then(on_success, on_failure); \ No newline at end of file +} \ No newline at end of file From 400327a0f16bcfff10aa04db30765b7f23ff2d76 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 9 Jul 2018 18:21:05 +0200 Subject: [PATCH 011/221] add test for joining preexisting room --- README.md | 1 + helpers.js | 6 ++++++ start.js | 12 +++++++++++- tests/join_room.js | 35 +++++++++++++++++++++++++++++++++++ tests/signup.js | 3 +-- 5 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 tests/join_room.js diff --git a/README.md b/README.md index c56a47fb49..c473db5555 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ This repository contains tests for the matrix-react-sdk web app. The tests fire ## Current tests - test riot loads (check title) - signup with custom homeserver + - join preexisting room ## Roadmap - get rid of jest, as a test framework won't be helpful to have a continuous flow going from one use case to another (think: do login, create a room, invite a user, ...). a test framework usually assumes the tests are semi-indepedent. diff --git a/helpers.js b/helpers.js index bd0035f13d..bb4f0b20ca 100644 --- a/helpers.js +++ b/helpers.js @@ -82,6 +82,11 @@ async function replace_input_text(input, text) { await input.type(text); } +async function wait_and_query_selector(page, selector, timeout = 500) { + await page.waitForSelector(selector, {visible: true, timeout}); + return await page.$(selector); +} + // other helpers function rnd_int(max) { @@ -104,6 +109,7 @@ module.exports = { get_outer_html, print_elements, replace_input_text, + wait_and_query_selector, rnd_int, riot_url, delay, diff --git a/start.js b/start.js index 9ba9cc0e55..002019438a 100644 --- a/start.js +++ b/start.js @@ -19,6 +19,7 @@ const helpers = require('./helpers'); const assert = require('assert'); const do_signup = require('./tests/signup'); const test_title = require('./tests/loads'); +const join_room = require('./tests/join_room'); global.riotserver = 'http://localhost:8080'; global.homeserver = 'http://localhost:8008'; @@ -31,11 +32,20 @@ async function run_tests() { await test_title(); process.stdout.write('done\n'); + + + const page = await helpers.new_page(); const username = 'bruno-' + helpers.rnd_int(10000); const password = 'testtest'; process.stdout.write(`* signing up as ${username} ... `); - await do_signup(username, password, homeserver); + await do_signup(page, username, password, homeserver); process.stdout.write('done\n'); + + const room = 'test'; + process.stdout.write(`* joining room ${room} ... `); + await join_room(page, room); + process.stdout.write('done\n'); + await end_session(); } diff --git a/tests/join_room.js b/tests/join_room.js new file mode 100644 index 0000000000..7975fad648 --- /dev/null +++ b/tests/join_room.js @@ -0,0 +1,35 @@ +/* +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 helpers = require('../helpers'); +const assert = require('assert'); + +module.exports = async function join_room(page, room_name) { + //TODO: brittle selector + const directory_button = await helpers.wait_and_query_selector(page, '.mx_RoleButton[aria-label="Room directory"]'); + await directory_button.click(); + + const room_input = await helpers.wait_and_query_selector(page, '.mx_DirectorySearchBox_input'); + await helpers.replace_input_text(room_input, room_name); + + const first_room_label = await helpers.wait_and_query_selector(page, '.mx_RoomDirectory_table .mx_RoomDirectory_name:first-child'); + await first_room_label.click(); + + const join_link = await helpers.wait_and_query_selector(page, '.mx_RoomPreviewBar_join_text a'); + await join_link.click(); + + await page.waitForSelector('.mx_MessageComposer'); +} \ No newline at end of file diff --git a/tests/signup.js b/tests/signup.js index b3443bd3ec..155c5a1e0a 100644 --- a/tests/signup.js +++ b/tests/signup.js @@ -17,8 +17,7 @@ limitations under the License. const helpers = require('../helpers'); const assert = require('assert'); -module.exports = async function do_signup(username, password, homeserver) { - const page = await helpers.new_page(); +module.exports = async function do_signup(page, username, password, homeserver) { const console_logs = helpers.log_console(page); const xhr_logs = helpers.log_xhr_requests(page); await page.goto(helpers.riot_url('/#/register')); From 838563f0a6d51227be47e1b35e6227d9eb537830 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 9 Jul 2018 18:21:43 +0200 Subject: [PATCH 012/221] add note to README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c473db5555..163bffbce7 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ This repository contains tests for the matrix-react-sdk web app. The tests fire ### Setup - - install dependencies with `npm install` + - install dependencies with `npm install` (will download copy of chrome) - have riot-web running on `localhost:8080` - have a local synapse running at `localhost:8008` From d4682eb5e68c9ad2d325cf2bc41c16395da4c56e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 9 Jul 2018 18:35:47 +0200 Subject: [PATCH 013/221] apply code style --- helpers.js | 42 +++++++++++++-------------- start.js | 37 ++++++++++-------------- tests/{join_room.js => join.js} | 18 ++++++------ tests/loads.js | 25 ----------------- tests/signup.js | 50 ++++++++++++++++----------------- 5 files changed, 70 insertions(+), 102 deletions(-) rename tests/{join_room.js => join.js} (51%) delete mode 100644 tests/loads.js diff --git a/helpers.js b/helpers.js index bb4f0b20ca..3e2467e622 100644 --- a/helpers.js +++ b/helpers.js @@ -16,7 +16,7 @@ limitations under the License. // puppeteer helpers -async function try_get_innertext(page, selector) { +async function tryGetInnertext(page, selector) { const field = await page.$(selector); if (field != null) { const text_handle = await field.getProperty('innerText'); @@ -25,7 +25,7 @@ async function try_get_innertext(page, selector) { return null; } -async function new_page() { +async function newPage() { const page = await browser.newPage(); await page.setViewport({ width: 1280, @@ -34,7 +34,7 @@ async function new_page() { return page; } -function log_console(page) { +function logConsole(page) { let buffer = ""; page.on('console', msg => { buffer += msg.text() + '\n'; @@ -46,7 +46,7 @@ function log_console(page) { } } -function log_xhr_requests(page) { +function logXHRRequests(page) { let buffer = ""; page.on('request', req => { const type = req.resourceType(); @@ -64,16 +64,16 @@ function log_xhr_requests(page) { } } -async function get_outer_html(element_handle) { +async function getOuterHTML(element_handle) { const html_handle = await element_handle.getProperty('outerHTML'); return await html_handle.jsonValue(); } -async function print_elements(label, elements) { - console.log(label, await Promise.all(elements.map(get_outer_html))); +async function printElements(label, elements) { + console.log(label, await Promise.all(elements.map(getOuterHTML))); } -async function replace_input_text(input, text) { +async function replaceInputText(input, text) { // click 3 times to select all text await input.click({clickCount: 3}); // then remove it with backspace @@ -82,18 +82,18 @@ async function replace_input_text(input, text) { await input.type(text); } -async function wait_and_query_selector(page, selector, timeout = 500) { +async function waitAndQuerySelector(page, selector, timeout = 500) { await page.waitForSelector(selector, {visible: true, timeout}); return await page.$(selector); } // other helpers -function rnd_int(max) { +function randomInt(max) { return Math.ceil(Math.random()*max); } -function riot_url(path) { +function riotUrl(path) { return riotserver + path; } @@ -102,15 +102,15 @@ function delay(ms) { } module.exports = { - try_get_innertext, - new_page, - log_console, - log_xhr_requests, - get_outer_html, - print_elements, - replace_input_text, - wait_and_query_selector, - rnd_int, - riot_url, + tryGetInnertext, + newPage, + logConsole, + logXHRRequests, + getOuterHTML, + printElements, + replaceInputText, + waitAndQuerySelector, + randomInt, + riotUrl, delay, } \ No newline at end of file diff --git a/start.js b/start.js index 002019438a..82b567c566 100644 --- a/start.js +++ b/start.js @@ -17,53 +17,46 @@ limitations under the License. const puppeteer = require('puppeteer'); const helpers = require('./helpers'); const assert = require('assert'); -const do_signup = require('./tests/signup'); -const test_title = require('./tests/loads'); -const join_room = require('./tests/join_room'); + +const signup = require('./tests/signup'); +const join = require('./tests/join'); global.riotserver = 'http://localhost:8080'; global.homeserver = 'http://localhost:8008'; global.browser = null; -async function run_tests() { - await start_session(); - - process.stdout.write(`* testing riot loads ... `); - await test_title(); - process.stdout.write('done\n'); - - - - const page = await helpers.new_page(); - const username = 'bruno-' + helpers.rnd_int(10000); +async function runTests() { + await startSession(); + const page = await helpers.newPage(); + const username = 'bruno-' + helpers.randomInt(10000); const password = 'testtest'; process.stdout.write(`* signing up as ${username} ... `); - await do_signup(page, username, password, homeserver); + await signup(page, username, password, homeserver); process.stdout.write('done\n'); const room = 'test'; process.stdout.write(`* joining room ${room} ... `); - await join_room(page, room); + await join(page, room); process.stdout.write('done\n'); - await end_session(); + await endSession(); } -async function start_session() { +async function startSession() { global.browser = await puppeteer.launch(); } -function end_session() { +function endSession() { return browser.close(); } -function on_success() { +function onSuccess() { console.log('all tests finished successfully'); } -function on_failure(err) { +function onFailure(err) { console.log('failure: ', err); process.exit(-1); } -run_tests().then(on_success, on_failure); \ No newline at end of file +runTests().then(onSuccess, onFailure); \ No newline at end of file diff --git a/tests/join_room.js b/tests/join.js similarity index 51% rename from tests/join_room.js rename to tests/join.js index 7975fad648..ea16a93936 100644 --- a/tests/join_room.js +++ b/tests/join.js @@ -17,19 +17,19 @@ limitations under the License. const helpers = require('../helpers'); const assert = require('assert'); -module.exports = async function join_room(page, room_name) { +module.exports = async function join(page, roomName) { //TODO: brittle selector - const directory_button = await helpers.wait_and_query_selector(page, '.mx_RoleButton[aria-label="Room directory"]'); - await directory_button.click(); + const directoryButton = await helpers.waitAndQuerySelector(page, '.mx_RoleButton[aria-label="Room directory"]'); + await directoryButton.click(); - const room_input = await helpers.wait_and_query_selector(page, '.mx_DirectorySearchBox_input'); - await helpers.replace_input_text(room_input, room_name); + const roomInput = await helpers.waitAndQuerySelector(page, '.mx_DirectorySearchBox_input'); + await helpers.replaceInputText(roomInput, roomName); - const first_room_label = await helpers.wait_and_query_selector(page, '.mx_RoomDirectory_table .mx_RoomDirectory_name:first-child'); - await first_room_label.click(); + const firstRoomLabel = await helpers.waitAndQuerySelector(page, '.mx_RoomDirectory_table .mx_RoomDirectory_name:first-child'); + await firstRoomLabel.click(); - const join_link = await helpers.wait_and_query_selector(page, '.mx_RoomPreviewBar_join_text a'); - await join_link.click(); + const joinLink = await helpers.waitAndQuerySelector(page, '.mx_RoomPreviewBar_join_text a'); + await joinLink.click(); await page.waitForSelector('.mx_MessageComposer'); } \ No newline at end of file diff --git a/tests/loads.js b/tests/loads.js deleted file mode 100644 index 7136b934db..0000000000 --- a/tests/loads.js +++ /dev/null @@ -1,25 +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 helpers = require('../helpers'); -const assert = require('assert'); - -module.exports = async function test_title() { - const page = await browser.newPage(); - await page.goto(helpers.riot_url('/')); - const title = await page.title(); - assert.strictEqual(title, "Riot"); -}; \ No newline at end of file diff --git a/tests/signup.js b/tests/signup.js index 155c5a1e0a..43fe6a87d4 100644 --- a/tests/signup.js +++ b/tests/signup.js @@ -17,62 +17,62 @@ limitations under the License. const helpers = require('../helpers'); const assert = require('assert'); -module.exports = async function do_signup(page, username, password, homeserver) { - const console_logs = helpers.log_console(page); - const xhr_logs = helpers.log_xhr_requests(page); - await page.goto(helpers.riot_url('/#/register')); +module.exports = async function signup(page, username, password, homeserver) { + const consoleLogs = helpers.logConsole(page); + const xhrLogs = helpers.logXHRRequests(page); + await page.goto(helpers.riotUrl('/#/register')); //click 'Custom server' radio button await page.waitForSelector('#advanced', {visible: true, timeout: 500}); await page.click('#advanced'); //fill out form await page.waitForSelector('.mx_ServerConfig', {visible: true, timeout: 500}); - const login_fields = await page.$$('.mx_Login_field'); - assert.strictEqual(login_fields.length, 7); - const username_field = login_fields[2]; - const password_field = login_fields[3]; - const password_repeat_field = login_fields[4]; - const hsurl_field = login_fields[5]; - await helpers.replace_input_text(username_field, username); - await helpers.replace_input_text(password_field, password); - await helpers.replace_input_text(password_repeat_field, password); - await helpers.replace_input_text(hsurl_field, homeserver); + const loginFields = await page.$$('.mx_Login_field'); + assert.strictEqual(loginFields.length, 7); + const usernameField = loginFields[2]; + const passwordField = loginFields[3]; + const passwordRepeatField = loginFields[4]; + const hsurlField = loginFields[5]; + await helpers.replaceInputText(usernameField, username); + await helpers.replaceInputText(passwordField, password); + await helpers.replaceInputText(passwordRepeatField, password); + await helpers.replaceInputText(hsurlField, homeserver); //wait over a second because Registration/ServerConfig have a 1000ms //delay to internally set the homeserver url //see Registration::render and ServerConfig::props::delayTimeMs await helpers.delay(1200); /// focus on the button to make sure error validation /// has happened before checking the form is good to go - const register_button = await page.$('.mx_Login_submit'); - await register_button.focus(); + const registerButton = await page.$('.mx_Login_submit'); + await registerButton.focus(); //check no errors - const error_text = await helpers.try_get_innertext(page, '.mx_Login_error'); + const error_text = await helpers.tryGetInnertext(page, '.mx_Login_error'); assert.strictEqual(!!error_text, false); //submit form await page.screenshot({path: "beforesubmit.png", fullPage: true}); - await register_button.click(); + await registerButton.click(); //confirm dialog saying you cant log back in without e-mail await page.waitForSelector('.mx_QuestionDialog', {visible: true, timeout: 500}); - const continue_button = await page.$('.mx_QuestionDialog button.mx_Dialog_primary'); - //await helpers.print_elements('continue_button', [continue_button]); - await continue_button.click(); + const continueButton = await page.$('.mx_QuestionDialog button.mx_Dialog_primary'); + //await helpers.printElements('continueButton', [continueButton]); + await continueButton.click(); //wait for registration to finish so the hash gets set //onhashchange better? await helpers.delay(1000); /* await page.screenshot({path: "afterlogin.png", fullPage: true}); console.log('browser console logs:'); - console.log(console_logs.logs()); + console.log(consoleLogs.logs()); console.log('xhr logs:'); - console.log(xhr_logs.logs()); + console.log(xhrLogs.logs()); */ - //print_elements('page', await page.$('#matrixchat')); + //printElements('page', await page.$('#matrixchat')); // await navigation_promise; //await page.waitForSelector('.mx_MatrixChat', {visible: true, timeout: 3000}); const url = page.url(); - assert.strictEqual(url, helpers.riot_url('/#/home')); + assert.strictEqual(url, helpers.riotUrl('/#/home')); } \ No newline at end of file From 9c5e43a693778c33e1e673bac2cfe39d06f28357 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 9 Jul 2018 18:40:25 +0200 Subject: [PATCH 014/221] cleanup --- start.js | 13 +++---------- tests/signup.js | 8 +++----- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/start.js b/start.js index 82b567c566..0cc80f2fde 100644 --- a/start.js +++ b/start.js @@ -26,8 +26,9 @@ global.homeserver = 'http://localhost:8008'; global.browser = null; async function runTests() { - await startSession(); + global.browser = await puppeteer.launch(); const page = await helpers.newPage(); + const username = 'bruno-' + helpers.randomInt(10000); const password = 'testtest'; process.stdout.write(`* signing up as ${username} ... `); @@ -39,15 +40,7 @@ async function runTests() { await join(page, room); process.stdout.write('done\n'); - await endSession(); -} - -async function startSession() { - global.browser = await puppeteer.launch(); -} - -function endSession() { - return browser.close(); + await browser.close(); } function onSuccess() { diff --git a/tests/signup.js b/tests/signup.js index 43fe6a87d4..2a0d6dc9b4 100644 --- a/tests/signup.js +++ b/tests/signup.js @@ -22,8 +22,8 @@ module.exports = async function signup(page, username, password, homeserver) { const xhrLogs = helpers.logXHRRequests(page); await page.goto(helpers.riotUrl('/#/register')); //click 'Custom server' radio button - await page.waitForSelector('#advanced', {visible: true, timeout: 500}); - await page.click('#advanced'); + const advancedRadioButton = await helpers.waitAndQuerySelector(page, '#advanced'); + await advancedRadioButton.click(); //fill out form await page.waitForSelector('.mx_ServerConfig', {visible: true, timeout: 500}); @@ -53,9 +53,7 @@ module.exports = async function signup(page, username, password, homeserver) { await registerButton.click(); //confirm dialog saying you cant log back in without e-mail - await page.waitForSelector('.mx_QuestionDialog', {visible: true, timeout: 500}); - const continueButton = await page.$('.mx_QuestionDialog button.mx_Dialog_primary'); - //await helpers.printElements('continueButton', [continueButton]); + const continueButton = await helpers.waitAndQuerySelector(page, '.mx_QuestionDialog button.mx_Dialog_primary'); await continueButton.click(); //wait for registration to finish so the hash gets set //onhashchange better? From 9a2d32e64208af6379b3e2334598b649c9274d6e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 10 Jul 2018 19:26:47 +0200 Subject: [PATCH 015/221] accept terms when joining --- helpers.js | 18 ++++++++++++++++++ start.js | 2 +- tests/join.js | 18 +++++++++++++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/helpers.js b/helpers.js index 3e2467e622..d57595f377 100644 --- a/helpers.js +++ b/helpers.js @@ -87,6 +87,23 @@ async function waitAndQuerySelector(page, selector, timeout = 500) { return await page.$(selector); } +function waitForNewPage(timeout = 500) { + return new Promise((resolve, reject) => { + const timeoutHandle = setTimeout(() => { + browser.removeEventListener('targetcreated', callback); + reject(new Error(`timeout of ${timeout}ms for waitForNewPage elapsed`)); + }, timeout); + + const callback = async (target) => { + clearTimeout(timeoutHandle); + const page = await target.page(); + resolve(page); + }; + + browser.once('targetcreated', callback); + }); +} + // other helpers function randomInt(max) { @@ -110,6 +127,7 @@ module.exports = { printElements, replaceInputText, waitAndQuerySelector, + waitForNewPage, randomInt, riotUrl, delay, diff --git a/start.js b/start.js index 0cc80f2fde..8a3ceb354b 100644 --- a/start.js +++ b/start.js @@ -37,7 +37,7 @@ async function runTests() { const room = 'test'; process.stdout.write(`* joining room ${room} ... `); - await join(page, room); + await join(page, room, true); process.stdout.write('done\n'); await browser.close(); diff --git a/tests/join.js b/tests/join.js index ea16a93936..79990af3a2 100644 --- a/tests/join.js +++ b/tests/join.js @@ -17,7 +17,7 @@ limitations under the License. const helpers = require('../helpers'); const assert = require('assert'); -module.exports = async function join(page, roomName) { +module.exports = async function join(page, roomName, acceptTerms = false) { //TODO: brittle selector const directoryButton = await helpers.waitAndQuerySelector(page, '.mx_RoleButton[aria-label="Room directory"]'); await directoryButton.click(); @@ -31,5 +31,21 @@ module.exports = async function join(page, roomName) { const joinLink = await helpers.waitAndQuerySelector(page, '.mx_RoomPreviewBar_join_text a'); await joinLink.click(); + if (acceptTerms) { + const reviewTermsButton = await helpers.waitAndQuerySelector(page, '.mx_QuestionDialog button.mx_Dialog_primary'); + const termsPagePromise = helpers.waitForNewPage(); + await reviewTermsButton.click(); + const termsPage = await termsPagePromise; + const acceptButton = await termsPage.$('input[type=submit]'); + await acceptButton.click(); + await helpers.delay(500); //TODO yuck, timers + //try to join again after accepting the terms + + //TODO need to do this because joinLink is detached after switching target + const joinLink2 = await helpers.waitAndQuerySelector(page, '.mx_RoomPreviewBar_join_text a'); + await joinLink2.click(); + } + + await page.waitForSelector('.mx_MessageComposer'); } \ No newline at end of file From 83eebfdecc3e8e5b86eb59f765256b7b8b06d2ce Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 16 Jul 2018 18:10:33 +0200 Subject: [PATCH 016/221] script to install local synapse --- synapse/.gitignore | 2 + .../config-templates/consent/homeserver.yaml | 697 ++++++++++++++++++ .../consent/res/templates/privacy/en/1.0.html | 23 + .../res/templates/privacy/en/success.html | 9 + synapse/install.sh | 33 + 5 files changed, 764 insertions(+) create mode 100644 synapse/.gitignore create mode 100644 synapse/config-templates/consent/homeserver.yaml create mode 100644 synapse/config-templates/consent/res/templates/privacy/en/1.0.html create mode 100644 synapse/config-templates/consent/res/templates/privacy/en/success.html create mode 100644 synapse/install.sh diff --git a/synapse/.gitignore b/synapse/.gitignore new file mode 100644 index 0000000000..aed68e9f30 --- /dev/null +++ b/synapse/.gitignore @@ -0,0 +1,2 @@ +installations +synapse.zip \ No newline at end of file diff --git a/synapse/config-templates/consent/homeserver.yaml b/synapse/config-templates/consent/homeserver.yaml new file mode 100644 index 0000000000..6ba15c9af0 --- /dev/null +++ b/synapse/config-templates/consent/homeserver.yaml @@ -0,0 +1,697 @@ +# vim:ft=yaml +# PEM encoded X509 certificate for TLS. +# You can replace the self-signed certificate that synapse +# autogenerates on launch with your own SSL certificate + key pair +# if you like. Any required intermediary certificates can be +# appended after the primary certificate in hierarchical order. +tls_certificate_path: "{{SYNAPSE_ROOT}}localhost.tls.crt" + +# PEM encoded private key for TLS +tls_private_key_path: "{{SYNAPSE_ROOT}}localhost.tls.key" + +# PEM dh parameters for ephemeral keys +tls_dh_params_path: "{{SYNAPSE_ROOT}}localhost.tls.dh" + +# Don't bind to the https port +no_tls: True + +# List of allowed TLS fingerprints for this server to publish along +# with the signing keys for this server. Other matrix servers that +# make HTTPS requests to this server will check that the TLS +# certificates returned by this server match one of the fingerprints. +# +# Synapse automatically adds the fingerprint of its own certificate +# to the list. So if federation traffic is handled directly by synapse +# then no modification to the list is required. +# +# If synapse is run behind a load balancer that handles the TLS then it +# will be necessary to add the fingerprints of the certificates used by +# the loadbalancers to this list if they are different to the one +# synapse is using. +# +# Homeservers are permitted to cache the list of TLS fingerprints +# returned in the key responses up to the "valid_until_ts" returned in +# key. It may be necessary to publish the fingerprints of a new +# certificate and wait until the "valid_until_ts" of the previous key +# responses have passed before deploying it. +# +# You can calculate a fingerprint from a given TLS listener via: +# openssl s_client -connect $host:$port < /dev/null 2> /dev/null | +# openssl x509 -outform DER | openssl sha256 -binary | base64 | tr -d '=' +# or by checking matrix.org/federationtester/api/report?server_name=$host +# +tls_fingerprints: [] +# tls_fingerprints: [{"sha256": ""}] + + +## Server ## + +# The domain name of the server, with optional explicit port. +# This is used by remote servers to connect to this server, +# e.g. matrix.org, localhost:8080, etc. +# This is also the last part of your UserID. +server_name: "localhost" + +# When running as a daemon, the file to store the pid in +pid_file: {{SYNAPSE_ROOT}}homeserver.pid + +# CPU affinity mask. Setting this restricts the CPUs on which the +# process will be scheduled. It is represented as a bitmask, with the +# lowest order bit corresponding to the first logical CPU and the +# highest order bit corresponding to the last logical CPU. Not all CPUs +# may exist on a given system but a mask may specify more CPUs than are +# present. +# +# For example: +# 0x00000001 is processor #0, +# 0x00000003 is processors #0 and #1, +# 0xFFFFFFFF is all processors (#0 through #31). +# +# Pinning a Python process to a single CPU is desirable, because Python +# is inherently single-threaded due to the GIL, and can suffer a +# 30-40% slowdown due to cache blow-out and thread context switching +# if the scheduler happens to schedule the underlying threads across +# different cores. See +# https://www.mirantis.com/blog/improve-performance-python-programs-restricting-single-cpu/. +# +# cpu_affinity: 0xFFFFFFFF + +# Whether to serve a web client from the HTTP/HTTPS root resource. +web_client: True + +# The root directory to server for the above web client. +# If left undefined, synapse will serve the matrix-angular-sdk web client. +# Make sure matrix-angular-sdk is installed with pip if web_client is True +# and web_client_location is undefined +# web_client_location: "/path/to/web/root" + +# The public-facing base URL for the client API (not including _matrix/...) +public_baseurl: http://localhost:8008/ + +# Set the soft limit on the number of file descriptors synapse can use +# Zero is used to indicate synapse should set the soft limit to the +# hard limit. +soft_file_limit: 0 + +# The GC threshold parameters to pass to `gc.set_threshold`, if defined +# gc_thresholds: [700, 10, 10] + +# Set the limit on the returned events in the timeline in the get +# and sync operations. The default value is -1, means no upper limit. +# filter_timeline_limit: 5000 + +# Whether room invites to users on this server should be blocked +# (except those sent by local server admins). The default is False. +# block_non_admin_invites: True + +# Restrict federation to the following whitelist of domains. +# N.B. we recommend also firewalling your federation listener to limit +# inbound federation traffic as early as possible, rather than relying +# purely on this application-layer restriction. If not specified, the +# default is to whitelist everything. +# +# federation_domain_whitelist: +# - lon.example.com +# - nyc.example.com +# - syd.example.com + +# List of ports that Synapse should listen on, their purpose and their +# configuration. +listeners: + # Main HTTPS listener + # For when matrix traffic is sent directly to synapse. + - + # The port to listen for HTTPS requests on. + port: 8448 + + # Local addresses to listen on. + # On Linux and Mac OS, `::` will listen on all IPv4 and IPv6 + # addresses by default. For most other OSes, this will only listen + # on IPv6. + bind_addresses: + - '::' + - '0.0.0.0' + + # This is a 'http' listener, allows us to specify 'resources'. + type: http + + tls: true + + # Use the X-Forwarded-For (XFF) header as the client IP and not the + # actual client IP. + x_forwarded: false + + # List of HTTP resources to serve on this listener. + resources: + - + # List of resources to host on this listener. + names: + - client # The client-server APIs, both v1 and v2 + - webclient # The bundled webclient. + + # Should synapse compress HTTP responses to clients that support it? + # This should be disabled if running synapse behind a load balancer + # that can do automatic compression. + compress: true + + - names: [federation] # Federation APIs + compress: false + + # optional list of additional endpoints which can be loaded via + # dynamic modules + # additional_resources: + # "/_matrix/my/custom/endpoint": + # module: my_module.CustomRequestHandler + # config: {} + + # Unsecure HTTP listener, + # For when matrix traffic passes through loadbalancer that unwraps TLS. + - port: 8008 + tls: false + bind_addresses: ['::', '0.0.0.0'] + type: http + + x_forwarded: false + + resources: + - names: [client, webclient, consent] + compress: true + - names: [federation] + compress: false + + # Turn on the twisted ssh manhole service on localhost on the given + # port. + # - port: 9000 + # bind_addresses: ['::1', '127.0.0.1'] + # type: manhole + + +# Database configuration +database: + # The database engine name + name: "sqlite3" + # Arguments to pass to the engine + args: + # Path to the database + database: "{{SYNAPSE_ROOT}}homeserver.db" + +# Number of events to cache in memory. +event_cache_size: "10K" + + + +# A yaml python logging config file +log_config: "{{SYNAPSE_ROOT}}localhost.log.config" + + +## Ratelimiting ## + +# Number of messages a client can send per second +rc_messages_per_second: 0.2 + +# Number of message a client can send before being throttled +rc_message_burst_count: 10.0 + +# The federation window size in milliseconds +federation_rc_window_size: 1000 + +# The number of federation requests from a single server in a window +# before the server will delay processing the request. +federation_rc_sleep_limit: 10 + +# The duration in milliseconds to delay processing events from +# remote servers by if they go over the sleep limit. +federation_rc_sleep_delay: 500 + +# The maximum number of concurrent federation requests allowed +# from a single server +federation_rc_reject_limit: 50 + +# The number of federation requests to concurrently process from a +# single server +federation_rc_concurrent: 3 + + + +# Directory where uploaded images and attachments are stored. +media_store_path: "{{SYNAPSE_ROOT}}media_store" + +# Media storage providers allow media to be stored in different +# locations. +# media_storage_providers: +# - module: file_system +# # Whether to write new local files. +# store_local: false +# # Whether to write new remote media +# store_remote: false +# # Whether to block upload requests waiting for write to this +# # provider to complete +# store_synchronous: false +# config: +# directory: /mnt/some/other/directory + +# Directory where in-progress uploads are stored. +uploads_path: "{{SYNAPSE_ROOT}}uploads" + +# The largest allowed upload size in bytes +max_upload_size: "10M" + +# Maximum number of pixels that will be thumbnailed +max_image_pixels: "32M" + +# Whether to generate new thumbnails on the fly to precisely match +# the resolution requested by the client. If true then whenever +# a new resolution is requested by the client the server will +# generate a new thumbnail. If false the server will pick a thumbnail +# from a precalculated list. +dynamic_thumbnails: false + +# List of thumbnail to precalculate when an image is uploaded. +thumbnail_sizes: +- width: 32 + height: 32 + method: crop +- width: 96 + height: 96 + method: crop +- width: 320 + height: 240 + method: scale +- width: 640 + height: 480 + method: scale +- width: 800 + height: 600 + method: scale + +# Is the preview URL API enabled? If enabled, you *must* specify +# an explicit url_preview_ip_range_blacklist of IPs that the spider is +# denied from accessing. +url_preview_enabled: False + +# List of IP address CIDR ranges that the URL preview spider is denied +# from accessing. There are no defaults: you must explicitly +# specify a list for URL previewing to work. You should specify any +# internal services in your network that you do not want synapse to try +# to connect to, otherwise anyone in any Matrix room could cause your +# synapse to issue arbitrary GET requests to your internal services, +# causing serious security issues. +# +# url_preview_ip_range_blacklist: +# - '127.0.0.0/8' +# - '10.0.0.0/8' +# - '172.16.0.0/12' +# - '192.168.0.0/16' +# - '100.64.0.0/10' +# - '169.254.0.0/16' +# - '::1/128' +# - 'fe80::/64' +# - 'fc00::/7' +# +# List of IP address CIDR ranges that the URL preview spider is allowed +# to access even if they are specified in url_preview_ip_range_blacklist. +# This is useful for specifying exceptions to wide-ranging blacklisted +# target IP ranges - e.g. for enabling URL previews for a specific private +# website only visible in your network. +# +# url_preview_ip_range_whitelist: +# - '192.168.1.1' + +# Optional list of URL matches that the URL preview spider is +# denied from accessing. You should use url_preview_ip_range_blacklist +# in preference to this, otherwise someone could define a public DNS +# entry that points to a private IP address and circumvent the blacklist. +# This is more useful if you know there is an entire shape of URL that +# you know that will never want synapse to try to spider. +# +# Each list entry is a dictionary of url component attributes as returned +# by urlparse.urlsplit as applied to the absolute form of the URL. See +# https://docs.python.org/2/library/urlparse.html#urlparse.urlsplit +# The values of the dictionary are treated as an filename match pattern +# applied to that component of URLs, unless they start with a ^ in which +# case they are treated as a regular expression match. If all the +# specified component matches for a given list item succeed, the URL is +# blacklisted. +# +# url_preview_url_blacklist: +# # blacklist any URL with a username in its URI +# - username: '*' +# +# # blacklist all *.google.com URLs +# - netloc: 'google.com' +# - netloc: '*.google.com' +# +# # blacklist all plain HTTP URLs +# - scheme: 'http' +# +# # blacklist http(s)://www.acme.com/foo +# - netloc: 'www.acme.com' +# path: '/foo' +# +# # blacklist any URL with a literal IPv4 address +# - netloc: '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' + +# The largest allowed URL preview spidering size in bytes +max_spider_size: "10M" + + + + +## Captcha ## +# See docs/CAPTCHA_SETUP for full details of configuring this. + +# This Home Server's ReCAPTCHA public key. +recaptcha_public_key: "YOUR_PUBLIC_KEY" + +# This Home Server's ReCAPTCHA private key. +recaptcha_private_key: "YOUR_PRIVATE_KEY" + +# Enables ReCaptcha checks when registering, preventing signup +# unless a captcha is answered. Requires a valid ReCaptcha +# public/private key. +enable_registration_captcha: False + +# A secret key used to bypass the captcha test entirely. +#captcha_bypass_secret: "YOUR_SECRET_HERE" + +# The API endpoint to use for verifying m.login.recaptcha responses. +recaptcha_siteverify_api: "https://www.google.com/recaptcha/api/siteverify" + + +## Turn ## + +# The public URIs of the TURN server to give to clients +turn_uris: [] + +# The shared secret used to compute passwords for the TURN server +turn_shared_secret: "YOUR_SHARED_SECRET" + +# The Username and password if the TURN server needs them and +# does not use a token +#turn_username: "TURNSERVER_USERNAME" +#turn_password: "TURNSERVER_PASSWORD" + +# How long generated TURN credentials last +turn_user_lifetime: "1h" + +# Whether guests should be allowed to use the TURN server. +# This defaults to True, otherwise VoIP will be unreliable for guests. +# However, it does introduce a slight security risk as it allows users to +# connect to arbitrary endpoints without having first signed up for a +# valid account (e.g. by passing a CAPTCHA). +turn_allow_guests: True + + +## Registration ## + +# Enable registration for new users. +enable_registration: True + +# The user must provide all of the below types of 3PID when registering. +# +# registrations_require_3pid: +# - email +# - msisdn + +# Mandate that users are only allowed to associate certain formats of +# 3PIDs with accounts on this server. +# +# allowed_local_3pids: +# - medium: email +# pattern: ".*@matrix\.org" +# - medium: email +# pattern: ".*@vector\.im" +# - medium: msisdn +# pattern: "\+44" + +# If set, allows registration by anyone who also has the shared +# secret, even if registration is otherwise disabled. +registration_shared_secret: "{{REGISTRATION_SHARED_SECRET}}" + +# Set the number of bcrypt rounds used to generate password hash. +# Larger numbers increase the work factor needed to generate the hash. +# The default number is 12 (which equates to 2^12 rounds). +# N.B. that increasing this will exponentially increase the time required +# to register or login - e.g. 24 => 2^24 rounds which will take >20 mins. +bcrypt_rounds: 12 + +# Allows users to register as guests without a password/email/etc, and +# participate in rooms hosted on this server which have been made +# accessible to anonymous users. +allow_guest_access: False + +# The list of identity servers trusted to verify third party +# identifiers by this server. +trusted_third_party_id_servers: + - matrix.org + - vector.im + - riot.im + +# Users who register on this homeserver will automatically be joined +# to these roomsS +#auto_join_rooms: +# - "#example:example.com" + + +## Metrics ### + +# Enable collection and rendering of performance metrics +enable_metrics: False +report_stats: False + + +## API Configuration ## + +# A list of event types that will be included in the room_invite_state +room_invite_state_types: + - "m.room.join_rules" + - "m.room.canonical_alias" + - "m.room.avatar" + - "m.room.name" + + +# A list of application service config file to use +app_service_config_files: [] + + +macaroon_secret_key: "{{MACAROON_SECRET_KEY}}" + +# Used to enable access token expiration. +expire_access_token: False + +# a secret which is used to calculate HMACs for form values, to stop +# falsification of values +form_secret: "{{FORM_SECRET}}" + +## Signing Keys ## + +# Path to the signing key to sign messages with +signing_key_path: "{{SYNAPSE_ROOT}}localhost.signing.key" + +# The keys that the server used to sign messages with but won't use +# to sign new messages. E.g. it has lost its private key +old_signing_keys: {} +# "ed25519:auto": +# # Base64 encoded public key +# key: "The public part of your old signing key." +# # Millisecond POSIX timestamp when the key expired. +# expired_ts: 123456789123 + +# How long key response published by this server is valid for. +# Used to set the valid_until_ts in /key/v2 APIs. +# Determines how quickly servers will query to check which keys +# are still valid. +key_refresh_interval: "1d" # 1 Day.block_non_admin_invites + +# The trusted servers to download signing keys from. +perspectives: + servers: + "matrix.org": + verify_keys: + "ed25519:auto": + key: "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw" + + + +# Enable SAML2 for registration and login. Uses pysaml2 +# config_path: Path to the sp_conf.py configuration file +# idp_redirect_url: Identity provider URL which will redirect +# the user back to /login/saml2 with proper info. +# See pysaml2 docs for format of config. +#saml2_config: +# enabled: true +# config_path: "{{SYNAPSE_ROOT}}sp_conf.py" +# idp_redirect_url: "http://localhost/idp" + + + +# Enable CAS for registration and login. +#cas_config: +# enabled: true +# server_url: "https://cas-server.com" +# service_url: "https://homeserver.domain.com:8448" +# #required_attributes: +# # name: value + + +# The JWT needs to contain a globally unique "sub" (subject) claim. +# +# jwt_config: +# enabled: true +# secret: "a secret" +# algorithm: "HS256" + + + +# Enable password for login. +password_config: + enabled: true + # Uncomment and change to a secret random string for extra security. + # DO NOT CHANGE THIS AFTER INITIAL SETUP! + #pepper: "" + + + +# Enable sending emails for notification events +# Defining a custom URL for Riot is only needed if email notifications +# should contain links to a self-hosted installation of Riot; when set +# the "app_name" setting is ignored. +# +# If your SMTP server requires authentication, the optional smtp_user & +# smtp_pass variables should be used +# +#email: +# enable_notifs: false +# smtp_host: "localhost" +# smtp_port: 25 +# smtp_user: "exampleusername" +# smtp_pass: "examplepassword" +# require_transport_security: False +# notif_from: "Your Friendly %(app)s Home Server " +# app_name: Matrix +# template_dir: res/templates +# notif_template_html: notif_mail.html +# notif_template_text: notif_mail.txt +# notif_for_new_users: True +# riot_base_url: "http://localhost/riot" + + +# password_providers: +# - module: "ldap_auth_provider.LdapAuthProvider" +# config: +# enabled: true +# uri: "ldap://ldap.example.com:389" +# start_tls: true +# base: "ou=users,dc=example,dc=com" +# attributes: +# uid: "cn" +# mail: "email" +# name: "givenName" +# #bind_dn: +# #bind_password: +# #filter: "(objectClass=posixAccount)" + + + +# Clients requesting push notifications can either have the body of +# the message sent in the notification poke along with other details +# like the sender, or just the event ID and room ID (`event_id_only`). +# If clients choose the former, this option controls whether the +# notification request includes the content of the event (other details +# like the sender are still included). For `event_id_only` push, it +# has no effect. + +# For modern android devices the notification content will still appear +# because it is loaded by the app. iPhone, however will send a +# notification saying only that a message arrived and who it came from. +# +#push: +# include_content: true + + +# spam_checker: +# module: "my_custom_project.SuperSpamChecker" +# config: +# example_option: 'things' + + +# Whether to allow non server admins to create groups on this server +enable_group_creation: false + +# If enabled, non server admins can only create groups with local parts +# starting with this prefix +# group_creation_prefix: "unofficial/" + + + +# User Directory configuration +# +# 'search_all_users' defines whether to search all users visible to your HS +# when searching the user directory, rather than limiting to users visible +# in public rooms. Defaults to false. If you set it True, you'll have to run +# UPDATE user_directory_stream_pos SET stream_id = NULL; +# on your database to tell it to rebuild the user_directory search indexes. +# +#user_directory: +# search_all_users: false + + +# User Consent configuration +# +# for detailed instructions, see +# https://github.com/matrix-org/synapse/blob/master/docs/consent_tracking.md +# +# Parts of this section are required if enabling the 'consent' resource under +# 'listeners', in particular 'template_dir' and 'version'. +# +# 'template_dir' gives the location of the templates for the HTML forms. +# This directory should contain one subdirectory per language (eg, 'en', 'fr'), +# and each language directory should contain the policy document (named as +# '.html') and a success page (success.html). +# +# 'version' specifies the 'current' version of the policy document. It defines +# the version to be served by the consent resource if there is no 'v' +# parameter. +# +# 'server_notice_content', if enabled, will send a user a "Server Notice" +# asking them to consent to the privacy policy. The 'server_notices' section +# must also be configured for this to work. Notices will *not* be sent to +# guest users unless 'send_server_notice_to_guests' is set to true. +# +# 'block_events_error', if set, will block any attempts to send events +# until the user consents to the privacy policy. The value of the setting is +# used as the text of the error. +# +user_consent: + template_dir: res/templates/privacy + version: 1.0 + server_notice_content: + msgtype: m.text + body: >- + To continue using this homeserver you must review and agree to the + terms and conditions at %(consent_uri)s + send_server_notice_to_guests: True + block_events_error: >- + To continue using this homeserver you must review and agree to the + terms and conditions at %(consent_uri)s + + + +# Server Notices room configuration +# +# Uncomment this section to enable a room which can be used to send notices +# from the server to users. It is a special room which cannot be left; notices +# come from a special "notices" user id. +# +# If you uncomment this section, you *must* define the system_mxid_localpart +# setting, which defines the id of the user which will be used to send the +# notices. +# +# It's also possible to override the room name, the display name of the +# "notices" user, and the avatar for the user. +# +server_notices: + system_mxid_localpart: notices + system_mxid_display_name: "Server Notices" + system_mxid_avatar_url: "mxc://localhost:8008/oumMVlgDnLYFaPVkExemNVVZ" + room_name: "Server Notices" diff --git a/synapse/config-templates/consent/res/templates/privacy/en/1.0.html b/synapse/config-templates/consent/res/templates/privacy/en/1.0.html new file mode 100644 index 0000000000..d4959b4bcb --- /dev/null +++ b/synapse/config-templates/consent/res/templates/privacy/en/1.0.html @@ -0,0 +1,23 @@ + + + + Test Privacy policy + + + {% if has_consented %} +

+ Thank you, you've already accepted the license. +

+ {% else %} +

+ Please accept the license! +

+
+ + + + +
+ {% endif %} + + \ No newline at end of file diff --git a/synapse/config-templates/consent/res/templates/privacy/en/success.html b/synapse/config-templates/consent/res/templates/privacy/en/success.html new file mode 100644 index 0000000000..abe27d87ca --- /dev/null +++ b/synapse/config-templates/consent/res/templates/privacy/en/success.html @@ -0,0 +1,9 @@ + + + + Test Privacy policy + + +

Danke schon

+ + \ No newline at end of file diff --git a/synapse/install.sh b/synapse/install.sh new file mode 100644 index 0000000000..47f1f746fc --- /dev/null +++ b/synapse/install.sh @@ -0,0 +1,33 @@ +# config +SYNAPSE_BRANCH=master +INSTALLATION_NAME=consent +SERVER_DIR=installations/$INSTALLATION_NAME +CONFIG_TEMPLATE=consent +PORT=8008 +# set current directory to script directory +BASE_DIR=$(realpath $(dirname $0)) +pushd $BASE_DIR +mkdir -p installations/ +curl https://codeload.github.com/matrix-org/synapse/zip/$SYNAPSE_BRANCH --output synapse.zip +unzip synapse.zip +mv synapse-$SYNAPSE_BRANCH $SERVER_DIR +pushd $SERVER_DIR +virtualenv -p python2.7 env +source env/bin/activate +pip install --upgrade pip +pip install --upgrade setuptools +pip install . +python -m synapse.app.homeserver \ + --server-name localhost \ + --config-path homeserver.yaml \ + --generate-config \ + --report-stats=no +# apply configuration +cp -r $BASE_DIR/config-templates/$CONFIG_TEMPLATE/. ./ +sed -i "s#{{SYNAPSE_ROOT}}#$(pwd)/#g" homeserver.yaml +sed -i "s#{{SYNAPSE_PORT}}#${PORT}/#g" homeserver.yaml +sed -i "s#{{FORM_SECRET}}#$(uuidgen)#g" homeserver.yaml +sed -i "s#{{REGISTRATION_SHARED_SECRET}}#$(uuidgen)#g" homeserver.yaml +sed -i "s#{{MACAROON_SECRET_KEY}}#$(uuidgen)#g" homeserver.yaml +popd #back to synapse root dir +popd #back to wherever we were \ No newline at end of file From fc4c425a22c11737d12b39ca83fc11efd1046d1e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 17 Jul 2018 12:38:20 +0200 Subject: [PATCH 017/221] do accepting terms as part of signup since we try to create a room with riot-bot after login, which fails with consent warning --- tests/join.js | 18 +----------------- tests/signup.js | 13 ++++++++++++- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/tests/join.js b/tests/join.js index 79990af3a2..ea16a93936 100644 --- a/tests/join.js +++ b/tests/join.js @@ -17,7 +17,7 @@ limitations under the License. const helpers = require('../helpers'); const assert = require('assert'); -module.exports = async function join(page, roomName, acceptTerms = false) { +module.exports = async function join(page, roomName) { //TODO: brittle selector const directoryButton = await helpers.waitAndQuerySelector(page, '.mx_RoleButton[aria-label="Room directory"]'); await directoryButton.click(); @@ -31,21 +31,5 @@ module.exports = async function join(page, roomName, acceptTerms = false) { const joinLink = await helpers.waitAndQuerySelector(page, '.mx_RoomPreviewBar_join_text a'); await joinLink.click(); - if (acceptTerms) { - const reviewTermsButton = await helpers.waitAndQuerySelector(page, '.mx_QuestionDialog button.mx_Dialog_primary'); - const termsPagePromise = helpers.waitForNewPage(); - await reviewTermsButton.click(); - const termsPage = await termsPagePromise; - const acceptButton = await termsPage.$('input[type=submit]'); - await acceptButton.click(); - await helpers.delay(500); //TODO yuck, timers - //try to join again after accepting the terms - - //TODO need to do this because joinLink is detached after switching target - const joinLink2 = await helpers.waitAndQuerySelector(page, '.mx_RoomPreviewBar_join_text a'); - await joinLink2.click(); - } - - await page.waitForSelector('.mx_MessageComposer'); } \ No newline at end of file diff --git a/tests/signup.js b/tests/signup.js index 2a0d6dc9b4..5560fc56cf 100644 --- a/tests/signup.js +++ b/tests/signup.js @@ -57,7 +57,6 @@ module.exports = async function signup(page, username, password, homeserver) { await continueButton.click(); //wait for registration to finish so the hash gets set //onhashchange better? - await helpers.delay(1000); /* await page.screenshot({path: "afterlogin.png", fullPage: true}); console.log('browser console logs:'); @@ -66,11 +65,23 @@ module.exports = async function signup(page, username, password, homeserver) { console.log(xhrLogs.logs()); */ + await acceptTerms(page); + await helpers.delay(10000); //printElements('page', await page.$('#matrixchat')); // await navigation_promise; //await page.waitForSelector('.mx_MatrixChat', {visible: true, timeout: 3000}); const url = page.url(); assert.strictEqual(url, helpers.riotUrl('/#/home')); +} + +async function acceptTerms(page) { + const reviewTermsButton = await helpers.waitAndQuerySelector(page, '.mx_QuestionDialog button.mx_Dialog_primary'); + const termsPagePromise = helpers.waitForNewPage(); + await reviewTermsButton.click(); + const termsPage = await termsPagePromise; + const acceptButton = await termsPage.$('input[type=submit]'); + await acceptButton.click(); + await helpers.delay(500); //TODO yuck, timers } \ No newline at end of file From dcf4be79b7336716b3a2379e7fd47c0b3564edb3 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 18 Jul 2018 17:52:29 +0200 Subject: [PATCH 018/221] add start and stop scripts for synapse --- synapse/start.sh | 4 ++++ synapse/stop.sh | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 synapse/start.sh create mode 100644 synapse/stop.sh diff --git a/synapse/start.sh b/synapse/start.sh new file mode 100644 index 0000000000..dec9a7d035 --- /dev/null +++ b/synapse/start.sh @@ -0,0 +1,4 @@ +pushd installations/consent +source env/bin/activate +./synctl start +popd \ No newline at end of file diff --git a/synapse/stop.sh b/synapse/stop.sh new file mode 100644 index 0000000000..916e774a99 --- /dev/null +++ b/synapse/stop.sh @@ -0,0 +1,4 @@ +pushd installations/consent +source env/bin/activate +./synctl stop +popd \ No newline at end of file From 2cb83334ed9c37aa7acc0fc01610af7aa554bace Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 18 Jul 2018 17:52:51 +0200 Subject: [PATCH 019/221] add script to install, start and stop riot --- riot/.gitignore | 1 + riot/install.sh | 8 ++++++++ riot/start.sh | 5 +++++ riot/stop.sh | 3 +++ 4 files changed, 17 insertions(+) create mode 100644 riot/.gitignore create mode 100644 riot/install.sh create mode 100644 riot/start.sh create mode 100644 riot/stop.sh diff --git a/riot/.gitignore b/riot/.gitignore new file mode 100644 index 0000000000..ce36d3b374 --- /dev/null +++ b/riot/.gitignore @@ -0,0 +1 @@ +riot-web \ No newline at end of file diff --git a/riot/install.sh b/riot/install.sh new file mode 100644 index 0000000000..3729c3c03a --- /dev/null +++ b/riot/install.sh @@ -0,0 +1,8 @@ +RIOT_BRANCH=master +curl -L https://github.com/vector-im/riot-web/archive/${RIOT_BRANCH}.zip --output riot.zip +unzip riot.zip +rm riot.zip +mv riot-web-${RIOT_BRANCH} riot-web +pushd riot-web +npm install +npm run build diff --git a/riot/start.sh b/riot/start.sh new file mode 100644 index 0000000000..6e9ad218e1 --- /dev/null +++ b/riot/start.sh @@ -0,0 +1,5 @@ +pushd riot-web/webapp/ +python -m SimpleHTTPServer 8080 & +PID=$! +popd +echo $PID > riot.pid \ No newline at end of file diff --git a/riot/stop.sh b/riot/stop.sh new file mode 100644 index 0000000000..d0a0d69502 --- /dev/null +++ b/riot/stop.sh @@ -0,0 +1,3 @@ +PIDFILE=riot.pid +kill $(cat $PIDFILE) +rm $PIDFILE \ No newline at end of file From 01612f71bf3ec8cc11e51dfd7473b37bfdd19891 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 18 Jul 2018 18:04:31 +0200 Subject: [PATCH 020/221] dont assume current directory in scripts --- riot/install.sh | 4 ++++ riot/start.sh | 5 ++++- riot/stop.sh | 5 ++++- synapse/start.sh | 3 +++ synapse/stop.sh | 3 +++ 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/riot/install.sh b/riot/install.sh index 3729c3c03a..2eedf0fefa 100644 --- a/riot/install.sh +++ b/riot/install.sh @@ -1,4 +1,7 @@ RIOT_BRANCH=master + +BASE_DIR=$(realpath $(dirname $0)) +pushd $BASE_DIR curl -L https://github.com/vector-im/riot-web/archive/${RIOT_BRANCH}.zip --output riot.zip unzip riot.zip rm riot.zip @@ -6,3 +9,4 @@ mv riot-web-${RIOT_BRANCH} riot-web pushd riot-web npm install npm run build +popd \ No newline at end of file diff --git a/riot/start.sh b/riot/start.sh index 6e9ad218e1..2eb3221511 100644 --- a/riot/start.sh +++ b/riot/start.sh @@ -1,5 +1,8 @@ +BASE_DIR=$(realpath $(dirname $0)) +pushd $BASE_DIR pushd riot-web/webapp/ python -m SimpleHTTPServer 8080 & PID=$! popd -echo $PID > riot.pid \ No newline at end of file +echo $PID > riot.pid +popd \ No newline at end of file diff --git a/riot/stop.sh b/riot/stop.sh index d0a0d69502..ca1da23476 100644 --- a/riot/stop.sh +++ b/riot/stop.sh @@ -1,3 +1,6 @@ +BASE_DIR=$(realpath $(dirname $0)) +pushd $BASE_DIR PIDFILE=riot.pid kill $(cat $PIDFILE) -rm $PIDFILE \ No newline at end of file +rm $PIDFILE +popd \ No newline at end of file diff --git a/synapse/start.sh b/synapse/start.sh index dec9a7d035..6d758630a4 100644 --- a/synapse/start.sh +++ b/synapse/start.sh @@ -1,4 +1,7 @@ +BASE_DIR=$(realpath $(dirname $0)) +pushd $BASE_DIR pushd installations/consent source env/bin/activate ./synctl start +popd popd \ No newline at end of file diff --git a/synapse/stop.sh b/synapse/stop.sh index 916e774a99..ab376d8ac5 100644 --- a/synapse/stop.sh +++ b/synapse/stop.sh @@ -1,4 +1,7 @@ +BASE_DIR=$(realpath $(dirname $0)) +pushd $BASE_DIR pushd installations/consent source env/bin/activate ./synctl stop +popd popd \ No newline at end of file From 7ecd7d387325fe809f1b0bc2c19e2450333b1e05 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Jul 2018 18:50:05 +0200 Subject: [PATCH 021/221] add template config file for riot installation --- riot/.gitignore | 3 ++- riot/config-template/config.json | 34 ++++++++++++++++++++++++++++++++ riot/install.sh | 1 + 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 riot/config-template/config.json diff --git a/riot/.gitignore b/riot/.gitignore index ce36d3b374..0f07d8e498 100644 --- a/riot/.gitignore +++ b/riot/.gitignore @@ -1 +1,2 @@ -riot-web \ No newline at end of file +riot-web +riot.pid \ No newline at end of file diff --git a/riot/config-template/config.json b/riot/config-template/config.json new file mode 100644 index 0000000000..6d5eeb9640 --- /dev/null +++ b/riot/config-template/config.json @@ -0,0 +1,34 @@ +{ + "default_hs_url": "http://localhost:8008", + "default_is_url": "https://vector.im", + "disable_custom_urls": false, + "disable_guests": false, + "disable_login_language_selector": false, + "disable_3pid_login": false, + "brand": "Riot", + "integrations_ui_url": "https://scalar.vector.im/", + "integrations_rest_url": "https://scalar.vector.im/api", + "bug_report_endpoint_url": "https://riot.im/bugreports/submit", + "features": { + "feature_groups": "labs", + "feature_pinning": "labs" + }, + "default_federate": true, + "welcomePageUrl": "home.html", + "default_theme": "light", + "roomDirectory": { + "servers": [ + "localhost:8008" + ] + }, + "welcomeUserId": "@someuser:localhost", + "piwik": { + "url": "https://piwik.riot.im/", + "whitelistedHSUrls": ["http://localhost:8008"], + "whitelistedISUrls": ["https://vector.im", "https://matrix.org"], + "siteId": 1 + }, + "enable_presence_by_hs_url": { + "https://matrix.org": false + } +} diff --git a/riot/install.sh b/riot/install.sh index 2eedf0fefa..22bb87f03e 100644 --- a/riot/install.sh +++ b/riot/install.sh @@ -6,6 +6,7 @@ curl -L https://github.com/vector-im/riot-web/archive/${RIOT_BRANCH}.zip --outpu unzip riot.zip rm riot.zip mv riot-web-${RIOT_BRANCH} riot-web +cp config-template/config.json riot-web/ pushd riot-web npm install npm run build From 1468be0db41c49248bba8244bac54842640b4fd1 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Jul 2018 18:50:29 +0200 Subject: [PATCH 022/221] add script to clear synapse db --- synapse/clear.sh | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 synapse/clear.sh diff --git a/synapse/clear.sh b/synapse/clear.sh new file mode 100644 index 0000000000..b2bee9ac47 --- /dev/null +++ b/synapse/clear.sh @@ -0,0 +1,6 @@ +BASE_DIR=$(realpath $(dirname $0)) +pushd $BASE_DIR +pushd installations/consent +rm homeserver.db +popd +popd \ No newline at end of file From bc1da0565e98b5d390e5f1f57954c707291de347 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Jul 2018 18:50:52 +0200 Subject: [PATCH 023/221] WIP: script to run tests on CI --- run.sh | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 run.sh diff --git a/run.sh b/run.sh new file mode 100644 index 0000000000..065d41c569 --- /dev/null +++ b/run.sh @@ -0,0 +1,6 @@ +sh synapse/clear.sh +sh synapse/start.sh +sh riot/start.sh +node start.js +sh riot/stop.sh +sh synapse/stop.sh \ No newline at end of file From a74a753a05514a4cc8ffb4c278c506c8e368a1cf Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Jul 2018 18:51:25 +0200 Subject: [PATCH 024/221] working consent test by accepting server notices invite and clicking on link, also create room --- helpers.js | 14 +++++++++++ start.js | 16 +++++++++--- tests/consent.js | 28 +++++++++++++++++++++ tests/create-room.js | 32 ++++++++++++++++++++++++ tests/server-notices-consent.js | 44 +++++++++++++++++++++++++++++++++ tests/signup.js | 14 ++--------- 6 files changed, 132 insertions(+), 16 deletions(-) create mode 100644 tests/consent.js create mode 100644 tests/create-room.js create mode 100644 tests/server-notices-consent.js diff --git a/helpers.js b/helpers.js index d57595f377..fae5c8e859 100644 --- a/helpers.js +++ b/helpers.js @@ -16,6 +16,7 @@ limitations under the License. // puppeteer helpers +// TODO: rename to queryAndInnertext? async function tryGetInnertext(page, selector) { const field = await page.$(selector); if (field != null) { @@ -25,6 +26,11 @@ async function tryGetInnertext(page, selector) { return null; } +async function innerText(page, field) { + const text_handle = await field.getProperty('innerText'); + return await text_handle.jsonValue(); +} + async function newPage() { const page = await browser.newPage(); await page.setViewport({ @@ -82,11 +88,17 @@ async function replaceInputText(input, text) { await input.type(text); } +// TODO: rename to waitAndQuery(Single)? async function waitAndQuerySelector(page, selector, timeout = 500) { await page.waitForSelector(selector, {visible: true, timeout}); return await page.$(selector); } +async function waitAndQueryAll(page, selector, timeout = 500) { + await page.waitForSelector(selector, {visible: true, timeout}); + return await page.$$(selector); +} + function waitForNewPage(timeout = 500) { return new Promise((resolve, reject) => { const timeoutHandle = setTimeout(() => { @@ -120,6 +132,7 @@ function delay(ms) { module.exports = { tryGetInnertext, + innerText, newPage, logConsole, logXHRRequests, @@ -127,6 +140,7 @@ module.exports = { printElements, replaceInputText, waitAndQuerySelector, + waitAndQueryAll, waitForNewPage, randomInt, riotUrl, diff --git a/start.js b/start.js index 8a3ceb354b..0c06bd9731 100644 --- a/start.js +++ b/start.js @@ -20,13 +20,16 @@ const assert = require('assert'); const signup = require('./tests/signup'); const join = require('./tests/join'); +const createRoom = require('./tests/create-room'); +const acceptServerNoticesInviteAndConsent = require('./tests/server-notices-consent'); + +const homeserver = 'http://localhost:8008'; global.riotserver = 'http://localhost:8080'; -global.homeserver = 'http://localhost:8008'; global.browser = null; async function runTests() { - global.browser = await puppeteer.launch(); + global.browser = await puppeteer.launch({headless: false}); const page = await helpers.newPage(); const username = 'bruno-' + helpers.randomInt(10000); @@ -35,9 +38,14 @@ async function runTests() { await signup(page, username, password, homeserver); process.stdout.write('done\n'); + const noticesName = "Server Notices"; + process.stdout.write(`* accepting "${noticesName}" and accepting terms & conditions ...`); + await acceptServerNoticesInviteAndConsent(page, noticesName); + process.stdout.write('done\n'); + const room = 'test'; - process.stdout.write(`* joining room ${room} ... `); - await join(page, room, true); + process.stdout.write(`* creating room ${room} ... `); + await createRoom(page, room); process.stdout.write('done\n'); await browser.close(); diff --git a/tests/consent.js b/tests/consent.js new file mode 100644 index 0000000000..3c8ada9a5e --- /dev/null +++ b/tests/consent.js @@ -0,0 +1,28 @@ +/* +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 helpers = require('../helpers'); +const assert = require('assert'); + +module.exports = async function acceptTerms(page) { + const reviewTermsButton = await helpers.waitAndQuerySelector(page, '.mx_QuestionDialog button.mx_Dialog_primary', 5000); + const termsPagePromise = helpers.waitForNewPage(); + await reviewTermsButton.click(); + const termsPage = await termsPagePromise; + const acceptButton = await termsPage.$('input[type=submit]'); + await acceptButton.click(); + await helpers.delay(500); //TODO yuck, timers +} \ No newline at end of file diff --git a/tests/create-room.js b/tests/create-room.js new file mode 100644 index 0000000000..4c9004bcaf --- /dev/null +++ b/tests/create-room.js @@ -0,0 +1,32 @@ +/* +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 helpers = require('../helpers'); +const assert = require('assert'); + +module.exports = async function createRoom(page, roomName) { + //TODO: brittle selector + const createRoomButton = await helpers.waitAndQuerySelector(page, '.mx_RoleButton[aria-label="Create new room"]'); + await createRoomButton.click(); + + const roomNameInput = await helpers.waitAndQuerySelector(page, '.mx_CreateRoomDialog_input'); + await helpers.replaceInputText(roomNameInput, roomName); + + const createButton = await helpers.waitAndQuerySelector(page, '.mx_Dialog_primary'); + await createButton.click(); + + await page.waitForSelector('.mx_MessageComposer'); +} \ No newline at end of file diff --git a/tests/server-notices-consent.js b/tests/server-notices-consent.js new file mode 100644 index 0000000000..2689036a96 --- /dev/null +++ b/tests/server-notices-consent.js @@ -0,0 +1,44 @@ +/* +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 helpers = require('../helpers'); +const assert = require('assert'); + +module.exports = async function acceptServerNoticesInviteAndConsent(page, name) { + //TODO: brittle selector + const invitesHandles = await helpers.waitAndQueryAll(page, '.mx_RoomTile_name.mx_RoomTile_invite'); + const invitesWithText = await Promise.all(invitesHandles.map(async (inviteHandle) => { + const text = await helpers.innerText(page, inviteHandle); + return {inviteHandle, text}; + })); + const inviteHandle = invitesWithText.find(({inviteHandle, text}) => { + return text.trim() === name; + }).inviteHandle; + + await inviteHandle.click(); + + const acceptInvitationLink = await helpers.waitAndQuerySelector(page, ".mx_RoomPreviewBar_join_text a:first-child"); + await acceptInvitationLink.click(); + + const consentLink = await helpers.waitAndQuerySelector(page, ".mx_EventTile_body a", 1000); + + const termsPagePromise = helpers.waitForNewPage(); + await consentLink.click(); + const termsPage = await termsPagePromise; + const acceptButton = await termsPage.$('input[type=submit]'); + await acceptButton.click(); + await helpers.delay(500); //TODO yuck, timers +} \ No newline at end of file diff --git a/tests/signup.js b/tests/signup.js index 5560fc56cf..e482c7dfea 100644 --- a/tests/signup.js +++ b/tests/signup.js @@ -65,9 +65,9 @@ module.exports = async function signup(page, username, password, homeserver) { console.log(xhrLogs.logs()); */ - await acceptTerms(page); + //await acceptTerms(page); - await helpers.delay(10000); + await helpers.delay(2000); //printElements('page', await page.$('#matrixchat')); // await navigation_promise; @@ -75,13 +75,3 @@ module.exports = async function signup(page, username, password, homeserver) { const url = page.url(); assert.strictEqual(url, helpers.riotUrl('/#/home')); } - -async function acceptTerms(page) { - const reviewTermsButton = await helpers.waitAndQuerySelector(page, '.mx_QuestionDialog button.mx_Dialog_primary'); - const termsPagePromise = helpers.waitForNewPage(); - await reviewTermsButton.click(); - const termsPage = await termsPagePromise; - const acceptButton = await termsPage.$('input[type=submit]'); - await acceptButton.click(); - await helpers.delay(500); //TODO yuck, timers -} \ No newline at end of file From 515e34cfde84c9854c9b10f518e72dd8492986ab Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Jul 2018 18:59:45 +0200 Subject: [PATCH 025/221] turn headless back on --- start.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/start.js b/start.js index 0c06bd9731..3eefdc68c0 100644 --- a/start.js +++ b/start.js @@ -29,7 +29,7 @@ global.riotserver = 'http://localhost:8080'; global.browser = null; async function runTests() { - global.browser = await puppeteer.launch({headless: false}); + global.browser = await puppeteer.launch(); const page = await helpers.newPage(); const username = 'bruno-' + helpers.randomInt(10000); From 410b32ff859bcb63427d6365d78f896cbeda19d6 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Jul 2018 19:00:27 +0200 Subject: [PATCH 026/221] make script runnable in one terminal, without server output garbling up test results. This won't work well on CI server but makes it clear to run locally --- run.sh | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/run.sh b/run.sh index 065d41c569..bc39208384 100644 --- a/run.sh +++ b/run.sh @@ -1,6 +1,4 @@ -sh synapse/clear.sh -sh synapse/start.sh -sh riot/start.sh -node start.js -sh riot/stop.sh -sh synapse/stop.sh \ No newline at end of file +tmux \ + new-session "sh riot/stop.sh; sh synapse/stop.sh; sh synapse/clear.sh; sh synapse/start.sh; sh riot/start.sh; read"\; \ + split-window "sleep 5; node start.js; sh riot/stop.sh; sh synapse/stop.sh; read"\; \ + select-layout even-vertical From 5f2fcefb4ef87dd40d2b34befede72243dcc5ef9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Jul 2018 19:00:38 +0200 Subject: [PATCH 027/221] update instructions --- README.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 163bffbce7..5ebcf58a87 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,28 @@ This repository contains tests for the matrix-react-sdk web app. The tests fire - start synapse with clean config/database on every test run - look into CI(Travis) integration +## It's broken! How do I see what's happening in the browser? + +Look for this line: +``` +puppeteer.launch(); +``` +Now change it to: +``` +puppeteer.launch({headless: false}); +``` + ## How to run ### Setup - + - install synapse with `sh synapse/install.sh`, this fetches the master branch at the moment. + - install riot with `sh riot/install.sh`, this fetches the master branch at the moment. - install dependencies with `npm install` (will download copy of chrome) - have riot-web running on `localhost:8080` - have a local synapse running at `localhost:8008` ### Run tests - - run tests with `node start.js` + +Run tests with `sh run.sh`. + +You should see the terminal split with on top the server output (both riot static server, and synapse), and on the bottom the tests running. From 40c09673640594cf2edef7d718c16728fce3b3c5 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Jul 2018 19:08:23 +0200 Subject: [PATCH 028/221] more readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5ebcf58a87..b6249fb6e4 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ puppeteer.launch({headless: false}); ## How to run ### Setup - - install synapse with `sh synapse/install.sh`, this fetches the master branch at the moment. + - install synapse with `sh synapse/install.sh`, this fetches the master branch at the moment. If anything fails here, please refer to the synapse README to see if you're missing one of the prerequisites. - install riot with `sh riot/install.sh`, this fetches the master branch at the moment. - install dependencies with `npm install` (will download copy of chrome) - have riot-web running on `localhost:8080` From bc06d370d0e5aeaa09149dfef7ac67c773a47701 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 23 Jul 2018 09:41:25 +0200 Subject: [PATCH 029/221] prevent stop scripts from polluting output --- riot/stop.sh | 4 ++-- synapse/stop.sh | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/riot/stop.sh b/riot/stop.sh index ca1da23476..59fef1dfd0 100644 --- a/riot/stop.sh +++ b/riot/stop.sh @@ -1,6 +1,6 @@ BASE_DIR=$(realpath $(dirname $0)) -pushd $BASE_DIR +pushd $BASE_DIR > /dev/null PIDFILE=riot.pid kill $(cat $PIDFILE) rm $PIDFILE -popd \ No newline at end of file +popd > /dev/null \ No newline at end of file diff --git a/synapse/stop.sh b/synapse/stop.sh index ab376d8ac5..f55d8b50db 100644 --- a/synapse/stop.sh +++ b/synapse/stop.sh @@ -1,7 +1,7 @@ BASE_DIR=$(realpath $(dirname $0)) -pushd $BASE_DIR -pushd installations/consent +pushd $BASE_DIR > /dev/null +pushd installations/consent > /dev/null source env/bin/activate ./synctl stop -popd -popd \ No newline at end of file +popd > /dev/null +popd > /dev/null \ No newline at end of file From eb10296c74609a9d2c8333a7a6c9d5b1887e7695 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 23 Jul 2018 10:09:30 +0200 Subject: [PATCH 030/221] disable welcomeUserId for now in riot config, flow seems broken --- riot/config-template/config.json | 1 - 1 file changed, 1 deletion(-) diff --git a/riot/config-template/config.json b/riot/config-template/config.json index 6d5eeb9640..39bbd6490a 100644 --- a/riot/config-template/config.json +++ b/riot/config-template/config.json @@ -21,7 +21,6 @@ "localhost:8008" ] }, - "welcomeUserId": "@someuser:localhost", "piwik": { "url": "https://piwik.riot.im/", "whitelistedHSUrls": ["http://localhost:8008"], From 978081b3c0354d0a30b548eda273fe0907012ef3 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 23 Jul 2018 10:09:52 +0200 Subject: [PATCH 031/221] remove obsolete code --- tests/signup.js | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/tests/signup.js b/tests/signup.js index e482c7dfea..4d922829d9 100644 --- a/tests/signup.js +++ b/tests/signup.js @@ -15,9 +15,10 @@ limitations under the License. */ const helpers = require('../helpers'); +const acceptTerms = require('./consent'); const assert = require('assert'); -module.exports = async function signup(page, username, password, homeserver) { +module.exports = async function signup(page, username, password, homeserver, options) { const consoleLogs = helpers.logConsole(page); const xhrLogs = helpers.logXHRRequests(page); await page.goto(helpers.riotUrl('/#/register')); @@ -57,21 +58,8 @@ module.exports = async function signup(page, username, password, homeserver) { await continueButton.click(); //wait for registration to finish so the hash gets set //onhashchange better? -/* - await page.screenshot({path: "afterlogin.png", fullPage: true}); - console.log('browser console logs:'); - console.log(consoleLogs.logs()); - console.log('xhr logs:'); - console.log(xhrLogs.logs()); -*/ - - //await acceptTerms(page); - await helpers.delay(2000); - //printElements('page', await page.$('#matrixchat')); -// await navigation_promise; - //await page.waitForSelector('.mx_MatrixChat', {visible: true, timeout: 3000}); const url = page.url(); assert.strictEqual(url, helpers.riotUrl('/#/home')); } From b42a0411f30aa7192597c635e4599e0dcc16f9e4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 23 Jul 2018 10:10:36 +0200 Subject: [PATCH 032/221] add IDEA for better debugging to readme (unrelated to PR really) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b6249fb6e4..e0e3306b74 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ This repository contains tests for the matrix-react-sdk web app. The tests fire - Run 2 synapse instances to test federation use cases. - start synapse with clean config/database on every test run - look into CI(Travis) integration +- create interactive mode, where window is opened, and browser is kept open until Ctrl^C, for easy test debugging. ## It's broken! How do I see what's happening in the browser? From 048a3670811ea30cf8863b45ef65a440ffd74c1b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 23 Jul 2018 10:21:38 +0200 Subject: [PATCH 033/221] use in-memory database, faster and no need to clear before every run --- run.sh | 2 +- synapse/clear.sh | 6 ------ synapse/config-templates/consent/homeserver.yaml | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) delete mode 100644 synapse/clear.sh diff --git a/run.sh b/run.sh index bc39208384..b64fa1dbdd 100644 --- a/run.sh +++ b/run.sh @@ -1,4 +1,4 @@ tmux \ - new-session "sh riot/stop.sh; sh synapse/stop.sh; sh synapse/clear.sh; sh synapse/start.sh; sh riot/start.sh; read"\; \ + new-session "sh riot/stop.sh; sh synapse/stop.sh; sh synapse/start.sh; sh riot/start.sh; read"\; \ split-window "sleep 5; node start.js; sh riot/stop.sh; sh synapse/stop.sh; read"\; \ select-layout even-vertical diff --git a/synapse/clear.sh b/synapse/clear.sh deleted file mode 100644 index b2bee9ac47..0000000000 --- a/synapse/clear.sh +++ /dev/null @@ -1,6 +0,0 @@ -BASE_DIR=$(realpath $(dirname $0)) -pushd $BASE_DIR -pushd installations/consent -rm homeserver.db -popd -popd \ No newline at end of file diff --git a/synapse/config-templates/consent/homeserver.yaml b/synapse/config-templates/consent/homeserver.yaml index 6ba15c9af0..a27fbf6f10 100644 --- a/synapse/config-templates/consent/homeserver.yaml +++ b/synapse/config-templates/consent/homeserver.yaml @@ -193,7 +193,7 @@ database: # Arguments to pass to the engine args: # Path to the database - database: "{{SYNAPSE_ROOT}}homeserver.db" + database: ":memory:" # Number of events to cache in memory. event_cache_size: "10K" From 5934bebafbe9efe549b0504c57bbbbf75d0a3889 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 23 Jul 2018 10:36:03 +0200 Subject: [PATCH 034/221] change test user name --- start.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/start.js b/start.js index 3eefdc68c0..1073f3c9d2 100644 --- a/start.js +++ b/start.js @@ -32,7 +32,7 @@ async function runTests() { global.browser = await puppeteer.launch(); const page = await helpers.newPage(); - const username = 'bruno-' + helpers.randomInt(10000); + const username = 'user-' + helpers.randomInt(10000); const password = 'testtest'; process.stdout.write(`* signing up as ${username} ... `); await signup(page, username, password, homeserver); From c693d861f45cdf0d8c1e3e3f984a83c358b8323b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 23 Jul 2018 10:36:21 +0200 Subject: [PATCH 035/221] link to code style document, instead of having local copy --- README.md | 9 +++ code_style.md | 184 -------------------------------------------------- 2 files changed, 9 insertions(+), 184 deletions(-) delete mode 100644 code_style.md diff --git a/README.md b/README.md index e0e3306b74..90a3e869d3 100644 --- a/README.md +++ b/README.md @@ -45,3 +45,12 @@ puppeteer.launch({headless: false}); Run tests with `sh run.sh`. You should see the terminal split with on top the server output (both riot static server, and synapse), and on the bottom the tests running. + +Developer Guide +=============== + +Please follow the standard Matrix contributor's guide: +https://github.com/matrix-org/synapse/tree/master/CONTRIBUTING.rst + +Please follow the Matrix JS/React code style as per: +https://github.com/matrix-org/matrix-react-sdk/blob/master/code_style.md diff --git a/code_style.md b/code_style.md deleted file mode 100644 index 2cac303e54..0000000000 --- a/code_style.md +++ /dev/null @@ -1,184 +0,0 @@ -Matrix JavaScript/ECMAScript Style Guide -======================================== - -The intention of this guide is to make Matrix's JavaScript codebase clean, -consistent with other popular JavaScript styles and consistent with the rest of -the Matrix codebase. For reference, the Matrix Python style guide can be found -at https://github.com/matrix-org/synapse/blob/master/docs/code_style.rst - -This document reflects how we would like Matrix JavaScript code to look, with -acknowledgement that a significant amount of code is written to older -standards. - -Write applications in modern ECMAScript and use a transpiler where necessary to -target older platforms. When writing library code, consider carefully whether -to write in ES5 to allow all JavaScript application to use the code directly or -writing in modern ECMAScript and using a transpile step to generate the file -that applications can then include. There are significant benefits in being -able to use modern ECMAScript, although the tooling for doing so can be awkward -for library code, especially with regard to translating source maps and line -number throgh from the original code to the final application. - -General Style -------------- -- 4 spaces to indent, for consistency with Matrix Python. -- 120 columns per line, but try to keep JavaScript code around the 80 column mark. - Inline JSX in particular can be nicer with more columns per line. -- No trailing whitespace at end of lines. -- Don't indent empty lines. -- One newline at the end of the file. -- Unix newlines, never `\r` -- Indent similar to our python code: break up long lines at logical boundaries, - more than one argument on a line is OK -- Use semicolons, for consistency with node. -- UpperCamelCase for class and type names -- lowerCamelCase for functions and variables. -- Single line ternary operators are fine. -- UPPER_CAMEL_CASE for constants -- Single quotes for strings by default, for consistency with most JavaScript styles: - - ```javascript - "bad" // Bad - 'good' // Good - ``` -- Use parentheses or `` ` `` instead of `\` for line continuation where ever possible -- Open braces on the same line (consistent with Node): - - ```javascript - if (x) { - console.log("I am a fish"); // Good - } - - if (x) - { - console.log("I am a fish"); // Bad - } - ``` -- Spaces after `if`, `for`, `else` etc, no space around the condition: - - ```javascript - if (x) { - console.log("I am a fish"); // Good - } - - if(x) { - console.log("I am a fish"); // Bad - } - - if ( x ) { - console.log("I am a fish"); // Bad - } - ``` -- No new line before else, catch, finally, etc: - - ```javascript - if (x) { - console.log("I am a fish"); - } else { - console.log("I am a chimp"); // Good - } - - if (x) { - console.log("I am a fish"); - } - else { - console.log("I am a chimp"); // Bad - } - ``` -- Declare one variable per var statement (consistent with Node). Unless they - are simple and closely related. If you put the next declaration on a new line, - treat yourself to another `var`: - - ```javascript - const key = "foo", - comparator = function(x, y) { - return x - y; - }; // Bad - - const key = "foo"; - const comparator = function(x, y) { - return x - y; - }; // Good - - let x = 0, y = 0; // Fine - - let x = 0; - let y = 0; // Also fine - ``` -- A single line `if` is fine, all others have braces. This prevents errors when adding to the code.: - - ```javascript - if (x) return true; // Fine - - if (x) { - return true; // Also fine - } - - if (x) - return true; // Not fine - ``` -- Terminate all multi-line lists, object literals, imports and ideally function calls with commas (if using a transpiler). Note that trailing function commas require explicit configuration in babel at time of writing: - - ```javascript - var mascots = [ - "Patrick", - "Shirley", - "Colin", - "Susan", - "Sir Arthur David" // Bad - ]; - - var mascots = [ - "Patrick", - "Shirley", - "Colin", - "Susan", - "Sir Arthur David", // Good - ]; - ``` -- Use `null`, `undefined` etc consistently with node: - Boolean variables and functions should always be either true or false. Don't set it to 0 unless it's supposed to be a number. - When something is intentionally missing or removed, set it to null. - If returning a boolean, type coerce: - - ```javascript - function hasThings() { - return !!length; // bad - return new Boolean(length); // REALLY bad - return Boolean(length); // good - } - ``` - Don't set things to undefined. Reserve that value to mean "not yet set to anything." - Boolean objects are verboten. -- Use JSDoc - -ECMAScript ----------- -- Use `const` unless you need a re-assignable variable. This ensures things you don't want to be re-assigned can't be. -- Be careful migrating files to newer syntax. - - Don't mix `require` and `import` in the same file. Either stick to the old style or change them all. - - Likewise, don't mix things like class properties and `MyClass.prototype.MY_CONSTANT = 42;` - - Be careful mixing arrow functions and regular functions, eg. if one function in a promise chain is an - arrow function, they probably all should be. -- Apart from that, newer ES features should be used whenever the author deems them to be appropriate. -- Flow annotations are welcome and encouraged. - -React ------ -- Use React.createClass rather than ES6 classes for components, as the boilerplate is way too heavy on ES6 currently. ES7 might improve it. -- Pull out functions in props to the class, generally as specific event handlers: - - ```jsx - // Bad - {doStuff();}}> // Equally bad - // Better - // Best, if onFooClick would do anything other than directly calling doStuff - ``` - - Not doing so is acceptable in a single case; in function-refs: - - ```jsx - this.component = self}> - ``` -- Think about whether your component really needs state: are you duplicating - information in component state that could be derived from the model? From 1643b9552e76414853c50a97be4a797896341d79 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 23 Jul 2018 11:20:07 +0200 Subject: [PATCH 036/221] test default server setup for signup --- start.js | 2 +- tests/signup.js | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/start.js b/start.js index 1073f3c9d2..d4b8dab076 100644 --- a/start.js +++ b/start.js @@ -35,7 +35,7 @@ async function runTests() { const username = 'user-' + helpers.randomInt(10000); const password = 'testtest'; process.stdout.write(`* signing up as ${username} ... `); - await signup(page, username, password, homeserver); + await signup(page, username, password); process.stdout.write('done\n'); const noticesName = "Server Notices"; diff --git a/tests/signup.js b/tests/signup.js index 4d922829d9..79f105ee70 100644 --- a/tests/signup.js +++ b/tests/signup.js @@ -18,16 +18,18 @@ const helpers = require('../helpers'); const acceptTerms = require('./consent'); const assert = require('assert'); -module.exports = async function signup(page, username, password, homeserver, options) { +module.exports = async function signup(page, username, password, homeserver) { const consoleLogs = helpers.logConsole(page); const xhrLogs = helpers.logXHRRequests(page); await page.goto(helpers.riotUrl('/#/register')); //click 'Custom server' radio button - const advancedRadioButton = await helpers.waitAndQuerySelector(page, '#advanced'); - await advancedRadioButton.click(); - + if (homeserver) { + const advancedRadioButton = await helpers.waitAndQuerySelector(page, '#advanced'); + await advancedRadioButton.click(); + } + // wait until register button is visible + await page.waitForSelector('.mx_Login_submit[value=Register]'); //fill out form - await page.waitForSelector('.mx_ServerConfig', {visible: true, timeout: 500}); const loginFields = await page.$$('.mx_Login_field'); assert.strictEqual(loginFields.length, 7); const usernameField = loginFields[2]; @@ -37,7 +39,10 @@ module.exports = async function signup(page, username, password, homeserver, opt await helpers.replaceInputText(usernameField, username); await helpers.replaceInputText(passwordField, password); await helpers.replaceInputText(passwordRepeatField, password); - await helpers.replaceInputText(hsurlField, homeserver); + if (homeserver) { + await page.waitForSelector('.mx_ServerConfig', {visible: true, timeout: 500}); + await helpers.replaceInputText(hsurlField, homeserver); + } //wait over a second because Registration/ServerConfig have a 1000ms //delay to internally set the homeserver url //see Registration::render and ServerConfig::props::delayTimeMs From ba1ee86c6732d60d811b56f0b3f2b19ead6db3b9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 23 Jul 2018 11:21:34 +0200 Subject: [PATCH 037/221] wait to be visible --- tests/signup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/signup.js b/tests/signup.js index 79f105ee70..65044f900c 100644 --- a/tests/signup.js +++ b/tests/signup.js @@ -28,7 +28,7 @@ module.exports = async function signup(page, username, password, homeserver) { await advancedRadioButton.click(); } // wait until register button is visible - await page.waitForSelector('.mx_Login_submit[value=Register]'); + await page.waitForSelector('.mx_Login_submit[value=Register]', {visible: true, timeout: 500}); //fill out form const loginFields = await page.$$('.mx_Login_field'); assert.strictEqual(loginFields.length, 7); From c9461dd2967f869af469575853566a3ec9d9d105 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 27 Jul 2018 13:29:59 +0200 Subject: [PATCH 038/221] hide riot static server output --- riot/start.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/riot/start.sh b/riot/start.sh index 2eb3221511..3e5077717e 100644 --- a/riot/start.sh +++ b/riot/start.sh @@ -1,8 +1,8 @@ BASE_DIR=$(realpath $(dirname $0)) -pushd $BASE_DIR -pushd riot-web/webapp/ -python -m SimpleHTTPServer 8080 & +pushd $BASE_DIR > /dev/null +pushd riot-web/webapp/ > /dev/null +python -m SimpleHTTPServer 8080 > /dev/null 2>&1 & PID=$! -popd +popd > /dev/null echo $PID > riot.pid -popd \ No newline at end of file +popd > /dev/null \ No newline at end of file From 0be2e023812f06013a8f0c086ecea7301a44eb3d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 27 Jul 2018 13:42:36 +0200 Subject: [PATCH 039/221] hide synapse schema update logs by redirecting stderr --- synapse/start.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/synapse/start.sh b/synapse/start.sh index 6d758630a4..f59a3641cc 100644 --- a/synapse/start.sh +++ b/synapse/start.sh @@ -1,7 +1,7 @@ BASE_DIR=$(realpath $(dirname $0)) -pushd $BASE_DIR -pushd installations/consent +pushd $BASE_DIR > /dev/null +pushd installations/consent > /dev/null source env/bin/activate -./synctl start -popd -popd \ No newline at end of file +./synctl start 2> /dev/null +popd > /dev/null +popd > /dev/null \ No newline at end of file From a6304ce83ebe21e0f5ae0d9c84a41bac159abf78 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 27 Jul 2018 13:43:12 +0200 Subject: [PATCH 040/221] now the output isn't overwhelming anymore, output what's happening at every step --- riot/start.sh | 4 +++- start.js | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/riot/start.sh b/riot/start.sh index 3e5077717e..5feb40eb2b 100644 --- a/riot/start.sh +++ b/riot/start.sh @@ -1,7 +1,9 @@ +PORT=8080 +echo "running riot on http://localhost:$PORT..." BASE_DIR=$(realpath $(dirname $0)) pushd $BASE_DIR > /dev/null pushd riot-web/webapp/ > /dev/null -python -m SimpleHTTPServer 8080 > /dev/null 2>&1 & +python -m SimpleHTTPServer $PORT > /dev/null 2>&1 & PID=$! popd > /dev/null echo $PID > riot.pid diff --git a/start.js b/start.js index d4b8dab076..a37aa4538a 100644 --- a/start.js +++ b/start.js @@ -29,6 +29,7 @@ global.riotserver = 'http://localhost:8080'; global.browser = null; async function runTests() { + console.log("running tests ..."); global.browser = await puppeteer.launch(); const page = await helpers.newPage(); From b3473a7220236f9af0ac0fcf4fc74b281ed89b5c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 27 Jul 2018 13:43:38 +0200 Subject: [PATCH 041/221] with no logs polluting the output, we dont need tmux anymore to split the terminal --- run.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/run.sh b/run.sh index b64fa1dbdd..d4d4261430 100644 --- a/run.sh +++ b/run.sh @@ -1,4 +1,5 @@ -tmux \ - new-session "sh riot/stop.sh; sh synapse/stop.sh; sh synapse/start.sh; sh riot/start.sh; read"\; \ - split-window "sleep 5; node start.js; sh riot/stop.sh; sh synapse/stop.sh; read"\; \ - select-layout even-vertical +sh synapse/start.sh +sh riot/start.sh +node start.js +sh riot/stop.sh +sh synapse/stop.sh From a4e7b14728c834f0d992e2b12866ce17c163f7d4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 27 Jul 2018 13:50:58 +0200 Subject: [PATCH 042/221] update README --- README.md | 12 +++++------- install.sh | 3 +++ 2 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 install.sh diff --git a/README.md b/README.md index 90a3e869d3..5bd4c1dadc 100644 --- a/README.md +++ b/README.md @@ -34,18 +34,16 @@ puppeteer.launch({headless: false}); ## How to run ### Setup - - install synapse with `sh synapse/install.sh`, this fetches the master branch at the moment. If anything fails here, please refer to the synapse README to see if you're missing one of the prerequisites. - - install riot with `sh riot/install.sh`, this fetches the master branch at the moment. - - install dependencies with `npm install` (will download copy of chrome) - - have riot-web running on `localhost:8080` - - have a local synapse running at `localhost:8008` + +Run `sh install.sh`. This will: + - install synapse, fetches the master branch at the moment. If anything fails here, please refer to the synapse README to see if you're missing one of the prerequisites. + - install riot, this fetches the master branch at the moment. + - install dependencies (will download copy of chrome) ### Run tests Run tests with `sh run.sh`. -You should see the terminal split with on top the server output (both riot static server, and synapse), and on the bottom the tests running. - Developer Guide =============== diff --git a/install.sh b/install.sh new file mode 100644 index 0000000000..c4e6d6253e --- /dev/null +++ b/install.sh @@ -0,0 +1,3 @@ +sh synapse/install.sh +sh riot/install.sh +npm install From 96374f4e5400d1132703927b32db22981fa27886 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 27 Jul 2018 14:00:01 +0200 Subject: [PATCH 043/221] only install synapse and riot if directory is not already there --- riot/install.sh | 13 ++++++++++--- synapse/install.sh | 9 ++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/riot/install.sh b/riot/install.sh index 22bb87f03e..43b39611d7 100644 --- a/riot/install.sh +++ b/riot/install.sh @@ -1,13 +1,20 @@ RIOT_BRANCH=master BASE_DIR=$(realpath $(dirname $0)) -pushd $BASE_DIR +if [[ -d $BASE_DIR/riot-web ]]; then + echo "riot is already installed" + exit +fi + + +pushd $BASE_DIR > /dev/null curl -L https://github.com/vector-im/riot-web/archive/${RIOT_BRANCH}.zip --output riot.zip unzip riot.zip rm riot.zip mv riot-web-${RIOT_BRANCH} riot-web cp config-template/config.json riot-web/ -pushd riot-web +pushd riot-web > /dev/null npm install npm run build -popd \ No newline at end of file +popd > /dev/null +popd > /dev/null diff --git a/synapse/install.sh b/synapse/install.sh index 47f1f746fc..959e529e6b 100644 --- a/synapse/install.sh +++ b/synapse/install.sh @@ -6,7 +6,14 @@ CONFIG_TEMPLATE=consent PORT=8008 # set current directory to script directory BASE_DIR=$(realpath $(dirname $0)) -pushd $BASE_DIR + +if [[ -d $BASE_DIR/$SERVER_DIR ]]; then + echo "synapse is already installed" + exit +fi + +pushd $BASE_DIR > /dev/null + mkdir -p installations/ curl https://codeload.github.com/matrix-org/synapse/zip/$SYNAPSE_BRANCH --output synapse.zip unzip synapse.zip From 5e1517eb4d93351a8497eb7401b648d348dedc07 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 27 Jul 2018 14:10:19 +0200 Subject: [PATCH 044/221] no need for push/popd in sub-shell --- riot/install.sh | 7 ++----- riot/start.sh | 3 +-- riot/stop.sh | 3 +-- synapse/install.sh | 6 ++---- synapse/start.sh | 6 ++---- synapse/stop.sh | 6 ++---- 6 files changed, 10 insertions(+), 21 deletions(-) diff --git a/riot/install.sh b/riot/install.sh index 43b39611d7..9e7e5bb2da 100644 --- a/riot/install.sh +++ b/riot/install.sh @@ -6,15 +6,12 @@ if [[ -d $BASE_DIR/riot-web ]]; then exit fi - -pushd $BASE_DIR > /dev/null +cd $BASE_DIR curl -L https://github.com/vector-im/riot-web/archive/${RIOT_BRANCH}.zip --output riot.zip unzip riot.zip rm riot.zip mv riot-web-${RIOT_BRANCH} riot-web cp config-template/config.json riot-web/ -pushd riot-web > /dev/null +cd riot-web npm install npm run build -popd > /dev/null -popd > /dev/null diff --git a/riot/start.sh b/riot/start.sh index 5feb40eb2b..141d3176e7 100644 --- a/riot/start.sh +++ b/riot/start.sh @@ -1,10 +1,9 @@ PORT=8080 echo "running riot on http://localhost:$PORT..." BASE_DIR=$(realpath $(dirname $0)) -pushd $BASE_DIR > /dev/null +cd $BASE_DIR/ pushd riot-web/webapp/ > /dev/null python -m SimpleHTTPServer $PORT > /dev/null 2>&1 & PID=$! popd > /dev/null echo $PID > riot.pid -popd > /dev/null \ No newline at end of file diff --git a/riot/stop.sh b/riot/stop.sh index 59fef1dfd0..148116cfe6 100644 --- a/riot/stop.sh +++ b/riot/stop.sh @@ -1,6 +1,5 @@ BASE_DIR=$(realpath $(dirname $0)) -pushd $BASE_DIR > /dev/null +cd $BASE_DIR PIDFILE=riot.pid kill $(cat $PIDFILE) rm $PIDFILE -popd > /dev/null \ No newline at end of file diff --git a/synapse/install.sh b/synapse/install.sh index 959e529e6b..7170ce6d30 100644 --- a/synapse/install.sh +++ b/synapse/install.sh @@ -12,13 +12,13 @@ if [[ -d $BASE_DIR/$SERVER_DIR ]]; then exit fi -pushd $BASE_DIR > /dev/null +cd $BASE_DIR mkdir -p installations/ curl https://codeload.github.com/matrix-org/synapse/zip/$SYNAPSE_BRANCH --output synapse.zip unzip synapse.zip mv synapse-$SYNAPSE_BRANCH $SERVER_DIR -pushd $SERVER_DIR +cd $SERVER_DIR virtualenv -p python2.7 env source env/bin/activate pip install --upgrade pip @@ -36,5 +36,3 @@ sed -i "s#{{SYNAPSE_PORT}}#${PORT}/#g" homeserver.yaml sed -i "s#{{FORM_SECRET}}#$(uuidgen)#g" homeserver.yaml sed -i "s#{{REGISTRATION_SHARED_SECRET}}#$(uuidgen)#g" homeserver.yaml sed -i "s#{{MACAROON_SECRET_KEY}}#$(uuidgen)#g" homeserver.yaml -popd #back to synapse root dir -popd #back to wherever we were \ No newline at end of file diff --git a/synapse/start.sh b/synapse/start.sh index f59a3641cc..e809fb906c 100644 --- a/synapse/start.sh +++ b/synapse/start.sh @@ -1,7 +1,5 @@ BASE_DIR=$(realpath $(dirname $0)) -pushd $BASE_DIR > /dev/null -pushd installations/consent > /dev/null +cd $BASE_DIR +cd installations/consent source env/bin/activate ./synctl start 2> /dev/null -popd > /dev/null -popd > /dev/null \ No newline at end of file diff --git a/synapse/stop.sh b/synapse/stop.sh index f55d8b50db..1c5ab2fed7 100644 --- a/synapse/stop.sh +++ b/synapse/stop.sh @@ -1,7 +1,5 @@ BASE_DIR=$(realpath $(dirname $0)) -pushd $BASE_DIR > /dev/null -pushd installations/consent > /dev/null +cd $BASE_DIR +cd installations/consent source env/bin/activate ./synctl stop -popd > /dev/null -popd > /dev/null \ No newline at end of file From 5389a42bc18d6150770abbe1350ad73e2938d7f1 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 27 Jul 2018 15:04:04 +0200 Subject: [PATCH 045/221] use readlink instead of realpath as it seems to be more portable --- riot/install.sh | 2 +- riot/start.sh | 2 +- riot/stop.sh | 2 +- synapse/install.sh | 2 +- synapse/start.sh | 2 +- synapse/stop.sh | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/riot/install.sh b/riot/install.sh index 9e7e5bb2da..8a8761b705 100644 --- a/riot/install.sh +++ b/riot/install.sh @@ -1,6 +1,6 @@ RIOT_BRANCH=master -BASE_DIR=$(realpath $(dirname $0)) +BASE_DIR=$(readlink -f $(dirname $0)) if [[ -d $BASE_DIR/riot-web ]]; then echo "riot is already installed" exit diff --git a/riot/start.sh b/riot/start.sh index 141d3176e7..ec3e5b32bc 100644 --- a/riot/start.sh +++ b/riot/start.sh @@ -1,6 +1,6 @@ PORT=8080 echo "running riot on http://localhost:$PORT..." -BASE_DIR=$(realpath $(dirname $0)) +BASE_DIR=$(readlink -f $(dirname $0)) cd $BASE_DIR/ pushd riot-web/webapp/ > /dev/null python -m SimpleHTTPServer $PORT > /dev/null 2>&1 & diff --git a/riot/stop.sh b/riot/stop.sh index 148116cfe6..40695c749c 100644 --- a/riot/stop.sh +++ b/riot/stop.sh @@ -1,4 +1,4 @@ -BASE_DIR=$(realpath $(dirname $0)) +BASE_DIR=$(readlink -f $(dirname $0)) cd $BASE_DIR PIDFILE=riot.pid kill $(cat $PIDFILE) diff --git a/synapse/install.sh b/synapse/install.sh index 7170ce6d30..661c98ecd5 100644 --- a/synapse/install.sh +++ b/synapse/install.sh @@ -5,7 +5,7 @@ SERVER_DIR=installations/$INSTALLATION_NAME CONFIG_TEMPLATE=consent PORT=8008 # set current directory to script directory -BASE_DIR=$(realpath $(dirname $0)) +BASE_DIR=$(readlink -f $(dirname $0)) if [[ -d $BASE_DIR/$SERVER_DIR ]]; then echo "synapse is already installed" diff --git a/synapse/start.sh b/synapse/start.sh index e809fb906c..f7af6ac0f7 100644 --- a/synapse/start.sh +++ b/synapse/start.sh @@ -1,4 +1,4 @@ -BASE_DIR=$(realpath $(dirname $0)) +BASE_DIR=$(readlink -f $(dirname $0)) cd $BASE_DIR cd installations/consent source env/bin/activate diff --git a/synapse/stop.sh b/synapse/stop.sh index 1c5ab2fed7..ff9b004533 100644 --- a/synapse/stop.sh +++ b/synapse/stop.sh @@ -1,4 +1,4 @@ -BASE_DIR=$(realpath $(dirname $0)) +BASE_DIR=$(readlink -f $(dirname $0)) cd $BASE_DIR cd installations/consent source env/bin/activate From ebc9859cce39efd965ea5f9b91b9bf9a725ebbf3 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 27 Jul 2018 15:07:43 +0200 Subject: [PATCH 046/221] add instruction to install without chrome download --- install.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/install.sh b/install.sh index c4e6d6253e..9004176e9a 100644 --- a/install.sh +++ b/install.sh @@ -1,3 +1,4 @@ +# run with PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true sh install.sh if chrome is already installed sh synapse/install.sh sh riot/install.sh npm install From 20becf87354725a7aff9cb402fcae3e76a25cc49 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 27 Jul 2018 15:08:14 +0200 Subject: [PATCH 047/221] force running scripts in bash, as it's not the default shell on Ubuntu (which is what Travis runs) --- install.sh | 1 + riot/install.sh | 3 ++- riot/start.sh | 1 + riot/stop.sh | 1 + run.sh | 1 + synapse/install.sh | 3 ++- synapse/start.sh | 1 + synapse/stop.sh | 1 + 8 files changed, 10 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index 9004176e9a..ca8a51bd69 100644 --- a/install.sh +++ b/install.sh @@ -1,3 +1,4 @@ +#!/bin/bash # run with PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true sh install.sh if chrome is already installed sh synapse/install.sh sh riot/install.sh diff --git a/riot/install.sh b/riot/install.sh index 8a8761b705..d9cc7292f0 100644 --- a/riot/install.sh +++ b/riot/install.sh @@ -1,7 +1,8 @@ +#!/bin/bash RIOT_BRANCH=master BASE_DIR=$(readlink -f $(dirname $0)) -if [[ -d $BASE_DIR/riot-web ]]; then +if [ -d $BASE_DIR/riot-web ]; then echo "riot is already installed" exit fi diff --git a/riot/start.sh b/riot/start.sh index ec3e5b32bc..3892a80a48 100644 --- a/riot/start.sh +++ b/riot/start.sh @@ -1,3 +1,4 @@ +#!/bin/bash PORT=8080 echo "running riot on http://localhost:$PORT..." BASE_DIR=$(readlink -f $(dirname $0)) diff --git a/riot/stop.sh b/riot/stop.sh index 40695c749c..a3dc722e58 100644 --- a/riot/stop.sh +++ b/riot/stop.sh @@ -1,3 +1,4 @@ +#!/bin/bash BASE_DIR=$(readlink -f $(dirname $0)) cd $BASE_DIR PIDFILE=riot.pid diff --git a/run.sh b/run.sh index d4d4261430..ae073d2ba9 100644 --- a/run.sh +++ b/run.sh @@ -1,3 +1,4 @@ +#!/bin/bash sh synapse/start.sh sh riot/start.sh node start.js diff --git a/synapse/install.sh b/synapse/install.sh index 661c98ecd5..8260f208c2 100644 --- a/synapse/install.sh +++ b/synapse/install.sh @@ -1,3 +1,4 @@ +#!/bin/bash # config SYNAPSE_BRANCH=master INSTALLATION_NAME=consent @@ -7,7 +8,7 @@ PORT=8008 # set current directory to script directory BASE_DIR=$(readlink -f $(dirname $0)) -if [[ -d $BASE_DIR/$SERVER_DIR ]]; then +if [ -d $BASE_DIR/$SERVER_DIR ]; then echo "synapse is already installed" exit fi diff --git a/synapse/start.sh b/synapse/start.sh index f7af6ac0f7..785909a0b1 100644 --- a/synapse/start.sh +++ b/synapse/start.sh @@ -1,3 +1,4 @@ +#!/bin/bash BASE_DIR=$(readlink -f $(dirname $0)) cd $BASE_DIR cd installations/consent diff --git a/synapse/stop.sh b/synapse/stop.sh index ff9b004533..d83ddd0e4a 100644 --- a/synapse/stop.sh +++ b/synapse/stop.sh @@ -1,3 +1,4 @@ +#!/bin/bash BASE_DIR=$(readlink -f $(dirname $0)) cd $BASE_DIR cd installations/consent From edf37e3592a85a7d92026a71dbbfc64ccffefa59 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 27 Jul 2018 15:15:10 +0200 Subject: [PATCH 048/221] add support for passing chrome path as env var --- start.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/start.js b/start.js index a37aa4538a..427fa319d0 100644 --- a/start.js +++ b/start.js @@ -30,7 +30,11 @@ global.browser = null; async function runTests() { console.log("running tests ..."); - global.browser = await puppeteer.launch(); + const options = {}; + if (process.env.CHROME_PATH) { + options.executablePath = process.env.CHROME_PATH; + } + global.browser = await puppeteer.launch(options); const page = await helpers.newPage(); const username = 'user-' + helpers.randomInt(10000); From c3b7e6c7cb6d7a7d983f2d7e6e393fff577439c6 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 27 Jul 2018 16:01:54 +0200 Subject: [PATCH 049/221] make scripts executable, running them with sh does something weird on travis --- README.md | 4 ++-- install.sh | 4 ++-- riot/install.sh | 0 riot/start.sh | 0 riot/stop.sh | 0 run.sh | 8 ++++---- synapse/install.sh | 0 synapse/start.sh | 0 synapse/stop.sh | 0 9 files changed, 8 insertions(+), 8 deletions(-) mode change 100644 => 100755 install.sh mode change 100644 => 100755 riot/install.sh mode change 100644 => 100755 riot/start.sh mode change 100644 => 100755 riot/stop.sh mode change 100644 => 100755 run.sh mode change 100644 => 100755 synapse/install.sh mode change 100644 => 100755 synapse/start.sh mode change 100644 => 100755 synapse/stop.sh diff --git a/README.md b/README.md index 5bd4c1dadc..b1a4e40aac 100644 --- a/README.md +++ b/README.md @@ -35,14 +35,14 @@ puppeteer.launch({headless: false}); ### Setup -Run `sh install.sh`. This will: +Run `./install.sh`. This will: - install synapse, fetches the master branch at the moment. If anything fails here, please refer to the synapse README to see if you're missing one of the prerequisites. - install riot, this fetches the master branch at the moment. - install dependencies (will download copy of chrome) ### Run tests -Run tests with `sh run.sh`. +Run tests with `./run.sh`. Developer Guide =============== diff --git a/install.sh b/install.sh old mode 100644 new mode 100755 index ca8a51bd69..98ce104ba0 --- a/install.sh +++ b/install.sh @@ -1,5 +1,5 @@ #!/bin/bash # run with PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true sh install.sh if chrome is already installed -sh synapse/install.sh -sh riot/install.sh +./synapse/install.sh +./riot/install.sh npm install diff --git a/riot/install.sh b/riot/install.sh old mode 100644 new mode 100755 diff --git a/riot/start.sh b/riot/start.sh old mode 100644 new mode 100755 diff --git a/riot/stop.sh b/riot/stop.sh old mode 100644 new mode 100755 diff --git a/run.sh b/run.sh old mode 100644 new mode 100755 index ae073d2ba9..90e1528d48 --- a/run.sh +++ b/run.sh @@ -1,6 +1,6 @@ #!/bin/bash -sh synapse/start.sh -sh riot/start.sh +./synapse/start.sh +./riot/start.sh node start.js -sh riot/stop.sh -sh synapse/stop.sh +./riot/stop.sh +./synapse/stop.sh diff --git a/synapse/install.sh b/synapse/install.sh old mode 100644 new mode 100755 diff --git a/synapse/start.sh b/synapse/start.sh old mode 100644 new mode 100755 diff --git a/synapse/stop.sh b/synapse/stop.sh old mode 100644 new mode 100755 From e8f626ba183831becbdf65e6c864e411b79a93a0 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 27 Jul 2018 16:04:41 +0200 Subject: [PATCH 050/221] exit on error --- install.sh | 1 + run.sh | 1 + start.js | 1 + 3 files changed, 3 insertions(+) diff --git a/install.sh b/install.sh index 98ce104ba0..4008099a3d 100755 --- a/install.sh +++ b/install.sh @@ -1,5 +1,6 @@ #!/bin/bash # run with PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true sh install.sh if chrome is already installed +set -e ./synapse/install.sh ./riot/install.sh npm install diff --git a/run.sh b/run.sh index 90e1528d48..c59627e3a6 100755 --- a/run.sh +++ b/run.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -e ./synapse/start.sh ./riot/start.sh node start.js diff --git a/start.js b/start.js index 427fa319d0..5e14539bfc 100644 --- a/start.js +++ b/start.js @@ -29,6 +29,7 @@ global.riotserver = 'http://localhost:8080'; global.browser = null; async function runTests() { + process.exit(-1); console.log("running tests ..."); const options = {}; if (process.env.CHROME_PATH) { From 976f041bbadbd43b434c336055f8a670a04c358a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 27 Jul 2018 16:22:17 +0200 Subject: [PATCH 051/221] remove test exit, and use port we are semi-sure is free --- riot/start.sh | 2 +- start.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/riot/start.sh b/riot/start.sh index 3892a80a48..8e7e742f98 100755 --- a/riot/start.sh +++ b/riot/start.sh @@ -1,5 +1,5 @@ #!/bin/bash -PORT=8080 +PORT=5000 echo "running riot on http://localhost:$PORT..." BASE_DIR=$(readlink -f $(dirname $0)) cd $BASE_DIR/ diff --git a/start.js b/start.js index 5e14539bfc..05f4df9d0c 100644 --- a/start.js +++ b/start.js @@ -25,11 +25,10 @@ const acceptServerNoticesInviteAndConsent = require('./tests/server-notices-cons const homeserver = 'http://localhost:8008'; -global.riotserver = 'http://localhost:8080'; +global.riotserver = 'http://localhost:5000'; global.browser = null; async function runTests() { - process.exit(-1); console.log("running tests ..."); const options = {}; if (process.env.CHROME_PATH) { From 5cd52e2ebd17fed24f33c91e7b2bcad151788272 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 27 Jul 2018 18:43:11 +0200 Subject: [PATCH 052/221] show browser logs on error --- helpers.js | 6 +++--- start.js | 14 +++++++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/helpers.js b/helpers.js index fae5c8e859..a1f7434a29 100644 --- a/helpers.js +++ b/helpers.js @@ -58,9 +58,9 @@ function logXHRRequests(page) { const type = req.resourceType(); if (type === 'xhr' || type === 'fetch') { buffer += `${req.method()} ${req.url()} \n`; - if (req.method() === "POST") { - buffer += " Post data: " + req.postData(); - } + // if (req.method() === "POST") { + // buffer += " Post data: " + req.postData(); + // } } }); return { diff --git a/start.js b/start.js index 05f4df9d0c..aa459537c2 100644 --- a/start.js +++ b/start.js @@ -28,6 +28,9 @@ const homeserver = 'http://localhost:8008'; global.riotserver = 'http://localhost:5000'; global.browser = null; +let consoleLogs = null; +let xhrLogs = null; + async function runTests() { console.log("running tests ..."); const options = {}; @@ -36,6 +39,9 @@ async function runTests() { } global.browser = await puppeteer.launch(options); const page = await helpers.newPage(); + + consoleLogs = helpers.logConsole(page); + xhrLogs = helpers.logXHRRequests(page); const username = 'user-' + helpers.randomInt(10000); const password = 'testtest'; @@ -44,10 +50,12 @@ async function runTests() { process.stdout.write('done\n'); const noticesName = "Server Notices"; - process.stdout.write(`* accepting "${noticesName}" and accepting terms & conditions ...`); + process.stdout.write(`* accepting "${noticesName}" and accepting terms & conditions ... `); await acceptServerNoticesInviteAndConsent(page, noticesName); process.stdout.write('done\n'); + throw new Error('blubby'); + const room = 'test'; process.stdout.write(`* creating room ${room} ... `); await createRoom(page, room); @@ -62,6 +70,10 @@ function onSuccess() { function onFailure(err) { console.log('failure: ', err); + console.log('console.log output:'); + console.log(consoleLogs.logs()); + console.log('XHR requests:'); + console.log(xhrLogs.logs()); process.exit(-1); } From 758da7865941d4243ceacaa44f055cb7678c28bc Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 27 Jul 2018 18:43:40 +0200 Subject: [PATCH 053/221] dont fail when trying to stop riot and its not running --- riot/stop.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/riot/stop.sh b/riot/stop.sh index a3dc722e58..7ed18887f9 100755 --- a/riot/stop.sh +++ b/riot/stop.sh @@ -2,5 +2,8 @@ BASE_DIR=$(readlink -f $(dirname $0)) cd $BASE_DIR PIDFILE=riot.pid -kill $(cat $PIDFILE) -rm $PIDFILE +if [ -f $PIDFILE ]; then + echo "stopping riot server ..." + kill $(cat $PIDFILE) + rm $PIDFILE +fi From 29d688543d34554c5b56c8ce78eb7491f8f51429 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 27 Jul 2018 18:44:01 +0200 Subject: [PATCH 054/221] stop servers on error in run script --- riot/start.sh | 2 +- run.sh | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/riot/start.sh b/riot/start.sh index 8e7e742f98..143e5d9f8f 100755 --- a/riot/start.sh +++ b/riot/start.sh @@ -1,6 +1,6 @@ #!/bin/bash PORT=5000 -echo "running riot on http://localhost:$PORT..." +echo "running riot on http://localhost:$PORT ..." BASE_DIR=$(readlink -f $(dirname $0)) cd $BASE_DIR/ pushd riot-web/webapp/ > /dev/null diff --git a/run.sh b/run.sh index c59627e3a6..02b2e4cbdf 100755 --- a/run.sh +++ b/run.sh @@ -1,7 +1,19 @@ #!/bin/bash -set -e + +stop_servers() { + ./riot/stop.sh + ./synapse/stop.sh +} + +handle_error() { + EXIT_CODE=$? + stop_servers + exit $EXIT_CODE +} + +trap 'handle_error' ERR + ./synapse/start.sh ./riot/start.sh node start.js -./riot/stop.sh -./synapse/stop.sh +stop_servers From 5129bb57b608cb68a404d35f1d26f47bd4809a12 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 27 Jul 2018 18:58:37 +0200 Subject: [PATCH 055/221] log all requests with their response code --- helpers.js | 9 +++++---- start.js | 2 -- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/helpers.js b/helpers.js index a1f7434a29..e830824e7c 100644 --- a/helpers.js +++ b/helpers.js @@ -54,14 +54,15 @@ function logConsole(page) { function logXHRRequests(page) { let buffer = ""; - page.on('request', req => { + page.on('requestfinished', async (req) => { const type = req.resourceType(); - if (type === 'xhr' || type === 'fetch') { - buffer += `${req.method()} ${req.url()} \n`; + const response = await req.response(); + //if (type === 'xhr' || type === 'fetch') { + buffer += `${type} ${response.status()} ${req.method()} ${req.url()} \n`; // if (req.method() === "POST") { // buffer += " Post data: " + req.postData(); // } - } + //} }); return { logs() { diff --git a/start.js b/start.js index aa459537c2..c4eba26b58 100644 --- a/start.js +++ b/start.js @@ -54,8 +54,6 @@ async function runTests() { await acceptServerNoticesInviteAndConsent(page, noticesName); process.stdout.write('done\n'); - throw new Error('blubby'); - const room = 'test'; process.stdout.write(`* creating room ${room} ... `); await createRoom(page, room); From 31fcf08fece786f7fc7c3fd96770037c0926dc2e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 31 Jul 2018 11:41:01 +0200 Subject: [PATCH 056/221] only allow one riot server instance simultaneously --- riot/start.sh | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/riot/start.sh b/riot/start.sh index 143e5d9f8f..2c76747e46 100755 --- a/riot/start.sh +++ b/riot/start.sh @@ -1,10 +1,18 @@ #!/bin/bash PORT=5000 -echo "running riot on http://localhost:$PORT ..." BASE_DIR=$(readlink -f $(dirname $0)) +PIDFILE=riot.pid +CONFIG_BACKUP=config.e2etests_backup.json + cd $BASE_DIR/ + +if [ -f $PIDFILE ]; then + exit +fi + +echo "running riot on http://localhost:$PORT ..." pushd riot-web/webapp/ > /dev/null python -m SimpleHTTPServer $PORT > /dev/null 2>&1 & PID=$! popd > /dev/null -echo $PID > riot.pid +echo $PID > $PIDFILE From e50420dd1baa991f9229f4dec10e82442684b7ac Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 27 Jul 2018 19:27:53 +0200 Subject: [PATCH 057/221] apply config file when starting riot, not installing, so we can support riots that were built by another process --- riot/install.sh | 1 - riot/start.sh | 7 +++++++ riot/stop.sh | 12 +++++++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/riot/install.sh b/riot/install.sh index d9cc7292f0..a215b46cea 100755 --- a/riot/install.sh +++ b/riot/install.sh @@ -12,7 +12,6 @@ curl -L https://github.com/vector-im/riot-web/archive/${RIOT_BRANCH}.zip --outpu unzip riot.zip rm riot.zip mv riot-web-${RIOT_BRANCH} riot-web -cp config-template/config.json riot-web/ cd riot-web npm install npm run build diff --git a/riot/start.sh b/riot/start.sh index 2c76747e46..35f66f68e3 100755 --- a/riot/start.sh +++ b/riot/start.sh @@ -12,6 +12,13 @@ fi echo "running riot on http://localhost:$PORT ..." pushd riot-web/webapp/ > /dev/null + +# backup config file before we copy template +if [ -f config.json ]; then + mv config.json $CONFIG_BACKUP +fi +cp $BASE_DIR/config-template/config.json . + python -m SimpleHTTPServer $PORT > /dev/null 2>&1 & PID=$! popd > /dev/null diff --git a/riot/stop.sh b/riot/stop.sh index 7ed18887f9..0773174fd1 100755 --- a/riot/stop.sh +++ b/riot/stop.sh @@ -1,9 +1,19 @@ #!/bin/bash BASE_DIR=$(readlink -f $(dirname $0)) -cd $BASE_DIR PIDFILE=riot.pid +CONFIG_BACKUP=config.e2etests_backup.json + +cd $BASE_DIR + if [ -f $PIDFILE ]; then echo "stopping riot server ..." kill $(cat $PIDFILE) rm $PIDFILE + + # revert config file + cd riot-web/webapp + rm config.json + if [ -f $CONFIG_BACKUP ]; then + mv $CONFIG_BACKUP config.json + fi fi From a5c891144567442f5a3fee2a142af4842de7f6e2 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 27 Jul 2018 20:01:13 +0200 Subject: [PATCH 058/221] output document html on error and dont make a screenshot on submit --- start.js | 13 ++++++++++++- tests/signup.js | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/start.js b/start.js index c4eba26b58..c109430fb8 100644 --- a/start.js +++ b/start.js @@ -30,6 +30,7 @@ global.browser = null; let consoleLogs = null; let xhrLogs = null; +let globalPage = null; async function runTests() { console.log("running tests ..."); @@ -39,6 +40,7 @@ async function runTests() { } global.browser = await puppeteer.launch(options); const page = await helpers.newPage(); + globalPage = page; consoleLogs = helpers.logConsole(page); xhrLogs = helpers.logXHRRequests(page); @@ -66,12 +68,21 @@ function onSuccess() { console.log('all tests finished successfully'); } -function onFailure(err) { +async function onFailure(err) { + + let documentHtml = "no page"; + if (globalPage) { + documentHtml = await globalPage.content(); + } + console.log('failure: ', err); console.log('console.log output:'); console.log(consoleLogs.logs()); console.log('XHR requests:'); console.log(xhrLogs.logs()); + console.log('document html:'); + console.log(documentHtml); + process.exit(-1); } diff --git a/tests/signup.js b/tests/signup.js index 65044f900c..06035b61e3 100644 --- a/tests/signup.js +++ b/tests/signup.js @@ -55,7 +55,7 @@ module.exports = async function signup(page, username, password, homeserver) { const error_text = await helpers.tryGetInnertext(page, '.mx_Login_error'); assert.strictEqual(!!error_text, false); //submit form - await page.screenshot({path: "beforesubmit.png", fullPage: true}); + //await page.screenshot({path: "beforesubmit.png", fullPage: true}); await registerButton.click(); //confirm dialog saying you cant log back in without e-mail From d738b404ca207337ea8295280781c55e3c11439f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 30 Jul 2018 11:11:06 +0200 Subject: [PATCH 059/221] try upgrading puppeteer --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 48644df401..1cbdf5bd26 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,6 @@ "author": "", "license": "ISC", "dependencies": { - "puppeteer": "^1.5.0" + "puppeteer": "^1.6.0" } } From c357a0158dac92af4c30fc22258b56e2f4c7cc5f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 30 Jul 2018 13:40:23 +0200 Subject: [PATCH 060/221] no need to log contents of zip files --- riot/install.sh | 2 +- synapse/install.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/riot/install.sh b/riot/install.sh index a215b46cea..7f37fa9457 100755 --- a/riot/install.sh +++ b/riot/install.sh @@ -9,7 +9,7 @@ fi cd $BASE_DIR curl -L https://github.com/vector-im/riot-web/archive/${RIOT_BRANCH}.zip --output riot.zip -unzip riot.zip +unzip -q riot.zip rm riot.zip mv riot-web-${RIOT_BRANCH} riot-web cd riot-web diff --git a/synapse/install.sh b/synapse/install.sh index 8260f208c2..dc4a08cb41 100755 --- a/synapse/install.sh +++ b/synapse/install.sh @@ -17,7 +17,7 @@ cd $BASE_DIR mkdir -p installations/ curl https://codeload.github.com/matrix-org/synapse/zip/$SYNAPSE_BRANCH --output synapse.zip -unzip synapse.zip +unzip -q synapse.zip mv synapse-$SYNAPSE_BRANCH $SERVER_DIR cd $SERVER_DIR virtualenv -p python2.7 env From 9a2f3094866d56281a6de930ee2c170eeb12325b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 31 Jul 2018 10:08:01 +0200 Subject: [PATCH 061/221] xhr and console logs are done for all tests now, no need to do it in signup anymore --- tests/signup.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/signup.js b/tests/signup.js index 06035b61e3..70c478aed1 100644 --- a/tests/signup.js +++ b/tests/signup.js @@ -19,8 +19,6 @@ const acceptTerms = require('./consent'); const assert = require('assert'); module.exports = async function signup(page, username, password, homeserver) { - const consoleLogs = helpers.logConsole(page); - const xhrLogs = helpers.logXHRRequests(page); await page.goto(helpers.riotUrl('/#/register')); //click 'Custom server' radio button if (homeserver) { From 387657721898c3d1618ab2ae29bbc2a759548288 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 31 Jul 2018 10:25:26 +0200 Subject: [PATCH 062/221] log when using external chrome! --- start.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/start.js b/start.js index c109430fb8..4912f901c1 100644 --- a/start.js +++ b/start.js @@ -36,7 +36,9 @@ async function runTests() { console.log("running tests ..."); const options = {}; if (process.env.CHROME_PATH) { - options.executablePath = process.env.CHROME_PATH; + const path = process.env.CHROME_PATH; + console.log(`(using external chrome/chromium at ${path}, make sure it's compatible with puppeteer)`); + options.executablePath = path; } global.browser = await puppeteer.launch(options); const page = await helpers.newPage(); From f57628e3d0a0b6b53484df6d95249da4cde6935c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 31 Jul 2018 12:54:39 +0200 Subject: [PATCH 063/221] dont swallow riot server errors --- riot/start.sh | 42 ++++++++++++++++++++++++++++++++++-------- riot/stop.sh | 3 ++- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/riot/start.sh b/riot/start.sh index 35f66f68e3..1127535d2b 100755 --- a/riot/start.sh +++ b/riot/start.sh @@ -1,16 +1,15 @@ #!/bin/bash PORT=5000 BASE_DIR=$(readlink -f $(dirname $0)) -PIDFILE=riot.pid +PIDFILE=$BASE_DIR/riot.pid CONFIG_BACKUP=config.e2etests_backup.json -cd $BASE_DIR/ - if [ -f $PIDFILE ]; then exit fi -echo "running riot on http://localhost:$PORT ..." +cd $BASE_DIR/ +echo -n "starting riot on http://localhost:$PORT ... " pushd riot-web/webapp/ > /dev/null # backup config file before we copy template @@ -19,7 +18,34 @@ if [ -f config.json ]; then fi cp $BASE_DIR/config-template/config.json . -python -m SimpleHTTPServer $PORT > /dev/null 2>&1 & -PID=$! -popd > /dev/null -echo $PID > $PIDFILE +LOGFILE=$(mktemp) +# run web server in the background, showing output on error +( + python -m SimpleHTTPServer $PORT > $LOGFILE 2>&1 & + PID=$! + echo $PID > $PIDFILE + # wait so subshell does not exit + # otherwise sleep below would not work + wait $PID; RESULT=$? + + # NOT expected SIGTERM (128 + 15) + # from stop.sh? + if [ $RESULT -ne 143 ]; then + echo "failed" + cat $LOGFILE + rm $PIDFILE 2> /dev/null + fi + rm $LOGFILE + exit $RESULT +)& +# to be able to return the exit code for immediate errors (like address already in use) +# we wait for a short amount of time in the background and exit when the first +# child process exists +sleep 0.5 & +# wait the first child process to exit (python or sleep) +wait -n; RESULT=$? +# return exit code of first child to exit +if [ $RESULT -eq 0 ]; then + echo "running" +fi +exit $RESULT diff --git a/riot/stop.sh b/riot/stop.sh index 0773174fd1..8d3c925c80 100755 --- a/riot/stop.sh +++ b/riot/stop.sh @@ -7,8 +7,9 @@ cd $BASE_DIR if [ -f $PIDFILE ]; then echo "stopping riot server ..." - kill $(cat $PIDFILE) + PID=$(cat $PIDFILE) rm $PIDFILE + kill $PID # revert config file cd riot-web/webapp From 97fa7e03d13bd73726bcbd0e6c556a8e336163a4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 31 Jul 2018 14:45:14 +0200 Subject: [PATCH 064/221] dont swallow synapse startup errors --- synapse/start.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/synapse/start.sh b/synapse/start.sh index 785909a0b1..12b89b31ed 100755 --- a/synapse/start.sh +++ b/synapse/start.sh @@ -3,4 +3,10 @@ BASE_DIR=$(readlink -f $(dirname $0)) cd $BASE_DIR cd installations/consent source env/bin/activate -./synctl start 2> /dev/null +LOGFILE=$(mktemp) +./synctl start 2> $LOGFILE +EXIT_CODE=$? +if [ $EXIT_CODE -ne 0 ]; then + cat $LOGFILE +fi +exit $EXIT_CODE \ No newline at end of file From 7c91ecab7eaa7993e4d64faf1a35d5ca183987d4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 7 Aug 2018 16:45:34 +0200 Subject: [PATCH 065/221] create session object to scope a user, move helper methods there --- helpers.js | 149 ------------------ src/session.js | 147 +++++++++++++++++ {tests => src/tests}/consent.js | 9 +- {tests => src/tests}/create-room.js | 13 +- {tests => src/tests}/join.js | 15 +- .../tests}/server-notices-consent.js | 15 +- {tests => src/tests}/signup.js | 35 ++-- start.js | 36 ++--- 8 files changed, 203 insertions(+), 216 deletions(-) delete mode 100644 helpers.js create mode 100644 src/session.js rename {tests => src/tests}/consent.js (70%) rename {tests => src/tests}/create-room.js (57%) rename {tests => src/tests}/join.js (53%) rename {tests => src/tests}/server-notices-consent.js (68%) rename {tests => src/tests}/signup.js (60%) diff --git a/helpers.js b/helpers.js deleted file mode 100644 index e830824e7c..0000000000 --- a/helpers.js +++ /dev/null @@ -1,149 +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. -*/ - -// puppeteer helpers - -// TODO: rename to queryAndInnertext? -async function tryGetInnertext(page, selector) { - const field = await page.$(selector); - if (field != null) { - const text_handle = await field.getProperty('innerText'); - return await text_handle.jsonValue(); - } - return null; -} - -async function innerText(page, field) { - const text_handle = await field.getProperty('innerText'); - return await text_handle.jsonValue(); -} - -async function newPage() { - const page = await browser.newPage(); - await page.setViewport({ - width: 1280, - height: 800 - }); - return page; -} - -function logConsole(page) { - let buffer = ""; - page.on('console', msg => { - buffer += msg.text() + '\n'; - }); - return { - logs() { - return buffer; - } - } -} - -function logXHRRequests(page) { - let buffer = ""; - page.on('requestfinished', async (req) => { - const type = req.resourceType(); - const response = await req.response(); - //if (type === 'xhr' || type === 'fetch') { - buffer += `${type} ${response.status()} ${req.method()} ${req.url()} \n`; - // if (req.method() === "POST") { - // buffer += " Post data: " + req.postData(); - // } - //} - }); - return { - logs() { - return buffer; - } - } -} - -async function getOuterHTML(element_handle) { - const html_handle = await element_handle.getProperty('outerHTML'); - return await html_handle.jsonValue(); -} - -async function printElements(label, elements) { - console.log(label, await Promise.all(elements.map(getOuterHTML))); -} - -async function replaceInputText(input, text) { - // click 3 times to select all text - await input.click({clickCount: 3}); - // then remove it with backspace - await input.press('Backspace'); - // and type the new text - await input.type(text); -} - -// TODO: rename to waitAndQuery(Single)? -async function waitAndQuerySelector(page, selector, timeout = 500) { - await page.waitForSelector(selector, {visible: true, timeout}); - return await page.$(selector); -} - -async function waitAndQueryAll(page, selector, timeout = 500) { - await page.waitForSelector(selector, {visible: true, timeout}); - return await page.$$(selector); -} - -function waitForNewPage(timeout = 500) { - return new Promise((resolve, reject) => { - const timeoutHandle = setTimeout(() => { - browser.removeEventListener('targetcreated', callback); - reject(new Error(`timeout of ${timeout}ms for waitForNewPage elapsed`)); - }, timeout); - - const callback = async (target) => { - clearTimeout(timeoutHandle); - const page = await target.page(); - resolve(page); - }; - - browser.once('targetcreated', callback); - }); -} - -// other helpers - -function randomInt(max) { - return Math.ceil(Math.random()*max); -} - -function riotUrl(path) { - return riotserver + path; -} - -function delay(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -module.exports = { - tryGetInnertext, - innerText, - newPage, - logConsole, - logXHRRequests, - getOuterHTML, - printElements, - replaceInputText, - waitAndQuerySelector, - waitAndQueryAll, - waitForNewPage, - randomInt, - riotUrl, - delay, -} \ No newline at end of file diff --git a/src/session.js b/src/session.js new file mode 100644 index 0000000000..51bad4a3c9 --- /dev/null +++ b/src/session.js @@ -0,0 +1,147 @@ +/* +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 puppeteer = require('puppeteer'); + +module.exports = class RiotSession { + constructor(browser, page, username, riotserver) { + this.browser = browser; + this.page = page; + this.riotserver = riotserver; + this.username = username; + } + + static async create(username, puppeteerOptions, riotserver) { + const browser = await puppeteer.launch(puppeteerOptions); + const page = await browser.newPage(); + await page.setViewport({ + width: 1280, + height: 800 + }); + return new RiotSession(browser, page, username, riotserver); + } + + async tryGetInnertext(selector) { + const field = await this.page.$(selector); + if (field != null) { + const text_handle = await field.getProperty('innerText'); + return await text_handle.jsonValue(); + } + return null; + } + + async innerText(field) { + const text_handle = await field.getProperty('innerText'); + return await text_handle.jsonValue(); + } + + logConsole() { + let buffer = ""; + this.page.on('console', msg => { + buffer += msg.text() + '\n'; + }); + return { + logs() { + return buffer; + } + } + } + + logXHRRequests() { + let buffer = ""; + this.page.on('requestfinished', async (req) => { + const type = req.resourceType(); + const response = await req.response(); + //if (type === 'xhr' || type === 'fetch') { + buffer += `${type} ${response.status()} ${req.method()} ${req.url()} \n`; + // if (req.method() === "POST") { + // buffer += " Post data: " + req.postData(); + // } + //} + }); + return { + logs() { + return buffer; + } + } + } + + async getOuterHTML(element_handle) { + const html_handle = await element_handle.getProperty('outerHTML'); + return await html_handle.jsonValue(); + } + + async printElements(label, elements) { + console.log(label, await Promise.all(elements.map(getOuterHTML))); + } + + async replaceInputText(input, text) { + // click 3 times to select all text + await input.click({clickCount: 3}); + // then remove it with backspace + await input.press('Backspace'); + // and type the new text + await input.type(text); + } + + // TODO: rename to waitAndQuery(Single)? + async waitAndQuerySelector(selector, timeout = 500) { + await this.page.waitForSelector(selector, {visible: true, timeout}); + return await this.page.$(selector); + } + + async waitAndQueryAll(selector, timeout = 500) { + await this.page.waitForSelector(selector, {visible: true, timeout}); + return await this.page.$$(selector); + } + + waitForNewPage(timeout = 500) { + return new Promise((resolve, reject) => { + const timeoutHandle = setTimeout(() => { + this.browser.removeEventListener('targetcreated', callback); + reject(new Error(`timeout of ${timeout}ms for waitForNewPage elapsed`)); + }, timeout); + + const callback = async (target) => { + clearTimeout(timeoutHandle); + const page = await target.page(); + resolve(page); + }; + + this.browser.once('targetcreated', callback); + }); + } + + waitForSelector(selector) { + return this.page.waitForSelector(selector); + } + + goto(url) { + return this.page.goto(url); + } + + riotUrl(path) { + return this.riotserver + path; + } + + delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + close() { + return this.browser.close(); + } +} diff --git a/tests/consent.js b/src/tests/consent.js similarity index 70% rename from tests/consent.js rename to src/tests/consent.js index 3c8ada9a5e..09026a3082 100644 --- a/tests/consent.js +++ b/src/tests/consent.js @@ -14,15 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -const helpers = require('../helpers'); const assert = require('assert'); -module.exports = async function acceptTerms(page) { - const reviewTermsButton = await helpers.waitAndQuerySelector(page, '.mx_QuestionDialog button.mx_Dialog_primary', 5000); - const termsPagePromise = helpers.waitForNewPage(); +module.exports = async function acceptTerms(session) { + const reviewTermsButton = await session.waitAndQuerySelector('.mx_QuestionDialog button.mx_Dialog_primary', 5000); + const termsPagePromise = session.waitForNewPage(); await reviewTermsButton.click(); const termsPage = await termsPagePromise; const acceptButton = await termsPage.$('input[type=submit]'); await acceptButton.click(); - await helpers.delay(500); //TODO yuck, timers + await session.delay(500); //TODO yuck, timers } \ No newline at end of file diff --git a/tests/create-room.js b/src/tests/create-room.js similarity index 57% rename from tests/create-room.js rename to src/tests/create-room.js index 4c9004bcaf..948e0b115f 100644 --- a/tests/create-room.js +++ b/src/tests/create-room.js @@ -14,19 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -const helpers = require('../helpers'); const assert = require('assert'); -module.exports = async function createRoom(page, roomName) { +module.exports = async function createRoom(session, roomName) { //TODO: brittle selector - const createRoomButton = await helpers.waitAndQuerySelector(page, '.mx_RoleButton[aria-label="Create new room"]'); + const createRoomButton = await session.waitAndQuerySelector('.mx_RoleButton[aria-label="Create new room"]'); await createRoomButton.click(); - const roomNameInput = await helpers.waitAndQuerySelector(page, '.mx_CreateRoomDialog_input'); - await helpers.replaceInputText(roomNameInput, roomName); + const roomNameInput = await session.waitAndQuerySelector('.mx_CreateRoomDialog_input'); + await session.replaceInputText(roomNameInput, roomName); - const createButton = await helpers.waitAndQuerySelector(page, '.mx_Dialog_primary'); + const createButton = await session.waitAndQuerySelector('.mx_Dialog_primary'); await createButton.click(); - await page.waitForSelector('.mx_MessageComposer'); + await session.waitForSelector('.mx_MessageComposer'); } \ No newline at end of file diff --git a/tests/join.js b/src/tests/join.js similarity index 53% rename from tests/join.js rename to src/tests/join.js index ea16a93936..a359d6ef64 100644 --- a/tests/join.js +++ b/src/tests/join.js @@ -14,22 +14,21 @@ See the License for the specific language governing permissions and limitations under the License. */ -const helpers = require('../helpers'); const assert = require('assert'); -module.exports = async function join(page, roomName) { +module.exports = async function join(session, roomName) { //TODO: brittle selector - const directoryButton = await helpers.waitAndQuerySelector(page, '.mx_RoleButton[aria-label="Room directory"]'); + const directoryButton = await session.waitAndQuerySelector('.mx_RoleButton[aria-label="Room directory"]'); await directoryButton.click(); - const roomInput = await helpers.waitAndQuerySelector(page, '.mx_DirectorySearchBox_input'); - await helpers.replaceInputText(roomInput, roomName); + const roomInput = await session.waitAndQuerySelector('.mx_DirectorySearchBox_input'); + await session.replaceInputText(roomInput, roomName); - const firstRoomLabel = await helpers.waitAndQuerySelector(page, '.mx_RoomDirectory_table .mx_RoomDirectory_name:first-child'); + const firstRoomLabel = await session.waitAndQuerySelector('.mx_RoomDirectory_table .mx_RoomDirectory_name:first-child'); await firstRoomLabel.click(); - const joinLink = await helpers.waitAndQuerySelector(page, '.mx_RoomPreviewBar_join_text a'); + const joinLink = await session.waitAndQuerySelector('.mx_RoomPreviewBar_join_text a'); await joinLink.click(); - await page.waitForSelector('.mx_MessageComposer'); + await session.waitForSelector('.mx_MessageComposer'); } \ No newline at end of file diff --git a/tests/server-notices-consent.js b/src/tests/server-notices-consent.js similarity index 68% rename from tests/server-notices-consent.js rename to src/tests/server-notices-consent.js index 2689036a96..0eb4cd8722 100644 --- a/tests/server-notices-consent.js +++ b/src/tests/server-notices-consent.js @@ -14,14 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -const helpers = require('../helpers'); const assert = require('assert'); -module.exports = async function acceptServerNoticesInviteAndConsent(page, name) { +module.exports = async function acceptServerNoticesInviteAndConsent(session, name) { //TODO: brittle selector - const invitesHandles = await helpers.waitAndQueryAll(page, '.mx_RoomTile_name.mx_RoomTile_invite'); + const invitesHandles = await session.waitAndQueryAll('.mx_RoomTile_name.mx_RoomTile_invite'); const invitesWithText = await Promise.all(invitesHandles.map(async (inviteHandle) => { - const text = await helpers.innerText(page, inviteHandle); + const text = await session.innerText(inviteHandle); return {inviteHandle, text}; })); const inviteHandle = invitesWithText.find(({inviteHandle, text}) => { @@ -30,15 +29,15 @@ module.exports = async function acceptServerNoticesInviteAndConsent(page, name) await inviteHandle.click(); - const acceptInvitationLink = await helpers.waitAndQuerySelector(page, ".mx_RoomPreviewBar_join_text a:first-child"); + const acceptInvitationLink = await session.waitAndQuerySelector(".mx_RoomPreviewBar_join_text a:first-child"); await acceptInvitationLink.click(); - const consentLink = await helpers.waitAndQuerySelector(page, ".mx_EventTile_body a", 1000); + const consentLink = await session.waitAndQuerySelector(".mx_EventTile_body a", 1000); - const termsPagePromise = helpers.waitForNewPage(); + const termsPagePromise = session.waitForNewPage(); await consentLink.click(); const termsPage = await termsPagePromise; const acceptButton = await termsPage.$('input[type=submit]'); await acceptButton.click(); - await helpers.delay(500); //TODO yuck, timers + await session.delay(500); //TODO yuck, timers } \ No newline at end of file diff --git a/tests/signup.js b/src/tests/signup.js similarity index 60% rename from tests/signup.js rename to src/tests/signup.js index 70c478aed1..db6ad6208a 100644 --- a/tests/signup.js +++ b/src/tests/signup.js @@ -14,55 +14,54 @@ See the License for the specific language governing permissions and limitations under the License. */ -const helpers = require('../helpers'); const acceptTerms = require('./consent'); const assert = require('assert'); -module.exports = async function signup(page, username, password, homeserver) { - await page.goto(helpers.riotUrl('/#/register')); +module.exports = async function signup(session, username, password, homeserver) { + await session.goto(session.riotUrl('/#/register')); //click 'Custom server' radio button if (homeserver) { - const advancedRadioButton = await helpers.waitAndQuerySelector(page, '#advanced'); + const advancedRadioButton = await session.waitAndQuerySelector('#advanced'); await advancedRadioButton.click(); } // wait until register button is visible - await page.waitForSelector('.mx_Login_submit[value=Register]', {visible: true, timeout: 500}); + await session.waitForSelector('.mx_Login_submit[value=Register]', {visible: true, timeout: 500}); //fill out form - const loginFields = await page.$$('.mx_Login_field'); + const loginFields = await session.page.$$('.mx_Login_field'); assert.strictEqual(loginFields.length, 7); const usernameField = loginFields[2]; const passwordField = loginFields[3]; const passwordRepeatField = loginFields[4]; const hsurlField = loginFields[5]; - await helpers.replaceInputText(usernameField, username); - await helpers.replaceInputText(passwordField, password); - await helpers.replaceInputText(passwordRepeatField, password); + await session.replaceInputText(usernameField, username); + await session.replaceInputText(passwordField, password); + await session.replaceInputText(passwordRepeatField, password); if (homeserver) { - await page.waitForSelector('.mx_ServerConfig', {visible: true, timeout: 500}); - await helpers.replaceInputText(hsurlField, homeserver); + await session.waitForSelector('.mx_ServerConfig', {visible: true, timeout: 500}); + await session.replaceInputText(hsurlField, homeserver); } //wait over a second because Registration/ServerConfig have a 1000ms //delay to internally set the homeserver url //see Registration::render and ServerConfig::props::delayTimeMs - await helpers.delay(1200); + await session.delay(1200); /// focus on the button to make sure error validation /// has happened before checking the form is good to go - const registerButton = await page.$('.mx_Login_submit'); + const registerButton = await session.page.$('.mx_Login_submit'); await registerButton.focus(); //check no errors - const error_text = await helpers.tryGetInnertext(page, '.mx_Login_error'); + const error_text = await session.tryGetInnertext('.mx_Login_error'); assert.strictEqual(!!error_text, false); //submit form //await page.screenshot({path: "beforesubmit.png", fullPage: true}); await registerButton.click(); //confirm dialog saying you cant log back in without e-mail - const continueButton = await helpers.waitAndQuerySelector(page, '.mx_QuestionDialog button.mx_Dialog_primary'); + const continueButton = await session.waitAndQuerySelector('.mx_QuestionDialog button.mx_Dialog_primary'); await continueButton.click(); //wait for registration to finish so the hash gets set //onhashchange better? - await helpers.delay(2000); + await session.delay(2000); - const url = page.url(); - assert.strictEqual(url, helpers.riotUrl('/#/home')); + const url = session.page.url(); + assert.strictEqual(url, session.riotUrl('/#/home')); } diff --git a/start.js b/start.js index 4912f901c1..c1aecb65aa 100644 --- a/start.js +++ b/start.js @@ -14,19 +14,16 @@ See the License for the specific language governing permissions and limitations under the License. */ -const puppeteer = require('puppeteer'); -const helpers = require('./helpers'); const assert = require('assert'); +const RiotSession = require('./src/session'); -const signup = require('./tests/signup'); -const join = require('./tests/join'); -const createRoom = require('./tests/create-room'); -const acceptServerNoticesInviteAndConsent = require('./tests/server-notices-consent'); +const signup = require('./src/tests/signup'); +const join = require('./src/tests/join'); +const createRoom = require('./src/tests/create-room'); +const acceptServerNoticesInviteAndConsent = require('./src/tests/server-notices-consent'); const homeserver = 'http://localhost:8008'; - -global.riotserver = 'http://localhost:5000'; -global.browser = null; +const riotserver = 'http://localhost:5000'; let consoleLogs = null; let xhrLogs = null; @@ -40,30 +37,27 @@ async function runTests() { console.log(`(using external chrome/chromium at ${path}, make sure it's compatible with puppeteer)`); options.executablePath = path; } - global.browser = await puppeteer.launch(options); - const page = await helpers.newPage(); - globalPage = page; - consoleLogs = helpers.logConsole(page); - xhrLogs = helpers.logXHRRequests(page); + const alice = await RiotSession.create("alice", options, riotserver); + + consoleLogs = alice.logConsole(); + xhrLogs = alice.logXHRRequests(); - const username = 'user-' + helpers.randomInt(10000); - const password = 'testtest'; - process.stdout.write(`* signing up as ${username} ... `); - await signup(page, username, password); + process.stdout.write(`* signing up as ${alice.username} ... `); + await signup(alice, alice.username, 'testtest'); process.stdout.write('done\n'); const noticesName = "Server Notices"; process.stdout.write(`* accepting "${noticesName}" and accepting terms & conditions ... `); - await acceptServerNoticesInviteAndConsent(page, noticesName); + await acceptServerNoticesInviteAndConsent(alice, noticesName); process.stdout.write('done\n'); const room = 'test'; process.stdout.write(`* creating room ${room} ... `); - await createRoom(page, room); + await createRoom(alice, room); process.stdout.write('done\n'); - await browser.close(); + await alice.close(); } function onSuccess() { From 6b843eacfcf76be987273e63100e11b446a7102b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 7 Aug 2018 17:09:43 +0200 Subject: [PATCH 066/221] move log buffers into session, start logging implicitely --- src/session.js | 37 +++++++++++++++++++++++++++---------- start.js | 32 ++++++++++++++------------------ 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/src/session.js b/src/session.js index 51bad4a3c9..f8f41b20b8 100644 --- a/src/session.js +++ b/src/session.js @@ -16,12 +16,33 @@ limitations under the License. const puppeteer = require('puppeteer'); +class LogBuffer { + constructor(page, eventName, eventMapper, reduceAsync=false, initialValue = "") { + this.buffer = initialValue; + page.on(eventName, (arg) => { + const result = eventMapper(arg); + if (reduceAsync) { + result.then((r) => this.buffer += r); + } + else { + this.buffer += result; + } + }); + } +} + module.exports = class RiotSession { constructor(browser, page, username, riotserver) { this.browser = browser; this.page = page; this.riotserver = riotserver; this.username = username; + this.consoleLog = new LogBuffer(page, "console", (msg) => `${msg.text()}\n`); + this.networkLog = new LogBuffer(page, "requestfinished", async (req) => { + const type = req.resourceType(); + const response = await req.response(); + return `${type} ${response.status()} ${req.method()} ${req.url()} \n`; + }, true); } static async create(username, puppeteerOptions, riotserver) { @@ -48,16 +69,12 @@ module.exports = class RiotSession { return await text_handle.jsonValue(); } - logConsole() { - let buffer = ""; - this.page.on('console', msg => { - buffer += msg.text() + '\n'; - }); - return { - logs() { - return buffer; - } - } + consoleLogs() { + return this.consoleLog.buffer; + } + + networkLogs() { + return this.networkLog.buffer; } logXHRRequests() { diff --git a/start.js b/start.js index c1aecb65aa..0aa2cb9364 100644 --- a/start.js +++ b/start.js @@ -25,9 +25,7 @@ const acceptServerNoticesInviteAndConsent = require('./src/tests/server-notices- const homeserver = 'http://localhost:8008'; const riotserver = 'http://localhost:5000'; -let consoleLogs = null; -let xhrLogs = null; -let globalPage = null; +let sessions = []; async function runTests() { console.log("running tests ..."); @@ -39,9 +37,7 @@ async function runTests() { } const alice = await RiotSession.create("alice", options, riotserver); - - consoleLogs = alice.logConsole(); - xhrLogs = alice.logXHRRequests(); + sessions.push(alice); process.stdout.write(`* signing up as ${alice.username} ... `); await signup(alice, alice.username, 'testtest'); @@ -65,19 +61,19 @@ function onSuccess() { } async function onFailure(err) { - - let documentHtml = "no page"; - if (globalPage) { - documentHtml = await globalPage.content(); - } - console.log('failure: ', err); - console.log('console.log output:'); - console.log(consoleLogs.logs()); - console.log('XHR requests:'); - console.log(xhrLogs.logs()); - console.log('document html:'); - console.log(documentHtml); + for(var i = 0; i < sessions.length; ++i) { + const session = sessions[i]; + documentHtml = await session.page.content(); + console.log(`---------------- START OF ${session.username} LOGS ----------------`); + console.log('---------------- console.log output:'); + console.log(session.consoleLogs()); + console.log('---------------- network requests:'); + console.log(session.networkLogs()); + console.log('---------------- document html:'); + console.log(documentHtml); + console.log(`---------------- END OF ${session.username} LOGS ----------------`); + } process.exit(-1); } From 4c0ab117bfab1ce4c4caebe83c5f67c1e7488feb Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 7 Aug 2018 17:16:27 +0200 Subject: [PATCH 067/221] move outputting steps to session to scope it to username --- src/session.js | 15 +++++++++++++++ start.js | 14 +++++++------- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/session.js b/src/session.js index f8f41b20b8..5b0f78ccd3 100644 --- a/src/session.js +++ b/src/session.js @@ -31,6 +31,20 @@ class LogBuffer { } } +class Logger { + constructor(username) { + this.username = username; + } + + step(description) { + process.stdout.write(` * ${this.username} ${description} ... `); + } + + done() { + process.stdout.write("done\n"); + } +} + module.exports = class RiotSession { constructor(browser, page, username, riotserver) { this.browser = browser; @@ -43,6 +57,7 @@ module.exports = class RiotSession { const response = await req.response(); return `${type} ${response.status()} ${req.method()} ${req.url()} \n`; }, true); + this.log = new Logger(this.username); } static async create(username, puppeteerOptions, riotserver) { diff --git a/start.js b/start.js index 0aa2cb9364..9b0ed716e0 100644 --- a/start.js +++ b/start.js @@ -39,19 +39,19 @@ async function runTests() { const alice = await RiotSession.create("alice", options, riotserver); sessions.push(alice); - process.stdout.write(`* signing up as ${alice.username} ... `); + alice.log.step("signs up"); await signup(alice, alice.username, 'testtest'); - process.stdout.write('done\n'); - + alice.log.done(); + const noticesName = "Server Notices"; - process.stdout.write(`* accepting "${noticesName}" and accepting terms & conditions ... `); + alice.log.step(`accepts "${noticesName}" invite and accepting terms & conditions`); await acceptServerNoticesInviteAndConsent(alice, noticesName); - process.stdout.write('done\n'); + alice.log.done(); const room = 'test'; - process.stdout.write(`* creating room ${room} ... `); + alice.log.step(`creates room ${room}`); await createRoom(alice, room); - process.stdout.write('done\n'); + alice.log.done(); await alice.close(); } From 5fe386119087db97500eed5c379b80cb39b2a086 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 7 Aug 2018 17:23:01 +0200 Subject: [PATCH 068/221] create second user and join room first user creates --- start.js | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/start.js b/start.js index 9b0ed716e0..28eba781e5 100644 --- a/start.js +++ b/start.js @@ -27,6 +27,21 @@ const riotserver = 'http://localhost:5000'; let sessions = []; +async function createUser(username, options, riotserver) { + const session = await RiotSession.create(username, options, riotserver); + sessions.push(session); + + session.log.step("signs up"); + await signup(session, session.username, 'testtest'); + session.log.done(); + + const noticesName = "Server Notices"; + session.log.step(`accepts "${noticesName}" invite and accepting terms & conditions`); + await acceptServerNoticesInviteAndConsent(session, noticesName); + session.log.done(); + return session; +} + async function runTests() { console.log("running tests ..."); const options = {}; @@ -36,24 +51,21 @@ async function runTests() { options.executablePath = path; } - const alice = await RiotSession.create("alice", options, riotserver); - sessions.push(alice); - - alice.log.step("signs up"); - await signup(alice, alice.username, 'testtest'); - alice.log.done(); - - const noticesName = "Server Notices"; - alice.log.step(`accepts "${noticesName}" invite and accepting terms & conditions`); - await acceptServerNoticesInviteAndConsent(alice, noticesName); - alice.log.done(); + const alice = await createUser("alice", options, riotserver); + const bob = await createUser("bob", options, riotserver); const room = 'test'; alice.log.step(`creates room ${room}`); await createRoom(alice, room); alice.log.done(); + bob.log.step(`joins room ${room}`); + await createRoom(bob, room); + bob.log.done(); + + await alice.close(); + await bob.close(); } function onSuccess() { From 4e7df2126bcd42c6fd9a186d41fb68cc2ec767ff Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 7 Aug 2018 17:58:58 +0200 Subject: [PATCH 069/221] move step logging to tests, DRY; put test scenario in separate file, less globals --- src/scenario.js | 37 ++++++++++ src/tests/create-room.js | 2 + src/tests/join.js | 2 + src/tests/server-notices-consent.js | 8 ++- src/tests/signup.js | 2 + start.js | 102 ++++++++++++---------------- 6 files changed, 90 insertions(+), 63 deletions(-) create mode 100644 src/scenario.js diff --git a/src/scenario.js b/src/scenario.js new file mode 100644 index 0000000000..ee049a14f9 --- /dev/null +++ b/src/scenario.js @@ -0,0 +1,37 @@ +/* +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 signup = require('./tests/signup'); +const join = require('./tests/join'); +const createRoom = require('./tests/create-room'); +const acceptServerNoticesInviteAndConsent = require('./tests/server-notices-consent'); + +module.exports = async function scenario(createSession) { + async function createUser(username) { + const session = await createSession(username); + await signup(session, session.username, 'testtest'); + const noticesName = "Server Notices"; + await acceptServerNoticesInviteAndConsent(session, noticesName); + return session; + } + + const alice = await createUser("alice"); + const bob = await createUser("bob"); + const room = 'test'; + await createRoom(alice, room); + // await join(bob, room); +} diff --git a/src/tests/create-room.js b/src/tests/create-room.js index 948e0b115f..eff92baf83 100644 --- a/src/tests/create-room.js +++ b/src/tests/create-room.js @@ -17,6 +17,7 @@ limitations under the License. const assert = require('assert'); module.exports = async function createRoom(session, roomName) { + session.log.step(`creates room ${roomName}`); //TODO: brittle selector const createRoomButton = await session.waitAndQuerySelector('.mx_RoleButton[aria-label="Create new room"]'); await createRoomButton.click(); @@ -28,4 +29,5 @@ module.exports = async function createRoom(session, roomName) { await createButton.click(); await session.waitForSelector('.mx_MessageComposer'); + session.log.done(); } \ No newline at end of file diff --git a/src/tests/join.js b/src/tests/join.js index a359d6ef64..72d4fe10cf 100644 --- a/src/tests/join.js +++ b/src/tests/join.js @@ -17,6 +17,7 @@ limitations under the License. const assert = require('assert'); module.exports = async function join(session, roomName) { + session.log.step(`joins room ${roomName}`); //TODO: brittle selector const directoryButton = await session.waitAndQuerySelector('.mx_RoleButton[aria-label="Room directory"]'); await directoryButton.click(); @@ -31,4 +32,5 @@ module.exports = async function join(session, roomName) { await joinLink.click(); await session.waitForSelector('.mx_MessageComposer'); + session.log.done(); } \ No newline at end of file diff --git a/src/tests/server-notices-consent.js b/src/tests/server-notices-consent.js index 0eb4cd8722..53a318a169 100644 --- a/src/tests/server-notices-consent.js +++ b/src/tests/server-notices-consent.js @@ -16,7 +16,8 @@ limitations under the License. const assert = require('assert'); -module.exports = async function acceptServerNoticesInviteAndConsent(session, name) { +module.exports = async function acceptServerNoticesInviteAndConsent(session, noticesName) { + session.log.step(`accepts "${noticesName}" invite and accepting terms & conditions`); //TODO: brittle selector const invitesHandles = await session.waitAndQueryAll('.mx_RoomTile_name.mx_RoomTile_invite'); const invitesWithText = await Promise.all(invitesHandles.map(async (inviteHandle) => { @@ -24,7 +25,7 @@ module.exports = async function acceptServerNoticesInviteAndConsent(session, nam return {inviteHandle, text}; })); const inviteHandle = invitesWithText.find(({inviteHandle, text}) => { - return text.trim() === name; + return text.trim() === noticesName; }).inviteHandle; await inviteHandle.click(); @@ -40,4 +41,5 @@ module.exports = async function acceptServerNoticesInviteAndConsent(session, nam const acceptButton = await termsPage.$('input[type=submit]'); await acceptButton.click(); await session.delay(500); //TODO yuck, timers -} \ No newline at end of file + session.log.done(); +} \ No newline at end of file diff --git a/src/tests/signup.js b/src/tests/signup.js index db6ad6208a..6b3f06c12c 100644 --- a/src/tests/signup.js +++ b/src/tests/signup.js @@ -18,6 +18,7 @@ const acceptTerms = require('./consent'); const assert = require('assert'); module.exports = async function signup(session, username, password, homeserver) { + session.log.step("signs up"); await session.goto(session.riotUrl('/#/register')); //click 'Custom server' radio button if (homeserver) { @@ -64,4 +65,5 @@ module.exports = async function signup(session, username, password, homeserver) const url = session.page.url(); assert.strictEqual(url, session.riotUrl('/#/home')); + session.log.done(); } diff --git a/start.js b/start.js index 28eba781e5..5e235dd1ef 100644 --- a/start.js +++ b/start.js @@ -16,33 +16,13 @@ limitations under the License. const assert = require('assert'); const RiotSession = require('./src/session'); +const scenario = require('./src/scenario'); -const signup = require('./src/tests/signup'); -const join = require('./src/tests/join'); -const createRoom = require('./src/tests/create-room'); -const acceptServerNoticesInviteAndConsent = require('./src/tests/server-notices-consent'); - -const homeserver = 'http://localhost:8008'; const riotserver = 'http://localhost:5000'; -let sessions = []; - -async function createUser(username, options, riotserver) { - const session = await RiotSession.create(username, options, riotserver); - sessions.push(session); - - session.log.step("signs up"); - await signup(session, session.username, 'testtest'); - session.log.done(); - - const noticesName = "Server Notices"; - session.log.step(`accepts "${noticesName}" invite and accepting terms & conditions`); - await acceptServerNoticesInviteAndConsent(session, noticesName); - session.log.done(); - return session; -} - async function runTests() { + let sessions = []; + console.log("running tests ..."); const options = {}; if (process.env.CHROME_PATH) { @@ -51,43 +31,45 @@ async function runTests() { options.executablePath = path; } - const alice = await createUser("alice", options, riotserver); - const bob = await createUser("bob", options, riotserver); - - const room = 'test'; - alice.log.step(`creates room ${room}`); - await createRoom(alice, room); - alice.log.done(); - - bob.log.step(`joins room ${room}`); - await createRoom(bob, room); - bob.log.done(); - - - await alice.close(); - await bob.close(); -} - -function onSuccess() { - console.log('all tests finished successfully'); -} - -async function onFailure(err) { - console.log('failure: ', err); - for(var i = 0; i < sessions.length; ++i) { - const session = sessions[i]; - documentHtml = await session.page.content(); - console.log(`---------------- START OF ${session.username} LOGS ----------------`); - console.log('---------------- console.log output:'); - console.log(session.consoleLogs()); - console.log('---------------- network requests:'); - console.log(session.networkLogs()); - console.log('---------------- document html:'); - console.log(documentHtml); - console.log(`---------------- END OF ${session.username} LOGS ----------------`); + async function createSession(username) { + const session = await RiotSession.create(username, options, riotserver); + sessions.push(session); + return session; + } + + let failure = false; + try { + await scenario(createSession); + } catch(err) { + console.log('failure: ', err); + for(let i = 0; i < sessions.length; ++i) { + const session = sessions[i]; + documentHtml = await session.page.content(); + console.log(`---------------- START OF ${session.username} LOGS ----------------`); + console.log('---------------- console.log output:'); + console.log(session.consoleLogs()); + console.log('---------------- network requests:'); + console.log(session.networkLogs()); + console.log('---------------- document html:'); + console.log(documentHtml); + console.log(`---------------- END OF ${session.username} LOGS ----------------`); + } + failure = true; + } + + for(let i = 0; i < sessions.length; ++i) { + const session = sessions[i]; + await session.close(); + } + + if (failure) { + process.exit(-1); + } else { + console.log('all tests finished successfully'); } - - process.exit(-1); } -runTests().then(onSuccess, onFailure); \ No newline at end of file +runTests().catch(function(err) { + console.log(err); + process.exit(-1); +}); \ No newline at end of file From aaa5ee1a25b7d698214a55df594c1aad50ce6294 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 7 Aug 2018 18:21:53 +0200 Subject: [PATCH 070/221] more consistent naming on session methods --- src/session.js | 21 ++++++++++++--------- src/tests/consent.js | 2 +- src/tests/create-room.js | 8 ++++---- src/tests/join.js | 8 ++++---- src/tests/room-settings.js | 21 +++++++++++++++++++++ src/tests/server-notices-consent.js | 4 ++-- src/tests/signup.js | 16 ++++++++-------- 7 files changed, 52 insertions(+), 28 deletions(-) create mode 100644 src/tests/room-settings.js diff --git a/src/session.js b/src/session.js index 5b0f78ccd3..d82c90d4d9 100644 --- a/src/session.js +++ b/src/session.js @@ -129,15 +129,22 @@ module.exports = class RiotSession { await input.type(text); } - // TODO: rename to waitAndQuery(Single)? - async waitAndQuerySelector(selector, timeout = 500) { + query(selector) { + return this.page.$(selector); + } + + async waitAndQuery(selector, timeout = 500) { await this.page.waitForSelector(selector, {visible: true, timeout}); - return await this.page.$(selector); + return await this.query(selector); + } + + queryAll(selector) { + return this.page.$$(selector); } async waitAndQueryAll(selector, timeout = 500) { await this.page.waitForSelector(selector, {visible: true, timeout}); - return await this.page.$$(selector); + return await this.queryAll(selector); } waitForNewPage(timeout = 500) { @@ -157,15 +164,11 @@ module.exports = class RiotSession { }); } - waitForSelector(selector) { - return this.page.waitForSelector(selector); - } - goto(url) { return this.page.goto(url); } - riotUrl(path) { + url(path) { return this.riotserver + path; } diff --git a/src/tests/consent.js b/src/tests/consent.js index 09026a3082..cd3d51c1b6 100644 --- a/src/tests/consent.js +++ b/src/tests/consent.js @@ -17,7 +17,7 @@ limitations under the License. const assert = require('assert'); module.exports = async function acceptTerms(session) { - const reviewTermsButton = await session.waitAndQuerySelector('.mx_QuestionDialog button.mx_Dialog_primary', 5000); + const reviewTermsButton = await session.waitAndQuery('.mx_QuestionDialog button.mx_Dialog_primary', 5000); const termsPagePromise = session.waitForNewPage(); await reviewTermsButton.click(); const termsPage = await termsPagePromise; diff --git a/src/tests/create-room.js b/src/tests/create-room.js index eff92baf83..8f5b5c9e85 100644 --- a/src/tests/create-room.js +++ b/src/tests/create-room.js @@ -19,15 +19,15 @@ const assert = require('assert'); module.exports = async function createRoom(session, roomName) { session.log.step(`creates room ${roomName}`); //TODO: brittle selector - const createRoomButton = await session.waitAndQuerySelector('.mx_RoleButton[aria-label="Create new room"]'); + const createRoomButton = await session.waitAndQuery('.mx_RoleButton[aria-label="Create new room"]'); await createRoomButton.click(); - const roomNameInput = await session.waitAndQuerySelector('.mx_CreateRoomDialog_input'); + const roomNameInput = await session.waitAndQuery('.mx_CreateRoomDialog_input'); await session.replaceInputText(roomNameInput, roomName); - const createButton = await session.waitAndQuerySelector('.mx_Dialog_primary'); + const createButton = await session.waitAndQuery('.mx_Dialog_primary'); await createButton.click(); - await session.waitForSelector('.mx_MessageComposer'); + await session.waitAndQuery('.mx_MessageComposer'); session.log.done(); } \ No newline at end of file diff --git a/src/tests/join.js b/src/tests/join.js index 72d4fe10cf..0577b7a8b6 100644 --- a/src/tests/join.js +++ b/src/tests/join.js @@ -19,16 +19,16 @@ const assert = require('assert'); module.exports = async function join(session, roomName) { session.log.step(`joins room ${roomName}`); //TODO: brittle selector - const directoryButton = await session.waitAndQuerySelector('.mx_RoleButton[aria-label="Room directory"]'); + const directoryButton = await session.waitAndQuery('.mx_RoleButton[aria-label="Room directory"]'); await directoryButton.click(); - const roomInput = await session.waitAndQuerySelector('.mx_DirectorySearchBox_input'); + const roomInput = await session.waitAndQuery('.mx_DirectorySearchBox_input'); await session.replaceInputText(roomInput, roomName); - const firstRoomLabel = await session.waitAndQuerySelector('.mx_RoomDirectory_table .mx_RoomDirectory_name:first-child'); + const firstRoomLabel = await session.waitAndQuery('.mx_RoomDirectory_table .mx_RoomDirectory_name:first-child'); await firstRoomLabel.click(); - const joinLink = await session.waitAndQuerySelector('.mx_RoomPreviewBar_join_text a'); + const joinLink = await session.waitAndQuery('.mx_RoomPreviewBar_join_text a'); await joinLink.click(); await session.waitForSelector('.mx_MessageComposer'); diff --git a/src/tests/room-settings.js b/src/tests/room-settings.js new file mode 100644 index 0000000000..70d84de10f --- /dev/null +++ b/src/tests/room-settings.js @@ -0,0 +1,21 @@ +/* +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 assert = require('assert'); + +module.exports = async function changeRoomSettings(session, settings) { + session.waitFor +} \ No newline at end of file diff --git a/src/tests/server-notices-consent.js b/src/tests/server-notices-consent.js index 53a318a169..d52588f962 100644 --- a/src/tests/server-notices-consent.js +++ b/src/tests/server-notices-consent.js @@ -30,10 +30,10 @@ module.exports = async function acceptServerNoticesInviteAndConsent(session, not await inviteHandle.click(); - const acceptInvitationLink = await session.waitAndQuerySelector(".mx_RoomPreviewBar_join_text a:first-child"); + const acceptInvitationLink = await session.waitAndQuery(".mx_RoomPreviewBar_join_text a:first-child"); await acceptInvitationLink.click(); - const consentLink = await session.waitAndQuerySelector(".mx_EventTile_body a", 1000); + const consentLink = await session.waitAndQuery(".mx_EventTile_body a", 1000); const termsPagePromise = session.waitForNewPage(); await consentLink.click(); diff --git a/src/tests/signup.js b/src/tests/signup.js index 6b3f06c12c..434083cbb6 100644 --- a/src/tests/signup.js +++ b/src/tests/signup.js @@ -19,16 +19,16 @@ const assert = require('assert'); module.exports = async function signup(session, username, password, homeserver) { session.log.step("signs up"); - await session.goto(session.riotUrl('/#/register')); + await session.goto(session.url('/#/register')); //click 'Custom server' radio button if (homeserver) { - const advancedRadioButton = await session.waitAndQuerySelector('#advanced'); + const advancedRadioButton = await session.waitAndQuery('#advanced'); await advancedRadioButton.click(); } // wait until register button is visible - await session.waitForSelector('.mx_Login_submit[value=Register]', {visible: true, timeout: 500}); + await session.waitAndQuery('.mx_Login_submit[value=Register]'); //fill out form - const loginFields = await session.page.$$('.mx_Login_field'); + const loginFields = await session.queryAll('.mx_Login_field'); assert.strictEqual(loginFields.length, 7); const usernameField = loginFields[2]; const passwordField = loginFields[3]; @@ -38,7 +38,7 @@ module.exports = async function signup(session, username, password, homeserver) await session.replaceInputText(passwordField, password); await session.replaceInputText(passwordRepeatField, password); if (homeserver) { - await session.waitForSelector('.mx_ServerConfig', {visible: true, timeout: 500}); + await session.waitAndQuery('.mx_ServerConfig'); await session.replaceInputText(hsurlField, homeserver); } //wait over a second because Registration/ServerConfig have a 1000ms @@ -47,7 +47,7 @@ module.exports = async function signup(session, username, password, homeserver) await session.delay(1200); /// focus on the button to make sure error validation /// has happened before checking the form is good to go - const registerButton = await session.page.$('.mx_Login_submit'); + const registerButton = await session.query('.mx_Login_submit'); await registerButton.focus(); //check no errors const error_text = await session.tryGetInnertext('.mx_Login_error'); @@ -57,13 +57,13 @@ module.exports = async function signup(session, username, password, homeserver) await registerButton.click(); //confirm dialog saying you cant log back in without e-mail - const continueButton = await session.waitAndQuerySelector('.mx_QuestionDialog button.mx_Dialog_primary'); + const continueButton = await session.waitAndQuery('.mx_QuestionDialog button.mx_Dialog_primary'); await continueButton.click(); //wait for registration to finish so the hash gets set //onhashchange better? await session.delay(2000); const url = session.page.url(); - assert.strictEqual(url, session.riotUrl('/#/home')); + assert.strictEqual(url, session.url('/#/home')); session.log.done(); } From 2a7438e9fbdb3ab4afbc2f722b1536dd5ab946c3 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 7 Aug 2018 18:23:02 +0200 Subject: [PATCH 071/221] no need to double select here, might speed things up slightly --- src/session.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/session.js b/src/session.js index d82c90d4d9..e5e16400b8 100644 --- a/src/session.js +++ b/src/session.js @@ -133,9 +133,8 @@ module.exports = class RiotSession { return this.page.$(selector); } - async waitAndQuery(selector, timeout = 500) { - await this.page.waitForSelector(selector, {visible: true, timeout}); - return await this.query(selector); + waitAndQuery(selector, timeout = 500) { + return this.page.waitForSelector(selector, {visible: true, timeout}); } queryAll(selector) { @@ -143,7 +142,7 @@ module.exports = class RiotSession { } async waitAndQueryAll(selector, timeout = 500) { - await this.page.waitForSelector(selector, {visible: true, timeout}); + await this.waitAndQuery(selector, timeout); return await this.queryAll(selector); } From 643af2d344870e107944cb3f82a48a09d6026dd9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 7 Aug 2018 18:28:18 +0200 Subject: [PATCH 072/221] run synapse on custom port so it doesn't interfere with other synapses on dev machines --- riot/config-template/config.json | 6 +++--- synapse/config-templates/consent/homeserver.yaml | 6 +++--- synapse/install.sh | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/riot/config-template/config.json b/riot/config-template/config.json index 39bbd6490a..6277e567fc 100644 --- a/riot/config-template/config.json +++ b/riot/config-template/config.json @@ -1,5 +1,5 @@ { - "default_hs_url": "http://localhost:8008", + "default_hs_url": "http://localhost:5005", "default_is_url": "https://vector.im", "disable_custom_urls": false, "disable_guests": false, @@ -18,12 +18,12 @@ "default_theme": "light", "roomDirectory": { "servers": [ - "localhost:8008" + "localhost:5005" ] }, "piwik": { "url": "https://piwik.riot.im/", - "whitelistedHSUrls": ["http://localhost:8008"], + "whitelistedHSUrls": ["http://localhost:5005"], "whitelistedISUrls": ["https://vector.im", "https://matrix.org"], "siteId": 1 }, diff --git a/synapse/config-templates/consent/homeserver.yaml b/synapse/config-templates/consent/homeserver.yaml index a27fbf6f10..38aa4747b5 100644 --- a/synapse/config-templates/consent/homeserver.yaml +++ b/synapse/config-templates/consent/homeserver.yaml @@ -86,7 +86,7 @@ web_client: True # web_client_location: "/path/to/web/root" # The public-facing base URL for the client API (not including _matrix/...) -public_baseurl: http://localhost:8008/ +public_baseurl: http://localhost:{{SYNAPSE_PORT}}/ # Set the soft limit on the number of file descriptors synapse can use # Zero is used to indicate synapse should set the soft limit to the @@ -166,7 +166,7 @@ listeners: # Unsecure HTTP listener, # For when matrix traffic passes through loadbalancer that unwraps TLS. - - port: 8008 + - port: {{SYNAPSE_PORT}} tls: false bind_addresses: ['::', '0.0.0.0'] type: http @@ -693,5 +693,5 @@ user_consent: server_notices: system_mxid_localpart: notices system_mxid_display_name: "Server Notices" - system_mxid_avatar_url: "mxc://localhost:8008/oumMVlgDnLYFaPVkExemNVVZ" + system_mxid_avatar_url: "mxc://localhost:{{SYNAPSE_PORT}}/oumMVlgDnLYFaPVkExemNVVZ" room_name: "Server Notices" diff --git a/synapse/install.sh b/synapse/install.sh index dc4a08cb41..2e9b668b5e 100755 --- a/synapse/install.sh +++ b/synapse/install.sh @@ -4,7 +4,7 @@ SYNAPSE_BRANCH=master INSTALLATION_NAME=consent SERVER_DIR=installations/$INSTALLATION_NAME CONFIG_TEMPLATE=consent -PORT=8008 +PORT=5005 # set current directory to script directory BASE_DIR=$(readlink -f $(dirname $0)) @@ -33,7 +33,7 @@ python -m synapse.app.homeserver \ # apply configuration cp -r $BASE_DIR/config-templates/$CONFIG_TEMPLATE/. ./ sed -i "s#{{SYNAPSE_ROOT}}#$(pwd)/#g" homeserver.yaml -sed -i "s#{{SYNAPSE_PORT}}#${PORT}/#g" homeserver.yaml +sed -i "s#{{SYNAPSE_PORT}}#${PORT}#g" homeserver.yaml sed -i "s#{{FORM_SECRET}}#$(uuidgen)#g" homeserver.yaml sed -i "s#{{REGISTRATION_SHARED_SECRET}}#$(uuidgen)#g" homeserver.yaml sed -i "s#{{MACAROON_SECRET_KEY}}#$(uuidgen)#g" homeserver.yaml From a78c095cf657978e442113cc6e56cf80e60bbfb7 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 8 Aug 2018 11:39:17 +0200 Subject: [PATCH 073/221] add support for changing the room settings --- src/scenario.js | 4 ++- src/session.js | 39 +++++++++++++++-------- src/tests/join.js | 4 +-- src/tests/room-settings.js | 48 ++++++++++++++++++++++++++++- src/tests/server-notices-consent.js | 1 + start.js | 8 ++--- 6 files changed, 83 insertions(+), 21 deletions(-) diff --git a/src/scenario.js b/src/scenario.js index ee049a14f9..9aa0ed2ec0 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -18,6 +18,7 @@ limitations under the License. const signup = require('./tests/signup'); const join = require('./tests/join'); const createRoom = require('./tests/create-room'); +const changeRoomSettings = require('./tests/room-settings'); const acceptServerNoticesInviteAndConsent = require('./tests/server-notices-consent'); module.exports = async function scenario(createSession) { @@ -33,5 +34,6 @@ module.exports = async function scenario(createSession) { const bob = await createUser("bob"); const room = 'test'; await createRoom(alice, room); - // await join(bob, room); + await changeRoomSettings(alice, {directory: true, visibility: "public_no_guests"}); + await join(bob, room); } diff --git a/src/session.js b/src/session.js index e5e16400b8..4f6e04584f 100644 --- a/src/session.js +++ b/src/session.js @@ -33,15 +33,27 @@ class LogBuffer { class Logger { constructor(username) { + this.indent = 0; this.username = username; } - step(description) { - process.stdout.write(` * ${this.username} ${description} ... `); + startGroup(description) { + const indent = " ".repeat(this.indent * 2); + console.log(`${indent} * ${this.username} ${description}:`); + this.indent += 1; } - done() { - process.stdout.write("done\n"); + endGroup() { + this.indent -= 1; + } + + step(description) { + const indent = " ".repeat(this.indent * 2); + process.stdout.write(`${indent} * ${this.username} ${description} ... `); + } + + done(status = "done") { + process.stdout.write(status + "\n"); } } @@ -79,9 +91,17 @@ module.exports = class RiotSession { return null; } - async innerText(field) { - const text_handle = await field.getProperty('innerText'); - return await text_handle.jsonValue(); + async getElementProperty(handle, property) { + const propHandle = await handle.getProperty(property); + return await propHandle.jsonValue(); + } + + innerText(field) { + return this.getElementProperty(field, 'innerText'); + } + + getOuterHTML(element_handle) { + return this.getElementProperty(field, 'outerHTML'); } consoleLogs() { @@ -111,11 +131,6 @@ module.exports = class RiotSession { } } - async getOuterHTML(element_handle) { - const html_handle = await element_handle.getProperty('outerHTML'); - return await html_handle.jsonValue(); - } - async printElements(label, elements) { console.log(label, await Promise.all(elements.map(getOuterHTML))); } diff --git a/src/tests/join.js b/src/tests/join.js index 0577b7a8b6..3c76ad2c67 100644 --- a/src/tests/join.js +++ b/src/tests/join.js @@ -25,12 +25,12 @@ module.exports = async function join(session, roomName) { const roomInput = await session.waitAndQuery('.mx_DirectorySearchBox_input'); await session.replaceInputText(roomInput, roomName); - const firstRoomLabel = await session.waitAndQuery('.mx_RoomDirectory_table .mx_RoomDirectory_name:first-child'); + const firstRoomLabel = await session.waitAndQuery('.mx_RoomDirectory_table .mx_RoomDirectory_name:first-child', 1000); await firstRoomLabel.click(); const joinLink = await session.waitAndQuery('.mx_RoomPreviewBar_join_text a'); await joinLink.click(); - await session.waitForSelector('.mx_MessageComposer'); + await session.waitAndQuery('.mx_MessageComposer'); session.log.done(); } \ No newline at end of file diff --git a/src/tests/room-settings.js b/src/tests/room-settings.js index 70d84de10f..d7bbad3451 100644 --- a/src/tests/room-settings.js +++ b/src/tests/room-settings.js @@ -17,5 +17,51 @@ limitations under the License. const assert = require('assert'); module.exports = async function changeRoomSettings(session, settings) { - session.waitFor + session.log.startGroup(`changes the room settings`); + /// XXX delay is needed here, possible because the header is being rerendered + /// click doesn't do anything otherwise + await session.delay(500); + const settingsButton = await session.query(".mx_RoomHeader .mx_AccessibleButton[title=Settings]"); + await settingsButton.click(); + const checks = await session.waitAndQueryAll(".mx_RoomSettings_settings input[type=checkbox]"); + assert.equal(checks.length, 3); + const e2eEncryptionCheck = checks[0]; + const sendToUnverifiedDevices = checks[1]; + const isDirectory = checks[2]; + + if (typeof settings.directory === "boolean") { + session.log.step(`sets directory listing to ${settings.directory}`); + const checked = await session.getElementProperty(isDirectory, "checked"); + assert(typeof checked, "boolean"); + if (checked !== settings.directory) { + await isDirectory.click(); + session.log.done(); + } else { + session.log.done("already set"); + } + } + + if (settings.visibility) { + session.log.step(`sets visibility to ${settings.visibility}`); + const radios = await session.waitAndQueryAll(".mx_RoomSettings_settings input[type=radio]"); + assert.equal(radios.length, 7); + const inviteOnly = radios[0]; + const publicNoGuests = radios[1]; + const publicWithGuests = radios[2]; + + if (settings.visibility === "invite_only") { + await inviteOnly.click(); + } else if (settings.visibility === "public_no_guests") { + await publicNoGuests.click(); + } else if (settings.visibility === "public_with_guests") { + await publicWithGuests.click(); + } else { + throw new Error(`unrecognized room visibility setting: ${settings.visibility}`); + } + session.log.done(); + } + + const saveButton = await session.query(".mx_RoomHeader_wrapper .mx_RoomHeader_textButton"); + await saveButton.click(); + session.log.endGroup(); } \ No newline at end of file diff --git a/src/tests/server-notices-consent.js b/src/tests/server-notices-consent.js index d52588f962..def21d04c3 100644 --- a/src/tests/server-notices-consent.js +++ b/src/tests/server-notices-consent.js @@ -41,5 +41,6 @@ module.exports = async function acceptServerNoticesInviteAndConsent(session, not const acceptButton = await termsPage.$('input[type=submit]'); await acceptButton.click(); await session.delay(500); //TODO yuck, timers + await termsPage.close(); session.log.done(); } \ No newline at end of file diff --git a/start.js b/start.js index 5e235dd1ef..11dbe8d2fa 100644 --- a/start.js +++ b/start.js @@ -25,6 +25,7 @@ async function runTests() { console.log("running tests ..."); const options = {}; + // options.headless = false; if (process.env.CHROME_PATH) { const path = process.env.CHROME_PATH; console.log(`(using external chrome/chromium at ${path}, make sure it's compatible with puppeteer)`); @@ -41,6 +42,7 @@ async function runTests() { try { await scenario(createSession); } catch(err) { + failure = true; console.log('failure: ', err); for(let i = 0; i < sessions.length; ++i) { const session = sessions[i]; @@ -54,13 +56,9 @@ async function runTests() { console.log(documentHtml); console.log(`---------------- END OF ${session.username} LOGS ----------------`); } - failure = true; } - for(let i = 0; i < sessions.length; ++i) { - const session = sessions[i]; - await session.close(); - } + await Promise.all(sessions.map((session) => session.close())); if (failure) { process.exit(-1); From 1fd379b3d25208c8d53a5b6722129401090dc0a0 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 8 Aug 2018 12:17:36 +0200 Subject: [PATCH 074/221] wait to receive message from other user --- src/scenario.js | 4 ++++ src/tests/receive-message.js | 42 ++++++++++++++++++++++++++++++++++++ src/tests/room-settings.js | 2 +- src/tests/send-message.js | 25 +++++++++++++++++++++ 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 src/tests/receive-message.js create mode 100644 src/tests/send-message.js diff --git a/src/scenario.js b/src/scenario.js index 9aa0ed2ec0..14c901ba99 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -17,6 +17,8 @@ limitations under the License. const signup = require('./tests/signup'); const join = require('./tests/join'); +const sendMessage = require('./tests/send-message'); +const receiveMessage = require('./tests/receive-message'); const createRoom = require('./tests/create-room'); const changeRoomSettings = require('./tests/room-settings'); const acceptServerNoticesInviteAndConsent = require('./tests/server-notices-consent'); @@ -36,4 +38,6 @@ module.exports = async function scenario(createSession) { await createRoom(alice, room); await changeRoomSettings(alice, {directory: true, visibility: "public_no_guests"}); await join(bob, room); + await sendMessage(bob, "hi Alice!"); + await receiveMessage(alice, {sender: "bob", body: "hi Alice!"}); } diff --git a/src/tests/receive-message.js b/src/tests/receive-message.js new file mode 100644 index 0000000000..607bc1625e --- /dev/null +++ b/src/tests/receive-message.js @@ -0,0 +1,42 @@ +/* +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 assert = require('assert'); + + +async function getMessageFromTile(eventTile) { + const senderElement = await eventTile.$(".mx_SenderProfile_name"); + const bodyElement = await eventTile.$(".mx_EventTile_body"); + const sender = await(await senderElement.getProperty("innerText")).jsonValue(); + const body = await(await bodyElement.getProperty("innerText")).jsonValue(); + return {sender, body}; +} + +module.exports = async function receiveMessage(session, message) { + session.log.step(`waits to receive message from ${message.sender} in room`); + // wait for a response to come in that contains the message + // crude, but effective + await session.page.waitForResponse(async (response) => { + const body = await response.text(); + return body.indexOf(message.body) !== -1; + }); + + let lastTile = await session.waitAndQuery(".mx_EventTile_last"); + let lastMessage = await getMessageFromTile(lastTile); + assert.equal(lastMessage.body, message.body); + assert.equal(lastMessage.sender, message.sender); + session.log.done(); +} \ No newline at end of file diff --git a/src/tests/room-settings.js b/src/tests/room-settings.js index d7bbad3451..6001d14d34 100644 --- a/src/tests/room-settings.js +++ b/src/tests/room-settings.js @@ -32,7 +32,7 @@ module.exports = async function changeRoomSettings(session, settings) { if (typeof settings.directory === "boolean") { session.log.step(`sets directory listing to ${settings.directory}`); const checked = await session.getElementProperty(isDirectory, "checked"); - assert(typeof checked, "boolean"); + assert.equal(typeof checked, "boolean"); if (checked !== settings.directory) { await isDirectory.click(); session.log.done(); diff --git a/src/tests/send-message.js b/src/tests/send-message.js new file mode 100644 index 0000000000..8a61a15e94 --- /dev/null +++ b/src/tests/send-message.js @@ -0,0 +1,25 @@ +/* +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 assert = require('assert'); + +module.exports = async function sendMessage(session, message) { + session.log.step(`writes "${message}" in room`); + const composer = await session.waitAndQuery('.mx_MessageComposer'); + await composer.type(message); + await composer.press("Enter"); + session.log.done(); +} \ No newline at end of file From c5f064e389e970eaa7bbfe04acbf2a313f00655b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 8 Aug 2018 12:35:36 +0200 Subject: [PATCH 075/221] make receiving a bit more robust --- src/tests/receive-message.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/tests/receive-message.js b/src/tests/receive-message.js index 607bc1625e..c84aefcbfd 100644 --- a/src/tests/receive-message.js +++ b/src/tests/receive-message.js @@ -18,25 +18,25 @@ const assert = require('assert'); async function getMessageFromTile(eventTile) { - const senderElement = await eventTile.$(".mx_SenderProfile_name"); - const bodyElement = await eventTile.$(".mx_EventTile_body"); - const sender = await(await senderElement.getProperty("innerText")).jsonValue(); - const body = await(await bodyElement.getProperty("innerText")).jsonValue(); - return {sender, body}; } module.exports = async function receiveMessage(session, message) { - session.log.step(`waits to receive message from ${message.sender} in room`); + session.log.step(`receives message "${message.body}" from ${message.sender} in room`); // wait for a response to come in that contains the message // crude, but effective await session.page.waitForResponse(async (response) => { const body = await response.text(); return body.indexOf(message.body) !== -1; }); - - let lastTile = await session.waitAndQuery(".mx_EventTile_last"); - let lastMessage = await getMessageFromTile(lastTile); - assert.equal(lastMessage.body, message.body); - assert.equal(lastMessage.sender, message.sender); + // wait a bit for the incoming event to be rendered + await session.delay(100); + let lastTile = await session.query(".mx_EventTile_last"); + const senderElement = await lastTile.$(".mx_SenderProfile_name"); + const bodyElement = await lastTile.$(".mx_EventTile_body"); + const sender = await(await senderElement.getProperty("innerText")).jsonValue(); + const body = await(await bodyElement.getProperty("innerText")).jsonValue(); + + assert.equal(body, message.body); + assert.equal(sender, message.sender); session.log.done(); } \ No newline at end of file From 73c88fe603219c0c6a3f76d3eaef267e849f9c53 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 8 Aug 2018 12:35:50 +0200 Subject: [PATCH 076/221] prepare for more tests --- src/scenario.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/scenario.js b/src/scenario.js index 14c901ba99..07f9518029 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -34,10 +34,17 @@ module.exports = async function scenario(createSession) { const alice = await createUser("alice"); const bob = await createUser("bob"); + + await createDirectoryRoomAndTalk(alice, bob); +} + +async function createDirectoryRoomAndTalk(alice, bob) { + console.log(" creating a public room and join through directory:"); const room = 'test'; await createRoom(alice, room); await changeRoomSettings(alice, {directory: true, visibility: "public_no_guests"}); await join(bob, room); await sendMessage(bob, "hi Alice!"); await receiveMessage(alice, {sender: "bob", body: "hi Alice!"}); -} +} + From dc87e2bfe0a0268871f04c1b1946f01b851d4199 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 8 Aug 2018 12:42:34 +0200 Subject: [PATCH 077/221] avoid typos --- src/scenario.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/scenario.js b/src/scenario.js index 07f9518029..f035e94c35 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -41,10 +41,17 @@ module.exports = async function scenario(createSession) { async function createDirectoryRoomAndTalk(alice, bob) { console.log(" creating a public room and join through directory:"); const room = 'test'; + const message = "hi Alice!"; await createRoom(alice, room); await changeRoomSettings(alice, {directory: true, visibility: "public_no_guests"}); await join(bob, room); - await sendMessage(bob, "hi Alice!"); - await receiveMessage(alice, {sender: "bob", body: "hi Alice!"}); + await sendMessage(bob, message); + await receiveMessage(alice, {sender: "bob", body: message}); } +async function createE2ERoomAndTalk(alice, bob) { + await createRoom(bob, "secrets"); + await changeRoomSettings(bob, {encryption: true}); + await invite(bob, "@alice:localhost"); + await acceptInvite(alice, "secrets"); +} \ No newline at end of file From af0c0c0afe95eef2072ee936f7b06090ffa37be6 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 8 Aug 2018 15:22:54 +0200 Subject: [PATCH 078/221] add test scenario for e2e encryption --- src/scenario.js | 22 +++++++++++--- src/tests/accept-invite.js | 43 ++++++++++++++++++++++++++ src/tests/create-room.js | 2 +- src/tests/dialog.js | 47 +++++++++++++++++++++++++++++ src/tests/e2e-device.js | 31 +++++++++++++++++++ src/tests/invite.js | 30 ++++++++++++++++++ src/tests/join.js | 2 +- src/tests/room-settings.js | 30 +++++++++++++----- src/tests/server-notices-consent.js | 21 ++----------- src/tests/verify-device.js | 44 +++++++++++++++++++++++++++ start.js | 34 +++++++++++++-------- 11 files changed, 263 insertions(+), 43 deletions(-) create mode 100644 src/tests/accept-invite.js create mode 100644 src/tests/dialog.js create mode 100644 src/tests/e2e-device.js create mode 100644 src/tests/invite.js create mode 100644 src/tests/verify-device.js diff --git a/src/scenario.js b/src/scenario.js index f035e94c35..d7dd4f79e0 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -18,10 +18,14 @@ limitations under the License. const signup = require('./tests/signup'); const join = require('./tests/join'); const sendMessage = require('./tests/send-message'); +const acceptInvite = require('./tests/accept-invite'); +const invite = require('./tests/invite'); const receiveMessage = require('./tests/receive-message'); const createRoom = require('./tests/create-room'); const changeRoomSettings = require('./tests/room-settings'); const acceptServerNoticesInviteAndConsent = require('./tests/server-notices-consent'); +const getE2EDeviceFromSettings = require('./tests/e2e-device'); +const verifyDeviceForUser = require("./tests/verify-device"); module.exports = async function scenario(createSession) { async function createUser(username) { @@ -36,6 +40,7 @@ module.exports = async function scenario(createSession) { const bob = await createUser("bob"); await createDirectoryRoomAndTalk(alice, bob); + await createE2ERoomAndTalk(alice, bob); } async function createDirectoryRoomAndTalk(alice, bob) { @@ -47,11 +52,20 @@ async function createDirectoryRoomAndTalk(alice, bob) { await join(bob, room); await sendMessage(bob, message); await receiveMessage(alice, {sender: "bob", body: message}); -} +} async function createE2ERoomAndTalk(alice, bob) { - await createRoom(bob, "secrets"); + console.log(" creating an e2e encrypted room and join through invite:"); + const room = "secrets"; + const message = "Guess what I just heard?!" + await createRoom(bob, room); await changeRoomSettings(bob, {encryption: true}); await invite(bob, "@alice:localhost"); - await acceptInvite(alice, "secrets"); -} \ No newline at end of file + await acceptInvite(alice, room); + const bobDevice = await getE2EDeviceFromSettings(bob); + const aliceDevice = await getE2EDeviceFromSettings(alice); + await verifyDeviceForUser(bob, "alice", aliceDevice); + await verifyDeviceForUser(alice, "bob", bobDevice); + await sendMessage(alice, message); + await receiveMessage(bob, {sender: "alice", body: message, encrypted: true}); +} diff --git a/src/tests/accept-invite.js b/src/tests/accept-invite.js new file mode 100644 index 0000000000..25ce1bdf49 --- /dev/null +++ b/src/tests/accept-invite.js @@ -0,0 +1,43 @@ +/* +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 assert = require('assert'); +const {acceptDialogMaybe} = require('./dialog'); + +module.exports = async function acceptInvite(session, name) { + session.log.step(`accepts "${name}" invite`); + //TODO: brittle selector + const invitesHandles = await session.waitAndQueryAll('.mx_RoomTile_name.mx_RoomTile_invite', 1000); + const invitesWithText = await Promise.all(invitesHandles.map(async (inviteHandle) => { + const text = await session.innerText(inviteHandle); + return {inviteHandle, text}; + })); + const inviteHandle = invitesWithText.find(({inviteHandle, text}) => { + return text.trim() === name; + }).inviteHandle; + + await inviteHandle.click(); + + const acceptInvitationLink = await session.waitAndQuery(".mx_RoomPreviewBar_join_text a:first-child"); + await acceptInvitationLink.click(); + + // accept e2e warning dialog + try { + acceptDialogMaybe(session, "encryption"); + } catch(err) {} + + session.log.done(); +} \ No newline at end of file diff --git a/src/tests/create-room.js b/src/tests/create-room.js index 8f5b5c9e85..0f7f33ddff 100644 --- a/src/tests/create-room.js +++ b/src/tests/create-room.js @@ -17,7 +17,7 @@ limitations under the License. const assert = require('assert'); module.exports = async function createRoom(session, roomName) { - session.log.step(`creates room ${roomName}`); + session.log.step(`creates room "${roomName}"`); //TODO: brittle selector const createRoomButton = await session.waitAndQuery('.mx_RoleButton[aria-label="Create new room"]'); await createRoomButton.click(); diff --git a/src/tests/dialog.js b/src/tests/dialog.js new file mode 100644 index 0000000000..420438b3f9 --- /dev/null +++ b/src/tests/dialog.js @@ -0,0 +1,47 @@ +/* +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 assert = require('assert'); + + +async function acceptDialog(session, expectedContent) { + const foundDialog = await acceptDialogMaybe(session, expectedContent); + if (!foundDialog) { + throw new Error("could not find a dialog"); + } +} + +async function acceptDialogMaybe(session, expectedContent) { + let dialog = null; + try { + dialog = await session.waitAndQuery(".mx_QuestionDialog", 100); + } catch(err) { + return false; + } + if (expectedContent) { + const contentElement = await dialog.$(".mx_Dialog_content"); + const content = await (await contentElement.getProperty("innerText")).jsonValue(); + assert.ok(content.indexOf(expectedContent) !== -1); + } + const primaryButton = await dialog.$(".mx_Dialog_primary"); + await primaryButton.click(); + return true; +} + +module.exports = { + acceptDialog, + acceptDialogMaybe, +}; \ No newline at end of file diff --git a/src/tests/e2e-device.js b/src/tests/e2e-device.js new file mode 100644 index 0000000000..fd81ac43eb --- /dev/null +++ b/src/tests/e2e-device.js @@ -0,0 +1,31 @@ +/* +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 assert = require('assert'); + +module.exports = async function getE2EDeviceFromSettings(session) { + session.log.step(`gets e2e device/key from settings`); + const settingsButton = await session.query('.mx_BottomLeftMenu_settings'); + await settingsButton.click(); + const deviceAndKey = await session.waitAndQueryAll(".mx_UserSettings_section.mx_UserSettings_cryptoSection code"); + assert.equal(deviceAndKey.length, 2); + const id = await (await deviceAndKey[0].getProperty("innerText")).jsonValue(); + const key = await (await deviceAndKey[1].getProperty("innerText")).jsonValue(); + const closeButton = await session.query(".mx_RoomHeader_cancelButton"); + await closeButton.click(); + session.log.done(); + return {id, key}; +} \ No newline at end of file diff --git a/src/tests/invite.js b/src/tests/invite.js new file mode 100644 index 0000000000..37549aa2ca --- /dev/null +++ b/src/tests/invite.js @@ -0,0 +1,30 @@ +/* +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 assert = require('assert'); + +module.exports = async function invite(session, userId) { + session.log.step(`invites "${userId}" to room`); + await session.delay(200); + const inviteButton = await session.waitAndQuery(".mx_RightPanel_invite"); + await inviteButton.click(); + const inviteTextArea = await session.waitAndQuery(".mx_ChatInviteDialog textarea"); + await inviteTextArea.type(userId); + await inviteTextArea.press("Enter"); + const confirmButton = await session.query(".mx_Dialog_primary"); + await confirmButton.click(); + session.log.done(); +} \ No newline at end of file diff --git a/src/tests/join.js b/src/tests/join.js index 3c76ad2c67..8ab5e80f2d 100644 --- a/src/tests/join.js +++ b/src/tests/join.js @@ -17,7 +17,7 @@ limitations under the License. const assert = require('assert'); module.exports = async function join(session, roomName) { - session.log.step(`joins room ${roomName}`); + session.log.step(`joins room "${roomName}"`); //TODO: brittle selector const directoryButton = await session.waitAndQuery('.mx_RoleButton[aria-label="Room directory"]'); await directoryButton.click(); diff --git a/src/tests/room-settings.js b/src/tests/room-settings.js index 6001d14d34..127afa26dd 100644 --- a/src/tests/room-settings.js +++ b/src/tests/room-settings.js @@ -15,6 +15,19 @@ limitations under the License. */ const assert = require('assert'); +const {acceptDialog} = require('./dialog'); + +async function setCheckboxSetting(session, checkbox, enabled) { + const checked = await session.getElementProperty(checkbox, "checked"); + assert.equal(typeof checked, "boolean"); + if (checked !== enabled) { + await checkbox.click(); + session.log.done(); + return true; + } else { + session.log.done("already set"); + } +} module.exports = async function changeRoomSettings(session, settings) { session.log.startGroup(`changes the room settings`); @@ -31,13 +44,15 @@ module.exports = async function changeRoomSettings(session, settings) { if (typeof settings.directory === "boolean") { session.log.step(`sets directory listing to ${settings.directory}`); - const checked = await session.getElementProperty(isDirectory, "checked"); - assert.equal(typeof checked, "boolean"); - if (checked !== settings.directory) { - await isDirectory.click(); - session.log.done(); - } else { - session.log.done("already set"); + await setCheckboxSetting(session, isDirectory, settings.directory); + } + + if (typeof settings.encryption === "boolean") { + session.log.step(`sets room e2e encryption to ${settings.encryption}`); + const clicked = await setCheckboxSetting(session, e2eEncryptionCheck, settings.encryption); + // if enabling, accept beta warning dialog + if (clicked && settings.encryption) { + await acceptDialog(session, "encryption"); } } @@ -63,5 +78,6 @@ module.exports = async function changeRoomSettings(session, settings) { const saveButton = await session.query(".mx_RoomHeader_wrapper .mx_RoomHeader_textButton"); await saveButton.click(); + session.log.endGroup(); } \ No newline at end of file diff --git a/src/tests/server-notices-consent.js b/src/tests/server-notices-consent.js index def21d04c3..a217daa43b 100644 --- a/src/tests/server-notices-consent.js +++ b/src/tests/server-notices-consent.js @@ -15,26 +15,11 @@ limitations under the License. */ const assert = require('assert'); - +const acceptInvite = require("./accept-invite") module.exports = async function acceptServerNoticesInviteAndConsent(session, noticesName) { - session.log.step(`accepts "${noticesName}" invite and accepting terms & conditions`); - //TODO: brittle selector - const invitesHandles = await session.waitAndQueryAll('.mx_RoomTile_name.mx_RoomTile_invite'); - const invitesWithText = await Promise.all(invitesHandles.map(async (inviteHandle) => { - const text = await session.innerText(inviteHandle); - return {inviteHandle, text}; - })); - const inviteHandle = invitesWithText.find(({inviteHandle, text}) => { - return text.trim() === noticesName; - }).inviteHandle; - - await inviteHandle.click(); - - const acceptInvitationLink = await session.waitAndQuery(".mx_RoomPreviewBar_join_text a:first-child"); - await acceptInvitationLink.click(); - + await acceptInvite(session, noticesName); + session.log.step(`accepts terms & conditions`); const consentLink = await session.waitAndQuery(".mx_EventTile_body a", 1000); - const termsPagePromise = session.waitForNewPage(); await consentLink.click(); const termsPage = await termsPagePromise; diff --git a/src/tests/verify-device.js b/src/tests/verify-device.js new file mode 100644 index 0000000000..507de1b91e --- /dev/null +++ b/src/tests/verify-device.js @@ -0,0 +1,44 @@ +/* +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 assert = require('assert'); + +module.exports = async function verifyDeviceForUser(session, name, expectedDevice) { + session.log.step(`verifies e2e device for ${name}`); + const memberNameElements = await session.queryAll(".mx_MemberList .mx_EntityTile_name"); + const membersAndNames = await Promise.all(memberNameElements.map(async (el) => { + const innerTextHandle = await memberNameElements.getProperty("innerText"); + const innerText = await innerTextHandle.jsonValue(); + return [el, innerText]; + })); + const matchingMember = membersAndNames.filter(([el, text]) => { + return text === name; + }).map(([el, name]) => el); + await matchingMember.click(); + const firstVerifyButton = await session.waitAndQuery(".mx_MemberDeviceInfo_verify"); + await firstVerifyButton.click(); + const dialogCodeFields = await session.waitAndQueryAll(".mx_QuestionDialog code"); + assert.equal(dialogCodeFields.length, 2); + const deviceId = dialogCodeFields[0]; + const deviceKey = dialogCodeFields[1]; + assert.equal(expectedDevice.id, deviceId); + assert.equal(expectedDevice.key, deviceKey); + const confirmButton = await session.query(".mx_Dialog_primary"); + await confirmButton.click(); + const closeMemberInfo = await session.query(".mx_MemberInfo_cancel"); + await closeMemberInfo.click(); + session.log.done(); +} \ No newline at end of file diff --git a/start.js b/start.js index 11dbe8d2fa..f4005cbe85 100644 --- a/start.js +++ b/start.js @@ -20,12 +20,14 @@ const scenario = require('./src/scenario'); const riotserver = 'http://localhost:5000'; +const noLogs = process.argv.indexOf("--no-logs") !== -1; + async function runTests() { let sessions = []; console.log("running tests ..."); const options = {}; - // options.headless = false; + options.headless = false; if (process.env.CHROME_PATH) { const path = process.env.CHROME_PATH; console.log(`(using external chrome/chromium at ${path}, make sure it's compatible with puppeteer)`); @@ -44,20 +46,28 @@ async function runTests() { } catch(err) { failure = true; console.log('failure: ', err); - for(let i = 0; i < sessions.length; ++i) { - const session = sessions[i]; - documentHtml = await session.page.content(); - console.log(`---------------- START OF ${session.username} LOGS ----------------`); - console.log('---------------- console.log output:'); - console.log(session.consoleLogs()); - console.log('---------------- network requests:'); - console.log(session.networkLogs()); - console.log('---------------- document html:'); - console.log(documentHtml); - console.log(`---------------- END OF ${session.username} LOGS ----------------`); + if (!noLogs) { + for(let i = 0; i < sessions.length; ++i) { + const session = sessions[i]; + documentHtml = await session.page.content(); + console.log(`---------------- START OF ${session.username} LOGS ----------------`); + console.log('---------------- console.log output:'); + console.log(session.consoleLogs()); + console.log('---------------- network requests:'); + console.log(session.networkLogs()); + console.log('---------------- document html:'); + console.log(documentHtml); + console.log(`---------------- END OF ${session.username} LOGS ----------------`); + } } } + // wait 5 minutes on failure if not running headless + // to inspect what went wrong + if (failure && options.headless === false) { + await new Promise((resolve) => setTimeout(resolve, 5 * 60 * 1000)); + } + await Promise.all(sessions.map((session) => session.close())); if (failure) { From 2c983f8cee9a2b8a6989d032f04a20e723543e07 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 9 Aug 2018 14:23:09 +0200 Subject: [PATCH 079/221] fix composer issue and more --- src/scenario.js | 30 +++++++++++++++++++++-------- src/tests/accept-invite.js | 4 +--- src/tests/receive-message.js | 23 ++++++++++++++-------- src/tests/send-message.js | 6 +++++- src/tests/server-notices-consent.js | 4 ++-- src/tests/verify-device.js | 10 ++++------ start.js | 6 +++++- 7 files changed, 54 insertions(+), 29 deletions(-) diff --git a/src/scenario.js b/src/scenario.js index d7dd4f79e0..330db0fef2 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -15,6 +15,7 @@ limitations under the License. */ +const {acceptDialog} = require('./tests/dialog'); const signup = require('./tests/signup'); const join = require('./tests/join'); const sendMessage = require('./tests/send-message'); @@ -31,8 +32,7 @@ module.exports = async function scenario(createSession) { async function createUser(username) { const session = await createSession(username); await signup(session, session.username, 'testtest'); - const noticesName = "Server Notices"; - await acceptServerNoticesInviteAndConsent(session, noticesName); + await acceptServerNoticesInviteAndConsent(session); return session; } @@ -46,26 +46,40 @@ module.exports = async function scenario(createSession) { async function createDirectoryRoomAndTalk(alice, bob) { console.log(" creating a public room and join through directory:"); const room = 'test'; - const message = "hi Alice!"; await createRoom(alice, room); await changeRoomSettings(alice, {directory: true, visibility: "public_no_guests"}); await join(bob, room); - await sendMessage(bob, message); - await receiveMessage(alice, {sender: "bob", body: message}); + const bobMessage = "hi Alice!"; + await sendMessage(bob, bobMessage); + await receiveMessage(alice, {sender: "bob", body: bobMessage}); + const aliceMessage = "hi Bob, welcome!" + await sendMessage(alice, aliceMessage); + await receiveMessage(bob, {sender: "alice", body: aliceMessage}); } async function createE2ERoomAndTalk(alice, bob) { console.log(" creating an e2e encrypted room and join through invite:"); const room = "secrets"; - const message = "Guess what I just heard?!" await createRoom(bob, room); await changeRoomSettings(bob, {encryption: true}); await invite(bob, "@alice:localhost"); await acceptInvite(alice, room); const bobDevice = await getE2EDeviceFromSettings(bob); + // wait some time for the encryption warning dialog + // to appear after closing the settings + await bob.delay(500); + await acceptDialog(bob, "encryption"); const aliceDevice = await getE2EDeviceFromSettings(alice); + // wait some time for the encryption warning dialog + // to appear after closing the settings + await alice.delay(500); + await acceptDialog(alice, "encryption"); await verifyDeviceForUser(bob, "alice", aliceDevice); await verifyDeviceForUser(alice, "bob", bobDevice); - await sendMessage(alice, message); - await receiveMessage(bob, {sender: "alice", body: message, encrypted: true}); + const aliceMessage = "Guess what I just heard?!" + await sendMessage(alice, aliceMessage); + await receiveMessage(bob, {sender: "alice", body: aliceMessage, encrypted: true}); + const bobMessage = "You've got to tell me!"; + await sendMessage(bob, bobMessage); + await receiveMessage(alice, {sender: "bob", body: bobMessage, encrypted: true}); } diff --git a/src/tests/accept-invite.js b/src/tests/accept-invite.js index 25ce1bdf49..5cdeeb3d84 100644 --- a/src/tests/accept-invite.js +++ b/src/tests/accept-invite.js @@ -35,9 +35,7 @@ module.exports = async function acceptInvite(session, name) { await acceptInvitationLink.click(); // accept e2e warning dialog - try { - acceptDialogMaybe(session, "encryption"); - } catch(err) {} + acceptDialogMaybe(session, "encryption"); session.log.done(); } \ No newline at end of file diff --git a/src/tests/receive-message.js b/src/tests/receive-message.js index c84aefcbfd..9c963c45f4 100644 --- a/src/tests/receive-message.js +++ b/src/tests/receive-message.js @@ -16,26 +16,33 @@ limitations under the License. const assert = require('assert'); - -async function getMessageFromTile(eventTile) { -} - module.exports = async function receiveMessage(session, message) { - session.log.step(`receives message "${message.body}" from ${message.sender} in room`); + session.log.step(`receives message "${message.body}" from ${message.sender}`); // wait for a response to come in that contains the message // crude, but effective await session.page.waitForResponse(async (response) => { + if (response.request().url().indexOf("/sync") === -1) { + return false; + } const body = await response.text(); - return body.indexOf(message.body) !== -1; + if (message.encrypted) { + return body.indexOf(message.sender) !== -1 && + body.indexOf("m.room.encrypted") !== -1; + } else { + return body.indexOf(message.body) !== -1; + } }); // wait a bit for the incoming event to be rendered - await session.delay(100); + await session.delay(300); let lastTile = await session.query(".mx_EventTile_last"); const senderElement = await lastTile.$(".mx_SenderProfile_name"); const bodyElement = await lastTile.$(".mx_EventTile_body"); const sender = await(await senderElement.getProperty("innerText")).jsonValue(); const body = await(await bodyElement.getProperty("innerText")).jsonValue(); - + if (message.encrypted) { + const e2eIcon = await lastTile.$(".mx_EventTile_e2eIcon"); + assert.ok(e2eIcon); + } assert.equal(body, message.body); assert.equal(sender, message.sender); session.log.done(); diff --git a/src/tests/send-message.js b/src/tests/send-message.js index 8a61a15e94..e98d0dad72 100644 --- a/src/tests/send-message.js +++ b/src/tests/send-message.js @@ -18,8 +18,12 @@ const assert = require('assert'); module.exports = async function sendMessage(session, message) { session.log.step(`writes "${message}" in room`); - const composer = await session.waitAndQuery('.mx_MessageComposer'); + // this selector needs to be the element that has contenteditable=true, + // not any if its parents, otherwise it behaves flaky at best. + const composer = await session.waitAndQuery('.mx_MessageComposer_editor'); await composer.type(message); + const text = await session.innerText(composer); + assert.equal(text.trim(), message.trim()); await composer.press("Enter"); session.log.done(); } \ No newline at end of file diff --git a/src/tests/server-notices-consent.js b/src/tests/server-notices-consent.js index a217daa43b..3e07248daa 100644 --- a/src/tests/server-notices-consent.js +++ b/src/tests/server-notices-consent.js @@ -16,8 +16,8 @@ limitations under the License. const assert = require('assert'); const acceptInvite = require("./accept-invite") -module.exports = async function acceptServerNoticesInviteAndConsent(session, noticesName) { - await acceptInvite(session, noticesName); +module.exports = async function acceptServerNoticesInviteAndConsent(session) { + await acceptInvite(session, "Server Notices"); session.log.step(`accepts terms & conditions`); const consentLink = await session.waitAndQuery(".mx_EventTile_body a", 1000); const termsPagePromise = session.waitForNewPage(); diff --git a/src/tests/verify-device.js b/src/tests/verify-device.js index 507de1b91e..0622654876 100644 --- a/src/tests/verify-device.js +++ b/src/tests/verify-device.js @@ -20,20 +20,18 @@ module.exports = async function verifyDeviceForUser(session, name, expectedDevic session.log.step(`verifies e2e device for ${name}`); const memberNameElements = await session.queryAll(".mx_MemberList .mx_EntityTile_name"); const membersAndNames = await Promise.all(memberNameElements.map(async (el) => { - const innerTextHandle = await memberNameElements.getProperty("innerText"); - const innerText = await innerTextHandle.jsonValue(); - return [el, innerText]; + return [el, await session.innerText(el)]; })); const matchingMember = membersAndNames.filter(([el, text]) => { return text === name; - }).map(([el, name]) => el); + }).map(([el]) => el)[0]; await matchingMember.click(); const firstVerifyButton = await session.waitAndQuery(".mx_MemberDeviceInfo_verify"); await firstVerifyButton.click(); const dialogCodeFields = await session.waitAndQueryAll(".mx_QuestionDialog code"); assert.equal(dialogCodeFields.length, 2); - const deviceId = dialogCodeFields[0]; - const deviceKey = dialogCodeFields[1]; + const deviceId = await session.innerText(dialogCodeFields[0]); + const deviceKey = await session.innerText(dialogCodeFields[1]); assert.equal(expectedDevice.id, deviceId); assert.equal(expectedDevice.key, deviceKey); const confirmButton = await session.query(".mx_Dialog_primary"); diff --git a/start.js b/start.js index f4005cbe85..6c68050c97 100644 --- a/start.js +++ b/start.js @@ -21,13 +21,17 @@ const scenario = require('./src/scenario'); const riotserver = 'http://localhost:5000'; const noLogs = process.argv.indexOf("--no-logs") !== -1; +const debug = process.argv.indexOf("--debug") !== -1; async function runTests() { let sessions = []; console.log("running tests ..."); const options = {}; - options.headless = false; + if (debug) { + // options.slowMo = 10; + options.headless = false; + } if (process.env.CHROME_PATH) { const path = process.env.CHROME_PATH; console.log(`(using external chrome/chromium at ${path}, make sure it's compatible with puppeteer)`); From 377a20fffa45a00d1f37e7e040e53da2c0a73c14 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 14 Aug 2018 12:53:16 +0200 Subject: [PATCH 080/221] bring indentation in line with other front-end projects --- .editorconfig | 23 +++ package.json | 24 +-- riot/install.sh | 4 +- riot/start.sh | 36 ++-- riot/stop.sh | 20 +- src/scenario.js | 92 ++++----- src/session.js | 304 ++++++++++++++-------------- src/tests/accept-invite.js | 34 ++-- src/tests/consent.js | 14 +- src/tests/create-room.js | 22 +- src/tests/dialog.js | 40 ++-- src/tests/e2e-device.js | 24 +-- src/tests/invite.js | 22 +- src/tests/join.js | 26 +-- src/tests/receive-message.js | 56 ++--- src/tests/room-settings.js | 108 +++++----- src/tests/send-message.js | 20 +- src/tests/server-notices-consent.js | 24 +-- src/tests/signup.js | 94 ++++----- src/tests/verify-device.js | 46 ++--- start.js | 106 +++++----- synapse/install.sh | 4 +- 22 files changed, 583 insertions(+), 560 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..880331a09e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +# Copyright 2017 Aviral Dasgupta +# +# 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. + +root = true + +[*] +charset=utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true diff --git a/package.json b/package.json index 1cbdf5bd26..b5892a154a 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,14 @@ { - "name": "e2e-tests", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC", - "dependencies": { - "puppeteer": "^1.6.0" - } + "name": "e2e-tests", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "puppeteer": "^1.6.0" + } } diff --git a/riot/install.sh b/riot/install.sh index 7f37fa9457..209926d4c5 100755 --- a/riot/install.sh +++ b/riot/install.sh @@ -3,8 +3,8 @@ RIOT_BRANCH=master BASE_DIR=$(readlink -f $(dirname $0)) if [ -d $BASE_DIR/riot-web ]; then - echo "riot is already installed" - exit + echo "riot is already installed" + exit fi cd $BASE_DIR diff --git a/riot/start.sh b/riot/start.sh index 1127535d2b..0af9f4faef 100755 --- a/riot/start.sh +++ b/riot/start.sh @@ -5,7 +5,7 @@ PIDFILE=$BASE_DIR/riot.pid CONFIG_BACKUP=config.e2etests_backup.json if [ -f $PIDFILE ]; then - exit + exit fi cd $BASE_DIR/ @@ -14,29 +14,29 @@ pushd riot-web/webapp/ > /dev/null # backup config file before we copy template if [ -f config.json ]; then - mv config.json $CONFIG_BACKUP + mv config.json $CONFIG_BACKUP fi cp $BASE_DIR/config-template/config.json . LOGFILE=$(mktemp) # run web server in the background, showing output on error ( - python -m SimpleHTTPServer $PORT > $LOGFILE 2>&1 & - PID=$! - echo $PID > $PIDFILE - # wait so subshell does not exit - # otherwise sleep below would not work - wait $PID; RESULT=$? + python -m SimpleHTTPServer $PORT > $LOGFILE 2>&1 & + PID=$! + echo $PID > $PIDFILE + # wait so subshell does not exit + # otherwise sleep below would not work + wait $PID; RESULT=$? - # NOT expected SIGTERM (128 + 15) - # from stop.sh? - if [ $RESULT -ne 143 ]; then - echo "failed" - cat $LOGFILE - rm $PIDFILE 2> /dev/null - fi - rm $LOGFILE - exit $RESULT + # NOT expected SIGTERM (128 + 15) + # from stop.sh? + if [ $RESULT -ne 143 ]; then + echo "failed" + cat $LOGFILE + rm $PIDFILE 2> /dev/null + fi + rm $LOGFILE + exit $RESULT )& # to be able to return the exit code for immediate errors (like address already in use) # we wait for a short amount of time in the background and exit when the first @@ -46,6 +46,6 @@ sleep 0.5 & wait -n; RESULT=$? # return exit code of first child to exit if [ $RESULT -eq 0 ]; then - echo "running" + echo "running" fi exit $RESULT diff --git a/riot/stop.sh b/riot/stop.sh index 8d3c925c80..a3e07f574f 100755 --- a/riot/stop.sh +++ b/riot/stop.sh @@ -6,15 +6,15 @@ CONFIG_BACKUP=config.e2etests_backup.json cd $BASE_DIR if [ -f $PIDFILE ]; then - echo "stopping riot server ..." - PID=$(cat $PIDFILE) - rm $PIDFILE - kill $PID + echo "stopping riot server ..." + PID=$(cat $PIDFILE) + rm $PIDFILE + kill $PID - # revert config file - cd riot-web/webapp - rm config.json - if [ -f $CONFIG_BACKUP ]; then - mv $CONFIG_BACKUP config.json - fi + # revert config file + cd riot-web/webapp + rm config.json + if [ -f $CONFIG_BACKUP ]; then + mv $CONFIG_BACKUP config.json + fi fi diff --git a/src/scenario.js b/src/scenario.js index 330db0fef2..f7229057a8 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -5,7 +5,7 @@ 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 + 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, @@ -29,57 +29,57 @@ const getE2EDeviceFromSettings = require('./tests/e2e-device'); const verifyDeviceForUser = require("./tests/verify-device"); module.exports = async function scenario(createSession) { - async function createUser(username) { - const session = await createSession(username); - await signup(session, session.username, 'testtest'); - await acceptServerNoticesInviteAndConsent(session); - return session; - } + async function createUser(username) { + const session = await createSession(username); + await signup(session, session.username, 'testtest'); + await acceptServerNoticesInviteAndConsent(session); + return session; + } - const alice = await createUser("alice"); - const bob = await createUser("bob"); + const alice = await createUser("alice"); + const bob = await createUser("bob"); - await createDirectoryRoomAndTalk(alice, bob); - await createE2ERoomAndTalk(alice, bob); + await createDirectoryRoomAndTalk(alice, bob); + await createE2ERoomAndTalk(alice, bob); } async function createDirectoryRoomAndTalk(alice, bob) { - console.log(" creating a public room and join through directory:"); - const room = 'test'; - await createRoom(alice, room); - await changeRoomSettings(alice, {directory: true, visibility: "public_no_guests"}); - await join(bob, room); - const bobMessage = "hi Alice!"; - await sendMessage(bob, bobMessage); - await receiveMessage(alice, {sender: "bob", body: bobMessage}); - const aliceMessage = "hi Bob, welcome!" - await sendMessage(alice, aliceMessage); - await receiveMessage(bob, {sender: "alice", body: aliceMessage}); + console.log(" creating a public room and join through directory:"); + const room = 'test'; + await createRoom(alice, room); + await changeRoomSettings(alice, {directory: true, visibility: "public_no_guests"}); + await join(bob, room); + const bobMessage = "hi Alice!"; + await sendMessage(bob, bobMessage); + await receiveMessage(alice, {sender: "bob", body: bobMessage}); + const aliceMessage = "hi Bob, welcome!" + await sendMessage(alice, aliceMessage); + await receiveMessage(bob, {sender: "alice", body: aliceMessage}); } async function createE2ERoomAndTalk(alice, bob) { - console.log(" creating an e2e encrypted room and join through invite:"); - const room = "secrets"; - await createRoom(bob, room); - await changeRoomSettings(bob, {encryption: true}); - await invite(bob, "@alice:localhost"); - await acceptInvite(alice, room); - const bobDevice = await getE2EDeviceFromSettings(bob); - // wait some time for the encryption warning dialog - // to appear after closing the settings - await bob.delay(500); - await acceptDialog(bob, "encryption"); - const aliceDevice = await getE2EDeviceFromSettings(alice); - // wait some time for the encryption warning dialog - // to appear after closing the settings - await alice.delay(500); - await acceptDialog(alice, "encryption"); - await verifyDeviceForUser(bob, "alice", aliceDevice); - await verifyDeviceForUser(alice, "bob", bobDevice); - const aliceMessage = "Guess what I just heard?!" - await sendMessage(alice, aliceMessage); - await receiveMessage(bob, {sender: "alice", body: aliceMessage, encrypted: true}); - const bobMessage = "You've got to tell me!"; - await sendMessage(bob, bobMessage); - await receiveMessage(alice, {sender: "bob", body: bobMessage, encrypted: true}); + console.log(" creating an e2e encrypted room and join through invite:"); + const room = "secrets"; + await createRoom(bob, room); + await changeRoomSettings(bob, {encryption: true}); + await invite(bob, "@alice:localhost"); + await acceptInvite(alice, room); + const bobDevice = await getE2EDeviceFromSettings(bob); + // wait some time for the encryption warning dialog + // to appear after closing the settings + await bob.delay(500); + await acceptDialog(bob, "encryption"); + const aliceDevice = await getE2EDeviceFromSettings(alice); + // wait some time for the encryption warning dialog + // to appear after closing the settings + await alice.delay(500); + await acceptDialog(alice, "encryption"); + await verifyDeviceForUser(bob, "alice", aliceDevice); + await verifyDeviceForUser(alice, "bob", bobDevice); + const aliceMessage = "Guess what I just heard?!" + await sendMessage(alice, aliceMessage); + await receiveMessage(bob, {sender: "alice", body: aliceMessage, encrypted: true}); + const bobMessage = "You've got to tell me!"; + await sendMessage(bob, bobMessage); + await receiveMessage(alice, {sender: "bob", body: bobMessage, encrypted: true}); } diff --git a/src/session.js b/src/session.js index 4f6e04584f..570bb4b558 100644 --- a/src/session.js +++ b/src/session.js @@ -5,7 +5,7 @@ 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 + 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, @@ -17,180 +17,180 @@ limitations under the License. const puppeteer = require('puppeteer'); class LogBuffer { - constructor(page, eventName, eventMapper, reduceAsync=false, initialValue = "") { - this.buffer = initialValue; - page.on(eventName, (arg) => { - const result = eventMapper(arg); - if (reduceAsync) { - result.then((r) => this.buffer += r); - } - else { - this.buffer += result; - } - }); - } + constructor(page, eventName, eventMapper, reduceAsync=false, initialValue = "") { + this.buffer = initialValue; + page.on(eventName, (arg) => { + const result = eventMapper(arg); + if (reduceAsync) { + result.then((r) => this.buffer += r); + } + else { + this.buffer += result; + } + }); + } } class Logger { - constructor(username) { - this.indent = 0; - this.username = username; - } + constructor(username) { + this.indent = 0; + this.username = username; + } - startGroup(description) { - const indent = " ".repeat(this.indent * 2); - console.log(`${indent} * ${this.username} ${description}:`); - this.indent += 1; - } + startGroup(description) { + const indent = " ".repeat(this.indent * 2); + console.log(`${indent} * ${this.username} ${description}:`); + this.indent += 1; + } - endGroup() { - this.indent -= 1; - } + endGroup() { + this.indent -= 1; + } - step(description) { - const indent = " ".repeat(this.indent * 2); - process.stdout.write(`${indent} * ${this.username} ${description} ... `); - } + step(description) { + const indent = " ".repeat(this.indent * 2); + process.stdout.write(`${indent} * ${this.username} ${description} ... `); + } - done(status = "done") { - process.stdout.write(status + "\n"); - } + done(status = "done") { + process.stdout.write(status + "\n"); + } } module.exports = class RiotSession { - constructor(browser, page, username, riotserver) { - this.browser = browser; - this.page = page; - this.riotserver = riotserver; - this.username = username; - this.consoleLog = new LogBuffer(page, "console", (msg) => `${msg.text()}\n`); - this.networkLog = new LogBuffer(page, "requestfinished", async (req) => { - const type = req.resourceType(); - const response = await req.response(); - return `${type} ${response.status()} ${req.method()} ${req.url()} \n`; - }, true); - this.log = new Logger(this.username); - } - - static async create(username, puppeteerOptions, riotserver) { - const browser = await puppeteer.launch(puppeteerOptions); - const page = await browser.newPage(); - await page.setViewport({ - width: 1280, - height: 800 - }); - return new RiotSession(browser, page, username, riotserver); - } - - async tryGetInnertext(selector) { - const field = await this.page.$(selector); - if (field != null) { - const text_handle = await field.getProperty('innerText'); - return await text_handle.jsonValue(); + constructor(browser, page, username, riotserver) { + this.browser = browser; + this.page = page; + this.riotserver = riotserver; + this.username = username; + this.consoleLog = new LogBuffer(page, "console", (msg) => `${msg.text()}\n`); + this.networkLog = new LogBuffer(page, "requestfinished", async (req) => { + const type = req.resourceType(); + const response = await req.response(); + return `${type} ${response.status()} ${req.method()} ${req.url()} \n`; + }, true); + this.log = new Logger(this.username); } - return null; - } - async getElementProperty(handle, property) { - const propHandle = await handle.getProperty(property); - return await propHandle.jsonValue(); - } - - innerText(field) { - return this.getElementProperty(field, 'innerText'); - } - - getOuterHTML(element_handle) { - return this.getElementProperty(field, 'outerHTML'); - } - - consoleLogs() { - return this.consoleLog.buffer; - } - - networkLogs() { - return this.networkLog.buffer; - } - - logXHRRequests() { - let buffer = ""; - this.page.on('requestfinished', async (req) => { - const type = req.resourceType(); - const response = await req.response(); - //if (type === 'xhr' || type === 'fetch') { - buffer += `${type} ${response.status()} ${req.method()} ${req.url()} \n`; - // if (req.method() === "POST") { - // buffer += " Post data: " + req.postData(); - // } - //} - }); - return { - logs() { - return buffer; - } + static async create(username, puppeteerOptions, riotserver) { + const browser = await puppeteer.launch(puppeteerOptions); + const page = await browser.newPage(); + await page.setViewport({ + width: 1280, + height: 800 + }); + return new RiotSession(browser, page, username, riotserver); } - } - async printElements(label, elements) { - console.log(label, await Promise.all(elements.map(getOuterHTML))); - } + async tryGetInnertext(selector) { + const field = await this.page.$(selector); + if (field != null) { + const text_handle = await field.getProperty('innerText'); + return await text_handle.jsonValue(); + } + return null; + } - async replaceInputText(input, text) { - // click 3 times to select all text - await input.click({clickCount: 3}); - // then remove it with backspace - await input.press('Backspace'); - // and type the new text - await input.type(text); - } + async getElementProperty(handle, property) { + const propHandle = await handle.getProperty(property); + return await propHandle.jsonValue(); + } - query(selector) { - return this.page.$(selector); - } - - waitAndQuery(selector, timeout = 500) { - return this.page.waitForSelector(selector, {visible: true, timeout}); - } + innerText(field) { + return this.getElementProperty(field, 'innerText'); + } - queryAll(selector) { - return this.page.$$(selector); - } + getOuterHTML(element_handle) { + return this.getElementProperty(field, 'outerHTML'); + } - async waitAndQueryAll(selector, timeout = 500) { - await this.waitAndQuery(selector, timeout); - return await this.queryAll(selector); - } + consoleLogs() { + return this.consoleLog.buffer; + } - waitForNewPage(timeout = 500) { - return new Promise((resolve, reject) => { - const timeoutHandle = setTimeout(() => { - this.browser.removeEventListener('targetcreated', callback); - reject(new Error(`timeout of ${timeout}ms for waitForNewPage elapsed`)); - }, timeout); + networkLogs() { + return this.networkLog.buffer; + } - const callback = async (target) => { - clearTimeout(timeoutHandle); - const page = await target.page(); - resolve(page); - }; + logXHRRequests() { + let buffer = ""; + this.page.on('requestfinished', async (req) => { + const type = req.resourceType(); + const response = await req.response(); + //if (type === 'xhr' || type === 'fetch') { + buffer += `${type} ${response.status()} ${req.method()} ${req.url()} \n`; + // if (req.method() === "POST") { + // buffer += " Post data: " + req.postData(); + // } + //} + }); + return { + logs() { + return buffer; + } + } + } - this.browser.once('targetcreated', callback); - }); - } + async printElements(label, elements) { + console.log(label, await Promise.all(elements.map(getOuterHTML))); + } - goto(url) { - return this.page.goto(url); - } + async replaceInputText(input, text) { + // click 3 times to select all text + await input.click({clickCount: 3}); + // then remove it with backspace + await input.press('Backspace'); + // and type the new text + await input.type(text); + } - url(path) { - return this.riotserver + path; - } + query(selector) { + return this.page.$(selector); + } - delay(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); - } + waitAndQuery(selector, timeout = 500) { + return this.page.waitForSelector(selector, {visible: true, timeout}); + } - close() { - return this.browser.close(); - } + queryAll(selector) { + return this.page.$$(selector); + } + + async waitAndQueryAll(selector, timeout = 500) { + await this.waitAndQuery(selector, timeout); + return await this.queryAll(selector); + } + + waitForNewPage(timeout = 500) { + return new Promise((resolve, reject) => { + const timeoutHandle = setTimeout(() => { + this.browser.removeEventListener('targetcreated', callback); + reject(new Error(`timeout of ${timeout}ms for waitForNewPage elapsed`)); + }, timeout); + + const callback = async (target) => { + clearTimeout(timeoutHandle); + const page = await target.page(); + resolve(page); + }; + + this.browser.once('targetcreated', callback); + }); + } + + goto(url) { + return this.page.goto(url); + } + + url(path) { + return this.riotserver + path; + } + + delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + close() { + return this.browser.close(); + } } diff --git a/src/tests/accept-invite.js b/src/tests/accept-invite.js index 5cdeeb3d84..c83e0c02cc 100644 --- a/src/tests/accept-invite.js +++ b/src/tests/accept-invite.js @@ -5,7 +5,7 @@ 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 + 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, @@ -18,24 +18,24 @@ const assert = require('assert'); const {acceptDialogMaybe} = require('./dialog'); module.exports = async function acceptInvite(session, name) { - session.log.step(`accepts "${name}" invite`); - //TODO: brittle selector - const invitesHandles = await session.waitAndQueryAll('.mx_RoomTile_name.mx_RoomTile_invite', 1000); - const invitesWithText = await Promise.all(invitesHandles.map(async (inviteHandle) => { - const text = await session.innerText(inviteHandle); - return {inviteHandle, text}; - })); - const inviteHandle = invitesWithText.find(({inviteHandle, text}) => { - return text.trim() === name; - }).inviteHandle; + session.log.step(`accepts "${name}" invite`); + //TODO: brittle selector + const invitesHandles = await session.waitAndQueryAll('.mx_RoomTile_name.mx_RoomTile_invite', 1000); + const invitesWithText = await Promise.all(invitesHandles.map(async (inviteHandle) => { + const text = await session.innerText(inviteHandle); + return {inviteHandle, text}; + })); + const inviteHandle = invitesWithText.find(({inviteHandle, text}) => { + return text.trim() === name; + }).inviteHandle; - await inviteHandle.click(); + await inviteHandle.click(); - const acceptInvitationLink = await session.waitAndQuery(".mx_RoomPreviewBar_join_text a:first-child"); - await acceptInvitationLink.click(); + const acceptInvitationLink = await session.waitAndQuery(".mx_RoomPreviewBar_join_text a:first-child"); + await acceptInvitationLink.click(); - // accept e2e warning dialog - acceptDialogMaybe(session, "encryption"); + // accept e2e warning dialog + acceptDialogMaybe(session, "encryption"); - session.log.done(); + session.log.done(); } \ No newline at end of file diff --git a/src/tests/consent.js b/src/tests/consent.js index cd3d51c1b6..595caf4d99 100644 --- a/src/tests/consent.js +++ b/src/tests/consent.js @@ -17,11 +17,11 @@ limitations under the License. const assert = require('assert'); module.exports = async function acceptTerms(session) { - const reviewTermsButton = await session.waitAndQuery('.mx_QuestionDialog button.mx_Dialog_primary', 5000); - const termsPagePromise = session.waitForNewPage(); - await reviewTermsButton.click(); - const termsPage = await termsPagePromise; - const acceptButton = await termsPage.$('input[type=submit]'); - await acceptButton.click(); - await session.delay(500); //TODO yuck, timers + const reviewTermsButton = await session.waitAndQuery('.mx_QuestionDialog button.mx_Dialog_primary', 5000); + const termsPagePromise = session.waitForNewPage(); + await reviewTermsButton.click(); + const termsPage = await termsPagePromise; + const acceptButton = await termsPage.$('input[type=submit]'); + await acceptButton.click(); + await session.delay(500); //TODO yuck, timers } \ No newline at end of file diff --git a/src/tests/create-room.js b/src/tests/create-room.js index 0f7f33ddff..7d3488bfbe 100644 --- a/src/tests/create-room.js +++ b/src/tests/create-room.js @@ -5,7 +5,7 @@ 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 + 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, @@ -17,17 +17,17 @@ limitations under the License. const assert = require('assert'); module.exports = async function createRoom(session, roomName) { - session.log.step(`creates room "${roomName}"`); - //TODO: brittle selector - const createRoomButton = await session.waitAndQuery('.mx_RoleButton[aria-label="Create new room"]'); - await createRoomButton.click(); + session.log.step(`creates room "${roomName}"`); + //TODO: brittle selector + const createRoomButton = await session.waitAndQuery('.mx_RoleButton[aria-label="Create new room"]'); + await createRoomButton.click(); - const roomNameInput = await session.waitAndQuery('.mx_CreateRoomDialog_input'); - await session.replaceInputText(roomNameInput, roomName); + const roomNameInput = await session.waitAndQuery('.mx_CreateRoomDialog_input'); + await session.replaceInputText(roomNameInput, roomName); - const createButton = await session.waitAndQuery('.mx_Dialog_primary'); - await createButton.click(); + const createButton = await session.waitAndQuery('.mx_Dialog_primary'); + await createButton.click(); - await session.waitAndQuery('.mx_MessageComposer'); - session.log.done(); + await session.waitAndQuery('.mx_MessageComposer'); + session.log.done(); } \ No newline at end of file diff --git a/src/tests/dialog.js b/src/tests/dialog.js index 420438b3f9..8d9c798c45 100644 --- a/src/tests/dialog.js +++ b/src/tests/dialog.js @@ -18,30 +18,30 @@ const assert = require('assert'); async function acceptDialog(session, expectedContent) { - const foundDialog = await acceptDialogMaybe(session, expectedContent); - if (!foundDialog) { - throw new Error("could not find a dialog"); - } + const foundDialog = await acceptDialogMaybe(session, expectedContent); + if (!foundDialog) { + throw new Error("could not find a dialog"); + } } async function acceptDialogMaybe(session, expectedContent) { - let dialog = null; - try { - dialog = await session.waitAndQuery(".mx_QuestionDialog", 100); - } catch(err) { - return false; - } - if (expectedContent) { - const contentElement = await dialog.$(".mx_Dialog_content"); - const content = await (await contentElement.getProperty("innerText")).jsonValue(); - assert.ok(content.indexOf(expectedContent) !== -1); - } - const primaryButton = await dialog.$(".mx_Dialog_primary"); - await primaryButton.click(); - return true; + let dialog = null; + try { + dialog = await session.waitAndQuery(".mx_QuestionDialog", 100); + } catch(err) { + return false; + } + if (expectedContent) { + const contentElement = await dialog.$(".mx_Dialog_content"); + const content = await (await contentElement.getProperty("innerText")).jsonValue(); + assert.ok(content.indexOf(expectedContent) !== -1); + } + const primaryButton = await dialog.$(".mx_Dialog_primary"); + await primaryButton.click(); + return true; } module.exports = { - acceptDialog, - acceptDialogMaybe, + acceptDialog, + acceptDialogMaybe, }; \ No newline at end of file diff --git a/src/tests/e2e-device.js b/src/tests/e2e-device.js index fd81ac43eb..03b8c8ce2b 100644 --- a/src/tests/e2e-device.js +++ b/src/tests/e2e-device.js @@ -5,7 +5,7 @@ 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 + 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, @@ -17,15 +17,15 @@ limitations under the License. const assert = require('assert'); module.exports = async function getE2EDeviceFromSettings(session) { - session.log.step(`gets e2e device/key from settings`); - const settingsButton = await session.query('.mx_BottomLeftMenu_settings'); - await settingsButton.click(); - const deviceAndKey = await session.waitAndQueryAll(".mx_UserSettings_section.mx_UserSettings_cryptoSection code"); - assert.equal(deviceAndKey.length, 2); - const id = await (await deviceAndKey[0].getProperty("innerText")).jsonValue(); - const key = await (await deviceAndKey[1].getProperty("innerText")).jsonValue(); - const closeButton = await session.query(".mx_RoomHeader_cancelButton"); - await closeButton.click(); - session.log.done(); - return {id, key}; + session.log.step(`gets e2e device/key from settings`); + const settingsButton = await session.query('.mx_BottomLeftMenu_settings'); + await settingsButton.click(); + const deviceAndKey = await session.waitAndQueryAll(".mx_UserSettings_section.mx_UserSettings_cryptoSection code"); + assert.equal(deviceAndKey.length, 2); + const id = await (await deviceAndKey[0].getProperty("innerText")).jsonValue(); + const key = await (await deviceAndKey[1].getProperty("innerText")).jsonValue(); + const closeButton = await session.query(".mx_RoomHeader_cancelButton"); + await closeButton.click(); + session.log.done(); + return {id, key}; } \ No newline at end of file diff --git a/src/tests/invite.js b/src/tests/invite.js index 37549aa2ca..5a5c66b7c2 100644 --- a/src/tests/invite.js +++ b/src/tests/invite.js @@ -5,7 +5,7 @@ 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 + 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, @@ -17,14 +17,14 @@ limitations under the License. const assert = require('assert'); module.exports = async function invite(session, userId) { - session.log.step(`invites "${userId}" to room`); - await session.delay(200); - const inviteButton = await session.waitAndQuery(".mx_RightPanel_invite"); - await inviteButton.click(); - const inviteTextArea = await session.waitAndQuery(".mx_ChatInviteDialog textarea"); - await inviteTextArea.type(userId); - await inviteTextArea.press("Enter"); - const confirmButton = await session.query(".mx_Dialog_primary"); - await confirmButton.click(); - session.log.done(); + session.log.step(`invites "${userId}" to room`); + await session.delay(200); + const inviteButton = await session.waitAndQuery(".mx_RightPanel_invite"); + await inviteButton.click(); + const inviteTextArea = await session.waitAndQuery(".mx_ChatInviteDialog textarea"); + await inviteTextArea.type(userId); + await inviteTextArea.press("Enter"); + const confirmButton = await session.query(".mx_Dialog_primary"); + await confirmButton.click(); + session.log.done(); } \ No newline at end of file diff --git a/src/tests/join.js b/src/tests/join.js index 8ab5e80f2d..76b98ca397 100644 --- a/src/tests/join.js +++ b/src/tests/join.js @@ -5,7 +5,7 @@ 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 + 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, @@ -17,20 +17,20 @@ limitations under the License. const assert = require('assert'); module.exports = async function join(session, roomName) { - session.log.step(`joins room "${roomName}"`); - //TODO: brittle selector - const directoryButton = await session.waitAndQuery('.mx_RoleButton[aria-label="Room directory"]'); - await directoryButton.click(); + session.log.step(`joins room "${roomName}"`); + //TODO: brittle selector + const directoryButton = await session.waitAndQuery('.mx_RoleButton[aria-label="Room directory"]'); + await directoryButton.click(); - const roomInput = await session.waitAndQuery('.mx_DirectorySearchBox_input'); - await session.replaceInputText(roomInput, roomName); + const roomInput = await session.waitAndQuery('.mx_DirectorySearchBox_input'); + await session.replaceInputText(roomInput, roomName); - const firstRoomLabel = await session.waitAndQuery('.mx_RoomDirectory_table .mx_RoomDirectory_name:first-child', 1000); - await firstRoomLabel.click(); + const firstRoomLabel = await session.waitAndQuery('.mx_RoomDirectory_table .mx_RoomDirectory_name:first-child', 1000); + await firstRoomLabel.click(); - const joinLink = await session.waitAndQuery('.mx_RoomPreviewBar_join_text a'); - await joinLink.click(); + const joinLink = await session.waitAndQuery('.mx_RoomPreviewBar_join_text a'); + await joinLink.click(); - await session.waitAndQuery('.mx_MessageComposer'); - session.log.done(); + await session.waitAndQuery('.mx_MessageComposer'); + session.log.done(); } \ No newline at end of file diff --git a/src/tests/receive-message.js b/src/tests/receive-message.js index 9c963c45f4..73d0cac1a0 100644 --- a/src/tests/receive-message.js +++ b/src/tests/receive-message.js @@ -5,7 +5,7 @@ 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 + 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, @@ -17,33 +17,33 @@ limitations under the License. const assert = require('assert'); module.exports = async function receiveMessage(session, message) { - session.log.step(`receives message "${message.body}" from ${message.sender}`); - // wait for a response to come in that contains the message - // crude, but effective - await session.page.waitForResponse(async (response) => { - if (response.request().url().indexOf("/sync") === -1) { - return false; - } - const body = await response.text(); + session.log.step(`receives message "${message.body}" from ${message.sender}`); + // wait for a response to come in that contains the message + // crude, but effective + await session.page.waitForResponse(async (response) => { + if (response.request().url().indexOf("/sync") === -1) { + return false; + } + const body = await response.text(); + if (message.encrypted) { + return body.indexOf(message.sender) !== -1 && + body.indexOf("m.room.encrypted") !== -1; + } else { + return body.indexOf(message.body) !== -1; + } + }); + // wait a bit for the incoming event to be rendered + await session.delay(300); + let lastTile = await session.query(".mx_EventTile_last"); + const senderElement = await lastTile.$(".mx_SenderProfile_name"); + const bodyElement = await lastTile.$(".mx_EventTile_body"); + const sender = await(await senderElement.getProperty("innerText")).jsonValue(); + const body = await(await bodyElement.getProperty("innerText")).jsonValue(); if (message.encrypted) { - return body.indexOf(message.sender) !== -1 && - body.indexOf("m.room.encrypted") !== -1; - } else { - return body.indexOf(message.body) !== -1; + const e2eIcon = await lastTile.$(".mx_EventTile_e2eIcon"); + assert.ok(e2eIcon); } - }); - // wait a bit for the incoming event to be rendered - await session.delay(300); - let lastTile = await session.query(".mx_EventTile_last"); - const senderElement = await lastTile.$(".mx_SenderProfile_name"); - const bodyElement = await lastTile.$(".mx_EventTile_body"); - const sender = await(await senderElement.getProperty("innerText")).jsonValue(); - const body = await(await bodyElement.getProperty("innerText")).jsonValue(); - if (message.encrypted) { - const e2eIcon = await lastTile.$(".mx_EventTile_e2eIcon"); - assert.ok(e2eIcon); - } - assert.equal(body, message.body); - assert.equal(sender, message.sender); - session.log.done(); + assert.equal(body, message.body); + assert.equal(sender, message.sender); + session.log.done(); } \ No newline at end of file diff --git a/src/tests/room-settings.js b/src/tests/room-settings.js index 127afa26dd..663f275203 100644 --- a/src/tests/room-settings.js +++ b/src/tests/room-settings.js @@ -18,66 +18,66 @@ const assert = require('assert'); const {acceptDialog} = require('./dialog'); async function setCheckboxSetting(session, checkbox, enabled) { - const checked = await session.getElementProperty(checkbox, "checked"); - assert.equal(typeof checked, "boolean"); - if (checked !== enabled) { - await checkbox.click(); - session.log.done(); - return true; - } else { - session.log.done("already set"); - } + const checked = await session.getElementProperty(checkbox, "checked"); + assert.equal(typeof checked, "boolean"); + if (checked !== enabled) { + await checkbox.click(); + session.log.done(); + return true; + } else { + session.log.done("already set"); + } } module.exports = async function changeRoomSettings(session, settings) { - session.log.startGroup(`changes the room settings`); - /// XXX delay is needed here, possible because the header is being rerendered - /// click doesn't do anything otherwise - await session.delay(500); - const settingsButton = await session.query(".mx_RoomHeader .mx_AccessibleButton[title=Settings]"); - await settingsButton.click(); - const checks = await session.waitAndQueryAll(".mx_RoomSettings_settings input[type=checkbox]"); - assert.equal(checks.length, 3); - const e2eEncryptionCheck = checks[0]; - const sendToUnverifiedDevices = checks[1]; - const isDirectory = checks[2]; + session.log.startGroup(`changes the room settings`); + /// XXX delay is needed here, possible because the header is being rerendered + /// click doesn't do anything otherwise + await session.delay(500); + const settingsButton = await session.query(".mx_RoomHeader .mx_AccessibleButton[title=Settings]"); + await settingsButton.click(); + const checks = await session.waitAndQueryAll(".mx_RoomSettings_settings input[type=checkbox]"); + assert.equal(checks.length, 3); + const e2eEncryptionCheck = checks[0]; + const sendToUnverifiedDevices = checks[1]; + const isDirectory = checks[2]; - if (typeof settings.directory === "boolean") { - session.log.step(`sets directory listing to ${settings.directory}`); - await setCheckboxSetting(session, isDirectory, settings.directory); - } + if (typeof settings.directory === "boolean") { + session.log.step(`sets directory listing to ${settings.directory}`); + await setCheckboxSetting(session, isDirectory, settings.directory); + } - if (typeof settings.encryption === "boolean") { - session.log.step(`sets room e2e encryption to ${settings.encryption}`); - const clicked = await setCheckboxSetting(session, e2eEncryptionCheck, settings.encryption); - // if enabling, accept beta warning dialog - if (clicked && settings.encryption) { - await acceptDialog(session, "encryption"); - } - } + if (typeof settings.encryption === "boolean") { + session.log.step(`sets room e2e encryption to ${settings.encryption}`); + const clicked = await setCheckboxSetting(session, e2eEncryptionCheck, settings.encryption); + // if enabling, accept beta warning dialog + if (clicked && settings.encryption) { + await acceptDialog(session, "encryption"); + } + } - if (settings.visibility) { - session.log.step(`sets visibility to ${settings.visibility}`); - const radios = await session.waitAndQueryAll(".mx_RoomSettings_settings input[type=radio]"); - assert.equal(radios.length, 7); - const inviteOnly = radios[0]; - const publicNoGuests = radios[1]; - const publicWithGuests = radios[2]; - - if (settings.visibility === "invite_only") { - await inviteOnly.click(); - } else if (settings.visibility === "public_no_guests") { - await publicNoGuests.click(); - } else if (settings.visibility === "public_with_guests") { - await publicWithGuests.click(); - } else { - throw new Error(`unrecognized room visibility setting: ${settings.visibility}`); - } - session.log.done(); - } + if (settings.visibility) { + session.log.step(`sets visibility to ${settings.visibility}`); + const radios = await session.waitAndQueryAll(".mx_RoomSettings_settings input[type=radio]"); + assert.equal(radios.length, 7); + const inviteOnly = radios[0]; + const publicNoGuests = radios[1]; + const publicWithGuests = radios[2]; + + if (settings.visibility === "invite_only") { + await inviteOnly.click(); + } else if (settings.visibility === "public_no_guests") { + await publicNoGuests.click(); + } else if (settings.visibility === "public_with_guests") { + await publicWithGuests.click(); + } else { + throw new Error(`unrecognized room visibility setting: ${settings.visibility}`); + } + session.log.done(); + } - const saveButton = await session.query(".mx_RoomHeader_wrapper .mx_RoomHeader_textButton"); - await saveButton.click(); + const saveButton = await session.query(".mx_RoomHeader_wrapper .mx_RoomHeader_textButton"); + await saveButton.click(); - session.log.endGroup(); + session.log.endGroup(); } \ No newline at end of file diff --git a/src/tests/send-message.js b/src/tests/send-message.js index e98d0dad72..eb70f5ce23 100644 --- a/src/tests/send-message.js +++ b/src/tests/send-message.js @@ -5,7 +5,7 @@ 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 + 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, @@ -17,13 +17,13 @@ limitations under the License. const assert = require('assert'); module.exports = async function sendMessage(session, message) { - session.log.step(`writes "${message}" in room`); - // this selector needs to be the element that has contenteditable=true, - // not any if its parents, otherwise it behaves flaky at best. - const composer = await session.waitAndQuery('.mx_MessageComposer_editor'); - await composer.type(message); - const text = await session.innerText(composer); - assert.equal(text.trim(), message.trim()); - await composer.press("Enter"); - session.log.done(); + session.log.step(`writes "${message}" in room`); + // this selector needs to be the element that has contenteditable=true, + // not any if its parents, otherwise it behaves flaky at best. + const composer = await session.waitAndQuery('.mx_MessageComposer_editor'); + await composer.type(message); + const text = await session.innerText(composer); + assert.equal(text.trim(), message.trim()); + await composer.press("Enter"); + session.log.done(); } \ No newline at end of file diff --git a/src/tests/server-notices-consent.js b/src/tests/server-notices-consent.js index 3e07248daa..f1f4b7edae 100644 --- a/src/tests/server-notices-consent.js +++ b/src/tests/server-notices-consent.js @@ -5,7 +5,7 @@ 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 + 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, @@ -17,15 +17,15 @@ limitations under the License. const assert = require('assert'); const acceptInvite = require("./accept-invite") module.exports = async function acceptServerNoticesInviteAndConsent(session) { - await acceptInvite(session, "Server Notices"); - session.log.step(`accepts terms & conditions`); - const consentLink = await session.waitAndQuery(".mx_EventTile_body a", 1000); - const termsPagePromise = session.waitForNewPage(); - await consentLink.click(); - const termsPage = await termsPagePromise; - const acceptButton = await termsPage.$('input[type=submit]'); - await acceptButton.click(); - await session.delay(500); //TODO yuck, timers - await termsPage.close(); - session.log.done(); + await acceptInvite(session, "Server Notices"); + session.log.step(`accepts terms & conditions`); + const consentLink = await session.waitAndQuery(".mx_EventTile_body a", 1000); + const termsPagePromise = session.waitForNewPage(); + await consentLink.click(); + const termsPage = await termsPagePromise; + const acceptButton = await termsPage.$('input[type=submit]'); + await acceptButton.click(); + await session.delay(500); //TODO yuck, timers + await termsPage.close(); + session.log.done(); } \ No newline at end of file diff --git a/src/tests/signup.js b/src/tests/signup.js index 434083cbb6..363600f03d 100644 --- a/src/tests/signup.js +++ b/src/tests/signup.js @@ -5,7 +5,7 @@ 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 + 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, @@ -18,52 +18,52 @@ const acceptTerms = require('./consent'); const assert = require('assert'); module.exports = async function signup(session, username, password, homeserver) { - session.log.step("signs up"); - await session.goto(session.url('/#/register')); - //click 'Custom server' radio button - if (homeserver) { - const advancedRadioButton = await session.waitAndQuery('#advanced'); - await advancedRadioButton.click(); - } - // wait until register button is visible - await session.waitAndQuery('.mx_Login_submit[value=Register]'); - //fill out form - const loginFields = await session.queryAll('.mx_Login_field'); - assert.strictEqual(loginFields.length, 7); - const usernameField = loginFields[2]; - const passwordField = loginFields[3]; - const passwordRepeatField = loginFields[4]; - const hsurlField = loginFields[5]; - await session.replaceInputText(usernameField, username); - await session.replaceInputText(passwordField, password); - await session.replaceInputText(passwordRepeatField, password); - if (homeserver) { - await session.waitAndQuery('.mx_ServerConfig'); - await session.replaceInputText(hsurlField, homeserver); - } - //wait over a second because Registration/ServerConfig have a 1000ms - //delay to internally set the homeserver url - //see Registration::render and ServerConfig::props::delayTimeMs - await session.delay(1200); - /// focus on the button to make sure error validation - /// has happened before checking the form is good to go - const registerButton = await session.query('.mx_Login_submit'); - await registerButton.focus(); - //check no errors - const error_text = await session.tryGetInnertext('.mx_Login_error'); - assert.strictEqual(!!error_text, false); - //submit form - //await page.screenshot({path: "beforesubmit.png", fullPage: true}); - await registerButton.click(); + session.log.step("signs up"); + await session.goto(session.url('/#/register')); + //click 'Custom server' radio button + if (homeserver) { + const advancedRadioButton = await session.waitAndQuery('#advanced'); + await advancedRadioButton.click(); + } + // wait until register button is visible + await session.waitAndQuery('.mx_Login_submit[value=Register]'); + //fill out form + const loginFields = await session.queryAll('.mx_Login_field'); + assert.strictEqual(loginFields.length, 7); + const usernameField = loginFields[2]; + const passwordField = loginFields[3]; + const passwordRepeatField = loginFields[4]; + const hsurlField = loginFields[5]; + await session.replaceInputText(usernameField, username); + await session.replaceInputText(passwordField, password); + await session.replaceInputText(passwordRepeatField, password); + if (homeserver) { + await session.waitAndQuery('.mx_ServerConfig'); + await session.replaceInputText(hsurlField, homeserver); + } + //wait over a second because Registration/ServerConfig have a 1000ms + //delay to internally set the homeserver url + //see Registration::render and ServerConfig::props::delayTimeMs + await session.delay(1200); + /// focus on the button to make sure error validation + /// has happened before checking the form is good to go + const registerButton = await session.query('.mx_Login_submit'); + await registerButton.focus(); + //check no errors + const error_text = await session.tryGetInnertext('.mx_Login_error'); + assert.strictEqual(!!error_text, false); + //submit form + //await page.screenshot({path: "beforesubmit.png", fullPage: true}); + await registerButton.click(); - //confirm dialog saying you cant log back in without e-mail - const continueButton = await session.waitAndQuery('.mx_QuestionDialog button.mx_Dialog_primary'); - await continueButton.click(); - //wait for registration to finish so the hash gets set - //onhashchange better? - await session.delay(2000); + //confirm dialog saying you cant log back in without e-mail + const continueButton = await session.waitAndQuery('.mx_QuestionDialog button.mx_Dialog_primary'); + await continueButton.click(); + //wait for registration to finish so the hash gets set + //onhashchange better? + await session.delay(2000); - const url = session.page.url(); - assert.strictEqual(url, session.url('/#/home')); - session.log.done(); + const url = session.page.url(); + assert.strictEqual(url, session.url('/#/home')); + session.log.done(); } diff --git a/src/tests/verify-device.js b/src/tests/verify-device.js index 0622654876..7b01e7c756 100644 --- a/src/tests/verify-device.js +++ b/src/tests/verify-device.js @@ -5,7 +5,7 @@ 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 + 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, @@ -17,26 +17,26 @@ limitations under the License. const assert = require('assert'); module.exports = async function verifyDeviceForUser(session, name, expectedDevice) { - session.log.step(`verifies e2e device for ${name}`); - const memberNameElements = await session.queryAll(".mx_MemberList .mx_EntityTile_name"); - const membersAndNames = await Promise.all(memberNameElements.map(async (el) => { - return [el, await session.innerText(el)]; - })); - const matchingMember = membersAndNames.filter(([el, text]) => { - return text === name; - }).map(([el]) => el)[0]; - await matchingMember.click(); - const firstVerifyButton = await session.waitAndQuery(".mx_MemberDeviceInfo_verify"); - await firstVerifyButton.click(); - const dialogCodeFields = await session.waitAndQueryAll(".mx_QuestionDialog code"); - assert.equal(dialogCodeFields.length, 2); - const deviceId = await session.innerText(dialogCodeFields[0]); - const deviceKey = await session.innerText(dialogCodeFields[1]); - assert.equal(expectedDevice.id, deviceId); - assert.equal(expectedDevice.key, deviceKey); - const confirmButton = await session.query(".mx_Dialog_primary"); - await confirmButton.click(); - const closeMemberInfo = await session.query(".mx_MemberInfo_cancel"); - await closeMemberInfo.click(); - session.log.done(); + session.log.step(`verifies e2e device for ${name}`); + const memberNameElements = await session.queryAll(".mx_MemberList .mx_EntityTile_name"); + const membersAndNames = await Promise.all(memberNameElements.map(async (el) => { + return [el, await session.innerText(el)]; + })); + const matchingMember = membersAndNames.filter(([el, text]) => { + return text === name; + }).map(([el]) => el)[0]; + await matchingMember.click(); + const firstVerifyButton = await session.waitAndQuery(".mx_MemberDeviceInfo_verify"); + await firstVerifyButton.click(); + const dialogCodeFields = await session.waitAndQueryAll(".mx_QuestionDialog code"); + assert.equal(dialogCodeFields.length, 2); + const deviceId = await session.innerText(dialogCodeFields[0]); + const deviceKey = await session.innerText(dialogCodeFields[1]); + assert.equal(expectedDevice.id, deviceId); + assert.equal(expectedDevice.key, deviceKey); + const confirmButton = await session.query(".mx_Dialog_primary"); + await confirmButton.click(); + const closeMemberInfo = await session.query(".mx_MemberInfo_cancel"); + await closeMemberInfo.click(); + session.log.done(); } \ No newline at end of file diff --git a/start.js b/start.js index 6c68050c97..229a9ab535 100644 --- a/start.js +++ b/start.js @@ -5,7 +5,7 @@ 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 + 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, @@ -24,64 +24,64 @@ const noLogs = process.argv.indexOf("--no-logs") !== -1; const debug = process.argv.indexOf("--debug") !== -1; async function runTests() { - let sessions = []; + let sessions = []; - console.log("running tests ..."); - const options = {}; - if (debug) { - // options.slowMo = 10; - options.headless = false; - } - if (process.env.CHROME_PATH) { - const path = process.env.CHROME_PATH; - console.log(`(using external chrome/chromium at ${path}, make sure it's compatible with puppeteer)`); - options.executablePath = path; - } - - async function createSession(username) { - const session = await RiotSession.create(username, options, riotserver); - sessions.push(session); - return session; - } - - let failure = false; - try { - await scenario(createSession); - } catch(err) { - failure = true; - console.log('failure: ', err); - if (!noLogs) { - for(let i = 0; i < sessions.length; ++i) { - const session = sessions[i]; - documentHtml = await session.page.content(); - console.log(`---------------- START OF ${session.username} LOGS ----------------`); - console.log('---------------- console.log output:'); - console.log(session.consoleLogs()); - console.log('---------------- network requests:'); - console.log(session.networkLogs()); - console.log('---------------- document html:'); - console.log(documentHtml); - console.log(`---------------- END OF ${session.username} LOGS ----------------`); - } + console.log("running tests ..."); + const options = {}; + if (debug) { + options.slowMo = 20; + options.headless = false; + } + if (process.env.CHROME_PATH) { + const path = process.env.CHROME_PATH; + console.log(`(using external chrome/chromium at ${path}, make sure it's compatible with puppeteer)`); + options.executablePath = path; } - } - // wait 5 minutes on failure if not running headless - // to inspect what went wrong - if (failure && options.headless === false) { - await new Promise((resolve) => setTimeout(resolve, 5 * 60 * 1000)); - } + async function createSession(username) { + const session = await RiotSession.create(username, options, riotserver); + sessions.push(session); + return session; + } - await Promise.all(sessions.map((session) => session.close())); + let failure = false; + try { + await scenario(createSession); + } catch(err) { + failure = true; + console.log('failure: ', err); + if (!noLogs) { + for(let i = 0; i < sessions.length; ++i) { + const session = sessions[i]; + documentHtml = await session.page.content(); + console.log(`---------------- START OF ${session.username} LOGS ----------------`); + console.log('---------------- console.log output:'); + console.log(session.consoleLogs()); + console.log('---------------- network requests:'); + console.log(session.networkLogs()); + console.log('---------------- document html:'); + console.log(documentHtml); + console.log(`---------------- END OF ${session.username} LOGS ----------------`); + } + } + } - if (failure) { - process.exit(-1); - } else { - console.log('all tests finished successfully'); - } + // wait 5 minutes on failure if not running headless + // to inspect what went wrong + if (failure && options.headless === false) { + await new Promise((resolve) => setTimeout(resolve, 5 * 60 * 1000)); + } + + await Promise.all(sessions.map((session) => session.close())); + + if (failure) { + process.exit(-1); + } else { + console.log('all tests finished successfully'); + } } runTests().catch(function(err) { - console.log(err); - process.exit(-1); + console.log(err); + process.exit(-1); }); \ No newline at end of file diff --git a/synapse/install.sh b/synapse/install.sh index 2e9b668b5e..3d8172a9f6 100755 --- a/synapse/install.sh +++ b/synapse/install.sh @@ -9,8 +9,8 @@ PORT=5005 BASE_DIR=$(readlink -f $(dirname $0)) if [ -d $BASE_DIR/$SERVER_DIR ]; then - echo "synapse is already installed" - exit + echo "synapse is already installed" + exit fi cd $BASE_DIR From 8507cf82582f7810344a1b89594aa40ff2c3175e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 15 Aug 2018 10:49:06 +0200 Subject: [PATCH 081/221] add argument for passing riot server, makes local testing easier --- package.json | 1 + start.js | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index b5892a154a..8035fbb508 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "author": "", "license": "ISC", "dependencies": { + "commander": "^2.17.1", "puppeteer": "^1.6.0" } } diff --git a/start.js b/start.js index 229a9ab535..f1f7960555 100644 --- a/start.js +++ b/start.js @@ -18,18 +18,22 @@ const assert = require('assert'); const RiotSession = require('./src/session'); const scenario = require('./src/scenario'); -const riotserver = 'http://localhost:5000'; - -const noLogs = process.argv.indexOf("--no-logs") !== -1; -const debug = process.argv.indexOf("--debug") !== -1; +const program = require('commander'); +program + .option('--no-logs', "don't output logs, document html on error", false) + .option('--debug', "open browser window and slow down interactions", false) + .option('--riot-url [url]', "riot url to test", "http://localhost:5000") + .parse(process.argv); async function runTests() { let sessions = []; + console.log("program.riotUrl", program.riotUrl); console.log("running tests ..."); const options = {}; - if (debug) { + if (program.debug) { options.slowMo = 20; + options.devtools = true; options.headless = false; } if (process.env.CHROME_PATH) { @@ -39,7 +43,7 @@ async function runTests() { } async function createSession(username) { - const session = await RiotSession.create(username, options, riotserver); + const session = await RiotSession.create(username, options, program.riotUrl); sessions.push(session); return session; } @@ -50,7 +54,7 @@ async function runTests() { } catch(err) { failure = true; console.log('failure: ', err); - if (!noLogs) { + if (!program.noLogs) { for(let i = 0; i < sessions.length; ++i) { const session = sessions[i]; documentHtml = await session.page.content(); @@ -84,4 +88,4 @@ async function runTests() { runTests().catch(function(err) { console.log(err); process.exit(-1); -}); \ No newline at end of file +}); From 0e56250bc2cf2939d7e5ff8995888ecc559dfe79 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 15 Aug 2018 12:21:08 +0200 Subject: [PATCH 082/221] didnt mean to commit this --- start.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/start.js b/start.js index f1f7960555..ac9a2f8684 100644 --- a/start.js +++ b/start.js @@ -27,8 +27,6 @@ program async function runTests() { let sessions = []; - - console.log("program.riotUrl", program.riotUrl); console.log("running tests ..."); const options = {}; if (program.debug) { From 4f76ad83d515881ce878d5358cdb2485d26a4ea0 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 15 Aug 2018 15:04:24 +0200 Subject: [PATCH 083/221] increase timeout --- src/tests/e2e-device.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/e2e-device.js b/src/tests/e2e-device.js index 03b8c8ce2b..4be6677396 100644 --- a/src/tests/e2e-device.js +++ b/src/tests/e2e-device.js @@ -20,7 +20,7 @@ module.exports = async function getE2EDeviceFromSettings(session) { session.log.step(`gets e2e device/key from settings`); const settingsButton = await session.query('.mx_BottomLeftMenu_settings'); await settingsButton.click(); - const deviceAndKey = await session.waitAndQueryAll(".mx_UserSettings_section.mx_UserSettings_cryptoSection code"); + const deviceAndKey = await session.waitAndQueryAll(".mx_UserSettings_section.mx_UserSettings_cryptoSection code", 1000); assert.equal(deviceAndKey.length, 2); const id = await (await deviceAndKey[0].getProperty("innerText")).jsonValue(); const key = await (await deviceAndKey[1].getProperty("innerText")).jsonValue(); @@ -28,4 +28,4 @@ module.exports = async function getE2EDeviceFromSettings(session) { await closeButton.click(); session.log.done(); return {id, key}; -} \ No newline at end of file +} From 440b1032d5aa46a23c5ad8bd1dc4d089a5bb5c5f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 15 Aug 2018 15:17:11 +0200 Subject: [PATCH 084/221] increase receive message timeout --- src/tests/receive-message.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/receive-message.js b/src/tests/receive-message.js index 73d0cac1a0..9a283f4695 100644 --- a/src/tests/receive-message.js +++ b/src/tests/receive-message.js @@ -33,7 +33,7 @@ module.exports = async function receiveMessage(session, message) { } }); // wait a bit for the incoming event to be rendered - await session.delay(300); + await session.delay(500); let lastTile = await session.query(".mx_EventTile_last"); const senderElement = await lastTile.$(".mx_SenderProfile_name"); const bodyElement = await lastTile.$(".mx_EventTile_body"); @@ -46,4 +46,4 @@ module.exports = async function receiveMessage(session, message) { assert.equal(body, message.body); assert.equal(sender, message.sender); session.log.done(); -} \ No newline at end of file +} From fd67ace078f86c213d348b22625285ff935c5827 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 23 Aug 2018 00:27:30 +0200 Subject: [PATCH 085/221] increase timeouts so the tests dont timeout on build server --- src/scenario.js | 4 ++-- src/session.js | 6 +++--- src/tests/consent.js | 4 ++-- src/tests/invite.js | 4 ++-- src/tests/receive-message.js | 2 +- src/tests/room-settings.js | 6 +++--- src/tests/server-notices-consent.js | 4 ++-- src/tests/signup.js | 2 +- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/scenario.js b/src/scenario.js index f7229057a8..d859b74781 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -67,12 +67,12 @@ async function createE2ERoomAndTalk(alice, bob) { const bobDevice = await getE2EDeviceFromSettings(bob); // wait some time for the encryption warning dialog // to appear after closing the settings - await bob.delay(500); + await bob.delay(1000); await acceptDialog(bob, "encryption"); const aliceDevice = await getE2EDeviceFromSettings(alice); // wait some time for the encryption warning dialog // to appear after closing the settings - await alice.delay(500); + await alice.delay(1000); await acceptDialog(alice, "encryption"); await verifyDeviceForUser(bob, "alice", aliceDevice); await verifyDeviceForUser(alice, "bob", bobDevice); diff --git a/src/session.js b/src/session.js index 570bb4b558..ba0384b91f 100644 --- a/src/session.js +++ b/src/session.js @@ -148,7 +148,7 @@ module.exports = class RiotSession { return this.page.$(selector); } - waitAndQuery(selector, timeout = 500) { + waitAndQuery(selector, timeout = 5000) { return this.page.waitForSelector(selector, {visible: true, timeout}); } @@ -156,12 +156,12 @@ module.exports = class RiotSession { return this.page.$$(selector); } - async waitAndQueryAll(selector, timeout = 500) { + async waitAndQueryAll(selector, timeout = 5000) { await this.waitAndQuery(selector, timeout); return await this.queryAll(selector); } - waitForNewPage(timeout = 500) { + waitForNewPage(timeout = 5000) { return new Promise((resolve, reject) => { const timeoutHandle = setTimeout(() => { this.browser.removeEventListener('targetcreated', callback); diff --git a/src/tests/consent.js b/src/tests/consent.js index 595caf4d99..0b25eb06be 100644 --- a/src/tests/consent.js +++ b/src/tests/consent.js @@ -23,5 +23,5 @@ module.exports = async function acceptTerms(session) { const termsPage = await termsPagePromise; const acceptButton = await termsPage.$('input[type=submit]'); await acceptButton.click(); - await session.delay(500); //TODO yuck, timers -} \ No newline at end of file + await session.delay(1000); //TODO yuck, timers +} diff --git a/src/tests/invite.js b/src/tests/invite.js index 5a5c66b7c2..934beb6819 100644 --- a/src/tests/invite.js +++ b/src/tests/invite.js @@ -18,7 +18,7 @@ const assert = require('assert'); module.exports = async function invite(session, userId) { session.log.step(`invites "${userId}" to room`); - await session.delay(200); + await session.delay(1000); const inviteButton = await session.waitAndQuery(".mx_RightPanel_invite"); await inviteButton.click(); const inviteTextArea = await session.waitAndQuery(".mx_ChatInviteDialog textarea"); @@ -27,4 +27,4 @@ module.exports = async function invite(session, userId) { const confirmButton = await session.query(".mx_Dialog_primary"); await confirmButton.click(); session.log.done(); -} \ No newline at end of file +} diff --git a/src/tests/receive-message.js b/src/tests/receive-message.js index 9a283f4695..afe4247181 100644 --- a/src/tests/receive-message.js +++ b/src/tests/receive-message.js @@ -33,7 +33,7 @@ module.exports = async function receiveMessage(session, message) { } }); // wait a bit for the incoming event to be rendered - await session.delay(500); + await session.delay(1000); let lastTile = await session.query(".mx_EventTile_last"); const senderElement = await lastTile.$(".mx_SenderProfile_name"); const bodyElement = await lastTile.$(".mx_EventTile_body"); diff --git a/src/tests/room-settings.js b/src/tests/room-settings.js index 663f275203..9f802da711 100644 --- a/src/tests/room-settings.js +++ b/src/tests/room-settings.js @@ -33,7 +33,7 @@ module.exports = async function changeRoomSettings(session, settings) { session.log.startGroup(`changes the room settings`); /// XXX delay is needed here, possible because the header is being rerendered /// click doesn't do anything otherwise - await session.delay(500); + await session.delay(1000); const settingsButton = await session.query(".mx_RoomHeader .mx_AccessibleButton[title=Settings]"); await settingsButton.click(); const checks = await session.waitAndQueryAll(".mx_RoomSettings_settings input[type=checkbox]"); @@ -63,7 +63,7 @@ module.exports = async function changeRoomSettings(session, settings) { const inviteOnly = radios[0]; const publicNoGuests = radios[1]; const publicWithGuests = radios[2]; - + if (settings.visibility === "invite_only") { await inviteOnly.click(); } else if (settings.visibility === "public_no_guests") { @@ -80,4 +80,4 @@ module.exports = async function changeRoomSettings(session, settings) { await saveButton.click(); session.log.endGroup(); -} \ No newline at end of file +} diff --git a/src/tests/server-notices-consent.js b/src/tests/server-notices-consent.js index f1f4b7edae..d0c91da7b1 100644 --- a/src/tests/server-notices-consent.js +++ b/src/tests/server-notices-consent.js @@ -25,7 +25,7 @@ module.exports = async function acceptServerNoticesInviteAndConsent(session) { const termsPage = await termsPagePromise; const acceptButton = await termsPage.$('input[type=submit]'); await acceptButton.click(); - await session.delay(500); //TODO yuck, timers + await session.delay(1000); //TODO yuck, timers await termsPage.close(); session.log.done(); -} \ No newline at end of file +} diff --git a/src/tests/signup.js b/src/tests/signup.js index 363600f03d..b715e111a1 100644 --- a/src/tests/signup.js +++ b/src/tests/signup.js @@ -44,7 +44,7 @@ module.exports = async function signup(session, username, password, homeserver) //wait over a second because Registration/ServerConfig have a 1000ms //delay to internally set the homeserver url //see Registration::render and ServerConfig::props::delayTimeMs - await session.delay(1200); + await session.delay(1500); /// focus on the button to make sure error validation /// has happened before checking the form is good to go const registerButton = await session.query('.mx_Login_submit'); From f49b85897d2eef02574ef5719cf0dccb12986446 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 23 Aug 2018 00:29:24 +0200 Subject: [PATCH 086/221] remove specific timeout for selectors as these are not hard sleeps, but timeouts, its better to put them a bit larger, as in the best case they'll return quickly anyway and in the worst case where they need a lot of time it's still better if the tests don't fail --- src/tests/accept-invite.js | 4 ++-- src/tests/consent.js | 2 +- src/tests/dialog.js | 4 ++-- src/tests/server-notices-consent.js | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/tests/accept-invite.js b/src/tests/accept-invite.js index c83e0c02cc..8cc1a0b37d 100644 --- a/src/tests/accept-invite.js +++ b/src/tests/accept-invite.js @@ -20,7 +20,7 @@ const {acceptDialogMaybe} = require('./dialog'); module.exports = async function acceptInvite(session, name) { session.log.step(`accepts "${name}" invite`); //TODO: brittle selector - const invitesHandles = await session.waitAndQueryAll('.mx_RoomTile_name.mx_RoomTile_invite', 1000); + const invitesHandles = await session.waitAndQueryAll('.mx_RoomTile_name.mx_RoomTile_invite'); const invitesWithText = await Promise.all(invitesHandles.map(async (inviteHandle) => { const text = await session.innerText(inviteHandle); return {inviteHandle, text}; @@ -38,4 +38,4 @@ module.exports = async function acceptInvite(session, name) { acceptDialogMaybe(session, "encryption"); session.log.done(); -} \ No newline at end of file +} diff --git a/src/tests/consent.js b/src/tests/consent.js index 0b25eb06be..b4a6289fca 100644 --- a/src/tests/consent.js +++ b/src/tests/consent.js @@ -17,7 +17,7 @@ limitations under the License. const assert = require('assert'); module.exports = async function acceptTerms(session) { - const reviewTermsButton = await session.waitAndQuery('.mx_QuestionDialog button.mx_Dialog_primary', 5000); + const reviewTermsButton = await session.waitAndQuery('.mx_QuestionDialog button.mx_Dialog_primary'); const termsPagePromise = session.waitForNewPage(); await reviewTermsButton.click(); const termsPage = await termsPagePromise; diff --git a/src/tests/dialog.js b/src/tests/dialog.js index 8d9c798c45..89c70470d9 100644 --- a/src/tests/dialog.js +++ b/src/tests/dialog.js @@ -27,7 +27,7 @@ async function acceptDialog(session, expectedContent) { async function acceptDialogMaybe(session, expectedContent) { let dialog = null; try { - dialog = await session.waitAndQuery(".mx_QuestionDialog", 100); + dialog = await session.waitAndQuery(".mx_QuestionDialog"); } catch(err) { return false; } @@ -44,4 +44,4 @@ async function acceptDialogMaybe(session, expectedContent) { module.exports = { acceptDialog, acceptDialogMaybe, -}; \ No newline at end of file +}; diff --git a/src/tests/server-notices-consent.js b/src/tests/server-notices-consent.js index d0c91da7b1..25c3bb3bd5 100644 --- a/src/tests/server-notices-consent.js +++ b/src/tests/server-notices-consent.js @@ -19,7 +19,7 @@ const acceptInvite = require("./accept-invite") module.exports = async function acceptServerNoticesInviteAndConsent(session) { await acceptInvite(session, "Server Notices"); session.log.step(`accepts terms & conditions`); - const consentLink = await session.waitAndQuery(".mx_EventTile_body a", 1000); + const consentLink = await session.waitAndQuery(".mx_EventTile_body a"); const termsPagePromise = session.waitForNewPage(); await consentLink.click(); const termsPage = await termsPagePromise; From 6be597505026fdc006ce6e47f9b1795ad4626596 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 23 Aug 2018 10:03:37 +0200 Subject: [PATCH 087/221] dont assume new target is a new page --- src/session.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/session.js b/src/session.js index ba0384b91f..bcccc9d6cc 100644 --- a/src/session.js +++ b/src/session.js @@ -164,17 +164,21 @@ module.exports = class RiotSession { waitForNewPage(timeout = 5000) { return new Promise((resolve, reject) => { const timeoutHandle = setTimeout(() => { - this.browser.removeEventListener('targetcreated', callback); + this.browser.removeListener('targetcreated', callback); reject(new Error(`timeout of ${timeout}ms for waitForNewPage elapsed`)); }, timeout); const callback = async (target) => { + if (target.type() !== 'page') { + return; + } + this.browser.removeListener('targetcreated', callback); clearTimeout(timeoutHandle); const page = await target.page(); resolve(page); }; - this.browser.once('targetcreated', callback); + this.browser.on('targetcreated', callback); }); } From a65d6af8c5ae72ba691756e88adb0487cfed2e72 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 23 Aug 2018 10:04:06 +0200 Subject: [PATCH 088/221] encryption dialogs dont always appear coming back from settings... weird --- src/scenario.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/scenario.js b/src/scenario.js index d859b74781..394e89f7cd 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -15,7 +15,7 @@ limitations under the License. */ -const {acceptDialog} = require('./tests/dialog'); +const {acceptDialogMaybe} = require('./tests/dialog'); const signup = require('./tests/signup'); const join = require('./tests/join'); const sendMessage = require('./tests/send-message'); @@ -68,12 +68,12 @@ async function createE2ERoomAndTalk(alice, bob) { // wait some time for the encryption warning dialog // to appear after closing the settings await bob.delay(1000); - await acceptDialog(bob, "encryption"); + await acceptDialogMaybe(bob, "encryption"); const aliceDevice = await getE2EDeviceFromSettings(alice); // wait some time for the encryption warning dialog // to appear after closing the settings await alice.delay(1000); - await acceptDialog(alice, "encryption"); + await acceptDialogMaybe(alice, "encryption"); await verifyDeviceForUser(bob, "alice", aliceDevice); await verifyDeviceForUser(alice, "bob", bobDevice); const aliceMessage = "Guess what I just heard?!" From 2321e43fd8c9622efbe539efaace4089021474fc Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 23 Aug 2018 10:04:37 +0200 Subject: [PATCH 089/221] commander inverts the meaning of program args by itself ... nice, I guess --- start.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/start.js b/start.js index ac9a2f8684..f3eac32f9f 100644 --- a/start.js +++ b/start.js @@ -52,7 +52,7 @@ async function runTests() { } catch(err) { failure = true; console.log('failure: ', err); - if (!program.noLogs) { + if (program.logs) { for(let i = 0; i < sessions.length; ++i) { const session = sessions[i]; documentHtml = await session.page.content(); From 98aafd6abbe63748886e198bca0ed044d8bb86de Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 11 Sep 2018 14:37:36 +0200 Subject: [PATCH 090/221] add rest/non-browser session, which we can create a lot more off --- package.json | 6 +++- src/rest/consent.js | 30 ++++++++++++++++ src/rest/factory.js | 77 +++++++++++++++++++++++++++++++++++++++ src/rest/room.js | 42 ++++++++++++++++++++++ src/rest/session.js | 88 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 src/rest/consent.js create mode 100644 src/rest/factory.js create mode 100644 src/rest/room.js create mode 100644 src/rest/session.js diff --git a/package.json b/package.json index 8035fbb508..f3c47ac491 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,11 @@ "author": "", "license": "ISC", "dependencies": { + "cheerio": "^1.0.0-rc.2", "commander": "^2.17.1", - "puppeteer": "^1.6.0" + "puppeteer": "^1.6.0", + "request": "^2.88.0", + "request-promise-native": "^1.0.5", + "uuid": "^3.3.2" } } diff --git a/src/rest/consent.js b/src/rest/consent.js new file mode 100644 index 0000000000..1e36f541a3 --- /dev/null +++ b/src/rest/consent.js @@ -0,0 +1,30 @@ +/* +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 cheerio = require('cheerio'); +const url = require("url"); + +module.exports.approveConsent = async function(consentUrl) { + const body = await request.get(consentUrl); + const doc = cheerio.load(body); + const v = doc("input[name=v]").val(); + const u = doc("input[name=u]").val(); + const h = doc("input[name=h]").val(); + const formAction = doc("form").attr("action"); + const absAction = url.resolve(consentUrl, formAction); + await request.post(absAction).form({v, u, h}); +}; diff --git a/src/rest/factory.js b/src/rest/factory.js new file mode 100644 index 0000000000..2df6624ef9 --- /dev/null +++ b/src/rest/factory.js @@ -0,0 +1,77 @@ +/* +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 util = require('util'); +const exec = util.promisify(require('child_process').exec); +const request = require('request-promise-native'); +const RestSession = require('./session'); + +module.exports = class RestSessionFactory { + constructor(synapseSubdir, hsUrl, cwd) { + this.synapseSubdir = synapseSubdir; + this.hsUrl = hsUrl; + this.cwd = cwd; + } + + async createSession(username, password) { + await this._register(username, password); + const authResult = await this._authenticate(username, password); + return new RestSession(authResult); + } + + _register(username, password) { + const registerArgs = [ + '-c homeserver.yaml', + `-u ${username}`, + `-p ${password}`, + // '--regular-user', + '-a', //until PR gets merged + this.hsUrl + ]; + const registerCmd = `./scripts/register_new_matrix_user ${registerArgs.join(' ')}`; + const allCmds = [ + `cd ${this.synapseSubdir}`, + "source env/bin/activate", + registerCmd + ].join(';'); + + return exec(allCmds, {cwd: this.cwd, encoding: 'utf-8'}).catch((result) => { + const lines = result.stdout.trim().split('\n'); + const failureReason = lines[lines.length - 1]; + throw new Error(`creating user ${username} failed: ${failureReason}`); + }); + } + + async _authenticate(username, password) { + const requestBody = { + "type": "m.login.password", + "identifier": { + "type": "m.id.user", + "user": username + }, + "password": password + }; + const url = `${this.hsUrl}/_matrix/client/r0/login`; + const responseBody = await request.post({url, json: true, body: requestBody}); + return { + accessToken: responseBody.access_token, + homeServer: responseBody.home_server, + userId: responseBody.user_id, + deviceId: responseBody.device_id, + hsUrl: this.hsUrl, + }; + } +} diff --git a/src/rest/room.js b/src/rest/room.js new file mode 100644 index 0000000000..b7da1789ff --- /dev/null +++ b/src/rest/room.js @@ -0,0 +1,42 @@ +/* +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 uuidv4 = require('uuid/v4'); + +/* no pun intented */ +module.exports = class RestRoom { + constructor(session, roomId) { + this.session = session; + this.roomId = roomId; + } + + async talk(message) { + const txId = uuidv4(); + await this.session._put(`/rooms/${this.roomId}/send/m.room.message/${txId}`, { + "msgtype": "m.text", + "body": message + }); + return txId; + } + + async leave() { + await this.session._post(`/rooms/${this.roomId}/leave`); + } + + id() { + return this.roomId; + } +} diff --git a/src/rest/session.js b/src/rest/session.js new file mode 100644 index 0000000000..f57d0467f5 --- /dev/null +++ b/src/rest/session.js @@ -0,0 +1,88 @@ +/* +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 RestRoom = require('./room'); +const {approveConsent} = require('./consent'); + +module.exports = class RestSession { + constructor(credentials) { + this.credentials = credentials; + } + + _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}`); + } + } + } + + async join(roomId) { + const {room_id} = await this._post(`/rooms/${roomId}/join`); + return new RestRoom(this, room_id); + } + + + async createRoom(name, options) { + 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 {room_id} = await this._post(`/createRoom`, body); + return new RestRoom(this, room_id); + } +} From afc678fea066e8d5e75461bb078c0dacb2401eee Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 11 Sep 2018 14:46:25 +0200 Subject: [PATCH 091/221] pass rest session creator to scenario --- src/scenario.js | 2 +- start.js | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/scenario.js b/src/scenario.js index f7229057a8..f774a8649d 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -28,7 +28,7 @@ const acceptServerNoticesInviteAndConsent = require('./tests/server-notices-cons const getE2EDeviceFromSettings = require('./tests/e2e-device'); const verifyDeviceForUser = require("./tests/verify-device"); -module.exports = async function scenario(createSession) { +module.exports = async function scenario(createSession, createRestSession) { async function createUser(username) { const session = await createSession(username); await signup(session, session.username, 'testtest'); diff --git a/start.js b/start.js index ac9a2f8684..c21fcd0a2d 100644 --- a/start.js +++ b/start.js @@ -17,6 +17,7 @@ limitations under the License. const assert = require('assert'); const RiotSession = require('./src/session'); const scenario = require('./src/scenario'); +const RestSessionFactory = require('./src/rest/factory'); const program = require('commander'); program @@ -40,6 +41,16 @@ async function runTests() { options.executablePath = path; } + const restFactory = new RestSessionFactory( + 'synapse/installations/consent', + 'http://localhost:5005', + __dirname + ); + + function createRestSession(username, password) { + return restFactory.createSession(username, password); + } + async function createSession(username) { const session = await RiotSession.create(username, options, program.riotUrl); sessions.push(session); @@ -48,7 +59,7 @@ async function runTests() { let failure = false; try { - await scenario(createSession); + await scenario(createSession, createRestSession); } catch(err) { failure = true; console.log('failure: ', err); From 3c5e73d64414e69a46cf1624c4cb91254570a2e5 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 11 Sep 2018 14:54:14 +0200 Subject: [PATCH 092/221] support setting the display name on rest session --- src/rest/session.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/rest/session.js b/src/rest/session.js index f57d0467f5..caa10a430b 100644 --- a/src/rest/session.js +++ b/src/rest/session.js @@ -57,12 +57,17 @@ module.exports = class RestSession { } } + async setDisplayName(displayName) { + await this._put(`/profile/${this.credentials.userId}/displayname`, { + displayname: displayName + }); + } + async join(roomId) { const {room_id} = await this._post(`/rooms/${roomId}/join`); return new RestRoom(this, room_id); } - async createRoom(name, options) { const body = { name, From 48d95c228a2e4f483c4602f31fb12d55e7e0da2c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 11 Sep 2018 15:02:02 +0200 Subject: [PATCH 093/221] creator instead of factory, as it does registration and authentication --- src/rest/{factory.js => creator.js} | 2 +- start.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/rest/{factory.js => creator.js} (98%) diff --git a/src/rest/factory.js b/src/rest/creator.js similarity index 98% rename from src/rest/factory.js rename to src/rest/creator.js index 2df6624ef9..05976552d0 100644 --- a/src/rest/factory.js +++ b/src/rest/creator.js @@ -19,7 +19,7 @@ const exec = util.promisify(require('child_process').exec); const request = require('request-promise-native'); const RestSession = require('./session'); -module.exports = class RestSessionFactory { +module.exports = class RestSessionCreator { constructor(synapseSubdir, hsUrl, cwd) { this.synapseSubdir = synapseSubdir; this.hsUrl = hsUrl; diff --git a/start.js b/start.js index c21fcd0a2d..e39681f71b 100644 --- a/start.js +++ b/start.js @@ -17,7 +17,7 @@ limitations under the License. const assert = require('assert'); const RiotSession = require('./src/session'); const scenario = require('./src/scenario'); -const RestSessionFactory = require('./src/rest/factory'); +const RestSessionCreator = require('./src/rest/creator'); const program = require('commander'); program @@ -41,14 +41,14 @@ async function runTests() { options.executablePath = path; } - const restFactory = new RestSessionFactory( + const restCreator = new RestSessionCreator( 'synapse/installations/consent', 'http://localhost:5005', __dirname ); function createRestSession(username, password) { - return restFactory.createSession(username, password); + return restCreator.createSession(username, password); } async function createSession(username) { From 827e6365bbe4779c89fe9a1ea87856a9f76cb4c3 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 11 Sep 2018 16:18:27 +0200 Subject: [PATCH 094/221] add wrapper around multiple rest sessions --- src/rest/creator.js | 7 +++++ src/rest/multi.js | 61 ++++++++++++++++++++++++++++++++++++++++ src/rest/room.js | 10 +++---- src/rest/session.js | 68 +++++++++++++++++++++++++-------------------- 4 files changed, 111 insertions(+), 35 deletions(-) create mode 100644 src/rest/multi.js diff --git a/src/rest/creator.js b/src/rest/creator.js index 05976552d0..9090a21e70 100644 --- a/src/rest/creator.js +++ b/src/rest/creator.js @@ -18,6 +18,7 @@ const util = require('util'); const exec = util.promisify(require('child_process').exec); const request = require('request-promise-native'); const RestSession = require('./session'); +const RestMultiSession = require('./multi'); module.exports = class RestSessionCreator { constructor(synapseSubdir, hsUrl, cwd) { @@ -26,6 +27,12 @@ module.exports = class RestSessionCreator { this.cwd = cwd; } + async createSessionRange(usernames, password) { + const sessionPromises = usernames.map((username) => this.createSession(username, password)); + const sessions = await Promise.all(sessionPromises); + return new RestMultiSession(sessions); + } + async createSession(username, password) { await this._register(username, password); const authResult = await this._authenticate(username, password); diff --git a/src/rest/multi.js b/src/rest/multi.js new file mode 100644 index 0000000000..12ebe9d4ab --- /dev/null +++ b/src/rest/multi.js @@ -0,0 +1,61 @@ +/* +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 RestRoom = require('./room'); +const {approveConsent} = require('./consent'); + +module.exports = class RestMultiSession { + constructor(sessions) { + this.sessions = sessions; + } + + slice(start, end) { + return new RestMultiSession(this.sessions.slice(start, end)); + } + + pop(userName) { + const idx = this.sessions.findIndex((s) => s.userName() === userName); + if(idx === -1) { + throw new Error(`user ${userName} not found`); + } + const session = this.sessions.splice(idx, 1)[0]; + return session; + } + + async setDisplayName(fn) { + await Promise.all(this.sessions.map((s) => s.setDisplayName(fn(s)))); + } + + async join(roomId) { + const rooms = await Promise.all(this.sessions.map((s) => s.join(roomId))); + return new RestMultiRoom(rooms); + } +} + +class RestMultiRoom { + constructor(rooms) { + this.rooms = rooms; + } + + async talk(message) { + await Promise.all(this.rooms.map((r) => r.talk(message))); + } + + async leave() { + await Promise.all(this.rooms.map((r) => r.leave())); + } +} diff --git a/src/rest/room.js b/src/rest/room.js index b7da1789ff..d8de958a27 100644 --- a/src/rest/room.js +++ b/src/rest/room.js @@ -20,12 +20,12 @@ const uuidv4 = require('uuid/v4'); module.exports = class RestRoom { constructor(session, roomId) { this.session = session; - this.roomId = roomId; + this._roomId = roomId; } async talk(message) { const txId = uuidv4(); - await this.session._put(`/rooms/${this.roomId}/send/m.room.message/${txId}`, { + await this.session._put(`/rooms/${this._roomId}/send/m.room.message/${txId}`, { "msgtype": "m.text", "body": message }); @@ -33,10 +33,10 @@ module.exports = class RestRoom { } async leave() { - await this.session._post(`/rooms/${this.roomId}/leave`); + await this.session._post(`/rooms/${this._roomId}/leave`); } - id() { - return this.roomId; + roomId() { + return this._roomId; } } diff --git a/src/rest/session.js b/src/rest/session.js index caa10a430b..44be15c3ff 100644 --- a/src/rest/session.js +++ b/src/rest/session.js @@ -23,38 +23,12 @@ module.exports = class RestSession { this.credentials = credentials; } - _post(csApiPath, body) { - return this._request("POST", csApiPath, body); + userId() { + return this.credentials.userId; } - _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}`); - } - } + userName() { + return this.credentials.userId.split(":")[0].substr(1); } async setDisplayName(displayName) { @@ -90,4 +64,38 @@ module.exports = class RestSession { const {room_id} = await this._post(`/createRoom`, body); return new RestRoom(this, room_id); } + + _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}`); + } + } + } } From 4a4b1f65aa2d418d8c616b2501b25ceb49f74a5e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 11 Sep 2018 18:28:50 +0200 Subject: [PATCH 095/221] wait for the message to be sent --- src/scenario.js | 9 +++++++-- src/session.js | 23 ++++++++++++++++++++--- src/tests/e2e-device.js | 31 ------------------------------- src/tests/send-message.js | 4 +++- start.js | 6 ++++-- synapse/install.sh | 4 +++- 6 files changed, 37 insertions(+), 40 deletions(-) delete mode 100644 src/tests/e2e-device.js diff --git a/src/scenario.js b/src/scenario.js index b468cad823..3145c9471a 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -25,13 +25,13 @@ const receiveMessage = require('./tests/receive-message'); const createRoom = require('./tests/create-room'); const changeRoomSettings = require('./tests/room-settings'); const acceptServerNoticesInviteAndConsent = require('./tests/server-notices-consent'); -const getE2EDeviceFromSettings = require('./tests/e2e-device'); +const {enableLazyLoading, getE2EDeviceFromSettings} = require('./tests/settings'); const verifyDeviceForUser = require("./tests/verify-device"); module.exports = async function scenario(createSession, createRestSession) { async function createUser(username) { const session = await createSession(username); - await signup(session, session.username, 'testtest'); + await signup(session, session.username, 'testtest', session.hsUrl); await acceptServerNoticesInviteAndConsent(session); return session; } @@ -83,3 +83,8 @@ async function createE2ERoomAndTalk(alice, bob) { await sendMessage(bob, bobMessage); await receiveMessage(alice, {sender: "bob", body: bobMessage, encrypted: true}); } + +async function aLLtest(alice, bob) { + await enableLazyLoading(alice); + +} diff --git a/src/session.js b/src/session.js index bcccc9d6cc..60cbfa9099 100644 --- a/src/session.js +++ b/src/session.js @@ -58,9 +58,10 @@ class Logger { } module.exports = class RiotSession { - constructor(browser, page, username, riotserver) { + constructor(browser, page, username, riotserver, hsUrl) { this.browser = browser; this.page = page; + this.hsUrl = hsUrl; this.riotserver = riotserver; this.username = username; this.consoleLog = new LogBuffer(page, "console", (msg) => `${msg.text()}\n`); @@ -72,14 +73,14 @@ module.exports = class RiotSession { this.log = new Logger(this.username); } - static async create(username, puppeteerOptions, riotserver) { + static async create(username, puppeteerOptions, riotserver, hsUrl) { const browser = await puppeteer.launch(puppeteerOptions); const page = await browser.newPage(); await page.setViewport({ width: 1280, height: 800 }); - return new RiotSession(browser, page, username, riotserver); + return new RiotSession(browser, page, username, riotserver, hsUrl); } async tryGetInnertext(selector) { @@ -161,6 +162,22 @@ module.exports = class RiotSession { return await this.queryAll(selector); } + waitForReload(timeout = 5000) { + return new Promise((resolve, reject) => { + const timeoutHandle = setTimeout(() => { + this.browser.removeEventListener('domcontentloaded', callback); + reject(new Error(`timeout of ${timeout}ms for waitForReload elapsed`)); + }, timeout); + + const callback = async () => { + clearTimeout(timeoutHandle); + resolve(); + }; + + this.page.once('domcontentloaded', callback); + }); + } + waitForNewPage(timeout = 5000) { return new Promise((resolve, reject) => { const timeoutHandle = setTimeout(() => { diff --git a/src/tests/e2e-device.js b/src/tests/e2e-device.js deleted file mode 100644 index 4be6677396..0000000000 --- a/src/tests/e2e-device.js +++ /dev/null @@ -1,31 +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 assert = require('assert'); - -module.exports = async function getE2EDeviceFromSettings(session) { - session.log.step(`gets e2e device/key from settings`); - const settingsButton = await session.query('.mx_BottomLeftMenu_settings'); - await settingsButton.click(); - const deviceAndKey = await session.waitAndQueryAll(".mx_UserSettings_section.mx_UserSettings_cryptoSection code", 1000); - assert.equal(deviceAndKey.length, 2); - const id = await (await deviceAndKey[0].getProperty("innerText")).jsonValue(); - const key = await (await deviceAndKey[1].getProperty("innerText")).jsonValue(); - const closeButton = await session.query(".mx_RoomHeader_cancelButton"); - await closeButton.click(); - session.log.done(); - return {id, key}; -} diff --git a/src/tests/send-message.js b/src/tests/send-message.js index eb70f5ce23..5bf289b03a 100644 --- a/src/tests/send-message.js +++ b/src/tests/send-message.js @@ -25,5 +25,7 @@ module.exports = async function sendMessage(session, message) { const text = await session.innerText(composer); assert.equal(text.trim(), message.trim()); await composer.press("Enter"); + // wait for the message to appear sent + await session.waitAndQuery(".mx_EventTile_last:not(.mx_EventTile_sending)"); session.log.done(); -} \ No newline at end of file +} diff --git a/start.js b/start.js index 5f6681519b..62ec29d6a1 100644 --- a/start.js +++ b/start.js @@ -26,6 +26,8 @@ program .option('--riot-url [url]', "riot url to test", "http://localhost:5000") .parse(process.argv); +const hsUrl = 'http://localhost:5005'; + async function runTests() { let sessions = []; console.log("running tests ..."); @@ -43,7 +45,7 @@ async function runTests() { const restCreator = new RestSessionCreator( 'synapse/installations/consent', - 'http://localhost:5005', + hsUrl, __dirname ); @@ -52,7 +54,7 @@ async function runTests() { } async function createSession(username) { - const session = await RiotSession.create(username, options, program.riotUrl); + const session = await RiotSession.create(username, options, program.riotUrl, hsUrl); sessions.push(session); return session; } diff --git a/synapse/install.sh b/synapse/install.sh index 3d8172a9f6..a438ea5dc2 100755 --- a/synapse/install.sh +++ b/synapse/install.sh @@ -31,9 +31,11 @@ python -m synapse.app.homeserver \ --generate-config \ --report-stats=no # apply configuration +REGISTRATION_SHARED_SECRET=$(uuidgen) cp -r $BASE_DIR/config-templates/$CONFIG_TEMPLATE/. ./ sed -i "s#{{SYNAPSE_ROOT}}#$(pwd)/#g" homeserver.yaml sed -i "s#{{SYNAPSE_PORT}}#${PORT}#g" homeserver.yaml sed -i "s#{{FORM_SECRET}}#$(uuidgen)#g" homeserver.yaml -sed -i "s#{{REGISTRATION_SHARED_SECRET}}#$(uuidgen)#g" homeserver.yaml +sed -i "s#{{REGISTRATION_SHARED_SECRET}}#${REGISTRATION_SHARED_SECRET}#g" homeserver.yaml sed -i "s#{{MACAROON_SECRET_KEY}}#$(uuidgen)#g" homeserver.yaml +echo REGISTRATION_SHARED_SECRET=$REGISTRATION_SHARED_SECRET= From be4c1cb899e7f9e50e29d3a54aa6f569ce34447e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 11 Sep 2018 18:29:05 +0200 Subject: [PATCH 096/221] support setting the room alias --- src/tests/room-settings.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/tests/room-settings.js b/src/tests/room-settings.js index 9f802da711..95c7538431 100644 --- a/src/tests/room-settings.js +++ b/src/tests/room-settings.js @@ -76,6 +76,13 @@ module.exports = async function changeRoomSettings(session, settings) { session.log.done(); } + if (settings.alias) { + session.log.step(`sets alias to ${settings.alias}`); + const aliasField = await session.waitAndQuery(".mx_RoomSettings .mx_EditableItemList .mx_EditableItem_editable"); + await session.replaceInputText(aliasField, settings.alias); + session.log.done(); + } + const saveButton = await session.query(".mx_RoomHeader_wrapper .mx_RoomHeader_textButton"); await saveButton.click(); From 2be413ba6d025813e18ee1f6766ae710b8147439 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 11 Sep 2018 18:29:52 +0200 Subject: [PATCH 097/221] allow clients to send messages faster, in order to speed up the test --- synapse/config-templates/consent/homeserver.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/synapse/config-templates/consent/homeserver.yaml b/synapse/config-templates/consent/homeserver.yaml index 38aa4747b5..9fa16ebe5f 100644 --- a/synapse/config-templates/consent/homeserver.yaml +++ b/synapse/config-templates/consent/homeserver.yaml @@ -207,10 +207,10 @@ log_config: "{{SYNAPSE_ROOT}}localhost.log.config" ## Ratelimiting ## # Number of messages a client can send per second -rc_messages_per_second: 0.2 +rc_messages_per_second: 100 # Number of message a client can send before being throttled -rc_message_burst_count: 10.0 +rc_message_burst_count: 20.0 # The federation window size in milliseconds federation_rc_window_size: 1000 From ff20bc783dc77e61ad21d024fe72c09be6ae2323 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 11 Sep 2018 18:30:17 +0200 Subject: [PATCH 098/221] support joining with a room alias for rest session --- src/rest/session.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rest/session.js b/src/rest/session.js index 44be15c3ff..846e83cc84 100644 --- a/src/rest/session.js +++ b/src/rest/session.js @@ -37,8 +37,8 @@ module.exports = class RestSession { }); } - async join(roomId) { - const {room_id} = await this._post(`/rooms/${roomId}/join`); + async join(roomIdOrAlias) { + const {room_id} = await this._post(`/join/${encodeURIComponent(roomIdOrAlias)}`); return new RestRoom(this, room_id); } From abc7c4c3acb334d6b300a1baa10f7338e0c3e89c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 11 Sep 2018 18:30:57 +0200 Subject: [PATCH 099/221] join use cases that touch settings in one file, as selectors are similar --- src/tests/settings.js | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/tests/settings.js diff --git a/src/tests/settings.js b/src/tests/settings.js new file mode 100644 index 0000000000..5649671e7a --- /dev/null +++ b/src/tests/settings.js @@ -0,0 +1,43 @@ +/* +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 assert = require('assert'); + +module.exports.enableLazyLoading = async function(session) { + session.log.step(`enables lazy loading of members in the lab settings`); + const settingsButton = await session.query('.mx_BottomLeftMenu_settings'); + await settingsButton.click(); + const llCheckbox = await session.waitAndQuery("#feature_lazyloading"); + await llCheckbox.click(); + await session.waitForReload(); + const closeButton = await session.waitAndQuery(".mx_RoomHeader_cancelButton"); + await closeButton.click(); + session.log.done(); +} + +module.exports.getE2EDeviceFromSettings = async function(session) { + session.log.step(`gets e2e device/key from settings`); + const settingsButton = await session.query('.mx_BottomLeftMenu_settings'); + await settingsButton.click(); + const deviceAndKey = await session.waitAndQueryAll(".mx_UserSettings_section.mx_UserSettings_cryptoSection code"); + assert.equal(deviceAndKey.length, 2); + const id = await (await deviceAndKey[0].getProperty("innerText")).jsonValue(); + const key = await (await deviceAndKey[1].getProperty("innerText")).jsonValue(); + const closeButton = await session.query(".mx_RoomHeader_cancelButton"); + await closeButton.click(); + session.log.done(); + return {id, key}; +} From 3db32c93d427ae082cf625d17d02109647f5e946 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 11 Sep 2018 18:32:18 +0200 Subject: [PATCH 100/221] past rest creator to scenario to also be able to call createSessionRange --- start.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/start.js b/start.js index 62ec29d6a1..3367787905 100644 --- a/start.js +++ b/start.js @@ -49,10 +49,6 @@ async function runTests() { __dirname ); - function createRestSession(username, password) { - return restCreator.createSession(username, password); - } - async function createSession(username) { const session = await RiotSession.create(username, options, program.riotUrl, hsUrl); sessions.push(session); @@ -61,7 +57,7 @@ async function runTests() { let failure = false; try { - await scenario(createSession, createRestSession); + await scenario(createSession, restCreator); } catch(err) { failure = true; console.log('failure: ', err); From dcf96e146167db46fe348ebe5c13eb9762479d45 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 11 Sep 2018 18:32:32 +0200 Subject: [PATCH 101/221] WIP for LL test --- src/scenario.js | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/src/scenario.js b/src/scenario.js index 3145c9471a..24983d8cbf 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -28,7 +28,7 @@ const acceptServerNoticesInviteAndConsent = require('./tests/server-notices-cons const {enableLazyLoading, getE2EDeviceFromSettings} = require('./tests/settings'); const verifyDeviceForUser = require("./tests/verify-device"); -module.exports = async function scenario(createSession, createRestSession) { +module.exports = async function scenario(createSession, restCreator) { async function createUser(username) { const session = await createSession(username); await signup(session, session.username, 'testtest', session.hsUrl); @@ -38,9 +38,26 @@ module.exports = async function scenario(createSession, createRestSession) { const alice = await createUser("alice"); const bob = await createUser("bob"); + const charlies = await createRestUsers(restCreator); - await createDirectoryRoomAndTalk(alice, bob); - await createE2ERoomAndTalk(alice, bob); + // await createDirectoryRoomAndTalk(alice, bob); + // await createE2ERoomAndTalk(alice, bob); + await aLazyLoadingTest(alice, bob, charlies); +} + +function range(start, amount, step = 1) { + const r = []; + for (let i = 0; i < amount; ++i) { + r.push(start + (i * step)); + } + return r; +} + +async function createRestUsers(restCreator) { + const usernames = range(1, 10).map((i) => `charly-${i}`); + const charlies = await restCreator.createSessionRange(usernames, 'testtest'); + await charlies.setDisplayName((s) => `Charly #${s.userName().split('-')[1]}`); + return charlies; } async function createDirectoryRoomAndTalk(alice, bob) { @@ -84,7 +101,18 @@ async function createE2ERoomAndTalk(alice, bob) { await receiveMessage(alice, {sender: "bob", body: bobMessage, encrypted: true}); } -async function aLLtest(alice, bob) { +async function aLazyLoadingTest(alice, bob, charlies) { await enableLazyLoading(alice); - + const room = "Lazy Loading Test"; + const alias = "#lltest:localhost"; + await createRoom(bob, room); + await changeRoomSettings(bob, {directory: true, visibility: "public_no_guests", alias}); + // wait for alias to be set by server after clicking "save" + await bob.delay(500); + await charlies.join(alias); + const messageRange = range(1, 20); + for(let i = 20; i >= 1; --i) { + await sendMessage(bob, `I will only say this ${i} time(s)!`); + } + await join(alice, room); } From 244d5b08514f7f2f518274261e40d6352cc5c5a5 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 12 Sep 2018 09:47:57 +0200 Subject: [PATCH 102/221] dont show all 20 send messages support muting a logger and chaining calls --- src/scenario.js | 3 +++ src/session.js | 31 ++++++++++++++++++++++++++----- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/scenario.js b/src/scenario.js index 24983d8cbf..840cd3e0dc 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -111,8 +111,11 @@ async function aLazyLoadingTest(alice, bob, charlies) { await bob.delay(500); await charlies.join(alias); const messageRange = range(1, 20); + bob.log.step("sends 20 messages").mute(); for(let i = 20; i >= 1; --i) { await sendMessage(bob, `I will only say this ${i} time(s)!`); } + bob.log.unmute().done(); await join(alice, room); + } diff --git a/src/session.js b/src/session.js index 60cbfa9099..0bfe781e61 100644 --- a/src/session.js +++ b/src/session.js @@ -35,25 +35,46 @@ class Logger { constructor(username) { this.indent = 0; this.username = username; + this.muted = false; } startGroup(description) { - const indent = " ".repeat(this.indent * 2); - console.log(`${indent} * ${this.username} ${description}:`); + if (!this.muted) { + const indent = " ".repeat(this.indent * 2); + console.log(`${indent} * ${this.username} ${description}:`); + } this.indent += 1; + return this; } endGroup() { this.indent -= 1; + return this; } step(description) { - const indent = " ".repeat(this.indent * 2); - process.stdout.write(`${indent} * ${this.username} ${description} ... `); + if (!this.muted) { + const indent = " ".repeat(this.indent * 2); + process.stdout.write(`${indent} * ${this.username} ${description} ... `); + } + return this; } done(status = "done") { - process.stdout.write(status + "\n"); + if (!this.muted) { + process.stdout.write(status + "\n"); + } + return this; + } + + mute() { + this.muted = true; + return this; + } + + unmute() { + this.muted = false; + return this; } } From 249cf4f87efb4492ca5098b272ed3704f64dbcf2 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 12 Sep 2018 14:49:48 +0200 Subject: [PATCH 103/221] implement reading and scrolling timeline, group timeline related code --- src/scenario.js | 6 +- src/tests/receive-message.js | 49 -------------- src/tests/timeline.js | 125 +++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 50 deletions(-) delete mode 100644 src/tests/receive-message.js create mode 100644 src/tests/timeline.js diff --git a/src/scenario.js b/src/scenario.js index 840cd3e0dc..70ceae1471 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -21,7 +21,11 @@ const join = require('./tests/join'); const sendMessage = require('./tests/send-message'); const acceptInvite = require('./tests/accept-invite'); const invite = require('./tests/invite'); -const receiveMessage = require('./tests/receive-message'); +const { + receiveMessage, + checkTimelineContains, + scrollToTimelineTop +} = require('./tests/timeline'); const createRoom = require('./tests/create-room'); const changeRoomSettings = require('./tests/room-settings'); const acceptServerNoticesInviteAndConsent = require('./tests/server-notices-consent'); diff --git a/src/tests/receive-message.js b/src/tests/receive-message.js deleted file mode 100644 index afe4247181..0000000000 --- a/src/tests/receive-message.js +++ /dev/null @@ -1,49 +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 assert = require('assert'); - -module.exports = async function receiveMessage(session, message) { - session.log.step(`receives message "${message.body}" from ${message.sender}`); - // wait for a response to come in that contains the message - // crude, but effective - await session.page.waitForResponse(async (response) => { - if (response.request().url().indexOf("/sync") === -1) { - return false; - } - const body = await response.text(); - if (message.encrypted) { - return body.indexOf(message.sender) !== -1 && - body.indexOf("m.room.encrypted") !== -1; - } else { - return body.indexOf(message.body) !== -1; - } - }); - // wait a bit for the incoming event to be rendered - await session.delay(1000); - let lastTile = await session.query(".mx_EventTile_last"); - const senderElement = await lastTile.$(".mx_SenderProfile_name"); - const bodyElement = await lastTile.$(".mx_EventTile_body"); - const sender = await(await senderElement.getProperty("innerText")).jsonValue(); - const body = await(await bodyElement.getProperty("innerText")).jsonValue(); - if (message.encrypted) { - const e2eIcon = await lastTile.$(".mx_EventTile_e2eIcon"); - assert.ok(e2eIcon); - } - assert.equal(body, message.body); - assert.equal(sender, message.sender); - session.log.done(); -} diff --git a/src/tests/timeline.js b/src/tests/timeline.js new file mode 100644 index 0000000000..1e724a2d39 --- /dev/null +++ b/src/tests/timeline.js @@ -0,0 +1,125 @@ +/* +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 assert = require('assert'); + +module.exports.scrollToTimelineTop = async function(session) { + session.log.step(`scrolls to the top of the timeline`); + await session.page.evaluate(() => { + return Promise.resolve().then(async () => { + const timelineScrollView = document.querySelector(".mx_RoomView .gm-scroll-view"); + let timedOut = false; + let timeoutHandle = null; + // set scrollTop to 0 in a loop and check every 50ms + // if content became available (scrollTop not being 0 anymore), + // assume everything is loaded after 1000ms + do { + if (timelineScrollView.scrollTop !== 0) { + if (timeoutHandle) { + clearTimeout(timeoutHandle); + } + timeoutHandle = setTimeout(() => timedOut = true, 1000); + timelineScrollView.scrollTop = 0; + } else { + await new Promise((resolve) => setTimeout(resolve, 50)); + } + } while (!timedOut) + }); + }) + session.log.done(); +} + +module.exports.receiveMessage = async function(session, expectedMessage) { + session.log.step(`receives message "${expectedMessage.body}" from ${expectedMessage.sender}`); + // wait for a response to come in that contains the message + // crude, but effective + await session.page.waitForResponse(async (response) => { + if (response.request().url().indexOf("/sync") === -1) { + return false; + } + const body = await response.text(); + if (expectedMessage.encrypted) { + return body.indexOf(expectedMessage.sender) !== -1 && + body.indexOf("m.room.encrypted") !== -1; + } else { + return body.indexOf(expectedMessage.body) !== -1; + } + }); + // wait a bit for the incoming event to be rendered + await session.delay(1000); + const lastTile = await getLastEventTile(session); + const foundMessage = await getMessageFromEventTile(lastTile); + assertMessage(foundMessage, expectedMessage); + session.log.done(); +} + + +module.exports.checkTimelineContains = async function (session, expectedMessages, sendersDescription) { + session.log.step(`checks timeline contains ${expectedMessages.length} ` + + `given messages${sendersDescription ? ` from ${sendersDescription}`:""}`); + const eventTiles = await getAllEventTiles(session); + let timelineMessages = await Promise.all(eventTiles.map((eventTile) => { + return getMessageFromEventTile(eventTile); + })); + //filter out tiles that were not messages + timelineMessages = timelineMessages .filter((m) => !!m); + expectedMessages.forEach((expectedMessage) => { + const foundMessage = timelineMessages.find((message) => { + return message.sender === expectedMessage.sender && + message.body === expectedMessage.body; + }); + assertMessage(foundMessage, expectedMessage); + }); + + session.log.done(); +} + +function assertMessage(foundMessage, expectedMessage) { + assert(foundMessage, `message ${JSON.stringify(expectedMessage)} not found in timeline`); + assert.equal(foundMessage.body, expectedMessage.body); + assert.equal(foundMessage.sender, expectedMessage.sender); + if (expectedMessage.hasOwnProperty("encrypted")) { + assert.equal(foundMessage.encrypted, expectedMessage.encrypted); + } +} + +function getLastEventTile(session) { + return session.query(".mx_EventTile_last"); +} + +function getAllEventTiles(session) { + return session.queryAll(".mx_RoomView_MessageList > *"); +} + +async function getMessageFromEventTile(eventTile) { + const senderElement = await eventTile.$(".mx_SenderProfile_name"); + const bodyElement = await eventTile.$(".mx_EventTile_body"); + let sender = null; + if (senderElement) { + sender = await(await senderElement.getProperty("innerText")).jsonValue(); + } + if (!bodyElement) { + return null; + } + const body = await(await bodyElement.getProperty("innerText")).jsonValue(); + const e2eIcon = await eventTile.$(".mx_EventTile_e2eIcon"); + + return { + sender, + body, + encrypted: !!e2eIcon + }; +} From 4057ec8a6accf6bdfa87f4c9e62f31b64a4f14fb Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 12 Sep 2018 14:51:00 +0200 Subject: [PATCH 104/221] store displayName on RestSession to use it in tests --- src/rest/session.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/rest/session.js b/src/rest/session.js index 846e83cc84..ece04f3352 100644 --- a/src/rest/session.js +++ b/src/rest/session.js @@ -20,19 +20,25 @@ const {approveConsent} = require('./consent'); module.exports = class RestSession { constructor(credentials) { - this.credentials = credentials; + this._credentials = credentials; + this._displayName = null; } userId() { - return this.credentials.userId; + return this._credentials.userId; } userName() { - return this.credentials.userId.split(":")[0].substr(1); + return this._credentials.userId.split(":")[0].substr(1); + } + + displayName() { + return this._displayName; } async setDisplayName(displayName) { - await this._put(`/profile/${this.credentials.userId}/displayname`, { + this._displayName = displayName; + await this._put(`/profile/${this._credentials.userId}/displayname`, { displayname: displayName }); } @@ -76,10 +82,10 @@ module.exports = class RestSession { async _request(method, csApiPath, body) { try { const responseBody = await request({ - url: `${this.credentials.hsUrl}/_matrix/client/r0${csApiPath}`, + url: `${this._credentials.hsUrl}/_matrix/client/r0${csApiPath}`, method, headers: { - "Authorization": `Bearer ${this.credentials.accessToken}` + "Authorization": `Bearer ${this._credentials.accessToken}` }, json: true, body From 29aec256df052b758a0883a397997825ed5114ed Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 12 Sep 2018 14:53:19 +0200 Subject: [PATCH 105/221] finish basic LL test to see if display names appear from lazy loaded state --- src/scenario.js | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/scenario.js b/src/scenario.js index 70ceae1471..946a4122ef 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -44,8 +44,8 @@ module.exports = async function scenario(createSession, restCreator) { const bob = await createUser("bob"); const charlies = await createRestUsers(restCreator); - // await createDirectoryRoomAndTalk(alice, bob); - // await createE2ERoomAndTalk(alice, bob); + await createDirectoryRoomAndTalk(alice, bob); + await createE2ERoomAndTalk(alice, bob); await aLazyLoadingTest(alice, bob, charlies); } @@ -106,20 +106,36 @@ async function createE2ERoomAndTalk(alice, bob) { } async function aLazyLoadingTest(alice, bob, charlies) { + console.log(" creating a room for lazy loading member scenarios:"); await enableLazyLoading(alice); const room = "Lazy Loading Test"; const alias = "#lltest:localhost"; + const charlyMsg1 = "hi bob!"; + const charlyMsg2 = "how's it going??"; await createRoom(bob, room); await changeRoomSettings(bob, {directory: true, visibility: "public_no_guests", alias}); // wait for alias to be set by server after clicking "save" + // so the charlies can join it. await bob.delay(500); - await charlies.join(alias); - const messageRange = range(1, 20); + const charlyMembers = await charlies.join(alias); + await charlyMembers.talk(charlyMsg1); + await charlyMembers.talk(charlyMsg2); bob.log.step("sends 20 messages").mute(); for(let i = 20; i >= 1; --i) { await sendMessage(bob, `I will only say this ${i} time(s)!`); } bob.log.unmute().done(); - await join(alice, room); - + await join(alice, alias); + await scrollToTimelineTop(alice); + //alice should see 2 messages from every charly with + //the correct display name + const expectedMessages = [charlyMsg1, charlyMsg2].reduce((messages, msgText) => { + return charlies.sessions.reduce((messages, charly) => { + return messages.concat({ + sender: charly.displayName(), + body: msgText, + }); + }, messages); + }, []); + await checkTimelineContains(alice, expectedMessages, "Charly #1-10"); } From 7bcb255a2c38dcb220d67a85303d84b3fa5a503b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 12 Sep 2018 16:47:24 +0200 Subject: [PATCH 106/221] increase timeout here in case this wouldnt be enough for the CI server --- src/tests/timeline.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/timeline.js b/src/tests/timeline.js index 1e724a2d39..beaaec5e5a 100644 --- a/src/tests/timeline.js +++ b/src/tests/timeline.js @@ -25,13 +25,13 @@ module.exports.scrollToTimelineTop = async function(session) { let timeoutHandle = null; // set scrollTop to 0 in a loop and check every 50ms // if content became available (scrollTop not being 0 anymore), - // assume everything is loaded after 1000ms + // assume everything is loaded after 3s do { if (timelineScrollView.scrollTop !== 0) { if (timeoutHandle) { clearTimeout(timeoutHandle); } - timeoutHandle = setTimeout(() => timedOut = true, 1000); + timeoutHandle = setTimeout(() => timedOut = true, 3000); timelineScrollView.scrollTop = 0; } else { await new Promise((resolve) => setTimeout(resolve, 50)); From e843d532ebe38dff7b6b934226c4f355f4689c76 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 12 Sep 2018 16:48:40 +0200 Subject: [PATCH 107/221] these changes were not needed in the end --- synapse/install.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/synapse/install.sh b/synapse/install.sh index a438ea5dc2..3d8172a9f6 100755 --- a/synapse/install.sh +++ b/synapse/install.sh @@ -31,11 +31,9 @@ python -m synapse.app.homeserver \ --generate-config \ --report-stats=no # apply configuration -REGISTRATION_SHARED_SECRET=$(uuidgen) cp -r $BASE_DIR/config-templates/$CONFIG_TEMPLATE/. ./ sed -i "s#{{SYNAPSE_ROOT}}#$(pwd)/#g" homeserver.yaml sed -i "s#{{SYNAPSE_PORT}}#${PORT}#g" homeserver.yaml sed -i "s#{{FORM_SECRET}}#$(uuidgen)#g" homeserver.yaml -sed -i "s#{{REGISTRATION_SHARED_SECRET}}#${REGISTRATION_SHARED_SECRET}#g" homeserver.yaml +sed -i "s#{{REGISTRATION_SHARED_SECRET}}#$(uuidgen)#g" homeserver.yaml sed -i "s#{{MACAROON_SECRET_KEY}}#$(uuidgen)#g" homeserver.yaml -echo REGISTRATION_SHARED_SECRET=$REGISTRATION_SHARED_SECRET= From c8fec947e47930f249b0fe31a3119328dbb8bec9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 12 Sep 2018 17:27:51 +0200 Subject: [PATCH 108/221] structure flags better and document them --- README.md | 56 ++++++++++++++++++++++--------------------------------- start.js | 15 ++++++++------- 2 files changed, 30 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index b1a4e40aac..acfeed8257 100644 --- a/README.md +++ b/README.md @@ -2,48 +2,36 @@ This repository contains tests for the matrix-react-sdk web app. The tests fire up a headless chrome and simulate user interaction (end-to-end). Note that end-to-end has little to do with the end-to-end encryption matrix supports, just that we test the full stack, going from user interaction to expected DOM in the browser. -## Current tests - - test riot loads (check title) - - signup with custom homeserver - - join preexisting room - -## Roadmap -- get rid of jest, as a test framework won't be helpful to have a continuous flow going from one use case to another (think: do login, create a room, invite a user, ...). a test framework usually assumes the tests are semi-indepedent. -- better error reporting (show console.log, XHR requests, partial DOM, screenshot) on error -- cleanup helper methods -- add more css id's/classes to riot web to make css selectors in test less brittle. -- avoid delay when waiting for location.hash to change -- more tests! -- setup installing & running riot and synapse as part of the tests. - - Run 2 synapse instances to test federation use cases. - - start synapse with clean config/database on every test run -- look into CI(Travis) integration -- create interactive mode, where window is opened, and browser is kept open until Ctrl^C, for easy test debugging. - -## It's broken! How do I see what's happening in the browser? - -Look for this line: -``` -puppeteer.launch(); -``` -Now change it to: -``` -puppeteer.launch({headless: false}); -``` - -## How to run - -### Setup +## Setup Run `./install.sh`. This will: - install synapse, fetches the master branch at the moment. If anything fails here, please refer to the synapse README to see if you're missing one of the prerequisites. - install riot, this fetches the master branch at the moment. - install dependencies (will download copy of chrome) -### Run tests - +## Running the tests + Run tests with `./run.sh`. +### Debug tests locally. + +`./run.sh` will run the tests against the riot copy present in `riot/riot-web` served by a static python http server. You can symlink your `riot-web` develop copy here but that doesn't work well with webpack recompiling. You can run the test runner directly and specify parameters to get more insight into a failure or run the tests against your local webpack server. + +``` +./synapse/stop.sh && \ +./synapse/start.sh && \ +node start.js +``` +It's important to always stop and start synapse before each run of the tests to clear the in-memory sqlite database it uses, as the tests assume a blank slate. + +start.js accepts the following parameters that can help running the tests locally: + + - `--no-logs` dont show the excessive logging show by default (meant for CI), just where the test failed. + - `--riot-url ` don't use the riot copy and static server provided by the tests, but use a running server like the webpack watch server to run the tests against. Make sure to have `welcomeUserId` disabled in your config as the tests assume there is no riot-bot currently. + - `--slow-mo` run the tests a bit slower, so it's easier to follow along with `--windowed`. + - `--windowed` run the tests in an actual browser window Try to limit interacting with the windows while the tests are running. Hovering over the window tends to fail the tests, dragging the title bar should be fine though. + - `--dev-tools` open the devtools in the browser window, only applies if `--windowed` is set as well. + Developer Guide =============== diff --git a/start.js b/start.js index f3eac32f9f..630a3a6d3d 100644 --- a/start.js +++ b/start.js @@ -21,19 +21,20 @@ const scenario = require('./src/scenario'); const program = require('commander'); program .option('--no-logs', "don't output logs, document html on error", false) - .option('--debug', "open browser window and slow down interactions", false) .option('--riot-url [url]', "riot url to test", "http://localhost:5000") + .option('--windowed', "dont run tests headless", false) + .option('--slow-mo', "run tests slower to follow whats going on", false) + .option('--dev-tools', "open chrome devtools in browser window", false) .parse(process.argv); async function runTests() { let sessions = []; console.log("running tests ..."); - const options = {}; - if (program.debug) { - options.slowMo = 20; - options.devtools = true; - options.headless = false; - } + const options = { + slowMo: program.slowMo ? 20 : undefined, + devtools: program.devTools, + headless: !program.windowed, + }; if (process.env.CHROME_PATH) { const path = process.env.CHROME_PATH; console.log(`(using external chrome/chromium at ${path}, make sure it's compatible with puppeteer)`); From 5745e9ed0cfeb745046fc8788dccf012334b6b3a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 12 Sep 2018 18:36:02 +0200 Subject: [PATCH 109/221] move Logger and LogBuffer to own module --- src/logbuffer.js | 30 +++++++++++++++++++++++ src/logger.js | 62 ++++++++++++++++++++++++++++++++++++++++++++++ src/session.js | 64 ++---------------------------------------------- 3 files changed, 94 insertions(+), 62 deletions(-) create mode 100644 src/logbuffer.js create mode 100644 src/logger.js diff --git a/src/logbuffer.js b/src/logbuffer.js new file mode 100644 index 0000000000..8bf6285e25 --- /dev/null +++ b/src/logbuffer.js @@ -0,0 +1,30 @@ +/* +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. +*/ + +module.exports = class LogBuffer { + constructor(page, eventName, eventMapper, reduceAsync=false, initialValue = "") { + this.buffer = initialValue; + page.on(eventName, (arg) => { + const result = eventMapper(arg); + if (reduceAsync) { + result.then((r) => this.buffer += r); + } + else { + this.buffer += result; + } + }); + } +} diff --git a/src/logger.js b/src/logger.js new file mode 100644 index 0000000000..be3ebde75b --- /dev/null +++ b/src/logger.js @@ -0,0 +1,62 @@ +/* +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. +*/ + +module.exports = class Logger { + constructor(username) { + this.indent = 0; + this.username = username; + this.muted = false; + } + + startGroup(description) { + if (!this.muted) { + const indent = " ".repeat(this.indent * 2); + console.log(`${indent} * ${this.username} ${description}:`); + } + this.indent += 1; + return this; + } + + endGroup() { + this.indent -= 1; + return this; + } + + step(description) { + if (!this.muted) { + const indent = " ".repeat(this.indent * 2); + process.stdout.write(`${indent} * ${this.username} ${description} ... `); + } + return this; + } + + done(status = "done") { + if (!this.muted) { + process.stdout.write(status + "\n"); + } + return this; + } + + mute() { + this.muted = true; + return this; + } + + unmute() { + this.muted = false; + return this; + } +} diff --git a/src/session.js b/src/session.js index 0bfe781e61..86cdd05e95 100644 --- a/src/session.js +++ b/src/session.js @@ -15,68 +15,8 @@ limitations under the License. */ const puppeteer = require('puppeteer'); - -class LogBuffer { - constructor(page, eventName, eventMapper, reduceAsync=false, initialValue = "") { - this.buffer = initialValue; - page.on(eventName, (arg) => { - const result = eventMapper(arg); - if (reduceAsync) { - result.then((r) => this.buffer += r); - } - else { - this.buffer += result; - } - }); - } -} - -class Logger { - constructor(username) { - this.indent = 0; - this.username = username; - this.muted = false; - } - - startGroup(description) { - if (!this.muted) { - const indent = " ".repeat(this.indent * 2); - console.log(`${indent} * ${this.username} ${description}:`); - } - this.indent += 1; - return this; - } - - endGroup() { - this.indent -= 1; - return this; - } - - step(description) { - if (!this.muted) { - const indent = " ".repeat(this.indent * 2); - process.stdout.write(`${indent} * ${this.username} ${description} ... `); - } - return this; - } - - done(status = "done") { - if (!this.muted) { - process.stdout.write(status + "\n"); - } - return this; - } - - mute() { - this.muted = true; - return this; - } - - unmute() { - this.muted = false; - return this; - } -} +const Logger = require('./logger'); +const LogBuffer = require('./logbuffer'); module.exports = class RiotSession { constructor(browser, page, username, riotserver, hsUrl) { From 923ae90576d3beb3a1070d75880d1c7aaf3a06c0 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 12 Sep 2018 18:38:42 +0200 Subject: [PATCH 110/221] move range and delay over to util module --- src/scenario.js | 13 +++---------- src/session.js | 3 ++- src/util.js | 27 +++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 src/util.js diff --git a/src/scenario.js b/src/scenario.js index 946a4122ef..3538fa2d40 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -21,6 +21,7 @@ const join = require('./tests/join'); const sendMessage = require('./tests/send-message'); const acceptInvite = require('./tests/accept-invite'); const invite = require('./tests/invite'); +const {delay, range} = require('./util'); const { receiveMessage, checkTimelineContains, @@ -49,14 +50,6 @@ module.exports = async function scenario(createSession, restCreator) { await aLazyLoadingTest(alice, bob, charlies); } -function range(start, amount, step = 1) { - const r = []; - for (let i = 0; i < amount; ++i) { - r.push(start + (i * step)); - } - return r; -} - async function createRestUsers(restCreator) { const usernames = range(1, 10).map((i) => `charly-${i}`); const charlies = await restCreator.createSessionRange(usernames, 'testtest'); @@ -88,12 +81,12 @@ async function createE2ERoomAndTalk(alice, bob) { const bobDevice = await getE2EDeviceFromSettings(bob); // wait some time for the encryption warning dialog // to appear after closing the settings - await bob.delay(1000); + await delay(1000); await acceptDialogMaybe(bob, "encryption"); const aliceDevice = await getE2EDeviceFromSettings(alice); // wait some time for the encryption warning dialog // to appear after closing the settings - await alice.delay(1000); + await delay(1000); await acceptDialogMaybe(alice, "encryption"); await verifyDeviceForUser(bob, "alice", aliceDevice); await verifyDeviceForUser(alice, "bob", bobDevice); diff --git a/src/session.js b/src/session.js index 86cdd05e95..839b4a495b 100644 --- a/src/session.js +++ b/src/session.js @@ -17,6 +17,7 @@ limitations under the License. const puppeteer = require('puppeteer'); const Logger = require('./logger'); const LogBuffer = require('./logbuffer'); +const {delay} = require('./util'); module.exports = class RiotSession { constructor(browser, page, username, riotserver, hsUrl) { @@ -169,7 +170,7 @@ module.exports = class RiotSession { } delay(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); + return delay(ms); } close() { diff --git a/src/util.js b/src/util.js new file mode 100644 index 0000000000..8080d771be --- /dev/null +++ b/src/util.js @@ -0,0 +1,27 @@ +/* +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. +*/ + +module.exports.range = function(start, amount, step = 1) { + const r = []; + for (let i = 0; i < amount; ++i) { + r.push(start + (i * step)); + } + return r; +} + +module.exports.delay = function(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} From 5ec8f6f9b41cd8d65093ae70f6031372ccb30865 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 12 Sep 2018 18:40:25 +0200 Subject: [PATCH 111/221] rename tests folder to the more accurate usecases --- src/scenario.js | 24 +++++++++---------- src/{tests => usecases}/accept-invite.js | 0 src/{tests => usecases}/consent.js | 0 src/{tests => usecases}/create-room.js | 0 src/{tests => usecases}/dialog.js | 0 src/{tests => usecases}/invite.js | 0 src/{tests => usecases}/join.js | 0 src/{tests => usecases}/room-settings.js | 0 src/{tests => usecases}/send-message.js | 0 .../server-notices-consent.js | 0 src/{tests => usecases}/settings.js | 0 src/{tests => usecases}/signup.js | 0 src/{tests => usecases}/timeline.js | 0 src/{tests => usecases}/verify-device.js | 0 14 files changed, 12 insertions(+), 12 deletions(-) rename src/{tests => usecases}/accept-invite.js (100%) rename src/{tests => usecases}/consent.js (100%) rename src/{tests => usecases}/create-room.js (100%) rename src/{tests => usecases}/dialog.js (100%) rename src/{tests => usecases}/invite.js (100%) rename src/{tests => usecases}/join.js (100%) rename src/{tests => usecases}/room-settings.js (100%) rename src/{tests => usecases}/send-message.js (100%) rename src/{tests => usecases}/server-notices-consent.js (100%) rename src/{tests => usecases}/settings.js (100%) rename src/{tests => usecases}/signup.js (100%) rename src/{tests => usecases}/timeline.js (100%) rename src/{tests => usecases}/verify-device.js (100%) diff --git a/src/scenario.js b/src/scenario.js index 3538fa2d40..77307689ea 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -15,23 +15,23 @@ limitations under the License. */ -const {acceptDialogMaybe} = require('./tests/dialog'); -const signup = require('./tests/signup'); -const join = require('./tests/join'); -const sendMessage = require('./tests/send-message'); -const acceptInvite = require('./tests/accept-invite'); -const invite = require('./tests/invite'); const {delay, range} = require('./util'); +const {acceptDialogMaybe} = require('./usecases/dialog'); +const signup = require('./usecases/signup'); +const join = require('./usecases/join'); +const sendMessage = require('./usecases/send-message'); +const acceptInvite = require('./usecases/accept-invite'); +const invite = require('./usecases/invite'); const { receiveMessage, checkTimelineContains, scrollToTimelineTop -} = require('./tests/timeline'); -const createRoom = require('./tests/create-room'); -const changeRoomSettings = require('./tests/room-settings'); -const acceptServerNoticesInviteAndConsent = require('./tests/server-notices-consent'); -const {enableLazyLoading, getE2EDeviceFromSettings} = require('./tests/settings'); -const verifyDeviceForUser = require("./tests/verify-device"); +} = require('./usecases/timeline'); +const createRoom = require('./usecases/create-room'); +const changeRoomSettings = require('./usecases/room-settings'); +const acceptServerNoticesInviteAndConsent = require('./usecases/server-notices-consent'); +const {enableLazyLoading, getE2EDeviceFromSettings} = require('./usecases/settings'); +const verifyDeviceForUser = require("./usecases/verify-device"); module.exports = async function scenario(createSession, restCreator) { async function createUser(username) { diff --git a/src/tests/accept-invite.js b/src/usecases/accept-invite.js similarity index 100% rename from src/tests/accept-invite.js rename to src/usecases/accept-invite.js diff --git a/src/tests/consent.js b/src/usecases/consent.js similarity index 100% rename from src/tests/consent.js rename to src/usecases/consent.js diff --git a/src/tests/create-room.js b/src/usecases/create-room.js similarity index 100% rename from src/tests/create-room.js rename to src/usecases/create-room.js diff --git a/src/tests/dialog.js b/src/usecases/dialog.js similarity index 100% rename from src/tests/dialog.js rename to src/usecases/dialog.js diff --git a/src/tests/invite.js b/src/usecases/invite.js similarity index 100% rename from src/tests/invite.js rename to src/usecases/invite.js diff --git a/src/tests/join.js b/src/usecases/join.js similarity index 100% rename from src/tests/join.js rename to src/usecases/join.js diff --git a/src/tests/room-settings.js b/src/usecases/room-settings.js similarity index 100% rename from src/tests/room-settings.js rename to src/usecases/room-settings.js diff --git a/src/tests/send-message.js b/src/usecases/send-message.js similarity index 100% rename from src/tests/send-message.js rename to src/usecases/send-message.js diff --git a/src/tests/server-notices-consent.js b/src/usecases/server-notices-consent.js similarity index 100% rename from src/tests/server-notices-consent.js rename to src/usecases/server-notices-consent.js diff --git a/src/tests/settings.js b/src/usecases/settings.js similarity index 100% rename from src/tests/settings.js rename to src/usecases/settings.js diff --git a/src/tests/signup.js b/src/usecases/signup.js similarity index 100% rename from src/tests/signup.js rename to src/usecases/signup.js diff --git a/src/tests/timeline.js b/src/usecases/timeline.js similarity index 100% rename from src/tests/timeline.js rename to src/usecases/timeline.js diff --git a/src/tests/verify-device.js b/src/usecases/verify-device.js similarity index 100% rename from src/tests/verify-device.js rename to src/usecases/verify-device.js From 1725e7524b6c71482218625703220a36d52bc7fa Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 13 Sep 2018 10:31:15 +0200 Subject: [PATCH 112/221] split up scenarios in multiple files as lazy-loading scenarios grow --- src/scenario.js | 101 +++----------------------------- src/scenarios/README.md | 1 + src/scenarios/directory.js | 36 ++++++++++++ src/scenarios/e2e-encryption.js | 55 +++++++++++++++++ src/scenarios/lazy-loading.js | 62 ++++++++++++++++++++ src/session.js | 2 +- src/usecases/README.md | 2 + 7 files changed, 164 insertions(+), 95 deletions(-) create mode 100644 src/scenarios/README.md create mode 100644 src/scenarios/directory.js create mode 100644 src/scenarios/e2e-encryption.js create mode 100644 src/scenarios/lazy-loading.js create mode 100644 src/usecases/README.md diff --git a/src/scenario.js b/src/scenario.js index 77307689ea..f0b4ad988b 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -15,23 +15,12 @@ limitations under the License. */ -const {delay, range} = require('./util'); -const {acceptDialogMaybe} = require('./usecases/dialog'); +const {range} = require('./util'); const signup = require('./usecases/signup'); -const join = require('./usecases/join'); -const sendMessage = require('./usecases/send-message'); -const acceptInvite = require('./usecases/accept-invite'); -const invite = require('./usecases/invite'); -const { - receiveMessage, - checkTimelineContains, - scrollToTimelineTop -} = require('./usecases/timeline'); -const createRoom = require('./usecases/create-room'); -const changeRoomSettings = require('./usecases/room-settings'); const acceptServerNoticesInviteAndConsent = require('./usecases/server-notices-consent'); -const {enableLazyLoading, getE2EDeviceFromSettings} = require('./usecases/settings'); -const verifyDeviceForUser = require("./usecases/verify-device"); +const roomDirectoryScenarios = require('./scenarios/directory'); +const lazyLoadingScenarios = require('./scenarios/lazy-loading'); +const e2eEncryptionScenarios = require('./scenarios/e2e-encryption'); module.exports = async function scenario(createSession, restCreator) { async function createUser(username) { @@ -45,9 +34,9 @@ module.exports = async function scenario(createSession, restCreator) { const bob = await createUser("bob"); const charlies = await createRestUsers(restCreator); - await createDirectoryRoomAndTalk(alice, bob); - await createE2ERoomAndTalk(alice, bob); - await aLazyLoadingTest(alice, bob, charlies); + await roomDirectoryScenarios(alice, bob); + await e2eEncryptionScenarios(alice, bob); + await lazyLoadingScenarios(alice, bob, charlies); } async function createRestUsers(restCreator) { @@ -56,79 +45,3 @@ async function createRestUsers(restCreator) { await charlies.setDisplayName((s) => `Charly #${s.userName().split('-')[1]}`); return charlies; } - -async function createDirectoryRoomAndTalk(alice, bob) { - console.log(" creating a public room and join through directory:"); - const room = 'test'; - await createRoom(alice, room); - await changeRoomSettings(alice, {directory: true, visibility: "public_no_guests"}); - await join(bob, room); - const bobMessage = "hi Alice!"; - await sendMessage(bob, bobMessage); - await receiveMessage(alice, {sender: "bob", body: bobMessage}); - const aliceMessage = "hi Bob, welcome!" - await sendMessage(alice, aliceMessage); - await receiveMessage(bob, {sender: "alice", body: aliceMessage}); -} - -async function createE2ERoomAndTalk(alice, bob) { - console.log(" creating an e2e encrypted room and join through invite:"); - const room = "secrets"; - await createRoom(bob, room); - await changeRoomSettings(bob, {encryption: true}); - await invite(bob, "@alice:localhost"); - await acceptInvite(alice, room); - const bobDevice = await getE2EDeviceFromSettings(bob); - // wait some time for the encryption warning dialog - // to appear after closing the settings - await delay(1000); - await acceptDialogMaybe(bob, "encryption"); - const aliceDevice = await getE2EDeviceFromSettings(alice); - // wait some time for the encryption warning dialog - // to appear after closing the settings - await delay(1000); - await acceptDialogMaybe(alice, "encryption"); - await verifyDeviceForUser(bob, "alice", aliceDevice); - await verifyDeviceForUser(alice, "bob", bobDevice); - const aliceMessage = "Guess what I just heard?!" - await sendMessage(alice, aliceMessage); - await receiveMessage(bob, {sender: "alice", body: aliceMessage, encrypted: true}); - const bobMessage = "You've got to tell me!"; - await sendMessage(bob, bobMessage); - await receiveMessage(alice, {sender: "bob", body: bobMessage, encrypted: true}); -} - -async function aLazyLoadingTest(alice, bob, charlies) { - console.log(" creating a room for lazy loading member scenarios:"); - await enableLazyLoading(alice); - const room = "Lazy Loading Test"; - const alias = "#lltest:localhost"; - const charlyMsg1 = "hi bob!"; - const charlyMsg2 = "how's it going??"; - await createRoom(bob, room); - await changeRoomSettings(bob, {directory: true, visibility: "public_no_guests", alias}); - // wait for alias to be set by server after clicking "save" - // so the charlies can join it. - await bob.delay(500); - const charlyMembers = await charlies.join(alias); - await charlyMembers.talk(charlyMsg1); - await charlyMembers.talk(charlyMsg2); - bob.log.step("sends 20 messages").mute(); - for(let i = 20; i >= 1; --i) { - await sendMessage(bob, `I will only say this ${i} time(s)!`); - } - bob.log.unmute().done(); - await join(alice, alias); - await scrollToTimelineTop(alice); - //alice should see 2 messages from every charly with - //the correct display name - const expectedMessages = [charlyMsg1, charlyMsg2].reduce((messages, msgText) => { - return charlies.sessions.reduce((messages, charly) => { - return messages.concat({ - sender: charly.displayName(), - body: msgText, - }); - }, messages); - }, []); - await checkTimelineContains(alice, expectedMessages, "Charly #1-10"); -} diff --git a/src/scenarios/README.md b/src/scenarios/README.md new file mode 100644 index 0000000000..4eabc8f9ef --- /dev/null +++ b/src/scenarios/README.md @@ -0,0 +1 @@ +scenarios contains the high-level playbook for the test suite diff --git a/src/scenarios/directory.js b/src/scenarios/directory.js new file mode 100644 index 0000000000..3b87d64d78 --- /dev/null +++ b/src/scenarios/directory.js @@ -0,0 +1,36 @@ +/* +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 join = require('../usecases/join'); +const sendMessage = require('../usecases/send-message'); +const {receiveMessage} = require('../usecases/timeline'); +const createRoom = require('../usecases/create-room'); +const changeRoomSettings = require('../usecases/room-settings'); + +module.exports = async function roomDirectoryScenarios(alice, bob) { + console.log(" creating a public room and join through directory:"); + const room = 'test'; + await createRoom(alice, room); + await changeRoomSettings(alice, {directory: true, visibility: "public_no_guests"}); + await join(bob, room); //looks up room in directory + const bobMessage = "hi Alice!"; + await sendMessage(bob, bobMessage); + await receiveMessage(alice, {sender: "bob", body: bobMessage}); + const aliceMessage = "hi Bob, welcome!" + await sendMessage(alice, aliceMessage); + await receiveMessage(bob, {sender: "alice", body: aliceMessage}); +} diff --git a/src/scenarios/e2e-encryption.js b/src/scenarios/e2e-encryption.js new file mode 100644 index 0000000000..938dc5e592 --- /dev/null +++ b/src/scenarios/e2e-encryption.js @@ -0,0 +1,55 @@ +/* +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 {delay} = require('../util'); +const {acceptDialogMaybe} = require('../usecases/dialog'); +const join = require('../usecases/join'); +const sendMessage = require('../usecases/send-message'); +const acceptInvite = require('../usecases/accept-invite'); +const invite = require('../usecases/invite'); +const {receiveMessage} = require('../usecases/timeline'); +const createRoom = require('../usecases/create-room'); +const changeRoomSettings = require('../usecases/room-settings'); +const {getE2EDeviceFromSettings} = require('../usecases/settings'); +const verifyDeviceForUser = require('../usecases/verify-device'); + +module.exports = async function e2eEncryptionScenarios(alice, bob) { + console.log(" creating an e2e encrypted room and join through invite:"); + const room = "secrets"; + await createRoom(bob, room); + await changeRoomSettings(bob, {encryption: true}); + await invite(bob, "@alice:localhost"); + await acceptInvite(alice, room); + const bobDevice = await getE2EDeviceFromSettings(bob); + // wait some time for the encryption warning dialog + // to appear after closing the settings + await delay(1000); + await acceptDialogMaybe(bob, "encryption"); + const aliceDevice = await getE2EDeviceFromSettings(alice); + // wait some time for the encryption warning dialog + // to appear after closing the settings + await delay(1000); + await acceptDialogMaybe(alice, "encryption"); + await verifyDeviceForUser(bob, "alice", aliceDevice); + await verifyDeviceForUser(alice, "bob", bobDevice); + const aliceMessage = "Guess what I just heard?!" + await sendMessage(alice, aliceMessage); + await receiveMessage(bob, {sender: "alice", body: aliceMessage, encrypted: true}); + const bobMessage = "You've got to tell me!"; + await sendMessage(bob, bobMessage); + await receiveMessage(alice, {sender: "bob", body: bobMessage, encrypted: true}); +} diff --git a/src/scenarios/lazy-loading.js b/src/scenarios/lazy-loading.js new file mode 100644 index 0000000000..f7360622a1 --- /dev/null +++ b/src/scenarios/lazy-loading.js @@ -0,0 +1,62 @@ +/* +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 {delay} = require('../util'); +const join = require('../usecases/join'); +const sendMessage = require('../usecases/send-message'); +const { + checkTimelineContains, + scrollToTimelineTop +} = require('../usecases/timeline'); +const createRoom = require('../usecases/create-room'); +const changeRoomSettings = require('../usecases/room-settings'); +const {enableLazyLoading} = require('../usecases/settings'); + +module.exports = async function lazyLoadingScenarios(alice, bob, charlies) { + console.log(" creating a room for lazy loading member scenarios:"); + await enableLazyLoading(alice); + const room = "Lazy Loading Test"; + const alias = "#lltest:localhost"; + const charlyMsg1 = "hi bob!"; + const charlyMsg2 = "how's it going??"; + await createRoom(bob, room); + await changeRoomSettings(bob, {directory: true, visibility: "public_no_guests", alias}); + // wait for alias to be set by server after clicking "save" + // so the charlies can join it. + await bob.delay(500); + const charlyMembers = await charlies.join(alias); + await charlyMembers.talk(charlyMsg1); + await charlyMembers.talk(charlyMsg2); + bob.log.step("sends 20 messages").mute(); + for(let i = 20; i >= 1; --i) { + await sendMessage(bob, `I will only say this ${i} time(s)!`); + } + bob.log.unmute().done(); + await join(alice, alias); + await scrollToTimelineTop(alice); + //alice should see 2 messages from every charly with + //the correct display name + const expectedMessages = [charlyMsg1, charlyMsg2].reduce((messages, msgText) => { + return charlies.sessions.reduce((messages, charly) => { + return messages.concat({ + sender: charly.displayName(), + body: msgText, + }); + }, messages); + }, []); + await checkTimelineContains(alice, expectedMessages, "Charly #1-10"); +} diff --git a/src/session.js b/src/session.js index 839b4a495b..3f233ee8f2 100644 --- a/src/session.js +++ b/src/session.js @@ -124,7 +124,7 @@ module.exports = class RiotSession { return await this.queryAll(selector); } - waitForReload(timeout = 5000) { + waitForReload(timeout = 10000) { return new Promise((resolve, reject) => { const timeoutHandle = setTimeout(() => { this.browser.removeEventListener('domcontentloaded', callback); diff --git a/src/usecases/README.md b/src/usecases/README.md new file mode 100644 index 0000000000..daa990e15c --- /dev/null +++ b/src/usecases/README.md @@ -0,0 +1,2 @@ +use cases contains the detailed DOM interactions to perform a given use case, may also do some assertions. +use cases are often used in multiple scenarios. From 5d06c65ce5d28d64a1efd2dd4e746cbf2c999e29 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 13 Sep 2018 12:02:49 +0200 Subject: [PATCH 113/221] split up ll tests in several functions --- src/scenarios/lazy-loading.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/scenarios/lazy-loading.js b/src/scenarios/lazy-loading.js index f7360622a1..0adb3fc793 100644 --- a/src/scenarios/lazy-loading.js +++ b/src/scenarios/lazy-loading.js @@ -29,10 +29,16 @@ const {enableLazyLoading} = require('../usecases/settings'); module.exports = async function lazyLoadingScenarios(alice, bob, charlies) { console.log(" creating a room for lazy loading member scenarios:"); await enableLazyLoading(alice); - const room = "Lazy Loading Test"; - const alias = "#lltest:localhost"; - const charlyMsg1 = "hi bob!"; - const charlyMsg2 = "how's it going??"; + await setupRoomWithBobAliceAndCharlies(alice, bob, charlies); + await checkPaginatedDisplayNames(alice, charlies); +} + +const room = "Lazy Loading Test"; +const alias = "#lltest:localhost"; +const charlyMsg1 = "hi bob!"; +const charlyMsg2 = "how's it going??"; + +async function setupRoomWithBobAliceAndCharlies(alice, bob, charlies) { await createRoom(bob, room); await changeRoomSettings(bob, {directory: true, visibility: "public_no_guests", alias}); // wait for alias to be set by server after clicking "save" @@ -47,6 +53,9 @@ module.exports = async function lazyLoadingScenarios(alice, bob, charlies) { } bob.log.unmute().done(); await join(alice, alias); +} + +async function checkPaginatedDisplayNames(alice, charlies) { await scrollToTimelineTop(alice); //alice should see 2 messages from every charly with //the correct display name From 239e6a4bcef1a92aa69d48923b8f46e072d8c00c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 13 Sep 2018 12:03:29 +0200 Subject: [PATCH 114/221] add ll tests to check if all expected members are in memberlist also move verify-device use case to timeline to reuse memberlist query for this test. --- src/scenarios/e2e-encryption.js | 2 +- src/scenarios/lazy-loading.js | 16 ++++++++++++ .../{verify-device.js => memberlist.js} | 26 ++++++++++++------- 3 files changed, 33 insertions(+), 11 deletions(-) rename src/usecases/{verify-device.js => memberlist.js} (68%) diff --git a/src/scenarios/e2e-encryption.js b/src/scenarios/e2e-encryption.js index 938dc5e592..51d8a70236 100644 --- a/src/scenarios/e2e-encryption.js +++ b/src/scenarios/e2e-encryption.js @@ -25,7 +25,7 @@ const {receiveMessage} = require('../usecases/timeline'); const createRoom = require('../usecases/create-room'); const changeRoomSettings = require('../usecases/room-settings'); const {getE2EDeviceFromSettings} = require('../usecases/settings'); -const verifyDeviceForUser = require('../usecases/verify-device'); +const {verifyDeviceForUser} = require('../usecases/memberlist'); module.exports = async function e2eEncryptionScenarios(alice, bob) { console.log(" creating an e2e encrypted room and join through invite:"); diff --git a/src/scenarios/lazy-loading.js b/src/scenarios/lazy-loading.js index 0adb3fc793..bd7c1d1507 100644 --- a/src/scenarios/lazy-loading.js +++ b/src/scenarios/lazy-loading.js @@ -23,14 +23,17 @@ const { scrollToTimelineTop } = require('../usecases/timeline'); const createRoom = require('../usecases/create-room'); +const {getMembersInMemberlist} = require('../usecases/memberlist'); const changeRoomSettings = require('../usecases/room-settings'); const {enableLazyLoading} = require('../usecases/settings'); +const assert = require('assert'); module.exports = async function lazyLoadingScenarios(alice, bob, charlies) { console.log(" creating a room for lazy loading member scenarios:"); await enableLazyLoading(alice); await setupRoomWithBobAliceAndCharlies(alice, bob, charlies); await checkPaginatedDisplayNames(alice, charlies); + await checkMemberList(alice, charlies); } const room = "Lazy Loading Test"; @@ -69,3 +72,16 @@ async function checkPaginatedDisplayNames(alice, charlies) { }, []); await checkTimelineContains(alice, expectedMessages, "Charly #1-10"); } + +async function checkMemberList(alice, charlies) { + alice.log.step("checks the memberlist contains herself, bob and all charlies"); + const displayNames = (await getMembersInMemberlist(alice)).map((m) => m.displayName); + assert(displayNames.includes("alice")); + assert(displayNames.includes("bob")); + charlies.sessions.forEach((charly) => { + assert(displayNames.includes(charly.displayName()), + `${charly.displayName()} should be in the member list, ` + + `only have ${displayNames}`); + }); + alice.log.done(); +} diff --git a/src/usecases/verify-device.js b/src/usecases/memberlist.js similarity index 68% rename from src/usecases/verify-device.js rename to src/usecases/memberlist.js index 7b01e7c756..b018ed552c 100644 --- a/src/usecases/verify-device.js +++ b/src/usecases/memberlist.js @@ -16,16 +16,13 @@ limitations under the License. const assert = require('assert'); -module.exports = async function verifyDeviceForUser(session, name, expectedDevice) { +module.exports.verifyDeviceForUser = async function(session, name, expectedDevice) { session.log.step(`verifies e2e device for ${name}`); - const memberNameElements = await session.queryAll(".mx_MemberList .mx_EntityTile_name"); - const membersAndNames = await Promise.all(memberNameElements.map(async (el) => { - return [el, await session.innerText(el)]; - })); - const matchingMember = membersAndNames.filter(([el, text]) => { - return text === name; - }).map(([el]) => el)[0]; - await matchingMember.click(); + const membersAndNames = await getMembersInMemberlist(session); + const matchingLabel = membersAndNames.filter((m) => { + return m.displayName === name; + }).map((m) => m.label)[0]; + await matchingLabel.click(); const firstVerifyButton = await session.waitAndQuery(".mx_MemberDeviceInfo_verify"); await firstVerifyButton.click(); const dialogCodeFields = await session.waitAndQueryAll(".mx_QuestionDialog code"); @@ -39,4 +36,13 @@ module.exports = async function verifyDeviceForUser(session, name, expectedDevic const closeMemberInfo = await session.query(".mx_MemberInfo_cancel"); await closeMemberInfo.click(); session.log.done(); -} \ No newline at end of file +} + +async function getMembersInMemberlist(session) { + const memberNameElements = await session.waitAndQueryAll(".mx_MemberList .mx_EntityTile_name"); + return Promise.all(memberNameElements.map(async (el) => { + return {label: el, displayName: await session.innerText(el)}; + })); +} + +module.exports.getMembersInMemberlist = getMembersInMemberlist; From 9f4cf776c5de89a84714182b532ac75b7fd159b6 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 13 Sep 2018 12:04:18 +0200 Subject: [PATCH 115/221] make receiveMessage more robust by checking first if the message is not already in the timeline --- src/usecases/timeline.js | 49 ++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/src/usecases/timeline.js b/src/usecases/timeline.js index beaaec5e5a..32c468048c 100644 --- a/src/usecases/timeline.js +++ b/src/usecases/timeline.js @@ -46,23 +46,38 @@ module.exports.receiveMessage = async function(session, expectedMessage) { session.log.step(`receives message "${expectedMessage.body}" from ${expectedMessage.sender}`); // wait for a response to come in that contains the message // crude, but effective - await session.page.waitForResponse(async (response) => { - if (response.request().url().indexOf("/sync") === -1) { - return false; - } - const body = await response.text(); - if (expectedMessage.encrypted) { - return body.indexOf(expectedMessage.sender) !== -1 && - body.indexOf("m.room.encrypted") !== -1; - } else { - return body.indexOf(expectedMessage.body) !== -1; - } - }); - // wait a bit for the incoming event to be rendered - await session.delay(1000); - const lastTile = await getLastEventTile(session); - const foundMessage = await getMessageFromEventTile(lastTile); - assertMessage(foundMessage, expectedMessage); + + async function assertLastMessage() { + const lastTile = await getLastEventTile(session); + const lastMessage = await getMessageFromEventTile(lastTile); + await assertMessage(lastMessage, expectedMessage); + } + + // first try to see if the message is already the last message in the timeline + let isExpectedMessage = false; + try { + assertLastMessage(); + isExpectedMessage = true; + } catch(ex) {} + + if (!isExpectedMessage) { + await session.page.waitForResponse(async (response) => { + if (response.request().url().indexOf("/sync") === -1) { + return false; + } + const body = await response.text(); + if (expectedMessage.encrypted) { + return body.indexOf(expectedMessage.sender) !== -1 && + body.indexOf("m.room.encrypted") !== -1; + } else { + return body.indexOf(expectedMessage.body) !== -1; + } + }); + // wait a bit for the incoming event to be rendered + await session.delay(1000); + await assertLastMessage(); + } + session.log.done(); } From af255c63866603271c03e4dc80b032fd9f702aae Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 14 Sep 2018 09:52:34 +0200 Subject: [PATCH 116/221] dont assert the first time in receiveMessage, as it will show an ugly assert error while everything is fine, just need to wait longer --- src/usecases/timeline.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/usecases/timeline.js b/src/usecases/timeline.js index 32c468048c..466d7fb222 100644 --- a/src/usecases/timeline.js +++ b/src/usecases/timeline.js @@ -47,20 +47,23 @@ module.exports.receiveMessage = async function(session, expectedMessage) { // wait for a response to come in that contains the message // crude, but effective - async function assertLastMessage() { + async function getLastMessage() { const lastTile = await getLastEventTile(session); - const lastMessage = await getMessageFromEventTile(lastTile); - await assertMessage(lastMessage, expectedMessage); + return getMessageFromEventTile(lastTile); } - // first try to see if the message is already the last message in the timeline + let lastMessage = null; let isExpectedMessage = false; try { - assertLastMessage(); - isExpectedMessage = true; + lastMessage = await getLastMessage(); + isExpectedMessage = lastMessage && + lastMessage.body === expectedMessage.body && + lastMessage.sender === expectedMessage.sender; } catch(ex) {} - - if (!isExpectedMessage) { + // first try to see if the message is already the last message in the timeline + if (isExpectedMessage) { + assertMessage(lastMessage, expectedMessage); + } else { await session.page.waitForResponse(async (response) => { if (response.request().url().indexOf("/sync") === -1) { return false; @@ -75,7 +78,8 @@ module.exports.receiveMessage = async function(session, expectedMessage) { }); // wait a bit for the incoming event to be rendered await session.delay(1000); - await assertLastMessage(); + lastMessage = await getLastMessage(); + assertMessage(lastMessage, expectedMessage); } session.log.done(); From 6deb595fecfb66c5d0e62421c8c852cd759462a5 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 14 Sep 2018 12:17:22 +0200 Subject: [PATCH 117/221] add logging to rest session actions --- src/rest/creator.js | 4 ++-- src/rest/multi.js | 49 ++++++++++++++++++++++++++++++++++++--------- src/rest/room.js | 7 ++++++- src/rest/session.js | 12 +++++++++-- src/scenario.js | 2 +- 5 files changed, 58 insertions(+), 16 deletions(-) diff --git a/src/rest/creator.js b/src/rest/creator.js index 9090a21e70..84b1fbc70a 100644 --- a/src/rest/creator.js +++ b/src/rest/creator.js @@ -27,10 +27,10 @@ module.exports = class RestSessionCreator { this.cwd = cwd; } - async createSessionRange(usernames, password) { + async createSessionRange(usernames, password, groupName) { const sessionPromises = usernames.map((username) => this.createSession(username, password)); const sessions = await Promise.all(sessionPromises); - return new RestMultiSession(sessions); + return new RestMultiSession(sessions, groupName); } async createSession(username, password) { diff --git a/src/rest/multi.js b/src/rest/multi.js index 12ebe9d4ab..35bb11a0cf 100644 --- a/src/rest/multi.js +++ b/src/rest/multi.js @@ -17,14 +17,16 @@ limitations under the License. const request = require('request-promise-native'); const RestRoom = require('./room'); const {approveConsent} = require('./consent'); +const Logger = require('../logger'); module.exports = class RestMultiSession { - constructor(sessions) { + constructor(sessions, groupName) { + this.log = new Logger(groupName); this.sessions = sessions; } - slice(start, end) { - return new RestMultiSession(this.sessions.slice(start, end)); + slice(start, end, groupName) { + return new RestMultiSession(this.sessions.slice(start, end), groupName); } pop(userName) { @@ -37,25 +39,52 @@ module.exports = class RestMultiSession { } async setDisplayName(fn) { - await Promise.all(this.sessions.map((s) => s.setDisplayName(fn(s)))); + this.log.step("set their display name") + await Promise.all(this.sessions.map(async (s) => { + s.log.mute(); + await s.setDisplayName(fn(s)); + s.log.unmute(); + })); + this.log.done(); } - async join(roomId) { - const rooms = await Promise.all(this.sessions.map((s) => s.join(roomId))); - return new RestMultiRoom(rooms); + async join(roomIdOrAlias) { + this.log.step(`join ${roomIdOrAlias}`) + const rooms = await Promise.all(this.sessions.map(async (s) => { + s.log.mute(); + const room = await s.join(roomIdOrAlias); + s.log.unmute(); + return room; + })); + this.log.done(); + return new RestMultiRoom(rooms, roomIdOrAlias, this.log); } } class RestMultiRoom { - constructor(rooms) { + constructor(rooms, roomIdOrAlias, log) { this.rooms = rooms; + this.roomIdOrAlias = roomIdOrAlias; + this.log = log; } async talk(message) { - await Promise.all(this.rooms.map((r) => r.talk(message))); + this.log.step(`say "${message}" in ${this.roomIdOrAlias}`) + await Promise.all(this.rooms.map(async (r) => { + r.log.mute(); + await r.talk(message); + r.log.unmute(); + })); + this.log.done(); } async leave() { - await Promise.all(this.rooms.map((r) => r.leave())); + this.log.step(`leave ${this.roomIdOrAlias}`) + await Promise.all(this.rooms.map(async (r) => { + r.log.mute(); + await r.leave(message); + r.log.unmute(); + })); + this.log.done(); } } diff --git a/src/rest/room.js b/src/rest/room.js index d8de958a27..a7f40af594 100644 --- a/src/rest/room.js +++ b/src/rest/room.js @@ -18,22 +18,27 @@ const uuidv4 = require('uuid/v4'); /* no pun intented */ module.exports = class RestRoom { - constructor(session, roomId) { + constructor(session, roomId, log) { this.session = session; this._roomId = roomId; + this.log = log; } async talk(message) { + this.log.step(`says "${message}" in ${this._roomId}`) const txId = uuidv4(); await this.session._put(`/rooms/${this._roomId}/send/m.room.message/${txId}`, { "msgtype": "m.text", "body": message }); + this.log.done(); return txId; } async leave() { + this.log.step(`leaves ${this._roomId}`) await this.session._post(`/rooms/${this._roomId}/leave`); + this.log.done(); } roomId() { diff --git a/src/rest/session.js b/src/rest/session.js index ece04f3352..21922a69f1 100644 --- a/src/rest/session.js +++ b/src/rest/session.js @@ -15,11 +15,13 @@ 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; } @@ -37,18 +39,23 @@ module.exports = class RestSession { } 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 {room_id} = await this._post(`/join/${encodeURIComponent(roomIdOrAlias)}`); - return new RestRoom(this, room_id); + this.log.done(); + return new RestRoom(this, room_id, this.log); } async createRoom(name, options) { + this.log.step(`creates room ${name}`); const body = { name, }; @@ -68,7 +75,8 @@ module.exports = class RestSession { } const {room_id} = await this._post(`/createRoom`, body); - return new RestRoom(this, room_id); + this.log.done(); + return new RestRoom(this, room_id, this.log); } _post(csApiPath, body) { diff --git a/src/scenario.js b/src/scenario.js index f0b4ad988b..12cff7d498 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -41,7 +41,7 @@ module.exports = async function scenario(createSession, restCreator) { async function createRestUsers(restCreator) { const usernames = range(1, 10).map((i) => `charly-${i}`); - const charlies = await restCreator.createSessionRange(usernames, 'testtest'); + const charlies = await restCreator.createSessionRange(usernames, "testtest", "charly-1..10"); await charlies.setDisplayName((s) => `Charly #${s.userName().split('-')[1]}`); return charlies; } From 16b2f09915cce5781cf8da90cf101491158044fc Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 14 Sep 2018 12:44:01 +0200 Subject: [PATCH 118/221] Test if members joining while user is offline are received after returning online with LL enabled --- src/rest/multi.js | 2 +- src/scenarios/lazy-loading.js | 29 +++++++++++++++++++++++++---- src/session.js | 7 +++++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/rest/multi.js b/src/rest/multi.js index 35bb11a0cf..3d24245ddf 100644 --- a/src/rest/multi.js +++ b/src/rest/multi.js @@ -25,7 +25,7 @@ module.exports = class RestMultiSession { this.sessions = sessions; } - slice(start, end, groupName) { + slice(groupName, start, end) { return new RestMultiSession(this.sessions.slice(start, end), groupName); } diff --git a/src/scenarios/lazy-loading.js b/src/scenarios/lazy-loading.js index bd7c1d1507..a606cc0421 100644 --- a/src/scenarios/lazy-loading.js +++ b/src/scenarios/lazy-loading.js @@ -31,9 +31,15 @@ const assert = require('assert'); module.exports = async function lazyLoadingScenarios(alice, bob, charlies) { console.log(" creating a room for lazy loading member scenarios:"); await enableLazyLoading(alice); - await setupRoomWithBobAliceAndCharlies(alice, bob, charlies); - await checkPaginatedDisplayNames(alice, charlies); - await checkMemberList(alice, charlies); + const charly1to5 = charlies.slice("charly-1..5", 0, 5); + const charly6to10 = charlies.slice("charly-6..10", 5); + assert(charly1to5.sessions.length, 5); + assert(charly6to10.sessions.length, 5); + await setupRoomWithBobAliceAndCharlies(alice, bob, charly1to5); + await checkPaginatedDisplayNames(alice, charly1to5); + await checkMemberList(alice, charly1to5); + await joinCharliesWhileAliceIsOffline(alice, charly6to10); + await checkMemberList(alice, charly6to10); } const room = "Lazy Loading Test"; @@ -70,7 +76,7 @@ async function checkPaginatedDisplayNames(alice, charlies) { }); }, messages); }, []); - await checkTimelineContains(alice, expectedMessages, "Charly #1-10"); + await checkTimelineContains(alice, expectedMessages, "Charly #1-5"); } async function checkMemberList(alice, charlies) { @@ -85,3 +91,18 @@ async function checkMemberList(alice, charlies) { }); alice.log.done(); } + +async function joinCharliesWhileAliceIsOffline(alice, charly6to10) { + await alice.setOffline(true); + await delay(1000); + const members6to10 = await charly6to10.join(alias); + const member6 = members6to10.rooms[0]; + member6.log.step("sends 20 messages").mute(); + for(let i = 20; i >= 1; --i) { + await member6.talk("where is charly?"); + } + member6.log.unmute().done(); + await delay(1000); + await alice.setOffline(false); + await delay(1000); +} diff --git a/src/session.js b/src/session.js index 3f233ee8f2..82a66fda39 100644 --- a/src/session.js +++ b/src/session.js @@ -173,6 +173,13 @@ module.exports = class RiotSession { return delay(ms); } + async setOffline(enabled) { + const description = enabled ? "offline" : "back online"; + this.log.step(`goes ${description}`); + await this.page.setOfflineMode(enabled); + this.log.done(); + } + close() { return this.browser.close(); } From 36708cc5db5cc303904884bc9ea50ac76bcb3a5c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 14 Sep 2018 14:45:40 +0200 Subject: [PATCH 119/221] wait for next sync before inspecting memberlist before we needed a 10s delay here to make the test work reliable, this should be faster in the best case. --- src/scenarios/lazy-loading.js | 5 +++-- src/session.js | 27 +++++++++++++++++++++++++++ src/usecases/timeline.js | 5 +---- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/scenarios/lazy-loading.js b/src/scenarios/lazy-loading.js index a606cc0421..15826af568 100644 --- a/src/scenarios/lazy-loading.js +++ b/src/scenarios/lazy-loading.js @@ -102,7 +102,8 @@ async function joinCharliesWhileAliceIsOffline(alice, charly6to10) { await member6.talk("where is charly?"); } member6.log.unmute().done(); - await delay(1000); + const catchupPromise = alice.waitForNextSuccessfulSync(); await alice.setOffline(false); - await delay(1000); + await catchupPromise; + await delay(2000); } diff --git a/src/session.js b/src/session.js index 82a66fda39..7ea980bd32 100644 --- a/src/session.js +++ b/src/session.js @@ -161,6 +161,33 @@ module.exports = class RiotSession { }); } + waitForSyncResponseWith(predicate) { + return this.page.waitForResponse(async (response) => { + if (response.request().url().indexOf("/sync") === -1) { + return false; + } + return predicate(response); + }); + } + + /** wait for a /sync request started after this call that gets a 200 response */ + async waitForNextSuccessfulSync() { + const syncUrls = []; + function onRequest(request) { + if (request.url().indexOf("/sync") !== -1) { + syncUrls.push(request.url()); + } + } + + this.page.on('request', onRequest); + + await this.page.waitForResponse((response) => { + return syncUrls.includes(response.request().url()) && response.status() === 200; + }); + + this.page.removeListener('request', onRequest); + } + goto(url) { return this.page.goto(url); } diff --git a/src/usecases/timeline.js b/src/usecases/timeline.js index 466d7fb222..dce0203660 100644 --- a/src/usecases/timeline.js +++ b/src/usecases/timeline.js @@ -64,10 +64,7 @@ module.exports.receiveMessage = async function(session, expectedMessage) { if (isExpectedMessage) { assertMessage(lastMessage, expectedMessage); } else { - await session.page.waitForResponse(async (response) => { - if (response.request().url().indexOf("/sync") === -1) { - return false; - } + await session.waitForSyncResponseWith(async (response) => { const body = await response.text(); if (expectedMessage.encrypted) { return body.indexOf(expectedMessage.sender) !== -1 && From 992a0be4d08f877e731b71db3d0da0b1687ae8fd Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 14 Sep 2018 14:46:25 +0200 Subject: [PATCH 120/221] DRY usernames --- src/scenarios/lazy-loading.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scenarios/lazy-loading.js b/src/scenarios/lazy-loading.js index 15826af568..c33e83215c 100644 --- a/src/scenarios/lazy-loading.js +++ b/src/scenarios/lazy-loading.js @@ -76,11 +76,11 @@ async function checkPaginatedDisplayNames(alice, charlies) { }); }, messages); }, []); - await checkTimelineContains(alice, expectedMessages, "Charly #1-5"); + await checkTimelineContains(alice, expectedMessages, charlies.log.username); } async function checkMemberList(alice, charlies) { - alice.log.step("checks the memberlist contains herself, bob and all charlies"); + alice.log.step(`checks the memberlist contains herself, bob and ${charlies.log.username}`); const displayNames = (await getMembersInMemberlist(alice)).map((m) => m.displayName); assert(displayNames.includes("alice")); assert(displayNames.includes("bob")); From 8cff961ec82469e5321634992d45aad9a00c2e25 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 14 Sep 2018 14:46:42 +0200 Subject: [PATCH 121/221] use develop for now as LL with gappy syncs is fixed on that branch for now --- synapse/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse/install.sh b/synapse/install.sh index 3d8172a9f6..37dfd7d7e2 100755 --- a/synapse/install.sh +++ b/synapse/install.sh @@ -1,6 +1,6 @@ #!/bin/bash # config -SYNAPSE_BRANCH=master +SYNAPSE_BRANCH=develop INSTALLATION_NAME=consent SERVER_DIR=installations/$INSTALLATION_NAME CONFIG_TEMPLATE=consent From 42c1b95b7c4f57527c5bed3e04aa5cdd7c369f22 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 18 Sep 2018 10:17:03 +0200 Subject: [PATCH 122/221] spit out logs for creating REST users to figure out what is going on with the CI server --- src/rest/creator.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/rest/creator.js b/src/rest/creator.js index 84b1fbc70a..cc87134108 100644 --- a/src/rest/creator.js +++ b/src/rest/creator.js @@ -35,11 +35,12 @@ module.exports = class RestSessionCreator { async createSession(username, password) { await this._register(username, password); + console.log(` * created REST user ${username} ... done`); const authResult = await this._authenticate(username, password); return new RestSession(authResult); } - _register(username, password) { + async _register(username, password) { const registerArgs = [ '-c homeserver.yaml', `-u ${username}`, @@ -55,11 +56,14 @@ module.exports = class RestSessionCreator { registerCmd ].join(';'); - return exec(allCmds, {cwd: this.cwd, encoding: 'utf-8'}).catch((result) => { + try { + await exec(allCmds, {cwd: this.cwd, encoding: 'utf-8'}); + } catch (result) { const lines = result.stdout.trim().split('\n'); const failureReason = lines[lines.length - 1]; - throw new Error(`creating user ${username} failed: ${failureReason}`); - }); + const logs = (await exec("tail -n 100 synapse/installations/consent/homeserver.log")).stdout; + throw new Error(`creating user ${username} failed: ${failureReason}, synapse logs:\n${logs}`); + } } async _authenticate(username, password) { From 0d86b82e3ae19729e72c0a9b0196a8b761cd1949 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 18 Sep 2018 11:13:02 +0200 Subject: [PATCH 123/221] increase timeout for server notices room --- src/usecases/server-notices-consent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/usecases/server-notices-consent.js b/src/usecases/server-notices-consent.js index 25c3bb3bd5..eb42dcdaf7 100644 --- a/src/usecases/server-notices-consent.js +++ b/src/usecases/server-notices-consent.js @@ -19,7 +19,7 @@ const acceptInvite = require("./accept-invite") module.exports = async function acceptServerNoticesInviteAndConsent(session) { await acceptInvite(session, "Server Notices"); session.log.step(`accepts terms & conditions`); - const consentLink = await session.waitAndQuery(".mx_EventTile_body a"); + const consentLink = await session.waitAndQuery(".mx_EventTile_body a", 10000); const termsPagePromise = session.waitForNewPage(); await consentLink.click(); const termsPage = await termsPagePromise; From a84162ede84a4648c49361cead0c4c40c7fb3801 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 18 Sep 2018 16:11:07 +0200 Subject: [PATCH 124/221] use patched synapse so admin rest api works with python 2.7.6 --- synapse/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse/install.sh b/synapse/install.sh index 37dfd7d7e2..1ae2212e7e 100755 --- a/synapse/install.sh +++ b/synapse/install.sh @@ -1,6 +1,6 @@ #!/bin/bash # config -SYNAPSE_BRANCH=develop +SYNAPSE_BRANCH=bwindels/adminapibeforepy277 INSTALLATION_NAME=consent SERVER_DIR=installations/$INSTALLATION_NAME CONFIG_TEMPLATE=consent From cf397efed5e46b59b1609f4c16ddb12aab483bdf Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 18 Sep 2018 16:50:33 +0200 Subject: [PATCH 125/221] disable LL tests on travis CI --- run.sh | 2 +- src/scenario.js | 15 ++++++++++++--- start.js | 3 ++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/run.sh b/run.sh index 02b2e4cbdf..daa3bd222c 100755 --- a/run.sh +++ b/run.sh @@ -15,5 +15,5 @@ trap 'handle_error' ERR ./synapse/start.sh ./riot/start.sh -node start.js +node start.js --travis stop_servers diff --git a/src/scenario.js b/src/scenario.js index 12cff7d498..2fd52de679 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -22,7 +22,7 @@ const roomDirectoryScenarios = require('./scenarios/directory'); const lazyLoadingScenarios = require('./scenarios/lazy-loading'); const e2eEncryptionScenarios = require('./scenarios/e2e-encryption'); -module.exports = async function scenario(createSession, restCreator) { +module.exports = async function scenario(createSession, restCreator, runningOnTravis) { async function createUser(username) { const session = await createSession(username); await signup(session, session.username, 'testtest', session.hsUrl); @@ -32,11 +32,20 @@ module.exports = async function scenario(createSession, restCreator) { const alice = await createUser("alice"); const bob = await createUser("bob"); - const charlies = await createRestUsers(restCreator); await roomDirectoryScenarios(alice, bob); await e2eEncryptionScenarios(alice, bob); - await lazyLoadingScenarios(alice, bob, charlies); + + // disable LL tests until we can run synapse on anything > than 2.7.7 as + // /admin/register fails with a missing method. + // either switch to python3 on synapse, + // blocked on https://github.com/matrix-org/synapse/issues/3900 + // or use a more recent version of ubuntu + // or switch to circleci? + if (!runningOnTravis) { + const charlies = await createRestUsers(restCreator); + await lazyLoadingScenarios(alice, bob, charlies); + } } async function createRestUsers(restCreator) { diff --git a/start.js b/start.js index 1c3f27bbe3..18ccb438ec 100644 --- a/start.js +++ b/start.js @@ -26,6 +26,7 @@ program .option('--windowed', "dont run tests headless", false) .option('--slow-mo', "run tests slower to follow whats going on", false) .option('--dev-tools', "open chrome devtools in browser window", false) + .option('--travis', "running on travis CI, disable tests known to break on Ubuntu 14.04 LTS", false) .parse(process.argv); const hsUrl = 'http://localhost:5005'; @@ -58,7 +59,7 @@ async function runTests() { let failure = false; try { - await scenario(createSession, restCreator); + await scenario(createSession, restCreator, program.travis); } catch(err) { failure = true; console.log('failure: ', err); From d47f782c214c62b3feebf393613eb8aa3e2c4896 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 18 Sep 2018 16:51:22 +0200 Subject: [PATCH 126/221] Revert "increase timeout for server notices room" --- src/usecases/server-notices-consent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/usecases/server-notices-consent.js b/src/usecases/server-notices-consent.js index eb42dcdaf7..25c3bb3bd5 100644 --- a/src/usecases/server-notices-consent.js +++ b/src/usecases/server-notices-consent.js @@ -19,7 +19,7 @@ const acceptInvite = require("./accept-invite") module.exports = async function acceptServerNoticesInviteAndConsent(session) { await acceptInvite(session, "Server Notices"); session.log.step(`accepts terms & conditions`); - const consentLink = await session.waitAndQuery(".mx_EventTile_body a", 10000); + const consentLink = await session.waitAndQuery(".mx_EventTile_body a"); const termsPagePromise = session.waitForNewPage(); await consentLink.click(); const termsPage = await termsPagePromise; From 70eb4805534bc81f6052d62464f260093cca60cc Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 18 Sep 2018 16:51:50 +0200 Subject: [PATCH 127/221] Revert "use patched synapse so admin rest api works with python 2.7.6" --- synapse/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse/install.sh b/synapse/install.sh index 1ae2212e7e..37dfd7d7e2 100755 --- a/synapse/install.sh +++ b/synapse/install.sh @@ -1,6 +1,6 @@ #!/bin/bash # config -SYNAPSE_BRANCH=bwindels/adminapibeforepy277 +SYNAPSE_BRANCH=develop INSTALLATION_NAME=consent SERVER_DIR=installations/$INSTALLATION_NAME CONFIG_TEMPLATE=consent From 13b20bb1924690c125c78f159623733c183fdff2 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 19 Sep 2018 10:56:39 +0200 Subject: [PATCH 128/221] pass parameters through instead of hardcoding --travis --- run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run.sh b/run.sh index daa3bd222c..569940e1ae 100755 --- a/run.sh +++ b/run.sh @@ -15,5 +15,5 @@ trap 'handle_error' ERR ./synapse/start.sh ./riot/start.sh -node start.js --travis +node start.js $@ stop_servers From a637ad85ad27eb48fdf39463bcf8d4cbf5ad137e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 21 Sep 2018 11:25:26 +0200 Subject: [PATCH 129/221] set a room alias for a public room, as required now --- src/scenarios/directory.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scenarios/directory.js b/src/scenarios/directory.js index 3b87d64d78..cfe72ccef3 100644 --- a/src/scenarios/directory.js +++ b/src/scenarios/directory.js @@ -25,7 +25,7 @@ module.exports = async function roomDirectoryScenarios(alice, bob) { console.log(" creating a public room and join through directory:"); const room = 'test'; await createRoom(alice, room); - await changeRoomSettings(alice, {directory: true, visibility: "public_no_guests"}); + await changeRoomSettings(alice, {directory: true, visibility: "public_no_guests", alias: "#test"}); await join(bob, room); //looks up room in directory const bobMessage = "hi Alice!"; await sendMessage(bob, bobMessage); From 8ee7623d9010dc5fa1e35404084747c5b97953f6 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 25 Sep 2018 18:01:10 +0100 Subject: [PATCH 130/221] current tests need riot develop to set a room alias without a domain name --- riot/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/riot/install.sh b/riot/install.sh index 209926d4c5..9b5a0a0757 100755 --- a/riot/install.sh +++ b/riot/install.sh @@ -1,5 +1,5 @@ #!/bin/bash -RIOT_BRANCH=master +RIOT_BRANCH=develop BASE_DIR=$(readlink -f $(dirname $0)) if [ -d $BASE_DIR/riot-web ]; then From 04b64dbae9f2a4ea0169ed3224921ea051bc91fc Mon Sep 17 00:00:00 2001 From: Tom Lant Date: Tue, 25 Sep 2018 18:45:08 +0100 Subject: [PATCH 131/221] Some changes to make the testing script run on mac, too, + a multithreaded server for riot --- riot/install.sh | 21 +++++++++++++++++++-- riot/start.sh | 11 +++++++---- riot/stop.sh | 4 +++- run.sh | 1 + synapse/install.sh | 27 ++++++++++++++++++++------- synapse/start.sh | 6 ++++-- synapse/stop.sh | 4 +++- 7 files changed, 57 insertions(+), 17 deletions(-) diff --git a/riot/install.sh b/riot/install.sh index 209926d4c5..9b85b4cb13 100755 --- a/riot/install.sh +++ b/riot/install.sh @@ -1,12 +1,29 @@ #!/bin/bash -RIOT_BRANCH=master +set -e -BASE_DIR=$(readlink -f $(dirname $0)) +RIOT_BRANCH=master +BASE_DIR=$(cd $(dirname $0) && pwd) if [ -d $BASE_DIR/riot-web ]; then echo "riot is already installed" exit fi +# Install ComplexHttpServer (a drop in replacement for Python's SimpleHttpServer +# but with support for multiple threads) into a virtualenv. +( + virtualenv $BASE_DIR/env + source $BASE_DIR/env/bin/activate + + # Having been bitten by pip SSL fail too many times, I don't trust the existing pip + # to be able to --upgrade itself, so grab a new one fresh from source. + curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py + python get-pip.py + + pip install ComplexHttpServer + + deactivate +) + cd $BASE_DIR curl -L https://github.com/vector-im/riot-web/archive/${RIOT_BRANCH}.zip --output riot.zip unzip -q riot.zip diff --git a/riot/start.sh b/riot/start.sh index 0af9f4faef..be226ed257 100755 --- a/riot/start.sh +++ b/riot/start.sh @@ -1,6 +1,8 @@ -#!/bin/bash +#!/usr/bin/env bash +set -e + PORT=5000 -BASE_DIR=$(readlink -f $(dirname $0)) +BASE_DIR=$(cd $(dirname $0) && pwd) PIDFILE=$BASE_DIR/riot.pid CONFIG_BACKUP=config.e2etests_backup.json @@ -21,7 +23,8 @@ cp $BASE_DIR/config-template/config.json . LOGFILE=$(mktemp) # run web server in the background, showing output on error ( - python -m SimpleHTTPServer $PORT > $LOGFILE 2>&1 & + source $BASE_DIR/env/bin/activate + python -m ComplexHTTPServer $PORT > $LOGFILE 2>&1 & PID=$! echo $PID > $PIDFILE # wait so subshell does not exit @@ -40,7 +43,7 @@ LOGFILE=$(mktemp) )& # to be able to return the exit code for immediate errors (like address already in use) # we wait for a short amount of time in the background and exit when the first -# child process exists +# child process exits sleep 0.5 & # wait the first child process to exit (python or sleep) wait -n; RESULT=$? diff --git a/riot/stop.sh b/riot/stop.sh index a3e07f574f..eb99fa11cc 100755 --- a/riot/stop.sh +++ b/riot/stop.sh @@ -1,5 +1,7 @@ #!/bin/bash -BASE_DIR=$(readlink -f $(dirname $0)) +set -e + +BASE_DIR=$(cd $(dirname $0) && pwd) PIDFILE=riot.pid CONFIG_BACKUP=config.e2etests_backup.json diff --git a/run.sh b/run.sh index 569940e1ae..0e03b733ce 100755 --- a/run.sh +++ b/run.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -e stop_servers() { ./riot/stop.sh diff --git a/synapse/install.sh b/synapse/install.sh index 37dfd7d7e2..b80b1ac705 100755 --- a/synapse/install.sh +++ b/synapse/install.sh @@ -1,4 +1,6 @@ #!/bin/bash +set -e + # config SYNAPSE_BRANCH=develop INSTALLATION_NAME=consent @@ -6,7 +8,7 @@ SERVER_DIR=installations/$INSTALLATION_NAME CONFIG_TEMPLATE=consent PORT=5005 # set current directory to script directory -BASE_DIR=$(readlink -f $(dirname $0)) +BASE_DIR=$(cd $(dirname $0) && pwd) if [ -d $BASE_DIR/$SERVER_DIR ]; then echo "synapse is already installed" @@ -22,9 +24,17 @@ mv synapse-$SYNAPSE_BRANCH $SERVER_DIR cd $SERVER_DIR virtualenv -p python2.7 env source env/bin/activate -pip install --upgrade pip + +# Having been bitten by pip SSL fail too many times, I don't trust the existing pip +# to be able to --upgrade itself, so grab a new one fresh from source. +curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py +python get-pip.py + pip install --upgrade setuptools +python synapse/python_dependencies.py | xargs pip install +pip install lxml mock pip install . + python -m synapse.app.homeserver \ --server-name localhost \ --config-path homeserver.yaml \ @@ -32,8 +42,11 @@ python -m synapse.app.homeserver \ --report-stats=no # apply configuration cp -r $BASE_DIR/config-templates/$CONFIG_TEMPLATE/. ./ -sed -i "s#{{SYNAPSE_ROOT}}#$(pwd)/#g" homeserver.yaml -sed -i "s#{{SYNAPSE_PORT}}#${PORT}#g" homeserver.yaml -sed -i "s#{{FORM_SECRET}}#$(uuidgen)#g" homeserver.yaml -sed -i "s#{{REGISTRATION_SHARED_SECRET}}#$(uuidgen)#g" homeserver.yaml -sed -i "s#{{MACAROON_SECRET_KEY}}#$(uuidgen)#g" homeserver.yaml + +# Hashes used instead of slashes because we'll get a value back from $(pwd) that'll be +# full of un-escapable slashes. +sed -i '' "s#{{SYNAPSE_ROOT}}#$(pwd)/#g" homeserver.yaml +sed -i '' "s#{{SYNAPSE_PORT}}#${PORT}#g" homeserver.yaml +sed -i '' "s#{{FORM_SECRET}}#$(uuidgen)#g" homeserver.yaml +sed -i '' "s#{{REGISTRATION_SHARED_SECRET}}#$(uuidgen)#g" homeserver.yaml +sed -i '' "s#{{MACAROON_SECRET_KEY}}#$(uuidgen)#g" homeserver.yaml diff --git a/synapse/start.sh b/synapse/start.sh index 12b89b31ed..379de3850c 100755 --- a/synapse/start.sh +++ b/synapse/start.sh @@ -1,5 +1,7 @@ #!/bin/bash -BASE_DIR=$(readlink -f $(dirname $0)) +set -e + +BASE_DIR=$(cd $(dirname $0) && pwd) cd $BASE_DIR cd installations/consent source env/bin/activate @@ -9,4 +11,4 @@ EXIT_CODE=$? if [ $EXIT_CODE -ne 0 ]; then cat $LOGFILE fi -exit $EXIT_CODE \ No newline at end of file +exit $EXIT_CODE diff --git a/synapse/stop.sh b/synapse/stop.sh index d83ddd0e4a..a7716744ef 100755 --- a/synapse/stop.sh +++ b/synapse/stop.sh @@ -1,5 +1,7 @@ #!/bin/bash -BASE_DIR=$(readlink -f $(dirname $0)) +set -e + +BASE_DIR=$(cd $(dirname $0) && pwd) cd $BASE_DIR cd installations/consent source env/bin/activate From 861af62208b5b6642c8dbee4f4f714d7157d8d0a Mon Sep 17 00:00:00 2001 From: Tom Lant Date: Thu, 27 Sep 2018 13:21:45 +0100 Subject: [PATCH 132/221] Make the sed usage cross-platform compatible --- synapse/install.sh | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/synapse/install.sh b/synapse/install.sh index b80b1ac705..c83ca6512a 100755 --- a/synapse/install.sh +++ b/synapse/install.sh @@ -45,8 +45,11 @@ cp -r $BASE_DIR/config-templates/$CONFIG_TEMPLATE/. ./ # Hashes used instead of slashes because we'll get a value back from $(pwd) that'll be # full of un-escapable slashes. -sed -i '' "s#{{SYNAPSE_ROOT}}#$(pwd)/#g" homeserver.yaml -sed -i '' "s#{{SYNAPSE_PORT}}#${PORT}#g" homeserver.yaml -sed -i '' "s#{{FORM_SECRET}}#$(uuidgen)#g" homeserver.yaml -sed -i '' "s#{{REGISTRATION_SHARED_SECRET}}#$(uuidgen)#g" homeserver.yaml -sed -i '' "s#{{MACAROON_SECRET_KEY}}#$(uuidgen)#g" homeserver.yaml +# Manually directing output to .templated file and then manually renaming back on top +# of the original file because -i is a nonstandard sed feature which is implemented +# differently, across os X and ubuntu at least +sed "s#{{SYNAPSE_ROOT}}#$(pwd)/#g" homeserver.yaml > homeserver.yaml.templated && mv homeserver.yaml.templated homeserver.yaml +sed "s#{{SYNAPSE_PORT}}#${PORT}#g" homeserver.yaml > homeserver.yaml.templated && mv homeserver.yaml.templated homeserver.yaml +sed "s#{{FORM_SECRET}}#$(uuidgen)#g" homeserver.yaml > homeserver.yaml.templated && mv homeserver.yaml.templated homeserver.yaml +sed "s#{{REGISTRATION_SHARED_SECRET}}#$(uuidgen)#g" homeserver.yaml > homeserver.yaml.templated && mv homeserver.yaml.templated homeserver.yaml +sed "s#{{MACAROON_SECRET_KEY}}#$(uuidgen)#g" homeserver.yaml > homeserver.yaml.templated && mv homeserver.yaml.templated homeserver.yaml From 1147508c345d7744185ec9619ab7e1e4cc4d9356 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 27 Sep 2018 18:09:27 +0100 Subject: [PATCH 133/221] list of tests we want to write --- TODO.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000000..4cbcba801b --- /dev/null +++ b/TODO.md @@ -0,0 +1,4 @@ +join a peekable room by directory +join a peekable room by invite +join a non-peekable room by directory +join a non-peekable room by invite From b2bd134945a907c6bdd29d98785c33737a13db58 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 8 Oct 2018 16:58:01 +0200 Subject: [PATCH 134/221] add config file instructions to run with --riot-url --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index acfeed8257..4f4f9217e3 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,10 @@ It's important to always stop and start synapse before each run of the tests to start.js accepts the following parameters that can help running the tests locally: - `--no-logs` dont show the excessive logging show by default (meant for CI), just where the test failed. - - `--riot-url ` don't use the riot copy and static server provided by the tests, but use a running server like the webpack watch server to run the tests against. Make sure to have `welcomeUserId` disabled in your config as the tests assume there is no riot-bot currently. + - `--riot-url ` don't use the riot copy and static server provided by the tests, but use a running server like the webpack watch server to run the tests against. Make sure to have the following local config: + - `welcomeUserId` disabled as the tests assume there is no riot-bot currently. Make sure to set the default homeserver to + - `"default_hs_url": "http://localhost:5005"`, to use the e2e tests synapse (the tests use the default HS to run against atm) + - `"feature_lazyloading": "labs"`, currently assumes lazy loading needs to be turned on in the settings, will change soon. - `--slow-mo` run the tests a bit slower, so it's easier to follow along with `--windowed`. - `--windowed` run the tests in an actual browser window Try to limit interacting with the windows while the tests are running. Hovering over the window tends to fail the tests, dragging the title bar should be fine though. - `--dev-tools` open the devtools in the browser window, only applies if `--windowed` is set as well. From 1a2254677c91dc291180a26a5f736bb3e852e6ca Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 9 Oct 2018 15:15:03 +0200 Subject: [PATCH 135/221] test leaving members disappear from memberlist --- src/rest/multi.js | 7 ++++++- src/rest/session.js | 14 +++++++++++++- src/scenarios/lazy-loading.js | 15 +++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/rest/multi.js b/src/rest/multi.js index 3d24245ddf..b930a27c1e 100644 --- a/src/rest/multi.js +++ b/src/rest/multi.js @@ -59,6 +59,11 @@ module.exports = class RestMultiSession { this.log.done(); return new RestMultiRoom(rooms, roomIdOrAlias, this.log); } + + room(roomIdOrAlias) { + const rooms = this.sessions.map(s => s.room(roomIdOrAlias)); + return new RestMultiRoom(rooms, roomIdOrAlias, this.log); + } } class RestMultiRoom { @@ -82,7 +87,7 @@ class RestMultiRoom { this.log.step(`leave ${this.roomIdOrAlias}`) await Promise.all(this.rooms.map(async (r) => { r.log.mute(); - await r.leave(message); + await r.leave(); r.log.unmute(); })); this.log.done(); diff --git a/src/rest/session.js b/src/rest/session.js index 21922a69f1..de05cd4b5c 100644 --- a/src/rest/session.js +++ b/src/rest/session.js @@ -24,6 +24,7 @@ module.exports = class RestSession { this.log = new Logger(credentials.userId); this._credentials = credentials; this._displayName = null; + this._rooms = {}; } userId() { @@ -51,7 +52,18 @@ module.exports = class RestSession { this.log.step(`joins ${roomIdOrAlias}`); const {room_id} = await this._post(`/join/${encodeURIComponent(roomIdOrAlias)}`); this.log.done(); - return new RestRoom(this, room_id, this.log); + const room = new RestRoom(this, room_id, this.log); + this._rooms[room_id] = 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) { diff --git a/src/scenarios/lazy-loading.js b/src/scenarios/lazy-loading.js index c33e83215c..7fd67153f0 100644 --- a/src/scenarios/lazy-loading.js +++ b/src/scenarios/lazy-loading.js @@ -40,6 +40,10 @@ module.exports = async function lazyLoadingScenarios(alice, bob, charlies) { await checkMemberList(alice, charly1to5); await joinCharliesWhileAliceIsOffline(alice, charly6to10); await checkMemberList(alice, charly6to10); + await charlies.room(alias).leave(); + await delay(1000); + await checkMemberListLacksCharlies(alice, charlies); + await checkMemberListLacksCharlies(bob, charlies); } const room = "Lazy Loading Test"; @@ -92,6 +96,17 @@ async function checkMemberList(alice, charlies) { alice.log.done(); } +async function checkMemberListLacksCharlies(session, charlies) { + session.log.step(`checks the memberlist doesn't contain ${charlies.log.username}`); + const displayNames = (await getMembersInMemberlist(session)).map((m) => m.displayName); + charlies.sessions.forEach((charly) => { + assert(!displayNames.includes(charly.displayName()), + `${charly.displayName()} should not be in the member list, ` + + `only have ${displayNames}`); + }); + session.log.done(); +} + async function joinCharliesWhileAliceIsOffline(alice, charly6to10) { await alice.setOffline(true); await delay(1000); From f607cb27027d0f5180ac69b7a040672502912ca2 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 1 Nov 2018 17:55:48 -0600 Subject: [PATCH 136/221] Fix the registration process to handle m.login.terms auth --- src/usecases/signup.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/usecases/signup.js b/src/usecases/signup.js index b715e111a1..dfd97a975f 100644 --- a/src/usecases/signup.js +++ b/src/usecases/signup.js @@ -59,6 +59,12 @@ module.exports = async function signup(session, username, password, homeserver) //confirm dialog saying you cant log back in without e-mail const continueButton = await session.waitAndQuery('.mx_QuestionDialog button.mx_Dialog_primary'); await continueButton.click(); + + //find the privacy policy checkbox and check it + //this should automatically move ahead with registration + const policyCheckbox = await session.waitAndQuery('.mx_Login_box input[type="checkbox"]'); + await policyCheckbox.click(); + //wait for registration to finish so the hash gets set //onhashchange better? await session.delay(2000); From d57a56d7a8e873b57dababd511b103d61b94b593 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 1 Nov 2018 18:20:55 -0600 Subject: [PATCH 137/221] There is no more server notices invite on signup --- src/scenario.js | 2 -- src/usecases/consent.js | 27 ---------------------- src/usecases/server-notices-consent.js | 31 -------------------------- src/usecases/signup.js | 1 - 4 files changed, 61 deletions(-) delete mode 100644 src/usecases/consent.js delete mode 100644 src/usecases/server-notices-consent.js diff --git a/src/scenario.js b/src/scenario.js index 2fd52de679..5b9d1f2906 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -17,7 +17,6 @@ limitations under the License. const {range} = require('./util'); const signup = require('./usecases/signup'); -const acceptServerNoticesInviteAndConsent = require('./usecases/server-notices-consent'); const roomDirectoryScenarios = require('./scenarios/directory'); const lazyLoadingScenarios = require('./scenarios/lazy-loading'); const e2eEncryptionScenarios = require('./scenarios/e2e-encryption'); @@ -26,7 +25,6 @@ module.exports = async function scenario(createSession, restCreator, runningOnTr async function createUser(username) { const session = await createSession(username); await signup(session, session.username, 'testtest', session.hsUrl); - await acceptServerNoticesInviteAndConsent(session); return session; } diff --git a/src/usecases/consent.js b/src/usecases/consent.js deleted file mode 100644 index b4a6289fca..0000000000 --- a/src/usecases/consent.js +++ /dev/null @@ -1,27 +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 assert = require('assert'); - -module.exports = async function acceptTerms(session) { - const reviewTermsButton = await session.waitAndQuery('.mx_QuestionDialog button.mx_Dialog_primary'); - const termsPagePromise = session.waitForNewPage(); - await reviewTermsButton.click(); - const termsPage = await termsPagePromise; - const acceptButton = await termsPage.$('input[type=submit]'); - await acceptButton.click(); - await session.delay(1000); //TODO yuck, timers -} diff --git a/src/usecases/server-notices-consent.js b/src/usecases/server-notices-consent.js deleted file mode 100644 index 25c3bb3bd5..0000000000 --- a/src/usecases/server-notices-consent.js +++ /dev/null @@ -1,31 +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 assert = require('assert'); -const acceptInvite = require("./accept-invite") -module.exports = async function acceptServerNoticesInviteAndConsent(session) { - await acceptInvite(session, "Server Notices"); - session.log.step(`accepts terms & conditions`); - const consentLink = await session.waitAndQuery(".mx_EventTile_body a"); - const termsPagePromise = session.waitForNewPage(); - await consentLink.click(); - const termsPage = await termsPagePromise; - const acceptButton = await termsPage.$('input[type=submit]'); - await acceptButton.click(); - await session.delay(1000); //TODO yuck, timers - await termsPage.close(); - session.log.done(); -} diff --git a/src/usecases/signup.js b/src/usecases/signup.js index dfd97a975f..bf2a512a91 100644 --- a/src/usecases/signup.js +++ b/src/usecases/signup.js @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -const acceptTerms = require('./consent'); const assert = require('assert'); module.exports = async function signup(session, username, password, homeserver) { From 1a0f09543bc3bf2141967c9098cfeee6f619124e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 7 Nov 2018 15:26:30 -0700 Subject: [PATCH 138/221] Tell synapse to require consent at registration To fix issues where the tests can't correctly test terms auth. --- synapse/config-templates/consent/homeserver.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/synapse/config-templates/consent/homeserver.yaml b/synapse/config-templates/consent/homeserver.yaml index 9fa16ebe5f..4f3837a878 100644 --- a/synapse/config-templates/consent/homeserver.yaml +++ b/synapse/config-templates/consent/homeserver.yaml @@ -674,6 +674,7 @@ user_consent: block_events_error: >- To continue using this homeserver you must review and agree to the terms and conditions at %(consent_uri)s + require_at_registration: true From 2e839d545adb848e5578e6ae53b16a5ebc595710 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 15 Nov 2018 20:23:15 -0700 Subject: [PATCH 139/221] Click the 'Accept' button as part of the signup process Part of https://github.com/vector-im/riot-web/issues/7700 --- src/usecases/signup.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/usecases/signup.js b/src/usecases/signup.js index bf2a512a91..825a2c27fa 100644 --- a/src/usecases/signup.js +++ b/src/usecases/signup.js @@ -60,10 +60,13 @@ module.exports = async function signup(session, username, password, homeserver) await continueButton.click(); //find the privacy policy checkbox and check it - //this should automatically move ahead with registration const policyCheckbox = await session.waitAndQuery('.mx_Login_box input[type="checkbox"]'); await policyCheckbox.click(); + //now click the 'Accept' button to agree to the privacy policy + const acceptButton = await session.waitAndQuery('.mx_InteractiveAuthEntryComponents_termsSubmit'); + await acceptButton.click(); + //wait for registration to finish so the hash gets set //onhashchange better? await session.delay(2000); From 19c4f4a8c6aa039aa6269299468b0c006e908602 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 21 Dec 2018 18:36:27 -0700 Subject: [PATCH 140/221] Install jinja2 --- synapse/install.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/synapse/install.sh b/synapse/install.sh index 37dfd7d7e2..1b27f0952d 100755 --- a/synapse/install.sh +++ b/synapse/install.sh @@ -25,6 +25,7 @@ source env/bin/activate pip install --upgrade pip pip install --upgrade setuptools pip install . +pip install jinja2 # We use the ConsentResource, which requires jinja2 python -m synapse.app.homeserver \ --server-name localhost \ --config-path homeserver.yaml \ From 7ac19b0beab275460d03863fd10cba2f43ff6849 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 29 Mar 2019 11:25:21 +0100 Subject: [PATCH 141/221] adjust synapse install script for python3 and config file changes --- .../config-templates/consent/homeserver.yaml | 1030 ++++++++++++----- synapse/install.sh | 5 +- 2 files changed, 717 insertions(+), 318 deletions(-) diff --git a/synapse/config-templates/consent/homeserver.yaml b/synapse/config-templates/consent/homeserver.yaml index 4f3837a878..222ddb956f 100644 --- a/synapse/config-templates/consent/homeserver.yaml +++ b/synapse/config-templates/consent/homeserver.yaml @@ -1,19 +1,313 @@ # vim:ft=yaml -# PEM encoded X509 certificate for TLS. -# You can replace the self-signed certificate that synapse -# autogenerates on launch with your own SSL certificate + key pair -# if you like. Any required intermediary certificates can be -# appended after the primary certificate in hierarchical order. -tls_certificate_path: "{{SYNAPSE_ROOT}}localhost.tls.crt" -# PEM encoded private key for TLS -tls_private_key_path: "{{SYNAPSE_ROOT}}localhost.tls.key" +## Server ## -# PEM dh parameters for ephemeral keys -tls_dh_params_path: "{{SYNAPSE_ROOT}}localhost.tls.dh" +# The domain name of the server, with optional explicit port. +# This is used by remote servers to connect to this server, +# e.g. matrix.org, localhost:8080, etc. +# This is also the last part of your UserID. +# +server_name: "localhost" -# Don't bind to the https port -no_tls: True +# When running as a daemon, the file to store the pid in +# +pid_file: {{SYNAPSE_ROOT}}homeserver.pid + +# CPU affinity mask. Setting this restricts the CPUs on which the +# process will be scheduled. It is represented as a bitmask, with the +# lowest order bit corresponding to the first logical CPU and the +# highest order bit corresponding to the last logical CPU. Not all CPUs +# may exist on a given system but a mask may specify more CPUs than are +# present. +# +# For example: +# 0x00000001 is processor #0, +# 0x00000003 is processors #0 and #1, +# 0xFFFFFFFF is all processors (#0 through #31). +# +# Pinning a Python process to a single CPU is desirable, because Python +# is inherently single-threaded due to the GIL, and can suffer a +# 30-40% slowdown due to cache blow-out and thread context switching +# if the scheduler happens to schedule the underlying threads across +# different cores. See +# https://www.mirantis.com/blog/improve-performance-python-programs-restricting-single-cpu/. +# +# This setting requires the affinity package to be installed! +# +#cpu_affinity: 0xFFFFFFFF + +# The path to the web client which will be served at /_matrix/client/ +# if 'webclient' is configured under the 'listeners' configuration. +# +#web_client_location: "/path/to/web/root" + +# The public-facing base URL that clients use to access this HS +# (not including _matrix/...). This is the same URL a user would +# enter into the 'custom HS URL' field on their client. If you +# use synapse with a reverse proxy, this should be the URL to reach +# synapse via the proxy. +# +public_baseurl: http://localhost:{{SYNAPSE_PORT}}/ + +# Set the soft limit on the number of file descriptors synapse can use +# Zero is used to indicate synapse should set the soft limit to the +# hard limit. +# +#soft_file_limit: 0 + +# Set to false to disable presence tracking on this homeserver. +# +#use_presence: false + +# The GC threshold parameters to pass to `gc.set_threshold`, if defined +# +#gc_thresholds: [700, 10, 10] + +# Set the limit on the returned events in the timeline in the get +# and sync operations. The default value is -1, means no upper limit. +# +#filter_timeline_limit: 5000 + +# Whether room invites to users on this server should be blocked +# (except those sent by local server admins). The default is False. +# +#block_non_admin_invites: True + +# Room searching +# +# If disabled, new messages will not be indexed for searching and users +# will receive errors when searching for messages. Defaults to enabled. +# +#enable_search: false + +# Restrict federation to the following whitelist of domains. +# N.B. we recommend also firewalling your federation listener to limit +# inbound federation traffic as early as possible, rather than relying +# purely on this application-layer restriction. If not specified, the +# default is to whitelist everything. +# +#federation_domain_whitelist: +# - lon.example.com +# - nyc.example.com +# - syd.example.com + +# List of ports that Synapse should listen on, their purpose and their +# configuration. +# +# Options for each listener include: +# +# port: the TCP port to bind to +# +# bind_addresses: a list of local addresses to listen on. The default is +# 'all local interfaces'. +# +# type: the type of listener. Normally 'http', but other valid options are: +# 'manhole' (see docs/manhole.md), +# 'metrics' (see docs/metrics-howto.rst), +# 'replication' (see docs/workers.rst). +# +# tls: set to true to enable TLS for this listener. Will use the TLS +# key/cert specified in tls_private_key_path / tls_certificate_path. +# +# x_forwarded: Only valid for an 'http' listener. Set to true to use the +# X-Forwarded-For header as the client IP. Useful when Synapse is +# behind a reverse-proxy. +# +# resources: Only valid for an 'http' listener. A list of resources to host +# on this port. Options for each resource are: +# +# names: a list of names of HTTP resources. See below for a list of +# valid resource names. +# +# compress: set to true to enable HTTP comression for this resource. +# +# additional_resources: Only valid for an 'http' listener. A map of +# additional endpoints which should be loaded via dynamic modules. +# +# Valid resource names are: +# +# client: the client-server API (/_matrix/client). Also implies 'media' and +# 'static'. +# +# consent: user consent forms (/_matrix/consent). See +# docs/consent_tracking.md. +# +# federation: the server-server API (/_matrix/federation). Also implies +# 'media', 'keys', 'openid' +# +# keys: the key discovery API (/_matrix/keys). +# +# media: the media API (/_matrix/media). +# +# metrics: the metrics interface. See docs/metrics-howto.rst. +# +# openid: OpenID authentication. +# +# replication: the HTTP replication API (/_synapse/replication). See +# docs/workers.rst. +# +# static: static resources under synapse/static (/_matrix/static). (Mostly +# useful for 'fallback authentication'.) +# +# webclient: A web client. Requires web_client_location to be set. +# +listeners: + # TLS-enabled listener: for when matrix traffic is sent directly to synapse. + # + # Disabled by default. To enable it, uncomment the following. (Note that you + # will also need to give Synapse a TLS key and certificate: see the TLS section + # below.) + # + #- port: 8448 + # type: http + # tls: true + # resources: + # - names: [client, federation] + + # Unsecure HTTP listener: for when matrix traffic passes through a reverse proxy + # that unwraps TLS. + # + # If you plan to use a reverse proxy, please see + # https://github.com/matrix-org/synapse/blob/master/docs/reverse_proxy.rst. + # + - port: {{SYNAPSE_PORT}} + tls: false + bind_addresses: ['::1', '127.0.0.1'] + type: http + x_forwarded: true + + resources: + - names: [client, federation] + compress: false + + # example additonal_resources: + # + #additional_resources: + # "/_matrix/my/custom/endpoint": + # module: my_module.CustomRequestHandler + # config: {} + + # Turn on the twisted ssh manhole service on localhost on the given + # port. + # + #- port: 9000 + # bind_addresses: ['::1', '127.0.0.1'] + # type: manhole + + +## Homeserver blocking ## + +# How to reach the server admin, used in ResourceLimitError +# +#admin_contact: 'mailto:admin@server.com' + +# Global blocking +# +#hs_disabled: False +#hs_disabled_message: 'Human readable reason for why the HS is blocked' +#hs_disabled_limit_type: 'error code(str), to help clients decode reason' + +# Monthly Active User Blocking +# +#limit_usage_by_mau: False +#max_mau_value: 50 +#mau_trial_days: 2 + +# If enabled, the metrics for the number of monthly active users will +# be populated, however no one will be limited. If limit_usage_by_mau +# is true, this is implied to be true. +# +#mau_stats_only: False + +# Sometimes the server admin will want to ensure certain accounts are +# never blocked by mau checking. These accounts are specified here. +# +#mau_limit_reserved_threepids: +# - medium: 'email' +# address: 'reserved_user@example.com' + + +## TLS ## + +# PEM-encoded X509 certificate for TLS. +# This certificate, as of Synapse 1.0, will need to be a valid and verifiable +# certificate, signed by a recognised Certificate Authority. +# +# See 'ACME support' below to enable auto-provisioning this certificate via +# Let's Encrypt. +# +# If supplying your own, be sure to use a `.pem` file that includes the +# full certificate chain including any intermediate certificates (for +# instance, if using certbot, use `fullchain.pem` as your certificate, +# not `cert.pem`). +# +#tls_certificate_path: "{{SYNAPSE_ROOT}}localhost.tls.crt" + +# PEM-encoded private key for TLS +# +#tls_private_key_path: "{{SYNAPSE_ROOT}}localhost.tls.key" + +# ACME support: This will configure Synapse to request a valid TLS certificate +# for your configured `server_name` via Let's Encrypt. +# +# Note that provisioning a certificate in this way requires port 80 to be +# routed to Synapse so that it can complete the http-01 ACME challenge. +# By default, if you enable ACME support, Synapse will attempt to listen on +# port 80 for incoming http-01 challenges - however, this will likely fail +# with 'Permission denied' or a similar error. +# +# There are a couple of potential solutions to this: +# +# * If you already have an Apache, Nginx, or similar listening on port 80, +# you can configure Synapse to use an alternate port, and have your web +# server forward the requests. For example, assuming you set 'port: 8009' +# below, on Apache, you would write: +# +# ProxyPass /.well-known/acme-challenge http://localhost:8009/.well-known/acme-challenge +# +# * Alternatively, you can use something like `authbind` to give Synapse +# permission to listen on port 80. +# +acme: + # ACME support is disabled by default. Uncomment the following line + # (and tls_certificate_path and tls_private_key_path above) to enable it. + # + #enabled: true + + # Endpoint to use to request certificates. If you only want to test, + # use Let's Encrypt's staging url: + # https://acme-staging.api.letsencrypt.org/directory + # + #url: https://acme-v01.api.letsencrypt.org/directory + + # Port number to listen on for the HTTP-01 challenge. Change this if + # you are forwarding connections through Apache/Nginx/etc. + # + #port: 80 + + # Local addresses to listen on for incoming connections. + # Again, you may want to change this if you are forwarding connections + # through Apache/Nginx/etc. + # + #bind_addresses: ['::', '0.0.0.0'] + + # How many days remaining on a certificate before it is renewed. + # + #reprovision_threshold: 30 + + # The domain that the certificate should be for. Normally this + # should be the same as your Matrix domain (i.e., 'server_name'), but, + # by putting a file at 'https:///.well-known/matrix/server', + # you can delegate incoming traffic to another server. If you do that, + # you should give the target of the delegation here. + # + # For example: if your 'server_name' is 'example.com', but + # 'https://example.com/.well-known/matrix/server' delegates to + # 'matrix.example.com', you should put 'matrix.example.com' here. + # + # If not set, defaults to your 'server_name'. + # + #domain: matrix.example.com # List of allowed TLS fingerprints for this server to publish along # with the signing keys for this server. Other matrix servers that @@ -40,153 +334,12 @@ no_tls: True # openssl x509 -outform DER | openssl sha256 -binary | base64 | tr -d '=' # or by checking matrix.org/federationtester/api/report?server_name=$host # -tls_fingerprints: [] -# tls_fingerprints: [{"sha256": ""}] +#tls_fingerprints: [{"sha256": ""}] -## Server ## -# The domain name of the server, with optional explicit port. -# This is used by remote servers to connect to this server, -# e.g. matrix.org, localhost:8080, etc. -# This is also the last part of your UserID. -server_name: "localhost" +## Database ## -# When running as a daemon, the file to store the pid in -pid_file: {{SYNAPSE_ROOT}}homeserver.pid - -# CPU affinity mask. Setting this restricts the CPUs on which the -# process will be scheduled. It is represented as a bitmask, with the -# lowest order bit corresponding to the first logical CPU and the -# highest order bit corresponding to the last logical CPU. Not all CPUs -# may exist on a given system but a mask may specify more CPUs than are -# present. -# -# For example: -# 0x00000001 is processor #0, -# 0x00000003 is processors #0 and #1, -# 0xFFFFFFFF is all processors (#0 through #31). -# -# Pinning a Python process to a single CPU is desirable, because Python -# is inherently single-threaded due to the GIL, and can suffer a -# 30-40% slowdown due to cache blow-out and thread context switching -# if the scheduler happens to schedule the underlying threads across -# different cores. See -# https://www.mirantis.com/blog/improve-performance-python-programs-restricting-single-cpu/. -# -# cpu_affinity: 0xFFFFFFFF - -# Whether to serve a web client from the HTTP/HTTPS root resource. -web_client: True - -# The root directory to server for the above web client. -# If left undefined, synapse will serve the matrix-angular-sdk web client. -# Make sure matrix-angular-sdk is installed with pip if web_client is True -# and web_client_location is undefined -# web_client_location: "/path/to/web/root" - -# The public-facing base URL for the client API (not including _matrix/...) -public_baseurl: http://localhost:{{SYNAPSE_PORT}}/ - -# Set the soft limit on the number of file descriptors synapse can use -# Zero is used to indicate synapse should set the soft limit to the -# hard limit. -soft_file_limit: 0 - -# The GC threshold parameters to pass to `gc.set_threshold`, if defined -# gc_thresholds: [700, 10, 10] - -# Set the limit on the returned events in the timeline in the get -# and sync operations. The default value is -1, means no upper limit. -# filter_timeline_limit: 5000 - -# Whether room invites to users on this server should be blocked -# (except those sent by local server admins). The default is False. -# block_non_admin_invites: True - -# Restrict federation to the following whitelist of domains. -# N.B. we recommend also firewalling your federation listener to limit -# inbound federation traffic as early as possible, rather than relying -# purely on this application-layer restriction. If not specified, the -# default is to whitelist everything. -# -# federation_domain_whitelist: -# - lon.example.com -# - nyc.example.com -# - syd.example.com - -# List of ports that Synapse should listen on, their purpose and their -# configuration. -listeners: - # Main HTTPS listener - # For when matrix traffic is sent directly to synapse. - - - # The port to listen for HTTPS requests on. - port: 8448 - - # Local addresses to listen on. - # On Linux and Mac OS, `::` will listen on all IPv4 and IPv6 - # addresses by default. For most other OSes, this will only listen - # on IPv6. - bind_addresses: - - '::' - - '0.0.0.0' - - # This is a 'http' listener, allows us to specify 'resources'. - type: http - - tls: true - - # Use the X-Forwarded-For (XFF) header as the client IP and not the - # actual client IP. - x_forwarded: false - - # List of HTTP resources to serve on this listener. - resources: - - - # List of resources to host on this listener. - names: - - client # The client-server APIs, both v1 and v2 - - webclient # The bundled webclient. - - # Should synapse compress HTTP responses to clients that support it? - # This should be disabled if running synapse behind a load balancer - # that can do automatic compression. - compress: true - - - names: [federation] # Federation APIs - compress: false - - # optional list of additional endpoints which can be loaded via - # dynamic modules - # additional_resources: - # "/_matrix/my/custom/endpoint": - # module: my_module.CustomRequestHandler - # config: {} - - # Unsecure HTTP listener, - # For when matrix traffic passes through loadbalancer that unwraps TLS. - - port: {{SYNAPSE_PORT}} - tls: false - bind_addresses: ['::', '0.0.0.0'] - type: http - - x_forwarded: false - - resources: - - names: [client, webclient, consent] - compress: true - - names: [federation] - compress: false - - # Turn on the twisted ssh manhole service on localhost on the given - # port. - # - port: 9000 - # bind_addresses: ['::1', '127.0.0.1'] - # type: manhole - - -# Database configuration database: # The database engine name name: "sqlite3" @@ -196,98 +349,158 @@ database: database: ":memory:" # Number of events to cache in memory. -event_cache_size: "10K" +# +#event_cache_size: 10K +## Logging ## # A yaml python logging config file +# log_config: "{{SYNAPSE_ROOT}}localhost.log.config" ## Ratelimiting ## # Number of messages a client can send per second -rc_messages_per_second: 100 +# +#rc_messages_per_second: 0.2 # Number of message a client can send before being throttled -rc_message_burst_count: 20.0 +# +#rc_message_burst_count: 10.0 + +# Ratelimiting settings for registration and login. +# +# Each ratelimiting configuration is made of two parameters: +# - per_second: number of requests a client can send per second. +# - burst_count: number of requests a client can send before being throttled. +# +# Synapse currently uses the following configurations: +# - one for registration that ratelimits registration requests based on the +# client's IP address. +# - one for login that ratelimits login requests based on the client's IP +# address. +# - one for login that ratelimits login requests based on the account the +# client is attempting to log into. +# - one for login that ratelimits login requests based on the account the +# client is attempting to log into, based on the amount of failed login +# attempts for this account. +# +# The defaults are as shown below. +# +#rc_registration: +# per_second: 0.17 +# burst_count: 3 +# +#rc_login: +# address: +# per_second: 0.17 +# burst_count: 3 +# account: +# per_second: 0.17 +# burst_count: 3 +# failed_attempts: +# per_second: 0.17 +# burst_count: 3 # The federation window size in milliseconds -federation_rc_window_size: 1000 +# +#federation_rc_window_size: 1000 # The number of federation requests from a single server in a window # before the server will delay processing the request. -federation_rc_sleep_limit: 10 +# +#federation_rc_sleep_limit: 10 # The duration in milliseconds to delay processing events from # remote servers by if they go over the sleep limit. -federation_rc_sleep_delay: 500 +# +#federation_rc_sleep_delay: 500 # The maximum number of concurrent federation requests allowed # from a single server -federation_rc_reject_limit: 50 +# +#federation_rc_reject_limit: 50 # The number of federation requests to concurrently process from a # single server -federation_rc_concurrent: 3 +# +#federation_rc_concurrent: 3 + +# Target outgoing federation transaction frequency for sending read-receipts, +# per-room. +# +# If we end up trying to send out more read-receipts, they will get buffered up +# into fewer transactions. +# +#federation_rr_transactions_per_room_per_second: 50 # Directory where uploaded images and attachments are stored. +# media_store_path: "{{SYNAPSE_ROOT}}media_store" # Media storage providers allow media to be stored in different # locations. -# media_storage_providers: -# - module: file_system -# # Whether to write new local files. -# store_local: false -# # Whether to write new remote media -# store_remote: false -# # Whether to block upload requests waiting for write to this -# # provider to complete -# store_synchronous: false -# config: -# directory: /mnt/some/other/directory +# +#media_storage_providers: +# - module: file_system +# # Whether to write new local files. +# store_local: false +# # Whether to write new remote media +# store_remote: false +# # Whether to block upload requests waiting for write to this +# # provider to complete +# store_synchronous: false +# config: +# directory: /mnt/some/other/directory # Directory where in-progress uploads are stored. +# uploads_path: "{{SYNAPSE_ROOT}}uploads" # The largest allowed upload size in bytes -max_upload_size: "10M" +# +#max_upload_size: 10M # Maximum number of pixels that will be thumbnailed -max_image_pixels: "32M" +# +#max_image_pixels: 32M # Whether to generate new thumbnails on the fly to precisely match # the resolution requested by the client. If true then whenever # a new resolution is requested by the client the server will # generate a new thumbnail. If false the server will pick a thumbnail # from a precalculated list. -dynamic_thumbnails: false +# +#dynamic_thumbnails: false -# List of thumbnail to precalculate when an image is uploaded. -thumbnail_sizes: -- width: 32 - height: 32 - method: crop -- width: 96 - height: 96 - method: crop -- width: 320 - height: 240 - method: scale -- width: 640 - height: 480 - method: scale -- width: 800 - height: 600 - method: scale +# List of thumbnails to precalculate when an image is uploaded. +# +#thumbnail_sizes: +# - width: 32 +# height: 32 +# method: crop +# - width: 96 +# height: 96 +# method: crop +# - width: 320 +# height: 240 +# method: scale +# - width: 640 +# height: 480 +# method: scale +# - width: 800 +# height: 600 +# method: scale # Is the preview URL API enabled? If enabled, you *must* specify # an explicit url_preview_ip_range_blacklist of IPs that the spider is # denied from accessing. -url_preview_enabled: False +# +#url_preview_enabled: false # List of IP address CIDR ranges that the URL preview spider is denied # from accessing. There are no defaults: you must explicitly @@ -297,16 +510,16 @@ url_preview_enabled: False # synapse to issue arbitrary GET requests to your internal services, # causing serious security issues. # -# url_preview_ip_range_blacklist: -# - '127.0.0.0/8' -# - '10.0.0.0/8' -# - '172.16.0.0/12' -# - '192.168.0.0/16' -# - '100.64.0.0/10' -# - '169.254.0.0/16' -# - '::1/128' -# - 'fe80::/64' -# - 'fc00::/7' +#url_preview_ip_range_blacklist: +# - '127.0.0.0/8' +# - '10.0.0.0/8' +# - '172.16.0.0/12' +# - '192.168.0.0/16' +# - '100.64.0.0/10' +# - '169.254.0.0/16' +# - '::1/128' +# - 'fe80::/64' +# - 'fc00::/7' # # List of IP address CIDR ranges that the URL preview spider is allowed # to access even if they are specified in url_preview_ip_range_blacklist. @@ -314,8 +527,8 @@ url_preview_enabled: False # target IP ranges - e.g. for enabling URL previews for a specific private # website only visible in your network. # -# url_preview_ip_range_whitelist: -# - '192.168.1.1' +#url_preview_ip_range_whitelist: +# - '192.168.1.1' # Optional list of URL matches that the URL preview spider is # denied from accessing. You should use url_preview_ip_range_blacklist @@ -333,99 +546,118 @@ url_preview_enabled: False # specified component matches for a given list item succeed, the URL is # blacklisted. # -# url_preview_url_blacklist: -# # blacklist any URL with a username in its URI -# - username: '*' +#url_preview_url_blacklist: +# # blacklist any URL with a username in its URI +# - username: '*' # -# # blacklist all *.google.com URLs -# - netloc: 'google.com' -# - netloc: '*.google.com' +# # blacklist all *.google.com URLs +# - netloc: 'google.com' +# - netloc: '*.google.com' # -# # blacklist all plain HTTP URLs -# - scheme: 'http' +# # blacklist all plain HTTP URLs +# - scheme: 'http' # -# # blacklist http(s)://www.acme.com/foo -# - netloc: 'www.acme.com' -# path: '/foo' +# # blacklist http(s)://www.acme.com/foo +# - netloc: 'www.acme.com' +# path: '/foo' # -# # blacklist any URL with a literal IPv4 address -# - netloc: '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' +# # blacklist any URL with a literal IPv4 address +# - netloc: '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' # The largest allowed URL preview spidering size in bytes -max_spider_size: "10M" - - +# +#max_spider_size: 10M ## Captcha ## # See docs/CAPTCHA_SETUP for full details of configuring this. # This Home Server's ReCAPTCHA public key. -recaptcha_public_key: "YOUR_PUBLIC_KEY" +# +#recaptcha_public_key: "YOUR_PUBLIC_KEY" # This Home Server's ReCAPTCHA private key. -recaptcha_private_key: "YOUR_PRIVATE_KEY" +# +#recaptcha_private_key: "YOUR_PRIVATE_KEY" # Enables ReCaptcha checks when registering, preventing signup # unless a captcha is answered. Requires a valid ReCaptcha # public/private key. -enable_registration_captcha: False +# +#enable_registration_captcha: false # A secret key used to bypass the captcha test entirely. +# #captcha_bypass_secret: "YOUR_SECRET_HERE" # The API endpoint to use for verifying m.login.recaptcha responses. -recaptcha_siteverify_api: "https://www.google.com/recaptcha/api/siteverify" +# +#recaptcha_siteverify_api: "https://www.recaptcha.net/recaptcha/api/siteverify" -## Turn ## +## TURN ## # The public URIs of the TURN server to give to clients -turn_uris: [] +# +#turn_uris: [] # The shared secret used to compute passwords for the TURN server -turn_shared_secret: "YOUR_SHARED_SECRET" +# +#turn_shared_secret: "YOUR_SHARED_SECRET" # The Username and password if the TURN server needs them and # does not use a token +# #turn_username: "TURNSERVER_USERNAME" #turn_password: "TURNSERVER_PASSWORD" # How long generated TURN credentials last -turn_user_lifetime: "1h" +# +#turn_user_lifetime: 1h # Whether guests should be allowed to use the TURN server. # This defaults to True, otherwise VoIP will be unreliable for guests. # However, it does introduce a slight security risk as it allows users to # connect to arbitrary endpoints without having first signed up for a # valid account (e.g. by passing a CAPTCHA). -turn_allow_guests: True +# +#turn_allow_guests: True ## Registration ## +# +# Registration can be rate-limited using the parameters in the "Ratelimiting" +# section of this file. # Enable registration for new users. -enable_registration: True +# +enable_registration: true # The user must provide all of the below types of 3PID when registering. # -# registrations_require_3pid: -# - email -# - msisdn +#registrations_require_3pid: +# - email +# - msisdn + +# Explicitly disable asking for MSISDNs from the registration +# flow (overrides registrations_require_3pid if MSISDNs are set as required) +# +#disable_msisdn_registration: true # Mandate that users are only allowed to associate certain formats of # 3PIDs with accounts on this server. # -# allowed_local_3pids: -# - medium: email -# pattern: ".*@matrix\.org" -# - medium: email -# pattern: ".*@vector\.im" -# - medium: msisdn -# pattern: "\+44" +#allowed_local_3pids: +# - medium: email +# pattern: '.*@matrix\.org' +# - medium: email +# pattern: '.*@vector\.im' +# - medium: msisdn +# pattern: '\+44' -# If set, allows registration by anyone who also has the shared -# secret, even if registration is otherwise disabled. +# If set, allows registration of standard or admin accounts by anyone who +# has the shared secret, even if registration is otherwise disabled. +# registration_shared_secret: "{{REGISTRATION_SHARED_SECRET}}" # Set the number of bcrypt rounds used to generate password hash. @@ -433,64 +665,118 @@ registration_shared_secret: "{{REGISTRATION_SHARED_SECRET}}" # The default number is 12 (which equates to 2^12 rounds). # N.B. that increasing this will exponentially increase the time required # to register or login - e.g. 24 => 2^24 rounds which will take >20 mins. -bcrypt_rounds: 12 +# +#bcrypt_rounds: 12 # Allows users to register as guests without a password/email/etc, and # participate in rooms hosted on this server which have been made # accessible to anonymous users. -allow_guest_access: False +# +#allow_guest_access: false + +# The identity server which we suggest that clients should use when users log +# in on this server. +# +# (By default, no suggestion is made, so it is left up to the client. +# This setting is ignored unless public_baseurl is also set.) +# +#default_identity_server: https://matrix.org # The list of identity servers trusted to verify third party # identifiers by this server. -trusted_third_party_id_servers: - - matrix.org - - vector.im - - riot.im +# +# Also defines the ID server which will be called when an account is +# deactivated (one will be picked arbitrarily). +# +#trusted_third_party_id_servers: +# - matrix.org +# - vector.im # Users who register on this homeserver will automatically be joined -# to these roomsS +# to these rooms +# #auto_join_rooms: -# - "#example:example.com" +# - "#example:example.com" + +# Where auto_join_rooms are specified, setting this flag ensures that the +# the rooms exist by creating them when the first user on the +# homeserver registers. +# Setting to false means that if the rooms are not manually created, +# users cannot be auto-joined since they do not exist. +# +#autocreate_auto_join_rooms: true ## Metrics ### # Enable collection and rendering of performance metrics -enable_metrics: False -report_stats: False +# +#enable_metrics: False + +# Enable sentry integration +# NOTE: While attempts are made to ensure that the logs don't contain +# any sensitive information, this cannot be guaranteed. By enabling +# this option the sentry server may therefore receive sensitive +# information, and it in turn may then diseminate sensitive information +# through insecure notification channels if so configured. +# +#sentry: +# dsn: "..." + +# Whether or not to report anonymized homeserver usage statistics. +report_stats: false ## API Configuration ## # A list of event types that will be included in the room_invite_state -room_invite_state_types: - - "m.room.join_rules" - - "m.room.canonical_alias" - - "m.room.avatar" - - "m.room.name" +# +#room_invite_state_types: +# - "m.room.join_rules" +# - "m.room.canonical_alias" +# - "m.room.avatar" +# - "m.room.encryption" +# - "m.room.name" -# A list of application service config file to use -app_service_config_files: [] +# A list of application service config files to use +# +#app_service_config_files: +# - app_service_1.yaml +# - app_service_2.yaml + +# Uncomment to enable tracking of application service IP addresses. Implicitly +# enables MAU tracking for application service users. +# +#track_appservice_user_ips: True +# a secret which is used to sign access tokens. If none is specified, +# the registration_shared_secret is used, if one is given; otherwise, +# a secret key is derived from the signing key. +# macaroon_secret_key: "{{MACAROON_SECRET_KEY}}" # Used to enable access token expiration. -expire_access_token: False +# +#expire_access_token: False # a secret which is used to calculate HMACs for form values, to stop -# falsification of values +# falsification of values. Must be specified for the User Consent +# forms to work. +# form_secret: "{{FORM_SECRET}}" ## Signing Keys ## # Path to the signing key to sign messages with +# signing_key_path: "{{SYNAPSE_ROOT}}localhost.signing.key" # The keys that the server used to sign messages with but won't use # to sign new messages. E.g. it has lost its private key -old_signing_keys: {} +# +#old_signing_keys: # "ed25519:auto": # # Base64 encoded public key # key: "The public part of your old signing key." @@ -501,31 +787,65 @@ old_signing_keys: {} # Used to set the valid_until_ts in /key/v2 APIs. # Determines how quickly servers will query to check which keys # are still valid. -key_refresh_interval: "1d" # 1 Day.block_non_admin_invites +# +#key_refresh_interval: 1d # The trusted servers to download signing keys from. -perspectives: - servers: - "matrix.org": - verify_keys: - "ed25519:auto": - key: "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw" +# +#perspectives: +# servers: +# "matrix.org": +# verify_keys: +# "ed25519:auto": +# key: "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw" - -# Enable SAML2 for registration and login. Uses pysaml2 -# config_path: Path to the sp_conf.py configuration file -# idp_redirect_url: Identity provider URL which will redirect -# the user back to /login/saml2 with proper info. +# Enable SAML2 for registration and login. Uses pysaml2. +# +# `sp_config` is the configuration for the pysaml2 Service Provider. # See pysaml2 docs for format of config. +# +# Default values will be used for the 'entityid' and 'service' settings, +# so it is not normally necessary to specify them unless you need to +# override them. +# #saml2_config: -# enabled: true -# config_path: "{{SYNAPSE_ROOT}}sp_conf.py" -# idp_redirect_url: "http://localhost/idp" +# sp_config: +# # point this to the IdP's metadata. You can use either a local file or +# # (preferably) a URL. +# metadata: +# #local: ["saml2/idp.xml"] +# remote: +# - url: https://our_idp/metadata.xml +# +# # The rest of sp_config is just used to generate our metadata xml, and you +# # may well not need it, depending on your setup. Alternatively you +# # may need a whole lot more detail - see the pysaml2 docs! +# +# description: ["My awesome SP", "en"] +# name: ["Test SP", "en"] +# +# organization: +# name: Example com +# display_name: +# - ["Example co", "en"] +# url: "http://example.com" +# +# contact_person: +# - given_name: Bob +# sur_name: "the Sysadmin" +# email_address": ["admin@example.com"] +# contact_type": technical +# +# # Instead of putting the config inline as above, you can specify a +# # separate pysaml2 configuration file: +# # +# config_path: "{{SYNAPSE_ROOT}}sp_conf.py" # Enable CAS for registration and login. +# #cas_config: # enabled: true # server_url: "https://cas-server.com" @@ -536,19 +856,21 @@ perspectives: # The JWT needs to contain a globally unique "sub" (subject) claim. # -# jwt_config: -# enabled: true -# secret: "a secret" -# algorithm: "HS256" +#jwt_config: +# enabled: true +# secret: "a secret" +# algorithm: "HS256" - -# Enable password for login. password_config: - enabled: true + # Uncomment to disable password login + # + #enabled: false + # Uncomment and change to a secret random string for extra security. # DO NOT CHANGE THIS AFTER INITIAL SETUP! - #pepper: "" + # + #pepper: "EVEN_MORE_SECRET" @@ -569,27 +891,29 @@ password_config: # require_transport_security: False # notif_from: "Your Friendly %(app)s Home Server " # app_name: Matrix -# template_dir: res/templates +# # if template_dir is unset, uses the example templates that are part of +# # the Synapse distribution. +# #template_dir: res/templates # notif_template_html: notif_mail.html # notif_template_text: notif_mail.txt # notif_for_new_users: True # riot_base_url: "http://localhost/riot" -# password_providers: -# - module: "ldap_auth_provider.LdapAuthProvider" -# config: -# enabled: true -# uri: "ldap://ldap.example.com:389" -# start_tls: true -# base: "ou=users,dc=example,dc=com" -# attributes: -# uid: "cn" -# mail: "email" -# name: "givenName" -# #bind_dn: -# #bind_password: -# #filter: "(objectClass=posixAccount)" +#password_providers: +# - module: "ldap_auth_provider.LdapAuthProvider" +# config: +# enabled: true +# uri: "ldap://ldap.example.com:389" +# start_tls: true +# base: "ou=users,dc=example,dc=com" +# attributes: +# uid: "cn" +# mail: "email" +# name: "givenName" +# #bind_dn: +# #bind_password: +# #filter: "(objectClass=posixAccount)" @@ -600,32 +924,38 @@ password_config: # notification request includes the content of the event (other details # like the sender are still included). For `event_id_only` push, it # has no effect. - +# # For modern android devices the notification content will still appear # because it is loaded by the app. iPhone, however will send a # notification saying only that a message arrived and who it came from. # #push: -# include_content: true +# include_content: true -# spam_checker: -# module: "my_custom_project.SuperSpamChecker" -# config: -# example_option: 'things' +#spam_checker: +# module: "my_custom_project.SuperSpamChecker" +# config: +# example_option: 'things' -# Whether to allow non server admins to create groups on this server -enable_group_creation: false +# Uncomment to allow non-server-admin users to create groups on this server +# +#enable_group_creation: true # If enabled, non server admins can only create groups with local parts # starting with this prefix -# group_creation_prefix: "unofficial/" +# +#group_creation_prefix: "unofficial/" # User Directory configuration # +# 'enabled' defines whether users can search the user directory. If +# false then empty responses are returned to all queries. Defaults to +# true. +# # 'search_all_users' defines whether to search all users visible to your HS # when searching the user directory, rather than limiting to users visible # in public rooms. Defaults to false. If you set it True, you'll have to run @@ -633,7 +963,8 @@ enable_group_creation: false # on your database to tell it to rebuild the user_directory search indexes. # #user_directory: -# search_all_users: false +# enabled: true +# search_all_users: false # User Consent configuration @@ -662,6 +993,14 @@ enable_group_creation: false # until the user consents to the privacy policy. The value of the setting is # used as the text of the error. # +# 'require_at_registration', if enabled, will add a step to the registration +# process, similar to how captcha works. Users will be required to accept the +# policy before their account is created. +# +# 'policy_name' is the display name of the policy users will see when registering +# for an account. Has no effect unless `require_at_registration` is enabled. +# Defaults to "Privacy Policy". +# user_consent: template_dir: res/templates/privacy version: 1.0 @@ -676,8 +1015,6 @@ user_consent: terms and conditions at %(consent_uri)s require_at_registration: true - - # Server Notices room configuration # # Uncomment this section to enable a room which can be used to send notices @@ -696,3 +1033,66 @@ server_notices: system_mxid_display_name: "Server Notices" system_mxid_avatar_url: "mxc://localhost:{{SYNAPSE_PORT}}/oumMVlgDnLYFaPVkExemNVVZ" room_name: "Server Notices" + +# Uncomment to disable searching the public room list. When disabled +# blocks searching local and remote room lists for local and remote +# users by always returning an empty list for all queries. +# +#enable_room_list_search: false + +# The `alias_creation` option controls who's allowed to create aliases +# on this server. +# +# The format of this option is a list of rules that contain globs that +# match against user_id, room_id and the new alias (fully qualified with +# server name). The action in the first rule that matches is taken, +# which can currently either be "allow" or "deny". +# +# Missing user_id/room_id/alias fields default to "*". +# +# If no rules match the request is denied. An empty list means no one +# can create aliases. +# +# Options for the rules include: +# +# user_id: Matches against the creator of the alias +# alias: Matches against the alias being created +# room_id: Matches against the room ID the alias is being pointed at +# action: Whether to "allow" or "deny" the request if the rule matches +# +# The default is: +# +#alias_creation_rules: +# - user_id: "*" +# alias: "*" +# room_id: "*" +# action: allow + +# The `room_list_publication_rules` option controls who can publish and +# which rooms can be published in the public room list. +# +# The format of this option is the same as that for +# `alias_creation_rules`. +# +# If the room has one or more aliases associated with it, only one of +# the aliases needs to match the alias rule. If there are no aliases +# then only rules with `alias: *` match. +# +# If no rules match the request is denied. An empty list means no one +# can publish rooms. +# +# Options for the rules include: +# +# user_id: Matches agaisnt the creator of the alias +# room_id: Matches against the room ID being published +# alias: Matches against any current local or canonical aliases +# associated with the room +# action: Whether to "allow" or "deny" the request if the rule matches +# +# The default is: +# +#room_list_publication_rules: +# - user_id: "*" +# alias: "*" +# room_id: "*" +# action: allow diff --git a/synapse/install.sh b/synapse/install.sh index 1b27f0952d..4761e359fa 100755 --- a/synapse/install.sh +++ b/synapse/install.sh @@ -20,12 +20,11 @@ curl https://codeload.github.com/matrix-org/synapse/zip/$SYNAPSE_BRANCH --output unzip -q synapse.zip mv synapse-$SYNAPSE_BRANCH $SERVER_DIR cd $SERVER_DIR -virtualenv -p python2.7 env +virtualenv -p python3 env source env/bin/activate pip install --upgrade pip pip install --upgrade setuptools -pip install . -pip install jinja2 # We use the ConsentResource, which requires jinja2 +pip install matrix-synapse[all] python -m synapse.app.homeserver \ --server-name localhost \ --config-path homeserver.yaml \ From 3056d936f546c81614f86837096ab0ba373bf00f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 29 Mar 2019 11:55:33 +0100 Subject: [PATCH 142/221] use yarn and update dependencies, commit lock file --- .gitignore | 3 +- package.json | 6 +- yarn.lock | 759 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 763 insertions(+), 5 deletions(-) create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore index 8a48fb815d..24cd046858 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ node_modules -package-lock.json -*.png \ No newline at end of file +*.png diff --git a/package.json b/package.json index f3c47ac491..8372039258 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,10 @@ "license": "ISC", "dependencies": { "cheerio": "^1.0.0-rc.2", - "commander": "^2.17.1", - "puppeteer": "^1.6.0", + "commander": "^2.19.0", + "puppeteer": "^1.14.0", "request": "^2.88.0", - "request-promise-native": "^1.0.5", + "request-promise-native": "^1.0.7", "uuid": "^3.3.2" } } diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000000..bdf5608a7e --- /dev/null +++ b/yarn.lock @@ -0,0 +1,759 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/node@*": + version "11.12.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-11.12.1.tgz#d90123f6c61fdf2f7cddd286ddae891586dd3488" + integrity sha512-sKDlqv6COJrR7ar0+GqqhrXQDzQlMcqMnF2iEU6m9hLo8kxozoAGUazwPyELHlRVmjsbvlnGXjnzyptSXVmceA== + +agent-base@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" + integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== + dependencies: + es6-promisify "^5.0.0" + +ajv@^6.5.5: + version "6.10.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1" + integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg== + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +async-limiter@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" + integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" + integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +cheerio@^1.0.0-rc.2: + version "1.0.0-rc.2" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db" + integrity sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs= + dependencies: + css-select "~1.2.0" + dom-serializer "~0.1.0" + entities "~1.1.1" + htmlparser2 "^3.9.1" + lodash "^4.15.0" + parse5 "^3.0.1" + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" + integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== + dependencies: + delayed-stream "~1.0.0" + +commander@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" + integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +concat-stream@1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +css-select@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" + integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= + dependencies: + boolbase "~1.0.0" + css-what "2.1" + domutils "1.5.1" + nth-check "~1.0.1" + +css-what@2.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" + integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +dom-serializer@0, dom-serializer@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" + integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA== + dependencies: + domelementtype "^1.3.0" + entities "^1.1.1" + +domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + +domhandler@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" + integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== + dependencies: + domelementtype "1" + +domutils@1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" + integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= + dependencies: + dom-serializer "0" + domelementtype "1" + +domutils@^1.5.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" + integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== + dependencies: + dom-serializer "0" + domelementtype "1" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +entities@^1.1.1, entities@~1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" + integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== + +es6-promise@^4.0.3: + version "4.2.6" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.6.tgz#b685edd8258886365ea62b57d30de28fadcd974f" + integrity sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q== + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= + dependencies: + es6-promise "^4.0.3" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extract-zip@^1.6.6: + version "1.6.7" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9" + integrity sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k= + dependencies: + concat-stream "1.6.2" + debug "2.6.9" + mkdirp "0.5.1" + yauzl "2.4.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +fast-deep-equal@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + +fd-slicer@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" + integrity sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU= + dependencies: + pend "~1.2.0" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +glob@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.0: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + dependencies: + ajv "^6.5.5" + har-schema "^2.0.0" + +htmlparser2@^3.9.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" + integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== + dependencies: + domelementtype "^1.3.1" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^3.1.1" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +https-proxy-agent@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0" + integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ== + dependencies: + agent-base "^4.1.0" + debug "^3.1.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +lodash@^4.15.0, lodash@^4.17.11: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== + +mime-db@~1.38.0: + version "1.38.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.38.0.tgz#1a2aab16da9eb167b49c6e4df2d9c68d63d8e2ad" + integrity sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.22" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.22.tgz#fe6b355a190926ab7698c9a0556a11199b2199bd" + integrity sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog== + dependencies: + mime-db "~1.38.0" + +mime@^2.0.3: + version "2.4.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.0.tgz#e051fd881358585f3279df333fe694da0bcffdd6" + integrity sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +mkdirp@0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +nth-check@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" + integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== + dependencies: + boolbase "~1.0.0" + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +parse5@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" + integrity sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA== + dependencies: + "@types/node" "*" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== + +progress@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +proxy-from-env@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" + integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4= + +psl@^1.1.24, psl@^1.1.28: + version "1.1.31" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184" + integrity sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw== + +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +puppeteer@^1.14.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-1.14.0.tgz#828c1926b307200d5fc8289b99df4e13e962d339" + integrity sha512-SayS2wUX/8LF8Yo2Rkpc5nkAu4Jg3qu+OLTDSOZtisVQMB2Z5vjlY2TdPi/5CgZKiZroYIiyUN3sRX63El9iaw== + dependencies: + debug "^4.1.0" + extract-zip "^1.6.6" + https-proxy-agent "^2.2.1" + mime "^2.0.3" + progress "^2.0.1" + proxy-from-env "^1.0.0" + rimraf "^2.6.1" + ws "^6.1.0" + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +readable-stream@^2.2.2: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.2.0.tgz#de17f229864c120a9f56945756e4f32c4045245d" + integrity sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +request-promise-core@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.2.tgz#339f6aababcafdb31c799ff158700336301d3346" + integrity sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag== + dependencies: + lodash "^4.17.11" + +request-promise-native@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.7.tgz#a49868a624bdea5069f1251d0a836e0d89aa2c59" + integrity sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w== + dependencies: + request-promise-core "1.1.2" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" + +request@^2.88.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" + integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.0" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.4.3" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +rimraf@^2.6.1: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +stealthy-require@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" + integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= + +string_decoder@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" + integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w== + dependencies: + safe-buffer "~5.1.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +tough-cookie@^2.3.3: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tough-cookie@~2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== + dependencies: + psl "^1.1.24" + punycode "^1.4.1" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +uuid@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +ws@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + dependencies: + async-limiter "~1.0.0" + +yauzl@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005" + integrity sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU= + dependencies: + fd-slicer "~1.0.1" From ab5a2452ee482559068941024d6336b0d247ab8b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 29 Mar 2019 12:04:51 +0100 Subject: [PATCH 143/221] fix signup --- src/usecases/signup.js | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/src/usecases/signup.js b/src/usecases/signup.js index 825a2c27fa..1b9ba2872c 100644 --- a/src/usecases/signup.js +++ b/src/usecases/signup.js @@ -19,31 +19,26 @@ const assert = require('assert'); module.exports = async function signup(session, username, password, homeserver) { session.log.step("signs up"); await session.goto(session.url('/#/register')); - //click 'Custom server' radio button + // change the homeserver by clicking the "Change" link. if (homeserver) { - const advancedRadioButton = await session.waitAndQuery('#advanced'); - await advancedRadioButton.click(); + const changeServerDetailsLink = await session.waitAndQuery('.mx_AuthBody_editServerDetails'); + await changeServerDetailsLink.click(); + const hsInputField = await session.query('#mx_ServerConfig_hsUrl'); + await session.replaceInputText(hsInputField, homeserver); + const nextButton = await session.query('.mx_Login_submit'); + await nextButton.click(); } - // wait until register button is visible - await session.waitAndQuery('.mx_Login_submit[value=Register]'); //fill out form - const loginFields = await session.queryAll('.mx_Login_field'); - assert.strictEqual(loginFields.length, 7); - const usernameField = loginFields[2]; - const passwordField = loginFields[3]; - const passwordRepeatField = loginFields[4]; - const hsurlField = loginFields[5]; + const usernameField = await session.waitAndQuery("#mx_RegistrationForm_username"); + const passwordField = await session.waitAndQuery("#mx_RegistrationForm_password"); + const passwordRepeatField = await session.waitAndQuery("#mx_RegistrationForm_passwordConfirm"); await session.replaceInputText(usernameField, username); await session.replaceInputText(passwordField, password); await session.replaceInputText(passwordRepeatField, password); - if (homeserver) { - await session.waitAndQuery('.mx_ServerConfig'); - await session.replaceInputText(hsurlField, homeserver); - } - //wait over a second because Registration/ServerConfig have a 1000ms + //wait over a second because Registration/ServerConfig have a 250ms //delay to internally set the homeserver url //see Registration::render and ServerConfig::props::delayTimeMs - await session.delay(1500); + await session.delay(300); /// focus on the button to make sure error validation /// has happened before checking the form is good to go const registerButton = await session.query('.mx_Login_submit'); @@ -60,7 +55,7 @@ module.exports = async function signup(session, username, password, homeserver) await continueButton.click(); //find the privacy policy checkbox and check it - const policyCheckbox = await session.waitAndQuery('.mx_Login_box input[type="checkbox"]'); + const policyCheckbox = await session.waitAndQuery('.mx_InteractiveAuthEntryComponents_termsPolicy input'); await policyCheckbox.click(); //now click the 'Accept' button to agree to the privacy policy From 65ca1b33eea95a51ee2e461e5d9a648372f86685 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 29 Mar 2019 12:27:48 +0100 Subject: [PATCH 144/221] fix creating a room --- src/usecases/create-room.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/usecases/create-room.js b/src/usecases/create-room.js index 7d3488bfbe..79f1848198 100644 --- a/src/usecases/create-room.js +++ b/src/usecases/create-room.js @@ -18,8 +18,16 @@ const assert = require('assert'); module.exports = async function createRoom(session, roomName) { session.log.step(`creates room "${roomName}"`); - //TODO: brittle selector - const createRoomButton = await session.waitAndQuery('.mx_RoleButton[aria-label="Create new room"]'); + const roomListHeaders = await session.queryAll('.mx_RoomSubList_labelContainer'); + const roomListHeaderLabels = await Promise.all(roomListHeaders.map(h => session.innerText(h))); + const roomsIndex = roomListHeaderLabels.findIndex(l => l.toLowerCase().includes("rooms")); + if (roomsIndex === -1) { + throw new Error("could not find room list section that contains rooms in header"); + } + const roomsHeader = roomListHeaders[roomsIndex]; + const addRoomButton = await roomsHeader.$(".mx_RoomSubList_addRoom"); + await addRoomButton.click(); + const createRoomButton = await session.waitAndQuery('.mx_RoomDirectory_createRoom'); await createRoomButton.click(); const roomNameInput = await session.waitAndQuery('.mx_CreateRoomDialog_input'); @@ -30,4 +38,4 @@ module.exports = async function createRoom(session, roomName) { await session.waitAndQuery('.mx_MessageComposer'); session.log.done(); -} \ No newline at end of file +} From a27b92a49a42766d696d7e98d09df995d0dede01 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 29 Mar 2019 13:04:10 +0100 Subject: [PATCH 145/221] fix changing the room settings --- src/usecases/room-settings.js | 52 ++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/src/usecases/room-settings.js b/src/usecases/room-settings.js index 95c7538431..ca0bcf2a95 100644 --- a/src/usecases/room-settings.js +++ b/src/usecases/room-settings.js @@ -17,11 +17,11 @@ limitations under the License. const assert = require('assert'); const {acceptDialog} = require('./dialog'); -async function setCheckboxSetting(session, checkbox, enabled) { - const checked = await session.getElementProperty(checkbox, "checked"); - assert.equal(typeof checked, "boolean"); +async function setSettingsToggle(session, toggle, enabled) { + const className = await session.getElementProperty(toggle, "className"); + const checked = className.includes("mx_ToggleSwitch_on"); if (checked !== enabled) { - await checkbox.click(); + await toggle.click(); session.log.done(); return true; } else { @@ -31,25 +31,40 @@ async function setCheckboxSetting(session, checkbox, enabled) { module.exports = async function changeRoomSettings(session, settings) { session.log.startGroup(`changes the room settings`); - /// XXX delay is needed here, possible because the header is being rerendered + /// XXX delay is needed here, possibly because the header is being rerendered /// click doesn't do anything otherwise await session.delay(1000); const settingsButton = await session.query(".mx_RoomHeader .mx_AccessibleButton[title=Settings]"); await settingsButton.click(); - const checks = await session.waitAndQueryAll(".mx_RoomSettings_settings input[type=checkbox]"); - assert.equal(checks.length, 3); - const e2eEncryptionCheck = checks[0]; - const sendToUnverifiedDevices = checks[1]; - const isDirectory = checks[2]; + //find tabs + const tabButtons = await session.waitAndQueryAll(".mx_RoomSettingsDialog .mx_TabbedView_tabLabel"); + const tabLabels = await Promise.all(tabButtons.map(t => session.innerText(t))); + const securityTabButton = tabButtons[tabLabels.findIndex(l => l.toLowerCase().includes("security"))]; + + const generalSwitches = await session.waitAndQueryAll(".mx_RoomSettingsDialog .mx_ToggleSwitch"); + const isDirectory = generalSwitches[0]; if (typeof settings.directory === "boolean") { session.log.step(`sets directory listing to ${settings.directory}`); - await setCheckboxSetting(session, isDirectory, settings.directory); + await setSettingsToggle(session, isDirectory, settings.directory); } + if (settings.alias) { + session.log.step(`sets alias to ${settings.alias}`); + const aliasField = await session.waitAndQuery(".mx_RoomSettingsDialog .mx_AliasSettings input[type=text]"); + await session.replaceInputText(aliasField, settings.alias); + const addButton = await session.waitAndQuery(".mx_RoomSettingsDialog .mx_AliasSettings .mx_AccessibleButton"); + await addButton.click(); + session.log.done(); + } + + securityTabButton.click(); + const securitySwitches = await session.waitAndQueryAll(".mx_RoomSettingsDialog .mx_ToggleSwitch"); + const e2eEncryptionToggle = securitySwitches[0]; + if (typeof settings.encryption === "boolean") { session.log.step(`sets room e2e encryption to ${settings.encryption}`); - const clicked = await setCheckboxSetting(session, e2eEncryptionCheck, settings.encryption); + const clicked = await setSettingsToggle(session, e2eEncryptionToggle, settings.encryption); // if enabling, accept beta warning dialog if (clicked && settings.encryption) { await acceptDialog(session, "encryption"); @@ -58,7 +73,7 @@ module.exports = async function changeRoomSettings(session, settings) { if (settings.visibility) { session.log.step(`sets visibility to ${settings.visibility}`); - const radios = await session.waitAndQueryAll(".mx_RoomSettings_settings input[type=radio]"); + const radios = await session.waitAndQueryAll(".mx_RoomSettingsDialog input[type=radio]"); assert.equal(radios.length, 7); const inviteOnly = radios[0]; const publicNoGuests = radios[1]; @@ -76,15 +91,8 @@ module.exports = async function changeRoomSettings(session, settings) { session.log.done(); } - if (settings.alias) { - session.log.step(`sets alias to ${settings.alias}`); - const aliasField = await session.waitAndQuery(".mx_RoomSettings .mx_EditableItemList .mx_EditableItem_editable"); - await session.replaceInputText(aliasField, settings.alias); - session.log.done(); - } - - const saveButton = await session.query(".mx_RoomHeader_wrapper .mx_RoomHeader_textButton"); - await saveButton.click(); + const closeButton = await session.query(".mx_RoomSettingsDialog .mx_Dialog_cancelButton"); + await closeButton.click(); session.log.endGroup(); } From fe6a273ba947ae605d507d7f86382cead75660d4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 29 Mar 2019 13:59:42 +0100 Subject: [PATCH 146/221] fix joining a room through the room directory --- src/scenarios/directory.js | 2 +- src/usecases/create-room.js | 10 ++++++++-- src/usecases/join.js | 10 ++++------ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/scenarios/directory.js b/src/scenarios/directory.js index cfe72ccef3..582b6867b2 100644 --- a/src/scenarios/directory.js +++ b/src/scenarios/directory.js @@ -18,7 +18,7 @@ limitations under the License. const join = require('../usecases/join'); const sendMessage = require('../usecases/send-message'); const {receiveMessage} = require('../usecases/timeline'); -const createRoom = require('../usecases/create-room'); +const {createRoom} = require('../usecases/create-room'); const changeRoomSettings = require('../usecases/room-settings'); module.exports = async function roomDirectoryScenarios(alice, bob) { diff --git a/src/usecases/create-room.js b/src/usecases/create-room.js index 79f1848198..16d0620879 100644 --- a/src/usecases/create-room.js +++ b/src/usecases/create-room.js @@ -16,8 +16,7 @@ limitations under the License. const assert = require('assert'); -module.exports = async function createRoom(session, roomName) { - session.log.step(`creates room "${roomName}"`); +async function openRoomDirectory(session) { const roomListHeaders = await session.queryAll('.mx_RoomSubList_labelContainer'); const roomListHeaderLabels = await Promise.all(roomListHeaders.map(h => session.innerText(h))); const roomsIndex = roomListHeaderLabels.findIndex(l => l.toLowerCase().includes("rooms")); @@ -27,6 +26,11 @@ module.exports = async function createRoom(session, roomName) { const roomsHeader = roomListHeaders[roomsIndex]; const addRoomButton = await roomsHeader.$(".mx_RoomSubList_addRoom"); await addRoomButton.click(); +} + +async function createRoom(session, roomName) { + session.log.step(`creates room "${roomName}"`); + await openRoomDirectory(session); const createRoomButton = await session.waitAndQuery('.mx_RoomDirectory_createRoom'); await createRoomButton.click(); @@ -39,3 +43,5 @@ module.exports = async function createRoom(session, roomName) { await session.waitAndQuery('.mx_MessageComposer'); session.log.done(); } + +module.exports = {openRoomDirectory, createRoom}; diff --git a/src/usecases/join.js b/src/usecases/join.js index 76b98ca397..cba9e06660 100644 --- a/src/usecases/join.js +++ b/src/usecases/join.js @@ -15,14 +15,12 @@ limitations under the License. */ const assert = require('assert'); +const {openRoomDirectory} = require('./create-room'); module.exports = async function join(session, roomName) { session.log.step(`joins room "${roomName}"`); - //TODO: brittle selector - const directoryButton = await session.waitAndQuery('.mx_RoleButton[aria-label="Room directory"]'); - await directoryButton.click(); - - const roomInput = await session.waitAndQuery('.mx_DirectorySearchBox_input'); + await openRoomDirectory(session); + const roomInput = await session.waitAndQuery('.mx_DirectorySearchBox input'); await session.replaceInputText(roomInput, roomName); const firstRoomLabel = await session.waitAndQuery('.mx_RoomDirectory_table .mx_RoomDirectory_name:first-child', 1000); @@ -33,4 +31,4 @@ module.exports = async function join(session, roomName) { await session.waitAndQuery('.mx_MessageComposer'); session.log.done(); -} \ No newline at end of file +} From 5598214cd20a534bc0aaf3b8d9423ae34081ecb6 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 29 Mar 2019 16:59:05 +0100 Subject: [PATCH 147/221] fix writing in composer --- src/usecases/send-message.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/usecases/send-message.js b/src/usecases/send-message.js index 5bf289b03a..038171327c 100644 --- a/src/usecases/send-message.js +++ b/src/usecases/send-message.js @@ -21,6 +21,9 @@ module.exports = async function sendMessage(session, message) { // this selector needs to be the element that has contenteditable=true, // not any if its parents, otherwise it behaves flaky at best. const composer = await session.waitAndQuery('.mx_MessageComposer_editor'); + // sometimes the focus that type() does internally doesn't seem to work + // and calling click before seems to fix it 🤷 + await composer.click(); await composer.type(message); const text = await session.innerText(composer); assert.equal(text.trim(), message.trim()); From a1505971fc18eb627dbabb43a22ca23b15cd6057 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 29 Mar 2019 16:59:54 +0100 Subject: [PATCH 148/221] missed this when making createRoom export non-default earlier --- src/scenarios/e2e-encryption.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scenarios/e2e-encryption.js b/src/scenarios/e2e-encryption.js index 51d8a70236..7cd9f1ede9 100644 --- a/src/scenarios/e2e-encryption.js +++ b/src/scenarios/e2e-encryption.js @@ -22,7 +22,7 @@ const sendMessage = require('../usecases/send-message'); const acceptInvite = require('../usecases/accept-invite'); const invite = require('../usecases/invite'); const {receiveMessage} = require('../usecases/timeline'); -const createRoom = require('../usecases/create-room'); +const {createRoom} = require('../usecases/create-room'); const changeRoomSettings = require('../usecases/room-settings'); const {getE2EDeviceFromSettings} = require('../usecases/settings'); const {verifyDeviceForUser} = require('../usecases/memberlist'); From 2bf51da73e58ef16b3807302051293faa4b34008 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 2 Apr 2019 14:29:23 +0200 Subject: [PATCH 149/221] fix enabling e2e encryption in room settings --- src/usecases/room-settings.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/usecases/room-settings.js b/src/usecases/room-settings.js index ca0bcf2a95..e204d24a52 100644 --- a/src/usecases/room-settings.js +++ b/src/usecases/room-settings.js @@ -59,7 +59,8 @@ module.exports = async function changeRoomSettings(session, settings) { } securityTabButton.click(); - const securitySwitches = await session.waitAndQueryAll(".mx_RoomSettingsDialog .mx_ToggleSwitch"); + await session.delay(500); + const securitySwitches = await session.queryAll(".mx_RoomSettingsDialog .mx_ToggleSwitch"); const e2eEncryptionToggle = securitySwitches[0]; if (typeof settings.encryption === "boolean") { From ae5dc9d0b37507706479211d217a6f7da4d3f4ab Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 2 Apr 2019 14:29:47 +0200 Subject: [PATCH 150/221] fix inviting someone --- src/usecases/invite.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/usecases/invite.js b/src/usecases/invite.js index 934beb6819..bbfc38cd23 100644 --- a/src/usecases/invite.js +++ b/src/usecases/invite.js @@ -19,7 +19,7 @@ const assert = require('assert'); module.exports = async function invite(session, userId) { session.log.step(`invites "${userId}" to room`); await session.delay(1000); - const inviteButton = await session.waitAndQuery(".mx_RightPanel_invite"); + const inviteButton = await session.waitAndQuery(".mx_MemberList_invite"); await inviteButton.click(); const inviteTextArea = await session.waitAndQuery(".mx_ChatInviteDialog textarea"); await inviteTextArea.type(userId); From 9ab169254417f97638e9019cfaf45943733def12 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 2 Apr 2019 14:30:41 +0200 Subject: [PATCH 151/221] fix verification, replace device id/key with SAS verification. --- src/scenarios/e2e-encryption.js | 24 +++++------- src/usecases/dialog.js | 23 ++++++----- src/usecases/memberlist.js | 22 +++++++++++ src/usecases/room-settings.js | 2 +- src/usecases/verify.js | 68 +++++++++++++++++++++++++++++++++ 5 files changed, 114 insertions(+), 25 deletions(-) create mode 100644 src/usecases/verify.js diff --git a/src/scenarios/e2e-encryption.js b/src/scenarios/e2e-encryption.js index 7cd9f1ede9..c7a6a5d085 100644 --- a/src/scenarios/e2e-encryption.js +++ b/src/scenarios/e2e-encryption.js @@ -24,28 +24,24 @@ const invite = require('../usecases/invite'); const {receiveMessage} = require('../usecases/timeline'); const {createRoom} = require('../usecases/create-room'); const changeRoomSettings = require('../usecases/room-settings'); -const {getE2EDeviceFromSettings} = require('../usecases/settings'); -const {verifyDeviceForUser} = require('../usecases/memberlist'); +const {startSasVerifcation, acceptSasVerification} = require('../usecases/verify'); +const assert = require('assert'); module.exports = async function e2eEncryptionScenarios(alice, bob) { console.log(" creating an e2e encrypted room and join through invite:"); const room = "secrets"; await createRoom(bob, room); await changeRoomSettings(bob, {encryption: true}); + // await cancelKeyBackup(bob); await invite(bob, "@alice:localhost"); await acceptInvite(alice, room); - const bobDevice = await getE2EDeviceFromSettings(bob); - // wait some time for the encryption warning dialog - // to appear after closing the settings - await delay(1000); - await acceptDialogMaybe(bob, "encryption"); - const aliceDevice = await getE2EDeviceFromSettings(alice); - // wait some time for the encryption warning dialog - // to appear after closing the settings - await delay(1000); - await acceptDialogMaybe(alice, "encryption"); - await verifyDeviceForUser(bob, "alice", aliceDevice); - await verifyDeviceForUser(alice, "bob", bobDevice); + // do sas verifcation + bob.log.step(`starts SAS verification with ${alice.username}`); + const bobSasPromise = startSasVerifcation(bob, alice.username); + const aliceSasPromise = acceptSasVerification(alice, bob.username); + const [bobSas, aliceSas] = await Promise.all([bobSasPromise, aliceSasPromise]); + assert.deepEqual(bobSas, aliceSas); + bob.log.done(`done, (${bobSas.join(", ")}) matches!`); const aliceMessage = "Guess what I just heard?!" await sendMessage(alice, aliceMessage); await receiveMessage(bob, {sender: "alice", body: aliceMessage, encrypted: true}); diff --git a/src/usecases/dialog.js b/src/usecases/dialog.js index 89c70470d9..b11dac616d 100644 --- a/src/usecases/dialog.js +++ b/src/usecases/dialog.js @@ -16,27 +16,29 @@ limitations under the License. const assert = require('assert'); +async function assertDialog(session, expectedTitle) { + const titleElement = await session.waitAndQuery(".mx_Dialog .mx_Dialog_title"); + const dialogHeader = await session.innerText(titleElement); + assert(dialogHeader, expectedTitle); +} -async function acceptDialog(session, expectedContent) { - const foundDialog = await acceptDialogMaybe(session, expectedContent); +async function acceptDialog(session, expectedTitle) { + const foundDialog = await acceptDialogMaybe(session, expectedTitle); if (!foundDialog) { throw new Error("could not find a dialog"); } } -async function acceptDialogMaybe(session, expectedContent) { - let dialog = null; +async function acceptDialogMaybe(session, expectedTitle) { + let primaryButton = null; try { - dialog = await session.waitAndQuery(".mx_QuestionDialog"); + primaryButton = await session.waitAndQuery(".mx_Dialog [role=dialog] .mx_Dialog_primary"); } catch(err) { return false; } - if (expectedContent) { - const contentElement = await dialog.$(".mx_Dialog_content"); - const content = await (await contentElement.getProperty("innerText")).jsonValue(); - assert.ok(content.indexOf(expectedContent) !== -1); + if (expectedTitle) { + await assertDialog(session, expectedTitle); } - const primaryButton = await dialog.$(".mx_Dialog_primary"); await primaryButton.click(); return true; } @@ -44,4 +46,5 @@ async function acceptDialogMaybe(session, expectedContent) { module.exports = { acceptDialog, acceptDialogMaybe, + assertDialog, }; diff --git a/src/usecases/memberlist.js b/src/usecases/memberlist.js index b018ed552c..0cd8744853 100644 --- a/src/usecases/memberlist.js +++ b/src/usecases/memberlist.js @@ -16,6 +16,16 @@ limitations under the License. const assert = require('assert'); +async function openMemberInfo(session, name) { + const membersAndNames = await getMembersInMemberlist(session); + const matchingLabel = membersAndNames.filter((m) => { + return m.displayName === name; + }).map((m) => m.label)[0]; + await matchingLabel.click(); +}; + +module.exports.openMemberInfo = openMemberInfo; + module.exports.verifyDeviceForUser = async function(session, name, expectedDevice) { session.log.step(`verifies e2e device for ${name}`); const membersAndNames = await getMembersInMemberlist(session); @@ -23,8 +33,20 @@ module.exports.verifyDeviceForUser = async function(session, name, expectedDevic return m.displayName === name; }).map((m) => m.label)[0]; await matchingLabel.click(); + // click verify in member info const firstVerifyButton = await session.waitAndQuery(".mx_MemberDeviceInfo_verify"); await firstVerifyButton.click(); + // expect "Verify device" dialog and click "Begin Verification" + const dialogHeader = await session.innerText(await session.waitAndQuery(".mx_Dialog .mx_Dialog_title")); + assert(dialogHeader, "Verify device"); + const beginVerificationButton = await session.waitAndQuery(".mx_Dialog .mx_Dialog_primary") + await beginVerificationButton.click(); + // get emoji SAS labels + const sasLabelElements = await session.waitAndQueryAll(".mx_VerificationShowSas .mx_VerificationShowSas_emojiSas .mx_VerificationShowSas_emojiSas_label"); + const sasLabels = await Promise.all(sasLabelElements.map(e => session.innerText(e))); + console.log("my sas labels", sasLabels); + + const dialogCodeFields = await session.waitAndQueryAll(".mx_QuestionDialog code"); assert.equal(dialogCodeFields.length, 2); const deviceId = await session.innerText(dialogCodeFields[0]); diff --git a/src/usecases/room-settings.js b/src/usecases/room-settings.js index e204d24a52..95078d1c87 100644 --- a/src/usecases/room-settings.js +++ b/src/usecases/room-settings.js @@ -68,7 +68,7 @@ module.exports = async function changeRoomSettings(session, settings) { const clicked = await setSettingsToggle(session, e2eEncryptionToggle, settings.encryption); // if enabling, accept beta warning dialog if (clicked && settings.encryption) { - await acceptDialog(session, "encryption"); + await acceptDialog(session, "Enable encryption?"); } } diff --git a/src/usecases/verify.js b/src/usecases/verify.js new file mode 100644 index 0000000000..e9461c225e --- /dev/null +++ b/src/usecases/verify.js @@ -0,0 +1,68 @@ +/* +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 assert = require('assert'); +const {openMemberInfo} = require("./memberlist"); +const {assertDialog, acceptDialog} = require("./dialog"); + +async function assertVerified(session) { + const dialogSubTitle = await session.innerText(await session.waitAndQuery(".mx_Dialog h2")); + assert(dialogSubTitle, "Verified!"); +} + +async function startVerification(session, name) { + await openMemberInfo(session, name); + // click verify in member info + const firstVerifyButton = await session.waitAndQuery(".mx_MemberDeviceInfo_verify"); + await firstVerifyButton.click(); +} + +async function getSasCodes(session) { + const sasLabelElements = await session.waitAndQueryAll(".mx_VerificationShowSas .mx_VerificationShowSas_emojiSas .mx_VerificationShowSas_emojiSas_label"); + const sasLabels = await Promise.all(sasLabelElements.map(e => session.innerText(e))); + return sasLabels; +} + +module.exports.startSasVerifcation = async function(session, name) { + await startVerification(session, name); + // expect "Verify device" dialog and click "Begin Verification" + await assertDialog(session, "Verify device"); + // click "Begin Verification" + await acceptDialog(session); + const sasCodes = await getSasCodes(session); + // click "Verify" + await acceptDialog(session); + await assertVerified(session); + // click "Got it" when verification is done + await acceptDialog(session); + return sasCodes; +}; + +module.exports.acceptSasVerification = async function(session, name) { + await assertDialog(session, "Incoming Verification Request"); + const opponentLabelElement = await session.query(".mx_IncomingSasDialog_opponentProfile h2"); + const opponentLabel = await session.innerText(opponentLabelElement); + assert(opponentLabel, name); + // click "Continue" button + await acceptDialog(session); + const sasCodes = await getSasCodes(session); + // click "Verify" + await acceptDialog(session); + await assertVerified(session); + // click "Got it" when verification is done + await acceptDialog(session); + return sasCodes; +}; From 28bba4952b5d10fec01952b9c150de18f8ecfcb4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 2 Apr 2019 15:12:51 +0200 Subject: [PATCH 152/221] fix detecting e2e message in timeline --- src/usecases/timeline.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/usecases/timeline.js b/src/usecases/timeline.js index dce0203660..610d1f4e9b 100644 --- a/src/usecases/timeline.js +++ b/src/usecases/timeline.js @@ -117,11 +117,13 @@ function getLastEventTile(session) { } function getAllEventTiles(session) { - return session.queryAll(".mx_RoomView_MessageList > *"); + return session.queryAll(".mx_RoomView_MessageList .mx_EventTile"); } async function getMessageFromEventTile(eventTile) { const senderElement = await eventTile.$(".mx_SenderProfile_name"); + const className = await (await eventTile.getProperty("className")).jsonValue(); + const classNames = className.split(" "); const bodyElement = await eventTile.$(".mx_EventTile_body"); let sender = null; if (senderElement) { @@ -131,11 +133,10 @@ async function getMessageFromEventTile(eventTile) { return null; } const body = await(await bodyElement.getProperty("innerText")).jsonValue(); - const e2eIcon = await eventTile.$(".mx_EventTile_e2eIcon"); return { sender, body, - encrypted: !!e2eIcon + encrypted: classNames.includes("mx_EventTile_verified") }; } From 34171eab8cc1998a2301af81344cac524e3dbe22 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 2 Apr 2019 15:13:04 +0200 Subject: [PATCH 153/221] doing wait for /sync request to receive message, doesn't work well just poll every 200ms, feels way faster as before we were probably missing /sync requests --- src/session.js | 9 --------- src/usecases/timeline.js | 38 ++++++++++++++------------------------ 2 files changed, 14 insertions(+), 33 deletions(-) diff --git a/src/session.js b/src/session.js index 7ea980bd32..1363185753 100644 --- a/src/session.js +++ b/src/session.js @@ -161,15 +161,6 @@ module.exports = class RiotSession { }); } - waitForSyncResponseWith(predicate) { - return this.page.waitForResponse(async (response) => { - if (response.request().url().indexOf("/sync") === -1) { - return false; - } - return predicate(response); - }); - } - /** wait for a /sync request started after this call that gets a 200 response */ async waitForNextSuccessfulSync() { const syncUrls = []; diff --git a/src/usecases/timeline.js b/src/usecases/timeline.js index 610d1f4e9b..191117891d 100644 --- a/src/usecases/timeline.js +++ b/src/usecases/timeline.js @@ -54,31 +54,21 @@ module.exports.receiveMessage = async function(session, expectedMessage) { let lastMessage = null; let isExpectedMessage = false; - try { - lastMessage = await getLastMessage(); - isExpectedMessage = lastMessage && - lastMessage.body === expectedMessage.body && - lastMessage.sender === expectedMessage.sender; - } catch(ex) {} - // first try to see if the message is already the last message in the timeline - if (isExpectedMessage) { - assertMessage(lastMessage, expectedMessage); - } else { - await session.waitForSyncResponseWith(async (response) => { - const body = await response.text(); - if (expectedMessage.encrypted) { - return body.indexOf(expectedMessage.sender) !== -1 && - body.indexOf("m.room.encrypted") !== -1; - } else { - return body.indexOf(expectedMessage.body) !== -1; - } - }); - // wait a bit for the incoming event to be rendered - await session.delay(1000); - lastMessage = await getLastMessage(); - assertMessage(lastMessage, expectedMessage); + let totalTime = 0; + while (!isExpectedMessage) { + try { + lastMessage = await getLastMessage(); + isExpectedMessage = lastMessage && + lastMessage.body === expectedMessage.body && + lastMessage.sender === expectedMessage.sender + } catch(err) {} + if (totalTime > 5000) { + throw new Error("timed out after 5000ms"); + } + totalTime += 200; + await session.delay(200); } - + assertMessage(lastMessage, expectedMessage); session.log.done(); } From c1312f09ab5f1ff463f8f592ed7ebe7d900f5caf Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 2 Apr 2019 15:14:08 +0200 Subject: [PATCH 154/221] fix import --- src/scenarios/lazy-loading.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scenarios/lazy-loading.js b/src/scenarios/lazy-loading.js index c33e83215c..b1a1695b5c 100644 --- a/src/scenarios/lazy-loading.js +++ b/src/scenarios/lazy-loading.js @@ -22,7 +22,7 @@ const { checkTimelineContains, scrollToTimelineTop } = require('../usecases/timeline'); -const createRoom = require('../usecases/create-room'); +const {createRoom} = require('../usecases/create-room'); const {getMembersInMemberlist} = require('../usecases/memberlist'); const changeRoomSettings = require('../usecases/room-settings'); const {enableLazyLoading} = require('../usecases/settings'); From f197e9f97756e82ef626cab0a6b96fba8d2a5890 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 2 Apr 2019 15:14:24 +0200 Subject: [PATCH 155/221] lazy loading is not in labs anymore --- src/scenarios/lazy-loading.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/scenarios/lazy-loading.js b/src/scenarios/lazy-loading.js index b1a1695b5c..deb259a916 100644 --- a/src/scenarios/lazy-loading.js +++ b/src/scenarios/lazy-loading.js @@ -30,7 +30,6 @@ const assert = require('assert'); module.exports = async function lazyLoadingScenarios(alice, bob, charlies) { console.log(" creating a room for lazy loading member scenarios:"); - await enableLazyLoading(alice); const charly1to5 = charlies.slice("charly-1..5", 0, 5); const charly6to10 = charlies.slice("charly-6..10", 5); assert(charly1to5.sessions.length, 5); From 450430d66c02b50a063bb299bef7b8ab3d4ae80b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 2 Apr 2019 15:14:44 +0200 Subject: [PATCH 156/221] remove travis flag --- src/scenario.js | 14 +++----------- start.js | 3 +-- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/scenario.js b/src/scenario.js index 5b9d1f2906..4593960c10 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -21,7 +21,7 @@ const roomDirectoryScenarios = require('./scenarios/directory'); const lazyLoadingScenarios = require('./scenarios/lazy-loading'); const e2eEncryptionScenarios = require('./scenarios/e2e-encryption'); -module.exports = async function scenario(createSession, restCreator, runningOnTravis) { +module.exports = async function scenario(createSession, restCreator) { async function createUser(username) { const session = await createSession(username); await signup(session, session.username, 'testtest', session.hsUrl); @@ -34,16 +34,8 @@ module.exports = async function scenario(createSession, restCreator, runningOnTr await roomDirectoryScenarios(alice, bob); await e2eEncryptionScenarios(alice, bob); - // disable LL tests until we can run synapse on anything > than 2.7.7 as - // /admin/register fails with a missing method. - // either switch to python3 on synapse, - // blocked on https://github.com/matrix-org/synapse/issues/3900 - // or use a more recent version of ubuntu - // or switch to circleci? - if (!runningOnTravis) { - const charlies = await createRestUsers(restCreator); - await lazyLoadingScenarios(alice, bob, charlies); - } + const charlies = await createRestUsers(restCreator); + await lazyLoadingScenarios(alice, bob, charlies); } async function createRestUsers(restCreator) { diff --git a/start.js b/start.js index 18ccb438ec..1c3f27bbe3 100644 --- a/start.js +++ b/start.js @@ -26,7 +26,6 @@ program .option('--windowed', "dont run tests headless", false) .option('--slow-mo', "run tests slower to follow whats going on", false) .option('--dev-tools', "open chrome devtools in browser window", false) - .option('--travis', "running on travis CI, disable tests known to break on Ubuntu 14.04 LTS", false) .parse(process.argv); const hsUrl = 'http://localhost:5005'; @@ -59,7 +58,7 @@ async function runTests() { let failure = false; try { - await scenario(createSession, restCreator, program.travis); + await scenario(createSession, restCreator); } catch(err) { failure = true; console.log('failure: ', err); From 2449ddcfeede196fa7f4210b72b39bd8e40e70cf Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 2 Apr 2019 15:14:51 +0200 Subject: [PATCH 157/221] "disable" rate limiting for rest users --- .../config-templates/consent/homeserver.yaml | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/synapse/config-templates/consent/homeserver.yaml b/synapse/config-templates/consent/homeserver.yaml index 222ddb956f..80a967b047 100644 --- a/synapse/config-templates/consent/homeserver.yaml +++ b/synapse/config-templates/consent/homeserver.yaml @@ -364,11 +364,11 @@ log_config: "{{SYNAPSE_ROOT}}localhost.log.config" # Number of messages a client can send per second # -#rc_messages_per_second: 0.2 +rc_messages_per_second: 10000 # Number of message a client can send before being throttled # -#rc_message_burst_count: 10.0 +rc_message_burst_count: 10000 # Ratelimiting settings for registration and login. # @@ -389,20 +389,20 @@ log_config: "{{SYNAPSE_ROOT}}localhost.log.config" # # The defaults are as shown below. # -#rc_registration: -# per_second: 0.17 -# burst_count: 3 -# -#rc_login: -# address: -# per_second: 0.17 -# burst_count: 3 -# account: -# per_second: 0.17 -# burst_count: 3 -# failed_attempts: -# per_second: 0.17 -# burst_count: 3 +rc_registration: + per_second: 10000 + burst_count: 10000 + +rc_login: + address: + per_second: 10000 + burst_count: 10000 + account: + per_second: 10000 + burst_count: 10000 + failed_attempts: + per_second: 10000 + burst_count: 10000 # The federation window size in milliseconds # From d63a0c5aea5ca2053376354e812a70345300fcc5 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 2 Apr 2019 15:15:13 +0200 Subject: [PATCH 158/221] fix gete2e info and open settings, even though not used currently --- src/usecases/settings.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/usecases/settings.js b/src/usecases/settings.js index 5649671e7a..68a290c29d 100644 --- a/src/usecases/settings.js +++ b/src/usecases/settings.js @@ -16,6 +16,17 @@ limitations under the License. const assert = require('assert'); +async function openSettings(session, section) { + const menuButton = await session.query(".mx_TopLeftMenuButton_name"); + await menuButton.click(); + const settingsItem = await session.waitAndQuery(".mx_TopLeftMenu_icon_settings"); + await settingsItem.click(); + if (section) { + const sectionButton = await session.waitAndQuery(`.mx_UserSettingsDialog .mx_TabbedView_tabLabels .mx_UserSettingsDialog_${section}Icon`); + await sectionButton.click(); + } +} + module.exports.enableLazyLoading = async function(session) { session.log.step(`enables lazy loading of members in the lab settings`); const settingsButton = await session.query('.mx_BottomLeftMenu_settings'); @@ -30,13 +41,12 @@ module.exports.enableLazyLoading = async function(session) { module.exports.getE2EDeviceFromSettings = async function(session) { session.log.step(`gets e2e device/key from settings`); - const settingsButton = await session.query('.mx_BottomLeftMenu_settings'); - await settingsButton.click(); - const deviceAndKey = await session.waitAndQueryAll(".mx_UserSettings_section.mx_UserSettings_cryptoSection code"); + await openSettings(session, "security"); + const deviceAndKey = await session.waitAndQueryAll(".mx_SettingsTab_section .mx_SecurityUserSettingsTab_deviceInfo code"); assert.equal(deviceAndKey.length, 2); const id = await (await deviceAndKey[0].getProperty("innerText")).jsonValue(); const key = await (await deviceAndKey[1].getProperty("innerText")).jsonValue(); - const closeButton = await session.query(".mx_RoomHeader_cancelButton"); + const closeButton = await session.query(".mx_UserSettingsDialog .mx_Dialog_cancelButton"); await closeButton.click(); session.log.done(); return {id, key}; From e147cc9341a1b950760e669ada259d5bdd707d72 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 3 Apr 2019 14:31:31 +0200 Subject: [PATCH 159/221] this dialog isn't shown anymore and this was accepting the SAS dialog also lower timeout so we don't wait 5s if there is no dialog --- src/usecases/accept-invite.js | 3 --- src/usecases/dialog.js | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/usecases/accept-invite.js b/src/usecases/accept-invite.js index 8cc1a0b37d..03fbac28fa 100644 --- a/src/usecases/accept-invite.js +++ b/src/usecases/accept-invite.js @@ -34,8 +34,5 @@ module.exports = async function acceptInvite(session, name) { const acceptInvitationLink = await session.waitAndQuery(".mx_RoomPreviewBar_join_text a:first-child"); await acceptInvitationLink.click(); - // accept e2e warning dialog - acceptDialogMaybe(session, "encryption"); - session.log.done(); } diff --git a/src/usecases/dialog.js b/src/usecases/dialog.js index b11dac616d..da71f6b61f 100644 --- a/src/usecases/dialog.js +++ b/src/usecases/dialog.js @@ -32,7 +32,7 @@ async function acceptDialog(session, expectedTitle) { async function acceptDialogMaybe(session, expectedTitle) { let primaryButton = null; try { - primaryButton = await session.waitAndQuery(".mx_Dialog [role=dialog] .mx_Dialog_primary"); + primaryButton = await session.waitAndQuery(".mx_Dialog .mx_Dialog_primary", 50); } catch(err) { return false; } @@ -44,7 +44,7 @@ async function acceptDialogMaybe(session, expectedTitle) { } module.exports = { + assertDialog, acceptDialog, acceptDialogMaybe, - assertDialog, }; From e10e4b0eab4e0fea3d4c66cfdc9ef67f57df4a3a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 3 Apr 2019 14:37:03 +0200 Subject: [PATCH 160/221] nicer output, comment --- src/scenarios/e2e-encryption.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/scenarios/e2e-encryption.js b/src/scenarios/e2e-encryption.js index c7a6a5d085..29b97f2047 100644 --- a/src/scenarios/e2e-encryption.js +++ b/src/scenarios/e2e-encryption.js @@ -39,9 +39,10 @@ module.exports = async function e2eEncryptionScenarios(alice, bob) { bob.log.step(`starts SAS verification with ${alice.username}`); const bobSasPromise = startSasVerifcation(bob, alice.username); const aliceSasPromise = acceptSasVerification(alice, bob.username); + // wait in parallel, so they don't deadlock on each other const [bobSas, aliceSas] = await Promise.all([bobSasPromise, aliceSasPromise]); assert.deepEqual(bobSas, aliceSas); - bob.log.done(`done, (${bobSas.join(", ")}) matches!`); + bob.log.done(`done (match for ${bobSas.join(", ")})`); const aliceMessage = "Guess what I just heard?!" await sendMessage(alice, aliceMessage); await receiveMessage(bob, {sender: "alice", body: aliceMessage, encrypted: true}); From 2b2a4867bdbf446795f2980e417253286fabda7a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 3 Apr 2019 15:08:43 +0200 Subject: [PATCH 161/221] forgot to add consent listener again, this will fix REST registration --- synapse/config-templates/consent/homeserver.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse/config-templates/consent/homeserver.yaml b/synapse/config-templates/consent/homeserver.yaml index 80a967b047..7fdf8a887d 100644 --- a/synapse/config-templates/consent/homeserver.yaml +++ b/synapse/config-templates/consent/homeserver.yaml @@ -177,7 +177,7 @@ listeners: x_forwarded: true resources: - - names: [client, federation] + - names: [client, federation, consent] compress: false # example additonal_resources: From 5939d624999dfc42e7c4f067af1ebe9344c3bab8 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 3 Apr 2019 15:15:15 +0200 Subject: [PATCH 162/221] fix scrollpanel selector --- src/usecases/timeline.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/usecases/timeline.js b/src/usecases/timeline.js index 191117891d..3298464c8d 100644 --- a/src/usecases/timeline.js +++ b/src/usecases/timeline.js @@ -20,7 +20,7 @@ module.exports.scrollToTimelineTop = async function(session) { session.log.step(`scrolls to the top of the timeline`); await session.page.evaluate(() => { return Promise.resolve().then(async () => { - const timelineScrollView = document.querySelector(".mx_RoomView .gm-scroll-view"); + const timelineScrollView = document.querySelector(".mx_RoomView_timeline .mx_ScrollPanel"); let timedOut = false; let timeoutHandle = null; // set scrollTop to 0 in a loop and check every 50ms From 3affb8f068b011a99f17e6a088ec156524129218 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 3 Apr 2019 15:15:28 +0200 Subject: [PATCH 163/221] section for creating rest users --- src/scenario.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scenario.js b/src/scenario.js index 4593960c10..6176d3a171 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -33,7 +33,7 @@ module.exports = async function scenario(createSession, restCreator) { await roomDirectoryScenarios(alice, bob); await e2eEncryptionScenarios(alice, bob); - + console.log("create REST users:"); const charlies = await createRestUsers(restCreator); await lazyLoadingScenarios(alice, bob, charlies); } From 9c41ccce58019cfe2169b82b4a8ec09aff33550a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 3 Apr 2019 15:46:55 +0200 Subject: [PATCH 164/221] use shorter .bak suffix approach --- synapse/install.sh | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/synapse/install.sh b/synapse/install.sh index c83ca6512a..2cc68dee03 100755 --- a/synapse/install.sh +++ b/synapse/install.sh @@ -45,11 +45,10 @@ cp -r $BASE_DIR/config-templates/$CONFIG_TEMPLATE/. ./ # Hashes used instead of slashes because we'll get a value back from $(pwd) that'll be # full of un-escapable slashes. -# Manually directing output to .templated file and then manually renaming back on top -# of the original file because -i is a nonstandard sed feature which is implemented -# differently, across os X and ubuntu at least -sed "s#{{SYNAPSE_ROOT}}#$(pwd)/#g" homeserver.yaml > homeserver.yaml.templated && mv homeserver.yaml.templated homeserver.yaml -sed "s#{{SYNAPSE_PORT}}#${PORT}#g" homeserver.yaml > homeserver.yaml.templated && mv homeserver.yaml.templated homeserver.yaml -sed "s#{{FORM_SECRET}}#$(uuidgen)#g" homeserver.yaml > homeserver.yaml.templated && mv homeserver.yaml.templated homeserver.yaml -sed "s#{{REGISTRATION_SHARED_SECRET}}#$(uuidgen)#g" homeserver.yaml > homeserver.yaml.templated && mv homeserver.yaml.templated homeserver.yaml -sed "s#{{MACAROON_SECRET_KEY}}#$(uuidgen)#g" homeserver.yaml > homeserver.yaml.templated && mv homeserver.yaml.templated homeserver.yaml +# Use .bak suffix as using no suffix doesn't work macOS. +sed -i.bak "s#{{SYNAPSE_ROOT}}#$(pwd)/#g" homeserver.yaml +sed -i.bak "s#{{SYNAPSE_PORT}}#${PORT}#g" homeserver.yaml +sed -i.bak "s#{{FORM_SECRET}}#$(uuidgen)#g" homeserver.yaml +sed -i.bak "s#{{REGISTRATION_SHARED_SECRET}}#$(uuidgen)#g" homeserver.yaml +sed -i.bak "s#{{MACAROON_SECRET_KEY}}#$(uuidgen)#g" homeserver.yaml +rm *.bak From 5d4ded05b4e7c91de90f3ea3ab7e4f841d27637d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 3 Apr 2019 16:19:56 +0200 Subject: [PATCH 165/221] use yarn --- install.sh | 2 +- riot/install.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/install.sh b/install.sh index 4008099a3d..e1fed144ce 100755 --- a/install.sh +++ b/install.sh @@ -3,4 +3,4 @@ set -e ./synapse/install.sh ./riot/install.sh -npm install +yarn install diff --git a/riot/install.sh b/riot/install.sh index b89a767446..9422bd225a 100755 --- a/riot/install.sh +++ b/riot/install.sh @@ -30,5 +30,5 @@ unzip -q riot.zip rm riot.zip mv riot-web-${RIOT_BRANCH} riot-web cd riot-web -npm install -npm run build +yarn install +yarn run build From 146549a66a9c2257617591a3613284e8637fcb3d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 3 Apr 2019 16:20:26 +0200 Subject: [PATCH 166/221] keep complexhttpserver installation within riot folder and gitignore leftovers --- .gitignore | 1 + riot/install.sh | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 24cd046858..afca1ddcb3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules *.png +riot/env diff --git a/riot/install.sh b/riot/install.sh index 9422bd225a..59f945d51b 100755 --- a/riot/install.sh +++ b/riot/install.sh @@ -8,23 +8,24 @@ if [ -d $BASE_DIR/riot-web ]; then exit fi +cd $BASE_DIR # Install ComplexHttpServer (a drop in replacement for Python's SimpleHttpServer # but with support for multiple threads) into a virtualenv. ( - virtualenv $BASE_DIR/env - source $BASE_DIR/env/bin/activate + virtualenv env + source env/bin/activate # Having been bitten by pip SSL fail too many times, I don't trust the existing pip # to be able to --upgrade itself, so grab a new one fresh from source. curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py python get-pip.py + rm get-pip.py pip install ComplexHttpServer deactivate ) -cd $BASE_DIR curl -L https://github.com/vector-im/riot-web/archive/${RIOT_BRANCH}.zip --output riot.zip unzip -q riot.zip rm riot.zip From d93e6edb1dc2cf96f1ea2d5f074e8999c2ae6475 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 3 Apr 2019 17:01:49 +0200 Subject: [PATCH 167/221] use python3 to install riot webserver --- riot/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/riot/install.sh b/riot/install.sh index 59f945d51b..e8eef9427f 100755 --- a/riot/install.sh +++ b/riot/install.sh @@ -12,7 +12,7 @@ cd $BASE_DIR # Install ComplexHttpServer (a drop in replacement for Python's SimpleHttpServer # but with support for multiple threads) into a virtualenv. ( - virtualenv env + virtualenv -p python3 env source env/bin/activate # Having been bitten by pip SSL fail too many times, I don't trust the existing pip From 04e06c3cfaff402f137e4ac9a579f0d145a8439f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 4 Apr 2019 10:20:25 +0200 Subject: [PATCH 168/221] PR feedback --- src/usecases/signup.js | 2 +- src/usecases/verify.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/usecases/signup.js b/src/usecases/signup.js index 1b9ba2872c..2522346970 100644 --- a/src/usecases/signup.js +++ b/src/usecases/signup.js @@ -35,7 +35,7 @@ module.exports = async function signup(session, username, password, homeserver) await session.replaceInputText(usernameField, username); await session.replaceInputText(passwordField, password); await session.replaceInputText(passwordRepeatField, password); - //wait over a second because Registration/ServerConfig have a 250ms + //wait 300ms because Registration/ServerConfig have a 250ms //delay to internally set the homeserver url //see Registration::render and ServerConfig::props::delayTimeMs await session.delay(300); diff --git a/src/usecases/verify.js b/src/usecases/verify.js index e9461c225e..b08f727553 100644 --- a/src/usecases/verify.js +++ b/src/usecases/verify.js @@ -1,5 +1,5 @@ /* -Copyright 2018 New Vector Ltd +Copyright 2019 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. From cfff4a998db3af18e422f86217913ec1f0247cbd Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 5 Apr 2019 16:15:38 +0200 Subject: [PATCH 169/221] install ComplexHttpServer regardless of whether riot is already installed --- riot/install.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/riot/install.sh b/riot/install.sh index e8eef9427f..fda9537652 100755 --- a/riot/install.sh +++ b/riot/install.sh @@ -2,12 +2,6 @@ set -e RIOT_BRANCH=develop -BASE_DIR=$(cd $(dirname $0) && pwd) -if [ -d $BASE_DIR/riot-web ]; then - echo "riot is already installed" - exit -fi - cd $BASE_DIR # Install ComplexHttpServer (a drop in replacement for Python's SimpleHttpServer # but with support for multiple threads) into a virtualenv. @@ -26,6 +20,12 @@ cd $BASE_DIR deactivate ) +BASE_DIR=$(cd $(dirname $0) && pwd) +if [ -d $BASE_DIR/riot-web ]; then + echo "riot is already installed" + exit +fi + curl -L https://github.com/vector-im/riot-web/archive/${RIOT_BRANCH}.zip --output riot.zip unzip -q riot.zip rm riot.zip From 4e1ddf85a4792e76be7921aec3df4f8647fea34d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 5 Apr 2019 16:26:05 +0200 Subject: [PATCH 170/221] c/p error --- riot/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/riot/install.sh b/riot/install.sh index fda9537652..8a942a05ea 100755 --- a/riot/install.sh +++ b/riot/install.sh @@ -2,6 +2,7 @@ set -e RIOT_BRANCH=develop +BASE_DIR=$(cd $(dirname $0) && pwd) cd $BASE_DIR # Install ComplexHttpServer (a drop in replacement for Python's SimpleHttpServer # but with support for multiple threads) into a virtualenv. @@ -20,7 +21,6 @@ cd $BASE_DIR deactivate ) -BASE_DIR=$(cd $(dirname $0) && pwd) if [ -d $BASE_DIR/riot-web ]; then echo "riot is already installed" exit From 53eab479ecd4ffa8a6f53e6d5860bfb84d463a68 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 5 Apr 2019 16:45:07 +0200 Subject: [PATCH 171/221] pass --no-sandbox to puppeteer --- start.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/start.js b/start.js index 1c3f27bbe3..099282740d 100644 --- a/start.js +++ b/start.js @@ -26,6 +26,7 @@ program .option('--windowed', "dont run tests headless", false) .option('--slow-mo', "run tests slower to follow whats going on", false) .option('--dev-tools', "open chrome devtools in browser window", false) + .option('--no-sandbox', "same as puppeteer arg", false) .parse(process.argv); const hsUrl = 'http://localhost:5005'; @@ -37,7 +38,11 @@ async function runTests() { slowMo: program.slowMo ? 20 : undefined, devtools: program.devTools, headless: !program.windowed, + args: [], }; + if (!program.sandbox) { + options.args.push('--no-sandbox'); + } if (process.env.CHROME_PATH) { const path = process.env.CHROME_PATH; console.log(`(using external chrome/chromium at ${path}, make sure it's compatible with puppeteer)`); From 7e2d35fdfe48ee0d5360b987ede78b0a431ca096 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 5 Apr 2019 16:55:59 +0200 Subject: [PATCH 172/221] moar sandbox flags --- start.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/start.js b/start.js index 099282740d..8c8b640595 100644 --- a/start.js +++ b/start.js @@ -41,7 +41,7 @@ async function runTests() { args: [], }; if (!program.sandbox) { - options.args.push('--no-sandbox'); + options.args.push('--no-sandbox', '--disable-setuid-sandbox'); } if (process.env.CHROME_PATH) { const path = process.env.CHROME_PATH; From 492d8106b2a7ee6f06406b5c5c929dff9d2f5d55 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 8 Apr 2019 17:00:19 +0200 Subject: [PATCH 173/221] support writing logs to file --- start.js | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/start.js b/start.js index 8c8b640595..ab710664ec 100644 --- a/start.js +++ b/start.js @@ -18,6 +18,7 @@ const assert = require('assert'); const RiotSession = require('./src/session'); const scenario = require('./src/scenario'); const RestSessionCreator = require('./src/rest/creator'); +const fs = require("fs"); const program = require('commander'); program @@ -27,6 +28,7 @@ program .option('--slow-mo', "run tests slower to follow whats going on", false) .option('--dev-tools', "open chrome devtools in browser window", false) .option('--no-sandbox', "same as puppeteer arg", false) + .option('--error-log ', 'stdout, or a file to dump html and network logs in when the tests fail') .parse(process.argv); const hsUrl = 'http://localhost:5005'; @@ -67,18 +69,13 @@ async function runTests() { } catch(err) { failure = true; console.log('failure: ', err); - if (program.logs) { - for(let i = 0; i < sessions.length; ++i) { - const session = sessions[i]; - documentHtml = await session.page.content(); - console.log(`---------------- START OF ${session.username} LOGS ----------------`); - console.log('---------------- console.log output:'); - console.log(session.consoleLogs()); - console.log('---------------- network requests:'); - console.log(session.networkLogs()); - console.log('---------------- document html:'); - console.log(documentHtml); - console.log(`---------------- END OF ${session.username} LOGS ----------------`); + if (program.errorLog) { + const logs = await createLogs(sessions); + if (program.errorLog === "stdout") { + process.stdout.write(logs); + } else { + console.log(`wrote logs to "${program.errorLog}"`); + fs.writeFileSync(program.errorLog, logs); } } } @@ -98,6 +95,23 @@ async function runTests() { } } +async function createLogs(sessions) { + let logs = ""; + for(let i = 0; i < sessions.length; ++i) { + const session = sessions[i]; + documentHtml = await session.page.content(); + logs = logs + `---------------- START OF ${session.username} LOGS ----------------\n`; + logs = logs + '\n---------------- console.log output:\n'; + logs = logs + session.consoleLogs(); + logs = logs + '\n---------------- network requests:\n'; + logs = logs + session.networkLogs(); + logs = logs + '\n---------------- document html:\n'; + logs = logs + documentHtml; + logs = logs + `\n---------------- END OF ${session.username} LOGS ----------------\n`; + } + return logs; +} + runTests().catch(function(err) { console.log(err); process.exit(-1); From d1df0d98c67e2623d96a7015970ceea60fbc62de Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 9 Apr 2019 14:58:54 +0200 Subject: [PATCH 174/221] avoid ipv6, see if that makes buildkite happy --- synapse/config-templates/consent/homeserver.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse/config-templates/consent/homeserver.yaml b/synapse/config-templates/consent/homeserver.yaml index 7fdf8a887d..e07cf585d8 100644 --- a/synapse/config-templates/consent/homeserver.yaml +++ b/synapse/config-templates/consent/homeserver.yaml @@ -172,7 +172,7 @@ listeners: # - port: {{SYNAPSE_PORT}} tls: false - bind_addresses: ['::1', '127.0.0.1'] + bind_addresses: ['127.0.0.1'] type: http x_forwarded: true From 4591b26dab05dffd5db04187eb792837d858724c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 9 Apr 2019 15:30:20 +0200 Subject: [PATCH 175/221] show all of create rest user command output on failure --- src/rest/creator.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rest/creator.js b/src/rest/creator.js index cc87134108..35f20badb2 100644 --- a/src/rest/creator.js +++ b/src/rest/creator.js @@ -59,10 +59,10 @@ module.exports = class RestSessionCreator { try { await exec(allCmds, {cwd: this.cwd, encoding: 'utf-8'}); } catch (result) { - const lines = result.stdout.trim().split('\n'); - const failureReason = lines[lines.length - 1]; - const logs = (await exec("tail -n 100 synapse/installations/consent/homeserver.log")).stdout; - throw new Error(`creating user ${username} failed: ${failureReason}, synapse logs:\n${logs}`); + // const lines = result.stdout.trim().split('\n'); + // const failureReason = lines[lines.length - 1]; + // const logs = (await exec("tail -n 100 synapse/installations/consent/homeserver.log")).stdout; + throw new Error(`creating user ${username} failed, script output:\n ${result.stdout.trim()}`); } } From b88dc0ffd5dc492c44e508b65c3a10e81fbb54b4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 9 Apr 2019 15:45:16 +0200 Subject: [PATCH 176/221] show browser version when running tests --- src/scenario.js | 6 ++++++ start.js | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/scenario.js b/src/scenario.js index 6176d3a171..cd818fd7bc 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -22,8 +22,14 @@ const lazyLoadingScenarios = require('./scenarios/lazy-loading'); const e2eEncryptionScenarios = require('./scenarios/e2e-encryption'); module.exports = async function scenario(createSession, restCreator) { + let firstUser = true; async function createUser(username) { const session = await createSession(username); + if (firstUser) { + // only show browser version for first browser opened + console.log(`running tests on ${await session.browser.version()} ...`); + firstUser = false; + } await signup(session, session.username, 'testtest', session.hsUrl); return session; } diff --git a/start.js b/start.js index ab710664ec..87a21d565c 100644 --- a/start.js +++ b/start.js @@ -35,7 +35,6 @@ const hsUrl = 'http://localhost:5005'; async function runTests() { let sessions = []; - console.log("running tests ..."); const options = { slowMo: program.slowMo ? 20 : undefined, devtools: program.devTools, From 4c79e3bd0d589aed868b2a46a30fedff2cb56d2e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 9 Apr 2019 15:59:08 +0200 Subject: [PATCH 177/221] better error handling when creating rest user fails --- src/rest/creator.js | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/rest/creator.js b/src/rest/creator.js index 35f20badb2..ca414c097c 100644 --- a/src/rest/creator.js +++ b/src/rest/creator.js @@ -14,12 +14,23 @@ See the License for the specific language governing permissions and limitations under the License. */ -const util = require('util'); -const exec = util.promisify(require('child_process').exec); +const {exec} = require('child_process'); const request = require('request-promise-native'); const RestSession = require('./session'); const RestMultiSession = require('./multi'); +function execAsync(command, options) { + return new Promise((resolve, reject) => { + exec(command, options, (error, stdout, stderr) => { + if (error) { + reject(error); + } else { + resolve({stdout, stderr}); + } + }); + }); +} + module.exports = class RestSessionCreator { constructor(synapseSubdir, hsUrl, cwd) { this.synapseSubdir = synapseSubdir; @@ -56,14 +67,7 @@ module.exports = class RestSessionCreator { registerCmd ].join(';'); - try { - await exec(allCmds, {cwd: this.cwd, encoding: 'utf-8'}); - } catch (result) { - // const lines = result.stdout.trim().split('\n'); - // const failureReason = lines[lines.length - 1]; - // const logs = (await exec("tail -n 100 synapse/installations/consent/homeserver.log")).stdout; - throw new Error(`creating user ${username} failed, script output:\n ${result.stdout.trim()}`); - } + await execAsync(allCmds, {cwd: this.cwd, encoding: 'utf-8'}); } async _authenticate(username, password) { From 7fbfe3159a22857ddb09d8706c577a39dfd2b017 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 9 Apr 2019 16:19:44 +0200 Subject: [PATCH 178/221] dont assume bash when creating rest users --- src/rest/creator.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rest/creator.js b/src/rest/creator.js index ca414c097c..5330f88bbf 100644 --- a/src/rest/creator.js +++ b/src/rest/creator.js @@ -63,9 +63,9 @@ module.exports = class RestSessionCreator { const registerCmd = `./scripts/register_new_matrix_user ${registerArgs.join(' ')}`; const allCmds = [ `cd ${this.synapseSubdir}`, - "source env/bin/activate", + ". env/bin/activate", registerCmd - ].join(';'); + ].join('&&'); await execAsync(allCmds, {cwd: this.cwd, encoding: 'utf-8'}); } From 200f95b31221e5b6df0f7558651d23748d118904 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 9 Apr 2019 16:32:07 +0200 Subject: [PATCH 179/221] rest users dont need to be admin --- src/rest/creator.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rest/creator.js b/src/rest/creator.js index 5330f88bbf..2c15bae792 100644 --- a/src/rest/creator.js +++ b/src/rest/creator.js @@ -56,8 +56,7 @@ module.exports = class RestSessionCreator { '-c homeserver.yaml', `-u ${username}`, `-p ${password}`, - // '--regular-user', - '-a', //until PR gets merged + '--no-admin', this.hsUrl ]; const registerCmd = `./scripts/register_new_matrix_user ${registerArgs.join(' ')}`; From d978ce6b48f3d8de32ed233c8427a37dfa2a7239 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 9 Apr 2019 16:34:03 +0200 Subject: [PATCH 180/221] test upload artefacts on failure --- src/scenario.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scenario.js b/src/scenario.js index cd818fd7bc..fc5f62773b 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -42,6 +42,7 @@ module.exports = async function scenario(createSession, restCreator) { console.log("create REST users:"); const charlies = await createRestUsers(restCreator); await lazyLoadingScenarios(alice, bob, charlies); + throw new Error("f41l!!!"); } async function createRestUsers(restCreator) { From 6958fdb6e113bb7b0e5684ee383ffae028def666 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 9 Apr 2019 17:15:25 +0200 Subject: [PATCH 181/221] write html, console and network logs to different files --- start.js | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/start.js b/start.js index 87a21d565c..9bd78db4f8 100644 --- a/start.js +++ b/start.js @@ -28,7 +28,7 @@ program .option('--slow-mo', "run tests slower to follow whats going on", false) .option('--dev-tools', "open chrome devtools in browser window", false) .option('--no-sandbox', "same as puppeteer arg", false) - .option('--error-log ', 'stdout, or a file to dump html and network logs in when the tests fail') + .option('--log-directory ', 'a directory to dump html and network logs in when the tests fail') .parse(process.argv); const hsUrl = 'http://localhost:5005'; @@ -68,14 +68,8 @@ async function runTests() { } catch(err) { failure = true; console.log('failure: ', err); - if (program.errorLog) { - const logs = await createLogs(sessions); - if (program.errorLog === "stdout") { - process.stdout.write(logs); - } else { - console.log(`wrote logs to "${program.errorLog}"`); - fs.writeFileSync(program.errorLog, logs); - } + if (program.logDirectory) { + await writeLogs(sessions, program.logDirectory); } } @@ -94,19 +88,19 @@ async function runTests() { } } -async function createLogs(sessions) { +async function writeLogs(sessions, dir) { let logs = ""; for(let i = 0; i < sessions.length; ++i) { const session = sessions[i]; + const userLogDir = `${dir}/${session.username}`; + fs.mkdirSync(userLogDir); + const consoleLogName = `${userLogDir}/console.log`; + const networkLogName = `${userLogDir}/network.log`; + const appHtmlName = `${userLogDir}/app.html`; documentHtml = await session.page.content(); - logs = logs + `---------------- START OF ${session.username} LOGS ----------------\n`; - logs = logs + '\n---------------- console.log output:\n'; - logs = logs + session.consoleLogs(); - logs = logs + '\n---------------- network requests:\n'; - logs = logs + session.networkLogs(); - logs = logs + '\n---------------- document html:\n'; - logs = logs + documentHtml; - logs = logs + `\n---------------- END OF ${session.username} LOGS ----------------\n`; + fs.writeFileSync(appHtmlName, documentHtml); + fs.writeFileSync(networkLogName, session.networkLogs()); + fs.writeFileSync(consoleLogName, session.consoleLogs()); } return logs; } From f55a4487391e5eabfdefc0b9b14f5d5302b04dfc Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 9 Apr 2019 17:43:34 +0200 Subject: [PATCH 182/221] add screenshots to logs directory upon failure --- start.js | 1 + 1 file changed, 1 insertion(+) diff --git a/start.js b/start.js index 9bd78db4f8..43988095f9 100644 --- a/start.js +++ b/start.js @@ -101,6 +101,7 @@ async function writeLogs(sessions, dir) { fs.writeFileSync(appHtmlName, documentHtml); fs.writeFileSync(networkLogName, session.networkLogs()); fs.writeFileSync(consoleLogName, session.consoleLogs()); + await session.page.screenshot({path: `${userLogDir}/screenshot.png`}); } return logs; } From b0fb36dbb21c015fcfed772895342ed74b59a247 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 9 Apr 2019 18:09:18 +0200 Subject: [PATCH 183/221] remove debug error --- src/scenario.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/scenario.js b/src/scenario.js index fc5f62773b..cd818fd7bc 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -42,7 +42,6 @@ module.exports = async function scenario(createSession, restCreator) { console.log("create REST users:"); const charlies = await createRestUsers(restCreator); await lazyLoadingScenarios(alice, bob, charlies); - throw new Error("f41l!!!"); } async function createRestUsers(restCreator) { From be32414214d83f1f92b4e865fd4b7bc85693ee8e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 10 Apr 2019 14:32:13 +0200 Subject: [PATCH 184/221] fix broken selector --- src/usecases/invite.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/usecases/invite.js b/src/usecases/invite.js index bbfc38cd23..03dff7b30f 100644 --- a/src/usecases/invite.js +++ b/src/usecases/invite.js @@ -21,7 +21,7 @@ module.exports = async function invite(session, userId) { await session.delay(1000); const inviteButton = await session.waitAndQuery(".mx_MemberList_invite"); await inviteButton.click(); - const inviteTextArea = await session.waitAndQuery(".mx_ChatInviteDialog textarea"); + const inviteTextArea = await session.waitAndQuery(".mx_AddressPickerDialog textarea"); await inviteTextArea.type(userId); await inviteTextArea.press("Enter"); const confirmButton = await session.query(".mx_Dialog_primary"); From b01e126433d8d366a1f44314752eb2920a54a786 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 16 Apr 2019 10:30:14 +0200 Subject: [PATCH 185/221] wait longer to arrive at #home but poll every 100ms --- src/session.js | 13 +++++++++++++ src/usecases/signup.js | 8 +++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/session.js b/src/session.js index 1363185753..126112d7c7 100644 --- a/src/session.js +++ b/src/session.js @@ -201,4 +201,17 @@ module.exports = class RiotSession { close() { return this.browser.close(); } + + async poll(callback, timeout) { + const INTERVAL = 100; + let waited = 0; + while(waited < timeout) { + await this.delay(INTERVAL); + waited += INTERVAL; + if (callback()) { + return true; + } + } + return false; + } } diff --git a/src/usecases/signup.js b/src/usecases/signup.js index 2522346970..1f9605c30e 100644 --- a/src/usecases/signup.js +++ b/src/usecases/signup.js @@ -64,9 +64,11 @@ module.exports = async function signup(session, username, password, homeserver) //wait for registration to finish so the hash gets set //onhashchange better? - await session.delay(2000); - const url = session.page.url(); - assert.strictEqual(url, session.url('/#/home')); + const foundHomeUrl = await session.poll(() => { + const url = session.page.url(); + return url === session.url('/#/home'); + }, 5000); + assert(foundHomeUrl); session.log.done(); } From f489f55fd7708c955b78dff5eb9c84e8bf76cda7 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 16 Apr 2019 11:09:34 +0200 Subject: [PATCH 186/221] increase dialog timeout a bit --- src/usecases/dialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/usecases/dialog.js b/src/usecases/dialog.js index da71f6b61f..10b343128a 100644 --- a/src/usecases/dialog.js +++ b/src/usecases/dialog.js @@ -32,7 +32,7 @@ async function acceptDialog(session, expectedTitle) { async function acceptDialogMaybe(session, expectedTitle) { let primaryButton = null; try { - primaryButton = await session.waitAndQuery(".mx_Dialog .mx_Dialog_primary", 50); + primaryButton = await session.waitAndQuery(".mx_Dialog .mx_Dialog_primary", 100); } catch(err) { return false; } From 945daf294c411d597516dad37dcf484f9b9074b3 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 16 Apr 2019 11:14:55 +0200 Subject: [PATCH 187/221] log messages in timeline when expected not found (debug code) --- src/usecases/timeline.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/usecases/timeline.js b/src/usecases/timeline.js index 3298464c8d..71e47fbfbb 100644 --- a/src/usecases/timeline.js +++ b/src/usecases/timeline.js @@ -87,7 +87,12 @@ module.exports.checkTimelineContains = async function (session, expectedMessages return message.sender === expectedMessage.sender && message.body === expectedMessage.body; }); - assertMessage(foundMessage, expectedMessage); + try { + assertMessage(foundMessage, expectedMessage); + } catch(err) { + console.log("timelineMessages", timelineMessages); + throw err; + } }); session.log.done(); From 9610e9b57e25e4da6f41e070b43ff0cc50c7287c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 16 Apr 2019 11:43:32 +0200 Subject: [PATCH 188/221] take into account continuation tiles in when checking timeline messages --- src/usecases/timeline.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/usecases/timeline.js b/src/usecases/timeline.js index 71e47fbfbb..5556660e1c 100644 --- a/src/usecases/timeline.js +++ b/src/usecases/timeline.js @@ -81,7 +81,16 @@ module.exports.checkTimelineContains = async function (session, expectedMessages return getMessageFromEventTile(eventTile); })); //filter out tiles that were not messages - timelineMessages = timelineMessages .filter((m) => !!m); + timelineMessages = timelineMessages.filter((m) => !!m); + timelineMessages.reduce((prevSender, m) => { + if (m.continuation) { + m.sender = prevSender; + return prevSender; + } else { + return m.sender; + } + }); + expectedMessages.forEach((expectedMessage) => { const foundMessage = timelineMessages.find((message) => { return message.sender === expectedMessage.sender && @@ -132,6 +141,7 @@ async function getMessageFromEventTile(eventTile) { return { sender, body, - encrypted: classNames.includes("mx_EventTile_verified") + encrypted: classNames.includes("mx_EventTile_verified"), + continuation: classNames.includes("mx_EventTile_continuation"), }; } From 20c3023b9405f3358a9038bc36da7d3d92118731 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 16 Apr 2019 11:55:33 +0200 Subject: [PATCH 189/221] use session.poll as well for polling when receiving a message --- src/session.js | 9 ++++----- src/usecases/signup.js | 2 +- src/usecases/timeline.js | 22 +++++++++------------- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/session.js b/src/session.js index 126112d7c7..e1d0daf7fe 100644 --- a/src/session.js +++ b/src/session.js @@ -202,13 +202,12 @@ module.exports = class RiotSession { return this.browser.close(); } - async poll(callback, timeout) { - const INTERVAL = 100; + async poll(callback, timeout, interval = 100) { let waited = 0; while(waited < timeout) { - await this.delay(INTERVAL); - waited += INTERVAL; - if (callback()) { + await this.delay(interval); + waited += interval; + if (await callback()) { return true; } } diff --git a/src/usecases/signup.js b/src/usecases/signup.js index 1f9605c30e..17b9669e6c 100644 --- a/src/usecases/signup.js +++ b/src/usecases/signup.js @@ -65,7 +65,7 @@ module.exports = async function signup(session, username, password, homeserver) //wait for registration to finish so the hash gets set //onhashchange better? - const foundHomeUrl = await session.poll(() => { + const foundHomeUrl = await session.poll(async () => { const url = session.page.url(); return url === session.url('/#/home'); }, 5000); diff --git a/src/usecases/timeline.js b/src/usecases/timeline.js index 5556660e1c..7b28328e49 100644 --- a/src/usecases/timeline.js +++ b/src/usecases/timeline.js @@ -52,22 +52,18 @@ module.exports.receiveMessage = async function(session, expectedMessage) { return getMessageFromEventTile(lastTile); } - let lastMessage = null; - let isExpectedMessage = false; - let totalTime = 0; - while (!isExpectedMessage) { + let lastMessage; + await session.poll(async () => { try { lastMessage = await getLastMessage(); - isExpectedMessage = lastMessage && - lastMessage.body === expectedMessage.body && - lastMessage.sender === expectedMessage.sender - } catch(err) {} - if (totalTime > 5000) { - throw new Error("timed out after 5000ms"); + } catch(err) { + return false; } - totalTime += 200; - await session.delay(200); - } + // stop polling when found the expected message + return lastMessage && + lastMessage.body === expectedMessage.body && + lastMessage.sender === expectedMessage.sender; + }, 5000, 200); assertMessage(lastMessage, expectedMessage); session.log.done(); } From ee46c2b030d9806a6f7e1fac06cc8d471aed266f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 16 Apr 2019 12:07:59 +0200 Subject: [PATCH 190/221] wait for opponent label to appear in sas dialog --- src/usecases/verify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/usecases/verify.js b/src/usecases/verify.js index b08f727553..233bf4f954 100644 --- a/src/usecases/verify.js +++ b/src/usecases/verify.js @@ -53,7 +53,7 @@ module.exports.startSasVerifcation = async function(session, name) { module.exports.acceptSasVerification = async function(session, name) { await assertDialog(session, "Incoming Verification Request"); - const opponentLabelElement = await session.query(".mx_IncomingSasDialog_opponentProfile h2"); + const opponentLabelElement = await session.waitAndQuery(".mx_IncomingSasDialog_opponentProfile h2"); const opponentLabel = await session.innerText(opponentLabelElement); assert(opponentLabel, name); // click "Continue" button From ef59c59f37a90acd8f6f8eac2b6e5762d2fadec3 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 16 Apr 2019 12:22:48 +0200 Subject: [PATCH 191/221] poll these as well as ci is slowwww --- src/usecases/signup.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/usecases/signup.js b/src/usecases/signup.js index 17b9669e6c..0bfdb27a58 100644 --- a/src/usecases/signup.js +++ b/src/usecases/signup.js @@ -23,9 +23,9 @@ module.exports = async function signup(session, username, password, homeserver) if (homeserver) { const changeServerDetailsLink = await session.waitAndQuery('.mx_AuthBody_editServerDetails'); await changeServerDetailsLink.click(); - const hsInputField = await session.query('#mx_ServerConfig_hsUrl'); + const hsInputField = await session.waitAndQuery('#mx_ServerConfig_hsUrl'); await session.replaceInputText(hsInputField, homeserver); - const nextButton = await session.query('.mx_Login_submit'); + const nextButton = await session.waitAndQuery('.mx_Login_submit'); await nextButton.click(); } //fill out form @@ -41,7 +41,7 @@ module.exports = async function signup(session, username, password, homeserver) await session.delay(300); /// focus on the button to make sure error validation /// has happened before checking the form is good to go - const registerButton = await session.query('.mx_Login_submit'); + const registerButton = await session.waitAndQuery('.mx_Login_submit'); await registerButton.focus(); //check no errors const error_text = await session.tryGetInnertext('.mx_Login_error'); From c40f7f6a3c90feb58eeb55e17a9f13a37e33fd34 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 16 Apr 2019 14:59:35 +0200 Subject: [PATCH 192/221] add flag to throttle cpu to get failures because of slow ci locally --- src/session.js | 7 ++++++- start.js | 5 +++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/session.js b/src/session.js index e1d0daf7fe..80e3225f1f 100644 --- a/src/session.js +++ b/src/session.js @@ -35,13 +35,18 @@ module.exports = class RiotSession { this.log = new Logger(this.username); } - static async create(username, puppeteerOptions, riotserver, hsUrl) { + static async create(username, puppeteerOptions, riotserver, hsUrl, throttleCpuFactor = 1) { const browser = await puppeteer.launch(puppeteerOptions); const page = await browser.newPage(); await page.setViewport({ width: 1280, height: 800 }); + if (throttleCpuFactor !== 1) { + const client = await page.target().createCDPSession(); + console.log("throttling cpu by a factor of", throttleCpuFactor); + await client.send('Emulation.setCPUThrottlingRate', { rate: throttleCpuFactor }); + } return new RiotSession(browser, page, username, riotserver, hsUrl); } diff --git a/start.js b/start.js index 43988095f9..b98dc7f366 100644 --- a/start.js +++ b/start.js @@ -25,8 +25,9 @@ program .option('--no-logs', "don't output logs, document html on error", false) .option('--riot-url [url]', "riot url to test", "http://localhost:5000") .option('--windowed', "dont run tests headless", false) - .option('--slow-mo', "run tests slower to follow whats going on", false) + .option('--slow-mo', "type at a human speed", false) .option('--dev-tools', "open chrome devtools in browser window", false) + .option('--throttle-cpu [factor]', "factor to slow down the cpu with", parseFloat, 1.0) .option('--no-sandbox', "same as puppeteer arg", false) .option('--log-directory ', 'a directory to dump html and network logs in when the tests fail') .parse(process.argv); @@ -57,7 +58,7 @@ async function runTests() { ); async function createSession(username) { - const session = await RiotSession.create(username, options, program.riotUrl, hsUrl); + const session = await RiotSession.create(username, options, program.riotUrl, hsUrl, program.throttleCpu); sessions.push(session); return session; } From 48c1d46aa71b2d6278065a9f6c2c8fc8792cc834 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 16 Apr 2019 15:35:31 +0200 Subject: [PATCH 193/221] remove explicit timeouts from tests for selectors - gets rid of the waitAndQuery vs query distinction, all queries now wait if needed (called query and queryAll) - remove explicit timeouts, as they depend on the speed of the machine the tests run on --- src/session.js | 24 +++++++++++------------- src/usecases/accept-invite.js | 4 ++-- src/usecases/create-room.js | 8 ++++---- src/usecases/dialog.js | 4 ++-- src/usecases/invite.js | 4 ++-- src/usecases/join.js | 8 ++++---- src/usecases/memberlist.js | 12 ++++++------ src/usecases/room-settings.js | 10 +++++----- src/usecases/send-message.js | 4 ++-- src/usecases/settings.js | 10 +++++----- src/usecases/signup.js | 20 ++++++++++---------- src/usecases/verify.js | 8 ++++---- 12 files changed, 57 insertions(+), 59 deletions(-) diff --git a/src/session.js b/src/session.js index 80e3225f1f..4fe882c97d 100644 --- a/src/session.js +++ b/src/session.js @@ -19,6 +19,8 @@ const Logger = require('./logger'); const LogBuffer = require('./logbuffer'); const {delay} = require('./util'); +const DEFAULT_TIMEOUT = 20000; + module.exports = class RiotSession { constructor(browser, page, username, riotserver, hsUrl) { this.browser = browser; @@ -113,23 +115,18 @@ module.exports = class RiotSession { } query(selector) { - return this.page.$(selector); - } - - waitAndQuery(selector, timeout = 5000) { + const timeout = DEFAULT_TIMEOUT; return this.page.waitForSelector(selector, {visible: true, timeout}); } - queryAll(selector) { - return this.page.$$(selector); + async queryAll(selector) { + const timeout = DEFAULT_TIMEOUT; + await this.query(selector, timeout); + return await this.page.$$(selector); } - async waitAndQueryAll(selector, timeout = 5000) { - await this.waitAndQuery(selector, timeout); - return await this.queryAll(selector); - } - - waitForReload(timeout = 10000) { + waitForReload() { + const timeout = DEFAULT_TIMEOUT; return new Promise((resolve, reject) => { const timeoutHandle = setTimeout(() => { this.browser.removeEventListener('domcontentloaded', callback); @@ -145,7 +142,8 @@ module.exports = class RiotSession { }); } - waitForNewPage(timeout = 5000) { + waitForNewPage() { + const timeout = DEFAULT_TIMEOUT; return new Promise((resolve, reject) => { const timeoutHandle = setTimeout(() => { this.browser.removeListener('targetcreated', callback); diff --git a/src/usecases/accept-invite.js b/src/usecases/accept-invite.js index 03fbac28fa..3676f6641b 100644 --- a/src/usecases/accept-invite.js +++ b/src/usecases/accept-invite.js @@ -20,7 +20,7 @@ const {acceptDialogMaybe} = require('./dialog'); module.exports = async function acceptInvite(session, name) { session.log.step(`accepts "${name}" invite`); //TODO: brittle selector - const invitesHandles = await session.waitAndQueryAll('.mx_RoomTile_name.mx_RoomTile_invite'); + const invitesHandles = await session.queryAll('.mx_RoomTile_name.mx_RoomTile_invite'); const invitesWithText = await Promise.all(invitesHandles.map(async (inviteHandle) => { const text = await session.innerText(inviteHandle); return {inviteHandle, text}; @@ -31,7 +31,7 @@ module.exports = async function acceptInvite(session, name) { await inviteHandle.click(); - const acceptInvitationLink = await session.waitAndQuery(".mx_RoomPreviewBar_join_text a:first-child"); + const acceptInvitationLink = await session.query(".mx_RoomPreviewBar_join_text a:first-child"); await acceptInvitationLink.click(); session.log.done(); diff --git a/src/usecases/create-room.js b/src/usecases/create-room.js index 16d0620879..4578dbaf0a 100644 --- a/src/usecases/create-room.js +++ b/src/usecases/create-room.js @@ -31,16 +31,16 @@ async function openRoomDirectory(session) { async function createRoom(session, roomName) { session.log.step(`creates room "${roomName}"`); await openRoomDirectory(session); - const createRoomButton = await session.waitAndQuery('.mx_RoomDirectory_createRoom'); + const createRoomButton = await session.query('.mx_RoomDirectory_createRoom'); await createRoomButton.click(); - const roomNameInput = await session.waitAndQuery('.mx_CreateRoomDialog_input'); + const roomNameInput = await session.query('.mx_CreateRoomDialog_input'); await session.replaceInputText(roomNameInput, roomName); - const createButton = await session.waitAndQuery('.mx_Dialog_primary'); + const createButton = await session.query('.mx_Dialog_primary'); await createButton.click(); - await session.waitAndQuery('.mx_MessageComposer'); + await session.query('.mx_MessageComposer'); session.log.done(); } diff --git a/src/usecases/dialog.js b/src/usecases/dialog.js index 10b343128a..58f135de04 100644 --- a/src/usecases/dialog.js +++ b/src/usecases/dialog.js @@ -17,7 +17,7 @@ limitations under the License. const assert = require('assert'); async function assertDialog(session, expectedTitle) { - const titleElement = await session.waitAndQuery(".mx_Dialog .mx_Dialog_title"); + const titleElement = await session.query(".mx_Dialog .mx_Dialog_title"); const dialogHeader = await session.innerText(titleElement); assert(dialogHeader, expectedTitle); } @@ -32,7 +32,7 @@ async function acceptDialog(session, expectedTitle) { async function acceptDialogMaybe(session, expectedTitle) { let primaryButton = null; try { - primaryButton = await session.waitAndQuery(".mx_Dialog .mx_Dialog_primary", 100); + primaryButton = await session.query(".mx_Dialog .mx_Dialog_primary"); } catch(err) { return false; } diff --git a/src/usecases/invite.js b/src/usecases/invite.js index 03dff7b30f..7b3f8a2b48 100644 --- a/src/usecases/invite.js +++ b/src/usecases/invite.js @@ -19,9 +19,9 @@ const assert = require('assert'); module.exports = async function invite(session, userId) { session.log.step(`invites "${userId}" to room`); await session.delay(1000); - const inviteButton = await session.waitAndQuery(".mx_MemberList_invite"); + const inviteButton = await session.query(".mx_MemberList_invite"); await inviteButton.click(); - const inviteTextArea = await session.waitAndQuery(".mx_AddressPickerDialog textarea"); + const inviteTextArea = await session.query(".mx_AddressPickerDialog textarea"); await inviteTextArea.type(userId); await inviteTextArea.press("Enter"); const confirmButton = await session.query(".mx_Dialog_primary"); diff --git a/src/usecases/join.js b/src/usecases/join.js index cba9e06660..cf23fbfb64 100644 --- a/src/usecases/join.js +++ b/src/usecases/join.js @@ -20,15 +20,15 @@ const {openRoomDirectory} = require('./create-room'); module.exports = async function join(session, roomName) { session.log.step(`joins room "${roomName}"`); await openRoomDirectory(session); - const roomInput = await session.waitAndQuery('.mx_DirectorySearchBox input'); + const roomInput = await session.query('.mx_DirectorySearchBox input'); await session.replaceInputText(roomInput, roomName); - const firstRoomLabel = await session.waitAndQuery('.mx_RoomDirectory_table .mx_RoomDirectory_name:first-child', 1000); + const firstRoomLabel = await session.query('.mx_RoomDirectory_table .mx_RoomDirectory_name:first-child'); await firstRoomLabel.click(); - const joinLink = await session.waitAndQuery('.mx_RoomPreviewBar_join_text a'); + const joinLink = await session.query('.mx_RoomPreviewBar_join_text a'); await joinLink.click(); - await session.waitAndQuery('.mx_MessageComposer'); + await session.query('.mx_MessageComposer'); session.log.done(); } diff --git a/src/usecases/memberlist.js b/src/usecases/memberlist.js index 0cd8744853..5858e82bb8 100644 --- a/src/usecases/memberlist.js +++ b/src/usecases/memberlist.js @@ -34,20 +34,20 @@ module.exports.verifyDeviceForUser = async function(session, name, expectedDevic }).map((m) => m.label)[0]; await matchingLabel.click(); // click verify in member info - const firstVerifyButton = await session.waitAndQuery(".mx_MemberDeviceInfo_verify"); + const firstVerifyButton = await session.query(".mx_MemberDeviceInfo_verify"); await firstVerifyButton.click(); // expect "Verify device" dialog and click "Begin Verification" - const dialogHeader = await session.innerText(await session.waitAndQuery(".mx_Dialog .mx_Dialog_title")); + const dialogHeader = await session.innerText(await session.query(".mx_Dialog .mx_Dialog_title")); assert(dialogHeader, "Verify device"); - const beginVerificationButton = await session.waitAndQuery(".mx_Dialog .mx_Dialog_primary") + const beginVerificationButton = await session.query(".mx_Dialog .mx_Dialog_primary") await beginVerificationButton.click(); // get emoji SAS labels - const sasLabelElements = await session.waitAndQueryAll(".mx_VerificationShowSas .mx_VerificationShowSas_emojiSas .mx_VerificationShowSas_emojiSas_label"); + const sasLabelElements = await session.queryAll(".mx_VerificationShowSas .mx_VerificationShowSas_emojiSas .mx_VerificationShowSas_emojiSas_label"); const sasLabels = await Promise.all(sasLabelElements.map(e => session.innerText(e))); console.log("my sas labels", sasLabels); - const dialogCodeFields = await session.waitAndQueryAll(".mx_QuestionDialog code"); + const dialogCodeFields = await session.queryAll(".mx_QuestionDialog code"); assert.equal(dialogCodeFields.length, 2); const deviceId = await session.innerText(dialogCodeFields[0]); const deviceKey = await session.innerText(dialogCodeFields[1]); @@ -61,7 +61,7 @@ module.exports.verifyDeviceForUser = async function(session, name, expectedDevic } async function getMembersInMemberlist(session) { - const memberNameElements = await session.waitAndQueryAll(".mx_MemberList .mx_EntityTile_name"); + const memberNameElements = await session.queryAll(".mx_MemberList .mx_EntityTile_name"); return Promise.all(memberNameElements.map(async (el) => { return {label: el, displayName: await session.innerText(el)}; })); diff --git a/src/usecases/room-settings.js b/src/usecases/room-settings.js index 95078d1c87..bf3e60d490 100644 --- a/src/usecases/room-settings.js +++ b/src/usecases/room-settings.js @@ -37,11 +37,11 @@ module.exports = async function changeRoomSettings(session, settings) { const settingsButton = await session.query(".mx_RoomHeader .mx_AccessibleButton[title=Settings]"); await settingsButton.click(); //find tabs - const tabButtons = await session.waitAndQueryAll(".mx_RoomSettingsDialog .mx_TabbedView_tabLabel"); + const tabButtons = await session.queryAll(".mx_RoomSettingsDialog .mx_TabbedView_tabLabel"); const tabLabels = await Promise.all(tabButtons.map(t => session.innerText(t))); const securityTabButton = tabButtons[tabLabels.findIndex(l => l.toLowerCase().includes("security"))]; - const generalSwitches = await session.waitAndQueryAll(".mx_RoomSettingsDialog .mx_ToggleSwitch"); + const generalSwitches = await session.queryAll(".mx_RoomSettingsDialog .mx_ToggleSwitch"); const isDirectory = generalSwitches[0]; if (typeof settings.directory === "boolean") { @@ -51,9 +51,9 @@ module.exports = async function changeRoomSettings(session, settings) { if (settings.alias) { session.log.step(`sets alias to ${settings.alias}`); - const aliasField = await session.waitAndQuery(".mx_RoomSettingsDialog .mx_AliasSettings input[type=text]"); + const aliasField = await session.query(".mx_RoomSettingsDialog .mx_AliasSettings input[type=text]"); await session.replaceInputText(aliasField, settings.alias); - const addButton = await session.waitAndQuery(".mx_RoomSettingsDialog .mx_AliasSettings .mx_AccessibleButton"); + const addButton = await session.query(".mx_RoomSettingsDialog .mx_AliasSettings .mx_AccessibleButton"); await addButton.click(); session.log.done(); } @@ -74,7 +74,7 @@ module.exports = async function changeRoomSettings(session, settings) { if (settings.visibility) { session.log.step(`sets visibility to ${settings.visibility}`); - const radios = await session.waitAndQueryAll(".mx_RoomSettingsDialog input[type=radio]"); + const radios = await session.queryAll(".mx_RoomSettingsDialog input[type=radio]"); assert.equal(radios.length, 7); const inviteOnly = radios[0]; const publicNoGuests = radios[1]; diff --git a/src/usecases/send-message.js b/src/usecases/send-message.js index 038171327c..d3bd02cae3 100644 --- a/src/usecases/send-message.js +++ b/src/usecases/send-message.js @@ -20,7 +20,7 @@ module.exports = async function sendMessage(session, message) { session.log.step(`writes "${message}" in room`); // this selector needs to be the element that has contenteditable=true, // not any if its parents, otherwise it behaves flaky at best. - const composer = await session.waitAndQuery('.mx_MessageComposer_editor'); + const composer = await session.query('.mx_MessageComposer_editor'); // sometimes the focus that type() does internally doesn't seem to work // and calling click before seems to fix it 🤷 await composer.click(); @@ -29,6 +29,6 @@ module.exports = async function sendMessage(session, message) { assert.equal(text.trim(), message.trim()); await composer.press("Enter"); // wait for the message to appear sent - await session.waitAndQuery(".mx_EventTile_last:not(.mx_EventTile_sending)"); + await session.query(".mx_EventTile_last:not(.mx_EventTile_sending)"); session.log.done(); } diff --git a/src/usecases/settings.js b/src/usecases/settings.js index 68a290c29d..903524e6b8 100644 --- a/src/usecases/settings.js +++ b/src/usecases/settings.js @@ -19,10 +19,10 @@ const assert = require('assert'); async function openSettings(session, section) { const menuButton = await session.query(".mx_TopLeftMenuButton_name"); await menuButton.click(); - const settingsItem = await session.waitAndQuery(".mx_TopLeftMenu_icon_settings"); + const settingsItem = await session.query(".mx_TopLeftMenu_icon_settings"); await settingsItem.click(); if (section) { - const sectionButton = await session.waitAndQuery(`.mx_UserSettingsDialog .mx_TabbedView_tabLabels .mx_UserSettingsDialog_${section}Icon`); + const sectionButton = await session.query(`.mx_UserSettingsDialog .mx_TabbedView_tabLabels .mx_UserSettingsDialog_${section}Icon`); await sectionButton.click(); } } @@ -31,10 +31,10 @@ module.exports.enableLazyLoading = async function(session) { session.log.step(`enables lazy loading of members in the lab settings`); const settingsButton = await session.query('.mx_BottomLeftMenu_settings'); await settingsButton.click(); - const llCheckbox = await session.waitAndQuery("#feature_lazyloading"); + const llCheckbox = await session.query("#feature_lazyloading"); await llCheckbox.click(); await session.waitForReload(); - const closeButton = await session.waitAndQuery(".mx_RoomHeader_cancelButton"); + const closeButton = await session.query(".mx_RoomHeader_cancelButton"); await closeButton.click(); session.log.done(); } @@ -42,7 +42,7 @@ module.exports.enableLazyLoading = async function(session) { module.exports.getE2EDeviceFromSettings = async function(session) { session.log.step(`gets e2e device/key from settings`); await openSettings(session, "security"); - const deviceAndKey = await session.waitAndQueryAll(".mx_SettingsTab_section .mx_SecurityUserSettingsTab_deviceInfo code"); + const deviceAndKey = await session.queryAll(".mx_SettingsTab_section .mx_SecurityUserSettingsTab_deviceInfo code"); assert.equal(deviceAndKey.length, 2); const id = await (await deviceAndKey[0].getProperty("innerText")).jsonValue(); const key = await (await deviceAndKey[1].getProperty("innerText")).jsonValue(); diff --git a/src/usecases/signup.js b/src/usecases/signup.js index 0bfdb27a58..66bf773ca1 100644 --- a/src/usecases/signup.js +++ b/src/usecases/signup.js @@ -21,17 +21,17 @@ module.exports = async function signup(session, username, password, homeserver) await session.goto(session.url('/#/register')); // change the homeserver by clicking the "Change" link. if (homeserver) { - const changeServerDetailsLink = await session.waitAndQuery('.mx_AuthBody_editServerDetails'); + const changeServerDetailsLink = await session.query('.mx_AuthBody_editServerDetails'); await changeServerDetailsLink.click(); - const hsInputField = await session.waitAndQuery('#mx_ServerConfig_hsUrl'); + const hsInputField = await session.query('#mx_ServerConfig_hsUrl'); await session.replaceInputText(hsInputField, homeserver); - const nextButton = await session.waitAndQuery('.mx_Login_submit'); + const nextButton = await session.query('.mx_Login_submit'); await nextButton.click(); } //fill out form - const usernameField = await session.waitAndQuery("#mx_RegistrationForm_username"); - const passwordField = await session.waitAndQuery("#mx_RegistrationForm_password"); - const passwordRepeatField = await session.waitAndQuery("#mx_RegistrationForm_passwordConfirm"); + const usernameField = await session.query("#mx_RegistrationForm_username"); + const passwordField = await session.query("#mx_RegistrationForm_password"); + const passwordRepeatField = await session.query("#mx_RegistrationForm_passwordConfirm"); await session.replaceInputText(usernameField, username); await session.replaceInputText(passwordField, password); await session.replaceInputText(passwordRepeatField, password); @@ -41,7 +41,7 @@ module.exports = async function signup(session, username, password, homeserver) await session.delay(300); /// focus on the button to make sure error validation /// has happened before checking the form is good to go - const registerButton = await session.waitAndQuery('.mx_Login_submit'); + const registerButton = await session.query('.mx_Login_submit'); await registerButton.focus(); //check no errors const error_text = await session.tryGetInnertext('.mx_Login_error'); @@ -51,15 +51,15 @@ module.exports = async function signup(session, username, password, homeserver) await registerButton.click(); //confirm dialog saying you cant log back in without e-mail - const continueButton = await session.waitAndQuery('.mx_QuestionDialog button.mx_Dialog_primary'); + const continueButton = await session.query('.mx_QuestionDialog button.mx_Dialog_primary'); await continueButton.click(); //find the privacy policy checkbox and check it - const policyCheckbox = await session.waitAndQuery('.mx_InteractiveAuthEntryComponents_termsPolicy input'); + const policyCheckbox = await session.query('.mx_InteractiveAuthEntryComponents_termsPolicy input'); await policyCheckbox.click(); //now click the 'Accept' button to agree to the privacy policy - const acceptButton = await session.waitAndQuery('.mx_InteractiveAuthEntryComponents_termsSubmit'); + const acceptButton = await session.query('.mx_InteractiveAuthEntryComponents_termsSubmit'); await acceptButton.click(); //wait for registration to finish so the hash gets set diff --git a/src/usecases/verify.js b/src/usecases/verify.js index 233bf4f954..323765bebf 100644 --- a/src/usecases/verify.js +++ b/src/usecases/verify.js @@ -19,19 +19,19 @@ const {openMemberInfo} = require("./memberlist"); const {assertDialog, acceptDialog} = require("./dialog"); async function assertVerified(session) { - const dialogSubTitle = await session.innerText(await session.waitAndQuery(".mx_Dialog h2")); + const dialogSubTitle = await session.innerText(await session.query(".mx_Dialog h2")); assert(dialogSubTitle, "Verified!"); } async function startVerification(session, name) { await openMemberInfo(session, name); // click verify in member info - const firstVerifyButton = await session.waitAndQuery(".mx_MemberDeviceInfo_verify"); + const firstVerifyButton = await session.query(".mx_MemberDeviceInfo_verify"); await firstVerifyButton.click(); } async function getSasCodes(session) { - const sasLabelElements = await session.waitAndQueryAll(".mx_VerificationShowSas .mx_VerificationShowSas_emojiSas .mx_VerificationShowSas_emojiSas_label"); + const sasLabelElements = await session.queryAll(".mx_VerificationShowSas .mx_VerificationShowSas_emojiSas .mx_VerificationShowSas_emojiSas_label"); const sasLabels = await Promise.all(sasLabelElements.map(e => session.innerText(e))); return sasLabels; } @@ -53,7 +53,7 @@ module.exports.startSasVerifcation = async function(session, name) { module.exports.acceptSasVerification = async function(session, name) { await assertDialog(session, "Incoming Verification Request"); - const opponentLabelElement = await session.waitAndQuery(".mx_IncomingSasDialog_opponentProfile h2"); + const opponentLabelElement = await session.query(".mx_IncomingSasDialog_opponentProfile h2"); const opponentLabel = await session.innerText(opponentLabelElement); assert(opponentLabel, name); // click "Continue" button From eae830a4d45ad029105cfa92201b6ca6eb90edb3 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 16 Apr 2019 15:39:33 +0200 Subject: [PATCH 194/221] update readme --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4f4f9217e3..81655000a0 100644 --- a/README.md +++ b/README.md @@ -24,14 +24,12 @@ node start.js ``` It's important to always stop and start synapse before each run of the tests to clear the in-memory sqlite database it uses, as the tests assume a blank slate. -start.js accepts the following parameters that can help running the tests locally: +start.js accepts these parameters (and more, see `node start.js --help`) that can help running the tests locally: - - `--no-logs` dont show the excessive logging show by default (meant for CI), just where the test failed. - `--riot-url ` don't use the riot copy and static server provided by the tests, but use a running server like the webpack watch server to run the tests against. Make sure to have the following local config: - - `welcomeUserId` disabled as the tests assume there is no riot-bot currently. Make sure to set the default homeserver to - - `"default_hs_url": "http://localhost:5005"`, to use the e2e tests synapse (the tests use the default HS to run against atm) - - `"feature_lazyloading": "labs"`, currently assumes lazy loading needs to be turned on in the settings, will change soon. - - `--slow-mo` run the tests a bit slower, so it's easier to follow along with `--windowed`. + - `welcomeUserId` disabled as the tests assume there is no riot-bot currently. + - `--slow-mo` type at a human speed, useful with `--windowed`. + - `--throttle-cpu ` throttle cpu in the browser by the given factor. Useful to reproduce failures because of insufficient timeouts happening on the slower CI server. - `--windowed` run the tests in an actual browser window Try to limit interacting with the windows while the tests are running. Hovering over the window tends to fail the tests, dragging the title bar should be fine though. - `--dev-tools` open the devtools in the browser window, only applies if `--windowed` is set as well. From ee1e585301c686cd40537f1afa0843cc6aadfc69 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 16 Apr 2019 15:41:45 +0200 Subject: [PATCH 195/221] remove explicit timeout for polling as well --- src/session.js | 3 ++- src/usecases/signup.js | 2 +- src/usecases/timeline.js | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/session.js b/src/session.js index 4fe882c97d..8e85509b82 100644 --- a/src/session.js +++ b/src/session.js @@ -205,7 +205,8 @@ module.exports = class RiotSession { return this.browser.close(); } - async poll(callback, timeout, interval = 100) { + async poll(callback, interval = 100) { + const timeout = DEFAULT_TIMEOUT; let waited = 0; while(waited < timeout) { await this.delay(interval); diff --git a/src/usecases/signup.js b/src/usecases/signup.js index 66bf773ca1..cc58bff28b 100644 --- a/src/usecases/signup.js +++ b/src/usecases/signup.js @@ -68,7 +68,7 @@ module.exports = async function signup(session, username, password, homeserver) const foundHomeUrl = await session.poll(async () => { const url = session.page.url(); return url === session.url('/#/home'); - }, 5000); + }); assert(foundHomeUrl); session.log.done(); } diff --git a/src/usecases/timeline.js b/src/usecases/timeline.js index 7b28328e49..580d88ee6a 100644 --- a/src/usecases/timeline.js +++ b/src/usecases/timeline.js @@ -63,7 +63,7 @@ module.exports.receiveMessage = async function(session, expectedMessage) { return lastMessage && lastMessage.body === expectedMessage.body && lastMessage.sender === expectedMessage.sender; - }, 5000, 200); + }); assertMessage(lastMessage, expectedMessage); session.log.done(); } From bf0296602af6033c60f48ad2841aba7ee33f4470 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 16 Apr 2019 15:03:12 +0000 Subject: [PATCH 196/221] Update TODO.md --- TODO.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/TODO.md b/TODO.md index 4cbcba801b..f5ccebcf77 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +1,8 @@ -join a peekable room by directory -join a peekable room by invite -join a non-peekable room by directory -join a non-peekable room by invite + + - join a peekable room by directory + - join a peekable room by invite + - join a non-peekable room by directory + - join a non-peekable room by invite + - leave a room and check we move to the "next" room + - get kicked from a room and check that we show the correct message + - get banned " From 20d80e695c5684490032657519961d1f63dff1f5 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Apr 2019 11:44:32 +0200 Subject: [PATCH 197/221] adjust selectors for join and accept button in room preview bar --- src/usecases/accept-invite.js | 2 +- src/usecases/join.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/usecases/accept-invite.js b/src/usecases/accept-invite.js index 3676f6641b..bcecf41ed2 100644 --- a/src/usecases/accept-invite.js +++ b/src/usecases/accept-invite.js @@ -31,7 +31,7 @@ module.exports = async function acceptInvite(session, name) { await inviteHandle.click(); - const acceptInvitationLink = await session.query(".mx_RoomPreviewBar_join_text a:first-child"); + const acceptInvitationLink = await session.query(".mx_RoomPreviewBar_Invite .mx_AccessibleButton_kind_primary"); await acceptInvitationLink.click(); session.log.done(); diff --git a/src/usecases/join.js b/src/usecases/join.js index cf23fbfb64..9bc7007849 100644 --- a/src/usecases/join.js +++ b/src/usecases/join.js @@ -26,7 +26,7 @@ module.exports = async function join(session, roomName) { const firstRoomLabel = await session.query('.mx_RoomDirectory_table .mx_RoomDirectory_name:first-child'); await firstRoomLabel.click(); - const joinLink = await session.query('.mx_RoomPreviewBar_join_text a'); + const joinLink = await session.query('.mx_RoomPreviewBar_ViewingRoom .mx_AccessibleButton_kind_primary'); await joinLink.click(); await session.query('.mx_MessageComposer'); From 2527995e2e1fa51a9752974dc08fe4611fbec260 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 25 Apr 2019 14:23:10 +0100 Subject: [PATCH 198/221] Only install the minimum deps to pass --- synapse/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse/install.sh b/synapse/install.sh index c289b0f0ba..0b1d581118 100755 --- a/synapse/install.sh +++ b/synapse/install.sh @@ -31,7 +31,7 @@ curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py python get-pip.py pip install --upgrade setuptools -pip install matrix-synapse[all] +pip install matrix-synapse[resources.consent] python -m synapse.app.homeserver \ --server-name localhost \ --config-path homeserver.yaml \ From 1ffe0d1a24b52fd1e09cda145239fadb5a513d18 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 25 Apr 2019 14:23:51 +0100 Subject: [PATCH 199/221] Report location of Synpase log --- synapse/start.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/synapse/start.sh b/synapse/start.sh index 379de3850c..f8da097f7d 100755 --- a/synapse/start.sh +++ b/synapse/start.sh @@ -6,6 +6,7 @@ cd $BASE_DIR cd installations/consent source env/bin/activate LOGFILE=$(mktemp) +echo "Synapse log file at $LOGFILE" ./synctl start 2> $LOGFILE EXIT_CODE=$? if [ $EXIT_CODE -ne 0 ]; then From f82f9ecdb20b4730c71c61414abba73e9c88c627 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 25 Apr 2019 14:28:39 +0100 Subject: [PATCH 200/221] Use a stronger password --- src/scenario.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scenario.js b/src/scenario.js index cd818fd7bc..1ad177a4f5 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -30,7 +30,7 @@ module.exports = async function scenario(createSession, restCreator) { console.log(`running tests on ${await session.browser.version()} ...`); firstUser = false; } - await signup(session, session.username, 'testtest', session.hsUrl); + await signup(session, session.username, 'testsarefun!!!', session.hsUrl); return session; } From 3e6719e8abe97fec86520684f16d321b10727e49 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 25 Apr 2019 18:10:41 +0100 Subject: [PATCH 201/221] Wait for password validation --- src/usecases/signup.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/usecases/signup.js b/src/usecases/signup.js index cc58bff28b..a52baea961 100644 --- a/src/usecases/signup.js +++ b/src/usecases/signup.js @@ -43,6 +43,8 @@ module.exports = async function signup(session, username, password, homeserver) /// has happened before checking the form is good to go const registerButton = await session.query('.mx_Login_submit'); await registerButton.focus(); + // Password validation is async, wait for it to complete before submit + await session.query(".mx_Field_valid #mx_RegistrationForm_password"); //check no errors const error_text = await session.tryGetInnertext('.mx_Login_error'); assert.strictEqual(!!error_text, false); From 7a15acf224c193af725ab8c1901f7eea110540dc Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 24 Jun 2019 12:51:05 +0200 Subject: [PATCH 202/221] install synapse/develop (and deps) from pip instead of getting dependencies from synapse/master and running synapse/develop from git download --- synapse/install.sh | 10 +++++----- synapse/start.sh | 4 ++-- synapse/stop.sh | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) mode change 100755 => 100644 synapse/install.sh diff --git a/synapse/install.sh b/synapse/install.sh old mode 100755 new mode 100644 index 0b1d581118..8a9bb82e19 --- a/synapse/install.sh +++ b/synapse/install.sh @@ -16,11 +16,7 @@ if [ -d $BASE_DIR/$SERVER_DIR ]; then fi cd $BASE_DIR - -mkdir -p installations/ -curl https://codeload.github.com/matrix-org/synapse/zip/$SYNAPSE_BRANCH --output synapse.zip -unzip -q synapse.zip -mv synapse-$SYNAPSE_BRANCH $SERVER_DIR +mkdir -p $SERVER_DIR cd $SERVER_DIR virtualenv -p python3 env source env/bin/activate @@ -37,7 +33,9 @@ python -m synapse.app.homeserver \ --config-path homeserver.yaml \ --generate-config \ --report-stats=no +pip install https://codeload.github.com/matrix-org/synapse/zip/$SYNAPSE_BRANCH # apply configuration +pushd env/bin/ cp -r $BASE_DIR/config-templates/$CONFIG_TEMPLATE/. ./ # Hashes used instead of slashes because we'll get a value back from $(pwd) that'll be @@ -49,3 +47,5 @@ sed -i.bak "s#{{FORM_SECRET}}#$(uuidgen)#g" homeserver.yaml sed -i.bak "s#{{REGISTRATION_SHARED_SECRET}}#$(uuidgen)#g" homeserver.yaml sed -i.bak "s#{{MACAROON_SECRET_KEY}}#$(uuidgen)#g" homeserver.yaml rm *.bak + +popd diff --git a/synapse/start.sh b/synapse/start.sh index f8da097f7d..2ff6ae69d0 100755 --- a/synapse/start.sh +++ b/synapse/start.sh @@ -3,8 +3,8 @@ set -e BASE_DIR=$(cd $(dirname $0) && pwd) cd $BASE_DIR -cd installations/consent -source env/bin/activate +cd installations/consent/env/bin/ +source activate LOGFILE=$(mktemp) echo "Synapse log file at $LOGFILE" ./synctl start 2> $LOGFILE diff --git a/synapse/stop.sh b/synapse/stop.sh index a7716744ef..08258e5a8c 100755 --- a/synapse/stop.sh +++ b/synapse/stop.sh @@ -3,6 +3,6 @@ set -e BASE_DIR=$(cd $(dirname $0) && pwd) cd $BASE_DIR -cd installations/consent -source env/bin/activate +cd installations/consent/env/bin/ +source activate ./synctl stop From 894a07484c6738ca7f1ad404f173f92e6ed1006a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 24 Jun 2019 12:51:51 +0200 Subject: [PATCH 203/221] generate signing keys without generating config file and then overwr. it --- synapse/install.sh | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) mode change 100644 => 100755 synapse/install.sh diff --git a/synapse/install.sh b/synapse/install.sh old mode 100644 new mode 100755 index 8a9bb82e19..077258072c --- a/synapse/install.sh +++ b/synapse/install.sh @@ -27,12 +27,6 @@ curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py python get-pip.py pip install --upgrade setuptools -pip install matrix-synapse[resources.consent] -python -m synapse.app.homeserver \ - --server-name localhost \ - --config-path homeserver.yaml \ - --generate-config \ - --report-stats=no pip install https://codeload.github.com/matrix-org/synapse/zip/$SYNAPSE_BRANCH # apply configuration pushd env/bin/ @@ -49,3 +43,5 @@ sed -i.bak "s#{{MACAROON_SECRET_KEY}}#$(uuidgen)#g" homeserver.yaml rm *.bak popd +# generate signing keys for signing_key_path +python -m synapse.app.homeserver --generate-keys --config-dir env/bin/ -c env/bin/homeserver.yaml From d9def18184a0e6c5cfd1058abbde71122d2b6567 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 24 Jun 2019 15:00:51 +0200 Subject: [PATCH 204/221] adjust path to register script for pip installation --- src/rest/creator.js | 6 +++--- start.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rest/creator.js b/src/rest/creator.js index 2c15bae792..5897ae2683 100644 --- a/src/rest/creator.js +++ b/src/rest/creator.js @@ -59,12 +59,12 @@ module.exports = class RestSessionCreator { '--no-admin', this.hsUrl ]; - const registerCmd = `./scripts/register_new_matrix_user ${registerArgs.join(' ')}`; + const registerCmd = `./register_new_matrix_user ${registerArgs.join(' ')}`; const allCmds = [ `cd ${this.synapseSubdir}`, - ". env/bin/activate", + ". activate", registerCmd - ].join('&&'); + ].join(' && '); await execAsync(allCmds, {cwd: this.cwd, encoding: 'utf-8'}); } diff --git a/start.js b/start.js index b98dc7f366..d19b232236 100644 --- a/start.js +++ b/start.js @@ -52,7 +52,7 @@ async function runTests() { } const restCreator = new RestSessionCreator( - 'synapse/installations/consent', + 'synapse/installations/consent/env/bin', hsUrl, __dirname ); From a72934556c60c48734d815d629a839bf2183ecba Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 24 Jun 2019 15:30:18 +0200 Subject: [PATCH 205/221] look for activate in cwd --- src/rest/creator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rest/creator.js b/src/rest/creator.js index 5897ae2683..31c352b31a 100644 --- a/src/rest/creator.js +++ b/src/rest/creator.js @@ -62,7 +62,7 @@ module.exports = class RestSessionCreator { const registerCmd = `./register_new_matrix_user ${registerArgs.join(' ')}`; const allCmds = [ `cd ${this.synapseSubdir}`, - ". activate", + ". ./activate", registerCmd ].join(' && '); From 8a247e0ed748601502f9f951351e0f32a101b4bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2019 15:51:23 +0000 Subject: [PATCH 206/221] Bump lodash from 4.17.11 to 4.17.15 Bumps [lodash](https://github.com/lodash/lodash) from 4.17.11 to 4.17.15. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.11...4.17.15) Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index bdf5608a7e..4379b24946 100644 --- a/yarn.lock +++ b/yarn.lock @@ -425,9 +425,9 @@ jsprim@^1.2.2: verror "1.10.0" lodash@^4.15.0, lodash@^4.17.11: - version "4.17.11" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" - integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== mime-db@~1.38.0: version "1.38.0" From 7635e93896c84459180afb9b7c899e1f81dd0f6d Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 28 Aug 2019 11:41:29 +0100 Subject: [PATCH 207/221] Adjust tests for hidden IS field --- src/usecases/signup.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/usecases/signup.js b/src/usecases/signup.js index a52baea961..e43b47b5cd 100644 --- a/src/usecases/signup.js +++ b/src/usecases/signup.js @@ -27,6 +27,8 @@ module.exports = async function signup(session, username, password, homeserver) await session.replaceInputText(hsInputField, homeserver); const nextButton = await session.query('.mx_Login_submit'); await nextButton.click(); + await session.query('.mx_ServerConfig_identityServer_shown'); + await nextButton.click(); } //fill out form const usernameField = await session.query("#mx_RegistrationForm_username"); From 67b03b5a0f6ae65ead9999f7174e1df56a7862eb Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 10 Sep 2019 16:25:59 +0200 Subject: [PATCH 208/221] Fix signup: set custom hs through advanced section, and accept IS step --- src/usecases/signup.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/usecases/signup.js b/src/usecases/signup.js index a52baea961..b6a6d46311 100644 --- a/src/usecases/signup.js +++ b/src/usecases/signup.js @@ -21,11 +21,15 @@ module.exports = async function signup(session, username, password, homeserver) await session.goto(session.url('/#/register')); // change the homeserver by clicking the "Change" link. if (homeserver) { - const changeServerDetailsLink = await session.query('.mx_AuthBody_editServerDetails'); - await changeServerDetailsLink.click(); + const advancedButton = await session.query('.mx_ServerTypeSelector_type_Advanced'); + await advancedButton.click(); const hsInputField = await session.query('#mx_ServerConfig_hsUrl'); await session.replaceInputText(hsInputField, homeserver); const nextButton = await session.query('.mx_Login_submit'); + // accept homeserver + await nextButton.click(); + await session.delay(200); + // accept discovered identity server await nextButton.click(); } //fill out form From f66f5bedb676c5191c732d7b086937c7b3349eda Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 10 Sep 2019 16:27:02 +0200 Subject: [PATCH 209/221] Adjust how room directory and create room dialog should be opened --- src/usecases/create-room.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/usecases/create-room.js b/src/usecases/create-room.js index 4578dbaf0a..132a4bd782 100644 --- a/src/usecases/create-room.js +++ b/src/usecases/create-room.js @@ -17,6 +17,13 @@ limitations under the License. const assert = require('assert'); async function openRoomDirectory(session) { + const roomDirectoryButton = await session.query('.mx_LeftPanel_explore .mx_AccessibleButton'); + await roomDirectoryButton.click(); +} + +async function createRoom(session, roomName) { + session.log.step(`creates room "${roomName}"`); + const roomListHeaders = await session.queryAll('.mx_RoomSubList_labelContainer'); const roomListHeaderLabels = await Promise.all(roomListHeaders.map(h => session.innerText(h))); const roomsIndex = roomListHeaderLabels.findIndex(l => l.toLowerCase().includes("rooms")); @@ -26,13 +33,7 @@ async function openRoomDirectory(session) { const roomsHeader = roomListHeaders[roomsIndex]; const addRoomButton = await roomsHeader.$(".mx_RoomSubList_addRoom"); await addRoomButton.click(); -} -async function createRoom(session, roomName) { - session.log.step(`creates room "${roomName}"`); - await openRoomDirectory(session); - const createRoomButton = await session.query('.mx_RoomDirectory_createRoom'); - await createRoomButton.click(); const roomNameInput = await session.query('.mx_CreateRoomDialog_input'); await session.replaceInputText(roomNameInput, roomName); From 16591719e33d7e8addc8310943bca769b1ff6777 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 10 Sep 2019 15:40:17 +0000 Subject: [PATCH 210/221] Revert "Fix signup: set custom hs through advanced section, and accept IS step" --- src/usecases/signup.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/usecases/signup.js b/src/usecases/signup.js index a4f50b3e32..e43b47b5cd 100644 --- a/src/usecases/signup.js +++ b/src/usecases/signup.js @@ -21,15 +21,11 @@ module.exports = async function signup(session, username, password, homeserver) await session.goto(session.url('/#/register')); // change the homeserver by clicking the "Change" link. if (homeserver) { - const advancedButton = await session.query('.mx_ServerTypeSelector_type_Advanced'); - await advancedButton.click(); + const changeServerDetailsLink = await session.query('.mx_AuthBody_editServerDetails'); + await changeServerDetailsLink.click(); const hsInputField = await session.query('#mx_ServerConfig_hsUrl'); await session.replaceInputText(hsInputField, homeserver); const nextButton = await session.query('.mx_Login_submit'); - // accept homeserver - await nextButton.click(); - await session.delay(200); - // accept discovered identity server await nextButton.click(); await session.query('.mx_ServerConfig_identityServer_shown'); await nextButton.click(); From f2a3a136781d4221ea63e2eb213656337e6c59ad Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 11 Sep 2019 16:51:54 +0200 Subject: [PATCH 211/221] find new join button in room directory --- src/usecases/join.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/usecases/join.js b/src/usecases/join.js index 9bc7007849..3c14a76143 100644 --- a/src/usecases/join.js +++ b/src/usecases/join.js @@ -23,12 +23,8 @@ module.exports = async function join(session, roomName) { const roomInput = await session.query('.mx_DirectorySearchBox input'); await session.replaceInputText(roomInput, roomName); - const firstRoomLabel = await session.query('.mx_RoomDirectory_table .mx_RoomDirectory_name:first-child'); - await firstRoomLabel.click(); - - const joinLink = await session.query('.mx_RoomPreviewBar_ViewingRoom .mx_AccessibleButton_kind_primary'); - await joinLink.click(); - + const joinFirstLink = await session.query('.mx_RoomDirectory_table .mx_RoomDirectory_join .mx_AccessibleButton'); + await joinFirstLink.click(); await session.query('.mx_MessageComposer'); session.log.done(); } From a25056f21ee38a38a2f7eac6c27b3447bd072fb9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 13 Sep 2019 16:26:50 +0200 Subject: [PATCH 212/221] retry getting the scroll panel when retrying to get the scrolltop --- src/usecases/timeline.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/usecases/timeline.js b/src/usecases/timeline.js index 580d88ee6a..1770e0df9f 100644 --- a/src/usecases/timeline.js +++ b/src/usecases/timeline.js @@ -20,14 +20,14 @@ module.exports.scrollToTimelineTop = async function(session) { session.log.step(`scrolls to the top of the timeline`); await session.page.evaluate(() => { return Promise.resolve().then(async () => { - const timelineScrollView = document.querySelector(".mx_RoomView_timeline .mx_ScrollPanel"); let timedOut = false; let timeoutHandle = null; // set scrollTop to 0 in a loop and check every 50ms // if content became available (scrollTop not being 0 anymore), // assume everything is loaded after 3s do { - if (timelineScrollView.scrollTop !== 0) { + const timelineScrollView = document.querySelector(".mx_RoomView_timeline .mx_ScrollPanel"); + if (timelineScrollView && timelineScrollView.scrollTop !== 0) { if (timeoutHandle) { clearTimeout(timeoutHandle); } From 87be5fb9386dc00846ac20854e1b216688fea362 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 13 Sep 2019 16:31:00 +0200 Subject: [PATCH 213/221] try to fix selecting all text in Field components --- src/session.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/session.js b/src/session.js index 8e85509b82..f91bce5a46 100644 --- a/src/session.js +++ b/src/session.js @@ -108,6 +108,9 @@ module.exports = class RiotSession { async replaceInputText(input, text) { // click 3 times to select all text await input.click({clickCount: 3}); + // waiting here solves not having selected all the text by the 3x click above, + // presumably because of the Field label animation. + await this.delay(300); // then remove it with backspace await input.press('Backspace'); // and type the new text From c36673b992c61f5ec7848c79bbe9b11da50c713a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 13 Sep 2019 14:40:25 +0000 Subject: [PATCH 214/221] Revert "Revert "Fix signup: set custom hs through advanced section, and accept IS step"" --- src/usecases/signup.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/usecases/signup.js b/src/usecases/signup.js index e43b47b5cd..a4f50b3e32 100644 --- a/src/usecases/signup.js +++ b/src/usecases/signup.js @@ -21,11 +21,15 @@ module.exports = async function signup(session, username, password, homeserver) await session.goto(session.url('/#/register')); // change the homeserver by clicking the "Change" link. if (homeserver) { - const changeServerDetailsLink = await session.query('.mx_AuthBody_editServerDetails'); - await changeServerDetailsLink.click(); + const advancedButton = await session.query('.mx_ServerTypeSelector_type_Advanced'); + await advancedButton.click(); const hsInputField = await session.query('#mx_ServerConfig_hsUrl'); await session.replaceInputText(hsInputField, homeserver); const nextButton = await session.query('.mx_Login_submit'); + // accept homeserver + await nextButton.click(); + await session.delay(200); + // accept discovered identity server await nextButton.click(); await session.query('.mx_ServerConfig_identityServer_shown'); await nextButton.click(); From 1ea5047607ea6b78b93f8939077e49d2ec81dfe9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 16 Sep 2019 14:02:22 +0200 Subject: [PATCH 215/221] only need 2 clicks, not 3 --- src/usecases/signup.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/usecases/signup.js b/src/usecases/signup.js index a4f50b3e32..64cf52668c 100644 --- a/src/usecases/signup.js +++ b/src/usecases/signup.js @@ -28,10 +28,8 @@ module.exports = async function signup(session, username, password, homeserver) const nextButton = await session.query('.mx_Login_submit'); // accept homeserver await nextButton.click(); - await session.delay(200); - // accept discovered identity server - await nextButton.click(); await session.query('.mx_ServerConfig_identityServer_shown'); + // accept default identity server await nextButton.click(); } //fill out form From 06af5b3f224cf068ce9e12eb3edb6e5e943a4d6b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 17 Sep 2019 11:40:24 +0200 Subject: [PATCH 216/221] look for a change (HS) link in the registration but don't fail if its not there --- src/session.js | 3 +-- src/usecases/signup.js | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/session.js b/src/session.js index f91bce5a46..d3c26c07e4 100644 --- a/src/session.js +++ b/src/session.js @@ -117,8 +117,7 @@ module.exports = class RiotSession { await input.type(text); } - query(selector) { - const timeout = DEFAULT_TIMEOUT; + query(selector, timeout = DEFAULT_TIMEOUT) { return this.page.waitForSelector(selector, {visible: true, timeout}); } diff --git a/src/usecases/signup.js b/src/usecases/signup.js index 64cf52668c..b6fad58260 100644 --- a/src/usecases/signup.js +++ b/src/usecases/signup.js @@ -19,10 +19,23 @@ const assert = require('assert'); module.exports = async function signup(session, username, password, homeserver) { session.log.step("signs up"); await session.goto(session.url('/#/register')); - // change the homeserver by clicking the "Change" link. + // change the homeserver by clicking the advanced section if (homeserver) { const advancedButton = await session.query('.mx_ServerTypeSelector_type_Advanced'); await advancedButton.click(); + + // depending on what HS is configured as the default, the advanced registration + // goes the HS/IS entry directly (for matrix.org) or takes you to the user/pass entry (not matrix.org). + // To work with both, we look for the "Change" link in the user/pass entry but don't fail when we can't find it + // As this link should be visible immediately, and to not slow down the case where it isn't present, + // pick a lower timeout of 5000ms + try { + const changeHsField = await session.query('.mx_AuthBody_editServerDetails', 5000); + if (changeHsField) { + await changeHsField.click(); + } + } catch (err) {} + const hsInputField = await session.query('#mx_ServerConfig_hsUrl'); await session.replaceInputText(hsInputField, homeserver); const nextButton = await session.query('.mx_Login_submit'); From 03d928bf5da8dccd06946eace55ee26a669f891b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Sep 2019 17:22:43 +0200 Subject: [PATCH 217/221] adjust create room dialog name field selector --- src/usecases/create-room.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/usecases/create-room.js b/src/usecases/create-room.js index 132a4bd782..88547610f0 100644 --- a/src/usecases/create-room.js +++ b/src/usecases/create-room.js @@ -35,7 +35,7 @@ async function createRoom(session, roomName) { await addRoomButton.click(); - const roomNameInput = await session.query('.mx_CreateRoomDialog_input'); + const roomNameInput = await session.query('.mx_CreateRoomDialog_name input'); await session.replaceInputText(roomNameInput, roomName); const createButton = await session.query('.mx_Dialog_primary'); From fe1b786c507cb5fa8c05693d352bc4dff9d6cab2 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 23 Sep 2019 22:33:30 +0100 Subject: [PATCH 218/221] unbreak tests; we no longer prompt for IS at register --- src/usecases/signup.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/usecases/signup.js b/src/usecases/signup.js index b6fad58260..c3302a8a7f 100644 --- a/src/usecases/signup.js +++ b/src/usecases/signup.js @@ -41,7 +41,6 @@ module.exports = async function signup(session, username, password, homeserver) const nextButton = await session.query('.mx_Login_submit'); // accept homeserver await nextButton.click(); - await session.query('.mx_ServerConfig_identityServer_shown'); // accept default identity server await nextButton.click(); } From f5b605beaa8d97ae4a9049bf9ee2ac7997270c3f Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 23 Sep 2019 22:43:48 +0100 Subject: [PATCH 219/221] unbreak tests (take 2); we no longer prompt for IS at register --- src/usecases/signup.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/usecases/signup.js b/src/usecases/signup.js index c3302a8a7f..014d2ff786 100644 --- a/src/usecases/signup.js +++ b/src/usecases/signup.js @@ -41,8 +41,6 @@ module.exports = async function signup(session, username, password, homeserver) const nextButton = await session.query('.mx_Login_submit'); // accept homeserver await nextButton.click(); - // accept default identity server - await nextButton.click(); } //fill out form const usernameField = await session.query("#mx_RegistrationForm_username"); From 6cb9ef7e65f261bae577c25e0ca76c81c4f9c548 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 9 Oct 2019 16:51:35 +0200 Subject: [PATCH 220/221] remove editorconfig --- .editorconfig | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 880331a09e..0000000000 --- a/.editorconfig +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2017 Aviral Dasgupta -# -# 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. - -root = true - -[*] -charset=utf-8 -end_of_line = lf -insert_final_newline = true -indent_style = space -indent_size = 4 -trim_trailing_whitespace = true From ca86969f929014344b42d2d4b80b206f71952c97 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 9 Oct 2019 16:52:48 +0200 Subject: [PATCH 221/221] move everything to subfolder to merge into react-sdk --- .gitignore => test/end-to-end-tests/.gitignore | 0 README.md => test/end-to-end-tests/README.md | 0 TODO.md => test/end-to-end-tests/TODO.md | 0 install.sh => test/end-to-end-tests/install.sh | 0 package.json => test/end-to-end-tests/package.json | 0 {riot => test/end-to-end-tests/riot}/.gitignore | 0 {riot => test/end-to-end-tests/riot}/config-template/config.json | 0 {riot => test/end-to-end-tests/riot}/install.sh | 0 {riot => test/end-to-end-tests/riot}/start.sh | 0 {riot => test/end-to-end-tests/riot}/stop.sh | 0 run.sh => test/end-to-end-tests/run.sh | 0 {src => test/end-to-end-tests/src}/logbuffer.js | 0 {src => test/end-to-end-tests/src}/logger.js | 0 {src => test/end-to-end-tests/src}/rest/consent.js | 0 {src => test/end-to-end-tests/src}/rest/creator.js | 0 {src => test/end-to-end-tests/src}/rest/multi.js | 0 {src => test/end-to-end-tests/src}/rest/room.js | 0 {src => test/end-to-end-tests/src}/rest/session.js | 0 {src => test/end-to-end-tests/src}/scenario.js | 0 {src => test/end-to-end-tests/src}/scenarios/README.md | 0 {src => test/end-to-end-tests/src}/scenarios/directory.js | 0 {src => test/end-to-end-tests/src}/scenarios/e2e-encryption.js | 0 {src => test/end-to-end-tests/src}/scenarios/lazy-loading.js | 0 {src => test/end-to-end-tests/src}/session.js | 0 {src => test/end-to-end-tests/src}/usecases/README.md | 0 {src => test/end-to-end-tests/src}/usecases/accept-invite.js | 0 {src => test/end-to-end-tests/src}/usecases/create-room.js | 0 {src => test/end-to-end-tests/src}/usecases/dialog.js | 0 {src => test/end-to-end-tests/src}/usecases/invite.js | 0 {src => test/end-to-end-tests/src}/usecases/join.js | 0 {src => test/end-to-end-tests/src}/usecases/memberlist.js | 0 {src => test/end-to-end-tests/src}/usecases/room-settings.js | 0 {src => test/end-to-end-tests/src}/usecases/send-message.js | 0 {src => test/end-to-end-tests/src}/usecases/settings.js | 0 {src => test/end-to-end-tests/src}/usecases/signup.js | 0 {src => test/end-to-end-tests/src}/usecases/timeline.js | 0 {src => test/end-to-end-tests/src}/usecases/verify.js | 0 {src => test/end-to-end-tests/src}/util.js | 0 start.js => test/end-to-end-tests/start.js | 0 {synapse => test/end-to-end-tests/synapse}/.gitignore | 0 .../synapse}/config-templates/consent/homeserver.yaml | 0 .../config-templates/consent/res/templates/privacy/en/1.0.html | 0 .../consent/res/templates/privacy/en/success.html | 0 {synapse => test/end-to-end-tests/synapse}/install.sh | 0 {synapse => test/end-to-end-tests/synapse}/start.sh | 0 {synapse => test/end-to-end-tests/synapse}/stop.sh | 0 yarn.lock => test/end-to-end-tests/yarn.lock | 0 47 files changed, 0 insertions(+), 0 deletions(-) rename .gitignore => test/end-to-end-tests/.gitignore (100%) rename README.md => test/end-to-end-tests/README.md (100%) rename TODO.md => test/end-to-end-tests/TODO.md (100%) rename install.sh => test/end-to-end-tests/install.sh (100%) rename package.json => test/end-to-end-tests/package.json (100%) rename {riot => test/end-to-end-tests/riot}/.gitignore (100%) rename {riot => test/end-to-end-tests/riot}/config-template/config.json (100%) rename {riot => test/end-to-end-tests/riot}/install.sh (100%) rename {riot => test/end-to-end-tests/riot}/start.sh (100%) rename {riot => test/end-to-end-tests/riot}/stop.sh (100%) rename run.sh => test/end-to-end-tests/run.sh (100%) rename {src => test/end-to-end-tests/src}/logbuffer.js (100%) rename {src => test/end-to-end-tests/src}/logger.js (100%) rename {src => test/end-to-end-tests/src}/rest/consent.js (100%) rename {src => test/end-to-end-tests/src}/rest/creator.js (100%) rename {src => test/end-to-end-tests/src}/rest/multi.js (100%) rename {src => test/end-to-end-tests/src}/rest/room.js (100%) rename {src => test/end-to-end-tests/src}/rest/session.js (100%) rename {src => test/end-to-end-tests/src}/scenario.js (100%) rename {src => test/end-to-end-tests/src}/scenarios/README.md (100%) rename {src => test/end-to-end-tests/src}/scenarios/directory.js (100%) rename {src => test/end-to-end-tests/src}/scenarios/e2e-encryption.js (100%) rename {src => test/end-to-end-tests/src}/scenarios/lazy-loading.js (100%) rename {src => test/end-to-end-tests/src}/session.js (100%) rename {src => test/end-to-end-tests/src}/usecases/README.md (100%) rename {src => test/end-to-end-tests/src}/usecases/accept-invite.js (100%) rename {src => test/end-to-end-tests/src}/usecases/create-room.js (100%) rename {src => test/end-to-end-tests/src}/usecases/dialog.js (100%) rename {src => test/end-to-end-tests/src}/usecases/invite.js (100%) rename {src => test/end-to-end-tests/src}/usecases/join.js (100%) rename {src => test/end-to-end-tests/src}/usecases/memberlist.js (100%) rename {src => test/end-to-end-tests/src}/usecases/room-settings.js (100%) rename {src => test/end-to-end-tests/src}/usecases/send-message.js (100%) rename {src => test/end-to-end-tests/src}/usecases/settings.js (100%) rename {src => test/end-to-end-tests/src}/usecases/signup.js (100%) rename {src => test/end-to-end-tests/src}/usecases/timeline.js (100%) rename {src => test/end-to-end-tests/src}/usecases/verify.js (100%) rename {src => test/end-to-end-tests/src}/util.js (100%) rename start.js => test/end-to-end-tests/start.js (100%) rename {synapse => test/end-to-end-tests/synapse}/.gitignore (100%) rename {synapse => test/end-to-end-tests/synapse}/config-templates/consent/homeserver.yaml (100%) rename {synapse => test/end-to-end-tests/synapse}/config-templates/consent/res/templates/privacy/en/1.0.html (100%) rename {synapse => test/end-to-end-tests/synapse}/config-templates/consent/res/templates/privacy/en/success.html (100%) rename {synapse => test/end-to-end-tests/synapse}/install.sh (100%) rename {synapse => test/end-to-end-tests/synapse}/start.sh (100%) rename {synapse => test/end-to-end-tests/synapse}/stop.sh (100%) rename yarn.lock => test/end-to-end-tests/yarn.lock (100%) diff --git a/.gitignore b/test/end-to-end-tests/.gitignore similarity index 100% rename from .gitignore rename to test/end-to-end-tests/.gitignore diff --git a/README.md b/test/end-to-end-tests/README.md similarity index 100% rename from README.md rename to test/end-to-end-tests/README.md diff --git a/TODO.md b/test/end-to-end-tests/TODO.md similarity index 100% rename from TODO.md rename to test/end-to-end-tests/TODO.md diff --git a/install.sh b/test/end-to-end-tests/install.sh similarity index 100% rename from install.sh rename to test/end-to-end-tests/install.sh diff --git a/package.json b/test/end-to-end-tests/package.json similarity index 100% rename from package.json rename to test/end-to-end-tests/package.json diff --git a/riot/.gitignore b/test/end-to-end-tests/riot/.gitignore similarity index 100% rename from riot/.gitignore rename to test/end-to-end-tests/riot/.gitignore diff --git a/riot/config-template/config.json b/test/end-to-end-tests/riot/config-template/config.json similarity index 100% rename from riot/config-template/config.json rename to test/end-to-end-tests/riot/config-template/config.json diff --git a/riot/install.sh b/test/end-to-end-tests/riot/install.sh similarity index 100% rename from riot/install.sh rename to test/end-to-end-tests/riot/install.sh diff --git a/riot/start.sh b/test/end-to-end-tests/riot/start.sh similarity index 100% rename from riot/start.sh rename to test/end-to-end-tests/riot/start.sh diff --git a/riot/stop.sh b/test/end-to-end-tests/riot/stop.sh similarity index 100% rename from riot/stop.sh rename to test/end-to-end-tests/riot/stop.sh diff --git a/run.sh b/test/end-to-end-tests/run.sh similarity index 100% rename from run.sh rename to test/end-to-end-tests/run.sh diff --git a/src/logbuffer.js b/test/end-to-end-tests/src/logbuffer.js similarity index 100% rename from src/logbuffer.js rename to test/end-to-end-tests/src/logbuffer.js diff --git a/src/logger.js b/test/end-to-end-tests/src/logger.js similarity index 100% rename from src/logger.js rename to test/end-to-end-tests/src/logger.js diff --git a/src/rest/consent.js b/test/end-to-end-tests/src/rest/consent.js similarity index 100% rename from src/rest/consent.js rename to test/end-to-end-tests/src/rest/consent.js diff --git a/src/rest/creator.js b/test/end-to-end-tests/src/rest/creator.js similarity index 100% rename from src/rest/creator.js rename to test/end-to-end-tests/src/rest/creator.js diff --git a/src/rest/multi.js b/test/end-to-end-tests/src/rest/multi.js similarity index 100% rename from src/rest/multi.js rename to test/end-to-end-tests/src/rest/multi.js diff --git a/src/rest/room.js b/test/end-to-end-tests/src/rest/room.js similarity index 100% rename from src/rest/room.js rename to test/end-to-end-tests/src/rest/room.js diff --git a/src/rest/session.js b/test/end-to-end-tests/src/rest/session.js similarity index 100% rename from src/rest/session.js rename to test/end-to-end-tests/src/rest/session.js diff --git a/src/scenario.js b/test/end-to-end-tests/src/scenario.js similarity index 100% rename from src/scenario.js rename to test/end-to-end-tests/src/scenario.js diff --git a/src/scenarios/README.md b/test/end-to-end-tests/src/scenarios/README.md similarity index 100% rename from src/scenarios/README.md rename to test/end-to-end-tests/src/scenarios/README.md diff --git a/src/scenarios/directory.js b/test/end-to-end-tests/src/scenarios/directory.js similarity index 100% rename from src/scenarios/directory.js rename to test/end-to-end-tests/src/scenarios/directory.js diff --git a/src/scenarios/e2e-encryption.js b/test/end-to-end-tests/src/scenarios/e2e-encryption.js similarity index 100% rename from src/scenarios/e2e-encryption.js rename to test/end-to-end-tests/src/scenarios/e2e-encryption.js diff --git a/src/scenarios/lazy-loading.js b/test/end-to-end-tests/src/scenarios/lazy-loading.js similarity index 100% rename from src/scenarios/lazy-loading.js rename to test/end-to-end-tests/src/scenarios/lazy-loading.js diff --git a/src/session.js b/test/end-to-end-tests/src/session.js similarity index 100% rename from src/session.js rename to test/end-to-end-tests/src/session.js diff --git a/src/usecases/README.md b/test/end-to-end-tests/src/usecases/README.md similarity index 100% rename from src/usecases/README.md rename to test/end-to-end-tests/src/usecases/README.md diff --git a/src/usecases/accept-invite.js b/test/end-to-end-tests/src/usecases/accept-invite.js similarity index 100% rename from src/usecases/accept-invite.js rename to test/end-to-end-tests/src/usecases/accept-invite.js diff --git a/src/usecases/create-room.js b/test/end-to-end-tests/src/usecases/create-room.js similarity index 100% rename from src/usecases/create-room.js rename to test/end-to-end-tests/src/usecases/create-room.js diff --git a/src/usecases/dialog.js b/test/end-to-end-tests/src/usecases/dialog.js similarity index 100% rename from src/usecases/dialog.js rename to test/end-to-end-tests/src/usecases/dialog.js diff --git a/src/usecases/invite.js b/test/end-to-end-tests/src/usecases/invite.js similarity index 100% rename from src/usecases/invite.js rename to test/end-to-end-tests/src/usecases/invite.js diff --git a/src/usecases/join.js b/test/end-to-end-tests/src/usecases/join.js similarity index 100% rename from src/usecases/join.js rename to test/end-to-end-tests/src/usecases/join.js diff --git a/src/usecases/memberlist.js b/test/end-to-end-tests/src/usecases/memberlist.js similarity index 100% rename from src/usecases/memberlist.js rename to test/end-to-end-tests/src/usecases/memberlist.js diff --git a/src/usecases/room-settings.js b/test/end-to-end-tests/src/usecases/room-settings.js similarity index 100% rename from src/usecases/room-settings.js rename to test/end-to-end-tests/src/usecases/room-settings.js diff --git a/src/usecases/send-message.js b/test/end-to-end-tests/src/usecases/send-message.js similarity index 100% rename from src/usecases/send-message.js rename to test/end-to-end-tests/src/usecases/send-message.js diff --git a/src/usecases/settings.js b/test/end-to-end-tests/src/usecases/settings.js similarity index 100% rename from src/usecases/settings.js rename to test/end-to-end-tests/src/usecases/settings.js diff --git a/src/usecases/signup.js b/test/end-to-end-tests/src/usecases/signup.js similarity index 100% rename from src/usecases/signup.js rename to test/end-to-end-tests/src/usecases/signup.js diff --git a/src/usecases/timeline.js b/test/end-to-end-tests/src/usecases/timeline.js similarity index 100% rename from src/usecases/timeline.js rename to test/end-to-end-tests/src/usecases/timeline.js diff --git a/src/usecases/verify.js b/test/end-to-end-tests/src/usecases/verify.js similarity index 100% rename from src/usecases/verify.js rename to test/end-to-end-tests/src/usecases/verify.js diff --git a/src/util.js b/test/end-to-end-tests/src/util.js similarity index 100% rename from src/util.js rename to test/end-to-end-tests/src/util.js diff --git a/start.js b/test/end-to-end-tests/start.js similarity index 100% rename from start.js rename to test/end-to-end-tests/start.js diff --git a/synapse/.gitignore b/test/end-to-end-tests/synapse/.gitignore similarity index 100% rename from synapse/.gitignore rename to test/end-to-end-tests/synapse/.gitignore diff --git a/synapse/config-templates/consent/homeserver.yaml b/test/end-to-end-tests/synapse/config-templates/consent/homeserver.yaml similarity index 100% rename from synapse/config-templates/consent/homeserver.yaml rename to test/end-to-end-tests/synapse/config-templates/consent/homeserver.yaml diff --git a/synapse/config-templates/consent/res/templates/privacy/en/1.0.html b/test/end-to-end-tests/synapse/config-templates/consent/res/templates/privacy/en/1.0.html similarity index 100% rename from synapse/config-templates/consent/res/templates/privacy/en/1.0.html rename to test/end-to-end-tests/synapse/config-templates/consent/res/templates/privacy/en/1.0.html diff --git a/synapse/config-templates/consent/res/templates/privacy/en/success.html b/test/end-to-end-tests/synapse/config-templates/consent/res/templates/privacy/en/success.html similarity index 100% rename from synapse/config-templates/consent/res/templates/privacy/en/success.html rename to test/end-to-end-tests/synapse/config-templates/consent/res/templates/privacy/en/success.html diff --git a/synapse/install.sh b/test/end-to-end-tests/synapse/install.sh similarity index 100% rename from synapse/install.sh rename to test/end-to-end-tests/synapse/install.sh diff --git a/synapse/start.sh b/test/end-to-end-tests/synapse/start.sh similarity index 100% rename from synapse/start.sh rename to test/end-to-end-tests/synapse/start.sh diff --git a/synapse/stop.sh b/test/end-to-end-tests/synapse/stop.sh similarity index 100% rename from synapse/stop.sh rename to test/end-to-end-tests/synapse/stop.sh diff --git a/yarn.lock b/test/end-to-end-tests/yarn.lock similarity index 100% rename from yarn.lock rename to test/end-to-end-tests/yarn.lock