Skip to content

Release 1.0.0-beta.8#67

Merged
Maksandre merged 71 commits intomainfrom
release-1.0.0
Mar 7, 2026
Merged

Release 1.0.0-beta.8#67
Maksandre merged 71 commits intomainfrom
release-1.0.0

Conversation

@Maksandre
Copy link
Copy Markdown
Owner

No description provided.

…fest.json and improve error handling for service worker initialization
…n server; add additional Chrome flags in withWallets for improved performance
…g default timeout from Wallet class configuration
Maksandre and others added 28 commits February 28, 2026 04:59
* debug: dump storage key names during cache build

Log all chrome.storage.local key names during wallet cache building
to diagnose why CI gets 61 keys instead of expected 68. Also skip
tests temporarily to speed up CI iteration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add initial wait before storage stabilization polling

MetaMask fetches token lists asynchronously from its CDN after
onboarding (7 chains: ETH, Base, BSC, Polygon, Optimism, Arbitrum,
Linea). These arrive as storageService:TokenListController keys.

Without an initial delay, the stabilization algorithm sees 61 keys
as stable (30s of unchanged count) before the token list fetches
complete. This is the root cause of CI getting 61 keys vs 68.

Changes:
- Add 15s INITIAL_WAIT before first poll to let network fetches begin
- Increase TIMEOUT from 60s to 120s for slower CI environments
- Restore test execution in CI workflow

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* debug: navigate to home.html before polling + longer timeout

Try navigating back to home.html after onboarding to explicitly
trigger token list fetches, wait for networkidle, then poll with
120s timeout. Also dump key names for comparison.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* debug: add browser version info to CI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* debug: test MetaMask token API network connectivity from CI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: navigate to home.html before storage polling to trigger token list fetches

Root cause: After MetaMask onboarding, the code navigated to
sidepanel.html and immediately started polling chrome.storage.local
for stability. MetaMask's TokenListController fetches token data for
7 chains (ETH, Base, BSC, Polygon, Optimism, Arbitrum, Linea) from
token.api.cx.metamask.io, but these fetches are triggered by the
home.html UI initialization. Without visiting home.html and waiting
for network idle, the fetches never started, causing the cache to
stabilize at 61 keys instead of 68.

Fix:
- Navigate to home.html after onboarding and wait for networkidle
- Increase storage polling timeout from 60s to 120s for CI safety
- Restore full CI workflow with tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* debug: verify cache.js contains the fix in CI

Check if dist/scripts/cache.js actually has the home.html
navigation and networkidle wait after yarn build.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* cleanup: remove debug verification step from CI workflow

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: wait for confirm button explicitly in approve()

Remove the getByRole fallback that matched MetaMask's confirmation
queue navigation buttons (Previous/Next Confirmation) in CI.
Instead, wait explicitly for the test-id selectors to become visible.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use exact match for confirm button in approve() to avoid strict mode violation

The /confirm/i regex matched MetaMask's confirmation queue nav buttons
("Previous Confirmation" / "Next Confirmation"), causing strict mode
violations in CI. Changed to /^confirm$/i to match only the actual
Confirm button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: wait for MetaMask token API responses before storage polling

The networkidle wait alone was insufficient in CI - MetaMask's
TokenListController fetches start lazily after navigation. Now we
explicitly wait for a token.api.cx.metamask.io response and add a
5s buffer. Also wait for networkidle in onboard() before navigating
away from home.html to avoid aborting in-flight token fetches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add 30s wait for MetaMask background tasks before storage polling

In CI, MetaMask's TokenListController fetches complete in the MV3
service worker after home.html triggers initialization. Without
waiting, the stabilization loop prematurely locks at 61 keys before
the 7 token list cache keys are written. A 30s wait after onboard
gives the service worker time to complete all chain fetches.

Also removed premature sidepanel.html navigation from onboard() to
keep home.html active during cache building.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: restore sidepanel.html navigation in onboard()

Removing the sidepanel.html navigation from onboard() caused all cached
MetaMask tests to fail in CI with "Target page, context or browser has
been closed". The sidepanel.html page is required for MetaMask's
notification/approval UI to function correctly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: restore home.html navigation + networkidle in cache builder

Reverted buildCache.ts to the approach that produced 68 keys in CI:
navigate to home.html and wait for networkidle after onboard(). The
30s sleep approach left the page on sidepanel.html during cache save,
which caused MetaMask's cached profile to be in a broken state -
unlock() wouldn't work when tests loaded the profile.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: wait for unlock completion and dismiss queued notifications

- unlock() now waits for the lock screen to disappear before returning
- Test fixture waits for account menu to be visible after unlock
- Fixture dismisses any queued MetaMask notifications (e.g., Tron
  account removal) that appear on sidepanel after cache restore

These changes fix the "Target page, context or browser has been closed"
errors in CI where the fixture navigated to sidepanel.html before
unlock() had fully completed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: handle MetaMask onboarding screens after cache restore in CI

After restoring the cached profile and unlocking, MetaMask may re-show
the "Your wallet is ready!" and metametrics consent screens. The fixture
now detects and dismisses these before waiting for the main wallet UI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: make approve() resilient to blank sidepanel and promotional popups

- Reload page if confirm button is not visible within 5s (handles blank
  sidepanel.html in CI)
- Dismiss promotional overlays (e.g., "Transaction Shield") before
  clicking confirm

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: fall back to home.html when sidepanel.html is blank in CI

MetaMask's sidepanel.html sometimes fails to render in headless CI.
When the confirm/cancel button is not visible within 5s, approve() and
deny() now navigate to home.html which reliably shows the confirmation
UI. Also dismiss promotional popups that may overlay buttons.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* add max failure of 5 tests

* fix: navigate to sidepanel.html before approve/deny to refresh state

In headless CI, MetaMask's sidepanel.html can go stale and display a
blank page after connection setup. Re-navigating before clicking
confirm/cancel ensures the latest notification state is loaded.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve flaky MetaMask message tests and Polkadot connector issue

MetaMask approve/deny: Try current page first (fast path), then
navigate to sidepanel.html and explicitly waitFor the button to appear.
This fixes the race condition where the notification hasn't been
registered by MetaMask yet when we navigate.

Polkadot.js test: Wait for the connector button to become enabled,
reloading the page if needed. The extension injects window.injectedWeb3
asynchronously and the dApp may load before injection completes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: dismiss Transaction Shield popup blocking confirmation UI

MetaMask shows a "Transaction Shield" promotional popup on sidepanel
that blocks the confirm/cancel buttons. Dismiss it in both the fixture
setup and in approve()/deny() methods as a safety net. Also extracted
dismissPopups() helper method for reuse.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: make dismissPopups robust with multiple selectors and JS fallback

The Transaction Shield popup's close button doesn't match
button[aria-label="Close"]. Try multiple selectors, Escape key,
clicking outside, and finally JS DOM removal as fallbacks.
Also retry popup dismissal if confirm/cancel button wait times out.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use force-click fallback when popup overlay blocks confirm/cancel

The Transaction Shield popup can't be reliably dismissed (close button
selectors don't match). Instead, try normal click with 5s timeout, then
fall back to force: true to click through the overlay. Also removed the
JS DOM removal which was accidentally destroying the confirm button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use JS evaluate click to bypass popup overlay instead of force

Replace force:true click with page.evaluate() that calls element.click()
directly. JS click fires on the element regardless of overlay state and
properly triggers React event handlers. Only used as fallback when
Transaction Shield popup is detected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: navigate to home.html after unlock to ensure main UI loads

After cache restore, MetaMask may show notification screens instead
of the main wallet UI. Explicitly navigating to home.html forces
the wallet home view before checking for the account menu button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add text-based button fallback and improve popup dismissal

The page.evaluate() fallback in approve()/deny() only tried data-testid
selectors, but the Playwright locator also matches by getByRole (text).
If MetaMask's confirmation button has text "Confirm" but no matching
data-testid, the JS click was silently a no-op.

Also improves dismissPopups() with:
- Playwright getByRole("button", { name: /close/i }) (pierces shadow DOM)
- JS evaluate fallback to find close button by DOM structure
- CSS display:none fallback for the modal overlay

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use notification.html to avoid Transaction Shield popup

The Transaction Shield popup appears on every sidepanel.html navigation
in CI and cannot be dismissed (no CSS selector matches the X button).
Instead of fighting the popup, navigate to notification.html first —
MetaMask's dedicated confirmation page that doesn't show promotional
popups. Falls back to sidepanel.html if notification.html doesn't show
the confirmation.

Also changes approve/deny fast path from instant isVisible() check to
a 3s waitFor, giving MetaMask's service worker time to route the
notification before falling back to navigation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: disable Transaction Shield popup via MetaMask storage flag

