Skip to content

refactor(e2e): cross-network test coverage + scaffolding#366

Merged
AugustoL merged 17 commits into
openscan-explorer:devfrom
AugustoL:refactor/e2e-tests
Apr 28, 2026
Merged

refactor(e2e): cross-network test coverage + scaffolding#366
AugustoL merged 17 commits into
openscan-explorer:devfrom
AugustoL:refactor/e2e-tests

Conversation

@AugustoL

Copy link
Copy Markdown
Collaborator

Description

End-to-end test suite refactor addressing the gaps identified in the earlier e2e coverage review. Adds cross-network shared specs, L2 adapter-field assertions, coverage for previously orphaned production networks (Avalanche, Solana) and the 6 EVM testnets, plus scaffolding (typed network fixture table, hermetic mocked Playwright project, assertion helpers) so future specs are additions rather than copy-paste. Structured as 7 phases, each with its own implementation commit and a self-review commit. Ends with a live-RPC run of the full suite on a local dev server (350/361 pass) and fixes for every failure surfaced.

Zero app code changed.

Related Issue

Internal e2e coverage review.

Type of Change

  • Bug fix
  • New feature
  • Documentation update
  • Refactoring
  • Performance improvement
  • Other (please describe):

Changes Made

Phase 0 — Scaffolding (6294053, 962a266)

  • e2e/fixtures/networks.ts — typed fixture table for every production + testnet network with chainId, slug, family, urlPath, and canonical block/tx/address/token. Drives cross-network iteration.
  • e2e/fixtures/localStorage.tsclearAppState, setLanguage, setTheme, setRpcStrategy, setUserSetting, readUserSetting. Keys verified against SettingsContext.tsx / configExportImport.ts.
  • e2e/fixtures/rpcMock.tspage.route helpers for JSON-RPC stubs, HTTP errors, 429-then-success, offline.
  • e2e/fixtures/assertionsL2.ts + assertions.ts — reusable L2 field assertions and expectStillMounted smoke helper.
  • e2e/fixtures/test.ts — bounded retries with per-retry logging.
  • playwright.config.ts — split into chromium (live) and mocked (hermetic) projects.

Phase 1 — Cross-network shared specs (c9873cc, d6d3c23)

  • shared/errors.spec.ts (25 tests) — 404 tx / 404 block / malformed input / zero address / unknown network across Ethereum, Arbitrum, Optimism, Base.
  • shared/settings.spec.ts (6 tests) — theme / rpcStrategy / language persistence through the bundled openScan_user_settings blob.
  • shared/search.spec.ts (10 tests) — navbar search routes tx / address / block across chains.
  • shared/mocked/nft-safety.spec.ts, shared/mocked/rpc-strategy.spec.ts — skeletons with explicit "TODO phase 4" markers.

Phase 2 — L2 adapter field assertions (8176aea, 2908954)

  • evm-networks/l2-fields.spec.ts — first specs that assert the fields the L2 adapters exist to surface: Arbitrum l1BlockNumber / sendCount / sendRoot, OP-stack l1Fee / l1GasPrice / l1GasUsed. Drives off the per-network fixture tables. Blob-field assertions deferred with TODOs.

Phase 3 — Orphaned-network coverage (51c59a9, d87ea40)

  • evm-networks/avalanche.spec.ts (4 tests) — C-Chain (43114) smoke.
  • solana/smoke.spec.ts (5 tests) — network / slots / slot detail / account / validators.
  • testnets/smoke.spec.ts (18 tests) — table-driven over Sepolia, Arb Sepolia, OP Sepolia, Base Sepolia, Polygon Amoy, Avalanche Fuji.

Phase 4 — Feature depth (8dd0d9a, ebfa9ab)

  • shared/contract-interaction.spec.ts — Read/Write function section headers on WETH9 (verified, non-proxy).
  • shared/ai-and-worker.spec.ts — AI Analysis panel + button rendering on tx pages.
  • shared/event-logs.spec.ts.tx-log rendering in EventLogsTab.

Phase 5 — CI wiring (f0e5a8e, 5a1bd3b)

  • e2e-shared.yml — 6-shard matrix for the cross-network specs.
  • e2e-solana.yml, e2e-testnets.yml — new workflows.
  • e2e-nightly.yml — 06:00 UTC cron calling e2e-all.yml, explicitly documented as non-blocking.
  • e2e-evm-networks.yml matrix extended with avalanche + l2-fields shards.
  • e2e-all.yml orchestrator threads the new workflows.

