first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,66 @@
import { test, expect } from '../../../../fixtures/base';
test.describe(
'Executions Filter',
{
annotation: [{ type: 'owner', description: 'Catalysts' }],
},
() => {
test.beforeEach(async ({ n8n }) => {
await n8n.start.fromImportedWorkflow('Test_workflow_4_executions_view.json');
// Create mix of successful and failed executions
await n8n.executionsComposer.createExecutions(2);
await n8n.canvas.toggleNodeEnabled('Error');
await expect(n8n.canvas.disabledNodes()).toHaveCount(0);
await n8n.executionsComposer.createExecutions(2);
const executionsResponsePromise = n8n.page.waitForResponse((response) =>
response.url().includes('/rest/executions?filter='),
);
await n8n.canvas.clickExecutionsTab();
await executionsResponsePromise;
await expect(n8n.executions.getExecutionItems().first()).toBeVisible();
});
test('should filter executions by status and show filter badge', async ({ n8n }) => {
await n8n.executions.openFilter();
await expect(n8n.executions.getFilterForm()).toBeVisible();
// No badge before filtering
await expect(n8n.executions.getFilterBadge()).toBeHidden();
const filterRequestPromise = n8n.page.waitForResponse(
(response) =>
response.url().includes('/rest/executions?filter=') && response.url().includes('error'),
);
await n8n.executions.selectFilterStatus('Error');
await filterRequestPromise;
// Badge shows active filter count
await expect(n8n.executions.getFilterBadge()).toBeVisible();
// Only failed executions visible
await expect(n8n.executions.getFailedExecutionItems()).toHaveCount(2);
await expect(n8n.executions.getSuccessfulExecutionItems()).toHaveCount(0);
});
test('should reset filter and remove badge', async ({ n8n }) => {
await n8n.executions.openFilter();
await n8n.executions.selectFilterStatus('Success');
await expect(n8n.executions.getFilterBadge()).toBeVisible();
const resetRequestPromise = n8n.page.waitForResponse((response) =>
response.url().includes('/rest/executions?filter='),
);
await n8n.executions.resetFilter();
await resetRequestPromise;
await expect(n8n.executions.getFilterBadge()).toBeHidden();
await expect(n8n.executions.getExecutionItems()).toHaveCount(4);
});
},
);
@@ -0,0 +1,286 @@
import { test, expect } from '../../../../fixtures/base';
import executionOutOfMemoryResponse from '../../../../fixtures/execution-out-of-memory-server-response.json';
test.describe(
'Executions Filter',
{
annotation: [{ type: 'owner', description: 'Lifecycle & Governance' }],
},
() => {
test.beforeEach(async ({ n8n }) => {
await n8n.start.fromImportedWorkflow('Test_workflow_4_executions_view.json');
});
// There is flakiness in this test. When the dropdown is opened, at times it appears to automatically close even without user interaction.
// Manual timeouts have been added to try to mitigate this, but it still happens.
test.fixme(
'should keep popover open when selecting from dropdown inside it',
async ({ n8n }) => {
// Regression test: Element Plus dropdowns are teleported to body, causing
// Reka UI's DismissableLayer to detect clicks as "outside" and close the popover.
// This test verifies the popover stays open during and after dropdown selection.
// Create some executions first
await n8n.executionsComposer.createExecutions(2);
// Go to executions tab
await n8n.canvas.clickExecutionsTab();
await expect(n8n.executions.getExecutionItems().first()).toBeVisible();
// Open filter popover
await n8n.executions.openFilter();
const filterForm = n8n.executions.getFilterForm();
await n8n.page.waitForTimeout(1500);
await expect(filterForm).toBeVisible();
// Click to open the status dropdown
await n8n.executions.getStatusSelect().click();
// Verify popover is still open while dropdown is open
await expect(filterForm).toBeVisible();
// Set up listener for the filtered executions request
const filterRequestPromise = n8n.page.waitForRequest(
(request) =>
request.url().includes('/rest/executions?filter=') && request.url().includes('success'),
);
await n8n.page.waitForTimeout(500);
// Select an option from the dropdown
await n8n.page.getByRole('option', { name: 'Success' }).click();
// Verify the filter request was sent to the backend (confirms selection worked)
const filterRequest = await filterRequestPromise;
expect(filterRequest.url()).toContain('status');
expect(filterRequest.url()).toContain('success');
// KEY ASSERTION: Verify the popover did NOT close after selecting from dropdown
await expect(filterForm).toBeVisible();
},
);
},
);
const ERROR_MESSAGES = {
OUT_OF_MEMORY: 'Workflow did not finish, possible out-of-memory issue',
} as const;
const TIMEOUTS = {
EXECUTIONS_REFRESH_INTERVAL: 4000,
} as const;
test.describe('Workflow Executions', () => {
test.describe('when workflow is saved', () => {
test.beforeEach(async ({ n8n }) => {
await n8n.start.fromImportedWorkflow('Test_workflow_4_executions_view.json');
});
test('should render executions tab correctly', async ({ n8n }) => {
await n8n.executionsComposer.createExecutions(15);
await n8n.canvas.toggleNodeEnabled('Error');
await expect(n8n.canvas.disabledNodes()).toHaveCount(0);
await n8n.executionsComposer.createExecutions(2);
await n8n.canvas.toggleNodeEnabled('Error');
await expect(n8n.canvas.disabledNodes()).toHaveCount(1);
await n8n.executionsComposer.createExecutions(15);
const executionsResponsePromise = n8n.page.waitForResponse((response) =>
response.url().includes('/rest/executions?filter='),
);
await n8n.canvas.clickExecutionsTab();
await executionsResponsePromise;
await expect(n8n.executions.getExecutionItems().first()).toBeVisible();
const loadMoreResponsePromise = n8n.page.waitForResponse((response) =>
response.url().includes('/rest/executions?filter='),
);
await n8n.executions.scrollExecutionsListToBottom();
await loadMoreResponsePromise;
await expect(n8n.executions.getExecutionItems()).toHaveCount(30, { timeout: 15000 });
await expect(n8n.executions.getSuccessfulExecutionItems()).toHaveCount(28);
await expect(n8n.executions.getFailedExecutionItems()).toHaveCount(2);
await expect(n8n.executions.getFirstExecutionItem()).toHaveClass(/_active_/);
});
test('should not redirect back to execution tab when request is not done before leaving the page', async ({
n8n,
}) => {
await n8n.page.route('**/rest/executions?filter=*', (route) => route.continue());
await n8n.page.route('**/rest/executions/active?filter=*', (route) => route.continue());
await n8n.canvas.clickExecutionsTab();
await n8n.canvas.clickEditorTab();
await expect(n8n.page).toHaveURL(/\/workflow\/[^/]+$/, {
timeout: TIMEOUTS.EXECUTIONS_REFRESH_INTERVAL,
});
for (let i = 0; i < 3; i++) {
await n8n.canvas.clickExecutionsTab();
await n8n.canvas.clickEditorTab();
}
await expect(n8n.page).toHaveURL(/\/workflow\/[^/]+$/, {
timeout: TIMEOUTS.EXECUTIONS_REFRESH_INTERVAL,
});
await n8n.canvas.clickExecutionsTab();
// eslint-disable-next-line playwright/no-wait-for-timeout
await n8n.page.waitForTimeout(1000);
await n8n.canvas.clickEditorTab();
await expect(n8n.page).toHaveURL(/\/workflow\/[^/]+$/, {
timeout: TIMEOUTS.EXECUTIONS_REFRESH_INTERVAL,
});
});
test('should not redirect back to execution tab when slow request is not done before leaving the page', async ({
n8n,
}) => {
await n8n.page.route('**/rest/executions?filter=*', async (route) => {
await new Promise((resolve) => setTimeout(resolve, 2000));
await route.continue();
});
await n8n.page.route('**/rest/executions/active?filter=*', async (route) => {
await new Promise((resolve) => setTimeout(resolve, 2000));
await route.continue();
});
await n8n.canvas.clickExecutionsTab();
await n8n.page.waitForURL(/\/executions/);
await n8n.canvas.clickEditorTab();
await n8n.page.waitForURL(/\/workflow\/[^/]+$/);
await expect(n8n.page).toHaveURL(/\/workflow\/[^/]+$/, {
timeout: TIMEOUTS.EXECUTIONS_REFRESH_INTERVAL,
});
});
test('should error toast when server error message returned without stack trace', async ({
n8n,
}) => {
const responsePromise = n8n.page.waitForResponse(
(response) =>
response.url().includes('/rest/workflows/') &&
response.url().includes('/run') &&
response.request().method() === 'POST',
);
await n8n.canvas.clickExecuteWorkflowButton();
await responsePromise;
await n8n.page.route('**/rest/executions/*', async (route) => {
if (route.request().url().includes('?filter=')) {
await route.continue();
return;
}
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(executionOutOfMemoryResponse),
});
});
const executionDetailPromise = n8n.page.waitForResponse(
(response) =>
response.url().includes('/rest/executions/') && !response.url().includes('?filter='),
);
await n8n.canvas.clickExecutionsTab();
await executionDetailPromise;
const iframe = n8n.executions.getPreviewIframe();
await expect(iframe.locator('body')).not.toBeEmpty();
await n8n.executions.getErrorNotificationsInPreview().first().waitFor({ timeout: 5000 });
const errorNotification = n8n.executions
.getErrorNotificationsInPreview()
.filter({ hasText: ERROR_MESSAGES.OUT_OF_MEMORY });
await expect(errorNotification).toBeVisible();
});
test.fixme(
'should show workflow data in executions tab after hard reload and modify name and tags',
async () => {
// TODO: Migrate from Cypress
},
);
test.fixme('should load items and auto scroll after filter change', async () => {
// TODO: This should be a component test
});
test('should redirect back to editor after seeing a couple of execution using browser back button', async ({
n8n,
}) => {
await n8n.executionsComposer.createExecutions(15);
await n8n.canvas.toggleNodeEnabled('Error');
await expect(n8n.canvas.disabledNodes()).toHaveCount(0);
await n8n.executionsComposer.createExecutions(2);
await n8n.canvas.toggleNodeEnabled('Error');
await expect(n8n.canvas.disabledNodes()).toHaveCount(1);
await n8n.executionsComposer.createExecutions(15);
const executionsResponsePromise = n8n.page.waitForResponse((response) =>
response.url().includes('/rest/executions?filter='),
);
await n8n.canvas.clickExecutionsTab();
await executionsResponsePromise;
const iframe = n8n.executions.getPreviewIframe();
await expect(iframe.locator('body')).toBeAttached();
await n8n.executions.getExecutionItems().nth(2).click();
await expect(iframe.locator('body')).toBeAttached();
await n8n.executions.getExecutionItems().nth(4).click();
await expect(iframe.locator('body')).toBeAttached();
await n8n.executions.getExecutionItems().nth(6).click();
await expect(iframe.locator('body')).toBeAttached();
await n8n.page.goBack();
await expect(iframe.locator('body')).toBeAttached();
await n8n.page.goBack();
await expect(iframe.locator('body')).toBeAttached();
await n8n.page.goBack();
await expect(iframe.locator('body')).toBeAttached();
await n8n.page.goBack();
await expect(n8n.page).not.toHaveURL(/\/executions/);
await expect(n8n.page).toHaveURL(/\/workflow\//);
await expect(n8n.canvas.canvasPane()).toBeVisible();
});
});
test.describe('when new workflow is not saved', () => {
test.beforeEach(async ({ n8n }) => {
await n8n.start.fromBlankCanvas();
});
test('should open executions tab', async ({ n8n }) => {
await n8n.canvas.clickExecutionsTab();
await expect(n8n.executions.getExecutionsSidebar()).toBeVisible();
await expect(n8n.executions.getExecutionsEmptyList()).toBeVisible();
await expect(n8n.page.getByTestId('workflow-execution-no-trigger-content')).toBeVisible();
await n8n.page.getByRole('button', { name: 'Add first step' }).click();
await n8n.canvas.nodeCreatorItemByName('Trigger manually').click();
await n8n.canvas.clickExecutionsTab();
await expect(n8n.executions.getExecutionsSidebar()).toBeVisible();
await expect(n8n.executions.getExecutionsEmptyList()).toBeVisible();
await expect(n8n.page.getByTestId('workflow-execution-no-content')).toBeVisible();
await n8n.canvas.waitForSaveWorkflowCompleted();
await n8n.page.waitForURL(/\/workflow\/.+\/executions$/);
});
});
});