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

@civitas-cerebrum/singularity-engine

v0.1.4

Published

Platform-agnostic test automation engine. Semantic Steps API for web, mobile, and API testing.

Readme

Singularity Engine

NPM Version

A platform-agnostic test automation engine. Write once, run on web, mobile, and desktop.

@civitas-cerebrum/singularity-engine pairs with @civitas-cerebrum/element-repository to achieve a fully decoupled test automation architecture. By separating Element Acquisition from Element Interaction behind an abstract factory pattern, the same test steps work across Playwright (web), Appium (Android, iOS), and desktop apps (Windows, Mac) — without changing test code.

✨ The Unified Steps API

The Steps class provides a single, semantic API that works identically across platforms. Under the hood, an IInteractionFactory routes each call to the correct platform implementation.

// This test runs on web, Android, and iOS — no code changes needed
await steps.navigateTo('/');
await steps.click('addToCartButton', 'HomePage');
await steps.verifyText('total', 'CartPage', '$29.99');

// API testing built-in
const orders = await steps.apiGet<Order[]>('/api/orders');
await steps.verifyApiStatus(orders, 200);

🧠 AI-Friendly Test Development

The API is highly semantic and completely decoupled from the DOM and platform internals, making it an ideal framework for AI coding assistants. AI models generate robust test flows using plain-English strings ('CheckoutPage', 'submitButton') without hallucinating complex selectors.


📦 Installation

npm install @civitas-cerebrum/singularity-engine

Peer dependency: @playwright/test is required for web testing. Install it alongside:

npm install @civitas-cerebrum/singularity-engine @playwright/test

🚀 Quick Start

1. Create the test fixture

// tests/fixtures/base.ts
import { test as base, expect } from '@playwright/test';
import { baseFixture } from '@civitas-cerebrum/singularity-engine';

export const test = baseFixture(base, 'tests/data/page-repository.json');
export { expect };

2. Define your selectors

// tests/data/page-repository.json
{
  "pages": [
    {
      "name": "HomePage",
      "elements": [
        { "elementName": "searchInput", "selector": { "css": "[data-testid='search']" } },
        { "elementName": "addToCartButton", "selector": { "css": "[data-testid='add-to-cart']" } },
        { "elementName": "bookCard", "selector": { "css": "[data-testid^='book-card-']" } }
      ]
    },
    {
      "name": "CartPage",
      "elements": [
        { "elementName": "total", "selector": { "css": "[data-testid='cart-total']" } },
        { "elementName": "checkoutButton", "selector": { "css": "[data-testid='checkout-btn']" } },
        { "elementName": "emptyMessage", "selector": { "css": "[data-testid='cart-empty']" } }
      ]
    }
  ]
}

3. Write tests

// tests/checkout.spec.ts
import { test, expect } from '../fixtures/base';

test('complete purchase flow', async ({ steps }) => {
  // Navigate and interact
  await steps.navigateTo('/');
  await steps.fill('searchInput', 'HomePage', 'Dune');
  await steps.pressKey('Enter');
  await steps.waitForNetworkIdle();

  // Verify results
  const count = await steps.getCount('bookCard', 'HomePage');
  expect(count).toBeGreaterThan(0);

  // Click first result and add to cart
  await steps.clickNth('bookCard', 'HomePage', 0);
  await steps.click('addToCartButton', 'BookDetailPage');

  // Verify cart
  await steps.navigateTo('/cart');
  await steps.verifyPresence('total', 'CartPage');
  await steps.verifyText('total', 'CartPage');
});

4. Run

npx playwright test

✨ Features

Semantic Steps API

77 methods organised by purpose. No raw Playwright calls needed — every interaction goes through the Steps API:

// All interactions use (elementName, pageName) from the repository
await steps.click('submitButton', 'LoginPage');
await steps.fill('emailInput', 'LoginPage', '[email protected]');
await steps.verifyText('errorMessage', 'LoginPage', 'Invalid credentials');

🌐 API Interaction

Make typed HTTP requests directly from tests. Powered by @civitas-cerebrum/wasapi:

// Enable in fixture
export const test = baseFixture(base, 'tests/data/page-repository.json', {
  apiBaseUrl: 'http://localhost:8080',
});