Phase 6 — Docs (f4516fe, 25efaca)

  • README.md — expanded E2E section with directory layout, CI trigger matrix, and an "Adding a new spec" contributor guide.
  • .claude/rules/commands.md — documented --project=chromium recipe.

Post-run fixes (e5127ee, 5512207, 0ad83f3)

Running the suite against a local dev server surfaced real bugs, all in tests (refactor-scoped and pre-existing), none in app code:

  • e5127ee — refactor specs: TxAnalyser starts collapsed (need to click Events tab first); USDC is a proxy so Read/Write sections don't render (switched to WETH9); L2 assertion timeouts bumped; Arbitrum L1-field specs marked fixme pending an Arbitrum-native RPC.
  • 5512207 — 3 of 5 pre-existing failing mainnet specs: .loader-container doesn't exist on /blocks (skeleton table only); pagination click-then-assert races; Arbitrum Uniswap V3 test needed longer timeouts.
  • 0ad83f3 — last 2 pre-existing failures, both test-data bugs:
    • mainnet.ts had a fabricated fixture: hash 0xc55e2b90… labeled as "Type-2 USDC approval block 15,537,394" actually resolves on-chain to a 2016 Binance transfer at block 2M with empty input. USDC didn't deploy until 2018. Swapped to 0xbb4b3fc2… (verified EIP-1559 tx at block 20M with 242 bytes input data).
    • Arbitrum Gas Price locator matched both Gas Price: and Effective Gas Price: spans (strict-mode violation). Start-anchored regex /^Gas Price:/ isolates the target.

Test plan

  • npm run typecheck — passes.
  • npm run format:fix / npm run lint:fix — clean (one pre-existing getAlchemyBtcUrl warning unrelated).
  • Full npx playwright test --project=chromium run on local dev server: 350 passed, 5 failed, 1 flaky, 5 skipped, 29 min.
  • Re-ran the 5 failing specs after the fix commits: all 5 now pass.
  • Refactor-scoped specs re-verified: 77 runnable pass, 10 placeholders skipped, 0 fail.

Additional Notes

  • SECURITY_REVIEW_2026-04-23.md is intentionally not tracked by this branch — it belongs to the earlier security review task.
  • Backlog documented inline in skip-placeholder describe names:
    • Full hermetic NFT safe-href regression for the H-2 fix (shared/mocked/nft-safety.spec.ts).
    • RPC strategy / worker fallover matrix (shared/mocked/rpc-strategy.spec.ts).
    • Blob-field assertions on post-Dencun blocks.
    • Arbitrum L1-field specs (require an RPC that returns the extended receipt shape).
    • 100+ log stress variant of event-logs.spec.ts.
    • Unverified-contract coverage.
  • Commit message trailers omit any AI attribution per repo convention.

AugustoL added 17 commits April 23, 2026 16:09
Add infrastructure the rest of the e2e refactor plan leans on, without
moving or rewriting any existing spec:

- `e2e/fixtures/networks.ts` — typed table of every production + testnet
  network (ChainID, slug, adapter family, canonical block/tx/address/
  token) and groupings (EVM_PRODUCTION, EVM_TESTNETS, L2_NETWORKS). Lets
  future cross-network specs iterate instead of copy-pasting per chain.
- `e2e/fixtures/localStorage.ts` — `clearAppState`, `setLanguage`,
  `setTheme`, `setRpcStrategy`, `readLocalStorage` so settings-touching
  specs can reset state explicitly rather than relying on fullyParallel
  isolation guarantees.
- `e2e/fixtures/rpcMock.ts` — `page.route` helpers for JSON-RPC stubs,
  HTTP errors, 429-then-success, and offline. Needed by the upcoming
  strategy / worker-fallover / error-path specs.
- `e2e/fixtures/assertionsL2.ts` — `expectArbitrumL1Fields`,
  `expectOpStackL1Fee`, `expectBlobFields`. One-call assertions for the
  fields L2 adapters exist to surface.
- `e2e/fixtures/test.ts` — replace the unbounded growing-timeout retry
  with a fixed 60s base + 30s retry bonus, and log retries so flakiness
  is visible in CI output.
- `playwright.config.ts` — add a `mocked` project scoped to
  `e2e/tests/shared/mocked/**/*.spec.ts`. Default `chromium` project
  explicitly excludes it so hermetic tests and live-RPC tests don't
  cross-pollute. CI retries dropped from 3 to 2 (more signal, less
  masking).
