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

galen-ts

v0.1.3

Published

Layout testing framework for web applications, powered by Playwright

Downloads

206

Readme

galen-ts

Layout testing framework for web applications, powered by Playwright.

A TypeScript port of the Galen Framework — uses the same .gspec language for describing layouts and validating element positions, sizes, and visual properties across different viewports.

Installation

npm install galen-ts playwright

Quick Start

import { Galen, HtmlReportBuilder } from 'galen-ts';
import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await page.setViewportSize({ width: 1024, height: 768 });

const report = await Galen.checkLayout(page, './specs/homepage.gspec', {
  sectionFilter: { includedTags: ['desktop'], excludedTags: [] },
});

console.log(`Passed: ${report.passed}, Errors: ${report.errors}, Warnings: ${report.warnings}`);

// Generate HTML report
new HtmlReportBuilder().build(report, './reports');

await browser.close();

Galen Spec Language

Layout specs are written in .gspec files using the Galen Spec Language. The language describes page objects and their expected layout relationships.

Objects

@objects
    header              css     #header
    header-icon         css     #header img
    menu                id      main-menu
    login-button        xpath   //button[@id='login']
    menu-item-*         css     .menu-item

Supported locator types: css, xpath, id. If omitted, auto-detected (XPath if starts with /, CSS otherwise).

Specs

= Main Section =
    header:
        height 45 to 55px
        width 100% of viewport/width
        inside viewport 0px top, 0px left, 0px right

    login-button:
        visible
        inside header 5 to 15px top, 10 to 20px right
        text is "Log In"
        css font-size is "14px"

    menu:
        below header 0 to 5px
        width 150 to 180px

    header-icon:
        inside header 5px left
        above menu 20px
        centered vertically inside header 2px

Available Specs

| Spec | Example | Description | |------|---------|-------------| | width | width 100 to 200px | Element width | | height | height 50px | Element height | | above | above menu 10px | Distance above another element | | below | below header 0 to 5px | Distance below another element | | left-of | left-of button 20px | Distance to the left | | right-of | right-of icon >= 10px | Distance to the right | | inside | inside container 10px top, 5px left | Contained within element | | inside partly | inside partly viewport 0px left | Partially contained | | near | near button 5px left | Distance from nearest edges | | aligned horizontally | aligned horizontally centered button | Horizontal alignment | | aligned vertically | aligned vertically left menu | Vertical alignment | | centered | centered on container | Centered on/inside element | | text | text is "Hello" | Text content validation | | text | text contains "welcome" | Also: starts, ends, matches | | css | css font-size is "14px" | CSS property validation | | visible | visible | Element is visible | | absent | absent | Element is absent | | contains | contains button, icon | Contains child elements | | count | count any "menu-item-*" is 5 | Count matching elements | | component | component button.gspec | Nested component spec | | image | image file "expected.png", error 2% | Visual comparison | | color-scheme | color-scheme 40% white, 30% blue | Color distribution |

Range Syntax

width 100px                     # exact
width 100 to 200px              # between
width > 50px                    # greater than
width >= 50px                   # greater than or equals
width < 200px                   # less than
width <= 200px                  # less than or equals
width ~ 100px                   # approximate (±2px)
width ~ 100% of container/width # approximate percentage (±2pp of container)
width 50% of viewport/width     # percentage of reference

The ~ prefix is also accepted on error rates in aligned, centered, and image specs — e.g. aligned horizontally centered ~ 8px or image file "expected.png", error ~ 2%. Error rate is already a tolerance, so the tilde is purely readability sugar there.

Variables

@set
    header_height   50
    main_color      rgba(0, 0, 0, 0.5)

= Main =
    header:
        height ${header_height}px

Tags (Responsive Breakpoints)

@on mobile
    = Mobile Layout =
        menu:
            width 100% of viewport/width

@on desktop
    = Desktop Layout =
        menu:
            width 250px
            inside viewport 0px left
const report = await Galen.checkLayout(page, 'layout.gspec', {
  sectionFilter: { includedTags: ['mobile'], excludedTags: [] },
});

Loops

@objects
    menu-item-*     css  .menu-item

@forEach [menu-item-*] as item, prev as prevItem
    ${item}:
        aligned horizontally all ${prevItem}

@for [1-5] as index
    menu-item-${index}:
        height 40px

Groups

@objects
    username_textfield      css  input[name='username']
    password_textfield      css  input[name='password']
    login_button            css  .btn-login
    cancel_button           css  .btn-cancel

@groups
    (textfield, textfields)     username_textfield, password_textfield
    (button, buttons)           login_button, cancel_button
    (form_element, form_elements)   &textfields, &buttons

Conditionals

@if ${isLoggedIn}
    user-panel:
        visible
@else
    login-form:
        visible

Imports

@import common.gspec
@import components/header.gspec

Warnings

Prefix a spec with % to make it a warning instead of an error:

header:
    % height 45 to 55px

Reports

HTML Report

import { HtmlReportBuilder } from 'galen-ts';

new HtmlReportBuilder().build(report, './reports');
// Generates: reports/report.html, reports/report.json

JSON Report

import { JsonReportBuilder } from 'galen-ts';

new JsonReportBuilder().writeToFile(report, './reports/report.json');

JUnit XML Report

import { JunitReportBuilder } from 'galen-ts';

new JunitReportBuilder().writeToFile(report, './reports/junit.xml', 'Layout Tests');

Test Suites

For running multiple layout checks across pages and viewports, use the suite runner.

Suite File Format (.test)

@@ set
    domain  https://example.com

Homepage on desktop
-------------------------------
    open ${domain}/
    resize 1024x768
    check homepage.gspec --include desktop

Homepage on mobile
-------------------------------
    open ${domain}/
    resize 320x568
    check homepage.gspec --include mobile

Login page
-------------------------------
    open ${domain}/login
    resize 1024x768
    check login.gspec --include desktop --exclude experimental

Running Suites

import { SuiteReader, SuiteRunner } from 'galen-ts';
import { chromium } from 'playwright';

const browser = await chromium.launch();
const tests = new SuiteReader().read('./suites/full.test');

const runner = new SuiteRunner();
const result = await runner.run(tests, {
  browser,
  parallel: true,
  concurrency: 4,
  onTestComplete: (r) => {
    const status = r.error ? 'FAIL' : 'PASS';
    console.log(`[${status}] ${r.test.title}`);
  },
});

console.log(`Total: ${result.totalPassed} passed, ${result.totalErrors} errors`);
await browser.close();

Image Comparison

Image comparison requires a custom comparator (bring your own implementation using pixelmatch, sharp, or similar):

import { setImageComparator } from 'galen-ts';

setImageComparator(async (actual, expected, options) => {
  // Implement using pixelmatch, sharp, canvas, etc.
  return {
    percentage: 0.5,
    totalMismatchPixels: 120,
    offsetX: 0,
    offsetY: 0,
  };
});

API Reference

Galen.checkLayout(page, spec, options?)

| Parameter | Type | Description | |-----------|------|-------------| | page | PlaywrightPage \| Page | Playwright page or Page abstraction | | spec | string \| PageSpec | Path to .gspec file or parsed PageSpec | | options.sectionFilter | { includedTags, excludedTags } | Tag-based filtering | | options.properties | Record<string, string> | Properties for variable substitution | | options.variables | Record<string, unknown> | JS variables available in specs | | options.screenshot | Buffer | Pre-captured screenshot | | options.listener | ValidationListener | Custom validation listener |

Returns Promise<LayoutReport>.

Galen.readSpec(specPath, options?)

Parse a .gspec file without running validation.

Galen.readSpecFromText(text, options?)

Parse .gspec text inline.

Differences from Original Galen Framework

  • Playwright instead of Selenium WebDriver
  • TypeScript with full type safety
  • Async/await instead of synchronous Java execution
  • Pluggable image comparator instead of bundled Rainbow4J
  • ESM modules instead of Java packages
  • No OCR support (was Tesseract-based in original)
  • No built-in Rhino JS engine — uses Node.js Function for expressions

License

Apache License 2.0 — same as the original Galen Framework.