add test scenario for e2e encryption

pull/21833/head
Bruno Windels 2018-08-08 15:22:54 +02:00
parent dc87e2bfe0
commit af0c0c0afe
11 changed files with 263 additions and 43 deletions

View File

@ -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");
}
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});
}

View File

@ -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();
}

View File

@ -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();

47
src/tests/dialog.js Normal file
View File

@ -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,
};

31
src/tests/e2e-device.js Normal file
View File

@ -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};
}

30
src/tests/invite.js Normal file
View File

@ -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();
}

View File

@ -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();

View File

@ -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();
}

View File

@ -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;

View File

@ -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();
}

View File

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