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() {