diff --git a/cypress/e2e/pages/batches.pages.js b/cypress/e2e/pages/batches.pages.js new file mode 100644 index 0000000000..f614fb93ef --- /dev/null +++ b/cypress/e2e/pages/batches.pages.js @@ -0,0 +1,98 @@ +const tokenSelectorText = 'G(ö|oe)rli Ether' +const noLaterString = 'No, later' +const yesExecuteString = 'Yes, execute' +const newTransactionTitle = 'New transaction' +const sendTokensButn = 'Send tokens' +const nextBtn = 'Next' +const executeBtn = 'Execute' +const addToBatchBtn = 'Add to batch' +const confirmBatchBtn = 'Confirm batch' + +export const closeModalBtnBtn = '[data-testid="CloseIcon"]' +export const deleteTransactionbtn = '[title="Delete transaction"]' +export const batchTxTopBar = '[data-track="batching: Batch sidebar open"]' +export const batchTxCounter = '[data-track="batching: Batch sidebar open"] span > span' +export const addNewTxBatch = '[data-track="batching: Add new tx to batch"]' +export const batchedTransactionsStr = 'Batched transactions' +export const addInitialTransactionStr = 'Add an initial transaction to the batch' +export const transactionAddedToBatchStr = 'Transaction is added to batch' +export const addNewStransactionStr = 'Add new transaction' + +const recipientInput = 'input[name="recipient"]' +const tokenAddressInput = 'input[name="tokenAddress"]' +const listBox = 'ul[role="listbox"]' +const amountInput = '[name="amount"]' +const nonceInput = 'input[name="nonce"]' + +export function addToBatch(EOA, currentNonce, amount, verify = false) { + fillTransactionData(EOA, amount) + setNonceAndProceed(currentNonce) + // Execute the transaction if verification is required + if (verify) { + executeTransaction() + } + addToBatchButton() +} + +function fillTransactionData(EOA, amount) { + cy.get(recipientInput).type(EOA, { delay: 1 }) + // Click on the Token selector + cy.get(tokenAddressInput).prev().click() + cy.get(listBox).contains(new RegExp(tokenSelectorText)).click() + cy.get(amountInput).type(amount) + cy.contains(nextBtn).click() +} + +function setNonceAndProceed(currentNonce) { + cy.get(nonceInput).clear().type(currentNonce, { force: true }).type('{enter}', { force: true }) + cy.contains(executeBtn).scrollIntoView() +} + +function executeTransaction() { + cy.contains(yesExecuteString, { timeout: 4000 }).click() + cy.contains(addToBatchBtn).should('not.exist') +} + +function addToBatchButton() { + cy.contains(noLaterString, { timeout: 4000 }).click() + cy.contains(addToBatchBtn).should('be.visible').and('not.be.disabled').click() +} + +export function openBatchtransactionsModal() { + cy.get(batchTxTopBar).should('be.visible').click() + cy.contains(batchedTransactionsStr).should('be.visible') + cy.contains(addInitialTransactionStr) +} + +export function openNewTransactionModal() { + cy.get(addNewTxBatch).click() + cy.contains('h1', newTransactionTitle).should('be.visible') + cy.contains(sendTokensButn).click() +} + +export function verifyAmountTransactionsInBatch(count) { + cy.contains(batchedTransactionsStr, { timeout: 7000 }) + .should('be.visible') + .parents('aside') + .find('ul > li') + .should('have.length', count) +} + +export function clickOnConfirmBatchBtn() { + cy.contains(confirmBatchBtn).click() +} + +export function verifyBatchTransactionsCount(count) { + cy.contains(`This batch contains ${count} transactions`).should('be.visible') +} + +export function clickOnBatchCounter() { + cy.get(batchTxCounter).click() +} +export function verifyTransactionAdded() { + cy.contains(transactionAddedToBatchStr).should('be.visible') +} + +export function verifyBatchIconCount(count) { + cy.get(batchTxCounter).contains(count) +} diff --git a/cypress/e2e/pages/create_tx.pages.js b/cypress/e2e/pages/create_tx.pages.js new file mode 100644 index 0000000000..fc2a0905be --- /dev/null +++ b/cypress/e2e/pages/create_tx.pages.js @@ -0,0 +1,151 @@ +import * as constants from '../../support/constants' + +const newTransactionBtnStr = 'New transaction' +const recepientInput = 'input[name="recipient"]' +const sendTokensBtnStr = 'Send tokens' +const tokenAddressInput = 'input[name="tokenAddress"]' +const amountInput = 'input[name="amount"]' +const nonceInput = 'input[name="nonce"]' +const gasLimitInput = '[name="gasLimit"]' +const rotateLeftIcon = '[data-testid="RotateLeftIcon"]' + +const viewTransactionBtn = 'View transaction' +const transactionDetailsTitle = 'Transaction details' +const QueueLabel = 'needs to be executed first' +const TransactionSummary = 'Send-' + +const maxAmountBtnStr = 'Max' +const nextBtnStr = 'Next' +const nativeTokenTransferStr = 'Native token transfer' +const yesStr = 'Yes, ' +const estimatedFeeStr = 'Estimated fee' +const executeStr = 'Execute' +const transactionsPerHrStr = 'Transactions per hour' +const transactionsPerHr5Of5Str = '5 of 5' +const editBtnStr = 'Edit' +const executionParamsStr = 'Execution parameters' +const noLaterStr = 'No, later' +const signBtnStr = 'Sign' + +export function clickOnNewtransactionBtn() { + // Assert that "New transaction" button is visible + cy.contains(newTransactionBtnStr, { + timeout: 60_000, // `lastWallet` takes a while initialize in CI + }) + .should('be.visible') + .and('not.be.disabled') + + // Open the new transaction modal + cy.contains(newTransactionBtnStr).click() + cy.contains('h1', newTransactionBtnStr).should('be.visible') +} + +export function typeRecipientAddress(address) { + cy.get(recepientInput).type(address).should('have.value', address) +} +export function clickOnSendTokensBtn() { + cy.contains(sendTokensBtnStr).click() +} + +export function clickOnTokenselectorAndSelectGoerli() { + cy.get(tokenAddressInput).prev().click() + cy.get('ul[role="listbox"]').contains(constants.goerliToken).click() +} + +export function setMaxAmount() { + cy.contains(maxAmountBtnStr).click() +} + +export function verifyMaxAmount(token, tokenAbbreviation) { + cy.get(tokenAddressInput) + .prev() + .find('p') + .contains(token) + .next() + .then((element) => { + const maxBalance = element.text().replace(tokenAbbreviation, '').trim() + cy.get(amountInput).should('have.value', maxBalance) + console.log(maxBalance) + }) +} + +export function setSendValue(value) { + cy.get(amountInput).clear().type(value) +} + +export function clickOnNextBtn() { + cy.contains(nextBtnStr).click() +} + +export function verifySubmitBtnIsEnabled() { + cy.get('button[type="submit"]').should('not.be.disabled') +} + +export function verifyNativeTokenTransfer() { + cy.contains(nativeTokenTransferStr).should('be.visible') +} + +export function changeNonce(value) { + cy.get(nonceInput).clear().type(value, { force: true }).type('{enter}', { force: true }) +} + +export function verifyConfirmTransactionData() { + cy.contains(yesStr).should('exist') + cy.contains(estimatedFeeStr).should('exist') + + // Asserting the sponsored info is present + cy.contains(executeStr).scrollIntoView().should('be.visible') + + cy.get('span').contains(estimatedFeeStr).next().should('have.css', 'text-decoration-line', 'line-through') + cy.contains(transactionsPerHrStr) + cy.contains(transactionsPerHr5Of5Str) +} + +export function openExecutionParamsModal() { + cy.contains(estimatedFeeStr).click() + cy.contains(editBtnStr).click() +} + +export function verifyAndSubmitExecutionParams() { + cy.contains(executionParamsStr).parents('form').as('Paramsform') + + // Only gaslimit should be editable when the relayer is selected + const arrayNames = ['Wallet nonce', 'Max priority fee (Gwei)', 'Max fee (Gwei)'] + arrayNames.forEach((element) => { + cy.get('@Paramsform').find('label').contains(`${element}`).next().find('input').should('be.disabled') + }) + + cy.get('@Paramsform').find(gasLimitInput).clear().type('300000').invoke('prop', 'value').should('equal', '300000') + cy.get('@Paramsform').find(gasLimitInput).parent('div').find(rotateLeftIcon).click() + cy.get('@Paramsform').submit() +} + +export function clickOnNoLaterOption() { + // Asserts the execute checkbox is uncheckable (???) + cy.contains(noLaterStr).click() +} + +export function clickOnSignTransactionBtn() { + cy.contains(signBtnStr).click() +} + +export function waitForProposeRequest() { + cy.intercept('POST', constants.proposeEndPoint).as('ProposeTx') + cy.wait('@ProposeTx') +} + +export function clickViewTransaction() { + cy.contains(viewTransactionBtn).click() +} + +export function verifySingleTxPage() { + cy.get('h3').contains(transactionDetailsTitle).should('be.visible') +} + +export function verifyQueueLabel() { + cy.contains(QueueLabel).should('be.visible') +} + +export function verifyTransactionSummary(sendValue) { + cy.contains(TransactionSummary + `${sendValue} ${constants.tokenAbbreviation.gor}`).should('exist') +} diff --git a/cypress/e2e/pages/create_wallet.pages.js b/cypress/e2e/pages/create_wallet.pages.js new file mode 100644 index 0000000000..09742fdb50 --- /dev/null +++ b/cypress/e2e/pages/create_wallet.pages.js @@ -0,0 +1,90 @@ +import { add } from 'lodash' +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 function clickOnCreateNewAccuntBtn() { + cy.contains(newAccountBtnStr).click() +} + +export function typeWalletName(name) { + cy.get(nameInput).should('have.attr', 'placeholder').should('match', constants.goerlySafeName) + cy.get(nameInput).type(name).should('have.value', name) +} + +export function selectNetwork(network, regex = false) { + cy.get(selectNetworkBtn).click() + cy.contains(network).click() + + if (regex) { + regex = constants.networks.goerli + cy.get(selectNetworkBtn).click().invoke('text').should('match', regex) + } else { + cy.get(selectNetworkBtn).click().should('have.text', network) + } + cy.get('body').click() +} + +export function clickOnNextBtn() { + cy.contains('button', 'Next').click() +} + +export function verifyOwnerName(name, index) { + cy.get(ownerInput).eq(index).should('have.value', name) +} + +export function verifyOwnerAddress(address, index) { + cy.get(ownerAddress).eq(index).should('have.value', address) +} + +export function verifyThreshold(number) { + cy.get(thresholdInput).should('have.value', number) +} + +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) +} + +function clickOnAddNewOwnerBtn() { + cy.contains('button', 'Add new owner').click() +} + +export function addNewOwner(name, address, index) { + clickOnAddNewOwnerBtn() + typeOwnerName(name, index) + typeOwnerAddress(address, index) +} + +export function updateThreshold(number) { + cy.get(thresholdInput).parent().click() + cy.contains('li', number).click() +} + +export function removeOwner(index) { + cy.get(removeOwnerBtn).eq(index).click() +} + +export function verifySummaryData(safeName, ownerAddress, startThreshold, endThreshold) { + cy.contains(safeName) + cy.contains(ownerAddress) + cy.contains(`${startThreshold} out of ${endThreshold}`) +} + +function getOwnerNameInput(index) { + return `input[name="owners.${index}.name"]` +} + +function getOwnerAddressInput(index) { + return `input[name="owners.${index}.address"]` +} diff --git a/cypress/e2e/pages/dashboard.pages.js b/cypress/e2e/pages/dashboard.pages.js new file mode 100644 index 0000000000..a2d90a3984 --- /dev/null +++ b/cypress/e2e/pages/dashboard.pages.js @@ -0,0 +1,87 @@ +import * as constants from '../../support/constants' + +const connectAndTransactStr = 'Connect & transact' +const transactionQueueStr = 'Transaction queue' +const noTransactionStr = 'This Safe has no queued transactions' +const overviewStr = 'Overview' +const viewAssetsStr = 'View assets' +const tokensStr = 'Tokens' +const nftStr = 'NFTs' +const viewAllStr = 'View all' +const transactionBuilderStr = 'Use Transaction Builder' +const walletConnectStr = 'Use WalletConnect' +const safeAppStr = 'Safe Apps' +const exploreSafeApps = 'Explore Safe Apps' + +const txBuilder = 'a[href*="tx-builder"]' +const walletConnect = 'a[href*="wallet-connect"]' +const safeSpecificLink = 'a[href*="&appUrl=http"]' + +export function verifyConnectTransactStrIsVisible() { + cy.contains(connectAndTransactStr).should('be.visible') +} + +export function verifyOverviewWidgetData() { + // Alias for the Overview section + cy.contains('h2', overviewStr).parents('section').as('overviewSection') + + cy.get('@overviewSection').within(() => { + // Prefix is separated across elements in EthHashInfo + cy.contains(constants.TEST_SAFE).should('exist') + cy.contains('1/2') + cy.get(`a[href="${constants.BALANCE_URL}${encodeURIComponent(constants.TEST_SAFE)}"]`).contains(viewAssetsStr) + // Text next to Tokens contains a number greater than 0 + cy.contains('p', tokensStr).next().contains('1') + cy.contains('p', nftStr).next().contains('0') + }) +} + +export function verifyTxQueueWidget() { + // Alias for the Transaction queue section + cy.contains('h2', transactionQueueStr).parents('section').as('txQueueSection') + + cy.get('@txQueueSection').within(() => { + // There should be queued transactions + cy.contains(noTransactionStr).should('not.exist') + + // Queued txns + cy.contains(`a[href^="/transactions/tx?id=multisig_0x"]`, '13' + 'Send' + '-0.00002 GOR' + '1/1').should('exist') + + cy.contains(`a[href="${constants.transactionQueueUrl}${encodeURIComponent(constants.TEST_SAFE)}"]`, viewAllStr) + }) +} + +export function verifyFeaturedAppsSection() { + // Alias for the featured Safe Apps section + cy.contains('h2', connectAndTransactStr).parents('section').as('featuredSafeAppsSection') + + // Tx Builder app + cy.get('@featuredSafeAppsSection').within(() => { + // Transaction Builder + cy.contains(transactionBuilderStr) + cy.get(txBuilder).should('exist') + + // WalletConnect app + cy.contains(walletConnectStr) + cy.get(walletConnect).should('exist') + + // Featured apps have a Safe-specific link + cy.get(safeSpecificLink).should('have.length', 2) + }) +} + +export function verifySafeAppsSection() { + // Create an alias for the Safe Apps section + cy.contains('h2', safeAppStr).parents('section').as('safeAppsSection') + + cy.get('@safeAppsSection').contains(exploreSafeApps) + + // Regular safe apps + cy.get('@safeAppsSection').within(() => { + // Find exactly 5 Safe Apps cards inside the Safe Apps section + cy.get(`a[href^="${constants.openAppsUrl}${encodeURIComponent(constants.TEST_SAFE)}&appUrl=http"]`).should( + 'have.length', + 5, + ) + }) +} diff --git a/cypress/e2e/pages/main.page.js b/cypress/e2e/pages/main.page.js index 1d7d2c24ef..d42bd81195 100644 --- a/cypress/e2e/pages/main.page.js +++ b/cypress/e2e/pages/main.page.js @@ -1,3 +1,5 @@ +import * as constants from '../../support/constants' + const acceptSelection = 'Accept selection' const gotItBtn = 'Got it' @@ -5,3 +7,7 @@ export function acceptCookies() { cy.contains(acceptSelection).click() cy.contains(acceptSelection).should('not.exist') } + +export function verifyGoerliWalletHeader() { + cy.contains(constants.goerlyE2EWallet) +} diff --git a/cypress/e2e/smoke/batch_tx.cy.js b/cypress/e2e/smoke/batch_tx.cy.js index 83ebd9b6d2..a410ae6986 100644 --- a/cypress/e2e/smoke/batch_tx.cy.js +++ b/cypress/e2e/smoke/batch_tx.cy.js @@ -1,46 +1,42 @@ -const SAFE = 'gor:0x04f8b1EA3cBB315b87ced0E32deb5a43cC151a91' -const EOA = '0xE297437d6b53890cbf004e401F3acc67c8b39665' +import * as batch from '../pages/batches.pages' +import * as constants from '../../support/constants' +import * as main from '../../e2e/pages/main.page' const currentNonce = 3 -const BATCH_TX_TOPBAR = '[data-track="batching: Batch sidebar open"]' -const BATCH_TX_COUNTER = '[data-track="batching: Batch sidebar open"] span > span' -const ADD_NEW_TX_BATCH = '[data-track="batching: Add new tx to batch"]' const funds_first_tx = '0.001' const funds_second_tx = '0.002' -var transactionsInBatchList = 0 describe('Create batch transaction', () => { before(() => { - cy.visit(`/home?safe=${SAFE}`) - cy.contains('Accept selection').click() - - cy.contains(/E2E Wallet @ G(ö|oe)rli/, { timeout: 10000 }) + cy.visit(constants.homeUrl + constants.TEST_SAFE) + main.acceptCookies() + cy.contains(constants.goerlyE2EWallet, { timeout: 10000 }) }) it('Should open an empty batch list', () => { - cy.get(BATCH_TX_TOPBAR).should('be.visible').click() - cy.contains('Batched transactions').should('be.visible') - cy.contains('Add an initial transaction to the batch') - cy.get(ADD_NEW_TX_BATCH).click() + batch.openBatchtransactionsModal() + batch.openNewTransactionModal() }) it('Should see the add batch button in a transaction form', () => { //The "true" is to validate that the add to batch button is not visible if "Yes, execute" is selected - addToBatch(EOA, currentNonce, funds_first_tx, true) + batch.addToBatch(constants.EOA, currentNonce, funds_first_tx, true) }) it('Should see the transaction being added to the batch', () => { - cy.contains('Transaction is added to batch').should('be.visible') + cy.contains(batch.transactionAddedToBatchStr).should('be.visible') //The batch button in the header shows the transaction count - cy.get(BATCH_TX_COUNTER).contains('1').click() - amountTransactionsInBatch(1) + batch.verifyBatchIconCount(1) + batch.clickOnBatchCounter() + batch.verifyAmountTransactionsInBatch(1) }) it('Should add a second transaction to the batch', () => { - cy.contains('Add new transaction').click() - addToBatch(EOA, currentNonce, funds_second_tx) - cy.get(BATCH_TX_COUNTER).contains('2').click() - amountTransactionsInBatch(2) + batch.openNewTransactionModal() + batch.addToBatch(constants.EOA, currentNonce, funds_second_tx) + batch.verifyBatchIconCount(2) + batch.clickOnBatchCounter() + batch.verifyAmountTransactionsInBatch(2) }) it.skip('Should swap transactions order', () => { @@ -48,53 +44,18 @@ describe('Create batch transaction', () => { }) it('Should confirm the batch and see 2 transactions in the form', () => { - cy.contains('Confirm batch').click() - cy.contains(`This batch contains ${transactionsInBatchList} transactions`).should('be.visible') + batch.clickOnConfirmBatchBtn() + batch.verifyBatchTransactionsCount(2) cy.contains(funds_first_tx).parents('ul').as('TransactionList') cy.get('@TransactionList').find('li').eq(0).find('span').eq(0).contains(funds_first_tx) cy.get('@TransactionList').find('li').eq(1).find('span').eq(0).contains(funds_second_tx) }) it('Should remove a transaction from the batch', () => { - cy.get(BATCH_TX_COUNTER).click() - cy.contains('Batched transactions').should('be.visible').parents('aside').find('ul > li').as('BatchList') - cy.get('@BatchList').find('[title="Delete transaction"]').eq(0).click() + batch.clickOnBatchCounter() + cy.contains(batch.batchedTransactionsStr).should('be.visible').parents('aside').find('ul > li').as('BatchList') + cy.get('@BatchList').find(batch.deleteTransactionbtn).eq(0).click() cy.get('@BatchList').should('have.length', 1) cy.get('@BatchList').contains(funds_first_tx).should('not.exist') }) }) - -const amountTransactionsInBatch = (count) => { - cy.contains('Batched transactions', { timeout: 7000 }) - .should('be.visible') - .parents('aside') - .find('ul > li') - .should('have.length', count) - transactionsInBatchList = count -} - -const addToBatch = (EOA, currentNonce, amount, verify = false) => { - // Modal is open - cy.contains('h1', 'New transaction').should('be.visible') - cy.contains('Send tokens').click() - - // Fill transaction data - cy.get('input[name="recipient"]').type(EOA, { delay: 1 }) - // Click on the Token selector - cy.get('input[name="tokenAddress"]').prev().click() - cy.get('ul[role="listbox"]') - - .contains(/G(ö|oe)rli Ether/) - .click() - cy.get('[name="amount"]').type(amount) - cy.contains('Next').click() - cy.get('input[name="nonce"]').clear().type(currentNonce, { force: true }).type('{enter}', { force: true }) - cy.contains('Execute').scrollIntoView() - //Only validates the button not showing once in the entire run - if (verify) { - cy.contains('Yes, execute', { timeout: 4000 }).click() - cy.contains('Add to batch').should('not.exist') - } - cy.contains('No, later', { timeout: 4000 }).click() - cy.contains('Add to batch').should('be.visible').and('not.be.disabled').click() -} diff --git a/cypress/e2e/smoke/create_safe_simple.cy.js b/cypress/e2e/smoke/create_safe_simple.cy.js index 9b248c84df..da63618b5e 100644 --- a/cypress/e2e/smoke/create_safe_simple.cy.js +++ b/cypress/e2e/smoke/create_safe_simple.cy.js @@ -1,81 +1,53 @@ 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' describe('Create Safe form', () => { it('should navigate to the form', () => { - cy.visit('/welcome') - - // Close cookie banner - cy.contains('button', 'Accept selection').click() - - // Ensure wallet is connected to correct chain via header - cy.contains(/E2E Wallet @ G(ö|oe)rli/) - - cy.contains('Create new Account').click() + cy.visit(constants.welcomeUrl) + main.acceptCookies() + main.verifyGoerliWalletHeader() + createwallet.clickOnCreateNewAccuntBtn() }) it('should allow setting a name', () => { - // Name input should have a placeholder ending in 'goerli-safe' - cy.get('input[name="name"]') - .should('have.attr', 'placeholder') - .should('match', /g(ö|oe)rli-safe/) - - // Input a custom name - cy.get('input[name="name"]').type('Test safe name').should('have.value', 'Test safe name') + createwallet.typeWalletName(safeName) }) it('should allow changing the network', () => { - // Switch to a different network - cy.get('[data-cy="create-safe-select-network"]').click() - cy.contains('Ethereum').click() - - // Switch back to Görli - cy.get('[data-cy="create-safe-select-network"]').click() - - // Prevent Base Mainnet Goerli from being selected - cy.contains('li span', /^G(ö|oe)rli$/).click() - - cy.contains('button', 'Next').click() + createwallet.selectNetwork(constants.networks.ethereum) + createwallet.selectNetwork(constants.networks.goerli, true) + createwallet.clickOnNextBtn() }) it('should display a default owner and threshold', () => { - // Default owner - cy.get('input[name="owners.0.address"]').should('have.value', constants.DEFAULT_OWNER_ADDRESS) - - // Default threshold - cy.get('input[name="threshold"]').should('have.value', 1) + createwallet.verifyOwnerAddress(constants.DEFAULT_OWNER_ADDRESS, 0) + createwallet.verifyThreshold(1) }) it('should allow changing the owner name', () => { - cy.get('input[name="owners.0.name"]').type('Test Owner Name') + createwallet.typeOwnerName(ownerName, 0) cy.contains('button', 'Back').click() cy.contains('button', 'Next').click() - cy.get('input[name="owners.0.name"]').should('have.value', 'Test Owner Name') + createwallet.verifyOwnerName(ownerName, 0) }) it('should add a new owner and update threshold', () => { - // Add new owner - cy.contains('button', 'Add new owner').click() - cy.get('input[name="owners.1.address"]').should('exist') - cy.get('input[name="owners.1.address"]').type(constants.EOA) - - // Update threshold - cy.get('input[name="threshold"]').parent().click() - cy.contains('li', '2').click() + createwallet.addNewOwner(ownerName2, constants.EOA, 1) + createwallet.updateThreshold(2) }) it('should remove an owner and update threshold', () => { - // Remove owner - cy.get('button[aria-label="Remove owner"]').click() - - // Threshold should change back to 1 - cy.get('input[name="threshold"]').should('have.value', 1) - - cy.contains('button', 'Next').click() + createwallet.removeOwner(0) + createwallet.verifyThreshold(1) + createwallet.clickOnNextBtn() }) it('should display summary on review page', () => { - cy.contains('Test safe name') - cy.contains(constants.DEFAULT_OWNER_ADDRESS) - cy.contains('1 out of 1') + createwallet.verifySummaryData(safeName, constants.DEFAULT_OWNER_ADDRESS, 1, 1) }) }) diff --git a/cypress/e2e/smoke/create_tx.cy.js b/cypress/e2e/smoke/create_tx.cy.js index 659839bfdc..43c27090ca 100644 --- a/cypress/e2e/smoke/create_tx.cy.js +++ b/cypress/e2e/smoke/create_tx.cy.js @@ -1,126 +1,45 @@ import * as constants from '../../support/constants' +import * as main from '../../e2e/pages/main.page' +import * as createtx from '../../e2e/pages/create_tx.pages' const sendValue = 0.00002 const currentNonce = 3 describe('Queue a transaction on 1/N', () => { before(() => { - cy.visit(`/home?safe=${constants.TEST_SAFE}`) - - cy.contains('Accept selection').click() + cy.visit(constants.homeUrl + constants.TEST_SAFE) + main.acceptCookies() }) it('should create and queue a transaction', () => { - // Assert that "New transaction" button is visible - cy.contains('New transaction', { - timeout: 60_000, // `lastWallet` takes a while initialize in CI - }) - .should('be.visible') - .and('not.be.disabled') - - // Open the new transaction modal - cy.contains('New transaction').click() - - // Modal is open - cy.contains('h1', 'New transaction').should('be.visible') - cy.contains('Send tokens').click() - - // Fill transaction data - cy.get('input[name="recipient"]').type(constants.EOA) - // Click on the Token selector - cy.get('input[name="tokenAddress"]').prev().click() - cy.get('ul[role="listbox"]') - .contains(/G(ö|oe)rli Ether/) - .click() - - // Insert max amount - cy.contains('Max').click() - - // Validates the "Max" button action, then clears and sets the actual sendValue - cy.get('input[name="tokenAddress"]') - .prev() - .find('p') - .contains(/G(ö|oe)rli Ether/) - .next() - .then((element) => { - const maxBalance = element.text().replace(' GOR', '').trim() - cy.get('input[name="amount"]').should('have.value', maxBalance) - }) - - cy.get('input[name="amount"]').clear().type(sendValue) - - cy.contains('Next').click() + createtx.clickOnNewtransactionBtn() + createtx.clickOnSendTokensBtn() + createtx.typeRecipientAddress(constants.EOA) + createtx.clickOnTokenselectorAndSelectGoerli() + createtx.setMaxAmount() + createtx.verifyMaxAmount(constants.goerliToken, constants.tokenAbbreviation.gor) + createtx.setSendValue(sendValue) + createtx.clickOnNextBtn() }) it('should create a queued transaction', () => { - cy.get('button[type="submit"]').should('not.be.disabled') - + createtx.verifySubmitBtnIsEnabled() cy.wait(1000) - - cy.contains('Native token transfer').should('be.visible') - - // Changes nonce to next one - cy.get('input[name="nonce"]').clear().type(currentNonce, { force: true }).type('{enter}', { force: true }) - - // Execution - cy.contains('Yes, ').should('exist') - cy.contains('Estimated fee').should('exist') - - // Asserting the sponsored info is present - cy.contains('Execute').scrollIntoView().should('be.visible') - - cy.get('span').contains('Estimated fee').next().should('have.css', 'text-decoration-line', 'line-through') - cy.contains('Transactions per hour') - cy.contains('5 of 5') - - cy.contains('Estimated fee').click() - cy.contains('Edit').click() - cy.contains('Execution parameters').parents('form').as('Paramsform') - - // Only gaslimit should be editable when the relayer is selected - const arrayNames = ['Wallet nonce', 'Max priority fee (Gwei)', 'Max fee (Gwei)'] - arrayNames.forEach((element) => { - cy.get('@Paramsform').find('label').contains(`${element}`).next().find('input').should('be.disabled') - }) - - cy.get('@Paramsform') - .find('[name="gasLimit"]') - .clear() - .type('300000') - .invoke('prop', 'value') - .should('equal', '300000') - cy.get('@Paramsform').find('[name="gasLimit"]').parent('div').find('[data-testid="RotateLeftIcon"]').click() - - cy.get('@Paramsform').submit() - - // Asserts the execute checkbox is uncheckable - cy.contains('No, later').click() - - cy.get('input[name="nonce"]') - .clear({ force: true }) - .type(currentNonce + 10, { force: true }) - .type('{enter}', { force: true }) - - cy.contains('Sign').click() + createtx.verifyNativeTokenTransfer() + createtx.changeNonce(currentNonce) + createtx.verifyConfirmTransactionData() + createtx.openExecutionParamsModal() + createtx.verifyAndSubmitExecutionParams() + createtx.clickOnNoLaterOption() + createtx.changeNonce(13) + createtx.clickOnSignTransactionBtn() }) it('should click the notification and see the transaction queued', () => { - // Wait for the /propose request - cy.intercept('POST', '/**/propose').as('ProposeTx') - cy.wait('@ProposeTx') - - // Click on the notification - cy.contains('View transaction').click() - - //cy.contains('Queue').click() - - // Single Tx page - cy.contains('h3', 'Transaction details').should('be.visible') - - // Queue label - cy.contains(`needs to be executed first`).should('be.visible') - - // Transaction summary - cy.contains('Send' + '-' + `${sendValue} GOR`).should('exist') + createtx.waitForProposeRequest() + createtx.clickViewTransaction() + createtx.verifySingleTxPage() + createtx.verifyQueueLabel() + createtx.verifyTransactionSummary(sendValue) }) }) diff --git a/cypress/e2e/smoke/dashboard.cy.js b/cypress/e2e/smoke/dashboard.cy.js index 73f5a61bb3..88f4249aa5 100644 --- a/cypress/e2e/smoke/dashboard.cy.js +++ b/cypress/e2e/smoke/dashboard.cy.js @@ -1,78 +1,27 @@ import * as constants from '../../support/constants' +import * as dashboard from '../pages/dashboard.pages' +import * as main from '../pages/main.page' describe('Dashboard', () => { before(() => { - // Go to the test Safe home page - cy.visit(`/home?safe=${constants.TEST_SAFE}`) - - cy.contains('button', 'Accept selection').click() - - // Wait for dashboard to initialize - cy.contains('Connect & transact') + cy.visit(constants.homeUrl + constants.TEST_SAFE) + main.acceptCookies() + dashboard.verifyConnectTransactStrIsVisible() }) it('should display the overview widget', () => { - // Alias for the Overview section - cy.contains('h2', 'Overview').parents('section').as('overviewSection') - - cy.get('@overviewSection').within(() => { - // Prefix is separated across elements in EthHashInfo - cy.contains(constants.TEST_SAFE).should('exist') - cy.contains('1/2') - cy.get(`a[href="/balances?safe=${encodeURIComponent(constants.TEST_SAFE)}"]`).contains('View assets') - // Text next to Tokens contains a number greater than 0 - cy.contains('p', 'Tokens').next().contains('1') - cy.contains('p', 'NFTs').next().contains('0') - }) + dashboard.verifyOverviewWidgetData() }) it('should display the tx queue widget', () => { - // Alias for the Transaction queue section - cy.contains('h2', 'Transaction queue').parents('section').as('txQueueSection') - - cy.get('@txQueueSection').within(() => { - // There should be queued transactions - cy.contains('This Safe has no queued transactions').should('not.exist') - - // Queued txns - cy.contains(`a[href^="/transactions/tx?id=multisig_0x"]`, '13' + 'Send' + '-0.00002 GOR' + '1/1').should('exist') - - cy.contains(`a[href="/transactions/queue?safe=${encodeURIComponent(constants.TEST_SAFE)}"]`, 'View all') - }) + dashboard.verifyTxQueueWidget() }) it('should display the featured Safe Apps', () => { - // Alias for the featured Safe Apps section - cy.contains('h2', 'Connect & transact').parents('section').as('featuredSafeAppsSection') - - // Tx Builder app - cy.get('@featuredSafeAppsSection').within(() => { - // Transaction Builder - cy.contains('Use Transaction Builder') - cy.get(`a[href*='tx-builder']`).should('exist') - - // WalletConnect app - cy.contains('Use WalletConnect') - cy.get(`a[href*='wallet-connect']`).should('exist') - - // Featured apps have a Safe-specific link - cy.get(`a[href*="&appUrl=http"]`).should('have.length', 2) - }) + dashboard.verifyFeaturedAppsSection() }) it('should show the Safe Apps Section', () => { - // Create an alias for the Safe Apps section - cy.contains('h2', 'Safe Apps').parents('section').as('safeAppsSection') - - cy.get('@safeAppsSection').contains('Explore Safe Apps') - - // Regular safe apps - cy.get('@safeAppsSection').within(() => { - // Find exactly 5 Safe Apps cards inside the Safe Apps section - cy.get(`a[href^="/apps/open?safe=${encodeURIComponent(constants.TEST_SAFE)}&appUrl=http"]`).should( - 'have.length', - 5, - ) - }) + dashboard.verifySafeAppsSection() }) }) diff --git a/cypress/support/constants.js b/cypress/support/constants.js index 57eaf2691a..48c250f579 100644 --- a/cypress/support/constants.js +++ b/cypress/support/constants.js @@ -12,9 +12,18 @@ export const BROWSER_PERMISSIONS_KEY = `${LS_NAMESPACE}SafeApps__browserPermissi export const SAFE_PERMISSIONS_KEY = `${LS_NAMESPACE}SafeApps__safePermissions` export const INFO_MODAL_KEY = `${LS_NAMESPACE}SafeApps__infoModal` +export const goerlyE2EWallet = /E2E Wallet @ G(ö|oe)rli/ +export const goerlySafeName = /g(ö|oe)rli-safe/ +export const goerliToken = /G(ö|oe)rli Ether/ + export const appUrlProd = 'https://safe-test-app.com' export const addressBookUrl = '/address-book?safe=' export const BALANCE_URL = '/balances?safe=' +export const transactionQueueUrl = '/transactions/queue?safe=' +export const openAppsUrl = '/apps/open?safe=' +export const homeUrl = '/home?safe=' +export const welcomeUrl = '/welcome' +export const proposeEndPoint = '/**/propose' export const GOERLI_CSV_ENTRY = { name: 'goerli user 1', @@ -24,3 +33,13 @@ export const GNO_CSV_ENTRY = { name: 'gno user 1', address: '0x61a0c717d18232711bC788F19C9Cd56a43cc8872', } + +export const networks = { + ethereum: 'Ethereum', + goerli: /^G(ö|oe)rli$/, + sepolia: 'Sepolia', +} + +export const tokenAbbreviation = { + gor: 'GOR', +}