Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multichain API e2e test: calling wallet_invokeMethod on the same dapp across three different connected chains#3836 #29522

Open
wants to merge 9 commits into
base: jl/caip-multichain-migrate-core
Choose a base branch
from
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
35 changes: 15 additions & 20 deletions test/e2e/flask/multichain-api/create-session.spec.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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';

ffmcgee725 marked this conversation as resolved.
Show resolved Hide resolved
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(
Expand All @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -219,7 +218,7 @@ describe('Multichain API', function () {
await initCreateSessionScopes(
driver,
['eip155:1337', 'eip155:1338'],
[DEFAULT_FIXTURE_ACCOUNT],
[ACCOUNT_1],
);

await addAccountInWalletAndAuthorize(driver);
Expand All @@ -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`,
);
},
);
Expand Down Expand Up @@ -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',
);
},
Expand Down
261 changes: 261 additions & 0 deletions test/e2e/flask/multichain-api/invoke-method.spec.ts
Original file line number Diff line number Diff line change
@@ -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 () {
adonesky1 marked this conversation as resolved.
Show resolved Hide resolved
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;
adonesky1 marked this conversation as resolved.
Show resolved Hide resolved

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()) {
ffmcgee725 marked this conversation as resolved.
Show resolved Hide resolved
await driver.delay(largeDelayMs);
ffmcgee725 marked this conversation as resolved.
Show resolved Hide resolved
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 &&
ffmcgee725 marked this conversation as resolved.
Show resolved Hide resolved
(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);
ffmcgee725 marked this conversation as resolved.
Show resolved Hide resolved
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);
ffmcgee725 marked this conversation as resolved.
Show resolved Hide resolved
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`,
);
}
ffmcgee725 marked this conversation as resolved.
Show resolved Hide resolved
},
);
});
});
});
});
17 changes: 13 additions & 4 deletions test/e2e/flask/multichain-api/testHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
} from '@metamask/multichain';
import {
DAPP_URL,
defaultGanacheOptions,
largeDelayMs,
multipleGanacheOptions,
openDapp,
regularDelayMs,
unlockWallet,
Expand All @@ -32,17 +32,17 @@
),
],
ganacheOptions: {
...defaultGanacheOptions,
...multipleGanacheOptions,
concurrent: [
{
port: 8546,
chainId: 1338,
ganacheOptions2: defaultGanacheOptions,
ganacheOptions2: multipleGanacheOptions,
},
{
port: 7777,
chainId: 1000,
ganacheOptions2: defaultGanacheOptions,
ganacheOptions2: multipleGanacheOptions,
},
],
},
Expand Down Expand Up @@ -83,7 +83,7 @@

// @ts-expect-error Driver.findNestedElement injects `fill` method onto returned element, but typescript compiler will not let us access this method without a complaint, so we override it.
scopeInput.fill(scope);
await driver.clickElement(`#add-custom-scope-button-${i}`);

Check warning on line 86 in test/e2e/flask/multichain-api/testHelpers.ts

View workflow job for this annotation

GitHub Actions / Test lint / Test lint

'#add' Hex color values are not allowed. Consider using design tokens instead. For support reach out to the design system team #metamask-design-system on Slack
}

for (const [i, account] of accounts.entries()) {
Expand All @@ -93,7 +93,7 @@

// @ts-expect-error Driver.findNestedElement injects `fill` method onto returned element, but typescript compiler will not let us access this method without a complaint, so we override it.
accountInput.fill(account);
await driver.clickElement(`#add-custom-address-button-${i}`);

Check warning on line 96 in test/e2e/flask/multichain-api/testHelpers.ts

View workflow job for this annotation

GitHub Actions / Test lint / Test lint

'#add' Hex color values are not allowed. Consider using design tokens instead. For support reach out to the design system team #metamask-design-system on Slack
}

await driver.clickElement({ text: 'wallet_createSession', tag: 'span' });
Expand Down Expand Up @@ -196,3 +196,12 @@
}
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(':', '\\:');
Loading
Loading