{"id":17965364,"url":"https://github.com/t2ym/reportage","last_synced_at":"2026-04-30T15:33:00.848Z","repository":{"id":148385972,"uuid":"620324569","full_name":"t2ym/reportage","owner":"t2ym","description":"scenarist-wrapped mocha sessions on browsers to any reporters","archived":false,"fork":false,"pushed_at":"2023-05-11T09:43:29.000Z","size":645,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-11-22T12:21:18.307Z","etag":null,"topics":["coverage","e2e","mocha"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/t2ym.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-03-28T13:12:16.000Z","updated_at":"2023-03-28T13:56:00.000Z","dependencies_parsed_at":null,"dependency_job_id":"d63b7092-8798-454c-9ac1-f72e7cc6c02b","html_url":"https://github.com/t2ym/reportage","commit_stats":{"total_commits":42,"total_committers":1,"mean_commits":42.0,"dds":0.0,"last_synced_commit":"3d665f57c09bae7e89c830774fa0740cf248cc53"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/t2ym/reportage","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t2ym%2Freportage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t2ym%2Freportage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t2ym%2Freportage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t2ym%2Freportage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/t2ym","download_url":"https://codeload.github.com/t2ym/reportage/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t2ym%2Freportage/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32469344,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"ssl_error","status_checked_at":"2026-04-30T13:12:06.837Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["coverage","e2e","mocha"],"created_at":"2024-10-29T12:41:54.713Z","updated_at":"2026-04-30T15:33:00.832Z","avatar_url":"https://github.com/t2ym.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![npm version](https://badge.fury.io/js/reportage.svg)](https://badge.fury.io/js/reportage)\n\n# reportage\n\n[scenarist](https://github.com/t2ym/scenarist)-wrapped mocha sessions on browsers to any reporters\n\n\u003cp align=\"center\"\u003e\n  \u003cimg width=\"80%\" src=\"https://raw.githubusercontent.com/wiki/t2ym/reportage/reportage-components.svg?sanitize=true\"\u003e\n  \u003cp align=\"center\"\u003eComponent Diagram\u003c/p\u003e\n\u003c/p\u003e\n\n## Table of Contents\n\n- [reportage](#reportage)\n  - [Table of Contents](#table-of-contents)\n  - [Motivation](#motivation)\n  - [Key Characteristics](#key-characteristics)\n  - [Getting Started](#getting-started)\n  - [Install](#install)\n  - [Run](#run)\n  - [Reports](#reports)\n  - [Components](#components)\n    - [Reporter Server](#reporter-server)\n    - [App Server](#app-server)\n    - [Browser](#browser)\n    - [Reporter Page](#reporter-page)\n    - [Mediator](#mediator)\n      - [`mediator-worker.js`](#mediator-workerjs)\n      - [`mediator-bridge.html`](#mediator-bridgehtml)\n      - [`mediator-worker-client.js`](#mediator-worker-clientjs)\n    - [App Pages](#app-pages)\n    - [`driver.js`](#driverjs)\n    - [`sandbox-global.js`](#sandbox-globaljs)\n    - [`proxy-reporter.js`](#proxy-reporterjs)\n    - [Extension](#extension)\n    - [`reportage` CLI](#reportage-cli)\n    - [`reportage.config.js`](#reportageconfigjs)\n    - [Other Configuration Files](#other-configuration-files)\n      - [`resolved-paths.js`](#resolved-pathsjs)\n      - [`nyc.config.mjs`](#nycconfigmjs)\n      - [`nginx.conf`](#nginxconf)\n    - [Suites](#suites)\n      - [`suites-loader.js`](#suites-loaderjs)\n      - [`mocha-loader.js`](#mocha-loaderjs)\n      - [`scenarist-loader.js`](#scenarist-loaderjs)\n      - [`common-suite.js`](#common-suitejs)\n      - [Test Suites](#test-suites)\n      - [Test Phases](#test-phases)\n  - [ToDos](#todos)\n  - [License](#license)\n\n## Motivation\n\n`reportage` is a general-purpose e2e web test runner while the key design goals include applicability to fortified [`thin-hook`](https://github.com/t2ym/thin-hook) applications\n\n`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\n\n## Key Characteristics\n\n| Features     | `reportage`  | `playwright` | `cypress`    |\n|:------------:|:-------------|:-------------|:-------------|\n| CLI          | optional     | mandatory    | mandatory    |\n| Test Scripts | browser      | automation   | browser      |\n| Target Frame | top frame    | top frame    | iframe       |\n\nThe 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`.\n\n## Getting Started\n\nSteps to perform tests on an example project\n\n```sh\n# clone the reportage project from GitHub\ngit clone https://github.com/t2ym/reportage\n# example project directory, which is excluded in the reportage npm package\ncd reportage/examples \n# select an example project\ncd vite-lit-ts-app \n# install dependencies \n# Note: the reportage npm package is installed from the local sources at ../.. in examples\nnpm i\n# start reporter server at port 3000 (customizable)\nnpm run reporter:start\n# start dev server with coverage support at port 3001 (customizable)\nnpm run dev:coverage\n# switch to another terminal as the vite dev server is running in foreground\n# CLI test\nnpm test\n# open mochawesome and coverage reports\ngoogle-chrome http://localhost:3000/test/mochawesome-report/mochawesome.html \\\n  http://localhost:3000/coverage/index.html\n```\n\nFor GUI test, follow the example project's [README](examples/vite-lit-ts-app/README.md)\n\n## Install\n\n```sh\nnpm i --save-dev reportage\n```\n\n## Run\n\n```sh\nreportage [config...]\n```\nThe default config is [`test/reportage.config.js`](#reportageconfigjs)\n\n## Reports\n\n- Typical report paths, which are visible from [`Reporter Server`](#reporter-server)\n  - `test/mochawesome-report/mochawesome.html` - Test report\n  - `coverage/index.html` - Coverage report\n\n## Components\n\n### Reporter Server\n\n| Features | Supported Configurations              | Notes                                        |\n|:--------:|:-------------------------------|:---------------------------------------------|\n| Host     | any (typically `localhost`)     | `localhost` or bound local address           |\n| Port     | any (e.g. `3000`)               | normally unprivileged ports                  |\n| Protocol | `http/https` v1.1, v2, v3       | security requirements must be met            |\n| CORS     | `Access-Control-Allow-Origin *` | injected scripts are fetched via CORS        |\n| Root     | project root                    | reportage and test suites must be accessible |\n\n- Reporter server is a static web server that serves\n  - HTML mocha reporter page (`reportage/reporter.html`) and\n  - scenarist-wrapped mocha suites to app pages via CORS\n- Typically, `nginx` with a local configuration works fine\n  - See `npm run reporter:start` script in [`examples/vite-lit-ts-app`](examples/vite-lit-ts-app/README.md)\n- Mochawesome HTML reporter and istanbul coverage reporter can be retrieved via the reporter server if so configured\n\n### App Server\n\n| Features | Supported Configurations       | Notes                                        |\n|:--------:|:-------------------------------|:---------------------------------------------|\n| Host     | any (typically 0.0.0.0)         | 127.0.0.* or *.testdomain for concurrency    |\n| Port     | any (e.g. `3001`)               | normally unprivileged ports                  |\n| Protocol | `http/https` v1.1, v2, v3       | security requirements must be met            |\n| Root     | any                             | any dev or dist server                       |\n\n- App server serves the target application\n  - For concurrent test execution, each tab must have a unique origin\n  - So multiple origins must be supported\n    - with tricky multi-origin IPv4 loopback addresses\n      - like `http://127.0.0.*:3001/` or\n    - with wildcard host names\n      - like `https://www{n}.testdomain:3001/`, resolving to the same (or different) IP address\n- If the server is static, the reporter server can serve as the app server as well\n- If the application is heavy, each origin can be served by a dedicated separate server\n\n### Browser\n\n| Features   | Supported Configurations       | Notes                                        |\n|:----------:|:-------------------------------|:---------------------------------------------|\n| Extension  | `node_modules/reportage/extension/chrome/` | see [Extension](#extension)       |\n| Automation | `puppeteer`                     | `playwright` might be supported in the future|\n| Popup blocking | `--disable-popup-blocking`  | prerequisite for opening tabs                |\n| IPC flooding | `--disable-ipc-flooding-protection` | prerequisite for stability             |\n| PushState  | `--disable-pushstate-throttle` | prerequisite for stability                    |\n| Timer      | `--disable-background-timer-throttling` | prerequisite for performance         |\n\n- Browser must support\n  - extension that can\n    - inject a script into target applications and\n    - clean up browser storages\n  - automation with `puppeteer`\n  - disabling of\n    - popup blocking\n    - IPC flooding protection\n    - pushState throttle\n    - background timer throttling\n  - discrete user profiles for testing\n    - since the configurations are inappropriate for ordinary browsing\n- Most of Chromium-based browsers can be used such as\n  - Chrome\n  - Microsoft Edge\n- It is recommended to set the following alias for GUI test\n```sh\nalias chrome='google-chrome --disable-ipc-flooding-protection --disable-pushstate-throttle --disable-background-timer-throttling --disable-popup-blocking '\n```\n- The above options are automatically set for `puppeteer` in `reportage` CLI\n  - Even with the options, concurrent test execution may become unstable partially because of Chromium's hard-coded limitation of [`6`](https://chromium.googlesource.com/chromium/src/net/+/master/socket/client_socket_pool_manager.cc#51) concurrent socket connections per host \n  - Cache-first service workers or aggresive caching policies should be able to mitigate the side effects of this limitation\n\n### Reporter Page\n\n| Features | Supported Configurations        | Notes                                        |\n|:--------:|:-------------------------------|:---------------------------------------------|\n| Host     | reporter server host            |                                              |\n| Port     | reporter server port            | normally unprivileged ports                  |\n| Protocol | reporter server protocol        | security requirements must be met            |\n| Path     | `/node_modules/reportage/reporter.html` | reportage package directory          |\n| Hash     | `#/test/reportage.config.js`    | path to configuration has to be set          |\n| Module   | `/node_modules/reportage/reporter.js` | main module for the page               |\n| Module   | `/node_modules/reportage/proxy-reporter.js` | mocha proxy reporter |\n\n- Typical URLs\n  - `http://localhost:3000/node_modules/reportage/reporter.html#/test/reportage.config.js`\n    - configuration file path is specified\n  - `http://localhost:3000/node_modules/reportage/reporter.html#/test/reportage.config.js?scope=basic`\n    - target scope is specified\n- Reporter page controls\n  - opening, closing, and navigation of tabs running target applications\n  - cleanup of browser storages\n  - dispatching of test suites to the tabs\n  - collection of test results and code coverages\n  - aggregation of the results and the coverages\n  - redirection of the aggregated results to\n    - HTML reporter in the reporter page and\n    - optionally reportage CLI via puppeteer\n- The page also has a control panel that filters\n  - target scope and\n  - target test class\n  - with \"Start ▶\" button to run the targeted suites\n- The hash of the reporter page contains\n  - path to reportage configuration file (typically `#/test/reportage.config.js`) and\n  - [optional] target scope (`?scope={scope name}`)\n  - [optional] target test index (`\u0026testIndex={number}`)\n  - [optional] target test class (`\u0026testClass={testClassName}`)\n  - [optional] target test step in a test scenario (`\u0026testStep={number}`)\n  - [optional] and other additional information (in the future)\n- The hash values are reflected to the control panel\n- The control panel values are reflected to the hash when the \"Start ▶\" button is clicked\n- \"Replay ▶\" buttons of test results in HTML reporter also change the hash values when they are clicked\n\n### Mediator\n\n| Features | Supported Configurations       | Notes                                        |\n|:--------:|:-------------------------------|:---------------------------------------------|\n| Host     | reporter server host            |                                              |\n| Port     | reporter server port            | normally unprivileged ports                  |\n| Protocol | reporter server protocol        | security requirements must be met            |\n\n- Mediator bridges cross-origin communication between reporter tab and app tabs with these 3 components\n  - [`mediator-worker.js`](#mediator-workerjs) SharedWorker script\n  - [`mediator-bridge.html`](#mediator-bridgehtml) that loads\n  - [`mediator-worker-client.js`](#mediator-worker-clientjs)\n\n#### `mediator-worker.js`\n\n| Features | Supported Configurations       | Notes                                        |\n|:--------:|:-------------------------------|:---------------------------------------------|\n| Path     | `/node_modules/reportage/mediator-worker.js` | SharedWorker                    |\n\n- `mediator-worker.js` is a `SharedWorker` that forwards messages via `MessagePort`\n- Each message has a target page ID to determine which tab receives the message\n\n#### `mediator-bridge.html`\n\n| Features | Supported Configurations       | Notes                                        |\n|:--------:|:-------------------------------|:---------------------------------------------|\n| Path     | `/node_modules/reportage/mediator-bridge.html` | opened by app tabs            |\n\n- `mediator-bridge.html` is opened by each app tab to execute `mediator-worker-client.js` in the reporter origin\n  - The tabs persist during test execution\n\n#### `mediator-worker-client.js`\n\n| Features | Supported Configurations       | Notes                                        |\n|:--------:|:-------------------------------|:---------------------------------------------|\n| Path     | `/node_modules/reportage/mediator-worker-client.js` | loaded by `mediator-bridge.html` |\n\n- `mediator-worker-client.js`\n  - loads `mediator-worker.js` and\n  - transfer a `MessagePort` instance to each app page,\n  - which is the opener of `mediator-bridge.html`\n\n### App Pages\n\n| Features | Supported Configurations       | Notes                                        |\n|:--------:|:-------------------------------|:---------------------------------------------|\n| Host     | app server host(s)              |                                              |\n| Port     | app server port                 | normally unprivileged ports                  |\n| Protocol | app server protocol             | security requirements must be met            |\n| Path     | any                             | no restriction on paths                      |\n| Modules  | any                             | no restriction on modules and scripts        |\n\n- Each app page runs in a separate browsing context with a dedicated process\n  - `driver.js` CORS script has to be injected so that [Reporter Page](#reporter-page) can perform test suites on the app\n\n### `driver.js`\n\n| Features | Supported Configurations       | Notes                                        |\n|:--------:|:-------------------------------|:---------------------------------------------|\n| Host     | reporter server host            |                                              |\n| Port     | reporter server port            | normally unprivileged ports                  |\n| Protocol | reporter server protocol        | security requirements must be met            |\n| Path     | `/node_modules/reportage/driver.js` | injected CORS module script              |\n| Hash     | `#/test/reportage.config.js`    | path to configuration has to be set          |\n\n- `driver.js` is injected to each app page to perform test suites on the app\n  - Typical CORS URL is\n    - `http://localhost:3000/node_modules/reportage/driver.js#/test/reportage.config.js`\n  - `driver.js` opens `mediator-bridge.html` tab to establish communication path to the reporter page\n\n### `sandbox-global.js`\n\n| Features | Supported Configurations       | Notes                                        |\n|:--------:|:-------------------------------|:---------------------------------------------|\n| Host     | reporter server host            |                                              |\n| Port     | reporter server port            | normally unprivileged ports                  |\n| Protocol | reporter server protocol        | security requirements must be met            |\n| Path     | `/node_modules/reportage/sandbox-global.js` |                                  |\n\n- `sandbox-global.js` provides a sandbox object for `mocha` and `scenarist`\n  - Functions and classes like `describe`, `it`, `Suite` are NOT exposed to global objects for target applications\n\n### `proxy-reporter.js`\n\n| Features | Supported Configurations       | Notes                                        |\n|:--------:|:-------------------------------|:---------------------------------------------|\n| Host     | reporter server host            |                                              |\n| Port     | reporter server port            | normally unprivileged ports                  |\n| Protocol | reporter server protocol        | security requirements must be met            |\n| Path     | `/node_modules/reportage/proxy-reporter.js` | imported by `reporter.js`, `driver.js`, and `cli.mjs` |\n\n- `proxy-reporter.js` defines\n  - `ProxyReporter` class that wraps and forwards mocha events to `Reporter Page` via `MessagePort`\n  - `ReceiverRunner` class that receives aggregated mocha events and redirects them to a mocha reporter\n\n### Extension\n\n| Features | Supported Configurations       | Notes                                        |\n|:--------:|:-------------------------------|:---------------------------------------------|\n| Local Path | `node_modules/reportage/extension/chrome/` | for Chrome for now                |\n\n- Test Helper browser extension performs these tasks\n  - injection of `driver.js` module to each top frame page\n  - cleanup of browser storages to set up clean test environments\n  - collection of navigation URLs of target app\n- Manual installation is required on GUI test\n  - open `chrome://extensions/`\n  - enable the Developer Mode\n  - install the non-packaged extension from `node_modules/reportage/extension/chrome/`\n- Automatically installed on each CLI test execution\n- The extension is inappropriate for normal browsing\n  - a dedicated user profile for testing has to be created\n  - an ephemeral user profile is automatically created for each `puppeteer` session in `reportage` CLI\n- Alternatively, `driver.js` script tag can be injected at [App Server](#app-server) or at build time\n  - `Config.driverInjectionMethod` must be set other than `Extension` if the script is injected at the server\n\n### `reportage` CLI\n\n| Features | Supported Configurations       | Notes                                        |\n|:--------:|:-------------------------------|:---------------------------------------------|\n| Local Path | `node_modules/.bin/reportage` | symbolic link to `cli.mjs`                   |\n| Module   | `node_modules/reportage/cli.mjs` |                                             |\n| config arg | paths to `reportage.config.js` | multiple configs can be specified           |\n| import arg | `--import {module}`            | import extra module(s) (optional)             |\n\n- `reportage` CLI\n  - takes config path(s) to load\n    - `test/reportage.config.js` is the default config if omitted\n  - opens `puppeteer` sessions to perform test suites by\n    - opening [Reporter Page](#reporter-page)\n    - clicking the \"Start ▶\" button\n    - redirecting mocha events to console reporters\n    - collecting coverage data to `.nyc_output/out.json`\n      - `nyc report` command is NOT invoked\n      - `posttest` npm script should run `npx nyc report` command\n        - coverage instrumentation is NOT done by `reportage` CLI\n          - instrumentation must be performed at\n            - build time or\n            - server middleware\n  - optionally imports module(s) that can export these optional hooks\n```js\n  const { onConfig, onReady, onMochaEvent, onEnd } = await import(\"module path\");\n  async onConfig({ Config });\n  async onReady({ Config, page, browser });\n  onMochaEvent({ Config, page, browser, event });\n  async onEnd({ Config, page, browser, event });\n```\n\n### `reportage.config.js`\n\n| Features | Supported Configurations       | Notes                                        |\n|:--------:|:--------------------------------|:---------------------------------------------|\n| Host     | reporter server host            |                                              |\n| Port     | reporter server port            | normally unprivileged ports                  |\n| Protocol | reporter server protocol        | security requirements must be met            |\n| Path     | any (typically `/test/reportage.config.js`) | path to configuration            |\n| Local Path | any (typically `test/reportage.config.js`) | local path to configuration     |\n\n- `reportage.config.js` is loaded by `reportage` CLI as well as browser modules\n  - `reporter.js` and `driver.js` are loaded with hash that contains a path to `reportage.config.js`\n\n- example `test/reportage.config.js` from [`vite-lit-ts-app`](examples/vite-lit-ts-app/README.md)\n  - properties starting with `_` are internal to `Config` object\n\n```js\nconst Config = {\n  configURL: import.meta.url,\n  get testConfigPath() {\n    return new URL(this.configURL).pathname;\n  },\n  _reporterWebRootRelativeToTestConfigPath: '../',\n  get testConfigPathOnReporter() {\n    if (new URL(this.configURL).protocol === 'file:') {\n      const baseLength = (new URL(this._reporterWebRootRelativeToTestConfigPath, this.configURL).pathname).length;\n      return (new URL(this.configURL)).pathname.substring(baseLength - 1);\n    }\n    else {\n      return this.testConfigPath;\n    }\n  },\n  _concurrency: 8,//typeof navigator === 'object' ? navigator.hardwareConcurrency : 1,\n  get _targetAppHosts() {\n    return [...function *() { for (let i = 1; i \u003c= Config._concurrency; i++) yield `http://127.0.0.${i}`; }()];\n  },\n  get _targetAppPorts() {\n    return [ 3001 ];\n  },\n  get targetAppTestBasePath() {\n    let pathname = new URL(this.configURL).pathname.split('/');\n    pathname[pathname.length - 1] = '';\n    return pathname.join('/'); // /test/\n  },\n  targetOrigin(host, port) {\n    // TODO: handle port=443 and '' properly\n    return `${host}:${port}`;\n  },\n  targetApp(origin, path) {\n    return new URL(path, origin).href;\n  },\n  * originGenerator() {\n    for (let host of this._targetAppHosts) {\n      for (let port of this._targetAppPorts) {\n        yield this.targetOrigin(host, port);\n      }  \n    }\n  },\n  driverInjectionMethod: [\n    'BuildTime',\n    'ServerMiddleware',\n    'Extension',\n  ][2],\n  get reporterOrigin() {\n    return `http://localhost:3000`;\n  },\n  async importedBy(importerURL) {\n    const _url = new URL(importerURL);\n    let pathElements = _url.pathname.split('/');\n    let reportagePackagePath;\n    if (pathElements.length \u003e= 3 \u0026\u0026\n        pathElements[pathElements.length - 1].endsWith('.js') \u0026\u0026\n        pathElements[pathElements.length - 2] === 'reportage' \u0026\u0026\n        pathElements[pathElements.length - 3] === 'node_modules') {\n      // */node_modules/reportage/*.js\n      reportagePackagePath = _url.pathname.substring(0, _url.pathname.length - pathElements[pathElements.length - 1].length)\n    }\n    else if (pathElements.length === 2 \u0026\u0026\n      pathElements[0] === '' \u0026\u0026\n      pathElements[1].endsWith('.js')) {\n      // /*.js\n      reportagePackagePath = _url.pathname.substring(0, 1); // '/'\n    }\n    else if (_url.protocol === 'file:' \u0026\u0026\n      (pathElements[pathElements.length - 1].endsWith('cli.mjs') || pathElements[pathElements.length - 1].endsWith('cli.js'))) {\n      reportagePackagePath = _url.pathname.substring(0, _url.pathname.length - pathElements[pathElements.length - 1].length)\n    }\n    if (reportagePackagePath) {\n      switch (pathElements[pathElements.length - 1]) {\n      case 'reporter.js': // must be called from reporter.js in reporter.html\n        this._pageType = 'reporter';\n        break;\n      case 'driver.js': // must be called from driver.js in target app pages\n        this._pageType = 'driver';\n        break;\n      case 'cli.mjs':\n      case 'reportage':\n        this._pageType = 'reportage';\n        break;\n      case 'cli.js':\n        this._pageType = 'reportage:instrumented';\n        break;\n      case 'mediator-worker.js':\n      case 'mediator-worker-client.js':\n        break;\n      default:\n        break;\n      }\n      if (this._pageType) {\n        this.reportagePackagePath = reportagePackagePath;\n      }\n    }\n    if (this.reportagePackagePath) {\n      const { default: resolvedPaths } = await import(new URL('resolved-paths.js', new URL(this.reportagePackagePath, this.configURL)).pathname);\n      this.resolvedPaths = resolvedPaths;\n    }\n    else {\n      throw new Error(`${import.meta.url}: Unexpected call to Config.importedBy(\"${importerURL}\")`);\n    }\n  },\n  resolve(bareSpecifier) { // primitive simulation of import maps\n    if (!this.reportagePackagePathOnTargetApp) {\n      throw new Error(`${import.meta.url}: reportagePackagePathOnTargetApp is missing in calling Config.resolve(\"${bareSpecifier}\")`);\n    }\n    if (!this.resolvedPaths) {\n      throw new Error(`${import.meta.url}: resolvedPath is missing in calling Config.resolve(\"${bareSpecifier}\")`);\n    }\n    if (!this.resolvedPaths[bareSpecifier]) {\n      throw new Error(`${import.meta.url}: resolvedPath[\"${bareSpecifier}\"] is missing in calling Config.resolve(\"${bareSpecifier}\")`);\n    }\n    return new URL(this.resolvedPaths[bareSpecifier], new URL(this.reportagePackagePathOnTargetApp, this.configURL).href).pathname;\n  },\n  get reportagePackagePathOnReporter() {\n    if (new URL(this.configURL).protocol === 'file:') {\n      const baseLength = (new URL(this._reporterWebRootRelativeToTestConfigPath, this.configURL).pathname).length;\n      return this.reportagePackagePath.substring(baseLength - 1);\n    }\n    else {\n      return this.reportagePackagePath;\n    }\n  },\n  get reportagePackagePathOnTargetApp() {\n    return this.reportagePackagePath;\n  },\n  mediatorWorkerPathRelativeToReportage: './mediator-worker.js',\n  _mediatorHtmlPathRelativeToReportage: './mediator.html',\n  get mediatorHtmlURL() {\n    return new URL(this._mediatorHtmlPathRelativeToReportage, new URL(this.reportagePackagePathOnReporter, this.reporterOrigin).href).href;\n  },\n  _reporterHtmlPathRelativeToReportage: 'reporter.html',\n  get reporterURL() {\n    return `${this.reporterOrigin}${this.reportagePackagePathOnReporter}${this._reporterHtmlPathRelativeToReportage}#${this.testConfigPathOnReporter}`;\n  },\n  get cleanupOptions() {\n    const commonOptions = {\n      RemovalOptions: {\n        since: 0,\n        origins: [Config.reporterOrigin, ...Config.originGenerator()], // chrome-only\n        //hostnames: [], // firefox-only\n      },\n      dataToRemove: {\n        start: { // only once per run; unnecessary for puppeteer sessions if a dedicated user profile is created for each session\n          // non-filterable by origins/hostnames - Be aware that other apps with the same user profile are affected as well\n          appcache: false, // [DEPRECATED FEATURE] Websites' appcaches.\n          downloads: false, // [BASICALLY IRRELEVANT TO WEB TESTS] The browser's download list.\n          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\n          formData: true, // [Autofill feature should be disabled in the browser Configurations] The browser's stored form data.\n          passwords: true, // [Autofill feature should be disabled in the browser Configurations] Stored passwords.\n          // filterable by origins/hostnames\n          cache: true, // The browser's cache.\n        },\n        end: { // only once per run\n          // non-filterable by origins/hostnames - Be aware that other apps with the same user profile are affected as well\n          appcache: false, // [DEPRECATED FEATURE] Websites' appcaches.\n          downloads: false, // [BASICALLY IRRELEVANT TO WEB TESTS] The browser's download list.\n          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\n          formData: true, // [Autofill feature should be disabled in the browser Configurations] The browser's stored form data.\n          passwords: true, // [Autofill feature should be disabled in the browser Configurations] Stored passwords.\n          // filterable by origins/hostnames\n          cookies: true, // The browser's cookies.\n          cache: true, // The browser's cache.\n          fileSystems: true, // Websites' file systems.; not on Firefox\n          indexedDB: true, // Websites' IndexedDB data.\n          localStorage: true, // Websites' local storage data.\n          cacheStorage: true, // Cache storage\n          serviceWorkers: true, // Service Workers.\n          webSQL: false, // [DEPRECATED FEATURE] Websites' WebSQL data.\n        },\n        window: { // on each window.open(targetAppOrigin)\n          // filterable by origins/hostnames\n          cookies: true, // [If the feature is not used, it can be false] The browser's cookies.\n          cache: false, // The browser's cache.\n          fileSystems: true, // Websites' file systems.; not on Firefox\n          indexedDB: true, // Websites' IndexedDB data.\n          localStorage: true, // Websites' local storage data.\n          cacheStorage: true, // Cache storage\n          serviceWorkers: true, // Service Workers.\n          webSQL: false, // [DEPRECATED FEATURE] Websites' WebSQL data.\n        },\n        suite: { // on each test scenario\n          // filterable by origins/hostnames\n          cookies: true, // [If the feature is not used, it can be false] The browser's cookies.\n          cache: false, // [TESTS MAY BECOME FLAKY IF CACHE IS CLEANED ON EACH SUITE AND CONCURRENCY IS HIGH] The browser's cache.\n          fileSystems: false, // [If the feature is not used, it can be false] Websites' file systems.; not on Firefox\n          indexedDB: false, // [If the feature is not used, it can be false] Websites' IndexedDB data.\n          localStorage: false, // [If the feature is not used, it can be false] Websites' local storage data.\n          cacheStorage: false, // [In most cases, Service Workers and Cache storage can persist over multiple test scenarios] Cache storage for Service Workers\n          serviceWorkers: false, // [In most cases, Service Workers and Cache storage can persist over multiple test scenarios] Service Workers. \n          webSQL: false, // [DEPRECATED FEATURE] Websites' WebSQL data.\n          // not supported in the browsingData.remove() API\n          sessionStorage: true, // cleanup by sessionStorage API itself at driver.js\n        },\n      },\n      timeout: 10000,\n    };\n    return commonOptions;\n  },\n  _suitesLoaderScriptRelativeToConfig: './suites-loader.js',\n  _scenaristLoaderScriptRelativeToReportage: './scenarist-loader.js',\n  get suitesLoaderPath() {\n    return new URL(this._suitesLoaderScriptRelativeToConfig + '#' + new URL(this.configURL).pathname, this.configURL).pathname;\n  },\n  get scenaristLoaderPath() {\n    return new URL(this._scenaristLoaderScriptRelativeToReportage, new URL(this.reportagePackagePath, this.configURL).href).pathname;\n  },\n  importOnlyTargetScope: true, // for performance\n  timeout: 5 * 1000, // 5sec\n  readyTimeout: 5 * 1000, // 5sec\n  readyTimeoutRetries: 2, // 2 retries\n  mediatorPortTimeout: 1 * 1000, // 5sec\n  beaconTimeout: 5 * 1000, // 5sec\n  setupInjectionTimeout: 1000, // 1sec\n  dispatcherStartInterval: 50, // 50ms - insert a wait between dispatcher start events\n  suitesLoaderRetries: 1, // 2 retries\n  windowTarget: '_blank',\n  windowFeatures: 'noopener,noreferrer',\n  mochaOptions: {\n    ui: 'bdd',\n    timeout: 60000,\n    checkLeaks: true,\n    cleanReferencesAfterRun: false, // References must not be cleaned until the proxy reporter completes transferring all events\n    retries: -1,\n  },\n  consoleReporter: 'mochawesome',\n  consoleReporterOptions: {\n    reportDir: './test/mochawesome-report/',\n    //reportFilename: '[status]_[datetime]-[name]-report',\n    autoOpen: false,\n    html: true,\n    json: true,\n    timeout: 5000,\n    consoleReporter: 'list',\n  },\n  coverageOptions: {\n    enabled: true,\n  },\n  get links() {\n    return {\n      mochawesome: new URL(this.consoleReporterOptions.reportDir + 'mochawesome.html', new URL(this._reporterWebRootRelativeToTestConfigPath, import.meta.url)).href,\n      coverage: new URL('coverage/index.html', new URL(this._reporterWebRootRelativeToTestConfigPath, import.meta.url)).href,\n    };\n  },\n  get _pathToChromeExtension() {\n    return this.reportagePackagePath +\n      (this.coverageOptions \u0026\u0026 this.coverageOptions.enabled \u0026\u0026 this._pageType === 'reportage:instrumented' ? 'test/instrumented/' : '') +\n      'extension/chrome';\n  },\n  get puppeteerLaunchOptions() {\n    return {\n      headless: 'new', // 'new' for headless; false for windowed\n      dumpio: false,\n      devtools: false,\n      defaultViewport: { // null for resizable viewport in a windowed mode\n        width: 1280,\n        height: 720,\n        //deviceScaleFactor: 1,\n        //hasTouch: false,\n        //isLandscape: false,\n        //isMobile: false,\n      },\n      args: [\n        '--disable-gpu',\n        //'--enable-logging=stderr',\n        //'--auto-open-devtools-for-tabs',\n        '--disable-ipc-flooding-protection',\n        '--disable-pushstate-throttle',\n        '--disable-background-timer-throttling',\n        '--disable-popup-blocking',\n        `--disable-extensions-except=${Config._pathToChromeExtension}`,\n        `--load-extension=${Config._pathToChromeExtension}`,\n        //'--user-data-dir=/home/t2ym/.config/google-chrome',\n        //'--profile-directory=Profile 1', // Non-puppeteer windows must be closed when a profile is specified\n      ],\n      executablePath: '/usr/bin/google-chrome',\n    };\n  },\n}\nexport default Config;\n```\n\n### Other Configuration Files\n\n#### `resolved-paths.js`\n\n| Features | Supported Configurations       | Notes                                        |\n|:--------:|:-------------------------------|:---------------------------------------------|\n| URL Path | `/node_modules/reportage/resolved-paths.js` | generated at postinstall     |\n\n- `resolved-paths.js` is a naive hack to resolve node module paths for these modules for static [Reporter Server](#reporter-server)\n  - `\"scenarist/Suite.js\"`\n  - `\"mocha/mocha.js\"`\n  - `\"mocha/mocha.css\"`\n  - `\"@esm-bundle/chai/esm/chai.js\"`\n\n#### `nyc.config.mjs`\n\n| Features | Supported Configurations       | Notes                                        |\n|:--------:|:-------------------------------|:---------------------------------------------|\n| Local Path | `nyc.config.mjs` (optional)   | local path to `nyc` configuration     |\n\n#### `nginx.conf`\n\n| Features | Supported Configurations       | Notes                                        |\n|:--------:|:-------------------------------|:---------------------------------------------|\n| Local Path | `nginx.conf` (optional)       | local path to `nginx` configuration     |\n\n### Suites\n\n#### `suites-loader.js`\n\n| Features | Supported Configurations       | Notes                                        |\n|:--------:|:-------------------------------|:---------------------------------------------|\n| Path | any (typically `/test/suites-loader.js`) | `Config._suitesLoaderScriptRelativeToConfig` |\n\n- `suites-loader.js` is configured at `Config._suitesLoaderScriptRelativeToConfig` to set the loader for test suites\n  - it typically loads `scenarist-loader.js` and test suites for scopes\n\n#### `mocha-loader.js`\n\n| Features | Supported Configurations       | Notes                                        |\n|:--------:|:-------------------------------|:---------------------------------------------|\n| Path | `/node_modules/reportage/mocha-loader.js` | loaded by `driver.js` and `reporter.js` |\n\n- `mocha-loader.js`\n  - fetches `mocha/mocha.js` script\n  - patches the source code for `reportage` by\n    - disabling `grep` search parameters\n    - exporting an installer to `sandbox` object\n\n#### `scenarist-loader.js`\n\n| Features | Supported Configurations      | Notes                                        |\n|:--------:|:-------------------------------|:---------------------------------------------|\n| Path | `/node_modules/reportage/scenarist-loader.js` | loaded by `suites-loader.js` |\n\n- `scenarist-loader.js`\n  - imports `sandbox` from `sandbox-global.js`\n  - fetches `scenarist` script version 1.1.10\n  - patches the source code for `reportage`\n    - use `sandbox` to get mocha functions such as `describe`, `it`, etc.\n    - add mocha's `this` argument to call `operation`, `checkpoint`, `setup`, `teardown` calls\n    - add `sandbox` argument to `run` calls\n  - no global `Suite` variable\n- the path is resolved by `resolved-paths.js`\n\n#### `common-suite.js`\n\n| Features | Supported Configurations       | Notes                                        |\n|:--------:|:-------------------------------|:---------------------------------------------|\n| Path | any (typically `/test/common-suite.js`) | loaded by `suites-loader.js` |\n\n- `common-suite.js` or any test suites can define common methods of test classes such as\n  - [`Test Phases`](#test-phases)\n  - utility functions, etc.\n\n#### Test Suites\n\n| Features | Supported Configurations       | Notes                                        |\n|:--------:|:-------------------------------|:---------------------------------------------|\n| Path | any (typically `/test/*-suite.js`) | loaded by `suites-loader.js` |\n\n- `Test Suites` are defined in test classes with `scenarist` UI\n  - they typically load `common-suite.js` and extend test classes\n  - they are loaded by `suites-loader.js`\n\n#### Test Phases\n\n- For non-SPA applications, each test scenario has to handle page navigation\n- `reportage` handles such test scenarios by introducing *Phase* concept\n  - [`vite-lit-ts-app`](examples/vite-lit-ts-app/README.md) example shows how to handle page transitions in a test scenario\n    - transition from `/` to `/external-navi-vite.html` by clicking the link and increment the `phase` number\n- \"Seeing is believing\" in the example project but an awkward explanation follows:\n  - `this.target` in test classes is originally designed for test fixtures\n  - In E2E tests, test fixtures are whole pages in top frames\n  - `this.target` is then reinterpreted as a container for parameters across a single test scenario with page transitions\n  - `this.target.phase` contains the current phase number in a test scenario starting from `0`\n  - `this.target.phase` is incremented before navigation to another page\n    - the trick is to set `this.target.deferredNavigation()` function to be called AFTER the phase finishes for the current mocha session\n      - to keep page navigations from destroying the running mocha test suites\n  - the value of `this.target` object is transferred to `Reporter Page` on navigation as `suiteParameters` object\n  - `Reporter Page` requests the incremented phase of the scenario with the stored `suiteParameters` object\n    - `suiteParameters` can store any clonable objects\n  - the test scenario can perform operations for the current phase and skip those not for the current phase\n\n## ToDos\n\n- [x] screenshot\n- pause-before-replay option for debugging\n- browser support\n- TBD\n\n## License\n\n[BSD-2-Clause](LICENSE.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ft2ym%2Freportage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ft2ym%2Freportage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ft2ym%2Freportage/lists"}