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

pw-sanitizer

v0.3.0

Published

Post-process Playwright HTML reports and trace files to redact secrets and remove noisy steps

Readme

playwright-sanitizer

Post-process Playwright HTML reports and trace files to redact secrets and remove noisy steps before sharing or archiving.


Why

Playwright's HTML reports and .zip trace files can contain sensitive data — auth headers, API keys, tokens, passwords — embedded in network request logs, action parameters, and step metadata. playwright-sanitizer scans those files after your test run and replaces or strips the sensitive content so they are safe to store in CI artifacts, share with teammates, or upload to a dashboard.

It also lets you delete repetitive or internal steps (e.g. health-check pings, internal logging actions) that create noise in the trace viewer.


Features

  • Redact secrets in HTML reports and trace .zip files by key name, value pattern, or both
  • Remove entire steps from traces with configurable timestamp repair
  • Three output modes: copy (safe default), in-place, side-by-side
  • Zero built-in patterns — you declare exactly what gets touched, nothing else runs
  • Dry-run mode to preview changes without writing files
  • Integrates as a Playwright reporter (pw-sanitizer/reporter) so it runs automatically after every test run — after the HTML report is written
  • Programmatic API for custom pipelines
  • Summary table printed to the console (and optionally written as JSON)

Supported Playwright formats

As of v0.2.0, the processors work against the artifact formats real Playwright versions actually produce (verified against @playwright/test 1.40–1.61):

  • Trace .zip files: every *.trace entry (test.trace, 0-trace.trace, ...) is processed as NDJSON — one JSON event per line, with steps stored as paired "before" / "after" events linked by callId/parentId. *.network entries are handled the same way, and resources/ files are redacted. Removal rules match against the "before" event titles (title, or apiName on older versions); descendants are found by walking the full parentId tree, including nested test.step and pw:api children. Archives with a legacy trace.json are still supported as a fallback.
  • HTML reports: the report data embedded as a base64-encoded zip (window.playwrightReportBase64 = "..." on older versions, <template id="playwrightReportBase64"> on newer ones) is decoded, its report.json and per-test-file JSON shards are sanitized (step trees pruned per orphanStrategy), then re-zipped and substituted back into the HTML. The pre-base64 window.__pw_report_data__ format is still supported as a fallback.

Integration tests run against genuine artifacts generated by real Playwright runs (see tests/fixtures/real/README.md).


Requirements

  • Node.js >= 18

Installation

npm install --save-dev pw-sanitizer

Screenshot redaction inside trace files requires the optional sharp dependency:

npm install --save-dev sharp

Quick start

1. Create a config file

// playwright-sanitizer.config.ts
import type { SanitizerConfig } from 'pw-sanitizer';

const config: SanitizerConfig = {
  redact: {
    patterns: [
      {
        id: 'auth-header',
        description: 'Authorization header value',
        key: 'authorization',
        severity: 'critical',
      },
      {
        id: 'api-key',
        key: /x-api-key/i,
        severity: 'high',
      },
    ],
  },
  remove: {
    rules: [
      {
        label: 'health-check pings',
        url: /\/health$/,
      },
    ],
  },
};

export default config;

2. Run the CLI

npx pw-sanitizer

Sanitized files are written to ./sanitized-report by default. Originals are untouched.


Configuration

Config is auto-discovered in this priority order:

| Priority | Source | |----------|--------| | 1 | --config <path> CLI flag | | 2 | playwright-sanitizer.config.ts | | 3 | playwright-sanitizer.config.js | | 4 | playwright-sanitizer.config.json | | 5 | sanitizer key inside playwright.config.ts |

Full config reference

[!NOTE] TypeScript configuration files (.ts) are supported natively by default using jiti. No separate runner or transpilation step is required.

import type { SanitizerConfig } from 'pw-sanitizer';

