Merged
Conversation
…Foundry integration
…ine ERC-20 components
…fest.json and improve error handling for service worker initialization
…n server; add additional Chrome flags in withWallets for improved performance
…ng and code readability
…ate example environment variables
…g default timeout from Wallet class configuration
Release 1.0.0 fix cache
* 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.