@majkapp/plugin-test
v1.1.0
Published
Beautiful, developer-friendly testing framework for MAJK plugins
Maintainers
Readme
@majkapp/plugin-test
Beautiful, developer-friendly testing framework for MAJK plugins.
Features
- 🎯 Simple & Intuitive - Clean API inspired by modern testing frameworks
- 🚀 Fast - Built on
uvu, one of the fastest test runners - 🎨 Beautiful Output - Rich console output with colors and formatting
- 📊 Multiple Reporters - Console, JSON, and HTML reports
- 🔧 Plugin-Aware - Built-in helpers for MAJK plugin testing
- 🧪 Mock Support - Easy mocking of plugin contexts and auth
- 🎭 Function Testing - Direct function invocation testing
- 🌐 UI Testing - Browser-based UI tests with Playwright and screenshot capture
Installation
npm install --save-dev @majkapp/plugin-testQuick Start
Initialize Test Structure
npx majk-test initThis creates:
tests/
plugin/
functions/
unit/ # Mock context and data
e2e/ # Real auth and data
ui/
unit/ # Mock browser
e2e/ # Real browser and authWrite Your First Test
Create tests/plugin/functions/unit/health.test.js:
import { test, assert, invoke } from '@majkapp/plugin-test';
test('health function returns ok', async () => {
const result = await invoke('health', {});
assert.is(result.status, 'ok');
});
test('health includes timestamp', async () => {
const result = await invoke('health', {});
assert.ok(result.timestamp);
});Run Tests
npx majk-testAPI Reference
Core Functions
test(name, fn)
Define a test:
test('should do something', async () => {
// test code
});assert
Assertions from uvu/assert:
assert.is(actual, expected)
assert.ok(value)
assert.not(actual, expected)
assert.equal(actual, expected) // deep equality
assert.throws(() => { ... })Mock Helpers
mock(options?)
Create a mock plugin context:
import { mock } from '@majkapp/plugin-test';
const context = mock()
.storage({ counter: 10 })
.config({ apiKey: 'test-key' })
.conversation('conv-123')
.user('user-456')
.build();With options:
const context = mock({
scenario: 'empty',
storage: { counter: 10 },
recordCalls: true
}).build();Available scenarios:
empty()- Empty contextwithBasicAuth()- Basic auth setupwithStorage()- Pre-populated storage
auth
Configure authentication:
import { auth } from '@majkapp/plugin-test';
// From environment variables
mock().auth(auth.fromEnv('PLUGIN')).build()
// From endpoint
mock().auth(auth.fromEndpoint('http://localhost:3000/auth')).build()
// Manual account
mock().auth(auth.manual('[email protected]', 'password')).build()
// With tokens
mock().auth(auth.withTokens(
'[email protected]',
'password',
'access-token',
'refresh-token'
)).build()Invoke Helpers
invoke(functionName, params, options?)
Invoke a plugin function:
import { invoke } from '@majkapp/plugin-test';
const result = await invoke('myFunction', { arg: 'value' });With options:
const result = await invoke('myFunction', params, {
timeout: 5000,
expectSuccess: true
});invokeAll(functionNames, params, options?)
Invoke multiple functions:
const results = await invokeAll(['func1', 'func2'], params);CLI Options
Basic Usage
npx majk-test [options]Options
--plugin-dir, -d <dir>- Plugin directory (default: current directory)--type, -t <type>- Test type:functions,ui, orall(default:all)--env, -e <env>- Environment:unit,e2e, orall(default:all)--include, -i <pattern>- Include pattern (can be repeated)--exclude, -x <pattern>- Exclude pattern (can be repeated)--reporter, -r <reporter>- Reporter:console,json,html(default:console)--output, -o <file>- Output file for JSON/HTML reporters--bail, -b- Stop on first failure--timeout <ms>- Test timeout in milliseconds--verbose, -v- Verbose output--config, -c <file>- Config file path
Examples
Run all tests:
npx majk-testRun only function unit tests:
npx majk-test --type functions --env unitRun tests matching pattern:
npx majk-test "**/*health*"Generate HTML report:
npx majk-test --reporter html --output report.htmlVerbose output with bail:
npx majk-test --verbose --bailConfiguration File
Create majk-test.config.js:
module.exports = {
type: 'all',
env: 'all',
reporter: 'console',
bail: false,
timeout: 5000,
verbose: false,
include: ['tests/**/*.test.js'],
exclude: ['tests/**/*.skip.js']
};Or majk-test.config.json:
{
"type": "functions",
"env": "unit",
"reporter": "console",
"bail": true
}Test Organization
Recommended Structure
tests/
plugin/
functions/
unit/
health.test.js
storage.test.js
e2e/
api-integration.test.js
ui/
unit/
component.test.js
e2e/
user-flow.test.jsNaming Conventions
- Use
.test.jsor.spec.jssuffix - Group related tests in the same file
- Use descriptive test names
Advanced Usage
Test Lifecycle
import { before, after, beforeEach, afterEach } from '@majkapp/plugin-test';
before(() => {
// Run once before all tests
});
after(() => {
// Run once after all tests
});
beforeEach(() => {
// Run before each test
});
afterEach(() => {
// Run after each test
});Skipping Tests
test.skip('not ready yet', async () => {
// This test will be skipped
});Only Run Specific Tests
test.only('focus on this', async () => {
// Only this test will run
});Call Recording
const mockHelper = mock()
.recordCalls()
.build();
const callLog = mockHelper.getCallLog();UI Testing
Using uiTest
The uiTest function provides a simple wrapper around @majkapp/bot-plugin-server for automated UI testing:
const { uiTest } = require('@majkapp/plugin-test');
// Configure once for all tests
uiTest.configure({
plugin: { dir: process.cwd() },
browser: { headless: true, slowMo: 200 }
});
// Write UI tests
uiTest('navigate and click button', async (bot) => {
await bot.navigate();
await bot.screenshot('01-home');
await bot.click('submitButton');
await bot.screenshot('02-after-click');
const page = bot.getPage();
const text = await page.textContent('[data-majk-id="result"]');
console.log(`Result: ${text}`);
});
uiTest('test with mock data', async (bot) => {
// Configure mock handlers for this test
bot.configure({
handlers: {
async getUsers() {
return [
{ id: '1', name: 'Test User 1' },
{ id: '2', name: 'Test User 2' }
];
}
}
});
await bot.navigate();
await bot.screenshot('01-users-loaded');
// Verify mock data appeared in UI
const page = bot.getPage();
const userCount = await page.locator('[data-majk-id="userItem"]').count();
console.log(`✅ Found ${userCount} users`);
});Bot API for UI Testing
The bot object provides these methods:
| Method | Description |
|--------|-------------|
| navigate(url?) | Navigate to plugin UI |
| click(id) | Click element with data-majk-id |
| type(field, value) | Type into data-majk-field |
| waitForState(state, opts?) | Wait for data-majk-state |
| screenshot(name) | Capture screenshot (auto-numbered) |
| getPage() | Get Playwright Page for advanced operations |
| configure(config) | Update bot configuration (e.g., handlers) |
Screenshot Management
Screenshots are automatically organized:
screenshots/
test-name/
00-first-screenshot.png
01-second-screenshot.png
02-error-click-button.png (automatic on error)Features:
- Sequential numbering (00, 01, 02...)
- Test name as directory
- Automatic error screenshots
- Clear output paths in console
Mock Handlers for UI Tests
Mock handlers intercept plugin API calls:
const mockHandlers = {
async getConversations({ limit = 100, offset = 0 }) {
return [
{
id: 'conv-1',
title: 'Plugin Architecture Discussion',
createdAt: new Date('2025-11-10').toISOString(),
messageCount: 15
}
];
},
async getConversationMessages({ conversationId }) {
return [
{
id: 'msg-1',
role: 'user',
content: 'Test message',
timestamp: new Date().toISOString()
}
];
}
};
uiTest('verify conversations display', async (bot) => {
bot.configure({ handlers: mockHandlers });
await bot.navigate();
const page = bot.getPage();
// Navigate to conversations route
await page.click('a:has-text("Conversations")');
await page.waitForTimeout(1000);
await bot.screenshot('conversations-loaded');
// Verify mock data
const count = await page.locator('[data-majk-id="conversationItem"]').count();
console.log(`✅ Found ${count} conversations`);
// Click first conversation
await page.click('[data-majk-id="conversationItem"]:first-child');
await page.waitForTimeout(500);
await bot.screenshot('conversation-selected');
// Verify messages
const messageText = await page.textContent('[data-majk-id="messageContent"]');
console.log(`✅ Message: ${messageText}`);
});Advanced UI Testing
Access Playwright's full API via bot.getPage():
uiTest('advanced interactions', async (bot) => {
await bot.navigate();
const page = bot.getPage();
// Custom selectors
await page.click('.my-custom-class');
// Wait for specific elements
await page.waitForSelector('[data-loaded="true"]', { timeout: 5000 });
// Extract data
const items = await page.locator('[data-majk-id="item"]').allTextContents();
console.log(`Items: ${items.join(', ')}`);
// Network interception
await page.route('**/api/**', route => {
route.fulfill({
status: 200,
body: JSON.stringify({ custom: 'response' })
});
});
// Fill forms
await page.fill('input[name="email"]', '[email protected]');
// Select dropdowns
await page.selectOption('select[name="role"]', 'admin');
// Standard bot API still works
await bot.screenshot('final-state');
});Running UI Tests
# Run all UI tests
npx majk-test --type ui
# Run UI e2e tests only
npx majk-test --type ui --env e2e
# Run specific UI test
npx majk-test tests/plugin/ui/e2e/my-test.test.js
# Show browser while testing (for debugging)
# Note: use bot-plugin-server directly for this:
npx bot-plugin-server --script tests/plugin/ui/e2e/my-test.test.js --plugin . --headed --slow-mo 500Reporters
Console Reporter (Default)
Beautiful colored output with symbols and formatting.
JSON Reporter
Machine-readable JSON output:
npx majk-test --reporter json --output results.jsonHTML Reporter
Visual HTML report:
npx majk-test --reporter html --output report.htmlTips & Best Practices
- Start with Unit Tests - They're faster and easier to debug
- Use E2E for Integration - Test real auth and data flows
- Keep Tests Focused - One concept per test
- Use Descriptive Names - Test names should explain what they verify
- Leverage Mock Scenarios - Reuse common setups
- Run Tests Often - Catch issues early
Examples
See the /examples directory for complete examples:
- Basic function testing
- Auth configuration
- Mock context usage
- E2E integration tests
License
MIT
