Updates to the e2e suite (#659)

* initial version of the update config script

* fix duplicated content

* update cypress ci settings

* add workflow to run e2e tests when pushing

* record cypress results to the dashboard

* pull the cypress record key and project id from the secrets

* add start-server-and-test to replace concurrently

* replace concurrently with start-server-and-test

* remove concurrently

* add new cypress target to record

* update cypress to 7.7.0

* add tests for signet

* add tests for testnet

* run tests on chrome and firefox

* update test matrix: add edge and run firefox on container

* fix copypasta

* update docker image for firefox

* fix task name again

* fix edge tests task name

* improve bisq tests

* update workflow config

* enable cypress debug logs

* add a manual trigger for the e2e tests

* add config:defaults target

* use more of the GHA options

* fix config command

* add cypress-fail-on-console-error

* upgrade cypress to v8.0.0

* add helper to wait for the loader skeleton to be gone

* use skeleton waiter on the tests

* remove manual test trigger

* fix tv test when only one mempool block is available

* add waiter for pagination

* add extra steps to debug firefox launch issue

* remove whoami step

* Revert "upgrade cypress to v8.0.0"

This reverts commit cb3ff7d906.

* remove userinfo debug step

* enable test retries in run mode

* update proxy config to reduce ECONNRESET errors

* add mock-socket dev dependency

* add helpers to mock websockets and detect page idleness

* stabilize mainnet tests

* fix tv mode test on Liquid

* add basic tests for the mainnet status page

* cleanup mainnet tests

* update bisq tests

* update signet tests

* update testnet tests

* add initial support for parameterized websocket mocks

* move testing dependencies to optionalDependencies

* comment out mempool size check until the live updates are fixed

* comment out tx regex test

* update fixture for the new difficulty adjustment component

* fix the assertions on the status page
This commit is contained in:
Felipe Knorr Kuhn
2021-07-25 11:03:47 -07:00
committed by GitHub
parent 723034b3d3
commit 9e46cde9b7
17 changed files with 2100 additions and 1561 deletions

View File

@@ -0,0 +1,63 @@
// source: chrisp_68 @ https://stackoverflow.com/questions/50525143/how-do-you-reliably-wait-for-page-idle-in-cypress-io-test
export class PageIdleDetector
{
defaultOptions: Object = { timeout: 60000 };
public WaitForPageToBeIdle(): void
{
this.WaitForPageToLoad();
this.WaitForAngularRequestsToComplete();
this.WaitForAngularDigestCycleToComplete();
this.WaitForAnimationsToStop();
}
public WaitForPageToLoad(options: Object = this.defaultOptions): void
{
cy.document(options).should((myDocument: any) =>
{
expect(myDocument.readyState, "WaitForPageToLoad").to.be.oneOf(["interactive", "complete"]);
});
}
public WaitForAngularRequestsToComplete(options: Object = this.defaultOptions): void
{
cy.window(options).should((myWindow: any) =>
{
if (!!myWindow.angular)
{
expect(this.NumberOfPendingAngularRequests(myWindow), "WaitForAngularRequestsToComplete").to.have.length(0);
}
});
}
public WaitForAngularDigestCycleToComplete(options: Object = this.defaultOptions): void
{
cy.window(options).should((myWindow: any) =>
{
if (!!myWindow.angular)
{
expect(this.AngularRootScopePhase(myWindow), "WaitForAngularDigestCycleToComplete").to.be.null;
}
});
}
public WaitForAnimationsToStop(options: Object = this.defaultOptions): void
{
cy.get(":animated", options).should("not.exist");
}
private getInjector(myWindow: any)
{
return myWindow.angular.element(myWindow.document.body).injector();
}
private NumberOfPendingAngularRequests(myWindow: any)
{
return this.getInjector(myWindow).get('$http').pendingRequests;
}
private AngularRootScopePhase(myWindow: any)
{
return this.getInjector(myWindow).get("$rootScope").$$phase;
}
}

View File

@@ -43,3 +43,24 @@
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
import 'cypress-wait-until';
import { PageIdleDetector } from './PageIdleDetector';
import { mockWebSocket } from './websocket';
Cypress.Commands.add('waitForSkeletonGone', () => {
cy.waitUntil(() => {
return Cypress.$('.skeleton-loader').length === 0;
}, { verbose: true, description: "waitForSkeletonGone", errorMsg: "skeleton loaders never went away", timeout: 7000, interval: 50});
});
Cypress.Commands.add(
"waitForPageIdle",
() => {
console.warn("Waiting for page idle state");
const pageIdleDetector = new PageIdleDetector();
pageIdleDetector.WaitForPageToBeIdle();
}
);
Cypress.Commands.add('mockMempoolSocket', () => {
mockWebSocket();
});

View File

@@ -15,3 +15,6 @@
// When a command from ./commands is ready to use, import with `import './commands'` syntax
import './commands';
import failOnConsoleError from 'cypress-fail-on-console-error';
failOnConsoleError();

View File

@@ -0,0 +1,84 @@
import { v4 as uuid } from 'uuid';
import { WebSocket, Server } from 'mock-socket';
declare global {
interface Window {
mockServer: Server;
mockSocket: WebSocket;
}
}
const mocks: { [key: string]: { server: Server; websocket: WebSocket } } = {};
const cleanupMock = (url: string) => {
if (mocks[url]) {
mocks[url].websocket.close();
mocks[url].server.stop();
delete mocks[url];
}
};
const createMock = (url: string) => {
cleanupMock(url);
const server = new Server(url);
const websocket = new WebSocket(url);
mocks[url] = { server, websocket };
return mocks[url];
};
export const mockWebSocket = () => {
cy.on('window:before:load', (win) => {
const winWebSocket = win.WebSocket;
cy.stub(win, 'WebSocket').callsFake((url) => {
console.log(url);
if ((new URL(url).pathname.indexOf('/sockjs-node/') !== 0)) {
const { server, websocket } = createMock(url);
win.mockServer = server;
win.mockServer.on('connection', (socket) => {
win.mockSocket = socket;
});
win.mockServer.on('message', (message) => {
console.log(message);
});
return websocket;
} else {
return new winWebSocket(url);
}
});
});
cy.on('window:before:unload', () => {
for (const url in mocks) {
cleanupMock(url);
}
});
};
export const emitMempoolInfo = ({
params
}: { params?: any } = {}) => {
cy.window().then((win) => {
//TODO: Refactor to take into account different parameterized mocking scenarios
switch (params.network) {
//TODO: Use network specific mocks
case "signet":
case "testnet":
default:
win.mockSocket.send('{"action":"init"}');
win.mockSocket.send('{"action":"want","data":["blocks","stats","mempool-blocks","live-2h-chart"]}');
cy.readFile('cypress/fixtures/mainnet_mempoolInfo.json', 'ascii').then((fixture) => {
win.mockSocket.send(JSON.stringify(fixture));
});
win.mockSocket.send('{"conversions":{"USD":32365.338815782445}}');
cy.readFile('cypress/fixtures/mainnet_live2hchart.json', 'ascii').then((fixture) => {
win.mockSocket.send(JSON.stringify(fixture));
});
}
});
cy.waitForSkeletonGone();
return cy.get('#mempool-block-0');
};