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

feat: Visual regression tests #736

Open
wants to merge 4 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
7 changes: 6 additions & 1 deletion .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ on:
jobs:
cypress-run:
runs-on: ubuntu-22.04
environment: Happo CI Env
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line enforces a specific environment for retrieving the API secrets and must be adjusted for this repository.

strategy:
matrix:
node-version: [22.x]
Expand All @@ -29,11 +30,15 @@ jobs:
node-version: ${{ matrix.node-version }}
# Install NPM dependencies, cache them correctly
# and run all Cypress tests
- name: Cypress run
- name: Cypress with Happo
# https://github.com/cypress-io/github-action/releases/tag/v6.7.7
uses: cypress-io/github-action@f1f0912d392f0d06bdd01fb9ebe3b3299e5806fb
with:
start: npm start
wait-on: 'http://localhost:3000'
wait-on-timeout: 120
browser: chrome
command-prefix: npx happo-e2e -- npx
env:
HAPPO_API_KEY: ${{ secrets.HAPPO_API_KEY }}
HAPPO_API_SECRET: ${{ secrets.HAPPO_API_SECRET }}
13 changes: 13 additions & 0 deletions .happo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const { RemoteBrowserTarget } = require('happo.io');

module.exports = {
apiKey: process.env.HAPPO_API_KEY,
apiSecret: process.env.HAPPO_API_SECRET,

targets: {
'chrome-desktop': new RemoteBrowserTarget('chrome', {
viewport: '1024x768',
hideBehavior: 'ignore',
}),
},
};
3 changes: 3 additions & 0 deletions cypress.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { defineConfig } from "cypress";
const happoTask = require('happo-cypress/task');

export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
setupNodeEvents(on, config) {
// implement node event listeners here
happoTask.register(on);
return config;
},
video: false,
},
Expand Down
83 changes: 81 additions & 2 deletions cypress/e2e/00-welcome.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('welcome and wallet type selection', () => {
cy.visit('/');
cy.findByText(/Welcome to Hathor Wallet/i)
.should("have.length", 1);
cy.get('body').happoScreenshot({ component: 'Welcome' });
});

