diff --git a/app/scripts/ui.js b/app/scripts/ui.js index 433624bcc939..eb88cbed7ec5 100644 --- a/app/scripts/ui.js +++ b/app/scripts/ui.js @@ -235,6 +235,24 @@ async function start() { } async function queryCurrentActiveTab(windowType) { + // Shims the activeTab for E2E test runs only if the + // "activeTabOrigin" querystring key=value is set + if (process.env.IN_TEST) { + const searchParams = new URLSearchParams(window.location.search); + const mockUrl = searchParams.get('activeTabOrigin'); + if (mockUrl) { + const { origin, protocol } = new URL(mockUrl); + const returnUrl = { + id: 'mock-site', + title: 'Mock Site', + url: mockUrl, + origin, + protocol, + }; + return returnUrl; + } + } + // At the time of writing we only have the `activeTab` permission which means // that this query will only succeed in the popup context (i.e. after a "browserAction") if (windowType !== ENVIRONMENT_TYPE_POPUP) { diff --git a/test/e2e/tests/request-queuing/ui.spec.js b/test/e2e/tests/request-queuing/ui.spec.js index 4c0aa8fea3e1..cea9e045ceed 100644 --- a/test/e2e/tests/request-queuing/ui.spec.js +++ b/test/e2e/tests/request-queuing/ui.spec.js @@ -1,5 +1,5 @@ const { strict: assert } = require('assert'); -const { Browser } = require('selenium-webdriver'); +const { Browser, until } = require('selenium-webdriver'); const FixtureBuilder = require('../../fixture-builder'); const { withFixtures, @@ -124,10 +124,16 @@ async function confirmTransaction(driver) { } async function switchToNetworkByName(driver, networkName) { - await driver.clickElement('[data-testid="network-display"]'); + await driver.clickElement('.mm-picker-network'); await driver.clickElement(`[data-testid="${networkName}"]`); } +async function openPopupWithActiveTabOrigin(driver, origin) { + await driver.openNewPage( + `${driver.extensionUrl}/${PAGES.POPUP}.html?activeTabOrigin=${origin}`, + ); +} + async function validateBalanceAndActivity( driver, expectedBalance, @@ -428,6 +434,178 @@ describe('Request-queue UI changes', function () { ); }); + it('should signal from UI to dapp the network change @no-mmi', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPreferencesControllerUseRequestQueueEnabled() + .withSelectedNetworkControllerPerDomain() + .build(), + ganacheOptions: defaultGanacheOptions, + title: this.test.fullTitle(), + driverOptions: { constrainWindowSize: true }, + }, + async ({ driver }) => { + // Navigate to extension home screen + await unlockWallet(driver); + + // Open the first dapp which starts on chain '0x539 + await openDappAndSwitchChain(driver, DAPP_URL); + + // Ensure the dapp starts on the correct network + await driver.wait( + until.elementTextContains( + await driver.findElement('#chainId'), + '0x539', + ), + ); + + // Open the popup with shimmed activeTabOrigin + await openPopupWithActiveTabOrigin(driver, DAPP_URL); + + // Switch to mainnet + await switchToNetworkByName(driver, 'Ethereum Mainnet'); + + // Switch back to the Dapp tab + await driver.switchToWindowWithUrl(DAPP_URL); + + // Check to make sure the dapp network changed + await driver.wait( + until.elementTextContains( + await driver.findElement('#chainId'), + '0x1', + ), + ); + }, + ); + }); + + it('should autoswitch networks to the last used network for domain', 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 }) => { + // Open fullscreen + await unlockWallet(driver); + + // Open the first dapp which starts on chain '0x539 + await openDappAndSwitchChain(driver, DAPP_URL); + + // Open tab 2, switch to Ethereum Mainnet + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1', 4); + + // Open the popup with shimmed activeTabOrigin + await openPopupWithActiveTabOrigin(driver, DAPP_URL); + + // Ensure network was reset to original + await driver.findElement({ + css: '.multichain-app-header__contents--avatar-network .mm-text', + text: 'Localhost 8545', + }); + + // Ensure toast is shown to the user + await driver.findElement({ + css: '.toast-text', + text: 'Localhost 8545 is now active on 127.0.0.1:8080', + }); + }, + ); + }); + + it('should autoswitch networks when last confirmation from another network is rejected', 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(), + driverOptions: { constrainWindowSize: true }, + }, + async ({ driver }) => { + await unlockWallet(driver); + + // Open the first dapp which starts on chain '0x539 + await openDappAndSwitchChain(driver, DAPP_URL); + + // Open tab 2, switch to Ethereum Mainnet + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1', 4); + await driver.waitForSelector({ + css: '.error-message-text', + text: 'You are on the Ethereum Mainnet.', + }); + await driver.delay(veryLargeDelayMs); + + // Start a Send on Ethereum Mainnet + await driver.clickElement('#sendButton'); + await driver.delay(regularDelayMs); + + // Open the popup with shimmed activeTabOrigin + await openPopupWithActiveTabOrigin(driver, DAPP_URL); + + // Ensure the confirmation pill shows Ethereum Mainnet + await driver.waitForSelector({ + css: '[data-testid="network-display"]', + text: 'Ethereum Mainnet', + }); + + // Reject the confirmation + await driver.clickElement( + '[data-testid="page-container-footer-cancel"]', + ); + + // Wait for network to automatically change to localhost + await driver.waitForSelector({ + css: '.multichain-app-header__contents--avatar-network .mm-text', + text: 'Localhost 8545', + }); + + // Ensure toast is shown to the user + await driver.waitForSelector({ + css: '.toast-text', + text: 'Localhost 8545 is now active on 127.0.0.1:8080', + }); + }, + ); + }); + it('should gracefully handle network connectivity failure for signatures @no-mmi', async function () { const port = 8546; const chainId = 1338; @@ -471,7 +649,7 @@ describe('Request-queue UI changes', function () { await driver.switchToWindowWithTitle( WINDOW_TITLES.ExtensionInFullScreenView, ); - await driver.findElement({ + await driver.waitForSelector({ css: '[data-testid="network-display"]', text: 'Ethereum Mainnet', }); diff --git a/test/e2e/webdriver/chrome.js b/test/e2e/webdriver/chrome.js index edbf90179f5a..2e90eec7dd67 100644 --- a/test/e2e/webdriver/chrome.js +++ b/test/e2e/webdriver/chrome.js @@ -22,7 +22,12 @@ function getProxyServer(proxyPort) { * A wrapper around a {@code WebDriver} instance exposing Chrome-specific functionality */ class ChromeDriver { - static async build({ openDevToolsForTabs, port, proxyPort }) { + static async build({ + openDevToolsForTabs, + constrainWindowSize, + port, + proxyPort, + }) { const args = [ `--proxy-server=${getProxyServer(proxyPort)}`, // Set proxy in the way that doesn't interfere with Selenium Manager '--disable-features=OptimizationGuideModelDownloading,OptimizationHintsFetching,OptimizationTargetPrediction,OptimizationHints,NetworkTimeServiceQuerying', // Stop chrome from calling home so much (auto-downloads of AI models; time sync) @@ -42,6 +47,10 @@ class ChromeDriver { args.push('--auto-open-devtools-for-tabs'); } + if (constrainWindowSize) { + args.push('--window-size=320,600'); + } + args.push('--log-level=3'); args.push('--enable-logging'); diff --git a/test/e2e/webdriver/firefox.js b/test/e2e/webdriver/firefox.js index e202c36c4092..b10c55c598d3 100644 --- a/test/e2e/webdriver/firefox.js +++ b/test/e2e/webdriver/firefox.js @@ -45,10 +45,11 @@ class FirefoxDriver { * @param {object} options - the options for the build * @param options.responsive * @param options.port + * @param options.constrainWindowSize * @param options.proxyPort * @returns {Promise<{driver: !ThenableWebDriver, extensionUrl: string, extensionId: string}>} */ - static async build({ responsive, port, proxyPort }) { + static async build({ responsive, port, constrainWindowSize, proxyPort }) { const templateProfile = fs.mkdtempSync(TEMP_PROFILE_PATH_PREFIX); const options = new firefox.Options().setProfile(templateProfile); @@ -99,7 +100,7 @@ class FirefoxDriver { const extensionId = await fxDriver.installExtension('dist/firefox'); const internalExtensionId = await fxDriver.getInternalId(); - if (responsive) { + if (responsive || constrainWindowSize) { await driver.manage().window().setRect({ width: 320, height: 600 }); } diff --git a/test/e2e/webdriver/index.js b/test/e2e/webdriver/index.js index 4d5197bb0dac..ddc0d5543822 100644 --- a/test/e2e/webdriver/index.js +++ b/test/e2e/webdriver/index.js @@ -6,6 +6,7 @@ const FirefoxDriver = require('./firefox'); async function buildWebDriver({ openDevToolsForTabs, port, + constrainWindowSize, timeOut, proxyPort, } = {}) { @@ -18,6 +19,7 @@ async function buildWebDriver({ } = await buildBrowserWebDriver(browser, { openDevToolsForTabs, port, + constrainWindowSize, proxyPort, }); const driver = new Driver(seleniumDriver, browser, extensionUrl, timeOut);