Move webdriver interactions into driver module (#7798)

The Selenium webdriver is difficult to use, and easy to misuse. To help
use the driver and make it easier to maintain our e2e tests, all driver
interactions are now performed via a `driver` module. This is basically
a wrapper class around the `selenium-webdriver` that exposes only the
methods we want to use directly, along with all of our helper methods.
feature/default_network_editable
Mark Stacey 5 years ago committed by GitHub
parent d802e09a8b
commit 27cfb6aa51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 0
      test/data/fetch-mocks.json
  2. 160
      test/e2e/address-book.spec.js
  3. 107
      test/e2e/ethereum-on.spec.js
  4. 235
      test/e2e/from-import-ui.spec.js
  5. 156
      test/e2e/helpers.js
  6. 108
      test/e2e/incremental-security.spec.js
  7. 140
      test/e2e/metamask-responsive-ui.spec.js
  8. 1019
      test/e2e/metamask-ui.spec.js
  9. 114
      test/e2e/permissions.spec.js
  10. 141
      test/e2e/send-edit.spec.js
  11. 83
      test/e2e/signature-request.spec.js
  12. 149
      test/e2e/threebox.spec.js
  13. 94
      test/e2e/web3.spec.js
  14. 153
      test/e2e/webdriver/driver.js
  15. 44
      test/e2e/webdriver/index.js
  16. 2
      test/integration/lib/confirm-sig-requests.js
  17. 2
      test/integration/lib/currency-localization.js
  18. 2
      test/integration/lib/tx-list-items.js

@ -1,15 +1,11 @@
const assert = require('assert') const assert = require('assert')
const webdriver = require('selenium-webdriver') const { By, until } = require('selenium-webdriver')
const { By, until } = webdriver
const { const {
checkBrowserForConsoleErrors,
delay,
findElement,
findElements,
verboseReportOnFailure,
setupFetchMocking,
prepareExtensionForTesting, prepareExtensionForTesting,
tinyDelayMs,
regularDelayMs,
largeDelayMs,
} = require('./helpers') } = require('./helpers')
const Ganache = require('./ganache') const Ganache = require('./ganache')
const enLocaleMessages = require('../../app/_locales/en/messages.json') const enLocaleMessages = require('../../app/_locales/en/messages.json')
@ -20,9 +16,6 @@ describe('MetaMask', function () {
let driver let driver
const testSeedPhrase = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress' const testSeedPhrase = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress'
const tinyDelayMs = 200
const regularDelayMs = tinyDelayMs * 2
const largeDelayMs = regularDelayMs * 2
this.timeout(0) this.timeout(0)
this.bail(true) this.bail(true)
@ -38,12 +31,11 @@ describe('MetaMask', function () {
}) })
const result = await prepareExtensionForTesting() const result = await prepareExtensionForTesting()
driver = result.driver driver = result.driver
await setupFetchMocking(driver)
}) })
afterEach(async function () { afterEach(async function () {
if (process.env.SELENIUM_BROWSER === 'chrome') { if (process.env.SELENIUM_BROWSER === 'chrome') {
const errors = await checkBrowserForConsoleErrors(driver) const errors = await driver.checkBrowserForConsoleErrors()
if (errors.length) { if (errors.length) {
const errorReports = errors.map(err => err.message) const errorReports = errors.map(err => err.message)
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
@ -51,7 +43,7 @@ describe('MetaMask', function () {
} }
} }
if (this.currentTest.state === 'failed') { if (this.currentTest.state === 'failed') {
await verboseReportOnFailure(driver, this.currentTest) await driver.verboseReportOnFailure(driver, this.currentTest)
} }
}) })
@ -62,63 +54,63 @@ describe('MetaMask', function () {
describe('Going through the first time flow', () => { describe('Going through the first time flow', () => {
it('clicks the continue button on the welcome screen', async () => { it('clicks the continue button on the welcome screen', async () => {
await findElement(driver, By.css('.welcome-page__header')) await driver.findElement(By.css('.welcome-page__header'))
const welcomeScreenBtn = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) const welcomeScreenBtn = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
welcomeScreenBtn.click() welcomeScreenBtn.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('clicks the "Create New Wallet" option', async () => { it('clicks the "Create New Wallet" option', async () => {
const customRpcButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Create a Wallet')]`)) const customRpcButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Create a Wallet')]`))
customRpcButton.click() customRpcButton.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('clicks the "No thanks" option on the metametrics opt-in screen', async () => { it('clicks the "No thanks" option on the metametrics opt-in screen', async () => {
const optOutButton = await findElement(driver, By.css('.btn-default')) const optOutButton = await driver.findElement(By.css('.btn-default'))
optOutButton.click() optOutButton.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('accepts a secure password', async () => { it('accepts a secure password', async () => {
const passwordBox = await findElement(driver, By.css('.first-time-flow__form #create-password')) const passwordBox = await driver.findElement(By.css('.first-time-flow__form #create-password'))
const passwordBoxConfirm = await findElement(driver, By.css('.first-time-flow__form #confirm-password')) const passwordBoxConfirm = await driver.findElement(By.css('.first-time-flow__form #confirm-password'))
const button = await findElement(driver, By.css('.first-time-flow__form button')) const button = await driver.findElement(By.css('.first-time-flow__form button'))
await passwordBox.sendKeys('correct horse battery staple') await passwordBox.sendKeys('correct horse battery staple')
await passwordBoxConfirm.sendKeys('correct horse battery staple') await passwordBoxConfirm.sendKeys('correct horse battery staple')
const tosCheckBox = await findElement(driver, By.css('.first-time-flow__checkbox')) const tosCheckBox = await driver.findElement(By.css('.first-time-flow__checkbox'))
await tosCheckBox.click() await tosCheckBox.click()
await button.click() await button.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
let seedPhrase let seedPhrase
it('reveals the seed phrase', async () => { it('reveals the seed phrase', async () => {
const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button') const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button')
await driver.wait(until.elementLocated(byRevealButton, 10000)) const revealSeedPhraseButton = await driver.findElement(byRevealButton, 10000)
const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000)
await revealSeedPhraseButton.click() await revealSeedPhraseButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
seedPhrase = await driver.findElement(By.css('.reveal-seed-phrase__secret-words')).getText() const revealedSeedPhrase = await driver.findElement(By.css('.reveal-seed-phrase__secret-words'))
seedPhrase = await revealedSeedPhrase.getText()
assert.equal(seedPhrase.split(' ').length, 12) assert.equal(seedPhrase.split(' ').length, 12)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.next.message}')]`)) const nextScreen = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.next.message}')]`))
await nextScreen.click() await nextScreen.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
async function clickWordAndWait (word) { async function clickWordAndWait (word) {
const xpath = `//div[contains(@class, 'confirm-seed-phrase__seed-word--shuffled') and not(contains(@class, 'confirm-seed-phrase__seed-word--selected')) and contains(text(), '${word}')]` const xpath = `//div[contains(@class, 'confirm-seed-phrase__seed-word--shuffled') and not(contains(@class, 'confirm-seed-phrase__seed-word--selected')) and contains(text(), '${word}')]`
const word0 = await findElement(driver, By.xpath(xpath), 10000) const word0 = await driver.findElement(By.xpath(xpath))
await word0.click() await word0.click()
await delay(tinyDelayMs) await driver.delay(tinyDelayMs)
} }
it('can retype the seed phrase', async () => { it('can retype the seed phrase', async () => {
@ -128,146 +120,148 @@ describe('MetaMask', function () {
await clickWordAndWait(word) await clickWordAndWait(word)
} }
const confirm = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`)) const confirm = await driver.findElement(By.xpath(`//button[contains(text(), 'Confirm')]`))
await confirm.click() await confirm.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('clicks through the success screen', async () => { it('clicks through the success screen', async () => {
await findElement(driver, By.xpath(`//div[contains(text(), 'Congratulations')]`)) await driver.findElement(By.xpath(`//div[contains(text(), 'Congratulations')]`))
const doneButton = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`)) const doneButton = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`))
await doneButton.click() await doneButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
}) })
describe('Import seed phrase', () => { describe('Import seed phrase', () => {
it('logs out of the vault', async () => { it('logs out of the vault', async () => {
await driver.findElement(By.css('.account-menu__icon')).click() const accountMenu = await driver.findElement(By.css('.account-menu__icon'))
await delay(regularDelayMs) await accountMenu.click()
await driver.delay(regularDelayMs)
const logoutButton = await findElement(driver, By.css('.account-menu__logout-button')) const logoutButton = await driver.findElement(By.css('.account-menu__logout-button'))
assert.equal(await logoutButton.getText(), 'Log out') assert.equal(await logoutButton.getText(), 'Log out')
await logoutButton.click() await logoutButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('imports seed phrase', async () => { it('imports seed phrase', async () => {
const restoreSeedLink = await findElement(driver, By.css('.unlock-page__link--import')) const restoreSeedLink = await driver.findElement(By.css('.unlock-page__link--import'))
assert.equal(await restoreSeedLink.getText(), 'Import using account seed phrase') assert.equal(await restoreSeedLink.getText(), 'Import using account seed phrase')
await restoreSeedLink.click() await restoreSeedLink.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const seedTextArea = await findElement(driver, By.css('textarea')) const seedTextArea = await driver.findElement(By.css('textarea'))
await seedTextArea.sendKeys(testSeedPhrase) await seedTextArea.sendKeys(testSeedPhrase)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const passwordInputs = await driver.findElements(By.css('input')) const passwordInputs = await driver.findElements(By.css('input'))
await delay(regularDelayMs) await driver.delay(regularDelayMs)
await passwordInputs[0].sendKeys('correct horse battery staple') await passwordInputs[0].sendKeys('correct horse battery staple')
await passwordInputs[1].sendKeys('correct horse battery staple') await passwordInputs[1].sendKeys('correct horse battery staple')
await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.restore.message}')]`)).click() const restoreButton = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.restore.message}')]`))
await delay(regularDelayMs) restoreButton.click()
await driver.delay(regularDelayMs)
}) })
it('balance renders', async () => { it('balance renders', async () => {
const balance = await findElement(driver, By.css('.balance-display .token-amount')) const balance = await driver.findElement(By.css('.balance-display .token-amount'))
await driver.wait(until.elementTextMatches(balance, /25\s*ETH/)) await driver.wait(until.elementTextMatches(balance, /25\s*ETH/))
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
}) })
describe('Adds an entry to the address book and sends eth to that address', () => { describe('Adds an entry to the address book and sends eth to that address', () => {
it('starts a send transaction', async function () { it('starts a send transaction', async function () {
const sendButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Send')]`)) const sendButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Send')]`))
await sendButton.click() await sendButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const inputAddress = await findElement(driver, By.css('input[placeholder="Search, public address (0x), or ENS"]')) const inputAddress = await driver.findElement(By.css('input[placeholder="Search, public address (0x), or ENS"]'))
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const addToAddressBookButton = await findElement(driver, By.css('.dialog.send__dialog.dialog--message')) const addToAddressBookButton = await driver.findElement(By.css('.dialog.send__dialog.dialog--message'))
await addToAddressBookButton.click() await addToAddressBookButton.click()
const addressBookAddModal = await driver.findElement(By.css('span .modal')) const addressBookAddModal = await driver.findElement(By.css('span .modal'))
await findElement(driver, By.css('.add-to-address-book-modal')) await driver.findElement(By.css('.add-to-address-book-modal'))
const addressBookInput = await findElement(driver, By.css('.add-to-address-book-modal__input')) const addressBookInput = await driver.findElement(By.css('.add-to-address-book-modal__input'))
await addressBookInput.sendKeys('Test Name 1') await addressBookInput.sendKeys('Test Name 1')
await delay(tinyDelayMs) await driver.delay(tinyDelayMs)
const addressBookSaveButton = await findElement(driver, By.css('.add-to-address-book-modal__footer .btn-primary')) const addressBookSaveButton = await driver.findElement(By.css('.add-to-address-book-modal__footer .btn-primary'))
await addressBookSaveButton.click() await addressBookSaveButton.click()
await driver.wait(until.stalenessOf(addressBookAddModal)) await driver.wait(until.stalenessOf(addressBookAddModal))
const inputAmount = await findElement(driver, By.css('.unit-input__input')) const inputAmount = await driver.findElement(By.css('.unit-input__input'))
await inputAmount.sendKeys('1') await inputAmount.sendKeys('1')
const inputValue = await inputAmount.getAttribute('value') const inputValue = await inputAmount.getAttribute('value')
assert.equal(inputValue, '1') assert.equal(inputValue, '1')
await delay(regularDelayMs) await driver.delay(regularDelayMs)
// Continue to next screen // Continue to next screen
const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), 'Next')]`)) const nextScreen = await driver.findElement(By.xpath(`//button[contains(text(), 'Next')]`))
await nextScreen.click() await nextScreen.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('confirms the transaction', async function () { it('confirms the transaction', async function () {
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`)) const confirmButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Confirm')]`))
await confirmButton.click() await confirmButton.click()
await delay(largeDelayMs * 2) await driver.delay(largeDelayMs * 2)
}) })
it('finds the transaction in the transactions list', async function () { it('finds the transaction in the transactions list', async function () {
await driver.wait(async () => { await driver.wait(async () => {
const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item')) const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item'))
return confirmedTxes.length === 1 return confirmedTxes.length === 1
}, 10000) }, 10000)
const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary')) const txValues = await driver.findElement(By.css('.transaction-list-item__amount--primary'))
await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/), 10000) await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/), 10000)
}) })
}) })
describe('Sends to an address book entry', () => { describe('Sends to an address book entry', () => {
it('starts a send transaction by clicking address book entry', async function () { it('starts a send transaction by clicking address book entry', async function () {
const sendButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Send')]`)) const sendButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Send')]`))
await sendButton.click() await sendButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const recipientRow = await findElement(driver, By.css('.send__select-recipient-wrapper__group-item')) const recipientRow = await driver.findElement(By.css('.send__select-recipient-wrapper__group-item'))
const recipientRowTitle = await findElement(driver, By.css('.send__select-recipient-wrapper__group-item__title')) const recipientRowTitle = await driver.findElement(By.css('.send__select-recipient-wrapper__group-item__title'))
const recipientRowTitleString = await recipientRowTitle.getText() const recipientRowTitleString = await recipientRowTitle.getText()
assert.equal(recipientRowTitleString, 'Test Name 1') assert.equal(recipientRowTitleString, 'Test Name 1')
await recipientRow.click() await recipientRow.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const inputAmount = await findElement(driver, By.css('.unit-input__input')) const inputAmount = await driver.findElement(By.css('.unit-input__input'))
await inputAmount.sendKeys('2') await inputAmount.sendKeys('2')
await delay(regularDelayMs) await driver.delay(regularDelayMs)
// Continue to next screen // Continue to next screen
const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), 'Next')]`)) const nextScreen = await driver.findElement(By.xpath(`//button[contains(text(), 'Next')]`))
await nextScreen.click() await nextScreen.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('confirms the transaction', async function () { it('confirms the transaction', async function () {
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`)) const confirmButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Confirm')]`))
await confirmButton.click() await confirmButton.click()
await delay(largeDelayMs * 2) await driver.delay(largeDelayMs * 2)
}) })
it('finds the transaction in the transactions list', async function () { it('finds the transaction in the transactions list', async function () {
await driver.wait(async () => { await driver.wait(async () => {
const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item')) const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item'))
return confirmedTxes.length === 2 return confirmedTxes.length === 2
}, 10000) }, 10000)
const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary')) const txValues = await driver.findElement(By.css('.transaction-list-item__amount--primary'))
await driver.wait(until.elementTextMatches(txValues, /-2\s*ETH/), 10000) await driver.wait(until.elementTextMatches(txValues, /-2\s*ETH/), 10000)
}) })
}) })