const config: SanitizerConfig = {
  redact: {
    // Inline patterns (merged with patternFiles)
    patterns: [
      {
        id: 'my-token',           // REQUIRED — unique identifier shown in summary
        description: 'API token', // optional, shown in summary output
        key: 'x-api-token',       // match field/header name (string = case-insensitive exact match)
        valuePattern: /^ey/,      // match field value (RegExp) — when set alongside key, BOTH must match
        severity: 'high',         // 'low' | 'medium' | 'high' | 'critical'  (informational only)
      },
    ],

    // External pattern files (.ts, .js, or .json) — merged with inline patterns
    patternFiles: ['./secrets/patterns.ts'],

    // Replacement string. Default: '[REDACTED]'
    placeholder: '[REDACTED]',

    // Partial redaction: keep first N and last N characters, mask the middle with '***'
    // Example: { prefix: 4, suffix: 4 } on 'Bearer eyJhbGci...' => 'Bear***..ci'
    // Takes priority over placeholder when set.
    partialRedaction: { prefix: 4, suffix: 4 },
  },

  remove: {
    // Inline rules (merged with ruleFiles)
    rules: [
      {
        label: 'noisy health checks',  // REQUIRED — shown in summary and dry-run log
        stepName: /health/i,           // match step name shown in the HTML report
        selector: '[data-testid="spinner"]', // match CSS selector or XPath locator
        url: /\/internal\//,           // match network request URL
        actionType: 'waitForTimeout',  // match Playwright internal action type

        // Safety guard: only remove if this step appears at least N times
        // consecutively within a test. Logs a warning and skips removal if not met.
        minConsecutiveOccurrences: 3,
      },
    ],

    // External rule files (.ts, .js, or .json) — merged with inline rules
    ruleFiles: ['./rules/remove-rules.ts'],

    // How to handle time after a step is deleted.
    // 'absorb-into-prev' (default): preceding step absorbs the deleted duration
    // 'absorb-into-next': following step's start time shifts back
    // 'gap': no adjustment; a gap appears in the timeline
    timestampStrategy: 'absorb-into-prev',

    // What to do with child steps when a parent is removed.
    // 'remove-children' (default): children are also removed
    // 'keep-shell': parent is kept as an empty container
    orphanStrategy: 'remove-children',

    // Preview mode — log what would be removed but don't write any files
    dryRun: false,
  },

  output: {
    reportDir: './playwright-report', // where to find HTML reports. Default: './playwright-report'

    // Where to find trace zips — string or array. Default: './test-results'
    // RECOMMENDED: include the report's data/ folder too. The HTML report's
    // built-in trace viewer opens the trace COPIES under playwright-report/data,
    // so sanitizing test-results alone leaves those copies dirty.
    // Overlapping matches are deduplicated by absolute path.
    traceDir: ['./test-results', './playwright-report/data'],

    // Output mode:
    // 'copy' (default): write sanitized files to dir, originals untouched
    // 'in-place': overwrite original files (ensure they are version-controlled!)
    // 'side-by-side': write '<name>.sanitized.<ext>' next to each original
    mode: 'copy',

    dir: './sanitized-report', // destination for 'copy' mode. Default: './sanitized-report'

    processReports: true,        // set to false to skip HTML report processing
    processTraces: true,         // set to false to skip trace file processing
    redactScreenshots: false,    // redact screenshots in traces (requires 'sharp')
  },

  reporting: {
    summary: true,                              // print summary table after processing. Default: true
    summaryFile: './sanitization-summary.json', // optional — write summary as JSON to this path
    logLevel: 'normal',                         // 'silent' | 'normal' | 'verbose'
  },
};

export default config;

External pattern and rule files

For large teams, keep patterns and rules in dedicated files so they can be versioned and shared across projects:

// secrets/patterns.ts
import type { RedactPattern } from 'pw-sanitizer';

const patterns: RedactPattern[] = [
  { id: 'auth-header', key: 'authorization', severity: 'critical' },
  { id: 'cookie',      key: 'cookie',        severity: 'high' },
  { id: 'set-cookie',  key: 'set-cookie',    severity: 'high' },
];

export default patterns;
// playwright-sanitizer.config.ts
export default {
  redact: { patternFiles: ['./secrets/patterns.ts'] },
};

JSON files are also supported. Note that RegExp is not available in JSON — strings are matched as case-insensitive exact key names:

[
  { "id": "auth-header", "key": "authorization", "severity": "critical" },
  { "id": "api-key",     "key": "x-api-key",     "severity": "high" }
]

CLI reference

Usage: pw-sanitizer [options]

Options:
  -c, --config <path>          Path to config file
  -r, --report <path>          HTML report directory (default: "./playwright-report")
  -t, --traces <path...>       Trace directory — repeatable (default: "./test-results")
  -o, --output <path>          Output directory (copy mode)
      --in-place               Overwrite original files
      --patterns <path...>     One or more pattern files
      --placeholder <string>   Redaction placeholder (default: "[REDACTED]")
      --dry-run                Log changes without writing files
      --no-traces              Skip trace file processing
      --no-reports             Skip HTML report processing
      --summary-output <path>  Write JSON summary to file
      --log-level <level>      silent | normal | verbose (default: "normal")
  -V, --version                Display version number
  -h, --help                   Display help

Precedence is strictly CLI flag > config file > built-in default. A flag only overrides its config-file counterpart when it is actually passed on the command line.

Examples

# Auto-discover config and run with defaults
npx pw-sanitizer

# Use a specific config file
npx pw-sanitizer --config ./ci/sanitizer.config.ts

# Overwrite originals (ensure files are version-controlled)
npx pw-sanitizer --in-place

# Preview without writing any files
npx pw-sanitizer --dry-run --log-level verbose

# Point to non-default directories
npx pw-sanitizer --report ./reports --traces ./artifacts/traces --output ./sanitized

# Cover both trace locations (test-results + the report's data/ copies)
npx pw-sanitizer --traces ./test-results ./playwright-report/data --in-place

