Files
8bfc31027e tests(e2e): restore popup settle time and fix smoke test failures (#7525)
* 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]>
2026-03-25 14:37:13 +01:00

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')
}