feature/default_network_editable
commit
701611e317
@ -0,0 +1,14 @@ |
|||||||
|
# MetaMask Philosophy |
||||||
|
|
||||||
|
## Mission |
||||||
|
|
||||||
|
Making it safe and easy for the most people to use the decentralized web to the greatest degree that is empowering to them. |
||||||
|
|
||||||
|
## Vision |
||||||
|
|
||||||
|
To realize the highest goals achievable for the human race with the twin powers of peer to peer networks and cryptography. To empower users to hold and use their own keys on these new networks as securely and intelligibly as possible, enabling a new world of peer to peer agreements and economies, in hopes that we may collectively overcome the many great problems that we face together, through the power of strong cooperation. |
||||||
|
|
||||||
|
## Strategy |
||||||
|
|
||||||
|
We provide software for users to manage accounts, for sites to easily propose actions to users, and for users to coherently review actions before approving them. We build on this rapidly evolving set of protocols with the goal of empowering the most people to the greatest degree, and aspire to continuously evolve our offering to pursue that goal. |
||||||
|
|
After Width: | Height: | Size: 1.0 KiB |
@ -0,0 +1,24 @@ |
|||||||
|
/** |
||||||
|
* Returns error without stack trace for better UI display |
||||||
|
* @param {Error} err - error |
||||||
|
* @returns {Error} Error with clean stack trace. |
||||||
|
*/ |
||||||
|
function cleanErrorStack(err){ |
||||||
|
var name = err.name |
||||||
|
name = (name === undefined) ? 'Error' : String(name) |
||||||
|
|
||||||
|
var msg = err.message |
||||||
|
msg = (msg === undefined) ? '' : String(msg) |
||||||
|
|
||||||
|
if (name === '') { |
||||||
|
err.stack = err.message |
||||||
|
} else if (msg === '') { |
||||||
|
err.stack = err.name |
||||||
|
} else { |
||||||
|
err.stack = err.name + ': ' + err.message |
||||||
|
} |
||||||
|
|
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = cleanErrorStack |
@ -0,0 +1,406 @@ |
|||||||
|
const path = require('path') |
||||||
|
const assert = require('assert') |
||||||
|
const webdriver = require('selenium-webdriver') |
||||||
|
const { By, Key } = webdriver |
||||||
|
const { |
||||||
|
delay, |
||||||
|
buildChromeWebDriver, |
||||||
|
buildFirefoxWebdriver, |
||||||
|
installWebExt, |
||||||
|
getExtensionIdChrome, |
||||||
|
getExtensionIdFirefox, |
||||||
|
} = require('../func') |
||||||
|
const { |
||||||
|
checkBrowserForConsoleErrors, |
||||||
|
loadExtension, |
||||||
|
verboseReportOnFailure, |
||||||
|
} = require('./helpers') |
||||||
|
|
||||||
|
describe('Using MetaMask with an existing account', function () { |
||||||
|
let extensionId |
||||||
|
let driver |
||||||
|
let tokenAddress |
||||||
|
|
||||||
|
const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent' |
||||||
|
const testAddress = '0xE18035BF8712672935FDB4e5e431b1a0183d2DFC' |
||||||
|
const regularDelayMs = 1000 |
||||||
|
const largeDelayMs = regularDelayMs * 2 |
||||||
|
const waitingNewPageDelayMs = regularDelayMs * 10 |
||||||
|
|
||||||
|
this.timeout(0) |
||||||
|
this.bail(true) |
||||||
|
|
||||||
|
before(async function () { |
||||||
|
switch (process.env.SELENIUM_BROWSER) { |
||||||
|
case 'chrome': { |
||||||
|
const extensionPath = path.resolve('dist/chrome') |
||||||
|
driver = buildChromeWebDriver(extensionPath) |
||||||
|
extensionId = await getExtensionIdChrome(driver) |
||||||
|
await driver.get(`chrome-extension://${extensionId}/popup.html`) |
||||||
|
await delay(regularDelayMs) |
||||||
|
break |
||||||
|
} |
||||||
|
case 'firefox': { |
||||||
|
const extensionPath = path.resolve('dist/firefox') |
||||||
|
driver = buildFirefoxWebdriver() |
||||||
|
await installWebExt(driver, extensionPath) |
||||||
|
await delay(regularDelayMs) |
||||||
|
extensionId = await getExtensionIdFirefox(driver) |
||||||
|
await driver.get(`moz-extension://${extensionId}/popup.html`) |
||||||
|
await delay(regularDelayMs) |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
afterEach(async function () { |
||||||
|
if (process.env.SELENIUM_BROWSER === 'chrome') { |
||||||
|
const errors = await checkBrowserForConsoleErrors(driver) |
||||||
|
if (errors.length) { |
||||||
|
const errorReports = errors.map(err => err.message) |
||||||
|
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` |
||||||
|
console.error(new Error(errorMessage)) |
||||||
|
} |
||||||
|
} |
||||||
|
if (this.currentTest.state === 'failed') { |
||||||
|
await verboseReportOnFailure(driver, this.currentTest) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
after(async function () { |
||||||
|
await driver.quit() |
||||||
|
}) |
||||||
|
|
||||||
|
describe('New UI setup', async function () { |
||||||
|
it('switches to first tab', async function () { |
||||||
|
const [firstTab] = await driver.getAllWindowHandles() |
||||||
|
await driver.switchTo().window(firstTab) |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('use the local network', async function () { |
||||||
|
const [networkSelector] = await driver.findElements(By.css('#network_component')) |
||||||
|
await networkSelector.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [localhost] = await driver.findElements(By.xpath(`//li[contains(text(), 'Localhost')]`)) |
||||||
|
await localhost.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('selects the new UI option', async () => { |
||||||
|
const button = await driver.findElement(By.xpath("//p[contains(text(), 'Try Beta Version')]")) |
||||||
|
await button.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
// Close all other tabs
|
||||||
|
const [oldUi, infoPage, newUi] = await driver.getAllWindowHandles() |
||||||
|
await driver.switchTo().window(oldUi) |
||||||
|
await driver.close() |
||||||
|
await driver.switchTo().window(infoPage) |
||||||
|
await driver.close() |
||||||
|
await driver.switchTo().window(newUi) |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [continueBtn] = await driver.findElements(By.css('.welcome-screen__button')) |
||||||
|
await continueBtn.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('First time flow starting from an existing seed phrase', () => { |
||||||
|
it('imports a seed phrase', async () => { |
||||||
|
const [seedPhrase] = await driver.findElements(By.xpath(`//a[contains(text(), 'Import with seed phrase')]`)) |
||||||
|
await seedPhrase.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [seedTextArea] = await driver.findElements(By.css('textarea.import-account__secret-phrase')) |
||||||
|
await seedTextArea.sendKeys(testSeedPhrase) |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [password] = await driver.findElements(By.id('password')) |
||||||
|
await password.sendKeys('correct horse battery staple') |
||||||
|
const [confirmPassword] = await driver.findElements(By.id('confirm-password')) |
||||||
|
confirmPassword.sendKeys('correct horse battery staple') |
||||||
|
|
||||||
|
const [importButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Import')]`)) |
||||||
|
await importButton.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('clicks through the privacy notice', async () => { |
||||||
|
const [nextScreen] = await driver.findElements(By.css('.tou button')) |
||||||
|
await nextScreen.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled() |
||||||
|
assert.equal(canClickThrough, false, 'disabled continue button') |
||||||
|
const element = await driver.findElement(By.linkText('Attributions')) |
||||||
|
await driver.executeScript('arguments[0].scrollIntoView(true)', element) |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [acceptTos] = await driver.findElements(By.css('.tou button')) |
||||||
|
await acceptTos.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Show account information', () => { |
||||||
|
it('shows the correct account address', async () => { |
||||||
|
await driver.findElement(By.css('.wallet-view__details-button')).click() |
||||||
|
await driver.findElement(By.css('.qr-wrapper')).isDisplayed() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [address] = await driver.findElements(By.css('input.qr-ellip-address')) |
||||||
|
assert.equal(await address.getAttribute('value'), testAddress) |
||||||
|
|
||||||
|
await driver.executeScript("document.querySelector('.account-modal-close').click()") |
||||||
|
await delay(largeDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('shows a QR code for the account', async () => { |
||||||
|
await driver.findElement(By.css('.wallet-view__details-button')).click() |
||||||
|
await driver.findElement(By.css('.qr-wrapper')).isDisplayed() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
await driver.executeScript("document.querySelector('.account-modal-close').click()") |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Log out and log back in', () => { |
||||||
|
it('logs out of the account', async () => { |
||||||
|
await driver.findElement(By.css('.account-menu__icon')).click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [logoutButton] = await driver.findElements(By.css('.account-menu__logout-button')) |
||||||
|
assert.equal(await logoutButton.getText(), 'Log out') |
||||||
|
await logoutButton.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('accepts the account password after lock', async () => { |
||||||
|
await driver.findElement(By.id('password')).sendKeys('correct horse battery staple') |
||||||
|
await driver.findElement(By.id('password')).sendKeys(Key.ENTER) |
||||||
|
await delay(largeDelayMs) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Add an account', () => { |
||||||
|
it('choose Create Account from the account menu', async () => { |
||||||
|
await driver.findElement(By.css('.account-menu__icon')).click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [createAccount] = await driver.findElements(By.xpath(`//div[contains(text(), 'Create Account')]`)) |
||||||
|
await createAccount.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('set account name', async () => { |
||||||
|
const [accountName] = await driver.findElements(By.css('.new-account-create-form input')) |
||||||
|
await accountName.sendKeys('2nd account') |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [createButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Create')]`)) |
||||||
|
await createButton.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should show the correct account name', async () => { |
||||||
|
const [accountName] = await driver.findElements(By.css('.account-name')) |
||||||
|
assert.equal(await accountName.getText(), '2nd account') |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Switch back to original account', () => { |
||||||
|
it('chooses the original account from the account menu', async () => { |
||||||
|
await driver.findElement(By.css('.account-menu__icon')).click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [originalAccountMenuItem] = await driver.findElements(By.css('.account-menu__name')) |
||||||
|
await originalAccountMenuItem.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Send ETH from inside MetaMask', () => { |
||||||
|
it('starts to send a transaction', async function () { |
||||||
|
const [sendButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Send')]`)) |
||||||
|
await sendButton.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [inputAddress] = await driver.findElements(By.css('input[placeholder="Recipient Address"]')) |
||||||
|
const [inputAmount] = await driver.findElements(By.css('.currency-display__input')) |
||||||
|
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') |
||||||
|
await inputAmount.sendKeys('1') |
||||||
|
|
||||||
|
// Set the gas limit
|
||||||
|
const [configureGas] = await driver.findElements(By.css('.send-v2__gas-fee-display button')) |
||||||
|
await configureGas.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [save] = await driver.findElements(By.xpath(`//button[contains(text(), 'Save')]`)) |
||||||
|
await save.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
// Continue to next screen
|
||||||
|
const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`)) |
||||||
|
await nextScreen.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('confirms the transaction', async function () { |
||||||
|
const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`)) |
||||||
|
await confirmButton.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('finds the transaction in the transactions list', async function () { |
||||||
|
const transactions = await driver.findElements(By.css('.tx-list-item')) |
||||||
|
assert.equal(transactions.length, 1) |
||||||
|
|
||||||
|
const txValues = await driver.findElements(By.css('.tx-list-value')) |
||||||
|
assert.equal(txValues.length, 1) |
||||||
|
assert.equal(await txValues[0].getText(), '1 ETH') |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Send ETH from Faucet', () => { |
||||||
|
it('starts a send transaction inside Faucet', async () => { |
||||||
|
await driver.executeScript('window.open("https://faucet.metamask.io")') |
||||||
|
await delay(waitingNewPageDelayMs) |
||||||
|
|
||||||
|
const [extension, faucet] = await driver.getAllWindowHandles() |
||||||
|
await driver.switchTo().window(faucet) |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [send1eth] = await driver.findElements(By.xpath(`//button[contains(text(), '10 ether')]`)) |
||||||
|
await send1eth.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
await driver.switchTo().window(extension) |
||||||
|
await loadExtension(driver, extensionId) |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(),'Confirm')]`)) |
||||||
|
await confirmButton.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
await driver.switchTo().window(faucet) |
||||||
|
await delay(regularDelayMs) |
||||||
|
await driver.close() |
||||||
|
await delay(regularDelayMs) |
||||||
|
await driver.switchTo().window(extension) |
||||||
|
await delay(regularDelayMs) |
||||||
|
await loadExtension(driver, extensionId) |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Add existing token using search', () => { |
||||||
|
it('clicks on the Add Token button', async () => { |
||||||
|
const [addToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Token')]`)) |
||||||
|
await addToken.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('picks an existing token', async () => { |
||||||
|
const [tokenSearch] = await driver.findElements(By.css('input.add-token__input')) |
||||||
|
await tokenSearch.sendKeys('BAT') |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [token] = await driver.findElements(By.xpath("//div[contains(text(), 'BAT')]")) |
||||||
|
await token.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`)) |
||||||
|
await nextScreen.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [addTokens] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Tokens')]`)) |
||||||
|
await addTokens.click() |
||||||
|
await delay(largeDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('renders the balance for the new token', async () => { |
||||||
|
const balance = await driver.findElement(By.css('.tx-view .balance-display .token-amount')) |
||||||
|
const tokenAmount = await balance.getText() |
||||||
|
assert.equal(tokenAmount, '0BAT') |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Add a custom token from TokenFactory', () => { |
||||||
|
it('creates a new token', async () => { |
||||||
|
await driver.executeScript('window.open("https://tokenfactory.surge.sh/#/factory")') |
||||||
|
await delay(waitingNewPageDelayMs) |
||||||
|
|
||||||
|
const [extension, tokenFactory] = await driver.getAllWindowHandles() |
||||||
|
await driver.switchTo().window(tokenFactory) |
||||||
|
const [ |
||||||
|
totalSupply, |
||||||
|
tokenName, |
||||||
|
tokenDecimal, |
||||||
|
tokenSymbol, |
||||||
|
] = await driver.findElements(By.css('input')) |
||||||
|
|
||||||
|
await totalSupply.sendKeys('100') |
||||||
|
await tokenName.sendKeys('Test') |
||||||
|
await tokenDecimal.sendKeys('0') |
||||||
|
await tokenSymbol.sendKeys('TST') |
||||||
|
|
||||||
|
const [createToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Create Token')]`)) |
||||||
|
await createToken.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
await driver.switchTo().window(extension) |
||||||
|
await loadExtension(driver, extensionId) |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(),'Confirm')]`)) |
||||||
|
await confirmButton.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
await driver.switchTo().window(tokenFactory) |
||||||
|
await delay(regularDelayMs) |
||||||
|
const tokenContactAddress = await driver.findElement(By.css('div > div > div:nth-child(2) > span:nth-child(3)')) |
||||||
|
tokenAddress = await tokenContactAddress.getText() |
||||||
|
await driver.close() |
||||||
|
await driver.switchTo().window(extension) |
||||||
|
await loadExtension(driver, extensionId) |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('clicks on the Add Token button', async () => { |
||||||
|
const [addToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Token')]`)) |
||||||
|
await addToken.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('picks the new Test token', async () => { |
||||||
|
const [addCustomToken] = await driver.findElements(By.xpath("//div[contains(text(), 'Custom Token')]")) |
||||||
|
await addCustomToken.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [newTokenAddress] = await driver.findElements(By.css('.add-token__add-custom-form input')) |
||||||
|
await newTokenAddress.sendKeys(tokenAddress) |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`)) |
||||||
|
await nextScreen.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [addTokens] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Tokens')]`)) |
||||||
|
await addTokens.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('renders the balance for the new token', async () => { |
||||||
|
const [balance] = await driver.findElements(By.css('.tx-view .balance-display .token-amount')) |
||||||
|
const tokenAmount = await balance.getText() |
||||||
|
assert.equal(tokenAmount, '100TST') |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
@ -0,0 +1,55 @@ |
|||||||
|
const fs = require('fs') |
||||||
|
const mkdirp = require('mkdirp') |
||||||
|
const pify = require('pify') |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
checkBrowserForConsoleErrors, |
||||||
|
loadExtension, |
||||||
|
verboseReportOnFailure, |
||||||
|
} |
||||||
|
|
||||||
|
async function loadExtension (driver, extensionId) { |
||||||
|
switch (process.env.SELENIUM_BROWSER) { |
||||||
|
case 'chrome': { |
||||||
|
await driver.get(`chrome-extension://${extensionId}/home.html`) |
||||||
|
break |
||||||
|
} |
||||||
|
case 'firefox': { |
||||||
|
await driver.get(`moz-extension://${extensionId}/home.html`) |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async function checkBrowserForConsoleErrors (driver) { |
||||||
|
const ignoredLogTypes = ['WARNING'] |
||||||
|
const ignoredErrorMessages = [ |
||||||
|
// React throws error warnings on "dataset", but still sets the data-* properties correctly
|
||||||
|
'Warning: Unknown prop `dataset` on ', |
||||||
|
// Third-party Favicon 404s show up as errors
|
||||||
|
'favicon.ico - Failed to load resource: the server responded with a status of 404 (Not Found)', |
||||||
|
// React Development build - known issue blocked by test build sys
|
||||||
|
'Warning: It looks like you\'re using a minified copy of the development build of React.', |
||||||
|
// Redux Development build - known issue blocked by test build sys
|
||||||
|
'This means that you are running a slower development build of Redux.', |
||||||
|
] |
||||||
|
const browserLogs = await driver.manage().logs().get('browser') |
||||||
|
const errorEntries = browserLogs.filter(entry => !ignoredLogTypes.includes(entry.level.toString())) |
||||||
|
const errorObjects = errorEntries.map(entry => entry.toJSON()) |
||||||
|
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) |
||||||
|
} |
@ -0,0 +1,491 @@ |
|||||||
|
const path = require('path') |
||||||
|
const assert = require('assert') |
||||||
|
const webdriver = require('selenium-webdriver') |
||||||
|
const { By, Key } = webdriver |
||||||
|
const { |
||||||
|
delay, |
||||||
|
buildChromeWebDriver, |
||||||
|
buildFirefoxWebdriver, |
||||||
|
installWebExt, |
||||||
|
getExtensionIdChrome, |
||||||
|
getExtensionIdFirefox, |
||||||
|
} = require('../func') |
||||||
|
const { |
||||||
|
checkBrowserForConsoleErrors, |
||||||
|
loadExtension, |
||||||
|
verboseReportOnFailure, |
||||||
|
} = require('./helpers') |
||||||
|
|
||||||
|
describe('MetaMask', function () { |
||||||
|
let extensionId |
||||||
|
let driver |
||||||
|
let tokenAddress |
||||||
|
|
||||||
|
const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent' |
||||||
|
const tinyDelayMs = 500 |
||||||
|
const regularDelayMs = tinyDelayMs * 2 |
||||||
|
const largeDelayMs = regularDelayMs * 2 |
||||||
|
const waitingNewPageDelayMs = regularDelayMs * 10 |
||||||
|
|
||||||
|
this.timeout(0) |
||||||
|
this.bail(true) |
||||||
|
|
||||||
|
before(async function () { |
||||||
|
switch (process.env.SELENIUM_BROWSER) { |
||||||
|
case 'chrome': { |
||||||
|
const extPath = path.resolve('dist/chrome') |
||||||
|
driver = buildChromeWebDriver(extPath) |
||||||
|
extensionId = await getExtensionIdChrome(driver) |
||||||
|
await driver.get(`chrome-extension://${extensionId}/popup.html`) |
||||||
|
break |
||||||
|
} |
||||||
|
case 'firefox': { |
||||||
|
const extPath = path.resolve('dist/firefox') |
||||||
|
driver = buildFirefoxWebdriver() |
||||||
|
await installWebExt(driver, extPath) |
||||||
|
await delay(700) |
||||||
|
extensionId = await getExtensionIdFirefox(driver) |
||||||
|
await driver.get(`moz-extension://${extensionId}/popup.html`) |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
afterEach(async function () { |
||||||
|
if (process.env.SELENIUM_BROWSER === 'chrome') { |
||||||
|
const errors = await checkBrowserForConsoleErrors(driver) |
||||||
|
if (errors.length) { |
||||||
|
const errorReports = errors.map(err => err.message) |
||||||
|
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` |
||||||
|
console.error(new Error(errorMessage)) |
||||||
|
} |
||||||
|
} |
||||||
|
if (this.currentTest.state === 'failed') { |
||||||
|
await verboseReportOnFailure(this.currentTest) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
after(async function () { |
||||||
|
await driver.quit() |
||||||
|
}) |
||||||
|
|
||||||
|
describe('New UI setup', async function () { |
||||||
|
it('switches to first tab', async function () { |
||||||
|
const [firstTab] = await driver.getAllWindowHandles() |
||||||
|
await driver.switchTo().window(firstTab) |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('use the local network', async function () { |
||||||
|
const [networkSelector] = await driver.findElements(By.css('#network_component')) |
||||||
|
await networkSelector.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [localhost] = await driver.findElements(By.xpath(`//li[contains(text(), 'Localhost')]`)) |
||||||
|
await localhost.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('selects the new UI option', async () => { |
||||||
|
const button = await driver.findElement(By.xpath("//p[contains(text(), 'Try Beta Version')]")) |
||||||
|
await button.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
// Close all other tabs
|
||||||
|
const [oldUi, infoPage, newUi] = await driver.getAllWindowHandles() |
||||||
|
await driver.switchTo().window(oldUi) |
||||||
|
await driver.close() |
||||||
|
await driver.switchTo().window(infoPage) |
||||||
|
await driver.close() |
||||||
|
await driver.switchTo().window(newUi) |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [continueBtn] = await driver.findElements(By.css('.welcome-screen__button')) |
||||||
|
await continueBtn.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Going through the first time flow', () => { |
||||||
|
it('accepts a secure password', async () => { |
||||||
|
const [passwordBox] = await driver.findElements(By.css('.create-password #create-password')) |
||||||
|
const [passwordBoxConfirm] = await driver.findElements(By.css('.create-password #confirm-password')) |
||||||
|
const [button] = await driver.findElements(By.css('.create-password button')) |
||||||
|
|
||||||
|
await passwordBox.sendKeys('correct horse battery staple') |
||||||
|
await passwordBoxConfirm.sendKeys('correct horse battery staple') |
||||||
|
await button.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('clicks through the unique image screen', async () => { |
||||||
|
const [nextScreen] = await driver.findElements(By.css('.unique-image button')) |
||||||
|
await nextScreen.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('clicks through the privacy notice', async () => { |
||||||
|
const [nextScreen] = await driver.findElements(By.css('.tou button')) |
||||||
|
await nextScreen.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled() |
||||||
|
assert.equal(canClickThrough, false, 'disabled continue button') |
||||||
|
const [bottomOfTos] = await driver.findElements(By.linkText('Attributions')) |
||||||
|
await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos) |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [acceptTos] = await driver.findElements(By.css('.tou button')) |
||||||
|
await acceptTos.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
let seedPhrase |
||||||
|
|
||||||
|
it('reveals the seed phrase', async () => { |
||||||
|
const [revealSeedPhrase] = await driver.findElements(By.css('.backup-phrase__secret-blocker')) |
||||||
|
await revealSeedPhrase.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
seedPhrase = await driver.findElement(By.css('.backup-phrase__secret-words')).getText() |
||||||
|
assert.equal(seedPhrase.split(' ').length, 12) |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [nextScreen] = await driver.findElements(By.css('.backup-phrase button')) |
||||||
|
await nextScreen.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('can retype the seed phrase', async () => { |
||||||
|
const words = seedPhrase.split(' ') |
||||||
|
|
||||||
|
const [word0] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[0]}')]`)) |
||||||
|
await word0.click() |
||||||
|
await delay(tinyDelayMs) |
||||||
|
|
||||||
|
const [word1] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[1]}')]`)) |
||||||
|
await word1.click() |
||||||
|
await delay(tinyDelayMs) |
||||||
|
|
||||||
|
const [word2] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[2]}')]`)) |
||||||
|
await word2.click() |
||||||
|
await delay(tinyDelayMs) |
||||||
|
|
||||||
|
const [word3] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[3]}')]`)) |
||||||
|
await word3.click() |
||||||
|
await delay(tinyDelayMs) |
||||||
|
|
||||||
|
const [word4] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[4]}')]`)) |
||||||
|
await word4.click() |
||||||
|
await delay(tinyDelayMs) |
||||||
|
|
||||||
|
const [word5] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[5]}')]`)) |
||||||
|
await word5.click() |
||||||
|
await delay(tinyDelayMs) |
||||||
|
|
||||||
|
const [word6] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[6]}')]`)) |
||||||
|
await word6.click() |
||||||
|
await delay(tinyDelayMs) |
||||||
|
|
||||||
|
const [word7] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[7]}')]`)) |
||||||
|
await word7.click() |
||||||
|
await delay(tinyDelayMs) |
||||||
|
|
||||||
|
const [word8] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[8]}')]`)) |
||||||
|
await word8.click() |
||||||
|
await delay(tinyDelayMs) |
||||||
|
|
||||||
|
const [word9] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[9]}')]`)) |
||||||
|
await word9.click() |
||||||
|
await delay(tinyDelayMs) |
||||||
|
|
||||||
|
const [word10] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[10]}')]`)) |
||||||
|
await word10.click() |
||||||
|
await delay(tinyDelayMs) |
||||||
|
|
||||||
|
const [word11] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[11]}')]`)) |
||||||
|
await word11.click() |
||||||
|
await delay(tinyDelayMs) |
||||||
|
|
||||||
|
const [confirm] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`)) |
||||||
|
await confirm.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('clicks through the deposit modal', async () => { |
||||||
|
const [closeModal] = await driver.findElements(By.css('.page-container__header-close')) |
||||||
|
await closeModal.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Show account information', () => { |
||||||
|
it('shows the QR code for the account', async () => { |
||||||
|
await driver.findElement(By.css('.wallet-view__details-button')).click() |
||||||
|
await driver.findElement(By.css('.qr-wrapper')).isDisplayed() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
await driver.executeScript("document.querySelector('.account-modal-close').click()") |
||||||
|
await delay(regularDelayMs * 4) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Log out an log back in', () => { |
||||||
|
it('logs out of the account', async () => { |
||||||
|
await driver.findElement(By.css('.account-menu__icon')).click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [logoutButton] = await driver.findElements(By.css('.account-menu__logout-button')) |
||||||
|
assert.equal(await logoutButton.getText(), 'Log out') |
||||||
|
await logoutButton.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('accepts the account password after lock', async () => { |
||||||
|
await driver.findElement(By.id('password')).sendKeys('correct horse battery staple') |
||||||
|
await driver.findElement(By.id('password')).sendKeys(Key.ENTER) |
||||||
|
await delay(regularDelayMs * 4) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Add account', () => { |
||||||
|
it('choose Create Account from the account menu', async () => { |
||||||
|
await driver.findElement(By.css('.account-menu__icon')).click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [createAccount] = await driver.findElements(By.xpath(`//div[contains(text(), 'Create Account')]`)) |
||||||
|
await createAccount.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('set account name', async () => { |
||||||
|
const [accountName] = await driver.findElements(By.css('.new-account-create-form input')) |
||||||
|
await accountName.sendKeys('2nd account') |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [create] = await driver.findElements(By.xpath(`//button[contains(text(), 'Create')]`)) |
||||||
|
await create.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should correct account name', async () => { |
||||||
|
const [accountName] = await driver.findElements(By.css('.account-name')) |
||||||
|
assert.equal(await accountName.getText(), '2nd account') |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Import seed phrase', () => { |
||||||
|
it('logs out of the vault', async () => { |
||||||
|
await driver.findElement(By.css('.account-menu__icon')).click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [logoutButton] = await driver.findElements(By.css('.account-menu__logout-button')) |
||||||
|
assert.equal(await logoutButton.getText(), 'Log out') |
||||||
|
await logoutButton.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('imports seed phrase', async () => { |
||||||
|
const [restoreSeedLink] = await driver.findElements(By.css('.unlock-page__link--import')) |
||||||
|
assert.equal(await restoreSeedLink.getText(), 'Import using account seed phrase') |
||||||
|
await restoreSeedLink.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [seedTextArea] = await driver.findElements(By.css('textarea')) |
||||||
|
await seedTextArea.sendKeys(testSeedPhrase) |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
await driver.findElement(By.id('password-box')).sendKeys('correct horse battery staple') |
||||||
|
await driver.findElement(By.id('password-box-confirm')).sendKeys('correct horse battery staple') |
||||||
|
await driver.findElement(By.css('button:nth-child(2)')).click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('balance renders', async () => { |
||||||
|
const balance = await driver.findElement(By.css('.balance-display .token-amount')) |
||||||
|
const tokenAmount = await balance.getText() |
||||||
|
assert.equal(tokenAmount, '100.000 ETH') |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Send ETH from inside MetaMask', () => { |
||||||
|
it('starts to send a transaction', async function () { |
||||||
|
const [sendButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Send')]`)) |
||||||
|
await sendButton.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [inputAddress] = await driver.findElements(By.css('input[placeholder="Recipient Address"]')) |
||||||
|
const [inputAmount] = await driver.findElements(By.css('.currency-display__input')) |
||||||
|
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') |
||||||
|
await inputAmount.sendKeys('1') |
||||||
|
|
||||||
|
// Set the gas limit
|
||||||
|
const [configureGas] = await driver.findElements(By.css('.send-v2__gas-fee-display button')) |
||||||
|
await configureGas.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [save] = await driver.findElements(By.xpath(`//button[contains(text(), 'Save')]`)) |
||||||
|
await save.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
// Continue to next screen
|
||||||
|
const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`)) |
||||||
|
await nextScreen.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('confirms the transaction', async function () { |
||||||
|
const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`)) |
||||||
|
await confirmButton.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('finds the transaction in the transactions list', async function () { |
||||||
|
const transactions = await driver.findElements(By.css('.tx-list-item')) |
||||||
|
assert.equal(transactions.length, 1) |
||||||
|
|
||||||
|
const txValues = await driver.findElements(By.css('.tx-list-value')) |
||||||
|
assert.equal(txValues.length, 1) |
||||||
|
assert.equal(await txValues[0].getText(), '1 ETH') |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Send ETH from Faucet', () => { |
||||||
|
it('starts a send transaction inside Faucet', async () => { |
||||||
|
await driver.executeScript('window.open("https://faucet.metamask.io")') |
||||||
|
await delay(waitingNewPageDelayMs) |
||||||
|
|
||||||
|
const [extension, faucet] = await driver.getAllWindowHandles() |
||||||
|
await driver.switchTo().window(faucet) |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [send1eth] = await driver.findElements(By.xpath(`//button[contains(text(), '10 ether')]`)) |
||||||
|
await send1eth.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
await driver.switchTo().window(extension) |
||||||
|
await loadExtension(driver, extensionId) |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`)) |
||||||
|
await confirmButton.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
await driver.switchTo().window(faucet) |
||||||
|
await delay(regularDelayMs) |
||||||
|
await driver.close() |
||||||
|
await delay(regularDelayMs) |
||||||
|
await driver.switchTo().window(extension) |
||||||
|
await delay(regularDelayMs) |
||||||
|
await loadExtension(driver, extensionId) |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Add existing token using search', () => { |
||||||
|
it('clicks on the Add Token button', async () => { |
||||||
|
const [addToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Token')]`)) |
||||||
|
await addToken.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('can pick a token from the existing options', async () => { |
||||||
|
const [tokenSearch] = await driver.findElements(By.css('input.add-token__input')) |
||||||
|
await tokenSearch.sendKeys('BAT') |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [token] = await driver.findElements(By.xpath("//div[contains(text(), 'BAT')]")) |
||||||
|
await token.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`)) |
||||||
|
await nextScreen.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [addTokens] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Tokens')]`)) |
||||||
|
await addTokens.click() |
||||||
|
await delay(largeDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('renders the balance for the chosen token', async () => { |
||||||
|
const balance = await driver.findElement(By.css('.tx-view .balance-display .token-amount')) |
||||||
|
const tokenAmount = await balance.getText() |
||||||
|
assert.equal(tokenAmount, '0BAT') |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Add a custom token from TokenFactory', () => { |
||||||
|
it('creates a new token', async () => { |
||||||
|
await driver.executeScript('window.open("https://tokenfactory.surge.sh/#/factory")') |
||||||
|
await delay(waitingNewPageDelayMs) |
||||||
|
|
||||||
|
const [extension, tokenFactory] = await driver.getAllWindowHandles() |
||||||
|
await driver.switchTo().window(tokenFactory) |
||||||
|
const [ |
||||||
|
totalSupply, |
||||||
|
tokenName, |
||||||
|
tokenDecimal, |
||||||
|
tokenSymbol, |
||||||
|
] = await driver.findElements(By.css('input')) |
||||||
|
|
||||||
|
await totalSupply.sendKeys('100') |
||||||
|
await tokenName.sendKeys('Test') |
||||||
|
await tokenDecimal.sendKeys('0') |
||||||
|
await tokenSymbol.sendKeys('TST') |
||||||
|
|
||||||
|
const [createToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Create Token')]`)) |
||||||
|
await createToken.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
await driver.switchTo().window(extension) |
||||||
|
await loadExtension(driver, extensionId) |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`)) |
||||||
|
await confirmButton.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
await driver.switchTo().window(tokenFactory) |
||||||
|
await delay(regularDelayMs) |
||||||
|
const tokenContactAddress = await driver.findElement(By.css('div > div > div:nth-child(2) > span:nth-child(3)')) |
||||||
|
tokenAddress = await tokenContactAddress.getText() |
||||||
|
await driver.close() |
||||||
|
await driver.switchTo().window(extension) |
||||||
|
await loadExtension(driver, extensionId) |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('clicks on the Add Token button', async () => { |
||||||
|
const [addToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Token')]`)) |
||||||
|
await addToken.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('picks the newly created Test token', async () => { |
||||||
|
const [addCustomToken] = await driver.findElements(By.xpath("//div[contains(text(), 'Custom Token')]")) |
||||||
|
await addCustomToken.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [newTokenAddress] = await driver.findElements(By.css('.add-token__add-custom-form input')) |
||||||
|
await newTokenAddress.sendKeys(tokenAddress) |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`)) |
||||||
|
await nextScreen.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
|
||||||
|
const [addTokens] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Tokens')]`)) |
||||||
|
await addTokens.click() |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
|
||||||
|
it('renders the balance for the new token', async () => { |
||||||
|
const [balance] = await driver.findElements(By.css('.tx-view .balance-display .token-amount')) |
||||||
|
const tokenAmount = await balance.getText() |
||||||
|
assert.equal(tokenAmount, '100TST') |
||||||
|
await delay(regularDelayMs) |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
@ -0,0 +1,10 @@ |
|||||||
|
#!/usr/bin/env bash |
||||||
|
|
||||||
|
set -e |
||||||
|
set -u |
||||||
|
set -o pipefail |
||||||
|
|
||||||
|
export PATH="$PATH:./node_modules/.bin" |
||||||
|
|
||||||
|
shell-parallel -s 'npm run ganache:start' -x 'sleep 5 && mocha test/e2e/beta/metamask-beta-ui.spec' |
||||||
|
shell-parallel -s 'npm run ganache:start' -x 'sleep 5 && mocha test/e2e/beta/from-import-beta-ui.spec' |
@ -1,113 +0,0 @@ |
|||||||
const Component = require('react').Component |
|
||||||
const h = require('react-hyperscript') |
|
||||||
const inherits = require('util').inherits |
|
||||||
|
|
||||||
module.exports = CurrencyInput |
|
||||||
|
|
||||||
inherits(CurrencyInput, Component) |
|
||||||
function CurrencyInput (props) { |
|
||||||
Component.call(this) |
|
||||||
|
|
||||||
const sanitizedValue = sanitizeValue(props.value) |
|
||||||
|
|
||||||
this.state = { |
|
||||||
value: sanitizedValue, |
|
||||||
emptyState: false, |
|
||||||
focused: false, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function removeNonDigits (str) { |
|
||||||
return str.match(/\d|$/g).join('') |
|
||||||
} |
|
||||||
|
|
||||||
// Removes characters that are not digits, then removes leading zeros
|
|
||||||
function sanitizeInteger (val) { |
|
||||||
return String(parseInt(removeNonDigits(val) || '0', 10)) |
|
||||||
} |
|
||||||
|
|
||||||
function sanitizeDecimal (val) { |
|
||||||
return removeNonDigits(val) |
|
||||||
} |
|
||||||
|
|
||||||
// Take a single string param and returns a non-negative integer or float as a string.
|
|
||||||
// Breaks the input into three parts: the integer, the decimal point, and the decimal/fractional part.
|
|
||||||
// Removes leading zeros from the integer, and non-digits from the integer and decimal
|
|
||||||
// The integer is returned as '0' in cases where it would be empty. A decimal point is
|
|
||||||
// included in the returned string if one is included in the param
|
|
||||||
// Examples:
|
|
||||||
// sanitizeValue('0') -> '0'
|
|
||||||
// sanitizeValue('a') -> '0'
|
|
||||||
// sanitizeValue('010.') -> '10.'
|
|
||||||
// sanitizeValue('0.005') -> '0.005'
|
|
||||||
// sanitizeValue('22.200') -> '22.200'
|
|
||||||
// sanitizeValue('.200') -> '0.200'
|
|
||||||
// sanitizeValue('a.b.1.c,89.123') -> '0.189123'
|
|
||||||
function sanitizeValue (value) { |
|
||||||
let [ , integer, point, decimal] = (/([^.]*)([.]?)([^.]*)/).exec(value) |
|
||||||
|
|
||||||
integer = sanitizeInteger(integer) || '0' |
|
||||||
decimal = sanitizeDecimal(decimal) |
|
||||||
|
|
||||||
return `${integer}${point}${decimal}` |
|
||||||
} |
|
||||||
|
|
||||||
CurrencyInput.prototype.handleChange = function (newValue) { |
|
||||||
const { onInputChange } = this.props |
|
||||||
const { value } = this.state |
|
||||||
|
|
||||||
let parsedValue = newValue |
|
||||||
const newValueLastIndex = newValue.length - 1 |
|
||||||
|
|
||||||
if (value === '0' && newValue[newValueLastIndex] === '0') { |
|
||||||
parsedValue = parsedValue.slice(0, newValueLastIndex) |
|
||||||
} |
|
||||||
const sanitizedValue = sanitizeValue(parsedValue) |
|
||||||
this.setState({ |
|
||||||
value: sanitizedValue, |
|
||||||
emptyState: newValue === '' && sanitizedValue === '0', |
|
||||||
}) |
|
||||||
onInputChange(sanitizedValue) |
|
||||||
} |
|
||||||
|
|
||||||
// If state.value === props.value plus a decimal point, or at least one
|
|
||||||
// zero or a decimal point and at least one zero, then this returns state.value
|
|
||||||
// after it is sanitized with getValueParts
|
|
||||||
CurrencyInput.prototype.getValueToRender = function () { |
|
||||||
const { value } = this.props |
|
||||||
const { value: stateValue } = this.state |
|
||||||
|
|
||||||
const trailingStateString = (new RegExp(`^${value}(.+)`)).exec(stateValue) |
|
||||||
const trailingDecimalAndZeroes = trailingStateString && (/^[.0]0*/).test(trailingStateString[1]) |
|
||||||
|
|
||||||
return sanitizeValue(trailingDecimalAndZeroes |
|
||||||
? stateValue |
|
||||||
: value) |
|
||||||
} |
|
||||||
|
|
||||||
CurrencyInput.prototype.render = function () { |
|
||||||
const { |
|
||||||
className, |
|
||||||
placeholder, |
|
||||||
readOnly, |
|
||||||
inputRef, |
|
||||||
type, |
|
||||||
} = this.props |
|
||||||
const { emptyState, focused } = this.state |
|
||||||
|
|
||||||
const inputSizeMultiplier = readOnly ? 1 : 1.2 |
|
||||||
|
|
||||||
const valueToRender = this.getValueToRender() |
|
||||||
return h('input', { |
|
||||||
className, |
|
||||||
type, |
|
||||||
value: emptyState ? '' : valueToRender, |
|
||||||
placeholder: focused ? '' : placeholder, |
|
||||||
size: valueToRender.length * inputSizeMultiplier, |
|
||||||
readOnly, |
|
||||||
onFocus: () => this.setState({ focused: true, emptyState: valueToRender === '0' }), |
|
||||||
onBlur: () => this.setState({ focused: false, emptyState: false }), |
|
||||||
onChange: e => this.handleChange(e.target.value), |
|
||||||
ref: inputRef, |
|
||||||
}) |
|
||||||
} |
|
@ -0,0 +1,54 @@ |
|||||||
|
import React, { Component } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import Button from '../../button' |
||||||
|
|
||||||
|
class ConfirmResetAccount extends Component { |
||||||
|
static propTypes = { |
||||||
|
hideModal: PropTypes.func.isRequired, |
||||||
|
resetAccount: PropTypes.func.isRequired, |
||||||
|
} |
||||||
|
|
||||||
|
static contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
handleReset () { |
||||||
|
this.props.resetAccount() |
||||||
|
.then(() => this.props.hideModal()) |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { t } = this.context |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="modal-container"> |
||||||
|
<div className="modal-container__content"> |
||||||
|
<div className="modal-container__title"> |
||||||
|
{ `${t('resetAccount')}?` } |
||||||
|
</div> |
||||||
|
<div className="modal-container__description"> |
||||||
|
{ t('resetAccountDescription') } |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="modal-container__footer"> |
||||||
|
<Button |
||||||
|
type="default" |
||||||
|
className="modal-container__footer-button" |
||||||
|
onClick={() => this.props.hideModal()} |
||||||
|
> |
||||||
|
{ t('nevermind') } |
||||||
|
</Button> |
||||||
|
<Button |
||||||
|
type="secondary" |
||||||
|
className="modal-container__footer-button" |
||||||
|
onClick={() => this.handleReset()} |
||||||
|
> |
||||||
|
{ t('reset') } |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default ConfirmResetAccount |
@ -0,0 +1,13 @@ |
|||||||
|
import { connect } from 'react-redux' |
||||||
|
import ConfirmResetAccount from './confirm-reset-account.component' |
||||||
|
|
||||||
|
const { hideModal, resetAccount } = require('../../../actions') |
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => { |
||||||
|
return { |
||||||
|
hideModal: () => dispatch(hideModal()), |
||||||
|
resetAccount: () => dispatch(resetAccount()), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps)(ConfirmResetAccount) |
@ -0,0 +1,2 @@ |
|||||||
|
import ConfirmResetAccount from './confirm-reset-account.container' |
||||||
|
module.exports = ConfirmResetAccount |
@ -0,0 +1,52 @@ |
|||||||
|
.modal-container { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
background-color: #fff; |
||||||
|
display: flex; |
||||||
|
flex-flow: column; |
||||||
|
border-radius: 8px; |
||||||
|
|
||||||
|
&__title { |
||||||
|
font-size: 1.5rem; |
||||||
|
font-weight: 500; |
||||||
|
padding: 16px 0; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
&__description { |
||||||
|
text-align: center; |
||||||
|
font-size: .875rem; |
||||||
|
} |
||||||
|
|
||||||
|
&__content { |
||||||
|
overflow-y: auto; |
||||||
|
flex: 1; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
align-items: center; |
||||||
|
padding: 32px; |
||||||
|
|
||||||
|
@media screen and (max-width: 575px) { |
||||||
|
justify-content: center; |
||||||
|
padding: 28px 20px; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&__footer { |
||||||
|
display: flex; |
||||||
|
flex-flow: row; |
||||||
|
justify-content: center; |
||||||
|
border-top: 1px solid #d2d8dd; |
||||||
|
padding: 16px; |
||||||
|
flex: 0 0 auto; |
||||||
|
|
||||||
|
&-button { |
||||||
|
min-width: 0; |
||||||
|
margin-right: 16px; |
||||||
|
|
||||||
|
&:last-of-type { |
||||||
|
margin-right: 0; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,46 +0,0 @@ |
|||||||
const { Component } = require('react') |
|
||||||
const PropTypes = require('prop-types') |
|
||||||
const h = require('react-hyperscript') |
|
||||||
const connect = require('react-redux').connect |
|
||||||
const actions = require('../../../actions') |
|
||||||
const NotifcationModal = require('../notification-modal') |
|
||||||
|
|
||||||
class ConfirmResetAccount extends Component { |
|
||||||
render () { |
|
||||||
const { resetAccount } = this.props |
|
||||||
|
|
||||||
return h(NotifcationModal, { |
|
||||||
header: 'Are you sure you want to reset account?', |
|
||||||
message: h('div', [ |
|
||||||
|
|
||||||
h('span', `Resetting is for developer use only. This button wipes the current account's transaction history,
|
|
||||||
which is used to calculate the current account nonce. `),
|
|
||||||
|
|
||||||
h('a.notification-modal__link', { |
|
||||||
href: 'http://metamask.helpscoutdocs.com/article/36-resetting-an-account', |
|
||||||
target: '_blank', |
|
||||||
onClick (event) { global.platform.openWindow({ url: event.target.href }) }, |
|
||||||
}, 'Read more.'), |
|
||||||
|
|
||||||
]), |
|
||||||
showCancelButton: true, |
|
||||||
showConfirmButton: true, |
|
||||||
onConfirm: resetAccount, |
|
||||||
|
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
ConfirmResetAccount.propTypes = { |
|
||||||
resetAccount: PropTypes.func, |
|
||||||
} |
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => { |
|
||||||
return { |
|
||||||
resetAccount: () => { |
|
||||||
dispatch(actions.resetAccount()) |
|
||||||
}, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
module.exports = connect(null, mapDispatchToProps)(ConfirmResetAccount) |
|
@ -0,0 +1,2 @@ |
|||||||
|
import Notification from './notification.container' |
||||||
|
module.exports = Notification |
@ -0,0 +1,30 @@ |
|||||||
|
import React from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import Button from '../../button' |
||||||
|
|
||||||
|
const Notification = (props, context) => { |
||||||
|
return ( |
||||||
|
<div className="modal-container"> |
||||||
|
{ props.children } |
||||||
|
<div className="modal-container__footer"> |
||||||
|
<Button |
||||||
|
type="primary" |
||||||
|
onClick={() => props.onHide()} |
||||||
|
> |
||||||
|
{ context.t('ok') } |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
Notification.propTypes = { |
||||||
|
onHide: PropTypes.func.isRequired, |
||||||
|
children: PropTypes.element, |
||||||
|
} |
||||||
|
|
||||||
|
Notification.contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
export default Notification |
@ -0,0 +1,38 @@ |
|||||||
|
import { connect } from 'react-redux' |
||||||
|
import Notification from './notification.component' |
||||||
|
|
||||||
|
const { hideModal } = require('../../../actions') |
||||||
|
|
||||||
|
const mapStateToProps = state => { |
||||||
|
const { appState: { modal: { modalState: { props } } } } = state |
||||||
|
const { onHide } = props |
||||||
|
return { |
||||||
|
onHide, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => { |
||||||
|
return { |
||||||
|
hideModal: () => dispatch(hideModal()), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const mergeProps = (stateProps, dispatchProps, ownProps) => { |
||||||
|
const { onHide, ...otherStateProps } = stateProps |
||||||
|
const { hideModal, ...otherDispatchProps } = dispatchProps |
||||||
|
|
||||||
|
return { |
||||||
|
...otherStateProps, |
||||||
|
...otherDispatchProps, |
||||||
|
...ownProps, |
||||||
|
onHide: () => { |
||||||
|
hideModal() |
||||||
|
|
||||||
|
if (onHide && typeof onHide === 'function') { |
||||||
|
onHide() |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(Notification) |
@ -0,0 +1,2 @@ |
|||||||
|
import TransactionConfirmed from './transaction-confirmed.component' |
||||||
|
module.exports = TransactionConfirmed |
@ -0,0 +1,24 @@ |
|||||||
|
import React from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
|
||||||
|
const TransactionConfirmed = (props, context) => { |
||||||
|
const { t } = context |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="modal-container__content"> |
||||||
|
<img src="images/check-icon.svg" /> |
||||||
|
<div className="modal-container__title"> |
||||||
|
{ `${t('confirmed')}!` } |
||||||
|
</div> |
||||||
|
<div className="modal-container__description"> |
||||||
|
{ t('initialTransactionConfirmed') } |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
TransactionConfirmed.contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
export default TransactionConfirmed |
@ -0,0 +1,2 @@ |
|||||||
|
import WelcomeBeta from './welcome-beta.component' |
||||||
|
module.exports = WelcomeBeta |
@ -0,0 +1,23 @@ |
|||||||
|
import React from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
|
||||||
|
const TransactionConfirmed = (props, context) => { |
||||||
|
const { t } = context |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="modal-container__content"> |
||||||
|
<div className="modal-container__title"> |
||||||
|
{ `${t('uiWelcome')}` } |
||||||
|
</div> |
||||||
|
<div className="modal-container__description"> |
||||||
|
{ t('uiWelcomeMessage') } |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
TransactionConfirmed.contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
export default TransactionConfirmed |
Loading…
Reference in new issue