Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
feat!: update recommended metamask version (#151)
Browse files Browse the repository at this point in the history
* feat!: update recommended metamask version

* implement screenshot helper

* implement hide portfolio popup action

* implement close what's new modal action

* remove obsolete test

* fix dappeteer methods broken on update

* chore: fix lint

* update metamask version to `10.20.0`

* fix addToken.ts

* address issues

* fix lint

* fix tests

* fix #4

* fix non visible buttons

Co-authored-by: Bernard Stojanovi膰 <bero4net@gmail.com>
  • Loading branch information
mpetrunic and BeroBurny committed Dec 15, 2022
1 parent bfce149 commit ccb3215
Show file tree
Hide file tree
Showing 13 changed files with 104 additions and 69 deletions.
12 changes: 9 additions & 3 deletions src/helpers/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@ export const clickOnSettingsSwitch = async (page: Page, text: string): Promise<v
};

export const openNetworkDropdown = async (page: Page): Promise<void> => {
const networkSwitcher = await page.waitForSelector('.network-display');
await networkSwitcher.click();
await page.waitForSelector('li.dropdown-menu-item');
const networkSwitcher = await page.waitForSelector('.network-display', { visible: true });
try {
await networkSwitcher.click();
await page.waitForSelector('.network-dropdown-list', { visible: true, timeout: 1000 });
} catch (e) {
//retry on fail
await networkSwitcher.click();
await page.waitForSelector('.network-dropdown-list', { visible: true, timeout: 1000 });
}
};

export const openProfileDropdown = async (page: Page): Promise<void> => {
Expand Down
7 changes: 4 additions & 3 deletions src/helpers/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ElementHandle, Page } from 'puppeteer';

// TODO: change text() with '.';
export const getElementByContent = (page: Page, text: string, type = '*'): Promise<ElementHandle | null> =>
page.waitForXPath(`//${type}[contains(text(), '${text}')]`, { timeout: 20000 });
page.waitForXPath(`//${type}[contains(text(), '${text}')]`, { timeout: 20000, visible: true });

export const getInputByLabel = (
page: Page,
Expand All @@ -23,7 +23,7 @@ export const getInputByLabel = (
]
: []),
].join('|'),
{ timeout },
{ timeout, visible: true },
);

export const getSettingsSwitch = (page: Page, text: string): Promise<ElementHandle | null> =>
Expand All @@ -32,6 +32,7 @@ export const getSettingsSwitch = (page: Page, text: string): Promise<ElementHand
`//span[contains(.,'${text}')]/parent::div/following-sibling::div/div/div/div`,
`//span[contains(.,'${text}')]/parent::div/following-sibling::div/div/label/div`,
].join('|'),
{ visible: true },
);

