Using Your Own Node Modules With Playwright

Felicia Walker - Oct 15 - - Dev Community

Most of the time your supporting Playwright code will probably live side by side in directories next to your spec files. However, if you need to share your supporting code with other teams or release it to the public, you may consider using a Node module.

I did just this to share UI, API, and load libraries of supporting Playwright code with other teams. They all had their own repositories and CI/CD pipelines and did not want to clone the test repository and deal with it. Publishing as node packages to a private registry seemed like a good solution. This way other teams could just pull them in like any other module. However, there were complications:

  • VS code and linters wanted the modules to be in ESM format
  • Playwright wanted the modules to be in CJS format
  • You need type files for intellisense

It took a lot of experimenting, borrowing from other open source projects, and swearing in order to get something that fixed all of these. Hopefully I can save you some of that pain with this article.

Much of this article overlaps with my earlier one on setting something similar up for K6

Overview

Here is a slightly simplified version of the Playwright setup I created:

There is sample code you can refer to at my playwright-template-complex-dev respository which covers all sections of this article. It is a simplified version of the diagram below.

UML diagram of a simplified code setup

There are three custom libraries which reside in their own repositories. The data library are container objects for various app entities. This creates a standard way to pass things around among the various parts. The UI library abstracts UI actions and implements the Playwright ones at a low level. The API library constructs and executes product specific API calls.

These three libraries are published to a private NPM repository. The Playwright code pulls from this, as well as the public repository, for the libraries it needs. It can also import any local files specific to its tests.

I won't cover how to set things up to access a private NPM registry since it depends on a lot of things outside the scope of this article.

Building You Own Node modules

I found you had to compile and bundle your package correctly or you would have issues with your IDE not recognizing types, Playwright not compiling properly, or Playwright blowing up when it tried to run your script. Again, I am working from memory so hopefully I have captured all of the details.

In my example each custom library was originally it was in it's own repository and published as an NPM package to a private registry. However, in the example they are just other directories for simplicity.

The idea is you need both the CSJ and ESM versions in the package. Typescript and modern Javascript want the ESM versions for use with 'import', but Playwright wants the older CJS version for use with 'require' in its compilation process.

How to do it

You will want to have this in your package.json file:

    "type": "commonjs",
    "main": "dist/cjs/index.js",
    "module": "dist/esm/index.mjs",
    "types": "index.d.ts",
    "typesVersions": {
        "*": {
            "somedir/*": [
                "dist/types/somedir/*.d.ts"
            ],
        }
    },
    "exports": {
        ".": {
            "types": "./dist/types/index.d.ts",
            "import": "./dist/esm/index.js",
            "require": "./dist/cjs/index.js"
        },
        "./somedir/*": {
            "types": "./dist/types/somedir/*.d.ts",
            "import": "./dist/esm/somedir/*.js",
            "require": "./dist/cjs/somedir/*.js"
        },
        "./package.json": "./package.json"
    },
    "files": [
        "dist",
        "tsconfig.json"
    ],
Enter fullscreen mode Exit fullscreen mode

The two blocks with "somedir" will need to be repeated for each directory within your package. These additions tell how to find the CJS and ESM versions of your modules files as well as the type files for each.

You also will need an /index.ts file in your source root that exports every file in your modules. For the example above, this could look something like:

export * from './somedir/xyzzy'
export * from './somedir/plugh'
export { default as FrobozzService } from './somedir/frobozz.service'
Enter fullscreen mode Exit fullscreen mode

I ended up borrowing and modifying a build script from the FakerJS project for building and bundling. To build you need to make the CJS version, the ESM version, and put a copy of the CSJ version in the ESM directory.

Once all of this built, you can publish and import into Playwright with no issues. Playwright should also not complain when you run your script and everything is compiled.

Conclusion

Packaging your Playwright code into node modules for use by Playwright and an IDE can be tricky. You will likely need to include ESM and CSJ versions of your code to satisfy both contexts.

Setting up your development environment to accomplish this is not too difficult. It mainly requires the use of a bundler configured to pull in the proper files and deal with them. When using you own node modules, however, you must make sure you build them correctly or issues will arise with your IDE, webpack, or Playwright.

. . . . . . .