Skip to content

feat(e2e): test selectors and e2e mocks - #8036

Merged
shoom3301 merged 211 commits into
developfrom
e2e-tests/p2
Aug 27, 2026
Merged

feat(e2e): test selectors and e2e mocks#8036
shoom3301 merged 211 commits into
developfrom
e2e-tests/p2

Conversation

@shoom3301

@shoom3301 shoom3301 commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

What changed

  • Adds a window.__COWSWAP_E2E__ runtime flag: under it, GTM analytics and the LaunchDarkly provider are swapped for no-ops, feature flags are read from window.__COWSWAP_E2E_FEATURE_FLAGS__ instead, and several update-interval consts (order-book polling, allowances, priority tokens, limit/expired-order checks) are compressed to ~2s via a new getUpdaterInterval() helper in common-const.
  • Bypasses the NEAR Intents deposit-address signature check only when NODE_ENV !== 'production' and the e2e flag is set — dead code in every deployed build (prod, staging, and Vercel previews all run the production webpack build), needed because e2e's mocked NEAR quote/attestation fixtures can't carry a real signature.
  • Adds stable locator hooks (data-testid/id/className) to several components so tests don't rely on style-derived selectors: CurrencyInputPanel fiat amount, PriceImpactIndicator, TradeDetailsAccordion, EthFlowStepper/Step, OrdersTable, AccountDetails activity list, approve-mode Toggle, CollapsibleBridgeRoute, SnackbarPopup, ConfirmDetailsItem/ReviewOrderModalAmountRow, TradeFormBlankButton.
  • Fixes 3 real bugs surfaced while building the e2e suite:
    • HydrateAtom wrote to its atom during render, which could update an already-mounted sibling mid-render (React warns "Cannot update a component while rendering a different component") and the write could be silently dropped — observed as the sell token intermittently reverting to "Select a token". Moved the write into useLayoutEffect.
    • OrdersFromApiUpdater and PendingOrdersUpdater both poll and write order status independently. If OrdersFromApiUpdater wrote FULFILLED first, the order left PendingOrdersUpdater's locally-tracked pending bucket before it could detect the transition, and the "Transaction completed" surplus modal never appeared. OrdersFromApiUpdater now detects the pending→fulfilled transition itself and queues the modal.
    • useSetupTradeAmountsFromUrl's "has an amount ever been set" ref was overwritten every render instead of staying sticky, so switching the buy/sell currency (which transiently reads the amount back as null for one render) could stomp a real typed sell amount with the "1 unit" default — the flaky enterSellAmount('1000') behavior tracked as CS-59.
  • getTokenFromMapping now normalizes addresses with getAddressKey from @cowprotocol/cow-sdk instead of viem's getAddress, per the repo's address-handling convention.

