Fetch and compile before debugging
pull/5370/head
yann300 5 years ago committed by GitHub
commit 4b5222d3b1
  1. 6650
      package-lock.json
  2. 12
      package.json
  3. 8
      src/app.js
  4. 12
      src/app/compiler/compiler-artefacts.js
  5. 20
      src/app/compiler/compiler-helpers.js
  6. 125
      src/app/compiler/compiler-sourceVerifier-fetchAndCompile.js
  7. 5
      src/app/compiler/compiler-utils.js
  8. 43
      src/app/tabs/debugger-tab.js
  9. 42
      src/app/tabs/debugger/debuggerUI.js
  10. 2
      src/app/tabs/debugger/debuggerUI/TxBrowser.js
  11. 7
      src/app/tabs/test-tab.js
  12. 10
      src/remixAppManager.js
  13. 32
      test-browser/tests/runAndDeploy.js

6650
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -55,13 +55,13 @@
"npm-merge-driver": "^2.3.5", "npm-merge-driver": "^2.3.5",
"npm-run-all": "^4.0.2", "npm-run-all": "^4.0.2",
"onchange": "^3.2.1", "onchange": "^3.2.1",
"remix-analyzer": "0.5.0-beta.2", "remix-analyzer": "0.5.2",
"remix-debug": "0.3.28", "remix-debug": "0.4.4",
"remix-lib": "0.4.24", "remix-lib": "0.4.29",
"remix-simulator": "0.1.9-beta.2", "remix-simulator": "0.1.9-beta.5",
"remix-solidity": "0.3.27", "remix-solidity": "0.3.30",
"remix-tabs": "1.0.48", "remix-tabs": "1.0.48",
"remix-tests": "0.1.30", "remix-tests": "0.1.33",
"remixd": "0.1.8-alpha.10", "remixd": "0.1.8-alpha.10",
"request": "^2.83.0", "request": "^2.83.0",
"rimraf": "^2.6.1", "rimraf": "^2.6.1",

@ -54,6 +54,7 @@ import { HiddenPanel } from './app/components/hidden-panel'
import { VerticalIcons } from './app/components/vertical-icons' import { VerticalIcons } from './app/components/vertical-icons'
import { LandingPage } from './app/ui/landing-page/landing-page' import { LandingPage } from './app/ui/landing-page/landing-page'
import { MainPanel } from './app/components/main-panel' import { MainPanel } from './app/components/main-panel'
import FetchAndCompile from './app/compiler/compiler-sourceVerifier-fetchAndCompile'
import migrateFileSystem from './migrateFileSystem' import migrateFileSystem from './migrateFileSystem'
@ -269,6 +270,8 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
const compilersArtefacts = new CompilersArtefacts() // store all the compilation results (key represent a compiler name) const compilersArtefacts = new CompilersArtefacts() // store all the compilation results (key represent a compiler name)
registry.put({api: compilersArtefacts, name: 'compilersartefacts'}) registry.put({api: compilersArtefacts, name: 'compilersartefacts'})
// service which fetch contract artifacts from sourve-verify, put artifacts in remix and compile it
const fetchAndCompile = new FetchAndCompile()
// ----------------- network service (resolve network id / name) ----- // ----------------- network service (resolve network id / name) -----
const networkModule = new NetworkModule(blockchain) const networkModule = new NetworkModule(blockchain)
// ----------------- represent the current selected web3 provider ---- // ----------------- represent the current selected web3 provider ----
@ -307,7 +310,8 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
offsetToLineColumnConverter, offsetToLineColumnConverter,
contextualListener, contextualListener,
terminal, terminal,
web3Provider web3Provider,
fetchAndCompile
]) ])
// LAYOUT & SYSTEM VIEWS // LAYOUT & SYSTEM VIEWS
@ -393,7 +397,7 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
await appManager.activatePlugin(['contentImport', 'theme', 'editor', 'fileManager', 'compilerMetadata', 'compilerArtefacts', 'network', 'web3Provider', 'offsetToLineColumnConverter']) await appManager.activatePlugin(['contentImport', 'theme', 'editor', 'fileManager', 'compilerMetadata', 'compilerArtefacts', 'network', 'web3Provider', 'offsetToLineColumnConverter'])
await appManager.activatePlugin(['mainPanel', 'menuicons']) await appManager.activatePlugin(['mainPanel', 'menuicons'])
await appManager.activatePlugin(['home', 'sidePanel', 'hiddenPanel', 'pluginManager', 'fileExplorers', 'settings', 'contextualListener', 'scriptRunner', 'terminal']) await appManager.activatePlugin(['home', 'sidePanel', 'hiddenPanel', 'pluginManager', 'fileExplorers', 'settings', 'contextualListener', 'scriptRunner', 'terminal', 'fetchAndCompile'])
// Set workspace after initial activation // Set workspace after initial activation
if (Array.isArray(workspace)) await appManager.activatePlugin(workspace) if (Array.isArray(workspace)) await appManager.activatePlugin(workspace)

