diff --git a/apps/remix-ide-e2e/src/commands/setupMetamask.ts b/apps/remix-ide-e2e/src/commands/setupMetamask.ts new file mode 100644 index 0000000000..1e322aa141 --- /dev/null +++ b/apps/remix-ide-e2e/src/commands/setupMetamask.ts @@ -0,0 +1,40 @@ +import { NightwatchBrowser } from "nightwatch" + +const EventEmitter = require('events') + +class MetaMask extends EventEmitter { + command (this: NightwatchBrowser, passphrase: string, password: string): NightwatchBrowser { + this.api.perform((done) => { + setupMetaMask(this.api, passphrase, password, () => { + done() + this.emit('complete') + }) + }) + return this + } +} + +function setupMetaMask (browser: NightwatchBrowser, passphrase: string, password: string, done: VoidFunction) { + browser + .switchBrowserWindow('chrome-extension://poemojpkcjbpmcccohjnomjffeinlafe/home.html#initialize/welcome', 'MetaMask', (browser) => { + browser.waitForElementPresent('.first-time-flow__button') + .click('.first-time-flow__button') + .waitForElementPresent('.select-action__select-button:nth-of-type(1) > .first-time-flow__button') + .click('.select-action__select-button:nth-of-type(1) > .first-time-flow__button') + .waitForElementPresent('.page-container__footer-button:nth-of-type(2)') + .click('.page-container__footer-button:nth-of-type(2)') + .waitForElementPresent('.first-time-flow__textarea') + .setValue('.first-time-flow__textarea', passphrase) + .setValue('*[autocomplete="new-password"]', password) + .setValue('*[autocomplete="confirm-password"]', password) + .click('.first-time-flow__checkbox') + .click('.first-time-flow__button') + .pause(5000) + .click('.first-time-flow__button') + .perform(() => { + done() + }) + }) +} + +module.exports = MetaMask diff --git a/apps/remix-ide-e2e/src/commands/switchBrowserWindow.ts b/apps/remix-ide-e2e/src/commands/switchBrowserWindow.ts new file mode 100644 index 0000000000..396e838a58 --- /dev/null +++ b/apps/remix-ide-e2e/src/commands/switchBrowserWindow.ts @@ -0,0 +1,28 @@ +import { NightwatchBrowser, NightwatchCallbackResult } from "nightwatch" + +const EventEmitter = require('events') + +class SwitchBrowserWindow extends EventEmitter { + command (this: NightwatchBrowser, url: string, windowName: string, cb: (browser: NightwatchBrowser, window?: NightwatchCallbackResult) => void): NightwatchBrowser { + this.api.perform((done) => { + switchWindow(this.api, url, windowName, cb) + done() + this.emit('complete') + }) + return this + } +} + +function switchWindow (browser: NightwatchBrowser, url: string, windowName: string, cb: (browser: NightwatchBrowser, window: NightwatchCallbackResult) => void) { + browser.execute(function (windowName) { + return window.open('', windowName, 'width=2560, height=1440') + }, [windowName], (newWindow) => { + browser.switchWindow(windowName) + .url(url) + .pause(5000) + .assert.urlContains(url) + if (cb) cb(browser, newWindow) + }) +} + +module.exports = SwitchBrowserWindow diff --git a/apps/remix-ide-e2e/src/tests/runAndDeploy.ts b/apps/remix-ide-e2e/src/tests/runAndDeploy.ts new file mode 100644 index 0000000000..cb1937314e --- /dev/null +++ b/apps/remix-ide-e2e/src/tests/runAndDeploy.ts @@ -0,0 +1,208 @@ +'use strict' +import { NightwatchBrowser } from "nightwatch" +import init from '../helpers/init' +import sauce from './sauce' + +const passphrase = process.env.account_passphrase +const password = process.env.account_password + +module.exports = { + + before: function (browser: NightwatchBrowser, done: VoidFunction) { + init(browser, done) + }, + + '@sources': function () { + return sources + }, + + 'Should load run and deploy tab': function (browser: NightwatchBrowser) { + browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]') + .clickLaunchIcon('udapp') + .waitForElementPresent('*[data-id="sidePanelSwapitTitle"]') + .assert.containsText('*[data-id="sidePanelSwapitTitle"]', 'DEPLOY & RUN TRANSACTIONS') + }, + + 'Should sign message using account key': function (browser: NightwatchBrowser) { + browser.waitForElementPresent('*[data-id="settingsRemixRunSignMsg"]') + .click('*[data-id="settingsRemixRunSignMsg"]') + .pause(2000) + .waitForElementPresent('*[data-id="modalDialogCustomPromptText"]') + .setValue('*[data-id="modalDialogCustomPromptText"]', 'Remix is cool!') + .assert.elementNotPresent('*[data-id="settingsRemixRunSignMsgHash"]') + .assert.elementNotPresent('*[data-id="settingsRemixRunSignMsgSignature"]') + .modalFooterOKClick() + .waitForElementPresent('*[data-id="modalDialogContainer"]', 12000) + .assert.elementPresent('*[data-id="settingsRemixRunSignMsgHash"]') + .assert.elementPresent('*[data-id="settingsRemixRunSignMsgSignature"]') + .modalFooterOKClick() + }, + + 'Should deploy contract on JavascriptVM': function (browser: NightwatchBrowser) { + browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]') + .clickLaunchIcon('fileExplorers') + .addFile('Greet.sol', sources[0]['browser/Greet.sol']) + .clickLaunchIcon('udapp') + .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') + .waitForElementPresent('*[data-id="Deploy - transact (not payable)"]') + .click('*[data-id="Deploy - transact (not payable)"]') + .pause(5000) + .testFunction('0xc39ee005c1e1368c84f02e458de4b41dbb966631a8714d15ef8362dada249ede', { + status: '0x1 Transaction mined and execution succeed', + 'transaction hash': '0xc39ee005c1e1368c84f02e458de4b41dbb966631a8714d15ef8362dada249ede' + }) + }, + + 'Should run low level interaction (fallback function)': function (browser: NightwatchBrowser) { + browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]') + .waitForElementPresent('*[data-id="universalDappUiTitleExpander"]') + .click('*[data-id="universalDappUiTitleExpander"]') + .waitForElementPresent('*[data-id="pluginManagerSettingsDeployAndRunLLTxSendTransaction"]') + .click('*[data-id="pluginManagerSettingsDeployAndRunLLTxSendTransaction"]') + .pause(5000) + .testFunction('0xfe718871ee0b4d03cdcac0e12e5b164efaf7e23ba952c07db76e62692867019b', { + status: '0x1 Transaction mined and execution succeed', + 'transaction hash': '0xfe718871ee0b4d03cdcac0e12e5b164efaf7e23ba952c07db76e62692867019b' + }) + }, + + 'Should connect to Goerli Test Network using MetaMask': function (browser: NightwatchBrowser) { + browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]') + .setupMetamask(passphrase, password) + .click('.network-indicator__down-arrow') + .useXpath().click("//span[text()='Goerli Test Network']") + .useCss().switchBrowserTab(0) + .refresh() + .waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) + .click('*[data-id="landingPageStartSolidity"]') + .pause(5000) + .clickLaunchIcon('udapp') + .waitForElementPresent('*[data-id="settingsSelectEnvOptions"]') + .click('*[data-id="settingsSelectEnvOptions"] option[id="injected-mode"]') + .waitForElementPresent('*[data-id="settingsNetworkEnv"]') + .assert.containsText('*[data-id="settingsNetworkEnv"]', 'Goerli (5) network') + .switchBrowserTab(2) + .waitForElementPresent('.page-container__footer-button:nth-of-type(2)') + .click('.page-container__footer-button:nth-of-type(2)') + .switchBrowserTab(0) + }, + + 'Should deploy contract on Goerli Test Network using MetaMask': function (browser: NightwatchBrowser) { + browser.waitForElementPresent('*[data-id="runTabSelectAccount"] option') + .clickLaunchIcon('fileExplorers') + .openFile('browser/Greet.sol') + .clickLaunchIcon('udapp') + .waitForElementPresent('*[data-id="Deploy - transact (not payable)"]') + .click('*[data-id="Deploy - transact (not payable)"]') + .switchBrowserTab(2) + .waitForElementPresent('.transaction-status--unapproved') + .click('.transaction-status--unapproved') + .waitForElementPresent('.page-container__footer-button:nth-of-type(2)') + .click('.page-container__footer-button:nth-of-type(2)') + .waitForElementPresent('.transaction-status--submitted') + .pause(25000) + .switchBrowserTab(0) + }, + + 'Should run low level interaction (fallback function) on Goerli Test Network using MetaMask': function (browser: NightwatchBrowser) { + browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]') + .waitForElementPresent('*[data-id="universalDappUiTitleExpander"]') + .click('*[data-id="universalDappUiTitleExpander"]') + .waitForElementPresent('*[data-id="pluginManagerSettingsDeployAndRunLLTxSendTransaction"]') + .click('*[data-id="pluginManagerSettingsDeployAndRunLLTxSendTransaction"]') + .switchBrowserTab(2) + .waitForElementPresent('.transaction-status--unapproved') + .click('.transaction-status--unapproved') + .waitForElementPresent('.page-container__footer-button:nth-of-type(2)') + .click('.page-container__footer-button:nth-of-type(2)') + .waitForElementPresent('.transaction-status--submitted') + .pause(25000) + .switchBrowserTab(0) + }, + + 'Should connect to Ethereum Main Network using MetaMask': function (browser: NightwatchBrowser) { + browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]') + .switchBrowserTab(2) + .waitForElementPresent('.network-indicator__down-arrow') + .click('.network-indicator__down-arrow') + .useXpath().click("//span[text()='Main Ethereum Network']") + .useCss().switchBrowserTab(0) + .refresh() + .waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) + .click('*[data-id="landingPageStartSolidity"]') + .pause(5000) + .clickLaunchIcon('udapp') + .waitForElementPresent('*[data-id="settingsSelectEnvOptions"]') + .click('*[data-id="settingsSelectEnvOptions"] option[id="injected-mode"]') + .waitForElementPresent('*[data-id="settingsNetworkEnv"]') + .assert.containsText('*[data-id="settingsNetworkEnv"]', 'Main (1) network') + }, + + 'Should deploy contract on Ethereum Main Network using MetaMask': function (browser: NightwatchBrowser) { + browser.waitForElementPresent('*[data-id="runTabSelectAccount"] option') + .clickLaunchIcon('fileExplorers') + .openFile('browser/Greet.sol') + .clickLaunchIcon('udapp') + .waitForElementPresent('*[data-id="Deploy - transact (not payable)"]') + .click('*[data-id="Deploy - transact (not payable)"]') + .waitForElementPresent('*[data-id="modalDialogContainer"]', 15000) + .pause(10000) + .assert.containsText('*[data-id="modalDialogModalBody"]', 'You are creating a transaction on the main network. Click confirm if you are sure to continue.') + .modalFooterCancelClick() + }, + + /* + * This test is using 3 differents services: + * - Metamask for getting the transaction + * - Source Verifier service for fetching the contract code + * - Ropsten node for retrieving the trace and storage + * + */ + 'Should debug Ropsten transaction with source highlighting using the source verifier service and MetaMask': function (browser: NightwatchBrowser) { + browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]') + .waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) + .switchBrowserTab(2) + .waitForElementPresent('.network-indicator__down-arrow') + .click('.network-indicator__down-arrow') + .useXpath().click("//span[text()='Ropsten Test Network']") // switch to Ropsten + .useCss().switchBrowserTab(0) + .refresh() + .clickLaunchIcon('pluginManager') // load debugger and source verification + // .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_source-verification"] button') + // debugger already activated .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_debugger"] button') + .clickLaunchIcon('udapp') + .waitForElementPresent('*[data-id="settingsSelectEnvOptions"]') + .click('*[data-id="settingsSelectEnvOptions"] option[id="injected-mode"]') // switch to Ropsten in udapp + .waitForElementPresent('*[data-id="settingsNetworkEnv"]') + .assert.containsText('*[data-id="settingsNetworkEnv"]', 'Ropsten (3) network') + .clickLaunchIcon('debugger') + .setValue('*[data-id="debuggerTransactionInput"]', '0x959371506b8f6223d71c709ac2eb2d0158104dca2d76ca949f1662712cf0e6db') // debug tx + .click('*[data-id="debuggerTransactionStartButton"]') + .waitForElementVisible('*[data-id="treeViewDivto"]', 30000) + .assert.containsText('*[data-id="stepdetail"]', 'loaded address:\n0x3c943Fb816694d7D1f4C738e3e7823818a88DD6C') + .assert.containsText('*[data-id="solidityLocals"]', 'to: 0x6C3CCC7FBA111707D5A1AAF2758E9D4F4AC5E7B1') + .end() + }, + tearDown: sauce +} + +const sources = [ + { + 'browser/Greet.sol': { + content: + ` + pragma solidity ^0.6.0; + contract helloWorld { + string public message; + + fallback () external { + message = 'Hello World!'; + } + + function greet(string memory _message) public { + message = _message; + } + }` + } + } +] diff --git a/apps/remix-ide-e2e/src/types/index.d.ts b/apps/remix-ide-e2e/src/types/index.d.ts index 7a69e4190d..08f1b8c450 100644 --- a/apps/remix-ide-e2e/src/types/index.d.ts +++ b/apps/remix-ide-e2e/src/types/index.d.ts @@ -41,7 +41,9 @@ declare module "nightwatch" { getInstalledPlugins(cb: (plugins: string[]) => void): NightwatchBrowser, verifyCallReturnValue(address: string, checks: string[]): NightwatchBrowser, testEditorValue(testvalue: string): NightwatchBrowser, - removeFile(path: string): NightwatchBrowser + removeFile(path: string): NightwatchBrowser, + switchBrowserWindow(url: string, windowName: string, cb: (browser: NightwatchBrowser, window?: NightwatchCallbackResult) => void): NightwatchBrowser, + setupMetamask(passphrase: string, password: string): NightwatchBrowser } export interface NightwatchBrowser {