Instead of trying to dismiss the undismissable Transaction Shield popup
through CSS selectors/JS, prevent it from appearing by setting
MetaMask's internal showShieldEntryModalOnce=null in chrome.storage.local.

This is the approach MetaMask's own e2e tests use (via fixture-builder).
Called during fixture setup on home.html before navigating to sidepanel.

Also reverts notification.html approach which didn't work (notification.html
doesn't display pending confirmations when navigated to in-tab) and
restores the simpler sidepanel.html approve/deny flow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use synchronous evaluate for disableShieldPopup to avoid LavaMoat

LavaMoat scuttles setInterval on MetaMask extension pages. Playwright
uses setInterval internally when awaiting async evaluate results
(Promise return). Switching to synchronous fire-and-forget evaluate
with waitForTimeout avoids triggering LavaMoat's scuttling protection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: run disableShieldPopup on sidepanel.html where evaluate works

LavaMoat blocks page.evaluate() on home.html but permits it on
sidepanel.html (where existing evaluate calls in dismissPopups/approve
already work). Navigate to sidepanel first, set the storage flag,
then reload sidepanel to get a popup-free view.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: integrate shield popup storage flag into dismissPopups() to avoid LavaMoat

Instead of calling disableShieldPopup() separately (which triggers LavaMoat
scuttling errors due to early page.evaluate()), the storage flag setting is
now integrated into dismissPopups()'s JS evaluate fallback. This evaluate
runs after several seconds of CSS selector attempts, giving LavaMoat time
to initialize. The fixture now calls dismissPopups() instead of the broken
disableShieldPopup() method.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: race popup vs button in approve/deny and wait for sidepanel load

Two key changes:
1. approve()/deny() now use Promise.race to handle the Transaction Shield
   popup and the target button appearing concurrently. If the popup wins,
   it's dismissed and then we wait for the button.
2. The fixture now waits for account-menu-icon to be visible before calling
   disableShieldPopup(), ensuring LavaMoat has fully initialized by the
   time page.evaluate() runs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use addScriptTag instead of evaluate for disableShieldPopup

page.evaluate() is blocked by LavaMoat's setInterval scuttling on MetaMask
extension pages. addScriptTag injects a <script> element directly into the
DOM, executing the code without Playwright's setInterval-based transport.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove disableShieldPopup (LavaMoat blocks all JS injection) and rely on Promise.race popup handling

LavaMoat scuttles setInterval on MetaMask extension pages, blocking both
page.evaluate() and page.addScriptTag(). Removed the broken
disableShieldPopup() method entirely.

Instead, approve/deny now use Promise.race to handle both the popup and
the target button appearing concurrently. If the popup appears first,
dismissPopups() closes it (and its JS evaluate fallback sets the storage
flag to prevent reappearance). The fixture just calls dismissPopups()
without trying to pre-emptively set the storage flag.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use exact MetaMask data-testid and aria-label selectors in dismissPopups

The shield modal close button uses data-testid="shield-entry-modal-close-button"
and aria-label="close" (lowercase). Previous selectors didn't match. Also wraps
the page.evaluate fallback in try/catch since LavaMoat blocks JS injection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: retry sidepanel navigation in waitAndClickButton for slow approvals

When the dApp triggers personal_sign, MetaMask's service worker may not
have registered the pending approval yet by the time we navigate to
sidepanel.html. The page loads with no confirmation, showing wallet home.

Now retries navigation up to 3 times with 10s waits, catching timeouts
gracefully instead of failing immediately.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: navigate to home.html in lock/unlock to avoid sidepanel notification overlays

The Solana account removal notification blocks the account-options-menu
on sidepanel.html. Navigating to home.html first ensures the main
wallet UI is visible for lock/unlock operations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: try notification.html for approval dialogs, not just sidepanel.html

personal_sign requests may not appear on sidepanel.html in headless Chrome.
notification.html is MetaMask's standard approval dialog page. Try it first,
then sidepanel.html, then notification.html again as a retry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: wait on current page first before navigating in waitAndClickButton

MetaMask's React router auto-navigates to the confirmation view when a
pending approval is registered. Navigating away with goto() can interrupt
this routing and lose the confirmation. Now waits 10s on the current page
first, then falls back to sidepanel.html navigation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: retry sidepanel navigation up to 3 times and fail explicitly on timeout

