diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/base.conf.js | 3 | ||||
-rw-r--r-- | test/e2e/metamask.spec.js | 34 | ||||
-rw-r--r-- | test/integration/lib/add-token.js | 71 | ||||
-rw-r--r-- | test/screens/new-ui.js | 225 |
4 files changed, 207 insertions, 126 deletions
diff --git a/test/base.conf.js b/test/base.conf.js index e2e9d44ba..956dce011 100644 --- a/test/base.conf.js +++ b/test/base.conf.js @@ -6,6 +6,9 @@ module.exports = function(config) { // base path that will be used to resolve all patterns (eg. files, exclude) basePath: process.cwd(), + // Uncomment to allow for longer timeouts + // browserNoActivityTimeout: 100000000, + browserConsoleLogOptions: { terminal: false, }, diff --git a/test/e2e/metamask.spec.js b/test/e2e/metamask.spec.js index 707ca2560..8ec7de16c 100644 --- a/test/e2e/metamask.spec.js +++ b/test/e2e/metamask.spec.js @@ -30,6 +30,18 @@ describe('Metamask popup page', function () { }) afterEach(async function () { + // logs command not supported in firefox + // https://github.com/SeleniumHQ/selenium/issues/2910 + if (process.env.SELENIUM_BROWSER === 'chrome') { + // check for console errors + const errors = await checkBrowserForConsoleErrors() + if (errors.length) { + const errorReports = errors.map(err => err.message) + const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` + this.test.error(new Error(errorMessage)) + } + } + // gather extra data if test failed if (this.currentTest.state === 'failed') { await verboseReportOnFailure(this.currentTest) } @@ -300,13 +312,33 @@ describe('Metamask popup page', function () { await driver.executeScript('window.metamask.setProviderType(arguments[0])', type) } + async function checkBrowserForConsoleErrors() { + const ignoredLogTypes = ['WARNING'] + const ignoredErrorMessages = [ + // React throws error warnings on "dataset", but still sets the data-* properties correctly + 'Warning: Unknown prop `dataset` on ', + // Third-party Favicon 404s show up as errors + 'favicon.ico - Failed to load resource: the server responded with a status of 404 (Not Found)', + // React Development build - known issue blocked by test build sys + 'Warning: It looks like you\'re using a minified copy of the development build of React.', + // Redux Development build - known issue blocked by test build sys + 'This means that you are running a slower development build of Redux.', + ] + const browserLogs = await driver.manage().logs().get('browser') + const errorEntries = browserLogs.filter(entry => !ignoredLogTypes.includes(entry.level.toString())) + const errorObjects = errorEntries.map(entry => entry.toJSON()) + // ignore all errors that contain a message in `ignoredErrorMessages` + const matchedErrorObjects = errorObjects.filter(entry => !ignoredErrorMessages.some(message => entry.message.includes(message))) + return matchedErrorObjects + } + async function verboseReportOnFailure (test) { let artifactDir if (process.env.SELENIUM_BROWSER === 'chrome') { artifactDir = `./test-artifacts/chrome/${test.title}` } else if (process.env.SELENIUM_BROWSER === 'firefox') { artifactDir = `./test-artifacts/firefox/${test.title}` - } + } const filepathBase = `${artifactDir}/test-failure` await pify(mkdirp)(artifactDir) // capture screenshot diff --git a/test/integration/lib/add-token.js b/test/integration/lib/add-token.js index 1840bdd39..e51c854d2 100644 --- a/test/integration/lib/add-token.js +++ b/test/integration/lib/add-token.js @@ -22,6 +22,11 @@ async function runAddTokenFlowTest (assert, done) { selectState.val('add token') reactTriggerChange(selectState[0]) + // Used to set values on TextField input component + const nativeInputValueSetter = Object.getOwnPropertyDescriptor( + window.HTMLInputElement.prototype, 'value' + ).set + // Check that no tokens have been added assert.ok($('.token-list-item').length === 0, 'no tokens added') @@ -31,14 +36,14 @@ async function runAddTokenFlowTest (assert, done) { addTokenButton[0].click() // Verify Add Token screen - let addTokenWrapper = await queryAsync($, '.add-token__wrapper') + let addTokenWrapper = await queryAsync($, '.page-container') assert.ok(addTokenWrapper[0], 'add token wrapper renders') - let addTokenTitle = await queryAsync($, '.add-token__header__title') + let addTokenTitle = await queryAsync($, '.page-container__title') assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct') // Cancel Add Token - const cancelAddTokenButton = await queryAsync($, 'button.btn-secondary--lg.add-token__cancel-button') + const cancelAddTokenButton = await queryAsync($, 'button.btn-secondary--lg.page-container__footer-button') assert.ok(cancelAddTokenButton[0], 'cancel add token button present') cancelAddTokenButton.click() @@ -50,20 +55,22 @@ async function runAddTokenFlowTest (assert, done) { addTokenButton[0].click() // Verify Add Token Screen - addTokenWrapper = await queryAsync($, '.add-token__wrapper') - addTokenTitle = await queryAsync($, '.add-token__header__title') + addTokenWrapper = await queryAsync($, '.page-container') + addTokenTitle = await queryAsync($, '.page-container__title') assert.ok(addTokenWrapper[0], 'add token wrapper renders') assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct') // Search for token - const searchInput = await queryAsync($, 'input.add-token__input') - searchInput.val('a') - reactTriggerChange(searchInput[0]) + const searchInput = (await findAsync(addTokenWrapper, '#search-tokens'))[0] + searchInput.focus() + await timeout(1000) + nativeInputValueSetter.call(searchInput, 'a') + searchInput.dispatchEvent(new Event('input', { bubbles: true})) // Click token to add - const tokenWrapper = await queryAsync($, 'div.add-token__token-wrapper') + const tokenWrapper = await queryAsync($, 'div.token-list__token') assert.ok(tokenWrapper[0], 'token found') - const tokenImageProp = tokenWrapper.find('.add-token__token-icon').css('background-image') + const tokenImageProp = tokenWrapper.find('.token-list__token-icon').css('background-image') const tokenImageUrl = tokenImageProp.slice(5, -2) tokenWrapper[0].click() @@ -73,11 +80,8 @@ async function runAddTokenFlowTest (assert, done) { nextButton[0].click() // Confirm Add token - assert.equal( - $('.add-token__description')[0].textContent, - 'Token balance(s)', - 'confirm add token rendered' - ) + const confirmAddToken = await queryAsync($, '.confirm-add-token') + assert.ok(confirmAddToken[0], 'confirm add token rendered') assert.ok($('button.btn-primary--lg')[0], 'confirm add token button found') $('button.btn-primary--lg')[0].click() @@ -91,39 +95,46 @@ async function runAddTokenFlowTest (assert, done) { assert.ok(addTokenButton[0], 'add token button present') addTokenButton[0].click() - const addTokenTabs = await queryAsync($, '.add-token__header__tabs__tab') + addTokenWrapper = await queryAsync($, '.page-container') + const addTokenTabs = await queryAsync($, '.page-container__tab') assert.equal(addTokenTabs.length, 2, 'expected number of tabs') assert.equal(addTokenTabs[1].textContent, 'Custom Token', 'Custom Token tab present') assert.ok(addTokenTabs[1], 'add custom token tab present') addTokenTabs[1].click() + await timeout(1000) // Input token contract address - const customInput = await queryAsync($, 'input.add-token__add-custom-input') - customInput.val('0x177af043D3A1Aed7cc5f2397C70248Fc6cDC056c') - reactTriggerChange(customInput[0]) + const customInput = (await findAsync(addTokenWrapper, '#custom-address'))[0] + customInput.focus() + await timeout(1000) + nativeInputValueSetter.call(customInput, '0x177af043D3A1Aed7cc5f2397C70248Fc6cDC056c') + customInput.dispatchEvent(new Event('input', { bubbles: true})) + // Click Next button - nextButton = await queryAsync($, 'button.btn-primary--lg') - assert.equal(nextButton[0].textContent, 'Next', 'next button rendered') - nextButton[0].click() + // nextButton = await queryAsync($, 'button.btn-primary--lg') + // assert.equal(nextButton[0].textContent, 'Next', 'next button rendered') + // nextButton[0].click() - // Verify symbol length error since contract address won't return symbol - const errorMessage = await queryAsync($, '.add-token__add-custom-error-message') + // // Verify symbol length error since contract address won't return symbol + const errorMessage = await queryAsync($, '#custom-symbol-helper-text') assert.ok(errorMessage[0], 'error rendered') $('button.btn-secondary--lg')[0].click() - // // Confirm Add token + // await timeout(100000) + + // Confirm Add token // assert.equal( - // $('.add-token__description')[0].textContent, + // $('.page-container__subtitle')[0].textContent, // 'Would you like to add these tokens?', // 'confirm add token rendered' // ) // assert.ok($('button.btn-primary--lg')[0], 'confirm add token button found') // $('button.btn-primary--lg')[0].click() - // // Verify added token image - // heroBalance = await queryAsync($, '.hero-balance') - // assert.ok(heroBalance, 'rendered hero balance') - // assert.ok(heroBalance.find('.identicon')[0], 'token added') + // Verify added token image + heroBalance = await queryAsync($, '.hero-balance') + assert.ok(heroBalance, 'rendered hero balance') + assert.ok(heroBalance.find('.identicon')[0], 'token added') } diff --git a/test/screens/new-ui.js b/test/screens/new-ui.js index 6a8822eb3..6b873ac85 100644 --- a/test/screens/new-ui.js +++ b/test/screens/new-ui.js @@ -5,29 +5,44 @@ const mkdirp = require('mkdirp') const rimraf = require('rimraf') const webdriver = require('selenium-webdriver') const endOfStream = require('end-of-stream') +const clipboardy = require('clipboardy') +const Ethjs = require('ethjs') const GIFEncoder = require('gifencoder') const pngFileStream = require('png-file-stream') const sizeOfPng = require('image-size/lib/types/png') const By = webdriver.By const { delay, buildWebDriver } = require('./func') const localesIndex = require('../../app/_locales/index.json') +// const localesIndex = [] + +const eth = new Ethjs(new Ethjs.HttpProvider('http://localhost:8545')) let driver +let screenshotCount = 0 + +captureAllScreens() +.then(async () => { + // build screenshots into gif + console.log('building gif...') + await generateGif() -captureAllScreens().catch((err) => { + await driver.quit() + process.exit() +}) +.catch(async (err) => { try { console.error(err) - verboseReportOnFailure() - driver.quit() + verboseReportOnFailure({ title: 'something broke' }) } catch (err) { console.error(err) } + + await driver.quit() process.exit(1) }) -async function captureAllScreens() { - let screenshotCount = 0 +async function captureAllScreens() { // common names let button let tabs @@ -74,10 +89,11 @@ async function captureAllScreens() { await driver.findElement(By.css('button')).click() await captureLanguageScreenShots('create password') + const password = '123456789' const passwordBox = await driver.findElement(By.css('input#create-password')) const passwordBoxConfirm = await driver.findElement(By.css('input#confirm-password')) - passwordBox.sendKeys('123456789') - passwordBoxConfirm.sendKeys('123456789') + passwordBox.sendKeys(password) + passwordBoxConfirm.sendKeys(password) await delay(500) await captureLanguageScreenShots('choose-password-filled') @@ -111,109 +127,123 @@ async function captureAllScreens() { await delay(300) await captureLanguageScreenShots('secret backup phrase - reveal') + const seedPhrase = await driver.findElement(By.css('.backup-phrase__secret-words')).getText() + const seedPhraseWords = seedPhrase.split(' ') await driver.findElement(By.css('button')).click() await delay(300) await captureLanguageScreenShots('confirm secret backup phrase') - // finish up - console.log('building gif...') - await generateGif() - await driver.quit() - return - - // - // await button.click() - // await delay(700) - // this.seedPhase = await driver.findElement(By.css('.twelve-word-phrase')).getText() - // await captureScreenShot('seed phrase') - // - // const continueAfterSeedPhrase = await driver.findElement(By.css('button')) - // await continueAfterSeedPhrase.click() - // await delay(300) - // await captureScreenShot('main screen') - // - // await driver.findElement(By.css('.sandwich-expando')).click() - // await delay(500) - // await captureScreenShot('menu') - - // await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)')).click() - // await captureScreenShot('main screen') - // it('should accept account password after lock', async () => { - // await delay(500) - // await driver.findElement(By.id('password-box')).sendKeys('123456789') - // await driver.findElement(By.css('button')).click() - // await delay(500) - // }) - // - // it('should show QR code option', async () => { - // await delay(300) - // await driver.findElement(By.css('.fa-ellipsis-h')).click() - // await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div:nth-child(1) > flex-column > div.name-label > div > span > i > div > div > li:nth-child(3)')).click() - // await delay(300) - // }) - // - // it('should show the account address', async () => { - // this.accountAddress = await driver.findElement(By.css('.ellip-address')).getText() - // await driver.findElement(By.css('.fa-arrow-left')).click() - // await delay(500) - // }) - - async function captureLanguageScreenShots(label) { - const nonEnglishLocales = localesIndex.filter(localeMeta => localeMeta.code !== 'en') - // take english shot - await captureScreenShot(`${label} (en)`) - for (let localeMeta of nonEnglishLocales) { - // set locale and take shot - await setLocale(localeMeta.code) - await delay(300) - await captureScreenShot(`${label} (${localeMeta.code})`) - } - // return locale to english - await setLocale('en') - await delay(300) + // enter seed phrase + const seedPhraseButtons = await driver.findElements(By.css('.backup-phrase__confirm-seed-options > button')) + const seedPhraseButtonWords = await Promise.all(seedPhraseButtons.map(button => button.getText())) + for (let targetWord of seedPhraseWords) { + const wordIndex = seedPhraseButtonWords.indexOf(targetWord) + if (wordIndex === -1) throw new Error(`Captured seed phrase word "${targetWord}" not in found seed phrase button options ${seedPhraseButtonWords.join(' ')}`) + await driver.findElement(By.css(`.backup-phrase__confirm-seed-options > button:nth-child(${wordIndex+1})`)).click() + await delay(100) } + await captureLanguageScreenShots('confirm secret backup phrase - words selected correctly') - async function setLocale(code) { - await driver.executeScript('window.metamask.updateCurrentLocale(arguments[0])', code) - } + await driver.findElement(By.css('.backup-phrase__content-wrapper .first-time-flow__button')).click() + await delay(300) + await captureLanguageScreenShots('metamask post-initialize greeter screen deposit ether') - async function setProviderType(type) { - await driver.executeScript('window.metamask.setProviderType(arguments[0])', type) - } + await driver.findElement(By.css('.page-container__header-close')).click() + await delay(300) + await captureLanguageScreenShots('metamask account main screen') - // cleanup - await driver.quit() + // account details + export private key + await driver.findElement(By.css('.wallet-view__name-container > .wallet-view__details-button')).click() + await delay(300) + await captureLanguageScreenShots('metamask account detail screen') - async function cleanScreenShotDir() { - await pify(rimraf)(`./test-artifacts/screens/`) - } + await driver.findElement(By.css('.account-modal__button:nth-of-type(2)')).click() + await delay(300) + await captureLanguageScreenShots('metamask account detail export private key screen - initial') - async function captureScreenShot(label) { - const shotIndex = screenshotCount.toString().padStart(4, '0') - screenshotCount++ - const artifactDir = `./test-artifacts/screens/` - await pify(mkdirp)(artifactDir) - // capture screenshot - const screenshot = await driver.takeScreenshot() - await pify(fs.writeFile)(`${artifactDir}/${shotIndex} - ${label}.png`, screenshot, { encoding: 'base64' }) - } + await driver.findElement(By.css('.private-key-password > input')).sendKeys(password) + await delay(300) + await captureLanguageScreenShots('metamask account detail export private key screen - password entered') - async function generateGif(){ - // calculate screenshot size - const screenshot = await driver.takeScreenshot() - const pngBuffer = Buffer.from(screenshot, 'base64') - const size = sizeOfPng.calculate(pngBuffer) + await driver.findElement(By.css('.btn-primary--lg.export-private-key__button')).click() + await delay(300) + await captureLanguageScreenShots('metamask account detail export private key screen - reveal key') - // read only the english pngs into gif - const encoder = new GIFEncoder(size.width, size.height) - const stream = pngFileStream('./test-artifacts/screens/* (en).png') - .pipe(encoder.createWriteStream({ repeat: 0, delay: 1000, quality: 10 })) - .pipe(fs.createWriteStream('./test-artifacts/screens/walkthrough (en).gif')) + await driver.findElement(By.css('.export-private-key__button')).click() + await delay(300) + await captureLanguageScreenShots('metamask account detail export private key screen - done') - // wait for end - await pify(endOfStream)(stream) + // get eth from Ganache + // const viewAddressButton = await driver.findElement(By.css('.wallet-view__address')) + // await driver.actions({ bridge: true }).move({ origin: viewAddressButton }).perform() + // console.log('driver.actions', driver.actions({ bridge: true })) + // await delay(300) + // await captureLanguageScreenShots('metamask home - hover copy address') + + await driver.findElement(By.css('.wallet-view__address')).click() + await delay(100) + await captureLanguageScreenShots('metamask home - hover copy address') + + const primaryAddress = clipboardy.readSync() + await requestEther(primaryAddress) + // wait for block polling + await delay(10000) + await captureLanguageScreenShots('metamask home - has ether') + +} + + +async function captureLanguageScreenShots(label) { + const nonEnglishLocales = localesIndex.filter(localeMeta => localeMeta.code !== 'en') + // take english shot + await captureScreenShot(`${label} (en)`) + for (let localeMeta of nonEnglishLocales) { + // set locale and take shot + await setLocale(localeMeta.code) + await delay(300) + await captureScreenShot(`${label} (${localeMeta.code})`) } + // return locale to english + await setLocale('en') + await delay(300) +} + +async function setLocale(code) { + await driver.executeScript('window.metamask.updateCurrentLocale(arguments[0])', code) +} + +async function setProviderType(type) { + await driver.executeScript('window.metamask.setProviderType(arguments[0])', type) +} +async function cleanScreenShotDir() { + await pify(rimraf)(`./test-artifacts/screens/`) +} + +async function captureScreenShot(label) { + const shotIndex = screenshotCount.toString().padStart(4, '0') + screenshotCount++ + const artifactDir = `./test-artifacts/screens/` + await pify(mkdirp)(artifactDir) + // capture screenshot + const screenshot = await driver.takeScreenshot() + await pify(fs.writeFile)(`${artifactDir}/${shotIndex} - ${label}.png`, screenshot, { encoding: 'base64' }) +} + +async function generateGif(){ + // calculate screenshot size + const screenshot = await driver.takeScreenshot() + const pngBuffer = Buffer.from(screenshot, 'base64') + const size = sizeOfPng.calculate(pngBuffer) + + // read only the english pngs into gif + const encoder = new GIFEncoder(size.width, size.height) + const stream = pngFileStream('./test-artifacts/screens/* (en).png') + .pipe(encoder.createWriteStream({ repeat: 0, delay: 1000, quality: 10 })) + .pipe(fs.createWriteStream('./test-artifacts/screens/walkthrough (en).gif')) + + // wait for end + await pify(endOfStream)(stream) } async function verboseReportOnFailure(test) { @@ -227,3 +257,8 @@ async function verboseReportOnFailure(test) { const htmlSource = await driver.getPageSource() await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource) } + +async function requestEther(address) { + const accounts = await eth.accounts() + await eth.sendTransaction({ from: accounts[0], to: address, value: 1 * 1e18, data: '0x0' }) +} |