A simple load balancer for Cypress tests.
Use this for parallelizing jobs across CI/CD solutions, or locally if set up.
Note: Requires extra setup on your own CI/CD environment to function correctly!
CYPRESS_LOAD_BALANCER_MAX_DURATIONS_ALLOWED
: Determines how many durations are saved per file. Deletes oldest durations once the maximum limit has been reached. Defaulted to 10.CYPRESS_LOAD_BALANCER_DEBUG
: Enables debugging output. Does not clear the console, so the output may be unusable.
Install the package to your project:
npm install --save-dev cypress-load-balancer
yarn add -D cypress-load-balancer
Add the following to your .gitignore
and other ignore files:
.cypress_load_balancer
In your Cypress configuration file, add the plugin separately to your e2e
configuration and also component
configuration, if you have one.
This will register load balancing for separate testing types
import { addCypressLoadBalancerPlugin } from "cypress-load-balancer";
defineConfig({
e2e: {
setupNodeEvents(on, config) {
addCypressLoadBalancerPlugin(on);
}
},
component: {
setupNodeEvents(on, config) {
addCypressLoadBalancerPlugin(on);
}
}
});
Currently, this only supports one configuration. I am considering how to handle multiple configs later on.
Now, when you run your suite, it will calculate the average for each file based on previous durations and output it into
.cypress_load_balancer/spec-map.json
. This is the load balancing map file.
This can be executed with npx cypress-load-balancer
:
$: npx cypress-load-balancer --help
cypress-load-balancer
Performs load balancing against a set of runners and Cypress specs
Commands:
cypress-load-balancer Performs load balancing against a set of
runners and Cypress specs [default]
cypress-load-balancer initialize Initializes the load balancing map file and
directory.
cypress-load-balancer merge Merges load balancing map files together
back to an original map.
Options:
--version Show version number [boolean]
-r, --runners The count of executable runners to use
[number] [required]
-t, --testing-type The testing type to use for load balancing
[string] [required] [choices: "e2e", "component"]
-F, --files An array of file paths relative to the current
working directory to use for load balancing.
Overrides finding Cypress specs by configuration
file.
If left empty, it will utilize a Cypress
configuration file to find test files to use for
load balancing.
The Cypress configuration file is implied to
exist at the base of the directory unless set by
"process.env.CYPRESS_CONFIG_FILE"
[array] [default: []]
--format, --fm Transforms the output of the runner jobs into
various formats.
"--transform spec": Converts the output of the
load balancer to be as an array of "--spec
{file}" formats
"--transform string": Spec files per runner are
joined with a comma; example:
"tests/spec.a.ts,tests/spec.b.ts"
"--transform newline": Spec files per runner are
joined with a newline; example:
"tests/spec.a.ts
tests/spec.b.ts"
[choices: "spec", "string", "newline"]
--set-gha-output, --gha Sets the output to the GitHub Actions step output
as "cypressLoadBalancerSpecs" [boolean]
-h, --help Show help [boolean]
Examples:
Load balancing for 6 runners against cypressLoadBalancer -r 6 -t
"component" testing with implied Cypress component
configuration of `./cypress.config.js`
Load balancing for 3 runners against cypressLoadBalancer -r 3 -t e2e -F
"e2e" testing with specified file paths cypress/e2e/foo.cy.js
cypress/e2e/bar.cy.js
cypress/e2e/wee.cy.js
Example of load balancing on shell:
#If running with DEBUG mode on, make sure to use `tail -1` to get the output correctly
specs=$(echo npx cypress-load-balancer -r 3 -t e2e | tail -1)
This probably will not work with tsx
or ts-node
-- I need to figure out why.
This is the basic idea of steps that need to occur in order to use load balancing properly. The load balancing map file needs to saved and persisted throughout all runs in a stable, base location. After all parallel test runs complete, their results can be merged back to the main file, which can be consumed on the next test runs, and so on.
- Install and configure the plugin. When Cypress runs, it will be able to locally save the results of the spec executions per each runner.
- Initialize the load balancer main map file in a persisted location that can be easily restored from cache. This means the main file needs to be in a place outside of the parallelized jobs to can be referenced by the parallelized jobs in order to save new results.
- Execute the load balancer against a number of runners. The output is able to be used for all parallelized jobs to instruct them which specs to execute.
- Run each parallelized job that will execute the Cypress testrunner with the spec output per each runner.
- Collect and save the output of the load balancing files from each job in a temporary location.
- After all parallelized test jobs complete, merge their load balancing map results back to the persisted map file and cached for later usage. In a GitHub Actions run, this means on pull request merge, the load balancing files from the base branch and the head branch need to be merged, then cached down to the base branch.
There are example workflows here:
.github/workflows/cypress-parallel.yml
: demonstrates (with some extra steps) how to perform load balancing and cache the files on runs. For pull requests, it will prepare the file to be saved when merging down to the base branch..github/workflows/save-map-to-base-branch-on-pr-merge.yml
: when the pull request is merged, the load balancing file from the test runs on the PR will be saved to the base branch.
When pull requests are merged, the latest load balancing map file is saved to the base branch so it can be used again. This allows the map to be saved on a trunk branch, so workflows can reuse and overwrite it when there are new pull requests with updated test results.
I'd love to add actual GitHub Actions for this project.
Someone please help me here :simple_smile:
See https://www.embedthis.com/blog/sensedeep/how-to-create-single-source-npm-module.html.
To be incredibly honest, I would not have been able to do this myself without the help from above. Huge thanks to the authors.
Because JavaScript is an unnecessarily difficult language to compile, this package does its best to handle both ESM and
CommonJS suites. Basically, it outputs ESM and CommonJS modules to /dist
as /dist/mjs
and /dist/cjs
, respectively.
The package.json
describes the exports as well. Finally, each output gets its own package.json
that describes the
type of module; these files are created with scrips/fixup
.
The TS Config files used are:
tsconfig.json
: The base file from which others are derived. Used for type checking but does not outputtsconfig.test.json
: Used for type checking test filestsconfig.commonjs.json
: Outputs CommonJS modules to/dist/cjs
tsconfig.esm.json
: Outputs ESM modules to/dist/mjs
To output distributed files, run yarn build
.