diff --git a/package.json b/package.json index 2b0dd630b50a..c8699e293fce 100644 --- a/package.json +++ b/package.json @@ -490,7 +490,7 @@ "@metamask/preferences-controller": "^15.0.1", "@metamask/test-bundler": "^1.0.0", "@metamask/test-dapp": "8.13.0", - "@metamask/test-dapp-multichain": "^0.5.0", + "@metamask/test-dapp-multichain": "^0.6.0", "@octokit/core": "^3.6.0", "@open-rpc/meta-schema": "^1.14.6", "@open-rpc/mock-server": "^1.7.5", diff --git a/test/e2e/flask/multichain-api/create-session.spec.ts b/test/e2e/flask/multichain-api/create-session.spec.ts index 44415f2c186d..dd5d426f99f9 100644 --- a/test/e2e/flask/multichain-api/create-session.spec.ts +++ b/test/e2e/flask/multichain-api/create-session.spec.ts @@ -1,9 +1,14 @@ import { strict as assert } from 'assert'; import { By } from 'selenium-webdriver'; -import { largeDelayMs, WINDOW_TITLES, withFixtures } from '../../helpers'; +import { + largeDelayMs, + WINDOW_TITLES, + withFixtures, + ACCOUNT_1, + ACCOUNT_2, +} from '../../helpers'; import { Driver } from '../../webdriver/driver'; import FixtureBuilder from '../../fixture-builder'; -import { DEFAULT_FIXTURE_ACCOUNT } from '../../constants'; import { initCreateSessionScopes, DEFAULT_MULTICHAIN_TEST_DAPP_FIXTURE_OPTIONS, @@ -15,11 +20,6 @@ import { } from './testHelpers'; describe('Multichain API', function () { - /** - * check {@link FixtureBuilder.withPreferencesControllerAdditionalAccountIdentities} for second injected account address. - */ - const SECOND_INJECTED_ACCOUNT = '0x09781764c08de8ca82e156bbf156a3ca217c7950'; - describe('Connect wallet to the multichain dapp via `externally_connectable`, call `wallet_createSession` with requested EVM scope that does NOT match one of the user’s enabled networks', function () { it("the specified EVM scopes that do not match the user's configured networks should be treated as if they were not requested", async function () { await withFixtures( @@ -37,7 +37,7 @@ describe('Multichain API', function () { driver: Driver; extensionId: string; }) => { - const scopesToIgnore = ['eip155:42161', 'eip155:10']; + const scopesToIgnore = ['eip155:1338', 'eip155:1000']; await openMultichainDappAndConnectWalletWithExternallyConnectable( driver, extensionId, @@ -144,8 +144,7 @@ describe('Multichain API', function () { }) => { const requestScopesToNetworkMap = { 'eip155:1': 'Ethereum Mainnet', - 'eip155:42161': 'Arbitrum One', - 'eip155:10': 'OP Mainnet', + 'eip155:59141': 'Linea Sepolia', }; const requestScopes = Object.keys(requestScopesToNetworkMap); @@ -219,7 +218,7 @@ describe('Multichain API', function () { await initCreateSessionScopes( driver, ['eip155:1337', 'eip155:1338'], - [DEFAULT_FIXTURE_ACCOUNT], + [ACCOUNT_1], ); await addAccountInWalletAndAuthorize(driver); @@ -241,11 +240,9 @@ describe('Multichain API', function () { assert.deepEqual( getSessionScopesResult.sessionScopes['eip155:1337'].accounts, - getExpectedSessionScope('eip155:1337', [ - DEFAULT_FIXTURE_ACCOUNT, - SECOND_INJECTED_ACCOUNT, - ]).accounts, - `Should add account ${SECOND_INJECTED_ACCOUNT} to scope`, + getExpectedSessionScope('eip155:1337', [ACCOUNT_1, ACCOUNT_2]) + .accounts, + `Should add account ${ACCOUNT_2} to scope`, ); }, ); @@ -331,10 +328,8 @@ describe('Multichain API', function () { assert.deepEqual( getSessionScopesResult.sessionScopes['eip155:1'].accounts, - getExpectedSessionScope('eip155:1', [ - DEFAULT_FIXTURE_ACCOUNT, - SECOND_INJECTED_ACCOUNT, - ]).accounts, + getExpectedSessionScope('eip155:1', [ACCOUNT_1, ACCOUNT_2]) + .accounts, 'The dapp should receive a response that includes permissions for the accounts that were selected for sharing', ); }, diff --git a/test/e2e/flask/multichain-api/invoke-method.spec.ts b/test/e2e/flask/multichain-api/invoke-method.spec.ts new file mode 100644 index 000000000000..9d8e19753221 --- /dev/null +++ b/test/e2e/flask/multichain-api/invoke-method.spec.ts @@ -0,0 +1,261 @@ +import { strict as assert } from 'assert'; +import { + ACCOUNT_1, + ACCOUNT_2, + convertETHToHexGwei, + largeDelayMs, + WINDOW_TITLES, + withFixtures, +} from '../../helpers'; +import { Driver } from '../../webdriver/driver'; +import FixtureBuilder from '../../fixture-builder'; +import { DEFAULT_GANACHE_ETH_BALANCE_DEC } from '../../constants'; +import { + initCreateSessionScopes, + DEFAULT_MULTICHAIN_TEST_DAPP_FIXTURE_OPTIONS, + openMultichainDappAndConnectWalletWithExternallyConnectable, + addAccountInWalletAndAuthorize, + escapeColon, +} from './testHelpers'; + +describe('Multichain API', function () { + const GANACHE_SCOPES = ['eip155:1337', 'eip155:1338', 'eip155:1000']; + const ACCOUNTS = [ACCOUNT_1, ACCOUNT_2]; + const DEFAULT_INITIAL_BALANCE_HEX = convertETHToHexGwei( + DEFAULT_GANACHE_ETH_BALANCE_DEC, + ); + + describe('Calling `wallet_invokeMethod` on the same dapp across three different connected chains', function () { + describe('Read operations: calling different methods on each connected scope', function () { + it('Should match selected method to the expected output', async function () { + await withFixtures( + { + title: this.test?.fullTitle(), + fixtures: new FixtureBuilder() + .withNetworkControllerTripleGanache() + .build(), + ...DEFAULT_MULTICHAIN_TEST_DAPP_FIXTURE_OPTIONS, + }, + async ({ + driver, + extensionId, + }: { + driver: Driver; + extensionId: string; + }) => { + await openMultichainDappAndConnectWalletWithExternallyConnectable( + driver, + extensionId, + ); + await initCreateSessionScopes(driver, GANACHE_SCOPES, ACCOUNTS); + await addAccountInWalletAndAuthorize(driver); + await driver.clickElement({ text: 'Connect', tag: 'button' }); + await driver.delay(largeDelayMs); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.MultichainTestDApp, + ); + + const TEST_METHODS = { + [GANACHE_SCOPES[0]]: 'eth_chainId', + [GANACHE_SCOPES[1]]: 'eth_getBalance', + [GANACHE_SCOPES[2]]: 'eth_gasPrice', + }; + const EXPECTED_RESULTS = { + [GANACHE_SCOPES[0]]: '0x539', + [GANACHE_SCOPES[1]]: DEFAULT_INITIAL_BALANCE_HEX, + [GANACHE_SCOPES[2]]: '0x77359400', + }; + + for (const scope of GANACHE_SCOPES) { + const invokeMethod = TEST_METHODS[scope]; + await driver.clickElementSafe( + `[data-testid="${scope}-${invokeMethod}-option"]`, + ); + + await driver.clickElementSafe( + `[data-testid="invoke-method-${scope}-btn"]`, + ); + + const resultElement = await driver.findElement( + `#invoke-method-${escapeColon(scope)}-${invokeMethod}-result-0`, + ); + + const result = await resultElement.getText(); + + assert.strictEqual( + result, + `"${EXPECTED_RESULTS[scope]}"`, + `${scope} method ${invokeMethod} expected "${EXPECTED_RESULTS[scope]}", got ${result} instead`, + ); + } + }, + ); + }); + }); + + describe('Write operations: calling `eth_sendTransaction` on each connected scope', function () { + const INDEX_FOR_ALTERNATE_ACCOUNT = 1; + + it('should match chosen addresses in each chain to the selected address per scope in extension window', async function () { + await withFixtures( + { + title: this.test?.fullTitle(), + fixtures: new FixtureBuilder() + .withNetworkControllerTripleGanache() + .build(), + ...DEFAULT_MULTICHAIN_TEST_DAPP_FIXTURE_OPTIONS, + }, + async ({ + driver, + extensionId, + }: { + driver: Driver; + extensionId: string; + }) => { + await openMultichainDappAndConnectWalletWithExternallyConnectable( + driver, + extensionId, + ); + await initCreateSessionScopes(driver, GANACHE_SCOPES, ACCOUNTS); + await addAccountInWalletAndAuthorize(driver); + await driver.clickElement({ text: 'Connect', tag: 'button' }); + + await driver.delay(largeDelayMs); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.MultichainTestDApp, + ); + + for (const [i, scope] of GANACHE_SCOPES.entries()) { + await driver.clickElementSafe( + `[data-testid="${scope}-eth_sendTransaction-option"]`, + ); + + i === INDEX_FOR_ALTERNATE_ACCOUNT && + (await driver.clickElementSafe( + `[data-testid="${scope}:${ACCOUNT_2}-option"]`, + )); + } + + await driver.clickElement({ + text: 'Invoke All Selected Methods', + tag: 'button', + }); + + for (const i of GANACHE_SCOPES.keys()) { + await driver.delay(largeDelayMs); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + const accountWebElement = await driver.findElement( + '[data-testid="sender-address"]', + ); + const accountText = await accountWebElement.getText(); + const expectedAccount = + i === INDEX_FOR_ALTERNATE_ACCOUNT ? 'Account 2' : 'Account 1'; + + assert.strictEqual( + accountText, + expectedAccount, + `Should have ${expectedAccount} selected, got ${accountText}`, + ); + + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + }); + } + }, + ); + }); + + it('should have less balance due to gas after transaction is sent', async function () { + await withFixtures( + { + title: this.test?.fullTitle(), + fixtures: new FixtureBuilder() + .withNetworkControllerTripleGanache() + .build(), + ...DEFAULT_MULTICHAIN_TEST_DAPP_FIXTURE_OPTIONS, + }, + async ({ + driver, + extensionId, + }: { + driver: Driver; + extensionId: string; + }) => { + await openMultichainDappAndConnectWalletWithExternallyConnectable( + driver, + extensionId, + ); + await initCreateSessionScopes(driver, GANACHE_SCOPES, ACCOUNTS); + await addAccountInWalletAndAuthorize(driver); + await driver.clickElement({ text: 'Connect', tag: 'button' }); + + await driver.delay(largeDelayMs); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.MultichainTestDApp, + ); + + for (const [i, scope] of GANACHE_SCOPES.entries()) { + await driver.clickElementSafe( + `[data-testid="${scope}-eth_sendTransaction-option"]`, + ); + + i === INDEX_FOR_ALTERNATE_ACCOUNT && + (await driver.clickElementSafe( + `[data-testid="${scope}:${ACCOUNT_2}-option"]`, + )); + } + + await driver.clickElement({ + text: 'Invoke All Selected Methods', + tag: 'button', + }); + + const totalNumberOfScopes = GANACHE_SCOPES.length; + for (let i = 0; i < totalNumberOfScopes; i++) { + await driver.delay(largeDelayMs); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + }); + } + + await driver.delay(largeDelayMs); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.MultichainTestDApp, + ); + + await driver.clickElementSafe({ + text: 'Clear Results', + tag: 'button', + }); + + for (const scope of GANACHE_SCOPES) { + await driver.clickElementSafe( + `[data-testid="${scope}-eth_getBalance-option"]`, + ); + + await driver.delay(largeDelayMs); + await driver.clickElementSafe( + `[data-testid="invoke-method-${scope}-btn"]`, + ); + + const resultWebElement = await driver.findElement( + `#invoke-method-${escapeColon(scope)}-eth_getBalance-result-0`, + ); + const currentBalance = await resultWebElement.getText(); + + assert.notStrictEqual( + currentBalance, + `"${DEFAULT_INITIAL_BALANCE_HEX}"`, + `${scope} scope balance should be different after eth_sendTransaction due to gas`, + ); + } + }, + ); + }); + }); + }); +}); diff --git a/test/e2e/flask/multichain-api/testHelpers.ts b/test/e2e/flask/multichain-api/testHelpers.ts index e89b4c3f0f7f..76c261caccc0 100644 --- a/test/e2e/flask/multichain-api/testHelpers.ts +++ b/test/e2e/flask/multichain-api/testHelpers.ts @@ -7,8 +7,8 @@ import { } from '@metamask/multichain'; import { DAPP_URL, - defaultGanacheOptions, largeDelayMs, + multipleGanacheOptions, openDapp, regularDelayMs, unlockWallet, @@ -32,17 +32,17 @@ export const DEFAULT_MULTICHAIN_TEST_DAPP_FIXTURE_OPTIONS = { ), ], ganacheOptions: { - ...defaultGanacheOptions, + ...multipleGanacheOptions, concurrent: [ { port: 8546, chainId: 1338, - ganacheOptions2: defaultGanacheOptions, + ganacheOptions2: multipleGanacheOptions, }, { port: 7777, chainId: 1000, - ganacheOptions2: defaultGanacheOptions, + ganacheOptions2: multipleGanacheOptions, }, ], }, @@ -196,3 +196,12 @@ export const updateNetworkCheckboxes = async ( } await driver.clickElement({ text: 'Update', tag: 'button' }); }; + +/** + * Sometimes we need to escape colon character when using {@link Driver.findElement}, otherwise selenium will treat this as an invalid selector. + * + * @param selector - string to manipulate. + * @returns string with escaped colon char. + */ +export const escapeColon = (selector: string): string => + selector.replace(':', '\\:'); diff --git a/yarn.lock b/yarn.lock index 3d2f0ca47aa0..3ca2cfd5701b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6555,10 +6555,10 @@ __metadata: languageName: node linkType: hard -"@metamask/test-dapp-multichain@npm:^0.5.0": - version: 0.5.0 - resolution: "@metamask/test-dapp-multichain@npm:0.5.0" - checksum: 10/8579e133d76de699bbebd5ea41ca323d87bac01f105816be28f68e7f9c3dc6ca4181abddbc06c222d8507976a48699c3c1f888c518561cf5deda414e06e25958 +"@metamask/test-dapp-multichain@npm:^0.6.0": + version: 0.6.0 + resolution: "@metamask/test-dapp-multichain@npm:0.6.0" + checksum: 10/23bb60422fa3986a648e487562697e7ca57dc97ac9ff693eeac391e673e5ebd838ad3a54160af8dbb195ab3eba497bf2a3767d76693bbbf6044ab6cdbd59b254 languageName: node linkType: hard @@ -27118,7 +27118,7 @@ __metadata: "@metamask/solana-wallet-snap": "npm:^1.0.4" "@metamask/test-bundler": "npm:^1.0.0" "@metamask/test-dapp": "npm:8.13.0" - "@metamask/test-dapp-multichain": "npm:^0.5.0" + "@metamask/test-dapp-multichain": "npm:^0.6.0" "@metamask/transaction-controller": "npm:^42.1.0" "@metamask/user-operation-controller": "npm:^21.0.0" "@metamask/utils": "npm:^10.0.1"