dappress 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -66,7 +66,7 @@ const wallet: WalletSetup = {
66
66
  export default wallet;
67
67
  ```
68
68
 
69
- Keep the seed phrase out of the repository, in `cypress.env.json` (git-ignored):
69
+ Without a seed phrase, Dappress makes a new wallet for each run: enough to connect and sign. To test with a wallet of yours, one that holds test funds for instance, give its seed phrase and keep it out of the repository, in `cypress.env.json` (git-ignored):
70
70
 
71
71
  ```json
72
72
  {
@@ -82,7 +82,6 @@ env:
82
82
  DAPPRESS_SEED_PHRASE: ${{ secrets.DAPPRESS_SEED_PHRASE }}
83
83
  ```
84
84
 
85
- When neither is provided, Dappress uses the public Hardhat / Anvil development wallet.
86
85
 
87
86
  Add the command types to `tsconfig.json`:
88
87
 
@@ -109,7 +108,7 @@ it('connects the wallet and signs in', () => {
109
108
  cy.visit('/');
110
109
  cy.contains('button', 'Connect wallet').click();
111
110
  cy.connectToDapp();
112
- cy.getAccountAddress().should('eq', '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266');
111
+ cy.getAccountAddress().should('match', /^0x[0-9a-f]{40}$/);
113
112
 
114
113
  cy.contains('button', 'Sign in').click();
115
114
  cy.confirmSignature();
@@ -134,6 +133,9 @@ The wallet is imported once, before the first test of each spec. Each command wa
134
133
  | `cy.rejectSwitchNetwork()` | Denies that permission |
135
134
  | `cy.approveAddToken()` | Adds the token requested by `wallet_watchAsset` |
136
135
  | `cy.rejectAddToken()` | Rejects the token |
136
+ | `cy.addAccount()` | Adds an account to the wallet and selects it. Yields its name, such as `Account 2`. |
137
+ | `cy.importAccount(privateKey)` | Imports an account from its private key and selects it. Yields its name. The key stays out of the command log. |
138
+ | `cy.switchAccount(name)` | Selects an account of the wallet by its name |
137
139
  | `cy.useNetwork(network?)` | Moves the dapp onto a network, adding it to MetaMask when needed. Defaults to the network of the wallet setup. |
138
140
  | `cy.getAccountAddress()` | Yields the address the dapp is connected with |
139
141
  | `cy.setupMetaMask()` | Imports the wallet, or unlocks it. Called automatically before each spec. |
@@ -150,22 +152,23 @@ Settings normally live in `cypress/wallet.setup.ts`. Secrets go in `cypress.env.
150
152
  | Option | Environment variable | Default |
151
153
  |---|---|---|
152
154
  | `metamaskVersion` | `DAPPRESS_METAMASK_VERSION` | `13.50.0`. The build is downloaded from MetaMask's GitHub releases on first run and cached in `~/.cache/dappress`. |
153
- | `seedPhrase` | `DAPPRESS_SEED_PHRASE` | `test test test test test test test test test test test junk`, the Hardhat / Anvil development wallet. Its first account is `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266`. |
155
+ | `seedPhrase` | `DAPPRESS_SEED_PHRASE` | None. Dappress makes a new wallet for each run, which nobody else knows and which is never written anywhere. |
154
156
  | `password` | `DAPPRESS_PASSWORD` | `Tester@1234`. It only protects the throwaway browser profile Cypress creates for each run. |
155
157
  | `network` | | None. The dapp stays on the network MetaMask starts on, Ethereum mainnet. |
158
+ | `backupAndSync` | | `false`. Dappress turns off MetaMask's backup and sync while importing the wallet, so that an account a test adds is not restored by the next import of the same seed phrase. With `true`, MetaMask keeps saving and restoring the accounts and contacts of that phrase. |
156
159
  | `autoSetup` | | `true`. Dappress imports or unlocks the wallet before the first test of each spec. Set to `false` to call `cy.setupMetaMask()` yourself. |
157
- | `cache` | | `false`. The wallet is imported in every run, about fifteen seconds. With `true`, it is imported once, in a browser Dappress opens before the run, and the resulting profile is reused by later runs, which then start by unlocking the wallet. |
160
+ | `cache` | | `false`. The wallet is imported in every run, about fifteen seconds. With `true`, and a seed phrase of your own, it is imported once, in a browser Dappress opens before the run, and the resulting profile is reused by later runs, which then start by unlocking the wallet. |
158
161
  | `timeout` | | `20000` ms. The time allowed for MetaMask to display a request before a command fails. |
159
162
 
160
163
  ### Security
161
164
 
162
- Use a wallet dedicated to testing, funded on test networks only. The default seed phrase is public. The seed phrase and password remain on the Node.js side: they are never exposed to the browser nor written to the Cypress command log. The wallet setup file contains no secret and can be committed.
165
+ When you give a seed phrase, make it one dedicated to testing, funded on test networks only. Avoid a phrase other people know, such as the Hardhat or Anvil development mnemonic: MetaMask restores the accounts others saved for it, so the wallet is not the one you expect. The seed phrase and password remain on the Node.js side: they are never exposed to the browser nor written to the Cypress command log. The wallet setup file contains no secret and can be committed.
163
166
 
164
167
  With `cache: true`, the profile under `~/.cache/dappress/profiles` holds the wallet's vault, encrypted by MetaMask with the password. Treat that directory like the seed phrase: keep it on the machine, and do not store it in a CI cache that other people or workflows can restore.
165
168
 
166
169
  ## Conformance suite
167
170
 
168
- The suite runs one test per command against [MetaMask's test dapp](https://metamask.github.io/test-dapp/), with a local [Anvil](https://getfoundry.sh) node for the funded transaction. It requires Chrome for Testing and Foundry.
171
+ The suite runs one test per command against [MetaMask's test dapp](https://metamask.github.io/test-dapp/). Each run makes a wallet nobody has used, with Foundry's `cast`, and starts a local [Anvil](https://getfoundry.sh) node that funds it. It requires Chrome for Testing and Foundry.
169
172
 
170
173
  ```bash
171
174
  npm run conformance # default MetaMask version
@@ -209,10 +212,12 @@ Selectors are MetaMask's `data-testid` attributes, each with the button's Englis
209
212
 
210
213
  - **Cypress exits immediately with `MODULE_NOT_FOUND`.** The terminal sets `ELECTRON_RUN_AS_NODE=1`, as some IDEs do. Run `env -u ELECTRON_RUN_AS_NODE npx cypress run …`.
211
214
  - **"MetaMask showed no confirmation".** The dapp sent no request, or sent it on a network whose RPC endpoint is unreachable.
215
+ - **Adding or importing an account never finishes.** `chromeWebSecurity: false` is set in the Cypress config. MetaMask then cannot start its snaps, which its account screens wait for. Leave Chrome's web security on, the Cypress default.
216
+ - **The wallet shows accounts you did not create.** The seed phrase is used elsewhere, and MetaMask restored what its cloud holds for it. This happens with well-known development mnemonics. Use a phrase made for your tests, or none.
212
217
 
213
218
  ## Status
214
219
 
215
- Verified with MetaMask 13.49.0 and 13.50.0, Cypress 16 and Chrome for Testing 154 on macOS, in headed mode. Headless execution and continuous integration are not validated yet.
220
+ Verified with MetaMask 13.49.0 and 13.50.0, Cypress 16 and Chrome for Testing 154, in headed mode, on macOS and on GitHub's Linux runners. Headless execution is not validated yet.
216
221
 
217
222
  ## License
218
223
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dappress",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "MetaMask automation for Cypress: load the real extension, import a test wallet, answer dapp requests with cy.* commands. Tracks the latest MetaMask release.",
5
5
  "license": "MIT",
6
6
  "author": "Benjamin Lassaut",
@@ -57,6 +57,7 @@
57
57
  "cypress": ">=13.6.0"
58
58
  },
59
59
  "dependencies": {
60
+ "@scure/bip39": "^1.6.0",
60
61
  "extract-zip": "^2.0.1",
61
62
  "puppeteer-core": "^24.43.1"
62
63
  },
package/src/actions.js CHANGED
@@ -27,6 +27,17 @@ function createTasks(options) {
27
27
  return state;
28
28
  }
29
29
 
30
+ /** A task that drives the wallet's own screens, from its full-screen page. */
31
+ const onHomePage = (flow) => async (browser, argument) => {
32
+ const home = await getHomePage(browser, await metamaskId(browser));
33
+ try {
34
+ await home.bringToFront();
35
+ return await flow(home, argument);
36
+ } finally {
37
+ await home.close();
38
+ }
39
+ };
40
+
30
41
  /** Approve the prompt a network change raised: "Add network" or the permission to switch. */
31
42
  async function approveNetworkChange(browser) {
32
43
  const page = await getConfirmationPage(browser, await metamaskId(browser), options.timeout);
@@ -39,7 +50,13 @@ function createTasks(options) {
39
50
  await metamask.decide(decision, page, options.timeout);
40
51
  };
41
52
 
42
- const actions = { setupWallet, approveNetworkChange };
53
+ const actions = {
54
+ setupWallet,
55
+ approveNetworkChange,
56
+ addAccount: onHomePage(metamask.addAccount),
57
+ switchAccount: onHomePage(metamask.switchAccount),
58
+ importAccount: onHomePage(metamask.importAccount),
59
+ };
43
60
  for (const decision of Object.keys(metamask.decisions)) actions[decision] = decide(decision);
44
61
 
45
62
  // cy.task() needs a value back: null when the action has nothing to say
package/src/config.js CHANGED
@@ -1,14 +1,20 @@
1
1
  const fs = require('node:fs');
2
2
  const os = require('node:os');
3
3
  const path = require('node:path');
4
+ const { generateMnemonic } = require('@scure/bip39');
5
+ const { wordlist } = require('@scure/bip39/wordlists/english');
4
6
 
5
7
  const DEFAULTS = {
6
8
  metamaskVersion: '13.50.0',
7
- // The Hardhat / Anvil development mnemonic. Account 0 is 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266.
8
- seedPhrase: 'test test test test test test test test test test test junk',
9
+ // None: a new wallet is made for each run. A phrase known to others is not
10
+ // blank, since MetaMask restores the accounts saved for it elsewhere.
11
+ seedPhrase: null,
9
12
  password: 'Tester@1234',
10
13
  // The network cy.connectToDapp() moves the dapp onto, as a wallet_addEthereumChain parameter; none by default
11
14
  network: null,
15
+ // MetaMask's backup and sync saves the accounts and contacts of a seed phrase
16
+ // and restores them on other installs. Off, a test's accounts don't come back.
17
+ backupAndSync: false,
12
18
  // Run cy.setupMetaMask() automatically before the first test of each spec
13
19
  autoSetup: true,
14
20
  // Import the wallet once and reuse the profile across runs (keeps the vault under cacheDir)
@@ -39,9 +45,20 @@ function resolveOptions(userOptions = {}, cypressConfig = {}) {
39
45
  for (const [key, value] of Object.entries(fromEnv)) {
40
46
  if (value) options[key] = value;
41
47
  }
48
+ if (!options.seedPhrase) useNewWallet(options);
42
49
  return options;
43
50
  }
44
51
 
52
+ /** No seed phrase was given: make one for this run. It is never written anywhere. */
53
+ function useNewWallet(options) {
54
+ options.seedPhrase = generateMnemonic(wordlist);
55
+ console.log('[dappress] No seed phrase configured: using a new wallet for this run');
56
+ if (options.cache) {
57
+ console.warn('[dappress] The profile cache needs a seed phrase of your own: importing the wallet in this run instead');
58
+ options.cache = false;
59
+ }
60
+ }
61
+
45
62
  /** The project's wallet setup file: { seedPhrase?, network? }. */
46
63
  function loadWalletSetup(projectRoot = process.cwd()) {
47
64
  const file = WALLET_SETUP_FILES.map((name) => path.join(projectRoot, name)).find((candidate) => fs.existsSync(candidate));
package/src/index.js CHANGED
@@ -16,6 +16,9 @@ const { createTasks } = require('./actions');
16
16
  */
17
17
  function configureDappress(on, config, userOptions = {}) {
18
18
  const options = resolveOptions(userOptions, config);
19
+ if (config.chromeWebSecurity === false) {
20
+ console.warn('[dappress] chromeWebSecurity is off: MetaMask cannot start its snaps, so adding or importing an account will hang');
21
+ }
19
22
 
20
23
  // Build the wallet profile before the run, out of the time Cypress allows a browser to come up
21
24
  on('before:run', async ({ browser }) => {
package/src/metamask.js CHANGED
@@ -30,6 +30,13 @@ const selectors = {
30
30
  metricsContinue: [testId('metametrics-i-agree'), buttonText('Continue')],
31
31
  downloadAppContinue: testId('download-app-continue'),
32
32
  done: [testId('onboarding-complete-done'), buttonText('Done')],
33
+ // "Manage default settings", offered next to "Done"
34
+ manageDefaultSettings: testId('manage-default-settings'),
35
+ // The first category, "General": its testid carries the translated label
36
+ generalSettings: 'xpath/(.//*[starts-with(@data-testid, "category-item-")])[1]',
37
+ backupAndSyncToggle: testId('backup-and-sync-toggle-button'),
38
+ categoryBack: testId('category-back-button'),
39
+ settingsBack: testId('privacy-settings-back-button'),
33
40
  },
34
41
  unlock: {
35
42
  password: [testId('unlock-password'), passwordInput(1)],
@@ -37,6 +44,18 @@ const selectors = {
37
44
  },
38
45
  home: {
39
46
  header: testId('parent-selector-header-navbar'),
47
+ accountMenu: testId('account-menu-icon'),
48
+ },
49
+ // The account list, opened from the home header
50
+ accounts: {
51
+ name: '[data-testid^="multichain-account-cell-name-"]',
52
+ cell: (name) => `xpath/.//*[contains(@class, "multichain-account-cell")][.//*[@data-testid="multichain-account-cell-name-${name}"]]`,
53
+ add: 'xpath/(.//*[@data-testid="add-multichain-account-button"])[1]',
54
+ addWallet: testId('account-list-add-wallet-button'),
55
+ importAccount: testId('choose-wallet-type-import-account'),
56
+ privateKey: '#private-key-box',
57
+ importConfirm: [testId('import-account-confirm-button'), buttonText('Import')],
58
+ back: testId('account-list-page-back-button'),
40
59
  },
41
60
  // "Connect this website with MetaMask"
42
61
  connect: {
@@ -98,7 +117,7 @@ async function walletState(page) {
98
117
  }
99
118
 
100
119
  /** Import a wallet from its seed phrase on a fresh MetaMask install. */
101
- async function onboard(page, { seedPhrase, password }) {
120
+ async function onboard(page, { seedPhrase, password, backupAndSync }) {
102
121
  const s = selectors.onboarding;
103
122
  await click(page, s.importWallet, { timeout: 30000 });
104
123
  await click(page, s.importWithSrp);
@@ -111,7 +130,7 @@ async function onboard(page, { seedPhrase, password }) {
111
130
  await click(page, s.passwordTerms);
112
131
  await clickWhenEnabled(page, s.passwordSubmit);
113
132
 
114
- await reachHome(page, { password });
133
+ await reachHome(page, { password, backupAndSync });
115
134
  }
116
135
 
117
136
  // MetaMask's sandbox (LavaMoat) blocks a scripted paste, so the phrase is
@@ -139,9 +158,10 @@ async function unlock(page, { password }) {
139
158
  * "Done", MetaMask takes a moment to record the onboarding as complete and
140
159
  * redirects to it until then, so the home page is reloaded.
141
160
  */
142
- async function reachHome(page, { password }) {
161
+ async function reachHome(page, { password, backupAndSync }) {
143
162
  const s = selectors.onboarding;
144
163
  const deadline = Date.now() + 90000;
164
+ let syncTurnedOff = false;
145
165
  while (Date.now() < deadline) {
146
166
  if (await isVisible(page, selectors.home.header, 500)) return;
147
167
  if (await isVisible(page, s.passkeyMaybeLater, 500)) await click(page, s.passkeyMaybeLater);
@@ -150,6 +170,10 @@ async function reachHome(page, { password }) {
150
170
  await click(page, s.metricsContinue);
151
171
  }
152
172
  if (await isVisible(page, s.downloadAppContinue, 500)) await click(page, s.downloadAppContinue);
173
+ if (backupAndSync === false && !syncTurnedOff && (await isVisible(page, s.manageDefaultSettings, 500))) {
174
+ await turnOffBackupAndSync(page);
175
+ syncTurnedOff = true;
176
+ }
153
177
  if (await isVisible(page, s.done, 500)) {
154
178
  await click(page, s.done);
155
179
  await sleep(1000);
@@ -163,6 +187,19 @@ async function reachHome(page, { password }) {
163
187
  throw await failure(page, 'MetaMask did not show the wallet');
164
188
  }
165
189
 
190
+ // Backup and sync restores, for a seed phrase, the accounts and contacts saved
191
+ // from other installs. Off, every import starts from the same state.
192
+ async function turnOffBackupAndSync(page) {
193
+ const s = selectors.onboarding;
194
+ await click(page, s.manageDefaultSettings);
195
+ await click(page, s.generalSettings);
196
+ const toggle = await waitFor(page, s.backupAndSyncToggle);
197
+ const on = await toggle.evaluate((el) => el.closest('.toggle-button')?.classList.contains('toggle-button--on') ?? el.className.includes('--on'));
198
+ if (on) await toggle.click();
199
+ await click(page, s.categoryBack);
200
+ await click(page, s.settingsBack);
201
+ }
202
+
166
203
  async function optOutOfMetrics(page) {
167
204
  const checkbox = await waitFor(page, selectors.onboarding.metricsCheckbox);
168
205
  const checked = await checkbox.evaluate((el) => el.getAttribute('data-checked') === 'true');
@@ -177,6 +214,86 @@ async function reloadHome(page) {
177
214
  await page.goto(homeUrl);
178
215
  }
179
216
 
217
+ /**
218
+ * Add an account to the wallet and select it. Yields its name, "Account N".
219
+ * A click that lands while the list is still settling is lost, so the button
220
+ * is pressed again when no account shows up.
221
+ */
222
+ async function addAccount(page) {
223
+ await click(page, selectors.home.accountMenu);
224
+ const before = await accountNames(page);
225
+ for (let attempt = 0; attempt < 3; attempt++) {
226
+ const added = (await newAccount(page, before, 0)) || (await pressAddAccount(page, before));
227
+ if (added) {
228
+ await selectAccount(page, added);
229
+ return added;
230
+ }
231
+ }
232
+ throw await failure(page, 'MetaMask added no account');
233
+ }
234
+
235
+ async function pressAddAccount(page, before) {
236
+ await clickWhenEnabled(page, selectors.accounts.add, { timeout: 30000 });
237
+ return newAccount(page, before, 15000);
238
+ }
239
+
240
+ /** The name of an account that wasn't in `before`, within `timeout` ms. */
241
+ async function newAccount(page, before, timeout) {
242
+ const deadline = Date.now() + timeout;
243
+ do {
244
+ const [added] = (await accountNames(page)).filter((name) => !before.includes(name));
245
+ if (added) return added;
246
+ await sleep(250);
247
+ } while (Date.now() < deadline);
248
+ return null;
249
+ }
250
+
251
+ async function accountNames(page) {
252
+ await waitFor(page, selectors.accounts.name);
253
+ return page.$$eval(selectors.accounts.name, (cells) => cells.map((cell) => cell.textContent.trim()));
254
+ }
255
+
256
+ /** Make `name` the selected account of the wallet. */
257
+ async function switchAccount(page, name) {
258
+ await click(page, selectors.home.accountMenu);
259
+ await selectAccount(page, name);
260
+ }
261
+
262
+ // Picking an account closes the list and shows it in the home header
263
+ async function selectAccount(page, name) {
264
+ await click(page, selectors.accounts.cell(name));
265
+ const deadline = Date.now() + 15000;
266
+ while (Date.now() < deadline) {
267
+ if ((await selectedAccount(page)) === name) return;
268
+ await sleep(250);
269
+ }
270
+ throw await failure(page, `MetaMask did not select "${name}"`);
271
+ }
272
+
273
+ async function selectedAccount(page) {
274
+ const menu = await page.$(selectors.home.accountMenu);
275
+ return menu ? menu.evaluate((el) => el.textContent.trim()) : null;
276
+ }
277
+
278
+ /** Import an account from its private key and select it. Yields its name. */
279
+ async function importAccount(page, privateKey) {
280
+ await waitFor(page, selectors.home.accountMenu);
281
+ const before = await selectedAccount(page);
282
+ // Straight to the form the account list's "Add wallet" menu leads to
283
+ const homeUrl = page.url().split('#')[0];
284
+ await page.goto('about:blank');
285
+ await page.goto(`${homeUrl}#/add-wallet-page`);
286
+ await fill(page, selectors.accounts.privateKey, privateKey, { timeout: 30000 });
287
+ await clickWhenEnabled(page, selectors.accounts.importConfirm);
288
+ const deadline = Date.now() + 30000;
289
+ while (Date.now() < deadline) {
290
+ const selected = await selectedAccount(page);
291
+ if (selected && selected !== before) return selected;
292
+ await sleep(250);
293
+ }
294
+ throw await failure(page, 'MetaMask did not import the account');
295
+ }
296
+
180
297
  /** Press the button of `decision` on the confirmation shown on `page`. */
181
298
  function decide(decision, page, timeout) {
182
299
  return pressAndWaitForDismissal(page, decisions[decision], timeout);
@@ -246,4 +363,4 @@ async function dismissModal(page) {
246
363
  await dispatchClick(page, selectors.modal.lastButton);
247
364
  }
248
365
 
249
- module.exports = { decisions, walletState, onboard, unlock, decide, approveNetworkChange };
366
+ module.exports = { decisions, walletState, onboard, unlock, addAccount, switchAccount, importAccount, decide, approveNetworkChange };
package/src/profile.js CHANGED
@@ -16,7 +16,7 @@ const builds = new Map();
16
16
 
17
17
  /** The cached profile for these options, built on first use. */
18
18
  function prepareProfile({ browserPath, extensionDir, options }) {
19
- const key = crypto.createHash('sha256').update([options.metamaskVersion, options.seedPhrase, options.password].join('|')).digest('hex').slice(0, 16);
19
+ const key = crypto.createHash('sha256').update([options.metamaskVersion, options.seedPhrase, options.password, options.backupAndSync].join('|')).digest('hex').slice(0, 16);
20
20
  if (!builds.has(key)) builds.set(key, buildProfile(path.join(options.cacheDir, 'profiles', key), { browserPath, extensionDir, options }));
21
21
  return builds.get(key);
22
22
  }
package/src/support.js CHANGED
@@ -7,8 +7,9 @@
7
7
 
8
8
  const options = Cypress.expose('dappress') || {};
9
9
 
10
- function metamask(action, argument = null) {
11
- Cypress.log({ name: 'metamask', message: argument ? `${action} ${argument}` : action });
10
+ // `shown` is what the command log displays for the argument, if anything
11
+ function metamask(action, argument = null, shown = '') {
12
+ Cypress.log({ name: 'metamask', message: `${action} ${shown}`.trim() });
12
13
  return cy.task(`dappress:${action}`, argument, { log: false });
13
14
  }
14
15
 
@@ -29,6 +30,12 @@ Cypress.Commands.add('rejectTransaction', () => metamask('rejectTransaction'));
29
30
  Cypress.Commands.add('approveAddToken', () => metamask('approveAddToken'));
30
31
  Cypress.Commands.add('rejectAddToken', () => metamask('rejectAddToken'));
31
32
 
33
+ // The wallet's own accounts. A new or imported account becomes the selected
34
+ // one; the dapp sees it once it connects with it.
35
+ Cypress.Commands.add('addAccount', () => metamask('addAccount'));
36
+ Cypress.Commands.add('switchAccount', (name) => metamask('switchAccount', name, name));
37
+ // The private key is kept out of the command log
38
+ Cypress.Commands.add('importAccount', (privateKey) => metamask('importAccount', privateKey));
32
39
 
33
40
  // Connect, then move the dapp onto the network from the wallet setup, if there is one
34
41
  Cypress.Commands.add('connectToDapp', () => {
package/types/index.d.ts CHANGED
@@ -12,7 +12,10 @@ export interface Network {
12
12
 
13
13
  /** What cypress/wallet.setup.ts exports. */
14
14
  export interface WalletSetup {
15
- /** Prefer DAPPRESS_SEED_PHRASE in cypress.env.json or the environment: this file is usually committed. */
15
+ /**
16
+ * The wallet to import. Without one, Dappress makes a new wallet for each run.
17
+ * Prefer DAPPRESS_SEED_PHRASE in cypress.env.json or the environment: this file is usually committed.
18
+ */
16
19
  seedPhrase?: string;
17
20
  /** The network the dapp is moved onto by cy.connectToDapp(). */
18
21
  network?: Network;
@@ -23,6 +26,11 @@ export interface DappressOptions extends WalletSetup {
23
26
  password?: string;
24
27
  /** MetaMask release to load, downloaded from GitHub on first run. */
25
28
  metamaskVersion?: string;
29
+ /**
30
+ * Keep MetaMask's backup and sync on. It saves the accounts and contacts of a
31
+ * seed phrase and restores them on other installs. Default: false.
32
+ */
33
+ backupAndSync?: boolean;
26
34
  /** Run cy.setupMetaMask() before the first test of each spec. Default: true. */
27
35
  autoSetup?: boolean;
28
36
  /** Import the wallet once and reuse the profile across runs. Keeps the vault under cacheDir. Default: false. */
@@ -27,6 +27,12 @@ declare global {
27
27
  /** Answers wallet_watchAsset ("Add suggested tokens"). */
28
28
  approveAddToken(): Chainable<void>;
29
29
  rejectAddToken(): Chainable<void>;
30
+ /** Adds an account to the wallet and selects it. Yields its name, "Account N". */
31
+ addAccount(): Chainable<string>;
32
+ /** Selects an account of the wallet by its name. */
33
+ switchAccount(name: string): Chainable<void>;
34
+ /** Imports an account from its private key and selects it. Yields its name. */
35
+ importAccount(privateKey: string): Chainable<string>;
30
36
  /** Moves the dapp onto a network, adding it to MetaMask if needed. Defaults to the wallet setup's network. Yields the chain id. */
31
37
  useNetwork(network?: Network): Chainable<string>;
32
38
  /** The address the dapp is connected with. */