mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 10:02:05 +00:00
test: Migrate UI tests from Cypress -> Playwright (no-changelog) (#18201)
This commit is contained in:
@@ -4,7 +4,7 @@ import { test, expect } from '@playwright/test';
|
||||
import { execSync } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
// @ts-expect-error - 'generate-schema' is not typed, so we ignore the TS error.
|
||||
import GenerateSchema from 'generate-schema';
|
||||
import generateSchema from 'generate-schema';
|
||||
import * as path from 'path';
|
||||
|
||||
import { findPackagesRoot } from '../../utils/path-helper';
|
||||
@@ -136,7 +136,7 @@ test.describe('Workflow Tests', () => {
|
||||
|
||||
// Optionally, validate the output against a JSON schema snapshot if enabled.
|
||||
if (SCHEMA_MODE && result.data && workflow.enableSchemaValidation) {
|
||||
const schema = GenerateSchema.json(result.data);
|
||||
const schema = generateSchema.json(result.data);
|
||||
expect(JSON.stringify(schema, null, 2)).toMatchSnapshot(
|
||||
`workflow-${workflow.id}-schema.snap`,
|
||||
);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
import { test, expect } from '../../fixtures/base';
|
||||
|
||||
const NOTIFICATIONS = {
|
||||
@@ -18,7 +20,9 @@ test.describe('Workflows', () => {
|
||||
await expect(n8n.canvas.getWorkflowTags()).toHaveText(['some-tag-1', 'some-tag-2']);
|
||||
});
|
||||
|
||||
test('should create a new workflow using add workflow button', async ({ n8n }) => {
|
||||
test('should create a new workflow using add workflow button and save successfully', async ({
|
||||
n8n,
|
||||
}) => {
|
||||
await n8n.workflows.clickAddWorkflowButton();
|
||||
|
||||
const workflowName = `Test Workflow ${Date.now()}`;
|
||||
@@ -31,9 +35,9 @@ test.describe('Workflows', () => {
|
||||
});
|
||||
|
||||
test('should search for workflows', async ({ n8n }) => {
|
||||
const date = Date.now();
|
||||
const specificName = `Specific Test ${date}`;
|
||||
const genericName = `Generic Test ${date}`;
|
||||
const uniqueId = nanoid(8);
|
||||
const specificName = `Specific Test ${uniqueId}`;
|
||||
const genericName = `Generic Test ${uniqueId}`;
|
||||
|
||||
await n8n.workflowComposer.createWorkflow(specificName);
|
||||
await n8n.goHome();
|
||||
@@ -47,7 +51,7 @@ test.describe('Workflows', () => {
|
||||
|
||||
// Search with partial term
|
||||
await n8n.workflows.clearSearch();
|
||||
await n8n.workflows.searchWorkflows(date.toString());
|
||||
await n8n.workflows.searchWorkflows(uniqueId);
|
||||
await expect(n8n.workflows.getWorkflowItems()).toHaveCount(2);
|
||||
|
||||
// Search for non-existent
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { test, expect } from '../../fixtures/base';
|
||||
|
||||
test.describe('Schedule Trigger node', () => {
|
||||
test.beforeEach(async ({ n8n }) => {
|
||||
await n8n.goHome();
|
||||
});
|
||||
|
||||
test('should execute schedule trigger node and return timestamp in output', async ({ n8n }) => {
|
||||
await n8n.workflows.clickAddWorkflowButton();
|
||||
await n8n.canvas.addNode('Schedule Trigger');
|
||||
|
||||
await n8n.ndv.execute();
|
||||
|
||||
await expect(n8n.ndv.getOutputPanel()).toContainText('timestamp');
|
||||
|
||||
await n8n.ndv.clickBackToCanvasButton();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { test, expect } from '../../fixtures/base';
|
||||
|
||||
test.describe('ADO-2270 Save button resets on webhook node open', () => {
|
||||
test('should not reset the save button if webhook node is opened and closed', async ({ n8n }) => {
|
||||
await n8n.goHome();
|
||||
|
||||
await n8n.workflows.clickAddWorkflowButton();
|
||||
await n8n.canvas.addNode('Webhook');
|
||||
|
||||
await n8n.page.keyboard.press('Escape');
|
||||
|
||||
await n8n.canvas.clickSaveWorkflowButton();
|
||||
|
||||
await n8n.canvas.openNode('Webhook');
|
||||
|
||||
await n8n.ndv.clickBackToCanvasButton();
|
||||
|
||||
await expect(n8n.canvas.workflowSaveButton()).toContainText('Saved');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import { test, expect } from '../../fixtures/base';
|
||||
|
||||
test.describe('Admin user', () => {
|
||||
test('should see same Settings sub menu items as instance owner', async ({ n8n, api }) => {
|
||||
await api.setupTest('signin-only', 'owner');
|
||||
await n8n.settings.goToSettings();
|
||||
|
||||
const ownerMenuItems = await n8n.settings.getMenuItems().count();
|
||||
|
||||
await api.setupTest('signin-only', 'admin');
|
||||
await n8n.settings.goToSettings();
|
||||
|
||||
await expect(n8n.settings.getMenuItems()).toHaveCount(ownerMenuItems);
|
||||
});
|
||||
});
|
||||
276
packages/testing/playwright/tests/ui/45-ai-assistant.spec.ts
Normal file
276
packages/testing/playwright/tests/ui/45-ai-assistant.spec.ts
Normal file
@@ -0,0 +1,276 @@
|
||||
import { test, expect } from '../../fixtures/base';
|
||||
import type { TestRequirements } from '../../Types';
|
||||
|
||||
const aiDisabledRequirements: TestRequirements = {
|
||||
config: {
|
||||
features: { aiAssistant: false },
|
||||
},
|
||||
};
|
||||
|
||||
const aiEnabledRequirements: TestRequirements = {
|
||||
config: {
|
||||
features: { aiAssistant: true },
|
||||
},
|
||||
};
|
||||
|
||||
const aiEnabledWithWorkflowRequirements: TestRequirements = {
|
||||
config: {
|
||||
features: { aiAssistant: true },
|
||||
},
|
||||
workflow: {
|
||||
'ai_assistant_test_workflow.json': 'AI_Assistant_Test_Workflow',
|
||||
},
|
||||
intercepts: {
|
||||
aiChat: {
|
||||
url: '**/rest/ai/chat',
|
||||
response: {
|
||||
sessionId: '1',
|
||||
messages: [
|
||||
{
|
||||
role: 'assistant',
|
||||
type: 'message',
|
||||
text: 'Hey, this is an assistant message',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const aiEnabledWithQuickRepliesRequirements: TestRequirements = {
|
||||
config: {
|
||||
features: { aiAssistant: true },
|
||||
},
|
||||
workflow: {
|
||||
'ai_assistant_test_workflow.json': 'AI_Assistant_Test_Workflow',
|
||||
},
|
||||
intercepts: {
|
||||
aiChat: {
|
||||
url: '**/rest/ai/chat',
|
||||
response: {
|
||||
sessionId: '1',
|
||||
messages: [
|
||||
{
|
||||
role: 'assistant',
|
||||
type: 'message',
|
||||
text: 'Hey, this is an assistant message',
|
||||
quickReplies: [
|
||||
{
|
||||
text: "Sure, let's do it",
|
||||
type: 'yes',
|
||||
},
|
||||
{
|
||||
text: "Nah, doesn't sound good",
|
||||
type: 'no',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const aiEnabledWithEndSessionRequirements: TestRequirements = {
|
||||
config: {
|
||||
features: { aiAssistant: true },
|
||||
},
|
||||
workflow: {
|
||||
'ai_assistant_test_workflow.json': 'AI_Assistant_Test_Workflow',
|
||||
},
|
||||
intercepts: {
|
||||
aiChat: {
|
||||
url: '**/rest/ai/chat',
|
||||
response: {
|
||||
sessionId: '1',
|
||||
messages: [
|
||||
{
|
||||
role: 'assistant',
|
||||
type: 'message',
|
||||
title: 'Glad to Help',
|
||||
text: "I'm glad I could help. If you have any more questions or need further assistance with your n8n workflows, feel free to ask!",
|
||||
},
|
||||
{
|
||||
role: 'assistant',
|
||||
type: 'event',
|
||||
eventName: 'end-session',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
test.describe('AI Assistant::disabled', () => {
|
||||
test('does not show assistant button if feature is disabled', async ({
|
||||
n8n,
|
||||
setupRequirements,
|
||||
}) => {
|
||||
await setupRequirements(aiDisabledRequirements);
|
||||
await expect(n8n.aiAssistant.getAskAssistantFloatingButton()).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('AI Assistant::enabled', () => {
|
||||
test('renders placeholder UI', async ({ n8n, setupRequirements }) => {
|
||||
await setupRequirements(aiEnabledRequirements);
|
||||
await n8n.page.goto('/workflow/new');
|
||||
|
||||
await expect(n8n.aiAssistant.getAskAssistantCanvasActionButton()).toBeVisible();
|
||||
|
||||
await n8n.aiAssistant.getAskAssistantCanvasActionButton().click();
|
||||
|
||||
await expect(n8n.aiAssistant.getAskAssistantChat()).toBeVisible();
|
||||
|
||||
await expect(n8n.aiAssistant.getPlaceholderMessage()).toBeVisible();
|
||||
|
||||
await expect(n8n.aiAssistant.getChatInput()).toBeVisible();
|
||||
|
||||
await expect(n8n.aiAssistant.getSendMessageButton()).toBeDisabled();
|
||||
|
||||
await expect(n8n.aiAssistant.getCloseChatButton()).toBeVisible();
|
||||
|
||||
await n8n.aiAssistant.getCloseChatButton().click();
|
||||
|
||||
await expect(n8n.aiAssistant.getAskAssistantChat()).toBeHidden();
|
||||
});
|
||||
|
||||
test('should show resizer when chat is open', async ({ n8n, setupRequirements }) => {
|
||||
await setupRequirements(aiEnabledRequirements);
|
||||
await n8n.page.goto('/workflow/new');
|
||||
|
||||
await n8n.aiAssistant.getAskAssistantCanvasActionButton().click();
|
||||
|
||||
await expect(n8n.aiAssistant.getAskAssistantSidebarResizer()).toBeVisible();
|
||||
|
||||
await expect(n8n.aiAssistant.getAskAssistantChat()).toBeVisible();
|
||||
|
||||
await n8n.aiAssistant.getAskAssistantSidebarResizer().hover();
|
||||
|
||||
await n8n.aiAssistant.getCloseChatButton().click();
|
||||
});
|
||||
|
||||
test('should start chat session from node error view', async ({ n8n, setupRequirements }) => {
|
||||
await setupRequirements(aiEnabledWithWorkflowRequirements);
|
||||
|
||||
await n8n.canvas.openNode('Stop and Error');
|
||||
|
||||
await n8n.ndv.execute();
|
||||
|
||||
await expect(n8n.aiAssistant.getNodeErrorViewAssistantButton()).toBeVisible();
|
||||
await expect(n8n.aiAssistant.getNodeErrorViewAssistantButton()).toBeEnabled();
|
||||
|
||||
await n8n.aiAssistant.getNodeErrorViewAssistantButton().click();
|
||||
|
||||
await expect(n8n.aiAssistant.getChatMessagesAll()).toHaveCount(1);
|
||||
|
||||
await expect(n8n.aiAssistant.getChatMessagesAll()).toHaveCount(1);
|
||||
|
||||
await expect(n8n.aiAssistant.getChatMessagesAll().first()).toContainText(
|
||||
'Hey, this is an assistant message',
|
||||
);
|
||||
|
||||
await expect(n8n.aiAssistant.getNodeErrorViewAssistantButton()).toBeDisabled();
|
||||
});
|
||||
|
||||
test('should render chat input correctly', async ({ n8n, setupRequirements }) => {
|
||||
await setupRequirements(aiEnabledWithWorkflowRequirements);
|
||||
|
||||
await n8n.aiAssistant.getAskAssistantCanvasActionButton().click();
|
||||
|
||||
await expect(n8n.aiAssistant.getAskAssistantChat()).toBeVisible();
|
||||
await expect(n8n.aiAssistant.getChatInput()).toBeVisible();
|
||||
|
||||
await expect(n8n.aiAssistant.getSendMessageButton()).toBeDisabled();
|
||||
|
||||
await n8n.aiAssistant.getChatInput().fill('Test message');
|
||||
|
||||
await expect(n8n.aiAssistant.getChatInput()).toHaveValue('Test message');
|
||||
|
||||
await expect(n8n.aiAssistant.getSendMessageButton()).toBeEnabled();
|
||||
|
||||
await n8n.aiAssistant.getSendMessageButton().click();
|
||||
|
||||
await expect(n8n.aiAssistant.getChatMessagesUser()).toHaveCount(1);
|
||||
|
||||
await expect(n8n.aiAssistant.getChatMessagesUser()).toHaveCount(1);
|
||||
|
||||
await expect(n8n.aiAssistant.getChatInput()).toHaveValue('');
|
||||
});
|
||||
|
||||
test('should render and handle quick replies', async ({ n8n, setupRequirements }) => {
|
||||
await setupRequirements(aiEnabledWithQuickRepliesRequirements);
|
||||
|
||||
await n8n.canvas.openNode('Stop and Error');
|
||||
|
||||
await n8n.ndv.execute();
|
||||
|
||||
await n8n.aiAssistant.getNodeErrorViewAssistantButton().click();
|
||||
|
||||
await expect(n8n.aiAssistant.getQuickReplyButtons()).toHaveCount(2);
|
||||
|
||||
await expect(n8n.aiAssistant.getQuickReplyButtons()).toHaveCount(2);
|
||||
|
||||
await n8n.aiAssistant.getQuickReplyButtons().first().click();
|
||||
|
||||
await expect(n8n.aiAssistant.getChatMessagesUser()).toHaveCount(1);
|
||||
|
||||
await expect(n8n.aiAssistant.getChatMessagesUser()).toHaveCount(1);
|
||||
|
||||
await expect(n8n.aiAssistant.getChatMessagesUser().first()).toContainText("Sure, let's do it");
|
||||
});
|
||||
|
||||
test('should warn before starting a new session', async ({ n8n, setupRequirements }) => {
|
||||
await setupRequirements(aiEnabledWithWorkflowRequirements);
|
||||
|
||||
await n8n.canvas.openNode('Edit Fields');
|
||||
|
||||
await n8n.ndv.execute();
|
||||
|
||||
await n8n.aiAssistant.getNodeErrorViewAssistantButton().click();
|
||||
|
||||
await expect(n8n.aiAssistant.getChatMessagesAll()).toHaveCount(1);
|
||||
|
||||
await n8n.aiAssistant.getCloseChatButton().click();
|
||||
|
||||
await n8n.ndv.clickBackToCanvasButton();
|
||||
|
||||
await n8n.canvas.openNode('Stop and Error');
|
||||
|
||||
await n8n.ndv.execute();
|
||||
|
||||
await n8n.aiAssistant.getNodeErrorViewAssistantButton().click();
|
||||
|
||||
await expect(n8n.aiAssistant.getNewAssistantSessionModal()).toBeVisible();
|
||||
|
||||
await n8n.aiAssistant
|
||||
.getNewAssistantSessionModal()
|
||||
.getByRole('button', { name: 'Start new session' })
|
||||
.click();
|
||||
|
||||
await expect(n8n.aiAssistant.getChatMessagesAll()).toHaveCount(1);
|
||||
|
||||
await expect(n8n.aiAssistant.getChatMessagesAll()).toHaveCount(1);
|
||||
});
|
||||
|
||||
test('should end chat session when `end_session` event is received', async ({
|
||||
n8n,
|
||||
setupRequirements,
|
||||
}) => {
|
||||
await setupRequirements(aiEnabledWithEndSessionRequirements);
|
||||
|
||||
await n8n.canvas.openNode('Stop and Error');
|
||||
|
||||
await n8n.ndv.execute();
|
||||
|
||||
await n8n.aiAssistant.getNodeErrorViewAssistantButton().click();
|
||||
|
||||
await expect(n8n.aiAssistant.getChatMessagesSystem()).toHaveCount(1);
|
||||
|
||||
await expect(n8n.aiAssistant.getChatMessagesSystem()).toHaveCount(1);
|
||||
|
||||
await expect(n8n.aiAssistant.getChatMessagesSystem().first()).toContainText(
|
||||
'session has ended',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
import { test, expect } from '../../fixtures/base';
|
||||
import type { TestRequirements } from '../../Types';
|
||||
|
||||
const telemetryDisabledRequirements: TestRequirements = {
|
||||
config: {
|
||||
settings: {
|
||||
telemetry: { enabled: false },
|
||||
},
|
||||
},
|
||||
storage: {
|
||||
'n8n-telemetry': JSON.stringify({ enabled: false }),
|
||||
},
|
||||
};
|
||||
|
||||
const telemetryEnabledRequirements: TestRequirements = {
|
||||
config: {
|
||||
settings: {
|
||||
telemetry: { enabled: true },
|
||||
instanceId: 'test-instance-id',
|
||||
},
|
||||
},
|
||||
storage: {
|
||||
'n8n-telemetry': JSON.stringify({ enabled: true }),
|
||||
'n8n-instance-id': 'test-instance-id',
|
||||
},
|
||||
intercepts: {
|
||||
iframeRequest: {
|
||||
url: 'https://n8n.io/self-install*',
|
||||
response: '<html><body>Test iframe content</body></html>',
|
||||
contentType: 'text/html',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
test.describe('n8n.io iframe', () => {
|
||||
test.describe('when telemetry is disabled', () => {
|
||||
test('should not load the iframe when visiting /home/workflows', async ({
|
||||
n8n,
|
||||
setupRequirements,
|
||||
}) => {
|
||||
await setupRequirements(telemetryDisabledRequirements);
|
||||
|
||||
await n8n.page.goto('/');
|
||||
await n8n.page.waitForLoadState();
|
||||
await expect(n8n.iframe.getIframe()).not.toBeAttached();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('when telemetry is enabled', () => {
|
||||
test('should load the iframe when visiting /home/workflows @auth:owner', async ({
|
||||
n8n,
|
||||
setupRequirements,
|
||||
api,
|
||||
}) => {
|
||||
await setupRequirements(telemetryEnabledRequirements);
|
||||
|
||||
// Get current user ID from the API
|
||||
const currentUser = await api.get('/rest/login');
|
||||
const testInstanceId = 'test-instance-id';
|
||||
const testUserId = currentUser.id;
|
||||
const iframeUrl = `https://n8n.io/self-install?instanceId=${testInstanceId}&userId=${testUserId}`;
|
||||
|
||||
await n8n.page.goto('/');
|
||||
await n8n.page.waitForLoadState();
|
||||
|
||||
const iframeElement = n8n.iframe.getIframeBySrc(iframeUrl);
|
||||
await expect(iframeElement).toBeAttached();
|
||||
|
||||
await expect(iframeElement).toHaveAttribute('src', iframeUrl);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,14 +1,3 @@
|
||||
import {
|
||||
getErrorActionItem,
|
||||
getEvaluationsActionItem,
|
||||
getIgnoreAllButton,
|
||||
getSuggestedActionItem,
|
||||
getSuggestedActionsButton,
|
||||
getSuggestedActionsPopover,
|
||||
getTimeSavedActionItem,
|
||||
} from '../../composables/ProductionChecklist';
|
||||
import { closeActivationModal } from '../../composables/WorkflowActivationModal';
|
||||
import { openWorkflowSettings } from '../../composables/WorkflowSettingsModal';
|
||||
import { test, expect } from '../../fixtures/base';
|
||||
|
||||
const SCHEDULE_TRIGGER_NODE_NAME = 'Schedule Trigger';
|
||||
@@ -22,35 +11,29 @@ test.describe('Workflow Production Checklist', () => {
|
||||
test('should show suggested actions automatically when workflow is first activated', async ({
|
||||
n8n,
|
||||
}) => {
|
||||
// Add a schedule trigger node (activatable)
|
||||
await n8n.canvas.addNodeAndCloseNDV(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.saveWorkflow();
|
||||
|
||||
// Verify suggested actions button is not visible
|
||||
await expect(getSuggestedActionsButton(n8n.page)).toBeHidden();
|
||||
await expect(n8n.canvas.getProductionChecklistButton()).toBeHidden();
|
||||
|
||||
// Activate the workflow
|
||||
await n8n.canvas.activateWorkflow();
|
||||
|
||||
// Activation Modal should be visible since it's first activation
|
||||
await closeActivationModal(n8n.page);
|
||||
await expect(n8n.workflowActivationModal.getModal()).toBeVisible();
|
||||
await n8n.workflowActivationModal.close();
|
||||
|
||||
// Verify suggested actions button and popover is visible
|
||||
await expect(getSuggestedActionsButton(n8n.page)).toBeVisible();
|
||||
await expect(getSuggestedActionsPopover(n8n.page)).toBeVisible();
|
||||
await expect(getSuggestedActionItem(n8n.page)).toHaveCount(2);
|
||||
await expect(getErrorActionItem(n8n.page)).toBeVisible();
|
||||
await expect(getTimeSavedActionItem(n8n.page)).toBeVisible();
|
||||
await expect(n8n.canvas.getProductionChecklistButton()).toBeVisible();
|
||||
await expect(n8n.canvas.getProductionChecklistPopover()).toBeVisible();
|
||||
await expect(n8n.canvas.getProductionChecklistActionItem()).toHaveCount(2);
|
||||
await expect(n8n.canvas.getErrorActionItem()).toBeVisible();
|
||||
await expect(n8n.canvas.getTimeSavedActionItem()).toBeVisible();
|
||||
});
|
||||
|
||||
test('should display evaluations action when AI node exists and feature is enabled', async ({
|
||||
n8n,
|
||||
api,
|
||||
}) => {
|
||||
// Enable evaluations feature
|
||||
await api.enableFeature('evaluation');
|
||||
|
||||
// Add schedule trigger and AI node
|
||||
await n8n.canvas.addNodeAndCloseNDV(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.addNodeAndCloseNDV('OpenAI', 'Create an assistant');
|
||||
|
||||
@@ -58,56 +41,50 @@ test.describe('Workflow Production Checklist', () => {
|
||||
|
||||
await n8n.canvas.saveWorkflow();
|
||||
await n8n.canvas.activateWorkflow();
|
||||
await closeActivationModal(n8n.page);
|
||||
await expect(n8n.workflowActivationModal.getModal()).toBeVisible();
|
||||
await n8n.workflowActivationModal.close();
|
||||
|
||||
// Suggested actions should be open
|
||||
await expect(getSuggestedActionsPopover(n8n.page)).toBeVisible();
|
||||
await expect(getSuggestedActionItem(n8n.page)).toHaveCount(3);
|
||||
await expect(n8n.canvas.getProductionChecklistPopover()).toBeVisible();
|
||||
await expect(n8n.canvas.getProductionChecklistActionItem()).toHaveCount(3);
|
||||
|
||||
// Verify evaluations action is present
|
||||
await expect(getEvaluationsActionItem(n8n.page)).toBeVisible();
|
||||
await getEvaluationsActionItem(n8n.page).click();
|
||||
await expect(n8n.canvas.getEvaluationsActionItem()).toBeVisible();
|
||||
await n8n.canvas.getEvaluationsActionItem().click();
|
||||
|
||||
// Verify navigation to evaluations page
|
||||
await expect(n8n.page).toHaveURL(/\/evaluation/);
|
||||
});
|
||||
|
||||
test('should open workflow settings modal when error workflow action is clicked', async ({
|
||||
n8n,
|
||||
}) => {
|
||||
// Add schedule trigger
|
||||
await n8n.canvas.addNodeAndCloseNDV(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.saveWorkflow();
|
||||
await n8n.canvas.activateWorkflow();
|
||||
await closeActivationModal(n8n.page);
|
||||
await expect(n8n.workflowActivationModal.getModal()).toBeVisible();
|
||||
await n8n.workflowActivationModal.close();
|
||||
|
||||
await expect(getSuggestedActionsPopover(n8n.page)).toBeVisible();
|
||||
await expect(n8n.canvas.getProductionChecklistPopover()).toBeVisible();
|
||||
|
||||
// Click error workflow action
|
||||
const errorAction = getErrorActionItem(n8n.page);
|
||||
const errorAction = n8n.canvas.getErrorActionItem();
|
||||
await expect(errorAction).toBeVisible();
|
||||
await errorAction.click();
|
||||
|
||||
// Verify workflow settings modal opens
|
||||
await expect(n8n.page.getByTestId('workflow-settings-dialog')).toBeVisible();
|
||||
await expect(n8n.page.getByTestId('workflow-settings-error-workflow')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should open workflow settings modal when time saved action is clicked', async ({ n8n }) => {
|
||||
// Add schedule trigger
|
||||
await n8n.canvas.addNodeAndCloseNDV(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.saveWorkflow();
|
||||
await n8n.canvas.activateWorkflow();
|
||||
await closeActivationModal(n8n.page);
|
||||
await expect(n8n.workflowActivationModal.getModal()).toBeVisible();
|
||||
await n8n.workflowActivationModal.close();
|
||||
|
||||
await expect(getSuggestedActionsPopover(n8n.page)).toBeVisible();
|
||||
await expect(n8n.canvas.getProductionChecklistPopover()).toBeVisible();
|
||||
|
||||
// Click time saved action
|
||||
const timeAction = getTimeSavedActionItem(n8n.page);
|
||||
const timeAction = n8n.canvas.getTimeSavedActionItem();
|
||||
await expect(timeAction).toBeVisible();
|
||||
await timeAction.click();
|
||||
|
||||
// Verify workflow settings modal opens
|
||||
await expect(n8n.page.getByTestId('workflow-settings-dialog')).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -115,73 +92,64 @@ test.describe('Workflow Production Checklist', () => {
|
||||
await n8n.canvas.addNodeAndCloseNDV(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.saveWorkflow();
|
||||
await n8n.canvas.activateWorkflow();
|
||||
await closeActivationModal(n8n.page);
|
||||
await expect(n8n.workflowActivationModal.getModal()).toBeVisible();
|
||||
await n8n.workflowActivationModal.close();
|
||||
|
||||
// Suggested actions popover should be open
|
||||
await expect(getSuggestedActionsPopover(n8n.page)).toBeVisible();
|
||||
await expect(n8n.canvas.getProductionChecklistPopover()).toBeVisible();
|
||||
|
||||
// Verify error workflow action is visible
|
||||
await expect(getSuggestedActionItem(n8n.page).first()).toContainText('error');
|
||||
await getSuggestedActionItem(n8n.page).first().getByTitle('Ignore').click();
|
||||
await n8n.page.waitForTimeout(500); // items disappear after timeout, not arbitrary
|
||||
await expect(getErrorActionItem(n8n.page)).toBeHidden();
|
||||
await expect(n8n.canvas.getProductionChecklistActionItem().first()).toContainText('error');
|
||||
await n8n.canvas.getProductionChecklistActionItem().first().getByTitle('Ignore').click();
|
||||
await expect(n8n.canvas.getErrorActionItem()).toBeHidden();
|
||||
|
||||
// Close and reopen popover
|
||||
await n8n.page.locator('body').click({ position: { x: 0, y: 0 } });
|
||||
await getSuggestedActionsButton(n8n.page).click();
|
||||
await n8n.canvas.clickProductionChecklistButton();
|
||||
|
||||
// Verify error workflow action is still no longer visible
|
||||
await expect(getErrorActionItem(n8n.page)).toBeHidden();
|
||||
await expect(getTimeSavedActionItem(n8n.page)).toBeVisible();
|
||||
await expect(n8n.canvas.getErrorActionItem()).toBeHidden();
|
||||
await expect(n8n.canvas.getTimeSavedActionItem()).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show completed state for configured actions', async ({ n8n }) => {
|
||||
// Add schedule trigger and activate workflow
|
||||
await n8n.canvas.addNodeAndCloseNDV(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.saveWorkflow();
|
||||
await n8n.canvas.activateWorkflow();
|
||||
await closeActivationModal(n8n.page);
|
||||
await expect(n8n.workflowActivationModal.getModal()).toBeVisible();
|
||||
await n8n.workflowActivationModal.close();
|
||||
|
||||
// Open workflow settings and set error workflow
|
||||
await openWorkflowSettings(n8n.page);
|
||||
await n8n.workflowSettingsModal.open();
|
||||
await expect(n8n.workflowSettingsModal.getModal()).toBeVisible();
|
||||
|
||||
// Set an error workflow (we'll use a dummy value)
|
||||
await n8n.page.getByTestId('workflow-settings-error-workflow').click();
|
||||
await n8n.page.getByRole('option', { name: 'My workflow' }).first().click();
|
||||
await n8n.page.getByRole('button', { name: 'Save' }).click();
|
||||
await n8n.workflowSettingsModal.selectErrorWorkflow('My workflow');
|
||||
await n8n.workflowSettingsModal.clickSave();
|
||||
await expect(n8n.page.getByTestId('workflow-settings-dialog')).toBeHidden();
|
||||
|
||||
// Open suggested actions
|
||||
await getSuggestedActionsButton(n8n.page).click();
|
||||
await expect(getSuggestedActionsPopover(n8n.page)).toBeVisible();
|
||||
await n8n.canvas.clickProductionChecklistButton();
|
||||
await expect(n8n.canvas.getProductionChecklistPopover()).toBeVisible();
|
||||
|
||||
// Verify error workflow action shows as completed
|
||||
await expect(
|
||||
getSuggestedActionItem(n8n.page).first().locator('svg[data-icon="circle-check"]'),
|
||||
n8n.canvas
|
||||
.getProductionChecklistActionItem()
|
||||
.first()
|
||||
.locator('svg[data-icon="circle-check"]'),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('should allow ignoring all actions with confirmation', async ({ n8n }) => {
|
||||
// Add schedule trigger
|
||||
await n8n.canvas.addNodeAndCloseNDV(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.saveWorkflow();
|
||||
await n8n.canvas.activateWorkflow();
|
||||
await closeActivationModal(n8n.page);
|
||||
await expect(n8n.workflowActivationModal.getModal()).toBeVisible();
|
||||
await n8n.workflowActivationModal.close();
|
||||
|
||||
// Suggested actions should be open
|
||||
await expect(getSuggestedActionsPopover(n8n.page)).toBeVisible();
|
||||
await expect(n8n.canvas.getProductionChecklistPopover()).toBeVisible();
|
||||
|
||||
// Click ignore all button
|
||||
await getIgnoreAllButton(n8n.page).click();
|
||||
await n8n.canvas.clickProductionChecklistIgnoreAll();
|
||||
|
||||
// Confirm in the dialog
|
||||
await expect(n8n.page.locator('.el-message-box')).toBeVisible();
|
||||
await n8n.page
|
||||
.locator('.el-message-box__btns button')
|
||||
.filter({ hasText: /ignore for all workflows/i })
|
||||
.click();
|
||||
|
||||
// Verify suggested actions button is no longer visible
|
||||
await expect(getSuggestedActionsButton(n8n.page)).toBeHidden();
|
||||
await expect(n8n.canvas.getProductionChecklistButton()).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { test, expect } from '../../fixtures/base';
|
||||
|
||||
test.describe('AI-716 Correctly set up agent model shows error', () => {
|
||||
test('should not show error when adding a sub-node with credential set-up', async ({ n8n }) => {
|
||||
await n8n.goHome();
|
||||
await n8n.workflows.clickAddWorkflowButton();
|
||||
|
||||
await n8n.canvas.addNode('AI Agent');
|
||||
|
||||
await n8n.page.keyboard.press('Escape');
|
||||
|
||||
await n8n.canvas.addNode('OpenAI Chat Model');
|
||||
|
||||
await n8n.credentials.createAndSaveNewCredential('apiKey', 'sk-123');
|
||||
|
||||
await n8n.page.keyboard.press('Escape');
|
||||
|
||||
await expect(n8n.canvas.getNodeIssuesByName('OpenAI Chat Model')).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { test, expect } from '../../fixtures/base';
|
||||
import type { TestRequirements } from '../../Types';
|
||||
|
||||
const requirements: TestRequirements = {
|
||||
workflow: {
|
||||
'Test_9999_SUG_38.json': 'SUG_38_Test_Workflow',
|
||||
},
|
||||
};
|
||||
|
||||
test.describe('SUG-38 Inline expression previews are not displayed in NDV', () => {
|
||||
test("should show resolved inline expression preview in NDV if the node's input data is populated", async ({
|
||||
n8n,
|
||||
setupRequirements,
|
||||
}) => {
|
||||
await setupRequirements(requirements);
|
||||
|
||||
await n8n.canvas.clickZoomToFitButton();
|
||||
|
||||
await n8n.workflowComposer.executeWorkflowAndWaitForNotification(
|
||||
'Workflow executed successfully',
|
||||
);
|
||||
|
||||
await n8n.canvas.openNode('Repro1');
|
||||
|
||||
await expect(n8n.ndv.getParameterExpressionPreviewValue()).toBeVisible();
|
||||
|
||||
await expect(n8n.ndv.getParameterExpressionPreviewValue()).toHaveText('hello there');
|
||||
});
|
||||
});
|
||||
@@ -1,26 +1,18 @@
|
||||
import { test, expect } from '../../fixtures/base';
|
||||
|
||||
test('default signin is as owner', async ({ n8n }) => {
|
||||
await n8n.goHome();
|
||||
await expect(n8n.page).toHaveURL(/\/workflow/);
|
||||
});
|
||||
test.describe('Authentication', () => {
|
||||
const testCases = [
|
||||
{ role: 'default', expectedUrl: /\/workflow/, auth: '' },
|
||||
{ role: 'owner', expectedUrl: /\/workflow/, auth: '@auth:owner' },
|
||||
{ role: 'admin', expectedUrl: /\/workflow/, auth: '@auth:admin' },
|
||||
{ role: 'member', expectedUrl: /\/workflow/, auth: '@auth:member' },
|
||||
{ role: 'none', expectedUrl: /\/signin/, auth: '@auth:none' },
|
||||
];
|
||||
|
||||
test('owner can access dashboard @auth:owner', async ({ n8n }) => {
|
||||
await n8n.goHome();
|
||||
await expect(n8n.page).toHaveURL(/\/workflow/);
|
||||
});
|
||||
|
||||
test('admin can access dashboard @auth:admin', async ({ n8n }) => {
|
||||
await n8n.goHome();
|
||||
await expect(n8n.page).toHaveURL(/\/workflow/);
|
||||
});
|
||||
|
||||
test('member can access dashboard @auth:member', async ({ n8n }) => {
|
||||
await n8n.goHome();
|
||||
await expect(n8n.page).toHaveURL(/\/workflow/);
|
||||
});
|
||||
|
||||
test('no auth can not access dashboard @auth:none', async ({ n8n }) => {
|
||||
await n8n.goHome();
|
||||
await expect(n8n.page).toHaveURL(/\/signin/);
|
||||
for (const { role, expectedUrl, auth } of testCases) {
|
||||
test(`${role} authentication ${auth}`, async ({ n8n }) => {
|
||||
await n8n.goHome();
|
||||
await expect(n8n.page).toHaveURL(expectedUrl);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { test, expect } from '../../fixtures/base';
|
||||
|
||||
test.describe
|
||||
.serial('Environment Feature Flags', () => {
|
||||
test('should set feature flags at runtime and load it back in envFeatureFlags from backend settings', async ({
|
||||
api,
|
||||
}) => {
|
||||
const setResponse = await api.setEnvFeatureFlags({
|
||||
N8N_ENV_FEAT_TEST: 'true',
|
||||
});
|
||||
|
||||
expect(setResponse.data.success).toBe(true);
|
||||
expect(setResponse.data.message).toBe('Environment feature flags updated');
|
||||
|
||||
expect(setResponse.data.flags).toBeInstanceOf(Object);
|
||||
expect(setResponse.data.flags['N8N_ENV_FEAT_TEST']).toBe('true');
|
||||
|
||||
const currentFlags = await api.getEnvFeatureFlags();
|
||||
|
||||
expect(currentFlags).toBeInstanceOf(Object);
|
||||
expect(currentFlags.data['N8N_ENV_FEAT_TEST']).toBe('true');
|
||||
});
|
||||
|
||||
test('should reset feature flags at runtime', async ({ api }) => {
|
||||
const setResponse1 = await api.setEnvFeatureFlags({
|
||||
N8N_ENV_FEAT_TEST: 'true',
|
||||
});
|
||||
|
||||
expect(setResponse1.data.success).toBe(true);
|
||||
expect(setResponse1.data.flags['N8N_ENV_FEAT_TEST']).toBe('true');
|
||||
|
||||
const clearResponse = await api.clearEnvFeatureFlags();
|
||||
|
||||
expect(clearResponse.data.success).toBe(true);
|
||||
|
||||
expect(clearResponse.data.flags).toBeInstanceOf(Object);
|
||||
expect(clearResponse.data.flags['N8N_ENV_FEAT_TEST']).toBeUndefined();
|
||||
|
||||
const currentFlags = await api.getEnvFeatureFlags();
|
||||
|
||||
expect(currentFlags).toBeInstanceOf(Object);
|
||||
expect(currentFlags.data['N8N_ENV_FEAT_TEST']).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,7 @@
|
||||
import { expect, test } from '../../fixtures/base';
|
||||
|
||||
// Example of importing a workflow from a file
|
||||
test.describe('PDF Test', () => {
|
||||
// eslint-disable-next-line playwright/no-skipped-test
|
||||
test.skip('Can read and write PDF files and extract text', async ({ n8n }) => {
|
||||
test('Can read and write PDF files and extract text', async ({ n8n }) => {
|
||||
await n8n.goHome();
|
||||
await n8n.workflows.clickAddWorkflowButton();
|
||||
await n8n.canvas.importWorkflow('test_pdf_workflow.json', 'PDF Workflow');
|
||||
|
||||
Reference in New Issue
Block a user