How to set up Synpress for Web3 dApp Frontend Test Automation with MetaMask
Setup, configuration, and example tests using Synpress / Cypress for end-to-end frontend test automation of a Web3 dApp with MetaMask login.
TL;DR: Add Synpress as a dependency to your project, create the default Cypress folder structure and a
synpress.json
config file. Run your tests withenv-cmd -f .env npx synpress run -cf synpress.json --config supportFile='support/index.js'
![](https://miro.medium.com/v2/resize:fit:700/1*UFkzEEmfRMaD7WoN9njI4A.png)
Synpress github repository
End-to-end frontend testing of a Web3 dApp presents unique challenges and sometimes requires special tooling compared to regular web apps. A particular problem presents the use of MetaMask as a means for authentication through the wallet address of users. Existing standard tooling does not support browser plugins, and therefore is inadequate as a fully automated end-to-end frontend testing suite.
This article will focus on the use of Synpress, a wrapper for the popular frontend testing framework Cypress. We will cover how to set our project up with Synpress, how to configure it, use the extended commands and how to write tests. MetaMask will not only serve as a means of authentication but also for interacting with a smart contract.
This guide is heavily influenced by the README
of the Synpress project.
Set up
We will follow the recommended folder structure from Cypress: example.
![](https://miro.medium.com/v2/resize:fit:700/1*25063p5izCldkxRlx_bAKw.png)
Default Cypress folder structure
The next step is to create a package.json
file to install the Synpress package and the necessary dev dependencies. Most of the dependencies are already taken care of by the Synpress package.json
, like cypress, @testing-library/cypress
and the necessary eslint-plugin
packages. If we want to use a later version of Cypress or any other package, we can also overrule the version defined by Synpress here.
Configuration of Synpress
The necessary configurations for Synpress/Cypress, like the viewport, timeouts, and default folders need to be specified in thesynpress.json
file. Here we also define our baseUrl
and our test file naming syntax.
The synpress.json
file has to be included with each Synpress execution with either the--configFile synpress.json
or the-cf synpress.json
flag.
Example synpress.json
Setting up MetaMask, selecting a network, and adding one or multiple accounts can be done via environment variables.
Selecting the network is done via theNETWORK_NAME
variable. We can choose between mainnet
, ropsten
, kovan
, rinkeby
, goerli
and localhost
.
To configure the account, we have the option to either pass in a private-key with PRIVATE_KEY
or a secret passphrase of a wallet with SECRET_WORDS
Example with localhost and a secret pass-phrase (in this particular case the hardhat default pass-phrase) default accounts
Linting and code-hinting
To use linting of test files with eslint
we need to create a .eslintrc.js
config file and extend with the Synpress eslint
configuration
Writing tests for Synpress
Tests are written in either the integration
or e2e
folder and generally follow one of the two naming conventions:
<name>.spec.js
<name>.test.js
Cypress provides a good quick-start tutorial on how to write tests: writing your first test
Since we are using Synpress, we can also use the custom commands, to interact with MetaMask. All commands are described here: index.d.ts
For most use cases, one of the following commands will be sufficient:
cy.rejectMetamaskAccess()
cy.acceptMetamaskAccess()
cy.rejectMetamaskTransaction()
cy.acceptMetamaskTransaction()
Most of the Synpress custom commands are asynchronous and return a boolean response once finished.
Following the recommended Cypress test structure, a test using the new Synpress commands can look similar to our login.spec.js
test.
New custom commands
Often it is cleaner to extract frequently used functionality in our test. For example, the setup and teardown of tests can be moved into custom commands, that can be executed like built-in cypress commands like cy.commandName()
.
To define custom commands, we can use the support/commands.js
file, provided by Cypress.
Example commands.js with login custom command
To include the new commands with Synpress, we have to include them in the index.js
file. Since the functionality of Synpress is also provided by custom commands, we have to load them in the same index.js
file, credit goes to cs-balazs for describing the solution here: https://github.com/Synthetixio/synpress/issues/346#issuecomment-1060506096
To run Synpress using our newly defined index.js
file, we have to pass the supportFile
flag as configuration like so:
npx synpress run -cf synpress.json --config supportFile='support/index.js'
Helpful custom commands
Synpress does not handle multiple accounts well. This custom command can be helpful, to disconnect all accounts and to have a clean setup for each test.
Example command to disconnect three accounts from dApp
Switching between the first three connected MetaMask accounts, with an additional check of successful switch:
Example command to switch between connected accounts
Running Synpress
Synpress adds in addition to the new commands a global before() where it installs the MetaMask plugin, and sets it up, as defined in the env file.
![](https://miro.medium.com/v2/resize:fit:700/1*hrxV_1by4o04xAn0SJiWJg.gif)
Setup of MetaMask by Synpress
To run all tests following the specified naming convention, we can execute the following command:
A successful test of our dApp app.banity.com looks like this:
![](https://miro.medium.com/v2/resize:fit:700/1*CO4C-FEc18a_qFtH9-VmNg.gif)
gallery.spec.js
Disclaimer: & developed the app app.banity.com for decom.ch