From f700aff583f73499bf0abb659d913a43bffd3cec Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Thu, 18 Dec 2025 16:56:58 +0200 Subject: [PATCH] fix(editor): Prevent Reka UI from interfering with Element Plus dropdown selections (#23370) --- .../src/components/N8nPopover/Popover.vue | 19 ++++++++ .../playwright/pages/ExecutionsPage.ts | 22 +++++++++ .../e2e/workflows/executions/list.spec.ts | 47 +++++++++++++++++++ 3 files changed, 88 insertions(+) diff --git a/packages/frontend/@n8n/design-system/src/components/N8nPopover/Popover.vue b/packages/frontend/@n8n/design-system/src/components/N8nPopover/Popover.vue index b03446e32aa..8ce97dfd78c 100644 --- a/packages/frontend/@n8n/design-system/src/components/N8nPopover/Popover.vue +++ b/packages/frontend/@n8n/design-system/src/components/N8nPopover/Popover.vue @@ -7,6 +7,8 @@ import { PopoverRoot, type PopoverRootProps, PopoverTrigger, + type PointerDownOutsideEvent, + type FocusOutsideEvent, } from 'reka-ui'; import { watch } from 'vue'; import type { CSSProperties } from 'vue'; @@ -89,6 +91,21 @@ function handleOpenAutoFocus(e: Event) { } } +/** + * Handles outside interaction events to prevent Reka UI from interfering + * with Element Plus dropdown selections. Element Plus teleports dropdowns + * to the body, so Reka UI's DismissableLayer detects clicks on them as + * "outside" clicks, which would otherwise prevent proper selection. + * + * TODO: This workaround can be removed once N8nSelect is migrated to Reka UI. + */ +function handleOutsideInteraction(e: PointerDownOutsideEvent | FocusOutsideEvent) { + const target = e.target as HTMLElement | null; + if (target?.closest('.el-popper, .el-select-dropdown')) { + e.preventDefault(); + } +} + // Watch open state to emit lifecycle events watch( () => props.open, @@ -118,6 +135,8 @@ watch( :style="{ width, zIndex }" :reference="reference" @open-auto-focus="handleOpenAutoFocus" + @pointer-down-outside="handleOutsideInteraction" + @interact-outside="handleOutsideInteraction" > { + await this.getFilterButton().click(); + } + + async selectStatus(status: string): Promise { + await this.getStatusSelect().click(); + await this.page.locator('.el-select-dropdown__item').filter({ hasText: status }).click(); + } } diff --git a/packages/testing/playwright/tests/e2e/workflows/executions/list.spec.ts b/packages/testing/playwright/tests/e2e/workflows/executions/list.spec.ts index 02c162c99e3..05238892bae 100644 --- a/packages/testing/playwright/tests/e2e/workflows/executions/list.spec.ts +++ b/packages/testing/playwright/tests/e2e/workflows/executions/list.spec.ts @@ -1,6 +1,53 @@ import { test, expect } from '../../../../fixtures/base'; import executionOutOfMemoryResponse from '../../../../fixtures/execution-out-of-memory-server-response.json'; +test.describe('Executions Filter', () => { + test.beforeEach(async ({ n8n }) => { + await n8n.start.fromImportedWorkflow('Test_workflow_4_executions_view.json'); + }); + + test('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 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'), + ); + + // Select an option from the dropdown + await n8n.page.locator('.el-select-dropdown__item').filter({ hasText: '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;