diff --git a/.circleci/config.yml b/.circleci/config.yml index fe365c9d2665..c5132bbb5c88 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -252,6 +252,7 @@ workflows: - test-mozilla-lint-desktop - test-mozilla-lint-flask - test-e2e-chrome + - test-e2e-chrome-multichain - test-e2e-firefox - test-e2e-chrome-flask - test-e2e-firefox-flask diff --git a/development/lib/retry.js b/development/lib/retry.js index 2b38aaf486a0..e7ce13dd2dc0 100644 --- a/development/lib/retry.js +++ b/development/lib/retry.js @@ -40,7 +40,7 @@ async function retry( return result; } } catch (error) { - console.error(error); + console.error('error caught in retry():', error); if (retryUntilFailure) { return null; } diff --git a/lavamoat/build-system/policy.json b/lavamoat/build-system/policy.json index f6fcc3efe20f..5601c8594ced 100644 --- a/lavamoat/build-system/policy.json +++ b/lavamoat/build-system/policy.json @@ -1192,6 +1192,33 @@ "@metamask/jazzicon>color>color-convert>color-name": true } }, + "@sentry/cli>which": { + "builtin": { + "path.join": true + }, + "globals": { + "process.cwd": true, + "process.env.OSTYPE": true, + "process.env.PATH": true, + "process.env.PATHEXT": true, + "process.platform": true + }, + "packages": { + "@sentry/cli>which>isexe": true + } + }, + "@sentry/cli>which>isexe": { + "builtin": { + "fs": true + }, + "globals": { + "TESTING_WINDOWS": true, + "process.env.PATHEXT": true, + "process.getgid": true, + "process.getuid": true, + "process.platform": true + } + }, "@storybook/addon-knobs>qs": { "packages": { "string.prototype.matchall>side-channel": true @@ -2082,9 +2109,9 @@ "process.platform": true }, "packages": { + "@sentry/cli>which": true, "cross-spawn>path-key": true, - "cross-spawn>shebang-command": true, - "mocha>which": true + "cross-spawn>shebang-command": true } }, "cross-spawn>path-key": { @@ -3160,7 +3187,7 @@ }, "eslint>minimatch>brace-expansion": { "packages": { - "mocha>minimatch>brace-expansion>concat-map": true, + "eslint>minimatch>brace-expansion>concat-map": true, "stylelint>balanced-match": true } }, @@ -6371,33 +6398,6 @@ "chalk>supports-color>has-flag": true } }, - "mocha>which": { - "builtin": { - "path.join": true - }, - "globals": { - "process.cwd": true, - "process.env.OSTYPE": true, - "process.env.PATH": true, - "process.env.PATHEXT": true, - "process.platform": true - }, - "packages": { - "mocha>which>isexe": true - } - }, - "mocha>which>isexe": { - "builtin": { - "fs": true - }, - "globals": { - "TESTING_WINDOWS": true, - "process.env.PATHEXT": true, - "process.getgid": true, - "process.getuid": true, - "process.platform": true - } - }, "nock>debug": { "builtin": { "tty.isatty": true, @@ -8066,7 +8066,7 @@ "process.platform": true }, "packages": { - "mocha>which>isexe": true + "@sentry/cli>which>isexe": true } }, "stylelint>globjoin": { diff --git a/package.json b/package.json index 4b9c1d243a9e..5f5f002463eb 100644 --- a/package.json +++ b/package.json @@ -425,6 +425,7 @@ "@types/gulp-sourcemaps": "^0.0.35", "@types/jest": "^29.1.2", "@types/madge": "^5.0.0", + "@types/mocha": "^10.0.3", "@types/node": "^17.0.21", "@types/pify": "^5.0.1", "@types/pump": "^1.1.1", @@ -434,6 +435,7 @@ "@types/react-router-dom": "^5.3.3", "@types/remote-redux-devtools": "^0.5.5", "@types/sass": "^1.43.1", + "@types/selenium-webdriver": "^4.1.19", "@types/sinon": "^10.0.13", "@types/w3c-web-hid": "^1.0.3", "@types/watchify": "^3.11.1", @@ -512,7 +514,7 @@ "lockfile-lint": "^4.10.6", "loose-envify": "^1.4.0", "madge": "^6.1.0", - "mocha": "^9.2.2", + "mocha": "^10.2.0", "mocha-junit-reporter": "^2.2.1", "mockttp": "^2.6.0", "nock": "^13.2.9", diff --git a/test/e2e/accounts/common.ts b/test/e2e/accounts/common.ts new file mode 100644 index 000000000000..c3f5e21f7234 --- /dev/null +++ b/test/e2e/accounts/common.ts @@ -0,0 +1,348 @@ +import { privateToAddress } from 'ethereumjs-util'; +import FixtureBuilder from '../fixture-builder'; +import { + PRIVATE_KEY, + PRIVATE_KEY_TWO, + WINDOW_TITLES, + clickSignOnSignatureConfirmation, + convertETHToHexGwei, + switchToOrOpenDapp, + unlockWallet, + validateContractDetails, +} from '../helpers'; +import { Driver } from '../webdriver/driver'; + +export const TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL = + 'https://metamask.github.io/snap-simple-keyring/1.0.1/'; + +export const ganacheOptions = { + accounts: [ + { + secretKey: PRIVATE_KEY, + balance: convertETHToHexGwei(25), + }, + { + secretKey: PRIVATE_KEY_TWO, + balance: convertETHToHexGwei(25), + }, + ], +}; + +/** + * These are fixtures specific to Account Snap E2E tests: + * -- connected to Test Dapp + * -- eth_sign enabled + * -- two private keys with 25 ETH each + * + * @param title + */ +export const accountSnapFixtures = (title: string | undefined) => { + return { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp(false) + .withPreferencesController({ + disabledRpcMethodPreferences: { + eth_sign: true, + }, + }) + .build(), + ganacheOptions, + failOnConsoleError: false, + title, + }; +}; + +// convert PRIVATE_KEY to public key +export const PUBLIC_KEY = privateToAddress( + Buffer.from(PRIVATE_KEY.slice(2), 'hex'), +).toString('hex'); + +export async function installSnapSimpleKeyring( + driver: Driver, + isAsyncFlow: boolean, +) { + await driver.navigate(); + + await unlockWallet(driver); + + // navigate to test Snaps page and connect + await driver.openNewPage(TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL); + await driver.clickElement('#connectButton'); + + await driver.delay(500); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); + + await driver.delay(500); + + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + await driver.clickElementSafe('[data-testid="snap-install-scroll"]'); + + await driver.waitForSelector({ text: 'Install' }); + + await driver.clickElement({ + text: 'Install', + tag: 'button', + }); + + await driver.waitForSelector({ text: 'OK' }); + + await driver.clickElement({ + text: 'OK', + tag: 'button', + }); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.SnapSimpleKeyringDapp); + + await driver.waitForSelector({ + text: 'Connected', + tag: 'span', + }); + + if (isAsyncFlow) { + await toggleAsyncFlow(driver); + } +} + +async function toggleAsyncFlow(driver: Driver) { + await driver.switchToWindowWithTitle(WINDOW_TITLES.SnapSimpleKeyringDapp); + + // click the parent of #use-sync-flow-toggle (trying to click the element itself gives "ElementNotInteractableError: could not be scrolled into view") + await driver.clickElement({ + xpath: '//input[@id="use-sync-flow-toggle"]/..', + }); +} + +export async function importKeyAndSwitch(driver: Driver) { + await driver.clickElement({ + text: 'Import account', + tag: 'div', + }); + + await driver.fill('#import-account-private-key', PRIVATE_KEY_TWO); + + await driver.clickElement({ + text: 'Import Account', + tag: 'button', + }); + + // Click "Create" on the Snap's confirmation popup + await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); + await driver.clickElement({ + css: '[data-testid="confirmation-submit-button"]', + text: 'Create', + }); + await driver.clickElement({ + css: '[data-testid="confirmation-submit-button"]', + text: 'Ok', + }); + await driver.switchToWindowWithTitle(WINDOW_TITLES.SnapSimpleKeyringDapp); + + await switchToAccount2(driver); +} + +export async function makeNewAccountAndSwitch(driver: Driver) { + await driver.clickElement({ + text: 'Create account', + tag: 'div', + }); + + await driver.clickElement({ + text: 'Create Account', + tag: 'button', + }); + + // Click "Create" on the Snap's confirmation popup + await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); + await driver.clickElement({ + css: '[data-testid="confirmation-submit-button"]', + text: 'Create', + }); + await driver.clickElement({ + css: '[data-testid="confirmation-submit-button"]', + text: 'Ok', + }); + await driver.switchToWindowWithTitle(WINDOW_TITLES.SnapSimpleKeyringDapp); + + const newPublicKey = await ( + await driver.findElement({ + text: '0x', + tag: 'p', + }) + ).getText(); + + await switchToAccount2(driver); + + return newPublicKey; +} + +async function switchToAccount2(driver: Driver) { + await driver.switchToWindowWithTitle(WINDOW_TITLES.ExtensionInFullScreenView); + + // click on Accounts + await driver.clickElement('[data-testid="account-menu-icon"]'); + + await driver.clickElement({ + tag: 'Button', + text: 'Account 2', + }); + + await driver.waitForElementNotPresent({ + tag: 'header', + text: 'Select an account', + }); +} + +export async function connectAccountToTestDapp(driver: Driver) { + await switchToOrOpenDapp(driver); + await driver.clickElement('#connectButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); + await driver.clickElement({ + text: 'Next', + tag: 'button', + css: '[data-testid="page-container-footer-next"]', + }); + await driver.clickElement({ + text: 'Connect', + tag: 'button', + css: '[data-testid="page-container-footer-next"]', + }); +} + +export async function disconnectFromTestDapp(driver: Driver) { + await driver.switchToWindowWithTitle(WINDOW_TITLES.ExtensionInFullScreenView); + await driver.clickElement('[data-testid="account-options-menu-button"]'); + await driver.clickElement('[data-testid="global-menu-connected-sites"]'); + await driver.clickElement({ text: 'Disconnect', tag: 'a' }); + await driver.clickElement({ text: 'Disconnect', tag: 'button' }); +} + +export async function approveOrRejectRequest(driver: Driver, flowType: string) { + await driver.switchToWindowWithTitle(WINDOW_TITLES.SnapSimpleKeyringDapp); + + await driver.clickElementUsingMouseMove({ + text: 'List requests', + tag: 'div', + }); + + await driver.clickElement({ + text: 'List Requests', + tag: 'button', + }); + + // get the JSON from the screen + const requestJSON = await ( + await driver.findElement({ + text: '"scope": "",', + tag: 'div', + }) + ).getText(); + + const requestID = JSON.parse(requestJSON)[0].id; + + if (flowType === 'approve') { + await driver.clickElementUsingMouseMove({ + text: 'Approve request', + tag: 'div', + }); + + await driver.fill('#approve-request-request-id', requestID); + + await driver.clickElement({ + text: 'Approve Request', + tag: 'button', + }); + } else if (flowType === 'reject') { + await driver.clickElementUsingMouseMove({ + text: 'Reject request', + tag: 'div', + }); + + await driver.fill('#reject-request-request-id', requestID); + + await driver.clickElement({ + text: 'Reject Request', + tag: 'button', + }); + } + + await driver.switchToWindowWithTitle(WINDOW_TITLES.ExtensionInFullScreenView); +} + +export async function signData( + driver: Driver, + locatorID: string, + newPublicKey: string, + flowType: string, +) { + const isAsyncFlow = flowType !== 'sync'; + + await switchToOrOpenDapp(driver); + + await driver.clickElement(locatorID); + + // behavior of chrome and firefox is different, + // chrome needs extra time to load the popup + if (driver.browser === 'chrome') { + await driver.delay(500); + } + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); + + // these three don't have a contract details page + if (!['#ethSign', '#personalSign', '#signTypedData'].includes(locatorID)) { + await validateContractDetails(driver); + } + + await clickSignOnSignatureConfirmation(driver, 3, locatorID); + + if (isAsyncFlow) { + await driver.delay(1000); + + // // Navigate to the Notification window and click 'Go to site' button + await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); + await driver.clickElement({ + text: 'Go to site', + tag: 'button', + }); + + await driver.delay(1000); + await approveOrRejectRequest(driver, flowType); + } + + await driver.delay(500); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + + if (flowType === 'sync' || flowType === 'approve') { + if (locatorID === '#ethSign') { + // there is no Verify button for #ethSign + await driver.findElement({ + css: '#ethSignResult', + text: '0x', // we are just making sure that it contains ANY hex value + }); + } else { + await driver.clickElement(`${locatorID}Verify`); + + const resultLocator = + locatorID === '#personalSign' + ? '#personalSignVerifyECRecoverResult' // the verify span IDs are different with Personal Sign + : `${locatorID}VerifyResult`; + + await driver.findElement({ + css: resultLocator, + text: newPublicKey.toLowerCase(), + }); + } + } else if (flowType === 'reject') { + // ensure the transaction was rejected by the Snap + await driver.findElement({ + text: 'Error: Request rejected by user or snap.', + }); + } +} diff --git a/test/e2e/accounts/test-create-snap-account.spec.js b/test/e2e/accounts/create-snap-account.spec.ts similarity index 93% rename from test/e2e/accounts/test-create-snap-account.spec.js rename to test/e2e/accounts/create-snap-account.spec.ts index 649f681350d6..70209d1915c8 100644 --- a/test/e2e/accounts/test-create-snap-account.spec.js +++ b/test/e2e/accounts/create-snap-account.spec.ts @@ -1,23 +1,25 @@ -const { - withFixtures, - defaultGanacheOptions, - unlockWallet, +import { Suite } from 'mocha'; +import FixtureBuilder from '../fixture-builder'; +import { WINDOW_TITLES, + defaultGanacheOptions, switchToNotificationWindow, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); -const { TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL } = require('./utils'); + unlockWallet, + withFixtures, +} from '../helpers'; +import { Driver } from '../webdriver/driver'; +import { TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL } from './common'; -describe('Create Snap Account', function () { +describe('Create Snap Account', function (this: Suite) { it('create Snap account popup contains correct Snap name and snapId', async function () { await withFixtures( { fixtures: new FixtureBuilder().build(), ganacheOptions: defaultGanacheOptions, failOnConsoleError: false, - title: this.test.fullTitle(), + title: this.test?.fullTitle(), }, - async ({ driver }) => { + async ({ driver }: { driver: Driver }) => { await driver.navigate(); await unlockWallet(driver); @@ -101,9 +103,9 @@ describe('Create Snap Account', function () { fixtures: new FixtureBuilder().build(), ganacheOptions: defaultGanacheOptions, failOnConsoleError: false, - title: this.test.fullTitle(), + title: this.test?.fullTitle(), }, - async ({ driver }) => { + async ({ driver }: { driver: Driver }) => { await driver.navigate(); await unlockWallet(driver); @@ -196,9 +198,9 @@ describe('Create Snap Account', function () { fixtures: new FixtureBuilder().build(), ganacheOptions: defaultGanacheOptions, failOnConsoleError: false, - title: this.test.fullTitle(), + title: this.test?.fullTitle(), }, - async ({ driver }) => { + async ({ driver }: { driver: Driver }) => { await driver.navigate(); await unlockWallet(driver); diff --git a/test/e2e/accounts/remove-account-snap.spec.ts b/test/e2e/accounts/remove-account-snap.spec.ts new file mode 100644 index 000000000000..dc22d117783d --- /dev/null +++ b/test/e2e/accounts/remove-account-snap.spec.ts @@ -0,0 +1,76 @@ +import { Suite } from 'mocha'; +import FixtureBuilder from '../fixture-builder'; +import { WINDOW_TITLES, defaultGanacheOptions, withFixtures } from '../helpers'; +import { Driver } from '../webdriver/driver'; +import { installSnapSimpleKeyring, makeNewAccountAndSwitch } from './common'; + +describe('Remove Account Snap', function (this: Suite) { + it('disable a snap and remove it', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: defaultGanacheOptions, + failOnConsoleError: false, + title: this.test?.fullTitle(), + }, + async ({ driver }: { driver: Driver }) => { + await installSnapSimpleKeyring(driver, false); + + await makeNewAccountAndSwitch(driver); + + // Navigate to settings. + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); + + await driver.clickElement({ text: 'Snaps', tag: 'div' }); + await driver.clickElement({ + text: 'MetaMask Simple Snap Keyring', + tag: 'p', + }); + + // Disable the snap. + await driver.clickElement('.toggle-button > div'); + + // Remove the snap. + const removeButton = await driver.findElement( + '[data-testid="remove-snap-button"]', + ); + await driver.scrollToElement(removeButton); + await driver.clickElement('[data-testid="remove-snap-button"]'); + + await driver.clickElement({ + text: 'Continue', + tag: 'button', + }); + + await driver.fill( + '[data-testid="remove-snap-confirmation-input"]', + 'MetaMask Simple Snap Keyring', + ); + + await driver.clickElement({ + text: 'Remove Snap', + tag: 'button', + }); + + // Checking result modal + await driver.findVisibleElement({ + text: 'MetaMask Simple Snap Keyring removed', + tag: 'p', + }); + + // Assert that the snap was removed. + await driver.findElement({ + css: '.mm-box', + text: "You don't have any snaps installed.", + tag: 'p', + }); + }, + ); + }); +}); diff --git a/test/e2e/accounts/snap-account-signatures-and-disconnects.spec.ts b/test/e2e/accounts/snap-account-signatures-and-disconnects.spec.ts new file mode 100644 index 000000000000..72e0b358c330 --- /dev/null +++ b/test/e2e/accounts/snap-account-signatures-and-disconnects.spec.ts @@ -0,0 +1,49 @@ +import { Suite } from 'mocha'; +import FixtureBuilder from '../fixture-builder'; +import { withFixtures } from '../helpers'; +import { Driver } from '../webdriver/driver'; +import { + installSnapSimpleKeyring, + makeNewAccountAndSwitch, + connectAccountToTestDapp, + disconnectFromTestDapp, + ganacheOptions, + signData, +} from './common'; + +describe('Snap Account Signatures and Disconnects', function (this: Suite) { + it('can connect to the Test Dapp, then #signTypedDataV3, disconnect then connect, then #signTypedDataV4 (async flow approve)', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder().build(), + ganacheOptions, + failOnConsoleError: false, + title: this.test?.fullTitle(), + }, + async ({ driver }: { driver: Driver }) => { + const flowType = 'approve'; + const isAsyncFlow = true; + + await installSnapSimpleKeyring(driver, isAsyncFlow); + + const newPublicKey = await makeNewAccountAndSwitch(driver); + + // open the Test Dapp and connect Account 2 to it + await connectAccountToTestDapp(driver); + + // do #signTypedDataV3 + await signData(driver, '#signTypedDataV3', newPublicKey, flowType); + + // disconnect from the Test Dapp + await disconnectFromTestDapp(driver); + + // reconnect Account 2 to the Test Dapp + await connectAccountToTestDapp(driver); + + // do #signTypedDataV4 + await signData(driver, '#signTypedDataV4', newPublicKey, flowType); + }, + ); + }); +}); diff --git a/test/e2e/accounts/snap-account-signatures.spec.ts b/test/e2e/accounts/snap-account-signatures.spec.ts new file mode 100644 index 000000000000..5c1a00a2c00c --- /dev/null +++ b/test/e2e/accounts/snap-account-signatures.spec.ts @@ -0,0 +1,49 @@ +import { Suite } from 'mocha'; +import { openDapp, withFixtures } from '../helpers'; +import { Driver } from '../webdriver/driver'; +import { + accountSnapFixtures, + installSnapSimpleKeyring, + makeNewAccountAndSwitch, + signData, +} from './common'; + +describe('Snap Account Signatures', function (this: Suite) { + this.timeout(120000); // This test is very long, so we need an unusually high timeout + + // Run sync, async approve, and async reject flows + // (in Jest we could do this with test.each, but that does not exist here) + ['sync', 'approve', 'reject'].forEach((flowType) => { + // generate title of the test from flowType + const title = `can sign with ${flowType} flow`; + + it(title, async () => { + await withFixtures( + accountSnapFixtures(title), + async ({ driver }: { driver: Driver }) => { + const isAsyncFlow = flowType !== 'sync'; + + await installSnapSimpleKeyring(driver, isAsyncFlow); + + const newPublicKey = await makeNewAccountAndSwitch(driver); + + await openDapp(driver); + + // Run all 6 signature types + const locatorIDs = [ + '#ethSign', + '#personalSign', + '#signTypedData', + '#signTypedDataV3', + '#signTypedDataV4', + '#signPermit', + ]; + + for (const locatorID of locatorIDs) { + await signData(driver, locatorID, newPublicKey, flowType); + } + }, + ); + }); + }); +}); diff --git a/test/e2e/accounts/snap-account-transfers.spec.ts b/test/e2e/accounts/snap-account-transfers.spec.ts new file mode 100644 index 000000000000..57da6be4c953 --- /dev/null +++ b/test/e2e/accounts/snap-account-transfers.spec.ts @@ -0,0 +1,90 @@ +import { Suite } from 'mocha'; +import { sendTransaction, withFixtures, WINDOW_TITLES } from '../helpers'; +import { Driver } from '../webdriver/driver'; +import { + accountSnapFixtures, + PUBLIC_KEY, + installSnapSimpleKeyring, + importKeyAndSwitch, + approveOrRejectRequest, +} from './common'; + +describe('Snap Account Transfers', function (this: Suite) { + it('can import a private key and transfer 1 ETH (sync flow)', async function () { + await withFixtures( + accountSnapFixtures(this.test?.fullTitle()), + async ({ driver }: { driver: Driver }) => { + await importPrivateKeyAndTransfer1ETH(driver, 'sync'); + }, + ); + }); + + it('can import a private key and transfer 1 ETH (async flow approve)', async function () { + await withFixtures( + accountSnapFixtures(this.test?.fullTitle()), + async ({ driver }: { driver: Driver }) => { + await importPrivateKeyAndTransfer1ETH(driver, 'approve'); + }, + ); + }); + + it('can import a private key and transfer 1 ETH (async flow reject)', async function () { + await withFixtures( + accountSnapFixtures(this.test?.fullTitle()), + async ({ driver }: { driver: Driver }) => { + await importPrivateKeyAndTransfer1ETH(driver, 'reject'); + }, + ); + }); + + /** + * @param driver + * @param flowType + */ + async function importPrivateKeyAndTransfer1ETH( + driver: Driver, + flowType: string, + ) { + const isAsyncFlow = flowType !== 'sync'; + + await installSnapSimpleKeyring(driver, isAsyncFlow); + await importKeyAndSwitch(driver); + + // send 1 ETH from Account 2 to Account 1 + await sendTransaction(driver, PUBLIC_KEY, 1, isAsyncFlow); + + // TODO: Update Test when Multichain Send Flow is added + if (process.env.MULTICHAIN) { + return; + } + + if (isAsyncFlow) { + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.navigate(); + await driver.clickElement({ + text: 'Go to site', + tag: 'button', + }); + + await driver.delay(1000); + await approveOrRejectRequest(driver, flowType); + } + + if (flowType === 'sync' || flowType === 'approve') { + // click on Accounts + await driver.clickElement('[data-testid="account-menu-icon"]'); + + // ensure one account has 26 ETH and the other has 24 ETH + await driver.findElement('[title="26 ETH"]'); + await driver.findElement('[title="24 ETH"]'); + } else if (flowType === 'reject') { + // ensure the transaction was rejected by the Snap + await driver.clickElement({ text: 'Activity', tag: 'button' }); + await driver.findElement( + '[data-original-title="Request rejected by user or snap."]', + ); + } + } +}); diff --git a/test/e2e/accounts/test-remove-accounts-snap.spec.js b/test/e2e/accounts/test-remove-accounts-snap.spec.js deleted file mode 100644 index 47750c4d094c..000000000000 --- a/test/e2e/accounts/test-remove-accounts-snap.spec.js +++ /dev/null @@ -1,141 +0,0 @@ -const { strict: assert } = require('assert'); -const { - withFixtures, - defaultGanacheOptions, - unlockWallet, - WINDOW_TITLES, - switchToNotificationWindow, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); -const { TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL } = require('./utils'); - -describe('Remove Account Snap', function () { - it('disable a snap and remove it', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, - failOnConsoleError: false, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await driver.navigate(); - - await unlockWallet(driver); - - // Navigate to test Snaps page and connect. - await driver.openNewPage(TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL); - - // Connect the dapp. - await driver.clickElement('#connectButton'); - await switchToNotificationWindow(driver); - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - // Scroll to the bottom of the page. - await driver.clickElementSafe('[data-testid="snap-install-scroll"]'); - - // Click the install button to install the snap. - await driver.waitForSelector({ text: 'Install' }); - await driver.clickElement({ - text: 'Install', - tag: 'button', - }); - await driver.waitForSelector({ text: 'OK' }); - await driver.clickElement({ - text: 'OK', - tag: 'button', - }); - - // Move back to the snap window to test the create account flow. - await driver.switchToWindowWithTitle( - WINDOW_TITLES.SnapSimpleKeyringDapp, - ); - - // Check the dapp connection status. - await driver.waitForSelector({ - css: '#snapConnected', - text: 'Connected', - }); - - // Create new account on dapp. - await driver.clickElement({ - text: 'Create account', - tag: 'div', - }); - await driver.clickElement({ - text: 'Create Account', - tag: 'button', - }); - await switchToNotificationWindow(driver); - await driver.clickElement('[data-testid="confirmation-submit-button"]'); - await driver.findElement({ - tag: 'div', - text: 'Your account is ready!', - }); - - // Click the OK button. - await driver.clickElement('[data-testid="confirmation-submit-button"]'); - - // Switch back to the test dapp window. - await driver.switchToWindowWithTitle( - WINDOW_TITLES.SnapSimpleKeyringDapp, - ); - - await driver.findElement({ - tag: 'p', - text: 'Successful request', - }); - - // Navigate to settings. - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement( - '[data-testid="account-options-menu-button"]', - ); - await driver.clickElement({ text: 'Snaps', tag: 'div' }); - await driver.clickElement({ - text: 'MetaMask Simple Snap Keyring', - tag: 'p', - }); - - // Disable the snap. - await driver.clickElement('.toggle-button > div'); - - // Remove the snap. - const removeButton = await driver.findElement( - '[data-testid="remove-snap-button"]', - ); - await driver.scrollToElement(removeButton); - await driver.clickElement('[data-testid="remove-snap-button"]'); - - await driver.clickElement({ - text: 'Continue', - tag: 'button', - }); - - await driver.fill( - '[data-testid="remove-snap-confirmation-input"]', - 'MetaMask Simple Snap Keyring', - ); - - await driver.clickElement({ - text: 'Remove Snap', - tag: 'button', - }); - - // Assert that the snap was removed. - const removeResult = await driver.findElement( - '.snaps__content__list__container--no-snaps_inner', - ); - assert.equal( - await removeResult.getText(), - "You don't have any snaps installed.", - ); - }, - ); - }); -}); diff --git a/test/e2e/accounts/test-snap-accounts.spec.js b/test/e2e/accounts/test-snap-accounts.spec.js deleted file mode 100644 index 32bd2604af81..000000000000 --- a/test/e2e/accounts/test-snap-accounts.spec.js +++ /dev/null @@ -1,595 +0,0 @@ -const { strict: assert } = require('assert'); -const util = require('ethereumjs-util'); -const FixtureBuilder = require('../fixture-builder'); -const { - convertETHToHexGwei, - openDapp, - PRIVATE_KEY, - PRIVATE_KEY_TWO, - defaultGanacheOptions, - sendTransaction, - switchToNotificationWindow, - switchToOrOpenDapp, - unlockWallet, - validateContractDetails, - WINDOW_TITLES, - withFixtures, -} = require('../helpers'); -const Driver = require('../webdriver/driver'); // eslint-disable-line no-unused-vars -- this is imported for JSDoc -const { TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL } = require('./utils'); - -describe('Test Snap Account', function () { - const ganacheOptions = { - accounts: [ - { - secretKey: PRIVATE_KEY, - balance: convertETHToHexGwei(25), - }, - { - secretKey: PRIVATE_KEY_TWO, - balance: convertETHToHexGwei(25), - }, - ], - }; - - const accountSnapFixtures = (title) => { - return { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp(false) - .build(), - ganacheOptions, - failOnConsoleError: false, - title, - }; - }; - - // convert PRIVATE_KEY to public key - const PUBLIC_KEY = util - .privateToAddress(Buffer.from(PRIVATE_KEY.slice(2), 'hex')) - .toString('hex'); - - it('can create a new Snap account', async function () { - await withFixtures( - accountSnapFixtures(this.test.fullTitle()), - async ({ driver }) => { - await installSnapSimpleKeyring(driver, false); - - await makeNewAccountAndSwitch(driver); - }, - ); - }); - - it('will display the keyring snap account removal warning', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, - failOnConsoleError: false, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await installSnapSimpleKeyring(driver, false); - - await makeNewAccountAndSwitch(driver); - - const windowHandles = await driver.waitUntilXWindowHandles( - 2, - 1000, - 10000, - ); - - // switch to metamask extension - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - windowHandles, - ); - - await driver.clickElement( - '[data-testid="account-options-menu-button"]', - ); - - await driver.clickElement({ text: 'Snaps', tag: 'div' }); - - await driver.clickElement( - '[data-testid="npm:@metamask/snap-simple-keyring-snap"]', - ); - - const removeButton = await driver.findElement( - '[data-testid="remove-snap-button"]', - ); - await driver.scrollToElement(removeButton); - await driver.clickElement('[data-testid="remove-snap-button"]'); - - assert.equal( - await driver.isElementPresentAndVisible({ text: 'Account 2' }), - true, - ); - - await driver.clickElement({ - text: 'Continue', - tag: 'button', - }); - - await driver.fill( - '[data-testid="remove-snap-confirmation-input"]', - 'MetaMask Simple Snap Keyring', - ); - - await driver.clickElement({ - text: 'Remove Snap', - tag: 'button', - }); - - // Checking result modal - assert.equal( - await driver.isElementPresentAndVisible({ - text: 'MetaMask Simple Snap Keyring removed', - tag: 'p', - }), - true, - ); - }, - ); - }); - - it('can import a private key and transfer 1 ETH (sync flow)', async function () { - await withFixtures( - accountSnapFixtures(this.test.fullTitle()), - async ({ driver }) => { - await importPrivateKeyAndTransfer1ETH(driver, 'sync'); - }, - ); - }); - - it('can import a private key and transfer 1 ETH (async flow approve)', async function () { - await withFixtures( - accountSnapFixtures(this.test.fullTitle()), - async ({ driver }) => { - await importPrivateKeyAndTransfer1ETH(driver, 'approve'); - }, - ); - }); - - it('can import a private key and transfer 1 ETH (async flow reject)', async function () { - await withFixtures( - accountSnapFixtures(this.test.fullTitle()), - async ({ driver }) => { - await importPrivateKeyAndTransfer1ETH(driver, 'reject'); - }, - ); - }); - - // run the full matrix of sign types and sync/async approve/async reject flows - // (in Jest we could do this with test.each, but that does not exist here) - [ - ['#personalSign', 'sync'], - ['#personalSign', 'approve'], - ['#personalSign', 'reject'], - ['#signTypedData', 'sync'], - ['#signTypedData', 'approve'], - ['#signTypedData', 'reject'], - ['#signTypedDataV3', 'sync'], - ['#signTypedDataV3', 'approve'], - ['#signTypedDataV3', 'reject'], - ['#signTypedDataV4', 'sync'], - ['#signTypedDataV4', 'approve'], - ['#signTypedDataV4', 'reject'], - ['#signPermit', 'sync'], - ['#signPermit', 'approve'], - ['#signPermit', 'reject'], - ].forEach(([locatorID, flowType]) => { - // generate title of the test from the locatorID and flowType - let title = `can ${locatorID} (${ - flowType === 'sync' ? 'sync' : 'async' - } flow`; - - title += flowType === 'sync' ? ')' : ` ${flowType})`; - - it(title, async function () { - await withFixtures( - accountSnapFixtures(this.test.fullTitle()), - async ({ driver }) => { - const isAsyncFlow = flowType !== 'sync'; - - await installSnapSimpleKeyring(driver, isAsyncFlow); - - const newPublicKey = await makeNewAccountAndSwitch(driver); - - await openDapp(driver); - - await signData(driver, locatorID, newPublicKey, flowType); - }, - ); - }); - }); - - it('can connect to the Test Dapp, then #signTypedDataV3, disconnect then connect, then #signTypedDataV4 (async flow approve)', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder().build(), - ganacheOptions, - failOnConsoleError: false, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - const flowType = 'approve'; - const isAsyncFlow = flowType !== 'sync'; - - await installSnapSimpleKeyring(driver, isAsyncFlow); - - const newPublicKey = await makeNewAccountAndSwitch(driver); - - // open the Test Dapp and connect Account 2 to it - await connectAccountToTestDapp(driver); - - // do #signTypedDataV3 - await signData(driver, '#signTypedDataV3', newPublicKey, flowType); - - // disconnect from the Test Dapp - await disconnectFromTestDapp(driver); - - // reconnect Account 2 to the Test Dapp - await connectAccountToTestDapp(driver); - - // do #signTypedDataV4 - await signData(driver, '#signTypedDataV4', newPublicKey, flowType); - }, - ); - }); - - /** - * @param {Driver} driver - * @param {string} flowType - */ - async function importPrivateKeyAndTransfer1ETH(driver, flowType) { - const isAsyncFlow = flowType !== 'sync'; - - await installSnapSimpleKeyring(driver, isAsyncFlow); - await importKeyAndSwitch(driver); - - // send 1 ETH from Account 2 to Account 1 - await sendTransaction(driver, PUBLIC_KEY, 1, isAsyncFlow); - - if (isAsyncFlow) { - await approveOrRejectRequest(driver, flowType); - } - - if (flowType === 'sync' || flowType === 'approve') { - // click on Accounts - await driver.clickElement('[data-testid="account-menu-icon"]'); - - // ensure one account has 26 ETH and the other has 24 ETH - await driver.findElement('[title="26 ETH"]'); - await driver.findElement('[title="24 ETH"]'); - } else if (flowType === 'reject') { - // ensure the transaction was rejected by the Snap - await driver.findElement( - '[data-original-title="Request rejected by user or snap."]', - ); - } - } - - /** - * @param {Driver} driver - * @param {string} locatorID - * @param {string} newPublicKey - * @param {string} flowType - */ - async function signData(driver, locatorID, newPublicKey, flowType) { - const isAsyncFlow = flowType !== 'sync'; - - await switchToOrOpenDapp(driver); - - await driver.clickElement(locatorID); - - // behaviour of chrome and firefox is different, - // chrome needs extra time to load the popup - if (driver.browser === 'chrome') { - await driver.delay(500); - } - const handles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.Notification, - handles, - 2000, - ); - - // these two don't have a contract details page - if (locatorID !== '#personalSign' && locatorID !== '#signTypedData') { - await validateContractDetails(driver); - } - - await driver.clickElement({ text: 'Sign', tag: 'button' }); - - if (isAsyncFlow) { - await approveOrRejectRequest(driver, flowType); - } - - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - - if (flowType === 'sync' || flowType === 'approve') { - await driver.clickElement(`${locatorID}Verify`); - - const resultLocator = - locatorID === '#personalSign' - ? '#personalSignVerifyECRecoverResult' // the verify span IDs are different with Personal Sign - : `${locatorID}VerifyResult`; - - await driver.findElement({ - css: resultLocator, - text: newPublicKey.toLowerCase(), - }); - } else if (flowType === 'reject') { - // ensure the transaction was rejected by the Snap - await driver.findElement({ - text: 'Error: Request rejected by user or snap.', - }); - } - } - - /** - * @param {Driver} driver - * @param {boolean} isAsyncFlow - */ - async function installSnapSimpleKeyring(driver, isAsyncFlow) { - driver.navigate(); - - await unlockWallet(driver); - - // navigate to test Snaps page and connect - await driver.openNewPage(TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL); - const connectButton = await driver.findElement('#connectButton'); - await driver.scrollToElement(connectButton); - await connectButton.click(); - - await switchToNotificationWindow(driver); - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - await driver.clickElementSafe('[data-testid="snap-install-scroll"]', 1000); - - await driver.waitForSelector({ text: 'Install' }); - - await driver.clickElement({ - text: 'Install', - tag: 'button', - }); - - await driver.waitForSelector({ text: 'OK' }); - - await driver.clickElement({ - text: 'OK', - tag: 'button', - }); - - await driver.switchToWindowWithTitle('SSK - Simple Snap Keyring'); - - await driver.waitForSelector({ - text: 'Connected', - tag: 'span', - }); - - if (isAsyncFlow) { - await toggleAsyncFlow(driver); - } - } - - /** - * @param {Driver} driver - */ - async function toggleAsyncFlow(driver) { - await driver.switchToWindowWithTitle('SSK - Simple Snap Keyring'); - - // click the parent of #use-sync-flow-toggle (trying to click the element itself gives "ElementNotInteractableError: could not be scrolled into view") - await driver.clickElement({ - xpath: '//input[@id="use-sync-flow-toggle"]/..', - }); - } - - /** - * @param {Driver} driver - */ - async function importKeyAndSwitch(driver) { - await driver.clickElement({ - text: 'Import account', - tag: 'div', - }); - - await driver.fill('#import-account-private-key', PRIVATE_KEY_TWO); - - await driver.clickElement({ - text: 'Import Account', - tag: 'button', - }); - - // Click "Create" on the Snap's confirmation popup - await switchToNotificationWindow(driver, 3); - await driver.clickElement('[data-testid="confirmation-submit-button"]'); - await driver.clickElement('[data-testid="confirmation-submit-button"]'); - await driver.switchToWindowWithTitle('SSK - Simple Snap Keyring'); - - await switchToAccount2(driver); - } - - /** - * @param {Driver} driver - */ - async function makeNewAccountAndSwitch(driver) { - await driver.clickElement({ - text: 'Create account', - tag: 'div', - }); - - await driver.clickElement({ - text: 'Create Account', - tag: 'button', - }); - - // Click "Create" on the Snap's confirmation popup - const handles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.Notification, - handles, - 2000, - ); - await driver.clickElement('[data-testid="confirmation-submit-button"]'); - await driver.clickElement('[data-testid="confirmation-submit-button"]'); - await driver.switchToWindowWithTitle('SSK - Simple Snap Keyring'); - - const newPublicKey = await ( - await driver.findElement({ - text: '0x', - tag: 'p', - }) - ).getText(); - - await switchToAccount2(driver); - - return newPublicKey; - } - - /** - * @param {Driver} driver - */ - async function switchToAccount2(driver) { - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // click on Accounts - await driver.clickElement('[data-testid="account-menu-icon"]'); - - const label = await driver.findElement({ - css: '.mm-tag', - text: 'Snaps (Beta)', - }); - - label.click(); - - await driver.waitForElementNotPresent('.mm-tag'); - } - - /** - * @param {Driver} driver - */ - async function connectAccountToTestDapp(driver) { - await switchToOrOpenDapp(driver); - await driver.clickElement('#connectButton'); - const handles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.Notification, - handles, - 2000, - ); - await driver.clickElement('[data-testid="page-container-footer-next"]'); - await driver.clickElement('[data-testid="page-container-footer-next"]'); - } - - /** - * @param {Driver} driver - */ - async function disconnectFromTestDapp(driver) { - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement('[data-testid="account-options-menu-button"]'); - await driver.clickElement('[data-testid="global-menu-connected-sites"]'); - await driver.clickElement({ text: 'Disconnect', tag: 'a' }); - await driver.clickElement({ text: 'Disconnect', tag: 'button' }); - } - - /** - * @param {Driver} driver - * @param {string} flowType - */ - async function approveOrRejectRequest(driver, flowType) { - // Click redirect button for async flows - if (flowType === 'approve' || flowType === 'reject') { - // There is a try catch here because when using the send eth flow, - // MetaMask is still active, and therefore the popup notification will - // no appear. The workaround is to reload the extension and - // force the notification to appear in the full window. - try { - // Adding a delay here because there is a race condition where - // the driver tries to switch to the first notification window - // and not the second notification window with the redirect button - await driver.delay(500); - const handles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.Notification, - handles, - 2000, - ); - } catch (error) { - console.log('SNAPS/ error switching to notification window', error); - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.navigate(); - } - await driver.clickElement({ - text: 'Go to site', - tag: 'button', - }); - - await driver.delay(500); - } - - await driver.switchToWindowWithTitle('SSK - Simple Snap Keyring'); - - await driver.clickElementUsingMouseMove({ - text: 'List requests', - tag: 'div', - }); - - await driver.clickElement({ - text: 'List Requests', - tag: 'button', - }); - - // get the JSON from the screen - const requestJSON = await ( - await driver.findElement({ - text: '"scope": "",', - tag: 'div', - }) - ).getText(); - - const requestID = JSON.parse(requestJSON)[0].id; - - if (flowType === 'approve') { - await driver.clickElementUsingMouseMove({ - text: 'Approve request', - tag: 'div', - }); - - await driver.fill('#approve-request-request-id', requestID); - - await driver.clickElement({ - text: 'Approve Request', - tag: 'button', - }); - } else if (flowType === 'reject') { - await driver.clickElementUsingMouseMove({ - text: 'Reject request', - tag: 'div', - }); - - await driver.fill('#reject-request-request-id', requestID); - - await driver.clickElement({ - text: 'Reject Request', - tag: 'button', - }); - } - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - } -}); diff --git a/test/e2e/accounts/utils.ts b/test/e2e/accounts/utils.ts deleted file mode 100644 index 7087531c91b2..000000000000 --- a/test/e2e/accounts/utils.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL = - 'https://metamask.github.io/snap-simple-keyring/1.0.1/'; diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index 01c3fca5ae07..1414d784f603 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -260,10 +260,13 @@ async function withFixtures(options, testSuite) { const WINDOW_TITLES = Object.freeze({ ExtensionInFullScreenView: 'MetaMask', - TestDApp: 'E2E Test Dapp', - Notification: 'MetaMask Notification', InstalledExtensions: 'Extensions', + Notification: 'MetaMask Notification', + Phishing: 'MetaMask Phishing Detection', + ServiceWorkerSettings: 'Inspect with Chrome Developer Tools', SnapSimpleKeyringDapp: 'SSK - Simple Snap Keyring', + TestDApp: 'E2E Test Dapp', + TestSnaps: 'Test Snaps', }); /** @@ -514,8 +517,7 @@ const testSRPDropdownIterations = async (options, driver, iterations) => { const passwordUnlockOpenSRPRevealQuiz = async (driver) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // navigate settings to reveal SRP await driver.clickElement('[data-testid="account-options-menu-button"]'); @@ -577,7 +579,13 @@ const switchToOrOpenDapp = async ( dappURL = DAPP_URL, ) => { try { - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + // Do an unusually fast switchToWindowWithTitle, just 1 second + await driver.switchToWindowWithTitle( + WINDOW_TITLES.TestDApp, + null, + 1000, + 1000, + ); } catch { await openDapp(driver, contract, dappURL); } @@ -598,9 +606,11 @@ const defaultGanacheOptions = { const openActionMenuAndStartSendFlow = async (driver) => { // TODO: Update Test when Multichain Send Flow is added if (process.env.MULTICHAIN) { - return; + await driver.clickElement('[data-testid="app-footer-actions-button"]'); + await driver.clickElement('[data-testid="select-action-modal-item-send"]'); + } else { + await driver.clickElement('[data-testid="eth-overview-send"]'); } - await driver.clickElement('[data-testid="eth-overview-send"]'); }; const sendTransaction = async ( @@ -700,10 +710,10 @@ async function waitForAccountRendered(driver) { ); } -const unlockWallet = async (driver) => { - await driver.fill('#password', 'correct horse battery staple'); +async function unlockWallet(driver) { + await driver.fill('#password', WALLET_PASSWORD); await driver.press('#password', driver.Key.ENTER); -}; +} const logInWithBalanceValidation = async (driver, ganacheServer) => { await unlockWallet(driver); @@ -738,12 +748,24 @@ function genRandInitBal(minETHBal = 10, maxETHBal = 100, decimalPlaces = 4) { * screen. * * @param {WebDriver} driver - * @param numHandles + * @param {number} numHandles + * @param {string} locatorID */ -async function clickSignOnSignatureConfirmation(driver, numHandles = 2) { +async function clickSignOnSignatureConfirmation( + driver, + numHandles = 2, // eslint-disable-line no-unused-vars + locatorID = null, +) { await driver.clickElement({ text: 'Sign', tag: 'button' }); - await driver.waitUntilXWindowHandles(numHandles); - await driver.getAllWindowHandles(); + + // #ethSign has a second Sign confirmation button that says "Your funds may be at risk" + if (locatorID === '#ethSign') { + await driver.clickElement({ + text: 'Sign', + tag: 'button', + css: '[data-testid="signature-warning-sign-button"]', + }); + } } /** @@ -781,8 +803,8 @@ async function validateContractDetails(driver) { * @param numHandles */ async function switchToNotificationWindow(driver, numHandles = 3) { - await driver.waitUntilXWindowHandles(numHandles); - const windowHandles = await driver.getAllWindowHandles(); + const windowHandles = await driver.waitUntilXWindowHandles(numHandles); + await driver.switchToWindowWithTitle( WINDOW_TITLES.Notification, windowHandles, diff --git a/test/e2e/json-rpc/switchEthereumChain.spec.js b/test/e2e/json-rpc/switchEthereumChain.spec.js index 64cbb7c570d2..f3bd2e87602d 100644 --- a/test/e2e/json-rpc/switchEthereumChain.spec.js +++ b/test/e2e/json-rpc/switchEthereumChain.spec.js @@ -5,6 +5,7 @@ const { openDapp, DAPP_URL, DAPP_ONE_URL, + unlockWallet, switchToNotificationWindow, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); @@ -27,8 +28,7 @@ describe('Switch Ethereum Chain for two dapps', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // open two dapps await openDapp(driver, undefined, DAPP_URL); @@ -70,16 +70,14 @@ describe('Switch Ethereum Chain for two dapps', function () { }); // Dapp One ChainId assertion - const dappOneChainId = await driver.findElement('#chainId'); - assert.equal(await dappOneChainId.getText(), '0x53a'); + await driver.findElement({ css: '#chainId', text: '0x53a' }); // Switch to Dapp Two await driver.switchToWindow(dappTwo); assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); // Dapp Two ChainId Assertion - const dappTwoChainId = await driver.findElement('#chainId'); - assert.equal(await dappTwoChainId.getText(), '0x53a'); + await driver.findElement({ css: '#chainId', text: '0x53a' }); }, ); }); @@ -174,8 +172,7 @@ describe('Switch Ethereum Chain for two dapps', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // open two dapps await openDapp(driver, undefined, DAPP_URL); @@ -248,8 +245,7 @@ describe('Switch Ethereum Chain for two dapps', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // open two dapps await openDapp(driver, undefined, DAPP_URL); diff --git a/test/e2e/metamask-ui.spec.js b/test/e2e/metamask-ui.spec.js index 075bb72c0d96..92590c1a0ed0 100644 --- a/test/e2e/metamask-ui.spec.js +++ b/test/e2e/metamask-ui.spec.js @@ -5,6 +5,7 @@ const enLocaleMessages = require('../../app/_locales/en/messages.json'); const createStaticServer = require('../../development/create-static-server'); const { TEST_SEED_PHRASE_TWO, + WALLET_PASSWORD, tinyDelayMs, regularDelayMs, largeDelayMs, @@ -86,9 +87,11 @@ describe('MetaMask @no-mmi', function () { }); it('accepts a secure password', async function () { - const password = 'correct horse battery staple'; - await driver.fill('[data-testid="create-password-new"]', password); - await driver.fill('[data-testid="create-password-confirm"]', password); + await driver.fill('[data-testid="create-password-new"]', WALLET_PASSWORD); + await driver.fill( + '[data-testid="create-password-confirm"]', + WALLET_PASSWORD, + ); await driver.clickElement('[data-testid="create-password-terms"]'); await driver.clickElement('[data-testid="create-password-wallet"]'); }); @@ -153,8 +156,8 @@ describe('MetaMask @no-mmi', function () { TEST_SEED_PHRASE_TWO, ); - await driver.fill('#password', 'correct horse battery staple'); - await driver.fill('#confirm-password', 'correct horse battery staple'); + await driver.fill('#password', WALLET_PASSWORD); + await driver.fill('#confirm-password', WALLET_PASSWORD); await driver.clickElement({ text: enLocaleMessages.restore.message, tag: 'button', diff --git a/test/e2e/run-all.js b/test/e2e/run-all.js index 117deb611139..602c1108fb6f 100644 --- a/test/e2e/run-all.js +++ b/test/e2e/run-all.js @@ -26,7 +26,7 @@ const getTestPathsForTestDir = async (testDir) => { if (itemInDirectory.isDirectory()) { const subDirPaths = await getTestPathsForTestDir(fullPath); testPaths.push(...subDirPaths); - } else if (fullPath.endsWith('.spec.js')) { + } else if (fullPath.endsWith('.spec.js') || fullPath.endsWith('.spec.ts')) { testPaths.push(fullPath); } } diff --git a/test/e2e/snaps/test-snap-bip-32.spec.js b/test/e2e/snaps/test-snap-bip-32.spec.js index fd4100bf6d99..b264cb70afa9 100644 --- a/test/e2e/snaps/test-snap-bip-32.spec.js +++ b/test/e2e/snaps/test-snap-bip-32.spec.js @@ -1,4 +1,9 @@ -const { withFixtures } = require('../helpers'); +const { + withFixtures, + unlockWallet, + switchToNotificationWindow, + WINDOW_TITLES, +} = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); @@ -21,10 +26,7 @@ describe('Test Snap bip-32', function () { }, async ({ driver }) => { await driver.navigate(); - - // enter pw into extension - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // navigate to test snaps page and connect await driver.driver.get(TEST_SNAPS_WEBSITE_URL); @@ -38,15 +40,7 @@ describe('Test Snap bip-32', function () { await driver.delay(1000); // switch to metamask extension and click connect - let windowHandles = await driver.waitUntilXWindowHandles( - 2, - 1000, - 10000, - ); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await switchToNotificationWindow(driver, 2); await driver.clickElement({ text: 'Connect', tag: 'button', @@ -54,7 +48,7 @@ describe('Test Snap bip-32', function () { await driver.waitForSelector({ text: 'Install' }); - await driver.clickElement('[data-testid="snap-install-scroll"]'); + await driver.clickElementSafe('[data-testid="snap-install-scroll"]'); await driver.clickElement({ text: 'Install', @@ -80,7 +74,7 @@ describe('Test Snap bip-32', function () { }); // switch back to test-snaps window - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); // wait for npm installation success await driver.waitForSelector({ @@ -114,19 +108,19 @@ describe('Test Snap bip-32', function () { await driver.clickElement('#sendBip32-secp256k1'); // hit 'approve' on the signature confirmation - windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await switchToNotificationWindow(driver, 2); await driver.clickElement({ text: 'Approve', tag: 'button', }); // switch back to the test-snaps window - windowHandles = await driver.waitUntilXWindowHandles(1, 1000, 10000); - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + let windowHandles = await driver.waitUntilXWindowHandles( + 1, + 1000, + 10000, + ); + await driver.switchToWindow(windowHandles[0]); // check results of the secp256k1 signature with waitForSelector await driver.waitForSelector({ @@ -144,18 +138,14 @@ describe('Test Snap bip-32', function () { await driver.clickElement('#sendBip32-ed25519'); // hit 'approve' on the custom confirm - windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await switchToNotificationWindow(driver, 2); await driver.clickElement({ text: 'Approve', tag: 'button', }); windowHandles = await driver.waitUntilXWindowHandles(1, 1000, 10000); - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + await driver.switchToWindow(windowHandles[0]); // check results of ed25519 signature with waitForSelector await driver.waitForSelector({ diff --git a/test/e2e/snaps/test-snap-bip-44.spec.js b/test/e2e/snaps/test-snap-bip-44.spec.js index 1eb563ac564c..d002ec0aeb23 100644 --- a/test/e2e/snaps/test-snap-bip-44.spec.js +++ b/test/e2e/snaps/test-snap-bip-44.spec.js @@ -1,4 +1,9 @@ -const { withFixtures } = require('../helpers'); +const { + withFixtures, + unlockWallet, + switchToNotificationWindow, + WINDOW_TITLES, +} = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); @@ -22,10 +27,7 @@ describe('Test Snap bip-44', function () { }, async ({ driver }) => { await driver.navigate(); - - // enter pw into extension - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // navigate to test snaps page and connect await driver.driver.get(TEST_SNAPS_WEBSITE_URL); @@ -39,15 +41,7 @@ describe('Test Snap bip-44', function () { await driver.delay(1000); // switch to metamask extension and click connect and approve - let windowHandles = await driver.waitUntilXWindowHandles( - 2, - 1000, - 10000, - ); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await switchToNotificationWindow(driver, 2); await driver.clickElement({ text: 'Connect', tag: 'button', @@ -76,7 +70,7 @@ describe('Test Snap bip-44', function () { }); // switch back to test-snaps window - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); // wait for npm installation success await driver.waitForSelector({ @@ -102,19 +96,19 @@ describe('Test Snap bip-44', function () { await driver.clickElement('#signBip44Message'); // Switch to approve signature message window and approve - windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await switchToNotificationWindow(driver, 2); await driver.clickElement({ text: 'Approve', tag: 'button', }); // switch back to test-snaps page - windowHandles = await driver.waitUntilXWindowHandles(1, 1000, 10000); - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + const windowHandles = await driver.waitUntilXWindowHandles( + 1, + 1000, + 10000, + ); + await driver.switchToWindow(windowHandles[0]); // check the results of the message signature using waitForSelector await driver.waitForSelector({ diff --git a/test/e2e/snaps/test-snap-cronjob.spec.js b/test/e2e/snaps/test-snap-cronjob.spec.js index 91466b94c32b..690c08fdb7d2 100644 --- a/test/e2e/snaps/test-snap-cronjob.spec.js +++ b/test/e2e/snaps/test-snap-cronjob.spec.js @@ -1,5 +1,4 @@ -const { strict: assert } = require('assert'); -const { withFixtures } = require('../helpers'); +const { withFixtures, unlockWallet } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); @@ -24,9 +23,7 @@ describe('Test Snap Cronjob', function () { async ({ driver }) => { await driver.navigate(); - // enter pw into extension - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // navigate to test snaps page and connect await driver.openNewPage(TEST_SNAPS_WEBSITE_URL); @@ -84,12 +81,10 @@ describe('Test Snap Cronjob', function () { await driver.delay(1000); // look for the dialog popup to verify cronjob fired - const error = await driver.findElement('.snap-delineator__content'); - const text = await error.getText(); - assert.equal( - text.includes(`Cronjob\nThis dialog was triggered by a cronjob.`), - true, - ); + await driver.findElement({ + css: '.snap-delineator__content', + text: 'This dialog was triggered by a cronjob', + }); // try to click on the Ok button and pass test if it works await driver.clickElement({ diff --git a/test/e2e/snaps/test-snap-management.spec.js b/test/e2e/snaps/test-snap-management.spec.js index 0e428f3869de..da7bac9a4a14 100644 --- a/test/e2e/snaps/test-snap-management.spec.js +++ b/test/e2e/snaps/test-snap-management.spec.js @@ -1,5 +1,5 @@ const { strict: assert } = require('assert'); -const { withFixtures } = require('../helpers'); +const { withFixtures, unlockWallet } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); @@ -23,10 +23,7 @@ describe('Test Snap Management', function () { }, async ({ driver }) => { await driver.navigate(); - - // enter pw into extension - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // open a new tab and navigate to test snaps page and connect await driver.openNewPage(TEST_SNAPS_WEBSITE_URL); @@ -144,14 +141,11 @@ describe('Test Snap Management', function () { await driver.delay(1000); // check the results of the removal - await driver.delay(2000); - const removeResult = await driver.findElement( - '.snaps__content__list__container--no-snaps_inner', - ); - assert.equal( - await removeResult.getText(), - "You don't have any snaps installed.", - ); + await driver.findElement({ + css: '.mm-box', + text: "You don't have any snaps installed.", + tag: 'p', + }); }, ); }); diff --git a/test/e2e/snaps/test-snap-notification.spec.js b/test/e2e/snaps/test-snap-notification.spec.js index 0d062826e554..23757d897ffd 100644 --- a/test/e2e/snaps/test-snap-notification.spec.js +++ b/test/e2e/snaps/test-snap-notification.spec.js @@ -1,5 +1,4 @@ -const { strict: assert } = require('assert'); -const { withFixtures } = require('../helpers'); +const { withFixtures, unlockWallet } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); @@ -23,10 +22,7 @@ describe('Test Snap Notification', function () { }, async ({ driver }) => { await driver.navigate(); - - // enter pw into extension - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // navigate to test snaps page await driver.openNewPage(TEST_SNAPS_WEBSITE_URL); @@ -88,10 +84,10 @@ describe('Test Snap Notification', function () { await driver.clickElement( '[data-testid="account-options-menu-button"]', ); - const notificationResult = await driver.findElement( - '[data-testid="global-menu-notification-count"]', - ); - assert.equal(await notificationResult.getText(), '1'); + await driver.findElement({ + css: '[data-testid="global-menu-notification-count"]', + text: '1', + }); await driver.clickElement('.menu__background'); // try to click on the account menu icon (via xpath) @@ -108,13 +104,10 @@ describe('Test Snap Notification', function () { await driver.delay(500); // look for the correct text in notifications (via xpath) - const notificationResultMessage = await driver.findElement( - '.notifications__item__details__message', - ); - assert.equal( - await notificationResultMessage.getText(), - 'Hello from within MetaMask!', - ); + await driver.findElement({ + css: '.notifications__item__details__message', + text: 'Hello from within MetaMask!', + }); }, ); }); diff --git a/test/e2e/snaps/test-snap-revoke-perm.spec.js b/test/e2e/snaps/test-snap-revoke-perm.spec.js index 6aa8e115018e..f55033773169 100644 --- a/test/e2e/snaps/test-snap-revoke-perm.spec.js +++ b/test/e2e/snaps/test-snap-revoke-perm.spec.js @@ -1,4 +1,9 @@ -const { withFixtures } = require('../helpers'); +const { + withFixtures, + WINDOW_TITLES, + switchToNotificationWindow, + unlockWallet, +} = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); @@ -22,10 +27,7 @@ describe('Test Snap revoke permission', function () { }, async ({ driver }) => { await driver.navigate(); - - // enter pw into extension - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // navigate to test snaps page and connect await driver.openNewPage(TEST_SNAPS_WEBSITE_URL); @@ -39,15 +41,7 @@ describe('Test Snap revoke permission', function () { await driver.delay(1000); // switch to metamask extension and click connect - const windowHandles = await driver.waitUntilXWindowHandles( - 3, - 1000, - 10000, - ); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await switchToNotificationWindow(driver, 3); await driver.clickElement({ text: 'Connect', tag: 'button', @@ -68,7 +62,7 @@ describe('Test Snap revoke permission', function () { }); // switch to test snap page - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); // wait for npm installation success await driver.waitForSelector({ @@ -85,15 +79,7 @@ describe('Test Snap revoke permission', function () { await driver.clickElement('#sendEthproviderAccounts'); // switch to metamask window and click through confirmations - const windowHandles2 = await driver.waitUntilXWindowHandles( - 3, - 1000, - 10000, - ); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles2, - ); + await switchToNotificationWindow(driver, 3); await driver.clickElement({ text: 'Next', tag: 'button', @@ -105,7 +91,7 @@ describe('Test Snap revoke permission', function () { }); // switch to test snap page - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); // check the results of the message signature using waitForSelector await driver.waitForSelector({ @@ -114,8 +100,9 @@ describe('Test Snap revoke permission', function () { }); // switch to the original MM tab - const extensionPage = windowHandles[0]; - await driver.switchToWindow(extensionPage); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); await driver.delay(1000); // click on the global action menu @@ -145,7 +132,7 @@ describe('Test Snap revoke permission', function () { }); // switch to test snap page - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); // find and click on send get version const snapButton4 = await driver.findElement( @@ -156,15 +143,8 @@ describe('Test Snap revoke permission', function () { await driver.clickElement('#sendEthproviderAccounts'); // switch to metamask window and click through confirmations - const windowHandles3 = await driver.waitUntilXWindowHandles( - 3, - 1000, - 10000, - ); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles3, - ); + await driver.delay(500); + await switchToNotificationWindow(driver, 3); await driver.clickElement({ text: 'Next', tag: 'button', @@ -176,7 +156,7 @@ describe('Test Snap revoke permission', function () { }); // switch to test snap page - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); // check the results of the message signature using waitForSelector await driver.waitForSelector({ diff --git a/test/e2e/snaps/test-snap-rpc.spec.js b/test/e2e/snaps/test-snap-rpc.spec.js index 35d12927ad0d..1da9856a0971 100644 --- a/test/e2e/snaps/test-snap-rpc.spec.js +++ b/test/e2e/snaps/test-snap-rpc.spec.js @@ -1,4 +1,8 @@ -const { withFixtures } = require('../helpers'); +const { + withFixtures, + switchToNotificationWindow, + unlockWallet, +} = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); @@ -22,10 +26,7 @@ describe('Test Snap RPC', function () { }, async ({ driver }) => { await driver.navigate(); - - // enter pw into extension - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // navigate to test snaps page await driver.driver.get(TEST_SNAPS_WEBSITE_URL); @@ -39,15 +40,7 @@ describe('Test Snap RPC', function () { await driver.delay(1000); // switch to metamask extension and click connect - let windowHandles = await driver.waitUntilXWindowHandles( - 2, - 1000, - 10000, - ); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await switchToNotificationWindow(driver, 2); await driver.clickElement({ text: 'Connect', tag: 'button', @@ -55,7 +48,7 @@ describe('Test Snap RPC', function () { await driver.waitForSelector({ text: 'Install' }); - await driver.clickElement('[data-testid="snap-install-scroll"]'); + await driver.clickElementSafe('[data-testid="snap-install-scroll"]'); await driver.clickElement({ text: 'Install', @@ -82,7 +75,7 @@ describe('Test Snap RPC', function () { }); // switch back to test-snaps window - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + await driver.switchToWindowWithTitle('Test Snaps'); const snapButton2 = await driver.findElement('#connectjson-rpc'); await driver.scrollToElement(snapButton2); @@ -90,11 +83,7 @@ describe('Test Snap RPC', function () { await driver.clickElement('#connectjson-rpc'); await driver.delay(1000); - windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await switchToNotificationWindow(driver, 2); await driver.clickElement({ text: 'Connect', tag: 'button', @@ -114,7 +103,7 @@ describe('Test Snap RPC', function () { tag: 'button', }); - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + await driver.switchToWindowWithTitle('Test Snaps'); // wait for npm installation success await driver.waitForSelector({ diff --git a/test/e2e/snaps/test-snap-update.spec.js b/test/e2e/snaps/test-snap-update.spec.js index e882cf381423..c587f97caace 100644 --- a/test/e2e/snaps/test-snap-update.spec.js +++ b/test/e2e/snaps/test-snap-update.spec.js @@ -1,4 +1,8 @@ -const { withFixtures } = require('../helpers'); +const { + withFixtures, + switchToNotificationWindow, + unlockWallet, +} = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); @@ -22,10 +26,7 @@ describe('Test Snap update', function () { }, async ({ driver }) => { await driver.navigate(); - - // enter pw into extension - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // open a new tab and navigate to test snaps page and connect await driver.driver.get(TEST_SNAPS_WEBSITE_URL); @@ -39,15 +40,7 @@ describe('Test Snap update', function () { await driver.delay(1000); // switch to metamask extension and click connect - let windowHandles = await driver.waitUntilXWindowHandles( - 2, - 1000, - 10000, - ); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await switchToNotificationWindow(driver, 2); await driver.clickElement({ text: 'Connect', tag: 'button', @@ -55,7 +48,7 @@ describe('Test Snap update', function () { await driver.waitForSelector({ text: 'Install' }); - await driver.clickElement('[data-testid="snap-install-scroll"]'); + await driver.clickElementSafe('[data-testid="snap-install-scroll"]'); await driver.clickElement({ text: 'Install', @@ -82,7 +75,12 @@ describe('Test Snap update', function () { }); // navigate to test snap page - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + let windowHandles = await driver.waitUntilXWindowHandles( + 1, + 1000, + 10000, + ); + await driver.switchToWindow(windowHandles[0]); // wait for npm installation success await driver.waitForSelector({ @@ -98,16 +96,11 @@ describe('Test Snap update', function () { await driver.delay(1000); // switch to metamask extension and update - await driver.waitUntilXWindowHandles(2, 1000, 10000); - windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await switchToNotificationWindow(driver, 2); await driver.waitForSelector({ text: 'Update' }); - await driver.clickElement('[data-testid="snap-update-scroll"]'); + await driver.clickElementSafe('[data-testid="snap-update-scroll"]'); await driver.clickElement({ text: 'Update', @@ -122,7 +115,8 @@ describe('Test Snap update', function () { }); // navigate to test snap page - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + windowHandles = await driver.waitUntilXWindowHandles(1, 1000, 10000); + await driver.switchToWindow(windowHandles[0]); // look for the correct version text await driver.waitForSelector({ diff --git a/test/e2e/tests/add-account.spec.js b/test/e2e/tests/add-account.spec.js index 6c9d76174984..135f94d4588b 100644 --- a/test/e2e/tests/add-account.spec.js +++ b/test/e2e/tests/add-account.spec.js @@ -9,13 +9,13 @@ const { convertToHexValue, regularDelayMs, unlockWallet, + WALLET_PASSWORD, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { shortenAddress } = require('../../../ui/helpers/utils/util'); describe('Add account', function () { - const testPassword = 'correct horse battery staple'; const firstAccount = '0x0Cc5261AB8cE458dc977078A3623E2BaDD27afD3'; const secondAccount = '0x3ED0eE22E0685Ebbf07b2360A8331693c413CC59'; @@ -76,7 +76,7 @@ describe('Add account', function () { await completeImportSRPOnboardingFlow( driver, TEST_SEED_PHRASE, - testPassword, + WALLET_PASSWORD, ); // Check address of 1st account diff --git a/test/e2e/tests/ens.spec.js b/test/e2e/tests/ens.spec.js index 583d918ea91a..5b645d2068e9 100644 --- a/test/e2e/tests/ens.spec.js +++ b/test/e2e/tests/ens.spec.js @@ -1,8 +1,8 @@ -const { strict: assert } = require('assert'); const { convertToHexValue, withFixtures, openActionMenuAndStartSendFlow, + unlockWallet, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); @@ -89,8 +89,7 @@ describe('ENS', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); await driver.waitForElementNotPresent('.loading-overlay'); @@ -108,25 +107,15 @@ describe('ENS', function () { '.send__select-recipient-wrapper__group-item__title', ); - const currentEnsDomain = await driver.findElement( - '.ens-input__selected-input__title', - ); - - assert.equal( - await currentEnsDomain.getText(), - 'test.eth', - 'Domain name not correct', - ); + await driver.findElement({ + css: '.ens-input__selected-input__title', + text: 'test.eth', + }); - const resolvedAddress = await driver.findElement( - '.ens-input__selected-input__subtitle', - ); - - assert.equal( - await resolvedAddress.getText(), - `0x${sampleAddress}`, - 'Resolved address not correct', - ); + await driver.findElement({ + css: '.ens-input__selected-input__subtitle', + text: `0x${sampleAddress}`, + }); }, ); }); diff --git a/test/e2e/tests/import-flow.spec.js b/test/e2e/tests/import-flow.spec.js index e8652b502475..89a08722e835 100644 --- a/test/e2e/tests/import-flow.spec.js +++ b/test/e2e/tests/import-flow.spec.js @@ -10,6 +10,8 @@ const { completeImportSRPOnboardingFlowWordByWord, findAnotherAccountFromAccountList, openActionMenuAndStartSendFlow, + unlockWallet, + WALLET_PASSWORD, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { emptyHtmlPage } = require('../mock-e2e'); @@ -37,7 +39,6 @@ async function mockTrezor(mockServer) { describe('Import flow @no-mmi', function () { it('Import wallet using Secret Recovery Phrase', async function () { - const testPassword = 'correct horse battery staple'; if (process.env.MULTICHAIN) { return; } @@ -54,7 +55,7 @@ describe('Import flow @no-mmi', function () { await completeImportSRPOnboardingFlow( driver, TEST_SEED_PHRASE, - testPassword, + WALLET_PASSWORD, ); // Show account information @@ -68,10 +69,10 @@ describe('Import flow @no-mmi', function () { // shows a QR code for the account await driver.findVisibleElement('.mm-modal'); // shows the correct account address - const address = await driver.findElement( - '.multichain-address-copy-button', - ); - assert.equal(await address.getText(), '0x0Cc52...7afD3'); + await driver.findElement({ + css: '.multichain-address-copy-button', + text: '0x0Cc52...7afD3', + }); await driver.clickElement('.mm-modal button[aria-label="Close"]'); @@ -79,15 +80,13 @@ describe('Import flow @no-mmi', function () { await driver.clickElement( '[data-testid="account-options-menu-button"]', ); - const lockButton = await driver.findClickableElement( - '[data-testid="global-menu-lock"]', - ); - assert.equal(await lockButton.getText(), 'Lock MetaMask'); - await lockButton.click(); + await driver.clickElement({ + css: '[data-testid="global-menu-lock"]', + text: 'Lock MetaMask', + }); // accepts the account password after lock - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // Create a new account // switches to localhost @@ -159,7 +158,6 @@ describe('Import flow @no-mmi', function () { }); it('Import wallet using Secret Recovery Phrase with pasting word by word', async function () { - const testPassword = 'correct horse battery staple'; const testAddress = '0x0Cc5261AB8cE458dc977078A3623E2BaDD27afD3'; await withFixtures( @@ -175,7 +173,7 @@ describe('Import flow @no-mmi', function () { await completeImportSRPOnboardingFlowWordByWord( driver, TEST_SEED_PHRASE, - testPassword, + WALLET_PASSWORD, ); // Show account information @@ -186,13 +184,10 @@ describe('Import flow @no-mmi', function () { await driver.clickElement('[data-testid="account-list-menu-details"'); await driver.findVisibleElement('.qr-code__wrapper'); // shows the correct account address - const address = process.env.MULTICHAIN - ? await driver.findElement('[data-testid="address-copy-button-text"]') - : await driver.findElement( - '.qr-code [data-testid="address-copy-button-text"]', - ); - - assert.equal(await address.getText(), testAddress); + await driver.findElement({ + css: '.qr-code [data-testid="address-copy-button-text"]', + text: testAddress, + }); }, ); }); @@ -214,8 +209,7 @@ describe('Import flow @no-mmi', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); await driver.clickElement('[data-testid="account-menu-icon"]'); await driver.clickElement( @@ -300,8 +294,7 @@ describe('Import flow @no-mmi', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // Imports an account with JSON file await driver.clickElement('[data-testid="account-menu-icon"]'); @@ -366,8 +359,7 @@ describe('Import flow @no-mmi', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // choose Import Account from the account menu await driver.clickElement('[data-testid="account-menu-icon"]'); @@ -406,8 +398,7 @@ describe('Import flow @no-mmi', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // choose Connect hardware wallet from the account menu await driver.clickElement('[data-testid="account-menu-icon"]'); @@ -424,8 +415,8 @@ describe('Import flow @no-mmi', function () { await driver.clickElement('.hw-connect__btn:nth-of-type(2)'); await driver.delay(largeDelayMs * 2); await driver.clickElement({ text: 'Continue', tag: 'button' }); - await driver.waitUntilXWindowHandles(2); - const allWindows = await driver.getAllWindowHandles(); + + const allWindows = await driver.waitUntilXWindowHandles(2); assert.equal(allWindows.length, 2); }, ); diff --git a/test/e2e/tests/metrics/unlock-wallet.spec.js b/test/e2e/tests/metrics/unlock-wallet.spec.js index e92b32a75715..dd8e35b09c25 100644 --- a/test/e2e/tests/metrics/unlock-wallet.spec.js +++ b/test/e2e/tests/metrics/unlock-wallet.spec.js @@ -37,11 +37,14 @@ describe('Unlock wallet', function () { await driver.navigate(); await unlockWallet(driver); await waitForAccountRendered(driver); + + let mockedRequests; await driver.wait(async () => { const isPending = await mockedEndpoint.isPending(); - return isPending === false; + mockedRequests = await mockedEndpoint.getSeenRequests(); + return isPending === false && mockedRequests.length === 3; }, 10000); - const mockedRequests = await mockedEndpoint.getSeenRequests(); + assert.equal(mockedRequests.length, 3); const [firstMock, secondMock, thirdMock] = mockedRequests; assertBatchValue(firstMock, 'Home', '/'); diff --git a/test/e2e/tests/nft/remove-erc1155.spec.js b/test/e2e/tests/nft/remove-erc1155.spec.js index 776a5d4ae9d5..4cf1410b41a3 100644 --- a/test/e2e/tests/nft/remove-erc1155.spec.js +++ b/test/e2e/tests/nft/remove-erc1155.spec.js @@ -34,10 +34,7 @@ describe('Remove ERC1155 NFT', function () { // Open the details page and click remove nft button await driver.clickElement('[data-testid="home__nfts-tab"]'); - const importedNftImage = await driver.findVisibleElement( - '.nft-item__container', - ); - await importedNftImage.click(); + await driver.clickElement('[data-testid="nft-image"]'); await driver.clickElement('[data-testid="nft-options__button"]'); await driver.clickElement('[data-testid="nft-item-remove"]'); diff --git a/test/e2e/tests/nft/view-erc1155-details.spec.js b/test/e2e/tests/nft/view-erc1155-details.spec.js index 554ba6a70acb..e265e4ef6b2e 100644 --- a/test/e2e/tests/nft/view-erc1155-details.spec.js +++ b/test/e2e/tests/nft/view-erc1155-details.spec.js @@ -1,4 +1,3 @@ -const { strict: assert } = require('assert'); const { convertToHexValue, withFixtures, @@ -35,36 +34,33 @@ describe('View ERC1155 NFT details', function () { // Click to open the NFT details page and check displayed account await driver.clickElement('[data-testid="home__nfts-tab"]'); + // wait for the image to load await driver.delay(regularDelayMs); - const importedNftImage = await driver.findVisibleElement( - '.nft-item__container', - ); - await importedNftImage.click(); - const detailsPageAccount = await driver.findElement( - '.asset-breadcrumb span:nth-of-type(2)', - ); - assert.equal(await detailsPageAccount.getText(), 'Account 1'); + await driver.clickElement('.nft-item__container'); + + await driver.findElement({ + css: '.asset-breadcrumb span:nth-of-type(2)', + text: 'Account 1', + }); // Check the displayed ERC1155 NFT details - const nftName = await driver.findElement('.nft-details__info h4'); - assert.equal(await nftName.getText(), 'Rocks'); + await driver.findElement({ + css: '.nft-details__info h4', + text: 'Rocks', + }); - const nftDescription = await driver.findElement( - '.nft-details__info h6:nth-of-type(2)', - ); - assert.equal( - await nftDescription.getText(), - 'This is a collection of Rock NFTs.', - ); + await driver.findElement({ + css: '.nft-details__info h6:nth-of-type(2)', + text: 'This is a collection of Rock NFTs.', + }); - const nftImage = await driver.findElement('.nft-item__container'); - assert.equal(await nftImage.isDisplayed(), true); + await driver.findVisibleElement('.nft-item__container'); - const nftContract = await driver.findElement( - '.nft-details__contract-wrapper', - ); - assert.equal(await nftContract.getText(), '0x581c3...45947'); + await driver.findElement({ + css: '.nft-details__contract-wrapper', + text: '0x581c3...45947', + }); }, ); }); diff --git a/test/e2e/tests/onboarding.spec.js b/test/e2e/tests/onboarding.spec.js index 5ab4cb9d30b4..173ff72ff072 100644 --- a/test/e2e/tests/onboarding.spec.js +++ b/test/e2e/tests/onboarding.spec.js @@ -11,11 +11,11 @@ const { testSRPDropdownIterations, locateAccountBalanceDOM, defaultGanacheOptions, + WALLET_PASSWORD, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); describe('MetaMask onboarding @no-mmi', function () { - const testPassword = 'correct horse battery staple'; const wrongSeedPhrase = 'test test test test test test test test test test test test'; const wrongTestPassword = 'test test test test'; @@ -41,7 +41,7 @@ describe('MetaMask onboarding @no-mmi', function () { async ({ driver }) => { await driver.navigate(); - await completeCreateNewWalletOnboardingFlow(driver, testPassword); + await completeCreateNewWalletOnboardingFlow(driver, WALLET_PASSWORD); const homePage = await driver.findElement('.home__main-view'); const homePageDisplayed = await homePage.isDisplayed(); @@ -65,7 +65,7 @@ describe('MetaMask onboarding @no-mmi', function () { await completeImportSRPOnboardingFlow( driver, TEST_SEED_PHRASE, - testPassword, + WALLET_PASSWORD, ); const homePage = await driver.findElement('.home__main-view'); @@ -156,7 +156,10 @@ describe('MetaMask onboarding @no-mmi', function () { await driver.clickElement('[data-testid="metametrics-no-thanks"]'); // Fill in confirm password field with incorrect password - await driver.fill('[data-testid="create-password-new"]', testPassword); + await driver.fill( + '[data-testid="create-password-new"]', + WALLET_PASSWORD, + ); await driver.fill( '[data-testid="create-password-confirm"]', wrongTestPassword, @@ -188,7 +191,11 @@ describe('MetaMask onboarding @no-mmi', function () { async ({ driver }) => { await driver.navigate(); - await importSRPOnboardingFlow(driver, TEST_SEED_PHRASE, testPassword); + await importSRPOnboardingFlow( + driver, + TEST_SEED_PHRASE, + WALLET_PASSWORD, + ); // Verify site assert.equal( await driver.isElementPresent({ @@ -219,10 +226,13 @@ describe('MetaMask onboarding @no-mmi', function () { await driver.clickElement('[data-testid="metametrics-no-thanks"]'); // Fill in confirm password field with correct password - await driver.fill('[data-testid="create-password-new"]', testPassword); + await driver.fill( + '[data-testid="create-password-new"]', + WALLET_PASSWORD, + ); await driver.fill( '[data-testid="create-password-confirm"]', - testPassword, + WALLET_PASSWORD, ); await driver.clickElement('[data-testid="create-password-terms"]'); await driver.clickElement('[data-testid="create-password-wallet"]'); @@ -257,7 +267,11 @@ describe('MetaMask onboarding @no-mmi', function () { async ({ driver, secondaryGanacheServer }) => { await driver.navigate(); - await importSRPOnboardingFlow(driver, TEST_SEED_PHRASE, testPassword); + await importSRPOnboardingFlow( + driver, + TEST_SEED_PHRASE, + WALLET_PASSWORD, + ); // Add custome network localhost 8546 during onboarding await driver.clickElement({ text: 'Advanced configuration', tag: 'a' }); diff --git a/test/e2e/tests/ppom-blockaid-alert.spec.js b/test/e2e/tests/ppom-blockaid-alert.spec.js index 1d7fbefda3a5..e0566effb305 100644 --- a/test/e2e/tests/ppom-blockaid-alert.spec.js +++ b/test/e2e/tests/ppom-blockaid-alert.spec.js @@ -8,6 +8,7 @@ const { openDapp, unlockWallet, withFixtures, + switchToNotificationWindow, } = require('../helpers'); const bannerAlertSelector = '[data-testid="security-provider-banner-alert"]'; @@ -183,12 +184,9 @@ describe('Confirmation Security Alert - Blockaid @no-mmi', function () { ); } - const windowHandles = await driver.waitUntilXWindowHandles(3); // Wait for confirmation pop-up - await driver.switchToWindowWithTitle( - WINDOW_TITLES.Notification, - windowHandles, - ); + await driver.delay(500); + await switchToNotificationWindow(driver, 3); const isPresent = await driver.isElementPresent(bannerAlertSelector); assert.equal( @@ -199,10 +197,7 @@ describe('Confirmation Security Alert - Blockaid @no-mmi', function () { // Wait for confirmation pop-up to close await driver.clickElement({ text: 'Reject', tag: 'button' }); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.TestDApp, - windowHandles, - ); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); } }, ); @@ -213,7 +208,8 @@ describe('Confirmation Security Alert - Blockaid @no-mmi', function () { * 'malicious_domain'. Some other tests are found in other files: * e.g. test/e2e/flask/ppom-blockaid-alert-.spec.js */ - it('should show security alerts for malicious requests', async function () { + // eslint-disable-next-line mocha/no-skipped-tests + it.skip('should show security alerts for malicious requests', async function () { await withFixtures( { dapp: true, @@ -236,16 +232,14 @@ describe('Confirmation Security Alert - Blockaid @no-mmi', function () { for (const config of testMaliciousConfigs) { const { expectedDescription, expectedReason, btnSelector } = config; + console.log('config', config); // Click TestDapp button to send JSON-RPC request await driver.clickElement(btnSelector); // Wait for confirmation pop-up - const windowHandles = await driver.waitUntilXWindowHandles(3); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.Notification, - windowHandles, - ); + await driver.delay(500); + await switchToNotificationWindow(driver, 3); // Find element by title const bannerAlertFoundByTitle = await driver.findElement({ @@ -265,10 +259,7 @@ describe('Confirmation Security Alert - Blockaid @no-mmi', function () { // Wait for confirmation pop-up to close await driver.clickElement({ text: 'Reject', tag: 'button' }); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.TestDApp, - windowHandles, - ); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); } }, ); @@ -298,11 +289,8 @@ describe('Confirmation Security Alert - Blockaid @no-mmi', function () { await driver.clickElement('#maliciousApprovalButton'); // Wait for confirmation pop-up - const windowHandles = await driver.waitUntilXWindowHandles(3); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.Notification, - windowHandles, - ); + await driver.delay(500); + await switchToNotificationWindow(driver, 3); const expectedTitle = 'Request may not be safe'; diff --git a/test/e2e/tests/send-eth.spec.js b/test/e2e/tests/send-eth.spec.js index c64efe1fc694..b84733be7538 100644 --- a/test/e2e/tests/send-eth.spec.js +++ b/test/e2e/tests/send-eth.spec.js @@ -7,6 +7,7 @@ const { locateAccountBalanceDOM, logInWithBalanceValidation, openActionMenuAndStartSendFlow, + unlockWallet, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); @@ -43,12 +44,10 @@ describe('Send ETH from inside MetaMask using default gas', function () { const inputAmount = await driver.findElement('.unit-input__input'); await inputAmount.fill('1000'); - const errorAmount = await driver.findElement('.send-v2__error-amount'); - assert.equal( - await errorAmount.getText(), - 'Insufficient funds for gas', - 'send screen should render an insufficient fund for gas error message', - ); + await driver.findElement({ + css: '.send-v2__error-amount', + text: 'Insufficient funds for gas', + }); await inputAmount.press(driver.Key.BACK_SPACE); await inputAmount.press(driver.Key.BACK_SPACE); @@ -175,8 +174,7 @@ describe('Send ETH from inside MetaMask using advanced gas modal', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); await openActionMenuAndStartSendFlow(driver); if (process.env.MULTICHAIN) { @@ -196,6 +194,7 @@ describe('Send ETH from inside MetaMask using advanced gas modal', function () { // Continue to next screen await driver.clickElement({ text: 'Next', tag: 'button' }); + await driver.delay(1000); const transactionAmounts = await driver.findElements( '.currency-display-component__text', ); @@ -243,8 +242,7 @@ describe('Send ETH from dapp using advanced gas controls', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // initiates a send from the dapp await openDapp(driver); @@ -294,15 +292,13 @@ describe('Send ETH from dapp using advanced gas controls', function () { }); // the transaction has the expected gas price - const txValue = await driver.findClickableElement( + driver.clickElement( '[data-testid="transaction-list-item-primary-currency"]', ); - await txValue.click(); - const gasPrice = await driver.waitForSelector({ + await driver.waitForSelector({ css: '[data-testid="transaction-breakdown__gas-price"]', text: '100', }); - assert.equal(await gasPrice.getText(), '100'); }, ); }); @@ -322,14 +318,13 @@ describe('Send ETH from dapp using advanced gas controls', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // initiates a transaction from the dapp await openDapp(driver); await driver.clickElement({ text: 'Create Token', tag: 'button' }); - await driver.waitUntilXWindowHandles(3); - const windowHandles = await driver.getAllWindowHandles(); + const windowHandles = await driver.waitUntilXWindowHandles(3); + const extension = windowHandles[0]; await driver.switchToWindowWithTitle( 'MetaMask Notification', @@ -358,7 +353,6 @@ describe('Send ETH from dapp using advanced gas controls', function () { text: '0.04503836 ETH', }); - await driver.findClickableElement({ text: 'Confirm', tag: 'button' }); await driver.clickElement({ text: 'Confirm', tag: 'button' }); await driver.waitUntilXWindowHandles(2); await driver.switchToWindow(extension); @@ -380,17 +374,13 @@ describe('Send ETH from dapp using advanced gas controls', function () { }); // the transaction has the expected gas value - const txValue = await driver.findClickableElement( + await driver.clickElement( '[data-testid="transaction-list-item-primary-currency"]', ); - await txValue.click(); - const baseFeeValue = await driver.waitForSelector( - { - text: '0.000000025', - }, - { timeout: 15000 }, - ); - assert.equal(await baseFeeValue.getText(), '0.000000025'); + + await driver.waitForSelector({ + text: '0.000000025', + }); }, ); }); @@ -442,12 +432,10 @@ describe('Send ETH from inside MetaMask to a Multisig Address', function () { // Go back to home screen to check txn await locateAccountBalanceDOM(driver, ganacheServer); await driver.clickElement('[data-testid="home__activity-tab"]'); - const txn = await driver.isElementPresent( + + await driver.findElement( '.transaction-list__completed-transactions .activity-list-item', ); - - assert.equal(txn, true); - await driver.assertElementNotPresent( '.transaction-status-label--failed', ); diff --git a/test/e2e/webdriver/chrome.js b/test/e2e/webdriver/chrome.js index 1b32bd6752d0..1bf470cc47ff 100644 --- a/test/e2e/webdriver/chrome.js +++ b/test/e2e/webdriver/chrome.js @@ -1,6 +1,7 @@ const { Builder } = require('selenium-webdriver'); const chrome = require('selenium-webdriver/chrome'); const proxy = require('selenium-webdriver/proxy'); +const { ThenableWebDriver } = require('selenium-webdriver'); // eslint-disable-line no-unused-vars -- this is imported for JSDoc /** * Proxy host to use for HTTPS requests @@ -14,7 +15,7 @@ const HTTPS_PROXY_HOST = '127.0.0.1:8000'; */ class ChromeDriver { static async build({ openDevToolsForTabs, port }) { - const args = [`load-extension=dist/chrome`]; + const args = [`load-extension=${process.cwd()}/dist/chrome`]; if (openDevToolsForTabs) { args.push('--auto-open-devtools-for-tabs'); } diff --git a/test/e2e/webdriver/driver.js b/test/e2e/webdriver/driver.js index 7d1da18245c2..2336db32f776 100644 --- a/test/e2e/webdriver/driver.js +++ b/test/e2e/webdriver/driver.js @@ -6,16 +6,25 @@ const { error: webdriverError, Key, until, + ThenableWebDriver, // eslint-disable-line no-unused-vars -- this is imported for JSDoc + WebElement, // eslint-disable-line no-unused-vars -- this is imported for JSDoc } = require('selenium-webdriver'); const cssToXPath = require('css-to-xpath'); const { retry } = require('../../../development/lib/retry'); +const PAGES = { + BACKGROUND: 'background', + HOME: 'home', + NOTIFICATION: 'notification', + POPUP: 'popup', +}; + /** * Temporary workaround to patch selenium's element handle API with methods * that match the playwright API for Elements * * @param {object} element - Selenium Element - * @param driver + * @param {!ThenableWebDriver} driver * @returns {object} modified Selenium Element */ function wrapElementWithAPI(element, driver) { @@ -49,13 +58,14 @@ function wrapElementWithAPI(element, driver) { until.elementIsNotPresent = function elementIsNotPresent(locator) { return new Condition(`Element not present`, function (driver) { - return driver.findElements(By.css(locator)).then(function (elements) { + return driver.findElements(locator).then(function (elements) { return elements.length === 0; }); }); }; /** + * This is MetaMask's custom E2E test driver, wrapping the Selenium WebDriver. * For Selenium WebDriver API documentation, see: * https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html */ @@ -195,8 +205,9 @@ class Driver { }, this.timeout); } - async waitForElementNotPresent(element) { - return await this.driver.wait(until.elementIsNotPresent(element)); + async waitForElementNotPresent(rawLocator) { + const locator = this.buildLocator(rawLocator); + return await this.driver.wait(until.elementIsNotPresent(locator)); } async quit() { @@ -205,6 +216,10 @@ class Driver { // Element interactions + /** + * @param {*} rawLocator + * @returns {WebElement} + */ async findElement(rawLocator) { const locator = this.buildLocator(rawLocator); const element = await this.driver.wait( @@ -369,7 +384,7 @@ class Driver { // Navigation - async navigate(page = Driver.PAGES.HOME) { + async navigate(page = PAGES.HOME) { const response = await this.driver.get(`${this.extensionUrl}/${page}.html`); // Wait for asyncronous JavaScript to load await this.driver.wait( @@ -624,11 +639,4 @@ function collectMetrics() { return results; } -Driver.PAGES = { - BACKGROUND: 'background', - HOME: 'home', - NOTIFICATION: 'notification', - POPUP: 'popup', -}; - -module.exports = Driver; +module.exports = { Driver, PAGES }; diff --git a/test/e2e/webdriver/firefox.js b/test/e2e/webdriver/firefox.js index 52e936f09210..5740a0bd179f 100644 --- a/test/e2e/webdriver/firefox.js +++ b/test/e2e/webdriver/firefox.js @@ -1,9 +1,15 @@ const fs = require('fs'); const os = require('os'); const path = require('path'); -const { Builder, By, until } = require('selenium-webdriver'); +const { + Builder, + By, + until, + ThenableWebDriver, // eslint-disable-line no-unused-vars -- this is imported for JSDoc +} = require('selenium-webdriver'); const firefox = require('selenium-webdriver/firefox'); const proxy = require('selenium-webdriver/proxy'); +const { retry } = require('../../../development/lib/retry'); /** * The prefix for temporary Firefox profiles. All Firefox profiles used for e2e tests @@ -87,18 +93,37 @@ class FirefoxDriver { } /** - * Returns the Internal UUID for the given extension + * Returns the Internal UUID for the given extension, with retries * * @returns {Promise} the Internal UUID for the given extension */ async getInternalId() { await this._driver.get('about:debugging#addons'); - return await this._driver - .wait( - until.elementLocated(By.xpath("//dl/div[contains(., 'UUID')]/dd")), - 1000, - ) - .getText(); + + // This method with 2 retries to find the UUID seems more stable on local e2e tests + let uuid; + await retry({ retries: 2 }, () => (uuid = this._waitOnceForUUID())); + + return uuid; + } + + /** + * Waits once to locate the temporary Firefox UUID, can be put in a retry loop + * + * @returns {Promise} the UUID for the given extension, or null if not found + * @private + */ + async _waitOnceForUUID() { + const uuidElement = await this._driver.wait( + until.elementLocated(By.xpath("//dl/div[contains(., 'UUID')]/dd")), + 1000, + ); + + if (uuidElement.getText) { + return uuidElement.getText(); + } + + return null; } } diff --git a/test/e2e/webdriver/index.js b/test/e2e/webdriver/index.js index aeb6bc96487b..900e2f599157 100644 --- a/test/e2e/webdriver/index.js +++ b/test/e2e/webdriver/index.js @@ -1,5 +1,5 @@ const { Browser } = require('selenium-webdriver'); -const Driver = require('./driver'); +const { Driver } = require('./driver'); const ChromeDriver = require('./chrome'); const FirefoxDriver = require('./firefox'); diff --git a/ui/components/app/snaps/snap-authorship-expanded/snap-authorship-expanded.js b/ui/components/app/snaps/snap-authorship-expanded/snap-authorship-expanded.js index 8b7b6384ff71..45c31963d619 100644 --- a/ui/components/app/snaps/snap-authorship-expanded/snap-authorship-expanded.js +++ b/ui/components/app/snaps/snap-authorship-expanded/snap-authorship-expanded.js @@ -178,7 +178,7 @@ const SnapAuthorshipExpanded = ({ snapId, className, snap }) => { alignItems={AlignItems.flexEnd} > diff --git a/ui/components/multichain/pages/send/__snapshots__/send.test.js.snap b/ui/components/multichain/pages/send/__snapshots__/send.test.js.snap index aef3be1a0431..9878dd30d98f 100644 --- a/ui/components/multichain/pages/send/__snapshots__/send.test.js.snap +++ b/ui/components/multichain/pages/send/__snapshots__/send.test.js.snap @@ -173,619 +173,720 @@ exports[`SendPage render renders correctly 1`] = `
- -
-

- 0x0DCD5...3E7bc -

-
-
- - 966.988 - - - ETH - + + + + + +
-
- - - - - - - - - -
-
-

- 0xeB9e6...64823 -

-
-
- - 0 - - - ETH - + Imported +

-

- Imported -

-
- - - - + +
+
-

- 0xca8f1...Cf281 -

-
+ class="send__select-recipient-wrapper__recent-group-wrapper" + />
- - 0 - - +
+
+
+
+
+ + + + + +
+
+
+
- ETH - +

+ Address Book Account 1 +

+

+ 0xc42e...8813 +

+
diff --git a/ui/components/multichain/pages/send/components/address-book.tsx b/ui/components/multichain/pages/send/components/address-book.tsx new file mode 100644 index 000000000000..1b8ba9c8fca8 --- /dev/null +++ b/ui/components/multichain/pages/send/components/address-book.tsx @@ -0,0 +1,115 @@ +import React, { useContext } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import Fuse from 'fuse.js'; +import { Label } from '../../../../component-library'; +import { I18nContext } from '../../../../../contexts/i18n'; +import ContactList from '../../../../app/contact-list'; +import { + getAddressBook, + getCurrentNetworkTransactions, +} from '../../../../../selectors'; +import { + addHistoryEntry, + getRecipientUserInput, + updateRecipient, + updateRecipientUserInput, +} from '../../../../../ducks/send'; +import { SendPageRow } from '.'; + +export const SendPageAddressBook = () => { + const t = useContext(I18nContext); + const dispatch = useDispatch(); + + const addressBook = useSelector(getAddressBook); + const contacts = addressBook.filter(({ name }) => Boolean(name)); + const currentNetworkTransactions = useSelector(getCurrentNetworkTransactions); + + const txList = [...currentNetworkTransactions].reverse(); + const nonContacts = addressBook + .filter(({ name }) => !name) + .map((nonContact) => { + const nonContactTx = txList.find( + (transaction) => + transaction.txParams.to === nonContact.address.toLowerCase(), + ); + return { ...nonContact, timestamp: nonContactTx?.time }; + }); + + const userInput = useSelector(getRecipientUserInput); + const contactFuse = new Fuse(contacts, { + shouldSort: true, + threshold: 0.45, + location: 0, + distance: 100, + maxPatternLength: 32, + minMatchCharLength: 1, + keys: [ + { name: 'name', weight: 0.5 }, + { name: 'address', weight: 0.5 }, + ], + }); + + const recentFuse = new Fuse(nonContacts, { + shouldSort: true, + threshold: 0.45, + location: 0, + distance: 100, + maxPatternLength: 32, + minMatchCharLength: 1, + keys: [{ name: 'address', weight: 0.5 }], + }); + + const searchForContacts = () => { + if (userInput) { + contactFuse.setCollection(contacts); + return contactFuse.search(userInput); + } + + return contacts; + }; + + const searchForRecents = () => { + if (userInput) { + recentFuse.setCollection(nonContacts); + return recentFuse.search(userInput); + } + + return nonContacts; + }; + + const selectRecipient = ( + address = '', + nickname = '', + type = 'user input', + ) => { + dispatch( + addHistoryEntry( + `sendFlow - User clicked recipient from ${type}. address: ${address}, nickname ${nickname}`, + ), + ); + dispatch(updateRecipient({ address, nickname })); + dispatch(updateRecipientUserInput(address)); + }; + + return ( + + {addressBook.length ? ( + <> + + { + selectRecipient( + address, + name, + `${name ? 'contact' : 'recent'} list`, + ); + }} + /> + + ) : null} + + ); +}; diff --git a/ui/components/multichain/pages/send/components/index.ts b/ui/components/multichain/pages/send/components/index.ts index 7c3c3ffecb85..f7413bfde7c7 100644 --- a/ui/components/multichain/pages/send/components/index.ts +++ b/ui/components/multichain/pages/send/components/index.ts @@ -3,3 +3,5 @@ export { SendPageAccountPicker } from './account-picker'; export { SendPageNetworkPicker } from './network-picker'; export { SendPageYourAccount } from './your-accounts'; export { SendPageRecipientInput } from './recipient-input'; +export { SendPageAddressBook } from './address-book'; +export { SendPageRecipient } from './recipient'; diff --git a/ui/components/multichain/pages/send/components/recipient.tsx b/ui/components/multichain/pages/send/components/recipient.tsx new file mode 100644 index 000000000000..e00c25ddf7dd --- /dev/null +++ b/ui/components/multichain/pages/send/components/recipient.tsx @@ -0,0 +1,125 @@ +import React, { useContext } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { I18nContext } from '../../../../../contexts/i18n'; +import { + addHistoryEntry, + getRecipient, + getRecipientUserInput, + updateRecipient, + updateRecipientUserInput, +} from '../../../../../ducks/send'; +import { + getDomainError, + getDomainResolution, + getDomainWarning, +} from '../../../../../ducks/domains'; +import { + BannerAlert, + BannerAlertSeverity, + Box, +} from '../../../../component-library'; +import { getAddressBookEntry } from '../../../../../selectors'; +import Identicon from '../../../../ui/identicon'; +import Confusable from '../../../../ui/confusable'; +import { ellipsify } from '../../../../../pages/send/send.utils'; +import { SendPageAddressBook, SendPageRow, SendPageYourAccount } from '.'; + +const renderExplicitAddress = ( + address: string, + nickname: string, + type: string, + dispatch: any, +) => { + return ( +
{ + dispatch( + addHistoryEntry( + `sendFlow - User clicked recipient from ${type}. address: ${address}, nickname ${nickname}`, + ), + ); + dispatch(updateRecipient({ address, nickname })); + dispatch(updateRecipientUserInput(address)); + }} + > + +
+
+ {nickname ? : ellipsify(address)} +
+ {nickname && ( +
+ {ellipsify(address)} +
+ )} +
+
+ ); +}; + +export const SendPageRecipient = () => { + const t = useContext(I18nContext); + const dispatch = useDispatch(); + + const recipient = useSelector(getRecipient); + const userInput = useSelector(getRecipientUserInput); + + const domainResolution = useSelector(getDomainResolution); + const domainError = useSelector(getDomainError); + const domainWarning = useSelector(getDomainWarning); + + let addressBookEntryName = ''; + const entry = useSelector((state) => + getAddressBookEntry(state, domainResolution), + ); + if (domainResolution && entry?.name) { + addressBookEntryName = entry.name; + } + + const showErrorBanner = + domainError || (recipient.error && recipient.error !== 'required'); + const showWarningBanner = + !showErrorBanner && (domainWarning || recipient.warning); + + let contents; + if (recipient.address) { + contents = renderExplicitAddress( + recipient.address, + recipient.nickname, + 'validated user input', + dispatch, + ); + } else if (domainResolution && !recipient.error) { + contents = renderExplicitAddress( + domainResolution, + addressBookEntryName ?? userInput, + 'ENS resolution', + dispatch, + ); + } else { + contents = ( + <> + {userInput ? null : } + + + ); + } + + return ( + + {showErrorBanner ? ( + + {t(domainError ?? recipient.error)} + + ) : null} + {showWarningBanner ? ( + + {t(domainWarning ?? recipient.warning)} + + ) : null} + {contents} + + ); +}; diff --git a/ui/components/multichain/pages/send/send.js b/ui/components/multichain/pages/send/send.js index c1632a5b69b1..58f68b81b985 100644 --- a/ui/components/multichain/pages/send/send.js +++ b/ui/components/multichain/pages/send/send.js @@ -27,8 +27,8 @@ import { getMostRecentOverviewPage } from '../../../../ducks/history/history'; import { SendPageAccountPicker, SendPageRecipientInput, - SendPageYourAccount, SendPageNetworkPicker, + SendPageRecipient, } from './components'; export const SendPage = () => { @@ -116,7 +116,7 @@ export const SendPage = () => { - +