diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 360e30a89303..0be8e2459f3a 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -3187,6 +3187,11 @@ export default class MetamaskController extends EventEmitter { setActiveNetwork: (networkConfigurationId) => { return this.networkController.setActiveNetwork(networkConfigurationId); }, + // Avoids returning the promise so that initial call to switch network + // doesn't block on the network lookup step + setActiveNetworkConfigurationId: (networkConfigurationId) => { + this.networkController.setActiveNetwork(networkConfigurationId); + }, setNetworkClientIdForDomain: (origin, networkClientId) => { return this.selectedNetworkController.setNetworkClientIdForDomain( origin, diff --git a/test/e2e/tests/request-queuing/ui.spec.js b/test/e2e/tests/request-queuing/ui.spec.js index 45429ad32263..d81252b0c4f5 100644 --- a/test/e2e/tests/request-queuing/ui.spec.js +++ b/test/e2e/tests/request-queuing/ui.spec.js @@ -80,6 +80,11 @@ async function selectDappClickSend(driver, dappUrl) { await driver.clickElement('#sendButton'); } +async function selectDappClickPersonalSign(driver, dappUrl) { + await driver.switchToWindowWithUrl(dappUrl); + await driver.clickElement('#personalSign'); +} + async function switchToNotificationPopoverValidateDetails( driver, expectedDetails, @@ -90,11 +95,13 @@ async function switchToNotificationPopoverValidateDetails( // Get UI details const networkPill = await driver.findElement( - '[data-testid="network-display"]', + // Differs between confirmation and signature + '[data-testid="network-display"], [data-testid="signature-request-network-display"]', ); const networkText = await networkPill.getText(); const originElement = await driver.findElement( - '.confirm-page-container-summary__origin bdi', + // Differs between confirmation and signature + '.confirm-page-container-summary__origin bdi, .request-signature__origin .chip__label', ); const originText = await originElement.getText(); @@ -165,7 +172,7 @@ async function validateBalanceAndActivity( } describe('Request-queue UI changes', function () { - it('UI should show network specific to domain @no-mmi', async function () { + it('should show network specific to domain @no-mmi', async function () { const port = 8546; const chainId = 1338; // 0x53a await withFixtures( @@ -355,4 +362,210 @@ describe('Request-queue UI changes', function () { }, ); }); + + it('should gracefully handle deleted network @no-mmi', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .withSelectedNetworkControllerPerDomain() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + dappOptions: { numberOfDapps: 2 }, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // Navigate to extension home screen + await driver.navigate(PAGES.HOME); + + // Open the first dapp + await openDappAndSwitchChain(driver, DAPP_URL); + + // Open the second dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1', 4); + + // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Ethereum Mainnet', + }); + + // Go to Settings, delete the first dapp's network + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); + await driver.clickElement('[data-testid="global-menu-settings"]'); + await driver.clickElement({ + css: '.tab-bar__tab__content__title', + text: 'Networks', + }); + await driver.clickElement({ + css: '.networks-tab__networks-list-name', + text: 'Localhost 8545', + }); + await driver.clickElement({ css: '.btn-danger', text: 'Delete' }); + await driver.clickElement({ + css: '.modal-container__footer-button', + text: 'Delete', + }); + + // Go back to first dapp, try an action, ensure deleted network doesn't block UI + // The current globally selected network, Ethereum Mainnet, should be used + await selectDappClickSend(driver, DAPP_URL); + await driver.delay(veryLargeDelayMs); + await switchToNotificationPopoverValidateDetails(driver, { + chainId: '0x1', + networkText: 'Ethereum Mainnet', + originText: DAPP_URL, + }); + }, + ); + }); + + it('should gracefully handle network connectivity failure for signatures @no-mmi', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .withSelectedNetworkControllerPerDomain() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + // This test intentionally quits Ganache while the extension is using it, causing + // PollingBlockTracker errors. These are expected. + ignoredConsoleErrors: ['PollingBlockTracker'], + dappOptions: { numberOfDapps: 2 }, + title: this.test.fullTitle(), + }, + async ({ driver, ganacheServer, secondaryGanacheServer }) => { + await unlockWallet(driver); + + // Navigate to extension home screen + await driver.navigate(PAGES.HOME); + + // Open the first dapp + await openDappAndSwitchChain(driver, DAPP_URL); + + // Open the second dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1', 4); + + // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Ethereum Mainnet', + }); + + // Kill ganache servers + await ganacheServer.quit(); + await secondaryGanacheServer[0].quit(); + + // Go back to first dapp, try an action, ensure network connection failure doesn't block UI + await selectDappClickPersonalSign(driver, DAPP_URL); + await driver.delay(veryLargeDelayMs); + await switchToNotificationPopoverValidateDetails(driver, { + chainId: '0x539', + networkText: 'Localhost 8545', + originText: DAPP_URL, + }); + }, + ); + }); + + it('should gracefully handle network connectivity failure for confirmations @no-mmi', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + // Presently confirmations take up to 10 seconds to display on a dead network + driverOptions: { timeOut: 30000 }, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .withSelectedNetworkControllerPerDomain() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + // This test intentionally quits Ganache while the extension is using it, causing + // PollingBlockTracker errors. These are expected. + ignoredConsoleErrors: ['PollingBlockTracker'], + dappOptions: { numberOfDapps: 2 }, + title: this.test.fullTitle(), + }, + async ({ driver, ganacheServer, secondaryGanacheServer }) => { + await unlockWallet(driver); + + // Navigate to extension home screen + await driver.navigate(PAGES.HOME); + + // Open the first dapp + await openDappAndSwitchChain(driver, DAPP_URL); + + // Open the second dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1', 4); + + // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Ethereum Mainnet', + }); + + // Kill ganache servers + await ganacheServer.quit(); + await secondaryGanacheServer[0].quit(); + + // Go back to first dapp, try an action, ensure network connection failure doesn't block UI + await selectDappClickSend(driver, DAPP_URL); + await switchToNotificationPopoverValidateDetails(driver, { + chainId: '0x539', + networkText: 'Localhost 8545', + originText: DAPP_URL, + }); + }, + ); + }); }); diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 186e59ccb8df..95f7f976f67f 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -2158,7 +2158,7 @@ export function automaticallySwitchNetwork( selectedTabOrigin: string, ): ThunkAction { return async (dispatch: MetaMaskReduxDispatch) => { - await dispatch(setActiveNetwork(networkClientIdForThisDomain)); + await setActiveNetworkConfigurationId(networkClientIdForThisDomain); await dispatch( setSwitchedNetworkDetails({ networkClientId: networkClientIdForThisDomain, @@ -2494,6 +2494,17 @@ export function setActiveNetwork( }; } +export async function setActiveNetworkConfigurationId( + networkConfigurationId: string, +): Promise { + log.debug( + `background.setActiveNetworkConfigurationId: ${networkConfigurationId}`, + ); + await submitRequestToBackground('setActiveNetworkConfigurationId', [ + networkConfigurationId, + ]); +} + export function rollbackToPreviousProvider(): ThunkAction< void, MetaMaskReduxState,