mirror of
https://github.com/luxfi/safe-wallet.git
synced 2026-10-30 15:19:21 +00:00
* fix(e2e): restore popup settle time and fix NFT click reliability
The previous cy.wait removal in closeOutreachPopup, acceptCookies2, and
closeSecurityNotice replaced delays with cy.get('body', { timeout })
which resolves immediately since body always exists, removing the settle
time needed for async popups to render before the conditional jQuery
check. This caused spending_limits tests to fail. Pin ubuntu-24.04 in
the smoke workflow to prevent Cypress Cloud parallel-group-params-mismatch
from differing runner OS versions. Add scrollIntoView and force click
for the inactive NFT icon inside a Tooltip wrapper.
* chore(ci): disable Cypress Cloud recording and split specs manually
Cypress Cloud parallel mode fails with parallel-group-params-mismatch
when GitHub runners have different Ubuntu minor versions (24.04.3 vs
24.04.4). Pinning ubuntu-24.04 doesn't help since both versions exist
under that label.
Disable recording to bypass the cross-machine validation and split
specs across containers using modular arithmetic so each container
runs a unique subset (~4 specs each).
* fix(e2e): revert cy.wait removals that caused test failures
Revert the Cypress page object changes from 47762e16c that replaced
cy.wait() calls with cy.get('body', { timeout }) patterns. The
replacement didn't work because body always exists immediately, removing
the settle time needed for async UI elements (popups, modals, animations)
to render before conditional jQuery checks.
This restores the original cy.wait() calls in: main.page.js,
address_book.page.js, assets.pages.js, dashboard.pages.js,
load_safe.pages.js, sidebar.pages.js, address_book.cy.js, and
load_safe.cy.js.
* fix(e2e): fix test selectors conflicting with keepMounted CaptchaModal
The CaptchaModal uses keepMounted on its ModalDialog, which means
data-testid="modal-title" (with text "Security check") is always
present in the DOM even when the modal is closed. This breaks:
- spending_limits: cy.get(modalTitle).should('have.text', 'New transaction')
finds the CaptchaModal's title first and fails the text check.
Fix: use cy.contains(modalTitle, text) to match on both selector and text.
- nfts: cy.get(modalTitle).should('not.exist') fails because the
CaptchaModal's title always exists in the DOM.
Fix: use should('not.be.visible') instead of should('not.exist').
---------
Co-authored-by: Hanzo Dev <[email protected]>
245 lines
8.5 KiB
JavaScript
245 lines
8.5 KiB
JavaScript
import * as constants from '../../support/constants.js'
|
|
import * as safeapps from './safeapps.pages.js'
|
|
import * as main from './main.page.js'
|
|
import * as createtx from './create_tx.pages.js'
|
|
import staticSafes from '../../fixtures/safes/static.js'
|
|
|
|
const transactionQueueStr = 'Pending transactions'
|
|
const noTransactionStr = 'This Safe has no queued transactions'
|
|
const overviewStr = 'Total'
|
|
const sendStr = 'Send'
|
|
const receiveStr = 'Receive'
|
|
const viewAllStr = 'View all'
|
|
const explorePossibleStr = "Explore what's possible"
|
|
const swapSuggestion = 'Swap tokens instantly'
|
|
|
|
const copyShareBtn = '[data-testid="copy-btn-icon"]'
|
|
const exploreAppsBtn = '[data-testid="explore-apps-btn"]'
|
|
const viewAllLink = '[data-testid="view-all-link"][href^="/transactions/queue"]'
|
|
const noTxText = '[data-testid="no-tx-text"]'
|
|
const actionRequiredPanel = '[data-testid="action-required-panel"]'
|
|
const actionRequiredPanelToggle = '[data-testid="action-required-panel-toggle"]'
|
|
const actionRequiredPanelContent = '[data-testid="action-required-panel-content"]'
|
|
export const pendingTxWidget = '[data-testid="pending-tx-widget"]'
|
|
export const pendingTxItem = '[data-testid="tx-pending-item"]'
|
|
export const assetsWidget = '[data-testid="assets-widget"]'
|
|
const singleTxDetailsHeader = '[data-testid="tx-details"]'
|
|
// Case #1 — Outdated official mastercopy (Info) → "Update"
|
|
export const outdatedOfficialTitlePrefix = 'New Safe version is available'
|
|
export const outdatedOfficialContent =
|
|
'Update now to take advantage of new features and the highest security standards available. You will need to confirm this update just like any other transaction.'
|
|
export const mastercopyActions = {
|
|
update: '[data-testid="update-mastercopy-btn"]',
|
|
migrate: '[data-testid="migrate-mastercopy-btn"]',
|
|
getCli: '[data-testid="get-cli-link"]',
|
|
}
|
|
export const reviewSignersTestId = '[data-testid="review-signers-btn"]'
|
|
|
|
// Case #2 & #3 — Unsupported mastercopy (Warning)
|
|
export const unsupportedMastercopyTitle = 'This Safe is running an unsupported version'
|
|
export const unsupportedMigratableContent =
|
|
'and may miss security fixes and improvements. You should migrate it to a compatible version.'
|
|
export const unsupportedCliContent =
|
|
'and may miss security fixes and improvements. You must use our CLI tool to migrate.'
|
|
|
|
const migrateSafeSubtitle = 'Update Safe Account base contract'
|
|
export const nonPinnedWarningTitle = 'Not in your trusted list'
|
|
export const trustThisSafeButtonTestId = '[data-testid="trust-this-safe-button"]'
|
|
const trustDialogTestId = '[data-testid="add-trusted-safe-dialog"]'
|
|
|
|
export function clickOnTxByIndex(index) {
|
|
// Wait for hydration to set the correct safe query param in the link href
|
|
cy.get(pendingTxItem)
|
|
.eq(index)
|
|
.should('have.attr', 'href')
|
|
.and('match', /safe=.{3,}/)
|
|
cy.get(pendingTxItem).eq(index).click()
|
|
cy.get(singleTxDetailsHeader).should('be.visible')
|
|
}
|
|
|
|
export function verifySingleTxItem(data) {
|
|
main.checkTextsExistWithinElement(createtx.transactionItem, data)
|
|
}
|
|
|
|
export function verifyDataInPendingTx(data) {
|
|
main.checkTextsExistWithinElement(pendingTxWidget, data)
|
|
}
|
|
|
|
export function verifyTxItemInPendingTx(data) {
|
|
let matchFound = false
|
|
|
|
cy.get(pendingTxItem)
|
|
.each(($item) => {
|
|
const itemText = $item.text()
|
|
const isMatch = data.every((tx) => itemText.includes(tx))
|
|
|
|
if (isMatch) {
|
|
matchFound = true
|
|
return false
|
|
}
|
|
})
|
|
.then(() => {
|
|
expect(matchFound).to.be.true
|
|
})
|
|
}
|
|
|
|
export function verifyEmptyTxSection() {
|
|
main.verifyElementsIsVisible([noTxText])
|
|
}
|
|
|
|
export function clickOnViewAllBtn() {
|
|
cy.get(viewAllLink).click()
|
|
}
|
|
|
|
export function pinAppByIndex(index) {
|
|
return cy
|
|
.get('[aria-label*="Pin"]')
|
|
.eq(index)
|
|
.click()
|
|
.then(() => {
|
|
cy.wait(1000)
|
|
return cy.get('[aria-label*="Unpin"]').eq(0).invoke('attr', 'aria-label')
|
|
})
|
|
}
|
|
|
|
export function clickOnPinBtnByName(name) {
|
|
cy.get(`[aria-label="${name}"]`).click()
|
|
}
|
|
|
|
export function verifyPinnedAppsCount(count) {
|
|
cy.get(`[aria-label*="Unpin"]`).should('have.length', count)
|
|
}
|
|
|
|
export function clickOnExploreAppsBtn() {
|
|
cy.get(exploreAppsBtn).click()
|
|
cy.get(safeapps.safeAppsList)
|
|
.should('exist')
|
|
.within(() => {
|
|
cy.get('li').should('have.length.at.least', 1)
|
|
})
|
|
}
|
|
|
|
export function verifyShareBtnWorks(index, data) {
|
|
cy.get(copyShareBtn)
|
|
.eq(index)
|
|
.click()
|
|
.wait(1000)
|
|
.then(() =>
|
|
cy.window().then((win) => {
|
|
win.navigator.clipboard.readText().then((text) => {
|
|
expect(text).to.contain(data)
|
|
})
|
|
}),
|
|
)
|
|
}
|
|
|
|
export function verifyOverviewWidgetData() {
|
|
// Alias for the Overview section
|
|
cy.contains('div', overviewStr).parents('section').as('overviewSection')
|
|
|
|
cy.get('@overviewSection').within(() => {
|
|
// Prefix is separated across elements in EthHashInfo
|
|
cy.get('button').contains(sendStr)
|
|
cy.get('button').contains(receiveStr)
|
|
})
|
|
}
|
|
|
|
export function verifyTxQueueWidget() {
|
|
// Alias for the Transaction queue section
|
|
cy.contains('p', 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"]`,
|
|
'Send' + `-0.00002 ${constants.tokenAbbreviation.sep}`,
|
|
).should('exist')
|
|
|
|
cy.contains(`a[href^="/transactions/tx?id=multisig_0x"]`, '1/1').should('exist')
|
|
|
|
cy.contains(
|
|
`a[href="${constants.transactionQueueUrl}${encodeURIComponent(staticSafes.SEP_STATIC_SAFE_2)}"]`,
|
|
viewAllStr,
|
|
)
|
|
})
|
|
}
|
|
|
|
export function verifyExplorePossibleSection() {
|
|
cy.contains('h2', explorePossibleStr).parents('section').as('explorePossibleSection')
|
|
cy.get('@explorePossibleSection').contains(swapSuggestion)
|
|
}
|
|
|
|
export function expandActionRequiredPanel() {
|
|
cy.get(actionRequiredPanel, { timeout: 30000 }).should('be.visible')
|
|
cy.get(actionRequiredPanelToggle).click()
|
|
cy.get(actionRequiredPanelContent, { timeout: 10000 }).should('be.visible')
|
|
}
|
|
|
|
/**
|
|
* Verify the action required panel shows the expected message count.
|
|
* @param {number} expectedCount - Expected count displayed in the panel badge
|
|
*/
|
|
export function verifyActionRequiredPanelCount(expectedCount) {
|
|
cy.get(actionRequiredPanel, { timeout: 30000 }).should('be.visible')
|
|
cy.get(actionRequiredPanel).contains('Action required').should('be.visible')
|
|
cy.get(actionRequiredPanel).invoke('text').should('include', String(expectedCount))
|
|
}
|
|
|
|
/**
|
|
* Verify a card in the action required panel by message(s) and/or action label.
|
|
* Uses main.verifyValuesExist for message verification (elements inside panel).
|
|
* @param {Object} options
|
|
* @param {boolean} [options.expandFirst=true] - Expand the panel before verifying
|
|
* @param {string[]} [options.messages=[]] - Text(s) that must be visible in the card (title, content)
|
|
* @param {string} [options.actionLabel] - Text on the action button/link to verify visible
|
|
*/
|
|
export function verifyActionRequiredCard({ expandFirst = true, messages = [], actionTestId } = {}) {
|
|
if (expandFirst) {
|
|
expandActionRequiredPanel()
|
|
}
|
|
if (messages.length > 0) {
|
|
main.verifyValuesExist(actionRequiredPanel, messages)
|
|
}
|
|
if (actionTestId) {
|
|
cy.get(actionRequiredPanel).within(() => {
|
|
cy.get(actionTestId).should('be.visible')
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Click an action (button or link) in the action required panel by its label text.
|
|
* @param {string} actionLabel - Text on the button/link to click
|
|
*/
|
|
export function clickActionInPanel(actionTestId) {
|
|
cy.get(actionRequiredPanel).within(() => {
|
|
cy.get(actionTestId).click()
|
|
})
|
|
}
|
|
|
|
export function verifyMigrateSafeFlowOpened() {
|
|
cy.contains(migrateSafeSubtitle, { timeout: 30000 }).should('be.visible')
|
|
}
|
|
|
|
/** Verifies the "Get CLI" link is visible in the action required panel (Case #3). */
|
|
export function verifyGetCliLinkInPanel() {
|
|
cy.get(actionRequiredPanel).within(() => {
|
|
cy.get(mastercopyActions.getCli, { timeout: 10000 }).should('be.visible')
|
|
})
|
|
}
|
|
|
|
export function verifyTrustDialogVisible() {
|
|
cy.get(trustDialogTestId, { timeout: 15000 }).should('be.visible')
|
|
}
|
|
|
|
/** Message in action-required-panel when signers differ across chains (dashboard) */
|
|
const differentSignersAcrossChainsMsg = 'different signers across different networks'
|
|
|
|
/** Verifies the "You have different signers across different networks" warning is displayed in action-required-panel. */
|
|
export function checkInconsistentSignersMsgDisplayed() {
|
|
cy.contains(differentSignersAcrossChainsMsg, { timeout: 30000 }).should('be.visible')
|
|
}
|