The waitAndClickButton method had a bug where the timeout path in
Strategy 2 fell through to btnLocator.first().click() without verifying
the button was actually visible. This caused approve() to silently
"succeed" without clicking the real confirmation button, leading to
assertSignatureSuccess() failures.

Now retries sidepanel.html navigation up to 3 times to give MetaMask's
service worker time to register pending approvals, and fails with a
clear error if the button never appears.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: bring MetaMask page to front before approval and tighten deny locators

Two fixes for the persistent sign message test failure:

1. Call page.bringToFront() before navigating to sidepanel.html so
   MetaMask's React router can receive service worker messages and
   auto-navigate to the confirmation view. Without this, the page
   stays in the background and never shows the pending approval.

2. Tighten deny() locators from /cancel|reject/i to /^cancel$/i and
   /^reject$/i to avoid matching MetaMask's "Reject all" notification
   button which exists on the wallet home page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: wait for MetaMask hash route navigation before looking for buttons

Instead of waiting for buttons directly (which can match false positives
on the wallet home page), wait for MetaMask's ConfirmationHandler to
auto-navigate to a confirmation route (#/confirm-transaction/ or
#/connect/). MetaMask uses HashRouter, so we match the hash fragment.

Also tightens deny() locators from /cancel|reject/i to /^cancel$/i and
/^reject$/i to prevent matching "Reject all" notification button.

Skips navigation if already on a confirmation route (e.g., when the
fixture has already set up the sidepanel on the connect page).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: dismiss queued notifications on home.html before expecting wallet UI

After cache restore, MetaMask may have queued Solana/Tron "Remove account"
notifications that the ConfirmationHandler auto-navigates to on home.html,
blocking the main wallet UI. The fixture now checks for and dismisses
"Reject all" notifications on home.html before expecting the
account-options-menu-button.

Also updates waitAndClickButton to wait for MetaMask's HashRouter to
navigate to a confirmation route (#/confirm-transaction/ or #/connect/)
before looking for buttons, avoiding false positives from wallet home
page elements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: bring MetaMask page to front and simplify waitAndClickButton

Replace the waitForURL-based approach with a simpler strategy:
1. bringToFront() so MetaMask can receive service worker state updates
2. Wait on the current page for 10s (Strategy 1)
3. Retry by navigating to sidepanel.html up to 3 times (Strategy 2)
4. Fail explicitly if button never appears

The bringToFront() is critical because the MetaMask page may be in
the background (after the dApp page calls bringToFront), and
background pages may not process service worker state broadcasts
promptly in headless mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: always navigate fresh to sidepanel and wait for confirmation cleanup

Root cause: waitAndClickButton skipped navigation when already on a
confirmation URL, clicking stale buttons from previous approvals.
Trace analysis showed approve() for sign message found and clicked a
leftover confirm button (18ms!) from the preceding network switch,
without ever seeing the actual sign request.

Changes:
- Always navigate to sidepanel.html fresh to reset ConfirmationHandler
- After clicking confirm/cancel, wait for URL to leave the confirmation
  route before returning, preventing stale state for subsequent calls

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: address code review feedback

- Move post-unlock stabilization from fixture into unlock() — users
  get a ready wallet state without replicating fixture logic
- Race all post-unlock states (metametrics, open wallet, notifications,
  ready) to avoid 13s of sequential timeout penalties in the happy path
- Remove 40 lines of dead page.evaluate() in dismissPopups() (LavaMoat
  blocks it) and extract waitForPopupHidden helper
- Add console.warn with URL to all silent catches in waitAndClickButton
- Add visibility wait in lock() before clicking menu button
- Use Locator type instead of ReturnType<typeof this.page.getByTestId>
- Make maxFailures CI-only, revert local project to headless: false

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: dismiss queued notifications individually during unlock

ConfirmationHandler auto-routes to #/confirmation/... for Solana/Tron
account removal notifications after cache restore. With only 1 queued
notification, "Reject all" isn't rendered (nav.tsx only shows it when
count > 1), so the previous race timed out silently.

Added confirmation-cancel-button as a 5th race target and extracted
dismissQueuedNotifications() that loops dismissing individual
notifications until the wallet UI is ready. Also removed the
sidepanel detour from unlock() since waitAndClickButton already
handles promotional popups during approvals.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Max Andreev <maxick20@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
@Maksandre Maksandre changed the title Release 1.0.0-beta.6 Release 1.0.0-beta.8 Mar 7, 2026
@Maksandre Maksandre merged commit 22bea6b into main Mar 7, 2026
4 checks passed
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