Add end-to-end tests with cypress

This commit is contained in:
Djuri Baars 2022-01-17 19:06:36 +01:00
parent 9f6b628ce2
commit 4155436820
19 changed files with 1147 additions and 55 deletions

15
.gitignore vendored
View file

@ -22,10 +22,6 @@ yarn-error.log
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
@ -41,4 +37,13 @@ testem.log
.DS_Store
Thumbs.db
.angular
.angular
### CypressIO ###
# gitignore template for the CypressIO, browser test framework
# website: https://www.cypress.io/
cypress/results/*
cypress/reports/*
cypress/screenshots/*
cypress/videos/*

View file

@ -1,27 +1,22 @@
# RingtoolsWeb
# Ringtools-Web
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 13.1.3.
A webbased tool which guides the process of organizing and balancing "Ring of Fires" on the Bitcoin Lightning network.
Built with Angular, Bootstrap and Socket.IO.
## Development server
## Development
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
Run `yarn start serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
Run `yarn build` to build the project. The build artifacts will be stored in the `dist/` directory.
## Running unit tests
## Tests
### Unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
### End-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

View file

@ -116,6 +116,37 @@
"src/**/*.html"
]
}
},
"cypress-run": {
"builder": "@cypress/schematic:cypress",
"options": {
"devServerTarget": "ringtools-web:serve"
},
"configurations": {
"production": {
"devServerTarget": "ringtools-web:serve:production"
}
}
},
"cypress-open": {
"builder": "@cypress/schematic:cypress",
"options": {
"watch": true,
"headless": false
}
},
"e2e": {
"builder": "@cypress/schematic:cypress",
"options": {
"devServerTarget": "ringtools-web:serve",
"watch": true,
"headless": false
},
"configurations": {
"production": {
"devServerTarget": "ringtools-web:serve:production"
}
}
}
}
}

9
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.ts",
"fixturesFolder": "cypress/fixtures",
"baseUrl": "http://localhost:4200"
}

View file

@ -0,0 +1,5 @@
{
"public_key": "043ca2e15917499a0c7de20f03a17b82a0aab1450dcaa0d704c5d969090bc10a2b",
"tg_username": "loremipsum",
"nodename": "Fixturenode"
}

View file

@ -0,0 +1,3 @@
describe('Overview', () => {
});

View file

@ -0,0 +1,290 @@
const nodeResponse = {
node: {
last_update: 1642378961,
pub_key:
'0380b3dbdf090cacee19eb4dc7a82630bd3de8b12608dd7bee971fb3cd2a5ae2fc',
alias: 'RingTools',
addresses: [
{ network: 'tcp', addr: '5.255.98.78:9735' },
{ network: 'tcp', addr: '[2a04:52c0:103:c1e3::1]:9735' },
{
network: 'tcp',
addr: 'ugysme7gdc2xvoxv3f3dtt3d32ztgif5bwgco6zwh3urzqvfuij5wtqd.onion:9735',
},
],
color: '#3399ff',
features: {
'0': { name: 'data-loss-protect', is_required: true, is_known: true },
'5': {
name: 'upfront-shutdown-script',
is_required: false,
is_known: true,
},
'7': { name: 'gossip-queries', is_required: false, is_known: true },
'9': { name: 'tlv-onion', is_required: false, is_known: true },
'12': { name: 'static-remote-key', is_required: true, is_known: true },
'14': { name: 'payment-addr', is_required: true, is_known: true },
'17': { name: 'multi-path-payments', is_required: false, is_known: true },
'19': { name: 'wumbo-channels', is_required: false, is_known: true },
'23': {
name: 'anchors-zero-fee-htlc-tx',
is_required: false,
is_known: true,
},
'31': { name: 'amp', is_required: false, is_known: true },
'45': {
name: 'explicit-commitment-type',
is_required: false,
is_known: true,
},
'2023': {
name: 'script-enforced-lease',
is_required: false,
is_known: true,
},
},
},
num_channels: 6,
total_capacity: '9200000',
channels: [
{
channel_id: '785999081309470721',
chan_point:
'60145d386de0566db420e979472cd3322988e58589e555c06f425bcdede43c83:1',
last_update: 1642425761,
node1_pub:
'025a467a7d2fe3080ec9910bac757281740033292b5c5908f73278c8e9015d762f',
node2_pub:
'0380b3dbdf090cacee19eb4dc7a82630bd3de8b12608dd7bee971fb3cd2a5ae2fc',
capacity: '1000000',
node1_policy: {
time_lock_delta: 40,
min_htlc: '1000',
fee_base_msat: '0',
fee_rate_milli_msat: '0',
disabled: false,
max_htlc_msat: '990000000',
last_update: 1642386269,
},
node2_policy: {
time_lock_delta: 420,
min_htlc: '1000',
fee_base_msat: '0',
fee_rate_milli_msat: '150',
disabled: false,
max_htlc_msat: '990000000',
last_update: 1642425761,
},
},
{
channel_id: '786125525145485312',
chan_point:
'c199a065220cbfbf19a653f5cc051031ace78ba881ffd48dd7850ff2c4d7d13f:0',
last_update: 1642411282,
node1_pub:
'0380b3dbdf090cacee19eb4dc7a82630bd3de8b12608dd7bee971fb3cd2a5ae2fc',
node2_pub:
'03c1a8ccc5c83b37059a3efbb72e633c0897172f2550475b93bdc14b5ca243a0d1',
capacity: '4000000',
node1_policy: {
time_lock_delta: 420,
min_htlc: '1000',
fee_base_msat: '0',
fee_rate_milli_msat: '150',
disabled: false,
max_htlc_msat: '3960000000',
last_update: 1642410202,
},
node2_policy: {
time_lock_delta: 420,
min_htlc: '1000',
fee_base_msat: '0',
fee_rate_milli_msat: '351',
disabled: false,
max_htlc_msat: '3960000000',
last_update: 1642411282,
},
},
{
channel_id: '786137619798753280',
chan_point:
'92106f5e186cb7ac479a4702df24ddf23f472557f0f64215932ea317e927851c:0',
last_update: 1642375361,
node1_pub:
'0380b3dbdf090cacee19eb4dc7a82630bd3de8b12608dd7bee971fb3cd2a5ae2fc',
node2_pub:
'03d6f80df785288de2fe5de19f24ba8a1db3d20647a88d0a903be9de3e7bb8fce1',
capacity: '2000000',
node1_policy: {
time_lock_delta: 420,
min_htlc: '1000',
fee_base_msat: '0',
fee_rate_milli_msat: '150',
disabled: false,
max_htlc_msat: '1980000000',
last_update: 1642375361,
},
node2_policy: {
time_lock_delta: 40,
min_htlc: '1000',
fee_base_msat: '200',
fee_rate_milli_msat: '10',
disabled: false,
max_htlc_msat: '594000000',
last_update: 1642355225,
},
},
{
channel_id: '786144216940806145',
chan_point:
'5e74e951977f3db6779a1e40ab96224647f303801c033e37fb27419d8fee3fe3:1',
last_update: 1642427089,
node1_pub:
'028b06637205225f29f6daf40b4a8281eb3dd8aceace945e7b012fcc9e6a1e1b39',
node2_pub:
'0380b3dbdf090cacee19eb4dc7a82630bd3de8b12608dd7bee971fb3cd2a5ae2fc',
capacity: '200000',
node1_policy: {
time_lock_delta: 40,
min_htlc: '1000',
fee_base_msat: '0',
fee_rate_milli_msat: '150',
disabled: false,
max_htlc_msat: '198000000',
last_update: 1642427089,
},
node2_policy: {
time_lock_delta: 420,
min_htlc: '1000',
fee_base_msat: '0',
fee_rate_milli_msat: '150',
disabled: false,
max_htlc_msat: '198000000',
last_update: 1642362761,
},
},
{
channel_id: '786744550258114560',
chan_point:
'06357025f79f3665df3341332ac8a84399dba2a7697af9721528c99a7296018b:0',
last_update: 1642418561,
node1_pub:
'0380b3dbdf090cacee19eb4dc7a82630bd3de8b12608dd7bee971fb3cd2a5ae2fc',
node2_pub:
'03a8ef90e092600119e57e1ae2aac06789e1346d016d56aa3f0f1c1a88e283a064',
capacity: '1000000',
node1_policy: {
time_lock_delta: 420,
min_htlc: '1000',
fee_base_msat: '0',
fee_rate_milli_msat: '150',
disabled: false,
max_htlc_msat: '990000000',
last_update: 1642418561,
},
node2_policy: {
time_lock_delta: 40,
min_htlc: '1000',
fee_base_msat: '0',
fee_rate_milli_msat: '100',
disabled: false,
max_htlc_msat: '990000000',
last_update: 1642401837,
},
},
{
channel_id: '786914974530469889',
chan_point:
'057f26f5523f508622c92e1519707eb0fc6010bb2b89fd01ea2fc231abeeb851:1',
last_update: 1642432967,
node1_pub:
'0380b3dbdf090cacee19eb4dc7a82630bd3de8b12608dd7bee971fb3cd2a5ae2fc',
node2_pub:
'03c163e6a4573d69147bcf7b0950900b4d34227663c365b73569efc3a10d31a511',
capacity: '1000000',
node1_policy: {
time_lock_delta: 420,
min_htlc: '1000',
fee_base_msat: '0',
fee_rate_milli_msat: '150',
disabled: false,
max_htlc_msat: '990000000',
last_update: 1642431161,
},
node2_policy: {
time_lock_delta: 40,
min_htlc: '1000',
fee_base_msat: '0',
fee_rate_milli_msat: '150',
disabled: false,
max_htlc_msat: '990000000',
last_update: 1642432967,
},
},
],
};
describe('Settings', () => {
beforeEach(() => {
cy.visit('/settings');
cy.intercept(
{
method: 'GET',
url: /\/node\//,
},
{
body: nodeResponse,
headers: {
'Access-Control-Allow-Origin': '*',
},
}
).as('nodeInfoRequest');
});
it('Parses the ring name correctly when it contains 5M', () => {
cy.get('#ringName').type('#SRROF_5Msats_6thRING');
cy.get('#parseCapacityBtn').click();
cy.get('[name=ringSize]').should('have.value', '5000000');
});
it('Parses the ring name correctly when it contains 500K', () => {
cy.get('#ringName').type('#SRROF_500Ksats_6thRING');
cy.get('#parseCapacityBtn').click();
cy.get('[name=ringSize]').should('have.value', '500000');
});
it('Can add a new node', () => {
cy.get('[name=pubKey]').type(
'0380b3dbdf090cacee19eb4dc7a82630bd3de8b12608dd7bee971fb3cd2a5ae2fc'
);
cy.get('[name=tgUsername]').type('Lorem ipsum');
cy.get('#addNodeOwnerBtn').click();
cy.wait('@nodeInfoRequest');
cy.get('table tbody tr').should('have.length', 2);
cy.get('table tbody tr:first td.node_key').should('have.text', '0380b3dbdf090cacee19eb4dc7a82630bd3de8b12608dd7bee971fb3cd2a5ae2fc');
});
it('Configure a ring manually', () => {
cy.get('#ringName').type('#SRROF_5Msats_6thRING');
cy.get('#parseCapacityBtn').click();
cy.get('[name=pubKey]').type(
'0380b3dbdf090cacee19eb4dc7a82630bd3de8b12608dd7bee971fb3cd2a5ae2fc'
);
cy.get('[name=tgUsername]').type('Lorem ipsum');
cy.get('#addNodeOwnerBtn').click();
cy.wait('@nodeInfoRequest');
cy.get('[name=saveRingSettings]').click();
cy.get('ul.list-group li').should('have.length', 1);
cy.get('#navbarRingname').should('have.text', '#SRROF_5Msats_6thRING');
});
});

View file

@ -0,0 +1,28 @@
describe('Check reachability of all pages', () => {
it('Visits the home page', () => {
cy.visit('/')
cy.contains('Welcome')
cy.contains('Documentation')
cy.contains('Donate')
})
it('Visits the overview page', () => {
cy.visit('/overview')
cy.contains('Ring participants')
cy.contains('Download Visual')
})
it('Visits the visual page', () => {
cy.visit('/visual')
cy.contains('Node connections')
cy.contains('Ring order')
cy.contains('Persist channel order')
})
it('Visits the settings page', () => {
cy.visit('/settings')
cy.contains('Parse capacity')
cy.contains('Manual add')
cy.contains('Saved Rings')
})
})

View file

@ -0,0 +1,5 @@
describe('Visual', () => {
beforeEach(() => {
cy.visit('/visual');
});
});

3
cypress/plugins/index.ts Normal file
View file

@ -0,0 +1,3 @@
// Plugins enable you to tap into, modify, or extend the internal behavior of Cypress
// For more info, visit https://on.cypress.io/plugins-api
module.exports = (on, config) => {}

View file

@ -0,0 +1,43 @@
// ***********************************************
// 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) => { ... })

17
cypress/support/index.ts Normal file
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';

8
cypress/tsconfig.json Normal file
View file

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

View file

@ -7,7 +7,10 @@
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"lint": "ng lint"
"lint": "ng lint",
"e2e": "ng e2e",
"cypress:open": "cypress open",
"cypress:run": "cypress run"
},
"private": true,
"dependencies": {
@ -55,6 +58,7 @@
"@angular-eslint/template-parser": "13.0.1",
"@angular/cli": "~13.1.3",
"@angular/compiler-cli": "~13.1.0",
"@cypress/schematic": "1.6.0",
"@ngrx/schematics": "^13.0.2",
"@types/jasmine": "~3.10.0",
"@types/node": "^17.0.9",
@ -70,6 +74,7 @@
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "~1.7.0",
"prettier": "^2.5.1",
"typescript": "~4.5.2"
"typescript": "~4.5.2",
"cypress": "latest"
}
}

View file

@ -8,15 +8,16 @@
<input type="text" class="form-control" formControlName="name" id="ringName"
placeholder="Ring Name" name="ringName">
<div class="input-group-append">
<button (click)="parseCapacityName()" class="btn btn-secondary" type="button">Parse capacity</button>
<button (click)="parseCapacityName()" class="btn btn-secondary" type="button"
id="parseCapacityBtn">Parse capacity</button>
</div>
</div>
</div>
<div class="form-group row">
<label for="ringName" class="col-sm-2 col-form-label">Ring size</label>
<label for="ringSize" class="col-sm-2 col-form-label">Ring size</label>
<div class="col-sm-10">
<input type="number" class="form-control" id="ringName" placeholder="Ring size"
<input type="number" class="form-control" id="ringSize" placeholder="Ring size"
formControlName="size" name="ringSize">
</div>
</div>
@ -27,7 +28,8 @@
<code>/ringurl</code>)</small></label>
<textarea class="form-control" id="pubkeys" rows="3" [(ngModel)]="pubkeysText"
[ngModelOptions]="{standalone: true}" spellcheck="false"></textarea>
<button (click)="processGroupnodes()" class="btn btn-primary mb-2">Import Groupnodes</button>
<button (click)="processGroupnodes()" class="btn btn-primary mb-2" id="importGroupnodesBtn"
type="button">Import Groupnodes</button>
</div>
<div class="form-check">
@ -71,10 +73,10 @@
<td scope="row" class="node_key">
<span>{{ item.pub_key }}</span>
</td>
<td>{{ item.nodename }}</td>
<td class="node_name">{{ item.nodename }}</td>
<td>{{ item.username_or_name }}</td>
<td><button class="btn btn-danger btn-small btn-sm delete-btn"
(click)="removeNodeOwner(item)">
(click)="removeNodeOwner(item)" name="removeNodeOwnerBtn">
<i class="bi bi-trash"></i>
</button>
</td>
@ -99,14 +101,13 @@
formControlName="tgUsername" name="tgUsername" placeholder="TG username">
</div>
</td>
<td><button class="btn btn-primary btn-small btn-sm">Add</button>
<td><button class="btn btn-primary btn-small btn-sm" id="addNodeOwnerBtn">Add</button>
</td>
</tr>
</tbody>
</table>
</form>
<button (click)="saveRingSettings()" class="btn btn-primary mb-2">Save Ring Settings</button>
<button (click)="exportJSON()" class="btn btn-secondary mb-2">Copy share URL</button>
<button (click)="saveRingSettings()" class="btn btn-primary mb-2" name="saveRingSettings" id="saveRingSettings">Save Ring Settings</button>
<p *ngIf="shareUrl"><small>copied to clipboard <a [href]="shareUrl">link</a></small></p>
<h3>Saved Rings</h3>
<ul class="list-group small">

View file

@ -188,6 +188,7 @@ export class SettingsComponent implements OnInit {
ringSize: ringSize,
};
this.store.dispatch(upsertRingSetting({ ringSetting: ringSettings }));
this.store.dispatch(setRingName(ringName));
}
processGroupnodes() {

View file

@ -19,7 +19,7 @@
</ul>
<div ngbDropdown class="d-flex">
<span class="navbar-text testnet" *ngIf="testnet">Testnet 🧪 &nbsp;</span>
<button class="btn btn-outline-primary" id="dropdownBasic1" ngbDropdownToggle>{{ getRingName() }}</button>
<button class="btn btn-outline-primary" id="navbarRingname" ngbDropdownToggle>{{ getRingName() }}</button>
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
<button ngbDropdownItem *ngFor="let item of ringSettings$ | async" (click)="loadSettings(item)">{{
item.cleanRingName }}</button>

View file

@ -52,7 +52,7 @@ export class RingDataService {
addNodeOwner(pubKey: string, tgUsername: string) {
this.lnData.getNodeInfo(pubKey).subscribe((nodeInfo: NodeInfo) => {
let no: NodeOwner = {
pub_key: nodeInfo.node.alias,
pub_key: nodeInfo.node.pub_key,
nodename: nodeInfo.node.alias,
first_name: tgUsername,
username: tgUsername,

687
yarn.lock

File diff suppressed because it is too large Load diff