- Seed `e2e/tests/shared/`, `e2e/tests/shared/mocked/`,
  `e2e/tests/testnets/`, and `e2e/tests/solana/` with placeholders so
  the directories land in git.

No existing spec is modified or moved. Playwright test discovery still
reports the same 281 tests; typecheck clean.
Self-review of 6294053 uncovered three issues that would silently break
downstream specs:

- `networks.ts` slugs invented rather than taken from
  `src/config/networks.json`: `ethereum` → `eth`, `arbitrum` → `arb`,
  `optimism` → `op`, `avalanche` → `avax`. Also add a `urlPath` field so
  specs know whether to put chainId or slug into the `/:networkId` URL
  segment (the convention used by existing per-network specs: chainId
  for EVM, slug for Bitcoin / Solana).
- `localStorage.ts` used several keys that don't exist in the app:
  `openScan_theme`, `openScan_rpcStrategy`, `openScan_workerUrl`,
  `OPENSCAN_METADATA_RPCS`, `openScan_customNetworks`. The real layout
  (per `SettingsContext.tsx` and `configExportImport.ts`) is a single
  bundled `openScan_user_settings` JSON blob plus a top-level
  `openScan_language` override. Rewrite the helper to merge-patch the
  bundle via `setUserSetting`, and expose `readUserSetting` for
  assertions. Drop the phantom keys from the clear list.
- `playwright.config.ts`: revert the gratuitous CI retries reduction
  from 3 → 2. The scaffolding commit shouldn't alter flakiness tuning;
  that belongs in its own change with evidence.
Land the cross-cutting specs the research flagged as highest-ROI gaps:

- `shared/errors.spec.ts` (20 tests): assert the app root and footer
  stay mounted after navigation to non-existent block / tx / address,
  malformed input (non-hex, non-digit), and an unknown network slug.
  Across Ethereum, Arbitrum, Optimism, Base. Deliberately does not
  assert specific i18n copy so the suite doesn't rot when strings
  change.
- `shared/settings.spec.ts` (6 tests): theme=light applies the
  `light-theme` body class; rpcStrategy and language overrides survive
  a reload; `setUserSetting` merges without clobbering sibling fields;
  `clearAppState` removes the bundled settings blob. Pure localStorage,
  no RPC dependency.
- `shared/search.spec.ts` (10 tests): navbar search routes tx hashes /
  addresses / block numbers to the correct chain-scoped URL; malformed
  input stays on the page. Covers Ethereum, Arbitrum, Base.
- `shared/mocked/nft-safety.spec.ts` (3 test.skip): regression test
  skeleton for the H-2 fix (`toSafeExternalHref`). Full implementation
  deferred to phase 4 because hermetic rendering of a token page
  requires stubbing `tokenURI` / `name` / `symbol` / `ownerOf` eth_calls
  *and* the metadata JSON fetch — too much surface for phase 1.
- `shared/mocked/rpc-strategy.spec.ts` (4 test.skip): skeleton for
  fallback / parallel / race strategy verification + worker
  Cloudflare → Vercel failover. Deferred to phase 4 with the same
  rationale.

Total: 281 → 329 tests (40 runnable + 7 skipped placeholders). Typecheck
clean; Playwright discovers both the `chromium` (live) and `mocked`
(hermetic) projects as defined in phase 0.
Self-review of c9873cc surfaced two issues:

- `shared/settings.spec.ts`: the "rpcStrategy survives a reload" test
  used `addInitScript` to seed and then called `page.reload()`. But
  addInitScript re-runs on every navigation including the reload, so
  the assertion passed tautologically — it never actually exercised
  SettingsContext's write-back path. Rewrite as a single navigation
  that waits for the context mount effect to merge DEFAULT_SETTINGS
  with the seeded patch before asserting the seeded field is still
  present. Rename "survives a reload" to reflect what the test
  actually proves (hydration + write-back preserves seeded fields).
  Same rename for the language test.
- `shared/mocked/nft-safety.spec.ts` and
  `shared/mocked/rpc-strategy.spec.ts`: skipped bodies contained
  `expect(true).toBe(true)` purely to make `expect` a used import.
  Drop the bodies and the expect import; add "— TODO phase 4" to each
  describe name so the CI report makes the defer explicit.
