From b3dfcab802f0e4d855102cbb49d90c029c9ffc7a Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 11 Feb 2026 14:11:37 -0800 Subject: [PATCH 01/16] fix wagmi test name --- appwright/tests/mm-connect/connection-wagmi.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appwright/tests/mm-connect/connection-wagmi.spec.js b/appwright/tests/mm-connect/connection-wagmi.spec.js index 2b6328fa5f89..fe8c88dd1a9e 100644 --- a/appwright/tests/mm-connect/connection-wagmi.spec.js +++ b/appwright/tests/mm-connect/connection-wagmi.spec.js @@ -56,7 +56,7 @@ test.afterAll(async () => { await playgroundServer.stop(); }); -test('@metamask/connect-wagmi - Connect via Wagmi to Local Browser Playground', async ({ +test('@metamask/connect-evm (wagmi) - Connect via Wagmi to Local Browser Playground', async ({ device, }) => { // Get platform-specific URL From 3bd081038db919c99f9cf1e6e0a4b132db73128d Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 11 Feb 2026 14:49:22 -0800 Subject: [PATCH 02/16] initial multiclient test --- .../mm-connect/connection-multiclient.spec.js | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 appwright/tests/mm-connect/connection-multiclient.spec.js diff --git a/appwright/tests/mm-connect/connection-multiclient.spec.js b/appwright/tests/mm-connect/connection-multiclient.spec.js new file mode 100644 index 000000000000..77f4765f812f --- /dev/null +++ b/appwright/tests/mm-connect/connection-multiclient.spec.js @@ -0,0 +1,150 @@ +import { test } from 'appwright'; + +import { login } from '../../utils/Flows.js'; +import { + launchMobileBrowser, + navigateToDapp, +} from '../../utils/MobileBrowser.js'; +import WalletMainScreen from '../../../wdio/screen-objects/WalletMainScreen.js'; +import BrowserPlaygroundDapp from '../../../wdio/screen-objects/BrowserPlaygroundDapp.js'; +import AndroidScreenHelpers from '../../../wdio/screen-objects/Native/Android.js'; +import DappConnectionModal from '../../../wdio/screen-objects/Modals/DappConnectionModal.js'; +import AppwrightHelpers from '../../../tests/framework/AppwrightHelpers.js'; +import { + DappServer, + DappVariants, + TestDapps, +} from '../../../tests/framework/index.ts'; +import { + getDappUrlForBrowser, + setupAdbReverse, + cleanupAdbReverse, +} from './utils.js'; + +const DAPP_NAME = 'MetaMask MultiChain API Test Dapp'; +const DAPP_PORT = 8090; + +// NOTE: This test requires the testing SRP to be used +const ACCOUNT_1_ADDRESS = '0x19a7Ad8256ab119655f1D758348501d598fC1C94'; + +// Create the playground server using the shared framework +const playgroundServer = new DappServer({ + dappCounter: 0, + rootDirectory: TestDapps[DappVariants.BROWSER_PLAYGROUND].dappPath, + dappVariant: DappVariants.BROWSER_PLAYGROUND, +}); + +// Start local playground server before all tests +test.beforeAll(async () => { + // Set port and start the server directly (bypassing Detox-specific utilities) + playgroundServer.setServerPort(DAPP_PORT); + await playgroundServer.start(); + + // Set up adb reverse for Android emulator access + setupAdbReverse(DAPP_PORT); +}); + +// Stop local playground server after all tests +test.afterAll(async () => { + cleanupAdbReverse(DAPP_PORT); + await playgroundServer.stop(); +}); + +test('@metamask/connect-multichain (multiple clients) - Connect multiple clients via Multichain API to Local Browser Playground', async ({ + device, +}) => { + // Get platform-specific URL + const platform = device.getPlatform?.() || 'android'; + const DAPP_URL = getDappUrlForBrowser(platform); + + // Initialize page objects with device + WalletMainScreen.device = device; + BrowserPlaygroundDapp.device = device; + AndroidScreenHelpers.device = device; + DappConnectionModal.device = device; + + await device.webDriverClient.updateSettings({ + waitForIdleTimeout: 100, + waitForSelectorTimeout: 0, + shouldWaitForQuiescence: false, + }); + + // + // Login and navigate to dapp + // + + await AppwrightHelpers.withNativeAction(device, async () => { + await login(device); + await WalletMainScreen.isMainWalletViewVisible(); + await launchMobileBrowser(device); + await navigateToDapp(device, DAPP_URL, DAPP_NAME); + }); + + await new Promise((resolve) => setTimeout(resolve, 5000)); + + // + // Connect via Multichain API + // + + // Tap the Connect button (multichain API - default scopes) + await AppwrightHelpers.withWebAction( + device, + async () => { + await BrowserPlaygroundDapp.tapConnect(); + }, + DAPP_URL, + ); + + // Handle connection approval in MetaMask + await AppwrightHelpers.withNativeAction(device, async () => { + await AndroidScreenHelpers.tapOpenDeeplinkWithMetaMask(); + await DappConnectionModal.tapEditNetworksButton(); + await DappConnectionModal.tapNetworkButton('Solana'); + await DappConnectionModal.tapUpdateNetworksButton(); + await DappConnectionModal.tapConnectButton(); + }); + + // Explicit pause to avoid navigating back too fast to the dapp + await new Promise((resolve) => setTimeout(resolve, 1000)); + await launchMobileBrowser(device); + await new Promise((resolve) => setTimeout(resolve, 1000)); + + await AppwrightHelpers.withWebAction( + device, + async () => { + // Verify connected by checking for scope cards section + await BrowserPlaygroundDapp.assertMultichainConnected(true); + + // Verify at least one scope card is visible (eip155:1 is default) + await BrowserPlaygroundDapp.assertScopeCardVisible('eip155:1'); + + // Verify evm card is visible + await BrowserPlaygroundDapp.assertConnected(true); + await BrowserPlaygroundDapp.assertChainIdValue('0x1'); + await BrowserPlaygroundDapp.assertActiveAccount(ACCOUNT_1_ADDRESS); + + // Verify wagmi card is visible + await BrowserPlaygroundDapp.assertWagmiConnected(true); + await BrowserPlaygroundDapp.assertWagmiChainIdValue('1'); + await BrowserPlaygroundDapp.assertWagmiActiveAccount(ACCOUNT_1_ADDRESS); + + // NOTE: The solana wallet standard does not respond to wallet_sessionChanged events + // meaning that we must manually trigger the client to check if it is connected. + // Since we are, there will be no approval prompt necessary to accept. + // TODO + }, + DAPP_URL, + ); + + // + // Cleanup - disconnect + // + + await AppwrightHelpers.withWebAction( + device, + async () => { + await BrowserPlaygroundDapp.tapDisconnect(); + }, + DAPP_URL, + ); +}); From 30105acecf87173e524d703597fbb735f95d04db Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Thu, 12 Feb 2026 16:11:59 -0800 Subject: [PATCH 03/16] WIP --- appwright/appwright.config.ts | 4 +- .../tests/mm-connect/connection-evm.spec.js | 2 +- .../mm-connect/connection-multichain.spec.js | 2 +- .../mm-connect/connection-multiclient.spec.js | 21 +++---- .../tests/mm-connect/connection-wagmi.spec.js | 2 +- package.json | 2 +- wdio/screen-objects/BrowserPlaygroundDapp.js | 57 +++++++++++++++++++ .../Modals/DappConnectionModal.js | 1 + yarn.lock | 8 +-- 9 files changed, 75 insertions(+), 24 deletions(-) diff --git a/appwright/appwright.config.ts b/appwright/appwright.config.ts index ca32330768ee..6af4b6d437e4 100644 --- a/appwright/appwright.config.ts +++ b/appwright/appwright.config.ts @@ -149,10 +149,10 @@ export default defineConfig({ platform: Platform.ANDROID, device: { provider: 'emulator', - name: 'Samsung Galaxy S24 Ultra', // this can be changed to your emulator name + name: 'Pixel_5_Pro_API_34', // this can be changed to your emulator name osVersion: '14', // this can be changed to your emulator version }, - buildPath: 'PATH-TO-BUILD', // Path to your .apk file + buildPath: '/Users/jiexi/Downloads/metamask-main-rc-3607.apk', // Path to your .apk file expectTimeout: 30 * 1000, //90 seconds increased since login the app takes longer }, }, diff --git a/appwright/tests/mm-connect/connection-evm.spec.js b/appwright/tests/mm-connect/connection-evm.spec.js index e94ffab20ce6..601b393ec555 100644 --- a/appwright/tests/mm-connect/connection-evm.spec.js +++ b/appwright/tests/mm-connect/connection-evm.spec.js @@ -56,7 +56,7 @@ test.afterAll(async () => { await playgroundServer.stop(); }); -test('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Browser Playground', async ({ +test.skip('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Browser Playground', async ({ device, }) => { const platform = device.getPlatform?.() || 'android'; diff --git a/appwright/tests/mm-connect/connection-multichain.spec.js b/appwright/tests/mm-connect/connection-multichain.spec.js index 079b203e315b..863247413261 100644 --- a/appwright/tests/mm-connect/connection-multichain.spec.js +++ b/appwright/tests/mm-connect/connection-multichain.spec.js @@ -47,7 +47,7 @@ test.afterAll(async () => { await playgroundServer.stop(); }); -test('@metamask/connect-multichain - Connect via Multichain API to Local Browser Playground', async ({ +test.skip('@metamask/connect-multichain - Connect via Multichain API to Local Browser Playground', async ({ device, }) => { // Get platform-specific URL diff --git a/appwright/tests/mm-connect/connection-multiclient.spec.js b/appwright/tests/mm-connect/connection-multiclient.spec.js index 77f4765f812f..74fd2b3a2e21 100644 --- a/appwright/tests/mm-connect/connection-multiclient.spec.js +++ b/appwright/tests/mm-connect/connection-multiclient.spec.js @@ -90,7 +90,10 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients await AppwrightHelpers.withWebAction( device, async () => { - await BrowserPlaygroundDapp.tapConnect(); + // Note: the Solana wallet standard provider itself has an issue where it does not + // listen for wallet_sessionChanged events, so we need to use the Solana's connect button + // as the entrypoint for now. + await BrowserPlaygroundDapp.tapSolanaConnect(); }, DAPP_URL, ); @@ -98,13 +101,9 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients // Handle connection approval in MetaMask await AppwrightHelpers.withNativeAction(device, async () => { await AndroidScreenHelpers.tapOpenDeeplinkWithMetaMask(); - await DappConnectionModal.tapEditNetworksButton(); - await DappConnectionModal.tapNetworkButton('Solana'); - await DappConnectionModal.tapUpdateNetworksButton(); await DappConnectionModal.tapConnectButton(); }); - // Explicit pause to avoid navigating back too fast to the dapp await new Promise((resolve) => setTimeout(resolve, 1000)); await launchMobileBrowser(device); await new Promise((resolve) => setTimeout(resolve, 1000)); @@ -112,26 +111,20 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients await AppwrightHelpers.withWebAction( device, async () => { - // Verify connected by checking for scope cards section await BrowserPlaygroundDapp.assertMultichainConnected(true); - - // Verify at least one scope card is visible (eip155:1 is default) await BrowserPlaygroundDapp.assertScopeCardVisible('eip155:1'); - // Verify evm card is visible await BrowserPlaygroundDapp.assertConnected(true); await BrowserPlaygroundDapp.assertChainIdValue('0x1'); await BrowserPlaygroundDapp.assertActiveAccount(ACCOUNT_1_ADDRESS); - // Verify wagmi card is visible await BrowserPlaygroundDapp.assertWagmiConnected(true); await BrowserPlaygroundDapp.assertWagmiChainIdValue('1'); await BrowserPlaygroundDapp.assertWagmiActiveAccount(ACCOUNT_1_ADDRESS); - // NOTE: The solana wallet standard does not respond to wallet_sessionChanged events - // meaning that we must manually trigger the client to check if it is connected. - // Since we are, there will be no approval prompt necessary to accept. - // TODO + await BrowserPlaygroundDapp.assertSolanaConnected(true); + await BrowserPlaygroundDapp.assertSolanaActiveAccount(ACCOUNT_1_ADDRESS); + }, DAPP_URL, ); diff --git a/appwright/tests/mm-connect/connection-wagmi.spec.js b/appwright/tests/mm-connect/connection-wagmi.spec.js index fe8c88dd1a9e..58d51b0aa512 100644 --- a/appwright/tests/mm-connect/connection-wagmi.spec.js +++ b/appwright/tests/mm-connect/connection-wagmi.spec.js @@ -56,7 +56,7 @@ test.afterAll(async () => { await playgroundServer.stop(); }); -test('@metamask/connect-evm (wagmi) - Connect via Wagmi to Local Browser Playground', async ({ +test.skip('@metamask/connect-evm (wagmi) - Connect via Wagmi to Local Browser Playground', async ({ device, }) => { // Get platform-specific URL diff --git a/package.json b/package.json index b8cf9e0fcecc..ecc143e1a260 100644 --- a/package.json +++ b/package.json @@ -518,7 +518,7 @@ "@metamask/abi-utils": "^3.0.0", "@metamask/auto-changelog": "^5.3.0", "@metamask/browser-passworder": "^5.0.0", - "@metamask/browser-playground": "0.2.0", + "@metamask/browser-playground": "file:../connect-monorepo/playground/browser-playground", "@metamask/build-utils": "^3.0.0", "@metamask/eslint-config-typescript": "^10.0.0", "@metamask/eslint-plugin-design-tokens": "^1.0.0", diff --git a/wdio/screen-objects/BrowserPlaygroundDapp.js b/wdio/screen-objects/BrowserPlaygroundDapp.js index eca5fe4449a7..946a0b54a840 100644 --- a/wdio/screen-objects/BrowserPlaygroundDapp.js +++ b/wdio/screen-objects/BrowserPlaygroundDapp.js @@ -181,6 +181,22 @@ class BrowserPlaygroundDapp { return this._getByTestId(`wagmi-btn-switch-chain-${chainId}`); } + // ============================================================ + // SOLANA CARD SELECTORS + // ============================================================ + + get solanaCard() { + return this._getByTestId('solana-card'); + } + + get solanaConnectButton() { + return this._getByTestId('app-btn-connect-solana'); + } + + get solanaAddressContainer() { + return this._getByTestId('solana-address-container'); + } + // ============================================================ // MULTICHAIN / SCOPE CARD SELECTORS // ============================================================ @@ -295,6 +311,16 @@ class BrowserPlaygroundDapp { await AppwrightGestures.typeText(element, message); } + // ============================================================ + // SOLANA ACTIONS + // ============================================================ + + async tapSolanaConnect() { + if (!this._device) return; + const element = await this.solanaConnectButton; + await AppwrightGestures.tap(element); + } + // ============================================================ // MULTICHAIN ACTIONS // ============================================================ @@ -454,6 +480,37 @@ class BrowserPlaygroundDapp { } } + // ============================================================ + // SOLANA ASSERTIONS + // ============================================================ + + /** + * Assert Solana is connected by checking for the solana address container + * @param {boolean} isConnected - Expected connection state + */ + async assertSolanaConnected(isConnected = true) { + if (!this._device) return; + + if (isConnected) { + const solanaCard = await this.solanaCard; + await expect(solanaCard).toBeVisible({ timeout: 10000 }); + } else { + const solanaConnectButton = await this.solanaConnectButton; + await expect(solanaConnectButton).toBeVisible({ timeout: 10000 }); + } + } + + /** + * Assert Solana active account address + * @param {string} expectedAccount - Expected account address + */ + async assertSolanaActiveAccount(expectedAddress) { + if (!this._device) return; + const addressElement = await this.solanaAddressContainer; + const text = await addressElement.getText(); + expect(text.toLowerCase()).toContain(expectedAddress); + } + // ============================================================ // MULTICHAIN ASSERTIONS // ============================================================ diff --git a/wdio/screen-objects/Modals/DappConnectionModal.js b/wdio/screen-objects/Modals/DappConnectionModal.js index 0fa365058a95..105a58ece732 100644 --- a/wdio/screen-objects/Modals/DappConnectionModal.js +++ b/wdio/screen-objects/Modals/DappConnectionModal.js @@ -153,6 +153,7 @@ class DappConnectionModal { } const element = await this.getNetworkButton(networkName); + await AppwrightGestures.scrollIntoView(this.device, element); await AppwrightGestures.tap(element) } diff --git a/yarn.lock b/yarn.lock index 6194ca2111d4..3cecc3c103b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7982,10 +7982,10 @@ __metadata: languageName: node linkType: hard -"@metamask/browser-playground@npm:0.2.0": +"@metamask/browser-playground@file:../connect-monorepo/playground/browser-playground::locator=metamask%40workspace%3A.": version: 0.2.0 - resolution: "@metamask/browser-playground@npm:0.2.0" - checksum: 10/4c06720cce0925cf6e6a4c7287dee64976037b3f772c933c5659dd16538658fd171d9377ab1e7790c89f08db72edccd37c6d477a72a75f91f72ee81dd9322eb6 + resolution: "@metamask/browser-playground@file:../connect-monorepo/playground/browser-playground#../connect-monorepo/playground/browser-playground::hash=66de85&locator=metamask%40workspace%3A." + checksum: 10/0925ed3e0d671c00e7d7c9730c71123e083e578fe27092996be44656f951603f8d4fdf8765ce3b77e5fe9b1f5bfe37343bb089a0e80beb8f3b1f98a114b3f829 languageName: node linkType: hard @@ -35125,7 +35125,7 @@ __metadata: "@metamask/bridge-controller": "npm:^65.2.0" "@metamask/bridge-status-controller": "npm:^66.0.0" "@metamask/browser-passworder": "npm:^5.0.0" - "@metamask/browser-playground": "npm:0.2.0" + "@metamask/browser-playground": "file:../connect-monorepo/playground/browser-playground" "@metamask/build-utils": "npm:^3.0.0" "@metamask/chain-agnostic-permission": "npm:^1.3.0" "@metamask/connectivity-controller": "npm:^0.1.0" From a50aae3e7444b4cbc4f04a655040f39121d212f2 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Fri, 13 Feb 2026 14:06:59 -0800 Subject: [PATCH 04/16] Fix solana account not syncing --- .../mm-connect/connection-multiclient.spec.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/appwright/tests/mm-connect/connection-multiclient.spec.js b/appwright/tests/mm-connect/connection-multiclient.spec.js index 74fd2b3a2e21..9a6c4d90deb3 100644 --- a/appwright/tests/mm-connect/connection-multiclient.spec.js +++ b/appwright/tests/mm-connect/connection-multiclient.spec.js @@ -20,6 +20,8 @@ import { setupAdbReverse, cleanupAdbReverse, } from './utils.js'; +import AppwrightGestures from '../../../tests/framework/AppwrightGestures.ts'; +import AccountListComponent from '../../../wdio/screen-objects/AccountListComponent.js'; const DAPP_NAME = 'MetaMask MultiChain API Test Dapp'; const DAPP_PORT = 8090; @@ -62,6 +64,7 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients BrowserPlaygroundDapp.device = device; AndroidScreenHelpers.device = device; DappConnectionModal.device = device; + AccountListComponent.device = device; await device.webDriverClient.updateSettings({ waitForIdleTimeout: 100, @@ -76,8 +79,20 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients await AppwrightHelpers.withNativeAction(device, async () => { await login(device); await WalletMainScreen.isMainWalletViewVisible(); + + // Cycle the app to ensure solana accounts are created + await AppwrightGestures.terminateApp(device); + await AppwrightGestures.activateApp(device); + await login(device); + await WalletMainScreen.isMainWalletViewVisible(); + await WalletMainScreen.tapIdenticon(); + await AccountListComponent.isComponentDisplayed(); + await AccountListComponent.waitForSyncingToComplete(); + await AccountListComponent.tapOnAccountByName('Account 1'); + await launchMobileBrowser(device); await navigateToDapp(device, DAPP_URL, DAPP_NAME); + }); await new Promise((resolve) => setTimeout(resolve, 5000)); From 1a1b5b41c0f72333454e6b5923c8513a18240d80 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Fri, 13 Feb 2026 14:07:14 -0800 Subject: [PATCH 05/16] Fix solana assertion --- .../mm-connect/connection-multiclient.spec.js | 14 +++++++++----- wdio/screen-objects/BrowserPlaygroundDapp.js | 10 ++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/appwright/tests/mm-connect/connection-multiclient.spec.js b/appwright/tests/mm-connect/connection-multiclient.spec.js index 9a6c4d90deb3..e344d0249e24 100644 --- a/appwright/tests/mm-connect/connection-multiclient.spec.js +++ b/appwright/tests/mm-connect/connection-multiclient.spec.js @@ -27,7 +27,8 @@ const DAPP_NAME = 'MetaMask MultiChain API Test Dapp'; const DAPP_PORT = 8090; // NOTE: This test requires the testing SRP to be used -const ACCOUNT_1_ADDRESS = '0x19a7Ad8256ab119655f1D758348501d598fC1C94'; +const ACCOUNT_1_EVM_ADDRESS = '0x19a7Ad8256ab119655f1D758348501d598fC1C94'; +const ACCOUNT_1_SOLANA_ADDRESS = '6fr9gpqbsszm6snzsjubu91jwxeduhwnvnkwxqksfwcz'; // Create the playground server using the shared framework const playgroundServer = new DappServer({ @@ -131,15 +132,14 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients await BrowserPlaygroundDapp.assertConnected(true); await BrowserPlaygroundDapp.assertChainIdValue('0x1'); - await BrowserPlaygroundDapp.assertActiveAccount(ACCOUNT_1_ADDRESS); + await BrowserPlaygroundDapp.assertActiveAccount(ACCOUNT_1_EVM_ADDRESS); await BrowserPlaygroundDapp.assertWagmiConnected(true); await BrowserPlaygroundDapp.assertWagmiChainIdValue('1'); - await BrowserPlaygroundDapp.assertWagmiActiveAccount(ACCOUNT_1_ADDRESS); + await BrowserPlaygroundDapp.assertWagmiActiveAccount(ACCOUNT_1_EVM_ADDRESS); await BrowserPlaygroundDapp.assertSolanaConnected(true); - await BrowserPlaygroundDapp.assertSolanaActiveAccount(ACCOUNT_1_ADDRESS); - + await BrowserPlaygroundDapp.assertSolanaActiveAccount(ACCOUNT_1_SOLANA_ADDRESS); }, DAPP_URL, ); @@ -151,6 +151,10 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients await AppwrightHelpers.withWebAction( device, async () => { + // Note: the Solana wallet standard provider itself has an issue where it does not + // listen for wallet_sessionChanged events, so we need to use the Solana's disconnect button + // to ensure the solana react hook state is reset correctly. + await BrowserPlaygroundDapp.tapSolanaDisconnect(); await BrowserPlaygroundDapp.tapDisconnect(); }, DAPP_URL, diff --git a/wdio/screen-objects/BrowserPlaygroundDapp.js b/wdio/screen-objects/BrowserPlaygroundDapp.js index 946a0b54a840..528afe451f48 100644 --- a/wdio/screen-objects/BrowserPlaygroundDapp.js +++ b/wdio/screen-objects/BrowserPlaygroundDapp.js @@ -193,6 +193,10 @@ class BrowserPlaygroundDapp { return this._getByTestId('app-btn-connect-solana'); } + get solanaDisconnectButton() { + return this._getByTestId('solana-btn-disconnect'); + } + get solanaAddressContainer() { return this._getByTestId('solana-address-container'); } @@ -321,6 +325,12 @@ class BrowserPlaygroundDapp { await AppwrightGestures.tap(element); } + async tapSolanaDisconnect() { + if (!this._device) return; + const element = await this.solanaDisconnectButton; + await AppwrightGestures.tap(element); + } + // ============================================================ // MULTICHAIN ACTIONS // ============================================================ From e79eaab9abcd90a3cd88857317a89788abbe4a72 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Fri, 13 Feb 2026 15:54:41 -0800 Subject: [PATCH 06/16] Update specs --- .../mm-connect/connection-multiclient.spec.js | 108 +++++++++++++++++- wdio/screen-objects/BrowserPlaygroundDapp.js | 22 +++- yarn.lock | 4 +- 3 files changed, 128 insertions(+), 6 deletions(-) diff --git a/appwright/tests/mm-connect/connection-multiclient.spec.js b/appwright/tests/mm-connect/connection-multiclient.spec.js index e344d0249e24..85afeea83b40 100644 --- a/appwright/tests/mm-connect/connection-multiclient.spec.js +++ b/appwright/tests/mm-connect/connection-multiclient.spec.js @@ -93,7 +93,6 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients await launchMobileBrowser(device); await navigateToDapp(device, DAPP_URL, DAPP_NAME); - }); await new Promise((resolve) => setTimeout(resolve, 5000)); @@ -129,6 +128,57 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients async () => { await BrowserPlaygroundDapp.assertMultichainConnected(true); await BrowserPlaygroundDapp.assertScopeCardVisible('eip155:1'); + await BrowserPlaygroundDapp.assertScopeCardVisible( + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + ); + + await BrowserPlaygroundDapp.assertConnected(true); + await BrowserPlaygroundDapp.assertChainIdValue('0x1'); + await BrowserPlaygroundDapp.assertActiveAccount(ACCOUNT_1_EVM_ADDRESS); + + await BrowserPlaygroundDapp.assertWagmiConnected(true); + await BrowserPlaygroundDapp.assertWagmiChainIdValue('1'); + await BrowserPlaygroundDapp.assertWagmiActiveAccount( + ACCOUNT_1_EVM_ADDRESS, + ); + + await BrowserPlaygroundDapp.assertSolanaConnected(true); + await BrowserPlaygroundDapp.assertSolanaActiveAccount( + ACCOUNT_1_SOLANA_ADDRESS, + ); + + // Disconnect EVM + await BrowserPlaygroundDapp.tapWagmiDisconnect(); + + await BrowserPlaygroundDapp.assertMultichainConnected(true); + await BrowserPlaygroundDapp.assertScopeCardNotVisible('eip155:1'); + await BrowserPlaygroundDapp.assertScopeCardVisible( + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + ); + + await BrowserPlaygroundDapp.assertConnected(false); + await BrowserPlaygroundDapp.assertWagmiConnected(false); + await BrowserPlaygroundDapp.assertSolanaConnected(true); + + // Reconnect EVM + await BrowserPlaygroundDapp.tapConnectWagmi(); + }, + DAPP_URL, + ); + + await AppwrightHelpers.withNativeAction(device, async () => { + await AndroidScreenHelpers.tapOpenDeeplinkWithMetaMask(); + await DappConnectionModal.tapConnectButton(); + }); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + await launchMobileBrowser(device); + await new Promise((resolve) => setTimeout(resolve, 1000)); + + await AppwrightHelpers.withWebAction( + device, + async () => { + await BrowserPlaygroundDapp.assertScopeCardVisible('eip155:1'); await BrowserPlaygroundDapp.assertConnected(true); await BrowserPlaygroundDapp.assertChainIdValue('0x1'); @@ -136,10 +186,62 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients await BrowserPlaygroundDapp.assertWagmiConnected(true); await BrowserPlaygroundDapp.assertWagmiChainIdValue('1'); - await BrowserPlaygroundDapp.assertWagmiActiveAccount(ACCOUNT_1_EVM_ADDRESS); + await BrowserPlaygroundDapp.assertWagmiActiveAccount( + ACCOUNT_1_EVM_ADDRESS, + ); + // Make sure solana is still connected + await BrowserPlaygroundDapp.assertScopeCardVisible( + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + ); await BrowserPlaygroundDapp.assertSolanaConnected(true); - await BrowserPlaygroundDapp.assertSolanaActiveAccount(ACCOUNT_1_SOLANA_ADDRESS); + await BrowserPlaygroundDapp.assertSolanaActiveAccount( + ACCOUNT_1_SOLANA_ADDRESS, + ); + + // Disconnect Solana + await BrowserPlaygroundDapp.tapSolanaDisconnect(); + + await BrowserPlaygroundDapp.assertScopeCardNotVisible( + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + ); + await BrowserPlaygroundDapp.assertSolanaConnected(false); + + // Make sure EVM is still connected + await BrowserPlaygroundDapp.assertScopeCardVisible('eip155:1'); + await BrowserPlaygroundDapp.assertConnected(true); + await BrowserPlaygroundDapp.assertWagmiConnected(true); + + // Reconnect Solana + await BrowserPlaygroundDapp.tapConnectSolana(); + }, + DAPP_URL, + ); + + await AppwrightHelpers.withNativeAction(device, async () => { + await AndroidScreenHelpers.tapOpenDeeplinkWithMetaMask(); + await DappConnectionModal.tapConnectButton(); + }); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + await launchMobileBrowser(device); + await new Promise((resolve) => setTimeout(resolve, 1000)); + + await AppwrightHelpers.withWebAction( + device, + async () => { + await BrowserPlaygroundDapp.assertScopeCardVisible( + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + ); + await BrowserPlaygroundDapp.assertSolanaConnected(true); + await BrowserPlaygroundDapp.assertSolanaActiveAccount( + ACCOUNT_1_SOLANA_ADDRESS, + ); + + // Make sure EVM is still connected + await BrowserPlaygroundDapp.assertScopeCardVisible('eip155:1'); + await BrowserPlaygroundDapp.assertConnected(true); + await BrowserPlaygroundDapp.assertWagmiConnected(true); }, DAPP_URL, ); diff --git a/wdio/screen-objects/BrowserPlaygroundDapp.js b/wdio/screen-objects/BrowserPlaygroundDapp.js index 528afe451f48..f618d04d2a5b 100644 --- a/wdio/screen-objects/BrowserPlaygroundDapp.js +++ b/wdio/screen-objects/BrowserPlaygroundDapp.js @@ -125,6 +125,10 @@ class BrowserPlaygroundDapp { return this._getByTestId('app-btn-connect-wagmi'); } + get wagmiDisconnectButton() { + return this._getByTestId('wagmi-btn-disconnect'); + } + get wagmiCard() { return this._getByTestId('wagmi-card'); } @@ -219,7 +223,7 @@ class BrowserPlaygroundDapp { */ getScopeCard(scope) { // The scope card ID uses dashes instead of colons (e.g., 'eip155-1' not 'eip155:1') - const escapedScope = scope.replace(/:/g, '-'); + const escapedScope = scope.toLowerCase().replace(/:/g, '-'); return this._getByTestId(`scope-card-${escapedScope}`); } @@ -291,6 +295,12 @@ class BrowserPlaygroundDapp { await AppwrightGestures.tap(element); } + async tapWagmiDisconnect() { + if (!this._device) return; + const element = await this.wagmiDisconnectButton; + await AppwrightGestures.tap(element); + } + async tapWagmiSignMessage() { if (!this._device) return; const element = await this.wagmiSignMessageButton; @@ -550,6 +560,16 @@ class BrowserPlaygroundDapp { const scopeCard = await this.getScopeCard(scope); await expect(scopeCard).toBeVisible({ timeout: 10000 }); } + + /** + * Assert a specific scope card is notvisible + * @param {string} scope - The CAIP-2 scope (e.g., 'eip155:1') + */ + async assertScopeCardNotVisible(scope) { + if (!this._device) return; + const scopeCard = await this.getScopeCard(scope); + await expect(scopeCard).not.toBeVisible({ timeout: 10000 }); + } } export default new BrowserPlaygroundDapp(); diff --git a/yarn.lock b/yarn.lock index 3cecc3c103b8..ac2eac6626c2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7984,8 +7984,8 @@ __metadata: "@metamask/browser-playground@file:../connect-monorepo/playground/browser-playground::locator=metamask%40workspace%3A.": version: 0.2.0 - resolution: "@metamask/browser-playground@file:../connect-monorepo/playground/browser-playground#../connect-monorepo/playground/browser-playground::hash=66de85&locator=metamask%40workspace%3A." - checksum: 10/0925ed3e0d671c00e7d7c9730c71123e083e578fe27092996be44656f951603f8d4fdf8765ce3b77e5fe9b1f5bfe37343bb089a0e80beb8f3b1f98a114b3f829 + resolution: "@metamask/browser-playground@file:../connect-monorepo/playground/browser-playground#../connect-monorepo/playground/browser-playground::hash=8de179&locator=metamask%40workspace%3A." + checksum: 10/5e6211b2902c4f87c67e59a78ad58df202d0bd423a2b52e01dc3b26ae7c759b1f29553e8454a4e2659732fb81cb42600a61e67e0016ea9e38b8b85cc323abbb1 languageName: node linkType: hard From 04cb82665f6f050c60eab75f9b0b221029794b42 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Tue, 17 Feb 2026 10:10:23 -0800 Subject: [PATCH 07/16] Fix spec --- appwright/tests/mm-connect/connection-multiclient.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appwright/tests/mm-connect/connection-multiclient.spec.js b/appwright/tests/mm-connect/connection-multiclient.spec.js index 85afeea83b40..f0c1d3c4ce2b 100644 --- a/appwright/tests/mm-connect/connection-multiclient.spec.js +++ b/appwright/tests/mm-connect/connection-multiclient.spec.js @@ -213,7 +213,7 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients await BrowserPlaygroundDapp.assertWagmiConnected(true); // Reconnect Solana - await BrowserPlaygroundDapp.tapConnectSolana(); + await BrowserPlaygroundDapp.tapSolanaConnect(); }, DAPP_URL, ); From a4357f184d4c70d1c17355ab0e77e56b367e36bb Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Thu, 19 Feb 2026 10:36:02 -0800 Subject: [PATCH 08/16] Add solana sign selectors --- wdio/screen-objects/BrowserPlaygroundDapp.js | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/wdio/screen-objects/BrowserPlaygroundDapp.js b/wdio/screen-objects/BrowserPlaygroundDapp.js index f618d04d2a5b..a3a18fa2e1a4 100644 --- a/wdio/screen-objects/BrowserPlaygroundDapp.js +++ b/wdio/screen-objects/BrowserPlaygroundDapp.js @@ -205,6 +205,14 @@ class BrowserPlaygroundDapp { return this._getByTestId('solana-address-container'); } + get solanaSignMessageButton() { + return this._getByTestId('solana-btn-sign-message'); + } + + get solanaSignedMessageResult() { + return this._getByTestId('solana-signed-message-result'); + } + // ============================================================ // MULTICHAIN / SCOPE CARD SELECTORS // ============================================================ @@ -341,6 +349,12 @@ class BrowserPlaygroundDapp { await AppwrightGestures.tap(element); } + async tapSolanaSignMessage() { + if (!this._device) return; + const element = await this.solanaSignMessageButton; + await AppwrightGestures.tap(element); + } + // ============================================================ // MULTICHAIN ACTIONS // ============================================================ @@ -531,6 +545,18 @@ class BrowserPlaygroundDapp { expect(text.toLowerCase()).toContain(expectedAddress); } + /** + * Assert Solana signed message result contains expected value + * @param {string} expectedValue - Expected signature or part of the signed message result + */ + async assertSolanaSignedMessageResult(expectedValue) { + if (!this._device) return; + const resultElement = await this.solanaSignedMessageResult; + await expect(resultElement).toBeVisible({ timeout: 10000 }); + const text = await resultElement.getText(); + expect(text).toContain(expectedValue); + } + // ============================================================ // MULTICHAIN ASSERTIONS // ============================================================ From baf3718ed16bc7ef4c1582a59f3c4d1b619f8510 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Thu, 19 Feb 2026 11:26:38 -0800 Subject: [PATCH 09/16] reduce initial delay from 5s to 1s --- tests/performance/mm-connect/connection-evm.spec.js | 2 +- tests/performance/mm-connect/connection-multiclient.spec.js | 2 +- tests/performance/mm-connect/connection-wagmi.spec.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/performance/mm-connect/connection-evm.spec.js b/tests/performance/mm-connect/connection-evm.spec.js index e1ceac69c6ab..0b1ca084d046 100644 --- a/tests/performance/mm-connect/connection-evm.spec.js +++ b/tests/performance/mm-connect/connection-evm.spec.js @@ -77,7 +77,7 @@ test.skip('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Br await launchMobileBrowser(device); await navigateToDapp(device, DAPP_URL, DAPP_NAME); }); - await new Promise((resolve) => setTimeout(resolve, 5000)); + await new Promise((resolve) => setTimeout(resolve, 1000)); await AppwrightHelpers.withWebAction( device, diff --git a/tests/performance/mm-connect/connection-multiclient.spec.js b/tests/performance/mm-connect/connection-multiclient.spec.js index f0c1d3c4ce2b..36d6444a1086 100644 --- a/tests/performance/mm-connect/connection-multiclient.spec.js +++ b/tests/performance/mm-connect/connection-multiclient.spec.js @@ -95,7 +95,7 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients await navigateToDapp(device, DAPP_URL, DAPP_NAME); }); - await new Promise((resolve) => setTimeout(resolve, 5000)); + await new Promise((resolve) => setTimeout(resolve, 1000)); // // Connect via Multichain API diff --git a/tests/performance/mm-connect/connection-wagmi.spec.js b/tests/performance/mm-connect/connection-wagmi.spec.js index 28fc1d9dfbc8..034f3fdeed5b 100644 --- a/tests/performance/mm-connect/connection-wagmi.spec.js +++ b/tests/performance/mm-connect/connection-wagmi.spec.js @@ -85,7 +85,7 @@ test.skip('@metamask/connect-evm (wagmi) - Connect via Wagmi to Local Browser Pl await navigateToDapp(device, DAPP_URL, DAPP_NAME); }); - await new Promise((resolve) => setTimeout(resolve, 5000)); + await new Promise((resolve) => setTimeout(resolve, 1000)); // // Connect via WAGMI From 48cd983e63f72de952a936b5636d7b04f5d7ce09 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Thu, 19 Feb 2026 11:47:40 -0800 Subject: [PATCH 10/16] Add SolanaSignModal --- wdio/screen-objects/Modals/SolanaSignModal.js | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 wdio/screen-objects/Modals/SolanaSignModal.js diff --git a/wdio/screen-objects/Modals/SolanaSignModal.js b/wdio/screen-objects/Modals/SolanaSignModal.js new file mode 100644 index 000000000000..9540982b5e68 --- /dev/null +++ b/wdio/screen-objects/Modals/SolanaSignModal.js @@ -0,0 +1,35 @@ +import AppwrightSelectors from '../../../tests/framework/AppwrightSelectors'; +import AppwrightGestures from '../../../tests/framework/AppwrightGestures'; + +class SolanaSignModal { + constructor() {} + + get device() { + return this._device; + } + + set device(device) { + this._device = device; + } + + get confirmButton() { + if (!this._device) { + return null; + } + + if (AppwrightSelectors.isAndroid(this._device)) { + return AppwrightSelectors.getElementByXpath(this._device, '//android.widget.TextView[@text="Confirm"]'); + } + } + + async tapConfirmButton() { + if (!this._device) { + return; + } + + const element = await this.confirmButton; + await AppwrightGestures.tap(element) + } +} + +export default new SolanaSignModal(); From 4471ff925eba4766ecbd708c2685fa0ceacd014e Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Thu, 19 Feb 2026 11:48:23 -0800 Subject: [PATCH 11/16] Add sign interactions --- .../mm-connect/connection-multiclient.spec.js | 178 +++++++++++++++++- 1 file changed, 172 insertions(+), 6 deletions(-) diff --git a/tests/performance/mm-connect/connection-multiclient.spec.js b/tests/performance/mm-connect/connection-multiclient.spec.js index 36d6444a1086..c7597711eb23 100644 --- a/tests/performance/mm-connect/connection-multiclient.spec.js +++ b/tests/performance/mm-connect/connection-multiclient.spec.js @@ -1,14 +1,17 @@ import { test } from 'appwright'; -import { login } from '../../utils/Flows.js'; +import { login } from '../../framework/utils/Flows.js'; import { launchMobileBrowser, + switchToMobileBrowser, navigateToDapp, -} from '../../utils/MobileBrowser.js'; +} from '../../framework/utils/MobileBrowser.js'; import WalletMainScreen from '../../../wdio/screen-objects/WalletMainScreen.js'; import BrowserPlaygroundDapp from '../../../wdio/screen-objects/BrowserPlaygroundDapp.js'; import AndroidScreenHelpers from '../../../wdio/screen-objects/Native/Android.js'; import DappConnectionModal from '../../../wdio/screen-objects/Modals/DappConnectionModal.js'; +import SignModal from '../../../wdio/screen-objects/Modals/SignModal.js'; +import SolanaSignModal from '../../../wdio/screen-objects/Modals/SolanaSignModal.js'; import AppwrightHelpers from '../../../tests/framework/AppwrightHelpers.js'; import { DappServer, @@ -30,6 +33,9 @@ const DAPP_PORT = 8090; const ACCOUNT_1_EVM_ADDRESS = '0x19a7Ad8256ab119655f1D758348501d598fC1C94'; const ACCOUNT_1_SOLANA_ADDRESS = '6fr9gpqbsszm6snzsjubu91jwxeduhwnvnkwxqksfwcz'; +const ACCOUNT_1_SOLANA_SIGNED_MESSAGE_RESULT = 'TnB0MoNjYOTozLwKcZskdyzYszWHetTLcDskffjqLgQ9nYUbM47JySKpEyTZtA48CdMsPK+erAeId6ayzBoJBQ=='; + + // Create the playground server using the shared framework const playgroundServer = new DappServer({ dappCounter: 0, @@ -65,6 +71,8 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients BrowserPlaygroundDapp.device = device; AndroidScreenHelpers.device = device; DappConnectionModal.device = device; + SignModal.device = device; + SolanaSignModal.device = device; AccountListComponent.device = device; await device.webDriverClient.updateSettings({ @@ -89,7 +97,6 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients await WalletMainScreen.tapIdenticon(); await AccountListComponent.isComponentDisplayed(); await AccountListComponent.waitForSyncingToComplete(); - await AccountListComponent.tapOnAccountByName('Account 1'); await launchMobileBrowser(device); await navigateToDapp(device, DAPP_URL, DAPP_NAME); @@ -120,7 +127,7 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients }); await new Promise((resolve) => setTimeout(resolve, 1000)); - await launchMobileBrowser(device); + await switchToMobileBrowser(device); await new Promise((resolve) => setTimeout(resolve, 1000)); await AppwrightHelpers.withWebAction( @@ -141,11 +148,72 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients await BrowserPlaygroundDapp.assertWagmiActiveAccount( ACCOUNT_1_EVM_ADDRESS, ); + // Verify wagmi personal sign works when wagmi is connected + await BrowserPlaygroundDapp.typeWagmiSignMessage('Hello MetaMask'); + await BrowserPlaygroundDapp.tapWagmiSignMessage(); + }, + DAPP_URL, + ); + + await AppwrightHelpers.withNativeAction(device, async () => { + await AndroidScreenHelpers.tapOpenDeeplinkWithMetaMask(); + await SignModal.tapConfirmButton(); + }); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + await switchToMobileBrowser(device); + await new Promise((resolve) => setTimeout(resolve, 1000)); + + await AppwrightHelpers.withWebAction( + device, + async () => { + await BrowserPlaygroundDapp.assertWagmiSignatureResult('0x'); await BrowserPlaygroundDapp.assertSolanaConnected(true); await BrowserPlaygroundDapp.assertSolanaActiveAccount( ACCOUNT_1_SOLANA_ADDRESS, ); + // Verify solana sign works when solana is connected + await BrowserPlaygroundDapp.tapSolanaSignMessage(); + }, + DAPP_URL, + ); + + await AppwrightHelpers.withNativeAction(device, async () => { + await AndroidScreenHelpers.tapOpenDeeplinkWithMetaMask(); + await SolanaSignModal.tapConfirmButton(); + }); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + await switchToMobileBrowser(device); + await new Promise((resolve) => setTimeout(resolve, 1000)); + + await AppwrightHelpers.withWebAction( + device, + async () => { + await BrowserPlaygroundDapp.assertSolanaSignedMessageResult(ACCOUNT_1_SOLANA_SIGNED_MESSAGE_RESULT); + + // Test EVM sign (legacy personal sign) when EVM is connected + await BrowserPlaygroundDapp.tapPersonalSign(); + }, + DAPP_URL, + ); + + await AppwrightHelpers.withNativeAction(device, async () => { + await AndroidScreenHelpers.tapOpenDeeplinkWithMetaMask(); + await SignModal.tapConfirmButton(); + }); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + await switchToMobileBrowser(device); + await new Promise((resolve) => setTimeout(resolve, 1000)); + + await AppwrightHelpers.withWebAction( + device, + async () => { + await BrowserPlaygroundDapp.assertResponseValue( + '0x361c13288b4ab02d50974efddf9e4e7ca651b81c298b614be908c4754abb1dd8328224645a1a8d0fab561c4b855c7bdcebea15db5ae8d1778a1ea791dbd05c2a1b', + ); // Disconnect EVM await BrowserPlaygroundDapp.tapWagmiDisconnect(); @@ -172,7 +240,7 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients }); await new Promise((resolve) => setTimeout(resolve, 1000)); - await launchMobileBrowser(device); + await switchToMobileBrowser(device); await new Promise((resolve) => setTimeout(resolve, 1000)); await AppwrightHelpers.withWebAction( @@ -189,6 +257,26 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients await BrowserPlaygroundDapp.assertWagmiActiveAccount( ACCOUNT_1_EVM_ADDRESS, ); + // Verify wagmi personal sign works when wagmi is connected + await BrowserPlaygroundDapp.typeWagmiSignMessage('Hello MetaMask'); + await BrowserPlaygroundDapp.tapWagmiSignMessage(); + }, + DAPP_URL, + ); + + await AppwrightHelpers.withNativeAction(device, async () => { + await AndroidScreenHelpers.tapOpenDeeplinkWithMetaMask(); + await SignModal.tapConfirmButton(); + }); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + await switchToMobileBrowser(device); + await new Promise((resolve) => setTimeout(resolve, 1000)); + + await AppwrightHelpers.withWebAction( + device, + async () => { + await BrowserPlaygroundDapp.assertWagmiSignatureResult('0x'); // Make sure solana is still connected await BrowserPlaygroundDapp.assertScopeCardVisible( @@ -198,6 +286,25 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients await BrowserPlaygroundDapp.assertSolanaActiveAccount( ACCOUNT_1_SOLANA_ADDRESS, ); + // Verify solana sign works when solana is connected + await BrowserPlaygroundDapp.tapSolanaSignMessage(); + }, + DAPP_URL, + ); + + await AppwrightHelpers.withNativeAction(device, async () => { + await AndroidScreenHelpers.tapOpenDeeplinkWithMetaMask(); + await SolanaSignModal.tapConfirmButton(); + }); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + await switchToMobileBrowser(device); + await new Promise((resolve) => setTimeout(resolve, 1000)); + + await AppwrightHelpers.withWebAction( + device, + async () => { + await BrowserPlaygroundDapp.assertSolanaSignedMessageResult(ACCOUNT_1_SOLANA_SIGNED_MESSAGE_RESULT); // Disconnect Solana await BrowserPlaygroundDapp.tapSolanaDisconnect(); @@ -211,6 +318,26 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients await BrowserPlaygroundDapp.assertScopeCardVisible('eip155:1'); await BrowserPlaygroundDapp.assertConnected(true); await BrowserPlaygroundDapp.assertWagmiConnected(true); + // Verify wagmi personal sign works when wagmi is connected + await BrowserPlaygroundDapp.typeWagmiSignMessage('Hello MetaMask'); + await BrowserPlaygroundDapp.tapWagmiSignMessage(); + }, + DAPP_URL, + ); + + await AppwrightHelpers.withNativeAction(device, async () => { + await AndroidScreenHelpers.tapOpenDeeplinkWithMetaMask(); + await SignModal.tapConfirmButton(); + }); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + await switchToMobileBrowser(device); + await new Promise((resolve) => setTimeout(resolve, 1000)); + + await AppwrightHelpers.withWebAction( + device, + async () => { + await BrowserPlaygroundDapp.assertWagmiSignatureResult('0x'); // Reconnect Solana await BrowserPlaygroundDapp.tapSolanaConnect(); @@ -224,7 +351,7 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients }); await new Promise((resolve) => setTimeout(resolve, 1000)); - await launchMobileBrowser(device); + await switchToMobileBrowser(device); await new Promise((resolve) => setTimeout(resolve, 1000)); await AppwrightHelpers.withWebAction( @@ -237,11 +364,50 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients await BrowserPlaygroundDapp.assertSolanaActiveAccount( ACCOUNT_1_SOLANA_ADDRESS, ); + // Verify solana sign works when solana is connected + await BrowserPlaygroundDapp.tapSolanaSignMessage(); + }, + DAPP_URL, + ); + + await AppwrightHelpers.withNativeAction(device, async () => { + await AndroidScreenHelpers.tapOpenDeeplinkWithMetaMask(); + await SolanaSignModal.tapConfirmButton(); + }); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + await switchToMobileBrowser(device); + await new Promise((resolve) => setTimeout(resolve, 1000)); + + await AppwrightHelpers.withWebAction( + device, + async () => { + await BrowserPlaygroundDapp.assertSolanaSignedMessageResult(ACCOUNT_1_SOLANA_SIGNED_MESSAGE_RESULT); // Make sure EVM is still connected await BrowserPlaygroundDapp.assertScopeCardVisible('eip155:1'); await BrowserPlaygroundDapp.assertConnected(true); await BrowserPlaygroundDapp.assertWagmiConnected(true); + // Verify wagmi personal sign works when wagmi is connected + await BrowserPlaygroundDapp.typeWagmiSignMessage('Hello MetaMask'); + await BrowserPlaygroundDapp.tapWagmiSignMessage(); + }, + DAPP_URL, + ); + + await AppwrightHelpers.withNativeAction(device, async () => { + await AndroidScreenHelpers.tapOpenDeeplinkWithMetaMask(); + await SignModal.tapConfirmButton(); + }); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + await switchToMobileBrowser(device); + await new Promise((resolve) => setTimeout(resolve, 1000)); + + await AppwrightHelpers.withWebAction( + device, + async () => { + await BrowserPlaygroundDapp.assertWagmiSignatureResult('0x'); }, DAPP_URL, ); From ffe732f8a5ae844a4324fd46455993290126a385 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Thu, 19 Feb 2026 15:17:30 -0800 Subject: [PATCH 12/16] Add concurrent connect test --- .../mm-connect/connection-multiclient.spec.js | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/performance/mm-connect/connection-multiclient.spec.js b/tests/performance/mm-connect/connection-multiclient.spec.js index c7597711eb23..66bcb416c5f4 100644 --- a/tests/performance/mm-connect/connection-multiclient.spec.js +++ b/tests/performance/mm-connect/connection-multiclient.spec.js @@ -408,10 +408,60 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients device, async () => { await BrowserPlaygroundDapp.assertWagmiSignatureResult('0x'); + + // Setup for concurrent connect test + + await BrowserPlaygroundDapp.tapSolanaDisconnect(); + await BrowserPlaygroundDapp.tapWagmiDisconnect(); + await BrowserPlaygroundDapp.tapSolanaConnect(); }, DAPP_URL, ); + await AppwrightHelpers.withNativeAction(device, async () => { + await AndroidScreenHelpers.tapOpenDeeplinkWithMetaMask(); + + // Purposely terminate the app without accepting the approval + await AppwrightGestures.terminateApp(device); + await AppwrightGestures.activateApp(device); + await login(device); + await WalletMainScreen.isMainWalletViewVisible(); + }); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + await switchToMobileBrowser(device); + await new Promise((resolve) => setTimeout(resolve, 1000)); + + await AppwrightHelpers.withWebAction( + device, + async () => { + await BrowserPlaygroundDapp.tapConnectWagmi(); + }, + DAPP_URL, + ); + + await AppwrightHelpers.withNativeAction(device, async () => { + await AndroidScreenHelpers.tapOpenDeeplinkWithMetaMask(); + await DappConnectionModal.tapConnectButton(); + }); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + await switchToMobileBrowser(device); + await new Promise((resolve) => setTimeout(resolve, 1000)); + + await AppwrightHelpers.withWebAction( + device, + async () => { + await BrowserPlaygroundDapp.assertScopeCardVisible('eip155:1'); + await BrowserPlaygroundDapp.assertConnected(true); + await BrowserPlaygroundDapp.assertWagmiConnected(true); + // Currently this is only possible if the solana connection attempt (the first one that initiated) was successful. + await BrowserPlaygroundDapp.assertSolanaConnected(true); + }, + DAPP_URL, + ); + + // // Cleanup - disconnect // From 554c78651c09df30b4eb2f0730228dbf77569235 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Thu, 19 Feb 2026 15:17:45 -0800 Subject: [PATCH 13/16] restore tests/appwright.config.ts --- tests/appwright.config.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/appwright.config.ts b/tests/appwright.config.ts index 200d2d5da47b..f5e9acc2640d 100644 --- a/tests/appwright.config.ts +++ b/tests/appwright.config.ts @@ -150,11 +150,11 @@ export default defineConfig({ platform: Platform.ANDROID, device: { provider: 'emulator', - name: 'Pixel_5_Pro_API_34', // this can be changed to your emulator name + name: 'Samsung Galaxy S24 Ultra', // this can be changed to your emulator name osVersion: '14', // this can be changed to your emulator version }, - buildPath: '/Users/jiexi/Downloads/metamask-main-rc-3607.apk', // Path to your .apk file - expectTimeout: 30 * 1000, //90 seconds increased since login the app takes longer + buildPath: 'PATH-TO-BUILD', // Path to your .apk file + expectTimeout: 30 * 1000, }, }, ], From 2da5c2685f487203dd2d8ef18bfdb979d90143a6 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Fri, 20 Feb 2026 14:34:54 -0800 Subject: [PATCH 14/16] Fix connect-evm test kind of --- .../mm-connect/connection-evm.spec.js | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/tests/performance/mm-connect/connection-evm.spec.js b/tests/performance/mm-connect/connection-evm.spec.js index 0b1ca084d046..05185644b0bb 100644 --- a/tests/performance/mm-connect/connection-evm.spec.js +++ b/tests/performance/mm-connect/connection-evm.spec.js @@ -2,9 +2,10 @@ import { test } from 'appwright'; import { login } from '../../framework/utils/Flows.js'; import { - launchMobileBrowser, + switchToMobileBrowser, navigateToDapp, refreshMobileBrowser, + launchMobileBrowser, } from '../../framework/utils/MobileBrowser.js'; import WalletMainScreen from '../../../wdio/screen-objects/WalletMainScreen.js'; import BrowserPlaygroundDapp from '../../../wdio/screen-objects/BrowserPlaygroundDapp.js'; @@ -74,6 +75,17 @@ test.skip('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Br await AppwrightHelpers.withNativeAction(device, async () => { await login(device); + await WalletMainScreen.isMainWalletViewVisible(); + + // Cycle the app to ensure all account groups are created + await AppwrightGestures.terminateApp(device); + await AppwrightGestures.activateApp(device); + await login(device); + await WalletMainScreen.isMainWalletViewVisible(); + await WalletMainScreen.tapIdenticon(); + await AccountListComponent.isComponentDisplayed(); + await AccountListComponent.waitForSyncingToComplete(); + await launchMobileBrowser(device); await navigateToDapp(device, DAPP_URL, DAPP_NAME); }); @@ -96,7 +108,7 @@ test.skip('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Br }); await new Promise((resolve) => setTimeout(resolve, 1000)); - await launchMobileBrowser(device); + await switchToMobileBrowser(device); await new Promise((resolve) => setTimeout(resolve, 1000)); await AppwrightHelpers.withWebAction( @@ -116,7 +128,7 @@ test.skip('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Br }); await new Promise((resolve) => setTimeout(resolve, 1000)); - await launchMobileBrowser(device); + await switchToMobileBrowser(device); await new Promise((resolve) => setTimeout(resolve, 1000)); await AppwrightHelpers.withWebAction( @@ -138,7 +150,7 @@ test.skip('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Br }); await new Promise((resolve) => setTimeout(resolve, 1000)); - await launchMobileBrowser(device); + await switchToMobileBrowser(device); await new Promise((resolve) => setTimeout(resolve, 1000)); await AppwrightHelpers.withWebAction( @@ -158,7 +170,7 @@ test.skip('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Br }); await new Promise((resolve) => setTimeout(resolve, 1000)); - await launchMobileBrowser(device); + await switchToMobileBrowser(device); await new Promise((resolve) => setTimeout(resolve, 1000)); await AppwrightHelpers.withWebAction( @@ -177,7 +189,7 @@ test.skip('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Br }); await new Promise((resolve) => setTimeout(resolve, 1000)); - await launchMobileBrowser(device); + await switchToMobileBrowser(device); await new Promise((resolve) => setTimeout(resolve, 1000)); await AppwrightHelpers.withWebAction( @@ -205,7 +217,7 @@ test.skip('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Br }); await new Promise((resolve) => setTimeout(resolve, 1000)); - await launchMobileBrowser(device); + await switchToMobileBrowser(device); await new Promise((resolve) => setTimeout(resolve, 1000)); await AppwrightHelpers.withWebAction( @@ -239,7 +251,7 @@ test.skip('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Br }); await new Promise((resolve) => setTimeout(resolve, 1000)); - await launchMobileBrowser(device); + await switchToMobileBrowser(device); await new Promise((resolve) => setTimeout(resolve, 1000)); await AppwrightHelpers.withWebAction( @@ -266,7 +278,7 @@ test.skip('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Br }); await new Promise((resolve) => setTimeout(resolve, 1000)); - await launchMobileBrowser(device); + await switchToMobileBrowser(device); await new Promise((resolve) => setTimeout(resolve, 1000)); await AppwrightHelpers.withWebAction( @@ -286,7 +298,7 @@ test.skip('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Br }); await new Promise((resolve) => setTimeout(resolve, 1000)); - await launchMobileBrowser(device); + await switchToMobileBrowser(device); await new Promise((resolve) => setTimeout(resolve, 1000)); await AppwrightHelpers.withWebAction( @@ -312,7 +324,7 @@ test.skip('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Br }); await new Promise((resolve) => setTimeout(resolve, 1000)); - await launchMobileBrowser(device); + await switchToMobileBrowser(device); await new Promise((resolve) => setTimeout(resolve, 1000)); await AppwrightHelpers.withNativeAction(device, async () => { @@ -345,7 +357,7 @@ test.skip('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Br }); await new Promise((resolve) => setTimeout(resolve, 1000)); - await launchMobileBrowser(device); + await switchToMobileBrowser(device); await new Promise((resolve) => setTimeout(resolve, 1000)); await AppwrightHelpers.withWebAction( From 3581eeff57f70fb946a6aaa646e5a09d185e15f9 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Fri, 20 Feb 2026 14:35:11 -0800 Subject: [PATCH 15/16] add extra reset to connect-multiclient --- tests/performance/mm-connect/connection-multiclient.spec.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/performance/mm-connect/connection-multiclient.spec.js b/tests/performance/mm-connect/connection-multiclient.spec.js index 66bcb416c5f4..bce85da42aff 100644 --- a/tests/performance/mm-connect/connection-multiclient.spec.js +++ b/tests/performance/mm-connect/connection-multiclient.spec.js @@ -97,6 +97,10 @@ test('@metamask/connect-multichain (multiple clients) - Connect multiple clients await WalletMainScreen.tapIdenticon(); await AccountListComponent.isComponentDisplayed(); await AccountListComponent.waitForSyncingToComplete(); + await AppwrightGestures.terminateApp(device); + await AppwrightGestures.activateApp(device); + await login(device); + await WalletMainScreen.isMainWalletViewVisible(); await launchMobileBrowser(device); await navigateToDapp(device, DAPP_URL, DAPP_NAME); From 662cc5a7f94646996017da6a0673128a3a4cce16 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Fri, 20 Feb 2026 14:37:21 -0800 Subject: [PATCH 16/16] add extra reset to connect-evm --- tests/performance/mm-connect/connection-evm.spec.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/performance/mm-connect/connection-evm.spec.js b/tests/performance/mm-connect/connection-evm.spec.js index 05185644b0bb..604fc9ccd425 100644 --- a/tests/performance/mm-connect/connection-evm.spec.js +++ b/tests/performance/mm-connect/connection-evm.spec.js @@ -85,6 +85,10 @@ test.skip('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Br await WalletMainScreen.tapIdenticon(); await AccountListComponent.isComponentDisplayed(); await AccountListComponent.waitForSyncingToComplete(); + await AppwrightGestures.terminateApp(device); + await AppwrightGestures.activateApp(device); + await login(device); + await WalletMainScreen.isMainWalletViewVisible(); await launchMobileBrowser(device); await navigateToDapp(device, DAPP_URL, DAPP_NAME);