npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@watershed-labs/ferret

v0.1.0

Published

CDP-powered rolling-buffer test recorder for Selenium and Cucumber

Readme

@watershed-labs/ferret

Failure Event Recorder & Report Emitter for Tests is a CDP-powered rolling-buffer test recorder for Selenium and Cucumber. When a scenario passes the capture buffers are silently flushed. When a scenario fails the buffers are written to a self-contained HTML report that requires no server, no viewer application, and no external dependencies.


Why Ferret

What existing tools cannot do

Playwright Trace Viewer is tied to the Playwright runner. Teams using Selenium cannot use it — the W3C WebDriver protocol that Selenium is built on is HTTP-based and synchronous, and fundamentally cannot stream the bidirectional CDP events that trace generation requires. Migrating to gain access to the viewer means rewriting the entire test suite. Reports are .zip archives that require either the npx playwright show-trace command or upload to trace.playwright.dev to render, because the viewer depends on ServiceWorkers which are disabled under the file:/// protocol. The viewer does correlate network events and DOM snapshots per action, but that capability is inseparable from the Playwright runner — it is not available to Selenium users under any configuration.

Cypress Test Replay is locked to the Cypress ecosystem and requires a paid account for CI usage beyond the free tier. It operates as a cloud service, which means all captured traffic — including request bodies and console output — leaves the network perimeter. This makes it categorically unavailable to organizations with data residency or compliance requirements.

Replay.io requires replacing the browser binary entirely. The infrastructure ask is prohibitive for teams running existing Selenium grids, and it also ships data to external servers.

None of the above produce a portable, single-file report that a developer can open directly from a CI artifact download, a Slack message, or an email attachment.

What Ferret does differently

Capture happens at the Chrome DevTools Protocol layer. CSP headers cannot block it and the application cannot overwrite the interceptors. CDP listeners survive page navigation automatically, so React Router, Angular Router, SvelteKit, Vue Router, and any other pushState-based framework require no special handling.

Every captured network request and console entry carries the index of the Cucumber step that was executing when it was recorded. The report groups events by step so the reader sees the causal chain rather than a flat chronological list.

Failures are classified by Selenium error type before the report is written. The error banner at the top of the report shows the type, a plain-language description, and — for assertion failures — the expected and actual values side by side.

The output is a single .html file with all data embedded. Nothing is transmitted externally.


Requirements

  • Node.js 18 or later
  • selenium-webdriver 4.x
  • @cucumber/cucumber 9.x or later
  • Chrome or Chromium (CDP is required; a console-only fallback runs on other browsers)

Installation

npm install --save-dev @watershed-labs/ferret

Setup

Constructor

TestRecorder accepts an optional driver and an optional partial config. Both can be omitted — package defaults apply and the driver can be provided later via setDriver().

// No arguments — defaults apply, driver must be set before start()
const recorder = new TestRecorder();

// Driver only — recommended when you have a module-level driver instance
const recorder = new TestRecorder(driver);

// Driver and partial config overrides
const recorder = new TestRecorder(driver, { outputDir: './my-reports' });

// Config only — driver provided later
const recorder = new TestRecorder(null, { consoleMinLevel: 'error' });

If no driver is resolved when start() is called, Ferret throws with a clear message indicating which resolution options are available.

Cucumber — manual hooks (recommended)

Wire the recorder directly into your own Before and After hooks. This keeps the lifecycle explicit and co-located with your world setup.

import { TestRecorder, classify } from '@watershed-labs/ferret';
import { driver } from './support/driver.js';

const recorder = new TestRecorder(driver);

Before(async function (scenario) {
    await recorder.start();
    recorder.reset(scenario.pickle.name);
});

After(async function (scenario) {
    const failed = scenario.result?.status === 'FAILED';
    const classification = classify(scenario.result?.exception ?? null);
    await recorder.teardown(scenario.pickle.name, failed, scenario.result?.exception, classification);
    await recorder.stop();
});

Cucumber — automatic hooks

If you prefer zero boilerplate, import the pre-built integration. It registers BeforeAll, AfterAll, Before, After, BeforeStep, and AfterStep automatically and uses the singleton recorder from the package.

In cucumber.js:

export default {
    default:
        '--import node_modules/@watershed-labs/ferret/integrations/cucumber.js --import support/**/*.js features/**/*.feature'
};

Or in your support index:

import '@watershed-labs/ferret/integrations/cucumber';

Provide the driver by setting getDriver in a local recorder.config.js:

export default {
    getDriver: () => global.driver
};

Other runners

The TestRecorder class has no dependency on Cucumber. Drive it from any hook system:

import { TestRecorder, classify } from '@watershed-labs/ferret';

const recorder = new TestRecorder(driver);

// Before each test
await recorder.start();
recorder.reset('My test name');

