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.
- 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
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
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.
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.
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.
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.
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);
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());
Similar to JSHandle.dispose
in Playwright.
Similar to JSHandle.getProperties
in Playwright.
Similar to JSHandle.getProperty
in Playwright.
Similar to JSHandle.jsonValue
in Playwright.
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.
Similar to browserContext.unroute
in Playwright.
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.
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.
export default {
tests: 'test/**/*.spec.*',
};
{
"tests": "test/**/*.spec.*"
}
import { ConfigOptions } from 'wrightplay/node';
const config: ConfigOptions = {
tests: 'test/**/*.spec.*',
};
export default 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',
]
wrightplay -s <path/to/setup>
wrightplay --setup <path/to/setup>
File to run before the test files.
wrightplay [pattern...]
Patterns for the target test files. Check out globby
for supported patterns.
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');
// ...
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.
wrightplay -b <browser>
wrightplay --browser <browser>
Browser type. One of: chromium
, firefox
, webkit
. Defaults to chromium
.
wrightplay --browser-server-options <json>
Options used to launch the browser server. See browserType.launchServer([options])
in Playwright for details.
In CLI, use --debug
.
Run the browser in headless mode. Defaults to true
unless the
devtools
option (in browserServerOptions
) is true
.
wrightplay -d
wrightplay --debug
CLI-only option.
This sets devtools
(in browserServerOptions
) to true
and headless
to false
.
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.
Current working directory. Defaults to process.cwd()
.
Reference:
In your package.json
, add:
{
"browser": {
"mocha": "mocha/mocha.js"
}
}
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
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
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.
npm i -D path stream events process
In package.json
, add:
{
"browser": {
"process": "process/browser.js"
}
}
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);
});
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)),
});
});