diff --git a/package.json b/package.json index c5e65cc87d39..88ccbc75f172 100644 --- a/package.json +++ b/package.json @@ -480,7 +480,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..0e0728cfb5eb 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( @@ -219,7 +219,7 @@ describe('Multichain API', function () { await initCreateSessionScopes( driver, ['eip155:1337', 'eip155:1338'], - [DEFAULT_FIXTURE_ACCOUNT], + [ACCOUNT_1], ); await addAccountInWalletAndAuthorize(driver); @@ -241,11 +241,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 +329,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..e8fa15cc462e --- /dev/null +++ b/test/e2e/flask/multichain-api/invoke-method.spec.ts @@ -0,0 +1,275 @@ +import { strict as assert } from 'assert'; +import { + ACCOUNT_1, + ACCOUNT_2, + largeDelayMs, + WINDOW_TITLES, + withFixtures, +} from '../../helpers'; +import { Driver } from '../../webdriver/driver'; +import FixtureBuilder from '../../fixture-builder'; +import { + initCreateSessionScopes, + DEFAULT_MULTICHAIN_TEST_DAPP_FIXTURE_OPTIONS, + openMultichainDappAndConnectWalletWithExternallyConnectable, + addAccountInWalletAndAuthorize, +} 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 = '0x15af1d78b58c40000'; + + 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', + }; + + const scopeCards = await driver.findElements('.scope-card'); + + for (const i of scopeCards.keys()) { + const scope = GANACHE_SCOPES[i]; + const invokeMethod = TEST_METHODS[GANACHE_SCOPES[i]]; + await driver.clickElementSafe( + `[data-testid="${scope}-${invokeMethod}-option"]`, + ); + + await driver.clickElementSafe( + `[data-testid="invoke-method-${scope}-btn"]`, + ); + + /** + * We need to escape colon character on the scope, otherwise selenium will treat this as an invalid selector + */ + const resultElement = await driver.findElement( + `#invoke-method-${scope.replace( + ':', + '\\:', + )}-${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, + ); + + const scopeCards = await driver.findElements('.scope-card'); + for (const i of scopeCards.keys()) { + const scope = GANACHE_SCOPES[i]; + 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', + }); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + 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, + ); + + const scopeCards = await driver.findElements('.scope-card'); + + for (const i of scopeCards.keys()) { + const scope = GANACHE_SCOPES[i]; + 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', + }); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + for (const _ of GANACHE_SCOPES) { + 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 i of scopeCards.keys()) { + const scope = GANACHE_SCOPES[i]; + 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-${scope.replace( + ':', + '\\:', + )}-eth_getBalance-result-0`, + ); + const currentBalance = await resultWebElement.getText(); + + assert.notStrictEqual( + currentBalance, + `"${DEFAULT_INITIAL_BALANCE_HEX}"`, // default initial hex balance + `${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..56b4cdaee973 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, }, ], }, diff --git a/yarn.lock b/yarn.lock index 7c6da79c04d4..3775ff126d34 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6535,10 +6535,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 @@ -26943,7 +26943,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"