Skip to content

Commit 987eaed

Browse files
committed
Add e2e browser tests for the esplora web ui
1 parent 812f5b0 commit 987eaed

File tree

10 files changed

+3538
-0
lines changed

10 files changed

+3538
-0
lines changed

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ data_bitcoin_regtest
1515
data_liquid_regtest
1616
www/libwally
1717
data_liquid_testnet
18+
tests

tests/.dockerignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
cypress/videos
3+
cypress/screenshots

tests/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
cypress/videos
3+
cypress/screenshots

tests/Dockerfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
FROM cypress/base:10
2+
WORKDIR /tests
3+
COPY package.json npm-shrinkwrap.json ./
4+
RUN npm install
5+
COPY . .
6+
ENV PATH=./node_modules/.bin:$PATH
7+
CMD cypress run
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
describe('home page', () => {
2+
before(() => {
3+
// Make sure we have some transactions to display in the Recent transactions list
4+
cy.task('bitcoind:make_tx_bulk', { num_txs: 6 })
5+
cy.wait(6000)
6+
cy.task('bitcoind:mine')
7+
})
8+
9+
beforeEach(() => {
10+
cy.visit('/')
11+
})
12+
13+
it('displays the latest blocks', () => {
14+
cy.get('.blocks-table .blocks-table-link-row')
15+
.should('have.length', 5)
16+
17+
// verify the last block height matches bitcoin'd block count
18+
cy.task('bitcoind', [ 'getblockcount' ]).then(tip_height => {
19+
cy.get('.blocks-table .blocks-table-link-row [data-label=Height]')
20+
.first().should('have.text', tip_height)
21+
})
22+
})
23+
24+
it('displays the latest transactions', () => {
25+
cy.scrollTo('bottom')
26+
cy.get('.transactions-table .transactions-table-link-row')
27+
.should('have.length', 5)
28+
})
29+
30+
it('can click a block to open it', () => {
31+
cy.get('.blocks-table .blocks-table-link-row [data-label=Height]').first().then($btn => {
32+
const block_height = $btn.text()
33+
$btn.click()
34+
35+
cy.url().should('include', '/block/')
36+
cy.get('h1.block-header-title')
37+
.should('have.text', `Block ${block_height}`)
38+
})
39+
})
40+
41+
it('can click a transaction to open it', () => {
42+
cy.get('.transactions-table .transactions-table-link-row [data-label=TXID]').first().then($btn => {
43+
const txid = $btn.text()
44+
$btn.click()
45+
46+
cy.url().should('include', `/tx/${txid}`)
47+
cy.get('.transaction-box .txn')
48+
.should('have.text', txid)
49+
})
50+
})
51+
})
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
describe('block page', () => {
2+
before(() => {
3+
// Prepare a block for inspection (with enough transactions to enable paging)
4+
cy.task('bitcoind:make_tx_bulk', { num_txs: 80 })
5+
cy.task('bitcoind:mine').then(block_hash => {
6+
cy.wait(1000)
7+
cy.visit(`/block/${block_hash}`)
8+
})
9+
})
10+
11+
it('displays the block header fields', () => {
12+
cy.get('.stats-table')
13+
.should('contain.text', 'Height')
14+
.should('contain.text', 'Status')
15+
.should('contain.text', 'Timestamp')
16+
})
17+
18+
it('can toggle advanced details', () => {
19+
cy.get('.stats-table')
20+
.should('not.contain.text', 'Merkle root')
21+
.should('not.contain.text', 'Nonce')
22+
23+
cy.get('.details-btn[data-toggle-block]')
24+
.click()
25+
26+
cy.get('.stats-table')
27+
.should('contain.text', 'Merkle root')
28+
.should('contain.text', 'Nonce')
29+
30+
cy.get('.details-btn[data-toggle-block]')
31+
.click()
32+
33+
cy.get('.stats-table')
34+
.should('not.contain.text', 'Merkle root')
35+
.should('not.contain.text', 'Nonce')
36+
})
37+
38+
it('displays the block\'s first 25 transactions', () => {
39+
cy.get('.transactions .transaction-box')
40+
.should('have.length', 25)
41+
})
42+
43+
it('can load more transactions', () => {
44+
cy.get('.load-more').click()
45+
cy.get('.transactions .transaction-box')
46+
.should('have.length', 50)
47+
48+
cy.get('.load-more').click()
49+
cy.get('.transactions .transaction-box')
50+
.should('have.length', 75)
51+
52+
cy.get('h3')
53+
.should('have.text', '75 of 81 Transactions')
54+
})
55+
})
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
describe('transaction page', function() {
2+
before(() => {
3+
// Prepare a transaction for inspection
4+
cy.task('bitcoind:make_tx', { confirmed: true }).as('test_tx').then(test_tx => {
5+
cy.wait(6000)
6+
cy.visit(`/tx/${test_tx.txid}`)
7+
})
8+
})
9+
10+
it('displays the transaction info', function() {
11+
cy.get('.stats-table')
12+
.should('contain.text', 'Status')
13+
.should('contain.text', 'Included in Block')
14+
.should('contain.text', 'Transaction fees')
15+
.should('contain.text', this.test_tx.block_hash)
16+
17+
cy.get('.transaction-box .footer')
18+
.should('contain.text', '1 Confirmation')
19+
20+
cy.get('.vout-header-container a')
21+
.contains(this.test_tx.addr)
22+
.should('exist')
23+
24+
cy.get('.vout-header-container .amount')
25+
.contains(`${this.test_tx.amount} rBTC`)
26+
.should('exist')
27+
})
28+
29+
it('can toggle advanced details', () => {
30+
function check (is_visible) {
31+
let is_not = is_visible ? '' : 'not.'
32+
cy.get('.vin .vin-body')
33+
.should(is_not + 'exist')
34+
cy.get('.vout .vout-body')
35+
.should(is_not + 'exist')
36+
cy.get('.transaction-box')
37+
.should(is_not + 'contain.text', 'Previous output address')
38+
.should(is_not + 'contain.text', 'scriptPubKey (asm)')
39+
}
40+
41+
check(false)
42+
cy.get('.details-btn').click()
43+
check(true)
44+
cy.get('.details-btn').click()
45+
check(false)
46+
})
47+
})

