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

feat: speedup initializing MetaMask #238

Merged
merged 51 commits into from
Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
4de874a
chore: update CI for unstable branch
mpetrunic Sep 22, 2022
00f9857
fix!: casing of MetaMask (#132)
Mrtenz Sep 22, 2022
d18efa4
fix: update ganache, fix test depending on goerli (#144)
mpetrunic Sep 23, 2022
dc9b5fe
fix: selector issues, useless timeouts, reorganise tests (#145)
mpetrunic Sep 29, 2022
c90db41
fix: import token flaky (#149)
mpetrunic Sep 30, 2022
0c5dc17
feat!: update recommended metamask version (#151)
mpetrunic Oct 4, 2022
9ab9b4c
chore: change eslint config to chainsafe shared (#152)
mpetrunic Oct 7, 2022
07775a7
feat: add support for installing metamask flask (#153)
mpetrunic Oct 10, 2022
564aa4b
feat: ability to install snap (#154)
mpetrunic Oct 12, 2022
d1a3ec4
fix: snap install faster, run all tests (#163)
mpetrunic Oct 26, 2022
e7d8435
feat: Add invokeSnap method; update installSnap method parameter; (#159)
Lykhoyda Oct 26, 2022
2661323
feat: add ability to accept dialogs (#138) (#164)
Lykhoyda Nov 2, 2022
c25c66f
feat!: add playwright support (#167)
mpetrunic Nov 9, 2022
4804b88
feat: added notification snap to methods-snap #137 (#166)
Lykhoyda Nov 10, 2022
708c87b
Merge remote-tracking branch 'origin/master' into unstable
mpetrunic Nov 10, 2022
ba9745f
feat: method to bootstrap snap env (#180)
mpetrunic Nov 11, 2022
b01e8d2
chore: node engine requirements (#184)
BeroBurny Nov 14, 2022
d9e4a4c
chore: remove metamask dir (#185)
mpetrunic Nov 15, 2022
a4c2031
fix: remove page param from install snap (#188)
mpetrunic Nov 15, 2022
4171ea4
feat!: replace outdated methods (#189)
mpetrunic Nov 22, 2022
e382c4c
chore: Ci enhancement (#193)
Tbaut Nov 23, 2022
d63f9e4
feat: allow signing typed data (#191)
Tbaut Nov 24, 2022
d9db933
fix: fix prompt clicking flakiness, fix multiple snap key permissions…
mpetrunic Nov 28, 2022
10c116e
feat: snap notifications 137 (#187)
Lykhoyda Nov 28, 2022
ef70dae
chore: Deprecate button clicks for tests (#195)
Tbaut Nov 30, 2022
3ed2f18
chore: Update documentation and Readme (#202)
Tbaut Dec 6, 2022
902b035
chore: Remove local server for dapp (#203)
Tbaut Dec 6, 2022
31ca258
move temporary user data dir in upper scope
BeroBurny Dec 7, 2022
ac43675
implement exporting state and running from a state
BeroBurny Dec 7, 2022
08deead
implement userDataDir for puppeteer
BeroBurny Dec 8, 2022
d82f84f
implement tests
BeroBurny Dec 8, 2022
05f4ae3
fix setupBootstrappedMetaMask for flask
BeroBurny Dec 9, 2022
7806714
fix flask
BeroBurny Dec 9, 2022
c75b302
implement addKeyToMetaMaskManifest
BeroBurny Jan 4, 2023
7f7ea85
Merge branch 'master' into beroburny/speedup-init
BeroBurny Jan 4, 2023
03df940
small fixes
BeroBurny Jan 4, 2023
6a814ab
improve userData testing
BeroBurny Jan 10, 2023
76758d7
fix jest config
BeroBurny Jan 10, 2023
baad180
implement default user profile
BeroBurny Jan 10, 2023
daa7223
small quality of life improvements
BeroBurny Jan 10, 2023
0a17923
improve documentation
BeroBurny Jan 16, 2023
e662a60
Merge branch 'master' into beroburny/speedup-init
BeroBurny Jan 16, 2023
8072c86
fix headless issues whit handling files
BeroBurny Jan 17, 2023
9fba7d2
Merge branch 'master' into beroburny/speedup-init
BeroBurny Jan 17, 2023
ce421ef
improve ci
BeroBurny Jan 17, 2023
51e4498
fix yml
BeroBurny Jan 17, 2023
63651eb
fix ci
BeroBurny Jan 17, 2023
0acc1be
improve ci
BeroBurny Jan 17, 2023
61bc018
Merge branch 'master' into beroburny/speedup-init
BeroBurny Jan 19, 2023
87caf77
fix export
BeroBurny Jan 19, 2023
30f7e66
address comments
BeroBurny Jan 19, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,29 @@ jobs:
with:
name: debug_screenshots
path: ./*.png
tests-user-data:
name: Tests UserData
runs-on: ubuntu-latest
needs: tests
strategy:
fail-fast: false
matrix:
automation: [ playwright, puppeteer ]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "16"
cache: 'yarn'
- name: Install
run: yarn --prefer-offline --frozen-lockfile
- name: Tests UserData
run: 'yarn run test:${{matrix.automation}}:userData --timeout 240000'

maybe-release:
name: release
runs-on: ubuntu-latest
needs: [tests, lint, build]
needs: [tests, lint, build, tests-user-data]
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
steps:
- uses: google-github-actions/release-please-action@v3 # it will analyze commits and create PR with new version and updated CHANGELOG:md file. On merging it will create github release page with changelog
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ dist
/.metamask
.idea
*.log
*.png
*.png

!userData/**/*.log
33 changes: 19 additions & 14 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ For additional information read root [readme](../README.md)
- [Bootstrap dAppeteer](#bootstrap)
- [Initialize Snap Environment](#initSnapEnv)
- [Get MetaMask Window](#getMetaMask)
- [metaMask methods](#methods)
- [MetaMask methods](#methods)
- [switchAccount](#switchAccount)
- [importPK](#importPK)
- [lock](#lock)
Expand Down Expand Up @@ -38,23 +38,26 @@ For additional information read root [readme](../README.md)
# dAppeteer setup methods

<a name="launch"></a>
## `dappeteer.launch(puppeteerLib: typeof puppeteer, options: OfficialOptions | CustomOptions): Promise<Browser>`
## `dappeteer.launch(options: DappeteerLaunchOptions): Promise<DappeteerBrowser>`
```typescript
interface OfficialOptions {
metaMaskVersion: 'latest' | string;
type DappeteerLaunchOptions = {
metaMaskVersion?:
| "latest"
| "local"
| string;
metaMaskLocation?: Path;
};

type Path = string | { download: string; extract: string; };
```
or
```typescript
interface CustomOptions {
metaMaskPath: string;
metaMaskPath?: string;
metaMaskFlask?: boolean;
automation?: "puppeteer" | "playwright";
browser: "chrome";
puppeteerOptions?: Parameters<typeof puppeteerLaunch>[0];
playwrightOptions?: PlaywrightLaunchOptions;
userDataDir?: string;
key?: string;
};
```

returns an instance of `browser` same as `puppeteer.launch`, but it also installs the MetaMask extension. [It supports all the regular `puppeteer.launch` options](https://github.com/puppeteer/puppeteer/blob/v5.5.0/docs/api.md#puppeteerlaunchoptions)
returns an instance of `DappeteerBrowser` for more information visit [browser page](docs/BROWSER.md)

<a name="setup"></a>
## `dappeteer.setupMetaMask(browser: Browser, options: MetaMaskOptions = {}, steps: Step[]): Promise<Dappeteer>`
Expand Down Expand Up @@ -91,6 +94,8 @@ type DappeteerLaunchOptions = {
browser: "chrome";
puppeteerOptions?: Omit<Parameters<typeof puppeteerLaunch>[0], "headless">;
playwrightOptions?: Omit<PlaywrightLaunchOptions, "headless">;
userDataDir?: string;
key?: string;
};

type MetaMaskOptions = {
Expand Down Expand Up @@ -139,7 +144,7 @@ it runs `dappeteer.launch` and `dappeteer.setupMetamask` and `snaps.installSnap`
## `dappeteer.getMetaMaskWindow(browser: Browser, version?: string): Promise<Dappeteer>`

<a name="methods"></a>
# metaMask methods
# MetaMask methods
`metaMask` is used as placeholder for dAppeteer returned by [`setupMetaMask`](setup) or [`getMetaMaskWindow`](getMetaMask)

<a name="switchAccount"></a>
Expand Down
163 changes: 163 additions & 0 deletions docs/BROWSER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# dAppeteer Browser

- [Methods](#methods)
- [isMetaMaskFlask](#isMetaMaskFlask)
- [pages](#pages)
- [newPage](#newPage)
- [getSource](#getSource)
- [close](#close)
- [wsEndpoint](#wsEndpoint)
- [getUserDataDirPath](#getUserDataDirPath)
- [storeUserData](#storeUserData)
- [Advance Usages](#advanced)
- [Storing and lunching browser from specific state](#storeAndRun)
- [Storing state](#storeAndRun-storing)
- [Starting from state](#storeAndRun-start)

<a name="methods"></a>
# dAppeteer Browser methods

<a name="isMetaMaskFlask"></a>
## `browser.isMetaMaskFlask(): boolean`
returns if browser runs MetaMask flask version

<a name="pages"></a>
## `browser.pages(): Promise<DappeteerPage<Page>[]>`
returns list of open DappeteerPages

<a name="newPage"></a>
## `browser.newPage(): Promise<DappeteerPage<Page>>`
open new blank page in browser and return DappeteerPage of it

<a name="getSource"></a>
## `browser.getSource(): Browser`
returns underlying browser instance of browser runner

<a name="close"></a>
## `browser.close(): Promise<void>`
it closes browser and clears temporary data

<a name="wsEndpoint"></a>
## `browser.wsEndpoint(): string`
returns web socket address

<a name="getUserDataDirPath"></a>
## `browser.getUserDataDirPath(): string`
return path of temporary dir of browsers user data

<a name="storeUserData"></a>
## `browser.storeUserData(destination: string): boolean`
copy current user data on desired destination

<a name="advanced"></a>
# Advanced usages

<a name="storeAndRun"></a>
## Storing and lunching browser from specific state
In situations when you want to skip setup or have state to fallback.
For an example, you can look at [`test/userData.spec.ts`](../test/userData.spec.ts)

<a name="storeAndRun-storing"></a>
### Storing state
There is few approach's storing a state.
Best one is with help with using browser method [`browser.storeUserData`](#storeUserData).

```js
import dappeteer from "@chainsafe/dappeteer";

async function store() {
const { metaMask, browser } = await dappeteer.bootstrap();

const dappPage = browser.newPage();
await dappPage.goto("https://chainsafe.io/");

// add custom network to a MetaMask
dappPage.evaluate(() => {
window.ethereum.request({
method: "wallet_addEthereumChain",
params: [
{
chainId: "0xa",
chainName: "Optimism",
nativeCurrency: {
name: "ETH",
symbol: "ETH", // 2-6 characters long
decimals: 18,
},
rpcUrls: ["https://mainnet.optimism.io"],
},
],
});
});
await metaMask.acceptAddNetwork(true);

// add custom token to a MetaMask
dappPage.evaluate(() => {
window.ethereum.request({
method: "wallet_watchAsset",
params: {
type: "ERC20",
options: {
address: "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58",
symbol: "USDT",
decimals: 18,
},
},
});
});
await metaMask.acceptAddToken();

// we are almost ready to store state
// but at first we need to give a bit of time to metamask to store state
await dappPage.waitForTimeout(1000);

// now we can store on desiered location
browser.storeUserData("./location-path");

// done!
await browser.close();
}

store();
```

<a name="storeAndRun-start"></a>
### Starting from state
For loading state from a stored configuration just need to include option `userDataDir` with path to a previously stored state.

```js
import dappeteer from "@chainsafe/dappeteer";

async function resume() {
const { metaMask, browser } = await dappeteer.bootstrap({
userDataDir: "./location-path",
});

const dappPage = browser.newPage();
await dappPage.goto("https://chainsafe.io/");

// done!
}

resume();
```

dAppeteer provides state for default `MetaMaskOptions` to get path for it, you need to use constants `DEFAULT_METAMASK_USERDATA` and for a flask
`DEFAULT_FLASK_USERDATA`.

```js
import dappeteer from "@chainsafe/dappeteer";

async function resume() {
const { metaMask, browser } = await dappeteer.bootstrap({
userDataDir: dappeteer.DEFAULT_METAMASK_USERDATA,
});

const dappPage = browser.newPage();
await dappPage.goto("https://chainsafe.io/");

// done!
}

resume();
```
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
"build": "tsc -p tsconfig.build.json",
"lint": "eslint --color --ext .ts src/ test/",
"lint:fix": "yarn run lint --fix",
"test:userData": "USER_DATA_TEST=true mocha --bail --require ts-node/register",
"test:puppeteer:userData": "AUTOMATION=puppeteer yarn run test:userData",
"test:playwright:userData": "AUTOMATION=playwright yarn run test:userData",
"test:mm": "mocha --bail --require ts-node/register --require test/global.ts",
"test:flask": "mocha --bail --require ts-node/register --require test/global_flask.ts",
"test:puppeteer:mm": "AUTOMATION=puppeteer yarn run test:mm",
Expand Down Expand Up @@ -43,6 +46,7 @@
"license": "MIT",
"dependencies": {
"@metamask/providers": "^9.1.0",
"fs-extra": "^11.1.0",
"node-stream-zip": "^1.13.0",
"serve-handler": "5.0.8",
"strict-event-emitter": "^0.2.8"
Expand All @@ -56,6 +60,7 @@
"@rushstack/eslint-patch": "^1.2.0",
"@types/chai": "^4.2.22",
"@types/chai-as-promised": "^7.1.5",
"@types/fs-extra": "^9.0.13",
"@types/mocha": "^9.1.1",
"@types/serve-handler": "^6.1.1",
"chai": "^4.3.4",
Expand Down
4 changes: 4 additions & 0 deletions src/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@ export interface DappeteerBrowser<Browser = unknown, Page = unknown>
close(): Promise<void>;

wsEndpoint(): string;

getUserDataDirPath(): string;

storeUserData(destination: string): boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

How are we using the boolean returned from the method?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It returns true if success false if fails somewhere
For future proofing and for users

Copy link
Contributor

Choose a reason for hiding this comment

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

Is it better to throw the error if the data is not stored? Can we assume the case that we can continue even if the method is failed?

}
14 changes: 14 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
import path from "path";
import { getDappateerPath } from "./helpers/utils";

export const EXAMPLE_WEBSITE = "http://example.org";

export const RECOMMENDED_METAMASK_VERSION = "v10.23.0";

export const DEFAULT_METAMASK_USERDATA = path.join(
getDappateerPath(),
"userData/chrome-mm"
mpetrunic marked this conversation as resolved.
Show resolved Hide resolved
);
export const DEFAULT_FLASK_USERDATA = path.join(
getDappateerPath(),
"userData/chrome-flask"
mpetrunic marked this conversation as resolved.
Show resolved Hide resolved
);
22 changes: 22 additions & 0 deletions src/helpers/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import path from "path";
import { copySync } from "fs-extra";

export async function retry<R>(
fn: () => Promise<R>,
count: number
Expand All @@ -12,3 +15,22 @@ export async function retry<R>(
}
throw error;
}

export function getDappateerPath(): string {
try {
return path.dirname(require.resolve("@chainsafe/dappeteer/package.json"));
} catch {
return path.resolve();
}
}

// blacklisted words for copy
const copyUserDataFilesExclude = ["LOCK", "Cache", "SingletonLock"];
export function copyUserDataFiles(from: string, to: string): void {
copySync(path.resolve(from), to, {
overwrite: true,
recursive: true,
filter: (src) =>
!copyUserDataFilesExclude.some((word) => src.includes(word)),
});
}
14 changes: 12 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,18 @@ export {
export { DappeteerBrowser } from "./browser";
export { DappeteerPage } from "./page";
export { DappeteerElementHandle } from "./element";
export { bootstrap, initSnapEnv, launch, setupMetaMask } from "./setup";
export {
bootstrap,
initSnapEnv,
launch,
setupMetaMask,
setupBootstrappedMetaMask,
} from "./setup";
export { DapeteerJestConfig } from "./jest/global";

// default constants
export const RECOMMENDED_METAMASK_VERSION = "v10.23.0";
export {
RECOMMENDED_METAMASK_VERSION,
DEFAULT_METAMASK_USERDATA,
DEFAULT_FLASK_USERDATA,
} from "./constants";
Loading