Skip to content

Commit

Permalink
Merge pull request #1549 from LiskHQ/1548-migrate-integration-tests
Browse files Browse the repository at this point in the history
Migrate integration tests - Closes #1548
  • Loading branch information
Andrei Klimenok authored Dec 12, 2018
2 parents 0dcca5e + f8d86d8 commit 3f7b614
Show file tree
Hide file tree
Showing 11 changed files with 182 additions and 115 deletions.
28 changes: 10 additions & 18 deletions docs/TEST_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,37 +83,29 @@ https://github.com/LiskHQ/lisk-hub/blob/79165170a326a7f98efee098732e55be37d31223

## Integration tests

### What do they test?
Integration of all components, utils, reducers, and middlewares on one page, e.g. [Login](/LiskHQ/lisk-hub/blob/development/test/integration/login.test.js), [Wallet](/LiskHQ/lisk-hub/blob/development/test/integration/wallet.test.js), [Delegates](/LiskHQ/lisk-hub/blob/development/test/integration/voting.test.js).
Recently were reimplemented using Cypress and now reside together with [e2e tests](/LiskHQ/lisk-hub/blob/development/test/cypress/e2e)

### How are they organized?
Integration tests for each page have their own `*.test.js` file in [/test/integration/](/LiskHQ/lisk-hub/blob/development/test/integration).
[Legacy tests](/LiskHQ/lisk-hub/blob/development/test/integration) are obsolete and should be removed after unit test will satisfy coverage criteria

Configuration is common with unit tests.

### How to run them?
They are run together with unit tests.

