mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
feat: Add performance plan presets for testcontainers (#18231)
This commit is contained in:
@@ -36,6 +36,25 @@ test('basic test', ...) // All modes, fully paralle
|
||||
test('postgres only @mode:postgres', ...) // Mode-specific
|
||||
test('needs clean db @db:reset', ...) // Sequential per worker
|
||||
test('chaos test @mode:multi-main @chaostest', ...) // Isolated per worker
|
||||
test('cloud resource test @cloud:trial', ...) // Cloud resource constraints
|
||||
```
|
||||
|
||||
## Fixture Selection
|
||||
- **`base.ts`**: Standard testing with worker-scoped containers (default choice)
|
||||
- **`cloud-only.ts`**: Cloud resource testing with guaranteed isolation
|
||||
- Use for performance testing under resource constraints
|
||||
- Requires `@cloud:*` tags (`@cloud:trial`, `@cloud:enterprise`, etc.)
|
||||
- Creates only cloud containers, no worker containers
|
||||
|
||||
```typescript
|
||||
// Standard testing
|
||||
import { test, expect } from '../fixtures/base';
|
||||
|
||||
// Cloud resource testing
|
||||
import { test, expect } from '../fixtures/cloud-only';
|
||||
test('Performance under constraints @cloud:trial', async ({ n8n, api }) => {
|
||||
// Test runs with 384MB RAM, 250 millicore CPU
|
||||
});
|
||||
```
|
||||
|
||||
## Tips
|
||||
@@ -47,6 +66,8 @@ test('chaos test @mode:multi-main @chaostest', ...) // Isolated per worker
|
||||
- **composables**: Multi-page interactions (e.g., `WorkflowComposer.executeWorkflowAndWaitForNotification()`)
|
||||
- **config**: Test setup and configuration (constants, test users, etc.)
|
||||
- **fixtures**: Custom test fixtures extending Playwright's base test
|
||||
- `base.ts`: Standard fixtures with worker-scoped containers
|
||||
- `cloud-only.ts`: Cloud resource testing with test-scoped containers only
|
||||
- **pages**: Page Object Models for UI interactions
|
||||
- **services**: API helpers for E2E controller, REST calls, etc.
|
||||
- **utils**: Utility functions (string manipulation, helpers, etc.)
|
||||
|
||||
@@ -2,7 +2,6 @@ import { test as base, expect } from '@playwright/test';
|
||||
import type { N8NStack } from 'n8n-containers/n8n-test-container-creation';
|
||||
import { createN8NStack } from 'n8n-containers/n8n-test-container-creation';
|
||||
import { ContainerTestHelpers } from 'n8n-containers/n8n-test-container-helpers';
|
||||
import { setTimeout as wait } from 'node:timers/promises';
|
||||
|
||||
import { setupDefaultInterceptors } from '../config/intercepts';
|
||||
import { n8nPage } from '../pages/n8nPage';
|
||||
@@ -70,9 +69,6 @@ export const test = base.extend<TestFixtures, WorkerFixtures>({
|
||||
console.log('Creating container with config:', containerConfig);
|
||||
const container = await createN8NStack(containerConfig);
|
||||
|
||||
// TODO: Remove this once we have a better way to wait for the container to be ready (e.g. healthcheck)
|
||||
await wait(3000);
|
||||
|
||||
console.log(`Container URL: ${container.baseUrl}`);
|
||||
|
||||
await use(container);
|
||||
|
||||
169
packages/testing/playwright/fixtures/cloud.ts
Normal file
169
packages/testing/playwright/fixtures/cloud.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* Cloud Resource Testing Fixtures
|
||||
*
|
||||
* This fixture provides cloud containers with worker containers.
|
||||
* Use this when you want to test with cloud resource constraints.
|
||||
*
|
||||
* Architecture:
|
||||
* - No worker containers - cloud containers only
|
||||
* - Test-scoped containers with resource limits
|
||||
* - Complete fixture chain (n8n, api, context, page)
|
||||
* - Per-test database reset
|
||||
*/
|
||||
|
||||
import { test as base, expect } from '@playwright/test';
|
||||
import type { N8NConfig, N8NStack } from 'n8n-containers/n8n-test-container-creation';
|
||||
import { createN8NStack } from 'n8n-containers/n8n-test-container-creation';
|
||||
import { type PerformancePlanName, BASE_PERFORMANCE_PLANS } from 'n8n-containers/performance-plans';
|
||||
|
||||
import { setupDefaultInterceptors } from '../config/intercepts';
|
||||
import { n8nPage } from '../pages/n8nPage';
|
||||
import { ApiHelpers } from '../services/api-helper';
|
||||
|
||||
/**
|
||||
* Create standardized project name for containers
|
||||
*/
|
||||
function createProjectName(prefix: string, profile: string, testTitle: string): string {
|
||||
return `${prefix}-${profile}-${testTitle.replace(/[^a-z0-9]/gi, '-').toLowerCase()}`;
|
||||
}
|
||||
|
||||
type CloudOnlyFixtures = {
|
||||
cloudContainer: N8NStack;
|
||||
n8n: n8nPage;
|
||||
api: ApiHelpers;
|
||||
baseURL: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract cloud resource profile from test tags
|
||||
* Looks for @cloud:trial, @cloud:enterprise, etc.
|
||||
*/
|
||||
function getCloudResourceProfile(tags: string[]): PerformancePlanName | null {
|
||||
const cloudTag = tags.find((tag) => tag.startsWith('@cloud:'));
|
||||
if (!cloudTag) return null;
|
||||
|
||||
const profile = cloudTag.replace('@cloud:', '');
|
||||
if (profile in BASE_PERFORMANCE_PLANS) {
|
||||
return profile;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cloud-only test fixtures - no worker containers, only cloud containers
|
||||
*/
|
||||
export const test = base.extend<CloudOnlyFixtures>({
|
||||
cloudContainer: async ({ browser }, use, testInfo) => {
|
||||
const cloudProfile = getCloudResourceProfile(testInfo.tags);
|
||||
|
||||
if (!cloudProfile) {
|
||||
throw new Error(
|
||||
`Cloud-only fixture requires @cloud:* tags. Found tags: ${testInfo.tags.join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (process.env.N8N_BASE_URL) {
|
||||
throw new Error('Cloud-only fixture cannot be used with N8N_BASE_URL environment variable');
|
||||
}
|
||||
|
||||
const resourceConfig = BASE_PERFORMANCE_PLANS[cloudProfile];
|
||||
console.log(`Creating cloud container: ${cloudProfile}`);
|
||||
|
||||
const config: N8NConfig = {
|
||||
resourceQuota: {
|
||||
memory: resourceConfig.memory,
|
||||
cpu: resourceConfig.cpu,
|
||||
},
|
||||
env: {
|
||||
E2E_TESTS: 'true',
|
||||
},
|
||||
projectName: createProjectName('n8n-stack-cloud', cloudProfile, testInfo.title),
|
||||
};
|
||||
|
||||
const stack = await createN8NStack(config);
|
||||
|
||||
console.log('🔄 Resetting database for cloud container');
|
||||
|
||||
const context = await browser.newContext({ baseURL: stack.baseUrl });
|
||||
const api = new ApiHelpers(context.request);
|
||||
|
||||
await api.resetDatabase();
|
||||
await context.close();
|
||||
|
||||
console.log(`✅ Cloud container ready: ${stack.baseUrl}`);
|
||||
|
||||
await use(stack);
|
||||
|
||||
// Cleanup
|
||||
console.log('🧹 Cleaning up cloud container');
|
||||
await stack.stop();
|
||||
},
|
||||
|
||||
// Base URL from cloud container
|
||||
baseURL: async ({ cloudContainer }, use) => {
|
||||
await use(cloudContainer.baseUrl);
|
||||
},
|
||||
|
||||
// Browser context with cloud container URL and interceptors
|
||||
context: async ({ context, baseURL }, use) => {
|
||||
await setupDefaultInterceptors(context);
|
||||
await use(context);
|
||||
},
|
||||
|
||||
// Page with authentication setup
|
||||
page: async ({ context }, use, testInfo) => {
|
||||
const page = await context.newPage();
|
||||
const api = new ApiHelpers(context.request);
|
||||
|
||||
// Set up authentication from tags (works for cloud containers)
|
||||
await api.setupFromTags(testInfo.tags);
|
||||
|
||||
await use(page);
|
||||
await page.close();
|
||||
},
|
||||
|
||||
// n8n page object
|
||||
n8n: async ({ page }, use) => {
|
||||
const n8nInstance = new n8nPage(page);
|
||||
await use(n8nInstance);
|
||||
},
|
||||
|
||||
// API helpers
|
||||
api: async ({ context }, use) => {
|
||||
const api = new ApiHelpers(context.request);
|
||||
await use(api);
|
||||
},
|
||||
});
|
||||
|
||||
export { expect };
|
||||
|
||||
/*
|
||||
CLOUD-ONLY FIXTURE BENEFITS:
|
||||
|
||||
✅ No worker containers: Only cloud containers are created
|
||||
✅ Guaranteed cloud testing: Tests must have @cloud:* tags or they fail
|
||||
✅ Complete fixture chain: Full n8n/api/context/page fixtures available
|
||||
✅ Fresh containers: Each test gets its own cloud container with resource limits
|
||||
✅ Clean database state: Per-test database reset with enhanced timing
|
||||
✅ Resource isolation: True cloud plan simulation without interference
|
||||
|
||||
Usage:
|
||||
|
||||
// Import the cloud-only fixture instead of base
|
||||
import { test, expect } from '../../fixtures/cloud-only';
|
||||
|
||||
test('Performance test @cloud:trial', async ({ n8n, api }) => {
|
||||
// This test runs ONLY on a trial plan container (768MB, 200 millicore)
|
||||
// No worker containers are created
|
||||
});
|
||||
|
||||
Flow:
|
||||
1. Detect @cloud:* tag (required)
|
||||
2. Create cloud container with resource limits
|
||||
3. Wait 5s + database reset with retries
|
||||
4. Provide complete n8n/api fixture chain
|
||||
5. Run test against cloud container only
|
||||
6. Clean up cloud container
|
||||
|
||||
Perfect for: Performance testing, resource constraint testing, cloud plan validation
|
||||
*/
|
||||
@@ -11,6 +11,7 @@
|
||||
"test:container:postgres": "playwright test --project='postgres:*'",
|
||||
"test:container:queue": "playwright test --project='queue:*'",
|
||||
"test:container:multi-main": "playwright test --project='multi-main:*'",
|
||||
"test:container:trial": "playwright test --project='trial:*'",
|
||||
"test:workflows:setup": "tsx ./tests/cli-workflows/setup-workflow-tests.ts",
|
||||
"test:workflows": "playwright test --project=cli-workflows",
|
||||
"test:workflows:schema": "SCHEMA=true playwright test --project=cli-workflows",
|
||||
|
||||
@@ -15,7 +15,7 @@ const CONTAINER_CONFIGS: Array<{ name: string; config: N8NConfig }> = [
|
||||
{ name: 'standard', config: {} },
|
||||
{ name: 'postgres', config: { postgres: true } },
|
||||
{ name: 'queue', config: { queueMode: true } },
|
||||
{ name: 'multi-main', config: { queueMode: { mains: 2, workers: 1 } } }, // Multi main is having timing issues on startup, needs to be resolved
|
||||
{ name: 'multi-main', config: { queueMode: { mains: 2, workers: 1 } } },
|
||||
];
|
||||
|
||||
export function getProjects(): Project[] {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Large Node Performance Tests with Cloud Resource Constraints
|
||||
*
|
||||
* These tests use @cloud-* tags to automatically create resource-limited containers
|
||||
* that simulate n8n Cloud plan constraints.
|
||||
*/
|
||||
|
||||
import { test, expect } from '../../fixtures/cloud';
|
||||
import type { n8nPage } from '../../pages/n8nPage';
|
||||
import { measurePerformance } from '../../utils/performance-helper';
|
||||
|
||||
async function setupPerformanceTest(n8n: n8nPage, size: number) {
|
||||
await n8n.goHome();
|
||||
await n8n.workflows.clickNewWorkflowCard();
|
||||
await n8n.canvas.importWorkflow('large.json', 'Large Workflow');
|
||||
await n8n.notifications.closeNotificationByText('Successful');
|
||||
|
||||
// Configure data size
|
||||
await n8n.canvas.openNode('Edit Fields');
|
||||
await n8n.page
|
||||
.getByTestId('parameter-input-value')
|
||||
.getByTestId('parameter-input-field')
|
||||
.fill(size.toString());
|
||||
await n8n.ndv.clickBackToCanvasButton();
|
||||
}
|
||||
|
||||
test.describe('Large Node Performance - Cloud Resources', () => {
|
||||
test('Large workflow with starter plan resources @cloud:starter', async ({ n8n }) => {
|
||||
await setupPerformanceTest(n8n, 30000);
|
||||
const loopSize = 20;
|
||||
const stats = [];
|
||||
|
||||
await n8n.workflowComposer.executeWorkflowAndWaitForNotification(
|
||||
'Workflow executed successfully',
|
||||
{
|
||||
timeout: 30000,
|
||||
},
|
||||
);
|
||||
|
||||
for (let i = 0; i < loopSize; i++) {
|
||||
const openNodeDuration = await measurePerformance(n8n.page, `open-node-${i}`, async () => {
|
||||
await n8n.canvas.openNode('Code');
|
||||
});
|
||||
|
||||
stats.push(openNodeDuration);
|
||||
await n8n.ndv.clickBackToCanvasButton();
|
||||
|
||||
console.log(`✓ Open node (${i + 1} of ${loopSize}): ${openNodeDuration.toFixed(1)}ms`);
|
||||
}
|
||||
const average = stats.reduce((a, b) => a + b, 0) / stats.length;
|
||||
console.log(`Average open node duration: ${average.toFixed(1)}ms`);
|
||||
expect(average).toBeLessThan(5000);
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
Usage:
|
||||
|
||||
# Run all performance tests (including cloud resource tests)
|
||||
pnpm --filter n8n-playwright test:performance
|
||||
|
||||
# Run only cloud resource tests
|
||||
pnpm --filter n8n-playwright test --grep "@cloud:"
|
||||
|
||||
# Run specific cloud plan tests
|
||||
pnpm --filter n8n-playwright test --grep "@cloud:trial"
|
||||
pnpm --filter n8n-playwright test --grep "@cloud:enterprise"
|
||||
|
||||
# Run this specific file (cloud resource tests only)
|
||||
pnpm --filter n8n-playwright test tests/performance/large-node-cloud.spec.ts
|
||||
*/
|
||||
Reference in New Issue
Block a user