@ -33,4 +33,16 @@ module.exports = class CompilerArtefacts extends Plugin {
this.compilersArtefacts['__last'] = new CompilerAbstract(languageVersion, data, source) this.compilersArtefacts['__last'] = new CompilerAbstract(languageVersion, data, source)
}) })
} }
addResolvedContract (address, compilerData) {
this.compilersArtefacts[address] = compilerData
}
isResolved (address) {
return this.compilersArtefacts[address] !== undefined
}
get (key) {
return this.compilersArtefacts[key]
}
} }

@ -0,0 +1,20 @@
'use strict'
import { canUseWorker } from './compiler-utils'
import { Compiler } from 'remix-solidity'
import CompilerAbstract from './compiler-abstract'
export const compile = async (compilationTargets, settings) => {
return await (() => {
return new Promise((resolve, reject) => {
const compiler = new Compiler(() => {})
compiler.set('evmVersion', settings.evmVersion)
compiler.set('optimize', settings.optimize)
compiler.loadVersion(canUseWorker(settings.version), settings.compilerUrl)
compiler.event.register('compilationFinished', (success, compilationData, source) => {
if (!success) return reject(compilationData)
resolve(new CompilerAbstract(settings.version, compilationData, source))
})
compiler.event.register('compilerLoaded', _ => compiler.compile(compilationTargets, ''))
})
})()
}

