Jest integration and test framework for Pilaf.
npm install @pilaf/frameworkJest integration and test framework for Pilaf.
Provides the StoryRunner for executing declarative test stories against Minecraft servers.
``bash`
pnpm add @pilaf/framework
`bash`
pnpm add -D jest@^29.0.0
The simplest way to test with bot players:
`javascript
const { createTestContext, cleanupTestContext } = require('@pilaf/framework');
describe('My Plugin Tests', () => {
let context;
beforeAll(async () => {
// Creates RCON connection + bot player in one call
context = await createTestContext({
username: 'TestPlayer',
host: 'localhost',
gamePort: 25565,
rconPort: 25575,
rconPassword: 'minecraft'
});
});
afterAll(async () => {
await cleanupTestContext(context);
});
it('should execute command as bot', async () => {
// Use bot.chat() for player commands
context.bot.chat('/gamemode creative');
// Use backend.sendCommand() for RCON commands
const result = await context.backend.sendCommand('seed');
expect(result.raw).toContain('Seed');
});
});
`
The StoryRunner executes declarative test stories against Minecraft servers.
`javascript
const { StoryRunner } = require('@pilaf/framework');
const runner = new StoryRunner();
const story = {
name: 'My Test Story',
setup: {
server: {
type: 'paper',
version: '1.21.8',
rcon: {
host: 'localhost',
port: 25575,
password: 'password'
}
},
players: [
{
name: 'Player1',
username: 'TestPlayer',
auth: 'offline'
}
]
},
steps: [
{
name: 'Make player an operator',
action: 'execute_command',
command: 'op Player1'
},
{
name: 'Execute player command',
action: 'execute_player_command',
player: 'Player1',
command: '/gamemode creative'
}
],
teardown: {
stop_server: false,
disconnect_players: true
}
};
const result = await runner.execute(story);
console.log(result.success); // true or false
`
`javascript`
setup: {
server: {
type: 'paper', // Server type (optional)
version: '1.21.8', // Minecraft version (optional)
rcon: {
host: 'localhost',
port: 25575,
password: 'password'
}
},
players: [
{
name: 'InternalName', // Reference name in steps
username: 'MinecraftName',
auth: 'offline' // or 'microsoft'
}
]
}
Each step has:
`javascript`
{
name: 'Step description',
action: 'action_name',
// action-specific parameters
}
Available Actions:
- execute_command - Execute RCON commandexecute_player_command
- - Execute command as playerchat
- - Send chat messagemove_forward
- - Move player forwardwait
- - Pause executionget_entities
- - Get nearby entitiesget_player_inventory
- - Get player inventoryget_player_location
- - Get player positioncalculate_distance
- - Calculate distance between positionslogin
- / logout - Reconnect/disconnect playerrespawn
- - Respawn playerassert
- - Make assertions
Store results from steps and use them later:
`javascript`
steps: [
{
name: 'Get position',
action: 'get_player_location',
player: 'P1',
store_as: 'start_pos'
},
{
name: 'Calculate distance',
action: 'calculate_distance',
from: '{start_pos}',
to: '{end_pos}',
store_as: 'distance'
}
]
`javascript`
teardown: {
stop_server: false, // Keep server running
disconnect_players: true // Disconnect players
}
Pilaf includes a custom Jest reporter that generates HTML reports:
`javascript`
// jest.config.js
module.exports = {
testMatch: ['*/.pilaf.test.js'],
reporters: [
'default',
['@pilaf/framework/lib/reporters/pilaf-reporter.js', {
outputPath: 'target/pilaf-reports/index.html',
suiteName: 'My Plugin Tests'
}]
]
};
The createTestContext() helper provides a simplified way to create bot players for testing. It sets up both RCON and Mineflayer backends simultaneously.
Why use this?
When testing Minecraft plugins with bot players, you need TWO separate connections:
1. Bot player - For executing player commands (via bot.chat())rcon.send()
2. RCON - For server commands that return responses (via )
The MineflayerBackend.sendCommand() method only sends commands via bot chat and always returns empty responses. You need RCON to get actual server responses for commands like /data get, /teleport, etc.
`javascript
const { createTestContext, cleanupTestContext } = require('@pilaf/framework');
describe('My Plugin', () => {
let context;
beforeAll(async () => {
context = await createTestContext({
username: 'TestPlayer',
rconPassword: 'dragon123'
});
});
afterAll(async () => {
await cleanupTestContext(context);
});
it('should verify entity position after teleport', async () => {
// Get position BEFORE (using RCON - returns actual data!)
const beforeResult = await context.rcon.send('data get entity TestPlayer Pos');
const beforePos = parsePosition(beforeResult);
// Execute ability (using bot chat)
context.bot.chat('/myplugin teleport 100 64 100');
await wait(1000);
// Get position AFTER (using RCON - returns actual data!)
const afterResult = await context.rcon.send('data get entity TestPlayer Pos');
const afterPos = parsePosition(afterResult);
// Verify teleportation
expect(afterPos.x).toBeCloseTo(100, 0);
});
});
`
API Reference:
`javascript
const context = await createTestContext({
username: 'TestPlayer', // Bot username (default: 'TestPlayer')
host: 'localhost', // Server host (default: 'localhost')
gamePort: 25565, // Game port (default: 25565)
rconPort: 25575, // RCON port (default: 25575)
rconPassword: 'minecraft', // RCON password (default: 'minecraft')
auth: 'offline' // Auth mode (default: 'offline')
});
// Returns:
{
backend: MineflayerBackend, // For bot control
rcon: RconBackend, // For server commands WITH RESPONSES
bot: Object, // The bot player instance
playerName: string // Bot's username
}
// Use RCON for commands that need responses
const result = await context.rcon.send('data get entity TestPlayer Pos');
// Use bot chat for player commands
context.bot.chat('/mycommand');
// Cleanup when done
await cleanupTestContext(context);
`
`javascript
const { captureState, compareStates } = require('@pilaf/framework/lib/helpers/state');
// Capture state with deep clone
const before = captureState(myObject);
// Compare states
const diff = compareStates(before, after);
console.log(diff.hasChanges); // true or false
console.log(diff.changes); // Array of changes
`
`javascript
const { waitForEvents, captureEvents } = require('@pilaf/framework/lib/helpers/events');
// Wait for events
const events = await waitForEvents(bot, 'chat', 3, 5000);
// Capture events
const capture = captureEvents(bot, ['chat', 'playerJoined']);
// ... do stuff ...
capture.release(); // Clean up listeners
`
Custom Jest matchers for game testing:
`javascript
const { matchers } = require('@pilaf/framework');
expect.extend(matchers);
// Use in tests
await expect(player).toHavePosition({ x: 100, y: 64, z: 100 });
await expect(player).toHaveInventoryItem('diamond');
``
MIT