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

@mechris3/glassbox

v0.1.48

Published

Debugger-style browser automation test runner with live execution control via CDP

Readme

@mechris3/glassbox

Debugger-style browser automation test runner using Chrome DevTools Protocol (CDP) directly. No Puppeteer, no Playwright — just raw WebSocket + JSON.

Features:

  • Live execution control: breakpoints, pause/resume, step-through
  • Visual dashboard with real-time action streaming
  • Headless batch runner for CI
  • Diagnostic data collection (console errors, network, performance)
  • MCP server for AI agent integration
  • Browser and profile auto-detection

Quick Start

# Install
npm install @mechris3/glassbox

# Scaffold a project (creates glassbox.config.ts + directories)
npx glassbox init

# Write journeys and page objects (see below), then:
npx glassbox serve     # Dashboard UI at http://localhost:3001
npx glassbox run       # Headless batch run (for CI)

glassbox init creates this structure in your current directory:

my-tests/
├── package.json          ← dependency + scripts
├── glassbox.config.ts    ← created by init
├── journeys/             ← put your journey files here
└── page-objects/         ← put your page objects here

The generated package.json includes convenience scripts:

npm run serve        # Start the visual dashboard at http://localhost:3001
npm run run          # Run all journeys headless (exit 0 = pass, 1 = fail)
npm run run:headed   # Run with a visible browser window

After running glassbox init, install dependencies and you're ready:

npm install
npm run serve

You can set this up as a standalone project or as a subfolder within an existing frontend project:

# Standalone
my-e2e-tests/
├── package.json
├── glassbox.config.ts
├── journeys/
└── page-objects/

# Inside an existing project
my-app/
├── src/
├── package.json
└── e2e/
    ├── glassbox.config.ts
    ├── journeys/
    └── page-objects/

Either way, run glassbox commands from the directory containing your glassbox.config.ts.

Prerequisites

  • Node.js 20+
  • A Chromium-based browser (Chrome, Brave, Edge)

Environment Variables

Config files, hooks, journeys, and page objects are all TypeScript running in Node — process.env works everywhere:

// page-objects/login.page.ts — credentials from env
async login() {
  const user = process.env.TEST_USERNAME ?? '[email protected]';
  const pass = process.env.TEST_PASSWORD ?? 'password123';
  await this.fill('[data-testid="email"]', user);
  await this.fill('[data-testid="password"]', pass);
  await this.click('[data-testid="submit"]');
}

// glassbox.config.ts — headless in CI
browser: { headless: process.env.CI === 'true' }

// helpers/before-each.ts — API key for test data service
const apiKey = process.env.TEST_API_KEY;
await fetch('http://...', { headers: { Authorization: `Bearer ${apiKey}` } });

Consumer Usage

Page Objects

Extend BasePage — no constructor needed. The following methods are available via this:

| Method | Description | |--------|-------------| | Interaction | | | click(selector) | Click an element | | hover(selector) | Hover over an element | | focus(selector) | Focus an element | | pressKey(key) | Press a key ('Enter', 'Tab', 'Escape', etc.) | | fill(selector, value) | Clear and set an input's value | | type(selector, value, {delay?}) | Type character by character | | uploadFile(selector, filePath) | Upload a file to a file input | | selectByIndex(selector, index) | Select dropdown option by index | | selectByValue(selector, value) | Select dropdown option by value | | selectByText(selector, text) | Select dropdown option by visible text | | Queries | | | getText(selector) | Get element's text content | | getInputValue(selector) | Get input's current value | | getAttribute(selector, attr) | Get an attribute value | | countElements(selector) | Count matching elements | | isVisible(selector) | Check if element is visible | | isDisabled(selector) | Check if element is disabled | | Waiting | | | waitForSelector(selector, {timeout?}) | Wait for element to appear | | waitForHidden(selector, {timeout?}) | Wait for element to disappear | | waitForText(selector, expected, {timeout?}) | Wait until text content matches | | waitForCount(selector, expected, {timeout?}) | Wait until element count matches | | waitForTimeout(ms) | Fixed delay (use sparingly) | | Navigation | | | goto(url) | Navigate (relative URLs resolve against target) | | clickAndWaitForNavigation(selector) | Click and wait for page load | | getCurrentUrl() | Get the current page URL | | Advanced | | | evaluate(script) | Execute arbitrary JS in the page | | clearSession(origin?) | Clear cookies and storage | | readClipboard() | Read text from clipboard |

Example:

// page-objects/login.page.ts
import { BasePage } from '@mechris3/glassbox';

export class LoginPage extends BasePage {
  private selectors = {
    username: '[data-testid="username"]',
    password: '[data-testid="password"]',
    submit: '[data-testid="submit"]',
  };

  async login(user: string, pass: string) {
    await this.fill(this.selectors.username, user);
    await this.fill(this.selectors.password, pass);
    await this.click(this.selectors.submit);
  }

  async verifyLoggedIn(expectedName: string) {
    const name = await this.getText('[data-testid="display-name"]');
    if (name !== expectedName) throw new Error(`Expected "${expectedName}", got "${name}"`);
  }
}

Journeys

Extend Journey — use this.page() to create page objects:

// journeys/login.journey.ts
import { Journey } from '@mechris3/glassbox';
import { LoginPage } from '../page-objects/login.page';

export class LoginJourney extends Journey {
  loginPage = this.page(LoginPage);

  async execute() {
    await this.loginPage.login('testuser', 'password123');
    await this.loginPage.verifyLoggedIn('testuser');
  }
}

Config

All paths in the config are relative to the config file's directory:

my-tests/
├── glassbox.config.ts
├── journeys/
│   └── login.journey.ts
├── page-objects/
│   └── login.page.ts
└── helpers/
    └── before-each.ts
// glassbox.config.ts
import { defineConfig } from '@mechris3/glassbox';

export default defineConfig({
  journeys: './journeys/**/*.journey.ts',
  browser: {
    defaultViewport: { width: 1280, height: 720 },
    headless: false,
  },
  server: { port: 3001 },
  testData: {
    beforeEach: './helpers/before-each.ts',
  },
});

Best Practices

Journeys are pure orchestration

A journey's execute() method should contain only calls to page object methods — no conditionals, no assertions, no direct selectors:

// Good — reads like a user story
async execute() {
  await this.loginPage.loginAs('[email protected]', 'alice123');
  await this.dashboardPage.verifyLoaded();
  await this.dashboardPage.navigateToProjects();
  await this.projectsPage.verifyProjectCount(6);
}

// Bad — logic leaking into journey
async execute() {
  await this.fill('[data-testid="email"]', '[email protected]');
  const count = await this.getText('[data-testid="count"]');
  if (parseInt(count) !== 6) throw new Error('wrong');
}

Page objects own everything

  • Selectors — store in a private selectors object, never expose them
  • InteractionloginAs(), addTask(), filterByStatus() — intention-revealing names
  • ValidationverifyLoaded(), verifyErrorMessage(), verifyTaskCount() — throw descriptive errors on failure
export class ProjectsPage extends BasePage {
  private selectors = {
    projectCard: '[data-testid="project-card"]',
    filterActive: '[data-testid="filter-active"]',
    count: '[data-testid="project-count"]',
  };

  async filterByActive() {
    await this.click(this.selectors.filterActive);
  }

  async verifyProjectCount(expected: number) {
    const count = await this.getText(this.selectors.count);
    if (count !== String(expected)) {
      throw new Error(`Expected ${expected} projects, got ${count}`);
    }
  }
}

Selector strategy

  • Always use [data-testid="..."] — never CSS classes, tag names, or DOM structure
  • For repeated items, include an identifier: [data-testid="task-item-${id}"]
  • If an element doesn't have a data-testid, add one to your app first

File organisation

glassbox/
├── page-objects/
│   ├── login.page.ts        ← one class per file
│   ├── dashboard.page.ts
│   └── projects.page.ts
├── journeys/
│   ├── login-and-browse.journey.ts
│   └── create-task.journey.ts
└── helpers/
    └── before-each.ts

