Files
6a7e570d5c fix(web/Spaces): Epic Spaces M2 (#7539)
* fix(web/Spaces): UI adjustments for the Sidebar and stories (#7527)

* fix: UI adjustments for the Sidebar and stories

* feat: deterministic color for the Space avatar on the Safe level

* refactor: replace hardcoded css value with a token

* feat(web/Spaces): Sidebar enhancements and polished Stories (#7552)

* fix: UI for the Actions Tray CTAs

* fix: UI for the Actions Tray CTAs

* fix: UI for the Header

* feat: add Settings btn to the Safe sidebar

* feat: CGW toggle in the Safe sidebar; tooltip when buttons are disabled

* feat: add a warning dot to the Settings tab when Safe version is outdated

* test: expand test suite for the Safe Sidebar

* test: add tests for sidebar when Safe is not deployed

* feat: add a story for the sidebar when Safe is not deployed

* refactor: replace hardcoded css value with a token

* refactor(web/Spaces): remove code duplications; Onboarding pages follow Feature Architecture; tracking fixes (#7575)

* refactor: move duplicated code from the sidebar to NavItem component

* refactor: Spaces Onboarding components support feature architecture

* fix: tracking for Members and Safe accounts (Spaces) per Space instance

* test: add tests for the useTrackSpace hook

* feat: unify top bar (#7596)

* feat: add topbar for safe and space

* feat: adjust tests

* fix: delete global search bar from space

* fix: css fix for mobile

* fix: add back the datatest id

* fix: fix styling and test id

* fix: fix smoke e2e test

* fix: add events tracking

* fix: fix unit tests

* fix: add back original features

* fix: fix test

* fix: fix regression tests

* fix: update tests

* fix: delete the place holder

* fix: fix sidebar top padding

* fix: fix logic

* fix: add back placeholder

* feat(web): add global search feature (#7618)

* feat(web): create initial global search input component

* feat(web): add initial seach section draft

* feat(web): create the mocked internal layout for the global search

* feat(web): integrate accounts page in the AccountsSection

* feat(web): add trusted safes list

* feat(web): create globalSeach slice

* feat(web): add keyboard observable hook to allow global search events

* feat(web): allow user to filter global search items by typing on the search input

* feat(web): Display no results when there is no items available for the user search

* refactor(global-search): streamline global search components and hooks, update imports, and enhance filtering logic

* fix(web): topbar conflicts

* fix(web): prettier

* feat(web): enhance GlobalSearchModal with route change handling and update section items for improved functionality

* fix: topbar styling improvement (#7637)

* feat(web/Spaces): Sidebar with Add To Space functionality (#7602)

* fix: add mock providers for the Safe Sidebar so all NavItems get rendered

* fix: UI adjustments for the Spaces Selector Dropdown

* feat: Add To Space back btn + Spaces dropdown UI in Stories

* feat: new sidebar on the Safe level (WIP)

* refactor: move new hooks and components to separate files; move test files to corresponding folders

* fix: Storybook fixes for stories with Back To Space btn

* refactor: remove unused CSS classes

* refactor: separate spaceId extraction fn in utils

* feat: notification on success when Safe is added to Space

* fix: treat a Safe that is added to a Space as a Qualified Safe

* refactor: wrap sidebar functions in useCallback

* fix: collapsed API button in the Spaces Sidebar (#7609)

* feat: New Transaction btn

* test: add tests for the new New Tx Btn

* fix: UI fixes for sidebar elements

* feat: disable adding to a Space if Safes per Space limit is exceeded

* refactor: minior refactoring of Space limit logic

* test: add tests for the Space limit functionality

* fix: typing

* feat: no Add To Space btn for cf safes + tests

* feat: stories for different Safe Sidebar states

* feat: add tracking events to the new sidebar + tests

* feat: add Add Spaces popup modal component + story

* refactor: extract the workspace header logic to a new component; add Add To Space modal to the UI

* feat: add Shadcn Dialog and wrap App To Space popup into it

* fix: UI fixes for the Add To Space modal

* fix: add hydration check to the Safe sidebar

* test: improve test coverage for new components

* refactor: adjust z-indexes

* fix: granular error notifications; minor UI fixes

* refactor: move the Add To Space modal into a separate spaces component

* refactor: rectructure Sidebar components

* refactor: simplify Space selector dropdown for better readability

* test: increase test coverage

* fix: add guard to preventback to space navigation if spaceId is falsy; refactor: minor refactoring

* fix: skip failing smoke tests (will be adjusted in another PR)

* refactor: simplify components complexity; address issues; skip failing tests

* fix: UI: sidebar icons styles match header icons styles

* feat: wrap sidebar icons in a tooltip

* refactor: prevent a re-render of nav items when tx data arrives later

* test: fix failing tests

* fix: remove duplicated closing but from Add To Space modal

* fix: show API errors in the toaster on safe addition

* chore: remove unused css classes

* refactor: remove redundant spaceName prop, derive from selectedSpace.name directly

* refactor: rename `NewTransactionButton` to `SidebarActionButton` as it handles not only new txs

* feat: highlight active item icon in the sidebar

* test: add more tests for the safe sidebar

* feat(web): add dashboard empty states with SetupWidget and improved accounts widget (#7638)

* feat(web): adjust accounts widget empty state component to follow new figma design

* feat(web): create static setup widget component

* feat(web): enhance SetupWidget with loading state and step sorting logic

* feat(web): enhance AddAccounts and SetupWidget components with external state handling and animations

* feat(web): enhance SetupWidget with dismissal logic and explore modal integration

* feat(web): implement local storage handling for SetupWidget dismissal in SpaceDashboard

* refactor(web): clean up SetupWidget test mocks and remove unused Card import in Dashboard

* fix(web): unit tests

* feat(web): add completion tracking for SetupWidget and update modal close handling

* feat(global-search): implement navigate-to actions (#7640)

* feat(web/Spaces): UI improvements for the sidebar (#7643)

* fix: cut Postgres details from the Add Account error message

* feat: add animation to the sidebar

* feat: adjust Sidebar skeleton

* feat: add animation to the sidebar top to enable smooth transition on toggle

* fix: adjust paddings in the top sidebar

* fix: adjust the tooltip text when Safes limit is reached

* feat: replace sidebar skeleton component with the per-row skeletons to enable faster load of static sidebar items

* refactor: performance optimization for animation consts

* fix: Spaces avatar position on minimized sidebar

* refactor: adjust css classes for a smoother transition with less repaints

* test: fix unit tests

* fix(web/Spaces): UI fixes for the Sidebar after Design QA (#7657)

* refactor: move Settings to the main sidebar menu

* fix: active sidebar item styles

* fix: static company logo for all sidebar states

* fix: minor UI fixes

* fix: active item UI in the sidebar when any page tab is opened (#7662)

* feat: regenerate CGW schema with address book timestamps (#7666)

* feat: redesign Spaces Address Book page with shadcn

- Rewrite address book page with shadcn Tabs, Table, Button, Input
- Two tabs: Workspace contacts and My contacts (space + local)
- Activity log tab showing contact additions/updates timeline
- Client-side pagination (25 per page) with fixed column widths
- Local contacts shown with address book icon in My contacts tab
- Import/Add contact buttons migrated to shadcn (modals remain MUI)
- Search bar filters per active tab, resets on tab switch

Depends on: safe-global/safe-client-gateway#3024

* feat: regenerate CGW schema with address book timestamps and member type renames

- Fetch updated OpenAPI schema from staging CGW that includes createdAt/updatedAt
  fields on SpaceAddressBookItemDto
- Fix createdAt/updatedAt swagger types from object to date-time string
- Update all Member → MemberDto and MemberDto → SpaceMemberDto type renames
  from the CGW schema changes
- Remove manual createdAt/updatedAt extension from AddressBookEntry type
- Add spaceId skip guard to useGetSpaceAddressBook hook
- Update all test mocks to include the new required fields

---------

* feat(web): add actions tray feature with redesigned action buttons (#7650)

* feat(web): redesign actions tray component

* feat(web): add noAssets prop to DashboardHeader for handling asset states

* fix(web): add redux mock on dashboard header component

* feat(web/Spaces): pass Safe address to the onboarding flow when creating a new Space (#7659)

* feat: pass ?safe= query param through onboarding to preselect safe

* test: add tests

* fix: preserve safe query param also when not SIWE authenticated

* fix: UI when not SIWE authenticated after starting Space creation

* fix: Story for the Add To Space modal

* fix: Safe preselection wirt the correct id

* test: add extra tests

* fix: adjust the guard so the query param gets passed tot he onboarding when no Spaces

* fix: preselection for a multichain safe

* refactor: replace hardcoded logic with useSafeQueryParam hook

* docs: add JSDoc to the helper function

* test: adjust tests

* refactor: fix conditionals

* feat: disable Add To Space btn when max N of spaces

* feat: validate safe address before preselection

* refactor: simplify complex pre-selection logic with explicit state

* refactor: replace hardcoded multichain safe prefix with a const

* feat: hide Add To Space when user is not signed in

* fix: adjust UI for sidebar navitems on the Space level

* fix: remove unused conditional for the url in Add To Space modal

* fix: typing and linting

* feat: safe drop down selector (#7652)

* feat: add trusted safe to safe selector dropdown

* feat: add header and footer for trusted safes

* feat: add accounts modal and its logic

* fix: fix unwanted eslint

* fix: fix tests

* feat: optimize space safe bar accounts modal

* fix: fix edege case when safe is not in local storage

* feat: add trusted safe button

* feat: add loading and error component

* feat: add address copy button for selector dropdown

* feat: add loading skeleton for selector drop down

* fix: styling fix when chrome extension is opened

* fix: fix the hover

* feat: add analytics

* fix: add tests

* fix: fix type

* feat: delete old all accounts modal and create the new one

* fix: fix rename z-index issue

* feat: show ens name as fallback

* feat: show ens name for global search window

* feat: update the color of the icon

* fix: unify the background color

* fix: show connect wallet when no pinned safe and wallect not connected

* feat: pin and unpin all networks

* feat: trusted safes and other safes

* feat: add similarity address check

* feat: add navigation

* fix: close dropdown when opening all acounts

* fix: fix transaction flow cut topbar

* fix: add the error handling logic for accounst modal

* feat: show toast when fetching users failing

* chore: move hook tests the sub folder

* fix: change safe logic for better readability

* fix: fix nested safe window position

* chore: update autogenerated types

* feat(web): add SelectSafeModal for space-level actions (#7669)

* feat(web): create selectSafeModal component

* feat(web): enhance NavigateToSection with space route handling and update SelectSafeModal styles

* feat(web): allow user to search by chain prefix

* fix(web): remove unnecessary hook

* fix(web): cover useSafeActionMapper with unit tests

* fix: update schema hash

* fix: fix imports after SDK version update

* feat(web/Spaces): disable actions for geoblocked users, no-assets, and unsupported chains (#7684)

* feat(web): disable safe accounts where swap feature is disabled

* feat(web): block the actions button if user is on a not allowed location

* fix: unit tests button tooltip

* fix(web): typo

* feat(web): create storybooks for actions tray component

* fix(web/Spaces): No flickering in the Safe Selector Dropdown (#7699)

* fix: avoid call duplication on Safe change in Safe Selector Dropdown; add tests

* docs: adjust comments

* chore: linting

* feat: hide SafeBar for account creation and adding (#7691)

* fix(web): block clicks on dimmed Receive and Build transaction buttons in empty Spaces (#7706)

* fix(web): set min 320px width for global search input, shrink on mobile (#7710)

* fix: Update Required warning in Settings UI on the icon instead of NavItem corner (#7716)

* feat(web): update corner radius for Spaces and Safe M2 (WA-2003) (#7709)

* feat(web): update corner radius for Spaces and Safe M2 (WA-2003)

- shadcn Button: 8px → 12px (rounded-sm → rounded-md)
- SafeWidget root: 8px → 24px (rounded-sm → rounded-xl)
- Spaces Dashboard Paper cards: 12px → 24px
- MUI theme: MuiCard + MuiPaper root → 24px, with scoped overrides
  keeping Dialog/Popover/Menu/Autocomplete/Alert at 6px so dropdowns
  and modals don't over-round.

* tests(web): update MUI snapshots for border-radius theme change

Emotion class hashes regenerated after MuiCard/MuiPaper/MuiAlert
styleOverrides were updated in the theme package.

---------

* fix: regression with Tx History -> Queue redirect (#7718)

* feat: create feedback popup (#7683)

* feat: create feedback popup

* fix: add comment

* fix: fix comment

* fix(web): snapshots (#7732)

* fix(web): Adjust accounts page alignement (#7733)

* fix(web): switch SafeCardReadOnly grid activation to 786px

Below 786px the card now stays as a flex row instead of collapsing into a
broken grid. Inner threshold and pending-tx cells also gate on min-[786px]
so they don't leak out as loose flex items between 640–785px. Also drops
a dead sm:grid-cols rule that was immediately overridden.

* fix(web): use right colors on the defualt button variant

* feat(web): move styles to css modules in accounts page

---------

* Revert "fix(web): Adjust accounts page alignement (#7733)" (#7741)

This reverts commit 52cadbec588bbd51beb6a13c12e3c1c5f4eb8331.

* feat(web): expand multi-chain safes into per-chain accordion (#7739)

* feat(web): expand multi-chain safes into per-chain accordion (EPD-473)

- All Accounts modal: turn `MultiSafeItemCard` into a `Collapsible` that
  mirrors `PinnedMultiSafeItem`; clicking a multi-chain row now reveals
  per-chain sub-items with balance, status badges, and pending count.
- `PinnedSafeSubItem`: show an "N pending" badge alongside the per-chain
  balance (used by the modal and pinned multi-safe accordions).
- Safe selector dropdown: new `MultiChainSafeItemRow` with the same
  expand-on-click pattern. Sub-items are `SelectItem`s so picking a
  chain reuses the existing navigation flow. Pending count, Read-only
  and Not-activated/Activating badges are surfaced per chain.
- Enrich `SafeItemDataChain` + `useSpaceSafeSelectorItems` with per-chain
  `queued`, `isReadOnly`, `isUndeployed`, `isActivating` from existing
  overview + undeployed-safes state (no extra HTTP calls).
- Remove dead `getMultiChainNavigationSafe` helper — no longer needed now
  that navigation happens via sub-item selection.

* fix(web): wire elementRef in PinnedSafeSubItem so overview loads

`useSafeItemData` gates `useGetSafeOverviewQuery` on `useOnceVisible(elementRef)`,
so consumers must attach the returned ref to a DOM node. The sub-item ignored it,
which kept the query permanently skipped — `fiatTotal` and `queued` never resolved,
so the per-chain balance and pending badge silently stayed hidden.

Wrap the Link in a ref'd div (same pattern as PinnedSafeItem) and tighten the
accordion padding so the sub-rows align with the header.

---------

* fix(web): border radius on transactions modal (#7746)

* fix(web): Adjust safe apps drawer padding top (#7751)

* fix: tooltip only on hover (#7752)

* chore: regenerate CGW schema and types (#7738)

Pick up latest CGW schema changes: drop unused contractAddresses from
Chain, add safeTxHash to relay endpoint, add createdAt/updatedAt to
SpaceAddressBookItemDto. Update test mocks to match the new shapes.

Also fix the check-sync script under Node 22: swap the ESM import.meta.url
pathname for __dirname and pin ts-node to CommonJS so the auto-generated
sync test stops failing with ERR_UNKNOWN_FILE_EXTENSION.

* chore: linting

* fix(web/Spaces): keep SafeSelector visible for space-level tx flows (#7743)

* fix(web/Spaces): keep SafeSelector visible for space-level tx flows

Swap in the SpaceSafeBar (with the Space's safes) instead of the
global search input when a tx modal is open on a space route, and
open TokenTransferFlow directly from the Actions Tray "Send" action.

* fix(web/Spaces): clear active Safe context when closing a space-level modal

The Send/Receive flows set ?safe= on the space route so the topbar can
show the selected Safe. On close that param is now cleared so the user
returns to a "no Safe selected" state on the Space page.

* fix(web): remove unnecessary information from the transaction modal

* fix(web): send user straight to the send tokens modal

---------

* fix(web): double border problem on notifications popup problem (#7748)

* fix(web): truncate long address book names and clear FAB from pagination (#7744)

- Name cell now truncates with ellipsis inside an overflow-hidden flex row
  (with min-w-0 on the text span so flex actually shrinks it below its
  intrinsic width) and shows the full name in a Tooltip on hover.
- Pagination row gets pr-16 so the chevron buttons sit clear of the help
  FAB's 72px bottom-right footprint on narrow viewports.

* fix: fix batch tx popup is cut by transaction modal (#7750)

* fix: fix bactch tx popup

* fix: eslint

* feat(web/Spaces): Indexing status in the sidebar + loading skeleton (#7757)

* feat: add Synced status to the sidebar

* feat: API popup is closed by default; fix: footer paddings

* refactor: tooltip copy

* feat: add SidebarSkeleton

* chore: linting

* fix: SidebarBurger visible on mid-size screens

* fix(web): elevate topbar while tx and recovery modals are open (#7753)

* fix(web): make the topbar fixed once the transaction modals/recover is opened

* fix(web): elevate topbar while tx and recovery modals are open

Wires the useTopbarElevation store into PageLayout and the two modal
providers so the topbar sits above the modal backdrop with a fixed
position while either flow is active.

* tests(web): cover useTopbarElevation store

Verifies the external store toggles correctly per modal id, resets on
close and unmount, and stays elevated while any modal is still open.

---------

* fix: fix topbar and wrapper position in tx builder (#7763)

* feat(web/Spaces): no Help Center popup (#7768)

* feat: remove unneeded Help Center popup

* fix: Feedback Popup position and border

* chore: remove unneeded test file

* feat: make all accounts button sticky and add scrolling chevron (#7766)

* feat: make all accounts button sticky and add scrolling chevron

* fix: dibale overscroll and hide the checkmark

* fix: fix the chevron icon showing logic

* fix: fix failed tests

* feat: optimize chain selector (#7734)

* feat: optimize chain selector

* feat: add not available tag

* fix: fix claude comments

* fix(web): align safe apps header and SDK widget with new topbar (#7774)

* feat(web/Spaces): Beamer and Contact Support in the Sidebar (#7775)

* feat: add Contact Suppoert with Pylon

* feat: add beamer to the sidebar

* fix(web/Spaces): adjust all e2e tests for the new UI (#7688)

* fix: import-export smoke tests

* fix: add smoke tests for the Safe Selector instead of deprecated Sidebar Accounts

* refactor: rename safe_selector to safe_navigation, update imports

* refactor: migrate sidebar data-testids to new SafeSelectorDropdown, AccountsModal and Sidebar components; update safe_navigation page object

* feat: adjust Address Book regression test

* feat: adjust Import Data regression test

* feat: adjust Multichain Network regression test

* feat: adjust Multichain NetworkSwitch regression test

* feat: adjust Nested Safes Curation regression test

* fix: change the entry point to Nested Safes everywhere in tests

* refactor: move navigation fns to separate files for better separation of concerns; update imports

* chore: skip tests that will be adjusted later when UI is ready

* feat: initial sidebar tests and safe selector tests; mark old sidebar tests `.skip`

* feat: Safe Selector (ex-Sidebar) tests for currency and adding a safe

* feat: Safe Selector (ex-Sidebar) tests for Trusted Safes and Connect Wallet

* feat: Safe Selector (ex-Sidebar) tests for adding Trusted Safes

* feat: new Safe Selector (ex-Sidebar) tests for the Searchbar in Accouts Modal

* feat: new Safe Selector (ex-Sidebar) tests for Import in Accouts Modal + welcome page

* chore: regenerate CGW schema and types (#7738)

Pick up latest CGW schema changes: drop unused contractAddresses from
Chain, add safeTxHash to relay endpoint, add createdAt/updatedAt to
SpaceAddressBookItemDto. Update test mocks to match the new shapes.

Also fix the check-sync script under Node 22: swap the ESM import.meta.url
pathname for __dirname and pin ts-node to CommonJS so the auto-generated
sync test stops failing with ERR_UNKNOWN_FILE_EXTENSION.

* chore: linting

* feat: tests for safes watchlist in dropdown

* feat: rewrite `sidebar_8` tests for the new ui

* feat: rewrite `sidebar_9` tests for the new ui

* feat: adjust tests from `sidebar_nonowners`

* feat: adjust tests for Multichain Setup

* feat: adjust tests for Multichain Safe Selector (ex-Sidebar)

* fix: Safe Selector regression tests

* fix: navigation for multichain safes

* fix: multichain network switch

* fix: Load Safe pages nav with new elements

* fix: multichain safe selector navigation

* fix: Spaces Basic Flow: create space, rename space, delete space -> flow fixes

* fix: Spaces Basic Flow: member invitation

* fix: Spaces Dashboard test assertions

* fix: multiple assertion fixes across Spaces flows

---------

Co-authored-by: katspaugh <[email protected]>

* fix(web): show pending transactions for all multichain accounts on accounts page (#7769)

* fix(web/Spaces): design fixes for accounts widget, batch sidebar and global search (#7767)

* fix(BatchSidebar): remove top margin from aside component

* fix(web): remove accounts widget chevron

* fix(web): adjust global search hover

* fix(web): remove global search right space on accounts and trusted safes section

* fix: fix top bar ui and disable ens name as fallback (#7776)

* fix: fix topbar styling and disable ens name as fallback

* fix: add test

* fix: linting

* fix(web): adjust corner and remove unnecessary information from the recovery modal (#7777)

* fix(web): drive topbar safe selector from URL not Redux (#7779)

* fix(web): drive topbar safe selector from URL not Redux

* fix: add redux address as fallback

* feat(web): disable space chain selector during tx flow (#7781)

* feat(web): disable space chain selector during tx flow

Disables the SpaceChainSelector with cursor-not-allowed and reduced
opacity while a transaction flow modal is open, and shows a tooltip
explaining that switching networks is not allowed in this screen.

* tests(web): cover disabled state of SpaceChainSelector during tx flow

---------

* fix: smoke tests (#7783)

* feat(web/Spaces): new tracking events (#7782)

* feat: add auth tracking events

* feat: tracking for space creation start

* feat: tracking events for Space creation

* feat: membership-related tracking events

* feat: add safe / remove safe tracking events

* feat: space dashboard view and switch space events

* feat: add events for Safe Selection

* feat: add events for Actions Tray, Add Contact, Decline Invite

* feat: add missing tracking events from old components

* chore: typing, linting

* feat: add super properties: workspace_id and auth

* feat: add Onboarding Wizard events

* fix: styles

* test: adjust failing tests

* fix(web/Spaces): UI fixes: Receive Assets, How To Build On Safe, Safe Selector Dropdown  (#7784)

* feat: remove obsolete Apps CTA from the UI; fix: no nested buttons

* fix: hydration in Safe Selector Dropdown

* fix: align safe selector skeletons

* fix: keep safe selector skeleton visible during unresolved hydration state

* feat: display >3 chains in the Safe Selector; chore: move tests to a separate folder

* fix: 0 top border radii in the Spending Limits modal

* fix: tooltip for the Tx Builder

---------

Co-authored-by: Clóvis Neto <[email protected]>
Co-authored-by: Tong Yao <[email protected]>
Co-authored-by: katspaugh <[email protected]>
Co-authored-by: katspaugh <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-04-30 11:40:38 +02:00

750 lines
23 KiB
JavaScript

import * as constants from '../../support/constants.js'
import * as main from './main.page.js'
import * as modal from './modals.page.js'
import * as navigation from './navigation.page.js'
import { safeHeaderInfo } from './import_export.pages.js'
import * as file from './import_export.pages.js'
import safes from '../../fixtures/safes/static.js'
import * as address_book from './address_book.page.js'
import * as create_wallet from '../pages/create_wallet.pages.js'
export const chainLogo = '[data-testid="chain-logo"]'
const safeIcon = '[data-testid="safe-icon"]'
const sidebarContainer = '[data-testid="sidebar-container"]'
const openSafesIcon = '[data-testid="open-safes-icon"]'
const qrModalBtn = '[data-testid="qr-modal-btn"]'
export const copyAddressBtn = '[data-testid="copy-address-btn"]'
const explorerBtn = '[data-testid="explorer-btn"]'
export const sideBarListItem = '[data-testid="sidebar-list-item"]'
const sideBarListItemWhatsNew = '[data-testid="list-item-whats-new"]'
const sideBarListItemNeedHelp = '[data-testid="list-item-need-help"]'
export const sidebarSettingsItem = '[data-testid="sidebar-settings-item"]'
export const sidebarListItem = '[data-testid="sidebar-list-item"]'
export const sideSafeListItem = '[data-testid="safe-list-item"]'
const sidebarSafeHeader = '[data-testid="safe-header-info"]'
const sidebarSafeContainer = '[data-testid="sidebar-safe-container"]'
const safeItemOptionsBtn = '[data-testid="safe-options-btn"]'
export const safeItemOptionsRenameBtn = '[data-testid="rename-btn"]'
export const safeItemOptionsRemoveBtn = '[data-testid="remove-btn"]'
export const safeItemOptionsAddChainBtn = '[data-testid="add-chain-btn"]'
const nameInput = '[data-testid="name-input"]'
const saveBtn = '[data-testid="save-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"]'
const expandSafesList = '[data-testid="expand-safes-list"]'
export const importBtn = '[data-testid="import-btn"]'
export const pendingActivationIcon = '[data-testid="pending-activation-icon"]'
const safeItemMenuIcon = '[data-testid="MoreVertIcon"]'
const multichainItemSummary = '[data-testid="multichain-item-summary"]'
const addChainDialog = "[data-testid='add-chain-dialog']"
export const addNetworkBtn = "[data-testid='add-network-btn']"
const modalAddNetworkBtn = "[data-testid='modal-add-network-btn']"
const subAccountContainer = '[data-testid="subacounts-container"]'
const groupBalance = '[data-testid="group-balance"]'
const groupAddress = '[data-testid="group-address"]'
const groupSafeIcon = '[data-testid="group-safe-icon"]'
const multichainTooltip = '[data-testid="multichain-tooltip"]'
const tooltipTrigger = '[data-slot="tooltip-trigger"]'
const networkInput = '[id="network-input"]'
const networkOption = 'li[role="option"]'
const showAllNetworks = '[data-testid="show-all-networks"]'
const showAllNetworksStr = 'Show all networks'
export const addNetworkOption = 'li[aria-label="Add network"]'
export const addedNetworkOption = 'li[role="option"]'
const modalAddNetworkName = '[data-testid="added-network"]'
const networkSeperator = 'div[role="separator"]'
export const addNetworkTooltip = '[data-testid="add-network-tooltip"]'
const pinnedAccountsContainer = '[data-testid="pinned-accounts"]'
const emptyPinnedList = '[data-testid="empty-pinned-list"]'
const boomarkIcon = '[data-testid="bookmark-icon"]'
const emptyAccountList = '[data-testid="empty-account-list"]'
const searchInput = '[id="search-by-name"]'
const accountsList = '[data-testid="accounts-list"]'
const sortbyBtn = '[data-testid="sortby-button"]'
export const currentSafeSection = '[data-testid="current-safe-section"]'
const readOnlyChip = '[data-testid="read-only-chip"]'
const addSafeBtn = '[data-testid="add-safe-button"]'
const indexStatusSection = '[data-testid="index-status"]'
const needHelpBtn = '[data-testid="list-item-need-help"]'
const openNestedSafeListBtn = '[data-track="nested-safes: Open nested Safe list"]'
const nestedSafeListPopover = '[data-testid="nested-safe-list"]'
const breadcrumpContainer = '[data-testid="safe-breadcrumb-container"]'
const parentSafeItem = 'div[aria-label="Parent Safe"]'
const nestedSafeItem = 'div[aria-label="Nested Safe"]'
const safeIconItem = '[data-testid="safe-icon"]'
export function clickOnOpenNestedSafeListBtn() {
cy.get(openNestedSafeListBtn).click()
}
export function checkSafesInPopverList(safes) {
main.verifyValuesExist(nestedSafeListPopover, safes)
}
export function checkSafesCountInPopverList(number) {
main.verifyElementsCount(nestedSafeListPopover, number)
}
export function clickOnSafeInPopover(safe) {
cy.get(nestedSafeListPopover).within(() => {
cy.contains(safe).click()
})
}
export function clickOnParentSafeInBreadcrumb() {
cy.wait(1000) // Needs time to render
cy.get(breadcrumpContainer).within(() => {
cy.get(parentSafeItem).within(() => {
cy.get('a').click()
})
})
}
export function checkParentSafeInBreadcrumb(name, address) {
cy.get(breadcrumpContainer).within(() => {
cy.get(parentSafeItem).within(() => {
cy.get(`a[href*="${address}"]`).should('contain', name)
})
})
}
export function checkNestedSafeInBreadcrumb(name) {
cy.get(breadcrumpContainer).within(() => {
cy.get(nestedSafeItem).within(() => {
cy.get('p').should('contain', name)
})
})
}
export const importBtnStr = 'Import'
export const exportBtnStr = 'Export'
export const undeployedSafe = 'Undeployed Sepolia'
export const notActivatedStr = 'Not activated'
export const addingNetworkNotPossibleStr = 'Adding another network is not possible for this Safe.'
export const createSafeMsg = (network) => `Successfully added your account on ${network}`
const signersNotConsistentConfirmTxViewMsg = (network) =>
`Signers are not consistent across networks on this account. Changing signers will only affect the account on ${network}`
const activateStr = 'You need to activate your Safe first'
const emptyPinnedMessage = 'Personalize your account list by clicking theicon on the accounts most important to you.'
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', 'Swap']
export const sideBarListItemsNew = ['Overview', 'Assets', 'Transactions', 'Address book', 'Apps']
export const sideBarSafes = {
safe1: '0xBb26E3717172d5000F87DeFd391994f789D80aEB',
safe2: '0x905934aA8758c06B2422F0C90D97d2fbb6677811',
safe3: '0xC96ee38f5A73C8A70b565CB8EA938D2aF913ee3B',
safe1short: '0xBb26...0aEB',
safe1short_: '0xBb26',
safe2short: '0x9059...7811',
safe3short: '0x86Cb...2C27',
safe4short: '0x9261...7E00',
multichain_short_: '0xC96e',
}
// 0x926186108f74dB20BFeb2b6c888E523C78cb7E00
export const sideBarSafesPendingActions = {
safe1: '0x5912f6616c84024cD1aff0D5b55bb36F5180fFdb',
safe1short: '0x5912...fFdb',
}
export const testSafeHeaderDetails = ['2/2', safes.SEP_STATIC_SAFE_9_SHORT]
const receiveAssetsStr = 'Receive assets'
const emptyPinnedListStr = 'Watch any Safe Account to keep an eye on its activity'
const emptySafeListStr = "You don't have any safes yet"
const accountsRegex = /(My accounts|Accounts) \((\d+)\)/
const confirmTxStr = (number) => `${number} to confirm`
const pedningTxStr = (n) => `${n} pending`
export const confirmGenStr = 'to confirm'
const searchResults = (number) => `Found ${number} result${number === 1 ? '' : 's'}`
const needHelpLink = 'https://help.safe.global'
export const sortOptions = {
lastVisited: '[data-testid="last-visited-option"]',
name: '[data-testid="name-option"]',
}
export function whatsNewBtnIsVisible() {
cy.get(sideBarListItemWhatsNew).should('be.visible')
}
export function checkSearchResults(number) {
cy.contains(searchResults(number)).should('exist')
}
export function checkNeedHelpBtnLink() {
cy.get(needHelpBtn).should('have.attr', 'href', needHelpLink)
}
export const multichainSafes = {
polygon: 'Multichain polygon',
sepolia: 'Multichain Sepolia',
}
export function searchSafe(safe) {
cy.get(searchInput).clear().type(safe, { force: true })
}
export function openSortOptionsMenu() {
cy.get(sortbyBtn).click()
}
export function selectSortOption(option) {
cy.get(option).click()
}
export function clearSearchInput() {
cy.get(searchInput).scrollIntoView().clear({ force: true })
}
export function verifySearchInputPosition() {
cy.get(searchInput).then(($searchInput) => {
cy.get(pinnedAccountsContainer).then(($pinnedList) => {
const searchInputPosition = $searchInput[0].compareDocumentPosition($pinnedList[0])
expect(searchInputPosition & Node.DOCUMENT_POSITION_FOLLOWING).to.equal(Node.DOCUMENT_POSITION_FOLLOWING)
})
})
}
export function verifyNumberOfPendingTxTag(tx) {
cy.get(pinnedAccountsContainer).within(() => {
cy.get('span').contains(pedningTxStr(tx))
})
}
export function verifyPinnedSafe(safe) {
cy.get(pinnedAccountsContainer).within(() => {
cy.get(sideSafeListItem).contains(safe)
})
}
export function verifyCurrentSafe(safe) {
cy.get(currentSafeSection).within(() => {
cy.get(sideSafeListItem).contains(safe)
})
}
export function verifyCurrentSafeReadOnly(number) {
cy.get(currentSafeSection).within(() => {
cy.get(readOnlyChip).should('have.length', number)
})
}
export function verifyIndexStatusPresent() {
cy.get(indexStatusSection).should('have.attr', 'href', constants.indexStatusUrl)
}
export function clickOnAddSafeBtn() {
cy.get(addSafeBtn).click()
cy.url().should('include', constants.loadNewSafeUrl)
}
export function verifyCurrentSafeDoesNotExist() {
cy.get(currentSafeSection).should('not.exist')
}
export function getImportBtn() {
return cy.get(importBtn).scrollIntoView().should('be.visible')
}
export function clickOnSidebarImportBtn() {
getImportBtn().click()
modal.verifyModalTitle(modal.modalTitiles.dataImport)
file.verifyValidImportInputExists()
}
export function clickOnCopyAddressBtn(expectedData) {
cy.window().then((win) => {
cy.stub(win.navigator.clipboard, 'writeText').as('clipboardWrite')
})
cy.get(copyAddressBtn).click()
cy.get('@clipboardWrite', { timeout: 10000 }).should('have.been.called')
cy.get('@clipboardWrite').then((stub) => {
const actualCallArgs = stub.args[0][0]
expect(actualCallArgs).to.include(expectedData)
})
}
export function showAllSafes() {
cy.wait(500)
cy.get('body').then(($body) => {
if ($body.find(expandSafesList).length > 0) {
cy.get(expandSafesList).click()
cy.wait(500)
}
})
}
export function verifyAccountsCollapsed() {
cy.get(expandSafesList).should('have.attr', 'aria-expanded', 'false')
}
export function verifyConnectBtnDisplayed() {
cy.get(emptyAccountList).within(() => {
create_wallet.verifyConnectWalletBtnDisplayed()
})
}
export function verifyNetworkIsDisplayed(netwrok) {
cy.get(sidebarContainer)
.should('be.visible')
.within(() => {
cy.get(chainLogo).should('contain', netwrok)
})
}
export function verifySafeHeaderDetails(details) {
main.checkTextsExistWithinElement(safeHeaderInfo, details)
main.verifyElementsExist([safeIcon, currencySection])
}
export function clickOnQRCodeBtn() {
cy.get(sidebarContainer).within(() => {
cy.get(qrModalBtn).should('have.length', 1).click()
})
}
export function verifyQRModalDisplayed() {
cy.get(modal.modal).should('be.visible')
cy.get(modal.modalTitle).should('contain', receiveAssetsStr)
}
export function verifyCopyAddressBtn(data) {
cy.wait(1000)
cy.get(sidebarContainer)
.should('be.visible')
.within(() => {
cy.get(copyAddressBtn)
.click()
.wait(1000)
.then(() =>
cy.window().then((win) => {
win.navigator.clipboard.readText().then((text) => {
expect(text).to.contain(data)
})
}),
)
})
}
export function verifyEtherscanLinkExists() {
main.verifyMinimumElementsCount(explorerBtn, 1)
cy.get(sidebarContainer)
.should('be.visible')
.within(() => {
cy.get(explorerBtn).should('have.attr', 'href').and('include', constants.etherscanlLink)
})
}
export function verifyNewTxBtnStatus(status) {
main.verifyElementsStatus([navigation.newTxBtn], status)
}
export function verifySideListItems() {
main.verifyValuesExist(sideBarListItem, sideBarListItems)
main.verifyElementsExist([sideBarListItemWhatsNew, sideBarListItemNeedHelp])
}
export function verifyTxCounter(counter) {
cy.get(sideBarListItem).contains(sideBarListItems[2]).should('contain', counter)
}
export function verifySideListItemsNew() {
main.verifyValuesExist(sideBarListItem, sideBarListItemsNew)
main.verifyElementsExist([sidebarSettingsItem, sideBarListItemNeedHelp])
}
export function verifyTxCounterNew(counter) {
cy.get('[data-testid="queued-tx-info"]').should('be.visible').and('contain.text', String(counter))
}
export function verifyNavItemDisabled(item) {
cy.get(`div[aria-label*="${activateStr}"]`).contains(item).should('exist')
}
export function verifySafeCount(count) {
main.verifyMinimumElementsCount(sideSafeListItem, count)
}
export function verifyAccountListSafeCount(count) {
cy.get(accountsList).within(() => {
cy.get(sideSafeListItem).should('have.length', count)
})
}
export function verifyAccountListSafeData(data) {
cy.get(accountsList).within(() => {
main.verifyValuesExist(sideSafeListItem, [data])
})
}
export function clickOnOpenSidebarBtn() {
cy.get(openSafesIcon).click()
}
// Expands all safes in the sidebar
export function openSidebar() {
clickOnOpenSidebarBtn()
cy.wait(500)
showAllSafes()
main.verifyElementsExist([sidebarSafeContainer])
}
export function verifyAddedSafesExist(safes) {
main.verifyValuesExist(sideSafeListItem, safes)
}
export function verifySafesDoNotExist(safes) {
main.verifyValuesDoNotExist(sidebarSafeContainer, safes)
}
export function verifyAddedSafesExistByIndex(index, safe) {
cy.get(sideSafeListItem).eq(index).should('contain', safe)
}
export function verifySafesByNetwork(netwrok, safes) {
cy.get(sidebarSafeContainer).within(() => {
cy.get(chainLogo)
.contains(netwrok)
.parent()
.next()
.within(() => {
main.verifyValuesExist(sideSafeListItem, safes)
})
})
}
function getSafeByName(safe) {
return cy.get(sidebarSafeContainer).find(sideSafeListItem).contains(safe).parents('span').parent().should('exist')
}
function getSafeItemOptions(name) {
return getSafeByName(name).within(() => {
cy.get(safeItemOptionsBtn)
})
}
export function verifySafeReadOnlyState(safe) {
getSafeItemOptions(safe).find(readOnlyVisibility).should('exist')
}
export function verifyMissingSignature(safe) {
getSafeItemOptions(safe).find(missingSignatureInfo).should('exist')
}
export function verifyQueuedTx(safe) {
return getSafeItemOptions(safe).find(queuedTxInfo).should('exist')
}
export function verifySafeIconData(safe) {
return getSafeByName(safe).find(safeIconItem).should('be.visible')
}
export function clickOnSafeItemOptionsBtn(name) {
getSafeItemOptions(name).find(safeItemOptionsBtn).click()
}
export function clickOnSafeItemOptionsBtnByIndex(index) {
cy.get(safeItemOptionsBtn).eq(index).click()
}
export function expandGroupSafes(index) {
cy.get(multichainItemSummary).eq(index).click()
}
export function clickOnMultichainItemOptionsBtn(index) {
cy.get(multichainItemSummary).eq(index).find(safeItemOptionsBtn).click()
}
export function checkMultichainTooltipExists(index) {
cy.get(multichainItemSummary).eq(index).find(tooltipTrigger).eq(0).focus()
cy.get(multichainTooltip).should('exist')
}
export function checkSafeGroupBalance(index) {
cy.get(multichainItemSummary)
.eq(index)
.find(groupBalance)
.invoke('text')
.should('include', '$')
.and('match', /\$?\s?\d+(\.\d{1,3})?/)
}
export function checkSafeGroupAddress(index, address) {
cy.get(multichainItemSummary)
.eq(index)
.find(groupAddress)
.invoke('text')
.then((text) => {
expect(text).to.include(address)
})
}
export function checkSafeGroupIconsExist(index, icons) {
cy.get(multichainItemSummary).eq(index).find(groupSafeIcon).should('have.length', 1)
cy.get(multichainItemSummary).eq(index).find(safeIcon).should('have.length', icons)
}
export function getSubAccountContainer(index) {
return cy.get(subAccountContainer).eq(index)
}
export function checkThereIsNoOptionsMenu(index) {
getSubAccountContainer(index).find(safeItemOptionsBtn).should('not.exist')
}
export function checkUndeployedSafeExists(index) {
return getSubAccountContainer(index).contains(notActivatedStr).should('exist')
}
export function checkMultichainSubSafeExists(safes) {
main.verifyValuesExist(subAccountContainer, safes)
}
export function checkAddNetworkBtnPosition(index) {
cy.get(multichainItemSummary)
.eq(index)
.should('exist')
.within(() => {
cy.get(addNetworkBtn)
.should('exist')
.should('be.visible')
.then(($btn) => {
expect($btn.parent().children().last()[0]).to.equal($btn[0])
})
})
}
export function clickOnAddNetworkBtn() {
cy.get(addNetworkBtn).eq(0).click()
cy.get(addChainDialog).should('be.visible')
}
export function getModalAddNetworkBtn() {
return cy.get(modalAddNetworkBtn)
}
export function clickOnNetworkInput() {
cy.get(networkInput).click()
}
export function getNetworkOptions() {
return cy.get(networkOption)
}
export function addNetwork(network) {
clickOnAddNetworkBtn()
clickOnNetworkInput()
getNetworkOptions().contains(network).click()
getModalAddNetworkBtn().click()
}
export function renameSafeItem(oldName, newName) {
clickOnSafeItemOptionsBtn(oldName)
clickOnRenameBtn()
typeSafeName(newName)
}
export function removeSafeItem(name) {
clickOnSafeItemOptionsBtn(name)
cy.wait(1000)
clickOnRemoveBtn()
confirmSafeItemRemoval()
verifyModalRemoved()
}
function typeSafeName(name) {
cy.get(nameInput).find('input').clear().type(name)
}
export function clickOnRenameBtn() {
cy.get(safeItemOptionsRenameBtn).click()
cy.get(address_book.entryDialog).should('exist')
}
function clickOnRemoveBtn() {
cy.get(safeItemOptionsRemoveBtn).click()
}
function confirmSafeItemRemoval() {
cy.get(deleteBtn).click()
}
export function verifySafeNameExists(name) {
cy.get(sidebarSafeContainer).within(() => {
cy.get(sideSafeListItem).contains(name)
})
}
export function verifySafeRemoved(name) {
main.verifyValuesDoNotExist(sidebarSafeContainer, [name])
}
export function clickOnSaveBtn() {
cy.get(saveBtn).click()
verifyModalRemoved()
}
function verifyModalRemoved() {
main.verifyElementsCount(modal.modalTitle, 0)
}
export function checkCurrencyInHeader(currency) {
cy.get(sidebarSafeHeader).within(() => {
cy.get(currencySection).contains(currency)
})
}
export function checkSafeAddressInHeader(address) {
main.verifyValuesExist(sidebarSafeHeader, address)
}
export function verifyPinnedListIsEmpty() {
cy.get(emptyPinnedList).should('contain.text', emptyPinnedMessage).find('svg').should('exist')
}
export function verifySafeListIsEmpty() {
main.verifyValuesExist(sidebarSafeContainer, [emptySafeListStr])
}
export function verifySafeBookmarkBtnExists(safe) {
getSafeByName(safe).within(() => {
cy.get(boomarkIcon).should('exist')
})
}
export function clickOnBookmarkBtn(safe) {
getSafeByName(safe).within(() => {
cy.get(boomarkIcon).click()
cy.wait(500)
})
}
export function verifySafeGiveNameOptionExists(index) {
cy.get(safeItemMenuIcon).eq(index).click()
clickOnRenameBtn()
}
export function checkAccountsCounter(value) {
verifySafeCount(2)
cy.get(sidebarSafeContainer)
.should('exist')
.then(($el) => {
const text = $el.text()
const match = text.match(accountsRegex)
expect(match).not.to.be.null
expect(match[0]).to.exist
})
}
export function checkTxToConfirm(numberOfTx) {
const str = confirmTxStr(numberOfTx)
main.verifyValuesExist(sideSafeListItem, [str])
}
export function verifyTxToConfirmDoesNotExist() {
main.verifyValuesDoNotExist(sideSafeListItem, [confirmGenStr])
}
export function checkBalanceExists() {
const balance = new RegExp(`\\s*\\d*\\.?\\d*\\s*`, 'i')
cy.get(chainLogo).next().contains(balance).should('exist')
}
export function clickOnAddOptionsBtn() {
cy.get(safeItemOptionsAddChainBtn).click()
}
export function checkAddChainDialogDisplayed() {
clickOnAddOptionsBtn()
cy.get(addChainDialog).should('be.visible')
}
export function clickOnShowAllNetworksBtn() {
cy.get(showAllNetworks).click()
}
// TODO: Remove after next release due to data-testid availability
export function clickOnShowAllNetworksStrBtn() {
cy.contains(showAllNetworksStr).click()
}
export function checkNetworkPresence(networks, optionSelector) {
return cy.get(optionSelector).then((options) => {
const optionTexts = [...options].map((option) => option.innerText)
networks.forEach((network) => {
const isNetworkPresent = optionTexts.some((text) => text.includes(network))
expect(isNetworkPresent).to.be.true
})
cy.wrap([...options].filter((option) => networks.some((network) => option.innerText.includes(network))))
})
}
export function checkNetworkIsNotEditable() {
cy.get(addChainDialog).within(() => {
cy.get(modalAddNetworkName).should('exist')
})
cy.get(addChainDialog).find(networkInput).should('not.exist')
}
export function checkNetworksInRange(expectedString, expectedCount, direction = 'below') {
const networkSeparator = networkSeperator
const startSelector = networkSeparator
const endSelector = direction === 'below' ? showAllNetworks : 'ul'
const traversalMethod = direction === 'below' ? 'nextUntil' : 'prevUntil'
return cy
.get(startSelector)
[traversalMethod](endSelector, 'li')
.then((liElements) => {
expect(liElements.length).to.equal(expectedCount)
const optionTexts = [...liElements].map((li) => li.innerText)
const isStringPresent = optionTexts.some((text) => text.includes(expectedString))
expect(isStringPresent).to.be.true
return cy.wrap(liElements)
})
}
export function checkInconsistentSignersMsgDisplayedConfirmTxView(network) {
cy.contains(signersNotConsistentConfirmTxViewMsg(network)).should('exist')
}
function getNetworkElements() {
return cy.get('span[data-track="overview: Add new network"] > li')
}
export function checkNetworkDisabled(networks) {
getNetworkElements().should('have.length.gte', 20)
getNetworkElements().each(($el) => {
const text = $el[0].innerText.trim()
console.log(`Element text: ${text}`)
const isDisabledNetwork = networks.some((network) => text.includes(network))
if (isDisabledNetwork) {
expect($el).to.have.attr('aria-disabled', 'true')
} else {
expect($el).not.to.have.attr('aria-disabled')
}
})
}
export function verifySidebarContainerVisible() {
cy.get(sidebarContainer).should('be.visible')
}
export function verifySidebarSettingsItemVisible() {
cy.get(sidebarSettingsItem).should('be.visible')
}
export function verifySidebarListItem() {
cy.get(sidebarListItem).should('be.visible')
}
export function verifyQueuedTxInfoCount(count) {
cy.get(queuedTxInfo).should('be.visible').and('contain.text', String(count))
}
export function verifyListItemNeedHelp() {
cy.get(sideBarListItemNeedHelp).should('be.visible')
}