Your resource for web content, online publishing
and the distribution of digital products.
S M T W T F S
 
 
 
 
 
1
 
2
 
3
 
4
 
5
 
6
 
7
 
8
 
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
 
28
 
29
 
30
 

How do DApps (decentralized applications) make money?

DATE POSTED:October 4, 2024

In this comprehensive guide, you’ll learn how to implement end-to-end testing for your decentralized application using Synpress. We’ll walk through testing a MetaMask-integrated application, covering everything from setup to execution.

After this article You should be able to test all Metamask featureAfter completing this guide, you’ll be able to:
  • Create User Acceptance Testing With Product Manager ☑️
  • Test Your Dapp Frontend Components ☑️
  • Test Your Smart Contract ☑️
  • Test Blockchain Networks ☑️
  • Generate Reports And Videos For Marketing And Analysis ☑️
Table Of Content

Project Initialization

  • Forking the Ready-to-Use Template
  • Project Structure Overview
Initial Setup
  • Installing Dependencies
  • Setting Up Environment Configuration
  • Example Environment Variables

Configuration Setup

  • DApp URL Configuration
  • Cypress Configuration
  • Environment Variable Handling
  • Base URL and Video Configuration

Browser Launch Configuration

  • Setting Browser-Specific Test Execution Parameters
  • Handling Different Browser Families (Chrome, Firefox, Electron)

Plugin Configuration

  • Synpress Plugin Setup
  • ESBuild and Cucumber Preprocessor Integration

Fail-Fast Configuration

  • Optimizing Test Execution with Fail-Fast

Cypress Configuration Export

  • Exporting the Complete Cypress Configuration

Writing Test Scenarios with Cucumber

  • Structuring Test Scenarios Collaboratively with Product Owners
  • Example: Accessing the Application with MetaMask

Understanding Gherkin Syntax

  • Using Given, When, Then, And in Test Cases
  • Example: Smart Contract Deployment and Transaction Confirmation

Complete Example: MetaMask Interactions

  • MetaMask Connection and DApp Operations
  • Test Case Examples for Signing and Transaction Approvals

Conclusion

  • Next Steps and Further Reading
  • Reference to Synpress Example and Documentation

References

  • Synpress Example
  • Synpress Documentation
✍️ All code mentioned on this article is included on this GitHub repositoryProject InitializationGetting Started

Begin by forking our ready-to-use template:

git clone [repository-url]Project Structure

The cloned project follows this organized structure:

.
├── Dockerfile
├── cypress
│ ├── downloads
│ ├── report
│ │ ├── cucumber.html
│ │ ├── messages
│ │ ├── screenshots
│ │ └── video
│ │
│ ├── screenshots
│. │
│ ├── support
│ │ ├── commands.js
│ │ ├── common.steps.js
│ │ ├── e2e-synpress.js
│ │ ├── e2e.js
│ │ ├── page-objects
│ │ │ └── home.js
│ │ └── support.js
│ └── tests
│ ├── 01-basic-connection.feature
│ ├── 01-basic-connection.steps.js
│ ├── 02-signatures.feature
│ ├── 02-signatures.steps.js
│ ├── 03-network-management.feature
│ ├── 03-network-management.steps.js
│ ├── 04-token-management.feature
│ ├── 04-token-management.steps.js
│ ├── 05-transactions.feature
│ └── 05-transactions.steps.js
├── cypress.config.js
├── cypress.env.json
├── package-lock.json
├── package.json
├── synpress.config.js
└── tsconfig.jsonInitial Setup
  1. Install dependency
npm i

2. Set up environment configuration:

cp .env.example .env

The file content should be like

NETWORK_NAME='Sepolia'
CHAIN_ID=11155111
RPC_URL='https://ethereum-sepolia-rpc.publicnode.com'
SYMBOL="ETH"
IS_TESTNET=true
# Add your wallet recovery phrase - ensure the wallet has at least 0.1 ETH on Sepolia
SECRET_WORDS="" # Generate using https://iancoleman.io/bip39/

To generate a seed phrase please use this online generator. All environment variables will be used in the upcoming sections.

Configuration SetupDApp URL Configuration

Update cypress.env.json with your testing parameters:

{
"ENV_NAME": "LOCAL",
"BASE_URL": "https://metamask.github.io/test-dapp/",
"WALLET_ADDRESS": ""
}Cypress Configuration

Inside cypress.config.js We should have all configurations related to the auto-generated videos and reports.

The code below is standardized you will find it on the template and can use it with all e2e testing projects with slightly different changes.

The setupNodeEvents function is a crucial Cypress configuration component that handles event listeners, plugins, and test environment preparation. Let's break down its implementation step by step:

// Inside the cypress.config.js
async function setupNodeEvents(on, config) {
// Code goes here
};Environment Variable Handling

First, create a utility function to manage environment variables:

async function setupNodeEvents(on, config) {
const getEnvValue = (key) => {
if (process.env[key] !== undefined) {
return process.env[key];
} else {
return config.env[key];
}
};Base URL and Video Configuration

Set up the test URL and configure video recording settings:

// After the getEnvValue function
const baseURL = getEnvValue("CYPRESS_BASE_URL") || config.env.BASE_URL;
config.env.BASE_URL = baseURL;
console.log(`The e2e tests will be executed at the URL: ${baseURL}`);

config.video = Boolean(getEnvValue("CYPRESS_VIDEO_ENABLED"));
config.videoCompression = Number(getEnvValue("CYPRESS_VIDEO_COMPRESSION"));

await addCucumberPreprocessorPlugin(on, config);

// The rest of the code...Browser Launch Configuration

Implement browser-specific settings for test execution:

// After setting addCucumberPreprocessorPlugin

on("before:browser:launch", (browser = {}, launchOptions, config) => {
if (!browser.isHeadless) return;

// the browser width and height we want to get
// our screenshots and videos will be of that resolution
const width = 1920;
const height = 1080;

launchOptions.args.push("--lang=en-US");
launchOptions.args.push("--force-lang=en-US");

if (browser.name === "chrome" && browser.isHeadless) {
launchOptions.args.push(`--window-size=${width},${height}`);

// force screen to be non-retina and just use our given resolution
launchOptions.args.push("--force-device-scale-factor=1");
}

if (browser.family === "chromium" && browser.name !== "electron") {
// Assicurati di sostituire 'C:\\Path\\To\\Your\\Chrome\\Profile' con il percorso reale
launchOptions.args.push(
"--user-data-dir=C:\\Path\\To\\Your\\Chrome\\Profile"
);
launchOptions.args.push("--profile-directory=Cypress");
launchOptions.args.push("--lang=en-US");
launchOptions.args.push("--no-first-run");
launchOptions.args.push("--no-default-browser-check");
launchOptions.args.push("--disable-features=PromptOnMultipleDownload");
}

if (browser.name === "electron" && browser.isHeadless) {
// might not work on CI for some reason
launchOptions.preferences.width = width;
launchOptions.preferences.height = height;
}

if (browser.name === "firefox" && browser.isHeadless) {
launchOptions.args.push(`--width=${width}`);
launchOptions.args.push(`--height=${height}`);
}

// IMPORTANT: return the updated browser launch options
return launchOptions;
});
Plugin Configuration

Set up necessary plugins and preprocessor:

synpressPlugins(on, config);

const esbuildPlugin = require("@badeball/cypress-cucumber-preprocessor/esbuild");
const createBundler = require("@bahmutov/cypress-esbuild-preprocessor");

on(
"file:preprocessor",
createBundler({
plugins: [esbuildPlugin.createEsbuildPlugin(config)],
})
);Fail-Fast Configuration

Implement fail-fast functionality to optimize test execution:

on("task", {
failFastResetSkip() {
setTimeout(() => {
console.log("Timeout reached, reset complete.");
}, 10000);
console.log("Resetting fail-fast skip status");
return null;
},
});

return config;Cypress Configuration Export

Finally, export the complete configuration:

module.exports = defineConfig({
viewportWidth: 1920,
viewportHeight: 1080,
defaultCommandTimeout: 120000,
requestTimeout: 120000,
responseTimeout: 120000,
e2e: {
setupNodeEvents,
video: false,
videoCompression: 32,
videosFolder: "cypress/report/video",
screenshotsFolder: "cypress/report/screenshots",
specPattern: ["**/*.feature", "cypress/tests/**/*.cy.{js,jsx,ts,tsx}"],
scrollBehavior: "nearest",
supportFile: "cypress/support/e2e-synpress.js",
},
env: {
TAGS: "not @ignore",
BASE_URL: "https://metamask.github.io/test-dapp",
CYPRESS_VIDEO_ENABLED: true,
CYPRESS_VIDEO_COMPRESSION: 32,
},
browser: {
name: "chrome",
arguments: ["--lang=en-US"],
},
synpress: {
walletConnect: {
bridge: "https://bridge.walletconnect.org",
qrcodeModal: true,
},
},
});Writing Test Scenarios with Cucumber

Cucumber test scenarios should be written collaboratively with product owners to ensure comprehensive test coverage. Here’s an example structure:

# cypress/tests/01-app-access.feature
Feature: The application works only with the Sepolia network

Scenario: The user accesses the page with Metamask connected to Sepolia network
Given A user with metamask installed connected to sepolia network
When the user accesses the app page
And the user accepts notifications
Then the page shows the account address
And the page shows the input address field
And the page doesn't show a network error messageUnderstanding Gherkin KeywordsGiven
  • Sets up initial context or preconditions
  • Example:
Given('the user clicks on the "Use MetaMask" button', () => {
cy.contains("button", "Use MetaMask").click();
});When
  • Specifies user or system actions
  • Example:
When("the user do a smart contract deployment", () => {
cy.get("#createToken").click();
cy.wait(10000);
cy.confirmMetamaskTransaction({
shouldWaitForPopupClosure: true,
gasConfig: {
gasLimit: 210000,
baseFee: 100,
priorityFee: 10,
},
})
.then((txData) => {
expect(txData.networkName).to.be.not.empty;
expect(txData.customNonce).to.be.not.empty;
expect(txData.confirmed).to.be.true;
})
.wait(15000);
});Then
  • Describes expected outcomes
  • Example:
Then("the transaction should be confirmed successfully", () => {
cy.get("@txConfirmed").should("be.true");
});And
  • Chains multiple steps
  • Example:
Scenario: Connect to dapp and perform various operations
Given A user with metamask installed connected to sepolia network
And the user accesses the app page
Then the user clicks on the "Use MetaMask" button
And the user connects to the dappComplete Example: MetaMask Interactionsimport { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor';

When('the user accesses the app page', () => {
cy.visit('/');
});

Then('the user clicks on the "Use MetaMask" button', () => {
cy.contains("button", "Use MetaMask").click();
});

When("the user connects to the dapp", () => {
cy.get("#connectButton").click();
cy.acceptMetamaskAccess();
});

Then("the user should be able to perform various Metamask operations", () => {
cy.get("#getAccounts").click();
cy.get("#getAccountsResult").should(
"have.text",
Cypress.env("WALLET_ADDRESS")
);

cy.get("#personalSign").click();
cy.confirmMetamaskSignatureRequest();
cy.get("#personalSignVerify").click();
cy.get("#personalSignVerifySigUtilResult").contains(
Cypress.env("WALLET_ADDRESS")
);
});⚠️ Things to take care of when using synpress
  1. Need a fast internet connection or you will face timeout issues.
  2. If you are using a public test net, not a private Devnet remember to check the blockchain speed as they may cause timeout issues too.
  3. Synpress now have this issue when submitting a transaction, #140 to fix it downgrade less than version 3.7.2 or edit the custom nonce

For a complete implementation with additional test cases including smart contract deployment and token approvals, please refer to the repository

References

Synpress Example

Synpress Documentation

The Full Guide For End To End Testing With Your Dapp With Synpress was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.