From 74502296ed22edd5300ea74fd7b483fab0736729 Mon Sep 17 00:00:00 2001 From: kumavis Date: Sat, 10 Mar 2018 11:09:45 -0800 Subject: [PATCH 1/8] ci - split integration tests into parallel firefox and chrome runs --- .circleci/config.yml | 58 +++++++++++++++++++++++++++++++++++++++++--- test/base.conf.js | 4 ++- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9cd0a24ec..df3e0ed19 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,12 +15,20 @@ workflows: - test-unit: requires: - prep-deps-npm - - test-integration-mascara: + - test-integration-mascara-chrome: + requires: + - prep-deps-npm + - prep-scss + - test-integration-mascara-firefox: requires: - prep-deps-npm - prep-deps-firefox - prep-scss - - test-integration-flat: + - test-integration-flat-chrome: + requires: + - prep-deps-npm + - prep-scss + - test-integration-flat-firefox: requires: - prep-deps-npm - prep-deps-firefox @@ -99,7 +107,9 @@ jobs: name: test:coverage command: npm run test:coverage - test-integration-flat: + test-integration-flat-firefox: + environment: + browsers: ["Firefox"] docker: - image: circleci/node:8-browsers steps: @@ -125,7 +135,28 @@ jobs: name: test:integration:flat command: npm run test:flat - test-integration-mascara: + test-integration-flat-chrome: + environment: + browsers: ["Chrome"] + docker: + - image: circleci/node:8-browsers + steps: + - checkout + - restore_cache: + key: dependency-cache-{{ checksum "package-lock.json" }} + - run: + name: Get Scss Cache key + # this allows us to checksum against a whole directory + command: find ui/app/css -type f -exec md5sum {} \; | sort -k 2 > scss_checksum + - restore_cache: + key: scss-cache-{{ checksum "scss_checksum" }} + - run: + name: test:integration:flat + command: npm run test:flat + + test-integration-mascara-firefox: + environment: + browsers: ["Firefox"] docker: - image: circleci/node:8-browsers steps: @@ -150,3 +181,22 @@ jobs: - run: name: test:integration:mascara command: npm run test:mascara + + test-integration-mascara-chrome: + environment: + browsers: ["Chrome"] + docker: + - image: circleci/node:8-browsers + steps: + - checkout + - restore_cache: + key: dependency-cache-{{ checksum "package-lock.json" }} + - run: + name: Get Scss Cache key + # this allows us to checksum against a whole directory + command: find ui/app/css -type f -exec md5sum {} \; | sort -k 2 > scss_checksum + - restore_cache: + key: scss-cache-{{ checksum "scss_checksum" }} + - run: + name: test:integration:mascara + command: npm run test:mascara diff --git a/test/base.conf.js b/test/base.conf.js index 82b9d8eec..adb5357e8 100644 --- a/test/base.conf.js +++ b/test/base.conf.js @@ -46,7 +46,9 @@ module.exports = function(config) { // start these browsers // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: ['Chrome', 'Firefox'], + browsers: process.env.browsers ? + JSON.parse(process.env.browsers) + : ['Chrome', 'Firefox'], // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits From 2c3e6552d203181bca1c69622a78e5faa633ce76 Mon Sep 17 00:00:00 2001 From: kumavis Date: Sat, 10 Mar 2018 11:13:27 -0800 Subject: [PATCH 2/8] ci - wrap env var in quotes --- .circleci/config.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index df3e0ed19..c14909783 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -109,7 +109,7 @@ jobs: test-integration-flat-firefox: environment: - browsers: ["Firefox"] + browsers: '["Firefox"]' docker: - image: circleci/node:8-browsers steps: @@ -137,7 +137,7 @@ jobs: test-integration-flat-chrome: environment: - browsers: ["Chrome"] + browsers: '["Chrome"]' docker: - image: circleci/node:8-browsers steps: @@ -156,7 +156,7 @@ jobs: test-integration-mascara-firefox: environment: - browsers: ["Firefox"] + browsers: '["Firefox"]' docker: - image: circleci/node:8-browsers steps: @@ -184,7 +184,7 @@ jobs: test-integration-mascara-chrome: environment: - browsers: ["Chrome"] + browsers: '["Chrome"]' docker: - image: circleci/node:8-browsers steps: From 7e56c6b6fa92453559f2510e390fce451de2dead Mon Sep 17 00:00:00 2001 From: kumavis Date: Sun, 11 Mar 2018 14:51:54 -0700 Subject: [PATCH 3/8] test - genStates - use async api --- development/genStates.js | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/development/genStates.js b/development/genStates.js index 39a672ee0..325ef71f3 100644 --- a/development/genStates.js +++ b/development/genStates.js @@ -1,18 +1,21 @@ const fs = require('fs') const path = require('path') +const { promisify } = require('util') -const statesPath = path.join(__dirname, 'states') -const stateNames = fs.readdirSync(statesPath) +start().catch(console.error) -const states = stateNames.reduce((result, stateFileName) => { - const statePath = path.join(__dirname, 'states', stateFileName) - const stateFile = fs.readFileSync(statePath).toString() - const state = JSON.parse(stateFile) - result[stateFileName.split('.')[0].replace(/-/g, ' ', 'g')] = state - return result -}, {}) - -const result = `module.exports = ${JSON.stringify(states)}` - -const statesJsonPath = path.join(__dirname, 'states.js') -fs.writeFileSync(statesJsonPath, result) +async function start () { + const statesPath = path.join(__dirname, 'states') + const stateFilesNames = await promisify(fs.readdirSync)(statesPath) + const states = {} + await Promise.all(stateFilesNames.map(async (stateFileName) => { + const stateFilePath = path.join(__dirname, 'states', stateFileName) + const stateFileContent = await promisify(fs.readFileSync)(stateFilePath, 'utf8') + const state = JSON.parse(stateFileContent) + const stateName = stateFileName.split('.')[0].replace(/-/g, ' ', 'g') + states[stateName] = state + })) + const generatedFileContent = `module.exports = ${JSON.stringify(states)}` + const generatedFilePath = path.join(__dirname, 'states.js') + await promisify(fs.writeFile)(generatedFilePath, generatedFileContent) +} From 0419276958a168f2d7b75c11cfe8d14cf5f39514 Mon Sep 17 00:00:00 2001 From: kumavis Date: Sun, 11 Mar 2018 14:52:29 -0700 Subject: [PATCH 4/8] test - poll for element instead of manual timeouts --- test/integration/lib/add-token.js | 73 +++++----------- test/integration/lib/first-time.js | 80 ++++++------------ test/integration/lib/send-new-ui.js | 127 +++++++++------------------- test/lib/util.js | 53 ++++++++++++ 4 files changed, 144 insertions(+), 189 deletions(-) create mode 100644 test/lib/util.js diff --git a/test/integration/lib/add-token.js b/test/integration/lib/add-token.js index dd4251cc4..42ed28dca 100644 --- a/test/integration/lib/add-token.js +++ b/test/integration/lib/add-token.js @@ -1,4 +1,9 @@ const reactTriggerChange = require('react-trigger-change') +const { + timeout, + queryAsync, + findAsync, +} = require('../../lib/util') QUnit.module('Add token flow') @@ -13,74 +18,60 @@ QUnit.test('successful add token flow', (assert) => { }) async function runAddTokenFlowTest (assert, done) { - const selectState = $('select') + const selectState = await queryAsync($, 'select') selectState.val('add token') reactTriggerChange(selectState[0]) - await timeout(2000) - // Check that no tokens have been added assert.ok($('.token-list-item').length === 0, 'no tokens added') // Go to Add Token screen - let addTokenButton = $('button.btn-clear.wallet-view__add-token-button') + let addTokenButton = await queryAsync($, 'button.btn-clear.wallet-view__add-token-button') assert.ok(addTokenButton[0], 'add token button present') addTokenButton[0].click() - await timeout(1000) - // Verify Add Token screen - let addTokenWrapper = $('.add-token__wrapper') + let addTokenWrapper = await queryAsync($, '.add-token__wrapper') assert.ok(addTokenWrapper[0], 'add token wrapper renders') - let addTokenTitle = $('.add-token__title') + let addTokenTitle = await queryAsync($, '.add-token__title') assert.equal(addTokenTitle[0].textContent, 'Add Token', 'add token title is correct') // Cancel Add Token - const cancelAddTokenButton = $('button.btn-cancel.add-token__button') + const cancelAddTokenButton = await queryAsync($, 'button.btn-cancel.add-token__button') assert.ok(cancelAddTokenButton[0], 'cancel add token button present') cancelAddTokenButton.click() - await timeout(1000) - assert.ok($('.wallet-view')[0], 'cancelled and returned to account detail wallet view') // Return to Add Token Screen - addTokenButton = $('button.btn-clear.wallet-view__add-token-button') + addTokenButton = await queryAsync($, 'button.btn-clear.wallet-view__add-token-button') assert.ok(addTokenButton[0], 'add token button present') addTokenButton[0].click() - await timeout(1000) - // Verify Add Token Screen - addTokenWrapper = $('.add-token__wrapper') - addTokenTitle = $('.add-token__title') + addTokenWrapper = await queryAsync($, '.add-token__wrapper') + addTokenTitle = await queryAsync($, '.add-token__title') assert.ok(addTokenWrapper[0], 'add token wrapper renders') assert.equal(addTokenTitle[0].textContent, 'Add Token', 'add token title is correct') // Search for token - const searchInput = $('input.add-token__input') + const searchInput = await queryAsync($, 'input.add-token__input') searchInput.val('a') reactTriggerChange(searchInput[0]) - await timeout() - // Click token to add - const tokenWrapper = $('div.add-token__token-wrapper') + const tokenWrapper = await queryAsync($, 'div.add-token__token-wrapper') assert.ok(tokenWrapper[0], 'token found') const tokenImageProp = tokenWrapper.find('.add-token__token-icon').css('background-image') const tokenImageUrl = tokenImageProp.slice(5, -2) tokenWrapper[0].click() - await timeout() - // Click Next button - let nextButton = $('button.btn-clear.add-token__button') + let nextButton = await queryAsync($, 'button.btn-clear.add-token__button') assert.equal(nextButton[0].textContent, 'Next', 'next button rendered') nextButton[0].click() - await timeout() - // Confirm Add token assert.equal( $('.add-token__description')[0].textContent, @@ -90,47 +81,35 @@ async function runAddTokenFlowTest (assert, done) { assert.ok($('button.btn-clear.add-token__button')[0], 'confirm add token button found') $('button.btn-clear.add-token__button')[0].click() - await timeout(2000) - // Verify added token image - let heroBalance = $('.hero-balance') + let heroBalance = await queryAsync($, '.hero-balance') assert.ok(heroBalance, 'rendered hero balance') assert.ok(tokenImageUrl.indexOf(heroBalance.find('img').attr('src')) > -1, 'token added') // Return to Add Token Screen - addTokenButton = $('button.btn-clear.wallet-view__add-token-button') + addTokenButton = await queryAsync($, 'button.btn-clear.wallet-view__add-token-button') assert.ok(addTokenButton[0], 'add token button present') addTokenButton[0].click() - await timeout(1000) - - const addCustom = $('.add-token__add-custom') + const addCustom = await queryAsync($, '.add-token__add-custom') assert.ok(addCustom[0], 'add custom token button present') addCustom[0].click() - await timeout() - // Input token contract address - const customInput = $('input.add-token__add-custom-input') + const customInput = await queryAsync($, 'input.add-token__add-custom-input') customInput.val('0x177af043D3A1Aed7cc5f2397C70248Fc6cDC056c') reactTriggerChange(customInput[0]) - await timeout(1000) - // Click Next button - nextButton = $('button.btn-clear.add-token__button') + nextButton = await queryAsync($, 'button.btn-clear.add-token__button') assert.equal(nextButton[0].textContent, 'Next', 'next button rendered') nextButton[0].click() - await timeout(1000) - // Verify symbol length error since contract address won't return symbol - const errorMessage = $('.add-token__add-custom-error-message') + const errorMessage = await queryAsync($, '.add-token__add-custom-error-message') assert.ok(errorMessage[0], 'error rendered') $('button.btn-cancel.add-token__button')[0].click() - await timeout(2000) - // // Confirm Add token // assert.equal( // $('.add-token__description')[0].textContent, @@ -141,13 +120,7 @@ async function runAddTokenFlowTest (assert, done) { // $('button.btn-clear.add-token__button')[0].click() // // Verify added token image - // heroBalance = $('.hero-balance') + // heroBalance = await queryAsync($, '.hero-balance') // assert.ok(heroBalance, 'rendered hero balance') // assert.ok(heroBalance.find('.identicon')[0], 'token added') } - -function timeout (time) { - return new Promise((resolve, reject) => { - setTimeout(resolve, time || 1500) - }) -} diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index 764eae47c..052d89518 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -1,6 +1,10 @@ const reactTriggerChange = require('react-trigger-change') const PASSWORD = 'password123' const runMascaraFirstTimeTest = require('./mascara-first-time') +const { + timeout, + findAsync, +} = require('../../lib/util') QUnit.module('first time usage') @@ -21,20 +25,19 @@ async function runFirstTimeUsageTest(assert, done) { selectState.val('first time') reactTriggerChange(selectState[0]) - await timeout(2000) const app = $('#app-content') // recurse notices while (true) { - const button = app.find('button') + const button = await findAsync(app, 'button') if (button.html() === 'Accept') { // still notices to accept - const termsPage = app.find('.markdown')[0] + const termsPageRaw = await findAsync(app, '.markdown') + const termsPage = (await findAsync(app, '.markdown'))[0] + console.log('termsPageRaw', termsPageRaw) termsPage.scrollTop = termsPage.scrollHeight - await timeout() console.log('Clearing notice') button.click() - await timeout() } else { // exit loop console.log('No more notices...') @@ -42,97 +45,68 @@ async function runFirstTimeUsageTest(assert, done) { } } - await timeout() - // Scroll through terms - const title = app.find('h1')[0] + const title = (await findAsync(app, 'h1'))[0] assert.equal(title.textContent, 'MetaMask', 'title screen') // enter password - const pwBox = app.find('#password-box')[0] - const confBox = app.find('#password-box-confirm')[0] + const pwBox = (await findAsync(app, '#password-box'))[0] + const confBox = (await findAsync(app, '#password-box-confirm'))[0] pwBox.value = PASSWORD confBox.value = PASSWORD - await timeout() - // create vault - const createButton = app.find('button.primary')[0] + const createButton = (await findAsync(app, 'button.primary'))[0] createButton.click() - await timeout(3000) - - const created = app.find('h3')[0] + await timeout() + const created = (await findAsync(app, 'h3'))[0] assert.equal(created.textContent, 'Vault Created', 'Vault created screen') // Agree button - const button = app.find('button')[0] + const button = (await findAsync(app, 'button'))[0] assert.ok(button, 'button present') button.click() - await timeout(1000) - - const detail = app.find('.account-detail-section')[0] + const detail = (await findAsync(app, '.account-detail-section'))[0] assert.ok(detail, 'Account detail section loaded.') - const sandwich = app.find('.sandwich-expando')[0] + const sandwich = (await findAsync(app, '.sandwich-expando'))[0] sandwich.click() - await timeout() - - const menu = app.find('.menu-droppo')[0] + const menu = (await findAsync(app, '.menu-droppo'))[0] const children = menu.children const logout = children[2] assert.ok(logout, 'Lock menu item found') logout.click() - await timeout(1000) - - const pwBox2 = app.find('#password-box')[0] + const pwBox2 = (await findAsync(app, '#password-box'))[0] pwBox2.value = PASSWORD - const createButton2 = app.find('button.primary')[0] + const createButton2 = (await findAsync(app, 'button.primary'))[0] createButton2.click() - await timeout(1000) - - const detail2 = app.find('.account-detail-section')[0] + const detail2 = (await findAsync(app, '.account-detail-section'))[0] assert.ok(detail2, 'Account detail section loaded again.') - await timeout() - // open account settings dropdown - const qrButton = app.find('.fa.fa-ellipsis-h')[0] + const qrButton = (await findAsync(app, '.fa.fa-ellipsis-h'))[0] qrButton.click() - await timeout(1000) - // qr code item - const qrButton2 = app.find('.dropdown-menu-item')[1] + const qrButton2 = (await findAsync(app, '.dropdown-menu-item'))[1] qrButton2.click() - await timeout(1000) - - const qrHeader = app.find('.qr-header')[0] - const qrContainer = app.find('#qr-container')[0] + const qrHeader = (await findAsync(app, '.qr-header'))[0] + const qrContainer = (await findAsync(app, '#qr-container'))[0] assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.') assert.ok(qrContainer, 'QR Container found') - await timeout() - - const networkMenu = app.find('.network-indicator')[0] + const networkMenu = (await findAsync(app, '.network-indicator'))[0] networkMenu.click() - await timeout() - - const networkMenu2 = app.find('.network-indicator')[0] + const networkMenu2 = (await findAsync(app, '.network-indicator'))[0] const children2 = networkMenu2.children children2.length[3] assert.ok(children2, 'All network options present') } - -function timeout (time) { - return new Promise((resolve, reject) => { - setTimeout(resolve, time || 1500) - }) -} \ No newline at end of file diff --git a/test/integration/lib/send-new-ui.js b/test/integration/lib/send-new-ui.js index 3456f2367..07be63a36 100644 --- a/test/integration/lib/send-new-ui.js +++ b/test/integration/lib/send-new-ui.js @@ -1,4 +1,9 @@ const reactTriggerChange = require('react-trigger-change') +const { + timeout, + queryAsync, + findAsync, +} = require('../../lib/util') const PASSWORD = 'password123' @@ -18,83 +23,67 @@ global.ethQuery = { async function runSendFlowTest(assert, done) { console.log('*** start runSendFlowTest') - const selectState = $('select') + const selectState = await queryAsync($, 'select') selectState.val('send new ui') reactTriggerChange(selectState[0]) - await timeout(2000) - - const sendScreenButton = $('button.btn-clear.hero-balance-button') + const sendScreenButton = await queryAsync($, 'button.btn-clear.hero-balance-button') assert.ok(sendScreenButton[1], 'send screen button present') sendScreenButton[1].click() - await timeout(1000) - - const sendTitle = $('.page-container__title') + const sendTitle = await queryAsync($, '.page-container__title') assert.equal(sendTitle[0].textContent, 'Send ETH', 'Send screen title is correct') - const sendCopy = $('.page-container__subtitle') + const sendCopy = await queryAsync($, '.page-container__subtitle') assert.equal(sendCopy[0].textContent, 'Only send ETH to an Ethereum address.', 'Send screen has copy') - const sendFromField = $('.send-v2__form-field') + const sendFromField = await queryAsync($, '.send-v2__form-field') assert.ok(sendFromField[0], 'send screen has a from field') - let sendFromFieldItemAddress = $('.account-list-item__account-name') + let sendFromFieldItemAddress = await queryAsync($, '.account-list-item__account-name') assert.equal(sendFromFieldItemAddress[0].textContent, 'Send Account 4', 'send from field shows correct account name') - const sendFromFieldItem = $('.account-list-item') + await timeout() + const sendFromFieldItem = await queryAsync($, '.account-list-item') sendFromFieldItem[0].click() - await timeout() - - const sendFromDropdownList = $('.send-v2__from-dropdown__list') + const sendFromDropdownList = await queryAsync($, '.send-v2__from-dropdown__list') assert.equal(sendFromDropdownList.children().length, 4, 'send from dropdown shows all accounts') console.log(`!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! sendFromDropdownList.children()[1]`, sendFromDropdownList.children()[1]); sendFromDropdownList.children()[1].click() - await timeout() - - sendFromFieldItemAddress = $('.account-list-item__account-name') + sendFromFieldItemAddress = await queryAsync($, '.account-list-item__account-name') console.log(`!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! sendFromFieldItemAddress[0]`, sendFromFieldItemAddress[0]); assert.equal(sendFromFieldItemAddress[0].textContent, 'Send Account 2', 'send from field dropdown changes account name') - let sendToFieldInput = $('.send-v2__to-autocomplete__input') + let sendToFieldInput = await queryAsync($, '.send-v2__to-autocomplete__input') sendToFieldInput[0].focus() - await timeout() - - const sendToDropdownList = $('.send-v2__from-dropdown__list') + const sendToDropdownList = await queryAsync($, '.send-v2__from-dropdown__list') assert.equal(sendToDropdownList.children().length, 5, 'send to dropdown shows all accounts and address book accounts') sendToDropdownList.children()[2].click() - await timeout() - const sendToAccountAddress = sendToFieldInput.val() assert.equal(sendToAccountAddress, '0x2f8d4a878cfa04a6e60d46362f5644deab66572d', 'send to dropdown selects the correct address') - const sendAmountField = $('.send-v2__form-row:eq(2)') + const sendAmountField = await queryAsync($, '.send-v2__form-row:eq(2)') sendAmountField.find('.currency-display')[0].click() - await timeout() - - const sendAmountFieldInput = sendAmountField.find('input:text') + const sendAmountFieldInput = await findAsync(sendAmountField, 'input:text') sendAmountFieldInput.val('5.1') reactTriggerChange(sendAmountField.find('input')[0]) - await timeout() - - let errorMessage = $('.send-v2__error') + let errorMessage = await queryAsync($, '.send-v2__error') assert.equal(errorMessage[0].textContent, 'Insufficient funds.', 'send should render an insufficient fund error message') sendAmountFieldInput.val('2.0') reactTriggerChange(sendAmountFieldInput[0]) - await timeout() errorMessage = $('.send-v2__error') assert.equal(errorMessage.length, 0, 'send should stop rendering amount error message after amount is corrected') - const sendGasField = $('.send-v2__gas-fee-display') + const sendGasField = await queryAsync($, '.send-v2__gas-fee-display') assert.equal( sendGasField.find('.currency-display__input-wrapper > input').val(), '0.000198', @@ -106,120 +95,86 @@ async function runSendFlowTest(assert, done) { 'send gas field should show estimated gas total converted to USD' ) - const sendGasOpenCustomizeModalButton = $('.send-v2__sliders-icon-container' - ) + const sendGasOpenCustomizeModalButton = await queryAsync($, '.send-v2__sliders-icon-container') sendGasOpenCustomizeModalButton[0].click() - await timeout(1000) - - const customizeGasModal = $('.send-v2__customize-gas') + const customizeGasModal = await queryAsync($, '.send-v2__customize-gas') assert.ok(customizeGasModal[0], 'should render the customize gas modal') - const customizeGasPriceInput = $('.send-v2__gas-modal-card').first().find('input') + const customizeGasPriceInput = (await queryAsync($, '.send-v2__gas-modal-card')).first().find('input') customizeGasPriceInput.val(50) reactTriggerChange(customizeGasPriceInput[0]) - const customizeGasLimitInput = $('.send-v2__gas-modal-card').last().find('input') + const customizeGasLimitInput = (await queryAsync($, '.send-v2__gas-modal-card')).last().find('input') customizeGasLimitInput.val(60000) reactTriggerChange(customizeGasLimitInput[0]) - await timeout() - - const customizeGasSaveButton = $('.send-v2__customize-gas__save') + const customizeGasSaveButton = await queryAsync($, '.send-v2__customize-gas__save') customizeGasSaveButton[0].click() - await timeout() - assert.equal( - sendGasField.find('.currency-display__input-wrapper > input').val(), + (await findAsync(sendGasField, '.currency-display__input-wrapper > input')).val(), '0.003', 'send gas field should show customized gas total' ) assert.equal( - sendGasField.find('.currency-display__converted-value')[0].textContent, + (await findAsync(sendGasField, '.currency-display__converted-value'))[0].textContent, '3.60 USD', 'send gas field should show customized gas total converted to USD' ) - const sendButton = $('button.btn-clear.page-container__footer-button') + const sendButton = await queryAsync($, 'button.btn-clear.page-container__footer-button') assert.equal(sendButton[0].textContent, 'Next', 'next button rendered') sendButton[0].click() - - await timeout(2000) + await timeout() selectState.val('send edit') reactTriggerChange(selectState[0]) - await timeout(2000) - - const confirmFromName = $('.confirm-screen-account-name').first() + const confirmFromName = (await queryAsync($, '.confirm-screen-account-name')).first() assert.equal(confirmFromName[0].textContent, 'Send Account 2', 'confirm screen should show correct from name') - const confirmToName = $('.confirm-screen-account-name').last() + const confirmToName = (await queryAsync($, '.confirm-screen-account-name')).last() assert.equal(confirmToName[0].textContent, 'Send Account 3', 'confirm screen should show correct to name') - const confirmScreenRows = $('.confirm-screen-rows') + const confirmScreenRows = await queryAsync($, '.confirm-screen-rows') const confirmScreenGas = confirmScreenRows.find('.confirm-screen-row-info')[2] assert.equal(confirmScreenGas.textContent, '3.6 USD', 'confirm screen should show correct gas') const confirmScreenTotal = confirmScreenRows.find('.confirm-screen-row-info')[3] assert.equal(confirmScreenTotal.textContent, '2405.36 USD', 'confirm screen should show correct total') - const confirmScreenBackButton = $('.confirm-screen-back-button') + const confirmScreenBackButton = await queryAsync($, '.confirm-screen-back-button') confirmScreenBackButton[0].click() - await timeout(1000) - - const sendFromFieldItemInEdit = $('.account-list-item') + const sendFromFieldItemInEdit = await queryAsync($, '.account-list-item') sendFromFieldItemInEdit[0].click() - await timeout() - - const sendFromDropdownListInEdit = $('.send-v2__from-dropdown__list') + const sendFromDropdownListInEdit = await queryAsync($, '.send-v2__from-dropdown__list') sendFromDropdownListInEdit.children()[2].click() - await timeout() - - const sendToFieldInputInEdit = $('.send-v2__to-autocomplete__input') + const sendToFieldInputInEdit = await queryAsync($, '.send-v2__to-autocomplete__input') sendToFieldInputInEdit[0].focus() sendToFieldInputInEdit.val('0xd85a4b6a394794842887b8284293d69163007bbb') - await timeout() - - const sendAmountFieldInEdit = $('.send-v2__form-row:eq(2)') + const sendAmountFieldInEdit = await queryAsync($, '.send-v2__form-row:eq(2)') sendAmountFieldInEdit.find('.currency-display')[0].click() - await timeout() - const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('input:text') sendAmountFieldInputInEdit.val('1.0') reactTriggerChange(sendAmountFieldInputInEdit[0]) - await timeout() - - const sendButtonInEdit = $('.btn-clear.page-container__footer-button') + const sendButtonInEdit = await queryAsync($, '.btn-clear.page-container__footer-button') assert.equal(sendButtonInEdit[0].textContent, 'Next', 'next button in edit rendered') sendButtonInEdit[0].click() - await timeout() - // TODO: Need a way to mock background so that we can test correct transition from editing to confirm selectState.val('confirm new ui') reactTriggerChange(selectState[0]) - - await timeout(2000) - const confirmScreenConfirmButton = $('.confirm-screen-confirm-button') + const confirmScreenConfirmButton = await queryAsync($, '.confirm-screen-confirm-button') console.log(`+++++++++++++++++++++++++++++++= confirmScreenConfirmButton[0]`, confirmScreenConfirmButton[0]); confirmScreenConfirmButton[0].click() - await timeout(2000) - - const txView = $('.tx-view') + const txView = await queryAsync($, '.tx-view') console.log(`++++++++++++++++++++++++++++++++ txView[0]`, txView[0]); assert.ok(txView[0], 'Should return to the account details screen after confirming') } - -function timeout (time) { - return new Promise((resolve, reject) => { - setTimeout(resolve, time || 1500) - }) -} \ No newline at end of file diff --git a/test/lib/util.js b/test/lib/util.js new file mode 100644 index 000000000..626280745 --- /dev/null +++ b/test/lib/util.js @@ -0,0 +1,53 @@ +module.exports = { + timeout, + queryAsync, + findAsync, + pollUntilTruthy, +} + +function timeout (time) { + return new Promise((resolve, reject) => { + setTimeout(resolve, time || 1500) + }) +} + +async function findAsync(container, selector, opts) { + try { + return await pollUntilTruthy(() => { + const result = container.find(selector) + if (result.length > 0) return result + }, opts) + } catch (err) { + throw new Error(`Failed to find element within interval: "${selector}"`) + } +} + +async function queryAsync(jQuery, selector, opts) { + try { + return await pollUntilTruthy(() => { + const result = jQuery(selector) + if (result.length > 0) return result + }, opts) + } catch (err) { + throw new Error(`Failed to find element within interval: "${selector}"`) + } +} + +async function pollUntilTruthy(fn, opts = {}){ + const pollingInterval = opts.pollingInterval || 100 + const timeoutInterval = opts.timeoutInterval || 5000 + const start = Date.now() + let result + while (!result) { + // check if timedout + const now = Date.now() + if ((now - start) > timeoutInterval) { + throw new Error(`pollUntilTruthy - failed to return truthy within interval`) + } + // check for result + result = fn() + // run again after timeout + await timeout(pollingInterval, timeoutInterval) + } + return result +} From 07fa26d1f094a7a405107c5f31674e65c94cea8d Mon Sep 17 00:00:00 2001 From: kumavis Date: Sun, 11 Mar 2018 18:52:27 -0700 Subject: [PATCH 5/8] test - add comment about test behavior oddity --- test/integration/index.js | 2 +- test/integration/lib/send-new-ui.js | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/test/integration/index.js b/test/integration/index.js index 144303dbb..b266ddf37 100644 --- a/test/integration/index.js +++ b/test/integration/index.js @@ -23,4 +23,4 @@ pump( console.log(`Integration test build completed: "${bundlePath}"`) process.exit(0) } -) \ No newline at end of file +) diff --git a/test/integration/lib/send-new-ui.js b/test/integration/lib/send-new-ui.js index 07be63a36..faab10fdf 100644 --- a/test/integration/lib/send-new-ui.js +++ b/test/integration/lib/send-new-ui.js @@ -43,17 +43,15 @@ async function runSendFlowTest(assert, done) { let sendFromFieldItemAddress = await queryAsync($, '.account-list-item__account-name') assert.equal(sendFromFieldItemAddress[0].textContent, 'Send Account 4', 'send from field shows correct account name') - await timeout() const sendFromFieldItem = await queryAsync($, '.account-list-item') sendFromFieldItem[0].click() + // this seems to fail if the firefox window is not in focus... const sendFromDropdownList = await queryAsync($, '.send-v2__from-dropdown__list') assert.equal(sendFromDropdownList.children().length, 4, 'send from dropdown shows all accounts') - console.log(`!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! sendFromDropdownList.children()[1]`, sendFromDropdownList.children()[1]); sendFromDropdownList.children()[1].click() sendFromFieldItemAddress = await queryAsync($, '.account-list-item__account-name') - console.log(`!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! sendFromFieldItemAddress[0]`, sendFromFieldItemAddress[0]); assert.equal(sendFromFieldItemAddress[0].textContent, 'Send Account 2', 'send from field dropdown changes account name') let sendToFieldInput = await queryAsync($, '.send-v2__to-autocomplete__input') From ecb792ae502dcacf168fb288ef0f2623e599eb78 Mon Sep 17 00:00:00 2001 From: kumavis Date: Sun, 11 Mar 2018 19:06:14 -0700 Subject: [PATCH 6/8] genStates - fix fs api calls --- development/genStates.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/development/genStates.js b/development/genStates.js index 325ef71f3..d92e2bc2e 100644 --- a/development/genStates.js +++ b/development/genStates.js @@ -6,11 +6,11 @@ start().catch(console.error) async function start () { const statesPath = path.join(__dirname, 'states') - const stateFilesNames = await promisify(fs.readdirSync)(statesPath) + const stateFilesNames = await promisify(fs.readdir)(statesPath) const states = {} await Promise.all(stateFilesNames.map(async (stateFileName) => { const stateFilePath = path.join(__dirname, 'states', stateFileName) - const stateFileContent = await promisify(fs.readFileSync)(stateFilePath, 'utf8') + const stateFileContent = await promisify(fs.readFile)(stateFilePath, 'utf8') const state = JSON.parse(stateFileContent) const stateName = stateFileName.split('.')[0].replace(/-/g, ' ', 'g') states[stateName] = state From 3537c391db30bdec1d3fabb361dffc2e4a3931da Mon Sep 17 00:00:00 2001 From: kumavis Date: Sun, 11 Mar 2018 19:29:35 -0700 Subject: [PATCH 7/8] test - use new async selectors for faster testing --- test/integration/lib/confirm-sig-requests.js | 44 ++++----- test/integration/lib/mascara-first-time.js | 94 +++++++------------- 2 files changed, 49 insertions(+), 89 deletions(-) diff --git a/test/integration/lib/confirm-sig-requests.js b/test/integration/lib/confirm-sig-requests.js index e49424c37..9737a2283 100644 --- a/test/integration/lib/confirm-sig-requests.js +++ b/test/integration/lib/confirm-sig-requests.js @@ -1,5 +1,9 @@ const reactTriggerChange = require('react-trigger-change') - +const { + timeout, + queryAsync, + findAsync, +} = require('../../lib/util') const PASSWORD = 'password123' QUnit.module('confirm sig requests') @@ -13,55 +17,41 @@ QUnit.test('successful confirmation of sig requests', (assert) => { }) async function runConfirmSigRequestsTest(assert, done) { - let selectState = $('select') + let selectState = await queryAsync($, 'select') selectState.val('confirm sig requests') reactTriggerChange(selectState[0]) - await timeout(2000) - - let confirmSigHeadline = $('.request-signature__headline') + let confirmSigHeadline = await queryAsync($, '.request-signature__headline') assert.equal(confirmSigHeadline[0].textContent, 'Your signature is being requested') - let confirmSigRowValue = $('.request-signature__row-value') + let confirmSigRowValue = await queryAsync($, '.request-signature__row-value') assert.ok(confirmSigRowValue[0].textContent.match(/^\#\sTerms\sof\sUse/)) - let confirmSigSignButton = $('.request-signature__footer__sign-button') + let confirmSigSignButton = await queryAsync($, '.request-signature__footer__sign-button') confirmSigSignButton[0].click() - await timeout(2000) - - confirmSigHeadline = $('.request-signature__headline') + confirmSigHeadline = await queryAsync($, '.request-signature__headline') assert.equal(confirmSigHeadline[0].textContent, 'Your signature is being requested') - let confirmSigMessage = $('.request-signature__notice') + let confirmSigMessage = await queryAsync($, '.request-signature__notice') assert.ok(confirmSigMessage[0].textContent.match(/^Signing\sthis\smessage/)) - confirmSigRowValue = $('.request-signature__row-value') + confirmSigRowValue = await queryAsync($, '.request-signature__row-value') assert.equal(confirmSigRowValue[0].textContent, '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0') - confirmSigSignButton = $('.request-signature__footer__sign-button') + confirmSigSignButton = await queryAsync($, '.request-signature__footer__sign-button') confirmSigSignButton[0].click() - await timeout(2000) - - confirmSigHeadline = $('.request-signature__headline') + confirmSigHeadline = await queryAsync($, '.request-signature__headline') assert.equal(confirmSigHeadline[0].textContent, 'Your signature is being requested') - confirmSigRowValue = $('.request-signature__row-value') + confirmSigRowValue = await queryAsync($, '.request-signature__row-value') assert.equal(confirmSigRowValue[0].textContent, 'Hi, Alice!') assert.equal(confirmSigRowValue[1].textContent, '1337') - confirmSigSignButton = $('.request-signature__footer__sign-button') + confirmSigSignButton = await queryAsync($, '.request-signature__footer__sign-button') confirmSigSignButton[0].click() - await timeout(2000) - - const txView = $('.tx-view') + const txView = await queryAsync($, '.tx-view') assert.ok(txView[0], 'Should return to the account details screen after confirming') } - -function timeout (time) { - return new Promise((resolve, reject) => { - setTimeout(resolve, time || 1500) - }) -} \ No newline at end of file diff --git a/test/integration/lib/mascara-first-time.js b/test/integration/lib/mascara-first-time.js index 515c7f383..d1afe12a0 100644 --- a/test/integration/lib/mascara-first-time.js +++ b/test/integration/lib/mascara-first-time.js @@ -1,119 +1,95 @@ const PASSWORD = 'password123' const reactTriggerChange = require('react-trigger-change') +const { + timeout, + findAsync, + queryAsync, +} = require('../../lib/util') async function runFirstTimeUsageTest (assert, done) { await timeout(4000) - const app = $('#app-content') + const app = await queryAsync($, '#app-content') await skipNotices(app) - await timeout() - // Scroll through terms - const title = app.find('.create-password__title').text() + const title = await findAsync(app, '.create-password__title').text() assert.equal(title, 'Create Password', 'create password screen') // enter password - const pwBox = app.find('.first-time-flow__input')[0] - const confBox = app.find('.first-time-flow__input')[1] + const pwBox = (await findAsync(app, '.first-time-flow__input'))[0] + const confBox = (await findAsync(app, '.first-time-flow__input'))[1] pwBox.value = PASSWORD confBox.value = PASSWORD reactTriggerChange(pwBox) reactTriggerChange(confBox) - - await timeout() - // Create Password - const createButton = app.find('button.first-time-flow__button')[0] + const createButton = (await findAsync(app, 'button.first-time-flow__button'))[0] createButton.click() - await timeout(3000) - - const created = app.find('.unique-image__title')[0] + const created = (await findAsync(app, '.unique-image__title'))[0] assert.equal(created.textContent, 'Your unique account image', 'unique image screen') // Agree button - let button = app.find('button')[0] + let button = (await findAsync(app, 'button'))[0] assert.ok(button, 'button present') button.click() - await timeout(1000) - await skipNotices(app) // secret backup phrase - const seedTitle = app.find('.backup-phrase__title')[0] + const seedTitle = (await findAsync(app, '.backup-phrase__title'))[0] assert.equal(seedTitle.textContent, 'Secret Backup Phrase', 'seed phrase screen') - app.find('.backup-phrase__reveal-button').click() - - await timeout(1000) - const seedPhrase = app.find('.backup-phrase__secret-words').text().split(' ') - app.find('.first-time-flow__button').click() + ;(await findAsync(app, '.backup-phrase__reveal-button')).click() + const seedPhrase = (await findAsync(app, '.backup-phrase__secret-words')).text().split(' ') + ;(await findAsync(app, '.first-time-flow__button')).click() + await timeout() const selectPhrase = text => { const option = $('.backup-phrase__confirm-seed-option') .filter((i, d) => d.textContent === text)[0] - $(option).click() } - await timeout(1000) - seedPhrase.forEach(sp => selectPhrase(sp)) - app.find('.first-time-flow__button').click() - await timeout(1000) + ;(await findAsync(app, '.first-time-flow__button')).click() // Deposit Ether Screen - const buyEthTitle = app.find('.buy-ether__title')[0] + const buyEthTitle = (await findAsync(app, '.buy-ether__title'))[0] assert.equal(buyEthTitle.textContent, 'Deposit Ether', 'deposit ether screen') - app.find('.buy-ether__do-it-later').click() - await timeout(1000) + ;(await findAsync(app, '.buy-ether__do-it-later')).click() - const menu = app.find('.account-menu__icon')[0] + const menu = (await findAsync(app, '.account-menu__icon'))[0] menu.click() - await timeout() - - const lock = app.find('.account-menu__logout-button')[0] + const lock = (await findAsync(app, '.account-menu__logout-button'))[0] assert.ok(lock, 'Lock menu item found') lock.click() - await timeout(1000) - - const pwBox2 = app.find('#password-box')[0] + const pwBox2 = (await findAsync(app, '#password-box'))[0] pwBox2.value = PASSWORD - const createButton2 = app.find('button.primary')[0] + const createButton2 = (await findAsync(app, 'button.primary'))[0] createButton2.click() - await timeout(1000) - - const detail2 = app.find('.wallet-view')[0] + const detail2 = (await findAsync(app, '.wallet-view'))[0] assert.ok(detail2, 'Account detail section loaded again.') - await timeout() - // open account settings dropdown - const qrButton = app.find('.wallet-view__details-button')[0] + const qrButton = (await findAsync(app, '.wallet-view__details-button'))[0] qrButton.click() - await timeout(1000) - - const qrHeader = app.find('.editable-label__value')[0] - const qrContainer = app.find('.qr-wrapper')[0] + const qrHeader = (await findAsync(app, '.editable-label__value'))[0] + const qrContainer = (await findAsync(app, '.qr-wrapper'))[0] assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.') assert.ok(qrContainer, 'QR Container found') - await timeout() - - const networkMenu = app.find('.network-component')[0] + const networkMenu = (await findAsync(app, '.network-component'))[0] networkMenu.click() - await timeout() - - const networkMenu2 = app.find('.network-indicator')[0] + const networkMenu2 = (await findAsync(app, '.network-indicator'))[0] const children2 = networkMenu2.children children2.length[3] assert.ok(children2, 'All network options present') @@ -121,18 +97,12 @@ async function runFirstTimeUsageTest (assert, done) { module.exports = runFirstTimeUsageTest -function timeout (time) { - return new Promise((resolve, reject) => { - setTimeout(resolve, time || 1500) - }) -} - async function skipNotices (app) { while (true) { - const button = app.find('button') + const button = await findAsync(app, 'button') if (button && button.html() === 'Accept') { // still notices to accept - const termsPage = app.find('.markdown')[0] + const termsPage = (await findAsync(app, '.markdown'))[0] if (!termsPage) { break } From 4bc69f72c3033cc9ea3e732f48da22d15c41a972 Mon Sep 17 00:00:00 2001 From: kumavis Date: Sun, 11 Mar 2018 21:39:46 -0700 Subject: [PATCH 8/8] test - mascara - fix async wrap --- test/integration/lib/mascara-first-time.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/lib/mascara-first-time.js b/test/integration/lib/mascara-first-time.js index d1afe12a0..bcbc94ff6 100644 --- a/test/integration/lib/mascara-first-time.js +++ b/test/integration/lib/mascara-first-time.js @@ -14,7 +14,7 @@ async function runFirstTimeUsageTest (assert, done) { await skipNotices(app) // Scroll through terms - const title = await findAsync(app, '.create-password__title').text() + const title = (await findAsync(app, '.create-password__title')).text() assert.equal(title, 'Create Password', 'create password screen') // enter password