From fc110724798b5d3123ac987f9a913197541dad01 Mon Sep 17 00:00:00 2001 From: Michael <30682308+mike10ca@users.noreply.github.com> Date: Wed, 11 Oct 2023 14:59:17 +0200 Subject: [PATCH] Tests: Add create safe tests (#2611) * Add Create Safe tests * Remove unused import --- cypress/e2e/pages/create_wallet.pages.js | 90 ++++++++++-- cypress/e2e/pages/main.page.js | 10 +- cypress/e2e/pages/owners.pages.js | 47 ++++++- cypress/e2e/smoke/create_safe_simple.cy.js | 152 +++++++++++++++++---- cypress/e2e/smoke/remove_owner.cy.js | 1 + cypress/support/constants.js | 10 ++ 6 files changed, 261 insertions(+), 49 deletions(-) diff --git a/cypress/e2e/pages/create_wallet.pages.js b/cypress/e2e/pages/create_wallet.pages.js index e00147261f..65ca64c824 100644 --- a/cypress/e2e/pages/create_wallet.pages.js +++ b/cypress/e2e/pages/create_wallet.pages.js @@ -1,20 +1,55 @@ import * as constants from '../../support/constants' -const newAccountBtnStr = 'Create new Account' - const nameInput = 'input[name="name"]' const selectNetworkBtn = '[data-cy="create-safe-select-network"]' const ownerInput = 'input[name^="owners"][name$="name"]' const ownerAddress = 'input[name^="owners"][name$="address"]' const thresholdInput = 'input[name="threshold"]' -const removeOwnerBtn = 'button[aria-label="Remove owner"]' +export const removeOwnerBtn = 'button[aria-label="Remove owner"]' +const connectingContainer = 'div[class*="connecting-container"]' +const createNewSafeBtn = 'span[data-track="create-safe: Open stepper"]' + +const changeNetworkWarningStr = 'Change your wallet network' +const safeAccountSetupStr = 'Safe Account setup' +const policy1_1 = '1/1 policy' +export const walletName = 'test1-sepolia-safe' +export const defaltSepoliaPlaceholder = 'sepolia-safe' + +export function verifyPolicy1_1() { + cy.contains(policy1_1).should('exist') + // TOD: Need data-cy for containers +} -export function clickOnCreateNewAccuntBtn() { - cy.contains(newAccountBtnStr).click() +export function verifyDefaultWalletName(name) { + cy.get(nameInput).invoke('attr', 'placeholder').should('include', name) +} + +export function verifyNextBtnIsDisabled() { + cy.get('button').contains('Next').should('be.disabled') +} + +export function verifyNextBtnIsEnabled() { + cy.get('button').contains('Next').should('not.be.disabled') +} + +export function checkNetworkChangeWarningMsg() { + cy.get('div').contains(changeNetworkWarningStr).should('exist') +} + +export function connectWallet() { + cy.get('onboard-v2') + .shadow() + .within(($modal) => { + cy.wrap($modal).contains('div', constants.connectWalletNames.e2e).click() + cy.wrap($modal).get(connectingContainer).should('exist') + }) +} + +export function clickOnCreateNewSafeBtn() { + cy.get(createNewSafeBtn).click().wait(1000) } export function typeWalletName(name) { - cy.get(nameInput).should('have.attr', 'placeholder').should('match', constants.goerlySafeName) cy.get(nameInput).type(name).should('have.value', name) } @@ -52,11 +87,16 @@ export function typeOwnerName(name, index) { cy.get(getOwnerNameInput(index)).type(name).should('have.value', name) } -function typeOwnerAddress(address, index) { - cy.get(getOwnerAddressInput(index)).type(address).should('have.value', address) +export function typeOwnerAddress(address, index, clearOnly = false) { + if (clearOnly) { + cy.get(getOwnerAddressInput(index)).clear() + cy.get('body').click() + return + } + cy.get(getOwnerAddressInput(index)).clear().type(address).should('have.value', address) } -function clickOnAddNewOwnerBtn() { +export function clickOnAddNewOwnerBtn() { cy.contains('button', 'Add new owner').click() } @@ -72,15 +112,41 @@ export function updateThreshold(number) { } export function removeOwner(index) { + // Index for remove owner btn which does not equal to number of owners cy.get(removeOwnerBtn).eq(index).click() } -export function verifySummaryData(safeName, ownerAddress, startThreshold, endThreshold) { - cy.contains(safeName) - cy.contains(ownerAddress) +export function verifySafeNameInSummaryStep(name) { + cy.contains(name) +} + +export function verifyOwnerNameInSummaryStep(name) { + cy.contains(name) +} + +export function verifyOwnerAddressInSummaryStep(address) { + cy.contains(address) +} + +export function verifyThresholdStringInSummaryStep(startThreshold, endThreshold) { cy.contains(`${startThreshold} out of ${endThreshold}`) } +export function verifyNetworkInSummaryStep(network) { + cy.get('div').contains('Name').parent().parent().contains(network) +} + +export function verifyEstimatedFeeInSummaryStep() { + cy.get('b') + .contains('ETH') + .parent() + .should(($element) => { + const text = 'a' + $element.text() + const pattern = /\d/ + expect(/\d/.test(text)).to.equal(true) + }) +} + function getOwnerNameInput(index) { return `input[name="owners.${index}.name"]` } diff --git a/cypress/e2e/pages/main.page.js b/cypress/e2e/pages/main.page.js index 0046c4dbc4..66b9828d36 100644 --- a/cypress/e2e/pages/main.page.js +++ b/cypress/e2e/pages/main.page.js @@ -36,11 +36,7 @@ export function verifyCheckboxeState(element, index, state) { } export function verifyInputValue(selector, value) { - cy.get(selector) - .invoke('val') - .should(($value) => { - console.log($value) - }) + cy.get(selector).invoke('val').should('include', value) } export function generateRandomString(length) { @@ -53,3 +49,7 @@ export function generateRandomString(length) { return result } + +export function verifyElementsCount(element, count) { + cy.get(element).should('have.length', count) +} diff --git a/cypress/e2e/pages/owners.pages.js b/cypress/e2e/pages/owners.pages.js index d885273f84..7d1e5a636d 100644 --- a/cypress/e2e/pages/owners.pages.js +++ b/cypress/e2e/pages/owners.pages.js @@ -17,6 +17,8 @@ const thresHoldDropDownIcon = 'svg[data-testid="ArrowDropDownIcon"]' const thresholdList = 'ul[role="listbox"]' const thresholdDropdown = 'div[aria-haspopup="listbox"]' const thresholdOption = 'li[role="option"]' +const existingOwnerAddressInput = (index) => `input[name="owners.${index}.address"]` +const existingOwnerNameInput = (index) => `input[name="owners.${index}.name"]` const disconnectBtnStr = 'Disconnect' const notConnectedStatus = 'Connect' @@ -33,6 +35,33 @@ export const safeAccountNonceStr = 'Safe Account nonce' export const nonOwnerErrorMsg = 'Your connected wallet is not an owner of this Safe Account' export const disconnectedUserErrorMsg = 'Please connect your wallet' +export function verifyNumberOfOwners(count) { + const indices = Array.from({ length: count }, (_, index) => index) + const names = indices.map(existingOwnerNameInput) + const addresses = indices.map(existingOwnerAddressInput) + + names.forEach((selector) => { + cy.get(selector).should('have.length', 1) + }) + + addresses.forEach((selector) => { + cy.get(selector).should('have.length', 1) + }) +} + +export function verifyExistingOwnerAddress(index, address) { + cy.get(existingOwnerAddressInput(index)).should('have.value', address) +} + +export function verifyExistingOwnerName(index, name) { + cy.get(existingOwnerNameInput(index)).should('have.value', name) +} + +export function typeExistingOwnerName(index, name) { + cy.get(existingOwnerNameInput(index)).clear().type(name) + main.verifyInputValue(existingOwnerNameInput(index), name) +} + export function verifyOwnerDeletionWindowDisplayed() { cy.get('div').contains(constants.transactionStatus.confirm).should('exist') cy.get('button').contains(backbtnStr).should('exist') @@ -43,15 +72,15 @@ function clickOnThresholdDropdown() { cy.get(thresholdDropdown).eq(1).click() } -function getThresholdOptions() { +export function getThresholdOptions() { return cy.get('ul').find(thresholdOption) } export function verifyThresholdLimit(startValue, endValue) { cy.get('p').contains(`out of ${endValue} owner(s)`) clickOnThresholdDropdown() - getThresholdOptions().should('have.length', 1) getThresholdOptions().eq(0).should('have.text', startValue) + cy.get('body').click() } export function verifyRemoveBtnIsEnabled() { @@ -107,6 +136,10 @@ export function clickOnDisconnectBtn() { cy.get('button').contains(notConnectedStatus) } +export function clickOnConnectBtn() { + cy.get('button').contains(notConnectedStatus).click().wait(1000) +} + export function waitForConnectionStatus() { cy.get('div').contains(e2eWalletStr) } @@ -127,9 +160,13 @@ export function verifyErrorMsgInvalidAddress(errorMsg) { cy.get('label').contains(errorMsg).should('be.visible') } +export function verifyValidWalletName(errorMsg) { + cy.get('label').contains(errorMsg).should('not.exist') +} + export function typeOwnerAddress(address) { cy.get(newOwnerAddress).clear().type(address) - main.verifyInputValue(newOwnerAddress, address) + main.verifyInputValue(newOwnerAddress, address.substring(4)) cy.wait(1000) } @@ -150,6 +187,10 @@ export function clickOnNextBtn() { cy.get('button').contains(nextBtnStr).click() } +export function clickOnBackBtn() { + cy.get('button').contains(backbtnStr).click() +} + export function verifyConfirmTransactionWindowDisplayed() { cy.get('div').contains(constants.transactionStatus.confirm).should('exist') cy.get('button').contains(executeBtnStr).should('exist') diff --git a/cypress/e2e/smoke/create_safe_simple.cy.js b/cypress/e2e/smoke/create_safe_simple.cy.js index 7d7b78dfe4..a135d89ddb 100644 --- a/cypress/e2e/smoke/create_safe_simple.cy.js +++ b/cypress/e2e/smoke/create_safe_simple.cy.js @@ -2,53 +2,147 @@ import * as constants from '../../support/constants' import * as main from '../../e2e/pages/main.page' import * as createwallet from '../pages/create_wallet.pages' -const safeName = 'Test safe name' -const ownerName = 'Test Owner Name' -const ownerName2 = 'Test Owner Name 2' +import * as owner from '../pages/owners.pages' -describe('Create Safe form', () => { - it('should navigate to the form', () => { +describe('Create Safe tests', () => { + beforeEach(() => { + cy.visit(constants.createNewSafeSepoliaUrl) cy.clearLocalStorage() - cy.visit(constants.welcomeUrl) main.acceptCookies() - main.verifyGoerliWalletHeader() - createwallet.clickOnCreateNewAccuntBtn() }) - it('should allow setting a name', () => { - createwallet.typeWalletName(safeName) + it('C55742: Verify a Wallet can be connected', () => { + owner.waitForConnectionStatus() + cy.visit(constants.welcomeUrl) + owner.clickOnWalletExpandMoreIcon() + owner.clickOnDisconnectBtn() + createwallet.clickOnCreateNewSafeBtn() + owner.clickOnConnectBtn() + createwallet.connectWallet() }) - it('should allow changing the network', () => { + it('C55743: Verify Next button is disabled until switching to network is done', () => { + owner.waitForConnectionStatus() createwallet.selectNetwork(constants.networks.ethereum) - createwallet.selectNetwork(constants.networks.goerli, true) - createwallet.clickOnNextBtn() + createwallet.checkNetworkChangeWarningMsg() + createwallet.verifyNextBtnIsDisabled() + createwallet.selectNetwork(constants.networks.sepolia) + createwallet.verifyNextBtnIsEnabled() }) - it('should display a default owner and threshold', () => { - createwallet.verifyOwnerAddress(constants.DEFAULT_OWNER_ADDRESS, 0) - createwallet.verifyThreshold(1) + it('C32378: Verify that a new Wallet has default name related to the selected network', () => { + owner.waitForConnectionStatus() + createwallet.verifyDefaultWalletName(createwallet.defaltSepoliaPlaceholder) }) - it('should allow changing the owner name', () => { - createwallet.typeOwnerName(ownerName, 0) - cy.contains('button', 'Back').click() - cy.contains('button', 'Next').click() - createwallet.verifyOwnerName(ownerName, 0) + it('C4790: Verify error message is displayed if wallet name input exceeds 50 characters', () => { + owner.waitForConnectionStatus() + createwallet.typeWalletName(main.generateRandomString(51)) + owner.verifyErrorMsgInvalidAddress(constants.addressBookErrrMsg.exceedChars) }) - it('should add a new owner and update threshold', () => { - createwallet.addNewOwner(ownerName2, constants.EOA, 1) - createwallet.updateThreshold(2) + it('C55744: Verify there is no error message is displayed if wallet name input contains less than 50 characters', () => { + owner.waitForConnectionStatus() + createwallet.typeWalletName(main.generateRandomString(50)) + owner.verifyValidWalletName(constants.addressBookErrrMsg.exceedChars) }) - it('should remove an owner and update threshold', () => { + it('C852: Verify current connected account is shown as default owner', () => { + owner.waitForConnectionStatus() + owner.clickOnNextBtn() + owner.verifyExistingOwnerAddress(0, constants.DEFAULT_OWNER_ADDRESS) + }) + + it('C4791: Verify error message is displayed if owner name input exceeds 50 characters', () => { + owner.waitForConnectionStatus() + owner.clickOnNextBtn() + owner.typeExistingOwnerName(0, main.generateRandomString(51)) + owner.verifyErrorMsgInvalidAddress(constants.addressBookErrrMsg.exceedChars) + }) + + it('C55745: Verify there is no error message is displayed if owner name input contains less than 50 characters', () => { + owner.waitForConnectionStatus() + owner.clickOnNextBtn() + owner.typeExistingOwnerName(0, main.generateRandomString(50)) + owner.verifyValidWalletName(constants.addressBookErrrMsg.exceedChars) + }) + + it('C55746: Verify Add and Remove Owner Row works as expected', () => { + owner.waitForConnectionStatus() + owner.clickOnNextBtn() + createwallet.clickOnAddNewOwnerBtn() + owner.verifyNumberOfOwners(2) + owner.verifyExistingOwnerAddress(1, '') + owner.verifyExistingOwnerName(1, '') createwallet.removeOwner(0) - createwallet.verifyThreshold(1) - createwallet.clickOnNextBtn() + main.verifyElementsCount(createwallet.removeOwnerBtn, 0) + createwallet.clickOnAddNewOwnerBtn() + owner.verifyNumberOfOwners(2) }) - it('should display summary on review page', () => { - createwallet.verifySummaryData(safeName, constants.DEFAULT_OWNER_ADDRESS, 1, 1) + it('C55748: Verify Threshold Setup', () => { + owner.waitForConnectionStatus() + owner.clickOnNextBtn() + createwallet.clickOnAddNewOwnerBtn() + owner.verifyNumberOfOwners(2) + createwallet.clickOnAddNewOwnerBtn() + owner.verifyNumberOfOwners(3) + owner.verifyThresholdLimit(1, 3) + createwallet.updateThreshold(3) + createwallet.removeOwner(1) + owner.verifyThresholdLimit(1, 2) + }) + + it('C55749: Verify data persistence', () => { + const ownerName = 'David' + owner.waitForConnectionStatus() + createwallet.typeWalletName(createwallet.walletName) + owner.clickOnNextBtn() + createwallet.clickOnAddNewOwnerBtn() + createwallet.typeOwnerName(ownerName, 1) + createwallet.typeOwnerAddress(constants.SEPOLIA_OWNER_2, 1) + owner.verifyThresholdLimit(1, 2) + owner.clickOnNextBtn() + createwallet.verifySafeNameInSummaryStep(createwallet.walletName) + createwallet.verifyOwnerNameInSummaryStep(ownerName) + createwallet.verifyOwnerAddressInSummaryStep(constants.DEFAULT_OWNER_ADDRESS) + createwallet.verifyOwnerAddressInSummaryStep(constants.DEFAULT_OWNER_ADDRESS) + createwallet.verifyThresholdStringInSummaryStep(1, 2) + createwallet.verifyNetworkInSummaryStep(constants.networks.sepolia) + owner.clickOnBackBtn() + owner.clickOnBackBtn() + cy.wait(1000) + owner.clickOnNextBtn() + owner.clickOnNextBtn() + createwallet.verifySafeNameInSummaryStep(createwallet.walletName) + createwallet.verifyOwnerNameInSummaryStep(ownerName) + createwallet.verifyOwnerAddressInSummaryStep(constants.DEFAULT_OWNER_ADDRESS) + createwallet.verifyOwnerAddressInSummaryStep(constants.DEFAULT_OWNER_ADDRESS) + createwallet.verifyThresholdStringInSummaryStep(1, 2) + createwallet.verifyNetworkInSummaryStep(constants.networks.sepolia) + createwallet.verifyEstimatedFeeInSummaryStep() + }) + + it('C55750: Verify tip is displayed on right side for threshold 1/1', () => { + owner.waitForConnectionStatus() + owner.clickOnNextBtn() + createwallet.verifyPolicy1_1() + }) + + it('C55747: Verify address input validation rules', () => { + owner.waitForConnectionStatus() + owner.clickOnNextBtn() + createwallet.clickOnAddNewOwnerBtn() + createwallet.typeOwnerAddress(main.generateRandomString(10), 1) + owner.verifyErrorMsgInvalidAddress(constants.addressBookErrrMsg.invalidFormat) + + createwallet.typeOwnerAddress(constants.DEFAULT_OWNER_ADDRESS, 1) + owner.verifyErrorMsgInvalidAddress(constants.addressBookErrrMsg.ownerAdded) + + createwallet.typeOwnerAddress(constants.DEFAULT_OWNER_ADDRESS.toUpperCase(), 1) + owner.verifyErrorMsgInvalidAddress(constants.addressBookErrrMsg.invalidChecksum) + + createwallet.typeOwnerAddress(constants.ENS_TEST_SEPOLIA_INVALID, 1) + owner.verifyErrorMsgInvalidAddress(constants.addressBookErrrMsg.failedResolve) }) }) diff --git a/cypress/e2e/smoke/remove_owner.cy.js b/cypress/e2e/smoke/remove_owner.cy.js index 4f2ac62d60..ccadce800e 100644 --- a/cypress/e2e/smoke/remove_owner.cy.js +++ b/cypress/e2e/smoke/remove_owner.cy.js @@ -42,6 +42,7 @@ describe('Remove an owner tests', () => { owner.waitForConnectionStatus() owner.openRemoveOwnerWindow(1) owner.verifyThresholdLimit(1, 1) + owner.getThresholdOptions().should('have.length', 1) }) it('Verify owner deletion confirmation is displayed ', () => { diff --git a/cypress/support/constants.js b/cypress/support/constants.js index 4d779ddba3..1d2010eb18 100644 --- a/cypress/support/constants.js +++ b/cypress/support/constants.js @@ -14,6 +14,8 @@ export const DEFAULT_OWNER_ADDRESS = '0xC16Db0251654C0a72E91B190d81eAD367d2C6fED export const SEPOLIA_OWNER_2 = '0x96D4c6fFC338912322813a77655fCC926b9A5aC5' export const TEST_SAFE_2 = 'gor:0xE96C43C54B08eC528e9e815fC3D02Ea94A320505' export const SIDEBAR_ADDRESS = '0x04f8...1a91' +export const ENS_TEST_SEPOLIA = 'testenssepolia.eth' +export const ENS_TEST_SEPOLIA_INVALID = 'ivladitestenssepolia.eth' export const BROWSER_PERMISSIONS_KEY = `${LS_NAMESPACE}SafeApps__browserPermissions` export const SAFE_PERMISSIONS_KEY = `${LS_NAMESPACE}SafeApps__safePermissions` @@ -33,6 +35,7 @@ export const openAppsUrl = '/apps/open?safe=' export const homeUrl = '/home?safe=' export const welcomeUrl = '/welcome' export const chainMaticUrl = '/welcome?chain=matic' +export const createNewSafeSepoliaUrl = '/new-safe/create?chain=sep' export const appsUrl = '/apps' export const requestPermissionsUrl = '/request-permissions' export const getPermissionsUrl = '/get-permissions' @@ -101,6 +104,9 @@ export const addressBookErrrMsg = { exceedChars: 'Maximum 50 symbols', ownSafe: 'Cannot use Safe Account itself as owner', alreadyAdded: 'Address already added', + ownerAdded: 'Owner is already added', + failedResolve: 'Failed to resolve the address', + emptyAddress: 'Owner', } export const addresBookContacts = { user1: { @@ -116,3 +122,7 @@ export const addresBookContacts = { export const localStorageKeys = { SAFE_v2__addressBook: 'SAFE_v2__addressBook', } + +export const connectWalletNames = { + e2e: 'E2E Wallet', +}