wrightplay
Experience the seamless synergy of Playwright with wrightplay: while Playwright takes the center stage for Node.js (e2e testing), wrightplay puts the spotlight back on browsers (unit testing).
For a complete example showing how to use wrightplay, check out wrightplay-demo.
Project Under Development
But feel free to try! All APIs should work, they just lack test cases and documentations.
When should I choose wrightplay?
- You want Node.js native coverage reports
- You want full TypeScript supports
- You want NET interceptor that intercepts all page requests, with in-page control
- You want source mapped error stack traces
- You don't want the error stack mapping to happen inside the browser
- You don't want the interceptor to occupy Service Worker
- You don't want to find, choose, and install a “loader” dependency for each browser
The key features come from:
- It converts chromium coverage output to Node.js format
- The source mapping of error traces happens outside the browser, affecting no page script but only the Node.js console output
- Everything written in TypeScript
- Browsers from Playwright
- Proxies
page.route
through WebSocket to a specific module within the page to intercept page requests with in-page control and without occupying Service Worker
Installation
npm i -D wrightplay
Install test browsers with Playwright's cli.
# Default browsers (chromium, firefox, and webkit)
npx playwright install
# Specific browser(s)
# browser: one of: chromium, chrome, chrome-beta, msedge, msedge-beta, msedge-dev, firefox, webkit
# E.g., npx playwright install chromium
npx playwright install <browser...>
To use browsers available on the machine, use channel
via browserServerOptions
.
For CI environments, check out
For more available options and descriptions, check out
Basic
Write a test setup
Listen to test ready
import { onInit } from 'wrightplay';
onInit(() => {
startTesting();
});
Pass a callback to onInit
to act when all the test files got successfully imported. If called multiple times, onInit
will call the callbacks in order. If a callback returns a promise, it will wait until the promise get fulfilled before calling the next callback. If a callback throws an error, the process will exit unsuccessfully.
Indicate the result of your tests
import { done } from 'wrightplay';
onTestEnd((failures) => {
// Pass the desired process exit number to `done`.
done(failures > 0 ? 1 : 0);
});
The process may never exit if you don't call this function.
Some test runners like Mocha require additional steps to run in browsers, see Working with... part for examples.
Start
If the tests inject the setup on their own,
wrightplay test/**/*.spec.ts
If the setup is separate and the tests don't inject it themselves,
wrightplay -s test/setup.ts test/**/*.spec.ts
If you want Node.js API,
import { Runner } from 'wrightplay/node';
const runner = new Runner({
setup: 'test/setup.ts',
tests: 'test/**/*.spec.ts',
});
process.exit(await runner.runTests());
Check Options for full option list.
HostHandle
APIs similar to JSHandle
in Playwright.
Just like you can pass a function from node to browser to run via page.evaluate
in Playwright, you can pass a function from browser to node via pageHandle.evaluate
in wrightplay.
pageHandle
and contextHandle
represent the Page
and BrowserContext
Playwright instance that controls the current page respectively.
Evaluate
Similar to JSHandle.evaluate
in Playwright.
import { pageHandle } from 'wrightplay';
const screenshotPath = 'screenshots/1.png';
await pageHandle.evaluate(async (page, path) => {
await page.screenshot({ path });
}, screenshotPath);
EvaluateHandle
Similar to JSHandle.evaluateHandle
in Playwright.
import { pageHandle } from 'wrightplay';
const browserHandle = await pageHandle
.evaluateHandle((page) => page.context().browser());
// "103.0.5060.42" on chromium as of writing
await browserHandle.evaluate((b) => b.version());
Dispose
Similar to JSHandle.dispose
in Playwright.
getProperties
Similar to JSHandle.getProperties
in Playwright.
getProperty
Similar to JSHandle.getProperty
in Playwright.
jsonValue
Similar to JSHandle.jsonValue
in Playwright.
Route
Dedicated API faster than wrapping contextRoute.evaluate
for routing, uses ArrayBuffer
and Blob
for binary data. The handler callback stays in the browser and has access to all the scopes like a normal function has.
Similar to browserContext.route
in Playwright.
import { contextRoute } from 'wrightplay';
const body = new Blob(['routed!']);
await contextRoute('hello', (r) => {
r.fulfill({ body });
}, { times: 1 });
// "routed!"
await (await fetch('hello')).text();
All the routes by this API will auto “unroute” on page unload.
Unroute
Similar to browserContext.unroute
in Playwright.
Coverage
Use NODE_V8_COVERAGE
environment variable to get coverage results. Tools like c8
that use NODE_V8_COVERAGE
internally work as well.
Note that firefox and webkit don't support coverage recording.
# Generate Node.js format coverage output to ./coverage/tmp/
cross-env NODE_V8_COVERAGE=coverage/tmp wrightplay test/*.spec.*
# Or use c8 coverage reports
c8 -a wrightplay test/*.spec.*
-a
, --exclude-after-remap
option enables c8
to properly parse 1:many source maps for wrightplay. c8
should enable this option by default, but they haven't yet.
Configuration file
You can put the test options (see Options) in a config file, and wrightplay will read it as the option base. See config
option for how the CLI resolves the config file path.
You can use an array of option objects to represent multiple test runs that should run in order.
JS
export default {
tests: 'test/**/*.spec.*',
};
JSON
{
"tests": "test/**/*.spec.*"
}
TS
import { ConfigOptions } from 'wrightplay/node';
const config: ConfigOptions = {
tests: 'test/**/*.spec.*',
};
export default config;
Options
config
wrightplay --config path/to/config/file
# Omit to use default
wrightplay
CLI-only option.
Path to config file. The CLI checks these files by default:
[
'package.json', // "wrightplay" property
'.wrightplayrc',
'.wrightplayrc.json',
'.wrightplayrc.ts',
'.wrightplayrc.mts',
'.wrightplayrc.cts',
'.wrightplayrc.js',
'.wrightplayrc.mjs',
'.wrightplayrc.cjs',
'.config/wrightplayrc',
'.config/wrightplayrc.json',
'.config/wrightplayrc.ts',
'.config/wrightplayrc.mts',
'.config/wrightplayrc.cts',
'.config/wrightplayrc.js',
'.config/wrightplayrc.mjs',
'.config/wrightplayrc.cjs',
'wrightplay.config.ts',
'wrightplay.config.mts',
'wrightplay.config.cts',
'wrightplay.config.js',
'wrightplay.config.mjs',
'wrightplay.config.cjs',
]
setup
wrightplay -s <path/to/setup>
wrightplay --setup <path/to/setup>
File to run before the test files.
tests
wrightplay [pattern...]
Patterns for the target test files. Check out globby
for supported patterns.
entryPoints
wrightplay [entry...]
Additional entry points to build. You can use this option to build workers.
In CLI, use format name=path/to/entry
. For example,
wrightplay worker=test/web-worker-helper.ts
or config file
{
"entryPoints": {
"worker": "test/web-worker.ts"
}
}
will make this available:
const worker = new Worker('/worker.js');
// ...
watch
wrightplay -w
wrightplay --watch
Monitor test file changes and trigger automatic test reruns. Defaults to false
.
Please be aware that on certain platforms, particularly in the context of large-scale projects, this feature might silently fail or raise some errors.
browser
wrightplay -b <browser>
wrightplay --browser <browser>
Browser type. One of: chromium
, firefox
, webkit
. Defaults to chromium
.
browserServerOptions
wrightplay --browser-server-options <json>
Options used to launch the browser server. See browserType.launchServer([options])
in Playwright for details.
headless
In CLI, use --debug
.
Run the browser in headless mode. Defaults to true
unless the
devtools
option (in browserServerOptions
) is true
.
debug
wrightplay -d
wrightplay --debug
CLI-only option.
This sets devtools
(in browserServerOptions
) to true
and headless
to false
.
noCov
wrightplay --no-cov
Disable coverage file output. This only matters when NODE_V8_COVERAGE
is set. Defaults to false
on chromium, true
on firefox and webkit.
cwd
Current working directory. Defaults to process.cwd()
.
Working with...
Mocha
Reference:
Use mocha.js
In your package.json
, add:
{
"browser": {
"mocha": "mocha/mocha.js"
}
}
Write mocha setup like
import 'mocha';
import { onInit, done } from 'wrightplay';
mocha.setup({
color: true,
fullTrace: true,
reporter: 'spec',
ui: 'bdd',
});
onInit(() => {
mocha.run((failures) => {
done(failures > 0 ? 1 : 0);
});
});
uvu
uvu
has no reliable or future-proof way to run and get the test results programmatically. Track lukeed/uvu · Issue #113.
Click here for an example setup that reads the test results by proxying the console messages.
tape
tape
needs some node-specific modules to work: path
, stream
, events
, and process
. So we need polyfills to get it to work in browsers.
Steps below may differ if you choose different providers.
Install polyfills
npm i -D path stream events process
In package.json
, add:
{
"browser": {
"process": "process/browser.js"
}
}
Write tape setup like
import process from 'process';
import { done } from 'wrightplay';
globalThis.process = process;
const { onFailure, onFinish } = await import('tape');
let exitCode = 0;
onFailure(() => {
exitCode = 1;
});
onFinish(() => {
done(exitCode);
});
Zora
Process zora messages
To setup zora, pipe zora reporters to read test results:
import { hold, report, createTAPReporter } from 'zora';
import { done, onInit } from 'wrightplay';
// Hold zora default run
hold();
// Record failed assertion
const tapReporter = createTAPReporter();
async function* record(stream: Parameters<typeof tapReporter>[0]) {
let exitCode = 0;
for await (const msg of stream) {
if (msg.type === 'ASSERTION' && !msg.data.pass) {
exitCode = 1;
} else if (msg.type === 'ERROR') {
done(1);
} if (msg.type === 'TEST_END') {
done(exitCode);
}
yield msg;
}
}
onInit(async () => {
// Run zora with piped reporter
await report({
reporter: (stream) => tapReporter(record(stream)),
});
});