reportage
v0.0.11
Published
Scenarist-wrapped mocha sessions on browsers to any reporters
Maintainers
Readme
reportage
scenarist-wrapped mocha sessions on browsers to any reporters
Table of Contents
- reportage
Motivation
reportage is a general-purpose e2e web test runner while the key design goals include applicability to fortified thin-hook applications
thin-hook applications must run in a top frame and detects unexpected intrusion into DOM and the global object except for the built-in automation interface that was originally designed for cache bundle generation
Key Characteristics
| Features | reportage | playwright | cypress |
|:------------:|:-------------|:-------------|:-------------|
| CLI | optional | mandatory | mandatory |
| Test Scripts | browser | automation | browser |
| Target Frame | top frame | top frame | iframe |
The table shows key architectural characteristics of reportage compared with common e2e test frameworks. The main focus here is how to satisfy the prerequisites for the motivation, not the rich features of playwright and cypress.
Getting Started
Steps to perform tests on an example project
# clone the reportage project from GitHub
git clone https://github.com/t2ym/reportage
# example project directory, which is excluded in the reportage npm package
cd reportage/examples
# select an example project
cd vite-lit-ts-app
# install dependencies
# Note: the reportage npm package is installed from the local sources at ../.. in examples
npm i
# start reporter server at port 3000 (customizable)
npm run reporter:start
# start dev server with coverage support at port 3001 (customizable)
npm run dev:coverage
# switch to another terminal as the vite dev server is running in foreground
# CLI test
npm test
# open mochawesome and coverage reports
google-chrome http://localhost:3000/test/mochawesome-report/mochawesome.html \
http://localhost:3000/coverage/index.htmlFor GUI test, follow the example project's README
Install
npm i --save-dev reportageRun
reportage [config...]The default config is test/reportage.config.js
Reports
- Typical report paths, which are visible from
Reporter Servertest/mochawesome-report/mochawesome.html- Test reportcoverage/index.html- Coverage report
Components
Reporter Server
| Features | Supported Configurations | Notes |
|:--------:|:-------------------------------|:---------------------------------------------|
| Host | any (typically localhost) | localhost or bound local address |
| Port | any (e.g. 3000) | normally unprivileged ports |
| Protocol | http/https v1.1, v2, v3 | security requirements must be met |
| CORS | Access-Control-Allow-Origin * | injected scripts are fetched via CORS |
| Root | project root | reportage and test suites must be accessible |
- Reporter server is a static web server that serves
- HTML mocha reporter page (
reportage/reporter.html) and - scenarist-wrapped mocha suites to app pages via CORS
- HTML mocha reporter page (
- Typically,
nginxwith a local configuration works fine- See
npm run reporter:startscript inexamples/vite-lit-ts-app
- See
- Mochawesome HTML reporter and istanbul coverage reporter can be retrieved via the reporter server if so configured
App Server
| Features | Supported Configurations | Notes |
|:--------:|:-------------------------------|:---------------------------------------------|
| Host | any (typically 0.0.0.0) | 127.0.0.* or *.testdomain for concurrency |
| Port | any (e.g. 3001) | normally unprivileged ports |
| Protocol | http/https v1.1, v2, v3 | security requirements must be met |
| Root | any | any dev or dist server |
- App server serves the target application
- For concurrent test execution, each tab must have a unique origin
- So multiple origins must be supported
- with tricky multi-origin IPv4 loopback addresses
- like
http://127.0.0.*:3001/or
- like
- with wildcard host names
- like
https://www{n}.testdomain:3001/, resolving to the same (or different) IP address
- like
- with tricky multi-origin IPv4 loopback addresses
- If the server is static, the reporter server can serve as the app server as well
- If the application is heavy, each origin can be served by a dedicated separate server
Browser
| Features | Supported Configurations | Notes |
|:----------:|:-------------------------------|:---------------------------------------------|
| Extension | node_modules/reportage/extension/chrome/ | see Extension |
| Automation | puppeteer | playwright might be supported in the future|
| Popup blocking | --disable-popup-blocking | prerequisite for opening tabs |
| IPC flooding | --disable-ipc-flooding-protection | prerequisite for stability |
| PushState | --disable-pushstate-throttle | prerequisite for stability |
| Timer | --disable-background-timer-throttling | prerequisite for performance |
- Browser must support
- extension that can
- inject a script into target applications and
- clean up browser storages
- automation with
puppeteer - disabling of
- popup blocking
- IPC flooding protection
- pushState throttle
- background timer throttling
- discrete user profiles for testing
- since the configurations are inappropriate for ordinary browsing
- extension that can
- Most of Chromium-based browsers can be used such as
- Chrome
- Microsoft Edge
- It is recommended to set the following alias for GUI test
alias chrome='google-chrome --disable-ipc-flooding-protection --disable-pushstate-throttle --disable-background-timer-throttling --disable-popup-blocking '- The above options are automatically set for
puppeteerinreportageCLI- Even with the options, concurrent test execution may become unstable partially because of Chromium's hard-coded limitation of
6concurrent socket connections per host - Cache-first service workers or aggresive caching policies should be able to mitigate the side effects of this limitation
- Even with the options, concurrent test execution may become unstable partially because of Chromium's hard-coded limitation of
Reporter Page
| Features | Supported Configurations | Notes |
|:--------:|:-------------------------------|:---------------------------------------------|
| Host | reporter server host | |
| Port | reporter server port | normally unprivileged ports |
| Protocol | reporter server protocol | security requirements must be met |
| Path | /node_modules/reportage/reporter.html | reportage package directory |
| Hash | #/test/reportage.config.js | path to configuration has to be set |
| Module | /node_modules/reportage/reporter.js | main module for the page |
| Module | /node_modules/reportage/proxy-reporter.js | mocha proxy reporter |
- Typical URLs
http://localhost:3000/node_modules/reportage/reporter.html#/test/reportage.config.js- configuration file path is specified
http://localhost:3000/node_modules/reportage/reporter.html#/test/reportage.config.js?scope=basic- target scope is specified
- Reporter page controls
- opening, closing, and navigation of tabs running target applications
- cleanup of browser storages
- dispatching of test suites to the tabs
- collection of test results and code coverages
- aggregation of the results and the coverages
- redirection of the aggregated results to
- HTML reporter in the reporter page and
- optionally reportage CLI via puppeteer
- The page also has a control panel that filters
- target scope and
- target test class
- with "Start ▶" button to run the targeted suites
- The hash of the reporter page contains
- path to reportage configuration file (typically
#/test/reportage.config.js) and - [optional] target scope (
?scope={scope name}) - [optional] target test index (
&testIndex={number}) - [optional] target test class (
&testClass={testClassName}) - [optional] target test step in a test scenario (
&testStep={number}) - [optional] and other additional information (in the future)
- path to reportage configuration file (typically
- The hash values are reflected to the control panel
- The control panel values are reflected to the hash when the "Start ▶" button is clicked
- "Replay ▶" buttons of test results in HTML reporter also change the hash values when they are clicked
Mediator
| Features | Supported Configurations | Notes | |:--------:|:-------------------------------|:---------------------------------------------| | Host | reporter server host | | | Port | reporter server port | normally unprivileged ports | | Protocol | reporter server protocol | security requirements must be met |
- Mediator bridges cross-origin communication between reporter tab and app tabs with these 3 components
mediator-worker.jsSharedWorker scriptmediator-bridge.htmlthat loadsmediator-worker-client.js
mediator-worker.js
| Features | Supported Configurations | Notes |
|:--------:|:-------------------------------|:---------------------------------------------|
| Path | /node_modules/reportage/mediator-worker.js | SharedWorker |
mediator-worker.jsis aSharedWorkerthat forwards messages viaMessagePort- Each message has a target page ID to determine which tab receives the message
mediator-bridge.html
| Features | Supported Configurations | Notes |
|:--------:|:-------------------------------|:---------------------------------------------|
| Path | /node_modules/reportage/mediator-bridge.html | opened by app tabs |
mediator-bridge.htmlis opened by each app tab to executemediator-worker-client.jsin the reporter origin- The tabs persist during test execution
mediator-worker-client.js
| Features | Supported Configurations | Notes |
|:--------:|:-------------------------------|:---------------------------------------------|
| Path | /node_modules/reportage/mediator-worker-client.js | loaded by mediator-bridge.html |
mediator-worker-client.js- loads
mediator-worker.jsand - transfer a
MessagePortinstance to each app page, - which is the opener of
mediator-bridge.html
- loads
App Pages
| Features | Supported Configurations | Notes | |:--------:|:-------------------------------|:---------------------------------------------| | Host | app server host(s) | | | Port | app server port | normally unprivileged ports | | Protocol | app server protocol | security requirements must be met | | Path | any | no restriction on paths | | Modules | any | no restriction on modules and scripts |
- Each app page runs in a separate browsing context with a dedicated process
driver.jsCORS script has to be injected so that Reporter Page can perform test suites on the app
driver.js
| Features | Supported Configurations | Notes |
|:--------:|:-------------------------------|:---------------------------------------------|
| Host | reporter server host | |
| Port | reporter server port | normally unprivileged ports |
| Protocol | reporter server protocol | security requirements must be met |
| Path | /node_modules/reportage/driver.js | injected CORS module script |
| Hash | #/test/reportage.config.js | path to configuration has to be set |
driver.jsis injected to each app page to perform test suites on the app- Typical CORS URL is
http://localhost:3000/node_modules/reportage/driver.js#/test/reportage.config.js
driver.jsopensmediator-bridge.htmltab to establish communication path to the reporter page
- Typical CORS URL is
sandbox-global.js
| Features | Supported Configurations | Notes |
|:--------:|:-------------------------------|:---------------------------------------------|
| Host | reporter server host | |
| Port | reporter server port | normally unprivileged ports |
| Protocol | reporter server protocol | security requirements must be met |
| Path | /node_modules/reportage/sandbox-global.js | |
sandbox-global.jsprovides a sandbox object formochaandscenarist- Functions and classes like
describe,it,Suiteare NOT exposed to global objects for target applications
- Functions and classes like
proxy-reporter.js
| Features | Supported Configurations | Notes |
|:--------:|:-------------------------------|:---------------------------------------------|
| Host | reporter server host | |
| Port | reporter server port | normally unprivileged ports |
| Protocol | reporter server protocol | security requirements must be met |
| Path | /node_modules/reportage/proxy-reporter.js | imported by reporter.js, driver.js, and cli.mjs |
proxy-reporter.jsdefinesProxyReporterclass that wraps and forwards mocha events toReporter PageviaMessagePortReceiverRunnerclass that receives aggregated mocha events and redirects them to a mocha reporter
Extension
| Features | Supported Configurations | Notes |
|:--------:|:-------------------------------|:---------------------------------------------|
| Local Path | node_modules/reportage/extension/chrome/ | for Chrome for now |
- Test Helper browser extension performs these tasks
- injection of
driver.jsmodule to each top frame page - cleanup of browser storages to set up clean test environments
- collection of navigation URLs of target app
- injection of
- Manual installation is required on GUI test
- open
chrome://extensions/ - enable the Developer Mode
- install the non-packaged extension from
node_modules/reportage/extension/chrome/
- open
- Automatically installed on each CLI test execution
- The extension is inappropriate for normal browsing
- a dedicated user profile for testing has to be created
- an ephemeral user profile is automatically created for each
puppeteersession inreportageCLI
- Alternatively,
driver.jsscript tag can be injected at App Server or at build timeConfig.driverInjectionMethodmust be set other thanExtensionif the script is injected at the server
reportage CLI
| Features | Supported Configurations | Notes |
|:--------:|:-------------------------------|:---------------------------------------------|
| Local Path | node_modules/.bin/reportage | symbolic link to cli.mjs |
| Module | node_modules/reportage/cli.mjs | |
| config arg | paths to reportage.config.js | multiple configs can be specified |
| import arg | --import {module} | import extra module(s) (optional) |
reportageCLI- takes config path(s) to load
test/reportage.config.jsis the default config if omitted
- opens
puppeteersessions to perform test suites by- opening Reporter Page
- clicking the "Start ▶" button
- redirecting mocha events to console reporters
- collecting coverage data to
.nyc_output/out.jsonnyc reportcommand is NOT invokedposttestnpm script should runnpx nyc reportcommand- coverage instrumentation is NOT done by
reportageCLI- instrumentation must be performed at
- build time or
- server middleware
- instrumentation must be performed at
- coverage instrumentation is NOT done by
- optionally imports module(s) that can export these optional hooks
- takes config path(s) to load
const { onConfig, onReady, onMochaEvent, onEnd } = await import("module path");
async onConfig({ Config });
async onReady({ Config, page, browser });
onMochaEvent({ Config, page, browser, event });
async onEnd({ Config, page, browser, event });reportage.config.js
| Features | Supported Configurations | Notes |
|:--------:|:--------------------------------|:---------------------------------------------|
| Host | reporter server host | |
| Port | reporter server port | normally unprivileged ports |
| Protocol | reporter server protocol | security requirements must be met |
| Path | any (typically /test/reportage.config.js) | path to configuration |
| Local Path | any (typically test/reportage.config.js) | local path to configuration |
reportage.config.jsis loaded byreportageCLI as well as browser modulesreporter.jsanddriver.jsare loaded with hash that contains a path toreportage.config.js
example
test/reportage.config.jsfromvite-lit-ts-app- properties starting with
_are internal toConfigobject
- properties starting with
const Config = {
configURL: import.meta.url,
get testConfigPath() {
return new URL(this.configURL).pathname;
},
_reporterWebRootRelativeToTestConfigPath: '../',
get testConfigPathOnReporter() {
if (new URL(this.configURL).protocol === 'file:') {
const baseLength = (new URL(this._reporterWebRootRelativeToTestConfigPath, this.configURL).pathname).length;
return (new URL(this.configURL)).pathname.substring(baseLength - 1);
}
else {
return this.testConfigPath;
}
},
_concurrency: 8,//typeof navigator === 'object' ? navigator.hardwareConcurrency : 1,
get _targetAppHosts() {
return [...function *() { for (let i = 1; i <= Config._concurrency; i++) yield `http://127.0.0.${i}`; }()];
},
get _targetAppPorts() {
return [ 3001 ];
},
get targetAppTestBasePath() {
let pathname = new URL(this.configURL).pathname.split('/');
pathname[pathname.length - 1] = '';
return pathname.join('/'); // /test/
},
targetOrigin(host, port) {
// TODO: handle port=443 and '' properly
return `${host}:${port}`;
},
targetApp(origin, path) {
return new URL(path, origin).href;
},
* originGenerator() {
for (let host of this._targetAppHosts) {
for (let port of this._targetAppPorts) {
yield this.targetOrigin(host, port);
}
}
},
driverInjectionMethod: [
'BuildTime',
'ServerMiddleware',
'Extension',
][2],
get reporterOrigin() {
return `http://localhost:3000`;
},
async importedBy(importerURL) {
const _url = new URL(importerURL);
let pathElements = _url.pathname.split('/');
let reportagePackagePath;
if (pathElements.length >= 3 &&
pathElements[pathElements.length - 1].endsWith('.js') &&
pathElements[pathElements.length - 2] === 'reportage' &&
pathElements[pathElements.length - 3] === 'node_modules') {
// */node_modules/reportage/*.js
reportagePackagePath = _url.pathname.substring(0, _url.pathname.length - pathElements[pathElements.length - 1].length)
}
else if (pathElements.length === 2 &&
pathElements[0] === '' &&
pathElements[1].endsWith('.js')) {
// /*.js
reportagePackagePath = _url.pathname.substring(0, 1); // '/'
}
else if (_url.protocol === 'file:' &&
(pathElements[pathElements.length - 1].endsWith('cli.mjs') || pathElements[pathElements.length - 1].endsWith('cli.js'))) {
reportagePackagePath = _url.pathname.substring(0, _url.pathname.length - pathElements[pathElements.length - 1].length)
}
if (reportagePackagePath) {
switch (pathElements[pathElements.length - 1]) {
case 'reporter.js': // must be called from reporter.js in reporter.html
this._pageType = 'reporter';
break;
case 'driver.js': // must be called from driver.js in target app pages
this._pageType = 'driver';
break;
case 'cli.mjs':
case 'reportage':
this._pageType = 'reportage';
break;
case 'cli.js':
this._pageType = 'reportage:instrumented';
break;
case 'mediator-worker.js':
case 'mediator-worker-client.js':
break;
default:
break;
}
if (this._pageType) {
this.reportagePackagePath = reportagePackagePath;
}
}
if (this.reportagePackagePath) {
const { default: resolvedPaths } = await import(new URL('resolved-paths.js', new URL(this.reportagePackagePath, this.configURL)).pathname);
this.resolvedPaths = resolvedPaths;
}
else {
throw new Error(`${import.meta.url}: Unexpected call to Config.importedBy("${importerURL}")`);
}
},
resolve(bareSpecifier) { // primitive simulation of import maps
if (!this.reportagePackagePathOnTargetApp) {
throw new Error(`${import.meta.url}: reportagePackagePathOnTargetApp is missing in calling Config.resolve("${bareSpecifier}")`);
}
if (!this.resolvedPaths) {
throw new Error(`${import.meta.url}: resolvedPath is missing in calling Config.resolve("${bareSpecifier}")`);
}
if (!this.resolvedPaths[bareSpecifier]) {
throw new Error(`${import.meta.url}: resolvedPath["${bareSpecifier}"] is missing in calling Config.resolve("${bareSpecifier}")`);
}
return new URL(this.resolvedPaths[bareSpecifier], new URL(this.reportagePackagePathOnTargetApp, this.configURL).href).pathname;
},
get reportagePackagePathOnReporter() {
if (new URL(this.configURL).protocol === 'file:') {
const baseLength = (new URL(this._reporterWebRootRelativeToTestConfigPath, this.configURL).pathname).length;
return this.reportagePackagePath.substring(baseLength - 1);
}
else {
return this.reportagePackagePath;
}
},
get reportagePackagePathOnTargetApp() {
return this.reportagePackagePath;
},
mediatorWorkerPathRelativeToReportage: './mediator-worker.js',
_mediatorHtmlPathRelativeToReportage: './mediator.html',
get mediatorHtmlURL() {
return new URL(this._mediatorHtmlPathRelativeToReportage, new URL(this.reportagePackagePathOnReporter, this.reporterOrigin).href).href;
},
_reporterHtmlPathRelativeToReportage: 'reporter.html',
get reporterURL() {
return `${this.reporterOrigin}${this.reportagePackagePathOnReporter}${this._reporterHtmlPathRelativeToReportage}#${this.testConfigPathOnReporter}`;
},
get cleanupOptions() {
const commonOptions = {
RemovalOptions: {
since: 0,
origins: [Config.reporterOrigin, ...Config.originGenerator()], // chrome-only
//hostnames: [], // firefox-only
},
dataToRemove: {
start: { // only once per run; unnecessary for puppeteer sessions if a dedicated user profile is created for each session
// non-filterable by origins/hostnames - Be aware that other apps with the same user profile are affected as well
appcache: false, // [DEPRECATED FEATURE] Websites' appcaches.
downloads: false, // [BASICALLY IRRELEVANT TO WEB TESTS] The browser's download list.
history: false, // [Session history is reset on navigating to the bottom of the history stack] The browser's history, which is different from window.history object
formData: true, // [Autofill feature should be disabled in the browser Configurations] The browser's stored form data.
passwords: true, // [Autofill feature should be disabled in the browser Configurations] Stored passwords.
// filterable by origins/hostnames
cache: true, // The browser's cache.
},
end: { // only once per run
// non-filterable by origins/hostnames - Be aware that other apps with the same user profile are affected as well
appcache: false, // [DEPRECATED FEATURE] Websites' appcaches.
downloads: false, // [BASICALLY IRRELEVANT TO WEB TESTS] The browser's download list.
history: false, // [Session history is reset on navigating to the bottom of the history stack] The browser's history, which is different from window.history object
formData: true, // [Autofill feature should be disabled in the browser Configurations] The browser's stored form data.
passwords: true, // [Autofill feature should be disabled in the browser Configurations] Stored passwords.
// filterable by origins/hostnames
cookies: true, // The browser's cookies.
cache: true, // The browser's cache.
fileSystems: true, // Websites' file systems.; not on Firefox
indexedDB: true, // Websites' IndexedDB data.
localStorage: true, // Websites' local storage data.
cacheStorage: true, // Cache storage
serviceWorkers: true, // Service Workers.
webSQL: false, // [DEPRECATED FEATURE] Websites' WebSQL data.
},
window: { // on each window.open(targetAppOrigin)
// filterable by origins/hostnames
cookies: true, // [If the feature is not used, it can be false] The browser's cookies.
cache: false, // The browser's cache.
fileSystems: true, // Websites' file systems.; not on Firefox
indexedDB: true, // Websites' IndexedDB data.
localStorage: true, // Websites' local storage data.
cacheStorage: true, // Cache storage
serviceWorkers: true, // Service Workers.
webSQL: false, // [DEPRECATED FEATURE] Websites' WebSQL data.
},
suite: { // on each test scenario
// filterable by origins/hostnames
cookies: true, // [If the feature is not used, it can be false] The browser's cookies.
cache: false, // [TESTS MAY BECOME FLAKY IF CACHE IS CLEANED ON EACH SUITE AND CONCURRENCY IS HIGH] The browser's cache.
fileSystems: false, // [If the feature is not used, it can be false] Websites' file systems.; not on Firefox
indexedDB: false, // [If the feature is not used, it can be false] Websites' IndexedDB data.
localStorage: false, // [If the feature is not used, it can be false] Websites' local storage data.
cacheStorage: false, // [In most cases, Service Workers and Cache storage can persist over multiple test scenarios] Cache storage for Service Workers
serviceWorkers: false, // [In most cases, Service Workers and Cache storage can persist over multiple test scenarios] Service Workers.
webSQL: false, // [DEPRECATED FEATURE] Websites' WebSQL data.
// not supported in the browsingData.remove() API
sessionStorage: true, // cleanup by sessionStorage API itself at driver.js
},
},
timeout: 10000,
};
return commonOptions;
},
_suitesLoaderScriptRelativeToConfig: './suites-loader.js',
_scenaristLoaderScriptRelativeToReportage: './scenarist-loader.js',
get suitesLoaderPath() {
return new URL(this._suitesLoaderScriptRelativeToConfig + '#' + new URL(this.configURL).pathname, this.configURL).pathname;
},
get scenaristLoaderPath() {
return new URL(this._scenaristLoaderScriptRelativeToReportage, new URL(this.reportagePackagePath, this.configURL).href).pathname;
},
importOnlyTargetScope: true, // for performance
timeout: 5 * 1000, // 5sec
readyTimeout: 5 * 1000, // 5sec
readyTimeoutRetries: 2, // 2 retries
mediatorPortTimeout: 1 * 1000, // 5sec
beaconTimeout: 5 * 1000, // 5sec
setupInjectionTimeout: 1000, // 1sec
dispatcherStartInterval: 50, // 50ms - insert a wait between dispatcher start events
suitesLoaderRetries: 1, // 2 retries
windowTarget: '_blank',
windowFeatures: 'noopener,noreferrer',
mochaOptions: {
ui: 'bdd',
timeout: 60000,
checkLeaks: true,
cleanReferencesAfterRun: false, // References must not be cleaned until the proxy reporter completes transferring all events
retries: -1,
},
consoleReporter: 'mochawesome',
consoleReporterOptions: {
reportDir: './test/mochawesome-report/',
//reportFilename: '[status]_[datetime]-[name]-report',
autoOpen: false,
html: true,
json: true,
timeout: 5000,
consoleReporter: 'list',
},
coverageOptions: {
enabled: true,
},
get links() {
return {
mochawesome: new URL(this.consoleReporterOptions.reportDir + 'mochawesome.html', new URL(this._reporterWebRootRelativeToTestConfigPath, import.meta.url)).href,
coverage: new URL('coverage/index.html', new URL(this._reporterWebRootRelativeToTestConfigPath, import.meta.url)).href,
};
},
get _pathToChromeExtension() {
return this.reportagePackagePath +
(this.coverageOptions && this.coverageOptions.enabled && this._pageType === 'reportage:instrumented' ? 'test/instrumented/' : '') +
'extension/chrome';
},
get puppeteerLaunchOptions() {
return {
headless: 'new', // 'new' for headless; false for windowed
dumpio: false,
devtools: false,
defaultViewport: { // null for resizable viewport in a windowed mode
width: 1280,
height: 720,
//deviceScaleFactor: 1,
//hasTouch: false,
//isLandscape: false,
//isMobile: false,
},
args: [
'--disable-gpu',
//'--enable-logging=stderr',
//'--auto-open-devtools-for-tabs',
'--disable-ipc-flooding-protection',
'--disable-pushstate-throttle',
'--disable-background-timer-throttling',
'--disable-popup-blocking',
`--disable-extensions-except=${Config._pathToChromeExtension}`,
`--load-extension=${Config._pathToChromeExtension}`,
//'--user-data-dir=/home/t2ym/.config/google-chrome',
//'--profile-directory=Profile 1', // Non-puppeteer windows must be closed when a profile is specified
],
executablePath: '/usr/bin/google-chrome',
};
},
}
export default Config;Other Configuration Files
resolved-paths.js
| Features | Supported Configurations | Notes |
|:--------:|:-------------------------------|:---------------------------------------------|
| URL Path | /node_modules/reportage/resolved-paths.js | generated at postinstall |
resolved-paths.jsis a naive hack to resolve node module paths for these modules for static Reporter Server"scenarist/Suite.js""mocha/mocha.js""mocha/mocha.css""@esm-bundle/chai/esm/chai.js"
nyc.config.mjs
| Features | Supported Configurations | Notes |
|:--------:|:-------------------------------|:---------------------------------------------|
| Local Path | nyc.config.mjs (optional) | local path to nyc configuration |
nginx.conf
| Features | Supported Configurations | Notes |
|:--------:|:-------------------------------|:---------------------------------------------|
| Local Path | nginx.conf (optional) | local path to nginx configuration |
Suites
suites-loader.js
| Features | Supported Configurations | Notes |
|:--------:|:-------------------------------|:---------------------------------------------|
| Path | any (typically /test/suites-loader.js) | Config._suitesLoaderScriptRelativeToConfig |
suites-loader.jsis configured atConfig._suitesLoaderScriptRelativeToConfigto set the loader for test suites- it typically loads
scenarist-loader.jsand test suites for scopes
- it typically loads
mocha-loader.js
| Features | Supported Configurations | Notes |
|:--------:|:-------------------------------|:---------------------------------------------|
| Path | /node_modules/reportage/mocha-loader.js | loaded by driver.js and reporter.js |
mocha-loader.js- fetches
mocha/mocha.jsscript - patches the source code for
reportageby- disabling
grepsearch parameters - exporting an installer to
sandboxobject
- disabling
- fetches
scenarist-loader.js
| Features | Supported Configurations | Notes |
|:--------:|:-------------------------------|:---------------------------------------------|
| Path | /node_modules/reportage/scenarist-loader.js | loaded by suites-loader.js |
scenarist-loader.js- imports
sandboxfromsandbox-global.js - fetches
scenaristscript version 1.1.10 - patches the source code for
reportage- use
sandboxto get mocha functions such asdescribe,it, etc. - add mocha's
thisargument to calloperation,checkpoint,setup,teardowncalls - add
sandboxargument toruncalls
- use
- no global
Suitevariable
- imports
- the path is resolved by
resolved-paths.js
common-suite.js
| Features | Supported Configurations | Notes |
|:--------:|:-------------------------------|:---------------------------------------------|
| Path | any (typically /test/common-suite.js) | loaded by suites-loader.js |
common-suite.jsor any test suites can define common methods of test classes such asTest Phases- utility functions, etc.
Test Suites
| Features | Supported Configurations | Notes |
|:--------:|:-------------------------------|:---------------------------------------------|
| Path | any (typically /test/*-suite.js) | loaded by suites-loader.js |
Test Suitesare defined in test classes withscenaristUI- they typically load
common-suite.jsand extend test classes - they are loaded by
suites-loader.js
- they typically load
Test Phases
- For non-SPA applications, each test scenario has to handle page navigation
reportagehandles such test scenarios by introducing Phase conceptvite-lit-ts-appexample shows how to handle page transitions in a test scenario- transition from
/to/external-navi-vite.htmlby clicking the link and increment thephasenumber
- transition from
- "Seeing is believing" in the example project but an awkward explanation follows:
this.targetin test classes is originally designed for test fixtures- In E2E tests, test fixtures are whole pages in top frames
this.targetis then reinterpreted as a container for parameters across a single test scenario with page transitionsthis.target.phasecontains the current phase number in a test scenario starting from0this.target.phaseis incremented before navigation to another page- the trick is to set
this.target.deferredNavigation()function to be called AFTER the phase finishes for the current mocha session- to keep page navigations from destroying the running mocha test suites
- the trick is to set
- the value of
this.targetobject is transferred toReporter Pageon navigation assuiteParametersobject Reporter Pagerequests the incremented phase of the scenario with the storedsuiteParametersobjectsuiteParameterscan store any clonable objects
- the test scenario can perform operations for the current phase and skip those not for the current phase
ToDos
- [x] screenshot
- pause-before-replay option for debugging
- browser support
- TBD