@ -3,15 +3,9 @@ const webdriver = require('selenium-webdriver')
const { By, until } = webdriver const { By, until } = webdriver
const { const {
checkBrowserForConsoleErrors,
delay,
findElement,
openNewPage,
verboseReportOnFailure,
waitUntilXWindowHandles,
switchToWindowWithTitle,
setupFetchMocking,
prepareExtensionForTesting, prepareExtensionForTesting,
regularDelayMs,
largeDelayMs,
} = require('./helpers') } = require('./helpers')
const Ganache = require('./ganache') const Ganache = require('./ganache')
const enLocaleMessages = require('../../app/_locales/en/messages.json') const enLocaleMessages = require('../../app/_locales/en/messages.json')
@ -22,10 +16,6 @@ describe('MetaMask', function () {
let driver let driver
let publicAddress let publicAddress
const tinyDelayMs = 200
const regularDelayMs = tinyDelayMs * 2
const largeDelayMs = regularDelayMs * 2
this.timeout(0) this.timeout(0)
this.bail(true) this.bail(true)
@ -40,12 +30,11 @@ describe('MetaMask', function () {
}) })
const result = await prepareExtensionForTesting() const result = await prepareExtensionForTesting()
driver = result.driver driver = result.driver
await setupFetchMocking(driver)
}) })
afterEach(async function () { afterEach(async function () {
if (process.env.SELENIUM_BROWSER === 'chrome') { if (process.env.SELENIUM_BROWSER === 'chrome') {
const errors = await checkBrowserForConsoleErrors(driver) const errors = await driver.checkBrowserForConsoleErrors(driver)
if (errors.length) { if (errors.length) {
const errorReports = errors.map(err => err.message) const errorReports = errors.map(err => err.message)
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
@ -53,7 +42,7 @@ describe('MetaMask', function () {
} }
} }
if (this.currentTest.state === 'failed') { if (this.currentTest.state === 'failed') {
await verboseReportOnFailure(driver, this.currentTest) await driver.verboseReportOnFailure(driver, this.currentTest)
} }
}) })
@ -64,51 +53,51 @@ describe('MetaMask', function () {
describe('Going through the first time flow, but skipping the seed phrase challenge', () => { describe('Going through the first time flow, but skipping the seed phrase challenge', () => {
it('clicks the continue button on the welcome screen', async () => { it('clicks the continue button on the welcome screen', async () => {
await findElement(driver, By.css('.welcome-page__header')) await driver.findElement(By.css('.welcome-page__header'))
const welcomeScreenBtn = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) const welcomeScreenBtn = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
welcomeScreenBtn.click() welcomeScreenBtn.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('clicks the "Create New Wallet" option', async () => { it('clicks the "Create New Wallet" option', async () => {
const customRpcButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Create a Wallet')]`)) const customRpcButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Create a Wallet')]`))
customRpcButton.click() customRpcButton.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('clicks the "No thanks" option on the metametrics opt-in screen', async () => { it('clicks the "No thanks" option on the metametrics opt-in screen', async () => {
const optOutButton = await findElement(driver, By.css('.btn-default')) const optOutButton = await driver.findElement(By.css('.btn-default'))
optOutButton.click() optOutButton.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('accepts a secure password', async () => { it('accepts a secure password', async () => {
const passwordBox = await findElement(driver, By.css('.first-time-flow__form #create-password')) const passwordBox = await driver.findElement(By.css('.first-time-flow__form #create-password'))
const passwordBoxConfirm = await findElement(driver, By.css('.first-time-flow__form #confirm-password')) const passwordBoxConfirm = await driver.findElement(By.css('.first-time-flow__form #confirm-password'))
const button = await findElement(driver, By.css('.first-time-flow__form button')) const button = await driver.findElement(By.css('.first-time-flow__form button'))
await passwordBox.sendKeys('correct horse battery staple') await passwordBox.sendKeys('correct horse battery staple')
await passwordBoxConfirm.sendKeys('correct horse battery staple') await passwordBoxConfirm.sendKeys('correct horse battery staple')
const tosCheckBox = await findElement(driver, By.css('.first-time-flow__checkbox')) const tosCheckBox = await driver.findElement(By.css('.first-time-flow__checkbox'))
await tosCheckBox.click() await tosCheckBox.click()
await button.click() await button.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('skips the seed phrase challenge', async () => { it('skips the seed phrase challenge', async () => {
const button = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.remindMeLater.message}')]`)) const button = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.remindMeLater.message}')]`))
await button.click() await button.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const detailsButton = await findElement(driver, By.css('.account-details__details-button')) const detailsButton = await driver.findElement(By.css('.account-details__details-button'))
await detailsButton.click() await detailsButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('gets the current accounts address', async () => { it('gets the current accounts address', async () => {
const addressInput = await findElement(driver, By.css('.qr-ellip-address')) const addressInput = await driver.findElement(By.css('.qr-ellip-address'))
publicAddress = await addressInput.getAttribute('value') publicAddress = await addressInput.getAttribute('value')
const accountModal = await driver.findElement(By.css('span .modal')) const accountModal = await driver.findElement(By.css('span .modal'))
@ -116,7 +105,7 @@ describe('MetaMask', function () {
await accountModalClose.click() await accountModalClose.click()
await driver.wait(until.stalenessOf(accountModal)) await driver.wait(until.stalenessOf(accountModal))
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
}) })
@ -127,69 +116,69 @@ describe('MetaMask', function () {
let dapp let dapp
it('connects to the dapp', async () => { it('connects to the dapp', async () => {
await openNewPage(driver, 'http://127.0.0.1:8080/') await driver.openNewPage('http://127.0.0.1:8080/')
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) const connectButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Connect')]`))
await connectButton.click() await connectButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
await waitUntilXWindowHandles(driver, 3) await driver.waitUntilXWindowHandles(3)
const windowHandles = await driver.getAllWindowHandles() const windowHandles = await driver.getAllWindowHandles()
extension = windowHandles[0] extension = windowHandles[0]
dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles) dapp = await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles)
popup = windowHandles.find(handle => handle !== extension && handle !== dapp) popup = windowHandles.find(handle => handle !== extension && handle !== dapp)
await driver.switchTo().window(popup) await driver.switchToWindow(popup)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const accountButton = await findElement(driver, By.css('.permissions-connect-choose-account__account')) const accountButton = await driver.findElement(By.css('.permissions-connect-choose-account__account'))
await accountButton.click() await accountButton.click()
const submitButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Submit')]`)) const submitButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Submit')]`))
await submitButton.click() await submitButton.click()
await waitUntilXWindowHandles(driver, 2) await driver.waitUntilXWindowHandles(2)
await driver.switchTo().window(dapp) await driver.switchToWindow(dapp)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('has the ganache network id within the dapp', async () => { it('has the ganache network id within the dapp', async () => {
const networkDiv = await findElement(driver, By.css('#network')) const networkDiv = await driver.findElement(By.css('#network'))
await delay(regularDelayMs) await driver.delay(regularDelayMs)
assert.equal(await networkDiv.getText(), '5777') assert.equal(await networkDiv.getText(), '5777')
}) })
it('changes the network', async () => { it('changes the network', async () => {
await driver.switchTo().window(extension) await driver.switchToWindow(extension)
const networkDropdown = await findElement(driver, By.css('.network-name')) const networkDropdown = await driver.findElement(By.css('.network-name'))
await networkDropdown.click() await networkDropdown.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const ropstenButton = await findElement(driver, By.xpath(`//span[contains(text(), 'Ropsten')]`)) const ropstenButton = await driver.findElement(By.xpath(`//span[contains(text(), 'Ropsten')]`))
await ropstenButton.click() await ropstenButton.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('sets the network div within the dapp', async () => { it('sets the network div within the dapp', async () => {
await driver.switchTo().window(dapp) await driver.switchToWindow(dapp)
const networkDiv = await findElement(driver, By.css('#network')) const networkDiv = await driver.findElement(By.css('#network'))
assert.equal(await networkDiv.getText(), '3') assert.equal(await networkDiv.getText(), '3')
}) })
it('sets the chainId div within the dapp', async () => { it('sets the chainId div within the dapp', async () => {
await driver.switchTo().window(dapp) await driver.switchToWindow(dapp)
const chainIdDiv = await findElement(driver, By.css('#chainId')) const chainIdDiv = await driver.findElement(By.css('#chainId'))
assert.equal(await chainIdDiv.getText(), '0x3') assert.equal(await chainIdDiv.getText(), '0x3')
}) })
it('sets the account div within the dapp', async () => { it('sets the account div within the dapp', async () => {
await driver.switchTo().window(dapp) await driver.switchToWindow(dapp)
const accountsDiv = await findElement(driver, By.css('#accounts')) const accountsDiv = await driver.findElement(By.css('#accounts'))
assert.equal(await accountsDiv.getText(), publicAddress.toLowerCase()) assert.equal(await accountsDiv.getText(), publicAddress.toLowerCase())
}) })
}) })

@ -3,13 +3,10 @@ const webdriver = require('selenium-webdriver')
const { By, Key, until } = webdriver const { By, Key, until } = webdriver
const { const {
checkBrowserForConsoleErrors,
delay,
verboseReportOnFailure,
findElement,
findElements,
setupFetchMocking,
prepareExtensionForTesting, prepareExtensionForTesting,
tinyDelayMs,
regularDelayMs,
largeDelayMs,
} = require('./helpers') } = require('./helpers')
const Ganache = require('./ganache') const Ganache = require('./ganache')
const enLocaleMessages = require('../../app/_locales/en/messages.json') const enLocaleMessages = require('../../app/_locales/en/messages.json')
@ -23,9 +20,6 @@ describe('Using MetaMask with an existing account', function () {
const testAddress = '0x0Cc5261AB8cE458dc977078A3623E2BaDD27afD3' const testAddress = '0x0Cc5261AB8cE458dc977078A3623E2BaDD27afD3'
const testPrivateKey2 = '14abe6f4aab7f9f626fe981c864d0adeb5685f289ac9270c27b8fd790b4235d6' const testPrivateKey2 = '14abe6f4aab7f9f626fe981c864d0adeb5685f289ac9270c27b8fd790b4235d6'
const testPrivateKey3 = 'F4EC2590A0C10DE95FBF4547845178910E40F5035320C516A18C117DE02B5669' const testPrivateKey3 = 'F4EC2590A0C10DE95FBF4547845178910E40F5035320C516A18C117DE02B5669'
const tinyDelayMs = 200
const regularDelayMs = 1000
const largeDelayMs = regularDelayMs * 2
this.timeout(0) this.timeout(0)
this.bail(true) this.bail(true)
@ -41,12 +35,11 @@ describe('Using MetaMask with an existing account', function () {
}) })
const result = await prepareExtensionForTesting() const result = await prepareExtensionForTesting()
driver = result.driver driver = result.driver
await setupFetchMocking(driver)
}) })
afterEach(async function () { afterEach(async function () {
if (process.env.SELENIUM_BROWSER === 'chrome') { if (process.env.SELENIUM_BROWSER === 'chrome') {
const errors = await checkBrowserForConsoleErrors(driver) const errors = await driver.checkBrowserForConsoleErrors(driver)
if (errors.length) { if (errors.length) {
const errorReports = errors.map(err => err.message) const errorReports = errors.map(err => err.message)
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
@ -54,7 +47,7 @@ describe('Using MetaMask with an existing account', function () {
} }
} }
if (this.currentTest.state === 'failed') { if (this.currentTest.state === 'failed') {
await verboseReportOnFailure(driver, this.currentTest) await driver.verboseReportOnFailure(driver, this.currentTest)
} }
}) })
@ -65,186 +58,191 @@ describe('Using MetaMask with an existing account', function () {
describe('First time flow starting from an existing seed phrase', () => { describe('First time flow starting from an existing seed phrase', () => {
it('clicks the continue button on the welcome screen', async () => { it('clicks the continue button on the welcome screen', async () => {
await findElement(driver, By.css('.welcome-page__header')) await driver.findElement(By.css('.welcome-page__header'))
const welcomeScreenBtn = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) const welcomeScreenBtn = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
welcomeScreenBtn.click() welcomeScreenBtn.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('clicks the "Import Wallet" option', async () => { it('clicks the "Import Wallet" option', async () => {
const customRpcButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Import Wallet')]`)) const customRpcButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Import Wallet')]`))
customRpcButton.click() customRpcButton.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('clicks the "No thanks" option on the metametrics opt-in screen', async () => { it('clicks the "No thanks" option on the metametrics opt-in screen', async () => {
const optOutButton = await findElement(driver, By.css('.btn-default')) const optOutButton = await driver.findElement(By.css('.btn-default'))
optOutButton.click() optOutButton.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('imports a seed phrase', async () => { it('imports a seed phrase', async () => {
const [seedTextArea] = await findElements(driver, By.css('textarea.first-time-flow__textarea')) const [seedTextArea] = await driver.findElements(By.css('textarea.first-time-flow__textarea'))
await seedTextArea.sendKeys(testSeedPhrase) await seedTextArea.sendKeys(testSeedPhrase)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const [password] = await findElements(driver, By.id('password')) const [password] = await driver.findElements(By.id('password'))
await password.sendKeys('correct horse battery staple') await password.sendKeys('correct horse battery staple')
const [confirmPassword] = await findElements(driver, By.id('confirm-password')) const [confirmPassword] = await driver.findElements(By.id('confirm-password'))
confirmPassword.sendKeys('correct horse battery staple') confirmPassword.sendKeys('correct horse battery staple')
const tosCheckBox = await findElement(driver, By.css('.first-time-flow__checkbox')) const tosCheckBox = await driver.findElement(By.css('.first-time-flow__checkbox'))
await tosCheckBox.click() await tosCheckBox.click()
const [importButton] = await findElements(driver, By.xpath(`//button[contains(text(), 'Import')]`)) const [importButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Import')]`))
await importButton.click() await importButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('clicks through the success screen', async () => { it('clicks through the success screen', async () => {
await findElement(driver, By.xpath(`//div[contains(text(), 'Congratulations')]`)) await driver.findElement(By.xpath(`//div[contains(text(), 'Congratulations')]`))
const doneButton = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`)) const doneButton = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`))
await doneButton.click() await doneButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
}) })
describe('Show account information', () => { describe('Show account information', () => {
it('shows the correct account address', async () => { it('shows the correct account address', async () => {
await driver.findElement(By.css('.account-details__details-button')).click() const accountDetailsButton = await driver.findElement(By.css('.account-details__details-button'))
await driver.findElement(By.css('.qr-wrapper')).isDisplayed() await accountDetailsButton.click()
await delay(regularDelayMs) await driver.findVisibleElement(By.css('.qr-wrapper'))
await driver.delay(regularDelayMs)
const [address] = await findElements(driver, By.css('input.qr-ellip-address')) const [address] = await driver.findElements(By.css('input.qr-ellip-address'))
assert.equal(await address.getAttribute('value'), testAddress) assert.equal(await address.getAttribute('value'), testAddress)
const accountModalClose = await driver.findElement(By.css('.account-modal-close')) const accountModalClose = await driver.findElement(By.css('.account-modal-close'))
await accountModalClose.click() await accountModalClose.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('shows a QR code for the account', async () => { it('shows a QR code for the account', async () => {
await driver.findElement(By.css('.account-details__details-button')).click() const accountDetailsButton = await driver.findElement(By.css('.account-details__details-button'))
await driver.findElement(By.css('.qr-wrapper')).isDisplayed() await accountDetailsButton.click()
await driver.findVisibleElement(By.css('.qr-wrapper'))
const detailModal = await driver.findElement(By.css('span .modal')) const detailModal = await driver.findElement(By.css('span .modal'))
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const accountModalClose = await driver.findElement(By.css('.account-modal-close')) const accountModalClose = await driver.findElement(By.css('.account-modal-close'))
await accountModalClose.click() await accountModalClose.click()
await driver.wait(until.stalenessOf(detailModal)) await driver.wait(until.stalenessOf(detailModal))
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
}) })
describe('Log out and log back in', () => { describe('Log out and log back in', () => {
it('logs out of the account', async () => { it('logs out of the account', async () => {
const accountIdenticon = driver.findElement(By.css('.account-menu__icon .identicon')) const accountIdenticon = await driver.findElement(By.css('.account-menu__icon .identicon'))
accountIdenticon.click() await accountIdenticon.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const [logoutButton] = await findElements(driver, By.css('.account-menu__logout-button')) const [logoutButton] = await driver.findElements(By.css('.account-menu__logout-button'))
assert.equal(await logoutButton.getText(), 'Log out') assert.equal(await logoutButton.getText(), 'Log out')
await logoutButton.click() await logoutButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('accepts the account password after lock', async () => { it('accepts the account password after lock', async () => {
await driver.findElement(By.id('password')).sendKeys('correct horse battery staple') const passwordField = await driver.findElement(By.id('password'))
await driver.findElement(By.id('password')).sendKeys(Key.ENTER) await passwordField.sendKeys('correct horse battery staple')
await delay(largeDelayMs) await passwordField.sendKeys(Key.ENTER)
await driver.delay(largeDelayMs)
}) })
}) })
describe('Add an account', () => { describe('Add an account', () => {
it('switches to localhost', async () => { it('switches to localhost', async () => {
const networkDropdown = await findElement(driver, By.css('.network-name')) const networkDropdown = await driver.findElement(By.css('.network-name'))
await networkDropdown.click() await networkDropdown.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const [localhost] = await findElements(driver, By.xpath(`//span[contains(text(), 'Localhost')]`)) const [localhost] = await driver.findElements(By.xpath(`//span[contains(text(), 'Localhost')]`))
await localhost.click() await localhost.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('choose Create Account from the account menu', async () => { it('choose Create Account from the account menu', async () => {
await driver.findElement(By.css('.account-menu__icon')).click() const accountMenuButton = await driver.findElement(By.css('.account-menu__icon'))
await delay(regularDelayMs) await accountMenuButton.click()
await driver.delay(regularDelayMs)
const [createAccount] = await findElements(driver, By.xpath(`//div[contains(text(), 'Create Account')]`)) const [createAccount] = await driver.findElements(By.xpath(`//div[contains(text(), 'Create Account')]`))
await createAccount.click() await createAccount.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('set account name', async () => { it('set account name', async () => {
const [accountName] = await findElements(driver, By.css('.new-account-create-form input')) const [accountName] = await driver.findElements(By.css('.new-account-create-form input'))
await accountName.sendKeys('2nd account') await accountName.sendKeys('2nd account')
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const [createButton] = await findElements(driver, By.xpath(`//button[contains(text(), 'Create')]`)) const [createButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Create')]`))
await createButton.click() await createButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('should show the correct account name', async () => { it('should show the correct account name', async () => {
const [accountName] = await findElements(driver, By.css('.account-details__account-name')) const [accountName] = await driver.findElements(By.css('.account-details__account-name'))
assert.equal(await accountName.getText(), '2nd account') assert.equal(await accountName.getText(), '2nd account')
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
}) })
describe('Switch back to original account', () => { describe('Switch back to original account', () => {
it('chooses the original account from the account menu', async () => { it('chooses the original account from the account menu', async () => {
await driver.findElement(By.css('.account-menu__icon')).click() const accountMenuButton = await driver.findElement(By.css('.account-menu__icon'))
await delay(regularDelayMs) await accountMenuButton.click()
await driver.delay(regularDelayMs)
const [originalAccountMenuItem] = await findElements(driver, By.css('.account-menu__name')) const [originalAccountMenuItem] = await driver.findElements(By.css('.account-menu__name'))
await originalAccountMenuItem.click() await originalAccountMenuItem.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
}) })
describe('Send ETH from inside MetaMask', () => { describe('Send ETH from inside MetaMask', () => {
it('starts a send transaction', async function () { it('starts a send transaction', async function () {
const sendButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Send')]`)) const sendButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Send')]`))
await sendButton.click() await sendButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const inputAddress = await findElement(driver, By.css('input[placeholder="Search, public address (0x), or ENS"]')) const inputAddress = await driver.findElement(By.css('input[placeholder="Search, public address (0x), or ENS"]'))
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
const inputAmount = await findElement(driver, By.css('.unit-input__input')) const inputAmount = await driver.findElement(By.css('.unit-input__input'))
await inputAmount.sendKeys('1') await inputAmount.sendKeys('1')
// Set the gas limit // Set the gas limit
const configureGas = await findElement(driver, By.css('.advanced-gas-options-btn')) const configureGas = await driver.findElement(By.css('.advanced-gas-options-btn'))
await configureGas.click() await configureGas.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const gasModal = await driver.findElement(By.css('span .modal')) const gasModal = await driver.findElement(By.css('span .modal'))
const save = await findElement(driver, By.xpath(`//button[contains(text(), 'Save')]`)) const save = await driver.findElement(By.xpath(`//button[contains(text(), 'Save')]`))
await save.click() await save.click()
await driver.wait(until.stalenessOf(gasModal)) await driver.wait(until.stalenessOf(gasModal))
await delay(regularDelayMs) await driver.delay(regularDelayMs)
// Continue to next screen // Continue to next screen
const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), 'Next')]`)) const nextScreen = await driver.findElement(By.xpath(`//button[contains(text(), 'Next')]`))
await nextScreen.click() await nextScreen.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('confirms the transaction', async function () { it('confirms the transaction', async function () {
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`)) const confirmButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Confirm')]`))
await confirmButton.click() await confirmButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('finds the transaction in the transactions list', async function () { it('finds the transaction in the transactions list', async function () {
await driver.wait(async () => { await driver.wait(async () => {
const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item')) const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item'))
return confirmedTxes.length === 1 return confirmedTxes.length === 1
}, 10000) }, 10000)
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary')) const txValues = await driver.findElements(By.css('.transaction-list-item__amount--primary'))
assert.equal(txValues.length, 1) assert.equal(txValues.length, 1)
assert.ok(/-1\s*ETH/.test(await txValues[0].getText())) assert.ok(/-1\s*ETH/.test(await txValues[0].getText()))
}) })
@ -252,102 +250,105 @@ describe('Using MetaMask with an existing account', function () {
describe('Imports an account with private key', () => { describe('Imports an account with private key', () => {
it('choose Create Account from the account menu', async () => { it('choose Create Account from the account menu', async () => {
await driver.findElement(By.css('.account-menu__icon')).click() const accountMenuButton = await driver.findElement(By.css('.account-menu__icon'))
await delay(regularDelayMs) await accountMenuButton.click()
await driver.delay(regularDelayMs)
const [importAccount] = await findElements(driver, By.xpath(`//div[contains(text(), 'Import Account')]`)) const [importAccount] = await driver.findElements(By.xpath(`//div[contains(text(), 'Import Account')]`))
await importAccount.click() await importAccount.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('enter private key', async () => { it('enter private key', async () => {
const privateKeyInput = await findElement(driver, By.css('#private-key-box')) const privateKeyInput = await driver.findElement(By.css('#private-key-box'))
await privateKeyInput.sendKeys(testPrivateKey2) await privateKeyInput.sendKeys(testPrivateKey2)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const importButtons = await findElements(driver, By.xpath(`//button[contains(text(), 'Import')]`)) const importButtons = await driver.findElements(By.xpath(`//button[contains(text(), 'Import')]`))
await importButtons[0].click() await importButtons[0].click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('should show the correct account name', async () => { it('should show the correct account name', async () => {
const [accountName] = await findElements(driver, By.css('.account-details__account-name')) const [accountName] = await driver.findElements(By.css('.account-details__account-name'))
assert.equal(await accountName.getText(), 'Account 4') assert.equal(await accountName.getText(), 'Account 4')
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('should show the imported label', async () => { it('should show the imported label', async () => {
const [importedLabel] = await findElements(driver, By.css('.account-details__keyring-label')) const [importedLabel] = await driver.findElements(By.css('.account-details__keyring-label'))
assert.equal(await importedLabel.getText(), 'IMPORTED') assert.equal(await importedLabel.getText(), 'IMPORTED')
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
}) })
describe('Imports and removes an account', () => { describe('Imports and removes an account', () => {
it('choose Create Account from the account menu', async () => { it('choose Create Account from the account menu', async () => {
await driver.findElement(By.css('.account-menu__icon')).click() const accountMenuButton = await driver.findElement(By.css('.account-menu__icon'))
await delay(regularDelayMs) await accountMenuButton.click()
await driver.delay(regularDelayMs)
const [importAccount] = await findElements(driver, By.xpath(`//div[contains(text(), 'Import Account')]`)) const [importAccount] = await driver.findElements(By.xpath(`//div[contains(text(), 'Import Account')]`))
await importAccount.click() await importAccount.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('enter private key', async () => { it('enter private key', async () => {
const privateKeyInput = await findElement(driver, By.css('#private-key-box')) const privateKeyInput = await driver.findElement(By.css('#private-key-box'))
await privateKeyInput.sendKeys(testPrivateKey3) await privateKeyInput.sendKeys(testPrivateKey3)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const importButtons = await findElements(driver, By.xpath(`//button[contains(text(), 'Import')]`)) const importButtons = await driver.findElements(By.xpath(`//button[contains(text(), 'Import')]`))
await importButtons[0].click() await importButtons[0].click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('should open the remove account modal', async () => { it('should open the remove account modal', async () => {
const [accountName] = await findElements(driver, By.css('.account-details__account-name')) const [accountName] = await driver.findElements(By.css('.account-details__account-name'))
assert.equal(await accountName.getText(), 'Account 5') assert.equal(await accountName.getText(), 'Account 5')
await delay(regularDelayMs) await driver.delay(regularDelayMs)
await driver.findElement(By.css('.account-menu__icon')).click() const accountMenuButton = await driver.findElement(By.css('.account-menu__icon'))
await delay(regularDelayMs) await accountMenuButton.click()
await driver.delay(regularDelayMs)
const accountListItems = await findElements(driver, By.css('.account-menu__account')) const accountListItems = await driver.findElements(By.css('.account-menu__account'))
assert.equal(accountListItems.length, 5) assert.equal(accountListItems.length, 5)
const removeAccountIcons = await findElements(driver, By.css('.remove-account-icon')) const removeAccountIcons = await driver.findElements(By.css('.remove-account-icon'))
await removeAccountIcons[1].click() await removeAccountIcons[1].click()
await delay(tinyDelayMs) await driver.delay(tinyDelayMs)
await findElement(driver, By.css('.confirm-remove-account__account')) await driver.findElement(By.css('.confirm-remove-account__account'))
}) })
it('should remove the account', async () => { it('should remove the account', async () => {
const removeButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Remove')]`)) const removeButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Remove')]`))
await removeButton.click() await removeButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const [accountName] = await findElements(driver, By.css('.account-details__account-name')) const [accountName] = await driver.findElements(By.css('.account-details__account-name'))
assert.equal(await accountName.getText(), 'Account 1') assert.equal(await accountName.getText(), 'Account 1')
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const accountListItems = await findElements(driver, By.css('.account-menu__account')) const accountListItems = await driver.findElements(By.css('.account-menu__account'))
assert.equal(accountListItems.length, 4) assert.equal(accountListItems.length, 4)
}) })
}) })
describe('Connects to a Hardware wallet', () => { describe('Connects to a Hardware wallet', () => {
it('choose Connect Hardware Wallet from the account menu', async () => { it('choose Connect Hardware Wallet from the account menu', async () => {
const [connectAccount] = await findElements(driver, By.xpath(`//div[contains(text(), 'Connect Hardware Wallet')]`)) const [connectAccount] = await driver.findElements(By.xpath(`//div[contains(text(), 'Connect Hardware Wallet')]`))
await connectAccount.click() await connectAccount.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('should open the TREZOR Connect popup', async () => { it('should open the TREZOR Connect popup', async () => {
const trezorButton = await findElements(driver, By.css('.hw-connect__btn')) const trezorButton = await driver.findElements(By.css('.hw-connect__btn'))
await trezorButton[1].click() await trezorButton[1].click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const connectButtons = await findElements(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) const connectButtons = await driver.findElements(By.xpath(`//button[contains(text(), 'Connect')]`))
await connectButtons[0].click() await connectButtons[0].click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const allWindows = await driver.getAllWindowHandles() const allWindows = await driver.getAllWindowHandles()
assert.equal(allWindows.length, 2) assert.equal(allWindows.length, 2)
}) })

@ -1,28 +1,10 @@
const fs = require('fs')
const mkdirp = require('mkdirp')
const pify = require('pify')
const assert = require('assert')
const { until } = require('selenium-webdriver')
const { buildWebDriver } = require('./webdriver') const { buildWebDriver } = require('./webdriver')
const fetchMockResponses = require('./fetch-mocks.json')
const tinyDelayMs = 200 const tinyDelayMs = 200
const regularDelayMs = tinyDelayMs * 2 const regularDelayMs = tinyDelayMs * 2
const largeDelayMs = regularDelayMs * 2 const largeDelayMs = regularDelayMs * 2
module.exports = { module.exports = {
assertElementNotPresent,
checkBrowserForConsoleErrors,
closeAllWindowHandlesExcept,
delay,
findElement,
findElements,
openNewPage,
switchToWindowWithTitle,
verboseReportOnFailure,
waitUntilXWindowHandles,
setupFetchMocking,
prepareExtensionForTesting, prepareExtensionForTesting,
tinyDelayMs, tinyDelayMs,
regularDelayMs, regularDelayMs,
@ -34,143 +16,5 @@ async function prepareExtensionForTesting ({ responsive, port } = {}) {
const extensionPath = `dist/${browser}` const extensionPath = `dist/${browser}`
const { driver, extensionId, extensionUrl } = await buildWebDriver({ browser, extensionPath, responsive, port }) const { driver, extensionId, extensionUrl } = await buildWebDriver({ browser, extensionPath, responsive, port })
await driver.get(extensionUrl)
return { driver, extensionId, extensionUrl } return { driver, extensionId, extensionUrl }
} }
async function setupFetchMocking (driver) {
// define fetchMocking script, to be evaluated in the browser
function fetchMocking (fetchMockResponses) {
window.origFetch = window.fetch.bind(window)
window.fetch = async (...args) => {
const url = args[0]
if (url === 'https://ethgasstation.info/json/ethgasAPI.json') {
return { json: async () => clone(fetchMockResponses.ethGasBasic) }
} else if (url === 'https://ethgasstation.info/json/predictTable.json') {
return { json: async () => clone(fetchMockResponses.ethGasPredictTable) }
} else if (url.match(/chromeextensionmm/)) {
return { json: async () => clone(fetchMockResponses.metametrics) }
}
return window.origFetch(...args)
}
if (window.chrome && window.chrome.webRequest) {
window.chrome.webRequest.onBeforeRequest.addListener(cancelInfuraRequest, { urls: ['https://*.infura.io/*'] }, ['blocking'])
}
function cancelInfuraRequest (requestDetails) {
console.log(`fetchMocking - Canceling request: "${requestDetails.url}"`)
return { cancel: true }
}
function clone (obj) {
return JSON.parse(JSON.stringify(obj))
}
}
// fetchMockResponses are parsed last minute to ensure that objects are uniquely instantiated
const fetchMockResponsesJson = JSON.stringify(fetchMockResponses)
// eval the fetchMocking script in the browser
await driver.executeScript(`(${fetchMocking})(${fetchMockResponsesJson})`)
}
async function checkBrowserForConsoleErrors (driver) {
const ignoredLogTypes = ['WARNING']
const ignoredErrorMessages = [
// Third-party Favicon 404s show up as errors
'favicon.ico - Failed to load resource: the server responded with a status of 404 (Not Found)',
]
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())
return errorObjects.filter(entry => !ignoredErrorMessages.some(message => entry.message.includes(message)))
}
async function verboseReportOnFailure (driver, 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)
const screenshot = await driver.takeScreenshot()
await pify(fs.writeFile)(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' })
const htmlSource = await driver.getPageSource()
await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource)
}
function delay (time) {
return new Promise(resolve => setTimeout(resolve, time))
}
async function findElement (driver, by, timeout = 10000) {
return driver.wait(until.elementLocated(by), timeout)
}
async function findElements (driver, by, timeout = 10000) {
return driver.wait(until.elementsLocated(by), timeout)
}
async function openNewPage (driver, url) {
const newHandle = await driver.switchTo().newWindow()
await driver.get(url)
return newHandle
}
async function waitUntilXWindowHandles (driver, x, delayStep = 1000, timeout = 5000) {
let timeElapsed = 0
while (timeElapsed <= timeout) {
const windowHandles = await driver.getAllWindowHandles()
if (windowHandles.length === x) {
return
}
await delay(delayStep)
timeElapsed += delayStep
}
throw new Error('waitUntilXWindowHandles timed out polling window handles')
}
async function switchToWindowWithTitle (driver, title, windowHandles) {
if (!windowHandles) {
windowHandles = await driver.getAllWindowHandles()
}
for (const handle of windowHandles) {
await driver.switchTo().window(handle)
const handleTitle = await driver.getTitle()
if (handleTitle === title) {
return handle
}
}
throw new Error('No window with title: ' + title)
}
/**
* Closes all windows except those in the given list of exceptions
* @param {object} driver the WebDriver instance
* @param {Array<string>} exceptions the list of window handle exceptions
* @param {Array?} windowHandles the full list of window handles
* @returns {Promise<void>}
*/
async function closeAllWindowHandlesExcept (driver, exceptions, windowHandles) {
windowHandles = windowHandles || await driver.getAllWindowHandles()
for (const handle of windowHandles) {
if (!exceptions.includes(handle)) {
await driver.switchTo().window(handle)
await delay(1000)
await driver.close()
await delay(1000)
}
}
}
async function assertElementNotPresent (webdriver, driver, by) {
let dataTab
try {
dataTab = await findElement(driver, by, 4000)
} catch (err) {
assert(err instanceof webdriver.error.NoSuchElementError || err instanceof webdriver.error.TimeoutError)
}
assert.ok(!dataTab, 'Found element that should not be present')
}

@ -3,15 +3,10 @@ const webdriver = require('selenium-webdriver')
const { By, until } = webdriver const { By, until } = webdriver
const { const {
assertElementNotPresent,
checkBrowserForConsoleErrors,
delay,
findElement,
findElements,
openNewPage,
verboseReportOnFailure,
setupFetchMocking,
prepareExtensionForTesting, prepareExtensionForTesting,
tinyDelayMs,
regularDelayMs,
largeDelayMs,
} = require('./helpers') } = require('./helpers')
const Ganache = require('./ganache') const Ganache = require('./ganache')
const enLocaleMessages = require('../../app/_locales/en/messages.json') const enLocaleMessages = require('../../app/_locales/en/messages.json')
@ -22,10 +17,6 @@ describe('MetaMask', function () {
let driver let driver
let publicAddress let publicAddress
const tinyDelayMs = 200
const regularDelayMs = tinyDelayMs * 2
const largeDelayMs = regularDelayMs * 2
this.timeout(0) this.timeout(0)
this.bail(true) this.bail(true)
@ -44,12 +35,11 @@ describe('MetaMask', function () {
}) })
const result = await prepareExtensionForTesting() const result = await prepareExtensionForTesting()
driver = result.driver driver = result.driver
await setupFetchMocking(driver)
}) })
afterEach(async function () { afterEach(async function () {
if (process.env.SELENIUM_BROWSER === 'chrome') { if (process.env.SELENIUM_BROWSER === 'chrome') {
const errors = await checkBrowserForConsoleErrors(driver) const errors = await driver.checkBrowserForConsoleErrors(driver)
if (errors.length) { if (errors.length) {
const errorReports = errors.map(err => err.message) const errorReports = errors.map(err => err.message)
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
@ -57,7 +47,7 @@ describe('MetaMask', function () {
} }
} }
if (this.currentTest.state === 'failed') { if (this.currentTest.state === 'failed') {
await verboseReportOnFailure(driver, this.currentTest) await driver.verboseReportOnFailure(driver, this.currentTest)
} }
}) })
@ -68,51 +58,51 @@ describe('MetaMask', function () {
describe('Going through the first time flow, but skipping the seed phrase challenge', () => { describe('Going through the first time flow, but skipping the seed phrase challenge', () => {
it('clicks the continue button on the welcome screen', async () => { it('clicks the continue button on the welcome screen', async () => {
await findElement(driver, By.css('.welcome-page__header')) await driver.findElement(By.css('.welcome-page__header'))
const welcomeScreenBtn = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) const welcomeScreenBtn = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
welcomeScreenBtn.click() welcomeScreenBtn.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('clicks the "Create New Wallet" option', async () => { it('clicks the "Create New Wallet" option', async () => {
const customRpcButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Create a Wallet')]`)) const customRpcButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Create a Wallet')]`))
customRpcButton.click() customRpcButton.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('clicks the "No thanks" option on the metametrics opt-in screen', async () => { it('clicks the "No thanks" option on the metametrics opt-in screen', async () => {
const optOutButton = await findElement(driver, By.css('.btn-default')) const optOutButton = await driver.findElement(By.css('.btn-default'))
optOutButton.click() optOutButton.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('accepts a secure password', async () => { it('accepts a secure password', async () => {
const passwordBox = await findElement(driver, By.css('.first-time-flow__form #create-password')) const passwordBox = await driver.findElement(By.css('.first-time-flow__form #create-password'))
const passwordBoxConfirm = await findElement(driver, By.css('.first-time-flow__form #confirm-password')) const passwordBoxConfirm = await driver.findElement(By.css('.first-time-flow__form #confirm-password'))
const button = await findElement(driver, By.css('.first-time-flow__form button')) const button = await driver.findElement(By.css('.first-time-flow__form button'))
await passwordBox.sendKeys('correct horse battery staple') await passwordBox.sendKeys('correct horse battery staple')
await passwordBoxConfirm.sendKeys('correct horse battery staple') await passwordBoxConfirm.sendKeys('correct horse battery staple')
const tosCheckBox = await findElement(driver, By.css('.first-time-flow__checkbox')) const tosCheckBox = await driver.findElement(By.css('.first-time-flow__checkbox'))
await tosCheckBox.click() await tosCheckBox.click()
await button.click() await button.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('skips the seed phrase challenge', async () => { it('skips the seed phrase challenge', async () => {
const button = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.remindMeLater.message}')]`)) const button = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.remindMeLater.message}')]`))
await button.click() await button.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const detailsButton = await findElement(driver, By.css('.account-details__details-button')) const detailsButton = await driver.findElement(By.css('.account-details__details-button'))
await detailsButton.click() await detailsButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('gets the current accounts address', async () => { it('gets the current accounts address', async () => {
const addressInput = await findElement(driver, By.css('.qr-ellip-address')) const addressInput = await driver.findElement(By.css('.qr-ellip-address'))
publicAddress = await addressInput.getAttribute('value') publicAddress = await addressInput.getAttribute('value')
const accountModal = await driver.findElement(By.css('span .modal')) const accountModal = await driver.findElement(By.css('span .modal'))
@ -121,7 +111,7 @@ describe('MetaMask', function () {
await accountModalClose.click() await accountModalClose.click()
await driver.wait(until.stalenessOf(accountModal)) await driver.wait(until.stalenessOf(accountModal))
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
}) })
@ -133,28 +123,28 @@ describe('MetaMask', function () {
const windowHandles = await driver.getAllWindowHandles() const windowHandles = await driver.getAllWindowHandles()
extension = windowHandles[0] extension = windowHandles[0]
await openNewPage(driver, 'http://127.0.0.1:8080/') await driver.openNewPage('http://127.0.0.1:8080/')
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('sends eth to the current account', async () => { it('sends eth to the current account', async () => {
const addressInput = await findElement(driver, By.css('#address')) const addressInput = await driver.findElement(By.css('#address'))
await addressInput.sendKeys(publicAddress) await addressInput.sendKeys(publicAddress)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const sendButton = await findElement(driver, By.css('#send')) const sendButton = await driver.findElement(By.css('#send'))
await sendButton.click() await sendButton.click()
const txStatus = await findElement(driver, By.css('#success')) const txStatus = await driver.findElement(By.css('#success'))
await driver.wait(until.elementTextMatches(txStatus, /Success/), 15000) await driver.wait(until.elementTextMatches(txStatus, /Success/), 15000)
}) })
it('switches back to MetaMask', async () => { it('switches back to MetaMask', async () => {
await driver.switchTo().window(extension) await driver.switchToWindow(extension)
}) })
it('should have the correct amount of eth', async () => { it('should have the correct amount of eth', async () => {
const balances = await findElements(driver, By.css('.currency-display-component__text')) const balances = await driver.findElements(By.css('.currency-display-component__text'))
await driver.wait(until.elementTextMatches(balances[0], /1/), 15000) await driver.wait(until.elementTextMatches(balances[0], /1/), 15000)
const balance = await balances[0].getText() const balance = await balances[0].getText()
@ -164,40 +154,40 @@ describe('MetaMask', function () {
describe('backs up the seed phrase', () => { describe('backs up the seed phrase', () => {
it('should show a backup reminder', async () => { it('should show a backup reminder', async () => {
const backupReminder = await findElements(driver, By.xpath("//div[contains(@class, 'home-notification__text') and contains(text(), 'Backup your Secret Recovery code to keep your wallet and funds secure')]")) const backupReminder = await driver.findElements(By.xpath("//div[contains(@class, 'home-notification__text') and contains(text(), 'Backup your Secret Recovery code to keep your wallet and funds secure')]"))
assert.equal(backupReminder.length, 1) assert.equal(backupReminder.length, 1)
}) })
it('should take the user to the seedphrase backup screen', async () => { it('should take the user to the seedphrase backup screen', async () => {
const backupButton = await findElement(driver, By.css('.home-notification__accept-button')) const backupButton = await driver.findElement(By.css('.home-notification__accept-button'))
await backupButton.click() await backupButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
let seedPhrase let seedPhrase
it('reveals the seed phrase', async () => { it('reveals the seed phrase', async () => {
const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button') const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button')
await driver.wait(until.elementLocated(byRevealButton, 10000)) const revealSeedPhraseButton = await driver.findElement(byRevealButton)
const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000)
await revealSeedPhraseButton.click() await revealSeedPhraseButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
seedPhrase = await driver.findElement(By.css('.reveal-seed-phrase__secret-words')).getText() const revealedSeedPhrase = await driver.findElement(By.css('.reveal-seed-phrase__secret-words'))
seedPhrase = await revealedSeedPhrase.getText()
assert.equal(seedPhrase.split(' ').length, 12) assert.equal(seedPhrase.split(' ').length, 12)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.next.message}')]`)) const nextScreen = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.next.message}')]`))
await nextScreen.click() await nextScreen.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
async function clickWordAndWait (word) { async function clickWordAndWait (word) {
const xpath = `//div[contains(@class, 'confirm-seed-phrase__seed-word--shuffled') and not(contains(@class, 'confirm-seed-phrase__seed-word--selected')) and contains(text(), '${word}')]` const xpath = `//div[contains(@class, 'confirm-seed-phrase__seed-word--shuffled') and not(contains(@class, 'confirm-seed-phrase__seed-word--selected')) and contains(text(), '${word}')]`
const word0 = await findElement(driver, By.xpath(xpath), 10000) const word0 = await driver.findElement(By.xpath(xpath))
await word0.click() await word0.click()
await delay(tinyDelayMs) await driver.delay(tinyDelayMs)
} }
it('can retype the seed phrase', async () => { it('can retype the seed phrase', async () => {
@ -207,19 +197,19 @@ describe('MetaMask', function () {
await clickWordAndWait(word) await clickWordAndWait(word)
} }
const confirm = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`)) const confirm = await driver.findElement(By.xpath(`//button[contains(text(), 'Confirm')]`))
await confirm.click() await confirm.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('can click through the success screen', async () => { it('can click through the success screen', async () => {
const confirm = await findElement(driver, By.xpath(`//button[contains(text(), 'All Done')]`)) const confirm = await driver.findElement(By.xpath(`//button[contains(text(), 'All Done')]`))
await confirm.click() await confirm.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('should have the correct amount of eth', async () => { it('should have the correct amount of eth', async () => {
const balances = await findElements(driver, By.css('.currency-display-component__text')) const balances = await driver.findElements(By.css('.currency-display-component__text'))
await driver.wait(until.elementTextMatches(balances[0], /1/), 15000) await driver.wait(until.elementTextMatches(balances[0], /1/), 15000)
const balance = await balances[0].getText() const balance = await balances[0].getText()
@ -227,7 +217,7 @@ describe('MetaMask', function () {
}) })
it('should not show a backup reminder', async () => { it('should not show a backup reminder', async () => {
await assertElementNotPresent(webdriver, driver, By.css('.backup-notification')) await driver.assertElementNotPresent(By.css('.backup-notification'))
}) })
}) })
}) })

@ -3,13 +3,10 @@ const webdriver = require('selenium-webdriver')
const { By, until } = webdriver const { By, until } = webdriver
const { const {
checkBrowserForConsoleErrors,
delay,
findElement,
findElements,
verboseReportOnFailure,
setupFetchMocking,
prepareExtensionForTesting, prepareExtensionForTesting,
tinyDelayMs,
regularDelayMs,
largeDelayMs,
} = require('./helpers') } = require('./helpers')
const Ganache = require('./ganache') const Ganache = require('./ganache')
const enLocaleMessages = require('../../app/_locales/en/messages.json') const enLocaleMessages = require('../../app/_locales/en/messages.json')
@ -20,9 +17,6 @@ describe('MetaMask', function () {
let driver let driver
const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent' const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'
const tinyDelayMs = 200
const regularDelayMs = tinyDelayMs * 2
const largeDelayMs = regularDelayMs * 2
this.timeout(0) this.timeout(0)
this.bail(true) this.bail(true)
@ -31,12 +25,11 @@ describe('MetaMask', function () {
await ganacheServer.start() await ganacheServer.start()
const result = await prepareExtensionForTesting({ responsive: true }) const result = await prepareExtensionForTesting({ responsive: true })
driver = result.driver driver = result.driver
await setupFetchMocking(driver)
}) })
afterEach(async function () { afterEach(async function () {
if (process.env.SELENIUM_BROWSER === 'chrome') { if (process.env.SELENIUM_BROWSER === 'chrome') {
const errors = await checkBrowserForConsoleErrors(driver) const errors = await driver.checkBrowserForConsoleErrors(driver)
if (errors.length) { if (errors.length) {
const errorReports = errors.map(err => err.message) const errorReports = errors.map(err => err.message)
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
@ -44,7 +37,7 @@ describe('MetaMask', function () {
} }
} }
if (this.currentTest.state === 'failed') { if (this.currentTest.state === 'failed') {
await verboseReportOnFailure(driver, this.currentTest) await driver.verboseReportOnFailure(driver, this.currentTest)
} }
}) })
@ -55,63 +48,63 @@ describe('MetaMask', function () {
describe('Going through the first time flow', () => { describe('Going through the first time flow', () => {
it('clicks the continue button on the welcome screen', async () => { it('clicks the continue button on the welcome screen', async () => {
await findElement(driver, By.css('.welcome-page__header')) await driver.findElement(By.css('.welcome-page__header'))
const welcomeScreenBtn = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) const welcomeScreenBtn = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
welcomeScreenBtn.click() welcomeScreenBtn.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('clicks the "Create New Wallet" option', async () => { it('clicks the "Create New Wallet" option', async () => {
const customRpcButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Create a Wallet')]`)) const customRpcButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Create a Wallet')]`))
customRpcButton.click() customRpcButton.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('clicks the "I agree" option on the metametrics opt-in screen', async () => { it('clicks the "I agree" option on the metametrics opt-in screen', async () => {
const optOutButton = await findElement(driver, By.css('.btn-primary')) const optOutButton = await driver.findElement(By.css('.btn-primary'))
optOutButton.click() optOutButton.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('accepts a secure password', async () => { it('accepts a secure password', async () => {
const passwordBox = await findElement(driver, By.css('.first-time-flow__form #create-password')) const passwordBox = await driver.findElement(By.css('.first-time-flow__form #create-password'))
const passwordBoxConfirm = await findElement(driver, By.css('.first-time-flow__form #confirm-password')) const passwordBoxConfirm = await driver.findElement(By.css('.first-time-flow__form #confirm-password'))
const button = await findElement(driver, By.css('.first-time-flow__form button')) const button = await driver.findElement(By.css('.first-time-flow__form button'))
await passwordBox.sendKeys('correct horse battery staple') await passwordBox.sendKeys('correct horse battery staple')
await passwordBoxConfirm.sendKeys('correct horse battery staple') await passwordBoxConfirm.sendKeys('correct horse battery staple')
const tosCheckBox = await findElement(driver, By.css('.first-time-flow__checkbox')) const tosCheckBox = await driver.findElement(By.css('.first-time-flow__checkbox'))
await tosCheckBox.click() await tosCheckBox.click()
await button.click() await button.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
let seedPhrase let seedPhrase
it('reveals the seed phrase', async () => { it('reveals the seed phrase', async () => {
const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button') const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button')
await driver.wait(until.elementLocated(byRevealButton, 10000)) const revealSeedPhraseButton = await driver.findElement(byRevealButton)
const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000)
await revealSeedPhraseButton.click() await revealSeedPhraseButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
seedPhrase = await driver.findElement(By.css('.reveal-seed-phrase__secret-words')).getText() const revealedSeedPhrase = await driver.findElement(By.css('.reveal-seed-phrase__secret-words'))
seedPhrase = await revealedSeedPhrase.getText()
assert.equal(seedPhrase.split(' ').length, 12) assert.equal(seedPhrase.split(' ').length, 12)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.next.message}')]`)) const nextScreen = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.next.message}')]`))
await nextScreen.click() await nextScreen.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
async function clickWordAndWait (word) { async function clickWordAndWait (word) {
const xpath = `//div[contains(@class, 'confirm-seed-phrase__seed-word--shuffled') and not(contains(@class, 'confirm-seed-phrase__seed-word--selected')) and contains(text(), '${word}')]` const xpath = `//div[contains(@class, 'confirm-seed-phrase__seed-word--shuffled') and not(contains(@class, 'confirm-seed-phrase__seed-word--selected')) and contains(text(), '${word}')]`
const word0 = await findElement(driver, By.xpath(xpath), 10000) const word0 = await driver.findElement(By.xpath(xpath))
await word0.click() await word0.click()
await delay(tinyDelayMs) await driver.delay(tinyDelayMs)
} }
it('can retype the seed phrase', async () => { it('can retype the seed phrase', async () => {
@ -121,126 +114,129 @@ describe('MetaMask', function () {
await clickWordAndWait(word) await clickWordAndWait(word)
} }
const confirm = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`)) const confirm = await driver.findElement(By.xpath(`//button[contains(text(), 'Confirm')]`))
await confirm.click() await confirm.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('clicks through the success screen', async () => { it('clicks through the success screen', async () => {
await findElement(driver, By.xpath(`//div[contains(text(), 'Congratulations')]`)) await driver.findElement(By.xpath(`//div[contains(text(), 'Congratulations')]`))
const doneButton = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`)) const doneButton = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`))
await doneButton.click() await doneButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
}) })
describe('Show account information', () => { describe('Show account information', () => {
it('show account details dropdown menu', async () => { it('show account details dropdown menu', async () => {
await driver.findElement(By.css('div.menu-bar__open-in-browser')).click() const openInBrowser = await driver.findElement(By.css('div.menu-bar__open-in-browser'))
await openInBrowser.click()
const options = await driver.findElements(By.css('div.menu.account-details-dropdown div.menu__item')) const options = await driver.findElements(By.css('div.menu.account-details-dropdown div.menu__item'))
assert.equal(options.length, 4) // HD Wallet type does not have to show the Remove Account option assert.equal(options.length, 4) // HD Wallet type does not have to show the Remove Account option
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
}) })
describe('Import seed phrase', () => { describe('Import seed phrase', () => {
it('logs out of the vault', async () => { it('logs out of the vault', async () => {
await driver.findElement(By.css('.account-menu__icon')).click() const accountMenuButton = await driver.findElement(By.css('.account-menu__icon'))
await delay(regularDelayMs) await accountMenuButton.click()
await driver.delay(regularDelayMs)
const logoutButton = await findElement(driver, By.css('.account-menu__logout-button')) const logoutButton = await driver.findElement(By.css('.account-menu__logout-button'))
assert.equal(await logoutButton.getText(), 'Log out') assert.equal(await logoutButton.getText(), 'Log out')
await logoutButton.click() await logoutButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('imports seed phrase', async () => { it('imports seed phrase', async () => {
const restoreSeedLink = await findElement(driver, By.css('.unlock-page__link--import')) const restoreSeedLink = await driver.findElement(By.css('.unlock-page__link--import'))
assert.equal(await restoreSeedLink.getText(), 'Import using account seed phrase') assert.equal(await restoreSeedLink.getText(), 'Import using account seed phrase')
await restoreSeedLink.click() await restoreSeedLink.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const seedTextArea = await findElement(driver, By.css('textarea')) const seedTextArea = await driver.findElement(By.css('textarea'))
await seedTextArea.sendKeys(testSeedPhrase) await seedTextArea.sendKeys(testSeedPhrase)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const passwordInputs = await driver.findElements(By.css('input')) const passwordInputs = await driver.findElements(By.css('input'))
await delay(regularDelayMs) await driver.delay(regularDelayMs)
await passwordInputs[0].sendKeys('correct horse battery staple') await passwordInputs[0].sendKeys('correct horse battery staple')
await passwordInputs[1].sendKeys('correct horse battery staple') await passwordInputs[1].sendKeys('correct horse battery staple')
await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.restore.message}')]`)).click() const restoreButton = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.restore.message}')]`))
await delay(regularDelayMs) await restoreButton.click()
await driver.delay(regularDelayMs)
}) })
it('switches to localhost', async () => { it('switches to localhost', async () => {
const networkDropdown = await findElement(driver, By.css('.network-name')) const networkDropdown = await driver.findElement(By.css('.network-name'))
await networkDropdown.click() await networkDropdown.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const [localhost] = await findElements(driver, By.xpath(`//span[contains(text(), 'Localhost')]`)) const [localhost] = await driver.findElements(By.xpath(`//span[contains(text(), 'Localhost')]`))
await localhost.click() await localhost.click()
await delay(largeDelayMs * 2) await driver.delay(largeDelayMs * 2)
}) })
it('balance renders', async () => { it('balance renders', async () => {
const balance = await findElement(driver, By.css('.transaction-view-balance__primary-balance')) const balance = await driver.findElement(By.css('.transaction-view-balance__primary-balance'))
await driver.wait(until.elementTextMatches(balance, /100\s*ETH/)) await driver.wait(until.elementTextMatches(balance, /100\s*ETH/))
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
}) })
describe('Send ETH from inside MetaMask', () => { describe('Send ETH from inside MetaMask', () => {
it('starts to send a transaction', async function () { it('starts to send a transaction', async function () {
const sendButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Send')]`)) const sendButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Send')]`))
await sendButton.click() await sendButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const inputAddress = await findElement(driver, By.css('input[placeholder="Search, public address (0x), or ENS"]')) const inputAddress = await driver.findElement(By.css('input[placeholder="Search, public address (0x), or ENS"]'))
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
const inputAmount = await findElement(driver, By.css('.unit-input__input')) const inputAmount = await driver.findElement(By.css('.unit-input__input'))
await inputAmount.sendKeys('1') await inputAmount.sendKeys('1')
const inputValue = await inputAmount.getAttribute('value') const inputValue = await inputAmount.getAttribute('value')
assert.equal(inputValue, '1') assert.equal(inputValue, '1')
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('opens and closes the gas modal', async function () { it('opens and closes the gas modal', async function () {
// Set the gas limit // Set the gas limit
const configureGas = await findElement(driver, By.css('.advanced-gas-options-btn')) const configureGas = await driver.findElement(By.css('.advanced-gas-options-btn'))
await configureGas.click() await configureGas.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const gasModal = await driver.findElement(By.css('span .modal')) const gasModal = await driver.findElement(By.css('span .modal'))
const save = await findElement(driver, By.css('.page-container__header-close-text')) const save = await driver.findElement(By.css('.page-container__header-close-text'))
await save.click() await save.click()
await driver.wait(until.stalenessOf(gasModal), 10000) await driver.wait(until.stalenessOf(gasModal), 10000)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('clicks through to the confirm screen', async function () { it('clicks through to the confirm screen', async function () {
// Continue to next screen // Continue to next screen
const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), 'Next')]`)) const nextScreen = await driver.findElement(By.xpath(`//button[contains(text(), 'Next')]`))
await nextScreen.click() await nextScreen.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('confirms the transaction', async function () { it('confirms the transaction', async function () {
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`)) const confirmButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Confirm')]`))
await confirmButton.click() await confirmButton.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('finds the transaction in the transactions list', async function () { it('finds the transaction in the transactions list', async function () {
await driver.wait(async () => { await driver.wait(async () => {
const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item')) const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item'))
return confirmedTxes.length === 1 return confirmedTxes.length === 1
}, 10000) }, 10000)
const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary')) const txValues = await driver.findElement(By.css('.transaction-list-item__amount--primary'))
await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/), 10000) await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/), 10000)
}) })
}) })

File diff suppressed because it is too large Load Diff

@ -3,16 +3,9 @@ const webdriver = require('selenium-webdriver')
const { By, until } = webdriver const { By, until } = webdriver
const { const {
checkBrowserForConsoleErrors,
delay,
findElement,
findElements,
openNewPage,
verboseReportOnFailure,
waitUntilXWindowHandles,
switchToWindowWithTitle,
setupFetchMocking,
prepareExtensionForTesting, prepareExtensionForTesting,
regularDelayMs,
largeDelayMs,
} = require('./helpers') } = require('./helpers')
const Ganache = require('./ganache') const Ganache = require('./ganache')
const enLocaleMessages = require('../../app/_locales/en/messages.json') const enLocaleMessages = require('../../app/_locales/en/messages.json')
@ -23,10 +16,6 @@ describe('MetaMask', function () {
let driver let driver
let publicAddress let publicAddress
const tinyDelayMs = 200
const regularDelayMs = tinyDelayMs * 2
const largeDelayMs = regularDelayMs * 2
this.timeout(0) this.timeout(0)
this.bail(true) this.bail(true)
@ -41,12 +30,11 @@ describe('MetaMask', function () {
}) })
const result = await prepareExtensionForTesting() const result = await prepareExtensionForTesting()
driver = result.driver driver = result.driver
await setupFetchMocking(driver)
}) })
afterEach(async function () { afterEach(async function () {
if (process.env.SELENIUM_BROWSER === 'chrome') { if (process.env.SELENIUM_BROWSER === 'chrome') {
const errors = await checkBrowserForConsoleErrors(driver) const errors = await driver.checkBrowserForConsoleErrors(driver)
if (errors.length) { if (errors.length) {
const errorReports = errors.map(err => err.message) const errorReports = errors.map(err => err.message)
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
@ -54,7 +42,7 @@ describe('MetaMask', function () {
} }
} }
if (this.currentTest.state === 'failed') { if (this.currentTest.state === 'failed') {
await verboseReportOnFailure(driver, this.currentTest) await driver.verboseReportOnFailure(driver, this.currentTest)
} }
}) })
@ -65,51 +53,51 @@ describe('MetaMask', function () {
describe('Going through the first time flow, but skipping the seed phrase challenge', () => { describe('Going through the first time flow, but skipping the seed phrase challenge', () => {
it('clicks the continue button on the welcome screen', async () => { it('clicks the continue button on the welcome screen', async () => {
await findElement(driver, By.css('.welcome-page__header')) await driver.findElement(By.css('.welcome-page__header'))
const welcomeScreenBtn = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) const welcomeScreenBtn = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
welcomeScreenBtn.click() welcomeScreenBtn.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('clicks the "Create New Wallet" option', async () => { it('clicks the "Create New Wallet" option', async () => {
const customRpcButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Create a Wallet')]`)) const customRpcButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Create a Wallet')]`))
customRpcButton.click() customRpcButton.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('clicks the "No thanks" option on the metametrics opt-in screen', async () => { it('clicks the "No thanks" option on the metametrics opt-in screen', async () => {
const optOutButton = await findElement(driver, By.css('.btn-default')) const optOutButton = await driver.findElement(By.css('.btn-default'))
optOutButton.click() optOutButton.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('accepts a secure password', async () => { it('accepts a secure password', async () => {
const passwordBox = await findElement(driver, By.css('.first-time-flow__form #create-password')) const passwordBox = await driver.findElement(By.css('.first-time-flow__form #create-password'))
const passwordBoxConfirm = await findElement(driver, By.css('.first-time-flow__form #confirm-password')) const passwordBoxConfirm = await driver.findElement(By.css('.first-time-flow__form #confirm-password'))
const button = await findElement(driver, By.css('.first-time-flow__form button')) const button = await driver.findElement(By.css('.first-time-flow__form button'))
await passwordBox.sendKeys('correct horse battery staple') await passwordBox.sendKeys('correct horse battery staple')
await passwordBoxConfirm.sendKeys('correct horse battery staple') await passwordBoxConfirm.sendKeys('correct horse battery staple')
const tosCheckBox = await findElement(driver, By.css('.first-time-flow__checkbox')) const tosCheckBox = await driver.findElement(By.css('.first-time-flow__checkbox'))
await tosCheckBox.click() await tosCheckBox.click()
await button.click() await button.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('skips the seed phrase challenge', async () => { it('skips the seed phrase challenge', async () => {
const button = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.remindMeLater.message}')]`)) const button = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.remindMeLater.message}')]`))
await button.click() await button.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const detailsButton = await findElement(driver, By.css('.account-details__details-button')) const detailsButton = await driver.findElement(By.css('.account-details__details-button'))
await detailsButton.click() await detailsButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('gets the current accounts address', async () => { it('gets the current accounts address', async () => {
const addressInput = await findElement(driver, By.css('.qr-ellip-address')) const addressInput = await driver.findElement(By.css('.qr-ellip-address'))
publicAddress = await addressInput.getAttribute('value') publicAddress = await addressInput.getAttribute('value')
const accountModal = await driver.findElement(By.css('span .modal')) const accountModal = await driver.findElement(By.css('span .modal'))
@ -117,7 +105,7 @@ describe('MetaMask', function () {
await accountModalClose.click() await accountModalClose.click()
await driver.wait(until.stalenessOf(accountModal)) await driver.wait(until.stalenessOf(accountModal))
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
}) })
@ -127,86 +115,86 @@ describe('MetaMask', function () {
let dapp let dapp
it('connects to the dapp', async () => { it('connects to the dapp', async () => {
await openNewPage(driver, 'http://127.0.0.1:8080/') await driver.openNewPage('http://127.0.0.1:8080/')
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) const connectButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Connect')]`))
await connectButton.click() await connectButton.click()
await waitUntilXWindowHandles(driver, 3) await driver.waitUntilXWindowHandles(3)
const windowHandles = await driver.getAllWindowHandles() const windowHandles = await driver.getAllWindowHandles()
extension = windowHandles[0] extension = windowHandles[0]
dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles) dapp = await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles)
popup = windowHandles.find(handle => handle !== extension && handle !== dapp) popup = windowHandles.find(handle => handle !== extension && handle !== dapp)
await driver.switchTo().window(popup) await driver.switchToWindow(popup)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const accountButton = await findElement(driver, By.css('.permissions-connect-choose-account__account')) const accountButton = await driver.findElement(By.css('.permissions-connect-choose-account__account'))
await accountButton.click() await accountButton.click()
const submitButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Submit')]`)) const submitButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Submit')]`))
await submitButton.click() await submitButton.click()
await waitUntilXWindowHandles(driver, 2) await driver.waitUntilXWindowHandles(2)
await driver.switchTo().window(extension) await driver.switchToWindow(extension)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('shows connected sites', async () => { it('shows connected sites', async () => {
const connectedSites = await findElement(driver, By.xpath(`//button[contains(text(), 'Connected Sites')]`)) const connectedSites = await driver.findElement(By.xpath(`//button[contains(text(), 'Connected Sites')]`))
await connectedSites.click() await connectedSites.click()
await findElement(driver, By.css('.connected-sites__title')) await driver.findElement(By.css('.connected-sites__title'))
const domains = await findElements(driver, By.css('.connected-sites-list__domain')) const domains = await driver.findElements(By.css('.connected-sites-list__domain'))
assert.equal(domains.length, 1) assert.equal(domains.length, 1)
const domainName = await findElement(driver, By.css('.connected-sites-list__domain-name')) const domainName = await driver.findElement(By.css('.connected-sites-list__domain-name'))
assert.equal(await domainName.getText(), 'E2E Test Dapp') assert.equal(await domainName.getText(), 'E2E Test Dapp')
await domains[0].click() await domains[0].click()
const permissionDescription = await findElement(driver, By.css('.connected-sites-list__permission-description')) const permissionDescription = await driver.findElement(By.css('.connected-sites-list__permission-description'))
assert.equal(await permissionDescription.getText(), 'View the address of the selected account') assert.equal(await permissionDescription.getText(), 'View the address of the selected account')
}) })
it('can get accounts within the dapp', async () => { it('can get accounts within the dapp', async () => {
await driver.switchTo().window(dapp) await driver.switchToWindow(dapp)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const getAccountsButton = await findElement(driver, By.xpath(`//button[contains(text(), 'eth_accounts')]`)) const getAccountsButton = await driver.findElement(By.xpath(`//button[contains(text(), 'eth_accounts')]`))
await getAccountsButton.click() await getAccountsButton.click()
const getAccountsResult = await findElement(driver, By.css('#getAccountsResult')) const getAccountsResult = await driver.findElement(By.css('#getAccountsResult'))
assert.equal((await getAccountsResult.getText()).toLowerCase(), publicAddress.toLowerCase()) assert.equal((await getAccountsResult.getText()).toLowerCase(), publicAddress.toLowerCase())
}) })
it('can disconnect all accounts', async () => { it('can disconnect all accounts', async () => {
await driver.switchTo().window(extension) await driver.switchToWindow(extension)
const disconnectAllButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Disconnect All')]`)) const disconnectAllButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Disconnect All')]`))
await disconnectAllButton.click() await disconnectAllButton.click()
const disconnectModal = await driver.findElement(By.css('span .modal')) const disconnectModal = await driver.findElement(By.css('span .modal'))
const disconnectAllModalButton = await findElement(driver, By.css('.disconnect-all-modal .btn-danger')) const disconnectAllModalButton = await driver.findElement(By.css('.disconnect-all-modal .btn-danger'))
await disconnectAllModalButton.click() await disconnectAllModalButton.click()
await driver.wait(until.stalenessOf(disconnectModal)) await driver.wait(until.stalenessOf(disconnectModal))
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('can no longer get accounts within the dapp', async () => { it('can no longer get accounts within the dapp', async () => {
await driver.switchTo().window(dapp) await driver.switchToWindow(dapp)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const getAccountsButton = await findElement(driver, By.xpath(`//button[contains(text(), 'eth_accounts')]`)) const getAccountsButton = await driver.findElement(By.xpath(`//button[contains(text(), 'eth_accounts')]`))
await getAccountsButton.click() await getAccountsButton.click()
const getAccountsResult = await findElement(driver, By.css('#getAccountsResult')) const getAccountsResult = await driver.findElement(By.css('#getAccountsResult'))
assert.equal(await getAccountsResult.getText(), 'Not able to get accounts') assert.equal(await getAccountsResult.getText(), 'Not able to get accounts')
}) })
}) })

@ -3,13 +3,10 @@ const webdriver = require('selenium-webdriver')
const { By, Key, until } = webdriver const { By, Key, until } = webdriver
const { const {
checkBrowserForConsoleErrors,
delay,
verboseReportOnFailure,
findElement,
findElements,
setupFetchMocking,
prepareExtensionForTesting, prepareExtensionForTesting,
tinyDelayMs,
regularDelayMs,
largeDelayMs,
} = require('./helpers') } = require('./helpers')
const Ganache = require('./ganache') const Ganache = require('./ganache')
const enLocaleMessages = require('../../app/_locales/en/messages.json') const enLocaleMessages = require('../../app/_locales/en/messages.json')
@ -20,9 +17,6 @@ describe('Using MetaMask with an existing account', function () {
let driver let driver
const testSeedPhrase = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress' const testSeedPhrase = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress'
const tinyDelayMs = 200
const regularDelayMs = 1000
const largeDelayMs = regularDelayMs * 2
this.timeout(0) this.timeout(0)
this.bail(true) this.bail(true)
@ -38,12 +32,11 @@ describe('Using MetaMask with an existing account', function () {
}) })
const result = await prepareExtensionForTesting() const result = await prepareExtensionForTesting()
driver = result.driver driver = result.driver
await setupFetchMocking(driver)
}) })
afterEach(async function () { afterEach(async function () {
if (process.env.SELENIUM_BROWSER === 'chrome') { if (process.env.SELENIUM_BROWSER === 'chrome') {
const errors = await checkBrowserForConsoleErrors(driver) const errors = await driver.checkBrowserForConsoleErrors(driver)
if (errors.length) { if (errors.length) {
const errorReports = errors.map(err => err.message) const errorReports = errors.map(err => err.message)
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
@ -51,7 +44,7 @@ describe('Using MetaMask with an existing account', function () {
} }
} }
if (this.currentTest.state === 'failed') { if (this.currentTest.state === 'failed') {
await verboseReportOnFailure(driver, this.currentTest) await driver.verboseReportOnFailure(driver, this.currentTest)
} }
}) })
@ -62,102 +55,102 @@ describe('Using MetaMask with an existing account', function () {
describe('First time flow starting from an existing seed phrase', () => { describe('First time flow starting from an existing seed phrase', () => {
it('clicks the continue button on the welcome screen', async () => { it('clicks the continue button on the welcome screen', async () => {
await findElement(driver, By.css('.welcome-page__header')) await driver.findElement(By.css('.welcome-page__header'))
const welcomeScreenBtn = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) const welcomeScreenBtn = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
welcomeScreenBtn.click() welcomeScreenBtn.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('clicks the "Import Wallet" option', async () => { it('clicks the "Import Wallet" option', async () => {
const customRpcButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Import Wallet')]`)) const customRpcButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Import Wallet')]`))
customRpcButton.click() customRpcButton.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('clicks the "No thanks" option on the metametrics opt-in screen', async () => { it('clicks the "No thanks" option on the metametrics opt-in screen', async () => {
const optOutButton = await findElement(driver, By.css('.btn-default')) const optOutButton = await driver.findElement(By.css('.btn-default'))
optOutButton.click() optOutButton.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('imports a seed phrase', async () => { it('imports a seed phrase', async () => {
const [seedTextArea] = await findElements(driver, By.css('textarea.first-time-flow__textarea')) const [seedTextArea] = await driver.findElements(By.css('textarea.first-time-flow__textarea'))
await seedTextArea.sendKeys(testSeedPhrase) await seedTextArea.sendKeys(testSeedPhrase)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const [password] = await findElements(driver, By.id('password')) const [password] = await driver.findElements(By.id('password'))
await password.sendKeys('correct horse battery staple') await password.sendKeys('correct horse battery staple')
const [confirmPassword] = await findElements(driver, By.id('confirm-password')) const [confirmPassword] = await driver.findElements(By.id('confirm-password'))
confirmPassword.sendKeys('correct horse battery staple') confirmPassword.sendKeys('correct horse battery staple')
const tosCheckBox = await findElement(driver, By.css('.first-time-flow__checkbox')) const tosCheckBox = await driver.findElement(By.css('.first-time-flow__checkbox'))
await tosCheckBox.click() await tosCheckBox.click()
const [importButton] = await findElements(driver, By.xpath(`//button[contains(text(), 'Import')]`)) const [importButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Import')]`))
await importButton.click() await importButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('clicks through the success screen', async () => { it('clicks through the success screen', async () => {
await findElement(driver, By.xpath(`//div[contains(text(), 'Congratulations')]`)) await driver.findElement(By.xpath(`//div[contains(text(), 'Congratulations')]`))
const doneButton = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`)) const doneButton = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`))
await doneButton.click() await doneButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
}) })
describe('Send ETH from inside MetaMask', () => { describe('Send ETH from inside MetaMask', () => {
it('starts a send transaction', async function () { it('starts a send transaction', async function () {
const sendButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Send')]`)) const sendButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Send')]`))
await sendButton.click() await sendButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const inputAddress = await findElement(driver, By.css('input[placeholder="Search, public address (0x), or ENS"]')) const inputAddress = await driver.findElement(By.css('input[placeholder="Search, public address (0x), or ENS"]'))
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
const inputAmount = await findElement(driver, By.css('.unit-input__input')) const inputAmount = await driver.findElement(By.css('.unit-input__input'))
await inputAmount.sendKeys('1') await inputAmount.sendKeys('1')
// Set the gas limit // Set the gas limit
const configureGas = await findElement(driver, By.css('.advanced-gas-options-btn')) const configureGas = await driver.findElement(By.css('.advanced-gas-options-btn'))
await configureGas.click() await configureGas.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const gasModal = await driver.findElement(By.css('span .modal')) const gasModal = await driver.findElement(By.css('span .modal'))
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-gas-inputs__gas-edit-row__input')) const [gasPriceInput, gasLimitInput] = await driver.findElements(By.css('.advanced-gas-inputs__gas-edit-row__input'))
await gasPriceInput.sendKeys(Key.chord(Key.CONTROL, 'a')) await gasPriceInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
await delay(50) await driver.delay(50)
await gasPriceInput.sendKeys(Key.BACK_SPACE) await gasPriceInput.sendKeys(Key.BACK_SPACE)
await delay(50) await driver.delay(50)
await gasPriceInput.sendKeys(Key.BACK_SPACE) await gasPriceInput.sendKeys(Key.BACK_SPACE)
await delay(50) await driver.delay(50)
await gasPriceInput.sendKeys('10') await gasPriceInput.sendKeys('10')
await delay(50) await driver.delay(50)
await delay(tinyDelayMs) await driver.delay(tinyDelayMs)
await delay(50) await driver.delay(50)
await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'a')) await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
await delay(50) await driver.delay(50)
await gasLimitInput.sendKeys('25000') await gasLimitInput.sendKeys('25000')
await delay(1000) await driver.delay(1000)
const save = await findElement(driver, By.xpath(`//button[contains(text(), 'Save')]`)) const save = await driver.findElement(By.xpath(`//button[contains(text(), 'Save')]`))
await save.click() await save.click()
await driver.wait(until.stalenessOf(gasModal)) await driver.wait(until.stalenessOf(gasModal))
await delay(regularDelayMs) await driver.delay(regularDelayMs)
// Continue to next screen // Continue to next screen
const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), 'Next')]`)) const nextScreen = await driver.findElement(By.xpath(`//button[contains(text(), 'Next')]`))
await nextScreen.click() await nextScreen.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('has correct value and fee on the confirm screen the transaction', async function () { it('has correct value and fee on the confirm screen the transaction', async function () {
const transactionAmounts = await findElements(driver, By.css('.currency-display-component__text')) const transactionAmounts = await driver.findElements(By.css('.currency-display-component__text'))
const transactionAmount = transactionAmounts[0] const transactionAmount = transactionAmounts[0]
assert.equal(await transactionAmount.getText(), '1') assert.equal(await transactionAmount.getText(), '1')
@ -166,55 +159,55 @@ describe('Using MetaMask with an existing account', function () {
}) })
it('edits the transaction', async function () { it('edits the transaction', async function () {
const editButton = await findElement(driver, By.css('.confirm-page-container-header__back-button')) const editButton = await driver.findElement(By.css('.confirm-page-container-header__back-button'))
await editButton.click() await editButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const inputAmount = await findElement(driver, By.css('.unit-input__input')) const inputAmount = await driver.findElement(By.css('.unit-input__input'))
await inputAmount.sendKeys(Key.chord(Key.CONTROL, 'a')) await inputAmount.sendKeys(Key.chord(Key.CONTROL, 'a'))
await delay(50) await driver.delay(50)
await inputAmount.sendKeys(Key.BACK_SPACE) await inputAmount.sendKeys(Key.BACK_SPACE)
await delay(50) await driver.delay(50)
await inputAmount.sendKeys('2.2') await inputAmount.sendKeys('2.2')
const configureGas = await findElement(driver, By.css('.advanced-gas-options-btn')) const configureGas = await driver.findElement(By.css('.advanced-gas-options-btn'))
await configureGas.click() await configureGas.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const gasModal = await driver.findElement(By.css('span .modal')) const gasModal = await driver.findElement(By.css('span .modal'))
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-gas-inputs__gas-edit-row__input')) const [gasPriceInput, gasLimitInput] = await driver.findElements(By.css('.advanced-gas-inputs__gas-edit-row__input'))
await gasPriceInput.sendKeys(Key.chord(Key.CONTROL, 'a')) await gasPriceInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
await delay(50) await driver.delay(50)
await gasPriceInput.sendKeys(Key.BACK_SPACE) await gasPriceInput.sendKeys(Key.BACK_SPACE)
await delay(50) await driver.delay(50)
await gasPriceInput.sendKeys(Key.BACK_SPACE) await gasPriceInput.sendKeys(Key.BACK_SPACE)
await delay(50) await driver.delay(50)
await gasPriceInput.sendKeys('8') await gasPriceInput.sendKeys('8')
await delay(50) await driver.delay(50)
await delay(tinyDelayMs) await driver.delay(tinyDelayMs)
await delay(50) await driver.delay(50)
await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'a')) await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
await delay(50) await driver.delay(50)
await gasLimitInput.sendKeys('100000') await gasLimitInput.sendKeys('100000')
await delay(1000) await driver.delay(1000)
const save = await findElement(driver, By.xpath(`//button[contains(text(), 'Save')]`)) const save = await driver.findElement(By.xpath(`//button[contains(text(), 'Save')]`))
await save.click() await save.click()
await driver.wait(until.stalenessOf(gasModal)) await driver.wait(until.stalenessOf(gasModal))
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), 'Next')]`)) const nextScreen = await driver.findElement(By.xpath(`//button[contains(text(), 'Next')]`))
await nextScreen.click() await nextScreen.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('has correct updated value on the confirm screen the transaction', async function () { it('has correct updated value on the confirm screen the transaction', async function () {
const transactionAmounts = await findElements(driver, By.css('.currency-display-component__text')) const transactionAmounts = await driver.findElements(By.css('.currency-display-component__text'))
const transactionAmount = transactionAmounts[0] const transactionAmount = transactionAmounts[0]
assert.equal(await transactionAmount.getText(), '2.2') assert.equal(await transactionAmount.getText(), '2.2')
@ -223,18 +216,18 @@ describe('Using MetaMask with an existing account', function () {
}) })
it('confirms the transaction', async function () { it('confirms the transaction', async function () {
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`)) const confirmButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Confirm')]`))
await confirmButton.click() await confirmButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('finds the transaction in the transactions list', async function () { it('finds the transaction in the transactions list', async function () {
await driver.wait(async () => { await driver.wait(async () => {
const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item')) const confirmedTxes = await driver.findElements(By.css('.transaction-list__completed-transactions .transaction-list-item'))
return confirmedTxes.length === 1 return confirmedTxes.length === 1
}, 10000) }, 10000)
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary')) const txValues = await driver.findElements(By.css('.transaction-list-item__amount--primary'))
assert.equal(txValues.length, 1) assert.equal(txValues.length, 1)
assert.ok(/-2.2\s*ETH/.test(await txValues[0].getText())) assert.ok(/-2.2\s*ETH/.test(await txValues[0].getText()))
}) })

@ -4,16 +4,9 @@ const webdriver = require('selenium-webdriver')
const { By, Key, until } = webdriver const { By, Key, until } = webdriver
const { const {
checkBrowserForConsoleErrors,
delay,
findElement,
findElements,
openNewPage,
verboseReportOnFailure,
waitUntilXWindowHandles,
switchToWindowWithTitle,
setupFetchMocking,
prepareExtensionForTesting, prepareExtensionForTesting,
regularDelayMs,
largeDelayMs,
} = require('./helpers') } = require('./helpers')
const Ganache = require('./ganache') const Ganache = require('./ganache')
const FixtureServer = require('./fixture-server') const FixtureServer = require('./fixture-server')
@ -26,10 +19,6 @@ describe('MetaMask', function () {
let driver let driver
let publicAddress let publicAddress
const tinyDelayMs = 200
const regularDelayMs = tinyDelayMs * 2
const largeDelayMs = regularDelayMs * 2
this.timeout(0) this.timeout(0)
this.bail(true) this.bail(true)
@ -40,12 +29,11 @@ describe('MetaMask', function () {
publicAddress = '0x5cfe73b6021e818b776b421b1c4db2474086a7e1' publicAddress = '0x5cfe73b6021e818b776b421b1c4db2474086a7e1'
const result = await prepareExtensionForTesting() const result = await prepareExtensionForTesting()
driver = result.driver driver = result.driver
await setupFetchMocking(driver)
}) })
afterEach(async function () { afterEach(async function () {
if (process.env.SELENIUM_BROWSER === 'chrome') { if (process.env.SELENIUM_BROWSER === 'chrome') {
const errors = await checkBrowserForConsoleErrors(driver) const errors = await driver.checkBrowserForConsoleErrors(driver)
if (errors.length) { if (errors.length) {
const errorReports = errors.map(err => err.message) const errorReports = errors.map(err => err.message)
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
@ -53,7 +41,7 @@ describe('MetaMask', function () {
} }
} }
if (this.currentTest.state === 'failed') { if (this.currentTest.state === 'failed') {
await verboseReportOnFailure(driver, this.currentTest) await driver.verboseReportOnFailure(driver, this.currentTest)
} }
}) })
@ -70,55 +58,56 @@ describe('MetaMask', function () {
let windowHandles let windowHandles
it('accepts the account password after lock', async () => { it('accepts the account password after lock', async () => {
await delay(1000) await driver.delay(1000)
await driver.findElement(By.id('password')).sendKeys('correct horse battery staple') const passwordField = await driver.findElement(By.id('password'))
await driver.findElement(By.id('password')).sendKeys(Key.ENTER) await passwordField.sendKeys('correct horse battery staple')
await delay(largeDelayMs * 4) await passwordField.sendKeys(Key.ENTER)
await driver.delay(largeDelayMs * 4)
}) })
it('connects to the dapp', async () => { it('connects to the dapp', async () => {
await openNewPage(driver, 'http://127.0.0.1:8080/') await driver.openNewPage('http://127.0.0.1:8080/')
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) const connectButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Connect')]`))
await connectButton.click() await connectButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
await waitUntilXWindowHandles(driver, 3) await driver.waitUntilXWindowHandles(3)
const windowHandles = await driver.getAllWindowHandles() const windowHandles = await driver.getAllWindowHandles()
extension = windowHandles[0] extension = windowHandles[0]
dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles) dapp = await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles)
popup = windowHandles.find(handle => handle !== extension && handle !== dapp) popup = windowHandles.find(handle => handle !== extension && handle !== dapp)
await driver.switchTo().window(popup) await driver.switchToWindow(popup)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const accountButton = await findElement(driver, By.css('.permissions-connect-choose-account__account')) const accountButton = await driver.findElement(By.css('.permissions-connect-choose-account__account'))
await accountButton.click() await accountButton.click()
const submitButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Submit')]`)) const submitButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Submit')]`))
await submitButton.click() await submitButton.click()
await waitUntilXWindowHandles(driver, 2) await driver.waitUntilXWindowHandles(2)
await driver.switchTo().window(dapp) await driver.switchToWindow(dapp)
}) })
it('creates a sign typed data signature request', async () => { it('creates a sign typed data signature request', async () => {
const signTypedMessage = await findElement(driver, By.xpath(`//button[contains(text(), 'Sign')]`), 10000) const signTypedMessage = await driver.findElement(By.xpath(`//button[contains(text(), 'Sign')]`), 10000)
await signTypedMessage.click() await signTypedMessage.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
windowHandles = await driver.getAllWindowHandles() windowHandles = await driver.getAllWindowHandles()
await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles) await driver.switchToWindowWithTitle('MetaMask Notification', windowHandles)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const title = await findElement(driver, By.css('.signature-request-content__title')) const title = await driver.findElement(By.css('.signature-request-content__title'))
const name = await findElement(driver, By.css('.signature-request-content__info--bolded')) const name = await driver.findElement(By.css('.signature-request-content__info--bolded'))
const content = await findElements(driver, By.css('.signature-request-content__info')) const content = await driver.findElements(By.css('.signature-request-content__info'))
const origin = content[0] const origin = content[0]
const address = content[1] const address = content[1]
assert.equal(await title.getText(), 'Signature Request') assert.equal(await title.getText(), 'Signature Request')
@ -128,20 +117,20 @@ describe('MetaMask', function () {
}) })
it('signs the transaction', async () => { it('signs the transaction', async () => {
const signButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Sign')]`), 10000) const signButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Sign')]`), 10000)
await signButton.click() await signButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
extension = windowHandles[0] extension = windowHandles[0]
await driver.switchTo().window(extension) await driver.switchToWindow(extension)
}) })
it('gets the current accounts address', async () => { it('gets the current accounts address', async () => {
const detailsButton = await findElement(driver, By.css('.account-details__details-button')) const detailsButton = await driver.findElement(By.css('.account-details__details-button'))
await detailsButton.click() await detailsButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const addressInput = await findElement(driver, By.css('.qr-ellip-address')) const addressInput = await driver.findElement(By.css('.qr-ellip-address'))
const newPublicAddress = await addressInput.getAttribute('value') const newPublicAddress = await addressInput.getAttribute('value')
const accountModal = await driver.findElement(By.css('span .modal')) const accountModal = await driver.findElement(By.css('span .modal'))
@ -149,7 +138,7 @@ describe('MetaMask', function () {
await accountModalClose.click() await accountModalClose.click()
await driver.wait(until.stalenessOf(accountModal)) await driver.wait(until.stalenessOf(accountModal))
await delay(regularDelayMs) await driver.delay(regularDelayMs)
assert.equal(newPublicAddress.toLowerCase(), publicAddress) assert.equal(newPublicAddress.toLowerCase(), publicAddress)
}) })
}) })

@ -4,13 +4,10 @@ const getPort = require('get-port')
const { By, until } = webdriver const { By, until } = webdriver
const { const {
checkBrowserForConsoleErrors,
delay,
findElement,
findElements,
verboseReportOnFailure,
setupFetchMocking,
prepareExtensionForTesting, prepareExtensionForTesting,
tinyDelayMs,
regularDelayMs,
largeDelayMs,
} = require('./helpers') } = require('./helpers')
const Ganache = require('./ganache') const Ganache = require('./ganache')
const enLocaleMessages = require('../../app/_locales/en/messages.json') const enLocaleMessages = require('../../app/_locales/en/messages.json')
@ -21,9 +18,6 @@ describe('MetaMask', function () {
let driver let driver
const testSeedPhrase = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress' const testSeedPhrase = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress'
const tinyDelayMs = 200
const regularDelayMs = tinyDelayMs * 2
const largeDelayMs = regularDelayMs * 2
this.timeout(0) this.timeout(0)
this.bail(true) this.bail(true)
@ -39,12 +33,11 @@ describe('MetaMask', function () {
}) })
const result = await prepareExtensionForTesting({ port: await getPort() }) const result = await prepareExtensionForTesting({ port: await getPort() })
driver = result.driver driver = result.driver
await setupFetchMocking(driver)
}) })
afterEach(async function () { afterEach(async function () {
if (process.env.SELENIUM_BROWSER === 'chrome') { if (process.env.SELENIUM_BROWSER === 'chrome') {
const errors = await checkBrowserForConsoleErrors(driver) const errors = await driver.checkBrowserForConsoleErrors(driver)
if (errors.length) { if (errors.length) {
const errorReports = errors.map(err => err.message) const errorReports = errors.map(err => err.message)
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
@ -52,7 +45,7 @@ describe('MetaMask', function () {
} }
} }
if (this.currentTest.state === 'failed') { if (this.currentTest.state === 'failed') {
await verboseReportOnFailure(driver, this.currentTest) await driver.verboseReportOnFailure(driver, this.currentTest)
} }
}) })
@ -65,70 +58,71 @@ describe('MetaMask', function () {
describe('First time flow starting from an existing seed phrase', () => { describe('First time flow starting from an existing seed phrase', () => {
it('clicks the continue button on the welcome screen', async () => { it('clicks the continue button on the welcome screen', async () => {
await findElement(driver, By.css('.welcome-page__header')) await driver.findElement(By.css('.welcome-page__header'))
const welcomeScreenBtn = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) const welcomeScreenBtn = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
welcomeScreenBtn.click() welcomeScreenBtn.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('clicks the "Import Wallet" option', async () => { it('clicks the "Import Wallet" option', async () => {
const customRpcButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Import Wallet')]`)) const customRpcButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Import Wallet')]`))
customRpcButton.click() customRpcButton.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('clicks the "No thanks" option on the metametrics opt-in screen', async () => { it('clicks the "No thanks" option on the metametrics opt-in screen', async () => {
const optOutButton = await findElement(driver, By.css('.btn-default')) const optOutButton = await driver.findElement(By.css('.btn-default'))
optOutButton.click() optOutButton.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('imports a seed phrase', async () => { it('imports a seed phrase', async () => {
const [seedTextArea] = await findElements(driver, By.css('textarea.first-time-flow__textarea')) const [seedTextArea] = await driver.findElements(By.css('textarea.first-time-flow__textarea'))
await seedTextArea.sendKeys(testSeedPhrase) await seedTextArea.sendKeys(testSeedPhrase)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const [password] = await findElements(driver, By.id('password')) const [password] = await driver.findElements(By.id('password'))
await password.sendKeys('correct horse battery staple') await password.sendKeys('correct horse battery staple')
const [confirmPassword] = await findElements(driver, By.id('confirm-password')) const [confirmPassword] = await driver.findElements(By.id('confirm-password'))
confirmPassword.sendKeys('correct horse battery staple') confirmPassword.sendKeys('correct horse battery staple')
const tosCheckBox = await findElement(driver, By.css('.first-time-flow__checkbox')) const tosCheckBox = await driver.findElement(By.css('.first-time-flow__checkbox'))
await tosCheckBox.click() await tosCheckBox.click()
const [importButton] = await findElements(driver, By.xpath(`//button[contains(text(), 'Import')]`)) const [importButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Import')]`))
await importButton.click() await importButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('clicks through the success screen', async () => { it('clicks through the success screen', async () => {
await findElement(driver, By.xpath(`//div[contains(text(), 'Congratulations')]`)) await driver.findElement(By.xpath(`//div[contains(text(), 'Congratulations')]`))
const doneButton = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`)) const doneButton = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`))
await doneButton.click() await doneButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('balance renders', async () => { it('balance renders', async () => {
const balance = await findElement(driver, By.css('.balance-display .token-amount')) const balance = await driver.findElement(By.css('.balance-display .token-amount'))
await driver.wait(until.elementTextMatches(balance, /25\s*ETH/)) await driver.wait(until.elementTextMatches(balance, /25\s*ETH/))
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
}) })
describe('turns on threebox syncing', () => { describe('turns on threebox syncing', () => {
it('goes to the settings screen', async () => { it('goes to the settings screen', async () => {
await driver.findElement(By.css('.account-menu__icon')).click() const accountMenuButton = await driver.findElement(By.css('.account-menu__icon'))
await delay(regularDelayMs) await accountMenuButton.click()
await driver.delay(regularDelayMs)
const settingsButton = await findElement(driver, By.xpath(`//div[contains(text(), 'Settings')]`)) const settingsButton = await driver.findElement(By.xpath(`//div[contains(text(), 'Settings')]`))
settingsButton.click() settingsButton.click()
}) })
it('turns on threebox syncing', async () => { it('turns on threebox syncing', async () => {
const advancedButton = await findElement(driver, By.xpath(`//div[contains(text(), 'Advanced')]`)) const advancedButton = await driver.findElement(By.xpath(`//div[contains(text(), 'Advanced')]`))
await advancedButton.click() await advancedButton.click()
const threeBoxToggle = await findElements(driver, By.css('.toggle-button')) const threeBoxToggle = await driver.findElements(By.css('.toggle-button'))
const threeBoxToggleButton = await threeBoxToggle[4].findElement(By.css('div')) const threeBoxToggleButton = await threeBoxToggle[4].findElement(By.css('div'))
await threeBoxToggleButton.click() await threeBoxToggleButton.click()
}) })
@ -137,37 +131,37 @@ describe('MetaMask', function () {
describe('updates settings and address book', () => { describe('updates settings and address book', () => {
it('adds an address to the contact list', async () => { it('adds an address to the contact list', async () => {
const generalButton = await findElement(driver, By.xpath(`//div[contains(text(), 'General')]`)) const generalButton = await driver.findElement(By.xpath(`//div[contains(text(), 'General')]`))
await generalButton.click() await generalButton.click()
}) })
it('turns on use of blockies', async () => { it('turns on use of blockies', async () => {
const toggleButton = await findElement(driver, By.css('.toggle-button > div')) const toggleButton = await driver.findElement(By.css('.toggle-button > div'))
await toggleButton.click() await toggleButton.click()
}) })
it('adds an address to the contact list', async () => { it('adds an address to the contact list', async () => {
const contactsButton = await findElement(driver, By.xpath(`//div[contains(text(), 'Contacts')]`)) const contactsButton = await driver.findElement(By.xpath(`//div[contains(text(), 'Contacts')]`))
await contactsButton.click() await contactsButton.click()
const addressBookAddButton = await findElement(driver, By.css('.address-book-add-button__button')) const addressBookAddButton = await driver.findElement(By.css('.address-book-add-button__button'))
await addressBookAddButton.click() await addressBookAddButton.click()
await delay(tinyDelayMs) await driver.delay(tinyDelayMs)
const addAddressInputs = await findElements(driver, By.css('input')) const addAddressInputs = await driver.findElements(By.css('input'))
await addAddressInputs[0].sendKeys('Test User Name 11') await addAddressInputs[0].sendKeys('Test User Name 11')
await delay(tinyDelayMs) await driver.delay(tinyDelayMs)
await addAddressInputs[1].sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') await addAddressInputs[1].sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
await delay(largeDelayMs * 2) await driver.delay(largeDelayMs * 2)
const saveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Save')]`)) const saveButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Save')]`))
await saveButton.click() await saveButton.click()
await findElement(driver, By.xpath(`//div[contains(text(), 'Test User Name 11')]`)) await driver.findElement(By.xpath(`//div[contains(text(), 'Test User Name 11')]`))
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
}) })
@ -179,7 +173,6 @@ describe('MetaMask', function () {
before(async function () { before(async function () {
const result = await prepareExtensionForTesting({ port: await getPort() }) const result = await prepareExtensionForTesting({ port: await getPort() })
driver2 = result.driver driver2 = result.driver
await setupFetchMocking(driver2)
}) })
after(async function () { after(async function () {
@ -188,84 +181,86 @@ describe('MetaMask', function () {
describe('First time flow starting from an existing seed phrase', () => { describe('First time flow starting from an existing seed phrase', () => {
it('clicks the continue button on the welcome screen', async () => { it('clicks the continue button on the welcome screen', async () => {
await findElement(driver2, By.css('.welcome-page__header')) await driver2.findElement(By.css('.welcome-page__header'))
const welcomeScreenBtn = await findElement(driver2, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) const welcomeScreenBtn = await driver2.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
welcomeScreenBtn.click() welcomeScreenBtn.click()
await delay(largeDelayMs) await driver2.delay(largeDelayMs)
}) })
it('clicks the "Import Wallet" option', async () => { it('clicks the "Import Wallet" option', async () => {
const customRpcButton = await findElement(driver2, By.xpath(`//button[contains(text(), 'Import Wallet')]`)) const customRpcButton = await driver2.findElement(By.xpath(`//button[contains(text(), 'Import Wallet')]`))
customRpcButton.click() customRpcButton.click()
await delay(largeDelayMs) await driver2.delay(largeDelayMs)
}) })
it('clicks the "No thanks" option on the metametrics opt-in screen', async () => { it('clicks the "No thanks" option on the metametrics opt-in screen', async () => {
const optOutButton = await findElement(driver2, By.css('.btn-default')) const optOutButton = await driver2.findElement(By.css('.btn-default'))
optOutButton.click() optOutButton.click()
await delay(largeDelayMs) await driver2.delay(largeDelayMs)
}) })
it('imports a seed phrase', async () => { it('imports a seed phrase', async () => {
const [seedTextArea] = await findElements(driver2, By.css('textarea.first-time-flow__textarea')) const [seedTextArea] = await driver2.findElements(By.css('textarea.first-time-flow__textarea'))
await seedTextArea.sendKeys(testSeedPhrase) await seedTextArea.sendKeys(testSeedPhrase)
await delay(regularDelayMs) await driver2.delay(regularDelayMs)
const [password] = await findElements(driver2, By.id('password')) const [password] = await driver2.findElements(By.id('password'))
await password.sendKeys('correct horse battery staple') await password.sendKeys('correct horse battery staple')
const [confirmPassword] = await findElements(driver2, By.id('confirm-password')) const [confirmPassword] = await driver2.findElements(By.id('confirm-password'))
confirmPassword.sendKeys('correct horse battery staple') confirmPassword.sendKeys('correct horse battery staple')
const tosCheckBox = await findElement(driver2, By.css('.first-time-flow__checkbox')) const tosCheckBox = await driver2.findElement(By.css('.first-time-flow__checkbox'))
await tosCheckBox.click() await tosCheckBox.click()
const [importButton] = await findElements(driver2, By.xpath(`//button[contains(text(), 'Import')]`)) const [importButton] = await driver2.findElements(By.xpath(`//button[contains(text(), 'Import')]`))
await importButton.click() await importButton.click()
await delay(regularDelayMs) await driver2.delay(regularDelayMs)
}) })
it('clicks through the success screen', async () => { it('clicks through the success screen', async () => {
await findElement(driver2, By.xpath(`//div[contains(text(), 'Congratulations')]`)) await driver2.findElement(By.xpath(`//div[contains(text(), 'Congratulations')]`))
const doneButton = await findElement(driver2, By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`)) const doneButton = await driver2.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`))
await doneButton.click() await doneButton.click()
await delay(regularDelayMs) await driver2.delay(regularDelayMs)
}) })
it('balance renders', async () => { it('balance renders', async () => {
const balance = await findElement(driver2, By.css('.balance-display .token-amount')) const balance = await driver2.findElement(By.css('.balance-display .token-amount'))
await driver2.wait(until.elementTextMatches(balance, /25\s*ETH/)) await driver2.wait(until.elementTextMatches(balance, /25\s*ETH/))
await delay(regularDelayMs) await driver2.delay(regularDelayMs)
}) })
}) })
describe('restores 3box data', () => { describe('restores 3box data', () => {
it('confirms the 3box restore notification', async () => { it('confirms the 3box restore notification', async () => {
const restoreButton = await findElement(driver2, By.css('.home-notification__accept-button')) const restoreButton = await driver2.findElement(By.css('.home-notification__accept-button'))
await restoreButton.click() await restoreButton.click()
}) })
// TODO: Fix tests from here forward; they're using the wrong driver
it('goes to the settings screen', async () => { it('goes to the settings screen', async () => {
await driver.findElement(By.css('.account-menu__icon')).click() const accountMenuButton = await driver.findElement(By.css('.account-menu__icon'))
await delay(regularDelayMs) await accountMenuButton.click()
await driver.delay(regularDelayMs)
const settingsButton = await findElement(driver, By.xpath(`//div[contains(text(), 'Settings')]`)) const settingsButton = await driver.findElement(By.xpath(`//div[contains(text(), 'Settings')]`))
settingsButton.click() settingsButton.click()
}) })
it('finds the blockies toggle turned on', async () => { it('finds the blockies toggle turned on', async () => {
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const toggleLabel = await findElement(driver, By.css('.toggle-button__status-label')) const toggleLabel = await driver.findElement(By.css('.toggle-button__status-label'))
const toggleLabelText = await toggleLabel.getText() const toggleLabelText = await toggleLabel.getText()
assert.equal(toggleLabelText, 'ON') assert.equal(toggleLabelText, 'ON')
}) })
it('finds the restored address in the contact list', async () => { it('finds the restored address in the contact list', async () => {
const contactsButton = await findElement(driver, By.xpath(`//div[contains(text(), 'Contacts')]`)) const contactsButton = await driver.findElement(By.xpath(`//div[contains(text(), 'Contacts')]`))
await contactsButton.click() await contactsButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
await findElement(driver, By.xpath(`//div[contains(text(), 'Test User Name 11')]`)) await driver.findElement(By.xpath(`//div[contains(text(), 'Test User Name 11')]`))
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
}) })
}) })

@ -3,16 +3,9 @@ const webdriver = require('selenium-webdriver')
const { By } = webdriver const { By } = webdriver
const { const {
checkBrowserForConsoleErrors,
delay,
findElement,
findElements,
openNewPage,
switchToWindowWithTitle,
verboseReportOnFailure,
waitUntilXWindowHandles,
setupFetchMocking,
prepareExtensionForTesting, prepareExtensionForTesting,
regularDelayMs,
largeDelayMs,
} = require('./helpers') } = require('./helpers')
const enLocaleMessages = require('../../app/_locales/en/messages.json') const enLocaleMessages = require('../../app/_locales/en/messages.json')
@ -20,14 +13,12 @@ describe('Using MetaMask with an existing account', function () {
let driver let driver
const testSeedPhrase = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress' const testSeedPhrase = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress'
const regularDelayMs = 1000
const largeDelayMs = regularDelayMs * 2
const button = async (x) => { const button = async (x) => {
const buttoncheck = x const buttoncheck = x
await buttoncheck.click() await buttoncheck.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
const [results] = await findElements(driver, By.css('#results')) const [results] = await driver.findElements(By.css('#results'))
const resulttext = await results.getText() const resulttext = await results.getText()
const parsedData = JSON.parse(resulttext) const parsedData = JSON.parse(resulttext)
@ -41,12 +32,11 @@ describe('Using MetaMask with an existing account', function () {
before(async function () { before(async function () {
const result = await prepareExtensionForTesting() const result = await prepareExtensionForTesting()
driver = result.driver driver = result.driver
await setupFetchMocking(driver)
}) })
afterEach(async function () { afterEach(async function () {
if (process.env.SELENIUM_BROWSER === 'chrome') { if (process.env.SELENIUM_BROWSER === 'chrome') {
const errors = await checkBrowserForConsoleErrors(driver) const errors = await driver.checkBrowserForConsoleErrors(driver)
if (errors.length) { if (errors.length) {
const errorReports = errors.map(err => err.message) const errorReports = errors.map(err => err.message)
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
@ -54,7 +44,7 @@ describe('Using MetaMask with an existing account', function () {
} }
} }
if (this.currentTest.state === 'failed') { if (this.currentTest.state === 'failed') {
await verboseReportOnFailure(driver, this.currentTest) await driver.verboseReportOnFailure(driver, this.currentTest)
} }
}) })
@ -64,47 +54,47 @@ describe('Using MetaMask with an existing account', function () {
describe('First time flow starting from an existing seed phrase', () => { describe('First time flow starting from an existing seed phrase', () => {
it('clicks the continue button on the welcome screen', async () => { it('clicks the continue button on the welcome screen', async () => {
await findElement(driver, By.css('.welcome-page__header')) await driver.findElement(By.css('.welcome-page__header'))
const welcomeScreenBtn = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) const welcomeScreenBtn = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
welcomeScreenBtn.click() welcomeScreenBtn.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('clicks the "Import Wallet" option', async () => { it('clicks the "Import Wallet" option', async () => {
const customRpcButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Import Wallet')]`)) const customRpcButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Import Wallet')]`))
customRpcButton.click() customRpcButton.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('clicks the "No thanks" option on the metametrics opt-in screen', async () => { it('clicks the "No thanks" option on the metametrics opt-in screen', async () => {
const optOutButton = await findElement(driver, By.css('.btn-default')) const optOutButton = await driver.findElement(By.css('.btn-default'))
optOutButton.click() optOutButton.click()
await delay(largeDelayMs) await driver.delay(largeDelayMs)
}) })
it('imports a seed phrase', async () => { it('imports a seed phrase', async () => {
const [seedTextArea] = await findElements(driver, By.css('textarea.first-time-flow__textarea')) const [seedTextArea] = await driver.findElements(By.css('textarea.first-time-flow__textarea'))
await seedTextArea.sendKeys(testSeedPhrase) await seedTextArea.sendKeys(testSeedPhrase)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const [password] = await findElements(driver, By.id('password')) const [password] = await driver.findElements(By.id('password'))
await password.sendKeys('correct horse battery staple') await password.sendKeys('correct horse battery staple')
const [confirmPassword] = await findElements(driver, By.id('confirm-password')) const [confirmPassword] = await driver.findElements(By.id('confirm-password'))
confirmPassword.sendKeys('correct horse battery staple') confirmPassword.sendKeys('correct horse battery staple')
const tosCheckBox = await findElement(driver, By.css('.first-time-flow__checkbox')) const tosCheckBox = await driver.findElement(By.css('.first-time-flow__checkbox'))
await tosCheckBox.click() await tosCheckBox.click()
const [importButton] = await findElements(driver, By.xpath(`//button[contains(text(), 'Import')]`)) const [importButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Import')]`))
await importButton.click() await importButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
it('clicks through the success screen', async () => { it('clicks through the success screen', async () => {
await findElement(driver, By.xpath(`//div[contains(text(), 'Congratulations')]`)) await driver.findElement(By.xpath(`//div[contains(text(), 'Congratulations')]`))
const doneButton = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`)) const doneButton = await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`))
await doneButton.click() await doneButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
}) })
@ -112,37 +102,37 @@ describe('Using MetaMask with an existing account', function () {
describe('opens dapp', () => { describe('opens dapp', () => {
it('switches to mainnet', async () => { it('switches to mainnet', async () => {
const networkDropdown = await findElement(driver, By.css('.network-name')) const networkDropdown = await driver.findElement(By.css('.network-name'))
await networkDropdown.click() await networkDropdown.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const [mainnet] = await findElements(driver, By.xpath(`//span[contains(text(), 'Main Ethereum Network')]`)) const [mainnet] = await driver.findElements(By.xpath(`//span[contains(text(), 'Main Ethereum Network')]`))
await mainnet.click() await mainnet.click()
await delay(largeDelayMs * 2) await driver.delay(largeDelayMs * 2)
}) })
it('connects to dapp', async () => { it('connects to dapp', async () => {
await openNewPage(driver, 'http://127.0.0.1:8080/') await driver.openNewPage('http://127.0.0.1:8080/')
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) const connectButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Connect')]`))
await connectButton.click() await connectButton.click()
await delay(regularDelayMs) await driver.delay(regularDelayMs)
await waitUntilXWindowHandles(driver, 3) await driver.waitUntilXWindowHandles(3)
const windowHandles = await driver.getAllWindowHandles() const windowHandles = await driver.getAllWindowHandles()
const extension = windowHandles[0] const extension = windowHandles[0]
const popup = await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles) const popup = await driver.switchToWindowWithTitle('MetaMask Notification', windowHandles)
const dapp = windowHandles.find(handle => handle !== extension && handle !== popup) const dapp = windowHandles.find(handle => handle !== extension && handle !== popup)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
const approveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) const approveButton = await driver.findElement(By.xpath(`//button[contains(text(), 'Connect')]`))
await approveButton.click() await approveButton.click()
await driver.switchTo().window(dapp) await driver.switchToWindow(dapp)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
}) })
}) })
@ -162,7 +152,7 @@ describe('Using MetaMask with an existing account', function () {
const result = parseInt(parsedData.result, 16) const result = parseInt(parsedData.result, 16)
assert.equal((typeof result === 'number'), true) assert.equal((typeof result === 'number'), true)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
} catch (err) { } catch (err) {
console.log(err) console.log(err)
assert(false) assert(false)
@ -183,7 +173,7 @@ describe('Using MetaMask with an existing account', function () {
const result = parsedData.result const result = parsedData.result
assert.equal(result, false) assert.equal(result, false)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
} catch (err) { } catch (err) {
console.log(err) console.log(err)
assert(false) assert(false)
@ -251,7 +241,7 @@ describe('Using MetaMask with an existing account', function () {
const result = parseInt(parsedData.result.parentHash, 16) const result = parseInt(parsedData.result.parentHash, 16)
assert.equal((typeof result === 'number'), true) assert.equal((typeof result === 'number'), true)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
} catch (err) { } catch (err) {
console.log(err) console.log(err)
@ -279,7 +269,7 @@ describe('Using MetaMask with an existing account', function () {
result = parseInt(parsedData.result.blockHash, 16) result = parseInt(parsedData.result.blockHash, 16)
assert.equal((typeof result === 'number' || (result === 0)), true) assert.equal((typeof result === 'number' || (result === 0)), true)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
} else { } else {
parsedData = await button(List[i]) parsedData = await button(List[i])
console.log(parsedData.result) console.log(parsedData.result)
@ -287,7 +277,7 @@ describe('Using MetaMask with an existing account', function () {
result = parseInt(parsedData.result, 16) result = parseInt(parsedData.result, 16)
assert.equal((typeof result === 'number' || (result === 0)), true) assert.equal((typeof result === 'number' || (result === 0)), true)
await delay(regularDelayMs) await driver.delay(regularDelayMs)
} }

@ -0,0 +1,153 @@
const { promises: fs } = require('fs')
const { until, error: webdriverError } = require('selenium-webdriver')
const { strict: assert } = require('assert')
class Driver {
/**
* @param {!ThenableWebDriver} driver - A {@code WebDriver} instance
* @param {string} browser - The type of browser this driver is controlling
* @param {number} timeout
*/
constructor (driver, browser, timeout = 10000) {
this.driver = driver
this.browser = browser
this.timeout = timeout
}
async delay (time) {
await new Promise(resolve => setTimeout(resolve, time))
}
async wait (condition, timeout = this.timeout) {
await this.driver.wait(condition, timeout)
}
async quit () {
await this.driver.quit()
}
// Element interactions
async findElement (locator) {
return await this.driver.wait(until.elementLocated(locator), this.timeout)
}
async findVisibleElement (locator) {
const element = await this.findElement(locator)
await this.driver.wait(until.elementIsVisible(element), this.timeout)
return element
}
findElements (locator) {
return this.driver.wait(until.elementsLocated(locator), this.timeout)
}
async clickElement (locator) {
const element = await this.findElement(locator)
await element.click()
}
async scrollToElement (element) {
await this.driver.executeScript('arguments[0].scrollIntoView(true)', element)
}
async assertElementNotPresent (locator) {
let dataTab
try {
dataTab = await this.findElement(locator)
} catch (err) {
assert(err instanceof webdriverError.NoSuchElementError || err instanceof webdriverError.TimeoutError)
}
assert.ok(!dataTab, 'Found element that should not be present')
}
// Window management
async openNewPage (url) {
const newHandle = await this.driver.switchTo().newWindow()
await this.driver.get(url)
return newHandle
}
async switchToWindow (handle) {
await this.driver.switchTo().window(handle)
}
async getAllWindowHandles () {
return await this.driver.getAllWindowHandles()
}
async waitUntilXWindowHandles (x, delayStep = 1000, timeout = 5000) {
let timeElapsed = 0
while (timeElapsed <= timeout) {
const windowHandles = await this.driver.getAllWindowHandles()
if (windowHandles.length === x) {
return
}
await this.delay(delayStep)
timeElapsed += delayStep
}
throw new Error('waitUntilXWindowHandles timed out polling window handles')
}
async switchToWindowWithTitle (title, windowHandles) {
if (!windowHandles) {
windowHandles = await this.driver.getAllWindowHandles()
}
for (const handle of windowHandles) {
await this.driver.switchTo().window(handle)
const handleTitle = await this.driver.getTitle()
if (handleTitle === title) {
return handle
}
}
throw new Error('No window with title: ' + title)
}
/**
* Closes all windows except those in the given list of exceptions
* @param {Array<string>} exceptions the list of window handle exceptions
* @param {Array?} windowHandles the full list of window handles
* @returns {Promise<void>}
*/
async closeAllWindowHandlesExcept (exceptions, windowHandles) {
windowHandles = windowHandles || await this.driver.getAllWindowHandles()
for (const handle of windowHandles) {
if (!exceptions.includes(handle)) {
await this.driver.switchTo().window(handle)
await this.delay(1000)
await this.driver.close()
await this.delay(1000)
}
}
}
// Error handling
async verboseReportOnFailure (test) {
const artifactDir = `./test-artifacts/${this.browser}/${test.title}`
const filepathBase = `${artifactDir}/test-failure`
await fs.mkdir(artifactDir, { recursive: true })
const screenshot = await this.driver.takeScreenshot()
await fs.writeFile(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' })
const htmlSource = await this.driver.getPageSource()
await fs.writeFile(`${filepathBase}-dom.html`, htmlSource)
}
async checkBrowserForConsoleErrors () {
const ignoredLogTypes = ['WARNING']
const ignoredErrorMessages = [
// Third-party Favicon 404s show up as errors
'favicon.ico - Failed to load resource: the server responded with a status of 404 (Not Found)',
]
const browserLogs = await this.driver.manage().logs().get('browser')
const errorEntries = browserLogs.filter(entry => !ignoredLogTypes.includes(entry.level.toString()))
const errorObjects = errorEntries.map(entry => entry.toJSON())
return errorObjects.filter(entry => !ignoredErrorMessages.some(message => entry.message.includes(message)))
}
}
module.exports = Driver

@ -1,23 +1,29 @@
const { Browser } = require('selenium-webdriver') const { Browser } = require('selenium-webdriver')
const Driver = require('./driver')
const ChromeDriver = require('./chrome') const ChromeDriver = require('./chrome')
const FirefoxDriver = require('./firefox') const FirefoxDriver = require('./firefox')
const fetchMockResponses = require('../../data/fetch-mocks.json')
const buildWebDriver = async function buildWebDriver ({ browser, extensionPath, responsive, port }) { async function buildWebDriver ({ browser, extensionPath, responsive, port }) {
switch (browser) { switch (browser) {
case Browser.CHROME: { case Browser.CHROME: {
const { driver, extensionId, extensionUrl } = await ChromeDriver.build({ extensionPath, responsive, port }) const { driver, extensionId, extensionUrl } = await ChromeDriver.build({ extensionPath, responsive, port })
setupFetchMocking(driver)
await driver.get(extensionUrl)
return { return {
driver, driver: new Driver(driver, browser),
extensionId, extensionId,
extensionUrl, extensionUrl,
} }
} }
case Browser.FIREFOX: { case Browser.FIREFOX: {
const { driver, extensionId, extensionUrl } = await FirefoxDriver.build({ extensionPath, responsive, port }) const { driver, extensionId, extensionUrl } = await FirefoxDriver.build({ extensionPath, responsive, port })
setupFetchMocking(driver)
await driver.get(extensionUrl)
return { return {
driver, driver: new Driver(driver, browser),
extensionId, extensionId,
extensionUrl, extensionUrl,
} }
@ -28,6 +34,38 @@ const buildWebDriver = async function buildWebDriver ({ browser, extensionPath,
} }
} }
async function setupFetchMocking (driver) {
// define fetchMocking script, to be evaluated in the browser
function fetchMocking (fetchMockResponses) {
window.origFetch = window.fetch.bind(window)
window.fetch = async (...args) => {
const url = args[0]
if (url === 'https://ethgasstation.info/json/ethgasAPI.json') {
return { json: async () => clone(fetchMockResponses.ethGasBasic) }
} else if (url === 'https://ethgasstation.info/json/predictTable.json') {
return { json: async () => clone(fetchMockResponses.ethGasPredictTable) }
} else if (url.match(/chromeextensionmm/)) {
return { json: async () => clone(fetchMockResponses.metametrics) }
}
return window.origFetch(...args)
}
if (window.chrome && window.chrome.webRequest) {
window.chrome.webRequest.onBeforeRequest.addListener(cancelInfuraRequest, { urls: ['https://*.infura.io/*'] }, ['blocking'])
}
function cancelInfuraRequest (requestDetails) {
console.log(`fetchMocking - Canceling request: "${requestDetails.url}"`)
return { cancel: true }
}
function clone (obj) {
return JSON.parse(JSON.stringify(obj))
}
}
// fetchMockResponses are parsed last minute to ensure that objects are uniquely instantiated
const fetchMockResponsesJson = JSON.stringify(fetchMockResponses)
// eval the fetchMocking script in the browser
await driver.executeScript(`(${fetchMocking})(${fetchMockResponsesJson})`)
}
module.exports = { module.exports = {
buildWebDriver, buildWebDriver,
} }

@ -3,7 +3,7 @@ const {
timeout, timeout,
queryAsync, queryAsync,
} = require('../../lib/util') } = require('../../lib/util')
const fetchMockResponses = require('../../e2e/fetch-mocks.json') const fetchMockResponses = require('../../data/fetch-mocks.json')
QUnit.module('confirm sig requests') QUnit.module('confirm sig requests')

@ -4,7 +4,7 @@ const {
queryAsync, queryAsync,
findAsync, findAsync,
} = require('../../lib/util') } = require('../../lib/util')
const fetchMockResponses = require('../../e2e/fetch-mocks.json') const fetchMockResponses = require('../../data/fetch-mocks.json')
QUnit.module('currency localization') QUnit.module('currency localization')

@ -3,7 +3,7 @@ const {
queryAsync, queryAsync,
findAsync, findAsync,
} = require('../../lib/util') } = require('../../lib/util')
const fetchMockResponses = require('../../e2e/fetch-mocks.json') const fetchMockResponses = require('../../data/fetch-mocks.json')
QUnit.module('tx list items') QUnit.module('tx list items')

Loading…
Cancel
Save