@ -0,0 +1,125 @@
const ethutil = require('ethereumjs-util')
import * as packageJson from '../../../package.json'
import { Plugin } from '@remixproject/engine'
import { urlFromVersion } from './compiler-utils'
import { compile } from './compiler-helpers'
import globalRegistry from '../../global/registry'
import remixLib from 'remix-lib'
const profile = {
name: 'fetchAndCompile',
methods: ['resolve'],
version: packageJson.version
}
export default class FetchAndCompile extends Plugin {
constructor () {
super(profile)
this.unresolvedAddresses = []
this.sourceVerifierNetWork = ['Main', 'Rinkeby', 'Ropsten', 'Goerli']
}
/**
* Fetch compiliation metadata from source-Verify from a given @arg contractAddress - https://github.com/ethereum/source-verify
* Put the artifacts in the file explorer
* Compile the code using Solidity compiler
* Returns compilation data
*
* @param {string} contractAddress - Address of the contrac to resolve
* @param {string} compilersartefacts - Object containing a mapping of compilation results (byContractAddress and __last)
* @return {CompilerAbstract} - compilation data targeting the given @arg contractAddress
*/
async resolve (contractAddress, targetPath, web3) {
contractAddress = ethutil.toChecksumAddress(contractAddress)
const compilersartefacts = globalRegistry.get('compilersartefacts').api
const localCompilation = () => compilersartefacts.get('__last') ? compilersartefacts.get('__last') : null
const resolved = compilersartefacts.get(contractAddress)
if (resolved) return resolved
if (this.unresolvedAddresses.includes(contractAddress)) return localCompilation()
// sometimes when doing an internal call, the only available artifact is the Solidity interface.
// resolving addresses of internal call would allow to step over the source code, even if the declaration was made using an Interface.
let network
try {
network = await this.call('network', 'detectNetwork')
} catch (e) {
return localCompilation()
}
if (!network) return localCompilation()
if (!this.sourceVerifierNetWork.includes(network.name)) return localCompilation()
// check if the contract if part of the local compilation result
const codeAtAddress = await web3.eth.getCode(contractAddress)
const compilation = localCompilation()
if (compilation) {
let found = false
compilation.visitContracts((contract) => {
found = remixLib.util.compareByteCode('0x' + contract.object.evm.deployedBytecode.object, codeAtAddress)
return found
})
if (found) {
compilersartefacts.addResolvedContract(contractAddress, compilation)
setTimeout(_ => this.emit('usingLocalCompilation', contractAddress), 0)
return compilation
}
}
let name = network.name.toLowerCase()
name === 'main' ? 'mainnet' : name // source-verifier api expect "mainnet" and not "main"
let data
try {
data = await this.call('source-verification', 'fetch', contractAddress, name.toLowerCase())
} catch (e) {
setTimeout(_ => this.emit('sourceVerificationNotAvailable'), 0)
this.unresolvedAddresses.push(contractAddress)
return localCompilation()
}
if (!data || !data.metadata) {
setTimeout(_ => this.emit('notFound', contractAddress), 0)
this.unresolvedAddresses.push(contractAddress)
return localCompilation()
}
// set the solidity contract code using metadata
await this.call('fileManager', 'setFile', `${targetPath}/${name}/${contractAddress}/metadata.json`, JSON.stringify(data.metadata, null, '\t'))
let compilationTargets = {}
for (let file in data.metadata.sources) {
const urls = data.metadata.sources[file].urls
for (let url of urls) {
if (url.includes('ipfs')) {
let stdUrl = `ipfs://${url.split('/')[2]}`
const source = await this.call('contentImport', 'resolve', stdUrl)
file = file.replace('browser/', '') // should be fixed in the remix IDE end.
const path = `${targetPath}/${name}/${contractAddress}/${file}`
await this.call('fileManager', 'setFile', path, source.content)
compilationTargets[path] = { content: source.content }
break
}
}
}
// compile
const settings = {
version: data.metadata.compiler.version,
languageName: data.metadata.language,
evmVersion: data.metadata.settings.evmVersion,
optimize: data.metadata.settings.optimizer.enabled,
compilerUrl: urlFromVersion(data.metadata.compiler.version)
}
try {
setTimeout(_ => this.emit('compiling', settings), 0)
const compData = await compile(compilationTargets, settings)
compilersartefacts.addResolvedContract(contractAddress, compData)
return compData
} catch (e) {
this.unresolvedAddresses.push(contractAddress)
setTimeout(_ => this.emit('compilationFailed'), 0)
return localCompilation()
}
}
}

