From 4e8cb58faa6621e78a3b61259c4c6b6dff98bd92 Mon Sep 17 00:00:00 2001 From: Vit Stanislav Date: Mon, 22 May 2017 15:04:40 +0200 Subject: [PATCH] Refactor e2e tests to multitple files and use cucumber #202 --- .eslintrc.json | 11 + Gruntfile.js | 2 +- e2e-test/setup.sh => e2e-test-setup.sh | 0 e2e-test/conf.js | 35 -- e2e-test/spec.js | 429 ------------------ {e2e-test => features}/.eslintrc.json | 3 +- features/forging.feature | 5 + features/login.feature | 19 + features/menu.feature | 38 ++ features/send.feature | 22 + features/step_definitions/forging.step.js | 10 + features/step_definitions/generic.step.js | 117 +++++ features/step_definitions/hooks.js | 34 ++ features/step_definitions/login.step.js | 16 + features/step_definitions/menu.step.js | 21 + features/step_definitions/top.step.js | 8 + features/step_definitions/voting.step.js | 26 ++ features/support/accounts.js | 28 ++ features/support/util.js | 45 ++ features/top.feature | 15 + features/transactions.feature | 5 + features/voting.feature | 39 ++ package.json | 5 +- protractor.conf.js | 28 ++ .../delegateRegistration.pug | 4 +- src/components/delegates/delegates.pug | 4 +- src/components/delegates/vote.pug | 2 +- src/components/forging/forging.pug | 2 +- src/components/header/header.pug | 4 +- src/components/login/login.pug | 8 +- src/components/login/save.pug | 4 +- src/components/send/send.pug | 6 +- src/components/signVerify/signMessage.pug | 4 +- src/components/signVerify/verifyMessage.pug | 6 +- test/components/header/header.spec.js | 4 +- 35 files changed, 517 insertions(+), 492 deletions(-) rename e2e-test/setup.sh => e2e-test-setup.sh (100%) delete mode 100644 e2e-test/conf.js delete mode 100644 e2e-test/spec.js rename {e2e-test => features}/.eslintrc.json (64%) create mode 100644 features/forging.feature create mode 100644 features/login.feature create mode 100644 features/menu.feature create mode 100644 features/send.feature create mode 100644 features/step_definitions/forging.step.js create mode 100644 features/step_definitions/generic.step.js create mode 100644 features/step_definitions/hooks.js create mode 100644 features/step_definitions/login.step.js create mode 100644 features/step_definitions/menu.step.js create mode 100644 features/step_definitions/top.step.js create mode 100644 features/step_definitions/voting.step.js create mode 100644 features/support/accounts.js create mode 100644 features/support/util.js create mode 100644 features/top.feature create mode 100644 features/transactions.feature create mode 100644 features/voting.feature create mode 100644 protractor.conf.js diff --git a/.eslintrc.json b/.eslintrc.json index da5ab0878..257226a38 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -15,6 +15,17 @@ "func-names": "off", "global-require": "off", "new-cap": ["error", { "newIsCapExceptions": ["mnemonic"]}], + "new-cap": ["error", { + "newIsCapExceptions": ["mnemonic"], + "capIsNewExceptions" : [ + "When", + "Then", + "Given", + "After", + "Before" + ] + }], + "no-loop-func": "off", "no-plusplus": "off", "no-restricted-properties": "off", diff --git a/Gruntfile.js b/Gruntfile.js index 751e5c24e..4befcfaf2 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -9,7 +9,7 @@ module.exports = function (grunt) { fix: false, }, all: { - src: ['src/**/*.js', 'e2e-test/**/*.js', 'test/**/*.js', 'app/main.js', '*.js'], + src: ['src/**/*.js', 'features/**/*.js', 'test/**/*.js', 'app/main.js', '*.js'], }, }, }); diff --git a/e2e-test/setup.sh b/e2e-test-setup.sh similarity index 100% rename from e2e-test/setup.sh rename to e2e-test-setup.sh diff --git a/e2e-test/conf.js b/e2e-test/conf.js deleted file mode 100644 index ccacb24a8..000000000 --- a/e2e-test/conf.js +++ /dev/null @@ -1,35 +0,0 @@ -const SpecReporter = require('jasmine-spec-reporter').SpecReporter; - -exports.config = { - specs: ['spec.js'], - directConnect: true, - capabilities: { - browserName: 'chrome', - }, - onPrepare() { - const env = jasmine.getEnv(); - - // FIXME the following like can make the output cleaner - // but then shell return code is always 0 even if some tests fail. - // env.clearReporters(); - - env.addReporter(new SpecReporter({ - spec: { - displayStacktrace: false, - displayDuration: true, - }, - summary: { - displayDuration: true, - }, - })); - - jasmine.getEnv().addReporter({ - specStarted(result) { - jasmine.getEnv().currentSpec = result; - }, - specDone() { - jasmine.getEnv().currentSpec = null; - }, - }); - }, -}; diff --git a/e2e-test/spec.js b/e2e-test/spec.js deleted file mode 100644 index 10227aa9c..000000000 --- a/e2e-test/spec.js +++ /dev/null @@ -1,429 +0,0 @@ -const fs = require('fs'); - -const masterAccount = { - passphrase: 'wagon stock borrow episode laundry kitten salute link globe zero feed marble', - address: '16313739661670634666L', -}; - -const delegateAccount = { - passphrase: 'recipe bomb asset salon coil symbol tiger engine assist pact pumpkin visit', - address: '537318935439898807L', - username: 'genesis_17', -}; - -const emptyAccount = { - passphrase: 'stay undo beyond powder sand laptop grow gloom apology hamster primary arrive', - address: '5932438298200837883L', -}; - -const accountDelegateCandidate = { - passphrase: 'right cat soul renew under climb middle maid powder churn cram coconut', - address: '544792633152563672L', - username: 'test', -}; - -const account2ndPassphraseCandidate = { - passphrase: 'dolphin inhale planet talk insect release maze engine guilt loan attend lawn', - address: '4264113712245538326L', -}; - -const EC = protractor.ExpectedConditions; -const waitTime = 5000; - -function waitForElemAndCheckItsText(selector, text) { - const elem = element.all(by.css(selector)).get(0); - browser.wait(EC.presenceOf(elem), waitTime, `waiting for element '${selector}'`); - expect(elem.getText()).toEqual(text, `inside element "${selector}"`); -} - -function waitForElemAndClickIt(selector) { - const elem = element.all(by.css(selector)).get(0); - browser.wait(EC.presenceOf(elem), waitTime, `waiting for element '${selector}'`); - elem.click(); -} - -function waitForElemAndSendKeys(selector, keys) { - const elem = element.all(by.css(selector)).get(0); - browser.wait(EC.presenceOf(elem), waitTime, `waiting for element '${selector}'`); - elem.sendKeys(keys); -} - -function checkErrorMessage(message) { - waitForElemAndCheckItsText('send .md-input-message-animation', message); -} - -function launchApp() { - browser.ignoreSynchronization = true; - browser.driver.manage().window().setSize(1000, 1000); - browser.refresh(); - browser.get('http://localhost:8080#/?peerStack=localhost'); -} - -function login(account) { - launchApp(); - waitForElemAndSendKeys('input[type="password"]', account.passphrase); - element(by.css('.md-button.md-primary.md-raised')).click(); -} - -function logout() { - const logoutButton = element(by.css('.logout')); - browser.wait(EC.presenceOf(logoutButton), waitTime); - logoutButton.click(); -} - -function send(fromAccount, toAddress, amount) { - login(fromAccount); - const sendElem = element(by.css('send')); - const sendModalButton = element(by.css('md-content.header button.send')); - - browser.wait(EC.presenceOf(sendModalButton), waitTime); - sendModalButton.click(); - browser.wait(EC.presenceOf(sendElem), waitTime); - - // wait for modal animation to finish - browser.sleep(1000); - element(by.css('send input[name="recipient"]')).sendKeys(toAddress); - element(by.css('send input[name="amount"]')).sendKeys(`${amount}`); - element(by.css('send input[name="recipient"]')).click(); - const sendButton = element.all(by.css('send button.md-primary')).get(0); - // browser.wait(EC.presenceOf(sendButton), waitTime); - sendButton.click(); -} - -function checkAlertDialog(title, text) { - waitForElemAndCheckItsText('md-dialog h2', title); - waitForElemAndCheckItsText('md-dialog .md-dialog-content-body', text); - const okButton = element(by.css('md-dialog .md-button.md-ink-ripple')); - okButton.click(); - browser.sleep(500); -} - -function checkIsLoggedIn() { - waitForElemAndCheckItsText('.logout', 'LOGOUT'); -} - -function testLogin() { - login(masterAccount); - checkIsLoggedIn(); -} - -function testLogout() { - login(masterAccount); - - logout(); - waitForElemAndCheckItsText('.md-button.md-primary.md-raised', 'LOGIN'); -} - -function doPassphraseGenerationProcedure(callback) { - /** - * Generates a sequence of random pairs of x,y coordinates on the screen that simulates - * the movement of mouse to produce a pass phrase. - */ - for (let i = 0; i < 250; i++) { - browser.actions() - .mouseMove(element(by.css('body')), { - x: 500 + (Math.floor((((i % 2) * 2) - 1) * (249 + (Math.random() * 250)))), - y: 500 + (Math.floor((((i % 2) * 2) - 1) * (249 + (Math.random() * 250)))), - }).perform(); - browser.sleep(5); - } - - waitForElemAndCheckItsText('.dialog-save h2', 'Save your passphrase in a safe place!'); - - element(by.css('.dialog-save textarea.passphrase')).getText().then((passphrase) => { - expect(passphrase).toBeDefined(); - const passphraseWords = passphrase.split(' '); - expect(passphraseWords.length).toEqual(12); - const nextButton = element.all(by.css('.dialog-save .md-button.md-ink-ripple')).get(1); - nextButton.click(); - - element.all(by.css('.dialog-save p.passphrase span')).get(0).getText().then((firstPartOfPassphrase) => { - const missingWordIndex = firstPartOfPassphrase.length ? - firstPartOfPassphrase.split(' ').length : - 0; - element(by.css('.dialog-save input')).sendKeys(passphraseWords[missingWordIndex]); - const okButton = element.all(by.css('.dialog-save .md-button.md-ink-ripple')).get(2); - okButton.click(); - - callback(); - }); - }); -} - -function testNewAccount() { - launchApp(); - - waitForElemAndClickIt('.md-button.md-primary'); - doPassphraseGenerationProcedure(checkIsLoggedIn); -} - -function testAddress() { - login(masterAccount); - waitForElemAndCheckItsText('.address', masterAccount.address); -} - -function testPeer() { - launchApp(); - login(masterAccount); - waitForElemAndCheckItsText('.peer .md-title', 'Peer'); - waitForElemAndCheckItsText('.peer .value', 'localhost:4000'); -} - -function testChangeNetwork() { - launchApp(); - - waitForElemAndClickIt('form md-select'); - - const optionElem = element.all(by.css('md-select-menu md-option')).get(1); - browser.wait(EC.presenceOf(optionElem), waitTime); - optionElem.click(); - - waitForElemAndCheckItsText('form md-select-value .md-text', 'Testnet'); -} - -function testShowBalance() { - login(masterAccount); - - const balanceElem = element(by.css('lsk.balance')); - browser.wait(EC.presenceOf(balanceElem), waitTime); - expect(balanceElem.getText()).toMatch(/\d+(\.\d+)? LSK/); -} - -function testSend() { - const amount = 1.1; - send(masterAccount, delegateAccount.address, amount); - browser.sleep(1000); - checkAlertDialog('Success', `${amount} LSK was successfully transferred to ${delegateAccount.address}`); -} - -function testSendWithNotEnoughFunds() { - send(emptyAccount, delegateAccount.address, 10000); - checkErrorMessage('Insufficient funds'); -} - -function testSendWithInvalidAddress() { - send(masterAccount, emptyAccount.address.substr(0, 10), 1); - checkErrorMessage('Invalid'); -} - -function testShowTransactions() { - login(masterAccount); - waitForElemAndCheckItsText('transactions .title', 'Transactions'); - expect(element.all(by.css('transactions table tbody tr')).count()).toEqual(10); -} - -function testSignMessage() { - const message = 'Hello world'; - const result = - '-----BEGIN LISK SIGNED MESSAGE-----\n' + - '-----MESSAGE-----\n' + - 'Hello world\n' + - '-----PUBLIC KEY-----\n' + - 'c094ebee7ec0c50ebee32918655e089f6e1a604b83bcaa760293c61e0f18ab6f\n' + - '-----SIGNATURE-----\n' + - '079331d868678fd5f272f09d6dc8792fb21335aec42af7f11caadbfbc17d4707e7d7f343854b0' + - 'c619b647b81ba3f29b23edb4eaf382a47c534746bad4529560b48656c6c6f20776f726c64\n' + - '-----END LISK SIGNED MESSAGE-----'; - - login(masterAccount); - waitForElemAndClickIt('header .md-icon-button'); - browser.sleep(1000); - waitForElemAndClickIt('md-menu-item .md-button.sign-message'); - element(by.css('textarea[name="message"]')).sendKeys(message); - browser.sleep(1000); - expect(element(by.css('textarea[name="result"]')).getAttribute('value')).toEqual(result); -} - -function testVerifyMessage() { - const publicKey = 'c094ebee7ec0c50ebee32918655e089f6e1a604b83bcaa760293c61e0f18ab6f'; - const signature = '079331d868678fd5f272f09d6dc8792fb21335aec42af7f11caadbfbc17d4707e7d7f343854b0' + - 'c619b647b81ba3f29b23edb4eaf382a47c534746bad4529560b48656c6c6f20776f726c64'; - const message = 'Hello world'; - - login(masterAccount); - waitForElemAndClickIt('header .md-icon-button'); - browser.sleep(1000); - waitForElemAndClickIt('md-menu-item .md-button.verify-message'); - element(by.css('input[name="publicKey"]')).sendKeys(publicKey); - element(by.css('textarea[name="signature"]')).sendKeys(signature); - browser.sleep(1000); - expect(element(by.css('textarea[name="result"]')).getAttribute('value')).toEqual(message); -} - -function test2ndPassphrase() { - login(account2ndPassphraseCandidate); - waitForElemAndClickIt('header .md-icon-button'); - browser.sleep(1000); - waitForElemAndClickIt('md-menu-item .md-button.register-second-passphrase'); - doPassphraseGenerationProcedure(() => { - browser.sleep(500); - checkAlertDialog('Success', 'Your second passphrase was successfully registered.'); - }); -} - -function testDelegateRegistration() { - login(accountDelegateCandidate); - waitForElemAndClickIt('header .md-icon-button'); - browser.sleep(1000); - waitForElemAndClickIt('md-menu-item .md-button.register-as-delegate'); - browser.sleep(500); - element(by.css('md-dialog input[name="delegateName"]')).sendKeys(accountDelegateCandidate.username); - waitForElemAndClickIt('md-dialog button.md-primary'); - - browser.sleep(500); - checkAlertDialog('Success', 'Delegate registration was successfully submitted. It can take several seconds before it is confirmed.'); -} - -function testForgingCenter() { - login(delegateAccount); - waitForElemAndClickIt('main md-tab-item:nth-child(3)'); - - // FIXME: there is some bug in forging center that makes it really slow to load - // should be fixed by @alihaghighatkhah in #174 - browser.sleep(5000); - - waitForElemAndCheckItsText('forging md-card .title', delegateAccount.username); - waitForElemAndCheckItsText('forging md-card md-card-title .md-title', 'Forged Blocks'); -} - -function testViewDelegates() { - login(masterAccount); - waitForElemAndClickIt('main md-tab-item:nth-child(2)'); - waitForElemAndCheckItsText('delegates table thead tr th:nth-child(1)', 'Vote'); - waitForElemAndCheckItsText('delegates table tbody tr td:nth-child(2)', '1'); - - expect(element.all(by.css('delegates table tbody tr')).count()).toEqual(20); -} - -function testSearchDelegates() { - login(masterAccount); - waitForElemAndClickIt('main md-tab-item:nth-child(2)'); - waitForElemAndCheckItsText('delegates table thead tr th:nth-child(1)', 'Vote'); - element(by.css('delegates input[name="name"]')).sendKeys(delegateAccount.username); - browser.sleep(500); - waitForElemAndCheckItsText('delegates table tbody tr td:nth-child(3)', delegateAccount.username); - - expect(element.all(by.css('delegates table tbody tr')).count()).toEqual(1); -} - -function testViewVotes() { - login(masterAccount); - waitForElemAndClickIt('main md-tab-item:nth-child(2)'); - waitForElemAndCheckItsText('delegates md-menu button span.ng-scope', 'MY VOTES (101)'); - waitForElemAndClickIt('delegates md-menu button'); - browser.sleep(500); - expect(element.all(by.css('md-menu-item.vote-list-item')).count()).toEqual(101); -} - -function testVoteFromTable() { - login(accountDelegateCandidate); - waitForElemAndClickIt('main md-tab-item:nth-child(2)'); - waitForElemAndClickIt('delegates tr:nth-child(3) md-checkbox'); - waitForElemAndClickIt('delegates tr:nth-child(5) md-checkbox'); - waitForElemAndClickIt('delegates tr:nth-child(8) md-checkbox'); - element.all(by.css('delegates md-card-title button.vote-button')).last().click(); - waitForElemAndClickIt('vote md-dialog-actions button.md-primary'); - waitForElemAndCheckItsText('md-toast', 'Voting successful'); -} - -function testVoteFromDialog() { - login(accountDelegateCandidate); - waitForElemAndClickIt('main md-tab-item:nth-child(2)'); - waitForElemAndClickIt('delegates tr:nth-child(3) md-checkbox'); - waitForElemAndClickIt('delegates tr:nth-child(3) md-checkbox'); - element.all(by.css('delegates md-card-title button.vote-button')).last().click(); - element.all(by.css('md-autocomplete-wrap input')).get(0).sendKeys('genesis_7'); - waitForElemAndClickIt('md-autocomplete-parent-scope'); - element.all(by.css('md-autocomplete-wrap input')).get(0).sendKeys('genesis_7'); - waitForElemAndClickIt('md-autocomplete-parent-scope'); - waitForElemAndClickIt('vote md-dialog-actions button.md-primary'); - waitForElemAndCheckItsText('md-toast', 'Voting successful'); -} - -function testUnvote() { - login(masterAccount); - waitForElemAndClickIt('main md-tab-item:nth-child(2)'); - waitForElemAndClickIt('delegates tr:nth-child(3) md-checkbox'); - waitForElemAndClickIt('delegates tr:nth-child(5) md-checkbox'); - waitForElemAndClickIt('delegates tr:nth-child(8) md-checkbox'); - element.all(by.css('delegates md-card-title button.vote-button')).last().click(); - waitForElemAndClickIt('vote md-dialog-actions button.md-primary'); - waitForElemAndCheckItsText('md-toast', 'Voting successful'); -} - -function writeScreenShot(data, filename) { - const stream = fs.createWriteStream(filename); - stream.write(new Buffer(data, 'base64')); - stream.end(); -} - -function slugify(text) { - return text.toString().toLowerCase() - .replace(/\s+/g, '-') // Replace spaces with - - .replace(/[^\w-]+/g, '') // Remove all non-word chars - .replace(/--+/g, '-') // Replace multiple - with single - - .replace(/^-+/, '') // Trim - from start of text - .replace(/-+$/, ''); // Trim - from end of text -} - -function takeScreenshotAfterFail() { - const currentSpec = jasmine.getEnv().currentSpec; - const specSlug = slugify([currentSpec.id, currentSpec.description].join(' ')); - if (currentSpec.failedExpectations.length) { - browser.takeScreenshot().then((png) => { - const dirName = 'e2e-test-screenshots'; - if (!fs.existsSync(dirName)) { - fs.mkdirSync(dirName); - } - writeScreenShot(png, `${dirName}/${specSlug}.png`); - }); - } -} - - -describe('Lisk Nano', () => { - afterEach(takeScreenshotAfterFail); - - describe('Login page', () => { - it('should allow to login', testLogin); - it('should allow to change network', testChangeNetwork); - it('should allow to create a new account', testNewAccount); - }); - - describe('Main page top area', () => { - it('should allow to logout', testLogout); - it('should show peer', testPeer); - it('should show address', testAddress); - it('should show balance', testShowBalance); - }); - - fdescribe('Top right menu', () => { - it('should allow to register second passphrase', test2ndPassphrase); - it('should allow to register as delegate', testDelegateRegistration); - it('should allow to sign message', testSignMessage); - it('should allow to verify message', testVerifyMessage); - }); - - describe('Send dialog', () => { - it('should allow to do a send when enough funds and correct address form', testSend); - it('should not allow to do a send when not enough funds', testSendWithNotEnoughFunds); - it('should not allow to do a send when invalid address', testSendWithInvalidAddress); - }); - - describe('Transactions tab', () => { - it('should show transactions', testShowTransactions); - }); - - describe('Forging tab', () => { - it('should allow to view forging center if account is delegate', testForgingCenter); - }); - - describe('Voting tab', () => { - it('should allow to view delegates', testViewDelegates); - it('should allow to search delegates', testSearchDelegates); - it('should allow to view my votes', testViewVotes); - it('should allow to select delegates in the "Voting" tab and vote for them', testVoteFromTable); - it('should allow to select delegates in the "Vote" dialog and vote for them', testVoteFromDialog); - it('should allow to remove votes form delegates', testUnvote); - }); -}); diff --git a/e2e-test/.eslintrc.json b/features/.eslintrc.json similarity index 64% rename from e2e-test/.eslintrc.json rename to features/.eslintrc.json index f4769b822..e3a50b129 100644 --- a/e2e-test/.eslintrc.json +++ b/features/.eslintrc.json @@ -4,11 +4,10 @@ "import" ], "env": { - "jasmine": true, "protractor": true }, "rules": { - "new-cap": ["error", { "newIsCapExceptions": ["mnemonic"]}], + "import/no-extraneous-dependencies": "off", "no-underscore-dangle": "off" } } diff --git a/features/forging.feature b/features/forging.feature new file mode 100644 index 000000000..ea18f43e7 --- /dev/null +++ b/features/forging.feature @@ -0,0 +1,5 @@ +Feature: Forging tab + Scenario: should allow to view forging center if account is delegate + Given I'm logged in as "delegate" + When I click tab number 3 + Then I should see forging center diff --git a/features/login.feature b/features/login.feature new file mode 100644 index 000000000..ba41f6d49 --- /dev/null +++ b/features/login.feature @@ -0,0 +1,19 @@ +Feature: Login page + Scenario: should allow to login + Given I'm on login page + When I fill in "wagon stock borrow episode laundry kitten salute link globe zero feed marble" to "passphrase" field + And I click "login button" + Then I should be logged in + + Scenario: should allow to change network + Given I'm on login page + When I select option no. 2 from "network" select + Then the option "Testnet" is selected in "network" select + + Scenario: should allow to create a new account + Given I'm on login page + When I click "new account button" + And I 250 times move mouse randomly + And I remember passphrase, click "yes its save button", fill in missing word + And I click "ok button" + Then I should be logged in diff --git a/features/menu.feature b/features/menu.feature new file mode 100644 index 000000000..41cfc819f --- /dev/null +++ b/features/menu.feature @@ -0,0 +1,38 @@ +Feature: Top right menu + Scenario: should allow to set 2nd passphrase + Given I'm logged in as "second passphrase candidate" + When I click "register second passphrase" in main menu + And I 250 times move mouse randomly + And I remember passphrase, click "yes its save button", fill in missing word + And I click "ok button" + Then I should see alert dialog with title "Success" and text "Your second passphrase was successfully registered." + + Scenario: should allow to register a delegate + Given I'm logged in as "delegate candidate" + When I click "register as delegate" in main menu + And I fill in "test" to "username" field + And I click "register button" + Then I should see alert dialog with title "Success" and text "Account was successfully registered as delegate." + + Scenario: should allow to sign message + Given I'm logged in as "any account" + When I click "sign message" in main menu + And I fill in "Hello world" to "message" field + Then I should see in "result" field: + """ + -----BEGIN LISK SIGNED MESSAGE----- + -----MESSAGE----- + Hello world + -----PUBLIC KEY----- + c094ebee7ec0c50ebee32918655e089f6e1a604b83bcaa760293c61e0f18ab6f + -----SIGNATURE----- + 079331d868678fd5f272f09d6dc8792fb21335aec42af7f11caadbfbc17d4707e7d7f343854b0c619b647b81ba3f29b23edb4eaf382a47c534746bad4529560b48656c6c6f20776f726c64 + -----END LISK SIGNED MESSAGE----- + """ + + Scenario: should allow to verify message + Given I'm logged in as "any account" + When I click "verify message" in main menu + And I fill in "c094ebee7ec0c50ebee32918655e089f6e1a604b83bcaa760293c61e0f18ab6f" to "public key" field + And I fill in "079331d868678fd5f272f09d6dc8792fb21335aec42af7f11caadbfbc17d4707e7d7f343854b0c619b647b81ba3f29b23edb4eaf382a47c534746bad4529560b48656c6c6f20776f726c64" to "signature" field + Then I should see "Hello world" in "result" field diff --git a/features/send.feature b/features/send.feature new file mode 100644 index 000000000..68070772f --- /dev/null +++ b/features/send.feature @@ -0,0 +1,22 @@ +Feature: Send dialog + Scenario: should allow to send when enough funds and correct address form + Given I'm logged in as "genesis" + When I click "send button" + And I fill in "1" to "amount" field + And I fill in "537318935439898807L" to "recipient" field + And I click "submit button" + Then I should see alert dialog with title "Success" and text "1 LSK was successfully transferred to 537318935439898807L" + + Scenario: should not allow to send when not enough funds + Given I'm logged in as "empty account" + When I click "send button" + And I fill in "1" to "amount" field + And I fill in "537318935439898807L" to "recipient" field + Then I should see "Insufficient funds" error message + + Scenario: should not allow to send when invalid address + Given I'm logged in as "any account" + When I click "send button" + And I fill in "1243409812409" to "recipient" field + And I fill in "1" to "amount" field + Then I should see "Invalid" error message diff --git a/features/step_definitions/forging.step.js b/features/step_definitions/forging.step.js new file mode 100644 index 000000000..5d1076510 --- /dev/null +++ b/features/step_definitions/forging.step.js @@ -0,0 +1,10 @@ +const { defineSupportCode } = require('cucumber'); +const { waitForElemAndCheckItsText } = require('../support/util.js'); + + +defineSupportCode(({ Then }) => { + Then('I should see forging center', (callback) => { + waitForElemAndCheckItsText('forging .delegate-name', 'genesis_17', callback); + waitForElemAndCheckItsText('forging md-card.forged-blocks md-card-title .md-title', 'Forged Blocks', callback); + }); +}); diff --git a/features/step_definitions/generic.step.js b/features/step_definitions/generic.step.js new file mode 100644 index 000000000..16332e770 --- /dev/null +++ b/features/step_definitions/generic.step.js @@ -0,0 +1,117 @@ +const { defineSupportCode } = require('cucumber'); +const chai = require('chai'); +const chaiAsPromised = require('chai-as-promised'); +const { + waitForElemAndCheckItsText, + waitForElemAndClickIt, + waitForElemAndSendKeys, + checkAlertDialog, + waitTime, +} = require('../support/util.js'); +const accounts = require('../support/accounts.js'); + +chai.use(chaiAsPromised); +const expect = chai.expect; +const EC = protractor.ExpectedConditions; + +defineSupportCode(({ Given, When, Then, setDefaultTimeout }) => { + setDefaultTimeout(20 * 1000); + + When('I fill in "{value}" to "{fieldName}" field', (value, fieldName, callback) => { + const selectorClass = `.${fieldName.replace(/ /g, '-')}`; + waitForElemAndSendKeys(`input${selectorClass}, textarea${selectorClass}`, value, callback); + }); + + Then('I should see "{value}" in "{fieldName}" field', (value, fieldName, callback) => { + const elem = element(by.css(`.${fieldName.replace(/ /g, '-')}`)); + expect(elem.getAttribute('value')).to.eventually.equal(value) + .and.notify(callback); + }); + + When('I click "{elementName}"', (elementName, callback) => { + const selector = `.${elementName.replace(/\s+/g, '-')}`; + waitForElemAndClickIt(selector, callback); + }); + + When('I click tab number {index}', (index, callback) => { + waitForElemAndClickIt(`main md-tab-item:nth-child(${index})`, callback); + }); + + When('I select option no. {index} from "{selectName}" select', (index, selectName, callback) => { + waitForElemAndClickIt(`md-select.${selectName}`); + const optionElem = element.all(by.css('md-select-menu md-option')).get(index - 1); + browser.wait(EC.presenceOf(optionElem), waitTime); + optionElem.click().then(callback); + }); + + Then('the option "{optionText}" is selected in "{selectName}" select', (optionText, selectName, callback) => { + waitForElemAndCheckItsText(`.${selectName} md-select-value .md-text`, optionText, callback); + }); + + Then('I should see toast saying "{text}"', (text, callback) => { + waitForElemAndCheckItsText('md-toast', text, callback); + }); + + Then('I should see alert dialog with title "{title}" and text "{text}"', (title, text, callback) => { + checkAlertDialog(title, text, callback); + }); + + Then('I should see table with {lineCount} lines', (lineCount, callback) => { + browser.sleep(3500); + expect(element.all(by.css('table tbody tr')).count()).to.eventually.equal(parseInt(lineCount, 10)) + .and.notify(callback); + }); + + Then('I should see "{elementName}"', (elementName, callback) => { + expect(element.all(by.css(`.${elementName.replace(/ /g, '-')}`)).count()).to.eventually.equal(1) + .and.notify(callback); + }); + + Then('I should see "{text}" error message', (text, callback) => { + waitForElemAndCheckItsText('.md-input-message-animation', text, callback); + }); + + Given('I\'m logged in as "{accountName}"', (accountName, callback) => { + browser.ignoreSynchronization = true; + browser.driver.manage().window().setSize(1000, 1000); + browser.driver.get('about:blank'); + browser.get('http://localhost:8080/#/?peerStack=localhost'); + waitForElemAndSendKeys('.passphrase', accounts[accountName].passphrase); + waitForElemAndClickIt('.md-button.md-primary.md-raised', callback); + }); + + When('I {iterations} times move mouse randomly', (iterations) => { + /** + * Generates a sequence of random pairs of x,y coordinates on the screen that simulates + * the movement of mouse to produce a pass phrase. + */ + for (let i = 0; i < iterations; i++) { + browser.actions() + .mouseMove(element(by.css('body')), { + x: 500 + (Math.floor((((i % 2) * 2) - 1) * (249 + (Math.random() * 250)))), + y: 500 + (Math.floor((((i % 2) * 2) - 1) * (249 + (Math.random() * 250)))), + }).perform(); + browser.sleep(5); + } + }); + + When('I remember passphrase, click "{nextButtonSelector}", fill in missing word', (nextButtonSelector, callback) => { + waitForElemAndCheckItsText('.dialog-save h2', 'Save your passphrase in a safe place!'); + + element(by.css('.dialog-save textarea.passphrase')).getText().then((passphrase) => { + // eslint-disable-next-line no-unused-expressions + expect(passphrase).to.not.be.undefined; + const passphraseWords = passphrase.split(' '); + expect(passphraseWords.length).to.equal(12); + waitForElemAndClickIt(`.${nextButtonSelector.replace(/ /g, '-')}`); + + element.all(by.css('.dialog-save p.passphrase span')).get(0).getText().then((firstPartOfPassphrase) => { + const missingWordIndex = firstPartOfPassphrase.length ? + firstPartOfPassphrase.split(' ').length : + 0; + element(by.css('.dialog-save input')).sendKeys(passphraseWords[missingWordIndex]).then(callback); + }); + }); + }); +}); + diff --git a/features/step_definitions/hooks.js b/features/step_definitions/hooks.js new file mode 100644 index 000000000..e41604ec8 --- /dev/null +++ b/features/step_definitions/hooks.js @@ -0,0 +1,34 @@ +const { defineSupportCode } = require('cucumber'); +const fs = require('fs'); + +function writeScreenShot(data, filename) { + const stream = fs.createWriteStream(filename); + stream.write(new Buffer(data, 'base64')); + stream.end(); +} + +function slugify(text) { + return text.toString().toLowerCase() + .replace(/\s+/g, '-') // Replace spaces with - + .replace(/[^\w-]+/g, '') // Remove all non-word chars + .replace(/--+/g, '-') // Replace multiple - with single - + .replace(/^-+/, '') // Trim - from start of text + .replace(/-+$/, ''); // Trim - from end of text +} + +defineSupportCode(({ After }) => { + After((scenario, callback) => { + if (scenario.isFailed()) { + const screnarioSlug = slugify([scenario.scenario.feature.name, scenario.scenario.name].join(' ')); + browser.takeScreenshot().then((screenshotBuffer) => { + if (!fs.existsSync(browser.params.screenshotFolder)) { + fs.mkdirSync(browser.params.screenshotFolder); + } + writeScreenShot(screenshotBuffer, `${browser.params.screenshotFolder}/${screnarioSlug}.png`); + callback(); + }); + } else { + callback(); + } + }); +}); diff --git a/features/step_definitions/login.step.js b/features/step_definitions/login.step.js new file mode 100644 index 000000000..42ee984b1 --- /dev/null +++ b/features/step_definitions/login.step.js @@ -0,0 +1,16 @@ +const { defineSupportCode } = require('cucumber'); +const { waitForElemAndCheckItsText } = require('../support/util.js'); + +defineSupportCode(({ Given, Then }) => { + Given('I\'m on login page', (callback) => { + browser.ignoreSynchronization = true; + browser.driver.manage().window().setSize(1000, 1000); + browser.driver.get('about:blank'); + browser.get('http://localhost:8080/#/?peerStack=localhost').then(callback); + }); + + Then('I should be logged in', (callback) => { + waitForElemAndCheckItsText('.logout-button', 'LOGOUT', callback); + }); +}); + diff --git a/features/step_definitions/menu.step.js b/features/step_definitions/menu.step.js new file mode 100644 index 000000000..25346b76e --- /dev/null +++ b/features/step_definitions/menu.step.js @@ -0,0 +1,21 @@ +const { defineSupportCode } = require('cucumber'); +const chai = require('chai'); +const chaiAsPromised = require('chai-as-promised'); +const { waitForElemAndClickIt } = require('../support/util.js'); + +chai.use(chaiAsPromised); +const expect = chai.expect; + +defineSupportCode(({ When, Then }) => { + When('I click "{itemSelector}" in main menu', (itemSelector, callback) => { + waitForElemAndClickIt('header .md-icon-button'); + browser.sleep(1000); + waitForElemAndClickIt(`md-menu-item .md-button.${itemSelector.replace(/ /g, '-')}`, callback); + }); + + Then('I should see in "{fieldName}" field:', (fieldName, value, callback) => { + const elem = element(by.css(`.${fieldName.replace(/ /g, '-')}`)); + expect(elem.getAttribute('value')).to.eventually.equal(value) + .and.notify(callback); + }); +}); diff --git a/features/step_definitions/top.step.js b/features/step_definitions/top.step.js new file mode 100644 index 000000000..2f37b8504 --- /dev/null +++ b/features/step_definitions/top.step.js @@ -0,0 +1,8 @@ +const { defineSupportCode } = require('cucumber'); +const { waitForElemAndCheckItsText } = require('../support/util.js'); + +defineSupportCode(({ Then }) => { + Then('I should be on login page', (callback) => { + waitForElemAndCheckItsText('.login-button', 'LOGIN', callback); + }); +}); diff --git a/features/step_definitions/voting.step.js b/features/step_definitions/voting.step.js new file mode 100644 index 000000000..bfcbced2d --- /dev/null +++ b/features/step_definitions/voting.step.js @@ -0,0 +1,26 @@ +const { defineSupportCode } = require('cucumber'); +const chai = require('chai'); +const chaiAsPromised = require('chai-as-promised'); +const { waitForElemAndClickIt } = require('../support/util.js'); + +chai.use(chaiAsPromised); +const expect = chai.expect; + +defineSupportCode(({ When, Then }) => { + When('I click checkbox on table row no. {index}', (index, callback) => { + waitForElemAndClickIt(`delegates tr:nth-child(${index}) md-checkbox`, callback); + }); + + When('Search twice for "{searchTerm}" in vote dialog', (searchTerm, callback) => { + element.all(by.css('md-autocomplete-wrap input')).get(0).sendKeys(searchTerm); + waitForElemAndClickIt('ul.md-autocomplete-suggestions li:nth-child(1) md-autocomplete-parent-scope'); + element.all(by.css('md-autocomplete-wrap input')).get(0).sendKeys(searchTerm); + waitForElemAndClickIt('ul.md-autocomplete-suggestions li:nth-child(1) md-autocomplete-parent-scope', callback); + }); + + Then('I should see delegates list with {count} lines', (count, callback) => { + expect(element.all(by.css('md-menu-item.vote-list-item')).count()) + .to.eventually.equal(parseInt(count, 10)) + .and.notify(callback); + }); +}); diff --git a/features/support/accounts.js b/features/support/accounts.js new file mode 100644 index 000000000..524e8a6aa --- /dev/null +++ b/features/support/accounts.js @@ -0,0 +1,28 @@ +const accounts = { + genesis: { + passphrase: 'wagon stock borrow episode laundry kitten salute link globe zero feed marble', + address: '16313739661670634666L', + }, + delegate: { + passphrase: 'recipe bomb asset salon coil symbol tiger engine assist pact pumpkin visit', + address: '537318935439898807L', + username: 'genesis_17', + }, + 'empty account': { + passphrase: 'stay undo beyond powder sand laptop grow gloom apology hamster primary arrive', + address: '5932438298200837883L', + }, + 'delegate candidate': { + passphrase: 'right cat soul renew under climb middle maid powder churn cram coconut', + address: '544792633152563672L', + username: 'test', + }, + 'second passphrase candidate': { + passphrase: 'dolphin inhale planet talk insect release maze engine guilt loan attend lawn', + address: '4264113712245538326L', + }, +}; +accounts['any account'] = accounts.genesis; + + +module.exports = accounts; diff --git a/features/support/util.js b/features/support/util.js new file mode 100644 index 000000000..cd2b3cf14 --- /dev/null +++ b/features/support/util.js @@ -0,0 +1,45 @@ +const chai = require('chai'); +const chaiAsPromised = require('chai-as-promised'); + +chai.use(chaiAsPromised); +const expect = chai.expect; +const EC = protractor.ExpectedConditions; +const waitTime = 4000; + +function waitForElemAndCheckItsText(selector, text, callback) { + const elem = element(by.css(selector)); + browser.wait(EC.presenceOf(elem), waitTime, `waiting for element '${selector}'`); + expect(elem.getText()).to.eventually.equal(text, `inside element "${selector}"`) + .and.notify(callback || (() => {})); +} + +function waitForElemAndClickIt(selector, callback) { + const elem = element(by.css(selector)); + browser.wait(EC.presenceOf(elem), waitTime, `waiting for element '${selector}'`); + elem.click().then(() => { + if (callback) callback(); + }); +} + +function waitForElemAndSendKeys(selector, keys, callback) { + const elem = element(by.css(selector)); + browser.wait(EC.presenceOf(elem), waitTime, `waiting for element '${selector}'`); + elem.sendKeys(keys).then(() => { + if (callback) callback(); + }); +} + +function checkAlertDialog(title, text, callback) { + waitForElemAndCheckItsText('md-dialog h2', title); + waitForElemAndCheckItsText('md-dialog .md-dialog-content-body', text, () => { + if (callback) callback(); + }); +} + +module.exports = { + waitForElemAndCheckItsText, + waitForElemAndClickIt, + waitForElemAndSendKeys, + checkAlertDialog, + waitTime, +}; diff --git a/features/top.feature b/features/top.feature new file mode 100644 index 000000000..08272c2af --- /dev/null +++ b/features/top.feature @@ -0,0 +1,15 @@ +Feature: Main page top area + Scenario: should allow to logout + Given I'm logged in as "any account" + When I click "logout button" + Then I should be on login page + Scenario: should show peer + Given I'm logged in as "any account" + Then I should see "peer" + Scenario: should show address + Given I'm logged in as "any account" + Then I should see "address" + Scenario: should show balance + Given I'm logged in as "any account" + Then I should see "balance" + diff --git a/features/transactions.feature b/features/transactions.feature new file mode 100644 index 000000000..62c89f070 --- /dev/null +++ b/features/transactions.feature @@ -0,0 +1,5 @@ +Feature: Transactions tab + Scenario: should show transactions + Given I'm logged in as "genesis" + When I click tab number 1 + Then I should see table with 10 lines diff --git a/features/voting.feature b/features/voting.feature new file mode 100644 index 000000000..c4b9a8594 --- /dev/null +++ b/features/voting.feature @@ -0,0 +1,39 @@ +Feature: Voting tab + Scenario: should allow to view delegates + Given I'm logged in as "any account" + When I click tab number 2 + Then I should see table with 20 lines + Scenario: should allow to search delegates + Given I'm logged in as "any account" + When I click tab number 2 + And I fill in "genesis_42" to "search" field + Then I should see table with 1 lines + Scenario: should allow to view my votes + Given I'm logged in as "genesis" + When I click tab number 2 + And I click "my votes button" + Then I should see delegates list with 101 lines + Scenario: should allow to select delegates in the "Voting" tab and vote for them + Given I'm logged in as "delegate candidate" + When I click tab number 2 + And I click checkbox on table row no. 3 + And I click checkbox on table row no. 5 + And I click checkbox on table row no. 8 + And I click "vote button" + And I click "submit button" + Then I should see toast saying "Voting successful" + Scenario: should allow to select delegates in the "Vote" dialog and vote for them + Given I'm logged in as "delegate candidate" + When I click tab number 2 + And I click "vote button" + And Search twice for "genesis_7" in vote dialog + And I click "submit button" + Then I should see toast saying "Voting successful" + Scenario: should allow to remove votes form delegates + Given I'm logged in as "genesis" + When I click tab number 2 + And I click checkbox on table row no. 3 + And I click checkbox on table row no. 5 + And I click "vote button" + And I click "submit button" + Then I should see toast saying "Voting successful" diff --git a/package.json b/package.json index f49509fa9..ef65d087e 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "scripts": { "build": "webpack --profile --progress --display-modules --display-exclude --display-chunks --display-cached --display-cached-assets", "dev": "webpack-dev-server --host 0.0.0.0 --profile --progress", - "e2e-test": "protractor e2e-test/conf.js", + "e2e-test": "protractor protractor.conf.js", "test": "grunt eslint && export NODE_ENV=test && karma start", "test-live": "export NODE_ENV=test && export LIVE=true && karma start", "start": "electron app", @@ -52,8 +52,10 @@ "babel-plugin-syntax-trailing-function-commas": "=6.22.0", "babel-preset-es2015": "=6.9.0", "chai": "=3.5.0", + "chai-as-promised": "=6.0.0", "clean-webpack-plugin": "=0.1.9", "css-loader": "=0.23.1", + "cucumber": "=2.2.0", "electron": "=1.6.2", "electron-builder": "=16.8.3", "eslint-config-airbnb": "=14.1.0", @@ -90,6 +92,7 @@ "phantomjs": "=2.1.7", "phantomjs-prebuilt": "=2.1.14", "protractor": "=5.1.1", + "protractor-cucumber-framework": "=3.1.0", "pug": "=2.0.0-beta11", "pug-cli": "=1.0.0-alpha6", "pug-loader": "=2.3.0", diff --git a/protractor.conf.js b/protractor.conf.js new file mode 100644 index 000000000..6b2bf33fd --- /dev/null +++ b/protractor.conf.js @@ -0,0 +1,28 @@ +// const SpecReporter = require('jasmine-spec-reporter').SpecReporter; + +exports.config = { + specs: [ + 'features/*.feature', + ], + + directConnect: true, + capabilities: { + browserName: 'chrome', + }, + framework: 'custom', + frameworkPath: require.resolve('protractor-cucumber-framework'), + + baseURL: 'http://localhost:8080/', + + cucumberOpts: { + require: 'features/step_definitions/*.js', + tags: false, + format: 'pretty', + profile: false, + 'no-source': true, + }, + + params: { + screenshotFolder: 'e2e-test-screenshots', + }, +}; diff --git a/src/components/delegateRegistration/delegateRegistration.pug b/src/components/delegateRegistration/delegateRegistration.pug index 91dcb73dc..e1d0d3b92 100644 --- a/src/components/delegateRegistration/delegateRegistration.pug +++ b/src/components/delegateRegistration/delegateRegistration.pug @@ -8,7 +8,7 @@ div.dialog-delegate-registration(aria-label='Vote for delegates') div md-input-container.md-block label Delegate name - input(type='text', name='delegateName', ng-model='$ctrl.form.name', required, ng-disabled='$ctrl.loading', md-autofocus) + input.username(type='text', name='delegateName', ng-model='$ctrl.form.name', required, ng-disabled='$ctrl.loading', md-autofocus) div(ng-messages='delegateRegistrationForm.name.$error') div(ng-message='required') Required md-input-container.md-block(ng-if='$ctrl.account.get().secondSignature') @@ -27,4 +27,4 @@ div.dialog-delegate-registration(aria-label='Vote for delegates') md-dialog-actions(layout='row') md-button.md-raised.md-secondary(ng-disabled='$ctrl.loading', ng-click='$ctrl.cancel(delegateRegistrationForm)') {{ 'Cancel' }} span(flex) - md-button.md-raised.md-primary(ng-disabled='!delegateRegistrationForm.$valid || $ctrl.loading', type='submit') {{ $ctrl.loading ? 'Registering...' : 'Register' }} + md-button.md-raised.md-primary.register-button(ng-disabled='!delegateRegistrationForm.$valid || $ctrl.loading', type='submit') {{ $ctrl.loading ? 'Registering...' : 'Register' }} diff --git a/src/components/delegates/delegates.pug b/src/components/delegates/delegates.pug index cec51219b..3f7e49d4a 100644 --- a/src/components/delegates/delegates.pug +++ b/src/components/delegates/delegates.pug @@ -19,7 +19,7 @@ div.offline-hide span.md-title(layout='row') md-input-container.md-block label Search - input(type='text', name='name', ng-model='search', ng-model-options='{ debounce: 200 }') + input.search(type='text', name='name', ng-model='search', ng-model-options='{ debounce: 200 }') i.material-icons.search-append(ng-click='$ctrl.clearSearch()', ng-if='search') close i.material-icons.search-append(ng-hide='search') search span.pull-right.right-action-buttons @@ -27,7 +27,7 @@ div.offline-hide i.material-icons list span Input Names md-menu.pull-right.right-action-buttons - md-button.pull-right(ng-click='$mdOpenMenu()', ng-disabled='$ctrl.votedList.length == 0') + md-button.pull-right.my-votes-button(ng-click='$mdOpenMenu()', ng-disabled='$ctrl.votedList.length == 0') i.material-icons visibility span My votes ({{$ctrl.votedList.length}}) md-menu-content(width='4') diff --git a/src/components/delegates/vote.pug b/src/components/delegates/vote.pug index c2813d5fb..47493d773 100644 --- a/src/components/delegates/vote.pug +++ b/src/components/delegates/vote.pug @@ -34,4 +34,4 @@ div.dialog-vote(aria-label='Vote for delegates') md-dialog-actions(layout='row') md-button(ng-click="$ctrl.$mdDialog.cancel()") Cancel span(flex) - md-button.md-primary(ng-disabled='!$ctrl.canVote()', ng-click="$ctrl.vote()") {{$ctrl.votingInProgress ? 'Voting...' : 'Confirm vote'}} + md-button.md-primary.submit-button(ng-disabled='!$ctrl.canVote()', ng-click="$ctrl.vote()") {{$ctrl.votingInProgress ? 'Voting...' : 'Confirm vote'}} diff --git a/src/components/forging/forging.pug b/src/components/forging/forging.pug index ee496c9a4..abac348d5 100644 --- a/src/components/forging/forging.pug +++ b/src/components/forging/forging.pug @@ -10,7 +10,7 @@ md-card.offline-hide md-card(flex-gt-xs=100, layout-padding) md-card-title md-card-title-text - span.md-title {{$ctrl.delegate.username}} + span.md-title.delegate-name {{$ctrl.delegate.username}} span(md-position-mode='target-right target') span {{$ctrl.statistics.total | lsk | number:2 }} LSK Earned md-content(layout='column', layout-gt-xs='row', ng-if='$ctrl.delegate.username') diff --git a/src/components/header/header.pug b/src/components/header/header.pug index 66a33c647..8f83b1465 100644 --- a/src/components/header/header.pug +++ b/src/components/header/header.pug @@ -1,8 +1,8 @@ md-content.header(layout='row', layout-align='center center', layout-padding) img.logo(src=require('../../assets/images/LISK-nano.png')) div(flex) - md-button.md-raised.md-primary.send(data-show-send-modal, ng-if='$root.logged') Send - md-button.md-raised.md-secondary.logout(ng-click='$root.logout()', ng-if='$root.logged') Logout + md-button.md-raised.md-primary.send-button(data-show-send-modal, ng-if='$root.logged') Send + md-button.md-raised.md-secondary.logout-button(ng-click='$root.logout()', ng-if='$root.logged') Logout md-menu.top-menu(ng-if='$root.logged', md-position-mode='target-right target', md-offset='14 0') md-button.md-icon-button(ng-click='$mdOpenMenu()') i.material-icons more_vert diff --git a/src/components/login/login.pug b/src/components/login/login.pug index 10806dfc7..98ad3406e 100644 --- a/src/components/login/login.pug +++ b/src/components/login/login.pug @@ -6,7 +6,7 @@ md-card form(ng-submit='$ctrl.passConfirmSubmit()') md-input-container.md-block label.select Network - md-select(ng-model='$ctrl.network', aria-label='Peer') + md-select.network(ng-model='$ctrl.network', aria-label='Network') md-option(ng-repeat='network in $ctrl.networks', ng-value='network') {{ network.name }} div(ng-if='$ctrl.network.custom') md-input-container.md-block @@ -14,11 +14,11 @@ md-card input(type="text", ng-model="$ctrl.network.address") md-input-container.md-block(md-is-error='$ctrl.valid === 0') label.pass Enter your passphrase - input(type="{{ $ctrl.show_passphrase ? 'text' : 'password' }}", ng-model='$ctrl.input_passphrase', ng-disabled='$ctrl.generatingNewPassphrase', autofocus) + input.passphrase(type="{{ $ctrl.show_passphrase ? 'text' : 'password' }}", ng-model='$ctrl.input_passphrase', ng-disabled='$ctrl.generatingNewPassphrase', autofocus) md-input-container.md-block md-checkbox.md-primary(ng-model="$ctrl.show_passphrase", aria-label="Show passphrase") Show passphrase md-content(layout='row', layout-align='center center') // md-button(ng-disabled='$ctrl.generatingNewPassphrase', ng-click='$ctrl.devTestAccount()') Dev Test Account - md-button.md-primary(ng-disabled='$ctrl.random || $ctrl.generatingNewPassphrase', ng-click='$ctrl.generatePassphrase()') NEW ACCOUNT - md-button.md-raised.md-primary(md-autofocus, ng-disabled='$ctrl.valid != undefined && $ctrl.valid !== 1', type='submit') Login + md-button.md-primary.new-account-button(ng-disabled='$ctrl.random || $ctrl.generatingNewPassphrase', ng-click='$ctrl.generatePassphrase()') NEW ACCOUNT + md-button.md-raised.md-primary.login-button(md-autofocus, ng-disabled='$ctrl.valid != undefined && $ctrl.valid !== 1', type='submit') Login passphrase(ng-if='$ctrl.generatingNewPassphrase', data-on-login='$ctrl.onLogin', data-target='primary-pass') diff --git a/src/components/login/save.pug b/src/components/login/save.pug index bd31386d8..cbbb24181 100644 --- a/src/components/login/save.pug +++ b/src/components/login/save.pug @@ -20,5 +20,5 @@ md-dialog.dialog-save(flex='80', aria-label='Save your passphrase in a safe plac md-dialog-actions(layout='row') md-button(ng-click="$ctrl.close()") Close span(flex) - md-button(ng-click="$ctrl.next()", ng-show='!$ctrl.enter') Yes! It's safe! - md-button(ng-click="$ctrl.ok()", ng-show='$ctrl.enter', ng-disabled='!$ctrl.missing_ok') OK + md-button.yes-its-save-button(ng-click="$ctrl.next()", ng-show='!$ctrl.enter') Yes! It's safe! + md-button.ok-button(ng-click="$ctrl.ok()", ng-show='$ctrl.enter', ng-disabled='!$ctrl.missing_ok') OK diff --git a/src/components/send/send.pug b/src/components/send/send.pug index 532c4292d..4544fd657 100644 --- a/src/components/send/send.pug +++ b/src/components/send/send.pug @@ -8,14 +8,14 @@ div.dialog-send(aria-label='Send funds') div md-input-container.md-block label Recipient Address - input(type='text', name='recipient', ng-model='$ctrl.recipient.value', required, ng-pattern='$ctrl.recipient.regexp', ng-disabled='$ctrl.loading') + input.recipient(type='text', name='recipient', ng-model='$ctrl.recipient.value', required, ng-pattern='$ctrl.recipient.regexp', ng-disabled='$ctrl.loading') div(ng-messages='$ctrl.transferForm.recipient.$error') div(ng-message='required') Required div(ng-message='pattern') Invalid div(layout="row") md-input-container.md-block.flex-95 label Transaction Amount - input(type='text', name='amount', ng-model='$ctrl.amount.value', required, ng-pattern='$ctrl.amount.regexp', ng-disabled='$ctrl.loading') + input.amount(type='text', name='amount', ng-model='$ctrl.amount.value', required, ng-pattern='$ctrl.amount.regexp', ng-disabled='$ctrl.loading') div.fee(ng-show='$ctrl.amount.value') Fee: 0.1 LSK div(ng-messages='$ctrl.transferForm.amount.$error') div(ng-message='required') Required @@ -35,4 +35,4 @@ div.dialog-send(aria-label='Send funds') md-dialog-actions(layout='row') md-button.md-raised.md-secondary(ng-disabled='$ctrl.loading', ng-click='$ctrl.cancel()') {{ 'Cancel' }} span(flex) - md-button.md-raised.md-primary(ng-disabled='!$ctrl.transferForm.$valid || $ctrl.loading', ng-click='$ctrl.send()') {{ $ctrl.loading ? 'Sending...' : 'Send' }} + md-button.md-raised.md-primary.submit-button(ng-disabled='!$ctrl.transferForm.$valid || $ctrl.loading', ng-click='$ctrl.send()') {{ $ctrl.loading ? 'Sending...' : 'Send' }} diff --git a/src/components/signVerify/signMessage.pug b/src/components/signVerify/signMessage.pug index cc8eb6e67..5a1287251 100644 --- a/src/components/signVerify/signMessage.pug +++ b/src/components/signVerify/signMessage.pug @@ -9,8 +9,8 @@ div form md-input-container.md-block label Message - textarea(name='message', ng-model='$ctrl.message', ng-change='$ctrl.sign()', md-autofocus) + textarea.message(name='message', ng-model='$ctrl.message', ng-change='$ctrl.sign()', md-autofocus) div(ng-show='$ctrl.result') md-input-container.md-block label Result - textarea(name='result', ng-model='$ctrl.result', readonly) + textarea.result(name='result', ng-model='$ctrl.result', readonly) diff --git a/src/components/signVerify/verifyMessage.pug b/src/components/signVerify/verifyMessage.pug index 6eee2480a..c5b67f65a 100644 --- a/src/components/signVerify/verifyMessage.pug +++ b/src/components/signVerify/verifyMessage.pug @@ -9,16 +9,16 @@ div form md-input-container.md-block(ng-class='{"md-input-invalid": $ctrl.publicKey.error.invalid}') label Public key - input(type='text', name='publicKey', ng-model='$ctrl.publicKey.value', ng-change='$ctrl.verify()', md-autofocus) + input.public-key(type='text', name='publicKey', ng-model='$ctrl.publicKey.value', ng-change='$ctrl.verify()', md-autofocus) div(ng-messages='$ctrl.publicKey.error') div(ng-message='invalid') Invalid md-input-container.md-block(ng-class='{"md-input-invalid": $ctrl.signature.error.invalid}') label Signature - textarea(name='signature', ng-model='$ctrl.signature.value', ng-change='$ctrl.verify()') + textarea.signature(name='signature', ng-model='$ctrl.signature.value', ng-change='$ctrl.verify()') div(ng-messages='$ctrl.signature.error') div(ng-message='invalid') Invalid div(ng-show='$ctrl.result') md-input-container.md-block label Original Message - textarea(name='result', ng-model='$ctrl.result', readonly) + textarea.result(name='result', ng-model='$ctrl.result', readonly) diff --git a/test/components/header/header.spec.js b/test/components/header/header.spec.js index ffd467a9a..5170f0c53 100644 --- a/test/components/header/header.spec.js +++ b/test/components/header/header.spec.js @@ -29,12 +29,12 @@ describe('Header component', () => { it(`should contain "${TRANSFER_BUTTON_TEXT}" button if $root.logged`, () => { $rootScope.logged = true; $scope.$digest(); - expect(element.find('button.md-primary.send').text()).to.equal(TRANSFER_BUTTON_TEXT); + expect(element.find('button.md-primary.send-button').text()).to.equal(TRANSFER_BUTTON_TEXT); }); const LOGOUT_BUTTON_TEXT = 'Logout'; it(`should contain "${LOGOUT_BUTTON_TEXT}" button if $root.logged`, () => { - expect(element.find('button.logout').text()).to.equal(LOGOUT_BUTTON_TEXT); + expect(element.find('button.logout-button').text()).to.equal(LOGOUT_BUTTON_TEXT); }); });