diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index 41951f973aa9..ba32c94cabec 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -664,6 +664,7 @@ const closeSRPReveal = async (driver) => { const DAPP_HOST_ADDRESS = '127.0.0.1:8080'; const DAPP_URL = `http://${DAPP_HOST_ADDRESS}`; const DAPP_ONE_URL = 'http://127.0.0.1:8081'; +const DAPP_TWO_URL = 'http://127.0.0.1:8082'; const openDapp = async (driver, contract = null, dappURL = DAPP_URL) => { return contract @@ -1121,6 +1122,7 @@ module.exports = { DAPP_HOST_ADDRESS, DAPP_URL, DAPP_ONE_URL, + DAPP_TWO_URL, TEST_SEED_PHRASE, TEST_SEED_PHRASE_TWO, PRIVATE_KEY, diff --git a/test/e2e/tests/request-queuing/ui.spec.js b/test/e2e/tests/request-queuing/ui.spec.js index e85d87226e84..45429ad32263 100644 --- a/test/e2e/tests/request-queuing/ui.spec.js +++ b/test/e2e/tests/request-queuing/ui.spec.js @@ -1,4 +1,5 @@ const { strict: assert } = require('assert'); +const { Browser } = require('selenium-webdriver'); const FixtureBuilder = require('../../fixture-builder'); const { withFixtures, @@ -11,21 +12,33 @@ const { defaultGanacheOptions, switchToNotificationWindow, veryLargeDelayMs, + DAPP_TWO_URL, } = require('../../helpers'); const { PAGES } = require('../../webdriver/driver'); -async function openDappAndSwitchChain(driver, dappUrl, chainId) { - const notificationWindowIndex = chainId ? 4 : 3; +// Window handle adjustments will need to be made for Non-MV3 Firefox +// due to OffscreenDocument. Additionally Firefox continually bombs +// with a "NoSuchWindowError: Browsing context has been discarded" whenever +// we try to open a third dapp, so this test run in Firefox will +// validate two dapps instead of 3 +const IS_FIREFOX = process.env.SELENIUM_BROWSER === Browser.FIREFOX; +async function openDappAndSwitchChain( + driver, + dappUrl, + chainId, + notificationWindowIndex = 3, +) { // Open the dapp await openDapp(driver, undefined, dappUrl); - await driver.delay(regularDelayMs); // Connect to the dapp await driver.findClickableElement({ text: 'Connect', tag: 'button' }); await driver.clickElement('#connectButton'); await driver.delay(regularDelayMs); + await switchToNotificationWindow(driver, notificationWindowIndex); + await driver.clickElement({ text: 'Next', tag: 'button', @@ -62,39 +75,99 @@ async function openDappAndSwitchChain(driver, dappUrl, chainId) { } } -async function selectDappClickSendGetNetwork(driver, dappUrl) { +async function selectDappClickSend(driver, dappUrl) { await driver.switchToWindowWithUrl(dappUrl); - // Windows: MetaMask, TestDapp1, TestDapp2 - const expectedWindowHandles = 3; - await driver.waitUntilXWindowHandles(expectedWindowHandles); - const currentWindowHandles = await driver.getAllWindowHandles(); await driver.clickElement('#sendButton'); +} - // Under mv3, we don't need to add to the current number of window handles - // because the offscreen document returned by getAllWindowHandles provides - // an extra window handle - const newWindowHandles = await driver.waitUntilXWindowHandles( - process.env.ENABLE_MV3 === 'true' || process.env.ENABLE_MV3 === undefined - ? currentWindowHandles.length - : currentWindowHandles.length + 1, - ); - const [newNotificationWindowHandle] = newWindowHandles.filter( - (h) => !currentWindowHandles.includes(h), - ); - await driver.switchToWindow(newNotificationWindowHandle); +async function switchToNotificationPopoverValidateDetails( + driver, + expectedDetails, +) { + // Switches to the MetaMask Dialog window for confirmation + const windowHandles = await driver.getAllWindowHandles(); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog, windowHandles); + // Get UI details const networkPill = await driver.findElement( '[data-testid="network-display"]', ); const networkText = await networkPill.getText(); - await driver.clickElement({ css: 'button', text: 'Reject' }); - return networkText; + const originElement = await driver.findElement( + '.confirm-page-container-summary__origin bdi', + ); + const originText = await originElement.getText(); + + // Get state details + const notificationWindowState = await driver.executeScript(() => + window.stateHooks?.getCleanAppState?.(), + ); + const { chainId } = notificationWindowState.metamask.providerConfig; + + // Ensure accuracy + validateConfirmationDetails( + { networkText, originText, chainId }, + expectedDetails, + ); +} + +async function rejectTransaction(driver) { + await driver.clickElement({ tag: 'button', text: 'Reject' }); +} + +async function confirmTransaction(driver) { + await driver.clickElement({ tag: 'button', text: 'Confirm' }); +} + +function validateConfirmationDetails( + { chainId, networkText, originText }, + expected, +) { + assert.equal(chainId, expected.chainId); + assert.equal(networkText, expected.networkText); + assert.equal(originText, expected.originText); +} + +async function switchToNetworkByName(driver, networkName) { + await driver.clickElement('[data-testid="network-display"]'); + await driver.clickElement(`[data-testid="${networkName}"]`); +} + +async function validateBalanceAndActivity( + driver, + expectedBalance, + expectedActivityEntries = 1, +) { + // Ensure the balance changed if the the transaction was confirmed + await driver.waitForSelector({ + css: '[data-testid="eth-overview__primary-currency"] .currency-display-component__text', + text: expectedBalance, + }); + + // Ensure there's an activity entry of "Send" and "Confirmed" + if (expectedActivityEntries) { + await driver.clickElement('[data-testid="account-overview__activity-tab"]'); + assert.equal( + ( + await driver.findElements({ + css: '[data-testid="activity-list-item-action"]', + text: 'Send', + }) + ).length, + expectedActivityEntries, + ); + assert.equal( + (await driver.findElements('.transaction-status-label--confirmed')) + .length, + expectedActivityEntries, + ); + } } describe('Request-queue UI changes', function () { it('UI should show network specific to domain @no-mmi', async function () { const port = 8546; - const chainId = 1338; + const chainId = 1338; // 0x53a await withFixtures( { dapp: true, @@ -126,7 +199,7 @@ describe('Request-queue UI changes', function () { await openDappAndSwitchChain(driver, DAPP_URL); // Open the second dapp and switch chains - await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a', 4); // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet await driver.switchToWindowWithTitle( @@ -134,22 +207,151 @@ describe('Request-queue UI changes', function () { ); await driver.findElement({ css: '[data-testid="network-display"]', - text: 'Ethereum Mainnet', + text: 'Localhost 8546', }); // Go to the first dapp, ensure it uses localhost - const dappOneNetworkPillText = await selectDappClickSendGetNetwork( - driver, - DAPP_URL, - ); - assert.equal(dappOneNetworkPillText, 'Localhost 8545'); + await selectDappClickSend(driver, DAPP_URL); + await switchToNotificationPopoverValidateDetails(driver, { + chainId: '0x539', + networkText: 'Localhost 8545', + originText: DAPP_URL, + }); + await rejectTransaction(driver); // Go to the second dapp, ensure it uses Ethereum Mainnet - const dappTwoNetworkPillText = await selectDappClickSendGetNetwork( - driver, - DAPP_ONE_URL, + await selectDappClickSend(driver, DAPP_ONE_URL); + await switchToNotificationPopoverValidateDetails(driver, { + chainId: '0x53a', + networkText: 'Localhost 8546', + originText: DAPP_ONE_URL, + }); + await rejectTransaction(driver); + }, + ); + }); + + it('handles three confirmations on three confirmations concurrently @no-mmi', async function () { + const port = 8546; + const chainId = 1338; // 0x53a + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerTripleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .withSelectedNetworkControllerPerDomain() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + // Ganache for network 1 + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + // Ganache for network 3 + { + port: 7777, + chainId: 1000, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + dappOptions: { numberOfDapps: 3 }, + 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, '0x53a', 4); + + if (!IS_FIREFOX) { + // Open the third dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_TWO_URL, '0x3e8', 5); + } + + // Trigger a send confirmation on the first dapp, do not confirm or reject + await selectDappClickSend(driver, DAPP_URL); + + // Trigger a send confirmation on the second dapp, do not confirm or reject + await selectDappClickSend(driver, DAPP_ONE_URL); + + if (!IS_FIREFOX) { + // Trigger a send confirmation on the third dapp, do not confirm or reject + await selectDappClickSend(driver, DAPP_TWO_URL); + } + + // Switch to the Notification window, ensure first transaction still showing + await switchToNotificationPopoverValidateDetails(driver, { + chainId: '0x539', + networkText: 'Localhost 8545', + originText: DAPP_URL, + }); + + // Confirm transaction, wait for first confirmation window to close, second to display + await confirmTransaction(driver); + await driver.delay(veryLargeDelayMs); + + // Switch to the new Notification window, ensure second transaction showing + await switchToNotificationPopoverValidateDetails(driver, { + chainId: '0x53a', + networkText: 'Localhost 8546', + originText: DAPP_ONE_URL, + }); + + // Reject this transaction, wait for second confirmation window to close, third to display + await rejectTransaction(driver); + await driver.delay(veryLargeDelayMs); + + if (!IS_FIREFOX) { + // Switch to the new Notification window, ensure third transaction showing + await switchToNotificationPopoverValidateDetails(driver, { + chainId: '0x3e8', + networkText: 'Localhost 7777', + originText: DAPP_TWO_URL, + }); + + // Confirm transaction + await confirmTransaction(driver); + } + + // With first and last confirmations confirmed, and second rejected, + // Ensure only first and last network balances were affected + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Wait for transaction to be completed on final confirmation + await driver.delay(veryLargeDelayMs); + + if (!IS_FIREFOX) { + // Start on the last joined network, whose send transaction was just confirmed + await validateBalanceAndActivity(driver, '24.9998'); + } + + // Switch to second network, ensure full balance + await switchToNetworkByName(driver, 'Localhost 8546'); + await validateBalanceAndActivity(driver, '25', 0); + + // Turn on test networks in Networks menu so Localhost 8545 is available + await driver.clickElement('[data-testid="network-display"]'); + await driver.clickElement('.mm-modal-content__dialog .toggle-button'); + await driver.clickElement( + '.mm-modal-content__dialog button[aria-label="Close"]', ); - assert.equal(dappTwoNetworkPillText, 'Ethereum Mainnet'); + + // Switch to first network, whose send transaction was just confirmed + await switchToNetworkByName(driver, 'Localhost 8545'); + await validateBalanceAndActivity(driver, '24.9998'); }, ); });