// In tests
const response = await steps.apiGet<{ status: string }>('/api/health');
await steps.verifyApiStatus(response, 200);
expect(response.body!.status).toBe('healthy');

const order = await steps.apiPost<Order>('/api/orders', {
  items: [{ bookId: 'book-001', quantity: 1 }]
});
expect(order.body!.status).toBe('COMPLETED');

// Check response headers
const headers = await steps.apiHead('/api/health');
expect(headers['content-type']).toContain('application/json');

Multiple API Providers

Connect to multiple backends simultaneously:

export const test = baseFixture(base, 'tests/data/page-repository.json', {
  apiBaseUrl: 'http://localhost:8080',         // default
  apiProviders: {
    payments: 'https://api.stripe.test/v1',    // named
    auth: 'https://auth.example.com',          // named
  },
});

// Default client
await steps.apiGet('/api/books');

// Named providers
await steps.apiPost('payments', '/charges', { amount: 1000 });
await steps.apiGet('auth', '/oauth/userinfo');

Boolean Visibility Check

isPresent() returns true/false without throwing — perfect for conditional flows:

const hasError = await steps.isPresent('errorMessage', 'LoginPage');
if (hasError) {
  const errorText = await steps.getText('errorMessage', 'LoginPage');
  console.log('Login failed:', errorText);
}

Navigation with Query Parameters

await steps.navigateTo('/', { query: { genre: 'Fiction', page: '2' } });
// Navigates to /?genre=Fiction&page=2

Email Testing

Send, receive, and verify emails via SMTP/IMAP:

export const test = baseFixture(base, 'tests/data/page-repository.json', {
  emailCredentials: {
    smtp: { host: 'smtp.example.com', port: 587, user: '...', pass: '...' },
    imap: { host: 'imap.example.com', port: 993, user: '...', pass: '...' },
  },
});

// In tests
await steps.sendEmail({ to: '[email protected]', subject: 'OTP', text: '123456' });
const email = await steps.receiveEmail({
  filters: [{ type: EmailFilterType.SUBJECT, value: 'OTP' }]
});
expect(email.text).toContain('123456');

Platform-Agnostic Testing

The same test code runs on web, Android, iOS, Windows, and Mac:

// This test works on all platforms — no code changes needed
await steps.navigateTo('/');
await steps.click('submitButton', 'HomePage');
await steps.verifyText('welcomeMessage', 'HomePage', 'Hello!');

The abstract factory pattern routes each call to the correct platform implementation. PlatformElement wraps both Playwright's Locator and WebdriverIO's Element — neither leaks into test code.

Viewport Testing

await steps.setViewport(375, 812);  // mobile
await steps.navigateTo('/');
await steps.verifyPresence('bookGrid', 'HomePage');

await steps.setViewport(1280, 720); // desktop

Screenshots

const pageScreenshot = await steps.screenshot();
const elementScreenshot = await steps.screenshot('bookGrid', 'HomePage');

Listed Element Interactions

Work with tables, lists, and repeating elements:

// Click a specific row by text
await steps.clickListedElement('orderCard', 'OrdersPage', { text: 'ORD-123' });

// Verify data within a list item
await steps.verifyListedElement('orderCard', 'OrdersPage', {
  text: 'ORD-123',
  child: { elementName: 'statusBadge', pageName: 'OrdersPage' },
  expectedText: 'COMPLETED'
});

// Extract data from a list item
const price = await steps.getListedElementData('cartItem', 'CartPage', {
  text: 'Dune',
  child: { elementName: 'itemPrice', pageName: 'CartPage' }
});

🧩 Included Fixtures

baseFixture provides these fixtures to every test:

| Fixture | Type | Description | |---------|------|-------------| | steps | Steps | The full Steps API | | repo | ElementRepository | Direct repository access for advanced queries | | interactions | ElementInteractions | Raw interaction API for custom locators | | contextStore | ContextStore | Shared in-memory key-value store between steps | | page | Page | Playwright page with auto-screenshot on failure |


🛠️ API Reference

🧭 Navigation