Lifecycle Hooks

Run code before/after journeys for test data setup and cleanup:

// glassbox.config.ts
export default defineConfig({
  hooks: {
    globalSetup: './helpers/global-setup.ts',    // Once before all journeys
    beforeEach: './helpers/before-each.ts',       // Before each journey
    afterEach: './helpers/after-each.ts',         // After each journey (even on failure)
    globalTeardown: './helpers/global-teardown.ts', // Once after all journeys
  },
});

Hook files export a default async function. Use them for any setup/teardown your tests need — resetting databases, seeding test data, clearing caches, etc:

// helpers/before-each.ts
import type { HookContext } from '@mechris3/glassbox';

export default async function(context: HookContext) {
  // Call your test API to reset state (can be any URL, not necessarily the target app)
  await fetch('http://localhost:4000/api/test-data/reset', { method: 'POST' });

  // Or seed specific data
  await fetch('http://localhost:4000/api/test-data/seed', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ users: ['testuser'], projects: 3 }),
  });
}

targetUrl comes from the dashboard settings panel (the URL field) or the --url flag when using the CLI. It's the URL of the app you're testing.

HookContext contains:

| Property | Available in | Description | |----------|-------------|-------------| | targetUrl | All hooks | The app URL being tested | | journeyName | beforeEach, afterEach | Display name of the current journey | | journeyPath | beforeEach, afterEach | File path of the current journey | | journeyStatus | afterEach only | 'passed' or 'failed' |

CLI Commands

Run all commands from the directory containing your glassbox.config.ts:

cd my-tests/   # where your glassbox.config.ts lives

Dashboard (visual UI):

npx glassbox serve         # Start dashboard at http://localhost:3001

Open the URL in your browser to see journeys, run them, set breakpoints, pause/step, and inspect results visually.

Headless runner (CI / terminal):

npx glassbox run                          # Run all journeys, exit 0/1
npx glassbox run login                    # Only journeys matching "login"
npx glassbox run --url http://localhost:3000  # Specify app URL
npx glassbox run --headed                 # Show the browser while running
npx glassbox run --no-fail-fast           # Don't stop at first failure

Other:

npx glassbox init          # Scaffold config + directories in current dir
npx glassbox mcp           # Start MCP server for AI agent integration

MCP Server (AI Integration)

The MCP server lets AI agents (Kiro, Claude) debug failed journeys by querying diagnostic data.

Add to your .kiro/settings/mcp.json:

{
  "mcpServers": {
    "glassbox": {
      "command": "npx",
      "args": ["glassbox", "mcp"],
      "env": { "GLASSBOX_URL": "http://localhost:3001" },
      "cwd": "/absolute/path/to/your/glassbox-tests"
    }
  }
}

cwd must be an absolute path to the directory containing your glassbox.config.ts and node_modules/. Kiro does not resolve relative paths in MCP configs.

Requires glassbox serve to be running. The AI gets these tools:

| Tool | Purpose | |------|---------| | getDiagnosticSummary | Summary of diagnostic counts (call first) | | getConsoleErrors | console.error/warn with stack traces | | getExceptions | Unhandled JS exceptions with source locations | | getNetworkFailures | Failed HTTP requests (4xx/5xx/network errors) | | getNetworkLog | All XHR/Fetch requests with status codes | | getTimeline | Chronological event stream | | getPerformanceMetrics | Heap size, DOM nodes, layout recalculations | | listJourneys | Available journey files | | runJourney | Run by name/ID (browser stays open on failure) | | getDomSnapshot | Inspect page HTML or a specific element | | getScreenshot | Capture page screenshot (base64 PNG) | | getAccessibilityTree | Full ARIA tree for understanding page structure | | resetDiagnostics | Clear data + close live session before re-run |

The AI can identify journeys by name ("login"), ID ("login-and-add-todos"), or full path. After a failure, the browser stays open so getDomSnapshot and getScreenshot can inspect the failed state.

License

Proprietary. All rights reserved.