diff --git a/cypress/e2e/pages/address_book.page.js b/cypress/e2e/pages/address_book.page.js index 611b7d826b..eff18a543a 100644 --- a/cypress/e2e/pages/address_book.page.js +++ b/cypress/e2e/pages/address_book.page.js @@ -1,41 +1,164 @@ -export const acceptSelection = 'Save settings' -export const addressBook = 'Address book' -const createEntryBtn = 'Create entry' +import * as constants from '../../support/constants' +import * as main from '../pages/main.page' +export const addressBookRecipient = '[data-testid="address-book-recipient"]' const beameriFrameContainer = '#beamerOverlay .iframeCointaner' const beamerInput = 'input[id="beamer"]' const nameInput = 'input[name="name"]' const addressInput = 'input[name="address"]' -export const addressBookRecipient = '[data-testid="address-book-recipient"]' -const saveBtn = 'Save' +const exportModalBtn = '[data-testid="export-modal-btn"]' export const editEntryBtn = 'button[aria-label="Edit entry"]' export const deleteEntryBtn = 'button[aria-label="Delete entry"]' export const deleteEntryModalBtnSection = '.MuiDialogActions-root' +const tableContainer = '[data-testid="table-container"]' +const tableRow = '[data-testid="table-row"]' +const importBtn = '[data-testid="import-btn"]' +const cancelImportBtn = '[data-testid="cancel-btn"]' +const uploadErrorMsg = '[data-testid="error-message"]' +const modalSummaryMessage = '[data-testid="summary-message"]' +const saveBtn = '[data-testid="save-btn"]' +const divInput = '[data-testid="name-input"]' +const exportSummary = '[data-testid="export-summary"]' +const sendBtn = '[data-testid="send-btn"]' +const nextPageBtn = 'button[aria-label="Go to next page"]' +const previousPageBtn = 'button[aria-label="Go to previous page"]' + +//TODO Move to specific component +const moreActionIcon = '[data-testid="MoreHorizIcon"]' + +export const acceptSelection = 'Save settings' +export const addressBook = 'Address book' +const createEntryBtn = 'Create entry' export const delteEntryModaldeleteBtn = 'Delete' -const importBtn = 'Import' const exportBtn = 'Export' -const exportModalBtn = '[data-testid="export-modal-btn"]' +// const saveBtn = 'Save' const whatsNewBtnStr = "What's new" const beamrCookiesStr = 'accept the "Beamer" cookies' +const headerImportBtnStr = 'Import' +const mandatoryNameStr = 'Name *' +const nameSortBtn = 'Name' +const addressortBtn = 'Address' +const addToAddressBookStr = 'Add to address book' + +export const emptyCSVFile = '../fixtures/address_book_empty_test.csv' +export const nonCSVFile = '../fixtures/balances.json' +export const duplicatedCSVFile = 'address_book_duplicated.csv' +export const validCSVFile = '../fixtures/address_book_test.csv' +export const networksCSVFile = '../fixtures/address_book_networks.csv' +export const addedSafesCSVFile = '../fixtures/address_book_addedsafes.csv' + +const sortSafe1 = 'AA Safe' +const sortSafe2 = 'BB Safe' + +export const entries = [ + '0x6E834E9D04ad6b26e1525dE1a37BFd9b215f40B7', + 'test-sepolia-3', + '0xf405BC611F4a4c89CCB3E4d083099f9C36D966f8', + 'sepolia-test-4', + '0x03042B890b99552b60A073F808100517fb148F60', + 'sepolia-test-5', + '0xBd69b0a9DC90eB6F9bAc3E4a5875f437348b6415', + 'assets-test-sepolia', +] + +export function clickOnNextPageBtn() { + cy.get(nextPageBtn).click() +} + +export function clickOnPrevPageBtn() { + cy.get(previousPageBtn).click() +} + +export function verifyCountOfSafes(count) { + main.verifyElementsCount(tableRow, count) +} +export function verifyRecipientData(data) { + main.verifyValuesExist(addressBookRecipient, data) +} + +export function clickOnSendBtn() { + cy.get(sendBtn).click() +} + +export function clickOnMoreActionsBtn() { + cy.get(moreActionIcon).click() +} + +export function clickOnAddToAddressBookBtn() { + cy.get('li span').contains(addToAddressBookStr).click() +} + +export function verifyExportMessage(count) { + let msg = `${count} address book` + cy.get(exportSummary).should('contain', msg) +} + +export function clickOnNameSortBtn() { + cy.get(tableContainer).contains(nameSortBtn).click() + cy.wait(500) +} + +export function clickOnAddrressSortBtn() { + cy.get(tableContainer).contains(addressortBtn).click() + cy.wait(500) +} + +export function verifyEntriesOrder(option = 'ascending') { + let address = constants.DEFAULT_OWNER_ADDRESS + let name = sortSafe1 + if (option == 'descending') { + address = constants.RECIPIENT_ADDRESS + name = sortSafe2 + } + + cy.get(tableRow).eq(0).contains(address) + cy.get(tableRow).eq(0).contains(name) +} + +export function addEntryByENS(name, ens) { + typeInName(name) + typeInAddress(ens) + clickOnSaveEntryBtn() + verifyNewEntryAdded(name, constants.SEPOLIA_TEST_SAFE_7) +} + +export function verifyModalSummaryMessage(entryCount, chainCount) { + cy.get(modalSummaryMessage).should( + 'contain', + `Found ${entryCount} entries on ${chainCount} ${chainCount > 1 ? 'chains' : 'chain'}`, + ) +} +export const uploadErrorMessages = { + fileType: 'File type must be text/csv', + emptyFile: 'No entries found in address book', +} + +export function verifyUploadExportMessage(msg) { + main.verifyValuesExist(uploadErrorMsg, msg) +} + +export function verifyImportBtnStatus(status) { + main.verifyElementsStatus([importBtn], status) +} + +export function verifyNumberOfRows(number) { + main.verifyElementsCount(tableRow, number) +} export function clickOnImportFileBtn() { - cy.contains(importBtn).click() + cy.contains(headerImportBtnStr).click() } -export function importFile() { - cy.get('[type="file"]').attachFile('../fixtures/address_book_test.csv') - // Import button should be enabled - cy.get('.MuiDialogActions-root').contains('Import').should('not.be.disabled') - cy.get('.MuiDialogActions-root').contains('Import').click() +export function importCSVFile(file) { + cy.get('[type="file"]').attachFile(file) } -export function verifyImportModalIsClosed() { - cy.get('Import address book').should('not.exist') +export function clickOnImportBtn() { + cy.get(importBtn).click() } -export function verifyDataImported(name, address) { - cy.contains(name).should('exist') - cy.contains(address).should('exist') +export function verifyDataImported(data) { + main.verifyValuesExist(tableContainer, data) } export function clickOnExportFileBtn() { @@ -59,7 +182,7 @@ export function typeInAddress(address) { } export function clickOnSaveEntryBtn() { - cy.contains('button', saveBtn).click() + cy.get(saveBtn).click() } export function verifyNewEntryAdded(name, address) { @@ -82,10 +205,6 @@ export function typeInNameInput(name) { cy.get(nameInput).clear().type(name).should('have.value', name) } -export function clickOnSaveButton() { - cy.contains('button', saveBtn).click() -} - export function verifyNameWasChanged(name, editedName) { cy.get(name).should('not.exist') cy.contains(editedName).should('exist') @@ -119,3 +238,9 @@ export function verifyBeameriFrameExists() { cy.wait(1000) cy.get(beameriFrameContainer).should('exist') } + +export function verifyEmptyOwnerNameNotAllowed() { + cy.get(nameInput).clear() + main.verifyElementsStatus([saveBtn], constants.enabledStates.disabled) + cy.get(divInput).contains(mandatoryNameStr) +} diff --git a/cypress/e2e/pages/assets.pages.js b/cypress/e2e/pages/assets.pages.js index 2509eefb30..444e4f58ab 100644 --- a/cypress/e2e/pages/assets.pages.js +++ b/cypress/e2e/pages/assets.pages.js @@ -17,6 +17,8 @@ const hiddenTokenSaveBtn = 'span[data-track="assets: Save hide dialog"]' const hiddenTokenCancelBtn = 'span[data-track="assets: Cancel hide dialog"]' const hiddenTokenDeselectAllBtn = 'span[data-track="assets: Deselect all hide dialog"]' const hiddenTokenIcon = 'svg[data-testid="VisibilityOffOutlinedIcon"]' +const currencySelector = '[data-testid="currency-selector"]' +const currencyItem = '[data-testid="currency-item"]' const hideTokenDefaultString = 'Hide tokens' const assetNameSortBtnStr = 'Asset' @@ -91,6 +93,15 @@ export const currentcyGnosisFormat = '< 0.00001 GNO' export const currencyOx = /^0x$/ export const currentcyOxFormat = '1.003 ZRX' +function clickOnCurrencySelector() { + cy.get(currencySelector).click() +} + +export function changeCurrency(currency) { + clickOnCurrencySelector() + cy.get(currencyItem).contains(currency).click() +} + export function clickOnSendBtn(index) { cy.get('button') .contains(sendBtnStr) diff --git a/cypress/e2e/pages/create_wallet.pages.js b/cypress/e2e/pages/create_wallet.pages.js index 4d458ddd43..77c4e945ef 100644 --- a/cypress/e2e/pages/create_wallet.pages.js +++ b/cypress/e2e/pages/create_wallet.pages.js @@ -10,7 +10,8 @@ const ownerAddress = 'input[name^="owners"][name$="address"]' const thresholdInput = 'input[name="threshold"]' export const removeOwnerBtn = 'button[aria-label="Remove owner"]' const connectingContainer = 'div[class*="connecting-container"]' -const createNewSafeBtn = 'span[data-track="create-safe: Continue to creation"]' +const createNewSafeBtn = '[data-testid="create-safe-btn"]' +const continueWithWalletBtn = 'span[data-track="create-safe: Continue to my accounts"]' const connectWalletBtn = 'Connect wallet' const googleConnectBtn = '[data-testid="google-connect-btn"]' const googleSignedinBtn = '[data-testid="signed-in-account-btn"]' @@ -110,6 +111,10 @@ export function clickOnCreateNewSafeBtn() { cy.get(createNewSafeBtn).click().wait(1000) } +export function clickOnContinueWithWalletBtn() { + cy.get(continueWithWalletBtn).click().wait(1000) +} + export function clickOnConnectWalletBtn() { cy.get(welcomeLoginScreen).within(() => { cy.get('button').contains(connectWalletBtn).should('be.visible').should('be.enabled').click().wait(1000) diff --git a/cypress/e2e/pages/navigation.page.js b/cypress/e2e/pages/navigation.page.js index 8748d2a581..f2fec60f15 100644 --- a/cypress/e2e/pages/navigation.page.js +++ b/cypress/e2e/pages/navigation.page.js @@ -9,6 +9,9 @@ const sentinelStart = 'div[data-testid="sentinelStart"]' const disconnectBtnStr = 'Disconnect' const notConnectedStatus = 'Connect' +export function verifyTxBtnStatus(status) { + cy.get(newTxBtn).should(status) +} export function clickOnSideNavigation(option) { cy.get(option).should('exist').click() } @@ -23,7 +26,7 @@ export function clickOnNewTxBtn() { export function clickOnWalletExpandMoreIcon() { cy.get(expandMoreIcon).eq(0).click() - cy.get(sentinelStart).next().should('be.visible') + cy.get(sentinelStart).next().should('exist') } export function clickOnDisconnectBtn() { diff --git a/cypress/e2e/pages/sidebar.pages.js b/cypress/e2e/pages/sidebar.pages.js index 9604103e55..003e324c39 100644 --- a/cypress/e2e/pages/sidebar.pages.js +++ b/cypress/e2e/pages/sidebar.pages.js @@ -15,7 +15,7 @@ const sideBarListItem = '[data-testid="sidebar-list-item"]' const sideBarListItemWhatsNew = '[data-testid="list-item-whats-new"]' const sideBarListItemNeedHelp = '[data-testid="list-item-need-help"]' const sideSafeListItem = '[data-testid="safe-list-item"]' -const sidebarSafeHeader = '[data-testid="sidebar-safe-header"]' +const sidebarSafeHeader = '[data-testid="safe-header-info"]' const sidebarSafeContainer = '[data-testid="sidebar-safe-container"]' const safeItemOptionsBtn = '[data-testid="safe-options-btn"]' const safeItemOptionsRenameBtn = '[data-testid="rename-btn"]' @@ -26,8 +26,10 @@ const cancelBtn = '[data-testid="cancel-btn"]' const deleteBtn = '[data-testid="delete-btn"]' const readOnlyVisibility = '[data-testid="read-only-visibility"]' const currencySection = '[data-testid="currency-section"]' +const missingSignatureInfo = '[data-testid="missing-signature-info"]' +const queuedTxInfo = '[data-testid="queued-tx-info"]' -export const addedSafesGnosis = ['0x17b3...98C8', '0x11A6...F1BB', '0xB8d7...642A'] +export const addedSafesEth = ['0x8675...a19b'] export const addedSafesSepolia = ['0x6d0b...6dC1', '0x5912...fFdb', '0x0637...708e', '0xD157...DE9a'] export const sideBarListItems = ['Home', 'Assets', 'Transactions', 'Address book', 'Apps', 'Settings'] export const testSafeHeaderDetails = ['2/2', constants.SEPOLIA_TEST_SAFE_13_SHORT] @@ -113,6 +115,7 @@ export function verifySafesByNetwork(netwrok, safes) { cy.get(sidebarSafeContainer).within(() => { cy.get(chainLogo) .contains(netwrok) + .parent() .next() .within(() => { main.verifyValuesExist(sideSafeListItem, safes) @@ -120,27 +123,26 @@ export function verifySafesByNetwork(netwrok, safes) { }) } +function getSafeItemByName(name) { + return cy.get(sidebarSafeContainer).find(sideSafeListItem).contains(name).parents('li') +} + export function verifySafeReadOnlyState(safe) { - cy.get(sidebarSafeContainer).within(() => { - cy.get(sideSafeListItem) - .contains(safe) - .parents('li') - .within(() => { - cy.get(readOnlyVisibility).should('exist') - }) - }) + getSafeItemByName(safe).find(readOnlyVisibility).should('exist') +} + +export function verifyMissingSignature(safe) { + getSafeItemByName(safe).find(missingSignatureInfo).should('exist') +} + +export function verifyQueuedTx(safe) { + return getSafeItemByName(safe).find(queuedTxInfo).should('exist') } function clickOnSafeItemOptionsBtn(name) { - cy.get(sidebarSafeContainer).within(() => { - cy.get(sideSafeListItem) - .contains(name) - .parents('li') - .within(() => { - cy.get(safeItemOptionsBtn).click() - }) - }) + getSafeItemByName(name).find(safeItemOptionsBtn).click() } + export function renameSafeItem(oldName, newName) { clickOnSafeItemOptionsBtn(oldName) clickOnRenameBtn() @@ -188,3 +190,9 @@ export function clickOnSaveBtn() { function verifyModalRemoved() { main.verifyElementsCount(modal.modalTitle, 0) } + +export function checkCurrencyInHeader(currency) { + cy.get(sidebarSafeHeader).within(() => { + cy.get(currencySection).contains(currency) + }) +} diff --git a/cypress/e2e/regression/address_book.cy.js b/cypress/e2e/regression/address_book.cy.js index edfbc15ba4..b2f9ee4150 100644 --- a/cypress/e2e/regression/address_book.cy.js +++ b/cypress/e2e/regression/address_book.cy.js @@ -5,18 +5,20 @@ import * as constants from '../../support/constants' import * as addressBook from '../../e2e/pages/address_book.page' import * as main from '../../e2e/pages/main.page' import * as ls from '../../support/localstorage_data.js' +import * as sidebar from '../pages/sidebar.pages.js' const NAME = 'Owner1' const EDITED_NAME = 'Edited Owner1' +const importedSafe = 'imported-safe' describe('Address book tests', () => { beforeEach(() => { - cy.clearLocalStorage() cy.visit(constants.addressBookUrl + constants.SEPOLIA_TEST_SAFE_1) + cy.clearLocalStorage() main.acceptCookies() }) - it('Verify entered entry in Name input can be saved', () => { + it('Verify owners name can be edited', () => { main .addToLocalStorage(constants.localStorageKeys.SAFE_v2__addressBook, ls.addressBookData.sepoliaAddress1) .then(() => { @@ -26,7 +28,7 @@ describe('Address book tests', () => { cy.reload() addressBook.clickOnEditEntryBtn() addressBook.typeInNameInput(EDITED_NAME) - addressBook.clickOnSaveButton() + addressBook.clickOnSaveEntryBtn() addressBook.verifyNameWasChanged(NAME, EDITED_NAME) }) }) @@ -51,8 +53,7 @@ describe('Address book tests', () => { cy.contains(constants.GNO_CSV_ENTRY.address).should('exist') }) - // TODO: Change title in Testrail. New title "...exported" - it('Verify the address book file can be downloaded', () => { + it('Verify the address book file can be exported', () => { main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__addressBook, ls.addressBookData.dataSet).then(() => { main .isItemInLocalstorage(constants.localStorageKeys.SAFE_v2__addressBook, ls.addressBookData.dataSet) @@ -64,6 +65,7 @@ describe('Address book tests', () => { const fileName = `safe-address-book-${date}.csv` addressBook.clickOnExportFileBtn() + addressBook.verifyExportMessage(12) addressBook.confirmExport() const downloadsFolder = Cypress.config('downloadsFolder') @@ -72,4 +74,37 @@ describe('Address book tests', () => { }) }) }) + + it('Verify that importing a csv file does not alter addresses in the Address book not present in the file', () => { + main + .addToLocalStorage(constants.localStorageKeys.SAFE_v2__addressBook, ls.addressBookData.sepoliaAddress1) + .then(() => { + main + .isItemInLocalstorage(constants.localStorageKeys.SAFE_v2__addressBook, ls.addressBookData.sepoliaAddress1) + .then(() => { + cy.wait(1000) + cy.reload() + addressBook.clickOnImportFileBtn() + addressBook.importCSVFile(addressBook.validCSVFile) + addressBook.clickOnImportBtn() + addressBook.verifyDataImported([constants.RECIPIENT_ADDRESS]) + }) + }) + }) + + it('Verify Safe name changes after uploading a csv file', () => { + main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__addedSafes, ls.addedSafes.set4).then(() => { + main + .addToLocalStorage(constants.localStorageKeys.SAFE_v2__addressBook, ls.addressBookData.addedSafesImport) + .then(() => { + cy.wait(1000) + cy.reload() + addressBook.clickOnImportFileBtn() + addressBook.importCSVFile(addressBook.addedSafesCSVFile) + addressBook.clickOnImportBtn() + sidebar.openSidebar() + sidebar.verifyAddedSafesExist([importedSafe]) + }) + }) + }) }) diff --git a/cypress/e2e/regression/address_book_2.cy.js b/cypress/e2e/regression/address_book_2.cy.js new file mode 100644 index 0000000000..d1c125d093 --- /dev/null +++ b/cypress/e2e/regression/address_book_2.cy.js @@ -0,0 +1,86 @@ +import * as constants from '../../support/constants.js' +import * as addressBook from '../pages/address_book.page.js' +import * as main from '../pages/main.page.js' +import * as ls from '../../support/localstorage_data.js' +import * as createtx from '../../e2e/pages/create_tx.pages' +import * as data from '../../fixtures/txhistory_data_data.json' + +const typeReceive = data.type.receive + +const owner1 = 'Automation owner' +const onwer2 = 'Changed Automation owner' +const onwer3 = 'New Automation owner' + +describe('Address book tests - 2', () => { + beforeEach(() => { + cy.visit(constants.addressBookUrl + constants.SEPOLIA_TEST_SAFE_1) + cy.clearLocalStorage() + cy.wait(1000) + main.acceptCookies() + }) + + it('Verify Name and Address columns sorting works', () => { + main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__addressBook, ls.addressBookData.sortingData) + cy.reload() + addressBook.clickOnNameSortBtn() + addressBook.verifyEntriesOrder() + addressBook.clickOnNameSortBtn() + addressBook.verifyEntriesOrder('descending') + + // Clicking twice is required to trigger actual click after swtiching from Name + addressBook.clickOnAddrressSortBtn() + addressBook.clickOnAddrressSortBtn() + addressBook.verifyEntriesOrder() + addressBook.clickOnAddrressSortBtn() + addressBook.verifyEntriesOrder('descending') + }) + + it('Verify that edit owners name changes the name in the settings', () => { + main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__addressBook, ls.addressBookData.sepoliaAddress2) + cy.reload() + addressBook.clickOnEditEntryBtn() + addressBook.typeInNameInput(onwer2) + addressBook.clickOnSaveEntryBtn() + addressBook.verifyNameWasChanged(owner1, onwer2) + cy.visit(constants.setupUrl + constants.SEPOLIA_TEST_SAFE_1) + addressBook.verifyNameWasChanged(owner1, onwer2) + }) + + it('Verify that editing an entry from the transaction details updates the name in address book', () => { + cy.visit(constants.transactionsHistoryUrl + constants.SEPOLIA_TEST_SAFE_8) + main.waitForHistoryCallToComplete() + createtx.clickOnTransactionItemByName(typeReceive.summaryTitle, typeReceive.summaryTxInfo) + addressBook.clickOnMoreActionsBtn() + addressBook.clickOnAddToAddressBookBtn() + addressBook.typeInNameInput(onwer3) + addressBook.clickOnSaveEntryBtn() + addressBook.verifyNameWasChanged(owner1, onwer3) + cy.visit(constants.addressBookUrl + constants.SEPOLIA_TEST_SAFE_8) + addressBook.verifyNameWasChanged(owner1, onwer3) + }) + + it('Verify copy to clipboard/Etherscan work as expected', () => { + main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__addressBook, ls.addressBookData.sepoliaAddress1) + cy.wait(1000) + cy.reload() + createtx.verifyCopyIconWorks(0, constants.RECIPIENT_ADDRESS) + createtx.verifyNumberOfExternalLinks(1) + }) + + it('Verify by default there 25 rows shown per page', () => { + main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__addressBook, ls.addressBookData.pagination) + cy.wait(1000) + cy.reload() + addressBook.verifyCountOfSafes(25) + }) + + it('Verify that clicking on next and previous page buttons shows safes', () => { + main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__addressBook, ls.addressBookData.pagination) + cy.wait(1000) + cy.reload() + addressBook.clickOnNextPageBtn() + addressBook.verifyCountOfSafes(1) + addressBook.clickOnPrevPageBtn() + addressBook.verifyCountOfSafes(25) + }) +}) diff --git a/cypress/e2e/regression/create_safe_simple.cy.js b/cypress/e2e/regression/create_safe_simple.cy.js index f8627e89d9..327179d93e 100644 --- a/cypress/e2e/regression/create_safe_simple.cy.js +++ b/cypress/e2e/regression/create_safe_simple.cy.js @@ -14,6 +14,7 @@ describe('Safe creation tests', () => { it('Verify Next button is disabled until switching to network is done', () => { owner.waitForConnectionStatus() createwallet.selectNetwork(constants.networks.ethereum) + createwallet.clickOnContinueWithWalletBtn() createwallet.clickOnCreateNewSafeBtn() createwallet.checkNetworkChangeWarningMsg() createwallet.verifyNextBtnIsDisabled() @@ -24,6 +25,7 @@ describe('Safe creation tests', () => { // TODO: Check unit tests it('Verify error message is displayed if wallet name input exceeds 50 characters', () => { owner.waitForConnectionStatus() + createwallet.clickOnContinueWithWalletBtn() createwallet.clickOnCreateNewSafeBtn() createwallet.typeWalletName(main.generateRandomString(51)) owner.verifyErrorMsgInvalidAddress(constants.addressBookErrrMsg.exceedChars) @@ -34,6 +36,7 @@ describe('Safe creation tests', () => { // TODO: Check unit tests it('Verify there is no error message is displayed if wallet name input contains less than 50 characters', () => { owner.waitForConnectionStatus() + createwallet.clickOnContinueWithWalletBtn() createwallet.clickOnCreateNewSafeBtn() createwallet.typeWalletName(main.generateRandomString(50)) owner.verifyValidWalletName(constants.addressBookErrrMsg.exceedChars) @@ -41,6 +44,7 @@ describe('Safe creation tests', () => { it('Verify current connected account is shown as default owner', () => { owner.waitForConnectionStatus() + createwallet.clickOnContinueWithWalletBtn() createwallet.clickOnCreateNewSafeBtn() createwallet.clickOnNextBtn() owner.verifyExistingOwnerAddress(0, constants.DEFAULT_OWNER_ADDRESS) @@ -49,6 +53,7 @@ describe('Safe creation tests', () => { // TODO: Check unit tests it('Verify error message is displayed if owner name input exceeds 50 characters', () => { owner.waitForConnectionStatus() + createwallet.clickOnContinueWithWalletBtn() createwallet.clickOnCreateNewSafeBtn() owner.typeExistingOwnerName(main.generateRandomString(51)) owner.verifyErrorMsgInvalidAddress(constants.addressBookErrrMsg.exceedChars) @@ -57,6 +62,7 @@ describe('Safe creation tests', () => { // TODO: Check unit tests it('Verify there is no error message is displayed if owner name input contains less than 50 characters', () => { owner.waitForConnectionStatus() + createwallet.clickOnContinueWithWalletBtn() createwallet.clickOnCreateNewSafeBtn() owner.typeExistingOwnerName(main.generateRandomString(50)) owner.verifyValidWalletName(constants.addressBookErrrMsg.exceedChars) @@ -65,6 +71,7 @@ describe('Safe creation tests', () => { it('Verify data persistence', () => { const ownerName = 'David' owner.waitForConnectionStatus() + createwallet.clickOnContinueWithWalletBtn() createwallet.clickOnCreateNewSafeBtn() createwallet.clickOnNextBtn() createwallet.clickOnAddNewOwnerBtn() @@ -97,6 +104,7 @@ describe('Safe creation tests', () => { it('Verify tip is displayed on right side for threshold 1/1', () => { owner.waitForConnectionStatus() + createwallet.clickOnContinueWithWalletBtn() createwallet.clickOnCreateNewSafeBtn() createwallet.clickOnNextBtn() createwallet.verifyPolicy1_1() @@ -105,6 +113,7 @@ describe('Safe creation tests', () => { // TODO: Check unit tests it('Verify address input validation rules', () => { owner.waitForConnectionStatus() + createwallet.clickOnContinueWithWalletBtn() createwallet.clickOnCreateNewSafeBtn() createwallet.clickOnNextBtn() createwallet.clickOnAddNewOwnerBtn() diff --git a/cypress/e2e/regression/sidebar_2.cy.js b/cypress/e2e/regression/sidebar_2.cy.js index daef0b5844..87ac18b101 100644 --- a/cypress/e2e/regression/sidebar_2.cy.js +++ b/cypress/e2e/regression/sidebar_2.cy.js @@ -2,14 +2,15 @@ import * as constants from '../../support/constants' import * as main from '../pages/main.page' import * as sideBar from '../pages/sidebar.pages' import * as ls from '../../support/localstorage_data.js' +import * as assets from '../pages/assets.pages.js' const newSafeName = 'Added safe 3' -const oldSafeName = 'Added safe 2' -const staticSafe100 = 'Added safe 100' +const addedSafe900 = 'Added safe 900' +const staticSafe200 = 'Added safe 200' describe('Sidebar added sidebar tests', () => { beforeEach(() => { - cy.visit(constants.homeUrl + constants.SEPOLIA_TEST_SAFE_13) + cy.visit(constants.BALANCE_URL + constants.SEPOLIA_TEST_SAFE_13) cy.wait(2000) cy.clearLocalStorage() main.acceptCookies() @@ -19,30 +20,45 @@ describe('Sidebar added sidebar tests', () => { it('Verify the safe added are listed in the sidebar', () => { sideBar.openSidebar() - sideBar.verifyAddedSafesExist(sideBar.addedSafesGnosis, sideBar.addedSafesSepolia) + sideBar.verifyAddedSafesExist(sideBar.addedSafesSepolia) }) it('Verify Safes are separated by networks', () => { sideBar.openSidebar() - sideBar.verifySafesByNetwork(constants.networks.gnosis, sideBar.addedSafesGnosis) + sideBar.verifySafesByNetwork(constants.networks.ethereum, sideBar.addedSafesEth) sideBar.verifySafesByNetwork(constants.networks.sepolia, sideBar.addedSafesSepolia) }) it('Verify a safe can be renamed', () => { sideBar.openSidebar() - sideBar.renameSafeItem(oldSafeName, newSafeName) + sideBar.renameSafeItem(addedSafe900, newSafeName) sideBar.clickOnSaveBtn() sideBar.verifySafeNameExists(newSafeName) }) it('Verify a safe can be removed', () => { sideBar.openSidebar() - sideBar.removeSafeItem(oldSafeName) - sideBar.verifySafeRemoved([oldSafeName]) + sideBar.removeSafeItem(addedSafe900) + sideBar.verifySafeRemoved([addedSafe900]) }) it('Verify the "Read only" tag if the connected user is not an owner of a safe', () => { sideBar.openSidebar() - sideBar.verifySafeReadOnlyState(staticSafe100) + sideBar.verifySafeReadOnlyState(addedSafe900) + }) + + it('Verify Fiat currency changes when edited in the assets tab', () => { + assets.changeCurrency(constants.currencies.cad) + sideBar.checkCurrencyInHeader(constants.currencies.cad) + }) + + it('Verify "wallet" tag counter if the safe has tx ready for execution', () => { + sideBar.openSidebar() + sideBar.verifyMissingSignature(staticSafe200) + }) + + it('Verify "Wallet" tag counter only shows for owners', () => { + sideBar.openSidebar() + sideBar.verifyQueuedTx(staticSafe200) }) }) diff --git a/cypress/e2e/regression/sidebar_nonowner.cy.js b/cypress/e2e/regression/sidebar_nonowner.cy.js new file mode 100644 index 0000000000..f88c2faedf --- /dev/null +++ b/cypress/e2e/regression/sidebar_nonowner.cy.js @@ -0,0 +1,29 @@ +import * as constants from '../../support/constants.js' +import * as main from '../pages/main.page.js' +import * as sideBar from '../pages/sidebar.pages.js' +import * as navigation from '../pages/navigation.page.js' +import * as ls from '../../support/localstorage_data.js' + +const addedOwner = 'Added owner' +const addedNonowner = 'Added non-owner' + +describe('Sidebar non-owner tests', () => { + beforeEach(() => { + cy.visit(constants.homeUrl + constants.SEPOLIA_TEST_SAFE_17_SIDEBAR_NONOWNER) + cy.wait(2000) + cy.clearLocalStorage() + main.acceptCookies() + main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__addedSafes, ls.addedSafes.set3) + main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__addressBook, ls.addressBookData.addedSafes) + }) + + it('Verify New Transaction button enabled for users with Spending limits allowed', () => { + navigation.verifyTxBtnStatus(constants.enabledStates.enabled) + }) + + it('Verify tag counting queue tx show for owners and non-owners', () => { + sideBar.openSidebar() + sideBar.verifyQueuedTx(addedOwner).contains(2) + sideBar.verifyQueuedTx(addedNonowner).contains(2) + }) +}) diff --git a/cypress/e2e/safe-apps/tx-builder.spec.cy.js b/cypress/e2e/safe-apps/tx-builder.spec.cy.js index c021504610..201feec1ee 100644 --- a/cypress/e2e/safe-apps/tx-builder.spec.cy.js +++ b/cypress/e2e/safe-apps/tx-builder.spec.cy.js @@ -197,9 +197,10 @@ describe('Transaction Builder tests', { defaultCommandTimeout: 20000 }, () => { }) }) - it('Verify a batch cannot be created without asset amount', () => { + it.skip('Verify a batch cannot be created without asset amount', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findByLabelText(safeapps.enterAddressStr).type(constants.SEPOLIA_TEST_SAFE_10) + getBody().findByText(safeapps.keepProxiABIStr).click() getBody().findByText(safeapps.addTransactionStr).click() getBody().findAllByText(safeapps.requiredStr).should('have.css', 'color', 'rgb(244, 67, 54)') }) @@ -266,9 +267,10 @@ describe('Transaction Builder tests', { defaultCommandTimeout: 20000 }, () => { }) }) - it('Verify a valid batch as successful can be simulated', () => { + it.skip('Verify a valid batch as successful can be simulated', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findByLabelText(safeapps.enterAddressStr).type(constants.SEPOLIA_TEST_SAFE_10) + getBody().findByText(safeapps.keepProxiABIStr).click() getBody().findByLabelText(safeapps.tokenAmount).type('0') getBody().findByText(safeapps.addTransactionStr).click() getBody().findByText(safeapps.createBatchStr).click() @@ -278,9 +280,10 @@ describe('Transaction Builder tests', { defaultCommandTimeout: 20000 }, () => { }) }) - it('Verify an invalid batch as failed can be simulated', () => { + it.skip('Verify an invalid batch as failed can be simulated', () => { cy.enter(iframeSelector).then((getBody) => { getBody().findByLabelText(safeapps.enterAddressStr).type(constants.SEPOLIA_TEST_SAFE_10) + getBody().findByText(safeapps.keepProxiABIStr).click() getBody().findByLabelText(safeapps.tokenAmount).type('100') getBody().findByText(safeapps.addTransactionStr).click() getBody().findByText(safeapps.createBatchStr).click() diff --git a/cypress/e2e/smoke/address_book.cy.js b/cypress/e2e/smoke/address_book.cy.js index 2f396ab06d..203c94c90c 100644 --- a/cypress/e2e/smoke/address_book.cy.js +++ b/cypress/e2e/smoke/address_book.cy.js @@ -5,12 +5,17 @@ import * as main from '../../e2e/pages/main.page' import * as ls from '../../support/localstorage_data.js' const NAME = 'Owner1' +const NAME_2 = 'Owner2' const EDITED_NAME = 'Edited Owner1' +const duplicateEntry = 'test-sepolia-90' +const owner1 = 'Automation owner' + +const recipientData = [owner1, constants.DEFAULT_OWNER_ADDRESS] describe('[SMOKE] Address book tests', () => { beforeEach(() => { - cy.clearLocalStorage() cy.visit(constants.addressBookUrl + constants.SEPOLIA_TEST_SAFE_1) + cy.clearLocalStorage() main.waitForHistoryCallToComplete() main.acceptCookies() }) @@ -37,8 +42,61 @@ describe('[SMOKE] Address book tests', () => { it('[SMOKE] Verify csv file can be imported', () => { addressBook.clickOnImportFileBtn() - addressBook.importFile() - addressBook.verifyImportModalIsClosed() - addressBook.verifyDataImported(constants.SEPOLIA_CSV_ENTRY.name, constants.SEPOLIA_CSV_ENTRY.address) + addressBook.importCSVFile(addressBook.validCSVFile) + addressBook.verifyImportBtnStatus(constants.enabledStates.enabled) + addressBook.clickOnImportBtn() + addressBook.verifyDataImported(addressBook.entries) + addressBook.verifyNumberOfRows(4) + }) + + it('[SMOKE] Import a csv file with an empty address/name/network in one row', () => { + addressBook.clickOnImportFileBtn() + addressBook.importCSVFile(addressBook.emptyCSVFile) + addressBook.verifyImportBtnStatus(constants.enabledStates.disabled) + addressBook.verifyUploadExportMessage([addressBook.uploadErrorMessages.emptyFile]) + }) + + it('[SMOKE] Import a non-csv file', () => { + addressBook.clickOnImportFileBtn() + addressBook.importCSVFile(addressBook.nonCSVFile) + addressBook.verifyImportBtnStatus(constants.enabledStates.disabled) + addressBook.verifyUploadExportMessage([addressBook.uploadErrorMessages.fileType]) + }) + + it('[SMOKE] Import a csv file with a repeated address and same network', () => { + addressBook.clickOnImportFileBtn() + addressBook.importCSVFile(addressBook.duplicatedCSVFile) + addressBook.verifyImportBtnStatus(constants.enabledStates.enabled) + addressBook.clickOnImportBtn() + addressBook.verifyDataImported([duplicateEntry]) + addressBook.verifyNumberOfRows(1) + }) + + it('[SMOKE] Verify modal shows the amount of entries and networks detected', () => { + addressBook.clickOnImportFileBtn() + addressBook.importCSVFile(addressBook.networksCSVFile) + addressBook.verifyImportBtnStatus(constants.enabledStates.enabled) + addressBook.verifyModalSummaryMessage(4, 3) + }) + + it('[SMOKE] Verify an entry can be added by ENS name', () => { + addressBook.clickOnCreateEntryBtn() + addressBook.addEntryByENS(NAME_2, constants.ENS_TEST_SEPOLIA) + }) + + it('[SMOKE] Verify empty name is not allowed when editing', () => { + main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__addressBook, ls.addressBookData.sepoliaAddress1) + cy.wait(1000) + cy.reload() + addressBook.clickOnEditEntryBtn() + addressBook.verifyEmptyOwnerNameNotAllowed() + }) + + it('[SMOKE] Verify clicking on Send button autofills the recipient filed with correct value', () => { + main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__addressBook, ls.addressBookData.sepoliaAddress2) + cy.wait(1000) + cy.reload() + addressBook.clickOnSendBtn() + addressBook.verifyRecipientData(recipientData) }) }) diff --git a/cypress/e2e/smoke/create_safe_simple.cy.js b/cypress/e2e/smoke/create_safe_simple.cy.js index 3578417fce..54e1491739 100644 --- a/cypress/e2e/smoke/create_safe_simple.cy.js +++ b/cypress/e2e/smoke/create_safe_simple.cy.js @@ -11,6 +11,7 @@ describe('[SMOKE] Safe creation tests', () => { main.acceptCookies() }) it('[SMOKE] Verify a Wallet can be connected', () => { + createwallet.clickOnContinueWithWalletBtn() createwallet.clickOnCreateNewSafeBtn() owner.clickOnWalletExpandMoreIcon() owner.clickOnDisconnectBtn() @@ -20,12 +21,14 @@ describe('[SMOKE] Safe creation tests', () => { it('[SMOKE] Verify that a new Wallet has default name related to the selected network', () => { owner.waitForConnectionStatus() + createwallet.clickOnContinueWithWalletBtn() createwallet.clickOnCreateNewSafeBtn() createwallet.verifyDefaultWalletName(createwallet.defaltSepoliaPlaceholder) }) it('[SMOKE] Verify Add and Remove Owner Row works as expected', () => { owner.waitForConnectionStatus() + createwallet.clickOnContinueWithWalletBtn() createwallet.clickOnCreateNewSafeBtn() createwallet.clickOnNextBtn() createwallet.clickOnAddNewOwnerBtn() @@ -40,6 +43,7 @@ describe('[SMOKE] Safe creation tests', () => { it('[SMOKE] Verify Threshold Setup', () => { owner.waitForConnectionStatus() + createwallet.clickOnContinueWithWalletBtn() createwallet.clickOnCreateNewSafeBtn() createwallet.clickOnNextBtn() createwallet.clickOnAddNewOwnerBtn() diff --git a/cypress/fixtures/address_book_addedsafes.csv b/cypress/fixtures/address_book_addedsafes.csv new file mode 100644 index 0000000000..afe54dcd76 --- /dev/null +++ b/cypress/fixtures/address_book_addedsafes.csv @@ -0,0 +1,2 @@ +address,name,chainId +0x6d0b6F96f665Bb4490f9ddb2e450Da2f7e546dC1,imported-safe,11155111 \ No newline at end of file diff --git a/cypress/fixtures/address_book_duplicated.csv b/cypress/fixtures/address_book_duplicated.csv new file mode 100644 index 0000000000..2302d1848a --- /dev/null +++ b/cypress/fixtures/address_book_duplicated.csv @@ -0,0 +1,3 @@ +address,name,chainId +0x6E834E9D04ad6b26e1525dE1a37BFd9b215f40B7,test-sepolia-9,11155111 +0x6E834E9D04ad6b26e1525dE1a37BFd9b215f40B7,test-sepolia-90,11155111 \ No newline at end of file diff --git a/cypress/fixtures/address_book_empty_test.csv b/cypress/fixtures/address_book_empty_test.csv new file mode 100644 index 0000000000..4918b1de41 --- /dev/null +++ b/cypress/fixtures/address_book_empty_test.csv @@ -0,0 +1 @@ +address,name,chainId \ No newline at end of file diff --git a/cypress/fixtures/address_book_networks.csv b/cypress/fixtures/address_book_networks.csv new file mode 100644 index 0000000000..4624dfb476 --- /dev/null +++ b/cypress/fixtures/address_book_networks.csv @@ -0,0 +1,5 @@ +address,name,chainId +0x8675B754342754A30A2AeF474D114d8460bca19b,"mainnet safe ",1 +0xB8d760a90a5ed54D3c2b3EFC231277e99188642A,"xDai Safe B8", 100 +0x91e11585c114129f3Ec940Aa648A4ac13668d0c2,"Biance safe91", 56 +0x61a0c717d18232711bC788F19C9Cd56a43cc8872,"MM account 1", 1 \ No newline at end of file diff --git a/cypress/support/constants.js b/cypress/support/constants.js index f83fb7cfc0..ffd84749aa 100644 --- a/cypress/support/constants.js +++ b/cypress/support/constants.js @@ -29,6 +29,7 @@ export const SEPOLIA_TEST_SAFE_14 = 'sep:0xC23e061252BFc7967203D054136d8fA7c7df2 // SAFE 15 is a safe with native tokens but the automation user is not its owner export const SEPOLIA_TEST_SAFE_15_TOKEN = 'sep:0xfC0A7ac73Fde7547ac0792Cca1D8A50CE0AFC4Df' export const SEPOLIA_TEST_SAFE_16_CREATE_TX = 'sep:0xc2F3645bfd395516d1a18CA6ad9298299d328C01' +export const SEPOLIA_TEST_SAFE_17_SIDEBAR_NONOWNER = 'sep:0x10B45a24640E2170B6AA63ea3A289D723a0C9cba' export const SEPOLIA_CONTRACT_SHORT = '0x11AB...34aF' export const SEPOLIA_RECIPIENT_ADDR_SHORT = '0x4DD4...7bde' export const GNO_TEST_SAFE = 'gno:0xB8d760a90a5ed54D3c2b3EFC231277e99188642A' @@ -134,6 +135,11 @@ export const tokenAbbreviation = { link: 'LINK', } +export const currencies = { + cad: 'CAD', + aud: 'AUD', +} + export const appNames = { walletConnect: 'walletconnect', customContract: 'compose custom contract', diff --git a/cypress/support/localstorage_data.js b/cypress/support/localstorage_data.js index 62e6a1c879..fca7bcc42e 100644 --- a/cypress/support/localstorage_data.js +++ b/cypress/support/localstorage_data.js @@ -317,12 +317,53 @@ export const addressBookData = { }, }, addedSafes: { - 100: { - '0x17b34aEf1428A358bA2eA360a098b8A3BEb698C8': 'Added safe 1', - '0x11A6B41322C57Bd0e56cEe06abB11A1E5c1FF1BB': 'Added safe 2', - '0xB8d760a90a5ed54D3c2b3EFC231277e99188642A': 'Added safe 100', + 1: { + '0x8675B754342754A30A2AeF474D114d8460bca19b': 'Added safe 900', + }, + 11155111: { + '0x0A0EEb6fBCc7c82259E548Fc4617175A357b3e71': 'Added safe 200', + '0xF21445699e91aC6F2EeeAF1a19510AC4197e59aB': 'Added owner', + '0x9E6DAfe829431e1892EcF8461FDAd02665170c31': 'Added non-owner', }, }, + sortingData: { + 11155111: { + '0xC16Db0251654C0a72E91B190d81eAD367d2C6fED': 'AA Safe', + '0x6a5602335a878ADDCa4BF63a050E34946B56B5bC': 'BB Safe', + }, + }, + pagination: { + 11155111: { + '0xB8Bfd72663602dB33A454e3D899fb1ee95F54c26': 'Safe 1', + '0x368D6B0Aa605253D19AB7C1F006a61Aa46bbECEb': 'Safe 2', + '0x9190cc22D592dDcf396Fa616ce84a9978fD96Fc9': 'Safe 3', + '0x98705770aF3b18db0a64597F6d4DCe825915fec0': 'Safe 4', + '0xC23e061252BFc7967203D054136d8fA7c7df2fc4': 'Safe 5', + '0x0Ec5EF749cce5185900819A3457C0f9129a9a9a1': 'Safe 6', + '0x0A0EEb6fBCc7c82259E548Fc4617175A357b3e71': 'Safe 7', + '0x10B45a24640E2170B6AA63ea3A289D723a0C9cba': 'Safe 8', + '0xF21445699e91aC6F2EeeAF1a19510AC4197e59aB': 'Safe 9', + '0x9E6DAfe829431e1892EcF8461FDAd02665170c31': 'Safe 10', + '0x6d0b6F96f665Bb4490f9ddb2e450Da2f7e546dC1': 'Safe 11', + '0xB8Bfd72663602dB33A454e3D899fb1ee95F54c26': 'Safe 12', + '0x6E834E9D04ad6b26e1525dE1a37BFd9b215f40B7': 'Safe 13', + '0xBb26E3717172d5000F87DeFd391994f789D80aEB': 'Safe 14', + '0x905934aA8758c06B2422F0C90D97d2fbb6677811': 'Safe 15', + '0xf8D6450d6ae36328cBAA97B1998C741be498c5D3': 'Safe 16', + '0xBd69b0a9DC90eB6F9bAc3E4a5875f437348b6415': 'Safe 17', + '0x5912f6616c84024cD1aff0D5b55bb36F5180fFdb': 'Safe 18', + '0x81034C61a318649F7aD43f9e8C1051427e326443': 'Safe 19', + '0x06373d5e45AD31BD354CeBfA8dB4eD2c75B8708e': 'Safe 20', + '0x4DD4cB2299E491E1B469245DB589ccB2B16d7bde': 'Safe 21', + '0xD1571E8Cc4438aFef2836DD9a0E5D09fb63EDE9a': 'Safe 22', + '0x691B95d2531BFf662767839d668d3D7651524C21': 'Safe 23', + '0x39419cC835046D0c7beca69638eBBDD0F9FD85e4': 'Safe 24', + '0xBf30F749FC027a5d79c4710D988F0D3C8e217A4F': 'Safe 25', + '0x8A89C14ed0900a95fc94075D0823f8c744789a40': 'Safe 26', + '0xc2F3645bfd395516d1a18CA6ad9298299d328C01': 'Safe 27', + }, + }, + cookies: { necessary: true, updates: true, analytics: true }, } export const safeSettings = { @@ -507,22 +548,54 @@ export const addedSafes = { threshold: 1, ethBalance: '0', }, + '0x0A0EEb6fBCc7c82259E548Fc4617175A357b3e71': { + owners: [ + { + value: '0x8aEf2f5c3F17261F6F1C4dA058D022BE92776af8', + name: null, + logoUri: null, + }, + { + value: '0xC16Db0251654C0a72E91B190d81eAD367d2C6fED', + name: null, + logoUri: null, + }, + ], + threshold: 2, + ethBalance: '0', + }, }, - 100: { - '0x17b34aEf1428A358bA2eA360a098b8A3BEb698C8': { + 1: { + '0x8675B754342754A30A2AeF474D114d8460bca19b': { owners: [{ value: '0x11B1D54B66e5e226D6f89069c21A569A22D98cfd' }], threshold: 1, ethBalance: '0.001000002', }, - '0x11A6B41322C57Bd0e56cEe06abB11A1E5c1FF1BB': { - owners: [{ value: '0x7724b234c9099C205F03b458944942bcEBA13408' }], - threshold: 1, + }, + }, + set3: { + 11155111: { + '0xF21445699e91aC6F2EeeAF1a19510AC4197e59aB': { + owners: [ + { + value: '0xC16Db0251654C0a72E91B190d81eAD367d2C6fED', + name: null, + logoUri: null, + }, + ], + threshold: 2, ethBalance: '0', }, - '0xB8d760a90a5ed54D3c2b3EFC231277e99188642A': { - owners: [{ value: '0x11B1D54B66e5e226D6f89069c21A569A22D98cfd' }], - threshold: 1, - ethBalance: '0.92132507668989', + '0x9E6DAfe829431e1892EcF8461FDAd02665170c31': { + owners: [ + { + value: '0x96D4c6fFC338912322813a77655fCC926b9A5aC5', + name: null, + logoUri: null, + }, + ], + threshold: 2, + ethBalance: '0', }, }, }, diff --git a/docs/release-procedure.md b/docs/release-procedure.md index ee1e755ed6..2e8002bbd3 100644 --- a/docs/release-procedure.md +++ b/docs/release-procedure.md @@ -8,16 +8,16 @@ After the PR is tested and approved by QA, it's merged into the `main` branch. ` Schematically: ``` - –> dev -> release/X.Y.Z -> main + –> dev -> release -> main ``` We prepare at least one release every sprint. Sprints are two weeks long. ### Preparing a release branch -* Create a code-freeze branch named `release/X.Y.Z` +* Create a code-freeze branch named `release` * If it's a regular release, this branch is typically based off of `dev` * For hot fixes, it would be `main` + cherry-picked commits -* Bump the version in the `package.json` +* Bump the version in the `package.json` as a separate commit with the commit message equal to the exact version * Create a PR with the list of changes > 💡 To generate a quick changelog: @@ -42,7 +42,7 @@ git reset --hard origin/main ``` * Pull from the release branch: ``` -git pull origin release/3.15.0 +git pull origin release ``` * Push: ``` diff --git a/package.json b/package.json index b939beeaf7..be84cd5a5b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "safe-wallet-web", "homepage": "https://github.com/safe-global/safe-wallet-web", "license": "GPL-3.0", - "version": "1.28.0", + "version": "1.29.1", "type": "module", "scripts": { "dev": "next dev", @@ -27,7 +27,8 @@ "cypress:run": "cypress run", "cypress:ci": "yarn cypress:run --config baseUrl=http://localhost:8080 --spec cypress/e2e/smoke/*.cy.js", "serve": "npx -y serve out -p ${REVERSE_PROXY_UI_PORT:=8080}", - "static-serve": "yarn build && yarn serve" + "static-serve": "yarn build && yarn serve", + "update-wc": "yarn add @walletconnect/web3wallet@latest @walletconnect/utils@latest @walletconnect/types@latest" }, "engines": { "node": ">=16" @@ -54,16 +55,16 @@ "@safe-global/safe-apps-sdk": "^9.0.0-next.1", "@safe-global/safe-core-sdk": "^3.3.5", "@safe-global/safe-core-sdk-utils": "^1.7.4", - "@safe-global/safe-ethers-lib": "^1.9.4", "@safe-global/safe-deployments": "1.32.0", - "@safe-global/safe-gateway-typescript-sdk": "^3.14.0", + "@safe-global/safe-ethers-lib": "^1.9.4", + "@safe-global/safe-gateway-typescript-sdk": "^3.15.0", "@safe-global/safe-modules-deployments": "^1.2.0", "@sentry/react": "^7.91.0", "@spindl-xyz/attribution-lite": "^1.4.0", "@tkey-mpc/common-types": "^8.2.2", "@truffle/hdwallet-provider": "^2.1.4", - "@walletconnect/utils": "^2.11.0", - "@walletconnect/web3wallet": "^1.10.0", + "@walletconnect/utils": "^2.11.1", + "@walletconnect/web3wallet": "^1.10.1", "@web3-onboard/coinbase": "^2.2.6", "@web3-onboard/core": "^2.21.2", "@web3-onboard/injected-wallets": "^2.10.7", @@ -118,7 +119,7 @@ "@types/react-gtm-module": "^2.0.1", "@types/semver": "^7.3.10", "@typescript-eslint/eslint-plugin": "^5.47.1", - "@walletconnect/types": "^2.11.0", + "@walletconnect/types": "^2.11.1", "cross-env": "^7.0.3", "cypress": "^12.15.0", "cypress-file-upload": "^5.0.8", diff --git a/src/components/address-book/AddressBookTable/index.tsx b/src/components/address-book/AddressBookTable/index.tsx index 542eb1bc5c..f1ee73aeec 100644 --- a/src/components/address-book/AddressBookTable/index.tsx +++ b/src/components/address-book/AddressBookTable/index.tsx @@ -120,6 +120,7 @@ function AddressBookTable({ chain, setTxFlow }: AddressBookTableProps) { {(isOk) => ( - diff --git a/src/components/address-book/ImportDialog/index.tsx b/src/components/address-book/ImportDialog/index.tsx index ac6d665eda..7b83a40d8b 100644 --- a/src/components/address-book/ImportDialog/index.tsx +++ b/src/components/address-book/ImportDialog/index.tsx @@ -128,7 +128,7 @@ const ImportDialog = ({ handleClose }: { handleClose: () => void }): ReactElemen name: acceptedFile.name, additionalInfo: formatFileSize(acceptedFile.size), summary: [ - + {`Found ${entryCount} entries on ${chainCount} ${chainCount > 1 ? 'chains' : 'chain'}`} , ], @@ -163,8 +163,16 @@ const ImportDialog = ({ handleClose }: { handleClose: () => void }): ReactElemen - - + diff --git a/src/components/balances/AssetsTable/index.tsx b/src/components/balances/AssetsTable/index.tsx index 224983b867..db035340b0 100644 --- a/src/components/balances/AssetsTable/index.tsx +++ b/src/components/balances/AssetsTable/index.tsx @@ -1,3 +1,4 @@ +import CheckBalance from '@/features/counterfactual/CheckBalance' import { type ReactElement, useMemo, useContext } from 'react' import { Button, Tooltip, Typography, SvgIcon, IconButton, Box, Checkbox, Skeleton } from '@mui/material' import type { TokenInfo } from '@safe-global/safe-gateway-typescript-sdk' @@ -138,7 +139,7 @@ const AssetsTable = ({ [hiddenAssets, balances.items, showHiddenAssets], ) - const hasNoAssets = balances.items.length === 1 && balances.items[0].balance === '0' + const hasNoAssets = !loading && balances.items.length === 1 && balances.items[0].balance === '0' const selectedAssetCount = visibleAssets?.filter((item) => isAssetSelected(item.tokenInfo.address)).length || 0 @@ -257,6 +258,8 @@ const AssetsTable = ({ )} + + ) } diff --git a/src/components/balances/CurrencySelect/index.tsx b/src/components/balances/CurrencySelect/index.tsx index bfec864f59..04d9af8e93 100644 --- a/src/components/balances/CurrencySelect/index.tsx +++ b/src/components/balances/CurrencySelect/index.tsx @@ -34,6 +34,7 @@ const CurrencySelect = (): ReactElement => { Currency