Add the assertions the research review flagged as the biggest quality
gap: the whole reason the L2 adapters exist (ArbitrumAdapter,
OptimismAdapter, BaseAdapter) is to surface fields the vanilla
EVMAdapter does not, and yet nothing in the current suite asserted any
of them.

- `evm-networks/l2-fields.spec.ts` (4 runnable + 3 skip-placeholders):
  - Arbitrum tx: receipt `l1BlockNumber`.
  - Arbitrum block: `sendCount`, `sendRoot` (L2→L1 messages).
  - Optimism tx: `l1Fee`, `l1GasPrice`, `l1GasUsed`.
  - Base tx: same OP-stack L1 fee breakdown.
  Each test drives off the first tx / block already pinned in the
  per-network fixture tables (`e2e/fixtures/{arbitrum,optimism,base}.ts`),
  so we reuse data the team has already validated rather than inventing
  fresh hashes.

- `e2e/fixtures/assertionsL2.ts` revision: split the original monolithic
  `expectArbitrumL1Fields` into `expectArbitrumTxL1Fields` (for
  `l1BlockNumber` on the tx page) and `expectArbitrumBlockFields` (for
  `sendCount` / `sendRoot` on the block page) — the original helper
  conflated two pages. Also swap the blob fields regex from
  `Blob Gas Price` to `Excess Blob Gas`, which is what BlockDisplay.tsx
  actually renders per `src/locales/en/block.json`. Label text sourced
  by grepping the English locale files so the assertions match exactly
  what the UI renders.

Blob-field assertions (Ethereum / OP / Base post-Dencun with real
blob-carrying blocks) deferred to phase 4 with explicit TODOs — picking
stable blob-bearing blocks per chain is a research task, and the
BlockDisplay component gates rendering on `blobGasUsed > 0`.
Self-review of 8176aea surfaced two minor cleanup items:

- Remove every `await page.waitForLoadState("domcontentloaded", …)`
  call. Playwright's `expect(locator).toBeVisible()` already auto-
  retries until the element appears or DEFAULT_TIMEOUT elapses; the
  extra wait is a no-op under HashRouter (DOMContentLoaded fires before
  client-side routing) and only obscures intent.
- `ARB_TX_HASH` selector simplified from `[1] ?? [0]` to `[0]`. Both
  Arbitrum fixture txs are post-Nitro and both carry `l1BlockNumber`
  in their receipts, so the first one is fine — there was no real
  reason to prefer the EIP-1559 entry over the legacy one.

No behavior change; tests still discover as 7 specs (4 runnable + 3
deferred).
Production networks that were registered in `src/config/networks.json`
and routed in `adaptersFactory.ts` but had zero e2e coverage:

- `evm-networks/avalanche.spec.ts` (4 tests): block, address
  (WAVAX contract), zero address, placeholder tx. Avalanche C-Chain
  (43114) uses the default EVMAdapter path; these smokes are shallow
  but close the "any page at all renders" gap.
- `solana/smoke.spec.ts` (5 tests): network landing, slots list, slot
  detail (canonical pinned slot), system-program account page,
  validators list. Solana adapter + pages existed with zero e2e — a
  regression would be invisible until a user reported it. Deep field
  assertions deferred to phase 4 with a `solana.ts` fixture.
- `testnets/smoke.spec.ts` (18 tests): table-driven over the 6 EVM
  testnets registered via metadata v1.2.1-alpha.0 — Sepolia, Arb
  Sepolia, OP Sepolia, Base Sepolia, Polygon Amoy, Avalanche Fuji.
  Block + address + tx page per testnet. The 5 new testnets from PR
  22f5845 shipped without CI gating; this restores that gate.

281 → 363 tests (40 runnable additions + 7 deferred placeholders).
Removes the stray `.gitkeep` files from `solana/` and `testnets/` now
that real specs live there.

All new specs use the shared `expectStillMounted` pattern introduced in
`shared/errors.spec.ts` and drive off `ALL_NETWORKS` / `EVM_TESTNETS`
groupings from phase 0's `networks.ts`, so adding a new production
network in the future only requires one fixture-table entry plus one
spec file rather than copy-pasting a full template.
The phase 3 smokes (avalanche, solana, testnets) plus phase 1's
errors.spec.ts each redefined the same `expectStillMounted` helper
with the same footer selector and timeout constants. Four copies of a
six-line function is copy-paste drift waiting to happen.

Move the helper and its `FOOTER_SELECTOR` constant to
`e2e/fixtures/assertions.ts` and import it from each spec. One file to
update when the footer selector rotates or the mount-verification
strategy changes.

No test behavior change; 363 tests discover identically.
Add three shallow specs that cover cross-network features the existing
suite touches only incidentally:

- `shared/contract-interaction.spec.ts` (3 tests): on USDC mainnet,
  assert the "Read Functions (N)" and "Write Functions (N)" section
  headers render. Neither submits a transaction (wallet signing is
  out of scope for e2e). Plus one smoke on a non-verified address to
  verify the page still renders without an ABI. Label regex sourced
  from `src/locales/en/address.json` (`readFunctionsCount` /
  `writeFunctionsCount`).

- `shared/ai-and-worker.spec.ts` (2 tests): on a tx page, assert the
  `<section class="ai-analysis-panel">` renders and the `.ai-analysis-
  button` is enabled. We don't click Analyze — that would burn Groq
  budget and couple to live availability. Worker failover (Cloudflare
  5xx → Vercel) remains in the `mocked/rpc-strategy.spec.ts` TODO.

- `shared/large-tx.spec.ts` (1 test): on the USDC approval tx pinned
  in `e2e/fixtures/mainnet.ts`, assert `.tx-log` row renders in
  `EventLogsTab`. Baseline coverage for the log-decode path; stress
  against a 100+ log tx deferred to phase 6 once a stable fixture is
  curated.

Full hermetic `shared/mocked/nft-safety.spec.ts` (regression test for
the H-2 `toSafeExternalHref` fix) remains a skip-placeholder. A
robust version requires mocking `tokenURI` + `name` + `symbol` +
`ownerOf` eth_calls as well as the metadata JSON fetch, which is
still heavier than the phase-4 budget.

363 → 369 tests runnable (+6 runnable, skips unchanged).
Self-review of 8dd0d9a: the third `contract-interaction.spec.ts` test
claimed to cover "contract with code but no verified source" but used
the zero address — which is an EOA with zero bytecode. The assertion
passed trivially and it duplicated `${net.name} — zero address renders`
already present in `errors.spec.ts`.

Remove the test rather than paper over it; note in a comment that a
real unverified-contract coverage case is a phase-6 item that requires
picking a stably-unverified mainnet contract.

369 → 368 tests runnable.
Extend the CI matrix so every spec group added in phases 1–4 has a
trigger, and add a nightly cron so live-RPC drift between PRs is
caught without blocking merges.

- `e2e-evm-networks.yml`: add `avalanche` shard (phase 3 Avalanche
  smoke) and `l2-fields` shard (phase 2 L2 adapter assertions) to the
  existing matrix.
- `e2e-shared.yml` (new): cross-network matrix over errors, search,
  settings, contract-interaction, ai-and-worker, large-tx. Six
  parallel shards, same shape as the evm-networks workflow. Uses the
  explicit `--project=chromium` flag so CI never accidentally runs the
  deferred `mocked` project's placeholder skips.
- `e2e-solana.yml` (new): single job for `e2e/tests/solana/`. Solana
  RPC is discovered via `@openscan/metadata` — no dedicated secret
  required.
- `e2e-testnets.yml` (new): single job for `e2e/tests/testnets/`.
  Same metadata-driven RPC discovery; takes the optional INFURA /
  ALCHEMY secrets for mainnet-adjacent cross-chain calls (ENS
  resolution during search).
- `e2e-all.yml`: thread the three new workflows into the orchestrator
  so manual "run everything" and the nightly both hit them.
- `e2e-nightly.yml` (new): 06:00 UTC cron that calls `e2e-all.yml`.
  Explicit comment in the header noting it must not gate merges —
  nightly red ≠ PR red.

Total CI coverage: 6 shards (up from 5) in evm-networks, 6 new shards
in shared, 1 new job each for solana / testnets, 1 nightly
orchestrator. Each per-PR workflow still triggers on `pull_request` →
`main`, preserving the existing gate model.
Phase 4's review commit re-scoped the "large-tx" spec down to a single
"at least one event log renders" assertion but left the filename as-is.
The file no longer exercises large-tx pagination or virtualization — it
just covers event-log decoding for a single-log tx. Rename for honesty
and update the CI matrix entry in `e2e-shared.yml` to match.

True "large tx" stress (100+ logs, virtualization) remains a phase-6
backlog item.
End of the refactor. No `.only` markers, no commented-out tests, no
unjustified timeouts in any spec added across phases 0–5. Two
documentation updates:

- `README.md`: expand the E2E section to reflect the new `e2e/tests/`
  layout (eth-mainnet, evm-networks, bitcoin, solana, testnets,
  shared, shared/mocked), list what each area covers, and describe
  the PR-vs-nightly CI triggers. Also document the two Playwright
  projects (`chromium` live + `mocked` hermetic) for readers who
  wonder why some specs are excluded from the default project.
- `.claude/rules/commands.md`: add the `--project=chromium` invocation
  as a documented recipe so future contributors know how to skip the
  hermetic suite when they don't need it.

Backlog not closed in this refactor (explicit TODOs landed in the
placeholder spec bodies):
- `shared/mocked/nft-safety.spec.ts` — H-2 regression with full
  `tokenURI` + metadata fetch mocking.
- `shared/mocked/rpc-strategy.spec.ts` — fallback / parallel / race
  strategy verification plus Cloudflare → Vercel worker failover.
- `evm-networks/l2-fields.spec.ts` — blob field assertions
  (blobGasUsed / excessBlobGas) on post-Dencun blocks.
- `solana/` deep field assertions (need a curated `solana.ts` fixture
  equivalent to `mainnet.ts`).
- Large-tx stress (100+ logs) in `event-logs.spec.ts`.
- Unverified-contract coverage in `contract-interaction.spec.ts`.

Final test count: 281 → 369 discovered (~88 additions; roughly 77
runnable + ~11 skip-placeholders documenting deferred work).
Self-review: the README E2E section described what exists but gave no
pointer for how to extend it. Contributors reading the file fresh would
need to reverse-engineer from existing specs + CI workflows to figure
out where a new spec belongs.

Add a short 4-step "Adding a new spec" block covering the four common
cases (new network / cross-network feature / chain-specific feature /
hermetic test) and which file + workflow touch each requires. Keeps
the change narrow to docs so it can't regress the code.
Running the refactor-scoped suite against a local dev server exposed
four bugs, all in specs added during phases 2–4. No application code
changes.

- `event-logs.spec.ts`: `TxAnalyser` starts collapsed for non-
  super-users (`collapsed = !isSuperUser`). Clicking the "Events (N)"
  tab is what expands the panel and mounts `.tx-log`. The original
  spec skipped this step. Also switch the fixture tx from the 2022
  USDC approval (block 15M, flaky on public RPCs) to the EIP-1559
  tx at block 20M that's already pinned in `mainnet.ts`.

- `contract-interaction.spec.ts`: two bugs —
  1. USDC is a proxy; `ContractInteraction.tsx` surfaces only the
     proxy's tiny admin ABI, so "Write Functions" never renders.
     Swap to WETH9 (verified, non-proxy, small stable ABI).
  2. Contract Details is collapsed by default. The `openContractDetails`
     helper now waits explicitly for the header element and then
     clicks it — the previous `Balance: OR Contract Details` race
     could resolve on `Balance:` before the header mounted, causing
     the subsequent `isVisible` check to return false and the click
     to be skipped.

- `l2-fields.spec.ts` / `assertionsL2.ts`:
  1. Default Playwright 5s timeout was too short — L2 receipts via
     public RPC can take 10–20s. Bump the helper timeout to
     `DEFAULT_TIMEOUT * 4` across all L2 assertions.
  2. The Arbitrum tx / block cases need an RPC that returns
     Arbitrum's extended receipt shape (`l1BlockNumber`, `sendCount`,
     `sendRoot`). Public endpoints strip these fields and the local
     Alchemy/Infura used in testing doesn't expose them either.
     Mark both as `test.fixme` with an explicit comment — to be
     un-fixmed once a confirmed Arbitrum-native RPC is wired into
     CI secrets and a conditional skip on that env var is added.
     The OP/Base L1 fee specs pass (OP clean, Base flaky-on-retry).

Run result on my local dev server with the test's Alchemy/Infura seed:
75 passed, 5 skipped (2 new fixme + 3 blob placeholders), 0 failed in
57s. Typecheck clean.
None of these tests come from the e2e refactor — they've been failing
intermittently on the `dev` branch against public RPCs because of the
same two root causes surfaced by the full-suite run:

1. `.loader-container` doesn't exist on `/blocks` — the component
   renders a skeleton table while loading (`src/components/pages/evm/
   blocks/index.tsx:165`). `toBeHidden({ timeout })` on that locator
   therefore passes instantly, leaving the subsequent assertions to
   race real data loading under default 5s timeouts. Rewrite the wait
   to anchor on `.blocks-header-main` — the element that mounts only
   in the data branch — with an RPC-sized budget.
2. Click-then-assert races on pagination: clicking "Older" triggers a
   navigate + RPC round-trip. The 5s default for "Newer becomes
   enabled" isn't enough when public endpoints are rate-limited.
   Replace with `page.waitForURL(/fromBlock=/)` as the navigation
   signal, then `toBeEnabled({ timeout: DEFAULT_TIMEOUT * 3 })`.

Covered:
- `blocks.spec.ts:5` (header) — anchor on data-branch element.
- `blocks.spec.ts:53` (single-line header) — same.
- `blocks.spec.ts:114` (pagination) — waitForURL + longer enabled-wait.
- `txs.spec.ts:120` (tx pagination navigate-between) — same
  multiplier-bump treatment applied to all pagination assertions.
- `arbitrum.spec.ts:250` (Uniswap V3 swap details) — Arbitrum public
  RPCs are slower than mainnet; every field assertion now gets the
  standard `DEFAULT_TIMEOUT * 3`.
- `transaction.spec.ts:80` (input data tab) — the "Input Data" tab
  lives inside `TxAnalyser`, which only mounts once `hasInputData`
  is true — derived from the receipt, which arrives after
  `waitForTxContent`. Explicit timeout on the tab assertion.

Re-run result on my local machine (public RPC, parallel workers):
before — 5 failed; after — 2 failed + 2 flaky (same tests), 51 passed.
The remaining 2 (USDC 2022 approval tx, Arbitrum V3 swap) appear to
hit an app-side stale-data bug when the public RPC returns a null /
partial tx — out of scope for e2e stability tuning.
…Price locator

Deep-dive on the last 2 "pre-existing" failures: the app is behaving
correctly in both cases; the tests had bugs.

1) `transaction.spec.ts` / `mainnet.ts` — the `USDC_APPROVAL` constant
   pointed to `0xc55e2b90168af69721…`. The fixture entry for that hash
   in `mainnet.ts` described it as a Type-2 USDC approval at block
   15,537,394 with `hasInputData: true`. On-chain reality (verified
   directly against a public RPC): that hash resolves to a **2016
   Binance transfer at block 2,000,000** with `input: "0x"` (no
   calldata). USDC did not deploy until 2018, so the fixture metadata
   was fabricated.

   The "displays transaction with input data" test looks for the
   "Input Data" tab inside `TxAnalyser`, which only mounts when
   `hasInputData` is true — derived from the actual tx.input byte
   length. Against real RPCs it therefore never rendered and the test
   timed out. Replace `USDC_APPROVAL` with `TX_WITH_INPUT_DATA`
   pointing to `0xbb4b3fc2…` (the EIP-1559 tx at block 20M already
   pinned in the fixture and verified to have 242 bytes of input
   data). Remove the stale USDC entry from `mainnet.ts` with a
   comment explaining what was wrong, so nobody reads the old
   metadata and re-imports it.

2) `arbitrum.spec.ts:250` — the locator
   `locator(".tx-label", { hasText: "Gas Price:" })` matches two
   spans on Arbitrum post-Nitro pages: `Gas Price:` AND
   `Effective Gas Price:` (receipt vs. tx). Playwright's
   `toBeVisible` in strict mode fails when the locator resolves to
   multiple elements. My first fix attempt (`/^Gas Price:$/`) also
   failed because the span's textContent is `"Gas Price:" +` the
   HelperTooltip's text — the span is not a pure text node. Use a
   start-anchored regex `/^Gas Price:/` (no `$`) so the match
   allows the tooltip suffix but still excludes the "Effective"-
   prefixed sibling.

Re-ran both specs in isolation: 8/8 passed in 1.3m. The refactor-
scoped suite also still passes unchanged. Zero changes to the app.
@github-actions

Copy link
Copy Markdown

🚀 Preview: https://pr-366--openscan.netlify.app
📝 Commit: 0ad83f3ba121ad5ab96407cc0402e977321bc7a4

@AugustoL AugustoL merged commit a324be0 into openscan-explorer:dev Apr 28, 2026
2 of 3 checks passed
@AugustoL AugustoL mentioned this pull request Apr 29, 2026
11 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants