Blog

Extending Synpress with additional MetaMask commands

Written by Max Hoheiser | 01. April 2022

A guide on how to find and define new selectors for Puppeteer and add custom commands for Cypress to Synpress.

TL;DR: We need to find the selector for the MetaMask button, add it to pages.metamask.<page>.js, add a custom command using the new selector to commands.metamask.js, and add the new command as a cypress command to plugins.index.js and support.commands.js.

 

© Girl with red hat on Unsplash

Synpress: End-to-end frontend testing of Web3 dApps with MetaMask plugin

Synpress is an extension of the popular end-to-end testing framework Cypress that covers the unique requirements of testing Web3 dApps. It adds the missing part of supporting browser plugins such as MetaMask to Cypress, the popular frontend testing tool. It does so by wrapping and extending it with the help of Puppeteer. It runs a global before routine, that installs a selected MetaMask plugin, and configures it. It also extends Cypress with custom commands to interact with MetaMask.

We have outlined in our previous post, how to set up and configure Synpress for your Web3 dApp.

Synpress already supports most of the necessary MetaMask commands such as accepting/rejecting access, as well as accepting/rejecting transactions, but not all commands are supported. In this guide, we are going to show how to customize Synpress and extend it with additional MetaMask commands. If you are only interested in the practical how-to part, you can jump ahead to: How to extend Synpress with additional MetaMask commands

Banity dApp and why we needed to extend Synpress

Over the last months, we at &amp developed BANITY — the first NFT collection of vanity bitcoin addresses for decom. The details about the project are outlined in this whitepaper: https://medium.com/@banity/banity-whitepaper-d335e27e0a5b . The dApp app.banity.com is based on React and connects to our smart contract on the Ethereum blockchain by using ethers.js. NFT Metadata is stored decentralized using the IPFS protocol and filecoin. Authentication of the user is handled via MetaMask.

To handle private data securely in our dApp, we use the private/public key pairs of the connected MetaMask wallets. Therefore, messages can only be decrypted by the intended recipient. A typical user flow looks something like this:

  1. Connect via MetaMask
  2. Fetch encrypted message
  3. Decrypt the message

Example of Banity dApp to decrypt a message

To decrypt and encrypt sensitive private data, we need the public encryption key and the decrypt message functionality of MetaMask. Both of them are currently not supported with Synpress version 1.1.1. Therefore we needed to extend it to include both of them, and we will also use the process as a guide on how to extend any MetaMask command.

How Synpress works using Puppeteer

Synpress uses Puppeteer, a Node.js library that provides API control of any Chromium-based web browser. To select buttons and interact with them, it uses standard DOM query selectors.

Required elements are defined on a per-page basis, such as the main MetaMask account page, the notification pop-up window, or the unlock page.

 

Example notification-page.js

Each flow is defined as an async function handling the interaction with MetaMask through Puppeteer.

 

Example of a function handling Puppeteer interaction

How to extend Synpress with additional MetaMask commands

To add new MetaMask functionality as a cy.<command> we have to find the correct selectors for all the interactions in the workflow, add them to an existing page definition or create a new one, define a new function to handle Puppeteer interaction, and add a new custom cypress command.

The complete pull request adding the custom MetaMask command, outlined below, can be found here: Encrypt, Decrypt.

1. Find the necessary selectors:

We can use the Chrome developer tools to find the selector. We don't have to use the complete class, but a unique part is sufficient. To test whether we have the correct selector, we can use a querySelector(selector) in the DOM via the console of the developer tools.

 

Example query selector

The selector for the confirm button, to provide a public encryption key in our case is:

request-encryption-public-key__footer__sign-button

2. Extend or create page definition:

With the new selector at hand, we can now create the necessary selection elements. In most cases, this will be in either the notification-page.js responsible for handling the MetaMask notification pop-up page, or the main-page.js.

In the case of our example, we are going to extend the notification-page.js with both the accept and reject button for the request to provide the public encryption key.

 

Accept and reject button in notification-page.js

3. Define a new function to handle Puppeteer interaction:

To interact with the page elements we created, we are going to define (or depending on your situation just extend an existing) function. To click on a button with Puppeteer, we just need the method waitAndclick().

Synpress defines all commands, that particularly relate to MetaMask functionality in the file commands/metamask.js

 

Example of functions using Puppeteer to interact with page elements

4. Add new custom cypress command:

The above-written function relies on a node.js environment for its execution and Cypress provides exactly that functionality via plugins.

 

Example addition to plugins/index.js

Since we want to use the new functionality in the end as a cy. command, we have to define a new Cypress command, using the created plugin functionality. New custom commands are defined in support/index.js like so:

 
 

Example of custom Cypress function

🚀 Now we can use our new cypress command in our test files!
 

To enable TypeScript type definition for our new extended functionality of Synpress, we can add it to support/index.d.js

 

Example type definition for new Cypress custom command

Writing tests using our new custom commands

These are the two new MetaMask functions we needed for our dApp:

Provide the public encryption key:

Example provide public encryption key

 

Example burn-token test execution

Decrypt signed message:

Decrypt signed message example

Example decrypt signed message test execution

If you have any questions, don't hesitate to contact me: