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

Commit

Permalink
feat: method to bootstrap snap env (#180)
Browse files Browse the repository at this point in the history
* feat: install snap from local files

* fix: installing and invokeing snaps

* disable addToken test

* disable fail fast

* replace binance smart chain
  • Loading branch information
mpetrunic committed Nov 11, 2022
1 parent 708c87b commit ba9745f
Show file tree
Hide file tree
Showing 15 changed files with 163 additions and 108 deletions.
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ jobs:
name: Checks
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
mm-version: [mm, flask]
automation: [playwright, puppeteer]
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
"license": "MIT",
"dependencies": {
"@metamask/providers": "^9.1.0",
"node-stream-zip": "^1.13.0"
"node-stream-zip": "^1.13.0",
"serve-handler": "5.0.8"
},
"devDependencies": {
"@chainsafe/eslint-config": "^1.0.0",
Expand All @@ -64,7 +65,6 @@
"playwright": "^1.27.1",
"prettier": "^2.2.1",
"puppeteer": "14.0.0",
"serve-handler": "5.0.8",
"solc": "0.5.2",
"ts-node": "10.8.1",
"typescript": "^4.7",
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const openNetworkDropdown = async (
await networkSwitcher.click();
await page.waitForSelector(".network-dropdown-list", {
visible: true,
timeout: 1000,
timeout: 3000,
});
}
};
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// re-export
export { getMetaMask, getMetaMaskWindow } from "./metamask";
export * from "./types";
export * from "./browser";
export * from "./page";
export * from "./element";
export * from "./setup";
export { DapeteerJestConfig } from "./jest/global";

Expand Down
37 changes: 37 additions & 0 deletions src/setup/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DappeteerBrowser } from "../browser";
import { DappeteerPage } from "../page";
import { InstallSnapOptions } from "../snap/install";
import { Dappeteer, DappeteerLaunchOptions, MetaMaskOptions } from "../types";
import { launch } from "./launch";
import { setupMetaMask } from "./setupMetaMask";
Expand Down Expand Up @@ -31,3 +32,39 @@ export const bootstrap = async ({
page: pages[0],
};
};

export const initSnapEnv = async (
opts: DappeteerLaunchOptions &
MetaMaskOptions &
InstallSnapOptions & { snapIdOrLocation: string }
): Promise<{
dappeteer: Dappeteer;
browser: DappeteerBrowser;
page: DappeteerPage;
snapId: string;
}> => {
const browser = await launch({
...opts,
metaMaskFlask: true,
});
const { snapIdOrLocation, seed, password, showTestNets } = opts;
const dappeteer = await setupMetaMask(browser, {
seed,
password,
showTestNets,
});
const page = dappeteer.page;

const snapId = await dappeteer.snaps.installSnap(
page,
snapIdOrLocation,
opts
);

return {
dappeteer,
browser,
page,
snapId,
};
};
39 changes: 39 additions & 0 deletions src/snap/install-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import http from "http";
import { AddressInfo } from "net";
import handler from "serve-handler";

export function toUrl(address: AddressInfo | string): string {
if (typeof address === "string") {
return address;
}
return `http://localhost:${address.port}`;
}

export async function startSnapServer(snapDist: string): Promise<http.Server> {
const server = http.createServer((req, res) => {
void handler(req, res, {
public: snapDist,
headers: [
{
source: "**/*",
headers: [
{
key: "Cache-Control",
value: "no-cache",
},
{
key: "Access-Control-Allow-Origin",
value: "*",
},
],
},
],
});
});
await new Promise<void>((resolve) => {
server.listen(0, () => {
resolve();
});
});
return server;
}
41 changes: 26 additions & 15 deletions src/snap/install.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import fs from "fs";
import http from "http";
import { MetaMaskInpageProvider } from "@metamask/providers";
import {
clickOnButton,
Expand All @@ -6,31 +8,40 @@ import {
openProfileDropdown,
} from "../helpers";
import { DappeteerPage } from "../page";
import { startSnapServer, toUrl } from "./install-utils";
import { flaskOnly } from "./utils";
import { InstallSnapResult } from "./types";

declare let window: { ethereum: MetaMaskInpageProvider };

export type InstallStep = (page: DappeteerPage) => Promise<void>;

export type InstallSnapOptions = {
hasPermissions: boolean;
hasKeyPermissions: boolean;
customSteps?: InstallStep[];
version?: string;
installationSnapUrl?: string;
};

export async function installSnap(
page: DappeteerPage,
snapId: string,
opts: {
hasPermissions: boolean;
hasKeyPermissions: boolean;
customSteps?: InstallStep[];
version?: string;
},
installationSnapUrl: string = "https://google.com"
): Promise<InstallSnapResult> {
snapIdOrLocation: string,
opts: InstallSnapOptions
): Promise<string> {
flaskOnly(page);
//need to open page to access window.ethereum
const installPage = await page.browser().newPage();
await installPage.goto(installationSnapUrl);
await installPage.goto(opts.installationSnapUrl ?? "https://google.com");
let snapServer: http.Server | undefined;
if (fs.existsSync(snapIdOrLocation)) {
//snap dist location
snapServer = await startSnapServer(snapIdOrLocation);
snapIdOrLocation = `local:${toUrl(snapServer.address())}`;
}
const installAction = installPage.evaluate(
(opts: { snapId: string; version?: string }) =>
window.ethereum.request<{ snaps: { [snapId: string]: {} } }>({
window.ethereum.request<InstallSnapResult>({
method: "wallet_enable",
params: [
{
Expand All @@ -40,7 +51,7 @@ export async function installSnap(
},
],
}),
{ snapId, version: opts.version }
{ snapId: snapIdOrLocation, version: opts.version }
);

await page.bringToFront();
Expand All @@ -65,11 +76,11 @@ export async function installSnap(

const result = await installAction;
await installPage.close({ runBeforeUnload: true });
if (!(snapId in result.snaps)) {
if (!(snapIdOrLocation in result.snaps)) {
throw new Error("Failed to install snap");
}

return result as InstallSnapResult;
snapServer.close();
return snapIdOrLocation;
}

export async function isSnapInstalled(
Expand Down
31 changes: 20 additions & 11 deletions src/snap/invokeSnap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,28 @@ export async function invokeSnap<
params?: P
): ReturnType<typeof window.ethereum.request<R>> {
flaskOnly(page);
return page.evaluate(
const result = await page.evaluate(
async (opts: { snapId: string; method: string; params: Unboxed<P> }) => {
return window.ethereum.request<R>({
method: "wallet_invokeSnap",
params: [
`${opts.snapId}`,
{
method: opts.method,
params: opts.params,
},
],
});
try {
return await window.ethereum.request<R>({
method: "wallet_invokeSnap",
params: [
`${opts.snapId}`,
{
method: opts.method,
params: opts.params,
},
],
});
} catch (e) {
return e as Error;
}
},
{ snapId, method, params }
);
if (result instanceof Error) {
throw result;
} else {
return result;
}
}
9 changes: 6 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { MetaMaskInpageProvider } from "@metamask/providers";
import type { LaunchOptions as PlaywrightLaunchOptions } from "playwright";
import type { launch as puppeteerLaunch } from "puppeteer";
import { MetaMaskInpageProvider } from "@metamask/providers";
import { DappeteerPage, Serializable } from "./page";
import { Path } from "./setup/utils/metaMaskDownloader";
import { InstallStep } from "./snap/install";
import { InstallSnapResult, NotificationList } from "./snap/types";
import { NotificationList } from "./snap/types";
import { RECOMMENDED_METAMASK_VERSION } from "./index";

export type DappeteerLaunchOptions = {
Expand Down Expand Up @@ -80,6 +80,9 @@ export type Dappeteer = {
method: string,
params?: P
) => Promise<Partial<R>>;
/**
*
*/
installSnap: (
page: DappeteerPage,
snapId: string,
Expand All @@ -90,7 +93,7 @@ export type Dappeteer = {
version?: string;
},
installationSnapUrl?: string
) => Promise<InstallSnapResult>;
) => Promise<string>;
acceptDialog: () => Promise<void>;
rejectDialog: () => Promise<void>;
};
Expand Down
12 changes: 6 additions & 6 deletions test/basic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ describe("basic interactions", function () {
expect(tokenBalance).to.be.equal(0);
});

// TODO: cover more cases
// TODO: Metamask UI is flaky there
it("should add token", async () => {
await metamask.switchNetwork("mainnet");
await metamask.addToken({
Expand All @@ -78,17 +78,17 @@ describe("basic interactions", function () {

it("should add network with required params", async () => {
await metamask.addNetwork({
networkName: "Binance Smart Chain",
rpc: "https://data-seed-prebsc-1-s1.binance.org:8545/",
chainId: 97,
symbol: "BNB",
networkName: "Optimism",
rpc: "https://mainnet.optimism.io",
chainId: 10,
symbol: "OP",
});

const selectedNetwork = await metamask.page.evaluate(
() =>
document.querySelector(".network-display > span:nth-child(2)").innerHTML
);
expect(selectedNetwork).to.be.equal("Binance Smart Chain");
expect(selectedNetwork).to.be.equal("Optimism");
await metamask.switchNetwork("local");
});

Expand Down
2 changes: 1 addition & 1 deletion test/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export type InjectableContext = Readonly<{
provider: Provider;
ethereum: Server<"ethereum">;
testPageServer: http.Server;
snapServers?: Record<Snaps, http.Server>;
snapServers?: Record<Snaps, string>;
browser: DappeteerBrowser;
metamask: Dappeteer;
contract: Contract;
Expand Down
43 changes: 7 additions & 36 deletions test/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import handler from "serve-handler";
import Web3 from "web3";

import { compileContracts } from "./contract";
import { toUrl } from "./utils/utils";

const counterContract: { address: string } | null = null;

Expand Down Expand Up @@ -87,16 +86,16 @@ export enum Snaps {
METHODS_SNAP = "methods-snap",
}

export async function startSnapServers(): Promise<Record<Snaps, http.Server>> {
export async function buildSnaps(): Promise<Record<Snaps, string>> {
return {
[Snaps.BASE_SNAP]: await startSnapServer(Snaps.BASE_SNAP),
[Snaps.KEYS_SNAP]: await startSnapServer(Snaps.KEYS_SNAP),
[Snaps.PERMISSIONS_SNAP]: await startSnapServer(Snaps.PERMISSIONS_SNAP),
[Snaps.METHODS_SNAP]: await startSnapServer(Snaps.METHODS_SNAP),
[Snaps.BASE_SNAP]: await buildSnap(Snaps.BASE_SNAP),
[Snaps.KEYS_SNAP]: await buildSnap(Snaps.KEYS_SNAP),
[Snaps.PERMISSIONS_SNAP]: await buildSnap(Snaps.PERMISSIONS_SNAP),
[Snaps.METHODS_SNAP]: await buildSnap(Snaps.METHODS_SNAP),
};
}

async function startSnapServer(snap: Snaps): Promise<http.Server> {
async function buildSnap(snap: Snaps): Promise<string> {
console.log(`Building ${snap}...`);
await new Promise((resolve, reject) => {
exec(`cd ./test/flask/${snap} && npx mm-snap build`, (error, stdout) => {
Expand All @@ -107,33 +106,5 @@ async function startSnapServer(snap: Snaps): Promise<http.Server> {
resolve(stdout);
});
});
console.log(`Starting ${snap} server...`);
const server = http.createServer((req, res) => {
void handler(req, res, {
public: path.resolve(__dirname, `./flask/${snap}`),
headers: [
{
source: "**/*",
headers: [
{
key: "Cache-Control",
value: "no-cache",
},
{
key: "Access-Control-Allow-Origin",
value: "*",
},
],
},
],
});
});

await new Promise<void>((resolve) => {
server.listen(0, () => {
console.log(`Server for ${snap} running at `, toUrl(server.address()));
resolve();
});
});
return server;
return `${path.resolve("./test/flask", snap)}`;
}
Loading

0 comments on commit ba9745f

Please sign in to comment.