New e2e test suite (#555)

* add @cypress/schematic as a dev dependency

* replace protractor with cypress

* remove the boilerplate test

* add the cypress-wait-until helper library

* add cypress-wait-until to all tests

* add signet to stg proxy config

* add proxy config for production

* add environment configs to angular.json

* update e2e target to use the production proxy

* update serve and start scripts to use the new environment config

* add the concurrently lib to the dev dependencies

* fix missing import

* ignore videos and screenshots saved by the e2e suite

* add fixtures for the tests

* update cypress npm scripts to use concurrently

* add some e2e tests
This commit is contained in:
Felipe Knorr Kuhn 2021-06-07 07:36:41 -07:00 committed by GitHub
parent 2ee96cae44
commit 1bd0c40c15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 3401 additions and 20209 deletions

5
frontend/.gitignore vendored
View File

@ -54,3 +54,8 @@ src/resources/pools.json
# environment config
mempool-frontend-config.json
generated-config.js
# e2e results
cypress/videos
cypress/screenshots

View File

@ -15,8 +15,8 @@
"prefix": "app",
"i18n": {
"sourceLocale": {
"code":"en-US",
"baseHref":"/"
"code": "en-US",
"baseHref": "/"
},
"locales": {
"ar": {
@ -173,6 +173,22 @@
"configurations": {
"production": {
"browserTarget": "mempool:build:production"
},
"local": {
"proxyConfig": "proxy.prod.conf.json",
"verbose": true
},
"staging": {
"proxyConfig": "proxy.stg.conf.json",
"disableHostCheck": true,
"host": "0.0.0.0",
"verbose": true
},
"local-prod": {
"proxyConfig": "proxy.prod.conf.json",
"disableHostCheck": true,
"host": "0.0.0.0",
"verbose": true
}
}
},
@ -205,8 +221,8 @@
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json",
"tsconfig.server.json"
"tsconfig.server.json",
"cypress/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
@ -214,10 +230,11 @@
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"builder": "@cypress/schematic:cypress",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "mempool:serve"
"devServerTarget": "mempool:serve:local-prod",
"watch": true,
"headless": false
},
"configurations": {
"production": {
@ -272,8 +289,27 @@
"configurations": {
"production": {}
}
},
"cypress-run": {
"builder": "@cypress/schematic:cypress",
"options": {
"devServerTarget": "mempool:serve"
},
"configurations": {
"production": {
"devServerTarget": "mempool:serve:production"
}
}
},
"cypress-open": {
"builder": "@cypress/schematic:cypress",
"options": {
"watch": true,
"headless": false
}
}
}
}},
}
},
"defaultProject": "mempool"
}

9
frontend/cypress.json Normal file
View File

@ -0,0 +1,9 @@
{
"integrationFolder": "cypress/integration",
"supportFile": "cypress/support/index.ts",
"videosFolder": "cypress/videos",
"screenshotsFolder": "cypress/screenshots",
"pluginsFile": "cypress/plugins/index.js",
"fixturesFolder": "cypress/fixtures",
"baseUrl": "http://localhost:4200"
}

View File