# Write a machine-readable summary for CI artifact ingestion
npx pw-sanitizer --summary-output ./sanitization-summary.json

Playwright reporter integration (recommended)

Register the sanitizer as a Playwright reporter and it runs automatically after every test run:

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  reporter: [
    ['html'],
    ['pw-sanitizer/reporter'], // list last by convention
  ],
  // ...rest of your config
});

This is the right hook for sanitizing HTML reports. Playwright's end-of-run order is:

  1. tests finish
  2. globalTeardown runs
  3. reporter.onEnd() for each reporter — this is where the HTML report and the playwright-report/data/*.zip trace copies get written
  4. reporter.onExit() for each reporter — where pw-sanitizer/reporter runs

Because all onEnd() calls complete before any onExit(), correctness does not depend on reporter order — listing the sanitizer last is just a readability convention. The reporter never prints to stdio, and sanitizer failures are caught and logged so they can never mask your test results.

Options: pass configPath to skip auto-discovery — ['pw-sanitizer/reporter', { configPath: './ci/sanitizer.config.ts' }].

[!TIP] Pair this with traceDir: ['./test-results', './playwright-report/data'] so the trace copies that the report's built-in trace viewer opens get sanitized too.

globalTeardown integration (trace-only)

[!WARNING] Playwright runs globalTeardown before reporters write their output. When this hook fires, the current run's HTML report and its data/ trace copies do not exist yet — at best you would sanitize the previous run's report. Only use this integration when you exclusively sanitize test-results traces; otherwise use pw-sanitizer/reporter above.

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  globalTeardown: require.resolve('pw-sanitizer/teardown'),
  // ...rest of your config
});

Config is auto-discovered as described above. Teardown failures are caught and logged — they will never mask your actual test results.

Embedding config in playwright.config.ts

If you prefer a single config file, add a sanitizer key directly to your Playwright config:

// playwright.config.ts
import { defineConfig } from '@playwright/test';
import type { SanitizerConfig } from 'pw-sanitizer';

export default defineConfig({
  reporter: [['html'], ['pw-sanitizer/reporter']],

  sanitizer: {
    redact: {
      patterns: [
        { id: 'auth', key: 'authorization', severity: 'critical' },
      ],
    },
    output: {
      mode: 'in-place',
      traceDir: ['./test-results', './playwright-report/data'],
    },
  } satisfies SanitizerConfig,
});

Programmatic API

import { sanitize, redactReport, redactTrace } from 'pw-sanitizer';

// Process all reports and traces using auto-discovered config
const results = await sanitize();

// Pass a config object directly (skips file discovery)
const results = await sanitize({
  redact: { patterns: [{ id: 'token', key: 'x-token' }] },
  output: { mode: 'copy', dir: './out' },
});

// Process a single HTML report
const result = await redactReport('./playwright-report/index.html', config);

// Process a single trace zip
const result = await redactTrace('./test-results/my-test/trace.zip', config);

Each call returns a ProcessResult (or an array of them for sanitize()):

interface ProcessResult {
  file: string;
  redactionsApplied: number;
  stepsRemoved: number;
  timestampRepairs: number;
  redactionMatches: Array<{ keyPath: string; patternId: string }>;
  removalMatches: Array<{ index: number; ruleLabel: string; event: TraceEvent }>;
}

Output modes

| Mode | Behaviour | |------|-----------| | copy (default) | Writes sanitized files to output.dir (./sanitized-report). Originals are never touched. | | in-place | Overwrites original files. A warning is printed. Use only with version control. | | side-by-side | Creates <name>.sanitized.<ext> next to each original file. |


How redaction works

Redaction walks every JSON structure inside the HTML report's embedded data and the trace file's event entries. For each field encountered, the declared patterns are checked:

  1. If key is a string — matched case-insensitively as an exact field name.
  2. If key is a RegExp — tested against the field name.
  3. If valuePattern is provided — both key and valuePattern must match (AND logic).
  4. The matched value is replaced with placeholder, or a partial redaction if partialRedaction is configured.

No built-in patterns are ever applied. Only what you declare runs.


How step removal works

Step removal operates on the trace event tree inside each .zip file:

  1. Each event is tested against your declared rules (stepName, selector, url, actionType).
  2. Matching events are collected and removed from the event list.
  3. Timestamps of surrounding events are adjusted according to timestampStrategy to keep the timeline coherent.
  4. Child steps of removed parents are handled according to orphanStrategy.

The minConsecutiveOccurrences safety guard prevents accidentally removing a step that only appears occasionally — if the actual consecutive count is below the threshold, the step is not removed and a warning is logged instead.


CI integration example

# .github/workflows/test.yml
- name: Run Playwright tests
  run: npx playwright test

- name: Sanitize Playwright artifacts
  run: npx pw-sanitizer --summary-output sanitization-summary.json

- name: Upload sanitized report
  uses: actions/upload-artifact@v4
  with:
    name: playwright-report
    path: sanitized-report/

License

MIT