Testing Overview
Testing pieces ensures they work correctly before contributing them to the community. Activepieces provides multiple ways to test:
Local Testing
Test in development environment
Unit Tests
Automated test suites
Integration Tests
End-to-end testing
Local Testing with Hot Reload
The fastest way to test during development is using hot reload.
Start Development Server
This starts:
- Frontend on
http://localhost:4200
- Backend on
http://localhost:3000
- Engine for flow execution
Open Activepieces
Navigate to http://localhost:4200 in your browser
Create a Test Flow
- Click “Create Flow”
- Add your piece as a trigger or action
- Configure the properties
- Click “Test” to execute
Make Changes
Edit your piece code - changes are reflected immediately without restart!// Edit action in src/lib/actions/my-action.ts
export const myAction = createAction({
// Your changes here
});
Save and test again - no rebuild needed.
Hot reloading is enabled by default in development mode. Changes to actions, triggers, properties, and authentication are all hot-reloaded.
Testing Actions
Manual Testing
Test actions directly in the flow builder:
export const sendEmail = createAction({
name: 'send_email',
displayName: 'Send Email',
props: {
to: Property.ShortText({
displayName: 'To',
required: true,
}),
subject: Property.ShortText({
displayName: 'Subject',
required: true,
}),
body: Property.LongText({
displayName: 'Body',
required: true,
}),
},
async run(context) {
// Your implementation
const result = await sendEmailApi({
to: context.propsValue.to,
subject: context.propsValue.subject,
body: context.propsValue.body,
});
return result;
},
// Optional: Custom test function
async test(context) {
// Use test data or a test endpoint
return await sendEmailApi({
to: 'test@example.com',
subject: 'Test Email',
body: 'This is a test',
});
},
});
Test Function
Provide a test function that uses safe test data:
export const createIssue = createAction({
name: 'create_issue',
displayName: 'Create Issue',
props: {
repository: Property.ShortText({
displayName: 'Repository',
required: true,
}),
title: Property.ShortText({
displayName: 'Title',
required: true,
}),
},
async run(context) {
return await createGitHubIssue({
repo: context.propsValue.repository,
title: context.propsValue.title,
labels: ['created-by-activepieces'],
});
},
// Test with a test repository
async test(context) {
return await createGitHubIssue({
repo: 'activepieces/test-repo',
title: '[TEST] ' + context.propsValue.title,
labels: ['test', 'automated'],
});
},
});
Testing Triggers
Polling Triggers
Polling triggers can be tested by clicking the “Test” button:
export const newItems = createTrigger({
name: 'new_items',
displayName: 'New Items',
type: TriggerStrategy.POLLING,
sampleData: {
id: '123',
title: 'Sample Item',
created_at: '2024-01-15T10:00:00Z',
},
async onEnable(context) {
// Initialize state
await context.store.put('lastId', null);
},
async onDisable(context) {
// Cleanup
await context.store.delete('lastId');
},
async run(context) {
const lastId = await context.store.get<string>('lastId');
const items = await fetchNewItems(lastId);
if (items.length > 0) {
await context.store.put('lastId', items[0].id);
}
return items;
},
// Optional: Custom test that returns sample data
async test(context) {
return [
{
id: '1',
title: 'Test Item 1',
created_at: new Date().toISOString(),
},
{
id: '2',
title: 'Test Item 2',
created_at: new Date().toISOString(),
},
];
},
});
Webhook Triggers
Test webhook triggers by:
- Enable the trigger in a flow
- Trigger the actual event in the external service
- Verify webhook was called and flow executed
export const newIssue = createTrigger({
name: 'new_issue',
displayName: 'New Issue',
type: TriggerStrategy.WEBHOOK,
sampleData: {
action: 'opened',
issue: {
id: 1,
title: 'Sample Issue',
},
},
async onEnable(context) {
// Register webhook
console.log('Webhook URL:', context.webhookUrl);
console.log('Webhook Secret:', context.webhookSecret);
const webhook = await registerWebhook({
url: context.webhookUrl,
secret: context.webhookSecret,
events: ['issues'],
});
await context.store.put('webhookId', webhook.id);
},
async onDisable(context) {
const webhookId = await context.store.get('webhookId');
if (webhookId) {
await deleteWebhook(webhookId);
}
},
async run(context) {
const payload = context.payload.body;
if (payload.action !== 'opened') {
return [];
}
return [payload];
},
});
For webhook testing, you’ll need to use a tool like ngrok or deploy to a public URL to receive webhooks during development.
Unit Tests
Write automated tests using Vitest:
Test File Structure
packages/pieces/community/my-piece/
├── src/
│ ├── index.ts
│ └── lib/
│ ├── actions/
│ │ ├── send-message.ts
│ │ └── send-message.test.ts ← Test file
│ └── triggers/
│ ├── new-message.ts
│ └── new-message.test.ts
└── package.json
Example: Action Test
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { sendMessage } from './send-message';
import { createMockContext } from '@activepieces/pieces-framework';
describe('Send Message Action', () => {
let mockContext;
beforeEach(() => {
// Create mock context
mockContext = createMockContext({
auth: {
access_token: 'test-token',
},
propsValue: {
channel: 'C1234567890',
text: 'Hello, World!',
},
});
// Mock fetch
global.fetch = vi.fn();
});
it('should send message successfully', async () => {
// Mock successful API response
(global.fetch as any).mockResolvedValue({
ok: true,
json: async () => ({
ok: true,
ts: '1234567890.123456',
message: {
text: 'Hello, World!',
},
}),
});
const result = await sendMessage.run(mockContext);
expect(result.ok).toBe(true);
expect(result.ts).toBe('1234567890.123456');
expect(global.fetch).toHaveBeenCalledWith(
'https://slack.com/api/chat.postMessage',
expect.objectContaining({
method: 'POST',
headers: expect.objectContaining({
'Authorization': 'Bearer test-token',
}),
})
);
});
it('should handle API errors', async () => {
// Mock API error
(global.fetch as any).mockResolvedValue({
ok: false,
json: async () => ({
ok: false,
error: 'channel_not_found',
}),
});
await expect(sendMessage.run(mockContext)).rejects.toThrow(
'channel_not_found'
);
});
it('should validate required fields', async () => {
mockContext.propsValue.text = '';
await expect(sendMessage.run(mockContext)).rejects.toThrow(
'Message text is required'
);
});
});
Example: Trigger Test
import { describe, it, expect, beforeEach } from 'vitest';
import { newMessageTrigger } from './new-message';
import { createMockContext } from '@activepieces/pieces-framework';
describe('New Message Trigger', () => {
let mockContext;
beforeEach(() => {
mockContext = createMockContext({
auth: {
access_token: 'test-token',
data: {
team_id: 'T1234567890',
},
},
propsValue: {
ignoreBots: false,
},
store: new Map(),
});
});
it('should process channel messages', async () => {
mockContext.payload = {
body: {
event: {
type: 'message',
channel_type: 'channel',
text: 'Hello!',
user: 'U1234567890',
},
},
};
const result = await newMessageTrigger.run(mockContext);
expect(result).toHaveLength(1);
expect(result[0].text).toBe('Hello!');
});
it('should ignore bot messages when configured', async () => {
mockContext.propsValue.ignoreBots = true;
mockContext.payload = {
body: {
event: {
type: 'message',
channel_type: 'channel',
text: 'Bot message',
bot_id: 'B1234567890',
},
},
};
const result = await newMessageTrigger.run(mockContext);
expect(result).toHaveLength(0);
});
it('should ignore DM messages', async () => {
mockContext.payload = {
body: {
event: {
type: 'message',
channel_type: 'im',
text: 'DM message',
},
},
};
const result = await newMessageTrigger.run(mockContext);
expect(result).toHaveLength(0);
});
});
Running Tests
Integration Tests
End-to-end tests that run actual flows:
import { test, expect } from '@playwright/test';
test('create and execute flow with my piece', async ({ page }) => {
// Navigate to Activepieces
await page.goto('http://localhost:4200');
// Create new flow
await page.click('text=Create Flow');
await page.fill('[placeholder="Flow Name"]', 'Test Flow');
// Add trigger
await page.click('text=Add Trigger');
await page.fill('[placeholder="Search"]', 'My Piece');
await page.click('text=New Event');
// Configure trigger
await page.fill('[name="webhookUrl"]', 'https://example.com/webhook');
await page.click('text=Save');
// Add action
await page.click('text=Add Step');
await page.fill('[placeholder="Search"]', 'My Piece');
await page.click('text=Send Message');
// Configure action
await page.fill('[name="channel"]', 'general');
await page.fill('[name="message"]', 'Test message');
// Test action
await page.click('text=Test');
// Verify output
await expect(page.locator('text=Success')).toBeVisible();
});
Run integration tests:
Testing Best Practices
Don’t just test the happy path:it('should handle network errors', async () => {
global.fetch = vi.fn().mockRejectedValue(
new Error('Network error')
);
await expect(action.run(context)).rejects.toThrow(
'Network error'
);
});
it('should handle invalid credentials', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: false,
status: 401,
});
await expect(action.run(context)).rejects.toThrow(
'Authentication failed'
);
});
Never make real API calls in tests:beforeEach(() => {
// Mock all HTTP requests
global.fetch = vi.fn();
});
afterEach(() => {
// Restore original fetch
vi.restoreAllMocks();
});
Provide realistic sample data for testing:const sampleIssue = {
id: 1,
number: 42,
title: 'Test Issue',
body: 'Description',
user: {
login: 'testuser',
id: 12345,
},
labels: [
{ name: 'bug' },
{ name: 'priority-high' },
],
created_at: '2024-01-15T10:00:00Z',
};
For triggers, test state storage:it('should track last processed item', async () => {
const store = new Map();
const context = createMockContext({ store });
await trigger.run(context);
expect(store.get('lastId')).toBe('item-123');
});
Debugging
Console Logging
Use console.log during development:
async run(context) {
console.log('Auth:', context.auth);
console.log('Props:', context.propsValue);
const result = await makeApiCall();
console.log('Result:', result);
return result;
}
VS Code Debugger
Set breakpoints and debug:
- Add launch configuration:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Piece",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "dev"],
"skipFiles": ["<node_internals>/**"],
"console": "integratedTerminal"
}
]
}
- Set breakpoints in your code
- Press F5 to start debugging
Network Debugging
Inspect HTTP requests:
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
const response = await httpClient.sendRequest({
method: HttpMethod.POST,
url: apiUrl,
headers: headers,
body: body,
// Enable request/response logging
verbose: true,
});
console.log('Status:', response.status);
console.log('Headers:', response.headers);
console.log('Body:', response.body);
Testing Checklist
Before contributing your piece:
Next Steps
Contribute Your Piece
Submit your piece to the community
Versioning
Learn about piece versioning