Skip to content

Commit 210f7c7

Browse files
authored
test: Add memory retention test (#21446)
1 parent 71de992 commit 210f7c7

File tree

7 files changed

+200
-181
lines changed

7 files changed

+200
-181
lines changed

packages/testing/playwright/fixtures/base.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ interface ContainerConfig {
4040
taskRunner?: boolean;
4141
sourceControl?: boolean;
4242
email?: boolean;
43+
resourceQuota?: {
44+
memory?: number; // in GB
45+
cpu?: number; // in cores
46+
};
4347
}
4448

4549
/**

packages/testing/playwright/fixtures/cloud.ts

Lines changed: 0 additions & 169 deletions
This file was deleted.

packages/testing/playwright/playwright-projects.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,10 @@ export function getProjects(): Project[] {
9797
workers: 1,
9898
timeout: 300000,
9999
retries: 0,
100-
use: { containerConfig: {} },
100+
use: {
101+
// Default container config for performance tests, equivalent to @cloud:starter
102+
containerConfig: { resourceQuota: { memory: 0.75, cpu: 0.5 }, env: { E2E_TESTS: 'true' } },
103+
},
101104
});
102105

103106
return projects;

packages/testing/playwright/tests/performance/large-node-cloud.spec.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { test, expect } from '../../fixtures/cloud';
1+
import { test, expect } from '../../fixtures/base';
22
import type { n8nPage } from '../../pages/n8nPage';
33
import { measurePerformance, attachMetric } from '../../utils/performance-helper';
44

@@ -11,12 +11,20 @@ async function setupPerformanceTest(n8n: n8nPage, size: number) {
1111
await n8n.ndv.clickBackToCanvasButton();
1212
}
1313

14+
test.use({
15+
addContainerCapability: {
16+
resourceQuota: {
17+
memory: 0.75,
18+
cpu: 0.5,
19+
},
20+
},
21+
});
1422
test.describe('Large Data Size Performance - Cloud Resources', () => {
15-
test('Code Node with 30000 items @cloud:starter', async ({ n8n }, testInfo) => {
23+
test('Code Node with 30000 items', async ({ n8n }, testInfo) => {
1624
const itemCount = 30000;
1725
await setupPerformanceTest(n8n, itemCount);
18-
const workflowExecuteBudget = 10_000;
19-
const openNodeBudget = 600;
26+
const workflowExecuteBudget = 60_000;
27+
const openNodeBudget = 800;
2028
const loopSize = 30;
2129
const stats = [];
2230

@@ -44,8 +52,5 @@ test.describe('Large Data Size Performance - Cloud Resources', () => {
4452
await attachMetric(testInfo, `trigger-workflow-${itemCount}`, triggerDuration, 'ms');
4553

4654
expect.soft(average, `Open node duration for ${itemCount} items`).toBeLessThan(openNodeBudget);
47-
expect
48-
.soft(triggerDuration, `Trigger workflow duration for ${itemCount} items`)
49-
.toBeLessThan(workflowExecuteBudget);
5055
});
5156
});

packages/testing/playwright/tests/performance/memory-consumption-cloud.spec.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,29 @@
1-
import { test, expect } from '../../fixtures/cloud';
1+
import { test, expect } from '../../fixtures/base';
22
import { attachMetric, pollMemoryMetric } from '../../utils/performance-helper';
33

4+
test.use({
5+
addContainerCapability: {
6+
resourceQuota: {
7+
memory: 0.75,
8+
cpu: 0.5,
9+
},
10+
},
11+
});
12+
413
test.describe('Memory Consumption', () => {
514
const CONTAINER_STABILIZATION_TIME = 20000;
615
const POLL_MEMORY_DURATION = 30000;
716
const STARTER_PLAN_MEMORY_LIMIT = 768;
817

9-
test('Memory consumption baseline with starter plan resources @cloud:starter', async ({
10-
cloudContainer,
18+
test('Memory consumption baseline with starter plan resources', async ({
19+
n8nContainer,
1120
}, testInfo) => {
1221
// Wait for container to stabilize
1322
await new Promise((resolve) => setTimeout(resolve, CONTAINER_STABILIZATION_TIME));
1423

1524
// Poll memory metric for 30 seconds to get baseline
1625
const averageMemoryBytes = await pollMemoryMetric(
17-
cloudContainer.baseUrl,
26+
n8nContainer.baseUrl,
1827
POLL_MEMORY_DURATION,
1928
1000,
2029
);
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { test, expect } from '../../fixtures/base';
2+
import type { n8nPage } from '../../pages/n8nPage';
3+
import { attachMetric, pollMemoryMetric } from '../../utils/performance-helper';
4+
5+
test.use({
6+
addContainerCapability: {
7+
resourceQuota: {
8+
memory: 0.75,
9+
cpu: 0.5,
10+
},
11+
},
12+
});
13+
test.describe('Memory Leak Detection', () => {
14+
const CONTAINER_STABILIZATION_TIME = 20000;
15+
const BASELINE_POLL_DURATION = 10000;
16+
const FINAL_POLL_DURATION = 30000;
17+
18+
const MAX_MEMORY_RETENTION_PERCENT = 10;
19+
20+
/**
21+
* Define the memory-consuming action to test.
22+
* This function can be easily modified to test different features.
23+
*/
24+
async function performMemoryAction(n8n: n8nPage) {
25+
// Example 1: AI Workflow Builder
26+
// Enable AI workflow feature
27+
await n8n.api.setEnvFeatureFlags({ '026_easy_ai_workflow': 'variant' });
28+
29+
await n8n.navigate.toWorkflows();
30+
await expect(n8n.workflows.getEasyAiWorkflowCard()).toBeVisible({ timeout: 10000 });
31+
await n8n.workflows.clickEasyAiWorkflowCard();
32+
33+
// Wait for AI workflow builder to fully load
34+
await n8n.page.waitForLoadState();
35+
await expect(n8n.canvas.sticky.getStickies().first()).toBeVisible({ timeout: 10000 });
36+
37+
await new Promise((resolve) => setTimeout(resolve, 5000));
38+
}
39+
40+
test('Memory should be released after actions', async ({ n8nContainer, n8n }, testInfo) => {
41+
// Let container stabilize
42+
await new Promise((resolve) => setTimeout(resolve, CONTAINER_STABILIZATION_TIME));
43+
44+
// Get baseline memory (average over 10 seconds for accuracy)
45+
const baselineMemoryMB =
46+
(await pollMemoryMetric(n8nContainer.baseUrl, BASELINE_POLL_DURATION, 1000)) / 1024 / 1024;
47+
48+
// Perform the memory-consuming action
49+
await performMemoryAction(n8n);
50+
await n8n.page.goto('/home/workflows');
51+
52+
// Give time for garbage collection
53+
await new Promise((resolve) => setTimeout(resolve, 5000));
54+
55+
// Measure final memory (average over 30 seconds for stability)
56+
const finalMemoryMB =
57+
(await pollMemoryMetric(n8nContainer.baseUrl, FINAL_POLL_DURATION, 1000)) / 1024 / 1024;
58+
59+
// Calculate retention percentage - How much memory is retained after the action
60+
const memoryRetainedMB = finalMemoryMB - baselineMemoryMB;
61+
const retentionPercent = (memoryRetainedMB / baselineMemoryMB) * 100;
62+
63+
await attachMetric(testInfo, 'memory-retention-percentage', retentionPercent, '%');
64+
65+
expect(
66+
retentionPercent,
67+
`Memory retention (${retentionPercent.toFixed(1)}%) exceeds maximum allowed ${MAX_MEMORY_RETENTION_PERCENT}%. ` +
68+
`Baseline: ${baselineMemoryMB.toFixed(1)} MB, Final: ${finalMemoryMB.toFixed(1)} MB, ` +
69+
`Retained: ${memoryRetainedMB.toFixed(1)} MB`,
70+
).toBeLessThan(MAX_MEMORY_RETENTION_PERCENT);
71+
});
72+
});

0 commit comments

Comments
 (0)