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 🙏

© 2025 – Pkg Stats / Ryan Hefner

ektest

v0.1.9

Published

Light weight simple testing library with Electron and Puppeteer support

Readme

ektest 🚀

A lightweight, zero-configuration testing library for modern JavaScript applications with ES modules and bundler support.

Why ektest?

Tired of complex configuration setups with Jest, Mocha, and other testing frameworks when working with ES modules and bundlers? ektest is designed to work out of the box with minimal to zero configuration, especially for projects using:

  • ✅ ES Modules (type: "module")
  • ✅ Webpack bundling
  • ✅ Modern JavaScript features
  • ✅ Node.js testing

Features

  • 🎯 Zero Configuration - Works out of the box
  • 📦 Bundler Integration - Built-in Webpack support
  • 🔄 ES Modules - Native support for modern JavaScript
  • 🎨 Beautiful Output - Clean, readable test results
  • 🚀 Lightweight - Minimal dependencies
  • 🔧 Configurable - Optional configuration when needed

Installation

npm install ektest

Quick Start

  1. Create a test file (e.g., math.test.js):
// math.test.js
test('addition should work', () => {
  expect('2 + 2', 2 + 2).toBe(4);
});

test('subtraction should work', () => {
  expect('5 - 3', 5 - 3).toBe(2);
});

test('arrays should contain elements', () => {
  expect('[1, 2, 3]', [1, 2, 3]).toHave([1, 2]);
});
  1. Run your tests:
npx ektest

That's it! 🎉

Available Matchers

ektest provides a comprehensive set of matchers for your assertions:

Note: The expect function takes two parameters: expect(name, value) where name is a descriptive string and value is the actual value to test.

// Equality
expect('value', value).toBe(4); // Strict equality (===)
expect('object', obj).toEqual(expected); // Deep equality for objects/arrays
expect('value', value).not.toBe(5);

// Truthiness
expect('value', value).toBeTruthy();
expect('value', value).toBeFalsy();
expect('value', value).toBeNull();
expect('value', value).toBeDefined();

// Numbers
expect('value', value).toBeGreaterThan(3);
expect('value', value).toBeLessThan(5);
expect('value', value).toBeNumber();

// Strings
expect('string', string).toMatch(/pattern/);
expect('string', string).toBeString();
expect('string', string).toContain('substring');

// Arrays and Objects
expect('array', array).toHave(item);
expect('array', array).toHave([item1, item2]); // Multiple items
expect('array', array).toContain(item); // Check if array contains item
expect('array', array).toBeArray();
expect('object', object).toHave('property');
expect('object', object).toBeObject();
expect('value', value).toBeEmpty();

// Type checking
expect('value', value).toBeInstanceOf(Array);
expect('value', value).toBeBoolean();

// Inclusion
expect('value', value).toBeIn([1, 2, 3, 4]);

Improved Error Messages

ektest provides clear, descriptive error messages with file locations and code snippets to help you quickly identify and fix issues:

test('user age validation', () => {
  const user = { name: 'John', age: 30 };
  expect('User age', user.age).toBe(25);
});

Error output:

✗ user age validation
  ✗ Comparison failed: expected values to be strictly equal (===)

    Expected: 25
    Received: 30

  at tests/user.test.js:3:30

  Code:
     1 | test('user age validation', () => {
     2 |   const user = { name: "John", age: 30 };
     3 |   expect("User age", user.age).toBe(25);
                                         ^
     4 | });

The error messages show:

  • 📝 Clear description of what failed
  • 📊 Both expected and actual values
  • 📍 Exact file location (file:line:column)
  • 🔍 Code snippet with the failing line highlighted

CLI Options

# Basic usage
npx ektest

# Detailed output
npx ektest --detailed
npx ektest -d

# Summary only
npx ektest --summary
npx ektest -s

Configuration

ektest works with zero configuration, but you can customize it by creating a ektest.config.json file:

{
  "testDir": "tests",
  "bundler": "webpack",
  "bundlerConfig": "custom-webpack.config.js"
}

Configuration Options

  • testDir (string): Directory to search for test files (default: current directory)
  • bundler (string): Bundler to use (default: "webpack")
  • bundlerConfig (string): Path to custom bundler configuration

Test File Discovery

ektest automatically finds test files with the pattern *.test.js in your project directory, excluding:

  • node_modules
  • dist
  • build
  • coverage
  • tools
  • docs
  • examples
  • scripts
  • vendor
  • public
  • assets
  • static
  • bin
  • fixtures
  • data
  • temp

Examples

Basic Test

// calculator.test.js
test('calculator adds numbers correctly', () => {
  const result = 2 + 3;
  expect('result', result).toBe(5);
});

Array and Object Tests

// collections.test.js
test('array contains elements', () => {
  const numbers = [1, 2, 3, 4, 5];
  expect('numbers', numbers).toHave(3);
  expect('numbers', numbers).toHave([1, 2]);
});

test('object has properties', () => {
  const user = { name: 'John', age: 30 };
  expect('user', user).toHave('name');
  expect('user', user).toHave(['name', 'age']);
});

Type Checking Tests

// types.test.js
test('type checking works', () => {
  expect('hello', 'hello').toBeString();
  expect('number', 42).toBeNumber();
  expect('boolean', true).toBeBoolean();
  expect('array', [1, 2, 3]).toBeArray();
  expect('object', {}).toBeObject();
});

Async Tests

// async.test.js
test('async operation works', async () => {
  const data = await fetchData();
  expect('data', data).toBeDefined();
  expect('data', data).toBeObject();
});

test('async calculation', async () => {
  const result = await new Promise((resolve) => {
    setTimeout(() => resolve(10 + 5), 100);
  });
  expect('result', result).toBe(15);
});

test('async array processing', async () => {
  const numbers = [1, 2, 3, 4, 5];
  const doubled = await Promise.all(numbers.map(async (n) => n * 2));
  expect('doubled', doubled).toHave([2, 4, 6, 8, 10]);
  expect('doubled', doubled).toBeArray();
});

Aborting Tests

You can abort test execution at any point using the abort() function. This is useful when a critical condition is met and continuing tests would be meaningless:

// abort.test.js
test('First test - this runs', async () => {
  expect('simple check', true).toBe(true);
  console.log('✓ First test passed');
});

test('Second test - abort here', async () => {
  const criticalCondition = await checkSystemState();

  if (!criticalCondition) {
    abort('Critical system error - cannot continue testing');
    return; // Exit this test
  }

  // This won't run if aborted
  expect('this check', true).toBe(true);
});

test('Third test - this will NOT run', async () => {
  // This test is skipped because abort() was called
  expect('never runs', 1).toBe(1);
});

Key points:

  • abort(message) stops all remaining tests from running
  • Aborted tests exit with code 2 (vs. 0 for pass, 1 for failures)
  • The abort message is displayed in the test summary
  • Useful for scenarios like: database connection failures, missing prerequisites, environment issues

Electron and Web App Testing with Puppeteer

ektest supports testing Electron applications and web applications using Puppeteer. This allows you to interact with your app's UI and test user interactions.

Installation

First, install the required dependency:

npm install --save-dev puppeteer

Setup

Use the setup() function (or the legacy setupElectron() alias) to launch your Electron app or web app and get a Puppeteer page instance. Cleanup is handled automatically after all tests complete - you don't need to call cleanup manually!

Testing Electron Apps

Auto-detection: If you have the electron package installed, setup() will automatically detect and use it. You only need to specify appPath if you're testing a different Electron executable.

test('Electron app launches', async () => {
  // Option 1: Auto-detect Electron (if electron package is installed)
  const { page } = await setup({
    puppeteerOptions: {
      headless: false,
      args: ['path/to/your/main.js'], // Your app's main file
    },
  });

  // Option 2: Specify custom Electron executable
  const { page } = await setup({
    appPath: 'path/to/electron.exe', // Custom Electron path
    puppeteerOptions: {
      headless: false,
      args: ['path/to/your/main.js'],
    },
  });

  // Your tests here - cleanup happens automatically!
});

Testing Web Apps

You can also test web applications by providing a url option. This launches a regular browser and navigates to the specified URL. The browser will use full viewport size for realistic testing:

test('web app login works', async () => {
  const { page } = await setup({
    url: 'http://localhost:3000', // Your web app URL
    puppeteerOptions: {
      headless: false, // Set to true for headless mode
    },
  });

  // Wait for login form to appear
  await waitFor('#username');

  // Interact with the login form
  const username = await query('#username');
  await username.type('testuser');

  const password = await query('#password');
  await password.type('password123');

  const loginButton = await query('#login-button');
  await loginButton.click();

  // Verify login was successful
  await waitFor('.dashboard', { timeout: 5000 });
  const dashboard = await query('.dashboard');
  const welcomeText = await dashboard.innerText;
  expect('welcome message', welcomeText).toContain('Welcome');
});

Query API

The query(selector) function allows you to find and interact with DOM elements in your Electron app.

The waitFor(selector, options?) function waits for an element to appear in the page before continuing.

The keyPress(keys, page?) function sends keyboard shortcuts and key combinations to the page.

test('can interact with UI elements', async () => {
  const { page } = await setup({
    url: 'http://localhost:3000',
  });

  // Wait for an element to appear
  await waitFor('#submit-button', { timeout: 5000 });

  // Query an element
  const button = await query('#submit-button');

  // Get inner text
  const text = await button.innerText;
  expect('button text', text).toBe('Submit');

  // Get inner HTML
  const html = await button.innerHTML;
  expect('button html', html).toBeString();

  // Type into an input field
  const input = await query('#username');
  await input.type('myusername'); // Types with random human-like delays

  // Type with custom delay
  const email = await query('#email');
  await email.type('[email protected]', { delay: 50 }); // 50ms between each key

  // Send keyboard shortcuts
  await keyPress('Enter'); // Press Enter key
  await keyPress('Ctrl+A'); // Select all (Ctrl+A)
  await keyPress('Shift+Enter'); // Shift+Enter combination
  await keyPress('Ctrl+Shift+K'); // Multiple modifiers
  await keyPress('Meta+V'); // Cmd+V on Mac, Win+V on Windows

  // Click the button
  await button.click();

  // Wait for a success message to appear
  await waitFor('#success-message', { timeout: 5000, visible: true });

  // Right-click for context menu
  await button.contextMenu();
});

keyPress Function

The keyPress(keys, page?) function sends keyboard shortcuts with modifiers to the page. It supports various key combinations:

// Simple keys
await keyPress('Enter');
await keyPress('Escape');
await keyPress('Tab');
await keyPress('ArrowDown');

// With modifiers (supports both + and - as separators)
await keyPress('Ctrl+A'); // Ctrl+A
await keyPress('Shift+Enter'); // Shift+Enter
await keyPress('Ctrl-Shift-K'); // Ctrl+Shift+K (dash separator)
await keyPress('Meta+C'); // Cmd+C on Mac, Win+C on Windows

// Multiple modifiers
await keyPress('Ctrl+Shift+A');
await keyPress('Alt+Shift+F');

// Using specific page (optional second parameter)
const { page } = await setup({ url: 'http://localhost:3000' });
await keyPress('Enter', page); // Explicitly pass page

Supported modifiers:

  • Ctrl or Control - Control key
  • Shift - Shift key
  • Alt - Alt key
  • Meta, Cmd, or Command - Meta/Command key (⌘ on Mac, ⊞ on Windows)

Note: The function uses the global page by default (from setup()), but you can pass a specific page instance as the second parameter if needed.

waitFor Function

The waitFor(selector, options?) function waits for an element to appear in the page:

// Wait for element to exist (default timeout: 30000ms)
await waitFor('#my-element');

// Wait with custom timeout
await waitFor('#my-element', { timeout: 5000 });

// Wait for element to be visible
await waitFor('#my-element', { visible: true });

// Wait for element to be hidden
await waitFor('#my-element', { hidden: true });

Options:

  • timeout (number, optional): Maximum time to wait in milliseconds (default: 30000)
  • visible (boolean, optional): Wait for element to be visible (default: false)
  • hidden (boolean, optional): Wait for element to be hidden (default: false)

Query Element Methods

The object returned by query() provides the following methods and properties:

  • innerText (Promise): Get the inner text content of the element
  • innerHTML (Promise): Get the inner HTML of the element
  • click() (Promise): Click the element
  • contextMenu() (Promise): Right-click the element to open context menu
  • type(text, options?) (Promise): Type text into the element with human-like delays
    • text (string): The text to type
    • options.delay (number, optional): Delay between keystrokes in milliseconds. If not specified, uses random delays between 50-150ms to mimic human typing
  • element (ElementHandle): Access the raw Puppeteer element for advanced operations
  • puppeteer (Page): Access the Puppeteer page instance for complex testing scenarios

