From 1e863e5a8a1e96e115000cc0fd5b3285e1a35d2a Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Thu, 2 Apr 2020 00:16:54 -0700 Subject: [PATCH 1/4] bump int test timeout from 5s to 10s, add test-integration-parts which is more reliable --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index e4b371f68..ce16bbb39 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "test-lib-view": "NODE_ENV=test-lib yarn run start", "test-loki-view": "NODE_ENV=test-loki yarn run start", "test-electron": "yarn grunt test", - "test-integration": "ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 5000 integration_test/integration_test.js", + "test-integration": "ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js", + "test-integration-parts": "ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js --grep 'registration' && ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js --grep 'openGroup' && ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js --grep 'addFriends' && ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js --grep 'linkDevice' && ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js --grep 'closedGroup'", "test-node": "mocha --recursive --exit test/app test/modules ts/test libloki/test/node", "eslint": "eslint --cache .", "eslint-fix": "eslint --fix .", From f5f9614680c073e362538d02491facf14c4caf63 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Thu, 2 Apr 2020 00:17:20 -0700 Subject: [PATCH 2/4] I looked up how to xpath byId, thought I'd include it --- integration_test/page-objects/common.page.js | 1 + 1 file changed, 1 insertion(+) diff --git a/integration_test/page-objects/common.page.js b/integration_test/page-objects/common.page.js index 9a3a77573..e5374f6d6 100644 --- a/integration_test/page-objects/common.page.js +++ b/integration_test/page-objects/common.page.js @@ -13,6 +13,7 @@ module.exports = { `//input[contains(@placeholder, "${placeholder}")]`, textAreaWithPlaceholder: placeholder => `//textarea[contains(@placeholder, "${placeholder}")]`, + byId: id => `//*[@id="${id}"]`, divWithClass: classname => `//div[contains(@class, "${classname}")]`, divWithClassAndText: (classname, text) => module.exports.objWithClassAndText('div', classname, text), From 3d93c0592949cffa291cb5db3b610b5a60efa993 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Thu, 2 Apr 2020 00:18:49 -0700 Subject: [PATCH 3/4] setValueWrapper refactor, fix process killing on macOs, remove unneeded resolves --- integration_test/common.js | 103 ++++++++++++++++++++++++++----------- 1 file changed, 74 insertions(+), 29 deletions(-) diff --git a/integration_test/common.js b/integration_test/common.js index ee8d35f98..306ae9bf1 100644 --- a/integration_test/common.js +++ b/integration_test/common.js @@ -50,6 +50,38 @@ module.exports = { return new Promise(resolve => setTimeout(resolve, ms)); }, + // a wrapper to work around electron/spectron bug + async setValueWrapper(app, selector, value) { + await app.client.element(selector).click(); + // keys, setValue and addValue hang on certain platforms + // could put a branch here to use one of those + // if we know what platforms are good and which ones are broken + await app.client.execute( + (slctr, val) => { + // eslint-disable-next-line no-undef + const iter = document.evaluate( + slctr, + // eslint-disable-next-line no-undef + document, + null, + // eslint-disable-next-line no-undef + XPathResult.UNORDERED_NODE_ITERATOR_TYPE, + null + ); + const elem = iter.iterateNext(); + if (elem) { + elem.value = val; + } else { + console.error('Cant find', slctr, elem, iter); + } + }, + selector, + value + ); + // let session js detect the text change + await app.client.element(selector).click(); + }, + async startApp(environment = 'test-integration-session') { const env = environment.startsWith('test-integration') ? 'test-integration' @@ -92,16 +124,16 @@ module.exports = { async stopApp(app1) { if (app1 && app1.isRunning()) { await app1.stop(); - return Promise.resolve(); } - return Promise.resolve(); }, async killallElectron() { + // rtharp - my 2nd client on MacOs needs: pkill -f "node_modules/.bin/electron" + // node_modules/electron/dist/electron is node_modules/electron/dist/Electron.app on MacOS const killStr = process.platform === 'win32' ? 'taskkill /im electron.exe /t /f' - : 'pkill -f "node_modules/electron/dist/electron"'; + : 'pkill -f "node_modules/electron/dist/electron" | pkill -f "node_modules/.bin/electron"'; return new Promise(resolve => { exec(killStr, (err, stdout, stderr) => { if (err) { @@ -145,23 +177,24 @@ module.exports = { stubOpenGroups = false, env = 'test-integration-session', }) { - const app1 = await this.startAndAssureCleanedApp(env); + const app = await this.startAndAssureCleanedApp(env); if (stubSnode) { await this.startStubSnodeServer(); - this.stubSnodeCalls(app1); + this.stubSnodeCalls(app); } if (stubOpenGroups) { - this.stubOpenGroupsCalls(app1); + this.stubOpenGroupsCalls(app); } if (mnemonic && displayName) { - await this.restoreFromMnemonic(app1, mnemonic, displayName); + await this.restoreFromMnemonic(app, mnemonic, displayName); + // not sure we need this - rtharp. await this.timeout(2000); } - return app1; + return app; }, async startAndStub2(props) { @@ -173,18 +206,23 @@ module.exports = { return app2; }, - async restoreFromMnemonic(app1, mnemonic, displayName) { - await app1.client.element(RegistrationPage.registrationTabSignIn).click(); - await app1.client.element(RegistrationPage.restoreFromSeedMode).click(); - await app1.client - .element(RegistrationPage.recoveryPhraseInput) - .setValue(mnemonic); - await app1.client - .element(RegistrationPage.displayNameInput) - .setValue(displayName); - - await app1.client.element(RegistrationPage.continueSessionButton).click(); - await app1.client.waitForExist( + async restoreFromMnemonic(app, mnemonic, displayName) { + await app.client.element(RegistrationPage.registrationTabSignIn).click(); + await app.client.element(RegistrationPage.restoreFromSeedMode).click(); + await this.setValueWrapper( + app, + RegistrationPage.recoveryPhraseInput, + mnemonic + ); + + await this.setValueWrapper( + app, + RegistrationPage.displayNameInput, + displayName + ); + + await app.client.element(RegistrationPage.continueSessionButton).click(); + await app.client.waitForExist( RegistrationPage.conversationListContainer, 4000 ); @@ -214,9 +252,11 @@ module.exports = { await app1.client.element(ConversationPage.contactsButtonSection).click(); await app1.client.element(ConversationPage.addContactButton).click(); - await app1.client - .element(ConversationPage.sessionIDInput) - .setValue(this.TEST_PUBKEY2); + await this.setValueWrapper( + app1, + ConversationPage.sessionIDInput, + this.TEST_PUBKEY2 + ); await app1.client.element(ConversationPage.nextButton).click(); await app1.client.waitForExist( ConversationPage.sendFriendRequestTextarea, @@ -224,9 +264,11 @@ module.exports = { ); // send a text message to that user (will be a friend request) - await app1.client - .element(ConversationPage.sendFriendRequestTextarea) - .setValue(textMessage); + await this.setValueWrapper( + app1, + ConversationPage.sendFriendRequestTextarea, + textMessage + ); await app1.client.keys('Enter'); await app1.client.waitForExist( ConversationPage.existingFriendRequestText(textMessage), @@ -284,9 +326,12 @@ module.exports = { // next trigger the link request from the app2 with the app1 pubkey await app2.client.element(RegistrationPage.registrationTabSignIn).click(); await app2.client.element(RegistrationPage.linkDeviceMode).click(); - await app2.client - .element(RegistrationPage.textareaLinkDevicePubkey) - .setValue(this.TEST_PUBKEY1); + + await this.setValueWrapper( + app2, + RegistrationPage.textareaLinkDevicePubkey, + this.TEST_PUBKEY1 + ); await app2.client.element(RegistrationPage.linkDeviceTriggerButton).click(); await app1.client.waitForExist(RegistrationPage.toastWrapper, 7000); let secretWordsapp1 = await app1.client From 0bffccb44120b6bd10bdbb70d398704932d478e0 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Thu, 2 Apr 2020 00:19:25 -0700 Subject: [PATCH 4/4] setValueWrapper refactor, add group prefix for easier grepping, DRY --- integration_test/add_friends_test.js | 12 +++-- integration_test/closed_group_test.js | 10 ++-- integration_test/link_device_test.js | 4 +- integration_test/open_group_test.js | 78 ++++++++++++++------------- integration_test/registration_test.js | 10 ++-- 5 files changed, 62 insertions(+), 52 deletions(-) diff --git a/integration_test/add_friends_test.js b/integration_test/add_friends_test.js index a86dbf1ec..aa8c04d4d 100644 --- a/integration_test/add_friends_test.js +++ b/integration_test/add_friends_test.js @@ -38,7 +38,7 @@ describe('Add friends', function() { await common.stopStubSnodeServer(); }); - it('can add a friend by sessionID', async () => { + it('addFriends: can add a friend by sessionID', async () => { const textMessage = common.generateSendMessageText(); await app.client.element(ConversationPage.contactsButtonSection).click(); @@ -46,13 +46,16 @@ describe('Add friends', function() { await app.client.isExisting(ConversationPage.leftPaneOverlay).should .eventually.be.true; - await app.client - .element(ConversationPage.sessionIDInput) - .setValue(common.TEST_PUBKEY2); + await common.setValueWrapper( + app, + ConversationPage.sessionIDInput, + common.TEST_PUBKEY2 + ); await app.client .element(ConversationPage.sessionIDInput) .getValue() .should.eventually.equal(common.TEST_PUBKEY2); + await app.client.element(ConversationPage.nextButton).click(); await app.client.waitForExist( ConversationPage.sendFriendRequestTextarea, @@ -68,6 +71,7 @@ describe('Add friends', function() { ConversationPage.existingFriendRequestText(textMessage), 1000 ); + // assure friend request message has been sent await common.timeout(3000); await app.client.isExisting(ConversationPage.retrySendButton).should diff --git a/integration_test/closed_group_test.js b/integration_test/closed_group_test.js index 42db71fbc..c937c97ff 100644 --- a/integration_test/closed_group_test.js +++ b/integration_test/closed_group_test.js @@ -23,14 +23,16 @@ describe('Closed groups', function() { await common.stopStubSnodeServer(); }); - it('can create a closed group with a friend and send/receive a message', async () => { + it('closedGroup: can create a closed group with a friend and send/receive a message', async () => { await app.client.element(ConversationPage.globeButtonSection).click(); await app.client.element(ConversationPage.createClosedGroupButton).click(); // fill the groupname - await app.client - .element(ConversationPage.closedGroupNameTextarea) - .setValue(common.VALID_CLOSED_GROUP_NAME1); + await common.setValueWrapper( + app, + ConversationPage.closedGroupNameTextarea, + common.VALID_CLOSED_GROUP_NAME1 + ); await app.client .element(ConversationPage.closedGroupNameTextarea) .getValue() diff --git a/integration_test/link_device_test.js b/integration_test/link_device_test.js index 14e60af03..a206a5e9c 100644 --- a/integration_test/link_device_test.js +++ b/integration_test/link_device_test.js @@ -36,11 +36,11 @@ describe('Link Device', function() { await common.stopStubSnodeServer(); }); - it('link two desktop devices', async () => { + it('linkDevice: link two desktop devices', async () => { await common.linkApp2ToApp(app, app2); }); - it('unlink two devices', async () => { + it('linkDevice: unlink two devices', async () => { await common.linkApp2ToApp(app, app2); await common.timeout(1000); await common.triggerUnlinkApp2FromApp(app, app2); diff --git a/integration_test/open_group_test.js b/integration_test/open_group_test.js index 486a5256b..9910fd68e 100644 --- a/integration_test/open_group_test.js +++ b/integration_test/open_group_test.js @@ -23,25 +23,25 @@ describe('Open groups', function() { await common.killallElectron(); }); - it('works with valid group url', async () => { + // reduce code duplication to get the initial join + async function joinOpenGroup(url, name) { await app.client.element(ConversationPage.globeButtonSection).click(); await app.client.element(ConversationPage.joinOpenGroupButton).click(); - await app.client - .element(ConversationPage.openGroupInputUrl) - .setValue(common.VALID_GROUP_URL); + await common.setValueWrapper(app, ConversationPage.openGroupInputUrl, url); await app.client .element(ConversationPage.openGroupInputUrl) .getValue() - .should.eventually.equal(common.VALID_GROUP_URL); + .should.eventually.equal(url); await app.client.element(ConversationPage.joinOpenGroupButton).click(); // validate session loader is shown await app.client.isExisting(ConversationPage.sessionLoader).should .eventually.be.true; + // account for slow home internet connection delays... await app.client.waitForExist( ConversationPage.sessionToastJoinOpenGroupSuccess, - 9000 + 60 * 1000 ); // validate overlay is closed @@ -50,35 +50,27 @@ describe('Open groups', function() { // validate open chat has been added await app.client.waitForExist( - ConversationPage.rowOpenGroupConversationName(common.VALID_GROUP_NAME), + ConversationPage.rowOpenGroupConversationName(name), 4000 ); + } - await common.timeout(1000); + it('openGroup: works with valid open group url', async () => { + await joinOpenGroup(common.VALID_GROUP_URL, common.VALID_GROUP_NAME); }); - it('cannot join two times the same open group', async () => { - await app.client.element(ConversationPage.globeButtonSection).click(); - await app.client.element(ConversationPage.joinOpenGroupButton).click(); - - await app.client - .element(ConversationPage.openGroupInputUrl) - .setValue(common.VALID_GROUP_URL2); - await app.client.element(ConversationPage.joinOpenGroupButton).click(); - // first add is a success - await common.timeout(2000); - await app.client.waitForExist( - ConversationPage.rowOpenGroupConversationName(common.VALID_GROUP_NAME2), - 8000 - ); + it('openGroup: cannot join two times the same open group', async () => { + await joinOpenGroup(common.VALID_GROUP_URL2, common.VALID_GROUP_NAME2); // adding a second time the same open group await app.client.element(ConversationPage.globeButtonSection).click(); await app.client.element(ConversationPage.joinOpenGroupButton).click(); - await app.client - .element(ConversationPage.openGroupInputUrl) - .setValue(common.VALID_GROUP_URL2); + await common.setValueWrapper( + app, + ConversationPage.openGroupInputUrl, + common.VALID_GROUP_URL2 + ); await app.client.element(ConversationPage.joinOpenGroupButton).click(); // validate session loader is not shown await app.client.isExisting(ConversationPage.sessionLoader).should @@ -86,7 +78,7 @@ describe('Open groups', function() { await app.client.waitForExist( ConversationPage.sessionToastJoinOpenGroupAlreadyExist, - 1000 + 1 * 1000 ); // validate overlay is still opened @@ -94,20 +86,28 @@ describe('Open groups', function() { .eventually.be.true; }); - it('can send message to open group', async () => { + it('openGroup: can send message to open group', async () => { // join dev-chat group await app.client.element(ConversationPage.globeButtonSection).click(); await app.client.element(ConversationPage.joinOpenGroupButton).click(); - await app.client - .element(ConversationPage.openGroupInputUrl) - .setValue(common.VALID_GROUP_URL2); + await common.setValueWrapper( + app, + ConversationPage.openGroupInputUrl, + common.VALID_GROUP_URL2 + ); await app.client.element(ConversationPage.joinOpenGroupButton).click(); - // first add is a success - await common.timeout(2000); + + // wait for toast to appear + await app.client.waitForExist( + ConversationPage.sessionToastJoinOpenGroupSuccess, + 30 * 1000 + ); + await common.timeout(5 * 1000); // wait for toast to clear + await app.client.waitForExist( ConversationPage.rowOpenGroupConversationName(common.VALID_GROUP_NAME2), - 8000 + 10 * 1000 ); // generate a message containing the current timestamp so we can find it in the list of messages const textMessage = common.generateSendMessageText(); @@ -118,14 +118,18 @@ describe('Open groups', function() { await app.client.isExisting( ConversationPage.rowOpenGroupConversationName(common.VALID_GROUP_NAME2) ); + await app.client .element( ConversationPage.rowOpenGroupConversationName(common.VALID_GROUP_NAME2) ) .click(); - await app.client - .element(ConversationPage.sendMessageTextarea) - .setValue(textMessage); + + await common.setValueWrapper( + app, + ConversationPage.sendMessageTextarea, + textMessage + ); await app.client .element(ConversationPage.sendMessageTextarea) .getValue() @@ -139,7 +143,7 @@ describe('Open groups', function() { // validate that the message has been added to the message list view await app.client.waitForExist( ConversationPage.existingSendMessageText(textMessage), - 3000 + 3 * 1000 ); // we should validate that the message has been added effectively sent // (checking the check icon on the metadata part of the message?) diff --git a/integration_test/registration_test.js b/integration_test/registration_test.js index 3e0a57aa7..a60a5cb52 100644 --- a/integration_test/registration_test.js +++ b/integration_test/registration_test.js @@ -21,12 +21,12 @@ describe('Window Test and Login', function() { await common.killallElectron(); }); - it('opens one window', async () => { + it('registration: opens one window', async () => { app = await common.startAndAssureCleanedApp(); app.client.getWindowCount().should.eventually.be.equal(1); }); - it('window title is correct', async () => { + it('registration: window title is correct', async () => { app = await common.startAndAssureCleanedApp(); app.client @@ -34,7 +34,7 @@ describe('Window Test and Login', function() { .should.eventually.be.equal('Session - test-integration-session'); }); - it('can restore from seed', async () => { + it('registration: can restore from seed', async () => { app = await common.startAndAssureCleanedApp(); await app.client.element(RegistrationPage.registrationTabSignIn).click(); @@ -70,7 +70,7 @@ describe('Window Test and Login', function() { .should.eventually.be.equal(common.TEST_PUBKEY1); }); - it('can create new account', async () => { + it('registration: can create new account', async () => { app = await common.startAndAssureCleanedApp(); await app.client.element(RegistrationPage.createSessionIDButton).click(); // wait for the animation of generated pubkey to finish @@ -98,7 +98,7 @@ describe('Window Test and Login', function() { .should.eventually.be.equal(pubkeyGenerated); }); - it('can delete account when logged in', async () => { + it('registration: can delete account when logged in', async () => { // login as user1 const login = { mnemonic: common.TEST_MNEMONIC1,