require('chromedriver') require('geckodriver') const Web3 = require('web3') const fs = require('fs-extra') const os = require('os') const path = require('path') const pify = require('pify') const prependFile = pify(require('prepend-file')) const webdriver = require('selenium-webdriver') const Command = require('selenium-webdriver/lib/command').Command const assert = require('assert') const { By, Key } = webdriver const { screens, elements, NETWORKS } = require('./elements') class Functions { constructor (driver) { this.driver = driver } async delay (time) { return new Promise(resolve => setTimeout(resolve, time)) } async createModifiedTestBuild ({ browser, srcPath }) { // copy build to test-builds directory const extPath = path.resolve(`test-builds/${browser}`) await fs.ensureDir(extPath) await fs.copy(srcPath, extPath) // inject METAMASK_TEST_CONFIG setting default test network const config = { NetworkController: { provider: { type: 'localhost' } } } await prependFile(`${extPath}/background.js`, `window.METAMASK_TEST_CONFIG=${JSON.stringify(config)};\n`) return { extPath } } static async setupBrowserAndExtension ({ browser, extPath }) { let driver, extensionId, extensionUri if (browser === 'chrome') { driver = this.buildChromeWebDriver(extPath) this.driver = driver extensionId = await this.getExtensionIdChrome() extensionUri = `chrome-extension://${extensionId}/home.html` } else if (browser === 'firefox') { driver = this.buildFirefoxWebdriver() this.driver = driver await this.installWebExt(extPath) await this.delay(700) extensionId = await this.getExtensionIdFirefox() extensionUri = `moz-extension://${extensionId}/home.html` } else { throw new Error(`Unknown Browser "${browser}"`) } return { driver, extensionId, extensionUri } } static async buildChromeWebDriver (extPath) { const tmpProfile = fs.mkdtempSync(path.join(os.tmpdir(), 'mm-chrome-profile')) return new webdriver.Builder() .withCapabilities({ chromeOptions: { args: [ `load-extension=${extPath}`, `user-data-dir=${tmpProfile}`, ], binary: process.env.SELENIUM_CHROME_BINARY, }, }) .build() } static async buildFirefoxWebdriver () { return new webdriver.Builder().build() } async getExtensionIdChrome () { await this.driver.get('chrome://extensions') const extensionId = await this.driver.executeScript('return document.querySelector("extensions-manager").shadowRoot.querySelector("cr-view-manager extensions-item-list").shadowRoot.querySelector("extensions-item:nth-child(2)").getAttribute("id")') return extensionId } async getExtensionIdFirefox () { await this.driver.get('about:debugging#addons') const extensionId = await this.driver.findElement(By.css('dd.addon-target-info-content:nth-child(6) > span:nth-child(1)')).getText() return extensionId } async installWebExt (extension) { const cmd = await new Command('moz-install-web-ext') .setParameter('path', path.resolve(extension)) .setParameter('temporary', true) await this.driver.getExecutor() .defineCommand(cmd.getName(), 'POST', '/session/:sessionId/moz/addon/install') return await this.driver.execute(cmd, 'installWebExt(' + extension + ')') } async verboseReportOnFailure ({ browser, title }) { const artifactDir = `./test-artifacts/${browser}/${title}` const filepathBase = `${artifactDir}/test-failure` await fs.ensureDir(artifactDir) 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 setProvider (network) { await this.delay(300) const menu = await this.waitUntilShowUp(screens.main.network) await menu.click() let counter switch (network) { case NETWORKS.POA: counter = 0 break case NETWORKS.DAI: counter = 1 break case NETWORKS.SOKOL: counter = 2 break case NETWORKS.MAINNET: counter = 3 break case NETWORKS.ROPSTEN: counter = 4 break case NETWORKS.KOVAN: counter = 5 break case NETWORKS.RINKEBY: counter = 6 break case NETWORKS.LOCALHOST: counter = 7 break case NETWORKS.CUSTOM: counter = 8 break default: counter = 7 } await this.driver.executeScript("document.getElementsByClassName('dropdown-menu-item')[" + counter + '].click();') } async scrollTo (element) { try { await this.driver.executeScript('arguments[0].scrollIntoView();', element) return true } catch (err) { return false } } async click (element) { try { await element.sendKeys(Key.RETURN) return true } catch (err) { return false } } async clearField (field, number) { await this.click(field) if (number === undefined) number = 40 for (let i = 0; i < number; i++) { await field.sendKeys(Key.BACK_SPACE) } } async waitUntilDisappear (by, Twait) { if (Twait === undefined) Twait = 10 do { if (!await this.isElementDisplayed(by)) return true } while (Twait-- > 0) return false } async waitUntilShowUp (by, Twait) { if (Twait === undefined) Twait = 200 do { await this.delay(100) if (await this.isElementDisplayed(by)) return await this.driver.findElement(by) } while (Twait-- > 0) return false } async waitUntilHasValue (element, Twait) { if (Twait === undefined) Twait = 200 let text do { await this.delay(100) text = await element.getAttribute('value') if (text !== '') return text } while (Twait-- > 0) return false } async isElementDisplayed (by) { try { return await this.driver.findElement(by).isDisplayed() } catch (err) { return false } } async assertTokensNotDisplayed () { try { await this.delay(800) await this.waitUntilDisappear(elements.loader) assert.notEqual(await this.waitUntilShowUp(screens.main.tokens.amount), false, 'App is frozen') // Check tokens title let locator = screens.main.tokens.counter if (process.env.SELENIUM_BROWSER === 'firefox') locator = screens.main.tokens.counterFF const tokensCounter = await this.waitUntilShowUp(locator) assert.notEqual(tokensCounter, false, '\'Token\'s counter isn\'t displayed ') assert.equal(await tokensCounter.getText(), screens.main.tokens.textNoTokens, 'Unexpected token presents') // Check if token presents const tokens = await this.driver.findElements(screens.main.tokens.token) assert.equal(tokens.length, 0, 'Unexpected token presents') return true } catch (err) { console.log(err) return false } } async isDisabledAddInexistentToken (tokenAddress) { await this.delay(500) try { const tab = await this.waitUntilShowUp(screens.main.tokens.menu) await this.click(tab) await this.delay(1000) const button = await this.waitUntilShowUp(screens.main.tokens.buttonAdd2, 300) await this.click(button) let count = 20 do { await this.delay(500) const tab = await this.waitUntilShowUp(screens.addToken.tab.custom, 10) try { await tab.click() } catch (err) { } } while ((await this.waitUntilShowUp(screens.addToken.custom.fields.contractAddress) === false) && (count-- > 0)) } catch (err) { } const fieldAddress = await this.waitUntilShowUp(screens.addToken.custom.fields.contractAddress) await this.clearField(fieldAddress) await fieldAddress.sendKeys(tokenAddress) const fieldSymbols = await this.waitUntilShowUp(screens.addToken.custom.fields.tokenSymbol) if (await fieldSymbols.isEnabled()) { console.log('field symbols enabled') return false } const fieldDecimals = await this.waitUntilShowUp(screens.addToken.custom.fields.tokenSymbol) if (await fieldDecimals.isEnabled()) { console.log('field decimals enabled') return false } const buttonAdd = await this.waitUntilShowUp(screens.addToken.custom.buttons.add) if (await buttonAdd.isEnabled()) { console.log('button add enabled') return false } const buttonCancel = await this.waitUntilShowUp(screens.addToken.custom.buttons.cancel) let counter = 20 do { await this.delay(500) await this.click(buttonCancel) } while (((await this.waitUntilShowUp(screens.main.identicon)) === false) && (counter-- > 0)) if (counter < 1) { console.log('button cancel doesn\'t work') return false } return true } async 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()) // ignore all errors that contain a message in `ignoredErrorMessages` const matchedErrorObjects = errorObjects.filter(entry => !ignoredErrorMessages.some(message => entry.message.includes(message))) return matchedErrorObjects } async switchToLastPage () { try { const allHandles = await this.driver.getAllWindowHandles() await this.driver.switchTo().window(allHandles[allHandles.length - 1]) let counter = 100 do { await this.delay(500) if (await this.driver.getCurrentUrl() !== '') return true } while (counter-- > 0) return true } catch (err) { return false } } async switchToFirstPage () { try { const allHandles = await this.driver.getAllWindowHandles() console.log('allHandles.length ' + allHandles.length) await this.driver.switchTo().window(allHandles[0]) let counter = 100 do { await this.delay(500) if (await this.driver.getCurrentUrl() !== '') return true } while (counter-- > 0) return true } catch (err) { return false } } async waitUntilCurrentUrl () { try { let title let counter = 20 do { await this.delay(500) title = await this.driver.getCurrentUrl() } while ((title === '') && (counter-- > 0)) if (counter < 1) return false return title } catch (err) { console.log(err) return false } } async createToken (owner, { supply, name, decimals, ticker }, isDelayed) { const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545/')) const abi = [ { 'constant': true, 'inputs': [], 'name': 'name', 'outputs': [ { 'name': '', 'type': 'string', }, ], 'payable': false, 'stateMutability': 'view', 'type': 'function', }, { 'constant': false, 'inputs': [ { 'name': '_spender', 'type': 'address', }, { 'name': '_value', 'type': 'uint256', }, ], 'name': 'approve', 'outputs': [ { 'name': 'success', 'type': 'bool', }, ], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function', }, { 'constant': true, 'inputs': [], 'name': 'totalSupply', 'outputs': [ { 'name': '', 'type': 'uint256', }, ], 'payable': false, 'stateMutability': 'view', 'type': 'function', }, { 'constant': false, 'inputs': [ { 'name': '_from', 'type': 'address', }, { 'name': '_to', 'type': 'address', }, { 'name': '_value', 'type': 'uint256', }, ], 'name': 'transferFrom', 'outputs': [ { 'name': 'success', 'type': 'bool', }, ], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function', }, { 'constant': true, 'inputs': [ { 'name': '', 'type': 'address', }, ], 'name': 'balances', 'outputs': [ { 'name': '', 'type': 'uint256', }, ], 'payable': false, 'stateMutability': 'view', 'type': 'function', }, { 'constant': true, 'inputs': [], 'name': 'decimals', 'outputs': [ { 'name': '', 'type': 'uint8', }, ], 'payable': false, 'stateMutability': 'view', 'type': 'function', }, { 'constant': true, 'inputs': [ { 'name': '', 'type': 'address', }, { 'name': '', 'type': 'address', }, ], 'name': 'allowed', 'outputs': [ { 'name': '', 'type': 'uint256', }, ], 'payable': false, 'stateMutability': 'view', 'type': 'function', }, { 'constant': true, 'inputs': [ { 'name': '_owner', 'type': 'address', }, ], 'name': 'balanceOf', 'outputs': [ { 'name': 'balance', 'type': 'uint256', }, ], 'payable': false, 'stateMutability': 'view', 'type': 'function', }, { 'constant': true, 'inputs': [], 'name': 'symbol', 'outputs': [ { 'name': '', 'type': 'string', }, ], 'payable': false, 'stateMutability': 'view', 'type': 'function', }, { 'constant': false, 'inputs': [ { 'name': '_to', 'type': 'address', }, { 'name': '_value', 'type': 'uint256', }, ], 'name': 'transfer', 'outputs': [ { 'name': 'success', 'type': 'bool', }, ], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function', }, { 'constant': true, 'inputs': [ { 'name': '_owner', 'type': 'address', }, { 'name': '_spender', 'type': 'address', }, ], 'name': 'allowance', 'outputs': [ { 'name': 'remaining', 'type': 'uint256', }, ], 'payable': false, 'stateMutability': 'view', 'type': 'function', }, { 'inputs': [ { 'name': '_initialAmount', 'type': 'uint256', }, { 'name': '_tokenName', 'type': 'string', }, { 'name': '_decimalUnits', 'type': 'uint8', }, { 'name': '_tokenSymbol', 'type': 'string', }, ], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor', }, { 'anonymous': false, 'inputs': [ { 'indexed': true, 'name': '_from', 'type': 'address', }, { 'indexed': true, 'name': '_to', 'type': 'address', }, { 'indexed': false, 'name': '_value', 'type': 'uint256', }, ], 'name': 'Transfer', 'type': 'event', }, { 'anonymous': false, 'inputs': [ { 'indexed': true, 'name': '_owner', 'type': 'address', }, { 'indexed': true, 'name': '_spender', 'type': 'address', }, { 'indexed': false, 'name': '_value', 'type': 'uint256', }, ], 'name': 'Approval', 'type': 'event', }, ] const bin = '608060405234801561001057600080fd5b50604051610e30380380610e308339810180604052810190808051906020019092919080518201929190602001805190602001909291908051820192919050505083600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508360008190555082600390805190602001906100b29291906100ee565b5081600460006101000a81548160ff021916908360ff16021790555080600590805190602001906100e49291906100ee565b5050505050610193565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061012f57805160ff191683800117855561015d565b8280016001018555821561015d579182015b8281111561015c578251825591602001919060010190610141565b5b50905061016a919061016e565b5090565b61019091905b8082111561018c576000816000905550600101610174565b5090565b90565b610c8e806101a26000396000f3006080604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b4578063095ea7b31461014457806318160ddd146101a957806323b872dd146101d457806327e235e314610259578063313ce567146102b05780635c658165146102e157806370a082311461035857806395d89b41146103af578063a9059cbb1461043f578063dd62ed3e146104a4575b600080fd5b3480156100c057600080fd5b506100c961051b565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101095780820151818401526020810190506100ee565b50505050905090810190601f1680156101365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561015057600080fd5b5061018f600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506105b9565b604051808215151515815260200191505060405180910390f35b3480156101b557600080fd5b506101be6106ab565b6040518082815260200191505060405180910390f35b3480156101e057600080fd5b5061023f600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506106b1565b604051808215151515815260200191505060405180910390f35b34801561026557600080fd5b5061029a600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061094b565b6040518082815260200191505060405180910390f35b3480156102bc57600080fd5b506102c5610963565b604051808260ff1660ff16815260200191505060405180910390f35b3480156102ed57600080fd5b50610342600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610976565b6040518082815260200191505060405180910390f35b34801561036457600080fd5b50610399600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061099b565b6040518082815260200191505060405180910390f35b3480156103bb57600080fd5b506103c46109e4565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156104045780820151818401526020810190506103e9565b50505050905090810190601f1680156104315780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561044b57600080fd5b5061048a600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610a82565b604051808215151515815260200191505060405180910390f35b3480156104b057600080fd5b50610505600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610bdb565b6040518082815260200191505060405180910390f35b60038054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105b15780601f10610586576101008083540402835291602001916105b1565b820191906000526020600020905b81548152906001019060200180831161059457829003601f168201915b505050505081565b600081600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60005481565b600080600260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101580156107825750828110155b151561078d57600080fd5b82600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254019250508190555082600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8110156108da5782600260008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b8373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef856040518082815260200191505060405180910390a360019150509392505050565b60016020528060005260406000206000915090505481565b600460009054906101000a900460ff1681565b6002602052816000526040600020602052806000526040600020600091509150505481565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60058054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610a7a5780601f10610a4f57610100808354040283529160200191610a7a565b820191906000526020600020905b815481529060010190602001808311610a5d57829003601f168201915b505050505081565b600081600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610ad257600080fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a36001905092915050565b6000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050929150505600a165627a7a72305820979c62ae45244f66d713b9272cd9a32a6b8c2ba4778ec9fb58a39dc893cb9cde0029' const tokenContract = web3.eth.contract(abi) const contractInstance = await tokenContract.new(supply, name, decimals, ticker, { data: bin, from: owner, gas: 4500000, function (err, tokenContract) { if (err) { console.log('Error of token creation: ' + err) } }, }) if (isDelayed) await this.delay(5000) return contractInstance.address } async executeTransferMethod (executor, address) { try { const buttonExecute = await this.waitUntilShowUp(screens.executeMethod.buttonExecuteMethod) assert.notEqual(buttonExecute, false, "button doesn't displayed") await buttonExecute.click() // Select method transfer const menu = await this.waitUntilShowUp(screens.executeMethod.selectArrow) await menu.click() await this.waitUntilShowUp(screens.executeMethod.items) const list = await this.driver.findElements(screens.executeMethod.items) await list[21].click() // Fill out value await this.waitUntilShowUp(screens.executeMethod.fieldParameter) const fields = await this.driver.findElements(screens.executeMethod.fieldParameter) assert.notEqual(fields[1], false, "field value isn't displayed") await fields[1].sendKeys('1') // Fill out address await this.clearField(fields[0], 100) await fields[0].sendKeys(address) assert.notEqual(fields[0], false, "field address isn't displayed") // Click button next const buttonNext = await this.waitUntilShowUp(screens.executeMethod.buttonNext) assert.notEqual(buttonNext, false, "button 'Next' isn't displayed") await buttonNext.click() // Select executor await this.waitUntilShowUp(screens.chooseContractExecutor.account) const accounts = await this.driver.findElements(screens.chooseContractExecutor.account) const account = accounts[executor + 1] await account.click() // Open confirm transaction const button = await this.waitUntilShowUp(screens.chooseContractExecutor.buttonNext) await button.click() return true } catch (err) { return false } } } module.exports.Functions = Functions