### What tools are used?
- All that are used for unit tests.
- [mocha-steps](https://www.npmjs.com/package/mocha-steps) to write the tests in a more [BDD](https://en.wikipedia.org/wiki/Behavior-driven_development) way.
- Our own **generic step definitions** [/test/utils/genericStepDefinition.js](LiskHQ/lisk-hub/blob/bfc94e4f46b4e2393bcc1a0ecd6f1bc85590b6a6/test/utils/genericStepDefinition.js)
- Our own **mount helper** that wraps `enzyme.mount` to avoid some code repetition: [/test/utils/mountHelpers.js](https://github.com/LiskHQ/lisk-hub/blob/bfc94e4f46b4e2393bcc1a0ecd6f1bc85590b6a6/test/utils/mountHelpers.js)


## E2E tests
## E2E and Integration tests

### What do they test?
Full user scenarios in the application as a whole, including the communication with Lisk Core.
User scenarios in the application, where interaction between components, localStorage, Lisk APIs happens

### How to run them?
See [relevant sections of README](/LiskHQ/lisk-hub#run-end-to-end-tests)

### How are they organized?
E2E tests for each major feature have the tests specified in its own `*.spec.js` in [/test/cypress/e2e/](/LiskHQ/lisk-hub/blob/development/test/cypress/e2e)
E2E and integration tests for each major feature have the tests specified in its own `*.spec.js` in [/test/cypress/e2e/](/LiskHQ/lisk-hub/blob/development/test/cypress/e2e)

Configuration is in [cypress.conf.js](/LiskHQ/lisk-hub/blob/development/cypress/cypress.conf.js).

### How do I decide whether I should write E2E or Integration test?
Use at least one e2e test for every feature / api call.

For the different set of inputs, for setting up app state you can use mocking / stubbing making it an integration test.

### What tools are used?
- [Cypress](https://www.cypress.io/) - JavaScript End to End Testing Framework

Expand Down
13 changes: 11 additions & 2 deletions test/constants/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,26 @@ const ss = {
filterAll: '.filter-all',
filterOutgoing: '.filter-out',
filterIncoming: '.filter-in',
filterVoted: '.filter-voted',
filterNotVoted: '.filter-not-voted',
seeAllTxsBtn: '.seeAllLink',
txDetailsBackButton: '.transaction-details-back-button',
recipientInput: '.recipient input',
transferSendTab: '.send-tab',
transferRequestTab: '.request-tab',
requestSpecificAmountBtn: '.specify-request',
requestLink: '.request-link',
confirmRequestBtn: '.confirm-request',
qrCode: '.qr-code',
emailLink: '.email-link',
confirmRequestBlock: '.confirm-request-step',
requestLink: '.request-link',
backButton: '.back',
accountInfoTab: '.account-info',
delegateStatisticsTab: '.delegate-statistics ',
votedAddress: '.votes .voter-address',
voterAdress: '.voters .voter-address',
delegateName: '.delegate-name',
accountAddress: '.copy-title',
selectionVotingNumber: '.selection h4',
searchInput: '#autosuggest-input',
showMoreVotesBtn: '.show-votes',
leftBlockAccountExplorer: '.explorer-account-left-block',
Expand Down Expand Up @@ -96,6 +103,7 @@ const ss = {
delegateRow: '.delegate-row',
delegateList: '.delegate-list',
delegateRank: '.delegate-rank',
delegateName: '.delegate-name',
delegateId: '.delegate-id',
delegateProductivity: '.delegate-productivity',
searchDelegateInput: 'input.search',
Expand Down Expand Up @@ -134,6 +142,7 @@ const ss = {
followedAccountTitle: '.account-title input',
takeTutorial: '.help-onboarding',
tutorialTooltip: '.joyride-tooltip__header',
priceChart: '.chartjs-size-monitor',
transactionRequestButton: '.tx-receive-bt',
transactionSendButton: '.tx-send-bt',
};
Expand Down
19 changes: 19 additions & 0 deletions test/cypress/e2e/dashboard.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import urls from '../../constants/urls';
import ss from '../../constants/selectors';

describe('Dashboard', () => {
it('Quick tips are present on page when logged out', () => {
cy.visit(urls.dashboard);
cy.get(ss.app).contains('What is a Lisk ID?');
});

it('Price chart is present on page', () => {
cy.visit(urls.dashboard);
cy.get(ss.priceChart);
});

it('There is no Empty state components on page when logged out', () => {
cy.visit(urls.dashboard);
cy.get(ss.emptyResultsMessage).should('not.exist');
});
});
36 changes: 36 additions & 0 deletions test/cypress/e2e/delegates.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,18 +109,24 @@ describe('Delegates', () => {
* @expect checkbox to be checked after voting back
* @expect balance decreases as tx is confirmed
*/
/* eslint-disable max-statements */
it('Unvote and Vote + Header balance is affected', () => {
cy.autologin(accounts.genesis.passphrase, networks.devnet.node);
cy.visit(urls.delegates);
cy.get(ss.headerBalance).invoke('text').as('balanceBefore');
cy.get(ss.nextBtn).should('be.disabled');
cy.get(ss.selectionVotingNumber).should('have.text', '0');
cy.get(ss.delegateRow).eq(0).as('dg');
cy.get('@dg').find(ss.voteCheckbox).should('have.class', 'checked');
// Unvote
cy.get('@dg').find(ss.voteCheckbox).click();
cy.get(ss.selectionVotingNumber).should('have.text', '1');
cy.get(ss.nextBtn).click();
cy.get(ss.delegateRow).should('have.length', 1);
cy.get(ss.confirmBtn).click();
cy.get(ss.voteResultHeader).contains('Votes submitted');
cy.get(ss.okayBtn).click();
cy.get(ss.selectionVotingNumber).should('have.text', '0');
cy.get(ss.delegateRow).eq(0).as('dg');
cy.get('@dg').find(ss.spinner);
cy.get('@dg').find(ss.voteCheckbox, { timeout: txConfirmationTimeout }).should('have.class', 'unchecked');
Expand All @@ -129,11 +135,16 @@ describe('Delegates', () => {
// compareBalances(this.balanceBefore, this.balanceAfter, txVotePrice);
// });
// Vote
cy.get(ss.nextBtn).should('be.disabled');
cy.get(ss.delegateRow).eq(0).as('dg');
cy.get('@dg').find(ss.voteCheckbox).click();
cy.get(ss.selectionVotingNumber).should('have.text', '1');
cy.get(ss.nextBtn).click();
cy.get(ss.delegateRow).should('have.length', 1);
cy.get(ss.confirmBtn).click();
cy.get(ss.voteResultHeader).contains('Votes submitted');
cy.get(ss.okayBtn).click();
cy.get(ss.selectionVotingNumber).should('have.text', '0');
cy.get(ss.delegateRow).eq(0).as('dg');
cy.get('@dg').find(ss.spinner);
cy.get('@dg').find(ss.voteCheckbox, { timeout: txConfirmationTimeout }).should('have.class', 'checked');
Expand Down Expand Up @@ -180,4 +191,29 @@ describe('Delegates', () => {
cy.visit(`${urls.delegatesVote}?votes=genesis_14,genesis_16`);
cy.get(ss.alreadyVotedPreselection).contains('genesis_14, genesis_16');
});

/**
* Delegate list filtering
* @expect on Voted tab only voted delegates are shown
* @expect on Not voted tab only not voted delegates are shown
* @expect on All tab all delegates are shown
*/
it('Filter voted/not voted delegates', () => {
cy.autologin(accounts.delegate.passphrase, networks.devnet.node);
cy.visit(urls.delegates);
// Filter Voted
cy.get(ss.filterVoted).click();
cy.get(ss.delegateRow).eq(0).find(ss.delegateName).contains('genesis_17');
cy.get(ss.delegateRow).should('have.length', 1);
// Filter Not voted
cy.get(ss.filterNotVoted).click();
cy.get(ss.searchDelegateInput).click().type('genesis_17');
cy.get(ss.delegateRow).should('not.exist');
cy.get(ss.searchDelegateInput).click().clear();
// Filter All
cy.get(ss.filterAll).click();
cy.get(ss.delegateRow).should('have.length', 100);
cy.get(ss.searchDelegateInput).click().type('genesis_17');
cy.get(ss.delegateRow).should('have.length', 1);
});
});
195 changes: 101 additions & 94 deletions test/cypress/e2e/search.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,104 +6,111 @@ const getSearchesObjFromLS = () => JSON.parse(localStorage.getItem('searches'));

describe('Search', () => {
const testnetTransaction = '755251579479131174';
[
{
name: 'Search for Lisk ID using keyboard Enter',
searchAction: () => {
cy.get(ss.searchInput).click().type(`${accounts.delegate.address}{enter}`);
},
},
{
name: 'Search for Lisk ID using suggestions',
searchAction: () => {
cy.get(ss.searchInput).click().type(`${accounts.delegate.address}`);
cy.get(ss.idResults).eq(0).click();
},
},
].forEach((testSet) => {
/**
* Search for Lisk ID
* @expect account page with corresponding ID is a result
* @expect localStorage have the searches object with correct address
* @expect localStorage have the searches object with correct searchTerm
*/
it(testSet.name, () => {
cy.autologin(accounts.genesis.passphrase, networks.devnet.node);
cy.visit('/');
testSet.searchAction();
cy.get(ss.leftBlockAccountExplorer).find(ss.accountAddress).should('have.text', accounts.delegate.address)
.and(() => {
expect(getSearchesObjFromLS()[0].id).to.equal(accounts.delegate.address);
expect(getSearchesObjFromLS()[0].searchTerm).to.equal(accounts.delegate.address);
});
});
const mainnetTransaction = '881002485778658401';
const mainnetDelegateName = 'tembo';

function assertAccountPage(accountsAddress) {
cy.get(ss.leftBlockAccountExplorer).find(ss.accountAddress).should('have.text', accountsAddress)
.and(() => {
expect(getSearchesObjFromLS()[0].id).to.equal(accountsAddress);
expect(getSearchesObjFromLS()[0].searchTerm).to.equal(accountsAddress);
});
}

function assertTransactionPage(transactionId) {
cy.get(ss.transactionId).should('have.text', transactionId)
.and(() => {
expect(getSearchesObjFromLS()[0].id).to.equal(transactionId);
expect(getSearchesObjFromLS()[0].searchTerm).to.equal(transactionId);
});
}

function assertDelegatePage(delegateName, delegateId) {
cy.get(ss.leftBlockAccountExplorer).find(ss.delegateName).should('have.text', delegateName)
.and(() => {
expect(getSearchesObjFromLS()[0].id).to.equal(delegateId);
expect(getSearchesObjFromLS()[0].searchTerm).to.equal(delegateName);
});
}

/**
* Search for Lisk ID using keyboard Enter, signed out
* @expect account page with corresponding ID is a result
* @expect localStorage have the searches object with correct address
* @expect localStorage have the searches object with correct searchTerm
*/
it('Search for Lisk ID using keyboard Enter, signed off', () => {
cy.visit('/');
cy.get(ss.searchInput).click().type(`${accounts.delegate.address}{enter}`);
assertAccountPage(accounts.delegate.address);
});

[
{
name: 'Search for Transaction using keyboard Enter',
searchAction: () => {
cy.get(ss.searchInput).click().type(`${testnetTransaction}{enter}`);
},
},
{
name: 'Search for Transaction using suggestions',
searchAction: () => {
cy.get(ss.searchInput).click().type(`${testnetTransaction}`);
cy.get(ss.transactionResults).eq(0).click();
},
},
].forEach((testSet) => {
/**
* Search for transaction
* @expect transaction details page a result
* @expect localStorage have the searches object with correct id
* @expect localStorage have the searches object with correct searchTerm
*/
it(testSet.name, () => {
cy.autologin(accounts.genesis.passphrase, networks.testnet.node);
cy.visit('/');
testSet.searchAction();
cy.get(ss.transactionId).should('have.text', testnetTransaction)
.and(() => {
expect(getSearchesObjFromLS()[0].id).to.equal(testnetTransaction);
expect(getSearchesObjFromLS()[0].searchTerm).to.equal(testnetTransaction);
});
});
/**
* Search for Lisk ID using suggestions, signed in
* @expect account page with corresponding ID is a result
* @expect localStorage have the searches object with correct address
* @expect localStorage have the searches object with correct searchTerm
*/
it('Search for Lisk ID using suggestions, signed in', () => {
cy.autologin(accounts.genesis.passphrase, networks.devnet.node);
cy.visit('/');
cy.get(ss.searchInput).click().type(`${accounts.delegate.address}`);
cy.get(ss.idResults).eq(0).click();
assertAccountPage(accounts.delegate.address);
});

[
// TODO reenable after #1351 fix
// {
// name: 'Search for Delegate using keyboard Enter',
// searchAction: () => {
// cy.get(ss.searchInput).click().type(`${accounts.delegate.username}{enter}`);
// },
// },
{
name: 'Search for Delegate using suggestions',
searchAction: () => {
cy.get(ss.searchInput).click().type(`${accounts.delegate.username}`);
cy.get(ss.delegateResults).eq(0).click();
},
},
].forEach((testSet) => {
/**
* Search for delegate
* @expect account page with corresponding ID is a result
* @expect localStorage have the searches object with correct address
* @expect localStorage have the searches object with correct searchTerm
*/
it(testSet.name, () => {
cy.autologin(accounts.genesis.passphrase, networks.devnet.node);
cy.visit('/');
testSet.searchAction();
cy.get(ss.leftBlockAccountExplorer).find(ss.delegateName).should('have.text', accounts.delegate.username)
.and(() => {
expect(getSearchesObjFromLS()[0].id).to.equal(accounts.delegate.address);
expect(getSearchesObjFromLS()[0].searchTerm).to.equal(accounts.delegate.username);
});
});
/**
* Search for transaction using keyboard Enter, signed off
* @expect transaction details page a result
* @expect localStorage have the searches object with correct id
* @expect localStorage have the searches object with correct searchTerm
*/
it('Search for Transaction using keyboard Enter, signed off', () => {
cy.visit('/');
cy.get(ss.searchInput).click().type(`${mainnetTransaction}{enter}`);
assertTransactionPage(mainnetTransaction);
});

/**
* Search for transaction using suggestions, signed in
* @expect transaction details page a result
* @expect localStorage have the searches object with correct id
* @expect localStorage have the searches object with correct searchTerm
*/
it('Search for Transaction using suggestions, signed in', () => {
cy.autologin(accounts.genesis.passphrase, networks.testnet.node);
cy.visit('/');
cy.get(ss.searchInput).click().type(`${testnetTransaction}`);
cy.get(ss.transactionResults).eq(0).click();
assertTransactionPage(testnetTransaction);
});

/**
* Search for delegate using keyboard Enter, signed off
* @expect account page with corresponding ID is a result
* @expect localStorage have the searches object with correct address
* @expect localStorage have the searches object with correct searchTerm
*/
// TODO reenable after #1351 fix
it.skip('Search for Delegate using keyboard Enter, signed off', () => {
cy.visit('/');
cy.get(ss.searchInput).click().type(`${mainnetDelegateName}{enter}`);
assertDelegatePage(accounts.delegate.username);
});

/**
* Search for delegate using suggestions, signed in
* @expect account page with corresponding ID is a result
* @expect localStorage have the searches object with correct address
* @expect localStorage have the searches object with correct searchTerm
*/

it('Search for Delegate using suggestions, signed in', () => {
cy.autologin(accounts.genesis.passphrase, networks.devnet.node);
cy.visit('/');
cy.get(ss.searchInput).click().type(`${accounts.delegate.username}`);
cy.get(ss.delegateResults).eq(0).click();
assertDelegatePage(accounts.delegate.username, accounts.delegate.address);
});

/**
Expand Down
1 change: 0 additions & 1 deletion test/cypress/e2e/txDetails.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ describe('Tx details', () => {
cy.get(ss.txHeader).contains('Transaction');
cy.get(ss.txSenderAddress).should('have.text', accounts.genesis.address);
cy.get(ss.txRecipientAddress).should('have.text', accounts.delegate.address);
cy.get(ss.txDatePlaceholder).should('have.text', 'Pending');
cy.get(ss.txAddedVotes).should('not.exist');
cy.get(ss.txRemovedVotes).should('not.exist');
cy.get(ss.txAmount).should('have.text', '-5');
Expand Down
Loading

0 comments on commit 3f7b614

Please sign in to comment.