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

E2E pipeline, common hooks, test for request token #5318

Merged
merged 39 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
fc4bd6f
Added boilerplate for adding an account and setting a network
oskarleonard Sep 14, 2023
9294dcd
Merge branch 'release/3.0.0' into 5080-e2e-request-token
oskarleonard Sep 14, 2023
ed72f8b
remove condition for now
oskarleonard Sep 14, 2023
c3b7f09
use headless mode
oskarleonard Sep 14, 2023
abdde85
Added e2e test: Request token should generate a copy link
oskarleonard Sep 15, 2023
a6fdd0d
Test without custom server
oskarleonard Sep 15, 2023
94452ad
Test with adding network to redux
oskarleonard Sep 15, 2023
40bce7a
set current network
oskarleonard Sep 15, 2023
a8ae8b7
close after feature and link with /
oskarleonard Sep 15, 2023
69e5079
adding wait
oskarleonard Sep 15, 2023
d740478
wait longer
oskarleonard Sep 15, 2023
45be96b
testing new config
oskarleonard Sep 15, 2023
1960448
trying with v0.7.0-rc.0
oskarleonard Sep 15, 2023
256e28e
trying with v0.7.0-rc.0 as SERVICE_BRANCH_NAME
oskarleonard Sep 15, 2023
e53f138
trying with bumping core
oskarleonard Sep 15, 2023
b28e613
Wait 4 seconds
oskarleonard Sep 15, 2023
f62ae09
try to remove parallelisation
oskarleonard Sep 15, 2023
5f61aa4
re add parallelism
oskarleonard Sep 15, 2023
73b56ee
Use devnet for e2e
oskarleonard Sep 15, 2023
f237281
try with default network
oskarleonard Sep 15, 2023
658619a
Testing with devnet
oskarleonard Sep 18, 2023
7eb853b
adding manually
oskarleonard Sep 18, 2023
c1ed34e
Add timeout
oskarleonard Sep 18, 2023
0e5bca5
Testing to add it to the background
oskarleonard Sep 18, 2023
6f61417
adding ignoreHTTPSErrors flag
oskarleonard Sep 18, 2023
6dc9d9d
Adding screenshoot to report
oskarleonard Sep 18, 2023
6a50618
add screenshoots to jenkins
oskarleonard Sep 18, 2023
b27176b
Changed path
oskarleonard Sep 18, 2023
3c1c112
try without headless
oskarleonard Sep 18, 2023
cf269e0
ignore screenshots folder
oskarleonard Sep 18, 2023
bef3eb4
Add e2e network in before hook, increase default timeout, change path…
oskarleonard Sep 18, 2023
0cb43e9
Add ignoreHTTPSErrors: true, flag
oskarleonard Sep 19, 2023
e7f8b9c
Only add screenshot for failed steps
oskarleonard Sep 19, 2023
da498d8
Added request token test for when there is a message and amount is sp…
oskarleonard Sep 20, 2023
23975de
Merge branch 'release/3.0.0' into 5080-e2e-request-token
oskarleonard Sep 20, 2023
b395c3d
Updated text copy to correspond to UI
oskarleonard Sep 20, 2023
a27f63d
Add 3 retries to our failing test since testing service network can b…
oskarleonard Sep 20, 2023
d8c3b75
collect network service urls in a const
oskarleonard Sep 20, 2023
9b1830e
Set retires in cucumber config
oskarleonard Sep 20, 2023
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
5 changes: 1 addition & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ setup/react/assets/fonts/gilroy/
tests/cypress/cypress
tests/cypress/screenshots
tests/cypress/videos
/test-results/
/playwright-report/
/playwright/.cache/