@ -1,6 +1,11 @@
const semver = require('semver') const semver = require('semver')
/* global Worker */ /* global Worker */
export const baseUrl = 'https://solc-bin.ethereum.org/bin'
export function urlFromVersion (version) {
return `${baseUrl}/soljson-v${version}.js`
}
/** /**
* Checks if the worker can be used to load a compiler. * Checks if the worker can be used to load a compiler.
* checks a compiler whitelist, browser support and OS. * checks a compiler whitelist, browser support and OS.

@ -1,8 +1,8 @@
var yo = require('yo-yo') const yo = require('yo-yo')
var css = require('./styles/debugger-tab-styles') const remixLib = require('remix-lib')
const css = require('./styles/debugger-tab-styles')
var DebuggerUI = require('./debugger/debuggerUI') import toaster from '../ui/tooltip'
const DebuggerUI = require('./debugger/debuggerUI')
import { ViewPlugin } from '@remixproject/engine' import { ViewPlugin } from '@remixproject/engine'
import * as packageJson from '../../../package.json' import * as packageJson from '../../../package.json'
@ -34,7 +34,38 @@ class DebuggerTab extends ViewPlugin {
<div class="${css.debuggerTabView}" id="debugView"> <div class="${css.debuggerTabView}" id="debugView">
<div id="debugger" class="${css.debugger}"></div> <div id="debugger" class="${css.debugger}"></div>
</div>` </div>`
this.debuggerUI = new DebuggerUI(this.el.querySelector('#debugger'), this.blockchain)
this.on('fetchAndCompile', 'compiling', (settings) => {
toaster(yo`<div><b>Recompiling and debugging with params</b><pre class="text-left">${JSON.stringify(settings, null, '\t')}</pre></div>`)
})
this.on('fetchAndCompile', 'compilationFailed', (data) => {
toaster(yo`<div><b>Compilation failed...</b> continuing <i>without</i> source code debugging.</div>`)
})
this.on('fetchAndCompile', 'notFound', (contractAddress) => {
toaster(yo`<div><b>Contract ${contractAddress} not found in source code repository</b> continuing <i>without</i> source code debugging.</div>`)
})
this.on('fetchAndCompile', 'usingLocalCompilation', (contractAddress) => {
toaster(yo`<div><b>Using compilation result from Solidity module</b></div>`)
})
this.on('fetchAndCompile', 'sourceVerificationNotAvailable', () => {
toaster(yo`<div><b>Source verification plugin not activated or not available.</b> continuing <i>without</i> source code debugging.</div>`)
})
this.debuggerUI = new DebuggerUI(
this.el.querySelector('#debugger'),
this.blockchain,
(address, receipt) => {
const target = (address && remixLib.helpers.trace.isContractCreation(address)) ? receipt.contractAddress : address
return this.call('fetchAndCompile', 'resolve', target || receipt.contractAddress || receipt.to, '.debug', this.blockchain.web3())
})
this.call('manager', 'activatePlugin', 'source-verification')
// this.call('manager', 'activatePlugin', 'udapp')
return this.el return this.el
} }

@ -30,9 +30,10 @@ var css = csjs`
class DebuggerUI { class DebuggerUI {
constructor (container, blockchain) { constructor (container, blockchain, fetchContractAndCompile) {
this.registry = globalRegistry this.registry = globalRegistry
this.blockchain = blockchain this.blockchain = blockchain
this.fetchContractAndCompile = fetchContractAndCompile
this.event = new EventManager() this.event = new EventManager()
this.isActive = false this.isActive = false
@ -43,6 +44,7 @@ class DebuggerUI {
this.stepManager = null this.stepManager = null
this.statusMessage = '' this.statusMessage = ''
this.currentReceipt
this.view this.view
@ -77,8 +79,14 @@ class DebuggerUI {
self.isActive = isActive self.isActive = isActive
}) })
this.debugger.event.register('newSourceLocation', function (lineColumnPos, rawLocation) { this.debugger.event.register('newSourceLocation', async function (lineColumnPos, rawLocation) {
self.sourceHighlighter.currentSourceLocation(lineColumnPos, rawLocation) const contracts = await self.fetchContractAndCompile(
self.currentReceipt.contractAddress || self.currentReceipt.to,
self.currentReceipt)
if (contracts) {
const path = contracts.getSourceName(rawLocation.file)
if (path) self.sourceHighlighter.currentSourceLocationFromfileName(lineColumnPos, path)
}
}) })
this.debugger.event.register('debuggerUnloaded', self.unLoad.bind(this)) this.debugger.event.register('debuggerUnloaded', self.unLoad.bind(this))
@ -122,15 +130,19 @@ class DebuggerUI {
async startDebugging (blockNumber, txNumber, tx) { async startDebugging (blockNumber, txNumber, tx) {
if (this.debugger) this.unLoad() if (this.debugger) this.unLoad()
let compilers = this.registry.get('compilersartefacts').api
let lastCompilationResult
if (compilers['__last']) lastCompilationResult = compilers['__last']
let web3 = await this.getDebugWeb3() let web3 = await this.getDebugWeb3()
this.currentReceipt = await web3.eth.getTransactionReceipt(txNumber)
this.debugger = new Debugger({ this.debugger = new Debugger({
web3, web3,
offsetToLineColumnConverter: this.registry.get('offsettolinecolumnconverter').api, offsetToLineColumnConverter: this.registry.get('offsettolinecolumnconverter').api,
compiler: { lastCompilationResult } compilationResult: async (address) => {
try {
return await this.fetchContractAndCompile(address, this.currentReceipt)
} catch (e) {
console.error(e)
}
return null
}
}) })
this.listenToEvents() this.listenToEvents()
@ -147,16 +159,20 @@ class DebuggerUI {
getTrace (hash) { getTrace (hash) {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const compilers = this.registry.get('compilersartefacts').api
let lastCompilationResult
if (compilers['__last']) lastCompilationResult = compilers['__last']
const web3 = await this.getDebugWeb3() const web3 = await this.getDebugWeb3()
this.currentReceipt = await web3.eth.getTransactionReceipt(hash)
const debug = new Debugger({ const debug = new Debugger({
web3, web3,
offsetToLineColumnConverter: this.registry.get('offsettolinecolumnconverter').api, offsetToLineColumnConverter: this.registry.get('offsettolinecolumnconverter').api,
compiler: { lastCompilationResult } compilationResult: async (address) => {
try {
return await this.fetchContractAndCompile(address, this.currentReceipt)
} catch (e) {
console.error(e)
}
return null
}
}) })
debug.debugger.traceManager.traceRetriever.getTrace(hash, (error, trace) => { debug.debugger.traceManager.traceRetriever.getTrace(hash, (error, trace) => {
if (error) return reject(error) if (error) return reject(error)

@ -93,6 +93,7 @@ TxBrowser.prototype.render = function () {
type='text' type='text'
oninput=${this.txInputChanged.bind(this)} oninput=${this.txInputChanged.bind(this)}
placeholder=${'Transaction hash, should start with 0x'} placeholder=${'Transaction hash, should start with 0x'}
data-id="debuggerTransactionInput"
/> />
` `
let txButton = yo` let txButton = yo`
@ -101,6 +102,7 @@ TxBrowser.prototype.render = function () {
id='load' id='load'
title='${this.state.debugging ? 'Stop' : 'Start'} debugging' title='${this.state.debugging ? 'Stop' : 'Start'} debugging'
onclick=${function () { self.submit() }} onclick=${function () { self.submit() }}
data-id="debuggerTransactionStartButton"
> >
${this.state.debugging ? 'Stop' : 'Start'} debugging ${this.state.debugging ? 'Stop' : 'Start'} debugging
</button> </button>

@ -4,7 +4,7 @@ var tooltip = require('../ui/tooltip')
var css = require('./styles/test-tab-styles') var css = require('./styles/test-tab-styles')
var remixTests = require('remix-tests') var remixTests = require('remix-tests')
import { ViewPlugin } from '@remixproject/engine' import { ViewPlugin } from '@remixproject/engine'
import { canUseWorker } from '../compiler/compiler-utils' import { canUseWorker, baseUrl } from '../compiler/compiler-utils'
const TestTabLogic = require('./testTab/testTab') const TestTabLogic = require('./testTab/testTab')
@ -33,7 +33,6 @@ module.exports = class TestTab extends ViewPlugin {
this.runningTestsNumber = 0 this.runningTestsNumber = 0
this.readyTestsNumber = 0 this.readyTestsNumber = 0
this.areTestsRunning = false this.areTestsRunning = false
this.baseurl = 'https://solc-bin.ethereum.org/bin'
appManager.event.on('activate', (name) => { appManager.event.on('activate', (name) => {
if (name === 'solidity') this.updateRunAction(fileManager.currentFile()) if (name === 'solidity') this.updateRunAction(fileManager.currentFile())
}) })
@ -204,7 +203,7 @@ module.exports = class TestTab extends ViewPlugin {
let runningTest = {} let runningTest = {}
runningTest[path] = { content } runningTest[path] = { content }
const {currentVersion, evmVersion, optimize} = this.compileTab.getCurrentCompilerConfig() const {currentVersion, evmVersion, optimize} = this.compileTab.getCurrentCompilerConfig()
const currentCompilerUrl = this.baseurl + '/' + currentVersion const currentCompilerUrl = baseUrl + '/' + currentVersion
const compilerConfig = { const compilerConfig = {
currentCompilerUrl, currentCompilerUrl,
evmVersion, evmVersion,
@ -230,7 +229,7 @@ module.exports = class TestTab extends ViewPlugin {
const runningTest = {} const runningTest = {}
runningTest[testFilePath] = { content } runningTest[testFilePath] = { content }
const {currentVersion, evmVersion, optimize} = this.compileTab.getCurrentCompilerConfig() const {currentVersion, evmVersion, optimize} = this.compileTab.getCurrentCompilerConfig()
const currentCompilerUrl = this.baseurl + '/' + currentVersion const currentCompilerUrl = baseUrl + '/' + currentVersion
const compilerConfig = { const compilerConfig = {
currentCompilerUrl, currentCompilerUrl,
evmVersion, evmVersion,

@ -5,15 +5,19 @@ import QueryParams from './lib/query-params'
import { PermissionHandler } from './app/ui/persmission-handler' import { PermissionHandler } from './app/ui/persmission-handler'
const requiredModules = [ // services + layout views + system views const requiredModules = [ // services + layout views + system views
'manager', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme', 'fileManager', 'contentImport', 'web3Provider', 'scriptRunner', 'manager', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme', 'fileManager', 'contentImport', 'web3Provider', 'scriptRunner', 'fetchAndCompile',
'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons', 'fileExplorers', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons', 'fileExplorers',
'terminal', 'settings', 'pluginManager'] 'terminal', 'settings', 'pluginManager']
export function isNative (name) { export function isNative (name) {
const nativePlugins = ['vyper', 'workshops', 'ethdoc', 'etherscan'] const nativePlugins = ['vyper', 'workshops']
return nativePlugins.includes(name) || requiredModules.includes(name) return nativePlugins.includes(name) || requiredModules.includes(name)
} }
export function canActivate (name) {
return ['manager', 'debugger'].includes(name)
}
export class RemixAppManager extends PluginManager { export class RemixAppManager extends PluginManager {
constructor (plugins) { constructor (plugins) {
@ -25,7 +29,7 @@ export class RemixAppManager extends PluginManager {
} }
async canActivate (from, to) { async canActivate (from, to) {
return from.name === 'manager' return canActivate(from.name)
} }
async canDeactivate (from, to) { async canDeactivate (from, to) {

@ -150,6 +150,38 @@ module.exports = {
.waitForElementPresent('*[data-id="modalDialogContainer"]') .waitForElementPresent('*[data-id="modalDialogContainer"]')
.assert.containsText('*[data-id="modalDialogModalBody"]', 'You are creating a transaction on the main network. Click confirm if you are sure to continue.') .assert.containsText('*[data-id="modalDialogModalBody"]', 'You are creating a transaction on the main network. Click confirm if you are sure to continue.')
.modalFooterCancelClick() .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) {
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"]', '0x5db1b4212e4f83e36bf5bc306888df50f01a73708a71322bdc6f39a76a7ebdaa') // debug tx
.click('*[data-id="debuggerTransactionStartButton"]')
.waitForElementVisible('*[data-id="treeViewDivto"]', 30000)
.assert.containsText('*[data-id="stepdetail"]', 'loaded address: 0x96d87AB604AEC7FB26C2E046CA5e6eBBB9D8b74D')
.assert.containsText('*[data-id="solidityLocals"]', 'to: 0x6C3CCC7FBA111707D5A1AAF2758E9D4F4AC5E7B1')
.end() .end()
}, },

Loading…
Cancel
Save