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

@zombieland/tallahassee

v0.1.1

Published

A browser module around JSDOM for testing a web application as opposed to a document. Navigation with headers, cookies, clicks and form submits.

Readme

Tallahassee

A browser module around JSDOM for testing a web application as opposed to a document. Navigation with headers, cookies, clicks and form submits.

I really want the name Tallahassee to remain, although Columbus sounds more browsery.

Table of Contents

Basic usage

import assert from 'node:assert/strict';
import { Browser } from '@zombieland/tallahassee';

let browser;
before('a browser with a default origin', () => {
	browser = new Browser('http://localhost:7411');
});

test('simple navigation', () => {
	const dom = await browser.navigateTo('/');
	assert.equal(dom.window.document.title, 'Zombieland');
});

test('detailed navigation', () => {
	const response = await browser.fetch('/', {
		headers: { 'Cookie': 'signed-in=1' },
	});
	assert.equal(response.status, 200);

	const dom = await browser.load(response, { runScripts: 'dangerously' });
	assert.equal(dom.window.document.title, 'Zombieland');
});

API

Browser

A module for testing navigation within an origin

import { Browser } from '@zombieland/tallahassee';

new Browser(origin[, cookieJar])

Creates a new browser instance

  • origin <string> Base URL used by browser.fetch
  • cookieJar <CookieJar> A jar of cookies to be used by browser.fetch method. Default new CookieJar()

browser.navigateTo(resource[, fetchOptions, loadOptions])

Fetches a document and loads a DOM

  • resource Passed on to browser.fetch
  • fetchOptions Passed on to browser.fetch
  • loadOptions Passed on to browser.load.
  • Returns: <Promise> Fulfills with a JSDOM on success
const dom = await browser.navigateTo(
	'/',
	{ headers: { 'Cookie'; 'some-cookie=value' } },
	{ runScripts: 'dangerously' }
);

browser.fetch(resource[, options])

Fetches a document. Useful for inspecting response details before loading DOM with browser.load().

  • resource <string> | <URL> | <Request> path (relative to the browser origin) / URL to a document or a request object
  • options <Object> A RequestInit dictionary
  • Returns: <Promise> Fulfills with a Response on success
const pendingResponse = browser.fetch('/', {
	headers: { 'Cookie'; 'some-cookie=value' }
});

Any request Cookie header will be applied to browser.cookieJar before actual fetch().

Any response Set-Cookie will be applied to browser.cookieJar before creating new JSDOM().

Redirects are followed manually with recursive calls to browser.fetch in order to properly set/get cookies from browser.cookieJar.

browser.load(resource[, options])

Loads a DOM from a document string or response

  • resource <string> | <Response> | <Promise> A document string or response string to load into JSDOM
  • options <Object> {[painter, resources, ...jsdomOptions]}
    • painter: <Painter> Little Rock Painter instance
    • resources: <Resources> Wichita Resources instance
    • jsdomOptions: <Object> Options to be passed onto JSDOM
      • Default:
        • runScripts: 'outside-only' if options.painter
        • pretendToBeVisual: true if options.painter
      • Fixed values:
        • url: url from resource if instance of Response
        • contentType: Content-Type response header from resource if instance of Response
        • cookieJar: cookieJar from browser instance
        • beforeParse: A function that will run:
          • options.painter?.beforeParse: From a Little Rock Painter instance
          • options.resources?.beforeParse: From a Wichita Resources instance
          • options.beforeParse
  • Returns: <Promise> Fulfills with a JSDOM on success
const dom = await browser.load(pendingResponse, {
	runScripts: 'dangerously'
});

If using Little Rock and/or Wichita their beforeParse methods will be run automatically if passed into options:

import { Browser } from "@zombieland/tallahassee";
import { Painter } from "@zombieland/little-rock";
import { ResourceLoader } from "@zombieland/wichita";

const browser = new Browser(…);
const pendingResponse = browser.fetch('/');
const dom = await browser.load(pendingResponse, {
	painter: new Painter(…),
	resources: new ResourceLoader(…),
});

