Skip to content

Commit

Permalink
test: [POM] Migrate onboarding metrics e2e tests to TS and Page Objec…
Browse files Browse the repository at this point in the history
…t Model to reduce flakiness (#28424)

## **Description**

- Migrate onboarding metrics e2e tests to TS and Page Object Model
- Use onboarding functions designed with Page Object Model, to reduce
flakiness.

[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/27155?quickstart=1)

## **Related issues**

Fixes:  #28425

## **Manual testing steps**
Check code readability, make sure tests pass.

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

<!-- [screenshots/recordings] -->

### **After**

<!-- [screenshots/recordings] -->

## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [x] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [x] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
  • Loading branch information
chloeYue authored Nov 19, 2024
1 parent 3c51786 commit 91acd9c
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 246 deletions.
107 changes: 0 additions & 107 deletions test/e2e/helpers.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const { strict: assert } = require('assert');
const path = require('path');
const { promises: fs, writeFileSync, readFileSync } = require('fs');
const BigNumber = require('bignumber.js');
Expand Down Expand Up @@ -386,106 +385,6 @@ const getWindowHandles = async (driver, handlesCount) => {
return { extension, dapp, popup };
};

/**
* @deprecated Please use page object functions in `onboarding.flow.ts` and in `pages/onboarding/*`.
* Begin the create new wallet flow on onboarding screen.
* @param {WebDriver} driver
*/
const onboardingBeginCreateNewWallet = async (driver) => {
// agree to terms of use
await driver.clickElement('[data-testid="onboarding-terms-checkbox"]');

// welcome
await driver.clickElement('[data-testid="onboarding-create-wallet"]');
};

/**
* @deprecated Please use page object functions in `onboarding.flow.ts` and in `pages/onboarding/*`.
* Choose either "I Agree" or "No Thanks" on the MetaMetrics onboarding screen
* @param {WebDriver} driver
* @param {boolean} option - true to opt into metrics, default is false
*/
const onboardingChooseMetametricsOption = async (driver, option = false) => {
const optionIdentifier = option ? 'i-agree' : 'no-thanks';
// metrics
await driver.clickElement(`[data-testid="metametrics-${optionIdentifier}"]`);
};

/**
* @deprecated Please use page object functions in `onboarding.flow.ts` and in `pages/onboarding/*`.
* Set a password for MetaMask during onboarding
* @param {WebDriver} driver
* @param {string} password - Password to set
*/
const onboardingCreatePassword = async (driver, password) => {
// create password
await driver.fill('[data-testid="create-password-new"]', password);
await driver.fill('[data-testid="create-password-confirm"]', password);
await driver.clickElement('[data-testid="create-password-terms"]');
await driver.clickElement('[data-testid="create-password-wallet"]');
};

/**
* @deprecated Please use page object functions in `onboarding.flow.ts` and in `pages/onboarding/*`.
* Choose to secure wallet, and then get recovery phrase and confirm the SRP
* during onboarding flow.
* @param {WebDriver} driver
*/
const onboardingRevealAndConfirmSRP = async (driver) => {
// secure my wallet
await driver.clickElement('[data-testid="secure-wallet-recommended"]');

// reveal SRP
await driver.clickElement('[data-testid="recovery-phrase-reveal"]');

const revealedSeedPhrase = await driver.findElement(
'[data-testid="recovery-phrase-chips"]',
);

const recoveryPhrase = await revealedSeedPhrase.getText();

await driver.clickElement('[data-testid="recovery-phrase-next"]');

// confirm SRP
const words = recoveryPhrase.split(/\s*(?:[0-9)]+|\n|\.|^$|$)\s*/u);
const finalWords = words.filter((str) => str !== '');
assert.equal(finalWords.length, 12);

await driver.fill('[data-testid="recovery-phrase-input-2"]', finalWords[2]);
await driver.fill('[data-testid="recovery-phrase-input-3"]', finalWords[3]);
await driver.fill('[data-testid="recovery-phrase-input-7"]', finalWords[7]);

await driver.clickElement('[data-testid="confirm-recovery-phrase"]');

await driver.clickElementAndWaitToDisappear({
tag: 'button',
text: 'Confirm',
});
};

/**
* @deprecated Please use page object functions in `onboarding.flow.ts` and in `pages/onboarding/*`.
* Complete the onboarding flow by confirming completion. Final step before the
* reminder to pin the extension.
* @param {WebDriver} driver
*/
const onboardingCompleteWalletCreation = async (driver) => {
// complete
await driver.findElement({ text: 'Congratulations', tag: 'h2' });
await driver.clickElement('[data-testid="onboarding-complete-done"]');
};

/**
* @deprecated Please use page object functions in `onboarding.flow.ts` and in `pages/onboarding/*`.
* Move through the steps of pinning extension after successful onboarding
* @param {WebDriver} driver
*/
const onboardingPinExtension = async (driver) => {
// pin extension
await driver.clickElement('[data-testid="pin-extension-next"]');
await driver.clickElement('[data-testid="pin-extension-done"]');
};

const openSRPRevealQuiz = async (driver) => {
// navigate settings to reveal SRP
await driver.clickElement('[data-testid="account-options-menu-button"]');
Expand Down Expand Up @@ -1099,12 +998,6 @@ module.exports = {
validateContractDetails,
switchToNotificationWindow,
getEventPayloads,
onboardingBeginCreateNewWallet,
onboardingChooseMetametricsOption,
onboardingCreatePassword,
onboardingRevealAndConfirmSRP,
onboardingCompleteWalletCreation,
onboardingPinExtension,
assertInAnyOrder,
genRandInitBal,
openActionMenuAndStartSendFlow,
Expand Down
42 changes: 32 additions & 10 deletions test/e2e/page-objects/flows/onboarding.flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,19 @@ import { E2E_SRP } from '../../default-fixture';
*
* @param options - The options object.
* @param options.driver - The WebDriver instance.
* @param options.password - The password to create. Defaults to WALLET_PASSWORD.
* @param options.needNavigateToNewPage - Whether to navigate to a new page to start the onboarding flow. Defaults to true.
* @param [options.password] - The password to create. Defaults to WALLET_PASSWORD.
* @param [options.participateInMetaMetrics] - Whether to participate in MetaMetrics. Defaults to false.
* @param [options.needNavigateToNewPage] - Indicates whether to navigate to a new page before starting the onboarding flow. Defaults to true.
*/
export const createNewWalletOnboardingFlow = async ({
driver,
password = WALLET_PASSWORD,
participateInMetaMetrics = false,
needNavigateToNewPage = true,
}: {
driver: Driver;
password?: string;
participateInMetaMetrics?: boolean;
needNavigateToNewPage?: boolean;
}): Promise<void> => {
console.log('Starting the creation of a new wallet onboarding flow');
Expand All @@ -37,7 +40,11 @@ export const createNewWalletOnboardingFlow = async ({

const onboardingMetricsPage = new OnboardingMetricsPage(driver);
await onboardingMetricsPage.check_pageIsLoaded();
await onboardingMetricsPage.clickNoThanksButton();
if (participateInMetaMetrics) {
await onboardingMetricsPage.clickIAgreeButton();
} else {
await onboardingMetricsPage.clickNoThanksButton();
}

const onboardingPasswordPage = new OnboardingPasswordPage(driver);
await onboardingPasswordPage.check_pageIsLoaded();
Expand Down Expand Up @@ -97,15 +104,30 @@ export const importSRPOnboardingFlow = async ({
/**
* Complete create new wallet onboarding flow
*
* @param driver - The WebDriver instance.
* @param password - The password to use. Defaults to WALLET_PASSWORD.
* @param options - The options object.
* @param options.driver - The WebDriver instance.
* @param [options.password] - The password to use. Defaults to WALLET_PASSWORD.
* @param [options.participateInMetaMetrics] - Whether to participate in MetaMetrics. Defaults to false.
* @param [options.needNavigateToNewPage] - Indicates whether to navigate to a new page before starting the onboarding flow. Defaults to true.
*/
export const completeCreateNewWalletOnboardingFlow = async (
driver: Driver,
password: string = WALLET_PASSWORD,
) => {
export const completeCreateNewWalletOnboardingFlow = async ({
driver,
password = WALLET_PASSWORD,
participateInMetaMetrics = false,
needNavigateToNewPage = true,
}: {
driver: Driver;
password?: string;
participateInMetaMetrics?: boolean;
needNavigateToNewPage?: boolean;
}): Promise<void> => {
console.log('start to complete create new wallet onboarding flow ');
await createNewWalletOnboardingFlow({ driver, password });
await createNewWalletOnboardingFlow({
driver,
password,
participateInMetaMetrics,
needNavigateToNewPage,
});
const onboardingCompletePage = new OnboardingCompletePage(driver);
await onboardingCompletePage.check_pageIsLoaded();
await onboardingCompletePage.check_congratulationsMessageIsDisplayed();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Driver } from '../../../webdriver/driver';
class OnboardingMetricsPage {
private driver: Driver;

private readonly iAgreeButton = '[data-testid="metametrics-i-agree"]';

private readonly metametricsMessage = {
text: 'Help us improve MetaMask',
tag: 'h2',
Expand Down Expand Up @@ -33,6 +35,10 @@ class OnboardingMetricsPage {
async clickNoThanksButton(): Promise<void> {
await this.driver.clickElementAndWaitToDisappear(this.noThanksButton);
}

async clickIAgreeButton(): Promise<void> {
await this.driver.clickElementAndWaitToDisappear(this.iAgreeButton);
}
}

export default OnboardingMetricsPage;
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
const { strict: assert } = require('assert');
const {
defaultGanacheOptions,
withFixtures,
onboardingBeginCreateNewWallet,
onboardingChooseMetametricsOption,
getEventPayloads,
tinyDelayMs,
} = require('../../helpers');
const FixtureBuilder = require('../../fixture-builder');
import { strict as assert } from 'assert';
import { Mockttp } from 'mockttp';
import { getEventPayloads, withFixtures } from '../../helpers';
import FixtureBuilder from '../../fixture-builder';
import OnboardingMetricsPage from '../../page-objects/pages/onboarding/onboarding-metrics-page';
import StartOnboardingPage from '../../page-objects/pages/onboarding/start-onboarding-page';

/**
* mocks the segment api multiple times for specific payloads that we expect to
* see when these tests are run. In this case we are looking for
* Mocks the segment API multiple times for specific payloads that we expect to
* see when these tests are run. In this case, we are looking for
* 'App Installed'. Do not use the constants from the metrics constants files,
* because if these change we want a strong indicator to our data team that the
* shape of data will change.
*
* @param {import('mockttp').Mockttp} mockServer
* @returns {Promise<import('mockttp/dist/pluggable-admin').MockttpClientResponse>[]}
* @param mockServer - The mock server instance.
* @returns
*/
async function mockSegment(mockServer) {
async function mockSegment(mockServer: Mockttp) {
return [
await mockServer
.forPost('https://api.segment.io/v1/batch')
Expand All @@ -44,15 +40,19 @@ describe('App Installed Events @no-mmi', function () {
participateInMetaMetrics: true,
})
.build(),
ganacheOptions: defaultGanacheOptions,
title: this.test.fullTitle(),
title: this.test?.fullTitle(),
testSpecificMock: mockSegment,
},
async ({ driver, mockedEndpoint: mockedEndpoints }) => {
await driver.navigate();
await driver.delay(tinyDelayMs);
await onboardingBeginCreateNewWallet(driver);
await onboardingChooseMetametricsOption(driver, true);
const startOnboardingPage = new StartOnboardingPage(driver);
await startOnboardingPage.check_pageIsLoaded();
await startOnboardingPage.checkTermsCheckbox();
await startOnboardingPage.clickCreateWalletButton();

const onboardingMetricsPage = new OnboardingMetricsPage(driver);
await onboardingMetricsPage.check_pageIsLoaded();
await onboardingMetricsPage.clickIAgreeButton();

const events = await getEventPayloads(driver, mockedEndpoints);
assert.equal(events.length, 1);
Expand All @@ -74,14 +74,19 @@ describe('App Installed Events @no-mmi', function () {
metaMetricsId: 'fake-metrics-id',
})
.build(),
defaultGanacheOptions,
title: this.test.fullTitle(),
title: this.test?.fullTitle(),
testSpecificMock: mockSegment,
},
async ({ driver, mockedEndpoint: mockedEndpoints }) => {
await driver.navigate();
await onboardingBeginCreateNewWallet(driver);
await onboardingChooseMetametricsOption(driver, false);
const startOnboardingPage = new StartOnboardingPage(driver);
await startOnboardingPage.check_pageIsLoaded();
await startOnboardingPage.checkTermsCheckbox();
await startOnboardingPage.clickCreateWalletButton();

const onboardingMetricsPage = new OnboardingMetricsPage(driver);
await onboardingMetricsPage.check_pageIsLoaded();
await onboardingMetricsPage.clickNoThanksButton();

const mockedRequests = await getEventPayloads(
driver,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,20 @@
const { strict: assert } = require('assert');
const {
defaultGanacheOptions,
withFixtures,
WALLET_PASSWORD,
onboardingBeginCreateNewWallet,
onboardingChooseMetametricsOption,
onboardingCreatePassword,
onboardingRevealAndConfirmSRP,
onboardingCompleteWalletCreation,
onboardingPinExtension,
getEventPayloads,
} = require('../../helpers');
const FixtureBuilder = require('../../fixture-builder');
import { strict as assert } from 'assert';
import { Mockttp } from 'mockttp';
import { getEventPayloads, withFixtures } from '../../helpers';
import FixtureBuilder from '../../fixture-builder';
import { completeCreateNewWalletOnboardingFlow } from '../../page-objects/flows/onboarding.flow';

/**
* mocks the segment api multiple times for specific payloads that we expect to
* see when these tests are run. In this case we are looking for
* Mocks the segment API multiple times for specific payloads that we expect to
* see when these tests are run. In this case, we are looking for
* 'Permissions Requested' and 'Permissions Received'. Do not use the constants
* from the metrics constants files, because if these change we want a strong
* indicator to our data team that the shape of data will change.
*
* @param {import('mockttp').Mockttp} mockServer
* @returns {Promise<import('mockttp/dist/pluggable-admin').MockttpClientResponse>[]}
* @param mockServer - The mock server instance.
* @returns
*/
async function mockSegment(mockServer) {
async function mockSegment(mockServer: Mockttp) {
return [
await mockServer
.forPost('https://api.segment.io/v1/batch')
Expand Down Expand Up @@ -72,20 +63,14 @@ describe('Nft detection event @no-mmi', function () {
useNftDetection: true,
})
.build(),
defaultGanacheOptions,
title: this.test.fullTitle(),
title: this.test?.fullTitle(),
testSpecificMock: mockSegment,
},
async ({ driver, mockedEndpoint: mockedEndpoints }) => {
await driver.navigate();

await onboardingBeginCreateNewWallet(driver);
await onboardingChooseMetametricsOption(driver, true);
await onboardingCreatePassword(driver, WALLET_PASSWORD);
await onboardingRevealAndConfirmSRP(driver);
await onboardingCompleteWalletCreation(driver);
await onboardingPinExtension(driver);

await completeCreateNewWalletOnboardingFlow({
driver,
participateInMetaMetrics: true,
});
const events = await getEventPayloads(driver, mockedEndpoints);
assert.equal(events.length, 3);
assert.deepStrictEqual(events[0].properties, {
Expand Down
Loading

0 comments on commit 91acd9c

Please sign in to comment.