Why

  • The e2e suite (scaffolded in PR #8035, exercised in #8038/#8039) needs stable selectors, fast polling against mocked endpoints, and a way to disable third-party SDKs (GTM/LaunchDarkly) that have no test-safe configuration — building it surfaced the three state-timing bugs above.

QA Testing

Preview URL QA:

  • Sell-amount stability across a currency switch (CS-59 regression): on the swap-dev preview, type a sell amount, then change the sell or buy token — the typed amount should be preserved, not reset to the "1 unit" default.

Developer verification:

  • smoke CI check passed on this PR.
  • Targeted unit tests cover the surplus-modal pending→fulfilled transition (OrdersFromApiUpdater.test.ts) and the URL-driven amount defaulting (useSetupTradeAmountsFromUrl.test.ts, useNavigateOnCurrencySelection.test.tsx).

Reviewer note:

  • The OrdersFromApiUpdater/PendingOrdersUpdater race only manifests when polling is fast relative to order fill time (production's default 30s order-book poll vs. e2e's compressed ~2s), so it isn't reliably reproducible through a preview URL — coverage relies on the added unit test.
  • The window.__COWSWAP_E2E__ branches and the NEAR Intents signature bypass are inert in any real deployment (guarded by NODE_ENV !== 'production', which every deployed build fails) — worth confirming that guard specifically since it's the one security-relevant change in this PR.

Preview URLs

Surface URL
swap-dev - branch preview URL https://swap-dev-git-e2e-tests-p2-cowswap-dev.vercel.app
explorer-dev - branch preview URL https://explorer-dev-git-e2e-tests-p2-cowswap-dev.vercel.app
widget-configurator - branch preview URL https://widget-configurator-git-e2e-tests-p2-cowswap-dev.vercel.app
storybook - branch preview URL https://storybook-git-e2e-tests-p2-cowswap-dev.vercel.app
cowfi - branch preview URL https://cowfi-git-e2e-tests-p2-cowswap.vercel.app

shoom3301 and others added 30 commits August 5, 2026 14:12
@vercel

vercel Bot commented Aug 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cowfi Ready Ready Preview Aug 27, 2026 10:04am
explorer-dev Ready Ready Preview Aug 27, 2026 10:04am
storybook Ready Ready Preview Aug 27, 2026 10:04am
swap-dev Ready Ready Preview Aug 27, 2026 10:04am
widget-configurator Ready Ready Preview Aug 27, 2026 10:04am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
cosmos Ignored Ignored Aug 27, 2026 10:04am
sdk-tools Ignored Ignored Preview Aug 27, 2026 10:04am

Request Review

@coderabbitai

coderabbitai Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 137925ed-1cad-4f09-8329-6c5048e37c3b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@shoom3301 shoom3301 changed the title E2e tests/p2 feat(e2e): test selectors and e2e mocks Aug 25, 2026
@shoom3301 shoom3301 self-assigned this Aug 25, 2026
@shoom3301
shoom3301 requested review from a team August 25, 2026 11:01
@shoom3301
shoom3301 marked this pull request as ready for review August 25, 2026 11:02
// `useLayoutEffect` still runs synchronously before the browser paints (no visible flicker,
// unlike a plain `useEffect`), but as a commit-phase effect rather than a render-phase one, it's
// safe to update other components from.
useLayoutEffect(() => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏👏


return (
<Wrapper className={className}>
<Wrapper className={'collapsible-bridge-route' + (className ?? '')}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing a space here. In any case, I've added clsx recently, so you can use that here: clsx(className, 'collapsible-bridge-route')

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: 3d42b14

const isSigning = tab.id === 'signing'
return (
<styledEl.TabButton
className="orders-table_tab"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mixed spacing.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed 3d42b14

const searchParams = new URLSearchParams(location.search)
const targetChainId = searchParams.get('targetChainId')
const recipient = searchParams.get('recipient')
/**

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not on this stack of PRs, but you might want to take a look at apps/cowswap-frontend/src/entities/routes/routes.atom.ts later.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I really forgot about it!

ref={ref}
id={id}
className={className}
className={'trade-form-blank-button ' + className}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not missing a space here, but you could also use clsx.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: 3d42b14

@Danziger Danziger left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor comments, but approving already.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Browser QA passed: sell-amount stability across currency switch, and the NEAR Intents bypass is confirmed absent from the shipped bundle

Outcome

  • ✅ Passed: sell amount survives currency switching (CS-59) — typed "123" as sell amount, then switched the buy token 5 times in rapid succession (DAI → USDT → WBTC → COW → USDC, ~300ms apart, no settle time between switches). Sell amount stayed 123 and the sell token stayed WETH on every switch — never reset to the "1 unit" default and never reverted to "Select a token".
  • ✅ Passed (primary security-relevant claim): the NEAR Intents signature-bypass branch is not just inert but entirely absent from the production bundle. Downloaded and grepped all JS chunks the preview loads: recoverDepositAddress appears exactly twice (the SDK's real method definition + its one legitimate call site) — zero assignment-style overrides. The literal string NODE_ENV appears exactly once in the whole bundle, and it's unrelated Vite env metadata, not the process.env.NODE_ENV !== 'production' guard. Terser fully dead-code-eliminated the bypass block, matching the PR's own claim.
  • Not checked: the OrdersFromApiUpdater/PendingOrdersUpdater pending→fulfilled race — per the PR's own reviewer note this only manifests under fast polling relative to fill time and isn't reliably reproducible through a preview URL; relying on the added unit test as the PR states.
  • Minor observation, non-blocking: the GTM/LaunchDarkly no-op swap (window.__COWSWAP_E2E__) has no NODE_ENV guard, unlike the NEAR bypass. Not a security issue — both reads happen at module-load time before any page script could set the flag, so it's not externally triggerable — just noting the asymmetry with the NEAR check in case it's not intentional.

Run details

  • Source: PR head 7902def via the PR-linked preview swap-dev-git-e2e-tests-p2-cowswap-dev.vercel.app (base branch e2e-tests/p1, stacked PR)
  • Environment: Linux; Chromium 151.0.7922.34 (Playwright), locale forced to en-US
  • Wallet: disconnected (both checked claims are wallet-independent)
  • AI assistance: Claude orchestrated Playwright browser execution, downloaded and grepped the deployed JS bundles for the bundle-inspection check, and drafted this note from the observed results.

How to retest

  • Open the preview → Swap → enter a sell amount → switch the buy token a few times quickly → confirm the sell amount and sell token don't reset.
  • For the NEAR guard: curl the app's static/index-*.js chunk(s) and confirm no .recoverDepositAddress= assignment and no live NODE_ENV !== 'production' branch survive minification.

Not checked / follow-up

  • ⚠️ OrdersFromApiUpdater/PendingOrdersUpdater race (surplus-modal timing) — not browser-reproducible per the PR's own note; covered by unit test only.
  • ⚠️ getTokenFromMapping address-normalization change — exercised incidentally by normal token selection above (WETH/USDC/DAI/USDT/WBTC/COW all resolved correctly) but not independently stress-tested.
Commands + setup
  • Preview: https://swap-dev-git-e2e-tests-p2-cowswap-dev.vercel.app
  • Playwright context: locale: 'en-US' (sandbox host locale is POSIX, which otherwise breaks the app's Intl calls — unrelated to the PR)
  • Bundle check: collected all same-origin .js response URLs loaded on initial page load, downloaded each, grep'd for COWSWAP_E2E, recoverDepositAddress, the hardcoded attestor address, and NODE_ENV

Generated using the pr-qa skill.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review (Claude Sonnet 5, worked 9m)

Review completed. I found no new non-duplicate comments worth posting.

I went deep on the four risk areas this PR touches (render-phase-write races, the OrdersFromApiUpdater/PendingOrdersUpdater surplus-modal interaction, the address-key normalization change, and the NEAR Intents bypass guard) rather than stopping at "looks reasonable":

  • HydrateAtom.tsx, useSetupTradeStateFromUrl.ts: both move a render-phase atom write to useLayoutEffect. Checked the one call site of useSetupTradeStateFromUrl's new return value (useSetupTradeState.ts) — correctly consumes it, no other callers.
  • OrdersFromApiUpdater.ts: _getOrdersToQueueForSurplusModal's UiOrderType.SWAP && !getIsBridgeOrder(order) filter matches PendingOrdersUpdater's existing eligibility check exactly (same file, line ~450-452). Traced whether both updaters could double-push the same order into surplusModalAtom (its push() has no dedup) — PendingOrdersUpdater's pendingRef.current is resynced from store state every render, so it drops an order from its own pending bucket before it could re-fire once this updater has already marked it fulfilled; and even in the worst case, removeSurplusOrderAtom filters out all matching IDs on dismiss, not just one. No user-visible double-modal.
  • getTokenFromMapping.ts: swapping viem's getAddress (checksum) for getAddressKey (lowercase) looked like a possible regression at first glance if the token maps were checksum-keyed. Checked — they aren't: useTokensByAddressMapForChain.ts and useTokensForOrdersList.ts already key every TokensByAddress map with getAddressKey, so the old checksum-first lookup was actually the mismatched one (it only worked via the tokens[address] fallback, and only when callers happened to pass an already-lowercased address). This change makes the lookup consistent with how the maps are actually built, and as a side effect it also drops a latent crash: getAddress() throws on a malformed address, getAddressKey() doesn't.
  • bridgingSdk.ts NEAR Intents bypass: confirmed by downloading and grepping the deployed preview's JS bundles (see the QA pass on this same PR) — the NODE_ENV !== 'production' branch is fully dead-code-eliminated in the production build, not just runtime-inert.
Review scope and related context
  • Existing thread on CollapsibleBridgeRoute/index.tsx:42, OrdersTabs.pure.tsx:60, and TradeFormBlankButton/index.tsx:115 (all reviewer nitpicks about spacing/clsx) — not repeated here; PR is already approved and these are non-blocking style notes, not correctness issues.
  • useNavigateOnCurrencySelection.ts's sticky-ref fix for CS-104 (sell token reverting to "Select a token" after picking the buy token) — same pattern as the two useLayoutEffect fixes above. Checked the asymmetry between inputCurrencyId (always reads the ref) and outputCurrencyId (prefers fresh state, falls back to the ref) — both converge to the same value in the non-race case since the ref is kept in sync every render; not a bug, just a stylistic inconsistency not worth a nitpick on its own.
  • The interval-wrapping changes (getUpdaterInterval in allowancesAtom.ts, PriorityTokensUpdater.tsx, useTokenAllowance.ts, legacy/state/orders/consts.ts) are mechanical and low-risk.
  • window.__COWSWAP_E2E__/__COWSWAP_E2E_FEATURE_FLAGS__ gate GTM/LaunchDarkly/feature-flags without the NODE_ENV guard the NEAR bypass has — already flagged as a non-blocking observation in the QA pass on this PR (module-load-time read only, not settable by an external page script, so not exploitable).

Generated using the pr-review skill from the CoW Protocol skills repo.

Base automatically changed from e2e-tests/p1 to develop August 27, 2026 10:05
@shoom3301
shoom3301 merged commit 6ade8bb into develop Aug 27, 2026
17 checks passed
@shoom3301
shoom3301 deleted the e2e-tests/p2 branch August 27, 2026 10:05
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 27, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants