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

casewright

v0.0.5

Published

Generate interactive HTML test documentation right from your Playwright test automation solution

Downloads

429

Readme

Casewright

Generate interactive HTML test documentation right from your Playwright test automation solution. Transform code-level test cases into human-readable documentation.

Casewright works two ways:

  • Reporter mode — runs as a Playwright reporter, enriching the docs with test status, duration, and screenshots.
  • Static modecasewright generate parses your test sources without running anything, producing docs from the source code alone.

Installation

npm install casewright

Quick Start

Reporter mode (with execution data)

Add Casewright as a reporter in your playwright.config.ts:

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

export default defineConfig({
  reporter: [['html'], ['casewright']],
})

Run your tests and documentation will be generated automatically:

npx playwright test

Static mode (no execution)

Generate docs from test sources without running them:

npx casewright generate

Casewright will look for playwright.config.ts in the current directory and use its testDir (or read it from casewright.config.ts if present — see Static mode for details).

Either way, open ./casewright/documentation.html to view your test documentation.

Configuration

Create a configuration file for more control:

npx casewright init

This creates casewright.config.ts:

// casewright.config.ts
import type { CasewrightConfig } from 'casewright'

const config: Partial<CasewrightConfig> = {
  places: {
    // Patterns to identify Page Object Models or containers
    patterns: [/Page$/, /Component$/, /View$/],
    mappings: {
      LoginPage: 'Login Screen',
    },
  },
  elements: {
    mappings: {
      submitBtn: 'Submit Button',
    },
  },
  actions: {
    // Method name → template. Use {target} for the element, {0}, {1} for arguments.
    mappings: {
      click: 'Tap {target}',
      fill: 'Input {0} into {target}',
    },
  },
  steps: {
    // Method/function names to exclude from documentation
    ignoreMethods: ['goto', 'waitForThisPageToLoad'],
  },
  values: {
    // When true, plain identifiers (e.g. `adminEmail`) are statically resolved
    // to their initializer's literal value instead of humanised to "Admin Email".
    // Property-chain resolution (`text.foo.bar`) is always on. Default: false.
    resolveVariables: false,
  },
  // Path to the test directory; only used by `casewright generate`. If unset,
  // generate falls back to reading testDir from playwright.config.ts.
  // testDir: './tests',
  // Path to tsconfig.json for resolving path aliases (@models/*, etc).
  // Defaults to ./tsconfig.json if unset.
  tsConfigPath: './tsconfig.json',
  output: {
    dir: './casewright',
    filename: 'documentation.html',
  },
  title: 'Test Documentation',
}

export default config

Getting maximum value from Casewright

Casewright is an opinionated reporter. To bring the maximum possible value and to correctly convert your Playwright solution into test documentation, it expects (he-he 🥸) that you:

  1. Use folders inside your tests folder as global categories, for example, tests/admin, tests/seller, tests/buyer etc.
  2. Use test.describe as test suites, test as, well, tests, and test.step as test steps. Casewright ignores files as it's usually a strictly technical aspect of solution, not a semantical one.
  3. Try to avoid code outside of test.step. Lines outside of steps will still be parsed and added to documentation but it's gonna feel weird to have steps along with orphaned lines.
  4. Separate semantically important actions from assertions and do not mix them within one custom function. If your POM function signs in and expects to be signed in at the same time, you are doing something wrong. If some actions or assertions are not that important or are strictly technical, it's okay to hide them in this way.
  5. Thoroughly think about your naming convention and try to remain as declarative as you can.
  6. Let the value resolver work for you, or pull values into named consts when it can't. Casewright statically resolves property chains of any depth (dictionary.greetings.welcome"Welcome back" when that JSON/TS value is reachable) and decomposes template literals (`${first} ${last}`"Ada Lovelace"). It does NOT resolve function calls, arithmetic, ternaries, or any expression it cannot reach statically. For those, extract a named const so the call site contains only an identifier:
// ❌ Function call — renders as raw code with a ⚠ marker
await usernameField.fill(randomToken(8) + '_robot')

// ✅ Named const — humanised by default, or resolved with values.resolveVariables
const guestUsername = randomToken(8) + '_robot'
await usernameField.fill(guestUsername)
// → Enter Guest Username in Username Field

Whenever the resolver can't reach a literal value, the affected step shows a small ⚠ next to the text with a tooltip listing the unresolved expression — that tells you exactly which call sites are worth refactoring.

String and number literals are fine as-is — only extract them when the variable name communicates intent better than the raw value.

  1. Remember that the Casewright reporter is still a custom reporter, so it can be called conditionally. The simplest way is to introduce an environment variable to control whether you want to re-create documentation:
// playwright.config.ts
import { defineConfig } from '@playwright/test'

const reporters: any[] = [['html']]

if (process.env.DOCS) {
  reporters.push(['casewright'])
}

export default defineConfig({
  reporter: reporters,
})

For doc-only updates that don't need execution data, npx casewright generate skips the test run entirely.

Features

Automatic Name Conversion

Casewright automatically converts code identifiers to human-readable text:

  • submitButton → "Submit Button"
  • dashboardPage.userProfile → "User Profile on Dashboard Page" (when dashboardPage matches the place patterns)
  • page.goto('/') → "Navigate to /"

Entity Types

Documentation recognizes four entity types:

  1. Places: Page Object Models or containers (matched by patterns like /Page$/)
  2. Elements: Locators on places (e.g., buttons, inputs)
  3. Actions: Playwright API calls or custom functions
  4. Variables: Named values passed to actions

Value Resolution

Argument expressions are resolved to their actual values when Casewright can statically reach them. This applies to property chains of any depth, JSON dictionaries imported as data, and (opt-in) plain identifiers.

Property chains (always on)

const copy = { checkout: { cta: { confirm: 'Place order' } } }
await expect(button).toHaveText(copy.checkout.cta.confirm)
// → Check that Button has text "Place order"

Works through nested object literals, local consts, named imports, and one level of re-exports.

JSON imports

A first-class case for text dictionaries:

// data/i18n.json
{ "greetings": { "welcome": "Welcome back" } }
import i18n from '@data/i18n.json' // or with { type: 'json' } in newer TS
await expect(banner).toHaveText(i18n.greetings.welcome)
// → Check that Banner has text "Welcome back"

Both default and named JSON imports are supported. The file is read, parsed, and walked the same way as TypeScript object literals.

Template literals

Template literals are decomposed: each ${expr} runs through the same resolver, and literal segments are kept verbatim:

const planet = 'Mars'
const habitat = 'colony'
await expect(banner).toHaveText(`${planet} ${habitat}`)
// → Check that Banner has text "Mars colony"   (with resolveVariables: true)
// → Check that Banner has text "Planet Habitat" (default, no marker)

// With a property chain inside the template (always resolved):
const profile = { name: 'Ada' }
await expect(heading).toHaveText(`Welcome, ${profile.name}`)
// → "Welcome, Ada"

Variable resolution (opt-in)

By default plain identifiers humanise to their name (adminEmailAdmin Email). Set values.resolveVariables: true to instead resolve them to their initializer value:

const guestUsername = 'turing42'
await usernameField.fill(guestUsername)
// → Enter "turing42" in Username Field   (resolveVariables: true)
// → Enter Guest Username in Username Field (default)

let and var are resolved on a best-effort basis using their initializer. Reassignment between declaration and call site is NOT detected — refactor to const if accurate output matters for a mutated variable.

Unresolved markers (⚠)

When Casewright attempts to resolve an expression and fails (parameter root, computed property key, function-call leaf, spread in target path, etc.), it falls back to the humanised name and adds a small ⚠ marker to the affected step. Hover the marker for a tooltip listing exactly which expressions failed to resolve. This is your map for "where could the docs be richer if I refactored this call site?"

TypeScript Path Alias Support

Casewright resolves TypeScript path aliases (e.g. @models/*, @utils/*) when searching for @overwrite, @ignore, and @expectation JSDoc tags across files, and when resolving values through imports. By default it picks up ./tsconfig.json from the current working directory; set tsConfigPath in your config to point at a different file.

Without a tsconfig, only relative imports (./, ../) are followed.

Custom Function Documentation

Document custom functions with the @overwrite JSDoc tag:

/**
 * @overwrite Check that cards are sorted in {order} order
 */
async function assertCardSorting(page: Page, order: 'asc' | 'desc') {
  // implementation
}

The {order} placeholder is replaced with the argument at the call site, following the same identifier/property/JSON resolution rules described above (types preserved on this path, so the ternary placeholder below keeps working).

Ternary Placeholders

Use {param?trueText:falseText} to render different text based on a boolean parameter:

/**
 * @overwrite {accept?Accept:Dismiss} the next confirmation modal
 */
async function processNextModal(page: Page, accept = true) {
  // implementation
}

When accept is truthy, renders as "Accept the next confirmation modal". When falsy, renders as "Dismiss the next confirmation modal".

Ignoring Items

Use the @ignore JSDoc tag to exclude suites, tests, steps, or custom functions from documentation:

/**
 * @ignore
 */
test.describe('Technical setup', () => {
  // this entire suite is hidden from docs
})

/**
 * @ignore
 */
async function internalSetup() {
  // calls to this function won't appear as steps
}

@ignore works on test.describe, test, test.step, and any custom function. When placed on a suite, all tests inside are ignored as well.

Config-Level Ignoring

For methods that should never appear as documentation steps across your entire project, use the steps.ignoreMethods config instead of adding @ignore to each one individually:

// casewright.config.ts
const config: Partial<CasewrightConfig> = {
  steps: {
    ignoreMethods: [
      'goto',
      'waitForThisPageToLoad',
      'waitForLoad',
      'readSetupData',
      'writeTeardownData',
    ],
  },
}

This filters out matching method calls everywhere — page.goto(), homePage.goto(), and standalone readSetupData() alike.

If a method has an @overwrite JSDoc tag, it will still appear in documentation regardless of the ignore list. Explicit JSDoc always takes priority over config-level filtering.

Marking Expectations

Use @expectation to mark a custom function as a verification step. Expectation steps are visually highlighted with a blue accent in the output:

/**
 * @overwrite Verify that user {name} is displayed
 * @expectation
 */
async function assertUserDisplayed(page: Page, name: string) {
  // implementation
}

Expectations are also auto-detected for:

  • Any expect() call
  • addDocumentationLine text starting with "check that"

Documentation Line Helper

Use addDocumentationLine to insert custom text steps that don't correspond to any code:

import { addDocumentationLine } from 'casewright'

test('checkout flow', async ({ page }) => {
  await test.step('Complete purchase', async () => {
    addDocumentationLine('User has items in cart from previous test')
    await checkoutPage.submit()
    addDocumentationLine('Check that order confirmation is displayed')
  })
})

Lines starting with "check that" (case-insensitive) are automatically marked as expectations.

Overwriting Step Documentation

Use overwriteDocumentationLines inside a test.step to replace all auto-detected steps with your own list:

import { overwriteDocumentationLines } from 'casewright'

await test.step('Process complex data', async () => {
  overwriteDocumentationLines([
    'Fetch data from external API',
    'Transform response into table format',
    'Check that all rows are valid',
  ])

  // actual implementation — ignored by Casewright
  const data = await api.fetch()
  const rows = transform(data)
  expect(rows.every((r) => r.valid)).toBe(true)
})

Documentation Screenshots

Use addDocumentationScreenshot to capture annotated screenshots that appear in the generated docs:

import { addDocumentationScreenshot } from 'casewright'

await test.step('Review dashboard', async () => {
  await addDocumentationScreenshot(page, 'dashboard-overview', {
    label: 'Dashboard with metrics',
    highlight: [{ locator: page.getByTestId('metrics-panel') }],
    notes: [
      {
        text: 'Key metrics section',
        target: page.getByTestId('metrics-panel'),
        angle: 270, // note above element
      },
    ],
  })
})

Options:

  • highlight — elements to outline (configurable border width/color/style)
  • notes — text annotations with arrows pointing to elements
  • fullPage — capture the full scrollable page
  • label — display text in documentation (defaults to the name)

In static mode, screenshot calls are still detected at parse time and represented as steps, but no image is captured — the doc displays a placeholder line instead of the screenshot. Use reporter mode to capture actual images.

Static Mode

casewright generate produces documentation from test sources alone, without running Playwright:

npx casewright generate            # uses testDir from casewright.config.ts or playwright.config.ts
npx casewright generate ./tests    # explicit test directory
npx casewright generate -c ./my-config.ts ./tests

testDir resolution (first match wins):

  1. Positional CLI argument (casewright generate ./tests).
  2. testDir field at the root of casewright.config.ts.
  3. Static parse of playwright.config.ts in the current directory. Casewright reads the literal testDir property out of the defineConfig({...}) argument; if absent, falls back to the config file's directory (Playwright's documented default).
  4. If none of the above succeeds, the command exits with a clear error.