// After each test
const classification = classify(caughtError ?? null);
await recorder.teardown('My test name', didFail, caughtError, classification);
await recorder.stop();

Configuration reference

All options are optional. Any value omitted falls back to the package default.

// recorder.config.js
export default {
    outputDir: './ferret-reports',
    alwaysDump: false,
    verbose: true,

    // Driver factory — used by the singleton and the automatic Cucumber integration.
    // Not needed when passing a driver directly to new TestRecorder(driver).
    getDriver: () => global.driver,

    // Network
    captureNetwork: true,
    maxNetworkEntries: 100,
    captureResponseBody: true,
    maxBodyBytes: 4096,

    // Global URI filters — strings (substring match) or RegExp
    networkAllowList: [], // empty = capture all
    networkDenyList: [/analytics/, '/healthcheck'],

    // Step-scoped URI filters
    // When a rule matches the current step type, its allowUrls replaces
    // the global networkAllowList for that step only.
    // The global networkDenyList still applies on top.
    //
    // stepTypes: 'given' | 'when' | 'then' | 'any'
    stepNetworkFilters: [
        { stepTypes: ['then'], allowUrls: ['/api/order', '/api/basket'] },
        { stepTypes: ['given'], allowUrls: [] }
    ],

    // Console
    captureConsole: true,
    maxConsoleEntries: 300,
    // 'verbose' | 'debug' | 'log' | 'info' | 'warning' | 'error'
    consoleMinLevel: 'warning',
    consoleDenySources: []
};

Report structure

Error banner Displayed at the top of every failure report. Shows the classified error type (ELEMENT_NOT_FOUND, ASSERTION, TIMEOUT, STALE_ELEMENT, CLICK_INTERCEPTED, and others), a plain-language description, and — for assertion failures — the expected and actual values side by side.

Steps tab (default view) Every Cucumber step shown in order with status and duration. The failed step is expanded automatically. Each step shows only the network requests and console entries that occurred while it was executing. Clicking a network entry jumps to the full Network tab with that URL pre-filtered.

Network tab All captured requests in chronological order, filterable by status class and searchable by URL. Each row shows method, status, URL, duration, and the step index. Expanding a row shows request and response bodies, pretty-printed if the content is JSON.

Console tab Captured console entries filterable by level. Expanding an error or warning entry shows the full stack trace. Internal navigation markers (SPA route changes, full page loads) are shown in the step timeline, not here.


URI filtering

The allow and deny lists accept strings (substring match) or RegExp objects.

When networkAllowList is non-empty, only matching URLs are captured. The deny list is always applied afterwards and can suppress any URL regardless of allow list membership.

stepNetworkFilters adds per-step-type rules on top. When a rule matches the current step type, its allowUrls replaces the global networkAllowList for that step only. The global networkDenyList still applies. A rule with an empty allowUrls array captures nothing during that step type.

Steps with no matching rule fall back to the global networkAllowList.


Console level filtering

The consoleMinLevel setting uses a ranked hierarchy. Setting it to 'warning' captures warnings and errors only and discards everything below. The hierarchy from lowest to highest is:

verbose  debug  log  info  warning  error

This is the recommended first configuration change for teams whose applications have verbose console.log output in their development builds.


CI integration

The report path is attached to the Cucumber scenario via this.attach() so any Cucumber formatter that reads attachments — Allure, html-reporter, custom formatters — will surface it automatically.

The path is also printed to stdout for CI log visibility. To preserve reports as build artifacts in GitHub Actions:

- uses: actions/upload-artifact@v4
  if: failure()
  with:
      name: ferret-reports
      path: ferret-reports/

Roadmap

  • Multi-tab and cross-origin iframe capture via CDP multi-target attachment
  • PII redaction via configurable field patterns applied to request and response bodies before the report is written

Directory tree

@watershed-labs/ferret
├── src/
│   ├── buffer.js          CircularBuffer — bounded FIFO, no dependencies
│   ├── classifier.js      Maps Selenium exceptions to typed ErrorClassification
│   ├── filters.js         URL allow/deny logic and console level gating
│   ├── recorder.js        TestRecorder class — CDP attachment, step markers, teardown
│   └── writer.js          Reads report-template.html, writes the final HTML file
├── integrations/
│   └── cucumber.js        Before/After/BeforeStep/AfterStep hook registration
├── index.js               Public entry point — exports recorder, TestRecorder, classify
├── types.js               JSDoc @typedef declarations shared across the project
├── recorder.config.js     Default configuration with inline documentation
├── console-agent.js       Browser-injected IIFE fallback for non-Chrome browsers
├── report-template.html   Self-contained HTML report viewer
├── eslint.config.js       ESLint flat config — recommended + sorted imports
├── package.json
└── README.md