Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: UX: Multichain: Fix dead network problem when switching tabs #25425

Merged
merged 8 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
219 changes: 216 additions & 3 deletions test/e2e/tests/request-queuing/ui.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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();

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
});
},
);
});
});
13 changes: 12 additions & 1 deletion ui/store/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2158,7 +2158,7 @@ export function automaticallySwitchNetwork(
selectedTabOrigin: string,
): ThunkAction<void, MetaMaskReduxState, unknown, AnyAction> {
return async (dispatch: MetaMaskReduxDispatch) => {
await dispatch(setActiveNetwork(networkClientIdForThisDomain));
await setActiveNetworkConfigurationId(networkClientIdForThisDomain);
await dispatch(
setSwitchedNetworkDetails({
networkClientId: networkClientIdForThisDomain,
Expand Down Expand Up @@ -2494,6 +2494,17 @@ export function setActiveNetwork(
};
}

export async function setActiveNetworkConfigurationId(
networkConfigurationId: string,
): Promise<undefined> {
log.debug(
`background.setActiveNetworkConfigurationId: ${networkConfigurationId}`,
);
await submitRequestToBackground('setActiveNetworkConfigurationId', [
networkConfigurationId,
]);
}

export function rollbackToPreviousProvider(): ThunkAction<
void,
MetaMaskReduxState,
Expand Down