synpress icon indicating copy to clipboard operation
synpress copied to clipboard

[๐Ÿ› Bug]: CLI cache building fails - extractWalletSetupFunction causes catastrophic backtracking

Open hobadams opened this issue 6 months ago โ€ข 1 comments

๐Ÿ”Ž Have you searched existing issues to avoid duplicates?

  • [x] I have made sure that my issue is not a duplicate.

๐Ÿงช Have you tested your code using latest version of Synpress?

๐Ÿ’ก Are you able to provide enough information to be able to reproduce your issue locally?

  • [x] I can provide enough details to reproduce my issue on local environment.

Synpress version

4.1.0

Node.js version

22.12.0

Operating system

macOS Sequoia 15.5

Run mode

Playwright + Synpress (as plugin)

CI platform (if applicable)

No response

Are you running your tests inside docker? (if applicable)

  • [ ] This issue could be related to docker.

What happened?

Overview

When running npx sympress to build the cache with a simple metamask config build hangs.

Details

I have cloned synpress locally and added debugging and found the issue comes from this line https://github.com/Synthetixio/synpress/blob/2563f907bc86ac400edd59678dc9e57085d1191d/packages/cache/src/utils/extractWalletSetupFunction.ts#L2

The regex cannot handle more complex config. https://www.regular-expressions.info/catastrophic.html

If my defineWalletSetup function is empty it builds correctly.

Here is my config, it's not huge but the regex pattern matcher can't handle it.

// Import necessary Synpress modules
import { defineWalletSetup } from "@synthetixio/synpress";
import { MetaMask, getExtensionId } from "@synthetixio/synpress/playwright";
import { BrowserContext, Page } from "@playwright/test";
import "dotenv/config";

// Define a test seed phrase and password
const SEED_PHRASE = process.env.METAMASK_SEED_PHRASE;
const PASSWORD = process.env.METAMASK_PASSWORD;

// Define the basic wallet setup
export default defineWalletSetup(
  PASSWORD,
  async (context: BrowserContext, walletPage: Page) => {
    // This is a workaround for the fact that the MetaMask extension ID changes, and this ID is required to detect the pop-ups. // [!code focus]
    // It won't be needed in the near future! ๐Ÿ˜‡ // [!code focus]
    const extensionId = await getExtensionId(context, "MetaMask");
    console.log("MetaMask extension ID:", extensionId);

    // Create a new MetaMask instance
    const metamask = new MetaMask(context, walletPage, PASSWORD, extensionId);

    // Import the wallet using the seed phrase
    await metamask.importWallet(SEED_PHRASE);

    // Additional setup steps can be added here, such as:
    // - Adding custom networks
    // - Importing tokens
    // - Setting up specific account states

    const page = await context.newPage();

    // 1. Set HTTP Auth headers BEFORE visiting the dapp
    const username = process.env.HTTP_USERNAME;
    const password = process.env.HTTP_PASSWORD;
    const auth = Buffer.from(`${username}:${password}`).toString("base64");
    await page.setExtraHTTPHeaders({
      Authorization: `Basic ${auth}`,
    });

    // Visit the dapp URL for the staker using MetaMask
    await page.goto("page here");

    await page
      .getByRole("banner")
      .getByRole("button", { name: "Connect wallet" })
      .click();
    await page.getByRole("button", { name: "MetaMask" }).click();

    await metamask.connectToDapp(["Account 1"]);
  },
);

Solution

A good solution could be to use a parser rather than regex here. Or find another way to load the config.

What is your expected behavior?

My expected behaviour is that my cache is built correctly.

How to reproduce the bug.

To reproduce the bug you can copy the config here into your playwrite project

// Import necessary Synpress modules
import { defineWalletSetup } from "@synthetixio/synpress";
import { MetaMask, getExtensionId } from "@synthetixio/synpress/playwright";
import { BrowserContext, Page } from "@playwright/test";
import "dotenv/config";

// Define a test seed phrase and password
const SEED_PHRASE = process.env.METAMASK_SEED_PHRASE;
const PASSWORD = process.env.METAMASK_PASSWORD;

// Define the basic wallet setup
export default defineWalletSetup(
  PASSWORD,
  async (context: BrowserContext, walletPage: Page) => {
    // This is a workaround for the fact that the MetaMask extension ID changes, and this ID is required to detect the pop-ups. // [!code focus]
    // It won't be needed in the near future! ๐Ÿ˜‡ // [!code focus]
    const extensionId = await getExtensionId(context, "MetaMask");
    console.log("MetaMask extension ID:", extensionId);

    // Create a new MetaMask instance
    const metamask = new MetaMask(context, walletPage, PASSWORD, extensionId);

    // Import the wallet using the seed phrase
    await metamask.importWallet(SEED_PHRASE);

    // Additional setup steps can be added here, such as:
    // - Adding custom networks
    // - Importing tokens
    // - Setting up specific account states

    const page = await context.newPage();

    // 1. Set HTTP Auth headers BEFORE visiting the dapp
    const username = process.env.HTTP_USERNAME;
    const password = process.env.HTTP_PASSWORD;
    const auth = Buffer.from(`${username}:${password}`).toString("base64");
    await page.setExtraHTTPHeaders({
      Authorization: `Basic ${auth}`,
    });

    // Visit the dapp URL for the staker using MetaMask
    await page.goto("page here");

    await page
      .getByRole("banner")
      .getByRole("button", { name: "Connect wallet" })
      .click();
    await page.getByRole("button", { name: "MetaMask" }).click();

    await metamask.connectToDapp(["Account 1"]);
  },
);

Now run npx sympress

You should see it hanging it the build step.

Alternatively go to https://regex101.com/

Paste the regex defineWalletSetup\s*\([^,]*,\s*(async\s*\([^)]*\)\s*=>\s*{(?:[^{}]*|{(?:[^{}]*|{[^{}]*})*})*})\s*\)

and the code (above)

And run...you will see it hangs.

Relevant log output


hobadams avatar Jun 18 '25 10:06 hobadams

Thanks @hobadams for pointing this out.

After investigating further, I found that the reason npx synpress isn't executing is due to the trailing comma at the end of the callback.

Image

I agree that this is a bug and the setup file should be extracted more intelligently to avoid these kinds of errors in the future.

To fix this, you should update your lint rules to remove trailing commas.

Tobechukwu-fieldlabs avatar Jun 19 '25 11:06 Tobechukwu-fieldlabs