| Method | Description | |--------|-------------| | navigateTo(url, options?) | Navigate to URL. Options: { query: { key: 'value' } } | | refresh() | Reload current page | | backOrForward(direction) | Browser history: 'back' or 'forward' | | setViewport(width, height) | Resize viewport | | switchToNewTab(action) | Execute action that opens new tab, return new page | | closeTab(targetPage?) | Close a tab | | getTabCount() | Number of open tabs |

🖱️ Interaction

| Method | Description | |--------|-------------| | click(element, page) | Click an element | | clickNth(element, page, index) | Click by zero-based index | | clickIfPresent(element, page) | Click if visible, returns boolean | | clickRandom(element, page) | Click a random match | | doubleClick(element, page) | Double-click | | rightClick(element, page) | Right-click (web only) | | hover(element, page) | Hover (web only) | | fill(element, page, text) | Clear and fill input | | clearInput(element, page) | Clear input field | | typeSequentially(element, page, text, delay?) | Type character by character | | pressKey(key) | Press keyboard key ('Enter', 'Escape', 'Tab', etc.) | | check(element, page) / uncheck(element, page) | Toggle checkbox | | selectDropdown(element, page, options?) | Select from <select> | | selectMultiple(element, page, values) | Multi-select | | uploadFile(element, page, filePath) | Upload file (web only) | | dragAndDrop(element, page, options) | Drag to target or by offset | | setSliderValue(element, page, value) | Set range input value | | scrollIntoView(element, page) | Scroll element into viewport | | scrollUntilFound(element, page, timeout?) | Scroll until element appears | | clickWithoutScrolling(element, page) | Click without auto-scrolling |

📊 Data Extraction

| Method | Description | |--------|-------------| | getText(element, page) | Get trimmed text content | | getAttribute(element, page, attr) | Get HTML attribute value | | getCount(element, page) | Count matching elements | | getInputValue(element, page) | Get input's value property | | getAll(element, page, options?) | Get text/attributes from all matches | | getCssProperty(element, page, property) | Get computed CSS (web only) | | screenshot() / screenshot(element, page) | Page or element screenshot |

✅ Verification

| Method | Description | |--------|-------------| | verifyPresence(element, page) | Assert visible (throws on failure) | | isPresent(element, page) | Boolean visibility check (never throws) | | verifyAbsence(element, page) | Assert hidden/detached | | verifyText(element, page, expected?) | Assert text matches; no args = not empty | | verifyTextContains(element, page, substring) | Assert text contains | | verifyCount(element, page, options) | Assert count: { exactly }, { greaterThan }, { lessThan } | | verifyState(element, page, state) | Assert state: 'enabled', 'disabled', 'checked', 'visible', etc. | | verifyAttribute(element, page, attr, expected) | Assert attribute value | | verifyInputValue(element, page, expected) | Assert input value | | verifyUrlContains(text) | Assert URL contains text | | verifyImages(element, page) | Verify images rendered (web only) | | verifyOrder(element, page, expectedTexts) | Assert text order in list | | verifyListOrder(element, page, direction) | Assert sorted: 'asc' or 'desc' | | verifyCssProperty(element, page, prop, expected) | Assert CSS value (web only) | | verifyTabCount(expected) | Assert tab count |

🌐 API Interaction

| Method | Description | |--------|-------------| | apiGet<T>(path, options?) | GET request. Options: { query, headers } | | apiPost<T>(path, body?, options?) | POST request. Options: { pathParams, query, headers } | | apiPut<T>(path, body?, options?) | PUT request | | apiDelete<T>(path, options?) | DELETE request | | apiPatch<T>(path, body?, options?) | PATCH request | | apiHead(path) | HEAD request — returns headers only | | verifyApiStatus(response, status) | Assert HTTP status code | | verifyApiHeader(response, name, value?) | Assert header presence/value |

All API methods support named providers: apiGet('providerName', path, options?).

📋 Listed Elements

| Method | Description | |--------|-------------| | clickListedElement(element, page, options) | Click within a list by text/attribute | | verifyListedElement(element, page, options) | Verify within a list | | getListedElementData(element, page, options) | Extract data from a list item | | dragAndDropListedElement(element, page, text, options) | Drag a list item |

⏳ Wait & Composite

