Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

End-to-end browser tests for the web ui (Cypress-based) #362

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ data_bitcoin_regtest
data_liquid_regtest
www/libwally
data_liquid_testnet
tests
9 changes: 8 additions & 1 deletion run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,13 @@ if [ -f /data/private_nodes ]; then
cat /data/private_nodes >> /data/.${DAEMON}.conf
fi

if [ -n "$EXPOSE_BITCOIND_RPC" ]; then
cat << CONF >> /data/.${DAEMON}.conf
rpcbind=0.0.0.0
rpcallowip=0.0.0.0/0
CONF
fi

TORRCFILE="/srv/explorer/source/contrib/${DAEMON}-${NETWORK}-${MODE}-torrc"
if [ -f $TORRCFILE ]; then
cp $TORRCFILE /etc/tor/torrc
Expand Down Expand Up @@ -264,7 +271,7 @@ if [ "${NETWORK}" == "regtest" ]; then
echo "Creating default wallet"
cli -rpcwait loadwallet default || cli createwallet default
address=$(cli -rpcwait getnewaddress)
cli generatetoaddress 100 ${address}
cli generatetoaddress 105 ${address}
cli stop
fi

Expand Down
3 changes: 3 additions & 0 deletions tests/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
cypress/videos
cypress/screenshots
3 changes: 3 additions & 0 deletions tests/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
cypress/videos
cypress/screenshots
7 changes: 7 additions & 0 deletions tests/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM cypress/base:10
WORKDIR /tests
COPY package.json npm-shrinkwrap.json ./
RUN npm install
COPY . .
ENV PATH=./node_modules/.bin:$PATH
CMD cypress run
51 changes: 51 additions & 0 deletions tests/cypress/integration/1-home.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
describe('home page', () => {
before(() => {
// Make sure we have some transactions to display in the Recent transactions list
cy.task('bitcoind:make_tx_bulk', { num_txs: 6 })
cy.wait(6000)
cy.task('bitcoind:mine')
})

beforeEach(() => {
cy.visit('/')
})

it('displays the latest blocks', () => {
cy.get('.blocks-table .blocks-table-link-row')
.should('have.length', 5)

// verify the last block height matches bitcoin'd block count
cy.task('bitcoind', [ 'getblockcount' ]).then(tip_height => {
cy.get('.blocks-table .blocks-table-link-row [data-label=Height]')
.first().should('have.text', tip_height)
})
})

it('displays the latest transactions', () => {
cy.scrollTo('bottom')
cy.get('.transactions-table .transactions-table-link-row')
.should('have.length', 5)
})

it('can click a block to open it', () => {
cy.get('.blocks-table .blocks-table-link-row [data-label=Height]').first().then($btn => {
const block_height = $btn.text()
$btn.click()

cy.url().should('include', '/block/')
cy.get('h1.block-header-title')
.should('have.text', `Block ${block_height}`)
})
})

it('can click a transaction to open it', () => {
cy.get('.transactions-table .transactions-table-link-row [data-label=TXID]').first().then($btn => {
const txid = $btn.text()
$btn.click()

cy.url().should('include', `/tx/${txid}`)
cy.get('.transaction-box .txn')
.should('have.text', txid)
})
})
})
55 changes: 55 additions & 0 deletions tests/cypress/integration/2-block.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
describe('block page', () => {
before(() => {
// Prepare a block for inspection (with enough transactions to enable paging)
cy.task('bitcoind:make_tx_bulk', { num_txs: 80 })
cy.task('bitcoind:mine').then(block_hash => {
cy.wait(1000)
cy.visit(`/block/${block_hash}`)
})
})

it('displays the block header fields', () => {
cy.get('.stats-table')
.should('contain.text', 'Height')
.should('contain.text', 'Status')
.should('contain.text', 'Timestamp')
})

it('can toggle advanced details', () => {
cy.get('.stats-table')
.should('not.contain.text', 'Merkle root')
.should('not.contain.text', 'Nonce')

cy.get('.details-btn[data-toggle-block]')
.click()

cy.get('.stats-table')
.should('contain.text', 'Merkle root')
.should('contain.text', 'Nonce')

cy.get('.details-btn[data-toggle-block]')
.click()

cy.get('.stats-table')
.should('not.contain.text', 'Merkle root')
.should('not.contain.text', 'Nonce')
})

it('displays the block\'s first 25 transactions', () => {
cy.get('.transactions .transaction-box')
.should('have.length', 25)
})

it('can load more transactions', () => {
cy.get('.load-more').click()
cy.get('.transactions .transaction-box')
.should('have.length', 50)

cy.get('.load-more').click()
cy.get('.transactions .transaction-box')
.should('have.length', 75)

cy.get('h3')
.should('have.text', '75 of 81 Transactions')
})
})
47 changes: 47 additions & 0 deletions tests/cypress/integration/3-transaction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
describe('transaction page', function() {
before(() => {
// Prepare a transaction for inspection
cy.task('bitcoind:make_tx', { confirmed: true }).as('test_tx').then(test_tx => {
cy.wait(6000)
cy.visit(`/tx/${test_tx.txid}`)
})
})

it('displays the transaction info', function() {
cy.get('.stats-table')
.should('contain.text', 'Status')
.should('contain.text', 'Included in Block')
.should('contain.text', 'Transaction fees')
.should('contain.text', this.test_tx.block_hash)

cy.get('.transaction-box .footer')
.should('contain.text', '1 Confirmation')

cy.get('.vout-header-container a')
.contains(this.test_tx.addr)
.should('exist')

cy.get('.vout-header-container .amount')
.contains(`${this.test_tx.amount} rBTC`)
.should('exist')
})

it('can toggle advanced details', () => {
function check (is_visible) {
let is_not = is_visible ? '' : 'not.'
cy.get('.vin .vin-body')
.should(is_not + 'exist')
cy.get('.vout .vout-body')
.should(is_not + 'exist')
cy.get('.transaction-box')
.should(is_not + 'contain.text', 'Previous output address')
.should(is_not + 'contain.text', 'scriptPubKey (asm)')
}

check(false)
cy.get('.details-btn').click()
check(true)
cy.get('.details-btn').click()
check(false)
})
})
48 changes: 48 additions & 0 deletions tests/cypress/integration/4-address.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
describe('address page', function() {
before(() => {
// Prepare an address for testing
cy.task('bitcoind:newaddr').as('test_addr').then(test_addr => {
cy.task('bitcoind:sendto', { addr: test_addr, amount: 0.1 }).as('test_txid1')
cy.task('bitcoind:sendto', { addr: test_addr, amount: 0.2 }).as('test_txid2')
cy.task('bitcoind:mine')
cy.task('bitcoind:sendto', { addr: test_addr, amount: 0.4 }).as('test_txid3')
cy.wait(6000)
cy.visit(`/address/${test_addr}`)
})
})

it('displays the address stats', function() {
cy.get('.stats-table')
.contains('> div', 'Confirmed received')
.find('div:eq(1)')
.should('have.text', '2 outputs (0.3 rBTC)')

cy.get('.stats-table')
.contains('> div', 'Unconfirmed received')
.find('div:eq(1)')
.should('have.text', '1 output (0.4 rBTC)')

cy.get('.stats-table')
.contains('> div', 'Total unspent')
.find('div:eq(1)')
.should('have.text', '3 outputs (0.7 rBTC)')
})

it('displays the address historical transactions', function(){
cy.get('.transactions .transaction-box')
.should('have.length', 3)

;[ [this.test_txid1,0.1], [this.test_txid2,0.2], [this.test_txid3,0.4] ].forEach(([txid, amount]) => {
cy.contains('.transaction-box', txid)
.should('exist')

.contains('.vout', this.test_addr)
.should('have.class', 'active')

.find('.amount')
.should('have.text', `${amount} rBTC`)
})
})

after(() => cy.task('bitcoind:mine'))
})
69 changes: 69 additions & 0 deletions tests/cypress/plugins/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
const fs = require('fs')
, urlp = require('url')
, assert = require('assert')
, BitcoinClient = require('bitcoin-core')