Complete Example

// electron-app.test.js
test('Electron app full workflow', async () => {
  const { page } = await setup({
    appPath: './dist/electron/MyApp.exe',
    puppeteerOptions: {
      headless: false,
    },
  });

  // Verify app title
  const title = await query('#app-title');
  const titleText = await title.innerText;
  expect('app title', titleText).toBe('My Awesome App');

  // Fill in a form with human-like typing
  const nameInput = await query('input[name="username"]');
  await nameInput.click();
  await nameInput.type('testuser'); // Types with random delays (50-150ms)

  const emailInput = await query('input[name="email"]');
  await emailInput.click();
  await emailInput.type('[email protected]', { delay: 30 }); // Types with 30ms delay

  // Use keyboard shortcuts
  await keyPress('Tab'); // Move to next field
  await keyPress('Ctrl+A'); // Select all text
  await keyPress('Shift+Enter'); // Submit with Shift+Enter

  // Submit the form
  const submitButton = await query('button[type="submit"]');
  await submitButton.click();

  // Wait for result
  await waitFor('#success-message');

  // Verify success message
  const successMsg = await query('#success-message');
  const msgText = await successMsg.innerText;
  expect('success message', msgText).toMatch(/success/i);
});

Advanced Testing with Puppeteer

For complex testing scenarios, you can access the Puppeteer page instance directly:

test('Advanced Puppeteer operations', async () => {
  const { page } = await setup({
    appPath: './dist/electron/MyApp.exe',
  });

  // Query an element
  const button = await query('#my-button');

  // Access Puppeteer page for advanced operations
  const puppeteerPage = button.puppeteer;

  // Take a screenshot
  await puppeteerPage.screenshot({ path: 'screenshot.png' });

  // Evaluate JavaScript in the page context
  const result = await puppeteerPage.evaluate(() => {
    return window.someGlobalVariable;
  });

  // Wait for navigation
  await Promise.all([puppeteerPage.waitForNavigation(), button.click()]);

  // Emulate network conditions
  const client = await puppeteerPage.target().createCDPSession();
  await client.send('Network.emulateNetworkConditions', {
    offline: false,
    downloadThroughput: (200 * 1024) / 8,
    uploadThroughput: (200 * 1024) / 8,
    latency: 20,
  });
});

Best Practices

  1. Automatic cleanup: Cleanup is handled automatically after tests complete - no need for manual cleanup calls!
  2. Wait for elements: Use page.waitForSelector() when waiting for dynamic content
  3. Access raw Puppeteer: Use .element property to access the raw Puppeteer ElementHandle for advanced operations
  4. Use puppeteer getter: Access .puppeteer to get the full Puppeteer page instance for complex scenarios like screenshots, network emulation, or CDP sessions

Project Structure

your-project/
├── src/
│   ├── math.js
│   └── utils.js
├── tests/
│   ├── math.test.js
│   └── utils.test.js
├── package.json
└── ektest.config.json (optional)

Roadmap

  • toEqual Matcher - Deep equality comparison for objects and arrays
  • toContain Matcher - Check if arrays/strings contain elements
  • Improved Error Messages - Clear error messages with file locations and code snippets
  • Keyboard Shortcuts - keyPress function for testing keyboard interactions
  • Full Viewport Support - Web apps use full browser window size
  • 🎯 More Matchers - Add toThrow and promise matchers
  • 🌐 Browser Testing - Run tests in real browsers
  • 📊 Code Coverage - Built-in coverage reporting
  • 🔄 More Bundlers - Support for Vite, Rollup, esbuild
  • 🎯 Test Runners - Parallel test execution
  • 📸 Snapshot Testing - Visual regression testing
  • 🔍 Test Debugging - Better debugging experience

Why Choose ektest?

Before (with other frameworks)

// Complex configuration required
// jest.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  extensionsToTreatAsEsm: ['.ts'],
  globals: {
    'ts-jest': {
      useESM: true,
    },
  },
  moduleNameMapping: {
    '^(\\.{1,2}/.*)\\.js$': '$1',
  },
};

After (with ektest)

npm install ektest
npx ektest

Contributing

We welcome contributions! Please feel free to submit issues and pull requests.

License

MIT © Ajit Kumar


Made with ❤️ for developers who want to focus on writing tests, not configuring them.