@ -0,0 +1,119 @@
{
"f59c5f3e8141f322276daa63ed5f307085808aea6d4ef9ba61e28154533fdec7": {
"asset_id": "f59c5f3e8141f322276daa63ed5f307085808aea6d4ef9ba61e28154533fdec7",
"contract": {
"entity": {
"domain": "listedreserve.com"
},
"issuer_pubkey": "031cc579d142a03b33cdd745922112821c16e5e8b74e3bd57f16f7fda872b6f1d0",
"name": "Liquid AUD",
"precision": 2,
"ticker": "AUDL",
"version": 0
},
"issuance_txin": {
"txid": "e5c5144ba3dc48259ae29023fe9f7775dec1fc049f456dd3d1f7178e31901fb5",
"vin": 0
},
"issuance_prevout": {
"txid": "ed48be2e035ffa425d2c6faaa82b6a7b648aed1246b6ac76c72e0408db8cf057",
"vout": 1
},
"name": "Liquid AUD",
"ticker": "AUDL",
"precision": 2,
"entity": {
"domain": "listedreserve.com"
},
"version": 0,
"issuer_pubkey": "031cc579d142a03b33cdd745922112821c16e5e8b74e3bd57f16f7fda872b6f1d0"
},
"0e99c1a6da379d1f4151fb9df90449d40d0608f6cb33a5bcbfc8c265f42bab0a": {
"asset_id": "0e99c1a6da379d1f4151fb9df90449d40d0608f6cb33a5bcbfc8c265f42bab0a",
"contract": {
"entity": {
"domain": "lcad.bullbitcoin.com"
},
"issuer_pubkey": "027fa34026195b05f3aa217335416811dca4f5b579d00271a1bb6304c0152458a8",
"name": "Liquid CAD",
"precision": 8,
"ticker": "LCAD",
"version": 0
},
"issuance_txin": {
"txid": "238badf029cadcf546d90ce23c7eafc2fa2082585c9bd62dc26f1aa11c7bd850",
"vin": 0
},
"issuance_prevout": {
"txid": "a87f13917c08c7ccd8eddb1830c5c9a2bcd59c7d167e9d528659ba40808a6b76",
"vout": 0
},
"name": "Liquid CAD",
"ticker": "LCAD",
"precision": 8,
"entity": {
"domain": "lcad.bullbitcoin.com"
},
"version": 0,
"issuer_pubkey": "027fa34026195b05f3aa217335416811dca4f5b579d00271a1bb6304c0152458a8"
},
"3438ecb49fc45c08e687de4749ed628c511e326460ea4336794e1cf02741329e": {
"asset_id": "3438ecb49fc45c08e687de4749ed628c511e326460ea4336794e1cf02741329e",
"contract": {
"entity": {
"domain": "settlenet.io"
},
"issuer_pubkey": "037b09d542bf7cea6a19fa624b4441790c1a6e44823597bf190e981a846a196541",
"name": "SETTLENET JPY Stablecoin by Crypto Garage",
"precision": 0,
"ticker": "JPYS",
"version": 0
},
"issuance_txin": {
"txid": "e33ad5ce8879297d8bfa7daa193920b94abd3fb12f4e8dade9543dbb292387cb",
"vin": 0
},
"issuance_prevout": {
"txid": "328c4fadd817ea75e634e3648eb4be0bf7e669539b8da921c0f77af3bc148894",
"vout": 1
},
"name": "SETTLENET JPY Stablecoin by Crypto Garage",
"ticker": "JPYS",
"precision": 0,
"entity": {
"domain": "settlenet.io"
},
"version": 0,
"issuer_pubkey": "037b09d542bf7cea6a19fa624b4441790c1a6e44823597bf190e981a846a196541"
},
"ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2": {
"asset_id": "ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2",
"contract": {
"entity": {
"domain": "tether.to"
},
"issuer_pubkey": "0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904",
"name": "Tether USD",
"precision": 8,
"ticker": "USDt",
"version": 0
},
"issuance_txin": {
"txid": "abb4080d91849e933ee2ed65da6b436f7c385cf363fb4aa08399f1e27c58ff3d",
"vin": 0
},
"issuance_prevout": {
"txid": "9596d259270ef5bac0020435e6d859aea633409483ba64e232b8ba04ce288668",
"vout": 0
},
"name": "Tether USD",
"ticker": "USDt",
"precision": 8,
"entity": {
"domain": "tether.to"
},
"version": 0,
"issuer_pubkey": "0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904"
}
}

View File

