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

Commit

Permalink
feat: add support for installing metamask flask (#153)
Browse files Browse the repository at this point in the history
feat: add support for installing metamask flaask
  • Loading branch information
mpetrunic committed Dec 15, 2022
1 parent 4aa5ca2 commit 71cf265
Show file tree
Hide file tree
Showing 16 changed files with 196 additions and 58 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ jobs:
checks:
name: Checks
runs-on: ubuntu-latest
strategy:
matrix:
mm-version: [mm, flask]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
Expand All @@ -25,7 +28,7 @@ jobs:
- name: Lint
run: yarn run lint
- name: Tests
run: xvfb-run --auto-servernum yarn run test --timeout 50000
run: 'xvfb-run --auto-servernum yarn run test:${{ matrix.mm-version }} --timeout 50000'
- uses: actions/upload-artifact@v3
if: always()
with:
Expand Down
3 changes: 0 additions & 3 deletions .mocharc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,5 @@ recursive: true
color: true
timeout: 20000
exit: true
require:
- 'ts-node/register'
- 'test/global.ts'
spec:
- 'test/**/*.spec.ts'
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
"build": "tsc -p tsconfig.build.json",
"lint": "eslint --color --ext .ts src/ test/",
"lint:fix": "yarn run lint --fix",
"test": "mocha",
"test:dev": "mocha --timeout 36000000"
"test": "yarn run test:*",
"test:mm": "mocha --require ts-node/register --require test/global.ts",
"test:flask": "mocha --require ts-node/register --require test/global_flask.ts"
},
"repository": {
"type": "git",
Expand Down
12 changes: 9 additions & 3 deletions src/helpers/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,18 @@ export const openNetworkDropdown = async (page: Page): Promise<void> => {
};

export const openProfileDropdown = async (page: Page): Promise<void> => {
const accountSwitcher = await page.waitForSelector(".identicon");
const accountSwitcher = await page.waitForSelector(".identicon", {
visible: true,
});
await accountSwitcher.click();
};

export const openAccountDropdown = async (page: Page): Promise<void> => {
const accMenu = await getAccountMenuButton(page);
await accMenu.click();
await page.waitForSelector(".menu__container.account-options-menu");
await page.waitForSelector(".menu__container.account-options-menu", {
visible: true,
});
};

export const clickOnElement = async (
Expand All @@ -64,7 +68,9 @@ export const clickOnButton = async (
};

export const clickOnLogo = async (page: Page): Promise<void> => {
const header = await page.waitForSelector(".app-header__logo-container");
const header = await page.waitForSelector(".app-header__logo-container", {
visible: true,
});
await header.click();
};

Expand Down
4 changes: 3 additions & 1 deletion src/helpers/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,6 @@ 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')]`, {
visible: true,
});
4 changes: 3 additions & 1 deletion src/metamask/addToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ export const addToken =
async ({ tokenAddress, symbol, decimals = 0 }: AddToken): Promise<void> => {
await page.bringToFront();
await clickOnButton(page, "Assets");
await page.waitForSelector(".asset-list-item__token-button");
await page.waitForSelector(".asset-list-item__token-button", {
visible: true,
});
await clickOnElement(page, "import tokens");
await clickOnButton(page, "Custom token");

Expand Down
23 changes: 19 additions & 4 deletions src/setup/launch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import { LaunchOptions } from "../types";
import { isNewerVersion } from "./isNewerVersion";
import downloader from "./metaMaskDownloader";

export type DappeteerBrowser = puppeteer.Browser & { flask?: boolean };

/**
* Launch Puppeteer chromium instance with MetaMask plugin installed
* */
export async function launch(
puppeteerLib: typeof puppeteer,
options: LaunchOptions
): Promise<puppeteer.Browser> {
): Promise<DappeteerBrowser> {
if (
!options ||
(!options.metaMaskVersion && !(options as CustomOptions).metaMaskPath)
Expand Down Expand Up @@ -50,19 +52,27 @@ export async function launch(
`Seems you are running older version of MetaMask that recommended by dappeteer team.
Use it at your own risk or set the recommended version "${RECOMMENDED_METAMASK_VERSION}".`
);
else console.log(`Running tests on MetaMask version ${metaMaskVersion}`);
else
console.log(
`Running tests on MetaMask version ${metaMaskVersion} (flask: ${String(
options.metaMaskFlask ?? false
)})`
);

console.log(); // new line

METAMASK_PATH = await downloader(metaMaskVersion, metaMaskLocation);
METAMASK_PATH = await downloader(metaMaskVersion, {
location: metaMaskLocation,
flask: options.metaMaskFlask,
});
} else {
console.log(`Running tests on local MetaMask build`);

METAMASK_PATH = (rest as CustomOptions).metaMaskPath;
/* eslint-enable no-console */
}

return puppeteerLib.launch({
const browser = await puppeteerLib.launch({
headless: false,
args: [
`--disable-extensions-except=${METAMASK_PATH}`,
Expand All @@ -71,4 +81,9 @@ export async function launch(
],
...rest,
});

if (options.metaMaskFlask) {
Object.assign(browser, { flask: true });
}
return browser;
}
42 changes: 30 additions & 12 deletions src/setup/metaMaskDownloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ export type Path =
extract: string;
};

export default async (version: string, location?: Path): Promise<string> => {
export default async (
version: string,
options?: { location?: Path; flask?: boolean }
): Promise<string> => {
const location = options.location;
const metaMaskDirectory =
typeof location === "string"
? location
Expand All @@ -26,17 +30,22 @@ export default async (version: string, location?: Path): Promise<string> => {
: location?.download || path.resolve(defaultDirectory, "download");

if (version !== "latest") {
const extractDestination = path.resolve(
metaMaskDirectory,
version.replace(/\./g, "_")
);
let filename = version.replace(/\./g, "_");
if (options?.flask) {
filename = "flask_" + filename;
}
const extractDestination = path.resolve(metaMaskDirectory, filename);
if (fs.existsSync(extractDestination)) return extractDestination;
}
const { filename, downloadUrl, tag } = await getMetaMaskReleases(version);
const extractDestination = path.resolve(
metaMaskDirectory,
tag.replace(/\./g, "_")
const { filename, downloadUrl, tag } = await getMetaMaskReleases(
version,
options?.flask ?? false
);
let destFilename = tag.replace(/\./g, "_");
if (options?.flask) {
destFilename = "flask_" + filename;
}
const extractDestination = path.resolve(metaMaskDirectory, destFilename);
if (!fs.existsSync(extractDestination)) {
const downloadedFile = await downloadMetaMaskReleases(
filename,
Expand Down Expand Up @@ -94,7 +103,10 @@ const downloadMetaMaskReleases = (
type MetaMaskReleases = { downloadUrl: string; filename: string; tag: string };
const metaMaskReleasesUrl =
"https://api.github.com/repos/metamask/metamask-extension/releases";
const getMetaMaskReleases = (version: string): Promise<MetaMaskReleases> =>
const getMetaMaskReleases = (
version: string,
flask: boolean
): Promise<MetaMaskReleases> =>
new Promise((resolve, reject) => {
// eslint-disable-next-line @typescript-eslint/naming-convention
const request = get(
Expand All @@ -118,16 +130,22 @@ const getMetaMaskReleases = (version: string): Promise<MetaMaskReleases> =>
) {
for (const asset of result.assets) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
if (asset.name.includes("chrome"))
if (
(!flask && asset.name.includes("chrome")) ||
(flask &&
asset.name.includes("flask") &&
asset.name.includes("chrome"))
) {
resolve({
downloadUrl: asset.browser_download_url,
filename: asset.name,
tag: result.tag_name,
});
}
}
}
}
reject(`Version ${version} not found!`);
reject(`Version ${version} (flask: ${String(flask)}) not found!`);
});
}
);
Expand Down
4 changes: 4 additions & 0 deletions src/setup/setupActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export async function showTestNets(metaMaskPage: Page): Promise<void> {
await clickOnLogo(metaMaskPage);
}

export async function acceptTheRisks(metaMaskPage: Page): Promise<void> {
await clickOnButton(metaMaskPage, "I accept the risks");
}

export async function confirmWelcomeScreen(metaMaskPage: Page): Promise<void> {
await clickOnButton(metaMaskPage, "Get started");
}
Expand Down
20 changes: 17 additions & 3 deletions src/setup/setupMetaMask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { Browser, BrowserContext, Page, Target } from "puppeteer";
import { getMetaMask } from "../metamask";
import { Dappeteer, MetaMaskOptions } from "../types";

import { DappeteerBrowser } from "./launch";
import {
acceptTheRisks,
closePortfolioTooltip,
closeWhatsNewModal,
confirmWelcomeScreen,
Expand All @@ -25,14 +27,26 @@ const defaultMetaMaskSteps: Step<MetaMaskOptions>[] = [
closeWhatsNewModal,
closeWhatsNewModal,
];
const flaskMetaMaskSteps: Step<MetaMaskOptions>[] = [
acceptTheRisks,
importAccount,
showTestNets,
closePortfolioTooltip,
closeWhatsNewModal,
closeWhatsNewModal,
];

export async function setupMetaMask<Options = MetaMaskOptions>(
browser: Browser | BrowserContext,
browser: Browser | BrowserContext | DappeteerBrowser,
options?: Options,
steps: Step<Options>[] = defaultMetaMaskSteps
steps?: Step<Options>[]
): Promise<Dappeteer> {
const page = await getMetamaskPage(browser);
await page.setViewport({ height: 1200, width: 800 });
steps = steps ?? defaultMetaMaskSteps;
if ((browser as DappeteerBrowser).flask) {
steps = flaskMetaMaskSteps;
}
await page.setViewport({ height: 800, width: 800 });
// goes through the installation steps required by MetaMask
for (const step of steps) {
await step(page, options);
Expand Down
5 changes: 4 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { Path } from "./setup/metaMaskDownloader";

import { RECOMMENDED_METAMASK_VERSION } from "./index";

export type LaunchOptions = OfficialOptions | CustomOptions;
export type LaunchOptions = (OfficialOptions | CustomOptions) & {
//install flask (canary) version of metamask.
metaMaskFlask?: boolean;
};

type PuppeteerLaunchOptions = puppeteer.LaunchOptions &
puppeteer.BrowserLaunchArgumentOptions &
Expand Down
2 changes: 1 addition & 1 deletion test/basic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Page } from "puppeteer";
import * as dappeteer from "../src";
import { openProfileDropdown } from "../src/helpers";

import { PASSWORD, TestContext } from "./global";
import { PASSWORD, TestContext } from "./constant";
import { clickElement } from "./utils/utils";

use(chaiAsPromised);
Expand Down
25 changes: 25 additions & 0 deletions test/constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import http from "http";

import { Provider, Server } from "ganache";
import { Browser } from "puppeteer";

import { Dappeteer } from "../src";

import { Contract } from "./deploy";

export type InjectableContext = Readonly<{
provider: Provider;
ethereum: Server<"ethereum">;
testPageServer: http.Server;
browser: Browser;
metamask: Dappeteer;
contract: Contract;
flask: boolean;
}>;

// TestContext will be used by all the test
export type TestContext = Mocha.Context & InjectableContext;

export const LOCAL_PREFUNDED_MNEMONIC =
"pioneer casual canoe gorilla embrace width fiction bounce spy exhibit another dog";
export const PASSWORD = "password1234";
2 changes: 1 addition & 1 deletion test/contract.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { Page } from "puppeteer";

import { Dappeteer } from "../src";

import { TestContext } from "./constant";
import { Contract } from "./deploy";
import { TestContext } from "./global";
import { clickElement, pause } from "./utils/utils";

describe("contract interactions", function () {
Expand Down
33 changes: 8 additions & 25 deletions test/global.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,16 @@
import http from "http";
import path from "path";

import { Provider, Server } from "ganache";
import puppeteer, { Browser } from "puppeteer";
import puppeteer from "puppeteer";

import * as dappeteer from "../src";
import { Dappeteer } from "../src";

import {
Contract,
deployContract,
startLocalEthereum,
startTestServer,
} from "./deploy";

export type InjectableContext = Readonly<{
provider: Provider;
ethereum: Server<"ethereum">;
testPageServer: http.Server;
browser: Browser;
metamask: Dappeteer;
contract: Contract;
}>;

// TestContext will be used by all the test
export type TestContext = Mocha.Context & InjectableContext;

export const LOCAL_PREFUNDED_MNEMONIC =
"pioneer casual canoe gorilla embrace width fiction bounce spy exhibit another dog";
export const PASSWORD = "password1234";
InjectableContext,
LOCAL_PREFUNDED_MNEMONIC,
PASSWORD,
TestContext,
} from "./constant";
import { deployContract, startLocalEthereum, startTestServer } from "./deploy";

export const mochaHooks = {
async beforeAll(this: Mocha.Context): Promise<void> {
Expand Down Expand Up @@ -57,6 +39,7 @@ export const mochaHooks = {
browser,
testPageServer: server,
metamask,
flask: false,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
contract,
};
Expand Down

0 comments on commit 71cf265

Please sign in to comment.