If playwright.config.ts sets testDir to a non-literal expression (process.env.X ?? './t', computed value, etc.), Casewright warns and asks you to set testDir explicitly in casewright.config.ts. The Playwright config is never executed.

What static mode omits:

  • Test status badges and the pass/fail/skip summary (no execution data exists).
  • Step durations.
  • Captured screenshots (the placeholder mentioned above is shown instead).
  • The sidebar filter buttons (All / Passed / Failed / Skipped) — meaningless without status.

Everything else — suite hierarchy, test names, step text, expectations, screenshots metadata, the search box, dark/light theme, ⚠ markers — appears identically.

A "Statically generated — no execution data" banner sits below the title so readers can immediately tell the docs lack execution information.

Interactive HTML Output

Generated documentation includes:

  • Collapsible tree view (suites → tests → steps)
  • Search and filter functionality (filters only in reporter mode)
  • Status indicators in reporter mode (passed / failed / skipped)
  • Dark/light theme toggle
  • Duration tracking in reporter mode
  • Print-friendly layout
  • Inline ⚠ markers for unresolved value expressions, with hover tooltips

API

Reporter Options

Pass options directly to the reporter:

;[
  'casewright',
  {
    title: 'My Test Documentation',
    output: {
      dir: './custom-dir',
      filename: 'tests.html',
    },
    places: {
      patterns: [/Page$/, /Screen$/],
    },
    values: {
      resolveVariables: true,
    },
  },
]

Programmatic Usage

Generate HTML from a manually-built model:

import { generateHtml, createDocModel, defaultConfig } from 'casewright'

const model = createDocModel()
// ... populate suites/tests/steps
const html = generateHtml(model, defaultConfig)

Build a model from a list of test files (the static generate command does this internally):

import {
  buildDocModelFromSources,
  generateHtml,
  SourceParser,
  defaultConfig,
} from 'casewright'

const parser = new SourceParser(defaultConfig)
const model = buildDocModelFromSources(
  ['./tests/login.spec.ts', './tests/checkout.spec.ts'],
  './tests',
  parser,
)
const html = generateHtml(model, defaultConfig)

Read testDir from a Playwright config without running anything:

import { readPlaywrightTestDir } from 'casewright'

const resolved = readPlaywrightTestDir(process.cwd())
if (resolved) {
  console.log(resolved.configFile, resolved.testDir, resolved.fromLiteral)
}

CLI Commands

# Create a configuration file
npx casewright init

# Generate documentation statically (no test execution)
npx casewright generate [testDir]
npx casewright generate ./tests --config ./casewright.config.ts

# Show help
npx casewright help

# Show version
npx casewright --version

License

MIT