diff --git a/app/src/main/index.js b/app/src/main/index.js index 46d35fab35..b404989001 100644 --- a/app/src/main/index.js +++ b/app/src/main/index.js @@ -121,7 +121,7 @@ function createWindow () { } else { startVueApp() } - if (DEV || process.env.COSMOS_DEVTOOLS) { + if (DEV || JSON.parse(process.env.COSMOS_DEVTOOLS || 'false')) { mainWindow.webContents.openDevTools() } @@ -150,6 +150,9 @@ function createWindow () { function startProcess (name, args, env) { let binPath + if (process.env.BINARY_PATH) { + binPath = process.env.BINARY_PATH + } else if (DEV || TEST) { // in dev mode or tests, use binaries installed in GOPATH let GOPATH = process.env.GOPATH @@ -302,11 +305,13 @@ if (!TEST) { process.on('exit', shutdown) process.on('uncaughtException', async function (err) { logError('[Uncaught Exception]', err) + console.error('[Uncaught Exception]', err) await shutdown() process.exit(1) }) process.on('unhandledRejection', async function (err) { logError('[Unhandled Promise Rejection]', err) + console.error('[Unhandled Promise Rejection]', err) await shutdown() process.exit(1) }) diff --git a/package.json b/package.json index 8edc919282..a87d63fcf0 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "pack:renderer": "cross-env NODE_ENV=production webpack --colors --config webpack.renderer.config.js", "test": "npm run lint && npm run test:unit", "test:unit": "cross-env LOGGING=false MOCK=false jest --maxWorkers=2", - "test:e2e": "tape \"test/e2e/!(main)*.js\"", + "test:e2e": "tape \"test/e2e/*.js\"", "test:exe": "node tasks/test-build.js", "test:coverage": "http-server test/unit/coverage/lcov-report", "vue:route": "node tasks/vue/route.js", diff --git a/test/e2e/common.js b/test/e2e/common.js index 4261028abb..80a49512c3 100644 --- a/test/e2e/common.js +++ b/test/e2e/common.js @@ -6,25 +6,64 @@ function sleep (ms) { } module.exports = { - navigate (t, client, linkText, titleText = linkText) { - t.test(`navigate to "${linkText}"`, async function (t) { - await client.$(`a*=${linkText}`).click() - await client.waitUntilTextExists('.ni-page-header-title', titleText) - t.pass(`navigated to "${linkText}"`) - t.end() - }) + async openMenu (client) { + if (await client.isExisting('.app-menu')) { + return + } + // close notifications as they overlay the menu button + while (await client.isExisting(`.ni-notification`)) { + await client.$(`.ni-notification`).click() + } + await client.$('#app-menu-button').click() + await client.waitForExist('.app-menu', 1000) + }, + async navigate (client, linkText, titleText = linkText) { + await module.exports.openMenu(client) + // click link + await client.$(`a*=${linkText}`).click() + await client.waitUntilTextExists('.ni-page-header-title', titleText) + console.log(`navigated to "${linkText}"`) }, newTempDir () { return join(tmpdir(), Math.random().toString(36).slice(2)) }, sleep, - async waitForText (el, text, timeout = 5000) { + async waitForText (elGetterFn, text, timeout = 5000) { let start = Date.now() - while (await el().getText() !== text) { + while (await elGetterFn().getText() !== text) { if (Date.now() - start >= timeout) { throw Error('Timed out waiting for text') } await sleep(100) } + }, + async login (client, account = 'testkey') { + console.log('logging into ' + account) + let accountsSelect = '#sign-in-name select' + + await selectOption(client, accountsSelect, account) + + await client.$('#sign-in-password').setValue('1234567890') + await client.$('.ni-session-footer button').click() + await client.waitForExist('#app-content', 5000) + + // checking if user is logged in + await module.exports.openMenu(client) + let activeUser = await client.$('.ni-li-user .ni-li-title').getText() + if (account !== activeUser) { + throw new Error('Incorrect user logged in') + } + }, + async logout (client) { + console.log('logging out') + await module.exports.openMenu(client) + + await client.$('.ni-li-user').click() + await client.$('.material-icons=exit_to_app').$('..').click() } } + +async function selectOption (client, selectSelector, text) { + await client.$(selectSelector).click() + await client.keys(text.split()) +} diff --git a/test/e2e/launch.js b/test/e2e/launch.js index 8c7b523357..4eb73f9dcc 100644 --- a/test/e2e/launch.js +++ b/test/e2e/launch.js @@ -4,55 +4,75 @@ let { Application } = require('spectron') let test = require('tape-promise/tape') let electron = require('electron') let { join } = require('path') -let { newTempDir } = require('./common.js') - -// re-use app instance -let app - -module.exports = async function launch (t) { - if (app) return app - - let home = newTempDir() - console.error(`ui home: ${home}`) - - app = new Application({ - path: electron, - args: [ - join(__dirname, '../../app/dist/main.js'), - '--disable-gpu', - '--no-sandbox' - ], - startTimeout: 10000, - waitTimeout: 10000, - env: { - COSMOS_TEST: 'true', - COSMOS_HOME: home, - COSMOS_NETWORK: join(__dirname, 'gaia-1') - } - }) +let { spawn } = require('child_process') +let { newTempDir, login } = require('./common.js') - await app.start() +let app, home, cliHome, started +let binary = process.env.BINARY_PATH - t.test('launch app', function (t) { - t.ok(app.isRunning(), 'app is running') - t.end() - }) +module.exports = function launch (t) { + if (!started) { + started = new Promise(async (resolve, reject) => { + console.log('using binary', binary) + + // TODO cleanup + home = newTempDir() + cliHome = join(newTempDir(), 'baseserver') + console.error(`ui home: ${home}`) + console.error(`node home: ${cliHome}`) + + await startLocalNode() + + app = new Application({ + path: electron, + args: [ + join(__dirname, '../../app/dist/main.js'), + '--disable-gpu', + '--no-sandbox' + ], + startTimeout: 10000, + waitTimeout: 10000, + env: { + // COSMOS_UI_ONLY: 'true', + // COSMOS_TEST: 'true', + COSMOS_NODE: 'localhost', + NODE_ENV: 'production', + PREVIEW: 'true', + COSMOS_DEVTOOLS: 0, // open devtools will cause issues with spectron, you can open them later manually + COSMOS_HOME: home, + COSMOS_NETWORK: 'test/e2e/localtestnet' + } + }) - t.test('wait for app to load', async function (t) { - await app.client.waitForExist('.header-item-logo', 5000) - .then(() => t.pass('app loaded')) - .catch(e => { - printAppLog(app) - t.fail() - throw e - }) - t.end() - }) + await startApp(app) + t.ok(app.isRunning(), 'app is running') + + console.log('stopping app to test consecutive run') + await app.stop() - return app + await createAccount('testkey', 'chair govern physical divorce tape movie slam field gloom process pen universe allow pyramid private ability') + await createAccount('testreceiver', 'crash ten rug mosquito cart south allow pluck shine island broom deputy hungry photo drift absorb') + console.log('restored test accounts') + + await startApp(app) + t.ok(app.isRunning(), 'app is running') + + await login(app.client, 'testkey') + + resolve({app, home}) + }) + } + + return started } test.onFinish(() => app ? app.stop() : null) +test.onFinish(async () => { + console.log('DONE: cleaning up') + await app ? app.stop() : null + // tape doesn't finish properly because of open processes like gaia + process.exit(0) +}) function printAppLog (app) { app.client.getMainProcessLogs().then(function (logs) { @@ -68,3 +88,65 @@ function printAppLog (app) { }) }) } + +async function startApp (app) { + await app.start() + + await app.client.waitForExist('.ni-session', 5000) + .catch(e => { + printAppLog(app) + throw e + }) +} + +async function startLocalNode () { + await new Promise((resolve, reject) => { + let child = spawn(binary, [ + 'node', 'init', + 'D0718DDFF62D301626B428A182F830CBB0AD21FC', + '--home', cliHome, + '--chain-id' , 'localtestnet' + ]) + child.once('exit', (code) => { + if (code === 0) resolve() + reject() + }) + }) + console.log('inited local node') + + await new Promise((resolve, reject) => { + // TODO cleanup + let localnodeProcess = spawn(binary, [ + 'node', 'start', + '--home', cliHome + ]) + localnodeProcess.stderr.pipe(process.stderr) + localnodeProcess.stdout.once('data', data => { + let msg = data.toString() + if (!msg.includes('Failed') && !msg.includes('Error')) { + resolve() + } + reject() + }) + localnodeProcess.once('exit', (code) => { + reject() + }) + }) + console.log('started local node') +} + +async function createAccount (name, seed) { + await new Promise((resolve, reject) => { + let child = spawn(binary, [ + 'client', 'keys', 'recover', name, + '--home', join(home, 'baseserver') + ]) + child.stdin.write('1234567890\n') + child.stdin.write(seed + '\n') + child.stderr.pipe(process.stdout) + child.once('exit', (code) => { + if (code === 0) resolve() + reject() + }) + }) +} \ No newline at end of file diff --git a/test/e2e/localtestnet/config.toml b/test/e2e/localtestnet/config.toml index 7406759d24..b264ba46d5 100644 --- a/test/e2e/localtestnet/config.toml +++ b/test/e2e/localtestnet/config.toml @@ -2,33 +2,27 @@ # For more information, see https://github.com/toml-lang/toml proxy_app = "tcp://127.0.0.1:46658" -moniker = "local" +moniker = "localhost" fast_sync = true -db_backend = "memdb" -#log_level = "mempool:error,*:debug" + +db_backend = "leveldb" log_level = "state:info,*:error" +#log_level = "*:debug" + [rpc] -laddr = "tcp://127.0.0.1:46657" +laddr = "tcp://0.0.0.0:46657" -#[mempool] -#recheck = false -#broadcast = false -#wal_dir = "" [consensus] -#max_block_size_txs = 10000 -#create_empty_blocks = false -#timeout_propose = 10000 -#skip_timeout_commit = true -#timeout_commit = 1 -#wal_light = true -#block_part_size = 262144 -create_empty_blocks_interval = 1 +create_empty_blocks_interval = 60 + +[tx_index] +index_all_tags = true + [p2p] -#max_msg_packet_payload_size=65536 -#send_rate=51200000 # 50 MB/s -#recv_rate=51200000 # 50 MB/s -laddr = "tcp://127.0.0.1:46656" -seeds = "" +max_num_peers = 300 +pex = true +laddr = "tcp://0.0.0.0:46656" +seeds = "localhost:46656" diff --git a/test/e2e/localtestnet/gaiaversion.txt b/test/e2e/localtestnet/gaiaversion.txt new file mode 100644 index 0000000000..48080b4cf2 --- /dev/null +++ b/test/e2e/localtestnet/gaiaversion.txt @@ -0,0 +1 @@ +v0.5.0 \ No newline at end of file diff --git a/test/e2e/main.js b/test/e2e/main.js deleted file mode 100644 index fc532ce33a..0000000000 --- a/test/e2e/main.js +++ /dev/null @@ -1,39 +0,0 @@ -let { join } = require('path') -let test = require('tape-promise/tape') -let proxyquire = require('proxyquire') -let { newTempDir } = require('./common.js') - -let home = newTempDir() -console.error(`ui home: ${home}`) - -Object.assign(process.env, { - COSMOS_TEST: 'true', - COSMOS_HOME: home, - COSMOS_NETWORK: join(__dirname, 'gaia-1') -}) - -test('main', async function (t) { - let main = proxyquire('../../app/dist/main', { - electron: { - app: { - on: () => {}, - quit: () => {} - } - } - }).default - - main - .then(() => { - t.ok(true, 'main function is running') - cleanUp(t, main) - }) - .catch(e => { - t.fail(e) - cleanUp(t, main) - }) -}) - -function cleanUp (t, main) { - main.shutdown() - t.end() -} diff --git a/test/e2e/signin.js b/test/e2e/signin.js index 18154c5cd6..a54bd17ff0 100644 --- a/test/e2e/signin.js +++ b/test/e2e/signin.js @@ -1,66 +1,138 @@ let test = require('tape-promise/tape') let launchApp = require('./launch.js') -let { navigate } = require('./common.js') +let { navigate, logout, openMenu } = require('./common.js') test('sign in', async function (t) { - let app, client + let {app, home} = await launchApp(t) + let client = app.client let el = (...args) => client.$(...args) + let continueButton = () => el('.ni-btn__value=Next').$('..') + + t.test('agreement', async function (t) { + await logout(client) + // go to login selection + await client.$('i=arrow_back').$('..').click() + await client.waitForExist('.ni-li-session', 1000) + // go to new account + await client.$('.ni-li-session-title=Create new account').$('..').$('..').click() - app = await launchApp(t) - console.log('app launched') - client = app.client + let accountName = () => el('#sign-up-name') + let password = () => el('#sign-in-password') + let warning = () => el('#sign-up-warning') + let backedup = () => el('#sign-up-backup') navigate(t, client, 'Sign In', 'Welcome to Cosmos Voyager') + t.test('did check warning', async function (t) { + await continueButton().click() + t.ok(await warning().$('..').$('..').$('..').isExisting('.ni-form-msg--error'), 'shows error') + await warning().click() + t.ok(!(await warning().$('..').$('..').$('..').isExisting('.ni-form-msg--error')), 'hides error') + t.end() + }) - t.test('agreement', async function (t) { - let input = 'input[placeholder="Enter here"]' - let continueButton = 'button=Continue' - let errorText = 'div*=Agreement must match' - - t.test('input incorrect agreement text', async function (t) { - await el(input).setValue('lol i don\'t understand the risks') - await el(continueButton).click() - await client.waitForVisible(errorText) - t.pass('error text is shown') + t.test('did check backup note', async function (t) { + await continueButton().click() + t.ok(await backedup().$('..').$('..').$('..').isExisting('.ni-form-msg--error'), 'shows error') + await backedup().click() + t.ok(!(await backedup().$('..').$('..').$('..').isExisting('.ni-form-msg--error')), 'hides error') t.end() }) - t.test('input correct agreement text', async function (t) { - await el(input).setValue('I understand the risks') - await el(continueButton).click() - try { - await el(errorText).isVisible() - t.fail('error text should not exist') - } catch (e) { - t.pass('no error text') - } + t.test('set account name', async function (t) { + await continueButton().click() + t.ok(await accountName().$('..').isExisting('.ni-form-msg--error'), 'shows error') + await accountName().click() + await client.keys('sign'.split()) + t.ok(await accountName().$('..').isExisting('.ni-form-msg--error'), 'shows error for too few letters') + await accountName().click() + await client.keys('in_test'.split()) + t.ok(!(await accountName().$('..').isExisting('.ni-form-msg--error')), 'hides error') t.end() }) + + t.test('set password', async function (t) { + await continueButton().click() + t.ok(await password().$('..').isExisting('.ni-form-msg--error'), 'shows error') + await password().click() + await client.keys('1234'.split()) + t.ok(await password().$('..').isExisting('.ni-form-msg--error'), 'shows error for too few letters') + await password().click() + await client.keys('567890'.split()) + t.ok(!(await password().$('..').isExisting('.ni-form-msg--error')), 'hides error') + t.end() + }) + + t.test('logs in', async function (t) { + await continueButton().click() + + // checking if user is logged in + await client.waitForExist('#app-content', 5000) + await openMenu(client) + let activeUser = await client.$('.ni-li-user .ni-li-title').getText() + t.ok('signin_test' === activeUser, 'user is logged in') + + t.end() + }) t.end() }) t.test('seed', async function (t) { - let input = 'input[placeholder="Input seed..."]' - let signInButton = 'button=Sign In' - - // TODO (after seed validation implemented) - t.skip('input incorrect seed text', async function (t) { - await el(input).setValue('seed123') - await el(signInButton).click() - let errorText = 'div*=TODO: add error message' - t.ok(await el(errorText).isVisible(), 'error is shown') + await logout(client) + // go to login selection + await client.$('i=arrow_back').$('..').click() + await client.waitForExist('.ni-li-session', 1000) + // go to import with seed + await client.$('.ni-li-session-title=Import with seed').$('..').$('..').click() + + let accountName = () => el('#import-name') + let password = () => el('#import-password') + let seed = () => el('#import-seed') + + t.test('set account name', async function (t) { + await continueButton().click() + t.ok(await accountName().$('..').isExisting('.ni-form-msg--error'), 'shows error') + await accountName().click() + await client.keys('seed'.split()) + t.ok(await accountName().$('..').isExisting('.ni-form-msg--error'), 'shows error for too few letters') + await accountName().click() + await client.keys('_test'.split()) + t.ok(!(await accountName().$('..').isExisting('.ni-form-msg--error')), 'hides error') + t.end() + }) + + t.test('set password', async function (t) { + await continueButton().click() + t.ok(await password().$('..').isExisting('.ni-form-msg--error'), 'shows error') + await password().click() + await client.keys('1234'.split()) + t.ok(await password().$('..').isExisting('.ni-form-msg--error'), 'shows error for too few letters') + await password().click() + await client.keys('567890'.split()) + t.ok(!(await password().$('..').isExisting('.ni-form-msg--error')), 'hides error') t.end() }) t.test('input correct seed text', async function (t) { - await el(input).setValue('TODO: put valid seed here') - await el(signInButton).click() - await client.waitUntilTextExists( - '.ni-page-header-title', - 'Balances') - t.pass('navigated') + await continueButton().click() + t.ok(await seed().$('..').isExisting('.ni-form-msg--error'), 'shows error') + await seed().click() + await client.keys('crash ten rug mosquito cart south allow pluck shine island broom deputy hungry photo drift absorb'.split()) + t.ok(!(await seed().$('..').isExisting('.ni-form-msg--error')), 'hides error') t.end() }) + + t.test('logs in', async function (t) { + await continueButton().click() + + // checking if user is logged in + await client.waitForExist('#app-content', 5000) + await openMenu(client) + let activeUser = await client.$('.ni-li-user .ni-li-title').getText() + t.ok('seed_test' === activeUser, 'user is logged in') + + t.end() + }) + t.end() }) t.end() diff --git a/test/e2e/wallet.js b/test/e2e/wallet.js index 4291b5bdeb..995bbc3afb 100644 --- a/test/e2e/wallet.js +++ b/test/e2e/wallet.js @@ -1,42 +1,13 @@ let { spawn } = require('child_process') let test = require('tape-promise/tape') let launchApp = require('./launch.js') -let { navigate, newTempDir, waitForText, sleep } = require('./common.js') - -function cliInit (t, home) { - t.test('basecli init', function (t) { - let child = spawn('basecli', [ - 'init', - '--home', home, - '--chain-id', 'localtestnet', - '--node', 'localhost:46657' - ]) - child.stdout.once('data', () => { - child.stdin.write('y\n') - child.once('exit', (code) => { - t.equal(code, 0, 'exited with exit code 0') - t.end() - }) - }) - }) +let { navigate, newTempDir, waitForText, sleep, login, logout } = require('./common.js') - t.test('recover basecli test key', function (t) { - let child = spawn('basecli', [ - 'keys', 'recover', 'testkey', - '--home', home - ]) - child.stdin.write('1234567890\n') - child.stdin.write('chair govern physical divorce tape movie slam field gloom process pen universe allow pyramid private ability\n') - child.once('exit', (code) => { - t.equal(code, 0, 'exited with exit code 0') - t.end() - }) - }) -} +let binary = process.env.BINARY_PATH function cliSendCoins (home, to, amount) { - let child = spawn('basecli', [ - 'tx', 'send', + let child = spawn(binary, [ + 'client', 'tx', 'send', '--name', 'testkey', '--to', to, '--amount', amount, @@ -50,107 +21,110 @@ function cliSendCoins (home, to, amount) { } test('wallet', async function (t) { - let app, client + let {app, home} = await launchApp(t) + let client = app.client let $ = (...args) => client.$(...args) - app = await launchApp(t) - client = app.client - - let cliHome = newTempDir() - console.error(`basecli home: ${cliHome}`) - cliInit(t, cliHome) + + await logout(client) + await login(client, 'testkey') let balanceEl = (denom) => - $(`div=${denom.toUpperCase()}`) + $(`//div[contains(text(), "${denom.toUpperCase()}")]`) .$('..') .$('div.ni-li-dd') - t.test('receive', async function (t) { - navigate(t, client, 'Balances') - - let address - t.test('address', async function (t) { - let addressEl = $('div=Address').$('..').$('div.ni-li-dd') - address = await addressEl.getText() - t.ok(address, `address: ${address}`) - t.equal(address.length, 40, 'address is correct length') - t.end() - }) + t.test('send', async function (t) { + async function goToSendPage () { + await navigate(client, 'Balances') + await $('.ni-li-dt=FERMION').$('..').$('..').click() + } - t.test('mycoin balance before receiving', async function (t) { - let mycoinEl = balanceEl('mycoin') - let balance = await mycoinEl.getText() - t.equal(balance, '0', 'mycoin balance is 0') - t.end() - }) + await navigate(client, 'Balances') - t.test('receive mycoin', async function (t) { - await cliSendCoins(cliHome, address, '1000mycoin') - t.end() - }) + let sendBtn = () => $('.ni-form-footer button') + let addressInput = () => $('#send-address') + let amountInput = () => $('#send-amount') + let denomBtn = (denom) => $(`option=${denom.toUpperCase()}`) - t.test('mycoin balance after receiving', async function (t) { - let mycoinEl = () => balanceEl('mycoin') - await waitForText(mycoinEl, '1000', 8000) - t.pass('received mycoin transaction') + t.test('fermion balance before sending', async function (t) { + let fermionEl = balanceEl('fermion') + let balance = await fermionEl.getText() + t.equal(balance, '9007199254740992', 'fermion balance is correct') t.end() }) - t.end() - }) - - t.test('send', async function (t) { - navigate(t, client, 'Send', 'Send Coins') - - let sendBtn = () => $('button=Send Now') - let addressInput = () => $('#send-address') - let amountInput = () => $('#send-amount') - let denomBtn = (denom) => $(`button=${denom.toUpperCase()}`) - t.test('hit send with empty form', async function (t) { + await goToSendPage() await sendBtn().click() - t.equal(await sendBtn().getText(), 'Send Now', 'not sending') + t.equal(await sendBtn().getText(), 'Send Tokens', 'not sending') t.end() }) t.test('address w/ less than 40 chars', async function (t) { + await goToSendPage() await addressInput().setValue('012345') await $('div=Address must be exactly 40 characters').waitForExist() t.pass('got correct error message') await sendBtn().click() - t.equal(await sendBtn().getText(), 'Send Now', 'not sending') + t.equal(await sendBtn().getText(), 'Send Tokens', 'not sending') t.end() }) t.test('address w/ 40 chars', async function (t) { + await goToSendPage() await addressInput().setValue('0'.repeat(40)) t.notOk(await client.isExisting('div=Address must be exactly 40 characters'), 'no error message') await sendBtn().click() - t.equal(await sendBtn().getText(), 'Send Now', 'not sending') + t.equal(await sendBtn().getText(), 'Send Tokens', 'not sending') t.end() }) t.test('amount set', async function (t) { + await goToSendPage() await amountInput().setValue('100') await sendBtn().click() - t.equal(await sendBtn().getText(), 'Send Now', 'not sending') - // await $('div=Denomination is required').waitForExist() - // t.pass('got correct error message') + t.equal(await sendBtn().getText(), 'Send Tokens', 'not sending') + t.end() }) - t.test('denom set', async function (t) { - await denomBtn('mycoin').click() - await sleep(100) + t.test('send', async function (t) { + await goToSendPage() + await amountInput().setValue('100') + await addressInput().setValue('3F52AFC4FB737A0296EFE331885FCC476980B3BD') await sendBtn().click() + await client.waitForExist('.ni-notification', 5000) + let msg = await client.$('.ni-notification .body').getText() + t.ok(msg.includes('Success'), 'Send successful') + t.end() }) - navigate(t, client, 'Balances') - t.test('own balance updated', async function (t) { - let mycoinEl = () => balanceEl('mycoin') - await waitForText(mycoinEl, '900') - t.pass('balance is now 900') + await navigate(client, 'Balances') + + // TODO should not be necessary + await sleep(1000) + await client.$('.material-icons=refresh').click() + + let mycoinEl = () => balanceEl('fermion') + await waitForText(mycoinEl, '9007199254740892') + t.pass('balance is reduced by 100') + t.end() + }) + + t.end() + }) + + t.test('receive', async function (t) { + t.test('fermion balance after receiving', async function (t) { + await logout(client) + await login(client, 'testreceiver') + await navigate(client, 'Balances') + + let fermionEl = () => balanceEl('fermion') + await waitForText(fermionEl, '100', 5000) + t.pass('received mycoin transaction') t.end() })