it('should only allow clicking the button after accepting terms', () => {
Expand All @@ -48,6 +49,7 @@ describe('welcome and wallet type selection', () => {
// Should be on wallet selection screen
cy.contains('software');
cy.contains('hardware');
cy.get('body').happoScreenshot({ component: 'WalletType' });
})

it('should not display the welcome screen after accepting terms', () => {
Expand All @@ -69,6 +71,7 @@ describe('welcome and wallet type selection', () => {

// Should be on software wallet warning screen
cy.contains('Using a software wallet is not the safest way');
cy.get('body').happoScreenshot({ component: 'SoftwareWalletWarning' });
const continueButton = 'Continue';
cy.findByText(continueButton).click();
assertFormIsInvalid();
Expand All @@ -78,6 +81,7 @@ describe('welcome and wallet type selection', () => {
cy.findByText(continueButton).click();

cy.contains('You can start a new wallet or import data');
cy.get('body').happoScreenshot({ component: 'SoftwareWalletStartOrImport' });
})
});

Expand All @@ -100,6 +104,7 @@ describe('create a new wallet and back it up', () => {
// Start the "New Wallet" use case
cy.findByText('New wallet').click();
cy.contains('A new wallet is generated by 24 words');
cy.get('body').happoScreenshot({ component: 'NewWalletInfo' });

// Check that it requires clicking the confirmation checkbox
const continueButton = 'Create my words';
Expand All @@ -112,10 +117,12 @@ describe('create a new wallet and back it up', () => {

// Confirm we're in the new words screen and skip the backup words flow
cy.contains('Your words have been created!');
cy.get('body').happoScreenshot({ component: 'NewWalletSelectBackup' });
cy.findByText('Do it later').click();

// Fill the password field with short password
cy.contains('Please, choose a password')
cy.get('body').happoScreenshot({ component: 'NewWalletPassword' });
cy.get('input[placeholder="Password"]').type('abc');
cy.get('input[placeholder="Confirm Password"]').type('abc');
cy.findByText('Next').click();
Expand All @@ -139,6 +146,7 @@ describe('create a new wallet and back it up', () => {

// Password was successful
cy.contains('The PIN is a 6-digit password');
cy.get('body').happoScreenshot({ component: 'NewWalletPIN' });

// Fill the PIN field with invalid characters
cy.get('input[placeholder="PIN"]').type('abc');
Expand All @@ -164,13 +172,26 @@ describe('create a new wallet and back it up', () => {

// PIN was successful
cy.contains('Loading transactions'); // For a few seconds this screen will be shown
cy.get('body').happoScreenshot({ component: 'NewWalletLoadingTransactions' });

// After a possibly large amount of time, the fullnode will have answered the empty tx history for the new wallet
cy.contains('Total: 0.00 HTR', { timeout: 20000 });
cy.contains(`You haven't done the backup`);
cy.get('body').happoScreenshot({
component: 'DashboardEmptyNoBackup',
transformDOM: {
selector: '.currentAddressExhibitionSpan',
transform: (element, doc) => {
const newElement = doc.createElement('span');
newElement.appendChild(element.cloneNode(true));
newElement.querySelector('span').textContent = 'placeholder-address';
return newElement;
}
},
});
})

it.only('should backup words and handle fullnode failure', () => {
it('should backup words and handle fullnode failure', () => {
// Navigate to the "Software Wallet" screen after the warning);
cy.contains('You can start a new wallet or import data');

Expand All @@ -192,8 +213,34 @@ describe('create a new wallet and back it up', () => {
cy.get('#hiddenWordsForTest').invoke('text').then(text => {
backupWords = text.split(' ');
});
cy.contains('Save the words and never share them.').should('be.visible');
cy.get('body').happoScreenshot({
component: 'NewWalletBackupWords',
transformDOM: {
selector: '#seedWordsExhibitionTable',
transform: (element, doc) => {
const newElement = doc.createElement('div');
newElement.appendChild(element.cloneNode(true));
// Replace the content of each cell in the table with a placeholder word, to make the visual diff stable
const cells = newElement.querySelectorAll('td');
for (let i = 0; i < cells.length; i++) {
cells[i].textContent = `${i+1}: placeholder`;
}
return newElement;
}
}
});
cy.findByText('Ok, I have saved them').click();

cy.contains('To make sure you saved, please select the word that corresponds to the number below.');
cy.get('body').happoScreenshot({
component: 'NewWalletBackupWordsValidation',
transformDOM: {
selector: 'article',
transform: transformWordButton
}
});

// Insert the first word correctly
let currentIndex = 1;
let correctWord = '';
Expand All @@ -218,6 +265,13 @@ describe('create a new wallet and back it up', () => {
.click();
})
cy.contains('Wrong word.');
cy.get('body').happoScreenshot({
component: 'NewWalletBackupWordsValidationWrong',
transformDOM: {
selector: 'article',
transform: transformWordButton
}
});

// Restart the process and select all correct words
cy.findByText('Click here to start the process again.').click();
Expand Down Expand Up @@ -258,6 +312,31 @@ describe('create a new wallet and back it up', () => {
cy.contains('Loading transactions'); // For a few seconds this screen will be shown

// There is a timeout in place that needs to be waited. The error should be handled gracefully
cy.contains('Request failed', { timeout: 15000 });
cy.contains('There has been a problem loading your wallet', { timeout: 15000 });
cy.get('body').happoScreenshot({ component: 'NewWalletLoadingTransactionsError' });


/**
* Replaces the buttons on the "Validation Step" with placeholder ones,
* so that the visual diff is not affected by the actual words.
* @param element
* @param doc
* @returns {*}
*/
function transformWordButton(element, doc) {
const newElement = doc.createElement('div');
newElement.appendChild(element.cloneNode(true));

// Replace the backup index title with a placeholder value
const title = newElement.querySelector('#validationStepIndex');
title.textContent = 'wordX';

// Replace the text of each button with a placeholder word
const buttons = newElement.querySelectorAll('button');
for (let i = 0; i < buttons.length; i++) {
buttons[i].textContent = `word${ i + 1 }`;
}
return newElement;
}
})
})
1 change: 1 addition & 0 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import "@testing-library/cypress/add-commands";
import 'happo-cypress';

/// <reference types="cypress" />
// ***********************************************
Expand Down
Loading
Loading