mirror of
https://github.com/luxfi/safe-wallet.git
synced 2026-10-30 15:19:21 +00:00
* tests: rewire tx_share_block specs to Audit Log share icon * chore: regenerate AUTO_GENERATED store types * Revert "chore: regenerate AUTO_GENERATED store types" --------- Co-authored-by: Liliya Soroka <[email protected]>
1163 lines
36 KiB
JavaScript
1163 lines
36 KiB
JavaScript
import * as constants from '../../support/constants'
|
|
import * as main from '../pages/main.page'
|
|
import * as wallet from '../pages/create_wallet.pages'
|
|
import * as modal from '../pages/modals.page'
|
|
import { dataRow } from '../pages/tables.page'
|
|
import * as navigation from './navigation.page'
|
|
import * as walletUtils from '../../support/utils/wallet.js'
|
|
import * as owner from './owners.pages'
|
|
|
|
export const delegateCallWarning = '[data-testid="delegate-call-warning"]'
|
|
export const policyChangeWarning = '[data-testid="threshold-warning"]'
|
|
export const tokenSelector = '[data-testid="token-selector"]'
|
|
const newTransactionBtnStr = 'New transaction'
|
|
const recepientInput = 'input[name="recipients.0.recipient"]'
|
|
const recepientInput_ = (index) => `input[name="recipients.${index}.recipient"]`
|
|
const tokenAddressInput = 'input[name="recipients.0.tokenAddress"]'
|
|
const amountInput = 'input[name="recipients.0.amount"]'
|
|
const amountInput_ = (index) => `input[name="recipients.${index}.amount"]`
|
|
const nonceInput = 'input[name="nonce"]'
|
|
const walletNonceInput = '[name="userNonce"]'
|
|
const gasLimitInput = '[name="gasLimit"]'
|
|
const maxPriorityFee = '[name="maxPriorityFeePerGas"]'
|
|
const maxFee = '[name="maxFeePerGas"]'
|
|
const rotateLeftIcon = '[data-testid="RotateLeftIcon"]'
|
|
export const transactionItem = '[data-testid="transaction-item"]'
|
|
export const connectedWalletExecMethod = '[data-testid="connected-wallet-execution-method"]'
|
|
export const relayExecMethod = '[data-testid="relay-execution-method"]'
|
|
export const connectedWalletMethod = '[data-testid="connected-wallet-execution-method"]'
|
|
export const payNowExecMethod = '[data-testid="pay-now-execution-method"]'
|
|
export const addToBatchBtn = '[data-testid="combo-submit-batching"]'
|
|
export const executeTxBtn = '[data-testid="execute-tx-btn"]'
|
|
const accordionDetails = '[data-testid="accordion-details"]'
|
|
export const copyIcon = '[data-testid="copy-btn-icon"]'
|
|
export const explorerBtn = '[data-testid="explorer-btn"]'
|
|
const transactionSideList = '[data-testid="transaction-actions-list"]'
|
|
const expandAllBtn = '[data-testid="expande-all-btn"]'
|
|
const collapseAllBtn = '[data-testid="collapse-all-btn"]'
|
|
export const txRowTitle = '[data-testid="tx-row-title"]'
|
|
const advancedDetails = '[data-testid="tx-advanced-details"]'
|
|
const baseGas = '[data-testid="tx-base-gas"]'
|
|
const requiredConfirmation = '[data-testid="required-confirmations"]'
|
|
export const txDate = '[data-testid="tx-date"]'
|
|
export const txType = '[data-testid="tx-type"]'
|
|
export const proposalStatus = '[data-testid="proposal-status"]'
|
|
export const txSigner = '[data-testid="signer"]'
|
|
const spamTokenWarningIcon = '[data-testid="warning"]'
|
|
const untrustedTokenWarningModal = '[data-testid="untrusted-token-warning"]'
|
|
const sendTokensBtn = '[data-testid="send-tokens-btn"]'
|
|
export const replacementNewSigner = '[data-testid="new-owner"]'
|
|
export const messageItem = '[data-testid="message-item"]'
|
|
const filterStartDateInput = '[data-testid="start-date"]'
|
|
const filterEndDateInput = '[data-testid="end-date"]'
|
|
const filterAmountInput = '[data-testid="amount-input"]'
|
|
const filterTokenInput = '[data-testid="token-input"]'
|
|
const filterNonceInput = '[data-testid="nonce-input"]'
|
|
const filterApplyBtn = '[data-testid="apply-btn"]'
|
|
const filterClearBtn = '[data-testid="clear-btn"]'
|
|
export const addressItem = '[data-testid="address-item"]'
|
|
const radioSelector = 'div[role="radiogroup"]'
|
|
const rejectTxBtn = '[data-testid="reject-btn"]'
|
|
const rejectChoiceBtn = '[data-track="reject-tx: Reject onchain button"]'
|
|
const replaceChoiceBtn = '[data-track="reject-tx: Replace tx button"]'
|
|
export const deleteChoiceBtn = '[data-track="reject-tx: Delete offchain button"]'
|
|
const deleteTxModalBtn = '[data-testid="delete-tx-btn"]'
|
|
const toggleUntrustedBtn = '[data-testid="toggle-untrusted"]'
|
|
const simulateTxBtn = '[data-testid="simulate-btn"]'
|
|
const simulateSuccess = '[data-testid="simulation-success-msg"]'
|
|
const signBtn = '[data-testid="combo-submit-sign"]'
|
|
export const continueSignBtn = '[data-testid="continue-sign-btn"]'
|
|
export const altImgDai = 'iframe[title="DAI"]'
|
|
export const altImgCow = 'iframe[title="COW"]'
|
|
export const altImgWeth = 'iframe[title="WETH"]'
|
|
export const altImgUsdc = 'iframe[title="USDC"]'
|
|
export const altImgUsdt = 'iframe[title="USDT"]'
|
|
export const altImgSwaps = 'svg[alt="Swap order"]'
|
|
export const altImgLimitOrder = 'svg[alt="Limit order"]'
|
|
export const altImgTwapOrder = 'svg[alt="Twap Order"]'
|
|
export const txShareLinkBtn = '[data-testid="share-tx-link-btn"]'
|
|
export const noteTextField = '[data-testid="tx-note-textfield"]'
|
|
const noteAlert = "[data-testid='tx-note-alert']"
|
|
const recoredTxNote = '[data-testid="tx-note"]'
|
|
const txNoteTooltip = '[data-testid="tx-note-tooltip"]'
|
|
const noteCreator = '[data-testid="note-creator"]'
|
|
const tableViewBtn = '[data-testid="table-view-btn"]'
|
|
const gridViewBtn = '[data-testid="grid-view-btn"]'
|
|
const txHexData = '[data-testid="tx-hex-data"]'
|
|
const txStack = '[data-testid="tx-stack"]'
|
|
const txOperation = '[data-testid="tx-operation"]'
|
|
const nonceFld = '[data-testid="nonce-fld"]'
|
|
const txHexDataRow = '[data-testid="tx-hexData"]'
|
|
const addrecipientBtn = '[data-testid="add-recipient-btn"]'
|
|
const removeRecipientBtn = '[data-testid="remove-recipient-btn"]'
|
|
const maxRecipientsReachedMsg = '[data-testid="max-recipients-reached"]'
|
|
const recipientsCount = '[data-testid="recipients-count"]'
|
|
const maxBtn = '[data-testid="max-btn"]'
|
|
const tokenAmountSection = '[data-testid="token-amount-section"]'
|
|
const insufficientBalanceError = '[data-testid="insufficient-balance-error"]'
|
|
const proposeTransactionBtn = '[data-testid="sign-btn"]'
|
|
|
|
const insufficientFundsErrorStr = 'Insufficient funds'
|
|
const viewTransactionBtn = 'View transaction'
|
|
const transactionDetailsTitle = 'Transaction details'
|
|
const QueueLabel = 'needs to be executed first'
|
|
export const hashesText = 'Hashes'
|
|
const TransactionSummary = 'Send '
|
|
const transactionsPerHrStr = 'free transactions left today'
|
|
const maxAmountBtnStr = 'Max'
|
|
const nativeTokenTransferStr = 'ETH'
|
|
const estimatedFeeStr = 'Estimated fee'
|
|
// Re-export for backward compatibility
|
|
export const executeStr = 'Execute'
|
|
const editBtnStr = 'Edit'
|
|
const executionParamsStr = 'Execution parameters'
|
|
const noLaterStr = 'No, later'
|
|
const confirmBtnStr = 'Confirm'
|
|
const expandAllBtnStr = 'Expand all'
|
|
const collapseAllBtnStr = 'Collapse all'
|
|
export const messageNestedStr = `"nestedString": "Test message 3 off-chain"`
|
|
const noTxFoundStr = (type) => `0 ${type} transactions found`
|
|
const deleteFromQueueStr = 'Delete from the queue'
|
|
const bulkExecuteBtn = (tx) => `Bulk execute ${tx} transactions`
|
|
const bulkConfirmationText = (tx) =>
|
|
`This transaction batches a total of ${tx} transactions from your queue into a single Ethereum transaction`
|
|
|
|
const disabledBultExecuteBtnTooltip =
|
|
'Batch execution is only available for transactions that have been fully signed and are strictly sequential in Safe Account nonce'
|
|
const enabledBulkExecuteBtnTooltip = 'All highlighted transactions will be included in the batch execution'
|
|
|
|
const bulkExecuteBtnStr = 'Bulk execute'
|
|
|
|
const batchModalTitle = 'Batch'
|
|
const gasLimit21000 = 'Gas limit must be at least 21000'
|
|
export const swapOrder = 'Swap order settlement'
|
|
export const bulkTxs = 'Bulk transactions'
|
|
export const txStr = 'Transactions'
|
|
export const txDetailsStr = 'Transaction details'
|
|
export const settingsStr = 'Settings'
|
|
export const assetsStr = 'Assets'
|
|
export const topAssetsStr = 'Top assets'
|
|
export const getStartedStr = 'Get started'
|
|
export const txNoteWarningMessage = 'Notes are publicly visible. Do not share any private or sensitive details.'
|
|
export const recordedTxNote = 'Tx note one'
|
|
|
|
const comboButton = '[data-testid="combo-submit-dropdown"]'
|
|
const comboButtonPopover = '[data-testid="combo-submit-popover"]'
|
|
export const comboButtonOptions = {
|
|
sign: 'Sign',
|
|
execute: 'Execute',
|
|
addToBatch: 'Add to batch',
|
|
}
|
|
|
|
const advancedParametersValues = {
|
|
walletNonce: '5500',
|
|
maxPriorityFee: '0.1234',
|
|
maxFee: '0.5678',
|
|
gasLimit: '300001',
|
|
}
|
|
const advancedParametersInputNames = {
|
|
walletNonce: 'Wallet nonce',
|
|
maxPriorityFee: 'Max priority fee (Gwei)',
|
|
maxFee: 'Max fee (Gwei)',
|
|
gasLimit: 'Gas limit',
|
|
}
|
|
|
|
// Transaction details on Tx creation
|
|
export const txAccordionDetails = '[data-testid="decoded-tx-details"]'
|
|
|
|
//Arrays for the Transaction Details on Tx creation for different type of txs
|
|
export const MultisendData = ['Call', 'multiSend', 'on', 'Safe: MultiSendCallOnly 1.4.1']
|
|
export const SafeProxy = ['Call', 'createProxyWithNonce', 'on', 'SafeProxyFactory 1.4.1']
|
|
|
|
export const tx_status = {
|
|
execution_needed: 'Execution needed',
|
|
execute: 'Execute',
|
|
proposal: 'Proposal',
|
|
}
|
|
export const filterTypes = {
|
|
incoming: 'Incoming',
|
|
outgoing: 'Outgoing',
|
|
module: 'Module-based',
|
|
}
|
|
|
|
export const txActions = {
|
|
setFallbackHandler: 'setFallbackHandler',
|
|
}
|
|
|
|
export const advancedDetailsViewOptions = {
|
|
table: 'table',
|
|
grid: 'grid',
|
|
}
|
|
|
|
//tbr - will check if it should be removed ( we can cound by data-testid="tx-hexData" elements)
|
|
export function checkHashesExist(count) {
|
|
cy.contains(txAccordionDetails)
|
|
.next()
|
|
.within(() => {
|
|
main.verifyElementsCount(txHexDataRow, count)
|
|
cy.get(txHexDataRow).each(($el) => {
|
|
cy.wrap($el)
|
|
.invoke('text')
|
|
.should('match', /0x[a-fA-F0-9]{64}/)
|
|
})
|
|
})
|
|
}
|
|
|
|
export function clickOnHashes() {
|
|
cy.contains(hashesText).click()
|
|
}
|
|
export function clickOnReplaceTxOption() {
|
|
cy.get(replaceChoiceBtn).find('button').click()
|
|
}
|
|
|
|
export function verifyReplaceChoiceBtnVisible() {
|
|
cy.get(replaceChoiceBtn).find('button').should('be.visible')
|
|
}
|
|
|
|
export function getRejectButton() {
|
|
return cy.get(rejectTxBtn)
|
|
}
|
|
|
|
export function clickOnRejectBtn() {
|
|
getRejectButton().click()
|
|
}
|
|
|
|
export function hoverOverRejectBtnBtn() {
|
|
getRejectButton().trigger('mouseover', { force: true })
|
|
}
|
|
|
|
export function verifyRejectBtnDisabled() {
|
|
getRejectButton().should('be.disabled')
|
|
}
|
|
|
|
export function verifyTxRejectModalVisible() {
|
|
main.verifyMinimumElementsCount(wallet.choiceBtn, 2)
|
|
}
|
|
|
|
export function clickOnRejectionChoiceBtn(choice) {
|
|
cy.get(wallet.choiceBtn).eq(choice).click()
|
|
}
|
|
|
|
export function verifyTxNonceDisplayed(nonce) {
|
|
cy.get(nonceFld).should('include.text', nonce)
|
|
}
|
|
|
|
export function checkNonceIsReadOnly() {
|
|
cy.get(nonceFld).then(($el) => {
|
|
expect($el[0].nodeName).to.equal('DIV')
|
|
})
|
|
}
|
|
|
|
export function verifyRejecChoiceBtnStatus(option) {
|
|
cy.get(rejectChoiceBtn).find('button').should(option)
|
|
}
|
|
|
|
export function verifyDeleteChoiceBtnStatus(option) {
|
|
cy.get(deleteChoiceBtn).find('button').should(option)
|
|
}
|
|
|
|
export function typeNoteText(text) {
|
|
const input = cy.get(noteTextField).find('input')
|
|
input.clear()
|
|
input.type(text)
|
|
}
|
|
|
|
export function checkMaxNoteLength() {
|
|
typeNoteText(main.generateRandomString(61))
|
|
cy.get(noteTextField).should('exist')
|
|
cy.get(noteTextField).contains('60/60').should('be.visible')
|
|
}
|
|
|
|
export function checkNoteWarningMsg() {
|
|
cy.get(noteAlert).invoke('text').should('include', txNoteWarningMessage)
|
|
}
|
|
|
|
export function checkNoteRecordedNote(note) {
|
|
cy.get(recoredTxNote).should('be.visible').invoke('text').should('include', note)
|
|
}
|
|
|
|
export function checkNoteCreator(creator) {
|
|
cy.get(txNoteTooltip).trigger('mouseover', { force: true })
|
|
cy.get(noteCreator).should('be.visible').invoke('text').should('include', creator)
|
|
}
|
|
|
|
export function checkNoteRecordedNoteReadOnly() {
|
|
cy.get(recoredTxNote).then(($p) => {
|
|
expect($p.prop('tagName')).to.equal('P')
|
|
})
|
|
}
|
|
|
|
export function clickOnShareLinkBtn() {
|
|
cy.get(txShareLinkBtn).click()
|
|
}
|
|
|
|
export function verifyCopiedURL() {
|
|
cy.window().then((win) => {
|
|
cy.stub(win.navigator.clipboard, 'writeText').as('clipboardWrite')
|
|
})
|
|
|
|
cy.url().then((currentUrl) => {
|
|
clickOnShareLinkBtn()
|
|
|
|
cy.get('@clipboardWrite').should('have.been.calledWith', currentUrl)
|
|
})
|
|
}
|
|
|
|
export function verifyBulkExecuteBtnIsEnabled(txs) {
|
|
return cy.get('button').contains(bulkExecuteBtn(txs)).should('be.enabled')
|
|
}
|
|
|
|
export function verifyEnabledBulkExecuteBtnTooltip() {
|
|
cy.get('button').contains(bulkExecuteBtnStr).trigger('mouseover', { force: true })
|
|
cy.contains(enabledBulkExecuteBtnTooltip).should('exist')
|
|
}
|
|
|
|
export function deleteTx() {
|
|
clickOnRejectBtn()
|
|
cy.get(wallet.choiceBtn).contains(deleteFromQueueStr).click()
|
|
cy.get(deleteTxModalBtn).click()
|
|
}
|
|
|
|
export function deleteAllTx() {
|
|
cy.get('body').then(($body) => {
|
|
if ($body.find(transactionItem).length > 0) {
|
|
cy.get(transactionItem).then(($items) => {
|
|
for (let i = $items.length - 1; i >= 0; i--) {
|
|
cy.wrap($items[i]).click({ force: true })
|
|
deleteTx()
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
export function setTxType(type) {
|
|
cy.get(radioSelector).find('label').contains(type).click()
|
|
}
|
|
|
|
export function verifyNoTxDisplayed(type) {
|
|
cy.get(transactionItem)
|
|
.should('have.length', 0)
|
|
.then(($items) => {
|
|
main.verifyElementsCount($items, 0)
|
|
})
|
|
|
|
cy.contains(noTxFoundStr(type)).should('be.visible')
|
|
}
|
|
|
|
export function clickOnApplyBtn() {
|
|
cy.get(filterApplyBtn).click()
|
|
}
|
|
|
|
export function checkApplyBtnEnabled() {
|
|
cy.get(filterApplyBtn).should('not.be', 'disabled')
|
|
}
|
|
|
|
export function clickOnClearBtn() {
|
|
cy.get(filterClearBtn).click()
|
|
}
|
|
|
|
export function fillFilterForm({ address, startDate, endDate, amount, token, nonce, recipient } = {}) {
|
|
checkApplyBtnEnabled()
|
|
cy.wait(2000)
|
|
const inputMap = {
|
|
address: { selector: addressItem, findInput: true },
|
|
startDate: { selector: filterStartDateInput, findInput: true },
|
|
endDate: { selector: filterEndDateInput, findInput: true },
|
|
amount: { selector: filterAmountInput, findInput: true },
|
|
token: { selector: filterTokenInput, findInput: true },
|
|
nonce: { selector: filterNonceInput, findInput: true },
|
|
recipient: { selector: addressItem, findInput: true },
|
|
}
|
|
|
|
Object.entries({ address, startDate, endDate, amount, token, nonce, recipient }).forEach(([key, value]) => {
|
|
if (value !== undefined) {
|
|
const { selector, findInput } = inputMap[key]
|
|
const element = findInput ? cy.get(selector).find('input') : cy.get(selector)
|
|
element.invoke('removeAttr', 'readonly').clear().type(value, { force: true })
|
|
}
|
|
})
|
|
}
|
|
|
|
export function clickOnFilterBtn() {
|
|
cy.get('button').then((buttons) => {
|
|
const filterButton = [...buttons].find((button) => {
|
|
return ['Filter', 'Incoming', 'Outgoing', 'Module-based'].includes(button.innerText)
|
|
})
|
|
|
|
if (filterButton) {
|
|
cy.wrap(filterButton).click()
|
|
} else {
|
|
throw new Error('No filter button found')
|
|
}
|
|
})
|
|
}
|
|
|
|
export function checkTxItemDate(index, date) {
|
|
cy.get(txDate).eq(index).should('contain', date)
|
|
}
|
|
|
|
export function clickOnSendTokensBtn() {
|
|
cy.get(sendTokensBtn).click()
|
|
}
|
|
export function verifyNumberOfTransactions(count) {
|
|
cy.get(txDate).should('have.length.at.least', count)
|
|
cy.get(transactionItem).should('have.length.at.least', count)
|
|
}
|
|
|
|
export function checkRequiredThreshold(count) {
|
|
cy.get(requiredConfirmation).should('be.visible').and('include.text', count)
|
|
}
|
|
|
|
export function verifyAddressNotCopied(index, data) {
|
|
cy.get(copyIcon)
|
|
.parent()
|
|
.eq(index)
|
|
.trigger('click')
|
|
.wait(1000)
|
|
.then(() =>
|
|
cy.window().then((win) => {
|
|
win.navigator.clipboard.readText().then((text) => {
|
|
expect(text).not.to.contain(data)
|
|
})
|
|
}),
|
|
)
|
|
cy.get(untrustedTokenWarningModal).should('be.visible')
|
|
}
|
|
|
|
export function verifyWarningModalVisible() {
|
|
cy.get(untrustedTokenWarningModal).should('be.visible')
|
|
}
|
|
|
|
export function clickOnCopyBtn(index) {
|
|
cy.get(copyIcon).parent().eq(index).trigger('click')
|
|
}
|
|
|
|
export function verifyCopyIconWorks(index, data) {
|
|
cy.get(copyIcon)
|
|
.parent()
|
|
.eq(index)
|
|
.trigger('click')
|
|
.wait(1000)
|
|
.then(() =>
|
|
cy.window().then((win) => {
|
|
win.navigator.clipboard.readText().then((text) => {
|
|
expect(text).to.contain(data)
|
|
})
|
|
}),
|
|
)
|
|
}
|
|
|
|
export function verifyNumberOfCopyIcons(number) {
|
|
main.verifyElementsCount(copyIcon, number)
|
|
}
|
|
|
|
export function verifyNumberOfExternalLinks(number) {
|
|
cy.get('main')
|
|
.find(explorerBtn)
|
|
//.parent()
|
|
// .parent()
|
|
// .next()
|
|
//.children('a')
|
|
.then(($links) => {
|
|
expect($links.length).to.be.at.least(number)
|
|
for (let i = 0; i < number; i++) {
|
|
cy.wrap($links[i]).should('have.attr', 'href').and('include', constants.etherscanlLink)
|
|
}
|
|
})
|
|
}
|
|
|
|
export function clickOnTransactionItemByName(name, token) {
|
|
cy.get(transactionItem)
|
|
.filter(':contains("' + name + '")')
|
|
.then(($elements) => {
|
|
if (token) {
|
|
$elements = $elements.filter(':contains("' + token + '")')
|
|
}
|
|
cy.wrap($elements.first()).click({ force: true })
|
|
})
|
|
}
|
|
|
|
export function clickOnTransactionItemByIndex(index) {
|
|
cy.get(messageItem)
|
|
.eq(index)
|
|
.then(($elements) => {
|
|
cy.wrap($elements).click({ force: true })
|
|
})
|
|
}
|
|
|
|
export function verifyExpandedDetails(data, warning) {
|
|
main.checkTextsExistWithinElement(accordionDetails, data)
|
|
if (warning) cy.get(warning).should('be.visible')
|
|
}
|
|
|
|
export function verifyTxHeaderDetails(data) {
|
|
main.checkTextsExistWithinElement(transactionItem, data)
|
|
}
|
|
|
|
export function verifyAdvancedDetails(data) {
|
|
main.checkTextsExistWithinElement(accordionDetails, data)
|
|
}
|
|
|
|
export function verifyActions(data) {
|
|
main.checkTextsExistWithinElement(accordionDetails, data)
|
|
}
|
|
|
|
export function clickOnExpandableAction(data) {
|
|
cy.get(accordionDetails).within(() => {
|
|
cy.get('div').contains(data).click()
|
|
})
|
|
}
|
|
|
|
export function clickOnAdvancedDetails() {
|
|
cy.get(advancedDetails).click()
|
|
//({ force: true })
|
|
}
|
|
|
|
export function expandAdvancedDetails(data) {
|
|
clickOnAdvancedDetails()
|
|
data.forEach((row) => {
|
|
cy.get('div').contains(row).should('be.visible')
|
|
})
|
|
}
|
|
//The whole block inside Transaction details accordion: data-root and advanced details together
|
|
export function verifytxAccordionDetails(data) {
|
|
main.checkTextsExistWithinElement(txAccordionDetails, data)
|
|
}
|
|
//Search in the element with the scroll
|
|
export function verifytxAccordionDetailsScroll(data) {
|
|
main.checkTextsExistWithinElementScroll(txAccordionDetails, data)
|
|
}
|
|
|
|
export function switchView(view) {
|
|
if (view === advancedDetailsViewOptions.table) {
|
|
cy.get(tableViewBtn).click()
|
|
cy.get(txHexData).should('be.visible')
|
|
} else {
|
|
cy.get(gridViewBtn).click()
|
|
cy.get(txOperation).should('be.visible')
|
|
}
|
|
}
|
|
|
|
export function clickOnCopyDataBtn(expectedData) {
|
|
cy.window().then((win) => {
|
|
cy.stub(win.navigator.clipboard, 'writeText').as('clipboardWrite')
|
|
})
|
|
|
|
cy.get(txStack).find('button').click()
|
|
cy.get('@clipboardWrite').should('have.been.calledWith', expectedData)
|
|
}
|
|
|
|
export function switchToGridView() {
|
|
cy.get(gridViewBtn).click()
|
|
}
|
|
|
|
export function collapseAdvancedDetails() {
|
|
clickOnAdvancedDetails()
|
|
cy.get(baseGas).should('not.exist')
|
|
}
|
|
|
|
export function expandAllActions(actions) {
|
|
cy.get(expandAllBtn).click()
|
|
main.checkTextsExistWithinElement(accordionDetails, actions)
|
|
}
|
|
|
|
export function clickOnExpandAllActionsBtn() {
|
|
cy.get(expandAllBtn).click()
|
|
}
|
|
|
|
export function collapseAllActions(data) {
|
|
cy.get(collapseAllBtn).click()
|
|
data.forEach((action) => {
|
|
cy.get(txRowTitle).contains(action).should('have.css', 'visibility', 'hidden')
|
|
})
|
|
}
|
|
|
|
export function verifyActionListExists(data) {
|
|
main.checkTextsExistWithinElement(transactionSideList, data)
|
|
}
|
|
|
|
export function verifySpamIconIsDisplayed(name, token) {
|
|
cy.get(transactionItem)
|
|
.filter(':contains("' + name + '")')
|
|
.filter(':contains("' + token + '")')
|
|
.then(($elements) => {
|
|
cy.wrap($elements.first()).then(($element) => {
|
|
cy.wrap($element).find(spamTokenWarningIcon).should('be.visible')
|
|
})
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Helper function to verify icon alt/title attribute
|
|
* Checks for iframe (title), img (alt), or svg (alt) in the given element
|
|
*/
|
|
function verifyIconAlt($container, expectedAlt, context = 'element') {
|
|
const $iframe = $container.find('iframe')
|
|
const $img = $container.find('img')
|
|
const $svg = $container.find('svg')
|
|
|
|
if ($iframe.length > 0) {
|
|
const $targetIframe = $iframe.first()
|
|
const title = $targetIframe.attr('title')
|
|
expect(title, `iframe title attribute should exist and equal "${expectedAlt}" in ${context}`).to.exist
|
|
expect(title).to.equal(expectedAlt)
|
|
} else if ($img.length > 0) {
|
|
const $targetImg = $img.first()
|
|
const alt = $targetImg.attr('alt')
|
|
expect(alt, `img alt attribute should exist and equal "${expectedAlt}" in ${context}`).to.exist
|
|
expect(alt).to.equal(expectedAlt)
|
|
} else if ($svg.length > 0) {
|
|
const $targetSvg = $svg.first()
|
|
const alt = $targetSvg.attr('alt')
|
|
expect(alt, `svg alt attribute should exist and equal "${expectedAlt}" in ${context}`).to.exist
|
|
expect(alt).to.equal(expectedAlt)
|
|
} else {
|
|
throw new Error(`Expected alt "${expectedAlt}" in ${context} but no iframe, img, or svg found`)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function to verify token symbol in token text elements
|
|
* Handles both single-word tokens (e.g., "ETH") and multi-word tokens (e.g., "FLOWER #6188", "$ ETH35.com")
|
|
*/
|
|
function verifyTokenSymbol($element, expectedToken) {
|
|
const tokenTextElements = $element.find('b[class*="tokenText"]')
|
|
expect(tokenTextElements.length, 'At least one token text element should exist').to.be.greaterThan(0)
|
|
|
|
// Check if the expected token appears in any of the token text elements
|
|
let found = false
|
|
tokenTextElements.each((index, el) => {
|
|
const tokenText = Cypress.$(el).text().trim()
|
|
if (tokenText && tokenText.includes(expectedToken)) {
|
|
found = true
|
|
return false // Break the loop
|
|
}
|
|
})
|
|
|
|
expect(found, `Token "${expectedToken}" should be found in token text elements`).to.be.true
|
|
}
|
|
|
|
export function verifySummaryByName(name, token, data, alt, altToken) {
|
|
if (!name) {
|
|
throw new Error('Name parameter is required for verification')
|
|
}
|
|
|
|
let selector = `${transactionItem}:contains("${name}")`
|
|
if (token) {
|
|
selector += `:contains("${token}")`
|
|
}
|
|
|
|
cy.get(selector).then(($elements) => {
|
|
expect($elements.length, `Transaction items found for name: ${name}`).to.be.greaterThan(0)
|
|
|
|
const $element = $elements.first()
|
|
|
|
// Verify data text content (use cy.wrap for retryability, e.g. async status like "Expired")
|
|
if (Array.isArray(data)) {
|
|
data.forEach((text) => {
|
|
cy.wrap($element).should('contain.text', text)
|
|
})
|
|
} else if (data) {
|
|
cy.wrap($element).should('contain.text', data)
|
|
}
|
|
|
|
// Verify transaction type icon (alt parameter)
|
|
if (alt) {
|
|
const $txTypeElement = $element.find(txType)
|
|
if ($txTypeElement.length > 0) {
|
|
// Transaction type icon is in the tx-type element
|
|
verifyIconAlt($txTypeElement, alt, 'tx-type element')
|
|
} else {
|
|
// Fallback: check entire element for backward compatibility
|
|
verifyIconAlt($element, alt, 'transaction element')
|
|
}
|
|
}
|
|
|
|
// Verify token symbol (altToken parameter)
|
|
if (altToken) {
|
|
verifyTokenSymbol($element, altToken)
|
|
}
|
|
})
|
|
}
|
|
export function verifySummaryByIndex(index, data, alt) {
|
|
cy.get(messageItem)
|
|
.eq(index)
|
|
.then(($elements) => {
|
|
cy.wrap($elements).then(($element) => {
|
|
if (Array.isArray(data)) {
|
|
data.forEach((text) => {
|
|
cy.wrap($element).contains(text).should('be.visible')
|
|
})
|
|
} else {
|
|
cy.wrap($element).contains(data).should('be.visible')
|
|
}
|
|
if (alt) cy.wrap($element).find('img').eq(0).should('have.attr', 'alt', alt).should('be.visible')
|
|
})
|
|
})
|
|
}
|
|
|
|
export function clickOnTransactionItem(item) {
|
|
cy.get(transactionItem).eq(item).scrollIntoView().click({ force: true })
|
|
}
|
|
|
|
export function verifyTransactionActionsVisibility(option) {
|
|
cy.get(transactionSideList).should(option)
|
|
}
|
|
|
|
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).clear().type(address).should('have.value', address)
|
|
}
|
|
|
|
export function typeRecipientAddress_(index, address) {
|
|
cy.get(recepientInput_(index)).clear().type(address).should('have.value', address)
|
|
}
|
|
|
|
export function verifyENSResolves(fullAddress) {
|
|
let split = fullAddress.split(':')
|
|
let noPrefixAddress = split[1]
|
|
cy.get(recepientInput).should('have.value', noPrefixAddress)
|
|
}
|
|
|
|
export function verifyRandomStringAddress(randomAddressString) {
|
|
typeRecipientAddress(randomAddressString)
|
|
cy.contains(constants.addressBookErrrMsg.invalidFormat).should('be.visible')
|
|
}
|
|
|
|
export function verifyWrongChecksum(wronglyChecksummedAddress) {
|
|
typeRecipientAddress(wronglyChecksummedAddress)
|
|
cy.contains(constants.addressBookErrrMsg.invalidChecksum).should('be.visible')
|
|
}
|
|
|
|
export function verifyAmountLargerThanCurrentBalance() {
|
|
setSendValue(9999)
|
|
cy.contains(constants.amountErrorMsg.largerThanCurrentBalance).should('be.visible')
|
|
}
|
|
|
|
export function verifyTooltipMessage(message) {
|
|
cy.get('div[role="tooltip"]').contains(message).should('be.visible')
|
|
}
|
|
|
|
export function selectCurrentWallet() {
|
|
cy.get(connectedWalletExecMethod).click()
|
|
}
|
|
|
|
export function verifyRelayerAttemptsAvailable() {
|
|
cy.contains(transactionsPerHrStr).should('exist')
|
|
}
|
|
|
|
export function clickOnTokenselectorAndSelectSepoliaEth() {
|
|
cy.get(tokenSelector).click()
|
|
cy.get('ul[role="listbox"]').contains(constants.tokenNames.sepoliaEther).click()
|
|
}
|
|
|
|
export function clickOnTokenselectorAndSelectToken(tokenName) {
|
|
cy.get(tokenSelector).click()
|
|
cy.get('ul[role="listbox"]').contains(tokenName).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 = parseFloat(element.text().replace(tokenAbbreviation, '').trim())
|
|
cy.get(amountInput).should(($input) => {
|
|
const actualValue = parseFloat($input.val())
|
|
expect(actualValue).to.be.closeTo(maxBalance, 0.1)
|
|
})
|
|
console.log(maxBalance)
|
|
})
|
|
}
|
|
|
|
export function setSendValue(value) {
|
|
cy.get(amountInput).clear().type(value)
|
|
}
|
|
|
|
export function setSendValue_(index, value) {
|
|
cy.get(amountInput_(index)).clear().type(value)
|
|
}
|
|
|
|
export function clickOnNextBtn() {
|
|
cy.contains(main.nextBtnStr).click()
|
|
}
|
|
|
|
export function verifySubmitBtnIsEnabled() {
|
|
cy.get('button[type="submit"]').should('not.be.disabled')
|
|
}
|
|
|
|
export function verifyContinueSignBtnIsEnabled() {
|
|
cy.get(continueSignBtn).should('not.be.disabled')
|
|
}
|
|
|
|
export function verifyAddToBatchBtnIsEnabled() {
|
|
return cy.get(addToBatchBtn).should('not.be.disabled')
|
|
}
|
|
|
|
export function verifyNativeTokenTransfer() {
|
|
cy.get(modal.cardContent).within(() => {
|
|
cy.contains('Send').should('be.visible')
|
|
cy.contains(nativeTokenTransferStr).should('be.visible')
|
|
})
|
|
}
|
|
|
|
export function changeNonce(value) {
|
|
cy.get(nonceInput).clear().type(value, { force: true })
|
|
}
|
|
|
|
export function hasNonce() {
|
|
cy.get(nonceInput).invoke('val').should('match', /^\d+$/)
|
|
}
|
|
|
|
export function verifyNonceInputValue(value) {
|
|
cy.get(nonceInput).should('have.value', value)
|
|
}
|
|
|
|
export function displayAdvancedDetails() {
|
|
cy.contains(estimatedFeeStr).click()
|
|
}
|
|
|
|
export function openExecutionParamsModal() {
|
|
displayAdvancedDetails()
|
|
cy.contains(editBtnStr).click()
|
|
}
|
|
|
|
export function verifyAndSubmitExecutionParams() {
|
|
cy.contains(executionParamsStr).parents('form').as('Paramsform')
|
|
const arrayNames = [
|
|
advancedParametersInputNames.walletNonce,
|
|
advancedParametersInputNames.maxPriorityFee,
|
|
advancedParametersInputNames.maxFee,
|
|
advancedParametersInputNames.gasLimit,
|
|
]
|
|
arrayNames.forEach((element) => {
|
|
cy.get('@Paramsform').find('label').contains(`${element}`).next().find('input').should('not.be.disabled')
|
|
})
|
|
|
|
cy.get('@Paramsform').find(gasLimitInput).clear().type('100').invoke('prop', 'value').should('equal', '100')
|
|
cy.contains(gasLimit21000).should('be.visible')
|
|
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 setAdvancedExecutionParams() {
|
|
cy.contains(executionParamsStr).parents('form').as('Paramsform')
|
|
cy.get('@Paramsform').find(gasLimitInput).clear().type(advancedParametersValues.gasLimit)
|
|
cy.get('@Paramsform').find(maxPriorityFee).clear().type(advancedParametersValues.maxPriorityFee)
|
|
cy.get('@Paramsform').find(maxFee).clear().type(advancedParametersValues.maxFee)
|
|
cy.get('@Paramsform').find(walletNonceInput).clear().type(advancedParametersValues.walletNonce)
|
|
cy.get('@Paramsform').submit()
|
|
}
|
|
|
|
export function verifyEditedExcutionParams() {
|
|
cy.contains(advancedParametersInputNames.walletNonce).next().should('contain', advancedParametersValues.walletNonce)
|
|
cy.contains(advancedParametersInputNames.gasLimit).next().should('contain', advancedParametersValues.gasLimit)
|
|
cy.contains(advancedParametersInputNames.maxPriorityFee)
|
|
.next()
|
|
.should('contain', advancedParametersValues.maxPriorityFee)
|
|
cy.contains(advancedParametersInputNames.maxFee).next().should('contain', advancedParametersValues.maxFee)
|
|
}
|
|
|
|
export function clickOnNoLaterOption() {
|
|
cy.contains(noLaterStr).click()
|
|
}
|
|
|
|
export function clickOnSignTransactionBtn() {
|
|
cy.get(signBtn).click()
|
|
}
|
|
|
|
export function clickOnProposeTransactionBtn() {
|
|
cy.get(proposeTransactionBtn).click()
|
|
}
|
|
|
|
export function clickOnContinueSignTransactionBtn() {
|
|
cy.get(continueSignBtn).click()
|
|
}
|
|
|
|
export function clickOnConfirmTransactionBtn() {
|
|
cy.get('button').contains(confirmBtnStr).click()
|
|
}
|
|
|
|
export function verifyConfirmTransactionBtnIsVisible() {
|
|
cy.get('button').contains(confirmBtnStr).should('be.visible')
|
|
}
|
|
|
|
export function clickOnConfirmBtn(index) {
|
|
cy.wait(2000)
|
|
cy.get(transactionItem)
|
|
.eq(index)
|
|
.within(() => {
|
|
cy.get('button')
|
|
.contains(confirmBtnStr)
|
|
.then((elements) => {
|
|
cy.wrap(elements[0]).click()
|
|
})
|
|
})
|
|
}
|
|
|
|
export function clickOnExecuteBtn(index) {
|
|
cy.wait(2000)
|
|
cy.get(transactionItem)
|
|
.eq(index)
|
|
.within(() => {
|
|
cy.get('button')
|
|
.contains(executeBtnStr)
|
|
.then((elements) => {
|
|
cy.wrap(elements[0]).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.sep}`).should('exist')
|
|
}
|
|
|
|
export function verifyDateExists(date) {
|
|
cy.contains('div', date).should('exist')
|
|
}
|
|
|
|
export function verifyImageAltTxt(index, text) {
|
|
cy.get('img').eq(index).should('have.attr', 'alt', text).should('be.visible')
|
|
}
|
|
|
|
export function verifyStatus(status) {
|
|
cy.contains('div', status).should('exist')
|
|
}
|
|
|
|
export function verifyTransactionStrExists(str) {
|
|
cy.contains(str).should('exist')
|
|
}
|
|
|
|
export function verifyTransactionStrNotVible(str) {
|
|
cy.contains(str).should('not.be.visible')
|
|
}
|
|
|
|
export function clickOnExpandAllBtn() {
|
|
cy.contains(expandAllBtnStr).click()
|
|
}
|
|
|
|
export function clickOnCollapseAllBtn() {
|
|
cy.contains(collapseAllBtnStr).click()
|
|
}
|
|
|
|
export function verifyTxDestinationAddress(receivedAddress) {
|
|
cy.get(receivedAddress).then((address) => {
|
|
cy.contains(address).should('exist')
|
|
})
|
|
}
|
|
|
|
export function verifyReplacedSigner(newSignerName) {
|
|
cy.get(replacementNewSigner).should('exist').contains(newSignerName)
|
|
}
|
|
|
|
function verifyBulkActions(actions) {
|
|
actions.forEach((action) => {
|
|
cy.contains(action).should('exist')
|
|
})
|
|
}
|
|
|
|
export function verifyBulkConfirmationScreen(tx, actions) {
|
|
cy.contains(bulkConfirmationText(tx))
|
|
verifyBulkActions(actions)
|
|
cy.get(modal.modalHeader).within(() => {
|
|
cy.contains(batchModalTitle).should('exist')
|
|
cy.get('svg').should('exist')
|
|
})
|
|
}
|
|
|
|
export function verifyBulkTxHistoryBlock(order, tx, actions) {
|
|
cy.contains(order)
|
|
.parent('div')
|
|
.parent()
|
|
.eq(0)
|
|
.within(() => {
|
|
cy.contains(tx)
|
|
verifyBulkActions(actions)
|
|
})
|
|
}
|
|
|
|
export function verifyBulkExecuteBtnIsDisabled() {
|
|
cy.get('button').contains(bulkExecuteBtnStr).should('be.disabled')
|
|
cy.get('button').contains(bulkExecuteBtnStr).trigger('mouseover', { force: true })
|
|
cy.contains(disabledBultExecuteBtnTooltip).should('exist')
|
|
}
|
|
|
|
export function toggleUntrustedTxs() {
|
|
cy.get(toggleUntrustedBtn).click()
|
|
}
|
|
|
|
export function clickOnSimulateTxBtn() {
|
|
cy.get(simulateTxBtn).click()
|
|
}
|
|
|
|
export function verifySuccessfulSimulation() {
|
|
cy.get(simulateSuccess).should('exist')
|
|
}
|
|
|
|
export function insufficientBalanceErrorExists() {
|
|
cy.get(insufficientBalanceError).should('exist')
|
|
}
|
|
|
|
export function recipientAddress(index, address) {
|
|
cy.contains(`Recipient ${index}`)
|
|
.parents(dataRow)
|
|
.within(() => {
|
|
cy.contains(address).should('exist')
|
|
})
|
|
}
|
|
|
|
export function insufficientFundsErrorExists(index) {
|
|
cy.get(tokenAmountSection)
|
|
.eq(index)
|
|
.within(() => {
|
|
cy.get('label').should('contain.text', insufficientFundsErrorStr)
|
|
})
|
|
}
|
|
|
|
export function checkTokenValue(index, value) {
|
|
cy.get(amountInput_(index)).should('have.value', value)
|
|
}
|
|
export function clickOnMaxBtn(index) {
|
|
cy.get(maxBtn).eq(index).click()
|
|
}
|
|
|
|
export function verifyAddRecipientBtnIsVisible() {
|
|
cy.get(addrecipientBtn).should('be.visible')
|
|
}
|
|
|
|
export function verifyAddRecipientBtnDoesNotExist() {
|
|
main.verifyElementsCount(addrecipientBtn, 0)
|
|
}
|
|
|
|
export function clickOnAddRecipientBtn() {
|
|
cy.get(addrecipientBtn).click()
|
|
}
|
|
|
|
export function clickOnRemoveRecipientBtn(index) {
|
|
cy.get(removeRecipientBtn).eq(index).click()
|
|
}
|
|
|
|
export function checkNumberOfRecipients(count) {
|
|
cy.get(recipientsCount).should('have.text', `${count}`)
|
|
}
|
|
|
|
export function checkMaxRecipientReached(attempt = 0) {
|
|
const maxAttempts = 4
|
|
|
|
cy.get('body').then(($body) => {
|
|
if ($body.find(maxRecipientsReachedMsg).length > 0) {
|
|
cy.get(maxRecipientsReachedMsg).should('exist')
|
|
cy.get(addrecipientBtn).should('be.disabled')
|
|
checkNumberOfRecipients('5/5')
|
|
return
|
|
}
|
|
|
|
if (attempt >= maxAttempts) {
|
|
throw new Error('Max attempts reached but message did not appear')
|
|
}
|
|
|
|
clickOnAddRecipientBtn()
|
|
checkNumberOfRecipients(`${attempt + 2}/5`)
|
|
checkMaxRecipientReached(attempt + 1)
|
|
})
|
|
}
|
|
|
|
export function selectComboButtonOption(option) {
|
|
cy.get(comboButton).click()
|
|
cy.get(comboButtonPopover).findByText(comboButtonOptions[option]).click()
|
|
}
|
|
|
|
export function checkThatComboButtonOptionIsNotPresent(option) {
|
|
cy.get('body').then(($body) => {
|
|
if ($body.find(comboButton).length > 0) {
|
|
cy.get(comboButton).then(($dropdown) => {
|
|
if ($dropdown.is(':visible')) {
|
|
cy.get(comboButton).click()
|
|
cy.get(comboButtonPopover).should('be.visible')
|
|
cy.get(comboButtonPopover).should('not.contain.text', option)
|
|
cy.get('body').click(0, 0)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
//Functions for the happy path flow
|
|
export function cleanTransactionQueue(safeAddress, signer) {
|
|
cy.visit(constants.transactionQueueUrl + safeAddress)
|
|
walletUtils.connectSigner(signer)
|
|
deleteAllTx()
|
|
navigation.clickOnWalletExpandMoreIcon()
|
|
navigation.clickOnDisconnectBtn()
|
|
}
|
|
|
|
export function switchToSignerAndConfirm(signer) {
|
|
navigation.clickOnWalletExpandMoreIcon()
|
|
navigation.clickOnDisconnectBtn()
|
|
walletUtils.connectSigner(signer)
|
|
clickOnConfirmTransactionBtn()
|
|
clickOnContinueSignTransactionBtn()
|
|
selectComboButtonOption('sign')
|
|
clickOnSignTransactionBtn()
|
|
}
|
|
|
|
export function deleteTransactionAndSwitchToSigner(signer) {
|
|
navigation.clickOnWalletExpandMoreIcon()
|
|
navigation.clickOnDisconnectBtn()
|
|
walletUtils.connectSigner(signer)
|
|
deleteTx()
|
|
}
|
|
|
|
export function createAddOwnerTransaction(safeAddress, signer, ownerAddress, ownerIndex = 2) {
|
|
// Create add owner transaction
|
|
cy.visit(constants.setupUrl + safeAddress)
|
|
walletUtils.connectSigner(signer)
|
|
owner.waitForConnectionStatus()
|
|
owner.openManageSignersWindow()
|
|
owner.clickOnAddSignerBtn()
|
|
owner.typeOwnerAddressManage(ownerIndex, ownerAddress)
|
|
changeNonce(1)
|
|
owner.clickOnNextBtnManage()
|
|
owner.verifyConfirmTransactionWindowDisplayed()
|
|
clickOnContinueSignTransactionBtn()
|
|
selectComboButtonOption('sign')
|
|
clickOnSignTransactionBtn()
|
|
clickViewTransaction()
|
|
}
|