## Cucumber
cucumber-report.html
/e2e/assets/
6 changes: 3 additions & 3 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ pipeline {
ansiColor('xterm')
}
parameters {
string(name: 'CORE_VERSION', defaultValue: '4.0.0-beta.4')
string(name: 'SERVICE_BRANCH_NAME', defaultValue: 'v0.7.0-beta.3')
string(name: 'CORE_VERSION', defaultValue: '4.0.0-rc.0')
string(name: 'SERVICE_BRANCH_NAME', defaultValue: 'v0.7.0-rc.0')
}
stages {
stage('install') {
Expand Down Expand Up @@ -109,7 +109,7 @@ pipeline {
}
post {
failure {
archiveArtifacts artifacts: 'cucumber-report.html', allowEmptyArchive: true
archiveArtifacts artifacts: 'e2e/assets/screenshots/', allowEmptyArchive: true
}
always {
sh '''
Expand Down
5 changes: 3 additions & 2 deletions cucumber.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
module.exports = {
default: {
parallel: 2,
format: ['html:cucumber-report.html'],
format: ['html:./e2e/assets/cucumber-report.html'],
paths: ['./e2e/features/**/*.feature'],
import: ['./e2e/steps/**/*.mjs', './e2e/steps/**/*.js'],
import: ['./e2e/hooks/hooks.mjs', './e2e/steps/**/*.mjs', './e2e/steps/**/*.js'],
dryRun: false,
publishQuite: true,
failFast: false,
retry: 3,
},
};
253 changes: 125 additions & 128 deletions e2e/features/account/AddAccount.feature

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions e2e/features/requestToken/RequestToken.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Feature: Request Token
Background: Add an account and navigate to wallet
Given I add an account with passphrase "peanut hundred pen hawk invite exclude brain chunk gadget wait wrong ready" password "Password1$" name 'test_acc'
And I go to page "wallet"
And I wait for "1 seconds"
And I click on a button with text "Request"
Then I should see "Request tokens"
And "lisk_mainchain" should be selected in "Recipient application" dropdown
And "Lisk" should be selected in "Token" dropdown
And button with text 'Copy link' should be disabled
And Element 'qrContainer' should contain class 'disabled'

Scenario: Request token should not generate a copy link if amount is 0
Given I type "0" in "amount"
oskarleonard marked this conversation as resolved.
Show resolved Hide resolved
Then button with text 'Copy link' should be disabled
And Element 'qrContainer' should contain class 'disabled'

Scenario: Request token should generate a copy link
Given I type "10" in "amount"
Then button with text "Copy link" should be enabled
And Element 'qrContainer' should not contain class 'disabled'
Given I click on a button with text "Copy link"
Then Clipboard should contain "lisk://wallet?modal=send&recipient=lskm9syv4wrjcjczpegz65zqxhk2cp9dkejs5wbjb&amount=10&token=0400000000000000&recipientChain=04000000"

oskarleonard marked this conversation as resolved.
Show resolved Hide resolved
Scenario: Request token should generate a copy link with message
Given I type "10" in "amount"
Then button with text "Copy link" should be enabled
And Element 'qrContainer' should not contain class 'disabled'
Given I click on a button with text "Add message (Optional)"
And I type "hello" in "reference-field"
And I click on a button with text "Copy link"
Then Clipboard should contain "lisk://wallet?modal=send&recipient=lskm9syv4wrjcjczpegz65zqxhk2cp9dkejs5wbjb&amount=10&reference=hello&token=0400000000000000&recipientChain=04000000"
5 changes: 5 additions & 0 deletions e2e/fixtures/networks.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const NETWORKS = {
devnet: {
serviceUrl: 'http://devnet-service.liskdev.net:9901',
}
}
3 changes: 3 additions & 0 deletions e2e/fixtures/page.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const fixture = {
page: null,
}
63 changes: 63 additions & 0 deletions e2e/hooks/hooks.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { BeforeAll, AfterAll, Before, After, setDefaultTimeout, Status } from '@cucumber/cucumber';
import playwright from 'playwright';
import { fixture } from '../fixtures/page.mjs';
import { NETWORKS } from '../fixtures/networks.mjs';

let browser;
let context;

setDefaultTimeout(10000);

BeforeAll(async function () {
browser = await playwright.chromium.launch({
headless: false,
args: ['--disable-web-security', '--allow-running-insecure-content'],
});
});

Before(async function () {
context = await browser.newContext({
ignoreHTTPSErrors: true,
permissions: ['clipboard-read', 'clipboard-write'],
});
const newPage = await context.newPage();
await newPage.goto(`${process.env.PW_BASE_URL}`);

const networkName = 'devnet';
const serviceUrl = NETWORKS.devnet.serviceUrl;
await newPage.getByTestId('network-application-trigger').click();
await newPage.getByText('Add network').click();

await newPage.getByTestId('name').fill(networkName);
await newPage.getByTestId('serviceUrl').fill(serviceUrl);
await newPage.getByTestId('add-network-button').click();

await newPage.getByTestId('selected-menu-item').click();
await newPage.getByText(networkName, { exact: true }).click();
await newPage.goto(`${process.env.PW_BASE_URL}`);

fixture.page = newPage;
});

After(async function ({ pickle, result }) {
let img;
const isFailedStep = result?.status === Status.FAILED;

if (isFailedStep) {
img = await fixture.page.screenshot({
path: `./e2e/assets/screenshots/${pickle.name}.png`,
type: 'png',
});
}

await fixture.page.close();
await context.close();

if (isFailedStep) {
await this.attach(img, 'image/png');
}
});

AfterAll(async function () {
await browser.close();
});
11 changes: 6 additions & 5 deletions e2e/steps/AddAccount.mjs
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
/* eslint-disable new-cap */
import { Then } from '@cucumber/cucumber';
import { expect } from '@playwright/test';
import { fixture } from '../fixtures/page.mjs';

Then('custom derivation path input field should be {string}', async function (state) {
if (state === 'enabled') {
await expect(this.page.getByText('Custom derivation path', { exact: true })).toBeTruthy();
await expect(this.page.getByTestId('custom-derivation-path')).toBeTruthy();
await expect(fixture.page.getByText('Custom derivation path', { exact: true })).toBeTruthy();
await expect(fixture.page.getByTestId('custom-derivation-path')).toBeTruthy();
} else {
await expect(
await this.page.getByText('Custom derivation path', { exact: true }).count()
await fixture.page.getByText('Custom derivation path', { exact: true }).count()
).toEqual(0);
}
});

Then('I should see the final add account step', async function () {
await expect(this.page.getByText(`Perfect! You're all set`, { exact: true })).toBeTruthy();
await expect(fixture.page.getByText(`Perfect! You're all set`, { exact: true })).toBeTruthy();
await expect(
this.page.getByText(
fixture.page.getByText(
'You can now download your encrypted secret recovery phrase and use it to add your account on other devices.',
{ exact: true }
)
Expand Down
132 changes: 99 additions & 33 deletions e2e/steps/common.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,55 @@
import { Given, Then } from '@cucumber/cucumber';
import { expect } from '@playwright/test';
import routes from '../fixtures/routes.mjs';
import { fixture } from '../fixtures/page.mjs';

Given('I navigate to page {string}', { timeout: 160 * 1000 }, async function (pageName) {
await this.openUrl(routes[pageName]);
Then('I go to page {string}', async function (pageName) {
await fixture.page.goto(`${process.env.PW_BASE_URL}/${pageName}`);
});

Given(
'I add an account with passphrase {string} password {string} name {string}',
async function (passphrase, password, name) {
const returnUrl = fixture.page.url();
await fixture.page.goto(`${process.env.PW_BASE_URL}${routes.wallet}`);
await fixture.page.getByText('Add account', { exact: true }).click();
await fixture.page.getByText('Secret recovery phrase', { exact: true }).click();

const phrases = passphrase.split(' ');
for (let index = 0; index < phrases.length; index++) {
// eslint-disable-next-line no-await-in-loop
await fixture.page.getByTestId(`recovery-${index}`).type(phrases[index]);
}

await fixture.page.getByText('Continue to set password', { exact: true }).click();
await fixture.page.getByTestId('password').fill(password);
await fixture.page.getByTestId('cPassword').fill(password);
await fixture.page.getByTestId('accountName').fill(name);
await fixture.page
.getByText('I agree to store my encrypted secret recovery phrase on this device.', {
exact: true,
})
.click();
await fixture.page.getByText('Save Account', { exact: true }).click();
await fixture.page.goto(`${returnUrl}`);
}
);

Given('I click on a button with text {string}', async function (buttonText) {
await this.page.getByText(buttonText, { exact: true }).click();
await fixture.page.getByText(buttonText, { exact: true }).click();
});

Then('Clipboard should contain {string}', async function (clipboardText) {
const text = await fixture.page.evaluate('navigator.clipboard.readText()');
expect(text).toContain(clipboardText);
});

Given('I click on a button with testId {string}', async function (testId) {
await this.page.getByTestId(testId).click();
await fixture.page.getByTestId(testId).click();
});

Given('I click on text {string}', async function (text) {
await this.page.getByText(text).click();
await fixture.page.getByText(text).click();
});

Given('I wait for {string}', async (timeout) => {
Expand All @@ -34,23 +68,27 @@ Given('I wait for {string}', async (timeout) => {
});

Then('I should see {string}', async function (textContent) {
await expect(this.page.getByText(textContent, { exact: true })).toBeVisible();
await expect(fixture.page.getByText(textContent, { exact: true })).toBeVisible();
});

Then('I should possibly see {string}', async function (textContent) {
await expect(this.page.getByText(textContent)).toBeVisible();
await expect(fixture.page.getByText(textContent)).toBeVisible();
});

Then('I should see an image with alt text {string}', async function (altText) {
await expect(this.page.getByAltText(altText)).toBeVisible();
await expect(fixture.page.getByAltText(altText)).toBeVisible();
});

Then('I should be redirected to route: {string}', { timeout: 120 * 1000 }, async function (route) {
await expect(this.page.url()).toBe(`${process.env.PW_BASE_URL}/${route}`);
Then('I should be redirected to route: {string}', async function (route) {
await expect(fixture.page.url()).toBe(`${process.env.PW_BASE_URL}/${route}`);
});

Then('button with text {string} should be disabled', async function (textContent) {
await expect(this.page.getByText(textContent, { exact: true })).toBeDisabled();
await expect(fixture.page.getByText(textContent, { exact: true })).toBeDisabled();
});

Then('button with text {string} should be enabled', async function (textContent) {
await expect(fixture.page.getByText(textContent, { exact: true })).not.toBeDisabled();
});

// eslint-disable-next-line max-statements
Expand All @@ -59,24 +97,28 @@ Given('I fill in mnemonic phrases {string}', async function (passPhrase) {

for (let index = 0; index < phrases.length; index++) {
// eslint-disable-next-line no-await-in-loop
await this.page.getByTestId(`recovery-${index}`).type(phrases[index]);
await fixture.page.getByTestId(`recovery-${index}`).type(phrases[index]);
}
});

Given('I type {string} in {string}', async function (text, dataTestId) {
await this.page.getByTestId(dataTestId).fill(text);
await fixture.page.getByTestId(dataTestId).fill(text);
});

Then(
'I should be on the password collection step having address: {string} and account name {string}',
async function (address, accountName) {
await expect(this.page.getByText('Enter your account password', { exact: true })).toBeTruthy();
await expect(this.page.getByText(address, { exact: true })).toBeTruthy();
await expect(this.page.getByText(accountName, { exact: true })).toBeTruthy();
await expect(
this.page.getByText(
fixture.page.getByText('Enter your account password', { exact: true })
).toBeTruthy();
await expect(fixture.page.getByText(address, { exact: true })).toBeTruthy();
await expect(fixture.page.getByText(accountName, { exact: true })).toBeTruthy();
await expect(
fixture.page.getByText(
'Please enter your account password to backup the secret recovery phrase.',
{ exact: true }
{
exact: true,
}
)
).toBeTruthy();
}
Expand All @@ -85,7 +127,7 @@ Then(
Given(
'I upload from file {string} with json content:',
async function (filename, encryptedAccountJson) {
await this.page.setInputFiles('input[role=button]', {
await fixture.page.setInputFiles('input[role=button]', {
name: `${filename}.json`,
mimeType: 'application/json',
buffer: Buffer.from(encryptedAccountJson),
Expand All @@ -94,29 +136,53 @@ Given(
);

Given('I switch to network {string}', async function (networkName) {
if (!(await this.page.getByText('Add application'))) {
await this.page.getByTestId('network-application-trigger').click();
if (!(await fixture.page.getByText('Add application'))) {
await fixture.page.getByTestId('network-application-trigger').click();
}
await expect(this.page.getByTestId('spinner')).not.toBeVisible({ timeout: 10000 });
await this.page.getByTestId('selected-menu-item').click();
await this.page.getByText(networkName, { exact: true }).click();
await expect(fixture.page.getByTestId('spinner')).not.toBeVisible();
await fixture.page.getByTestId('selected-menu-item').click();
await fixture.page.getByText(networkName, { exact: true }).click();
});

Given('I go back to the previous page', async function () {
await this.page.goBack();
await fixture.page.goBack();
});

Given(
'I add a custom network with name {string} and serviceUrl {string}',
async function (networkName, serviceUrl) {
await this.page.getByTestId('network-application-trigger').click({ timeout: 10000 });
await this.page.getByText('Add network').click({ timeout: 10000 });
await fixture.page.getByTestId('network-application-trigger').click();
await fixture.page.getByText('Add network').click();

await this.page.getByTestId('name').fill(networkName, { timeout: 10000 });
await this.page.getByTestId('serviceUrl').fill(serviceUrl, { timeout: 10000 });
await expect(await this.page.getByTestId('add-network-button')).not.toBeDisabled({
timeout: 10000,
});
await this.page.getByTestId('add-network-button').click({ timeout: 10000 });
await fixture.page.getByTestId('name').fill(networkName);
await fixture.page.getByTestId('serviceUrl').fill(serviceUrl);
await expect(await fixture.page.getByTestId('add-network-button')).not.toBeDisabled();
await fixture.page.getByTestId('add-network-button').click();
}
);

Then(
'{string} should be selected in {string} dropdown',
async function (selectedText, dropdownTitle) {
const title = fixture.page
.getByText(dropdownTitle, { exact: true })
.locator('..')
.getByTestId('selected-menu-item')
.getByText(selectedText, { exact: true });
const content = await title.textContent();
expect(content).toBeTruthy();
}
);

Then('Element {string} should contain class {string}', async function (testId, className) {
const regex = new RegExp(className);
await expect(fixture.page.getByTestId(testId)).toHaveClass(regex);
});

Then('Element {string} should not contain class {string}', async function (testId, className) {
const selector = await fixture.page.getByTestId(testId);
const classList = await selector.evaluate(el => [...el.classList]);
const hasClassname = classList.find((classItem) => classItem.includes(className));

await expect(hasClassname).toBeFalsy();
});
Loading