browser.captureNavigation(dom[, follow])

Captures navigation from link clicks and form submits.

  • dom <JSDOM> A DOM to observe
  • follow <Boolean> To follow request or not. Default false
  • Returns: <Promise> Resolves with a <Request> or <Response> from fetch if follow: true. Rejects with an Event which blocked the navigation.
const linkOrFormSubmit = dom.window.querySelector('a, button[type=submit]');

const pendingNavigation = browser.captureNavigation(dom, false);
linkOrFormSubmit.click();
const request = await pendingNavigation;
assert(request instanceof Request);

Or with follow: true to perform a call to browser.fetch()

const pendingNavigation = browser.captureNavigation(dom, true);
linkOrFormSubmit.click();
const response = await pendingNavigation;
assert(request instanceof Response);

Navigation will fail if stopped by:

  • Prevented default action of link click / form submit event using preventDefault()
  • Form element invalid event
await assert.reject(pendingNavigation, (event) => {
	assert.equal(event.type, 'invalid');
	assert.equal(event.target, form.elements[1]);
	return true;
})

Navigation is intercepted at the window level using event listeners. Promise will not settle if event propagation is stopped or if form submit is triggered without event, e.g. with the submit method.

ReverseProxy

A module for emulating a CDN like reverse proxy. Basically a wrapper around nock.

import { ReverseProxy } from '@zombieland/tallahassee';

new ReverseProxy(proxyOrigin, upstreamOrigin[, headers])

Creates HTTP interceptor for a public proxy origin and proxies request to a local upstream origin.

  • proxyOrigin <string> Public URL origin
  • upstreamOrigin <string> Server URL origin
  • headers <Object> | <Headers> Headers to pass along to server. Default Standard forwarding headers (Forwarded, X-Forwarded-Proto, X-Forwarded-Host) derived from proxyOrigin
  • Returns: <ReverseProxy>
import http from 'node:http';
import { Browser, ReverseProxy } from '@zombieland/tallahassee';

http.createServer(…).listen(7411);
const reverseProxy = new ReverseProxy('https://tallahassee.zl', 'http://localhost:7411')
const browser = new Browser('https://tallahassee.zl');
const dom = await browser.navigateTo('/safe-house');
assert.equal(dom.window.location, 'https://tallahassee.zl/safe-house');

reverseProxy.modifyUpstreamRequest(req)

Modifies the upstream request before it is sent to the upstream origin. This method can be overridden to customize request headers or other properties.

The default implementation applies the headers supplied to the constructor.

  • req <Request> The request object to be sent to the upstream origin
  • Returns: <Request> The modified request object
class CustomReverseProxy extends ReverseProxy {
	modifyUpstreamRequest(req) {
		req = super.modifyUpstreamRequest(req);

		const forwarded = req.headers.get('forwarded');
		req.headers.set('forwarded', `for=192.168.0.1;${forwarded}`);
		req.headers.set('Via', '1.1 MyProxy');
		
		return req;
	}
}

reverseProxy.clear()

Clears nocked responses for proxyOrigin

  • Returns: undefined
reverseProxy.clear();

Todo

  • [ ] Reloading page
  • [ ] Unload browser and all its active jsdom instances
  • [ ] Expose network requests
  • [x] Use node fetch / Response / Request
    • [x] Stable version of Nock
  • [x] In-page navigation (clicking links etc.)
  • [x] Containing requests to the app is currently done by setting up a nock scope around app origin which intercepts all reqs and proxies them through supertest. Not ideal for a bunch of reasons:
  • [x] Scrap use of SuperTest. It's incorrectly used as an HTTP lib because of its ability to make requests to a server. Not having a listening server makes handling of client side requests messy. Calls to XMLHttpRequest needs to be intercepted and cookies will need to be handled manually. Also having the consumer starting / stopping their server once per test process would be more performant than doing it adhoc for each request.