module.exports = (on, config) => {
config.baseUrl = config.baseUrl || process.env.BASE_URL
config.bitcoindUrl = config.bitcoindUrl || process.env.BITCOIND_URL

// TODO: auto spawn dev-server for testing

assert(config.baseUrl && config.bitcoindUrl, 'BASE_URL and BITCOIND_URL are required')

const bitcoind = new BitcoinClient(bitcoind_opt(config.bitcoindUrl))

on('task', {
bitcoind: async ([ method, ...params ]) =>
bitcoind.command(method, ...params)

, "bitcoind:mine": async () => {
const addr = await bitcoind.getNewAddress()
return (await bitcoind.generateToAddress(1, addr))[0]
}

, "bitcoind:make_tx": async ({ confirmed }) => {
const addr = await bitcoind.getNewAddress()
, amount = randAmount()
, txid = await bitcoind.sendToAddress(addr, amount)
, block_hash = confirmed ? await bitcoind.generateToAddress(1, await bitcoind.getNewAddress()) : null
return { txid, addr, amount, block_hash }
}

, "bitcoind:make_tx_bulk": async ({ num_txs }) => {
const addr = await bitcoind.getNewAddress()
, txids = []
for (let i=0; i<num_txs; i++) {
txids.push(await bitcoind.sendToAddress(addr, randAmount()))
}
return { txids, addr }
}

, "bitcoind:newaddr": () =>
bitcoind.getNewAddress()

, "bitcoind:sendto": ({ addr, amount }) =>
bitcoind.sendToAddress(addr, amount)
})

return config
}

function bitcoind_opt(url) {
const parsed = urlp.parse(url, true)
, auth_str = parsed.auth || (parsed.query.cookie && fs.readFileSync(parsed.query.cookie).toString())
, auth = auth_str && auth_str.split(':', 2).map(decodeURIComponent)

return {
host: parsed.hostname || 'localhost'
, port: parsed.port
, ssl: (parsed.protocol == 'https:')
, username: auth ? auth[0] : null
, password: auth ? auth[1] : null
, network:'regtest'
, wallet: parsed.query.wallet
}
}

const randAmount = () =>
`0.01${Math.random()*10000|0}`.replace(/0+$/, '')
Loading