A Step-by-Step Guide to Conducting Browser Checks for Web3 Apps using GuardianUI

Lipman
GuardianUI
Published in
8 min readMar 8, 2023

End-to-end testing is an essential part of web development, particularly when it comes to web3 applications that utilize smart contracts. While many teams are at least familiar with unit, integration, and end-to-end testing during the development pipeline, monitoring your deployed frontend is often overlooked. However, without it, you can’t be sure it’s safe for users to interact with your live app.

Here are just a few examples of the types of things developers should be concerned about with their frontends:

  • BadgerDAO users had $120M drained due to a malicious approval request from a UI exploit.
  • Curve Finance was hit with a DNS related attack resulting in $700k of lost user funds.
  • Users lost over $3M due to a supply chain attack on Miso (Sushiswap).
  • Nearly all cloud environments are vulnerable to Log4J attacks (and the FTC could take legal action against companies that don’t patch it).
  • Javascript injection attacks allow for arbitrary dynamic code execution and user session hijacking.
  • Using open source libraries leaves your app vulnerable. For example, the developer behind popular open-source NPM libraries ‘colors’ (aka colors.js on GitHub) and ‘faker’ (aka faker.js on GitHub) intentionally introduced mischievous commits that impacted thousands of applications relying on these libraries. This broke thegraph protocol’s NPMs which affected many web3 apps depending on thegraph.

The role of browser checks

GuardianUI makes it super simple for you to test and monitor your live app by utilizing browser checks that test your frontend’s contract interactions. If these browser checks pass, then you’re all good. If they fail, you either have bad code in production or your site is under attack. Either way, GuardianUI notifies your developers immediately so they can address the root issue.

DNS attacks, DDoS, npm injectionsExamples of why monitoring can make a difference and what kind of problems are often overlooked include

A browser check is a process in web development where a script is used to automate user behavior on a web page and typically involves controlling a headless Chromium browser using a Node.js script to simulate user interactions. Testing frameworks and browser check platforms such as Datadog, Cypress, and Checkly are widely used in web2, but no such solution has existed for web3’s unique use cases.

In web3, we need to verify that the smart contract interactions are correct, such as creating a swap or approval transaction and other actions that users might perform with their wallets in your app.

GuardianUI has extended the Playwright testing framework to handle web3 interactions and ensure that transactions are pointing to the correct contracts and approvals are giving the correct addresses access to user funds. We combine automated interactions and assertions to provide developers with confidence that their app works as expected.

Dissecting a browser check with GuardianUI

Let’s break down a browser check step-by-step with an example of an ETH <> BAL swap on Uniswap.

This E2E test will perform the following:

  1. Opens Uniswap’s app
  2. Connects a wallet to the app
  3. Selects $BAL as the input token
  4. Selects $ETH as the output token
  5. Sets the input amount to 0.1 BAL
  6. Prompts the approval notification
  7. Signs the approval notification with the wallet

The purpose of this test is to make sure the deployed Uniswap frontend creates the correct approval transaction (approving Uniswap’s v3 router to spend $BAL).

Overview

GuardianUI’s testing framework is built on top of Playwright (docs can be found here) and thus should be a familiar syntax to most developers. There are five primary phases of actions each test must go through:

  1. Setup
  2. Enter App
  3. Wallet Connection
  4. App Navigation
  5. Transaction Submission

Terminology

dappPage: You will see references to this in our example below in the app. This refers to our custom Playwright Page wrapper that handles confirming contract interactions against the app’s expected interactions. It is only used to set the target contract address and to set whether the test is focused on an approval or a general contract interaction.

Usage:

  • To set the target contract address, call dappPage.setContractAddress("0x...");
  • Ex: If you are testing an approval before a swap on Uniswap you would set the target contract address to the Permit2 address
  • Ex: If you are testing a swap on Uniswap you would set the target contract address to the UniversalRouter address

page: This is the standard Playwright Page object and should be used for all actions (navigation, clicking, entering text, etc) outside of setting the target contract address and the expected type of contract interaction

Finding Site Elements

There are four main ways to find and select elements on a site to interact with, all of which rely on the page.locator function to find the element:

Text:

  • If the text on the element you wish to identify only exists once on the current page use page.locator(text=Desired Text To Identify Here)
  • If the text on the element you wish to identify exists in multiple places and you wish to select the first use page.locator(text=Desired Text To Identify Here).first()
  • If the text on the element you wish to identify exists in multiple paces and you wish to select the nth copy use page.locator(button:has-text("Desired Text To Identify Here") >> nth=5) where button can be replaced with the relevant base HTML element type like div or p and 5 can be replace with any n value.

Classname:

  • Use page.locator("[class='name-of-class-here']")
  • Use page.locator(".name-of-class-here")

ID:

  • Use page.locator("[id='id-name-here']")
  • Use page.locator("#id-name-here")

Data Test ID:

  • Use page.locator("[data-testid='test-id-here']")

Note for non-devs: You can find class, ID, and data test ID selectors by right-clicking on the desired element in the browser and clicking “inspect” on the dropdown menu.

Interacting with Site Elements