export const getErrorMessage = async (page: Page): Promise<string | false> => {
Expand All @@ -47,4 +48,4 @@ export const getErrorMessage = async (page: Page): Promise<string | false> => {
};

export const getAccountMenuButton = (page: Page): Promise<ElementHandle | null> =>
page.waitForXPath(`//button[contains(@title,'Account Options')]`);
page.waitForXPath(`//button[contains(@title,'Account options')]`);
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ export * from './setup';
export { DappateerJestConfig } from './jest/global';

// default constants
export const RECOMMENDED_METAMASK_VERSION = 'v10.15.0';
export const RECOMMENDED_METAMASK_VERSION = 'v10.20.0';
9 changes: 6 additions & 3 deletions src/metamask/addNetwork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,18 @@ export const addNetwork = (page: Page, version?: string) => async ({
}: AddNetwork): Promise<void> => {
await page.bringToFront();
await openNetworkDropdown(page);
await clickOnButton(page, 'Add Network');
await clickOnButton(page, 'Add network');

const responsePromise = page.waitForResponse(
(response) => new URL(response.url()).pathname === new URL(rpc).pathname,
);

await typeOnInputField(page, 'Network Name', networkName);
await page.waitForTimeout(500);

await typeOnInputField(page, 'Network name', networkName);
await typeOnInputField(page, 'New RPC URL', rpc);
await typeOnInputField(page, 'Chain ID', String(chainId));
await typeOnInputField(page, 'Currency Symbol', symbol);
await typeOnInputField(page, 'Currency symbol', symbol);

await responsePromise;
await page.waitForTimeout(500);
Expand All @@ -32,4 +34,5 @@ export const addNetwork = (page: Page, version?: string) => async ({
await clickOnButton(page, 'Save');

await page.waitForXPath(`//*[text() = '${networkName}']`);
await clickOnButton(page, 'Got it');
};
28 changes: 11 additions & 17 deletions src/metamask/addToken.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Page } from 'puppeteer';

import { clickOnButton, clickOnElement, getInputByLabel, typeOnInputField } from '../helpers';
import { clickOnButton, clickOnElement, clickOnLogo, getElementByContent, typeOnInputField } from '../helpers';
import { AddToken } from '../index';

// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand All @@ -12,23 +12,17 @@ export const addToken = (page: Page, version?: string) => async ({
await page.bringToFront();
await clickOnButton(page, 'Assets');
await page.waitForSelector('.asset-list-item__token-button');
await clickOnElement(page, 'Import tokens');
await typeOnInputField(page, 'Token Contract Address', tokenAddress);
await clickOnElement(page, 'import tokens');
await clickOnButton(page, 'Custom token');

// wait to metamask pull token data
// TODO: handle case when contract is not containing symbol
const symbolInput = await getInputByLabel(page, 'Token Symbol');
await page.waitForFunction((node) => !!node.value, {}, symbolInput);
await typeOnInputField(page, 'Token decimal', String(decimals), true);
await typeOnInputField(page, 'Token contract address', tokenAddress);
await page.waitForTimeout(333);
await typeOnInputField(page, 'Token symbol', symbol, true);

if (symbol) {
await clickOnElement(page, 'Edit');
await typeOnInputField(page, 'Token Symbol', symbol, true);
}
await clickOnButton(page, 'Add custom token');
await clickOnButton(page, 'Import tokens');

const decimalsInput = await getInputByLabel(page, 'Token Decimal');
const isDisabled = await page.evaluate((node) => node.disabled, decimalsInput);
if (!isDisabled) await decimalsInput.type(String(decimals));

await clickOnButton(page, 'Add Custom Token');
await clickOnButton(page, 'Import Tokens');
await getElementByContent(page, symbol);
await clickOnLogo(page);
};
2 changes: 1 addition & 1 deletion src/metamask/importPk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const importPk = (page: Page, version?: string) => async (privateKey: str
await page.bringToFront();
await openProfileDropdown(page);

await clickOnElement(page, 'Import Account');
await clickOnElement(page, 'Import account');
await typeOnInputField(page, 'your private key', privateKey);
await clickOnButton(page, 'Import');

Expand Down
7 changes: 3 additions & 4 deletions src/metamask/switchNetwork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const switchNetwork = (page: Page, version?: string) => async (network =
await openNetworkDropdown(page);

const networkIndex = await page.evaluate((network) => {
const elements = document.querySelectorAll('li.dropdown-menu-item');
const elements = document.querySelectorAll('.network-name-item');
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
if ((element as HTMLLIElement).innerText.toLowerCase().includes(network.toLowerCase())) {
Expand All @@ -20,12 +20,11 @@ export const switchNetwork = (page: Page, version?: string) => async (network =
}, network);

const networkFullName = await page.evaluate((index) => {
const elements = document.querySelectorAll(`li.dropdown-menu-item > span`);
const elements = document.querySelectorAll(`.network-name-item`);
return (elements[index] as HTMLLIElement).innerText;
}, networkIndex);

const networkButton = (await page.$$('li.dropdown-menu-item'))[networkIndex];
const networkButton = (await page.$$('.network-name-item'))[networkIndex];
await networkButton.click();
await page.reload();
await page.waitForXPath(`//*[text() = '${networkFullName}']`);
};
23 changes: 20 additions & 3 deletions src/setup/setupActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ export async function showTestNets(metaMaskPage: Page): Promise<void> {
}

export async function confirmWelcomeScreen(metaMaskPage: Page): Promise<void> {
await clickOnButton(metaMaskPage, 'Get Started');
await clickOnButton(metaMaskPage, 'Get started');
}

export async function declineAnalytics(metaMaskPage: Page): Promise<void> {
await clickOnButton(metaMaskPage, 'No thanks');
}

export async function importAccount(
Expand All @@ -30,7 +34,6 @@ export async function importAccount(
}: MetaMaskOptions,
): Promise<void> {
await clickOnButton(metaMaskPage, 'Import wallet');
await clickOnButton(metaMaskPage, 'I Agree');

for (const [index, seedPart] of seed.split(' ').entries())
await typeOnInputField(metaMaskPage, `${index + 1}.`, seedPart);
Expand All @@ -43,7 +46,7 @@ export async function importAccount(
await acceptTerms.click();

await clickOnButton(metaMaskPage, 'Import');
await clickOnButton(metaMaskPage, 'All Done');
await clickOnButton(metaMaskPage, 'All done');
}

export const closePopup = async (page: Page): Promise<void> => {
Expand All @@ -52,3 +55,17 @@ export const closePopup = async (page: Page): Promise<void> => {
await new Promise((resolve) => setTimeout(resolve, 1000));
await page.$eval('.popover-header__button', (node: HTMLElement) => node.click());
};

export const closePortfolioTooltip = async (page: Page): Promise<void> => {
const closeButton = await page.waitForSelector(`div.home__subheader-link--tooltip-content-header > button`, {
timeout: 20000,
});
await closeButton.click();
await page.waitForTimeout(333);
};

export const closeWhatsNewModal = async (page: Page): Promise<void> => {
await page.reload();
await clickOnLogo(page);
await page.waitForTimeout(333);
};
29 changes: 25 additions & 4 deletions src/setup/setupMetaMask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,35 @@ import { Browser, BrowserContext, Page } from 'puppeteer';
import { getMetaMask } from '../metamask';
import { Dappeteer, MetaMaskOptions } from '../types';

import { closePopup, confirmWelcomeScreen, importAccount, showTestNets } from './setupActions';
import {
closePortfolioTooltip,
closeWhatsNewModal,
confirmWelcomeScreen,
declineAnalytics,
importAccount,
showTestNets,
} from './setupActions';

/**
* Setup MetaMask with base account
* */
type Step<Options> = (page: Page, options?: Options) => void;
const defaultMetaMaskSteps: Step<MetaMaskOptions>[] = [confirmWelcomeScreen, importAccount, closePopup, showTestNets];
const defaultMetaMaskSteps: Step<MetaMaskOptions>[] = [
confirmWelcomeScreen,
declineAnalytics,
importAccount,
showTestNets,
closePortfolioTooltip,
closeWhatsNewModal,
closeWhatsNewModal,
];

export async function setupMetaMask<Options = MetaMaskOptions>(
browser: Browser | BrowserContext,
options?: Options,
steps: Step<Options>[] = defaultMetaMaskSteps,
): Promise<Dappeteer> {
const page = await closeHomeScreen(browser);
const page = await getMetamaskPage(browser);
page.setViewport({ height: 1200, width: 800 });
// goes through the installation steps required by MetaMask
for (const step of steps) {
Expand All @@ -26,7 +41,13 @@ export async function setupMetaMask<Options = MetaMaskOptions>(
return getMetaMask(page);
}

async function closeHomeScreen(browser: Browser | BrowserContext): Promise<Page> {
async function getMetamaskPage(browser: Browser | BrowserContext): Promise<Page> {
const pages = await browser.pages();
for (const page of pages) {
if (page.url().match('chrome-extension://[a-z]+/home.html')) {
return page;
}
}
return new Promise((resolve, reject) => {
browser.on('targetcreated', async (target) => {
if (target.url().match('chrome-extension://[a-z]+/home.html')) {
Expand Down
17 changes: 12 additions & 5 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
import { BrowserLaunchArgumentOptions, Page } from 'puppeteer';
import * as puppeteer from 'puppeteer';

import { Path } from './setup/metaMaskDownloader';

import { RECOMMENDED_METAMASK_VERSION } from './index';

export type LaunchOptions = OfficialOptions | CustomOptions;

type DappaterBrowserLaunchArgumentOptions = Omit<BrowserLaunchArgumentOptions, 'headless'>;
type PuppeteerLaunchOptions = puppeteer.LaunchOptions &
puppeteer.BrowserLaunchArgumentOptions &
puppeteer.BrowserConnectOptions & {
product?: puppeteer.Product;
extraPrefsFirefox?: Record<string, unknown>;
};

type DappeteerLaunchOptions = Omit<PuppeteerLaunchOptions, 'headless'>;

export type OfficialOptions = DappaterBrowserLaunchArgumentOptions & {
export type OfficialOptions = DappeteerLaunchOptions & {
metaMaskVersion: typeof RECOMMENDED_METAMASK_VERSION | 'latest' | string;
metaMaskLocation?: Path;
};

export type CustomOptions = DappaterBrowserLaunchArgumentOptions & {
export type CustomOptions = DappeteerLaunchOptions & {
metaMaskVersion?: string;
metaMaskPath: string;
};
Expand Down Expand Up @@ -59,5 +66,5 @@ export type Dappeteer = {
deleteAccount: (accountNumber: number) => Promise<void>;
deleteNetwork: (name: string) => Promise<void>;
};
page: Page;
page: puppeteer.Page;
};
22 changes: 6 additions & 16 deletions test/basic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import chaiAsPromised from 'chai-as-promised';
import { Page } from 'puppeteer';

import * as dappeteer from '../src';
import { clickOnLogo, openProfileDropdown } from '../src/helpers';
import { openProfileDropdown } from '../src/helpers';

import { PASSWORD, TestContext } from './global';
import { clickElement } from './utils/utils';
Expand All @@ -26,6 +26,10 @@ describe('basic interactions', async function () {
}
});

afterEach(async function () {
await metamask.page.reload();
});

after(async function () {
await testPage.close();
});
Expand Down Expand Up @@ -60,12 +64,11 @@ describe('basic interactions', async function () {

// TODO: cover more cases
it('should add token', async () => {
await metamask.switchNetwork('kovan');
await metamask.switchNetwork('mainnet');
await metamask.addToken({
tokenAddress: '0x4f96fe3b7a6cf9725f59d353f723c1bdb64ca6aa',
symbol: 'KAKI',
});
await metamask.switchNetwork('local');
});

it('should add network with required params', async () => {
Expand All @@ -83,19 +86,6 @@ describe('basic interactions', async function () {
await metamask.switchNetwork('local');
});

it('should fail to add network with wrong chain ID', async () => {
await expect(
metamask.addNetwork({
networkName: 'Optimistic Ethereum Testnet Kovan',
rpc: 'https://kovan.optimism.io/',
chainId: 420,
symbol: 'KUR',
}),
).to.be.rejectedWith(SyntaxError);

await clickOnLogo(metamask.page);
});

it('should import private key', async () => {
const countAccounts = async (): Promise<number> => {
await openProfileDropdown(metamask.page);
Expand Down
6 changes: 2 additions & 4 deletions test/contract.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { TestContext } from './global';
import { clickElement, pause } from './utils/utils';

describe('contract interactions', async function () {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let contract: Contract;
let testPage: Page;
let metamask: Dappeteer;
Expand All @@ -24,15 +23,15 @@ describe('contract interactions', async function () {
} catch (e) {
//ignored
}
await metamask.switchAccount(1);
await metamask.switchNetwork('local');
});

after(async function (this: TestContext) {
await testPage.close();
});

it('should have increased count', async () => {
await metamask.switchAccount(1);
await metamask.switchNetwork('local');
await pause(1);
const counterBefore = await getCounterNumber(contract);
// click increase button
Expand All @@ -46,7 +45,6 @@ describe('contract interactions', async function () {
const counterAfter = await getCounterNumber(contract);

expect(counterAfter).to.be.equal(counterBefore + 1);
await metamask.switchNetwork('main');
});
});

Expand Down

0 comments on commit ccb3215

Please sign in to comment.