diff --git a/package.json b/package.json index 602befeb4ff3..ca83e479477a 100644 --- a/package.json +++ b/package.json @@ -498,7 +498,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/tests/performance/mm-connect/connection-evm.spec.js b/tests/performance/mm-connect/connection-evm.spec.js index 5e8fd33813fa..604fc9ccd425 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'; @@ -52,7 +53,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'; @@ -74,10 +75,25 @@ test('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Browser 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 AppwrightGestures.terminateApp(device); + await AppwrightGestures.activateApp(device); + await login(device); + await WalletMainScreen.isMainWalletViewVisible(); + 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, @@ -96,7 +112,7 @@ test('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Browser }); 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 +132,7 @@ test('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Browser }); 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 +154,7 @@ test('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Browser }); 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 +174,7 @@ test('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Browser }); 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 +193,7 @@ test('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Browser }); 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 +221,7 @@ test('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Browser }); 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 +255,7 @@ test('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Browser }); 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 +282,7 @@ test('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Browser }); 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 +302,7 @@ test('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Browser }); 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 +328,7 @@ test('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Browser }); 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 +361,7 @@ test('@metamask/connect-evm - Connect via EVM Legacy Connection to Local Browser }); await new Promise((resolve) => setTimeout(resolve, 1000)); - await launchMobileBrowser(device); + await switchToMobileBrowser(device); await new Promise((resolve) => setTimeout(resolve, 1000)); await AppwrightHelpers.withWebAction( diff --git a/tests/performance/mm-connect/connection-multichain.spec.js b/tests/performance/mm-connect/connection-multichain.spec.js index bd6ca90982ff..0c0818e955ed 100644 --- a/tests/performance/mm-connect/connection-multichain.spec.js +++ b/tests/performance/mm-connect/connection-multichain.spec.js @@ -44,7 +44,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 (use bs-local.com when running on BrowserStack Local tunnel) diff --git a/tests/performance/mm-connect/connection-multiclient.spec.js b/tests/performance/mm-connect/connection-multiclient.spec.js new file mode 100644 index 000000000000..bce85da42aff --- /dev/null +++ b/tests/performance/mm-connect/connection-multiclient.spec.js @@ -0,0 +1,484 @@ +import { test } from 'appwright'; + +import { login } from '../../framework/utils/Flows.js'; +import { + launchMobileBrowser, + switchToMobileBrowser, + navigateToDapp, +} 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, + DappVariants, + TestDapps, +} from '../../../tests/framework/index.ts'; +import { + getDappUrlForBrowser, + 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; + +// NOTE: This test requires the testing SRP to be used +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, + 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; + SignModal.device = device; + SolanaSignModal.device = device; + AccountListComponent.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(); + + // 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 AppwrightGestures.terminateApp(device); + await AppwrightGestures.activateApp(device); + await login(device); + await WalletMainScreen.isMainWalletViewVisible(); + + await launchMobileBrowser(device); + await navigateToDapp(device, DAPP_URL, DAPP_NAME); + }); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // + // Connect via Multichain API + // + + // Tap the Connect button (multichain API - default scopes) + 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 connect button + // as the entrypoint for now. + await BrowserPlaygroundDapp.tapSolanaConnect(); + }, + DAPP_URL, + ); + + // Handle connection approval in MetaMask + 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.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, + ); + // 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(); + + 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 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.assertChainIdValue('0x1'); + await BrowserPlaygroundDapp.assertActiveAccount(ACCOUNT_1_EVM_ADDRESS); + + await BrowserPlaygroundDapp.assertWagmiConnected(true); + await BrowserPlaygroundDapp.assertWagmiChainIdValue('1'); + 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( + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + ); + 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); + + // 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); + // 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(); + }, + 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( + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + ); + 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); + + // 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'); + + // 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 + // + + 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/tests/performance/mm-connect/connection-wagmi.spec.js b/tests/performance/mm-connect/connection-wagmi.spec.js index ae3676ec272c..034f3fdeed5b 100644 --- a/tests/performance/mm-connect/connection-wagmi.spec.js +++ b/tests/performance/mm-connect/connection-wagmi.spec.js @@ -52,7 +52,7 @@ test.afterAll(async () => { await playgroundServer.stop(); }); -test('@metamask/connect-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 @@ -85,7 +85,7 @@ test('@metamask/connect-wagmi - Connect via Wagmi to Local Browser Playground', await navigateToDapp(device, DAPP_URL, DAPP_NAME); }); - await new Promise((resolve) => setTimeout(resolve, 5000)); + await new Promise((resolve) => setTimeout(resolve, 1000)); // // Connect via WAGMI diff --git a/wdio/screen-objects/BrowserPlaygroundDapp.js b/wdio/screen-objects/BrowserPlaygroundDapp.js index eca5fe4449a7..a3a18fa2e1a4 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'); } @@ -181,6 +185,34 @@ 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 solanaDisconnectButton() { + return this._getByTestId('solana-btn-disconnect'); + } + + get solanaAddressContainer() { + 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 // ============================================================ @@ -199,7 +231,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}`); } @@ -271,6 +303,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; @@ -295,6 +333,28 @@ class BrowserPlaygroundDapp { await AppwrightGestures.typeText(element, message); } + // ============================================================ + // SOLANA ACTIONS + // ============================================================ + + async tapSolanaConnect() { + if (!this._device) return; + const element = await this.solanaConnectButton; + await AppwrightGestures.tap(element); + } + + async tapSolanaDisconnect() { + if (!this._device) return; + const element = await this.solanaDisconnectButton; + await AppwrightGestures.tap(element); + } + + async tapSolanaSignMessage() { + if (!this._device) return; + const element = await this.solanaSignMessageButton; + await AppwrightGestures.tap(element); + } + // ============================================================ // MULTICHAIN ACTIONS // ============================================================ @@ -454,6 +514,49 @@ 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); + } + + /** + * 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 // ============================================================ @@ -483,6 +586,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/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/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(); diff --git a/yarn.lock b/yarn.lock index d40d1f046bcd..6653c0c71fdf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8076,10 +8076,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=8de179&locator=metamask%40workspace%3A." + checksum: 10/5e6211b2902c4f87c67e59a78ad58df202d0bd423a2b52e01dc3b26ae7c759b1f29553e8454a4e2659732fb81cb42600a61e67e0016ea9e38b8b85cc323abbb1 languageName: node linkType: hard @@ -35472,7 +35472,7 @@ __metadata: "@metamask/bridge-controller": "npm:^66.1.1" "@metamask/bridge-status-controller": "npm:^66.0.2" "@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"