@ -0,0 +1,33 @@
{
"f59c5f3e8141f322276daa63ed5f307085808aea6d4ef9ba61e28154533fdec7": [
"listedreserve.com",
"AUDL",
"Liquid AUD",
2
],
"0e99c1a6da379d1f4151fb9df90449d40d0608f6cb33a5bcbfc8c265f42bab0a": [
"lcad.bullbitcoin.com",
"LCAD",
"Liquid CAD",
8
],
"6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d": [
null,
"L-BTC",
"Liquid Bitcoin",
8
],
"ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2": [
"tether.to",
"USDt",
"Tether USD",
8
],
"3438ecb49fc45c08e687de4749ed628c511e326460ea4336794e1cf02741329e": [
"settlenet.io",
"JPYS",
"SETTLENET JPY Stablecoin by Crypto Garage",
0
]
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,52 @@
describe('Bisq', () => {
beforeEach(() => {
cy.intercept('/sockjs-node/info*').as('socket')
cy.intercept('/bisq/api/markets/hloc?market=btc_usd&interval=day').as('hloc')
cy.intercept('/bisq/api/markets/ticker').as('ticker')
cy.intercept('/bisq/api/markets/markets').as('markets')
cy.intercept('/bisq/api/markets/volumes/7d').as('7d')
cy.intercept('/bisq/api/markets/trades?market=all').as('trades')
cy.intercept('/bisq/api/txs/*/*').as('txs')
cy.intercept('/bisq/api/blocks/*/*').as('blocks')
cy.intercept('/bisq/api/stats').as('stats')
})
it('loads the dashboard', () => {
cy.visit('/bisq')
cy.wait('@socket')
cy.wait('@hloc')
cy.wait('@ticker')
cy.wait('@markets')
cy.wait('@7d')
cy.wait('@trades')
})
it('loads the transactions screen', () => {
cy.visit('/bisq')
cy.get('li:nth-of-type(2) > a').click().then(() => {
cy.wait('@txs')
})
})
it('loads the blocks screen', () => {
cy.visit('/bisq')
cy.get('li:nth-of-type(3) > a').click().then(() => {
cy.wait('@blocks')
})
})
it('loads the stats screen', () => {
cy.visit('/bisq')
cy.get('li:nth-of-type(4) > a').click().then(() => {
cy.wait('@stats')
})
})
it('loads the api screen', () => {
cy.visit('/bisq')
cy.get('li:nth-of-type(5) > a').click().then(() => {
})
})
})

View File

@ -0,0 +1,81 @@
describe('Liquid', () => {
beforeEach(() => {
//TODO: Fix ng serve to deliver these files
cy.fixture('assets.minimal').then((json) => {
cy.intercept('/resources/assets.minimal.json', json)
})
cy.fixture('assets').then((json) => {
cy.intercept('/resources/assets.json', json)
})
})
it('loads the dashboard', () => {
cy.visit('/liquid')
})
it('loads the blocks page', () => {
cy.visit('/liquid/blocks')
})
it('loads a specific block page', () => {
cy.visit('/liquid/blocks')
})
it('loads the graphs page', () => {
cy.visit('/liquid/graphs')
})
it('loads the tv page - desktop', () => {
cy.visit('/liquid')
cy.get('li:nth-of-type(3) > a').click().then(() => {
cy.wait(1000)
})
})
it('loads the graphs page - mobile', () => {
cy.visit('/liquid')
cy.get('li:nth-of-type(3) > a').click().then(() => {
cy.viewport('iphone-6')
cy.wait(1000)
//TODO: Should we really support TV Mode in Mobile for Bisq?
//cy.get('.tv-only').should('be.visible')
})
})
describe('assets', () => {
it('shows the assets screen', () => {
cy.visit('/liquid')
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.get('table tr').should('have.length', 5)
})
})
it('allows searching assets', () => {
cy.visit('/liquid')
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => {
cy.get('table tr').should('have.length', 1)
})
})
})
it('shows a specific asset ID', () => {
cy.visit('/liquid')
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.get('.container-xl input').click().type('Liquid CAD').then(() => {
cy.get('table tr td:nth-of-type(4) a').click()
})
})
})
it('shows a specific asset issuance TX', () => {
cy.visit('/liquid')
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.get('.container-xl input').click().type('Liquid CAD').then(() => {
cy.get('table tr td:nth-of-type(5) a').click()
})
})
})
})
})

View File

@ -0,0 +1,98 @@
describe('Mainnet', () => {
beforeEach(() => {
cy.intercept('/api/block-height/*').as('block-height')
cy.intercept('/api/block/*').as('block')
cy.intercept('/api/block/*/txs/0').as('block-txs')
cy.intercept('/api/tx/*/outspends').as('tx-outspends')
//TODO: Fix ng serve to deliver this file
cy.fixture('pools').then((json) => {
cy.intercept('/resources/pools.json', json)
})
})
it('loads the dashboard', () => {
cy.visit('/')
cy.wait(1000)
})
it('loads the blocks screen', () => {
cy.visit('/')
cy.get('li:nth-of-type(2) > a').click().then(() => {
cy.wait(1000)
})
})
it('loads the graphs screen', () => {
cy.visit('/')
cy.get('li:nth-of-type(3) > a').click().then(() => {
cy.wait(1000)
})
})
describe('tv mode', () => {
it('loads the tv screen - desktop', () => {
cy.viewport('macbook-16')
cy.visit('/')
cy.get('li:nth-of-type(4) > a').click().then(() => {
cy.wait(1000)
cy.get('.tv-only').should('not.be.visible')
})
})
it('loads the tv screen - mobile', () => {
cy.visit('/')
cy.get('li:nth-of-type(4) > a').click().then(() => {
cy.viewport('iphone-6')
cy.wait(1000)
cy.get('.tv-only').should('be.visible')
})
})
})
it('loads the api screen', () => {
cy.visit('/')
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.wait(1000)
})
})
describe('blocks', () => {
it('shows empty blocks properly', () => {
cy.visit('/block/0000000000000000000bd14f744ef2e006e61c32214670de7eb891a5732ee775')
cy.get('h2').invoke('text').should('equal', '1 transaction')
})
it('expands and collapses the block details', () => {
cy.visit('/block/0')
cy.wait('@tx-outspends')
cy.get('.btn.btn-outline-info').click().then(() => {
cy.get('#details').should('be.visible');
})
cy.get('.btn.btn-outline-info').click().then(() => {
cy.get('#details').should('not.be.visible');
})
})
it('shows blocks with no pagination', () => {
cy.visit('/block/00000000000000000001ba40caf1ad4cec0ceb77692662315c151953bfd7c4c4')
cy.get('h2').invoke('text').should('equal', '19 transactions')
cy.get('ul.pagination').first().children().should('have.length', 5)
})
it('supports pagination on the block screen', () => {
//41 txs
cy.visit('/block/00000000000000000009f9b7b0f63ad50053ad12ec3b7f5ca951332f134f83d8')
cy.get('.header-bg.box > a').invoke('text').then((text1) => {
cy.get('.active + li').first().click().then(() => {
cy.get('.header-bg.box > a').invoke('text').then((text2) => {
expect(text1).not.to.eq(text2)
})
})
})
})
})
})

View File

@ -0,0 +1,11 @@
describe('Signet', () => {
it('loads the dashboard', () => {
cy.visit('/signet')
})
it.skip('loads all the pages properly', () => {
})
})

View File

@ -0,0 +1,10 @@
describe('Testnet', () => {
it('loads the dashboard', () => {
cy.visit('/testnet')
})
it.skip('loads all the pages properly', () => {
})
})

View File

@ -0,0 +1 @@
module.exports = (on, config) => {}

View File

@ -0,0 +1,45 @@
// ***********************************************
// This example namespace declaration will help
// with Intellisense and code completion in your
// IDE or Text Editor.
// ***********************************************
// declare namespace Cypress {
// interface Chainable<Subject = any> {
// customCommand(param: any): typeof customCommand;
// }
// }
//
// function customCommand(param: any): void {
// console.warn(param);
// }
//
// NOTE: You can use it like so:
// Cypress.Commands.add('customCommand', customCommand);
//
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
import 'cypress-wait-until';

View File

@ -0,0 +1,17 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// When a command from ./commands is ready to use, import with `import './commands'` syntax
import './commands';

View File

@ -0,0 +1,8 @@
{
"extends": "../tsconfig.json",
"include": ["**/*.ts"],
"compilerOptions": {
"sourceMap": false,
"types": ["cypress"]
}
}

21769
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -24,10 +24,12 @@
"tsc": "./node_modules/typescript/bin/tsc",
"i18n-extract-from-source": "./node_modules/@angular/cli/bin/ng xi18n --ivy --out-file ./src/locale/messages.xlf",
"i18n-pull-from-transifex": "tx pull -a --parallel --minimum-perc 1 --force",
"serve": "ng serve --proxy-config proxy.conf.json",
"serve:stg": "ng serve --host 0.0.0.0 --disable-host-check --proxy-config proxy.stg.conf.json --verbose",
"start": "npm run generate-config && npm run sync-assets-dev && ng serve --proxy-config proxy.conf.json",
"start:stg": "npm run generate-config && npm run sync-assets-dev && ng serve --host 0.0.0.0 --disable-host-check --proxy-config proxy.stg.conf.json",
"serve": "ng serve -c local",
"serve:stg": "ng serve -c staging",
"serve:local-prod": "ng serve -c local-prod",
"start": "npm run generate-config && npm run sync-assets-dev && ng serve -c local",
"start:stg": "npm run generate-config && npm run sync-assets-dev && ng serve -c staging",
"start:local-prod": "npm run generate-config && npm run sync-assets-dev && ng serve -c local-prod",
"build": "npm run generate-config && ng build --prod --localize && npm run sync-assets && npm run build-mempool.js",
"sync-assets": "node sync-assets.js && rsync -av ./dist/mempool/browser/en-US/resources ./dist/mempool/browser/resources",
"sync-assets-dev": "node sync-assets.js dev",
@ -39,7 +41,9 @@
"dev:ssr": "npm run generate-config && ng run mempool:serve-ssr",
"serve:ssr": "node server.run.js",
"build:ssr": "npm run build && ng run mempool:server:production && ./node_modules/typescript/bin/tsc server.run.ts",
"prerender": "ng run mempool:prerender"
"prerender": "ng run mempool:prerender",
"cypress:open": "concurrently \"ng serve -c local-prod\" \"cypress open\" --kill-others",
"cypress:run": "concurrently \"ng serve -c local-prod\" \"cypress run\" --kill-others"
},
"dependencies": {
"@angular/animations": "~11.2.8",
@ -81,12 +85,16 @@
"@angular/cli": "~11.2.7",
"@angular/compiler-cli": "~11.2.8",
"@angular/language-service": "~11.2.8",
"@cypress/schematic": "^1.3.0",
"@nguniversal/builders": "^11.2.1",
"@types/express": "^4.17.0",
"@types/jasmine": "~3.6.0",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"codelyzer": "^6.0.1",
"concurrently": "^6.2.0",
"cypress": "7.4.0",
"cypress-wait-until": "^1.7.1",
"http-proxy-middleware": "^1.0.5",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",

View File

@ -0,0 +1,87 @@
{
"/api/v1/ws": {
"target": "https://mempool.space",
"secure": false,
"ws": true
},
"/api/*": {
"target": "https://mempool.space",
"secure": false,
"changeOrigin": true,
"logLevel": "debug",
"pathRewrite": {
"^/api": "https://mempool.space/api"
},
"timeout": 3600000
},
"/testnet/api/v1/ws": {
"target": "https://mempool.space/testnet",
"secure": false,
"ws": true,
"loglevel": "debug",
"pathRewrite": {
"^/testnet/api": "/api/v1/ws"
}
},
"/testnet/api/*": {
"target": "https://mempool.space",
"secure": true,
"changeOrigin": true,
"loglevel": "debug",
"pathRewrite": {
"/testnet/api": "/testnet/api"
},
"timeout": 3600000
},
"/signet/api/v1/ws": {
"target": "https://mempool.space/signet",
"secure": false,
"ws": true,
"loglevel": "debug",
"pathRewrite": {
"^/signet/api": "/api/v1/ws"
}
},
"/signet/api/*": {
"target": "https://mempool.space",
"secure": true,
"changeOrigin": true,
"loglevel": "debug",
"pathRewrite": {
"/signet/api": "/signet/api"
},
"timeout": 3600000
},
"/bisq/api/v1/ws": {
"target": "https://mempool.space/bisq",
"secure": false,
"ws": true,
"pathRewrite": {
"^/bisq/api": "/api/v1/ws"
}
},
"/bisq/api/*": {
"target": "https://mempool.space/bisq",
"secure": false,
"changeOrigin": true,
"pathRewrite": {
"^/bisq/api/": "/api/v1/bisq/"
},
"timeout": 3600000
},
"/liquid/api/v1/ws": {
"target": "https://mempool.space",
"secure": false,
"ws": true
},
"/liquid/api/*": {
"target": "https://mempool.space",
"secure": false,
"changeOrigin": true,
"pathRewrite": {
"^/liquid/api/": "/liquid/api/"
},
"timeout": 3600000
}
}

View File

@ -31,6 +31,23 @@
},
"timeout": 3600000
},
"/signet/api/v1/ws": {
"target": "https://mempool.ninja/signet",
"secure": false,
"ws": true,
"pathRewrite": {
"^/signet/api": "/api/v1/ws"
}
},
"/signet/api/v1/*": {
"target": "https://mempool.ninja/signet",
"secure": false,
"changeOrigin": true,
"pathRewrite": {
"^/signet/api/v1": "/api/v1"
},
"timeout": 3600000
},
"/bisq/api/v1/ws": {
"target": "https://mempool.ninja/bisq",
"secure": false,