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

test: NOTIFY-1256 - Extending E2E tests for Account Sync #28067

Merged
merged 10 commits into from
Oct 24, 2024
107 changes: 86 additions & 21 deletions test/e2e/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ const completeImportSRPOnboardingFlowWordByWord = async (
await driver.clickElement('[data-testid="onboarding-import-wallet"]');

// metrics

await driver.clickElement('[data-testid="metametrics-no-thanks"]');

// import with recovery phrase, word by word
Expand Down Expand Up @@ -564,37 +565,85 @@ const onboardingPinExtension = async (driver) => {
await driver.clickElement('[data-testid="pin-extension-done"]');
};

const onboardingCompleteWalletCreationWithOptOut = async (driver) => {
/**
* Completes the onboarding flow with optional opt-out settings for wallet creation.
*
* This function navigates through the onboarding process, allowing for opt-out of certain features.
* It waits for the appropriate heading to appear, then proceeds to opt-out of third-party API
* integration for general and assets sections if specified in the optOutOptions.
*
* @param {WebDriver} driver - The Selenium WebDriver instance.
* @param {object} optOutOptions - Optional. An object specifying which features to opt-out of.
* @param {boolean} optOutOptions.basicFunctionality - Optional. Defaults to true. Opt-out of basic functionality.
* @param {boolean} optOutOptions.profileSync - Optional. Defaults to true. Opt-out of profile sync.
* @param {boolean} optOutOptions.assets - Optional. Defaults to true. Opt-out of assets options.
* @param {boolean} optOutOptions.isNewWallet - Optional. Defaults to true. Indicates if this is a new wallet creation.
*/
const onboardingCompleteWalletCreationWithOptOut = async (
driver,
optOutOptions = {},
) => {
const defaultOptOutOptions = {
basicFunctionality: true,
profileSync: true,
assets: true,
isNewWallet: true,
};

const optOutOptionsToUse = { ...defaultOptOutOptions, ...optOutOptions };

// wait for h2 to appear
await driver.findElement({ text: 'Congratulations!', tag: 'h2' });
await driver.findElement({
text: optOutOptionsToUse.isNewWallet
? 'Congratulations'
: 'Your wallet is ready',
tag: 'h2',
javiergarciavera marked this conversation as resolved.
Show resolved Hide resolved
});

// opt-out from third party API on general section
await driver.clickElementAndWaitToDisappear({
text: 'Manage default privacy settings',
tag: 'button',
});
await driver.clickElement({ text: 'General', tag: 'p' });
await driver.clickElement(
'[data-testid="basic-functionality-toggle"] .toggle-button',
);
await driver.clickElement('[id="basic-configuration-checkbox"]');
await driver.clickElementAndWaitToDisappear({
tag: 'button',
text: 'Turn off',
});

// opt-out from third party API on assets section
await driver.clickElement('[data-testid="category-back-button"]');
await driver.clickElement({ text: 'Assets', tag: 'p' });
await Promise.all(
(
await driver.findClickableElements(
'.toggle-button.toggle-button--on:not([data-testid="basic-functionality-toggle"] .toggle-button)',
)
).map((toggle) => toggle.click()),
);
if (optOutOptionsToUse.basicFunctionality) {
await driver.clickElement(
'[data-testid="basic-functionality-toggle"] .toggle-button',
);
await driver.clickElement('[id="basic-configuration-checkbox"]');
await driver.clickElementAndWaitToDisappear({
tag: 'button',
text: 'Turn off',
});
}

if (optOutOptionsToUse.profileSync) {
await driver.clickElement(
'[data-testid="profile-sync-toggle"] .toggle-button',
);
await driver.clickElementAndWaitToDisappear({
tag: 'button',
text: 'Turn off',
});
}

await driver.clickElement('[data-testid="category-back-button"]');

if (optOutOptionsToUse.assets) {
// opt-out from third party API on assets section
await driver.clickElement({ text: 'Assets', tag: 'p' });
await Promise.all(
(
await driver.findClickableElements(
'.toggle-button.toggle-button--on:not([data-testid="basic-functionality-toggle"] .toggle-button)',
)
).map((toggle) => toggle.click()),
);

await driver.clickElement('[data-testid="category-back-button"]');
}

// Wait until the onboarding carousel has stopped moving
// otherwise the click has no effect.
await driver.waitForElementToStopMoving(
Expand All @@ -610,15 +659,30 @@ const onboardingCompleteWalletCreationWithOptOut = async (driver) => {
await onboardingPinExtension(driver);
};

/**
* Completes the onboarding flow for creating a new wallet with opt-out options.
*
* This function guides the user through the onboarding process of creating a new wallet,
* including opting out of certain features as specified by the `optOutOptions` parameter.
*
* @param {object} driver - The Selenium driver instance.
* @param {string} password - The password to use for the new wallet.
* @param {object} optOutOptions - An object specifying the features to opt out of.
* @param {boolean} optOutOptions.isNewWallet - Indicates if this is a new wallet creation.
* @param {boolean} optOutOptions.basicFunctionality - Indicates if basic functionality should be opted out.
* @param {boolean} optOutOptions.profileSync - Indicates if profile sync should be opted out.
* @param {boolean} optOutOptions.assets - Indicates if assets should be opted out.
*/
const completeCreateNewWalletOnboardingFlowWithOptOut = async (
driver,
password,
optOutOptions,
) => {
await onboardingBeginCreateNewWallet(driver);
await onboardingChooseMetametricsOption(driver, false);
await onboardingCreatePassword(driver, password);
await onboardingRevealAndConfirmSRP(driver);
await onboardingCompleteWalletCreationWithOptOut(driver);
await onboardingCompleteWalletCreationWithOptOut(driver, optOutOptions);
};

const completeCreateNewWalletOnboardingFlow = async (driver, password) => {
Expand Down Expand Up @@ -1323,6 +1387,7 @@ module.exports = {
onboardingCreatePassword,
onboardingRevealAndConfirmSRP,
onboardingCompleteWalletCreation,
onboardingCompleteWalletCreationWithOptOut,
onboardingPinExtension,
assertInAnyOrder,
genRandInitBal,
Expand Down
55 changes: 55 additions & 0 deletions test/e2e/page-objects/pages/account-list-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@ class AccountListPage {
private readonly addAccountConfirmButton =
'[data-testid="submit-add-account-with-name"]';

private readonly importAccountConfirmButton =
'[data-testid="import-account-confirm-button"]';

private readonly addEthereumAccountButton =
'[data-testid="multichain-account-menu-popover-add-account"]';

private readonly addImportedAccountButton =
'[data-testid="multichain-account-menu-popover-add-imported-account"]';

private readonly addSnapAccountButton = {
text: 'Add account Snap',
tag: 'button',
Expand Down Expand Up @@ -54,6 +60,8 @@ class AccountListPage {
private readonly saveAccountLabelButton =
'[data-testid="save-account-label-input"]';

private readonly importAccountPrivateKeyInput = '#private-key-box';

constructor(driver: Driver) {
this.driver = driver;
}
Expand Down Expand Up @@ -86,6 +94,34 @@ class AccountListPage {
);
}

/**
* Adds a new account with default next available name.
*
*/
async addNewAccountWithDefaultName(): Promise<void> {
console.log(`Adding new account with next available name`);
await this.driver.clickElement(this.createAccountButton);
await this.driver.clickElement(this.addEthereumAccountButton);
await this.driver.clickElementAndWaitToDisappear(
this.addAccountConfirmButton,
);
}

/**
* Adds a new account with a custom label.
*
* @param privateKey - Private key of the account
*/
async addNewImportedAccount(privateKey: string): Promise<void> {
console.log(`Adding new imported account`);
await this.driver.clickElement(this.createAccountButton);
await this.driver.clickElement(this.addImportedAccountButton);
await this.driver.fill(this.importAccountPrivateKeyInput, privateKey);
await this.driver.clickElementAndWaitToDisappear(
this.importAccountConfirmButton,
);
}

/**
* Changes the label of the current account.
*
Expand Down Expand Up @@ -227,6 +263,25 @@ class AccountListPage {
console.log(`Check that hidden accounts list is displayed in account list`);
await this.driver.waitForSelector(this.hiddenAccountsList);
}

/**
* Verifies number of accounts currently showing in the accounts menu.
*
* @param expectedNumberOfAccounts - The expected number of accounts showing.
*/
async check_numberOfAvailableAccounts(
expectedNumberOfAccounts: number,
): Promise<void> {
console.log(
`Verify the number of accounts in the account menu is: ${expectedNumberOfAccounts}`,
);
await this.driver.wait(async () => {
const internalAccounts = await this.driver.findElements(
this.accountListItem,
);
return internalAccounts.length === expectedNumberOfAccounts;
}, 20000);
}
}

export default AccountListPage;
15 changes: 15 additions & 0 deletions test/e2e/tests/notifications/account-syncing/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
import { isManifestV3 } from '../../../../../shared/modules/mv3.utils';
import {
completeSRPRevealQuiz,
openSRPRevealQuiz,
tapAndHoldToRevealSRP,
} from '../../../helpers';
import { Driver } from '../../../webdriver/driver';

export const IS_ACCOUNT_SYNCING_ENABLED = isManifestV3;

export const getSRP = async (driver: Driver, password: string) => {
await openSRPRevealQuiz(driver);
await completeSRPRevealQuiz(driver);
await driver.fill('[data-testid="input-password"]', password);
await driver.press('[data-testid="input-password"]', driver.Key.ENTER);
await tapAndHoldToRevealSRP(driver);
return (await driver.findElement('[data-testid="srp_text"]')).getText();
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,22 @@ import {
import FixtureBuilder from '../../../fixture-builder';
import { mockNotificationServices } from '../mocks';
import {
NOTIFICATIONS_TEAM_IMPORTED_PRIVATE_KEY,
NOTIFICATIONS_TEAM_PASSWORD,
NOTIFICATIONS_TEAM_SEED_PHRASE,
} from '../constants';
import { UserStorageMockttpController } from '../../../helpers/user-storage/userStorageMockttpController';
import HeaderNavbar from '../../../page-objects/pages/header-navbar';
import AccountListPage from '../../../page-objects/pages/account-list-page';
import { accountsSyncMockResponse } from './mockData';
import { IS_ACCOUNT_SYNCING_ENABLED } from './helpers';

describe('Account syncing @no-mmi', function () {
describe('Account syncing - Import With Private Key @no-mmi', function () {
if (!IS_ACCOUNT_SYNCING_ENABLED) {
return;
}
describe('from inside MetaMask', function () {
it('syncs newly added accounts', async function () {
it('does not sync accounts imported with private keys', async function () {
const userStorageMockttpController = new UserStorageMockttpController();

await withFixtures(
Expand All @@ -46,25 +49,24 @@ describe('Account syncing @no-mmi', function () {
NOTIFICATIONS_TEAM_PASSWORD,
);

await driver.clickElement('[data-testid="account-menu-icon"]');
const header = new HeaderNavbar(driver);
await header.check_pageIsLoaded();
await header.openAccountMenu();

await driver.wait(async () => {
const internalAccounts = await driver.findElements(
'.multichain-account-list-item',
);
return internalAccounts.length === accountsSyncMockResponse.length;
}, 20000);

await driver.clickElement(
'[data-testid="multichain-account-menu-popover-action-button"]',
const accountListPage = new AccountListPage(driver);
await accountListPage.check_pageIsLoaded();
await accountListPage.check_numberOfAvailableAccounts(
accountsSyncMockResponse.length,
);
await driver.clickElement(
'[data-testid="multichain-account-menu-popover-add-account"]',
await accountListPage.check_accountDisplayedInAccountList(
'My First Synced Account',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd love to ditch these magic strings and use something like

import { SDK } from '@metamask/profile-sync-controller';

const decryptedAccountNames = await Promise.all(
      accountsSyncMockResponse.map(async (response) => {
        const decryptedAccountName = await SDK.Encryption.decryptString(
          response.Data,
          NOTIFICATIONS_TEAM_STORAGE_KEY,
        );
        return JSON.parse(decryptedAccountName).n;
      }),
    );
    
    ...
    
    for (const accountName of decryptedAccountNames) {
      await accountListPage.check_accountDisplayedInAccountList(
        accountName,
      );
    }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would apply to all other occurences of magic strings in this PR. Wdyt?

I just did this for mobile and it works great. Something to be wary of, we'll be adding some decryption workload to our CI by doing that. I'm just not sure how much. Let's discuss!

);
await driver.fill('#account-name', 'My third account');

await driver.clickElementAndWaitToDisappear(
'[data-testid="submit-add-account-with-name"]',
await accountListPage.check_accountDisplayedInAccountList(
'My Second Synced Account',
);
await accountListPage.openAccountOptionsMenu();
await accountListPage.addNewImportedAccount(
NOTIFICATIONS_TEAM_IMPORTED_PRIVATE_KEY,
);
},
);
Expand All @@ -90,29 +92,19 @@ describe('Account syncing @no-mmi', function () {
NOTIFICATIONS_TEAM_PASSWORD,
);

await driver.clickElement('[data-testid="account-menu-icon"]');
const header = new HeaderNavbar(driver);
await header.check_pageIsLoaded();
await header.openAccountMenu();

await driver.wait(async () => {
const internalAccounts = await driver.findElements(
'.multichain-account-list-item',
);
return (
internalAccounts.length ===
userStorageMockttpController.paths.get('accounts')?.response
.length
);
}, 20000);

await driver.wait(async () => {
const internalAccounts = await driver.findElements(
'.multichain-account-list-item .multichain-account-list-item__account-name',
);
const lastAccountName = await internalAccounts[
internalAccounts.length - 1
].getText();

return lastAccountName === 'My third account';
}, 20000);
const accountListPage = new AccountListPage(driver);
await accountListPage.check_pageIsLoaded();
await accountListPage.check_numberOfAvailableAccounts(2);
await accountListPage.check_accountDisplayedInAccountList(
'My First Synced Account',
);
await accountListPage.check_accountDisplayedInAccountList(
'My Second Synced Account',
);
},
);
});
Expand Down
Loading