To interact with a site element simply append the function for the action you wish to take to the element locator command from the prior section:

  • Click: await page.locator(text=Click Me).click()
  • Fill input: await page.locator("[class='input-section']").fill(100)

Phase 1: Setup

To set up a test with the GuardianUI framework there are two pieces of information you must specify:

  • What contract address should this user flow end up interacting with or approving to spend a user’s tokens?
  • Is this flow a token approval or a generic contract interaction?

Example: This would be a test that will result in a token approval to the Permit2 contract.

import { test } from "../framework/fixtures/DappTest";

test.describe("Uniswap", () => {
test("Should approve V3 router to spend BAL", async ({ page, dappPage }) => {
// Setup
dappPage.setContractAddress("0x000000000022d473030f116ddee9f6b43ac78ba3");
dappPage.setApprovalFocus(true);

// Complete the rest of the testing logic below
});
});

Phase 2: Enter App

To enter an app, provide the testing framework with the live URL where you wish to perform the test. Use the page.goto(url) asynchronous function to do this

Example: This would be for testing a swap on Uniswap

import { test } from "@framework/test";

test.describe("Test", () => {
test("Example test app entry", async ({ page, dappPage }) => {
// Set up the framework based on the instructions above

await page.goto("https://app.uniswap.org/#/swap");

// Complete the rest of the testing logic below
});
});

Phase 3: Wallet Connection

The wallet connection flow will vary from app to app, but it usually requires locating a “Connect Wallet” button, and then selecting the “Metamask” option in an ensuing modal. While the GuardianUI wallet is not actually Metamask, it is surfaced to apps in a way that looks like Metamask to avoid issues where sites may not have an option to select an injected wallet.

To get a sense of what to write, you’ll need manually go to your live application and identify the visual and textual elements you need to click to go from the not-connected state to the connected state.

Here’s the Uniswap example:

import { test } from "@framework/test";

test.describe("Test", () => {
test("Example test wallet connection", async ({ page, dappPage }) => {
// Set up the framework based on the instructions above

// Enter the app based on the instructions above

await page.waitForSelector("text=Connect Wallet");
await page.locator("text=Connect Wallet").first().click();
await page.locator("text=Metamask").click();

// Complete the rest of the testing logic below
});
});

Phase 4: App Navigation

This will vary dramatically from app to app but generally this requires identifying elements on the web page based on class or ID selectors and clicking or entering information into them.

To get a sense of what to write, manually go to your live application and identify the visual and textual elements you need to click to go from the not-connected state to the connected state. Use the “inspect” element tool mentioned in the “Finding Site Elements” section to help.

Example: This is based on setting up Uniswap’s frontend to swap BAL to ETH

import { test } from "@framework/test";

test.describe("Test", () => {
test("Example test app navigation", async ({ page, dappPage }) => {
// Set up the framework, enter the app, and connect wallet

// Select input token
await page.locator("#swap-currency-input .open-currency-select-button").click();
await page.locator(".token-item-0xba100000625a3754423978a60c9317c58a424e3D").click();

// Handle occasional popup
let popupIsVisible = await page.isVisible("text=I Understand");
if (popupIsVisible) await page.locator("text=I Understand").click();

// Select output token
await page.locator("#swap-currency-output .open-currency-select-button").click();
await page.locator(".token-item-ETHER").click();

// Handle occasional popup
popupIsVisible = await page.isVisible("text=I Understand");
if (popupIsVisible) await page.locator("text=I Understand").click();

// Set input amount
await page.locator("#token-search-input").waitFor({ state: "hidden" });
await page.locator("#swap-currency-input .token-amount-input").type("0.1");

// Complete the rest of the testing logic below
});
});

Phase 5: Transaction Submission

This requires two steps:

  1. Indicating to Playwright that it should listen for output to the console. When you tell Playwright to listen to the console, you should provide a predicate statement telling the test to listen for at least two seconds.
  2. Clicking the button that triggers the transaction.

Example: This is based on setting an approval in Uniswap for the previous BAL to ETH swap.

import { test } from "@framework/test";

test.describe("Test", () => {
test("Example test app navigation", async ({ page, dappPage }) => {
// Set up the framework, enter the app, connect wallet, and navigate interactions

// Execute approval
const consolePromise = page.waitForEvent("console", {
predicate: async (msg) => {
await page.waitForTimeout(1000);
return true;
}
});
await page.locator("text=Allow the Uniswap Protocol to use your BAL").click();
await consolePromise;
});
});

Browser checks = peace of mind

By conducting browser checks, developers can catch issues before they impact users and can provide a safer experience. The combination of these steps, automated interactions, and assertions helps to verify that the app is creating the expected smart contract interactions. GuardianUI’s custom modification to the Playwright framework makes conducting browser checks simple, reliable, and effective.

With GuardianUI, developers can test and monitor their site continuously using browser checks. If one fails, we immediately send you alerts with exactly what’s wrong so you can address the issue without flying blind and or dealing with guesswork.

About GuardianUI

GuardianUI is the testing and monitoring platform for web3 frontends. Our SaaS platform automates end-to-end testing, application monitoring for web3 critical paths, and real-time alerting and observability to ensure deployed apps create the expected smart contract interactions for users.

Apply for early access by filling out this form.

https://www.guardianui.com/

--

--