tests/cypress/plugins/index.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
const fs = require('fs')
2+
, urlp = require('url')
3+
, assert = require('assert')
4+
, BitcoinClient = require('bitcoin-core')
5+
6+
module.exports = (on, config) => {
7+
config.baseUrl = config.baseUrl || process.env.BASE_URL
8+
config.bitcoindUrl = config.bitcoindUrl || process.env.BITCOIND_URL
9+
10+
// TODO: auto spawn dev-server for testing
11+
12+
assert(config.baseUrl && config.bitcoindUrl, 'BASE_URL and BITCOIND_URL are required')
13+
14+
const bitcoind = new BitcoinClient(bitcoind_opt(config.bitcoindUrl))
15+
16+
on('task', {
17+
bitcoind: async ([ method, ...params ]) =>
18+
bitcoind.command(method, ...params)
19+
20+
, "bitcoind:mine": async () => {
21+
const addr = await bitcoind.getNewAddress()
22+
return (await bitcoind.generateToAddress(1, addr))[0]
23+
}
24+
25+
, "bitcoind:make_tx": async ({ confirmed }) => {
26+
const addr = await bitcoind.getNewAddress()
27+
, amount = randAmount()
28+
, txid = await bitcoind.sendToAddress(addr, amount)
29+
, block_hash = confirmed ? await bitcoind.generateToAddress(1, await bitcoind.getNewAddress()) : null
30+
return { txid, addr, amount, block_hash }
31+
}
32+
33+
, "bitcoind:make_tx_bulk": async ({ num_txs }) => {
34+
const addr = await bitcoind.getNewAddress()
35+
, txids = []
36+
for (let i=0; i<num_txs; i++) {
37+
txids.push(await bitcoind.sendToAddress(addr, randAmount()))
38+
}
39+
return { txids, addr }
40+
}
41+
})
42+
43+
return config
44+
}
45+
46+
function bitcoind_opt(url) {
47+
const parsed = urlp.parse(url, true)
48+
, auth_str = parsed.auth || (parsed.query.cookie && fs.readFileSync(parsed.query.cookie).toString())
49+
, auth = auth_str && auth_str.split(':', 2).map(decodeURIComponent)
50+
51+
return {
52+
host: parsed.hostname || 'localhost'
53+
, port: parsed.port
54+
, ssl: (parsed.protocol == 'https:')
55+
, username: auth ? auth[0] : null
56+
, password: auth ? auth[1] : null
57+
, network:'regtest'
58+
, wallet: parsed.query.wallet
59+
}
60+
}
61+
62+
const randAmount = () =>
63+
`0.01${Math.random()*10000|0}`.replace(/0+$/, '')

0 commit comments

Comments
 (0)