| Method | Description | |--------|-------------| | waitForState(element, page, state?) | Wait for element state (default: 'visible') | | waitForNetworkIdle() | Wait for network quiet | | waitForResponse(urlPattern, action) | Wait for network response during action | | waitAndClick(element, page) | Wait for visible, then click | | fillForm(page, fields) | Fill multiple form fields in one call | | retryUntil(action, verify, maxRetries?, delay?) | Retry action until verification passes |

📧 Email

| Method | Description | |--------|-------------| | sendEmail(options) | Send via SMTP | | receiveEmail(options) | Poll inbox for matching email | | receiveAllEmails(options) | Get all matching emails | | cleanEmails(options?) | Delete matching or all emails | | markEmail(action, options?) | Mark as read, flagged, archived |


🗂️ Page Repository

All selectors live in a single JSON file — the source of truth for element locations:

{
  "pages": [
    {
      "name": "LoginPage",
      "elements": [
        {
          "elementName": "emailInput",
          "selector": { "css": "input[data-testid='email']" }
        },
        {
          "elementName": "submitButton",
          "selector": {
            "css": "button[type='submit']",
            "text": "Sign In"
          }
        }
      ]
    }
  ]
}

Selector strategies: css, xpath, id, text, accessibility id, uiautomator, predicate, class chain

Naming conventions:

  • Page names: PascalCase (CheckoutPage, ProductDetailsPage)
  • Element names: camelCase (submitButton, cartTotal)

Multi-Platform Selectors

The same JSON file supports different selectors per platform:

{
  "pages": [
    {
      "name": "HomePage",
      "elements": [
        { "elementName": "submit", "selector": { "css": "button#submit" } }
      ]
    },
    {
      "name": "HomePage",
      "platform": "android",
      "elements": [
        { "elementName": "submit", "selector": { "accessibility id": "submit-btn" } }
      ]
    }
  ]
}

📱 Supported Platforms

| Platform | Driver | Status | |----------|--------|--------| | Web (Chromium, Firefox, WebKit) | Playwright | Stable | | Android | Appium + WebdriverIO | Stable | | iOS | Appium + WebdriverIO | Stable | | Windows | Appium + WinAppDriver | Experimental | | Mac | Appium + mac2 driver | Experimental |


🧱 Advanced Usage

Direct Repository Access

test('advanced query', async ({ repo, page }) => {
  const element = await repo.get('submitButton', 'HomePage');
  const selector = repo.getSelector('submitButton', 'HomePage');
  const allElements = await repo.getAll('productCard', 'HomePage');
});

Context Store

Share data between test steps:

test('multi-step flow', async ({ steps, contextStore }) => {
  // Save data in one step
  const orderId = await steps.getText('orderId', 'OrderPage');
  contextStore.set('orderId', orderId);

  // Use it later
  const savedId = contextStore.get<string>('orderId');
  await steps.navigateTo(`/orders/${savedId}`);
});

Form Filling

Fill multiple fields in one call:

await steps.fillForm('SignupPage', {
  usernameInput: 'john_doe',
  emailInput: '[email protected]',
  passwordInput: 'SecurePass123!',
  countrySelect: { type: DropdownSelectType.VALUE, value: 'us' }
});

Retry Until

Retry an action until a verification passes:

await steps.retryUntil(
  async () => { await steps.click('refreshButton', 'DashboardPage'); },
  async () => { await steps.verifyText('status', 'DashboardPage', 'Ready'); },
  3,     // max retries
  1000   // delay between retries (ms)
);

📦 Re-exported Types

These types are available directly from the package:

import {
  // Framework
  Steps, baseFixture, BaseFixtureOptions,
  ElementInteractions, ContextStore,

  // Element Repository
  ElementType, WebElement, PlatformElement,

  // Enums
  DropdownSelectType, EmailFilterType, EmailMarkAction,

  // API (wasapi)
  WasapiClient, ApiCall, ApiResponse, ResponsePair,
  GET, POST, PUT, DELETE, PATCH,
  FailedCallException, WasapiException, HttpMethod,

  // Email
  EmailClient,
} from '@civitas-cerebrum/singularity-engine';

📄 License

MIT


Powered by Civitas Cerebrum