test: Migrate NDV tests from Cypress -> Playwright (#19148)

This commit is contained in:
shortstacked
2025-09-04 09:06:51 +01:00
committed by GitHub
parent 4a21f79f5c
commit 36079da415
20 changed files with 3257 additions and 1047 deletions

View File

@@ -0,0 +1,42 @@
var File = function(url, object){
File.list = Array.isArray(File.list)? File.list : [];
File.progress = File.progress || 0;
this.progress = 0;
this.object = object;
this.url = url;
};
File.indexOf = function(term){
for(var index in File.list){
var file = File.list[index];
if (file.equals(term) || file.url === term || file.object === term) {
return index;
}
}
return -1;
};
File.find = function(term){
var index = File.indexOf(term);
return ~index && File.list[index];
};
File.prototype.equals = function(file){
var isFileType = file instanceof File;
return isFileType && this.url === file.url && this.object === file.object;
};
File.prototype.save = function(update){
update = typeof update === 'undefined'? true : update;
if(Array.isArray(File.list)){
var index = File.indexOf(this);
if(~index && update) {
File.list[index] = this;
console.warn('File `%s` has been loaded before and updated now for: %O.', this.url, this);
}else File.list.push(this);
console.log(File.list)
}else{
File.list = [this];
}
return this;
};

View File

@@ -0,0 +1,83 @@
{
"name": "My workflow 8",
"nodes": [
{
"parameters": {
"operation": "getAllPeople",
"limit": 10
},
"id": "39cd80ce-5a8f-4339-b3d5-c4af969dd330",
"name": "Customer Datastore (n8n training)",
"type": "n8n-nodes-base.n8nTrainingCustomerDatastore",
"typeVersion": 1,
"position": [940, 680]
},
{
"parameters": {
"values": {
"number": [
{
"name": "objectValue.prop1",
"value": 123
}
],
"string": [
{
"name": "objectValue.prop2",
"value": "someText"
}
]
},
"options": {
"dotNotation": true
}
},
"id": "6e4490f6-ba95-4400-beec-2caefdd4895a",
"name": "Set",
"type": "n8n-nodes-base.set",
"typeVersion": 1,
"position": [1300, 680]
},
{
"parameters": {},
"id": "58512a93-dabf-4584-817f-27c608c1bdd5",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [720, 680]
}
],
"pinData": {},
"connections": {
"Customer Datastore (n8n training)": {
"main": [
[
{
"node": "Set",
"type": "main",
"index": 0
}
]
]
},
"When clicking Execute workflow": {
"main": [
[
{
"node": "Customer Datastore (n8n training)",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {},
"versionId": "4a4f292a-92be-427c-848a-9582527f5ed3",
"id": "8",
"meta": {
"instanceId": "032eceae7493054b723340499be69ecbf4cbe28a7ec6df676b759000750b968d"
},
"tags": []
}

View File

@@ -17,6 +17,19 @@ export class CanvasPage extends BasePage {
return this.page.getByTestId('node-creator-item-name').getByText(subItemText, { exact: true });
}
getNthCreatorItem(index: number): Locator {
return this.page.getByTestId('node-creator-item').nth(index);
}
getNodeCreatorHeader(text?: string) {
const header = this.page.getByTestId('nodes-list-header');
return text ? header.filter({ hasText: text }) : header.first();
}
async selectNodeCreatorItemByText(nodeName: string) {
await this.page.getByText(nodeName).click();
}
nodeByName(nodeName: string): Locator {
return this.page.locator(`[data-test-id="canvas-node"][data-node-name="${nodeName}"]`);
}
@@ -546,6 +559,12 @@ export class CanvasPage extends BasePage {
await this.getManualChatInput().fill(message);
await this.getManualChatModal().locator('.chat-input-send-button').click();
}
/**
* Get all currently selected nodes on the canvas
*/
getSelectedNodes() {
return this.page.locator('[data-test-id="canvas-node"].selected');
}
async openExecutions() {
await this.page.getByTestId('radio-button-executions').click();

View File

@@ -23,11 +23,6 @@ export class NodeDetailsViewPage extends BasePage {
return this.getContainer().locator('.parameter-item').filter({ hasText: labelName });
}
/**
* Fill a parameter input field
* @param labelName - The label of the parameter e.g URL
* @param value - The value to fill in the input field e.g https://foo.bar
*/
async fillParameterInput(labelName: string, value: string) {
await this.getParameterByLabel(labelName).getByTestId('parameter-input-field').fill(value);
}
@@ -119,21 +114,14 @@ export class NodeDetailsViewPage extends BasePage {
return this.getOutputTableRow(row).locator('td').nth(col);
}
/**
* Get a cell from the output table body, this doesn't include the header row
* @param row - The row index
* @param col - The column index
*/
getOutputTbodyCell(row: number, col: number) {
return this.getOutputTable().locator('tbody tr').nth(row).locator('td').nth(col);
}
// Pin data operations
async setPinnedData(data: object | string) {
const pinnedData = typeof data === 'string' ? data : JSON.stringify(data);
await this.getEditPinnedDataButton().click();
// Wait for editor to appear and use broader selector
const editor = this.getOutputPanel().locator('[contenteditable="true"]');
await editor.waitFor();
await editor.click();
@@ -150,7 +138,6 @@ export class NodeDetailsViewPage extends BasePage {
await editor.click();
await editor.fill('');
// Set clipboard data and paste
await this.page.evaluate(async (jsonData) => {
await navigator.clipboard.writeText(JSON.stringify(jsonData));
}, data);
@@ -163,7 +150,6 @@ export class NodeDetailsViewPage extends BasePage {
await this.getRunDataPaneHeader().locator('button:visible').filter({ hasText: 'Save' }).click();
}
// Assignment collection methods for advanced tests
getAssignmentCollectionAdd(paramName: string) {
return this.page
.getByTestId(`assignment-collection-${paramName}`)
@@ -192,6 +178,10 @@ export class NodeDetailsViewPage extends BasePage {
return await this.page.request.get(path);
}
getWebhookUrl() {
return this.page.locator('.webhook-url').textContent();
}
getVisiblePoppers() {
return this.page.locator('.el-popper:visible');
}
@@ -206,104 +196,56 @@ export class NodeDetailsViewPage extends BasePage {
async typeInExpressionEditor(text: string) {
const editor = this.getInlineExpressionEditorInput();
await editor.click();
// We have to use type() instead of fill() because the editor is a CodeMirror editor
await editor.type(text);
}
/**
* Get parameter input by name (for Code node and similar)
* @param parameterName - The name of the parameter e.g 'jsCode', 'mode'
*/
getParameterInput(parameterName: string) {
return this.page.getByTestId(`parameter-input-${parameterName}`);
}
/**
* Get parameter input field
* @param parameterName - The name of the parameter
*/
getParameterInputField(parameterName: string) {
return this.getParameterInput(parameterName).locator('input');
}
/**
* Select option in parameter dropdown (improved with Playwright best practices)
* @param parameterName - The parameter name
* @param optionText - The text of the option to select
*/
async selectOptionInParameterDropdown(parameterName: string, optionText: string) {
const dropdown = this.getParameterInput(parameterName);
await dropdown.click();
// Wait for dropdown to be visible and select option - following Playwright best practices
await this.page.getByRole('option', { name: optionText }).click();
}
/**
* Click parameter dropdown by name (test-id based selector)
* @param parameterName - The parameter name e.g 'httpMethod', 'authentication'
*/
async clickParameterDropdown(parameterName: string): Promise<void> {
await this.clickByTestId(`parameter-input-${parameterName}`);
}
/**
* Select option from visible dropdown using Playwright role-based selectors
* This follows the pattern used in working n8n tests
* @param optionText - The text of the option to select
*/
async selectFromVisibleDropdown(optionText: string): Promise<void> {
// Use Playwright's role-based selector - this is more reliable than CSS selectors
await this.page.getByRole('option', { name: optionText }).click();
}
/**
* Fill parameter input field by parameter name
* @param parameterName - The parameter name e.g 'path', 'url'
* @param value - The value to fill
*/
async fillParameterInputByName(parameterName: string, value: string): Promise<void> {
const input = this.getParameterInputField(parameterName);
await input.click();
await input.fill(value);
}
/**
* Click parameter options expansion (e.g. for Response Code)
*/
async clickParameterOptions(): Promise<void> {
await this.page.locator('.param-options').click();
}
/**
* Get visible Element UI popper (dropdown/popover)
* Ported from Cypress pattern with Playwright selectors
*/
getVisiblePopper() {
return this.page.locator('.el-popper:visible');
}
/**
* Wait for parameter dropdown to be visible and ready for interaction
* @param parameterName - The parameter name
*/
async waitForParameterDropdown(parameterName: string): Promise<void> {
const dropdown = this.getParameterInput(parameterName);
await dropdown.waitFor({ state: 'visible' });
await expect(dropdown).toBeEnabled();
}
/**
* Click on a floating node in the NDV (for switching between connected nodes)
* @param nodeName - The name of the node to click
*/
async clickFloatingNode(nodeName: string) {
await this.page.locator(`[data-test-id="floating-node"][data-node-name="${nodeName}"]`).click();
}
/**
* Execute the previous node (useful for providing input data)
*/
async executePrevious() {
await this.clickByTestId('execute-previous-node');
}
@@ -378,9 +320,14 @@ export class NodeDetailsViewPage extends BasePage {
async setParameterDropdown(parameterName: string, optionText: string): Promise<void> {
await this.getParameterInput(parameterName).click();
await this.page.getByRole('option', { name: optionText }).click();
}
async changeNodeOperation(operationName: string): Promise<void> {
await this.setParameterDropdown('operation', operationName);
}
async setParameterInput(parameterName: string, value: string): Promise<void> {
await this.fillParameterInputByName(parameterName, value);
}
@@ -423,31 +370,21 @@ export class NodeDetailsViewPage extends BasePage {
case 'switch':
return await this.getSwitchParameterValue(parameterName);
default:
// Fallback for unknown types
return (await this.getParameterInput(parameterName).textContent()) ?? '';
}
}
/**
* Get value from a text parameter - simplified approach
*/
private async getTextParameterValue(parameterName: string): Promise<string> {
const parameterContainer = this.getParameterInput(parameterName);
const input = parameterContainer.locator('input').first();
return await input.inputValue();
}
/**
* Get value from a dropdown parameter
*/
private async getDropdownParameterValue(parameterName: string): Promise<string> {
const selectedOption = this.getParameterInput(parameterName).locator('.el-select__tags-text');
return (await selectedOption.textContent()) ?? '';
}
/**
* Get value from a switch parameter
*/
private async getSwitchParameterValue(parameterName: string): Promise<string> {
const switchElement = this.getParameterInput(parameterName).locator('.el-switch');
const isEnabled = (await switchElement.getAttribute('aria-checked')) === 'true';
@@ -503,8 +440,12 @@ export class NodeDetailsViewPage extends BasePage {
.first();
}
getNodeInputOptions() {
return this.getInputPanel().getByTestId('ndv-input-select');
}
async selectInputNode(nodeName: string) {
const inputSelect = this.getInputPanel().getByTestId('ndv-input-select');
const inputSelect = this.getNodeInputOptions();
await inputSelect.click();
await this.page.getByRole('option', { name: nodeName }).click();
}
@@ -572,6 +513,35 @@ export class NodeDetailsViewPage extends BasePage {
return this.getInlineExpressionEditorInput().locator('.cm-content');
}
getInlineExpressionEditorOutput() {
return this.page.getByTestId('inline-expression-editor-output');
}
getInlineExpressionEditorItemInput() {
return this.page.getByTestId('inline-expression-editor-item-input').locator('input');
}
getInlineExpressionEditorItemPrevButton() {
return this.page.getByTestId('inline-expression-editor-item-prev');
}
getInlineExpressionEditorItemNextButton() {
return this.page.getByTestId('inline-expression-editor-item-next');
}
async expressionSelectNextItem() {
await this.getInlineExpressionEditorItemNextButton().click();
}
async expressionSelectPrevItem() {
await this.getInlineExpressionEditorItemPrevButton().click();
}
async typeIntoParameterInput(parameterName: string, content: string): Promise<void> {
const input = this.getParameterInput(parameterName);
await input.type(content);
}
getInputTable() {
return this.getInputPanel().locator('table');
}
@@ -587,12 +557,10 @@ export class NodeDetailsViewPage extends BasePage {
async toggleCodeMode(switchTo: 'Run Once for Each Item' | 'Run Once for All Items') {
await this.getParameterInput('mode').click();
await this.page.getByRole('option', { name: switchTo }).click();
// This is a workaround to wait for the code editor to reinitialize after the mode switch
// eslint-disable-next-line playwright/no-wait-for-timeout
await this.page.waitForTimeout(2500);
}
// Pagination methods for output panel
getOutputPagination() {
return this.getOutputPanel().getByTestId('ndv-data-pagination');
}
@@ -616,17 +584,297 @@ export class NodeDetailsViewPage extends BasePage {
return (await this.getOutputTbodyCell(row, col).textContent()) ?? '';
}
/**
* Set parameter input value by clearing and filling (for parameters without standard test-id)
* @param parameterName - The parameter name
* @param value - The value to set
*/
async setParameterInputValue(parameterName: string, value: string): Promise<void> {
const input = this.getParameterInput(parameterName).locator('input');
await input.clear();
await input.fill(value);
}
getNodeRunErrorMessage() {
return this.page.getByTestId('node-error-message');
}
getNodeRunErrorDescription() {
return this.page.getByTestId('node-error-description');
}
getInputRunSelector() {
return this.getInputPanel().getByTestId('run-selector');
}
getOutputRunSelector() {
return this.getOutputPanel().getByTestId('run-selector');
}
async toggleOutputRunLinking() {
await this.getOutputPanel().getByTestId('link-run').click();
}
async toggleInputRunLinking() {
await this.getInputPanel().getByTestId('link-run').click();
}
getOutputLinkRun() {
return this.getOutputPanel().getByTestId('link-run');
}
getInputLinkRun() {
return this.getInputPanel().getByTestId('link-run');
}
async isOutputRunLinkingEnabled() {
const linkButton = this.getOutputLinkRun();
const classList = await linkButton.getAttribute('class');
return classList?.includes('linked') ?? false;
}
async ensureOutputRunLinking(shouldBeLinked: boolean = true) {
const isLinked = await this.isOutputRunLinkingEnabled();
if (isLinked !== shouldBeLinked) {
await this.toggleOutputRunLinking();
}
}
async changeInputRunSelector(value: string) {
const selector = this.getInputRunSelector();
await selector.click();
await this.page.getByRole('option', { name: value }).click();
}
async changeOutputRunSelector(value: string) {
const selector = this.getOutputRunSelector();
await selector.click();
await this.page.getByRole('option', { name: value }).click();
}
async getInputRunSelectorValue() {
return await this.getInputRunSelector().locator('input').inputValue();
}
async getOutputRunSelectorValue() {
return await this.getOutputRunSelector().locator('input').inputValue();
}
getOutputDisplayMode() {
return this.getOutputPanel().getByTestId('ndv-output-display-mode');
}
getSchemaViewItems() {
return this.getOutputPanel().locator('[data-test-id="run-data-schema-item"]');
}
getSchemaItem(key: string) {
return this.getSchemaViewItems().filter({ hasText: key });
}
async expandSchemaItem(itemText: string) {
const item = this.getSchemaItem(itemText);
await item.locator('.toggle').click();
}
getPaginationContainer() {
return this.getOutputPanel().locator('[class*="_pagination"]');
}
getExecuteNodeButton() {
return this.page.getByTestId('node-execute-button');
}
getTriggerPanelExecuteButton() {
return this.page.getByTestId('trigger-execute-button');
}
async openCodeEditorFullscreen() {
await this.page.getByTestId('code-editor-fullscreen-button').click();
}
getCodeEditorFullscreen() {
return this.page.getByTestId('code-editor-fullscreen').locator('.cm-content');
}
getCodeEditorDialog() {
return this.page.locator('.el-dialog');
}
async closeCodeEditorDialog() {
await this.getCodeEditorDialog().locator('.el-dialog__close').click();
}
getWebhookTriggerListening() {
return this.page.getByTestId('trigger-listening');
}
getNodeRunSuccessIndicator() {
return this.page.getByTestId('node-run-status-success');
}
getNodeRunErrorIndicator() {
return this.page.getByTestId('node-run-status-danger');
}
getNodeRunTooltipIndicator() {
return this.page.getByTestId('node-run-info');
}
async openSettings() {
await this.page.getByTestId('tab-settings').click();
}
getNodeVersion() {
return this.page.getByTestId('node-version');
}
getOutputSearchInput() {
return this.getOutputPanel().getByTestId('ndv-search');
}
getInputSearchInput() {
return this.getInputPanel().getByTestId('ndv-search');
}
async searchOutputData(searchTerm: string) {
const searchInput = this.getOutputSearchInput();
await searchInput.click();
await searchInput.fill(searchTerm);
}
async searchInputData(searchTerm: string) {
const searchInput = this.getInputSearchInput();
await searchInput.click();
await searchInput.fill(searchTerm);
}
getOutputItemsCount() {
return this.getOutputPanel().getByTestId('ndv-items-count');
}
getInputItemsCount() {
return this.getInputPanel().getByTestId('ndv-items-count');
}
/**
* Type multiple values into the first available text parameter field
* Useful for testing multiple parameter changes
*/
async fillFirstAvailableTextParameterMultipleTimes(values: string[]) {
const firstTextField = this.getNodeParameters().locator('input[type="text"]').first();
await firstTextField.click();
for (const value of values) {
await firstTextField.fill(value);
}
}
getFloatingNodeByPosition(position: 'inputMain' | 'outputMain' | 'inputSub' | 'outputSub') {
return this.page.locator(`[data-node-placement="${position}"]`);
}
getNodeNameContainer() {
return this.getContainer().getByTestId('node-title-container');
}
async clickFloatingNodeByPosition(
position: 'inputMain' | 'outputMain' | 'inputSub' | 'outputSub',
) {
// eslint-disable-next-line playwright/no-force-option
await this.getFloatingNodeByPosition(position).click({ force: true });
}
async navigateToNextFloatingNodeWithKeyboard() {
await this.page.keyboard.press('Shift+Meta+Alt+ArrowRight');
}
async navigateToPreviousFloatingNodeWithKeyboard() {
await this.page.keyboard.press('Shift+Meta+Alt+ArrowLeft');
}
async verifyFloatingNodeName(
position: 'inputMain' | 'outputMain' | 'inputSub' | 'outputSub',
nodeName: string,
index: number = 0,
) {
const floatingNode = this.getFloatingNodeByPosition(position).nth(index);
await expect(floatingNode).toHaveAttribute('data-node-name', nodeName);
}
async getFloatingNodeCount(position: 'inputMain' | 'outputMain' | 'inputSub' | 'outputSub') {
return await this.getFloatingNodeByPosition(position).count();
}
getAddSubNodeButton(connectionType: string, index: number = 0) {
return this.page.getByTestId(`add-subnode-${connectionType}-${index}`);
}
getSubNodeConnectionGroup(connectionType: string, index: number = 0) {
return this.page.getByTestId(`subnode-connection-group-${connectionType}-${index}`);
}
getFloatingSubNodes(connectionType: string, index: number = 0) {
return this.getSubNodeConnectionGroup(connectionType, index).getByTestId('floating-subnode');
}
getNodesWithIssues() {
return this.page.locator('[class*="hasIssues"]');
}
async connectAISubNode(connectionType: string, nodeName: string, index: number = 0) {
await this.getAddSubNodeButton(connectionType, index).click();
await this.page.getByText(nodeName).click();
await this.getFloatingNode().click();
}
getFloatingNode() {
return this.page.getByTestId('floating-node');
}
async getNodesWithIssuesCount() {
return await this.getNodesWithIssues().count();
}
async addItemToFixedCollection(collectionName: string) {
await this.page.getByTestId(`fixed-collection-${collectionName}`).click();
}
async clickParameterItemAction(actionText: string) {
await this.page.getByTestId('parameter-item').getByText(actionText).click();
}
getParameterItemWithText(text: string) {
return this.page.getByTestId('parameter-item').getByText(text);
}
getParameterInputWithIssues(parameterPath: string) {
return this.page.locator(
`[data-test-id="parameter-input-field"][title*="${parameterPath}"][title*="has issues"]`,
);
}
getResourceLocator(paramName: string) {
return this.page.getByTestId(`resource-locator-${paramName}`);
}
getResourceLocatorInput(paramName: string) {
return this.getResourceLocator(paramName).getByTestId('rlc-input-container');
}
getResourceLocatorModeSelector(paramName: string) {
return this.getResourceLocator(paramName).getByTestId('rlc-mode-selector');
}
async setRLCValue(paramName: string, value: string): Promise<void> {
await this.getResourceLocatorModeSelector(paramName).click();
const visibleOptions = this.page.locator('.el-popper:visible .el-select-dropdown__item');
await visibleOptions.last().click();
const input = this.getResourceLocatorInput(paramName).locator('input');
await input.fill(value);
}
async clickNodeCreatorInsertOneButton() {
await this.page.getByText('Insert one').click();
}
getInputSelect() {
return this.page.getByTestId('ndv-input-select').locator('input');
}

View File

@@ -52,7 +52,6 @@ test.describe('Workflows', () => {
// Search for specific workflow
await n8n.workflows.searchWorkflows(specificName);
await expect(n8n.workflows.getWorkflowItems()).toHaveCount(1);
await expect(n8n.workflows.getWorkflowByName(specificName)).toBeVisible();
// Search with partial term

View File

@@ -0,0 +1,800 @@
import {
CODE_NODE_NAME,
CODE_NODE_DISPLAY_NAME,
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
} from '../../config/constants';
import { test, expect } from '../../fixtures/base';
import type { n8nPage } from '../../pages/n8nPage';
test.describe('NDV', () => {
test.beforeEach(async ({ n8n }) => {
await n8n.start.fromBlankCanvas();
});
test('should show up when double clicked on a node and close when Back to canvas clicked', async ({
n8n,
}) => {
await n8n.canvas.addNode('Manual Trigger');
const canvasNodes = n8n.canvas.getCanvasNodes();
await canvasNodes.first().dblclick();
await expect(n8n.ndv.getContainer()).toBeVisible();
await n8n.ndv.clickBackToCanvasButton();
await expect(n8n.ndv.getContainer()).toBeHidden();
});
test('should show input panel when node is not connected', async ({ n8n }) => {
await n8n.canvas.addNode('Manual Trigger');
await n8n.canvas.deselectAll();
await n8n.canvas.addNode('Edit Fields (Set)', { closeNDV: true });
const canvasNodes = n8n.canvas.getCanvasNodes();
await canvasNodes.last().dblclick();
await expect(n8n.ndv.getContainer()).toBeVisible();
await expect(n8n.ndv.getInputPanel()).toContainText('Wire me up');
});
test('should test webhook node', async ({ n8n }) => {
await n8n.canvas.addNode('Webhook', { closeNDV: false });
await n8n.ndv.execute();
const webhookUrl = await n8n.ndv.getWebhookUrl();
await expect(n8n.ndv.getWebhookTriggerListening()).toBeVisible();
const response = await n8n.ndv.makeWebhookRequest(webhookUrl as string);
expect(response.status()).toBe(200);
await expect(n8n.ndv.getOutputPanel()).toBeVisible();
await expect(n8n.ndv.getOutputDataContainer()).toBeVisible();
});
test('should change input and go back to canvas', async ({ n8n }) => {
await n8n.start.fromImportedWorkflow('NDV-test-select-input.json');
await n8n.canvas.clickZoomToFitButton();
await n8n.canvas.getCanvasNodes().last().dblclick();
await n8n.ndv.execute();
await n8n.ndv.switchInputMode('Table');
await n8n.ndv.getNodeInputOptions().last().click();
await expect(n8n.ndv.getInputPanel()).toContainText('start');
await n8n.ndv.clickBackToCanvasButton();
await expect(n8n.ndv.getContainer()).toBeHidden();
});
test('should show correct validation state for resource locator params', async ({ n8n }) => {
await n8n.canvas.addNode('Typeform Trigger', { closeNDV: false });
await expect(n8n.ndv.getContainer()).toBeVisible();
await n8n.ndv.clickBackToCanvasButton();
await n8n.canvas.openNode('Typeform Trigger');
await expect(n8n.canvas.getNodeIssuesByName('Typeform Trigger')).toBeVisible();
});
test('should show validation errors only after blur or re-opening of NDV', async ({ n8n }) => {
await n8n.canvas.addNode('Manual Trigger');
await n8n.canvas.addNode('Airtable', { closeNDV: false, action: 'Search records' });
await expect(n8n.ndv.getContainer()).toBeVisible();
await expect(n8n.canvas.getNodeIssuesByName('Airtable')).toBeHidden();
await n8n.ndv.getParameterInputField('table').nth(1).focus();
await n8n.ndv.getParameterInputField('table').nth(1).blur();
await n8n.ndv.getParameterInputField('base').nth(1).focus();
await n8n.ndv.getParameterInputField('base').nth(1).blur();
await expect(n8n.ndv.getParameterInput('base')).toHaveClass(/has-issues|error|invalid/);
await expect(n8n.ndv.getParameterInput('table')).toHaveClass(/has-issues|error|invalid/);
await n8n.ndv.clickBackToCanvasButton();
await n8n.canvas.openNode('Search records');
await expect(n8n.canvas.getNodeIssuesByName('Search records')).toBeVisible();
});
test('should show all validation errors when opening pasted node', async ({ n8n }) => {
await n8n.start.fromImportedWorkflow('Test_workflow_ndv_errors.json');
const canvasNodes = n8n.canvas.getCanvasNodes();
await expect(canvasNodes).toHaveCount(1);
await n8n.canvas.openNode('Airtable');
await expect(n8n.canvas.getNodeIssuesByName('Airtable')).toBeVisible();
});
test('should render run errors correctly', async ({ n8n }) => {
await n8n.start.fromImportedWorkflow('Test_workflow_ndv_run_error.json');
await n8n.canvas.openNode('Error');
await n8n.ndv.execute();
await expect(n8n.ndv.getNodeRunErrorMessage()).toHaveText(
"Paired item data for item from node 'Break pairedItem chain' is unavailable. Ensure 'Break pairedItem chain' is providing the required output.",
);
await expect(n8n.ndv.getNodeRunErrorDescription()).toContainText(
"An expression here won't work because it uses .item and n8n can't figure out the matching item.",
);
await expect(n8n.ndv.getNodeRunErrorMessage()).toBeVisible();
await expect(n8n.ndv.getNodeRunErrorDescription()).toBeVisible();
});
test('should save workflow using keyboard shortcut from NDV', async ({ n8n }) => {
await n8n.canvas.addNode('Manual Trigger');
await n8n.canvas.addNode('Edit Fields (Set)', { closeNDV: false });
await expect(n8n.ndv.getContainer()).toBeVisible();
await n8n.page.keyboard.press('ControlOrMeta+s');
await expect(n8n.canvas.getWorkflowSaveButton()).toBeHidden();
});
test('webhook should fallback to webhookId if path is empty', async ({ n8n }) => {
await n8n.canvas.addNode('Webhook', { closeNDV: false });
await expect(n8n.canvas.getNodeIssuesByName('Webhook')).toBeHidden();
await expect(n8n.ndv.getExecuteNodeButton()).toBeEnabled();
await expect(n8n.ndv.getTriggerPanelExecuteButton()).toBeVisible();
await n8n.ndv.getParameterInputField('path').clear();
const webhookUrlsContainer = n8n.ndv.getContainer().getByText('Webhook URLs').locator('..');
const urlText = await webhookUrlsContainer.textContent();
const uuidRegex = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
expect(urlText).toMatch(uuidRegex);
await n8n.ndv.close();
await n8n.canvas.openNode('Webhook');
await n8n.ndv.fillParameterInput('path', 'test-path');
const updatedUrlText = await webhookUrlsContainer.textContent();
expect(updatedUrlText).toContain('test-path');
expect(updatedUrlText).not.toMatch(uuidRegex);
});
test.describe('test output schema view', () => {
const schemaKeys = [
'id',
'name',
'email',
'notes',
'country',
'created',
'objectValue',
'prop1',
'prop2',
];
const setupSchemaWorkflow = async (n8n: n8nPage) => {
await n8n.start.fromImportedWorkflow('Test_workflow_schema_test.json');
await n8n.canvas.clickZoomToFitButton();
await n8n.canvas.openNode('Set');
await n8n.ndv.execute();
};
test('should switch to output schema view and validate it', async ({ n8n }) => {
await setupSchemaWorkflow(n8n);
await n8n.ndv.switchOutputMode('Schema');
for (const key of schemaKeys) {
await expect(n8n.ndv.getSchemaViewItems().filter({ hasText: key })).toBeVisible();
}
});
test('should preserve schema view after execution', async ({ n8n }) => {
await setupSchemaWorkflow(n8n);
await n8n.ndv.switchOutputMode('Schema');
await n8n.ndv.execute();
for (const key of schemaKeys) {
await expect(n8n.ndv.getSchemaViewItems().filter({ hasText: key })).toBeVisible();
}
});
test('should collapse and expand nested schema object', async ({ n8n }) => {
await setupSchemaWorkflow(n8n);
const expandedObjectProps = ['prop1', 'prop2'];
await n8n.ndv.switchOutputMode('Schema');
for (const key of expandedObjectProps) {
await expect(n8n.ndv.getSchemaViewItems().filter({ hasText: key })).toBeVisible();
}
const objectValueItem = n8n.ndv.getSchemaViewItems().filter({ hasText: 'objectValue' });
await objectValueItem.locator('.toggle').click();
for (const key of expandedObjectProps) {
await expect(n8n.ndv.getSchemaViewItems().filter({ hasText: key })).not.toBeInViewport();
}
});
test('should not display pagination for schema', async ({ n8n }) => {
await setupSchemaWorkflow(n8n);
await n8n.ndv.clickBackToCanvasButton();
await n8n.canvas.deselectAll();
await n8n.canvas.nodeByName('Set').click();
await n8n.canvas.addNode('Customer Datastore (n8n training)');
await n8n.canvas.openNode('Customer Datastore (n8n training)');
await n8n.ndv.execute();
await expect(n8n.ndv.getOutputPanel().getByText('5 items')).toBeVisible();
await n8n.ndv.switchOutputMode('Schema');
const schemaItemsCount = await n8n.ndv.getSchemaViewItems().count();
expect(schemaItemsCount).toBeGreaterThan(0);
await n8n.ndv.switchOutputMode('JSON');
});
test('should display large schema', async ({ n8n }) => {
await n8n.start.fromImportedWorkflow('Test_workflow_schema_test_pinned_data.json');
await n8n.canvas.clickZoomToFitButton();
await n8n.canvas.openNode('Set');
await expect(n8n.ndv.getOutputPanel().getByText('20 items')).toBeVisible();
await expect(n8n.ndv.getOutputPanel().locator('[class*="_pagination"]')).toBeVisible();
await n8n.ndv.switchOutputMode('Schema');
await expect(n8n.ndv.getOutputPanel().locator('[class*="_pagination"]')).toBeHidden();
});
});
test('should display parameter hints correctly', async ({ n8n }) => {
await n8n.start.fromImportedWorkflow('Test_workflow_3.json');
await n8n.canvas.openNode('Set1');
await n8n.ndv.getParameterInputField('value').clear();
await n8n.ndv.getParameterInputField('value').fill('=');
await n8n.ndv.getInlineExpressionEditorContent().fill('hello');
await n8n.ndv.getParameterInputField('name').click();
await expect(n8n.ndv.getParameterExpressionPreviewValue()).toContainText('hello');
await n8n.ndv.getInlineExpressionEditorContent().fill('');
await n8n.ndv.getParameterInputField('name').click();
await expect(n8n.ndv.getParameterExpressionPreviewValue()).toContainText('[empty]');
await n8n.ndv.getInlineExpressionEditorContent().fill(' test');
await n8n.ndv.getParameterInputField('name').click();
await expect(n8n.ndv.getParameterExpressionPreviewValue()).toContainText(' test');
await n8n.ndv.getInlineExpressionEditorContent().fill(' ');
await n8n.ndv.getParameterInputField('name').click();
await expect(n8n.ndv.getParameterExpressionPreviewValue()).toContainText(' ');
await n8n.ndv.getInlineExpressionEditorContent().fill('<div></div>');
await n8n.ndv.getParameterInputField('name').click();
await expect(n8n.ndv.getParameterExpressionPreviewValue()).toContainText('<div></div>');
});
test('should properly show node execution indicator', async ({ n8n }) => {
await n8n.canvas.addNode('Manual Trigger');
await n8n.canvas.addNode('Code', { action: 'Code in JavaScript', closeNDV: false });
await expect(n8n.ndv.getNodeRunSuccessIndicator()).toBeHidden();
await expect(n8n.ndv.getNodeRunErrorIndicator()).toBeHidden();
await expect(n8n.ndv.getNodeRunTooltipIndicator()).toBeHidden();
await n8n.ndv.execute();
await expect(n8n.ndv.getNodeRunSuccessIndicator()).toBeVisible();
await expect(n8n.ndv.getNodeRunTooltipIndicator()).toBeVisible();
});
test('should show node name and version in settings', async ({ n8n }) => {
await n8n.start.fromImportedWorkflow('Test_workflow_ndv_version.json');
await n8n.canvas.openNode('Edit Fields (old)');
await n8n.ndv.openSettings();
await expect(n8n.ndv.getNodeVersion()).toContainText('Set node version 2');
await expect(n8n.ndv.getNodeVersion()).toContainText('Latest version: 3.4');
await n8n.ndv.close();
await n8n.canvas.openNode('Edit Fields (latest)');
await n8n.ndv.openSettings();
await expect(n8n.ndv.getNodeVersion()).toContainText('Edit Fields (Set) node version 3.4');
await expect(n8n.ndv.getNodeVersion()).toContainText('Latest');
await n8n.ndv.close();
await n8n.canvas.openNode('Function');
await n8n.ndv.openSettings();
await expect(n8n.ndv.getNodeVersion()).toContainText('Function node version 1');
await expect(n8n.ndv.getNodeVersion()).toContainText('Deprecated');
await n8n.ndv.close();
});
test('should not push NDV header out with a lot of code in Code node editor', async ({ n8n }) => {
await n8n.canvas.addNode('Manual Trigger');
await n8n.canvas.addNode('Code', { action: 'Code in JavaScript', closeNDV: false });
const codeEditor = n8n.ndv.getParameterInput('jsCode').locator('.cm-content');
await codeEditor.click();
await n8n.page.keyboard.press('ControlOrMeta+a');
await n8n.page.keyboard.press('Delete');
const dummyCode = Array(50)
.fill(
'console.log("This is a very long line of dummy JavaScript code that should not push the NDV header out of view");',
)
.join('\n');
await codeEditor.fill(dummyCode);
await expect(n8n.ndv.getExecuteNodeButton()).toBeVisible();
});
test('should allow editing code in fullscreen in the code editors', async ({ n8n }) => {
await n8n.canvas.addNode('Manual Trigger');
await n8n.canvas.addNode('Code', { action: 'Code in JavaScript', closeNDV: false });
await n8n.ndv.openCodeEditorFullscreen();
const fullscreenEditor = n8n.ndv.getCodeEditorFullscreen();
await fullscreenEditor.click();
await n8n.page.keyboard.press('ControlOrMeta+a');
await fullscreenEditor.fill('foo()');
await expect(fullscreenEditor).toContainText('foo()');
await n8n.ndv.closeCodeEditorDialog();
await expect(n8n.ndv.getParameterInput('jsCode').locator('.cm-content')).toContainText('foo()');
});
test('should keep search expanded after Execute step node run', async ({ n8n }) => {
await n8n.start.fromImportedWorkflow('Test_ndv_search.json');
await n8n.canvas.clickZoomToFitButton();
await n8n.workflowComposer.executeWorkflowAndWaitForNotification(
'Workflow executed successfully',
);
await n8n.canvas.openNode('Edit Fields');
await expect(n8n.ndv.getOutputPanel()).toBeVisible();
await n8n.ndv.searchOutputData('US');
await expect(n8n.ndv.getOutputTableRow(1).locator('mark')).toContainText('US');
await n8n.ndv.execute();
await expect(n8n.ndv.getOutputSearchInput()).toBeVisible();
await expect(n8n.ndv.getOutputSearchInput()).toHaveValue('US');
});
test('Should render xml and html tags as strings and can search', async ({ n8n }) => {
await n8n.start.fromImportedWorkflow('Test_workflow_xml_output.json');
await n8n.workflowComposer.executeWorkflowAndWaitForNotification(
'Workflow executed successfully',
);
await n8n.canvas.openNode('Edit Fields');
await expect(n8n.ndv.getOutputPanel().locator('[class*="active"]')).toContainText('Table');
await expect(n8n.ndv.getOutputTableRow(1)).toContainText(
'<?xml version="1.0" encoding="UTF-8"?> <library>',
);
await n8n.page.keyboard.press('/');
const searchInput = n8n.ndv.getOutputSearchInput();
await expect(searchInput).toBeFocused();
await searchInput.fill('<lib');
await expect(n8n.ndv.getOutputTableRow(1).locator('mark')).toContainText('<lib');
await n8n.ndv.switchOutputMode('JSON');
await expect(n8n.ndv.getOutputDataContainer().locator('.json-data')).toBeVisible();
});
test.describe('Run Data & Selectors - Advanced', () => {
test('can link and unlink run selectors between input and output', async ({ n8n }) => {
await n8n.start.fromImportedWorkflow('Test_workflow_5.json');
await n8n.canvas.clickZoomToFitButton();
await n8n.workflowComposer.executeWorkflowAndWaitForNotification(
'Workflow executed successfully',
);
await n8n.canvas.openNode('Set3');
await n8n.ndv.switchInputMode('Table');
await n8n.ndv.switchOutputMode('Table');
await n8n.ndv.ensureOutputRunLinking(true);
await n8n.ndv.getInputTbodyCell(0, 0).click();
expect(await n8n.ndv.getInputRunSelectorValue()).toContain('2 of 2 (6 items)');
expect(await n8n.ndv.getOutputRunSelectorValue()).toContain('2 of 2 (6 items)');
await n8n.ndv.changeOutputRunSelector('1 of 2 (6 items)');
expect(await n8n.ndv.getInputRunSelectorValue()).toContain('1 of 2 (6 items)');
await expect(n8n.ndv.getInputTbodyCell(0, 0)).toHaveText('1111');
await expect(n8n.ndv.getOutputTbodyCell(0, 0)).toHaveText('1111');
await n8n.ndv.getInputTbodyCell(0, 0).click();
await n8n.ndv.changeInputRunSelector('2 of 2 (6 items)');
expect(await n8n.ndv.getOutputRunSelectorValue()).toContain('2 of 2 (6 items)');
await n8n.ndv.toggleOutputRunLinking();
await n8n.ndv.getInputTbodyCell(0, 0).click();
await n8n.ndv.changeOutputRunSelector('1 of 2 (6 items)');
expect(await n8n.ndv.getInputRunSelectorValue()).toContain('2 of 2 (6 items)');
await n8n.ndv.toggleOutputRunLinking();
await n8n.ndv.getInputTbodyCell(0, 0).click();
expect(await n8n.ndv.getInputRunSelectorValue()).toContain('1 of 2 (6 items)');
await n8n.ndv.toggleInputRunLinking();
await n8n.ndv.getInputTbodyCell(0, 0).click();
await n8n.ndv.changeInputRunSelector('2 of 2 (6 items)');
expect(await n8n.ndv.getOutputRunSelectorValue()).toContain('1 of 2 (6 items)');
await n8n.ndv.toggleInputRunLinking();
await n8n.ndv.getInputTbodyCell(0, 0).click();
expect(await n8n.ndv.getOutputRunSelectorValue()).toContain('2 of 2 (6 items)');
});
});
test.describe('Remote Options & Network', () => {
test('should not retrieve remote options when a parameter value changes', async ({ n8n }) => {
let fetchParameterOptionsCallCount = 0;
await n8n.page.route('**/rest/dynamic-node-parameters/options', async (route) => {
fetchParameterOptionsCallCount++;
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ data: [] }),
});
});
await n8n.canvas.addNode('E2E Test', { action: 'Remote Options' });
await expect(n8n.ndv.getContainer()).toBeVisible();
await n8n.ndv.fillFirstAvailableTextParameterMultipleTimes(['test1', 'test2', 'test3']);
expect(fetchParameterOptionsCallCount).toBe(1);
});
test('Should show a notice when remote options cannot be fetched because of missing credentials', async ({
n8n,
}) => {
await n8n.page.route('**/rest/dynamic-node-parameters/options', async (route) => {
await route.fulfill({ status: 403 });
});
await n8n.canvas.addNode('Manual Trigger');
await n8n.canvas.addNode('Notion', { action: 'Update a database page', closeNDV: false });
await expect(n8n.ndv.getContainer()).toBeVisible();
await n8n.ndv.addItemToFixedCollection('propertiesUi');
await expect(
n8n.ndv.getParameterInputWithIssues('propertiesUi.propertyValues[0].key'),
).toBeVisible();
});
test('Should show error state when remote options cannot be fetched', async ({ n8n }) => {
await n8n.page.route('**/rest/dynamic-node-parameters/options', async (route) => {
await route.fulfill({ status: 500 });
});
await n8n.canvas.addNode('Manual Trigger');
await n8n.canvas.addNode('Notion', { action: 'Update a database page', closeNDV: false });
await expect(n8n.ndv.getContainer()).toBeVisible();
await n8n.credentials.createAndSaveNewCredential('apiKey', 'sk_test_123');
await n8n.ndv.addItemToFixedCollection('propertiesUi');
await expect(
n8n.ndv.getParameterInputWithIssues('propertiesUi.propertyValues[0].key'),
).toBeVisible();
});
});
test.describe('Floating Nodes Navigation', () => {
test('should traverse floating nodes with mouse', async ({ n8n }) => {
await n8n.start.fromImportedWorkflow('Floating_Nodes.json');
await n8n.canvas.getCanvasNodes().first().dblclick();
await expect(n8n.ndv.getContainer()).toBeVisible();
await expect(n8n.ndv.getFloatingNodeByPosition('inputMain')).toBeHidden();
await expect(n8n.ndv.getFloatingNodeByPosition('outputMain')).toBeVisible();
for (let i = 0; i < 4; i++) {
await n8n.ndv.clickFloatingNodeByPosition('outputMain');
await expect(n8n.ndv.getFloatingNodeByPosition('inputMain')).toBeVisible();
await expect(n8n.ndv.getFloatingNodeByPosition('outputMain')).toBeVisible();
await n8n.ndv.close();
await expect(n8n.canvas.getSelectedNodes()).toHaveCount(1);
await n8n.canvas.getSelectedNodes().first().dblclick();
await expect(n8n.ndv.getContainer()).toBeVisible();
}
await n8n.ndv.clickFloatingNodeByPosition('outputMain');
await expect(n8n.ndv.getFloatingNodeByPosition('inputMain')).toBeVisible();
for (let i = 0; i < 4; i++) {
await n8n.ndv.clickFloatingNodeByPosition('inputMain');
await expect(n8n.ndv.getFloatingNodeByPosition('outputMain')).toBeVisible();
await expect(n8n.ndv.getFloatingNodeByPosition('inputMain')).toBeVisible();
}
await n8n.ndv.clickFloatingNodeByPosition('inputMain');
await expect(n8n.ndv.getFloatingNodeByPosition('inputMain')).toBeHidden();
await expect(n8n.ndv.getFloatingNodeByPosition('inputSub')).toBeHidden();
await expect(n8n.ndv.getFloatingNodeByPosition('outputSub')).toBeHidden();
await n8n.ndv.close();
await expect(n8n.canvas.getSelectedNodes()).toHaveCount(1);
});
test('should traverse floating nodes with keyboard', async ({ n8n }) => {
await n8n.start.fromImportedWorkflow('Floating_Nodes.json');
await n8n.canvas.getCanvasNodes().first().dblclick();
await expect(n8n.ndv.getContainer()).toBeVisible();
await expect(n8n.ndv.getFloatingNodeByPosition('inputMain')).toBeHidden();
await expect(n8n.ndv.getFloatingNodeByPosition('outputMain')).toBeVisible();
for (let i = 0; i < 4; i++) {
await n8n.ndv.navigateToNextFloatingNodeWithKeyboard();
await expect(n8n.ndv.getFloatingNodeByPosition('inputMain')).toBeVisible();
await expect(n8n.ndv.getFloatingNodeByPosition('outputMain')).toBeVisible();
await n8n.ndv.close();
await expect(n8n.canvas.getSelectedNodes()).toHaveCount(1);
await n8n.canvas.getSelectedNodes().first().dblclick();
await expect(n8n.ndv.getContainer()).toBeVisible();
}
await n8n.ndv.navigateToNextFloatingNodeWithKeyboard();
await expect(n8n.ndv.getFloatingNodeByPosition('inputMain')).toBeVisible();
for (let i = 0; i < 4; i++) {
await n8n.ndv.navigateToPreviousFloatingNodeWithKeyboard();
await expect(n8n.ndv.getFloatingNodeByPosition('outputMain')).toBeVisible();
await expect(n8n.ndv.getFloatingNodeByPosition('inputMain')).toBeVisible();
}
await n8n.ndv.navigateToPreviousFloatingNodeWithKeyboard();
await expect(n8n.ndv.getFloatingNodeByPosition('inputMain')).toBeHidden();
await expect(n8n.ndv.getFloatingNodeByPosition('inputSub')).toBeHidden();
await expect(n8n.ndv.getFloatingNodeByPosition('outputSub')).toBeHidden();
await n8n.ndv.close();
await expect(n8n.canvas.getSelectedNodes()).toHaveCount(1);
});
test('should connect floating sub-nodes', async ({ n8n }) => {
await n8n.canvas.addNode('AI Agent', { closeNDV: false });
await expect(n8n.ndv.getContainer()).toBeVisible();
await n8n.ndv.connectAISubNode('ai_languageModel', 'Anthropic Chat Model');
await n8n.ndv.connectAISubNode('ai_memory', 'Simple Memory');
await n8n.ndv.connectAISubNode('ai_tool', 'HTTP Request Tool');
expect(await n8n.ndv.getNodesWithIssuesCount()).toBeGreaterThanOrEqual(2);
});
test('should have the floating nodes in correct order', async ({ n8n }) => {
await n8n.start.fromImportedWorkflow('Floating_Nodes.json');
await n8n.canvas.openNode('Merge');
await expect(n8n.ndv.getContainer()).toBeVisible();
expect(await n8n.ndv.getFloatingNodeCount('inputMain')).toBe(2);
await n8n.ndv.verifyFloatingNodeName('inputMain', 'Edit Fields1', 0);
await n8n.ndv.verifyFloatingNodeName('inputMain', 'Edit Fields0', 1);
await n8n.ndv.close();
await n8n.canvas.openNode('Merge1');
await expect(n8n.ndv.getContainer()).toBeVisible();
expect(await n8n.ndv.getFloatingNodeCount('inputMain')).toBe(2);
await n8n.ndv.verifyFloatingNodeName('inputMain', 'Edit Fields0', 0);
await n8n.ndv.verifyFloatingNodeName('inputMain', 'Edit Fields1', 1);
});
});
test.describe('Parameter Management - Advanced', () => {
test('Should clear mismatched collection parameters', async ({ n8n }) => {
await n8n.canvas.addNode('Manual Trigger');
await n8n.canvas.addNode('Notion', { action: 'Create a database page', closeNDV: false });
await expect(n8n.ndv.getContainer()).toBeVisible();
await n8n.ndv.addItemToFixedCollection('propertiesUi');
await n8n.ndv.changeNodeOperation('Update');
await expect(n8n.ndv.getParameterItemWithText('Currently no items exist')).toBeVisible();
});
test('Should keep RLC values after operation change', async ({ n8n }) => {
const TEST_DOC_ID = '1111';
await n8n.canvas.addNode('Manual Trigger');
await n8n.canvas.addNode('Google Sheets', { closeNDV: false, action: 'Append row in sheet' });
await expect(n8n.ndv.getContainer()).toBeVisible();
await n8n.ndv.setRLCValue('documentId', TEST_DOC_ID);
await n8n.ndv.changeNodeOperation('Append or Update Row');
const input = n8n.ndv.getResourceLocatorInput('documentId').locator('input');
await expect(input).toHaveValue(TEST_DOC_ID);
});
test('Should not clear resource/operation after credential change', async ({ n8n }) => {
await n8n.canvas.addNode('Manual Trigger');
await n8n.canvas.addNode('Discord', { closeNDV: false, action: 'Delete a message' });
await expect(n8n.ndv.getContainer()).toBeVisible();
await n8n.credentials.createAndSaveNewCredential('botToken', 'sk_test_123');
const resourceInput = n8n.ndv.getParameterInputField('resource');
const operationInput = n8n.ndv.getParameterInputField('operation');
await expect(resourceInput).toHaveValue('Message');
await expect(operationInput).toHaveValue('Delete');
});
});
test.describe('Node Creator Integration', () => {
test('Should open appropriate node creator after clicking on connection hint link', async ({
n8n,
}) => {
const hintMapper = {
Memory: 'AI Nodes',
'Output Parser': 'AI Nodes',
'Token Splitter': 'Document Loaders',
Tool: 'AI Nodes',
Embeddings: 'Vector Stores',
'Vector Store': 'Retrievers',
};
await n8n.canvas.importWorkflow(
'open_node_creator_for_connection.json',
'open_node_creator_for_connection',
);
for (const [node, group] of Object.entries(hintMapper)) {
await n8n.canvas.openNode(node);
await n8n.ndv.clickNodeCreatorInsertOneButton();
await expect(n8n.canvas.getNodeCreatorHeader(group)).toBeVisible();
await n8n.page.keyboard.press('Escape');
}
});
});
test.describe('Expression Editor Features', () => {
test('should allow selecting item for expressions', async ({ n8n }) => {
await n8n.canvas.importWorkflow('Test_workflow_3.json', 'My test workflow 2');
await n8n.workflowComposer.executeWorkflowAndWaitForNotification(
'Workflow executed successfully',
);
await n8n.canvas.openNode('Set');
await n8n.ndv.getAssignmentValue('assignments').getByText('Expression').click();
const expressionInput = n8n.ndv.getInlineExpressionEditorInput();
await expressionInput.click();
await n8n.ndv.clearExpressionEditor();
await n8n.ndv.typeInExpressionEditor('{{ $json.input[0].count');
await expect(n8n.ndv.getInlineExpressionEditorOutput()).toHaveText('0');
await n8n.ndv.expressionSelectNextItem();
await expect(n8n.ndv.getInlineExpressionEditorOutput()).toHaveText('1');
await expect(n8n.ndv.getInlineExpressionEditorItemInput()).toHaveValue('1');
await expect(n8n.ndv.getInlineExpressionEditorItemNextButton()).toBeDisabled();
await n8n.ndv.expressionSelectPrevItem();
await expect(n8n.ndv.getInlineExpressionEditorOutput()).toHaveText('0');
await expect(n8n.ndv.getInlineExpressionEditorItemInput()).toHaveValue('0');
});
});
test.describe('Schema & Data Views', () => {
test('should show data from the correct output in schema view', async ({ n8n }) => {
await n8n.canvas.importWorkflow('Test_workflow_multiple_outputs.json', 'Multiple outputs');
await n8n.workflowComposer.executeWorkflowAndWaitForNotification(
'Workflow executed successfully',
);
await n8n.canvas.openNode('Only Item 1');
await expect(n8n.ndv.getInputPanel()).toBeVisible();
await n8n.ndv.switchInputMode('Schema');
await expect(n8n.ndv.getInputSchemaItem('onlyOnItem1')).toBeVisible();
await n8n.ndv.close();
await n8n.canvas.openNode('Only Item 2');
await expect(n8n.ndv.getInputPanel()).toBeVisible();
await n8n.ndv.switchInputMode('Schema');
await expect(n8n.ndv.getInputSchemaItem('onlyOnItem2')).toBeVisible();
await n8n.ndv.close();
await n8n.canvas.openNode('Only Item 3');
await expect(n8n.ndv.getInputPanel()).toBeVisible();
await n8n.ndv.switchInputMode('Schema');
await expect(n8n.ndv.getInputSchemaItem('onlyOnItem3')).toBeVisible();
await n8n.ndv.close();
});
});
test.describe('Search Functionality - Advanced', () => {
test('should not show items count when searching in schema view', async ({ n8n }) => {
await n8n.canvas.importWorkflow('Test_ndv_search.json', 'NDV Search Test');
await n8n.canvas.openNode('Edit Fields');
await expect(n8n.ndv.getOutputPanel()).toBeVisible();
await n8n.ndv.execute();
await n8n.ndv.switchOutputMode('Schema');
await n8n.ndv.searchOutputData('US');
await expect(n8n.ndv.getOutputItemsCount()).toBeHidden();
});
test('should show additional tooltip when searching in schema view if no matches', async ({
n8n,
}) => {
await n8n.canvas.importWorkflow('Test_ndv_search.json', 'NDV Search Test');
await n8n.canvas.openNode('Edit Fields');
await expect(n8n.ndv.getOutputPanel()).toBeVisible();
await n8n.ndv.execute();
await n8n.ndv.switchOutputMode('Schema');
await n8n.ndv.searchOutputData('foo');
await expect(
n8n.ndv.getOutputPanel().getByText('To search field values, switch to table or JSON view.'),
).toBeVisible();
});
});
test.describe('Complex Edge Cases', () => {
test('ADO-2931 - should handle multiple branches of the same input with the first branch empty correctly', async ({
n8n,
}) => {
await n8n.canvas.importWorkflow(
'Test_ndv_two_branches_of_same_parent_false_populated.json',
'Multiple Branches Test',
);
await n8n.canvas.openNode('DebugHelper');
await expect(n8n.ndv.getInputPanel()).toBeVisible();
await expect(n8n.ndv.getOutputPanel()).toBeVisible();
await n8n.ndv.execute();
await expect(
n8n.ndv.getInputPanel().getByTestId('run-data-schema-item').filter({ hasText: 'a1' }),
).toBeVisible();
});
});
test.describe('Execution Indicators - Multi-Node', () => {
test('should properly show node execution indicator for multiple nodes', async ({ n8n }) => {
await n8n.canvas.addNode(CODE_NODE_NAME, { action: 'Code in JavaScript' });
await n8n.ndv.clickBackToCanvasButton();
await n8n.workflowComposer.executeWorkflowAndWaitForNotification(
'Workflow executed successfully',
);
await n8n.canvas.openNode(MANUAL_TRIGGER_NODE_DISPLAY_NAME);
await expect(n8n.ndv.getNodeRunSuccessIndicator()).toBeVisible();
await expect(n8n.ndv.getNodeRunTooltipIndicator()).toBeVisible();
await n8n.ndv.clickBackToCanvasButton();
await n8n.canvas.openNode(CODE_NODE_DISPLAY_NAME);
await expect(n8n.ndv.getNodeRunSuccessIndicator()).toBeVisible();
});
});
});

View File

@@ -0,0 +1,221 @@
{
"name": "Floating Nodes",
"nodes": [
{
"parameters": {},
"id": "d0eda550-2526-42a1-aa19-dee411c8acf9",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [700, 560]
},
{
"parameters": {
"options": {}
},
"id": "30412165-1229-4b21-9890-05bfbd9952ab",
"name": "Node 1",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [920, 560]
},
{
"parameters": {
"options": {}
},
"id": "201cc8fc-3124-47a3-bc08-b3917c1ddcd9",
"name": "Node 2",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [1100, 560]
},
{
"parameters": {
"options": {}
},
"id": "a29802bb-a284-495d-9917-6c6e42fef01e",
"name": "Node 3",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [1280, 560]
},
{
"parameters": {
"options": {}
},
"id": "a95a72b3-8b39-44e2-a05b-d8d677741c80",
"name": "Node 4",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [1440, 560]
},
{
"parameters": {},
"id": "4674f10d-6144-4a17-bbbb-350c3974438e",
"name": "Chain",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"typeVersion": 1,
"position": [1580, 560]
},
{
"parameters": {
"options": {}
},
"id": "58e12ea5-bd3e-4abf-abec-fcfb5c0a7955",
"name": "Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1,
"position": [1600, 740]
},
{
"parameters": {},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.1,
"position": [440, -140],
"id": "a00959d3-8d4b-40af-b4f2-35ca3d73fd84",
"name": "Merge"
},
{
"parameters": {
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [-20, -120],
"id": "a5cbc221-ccfd-4034-a648-6a192834af81",
"name": "Edit Fields0"
},
{
"parameters": {
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [0, 100],
"id": "d3b4c17a-bee8-418b-a721-5debafd1ce11",
"name": "Edit Fields1"
},
{
"parameters": {},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.1,
"position": [440, 100],
"id": "b23a2a43-ffac-41a5-a265-054e21a57d70",
"name": "Merge1"
}
],
"pinData": {},
"connections": {
"When clicking Execute workflow": {
"main": [
[
{
"node": "Node 1",
"type": "main",
"index": 0
}
]
]
},
"Node 1": {
"main": [
[
{
"node": "Node 2",
"type": "main",
"index": 0
}
]
]
},
"Node 3": {
"main": [
[
{
"node": "Node 4",
"type": "main",
"index": 0
}
]
]
},
"Node 2": {
"main": [
[
{
"node": "Node 3",
"type": "main",
"index": 0
}
]
]
},
"Chain": {
"main": [[]]
},
"Model": {
"ai_languageModel": [
[
{
"node": "Chain",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Node 4": {
"main": [
[
{
"node": "Chain",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields0": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
},
{
"node": "Merge1",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields1": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
},
{
"node": "Merge1",
"type": "main",
"index": 1
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "2730d156-a98a-4ac8-b481-5c16361fdba2",
"id": "6bzXMGxHuxeEaqsA",
"meta": {
"instanceId": "1838be0fa0389fbaf5e2e4aaedab4ddc79abc4175b433401abb22a281001b853"
},
"tags": []
}

View File

@@ -0,0 +1,74 @@
{
"name": "ff739753-9d6e-46a7-94d3-25bd03dd4973",
"nodes": [
{
"parameters": {},
"id": "c30d1114-d7f7-44dc-b55a-15312ef2d76d",
"name": "On clicking 'execute'",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [600, 580]
},
{
"parameters": {
"options": {}
},
"id": "5bf514bd-65ae-4a1c-8b69-a01bfeaad411",
"name": "Set",
"type": "n8n-nodes-base.set",
"typeVersion": 1,
"position": [820, 580]
},
{
"parameters": {
"options": {}
},
"id": "02bf49e9-44b2-4f4e-8cb2-8c02399208af",
"name": "Set1",
"type": "n8n-nodes-base.set",
"typeVersion": 1,
"position": [1040, 580]
}
],
"pinData": {
"On clicking 'execute'": [
{
"json": {
"start": true
}
}
]
},
"connections": {
"On clicking 'execute'": {
"main": [
[
{
"node": "Set",
"type": "main",
"index": 0
}
]
]
},
"Set": {
"main": [
[
{
"node": "Set1",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {},
"hash": "abd7b28aa2605c96ba24474d72cbe610",
"id": 3,
"meta": {
"instanceId": "08a83d394781701f5c18052cde68e8d92c88b26f5cc8809eb10ad545f14015cb"
},
"tags": []
}

View File

@@ -0,0 +1,126 @@
{
"name": "NDV search bugs (introduced by schema view?)",
"nodes": [
{
"parameters": {},
"id": "55635c7b-92ee-4d2d-a0c0-baff9ab071da",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"position": [800, 380],
"typeVersion": 1
},
{
"parameters": {
"operation": "getAllPeople"
},
"id": "4737af43-e49b-4c92-b76f-32605c047114",
"name": "Customer Datastore (n8n training)",
"type": "n8n-nodes-base.n8nTrainingCustomerDatastore",
"typeVersion": 1,
"position": [1020, 380]
},
{
"parameters": {
"assignments": {
"assignments": []
},
"includeOtherFields": true,
"options": {}
},
"id": "8cc9b374-1856-4f3f-9315-08e6e27840d8",
"name": "Edit Fields",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [1240, 380]
}
],
"pinData": {
"Customer Datastore (n8n training)": [
{
"json": {
"id": "23423532",
"name": "Jay Gatsby",
"email": "gatsby@west-egg.com",
"notes": "Keeps asking about a green light??",
"country": "US",
"created": "1925-04-10"
}
},
{
"json": {
"id": "23423533",
"name": "José Arcadio Buendía",
"email": "jab@macondo.co",
"notes": "Lots of people named after him. Very confusing",
"country": "CO",
"created": "1967-05-05"
}
},
{
"json": {
"id": "23423534",
"name": "Max Sendak",
"email": "info@in-and-out-of-weeks.org",
"notes": "Keeps rolling his terrible eyes",
"country": "US",
"created": "1963-04-09"
}
},
{
"json": {
"id": "23423535",
"name": "Zaphod Beeblebrox",
"email": "captain@heartofgold.com",
"notes": "Felt like I was talking to more than one person",
"country": null,
"created": "1979-10-12"
}
},
{
"json": {
"id": "23423536",
"name": "Edmund Pevensie",
"email": "edmund@narnia.gov",
"notes": "Passionate sailor",
"country": "UK",
"created": "1950-10-16"
}
}
]
},
"connections": {
"When clicking Execute workflow": {
"main": [
[
{
"node": "Customer Datastore (n8n training)",
"type": "main",
"index": 0
}
]
]
},
"Customer Datastore (n8n training)": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "20178044-fb64-4443-88dd-e941517520d0",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
},
"id": "aBVnTRON9Y2cSmse",
"tags": []
}

View File

@@ -0,0 +1,94 @@
{
"nodes": [
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "6f0cf983-824b-4339-a5de-6b374a23b4b0",
"leftValue": "={{ $json.a }}",
"rightValue": 3,
"operator": {
"type": "number",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [220, 0],
"id": "1755282a-ec4a-4d02-a833-0316ca413cc4",
"name": "If"
},
{
"parameters": {},
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [0, 0],
"id": "de1e7acf-12d8-4e56-ba42-709ffb397db2",
"name": "When clicking Execute workflow"
},
{
"parameters": {
"category": "randomData"
},
"type": "n8n-nodes-base.debugHelper",
"typeVersion": 1,
"position": [580, 0],
"id": "86440d33-f833-453c-bcaa-fff7e0083501",
"name": "DebugHelper",
"alwaysOutputData": true
}
],
"connections": {
"If": {
"main": [
[
{
"node": "DebugHelper",
"type": "main",
"index": 0
}
],
[
{
"node": "DebugHelper",
"type": "main",
"index": 0
}
]
]
},
"When clicking Execute workflow": {
"main": [
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {
"When clicking Execute workflow": [
{
"a": 1
},
{
"a": 2
}
]
}
}

View File

@@ -0,0 +1,258 @@
{
"meta": {
"instanceId": "8147b3a74cd161276e0f3bfc17369a724afab0d377593fada8be82d34c0c6a95"
},
"nodes": [
{
"parameters": {
"jsCode": "return [\n {\n id: 6666\n },\n {\n id: 3333\n },\n {\n id: 9999\n },\n {\n id: 1111\n },\n {\n id: 4444\n },\n {\n id: 8888\n },\n]"
},
"id": "5f023c7c-67ca-47a0-8a90-8227fcf29b9c",
"name": "Code",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [-520, 580]
},
{
"parameters": {
"values": {
"string": [
{
"name": "id",
"value": "={{ $json.id }}"
}
]
},
"options": {}
},
"id": "bd454282-9dd7-465f-9b9a-654a0c8532ec",
"name": "Set2",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [-40, 780]
},
{
"parameters": {},
"id": "ef63cdc5-50bc-4525-9873-7e7f7589a60e",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [-740, 580]
},
{
"parameters": {
"sortFieldsUi": {
"sortField": [
{
"fieldName": "id"
}
]
},
"options": {}
},
"id": "555a150c-d735-4331-b628-c1f1cfed2da1",
"name": "Sort",
"type": "n8n-nodes-base.sort",
"typeVersion": 1,
"position": [-280, 580]
},
{
"parameters": {
"values": {
"string": [
{
"name": "id",
"value": "={{ $json.id }}"
}
]
},
"options": {}
},
"id": "02372cb6-aac8-45c3-8600-f699901289ac",
"name": "Set",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [-60, 580]
},
{
"parameters": {
"options": {}
},
"id": "00d73944-218c-4896-af68-3f2855a922d1",
"name": "Set1",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [-280, 780]
},
{
"parameters": {
"conditions": {
"number": [
{
"value1": "={{ $json.id }}",
"operation": "smallerEqual",
"value2": 6666
}
]
}
},
"id": "211a7bef-32d1-4928-9cef-3a45f2e61379",
"name": "IF",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [160, 580]
},
{
"parameters": {
"options": {}
},
"id": "dcbd4745-832f-43d8-8a3c-dd80e8ca2777",
"name": "Set3",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [140, 780]
},
{
"parameters": {
"jsCode": "return [\n {\n id: 1000\n },\n {\n id: 300\n },\n {\n id: 2000\n },\n {\n id: 100\n },\n {\n id: 400\n },\n {\n id: 1300\n },\n]"
},
"id": "ec9c8f16-f3c8-4054-a6e9-4f1ebcdebb71",
"name": "Code1",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [-520, 780]
},
{
"parameters": {
"options": {}
},
"id": "42e89478-a53a-4d10-b20c-1dc5d5f953d5",
"name": "Set4",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [460, 460]
},
{
"parameters": {
"options": {}
},
"id": "5085eb1c-0345-4b9d-856a-2955279f2c5d",
"name": "Set5",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [460, 660]
}
],
"connections": {
"Code": {
"main": [
[
{
"node": "Sort",
"type": "main",
"index": 0
}
]
]
},
"Set2": {
"main": [
[
{
"node": "Set3",
"type": "main",
"index": 0
}
]
]
},
"When clicking Execute workflow": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
},
{
"node": "Code1",
"type": "main",
"index": 0
}
]
]
},
"Sort": {
"main": [
[
{
"node": "Set",
"type": "main",
"index": 0
},
{
"node": "Set2",
"type": "main",
"index": 0
}
]
]
},
"Set": {
"main": [
[
{
"node": "IF",
"type": "main",
"index": 0
}
]
]
},
"Set1": {
"main": [
[
{
"node": "Set2",
"type": "main",
"index": 0
}
]
]
},
"IF": {
"main": [
[
{
"node": "Set4",
"type": "main",
"index": 0
},
{
"node": "Set5",
"type": "main",
"index": 0
}
],
[
{
"node": "Set5",
"type": "main",
"index": 0
}
]
]
},
"Code1": {
"main": [
[
{
"node": "Set1",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,208 @@
{
"name": "Multiple outputs",
"nodes": [
{
"parameters": {},
"id": "64b27674-3da6-46ce-9008-e173182efa48",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"position": [16, -32],
"typeVersion": 1
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"leftValue": "={{ $json.code }}",
"rightValue": 1,
"operator": {
"type": "number",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Item1"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "a659050f-0867-471d-8914-d499b6ad7b31",
"leftValue": "={{ $json.code }}",
"rightValue": 2,
"operator": {
"type": "number",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Item2"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "109fc001-53af-48f1-b79c-5e9afc8b94bd",
"leftValue": "={{ $json.code }}",
"rightValue": 3,
"operator": {
"type": "number",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Item3"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.switch",
"position": [192, -32],
"id": "3863cc7a-8f45-46fc-a60c-36aad5b12877",
"name": "Switch",
"typeVersion": 3
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "f71bac89-8852-41b2-98dd-cb689f011dcb",
"name": "",
"value": "",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"position": [480, -192],
"id": "85940094-4656-4cdf-a871-1b3b46421de3",
"name": "Only Item 1",
"typeVersion": 3.4
},
{
"parameters": {
"options": {}
},
"type": "n8n-nodes-base.set",
"position": [480, -32],
"id": "a7f4e2b5-8cc9-4881-aa06-38601988740e",
"name": "Only Item 2",
"typeVersion": 3.4
},
{
"parameters": {
"options": {}
},
"type": "n8n-nodes-base.set",
"position": [480, 128],
"id": "7e44ad56-415a-4991-a70e-fea86c430031",
"name": "Only Item 3",
"typeVersion": 3.4
}
],
"pinData": {
"When clicking Execute workflow": [
{
"json": {
"name": "First item",
"onlyOnItem1": true,
"code": 1
}
},
{
"json": {
"name": "Second item",
"onlyOnItem2": true,
"code": 2
}
},
{
"json": {
"name": "Third item",
"onlyOnItem3": true,
"code": 3
}
}
]
},
"connections": {
"When clicking Execute workflow": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
},
"Switch": {
"main": [
[
{
"node": "Only Item 1",
"type": "main",
"index": 0
}
],
[
{
"node": "Only Item 2",
"type": "main",
"index": 0
}
],
[
{
"node": "Only Item 3",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "1e2a7b45-7730-42d6-989e-f3fa80de303e",
"meta": {
"instanceId": "27cc9b56542ad45b38725555722c50a1c3fee1670bbb67980558314ee08517c4"
},
"id": "V2ld4YU11fsHgr1z",
"tags": []
}

View File

@@ -0,0 +1,29 @@
{
"meta": {
"instanceId": "3204fc455f5cbeb4e71fdbd3b1dfaf0b088088dea3e639de49e61462b80ffc1d"
},
"nodes": [
{
"parameters": {
"application": {
"__rl": true,
"mode": "url",
"value": "",
"__regex": "https://airtable.com/([a-zA-Z0-9]{2,})"
},
"table": {
"__rl": true,
"mode": "url",
"value": "",
"__regex": "https://airtable.com/[a-zA-Z0-9]{2,}/([a-zA-Z0-9]{2,})"
}
},
"id": "e0c0cf7e-aa98-4b72-9645-6e64e2902bd1",
"name": "Airtable",
"type": "n8n-nodes-base.airtable",
"typeVersion": 1,
"position": [380, 180]
}
],
"connections": {}
}

View File

@@ -0,0 +1,150 @@
{
"name": "My workflow 52",
"nodes": [
{
"parameters": {
"jsCode": "\nreturn [\n {\n \"field\": \"the same\"\n }\n];"
},
"id": "38c14c4a-7af1-4b04-be76-f8e474c95569",
"name": "Break pairedItem chain",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [240, 1020]
},
{
"parameters": {
"options": {}
},
"id": "78c4964a-c4e8-47e5-81f3-89ba778feb8b",
"name": "Edit Fields",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [40, 1020]
},
{
"parameters": {},
"id": "4f4c6527-d565-448a-96bd-8f5414caf8cc",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [-180, 1020]
},
{
"parameters": {
"fields": {
"values": [
{
"stringValue": "={{ $('Edit Fields').item.json.name }}"
}
]
},
"options": {}
},
"id": "44f4e5da-bfe9-4dc3-8d1f-f38e9f364754",
"name": "Error",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [460, 1020]
}
],
"pinData": {
"Edit Fields": [
{
"json": {
"id": "23423532",
"name": "Jay Gatsby",
"email": "gatsby@west-egg.com",
"notes": "Keeps asking about a green light??",
"country": "US",
"created": "1925-04-10"
}
},
{
"json": {
"id": "23423533",
"name": "José Arcadio Buendía",
"email": "jab@macondo.co",
"notes": "Lots of people named after him. Very confusing",
"country": "CO",
"created": "1967-05-05"
}
},
{
"json": {
"id": "23423534",
"name": "Max Sendak",
"email": "info@in-and-out-of-weeks.org",
"notes": "Keeps rolling his terrible eyes",
"country": "US",
"created": "1963-04-09"
}
},
{
"json": {
"id": "23423535",
"name": "Zaphod Beeblebrox",
"email": "captain@heartofgold.com",
"notes": "Felt like I was talking to more than one person",
"country": null,
"created": "1979-10-12"
}
},
{
"json": {
"id": "23423536",
"name": "Edmund Pevensie",
"email": "edmund@narnia.gov",
"notes": "Passionate sailor",
"country": "UK",
"created": "1950-10-16"
}
}
]
},
"connections": {
"Break pairedItem chain": {
"main": [
[
{
"node": "Error",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields": {
"main": [
[
{
"node": "Break pairedItem chain",
"type": "main",
"index": 0
}
]
]
},
"When clicking Execute workflow": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "ca53267f-4eb4-481d-9e09-ecb97f6b09e2",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "27cc9b56542ad45b38725555722c50a1c3fee1670bbb67980558314ee08517c4"
},
"id": "6fr8GiRyMlZCiDQW",
"tags": []
}

View File

@@ -0,0 +1,41 @@
{
"name": "Node versions",
"nodes": [
{
"id": "2acca986-10a6-451e-b20a-86e95b50e627",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [460, 460]
},
{
"id": "1ea0a87c-3395-4bd9-84fb-cf8b0f769cc4",
"name": "Function",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [960, 460]
},
{
"id": "30bb9fab-bc89-4309-b42b-0fc586519c76",
"name": "Edit Fields (old)",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [800, 460]
},
{
"id": "a266b96c-3539-4034-b24c-c86c6d0ca31e",
"name": "Edit Fields (no typeVersion)",
"type": "n8n-nodes-base.set",
"position": [1120, 460]
},
{
"id": "273f60c9-08e7-457e-b01d-31e16c565171",
"name": "Edit Fields (latest)",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [640, 460]
}
],
"connections": {},
"pinData": {}
}

View File

@@ -0,0 +1,83 @@
{
"name": "My workflow 8",
"nodes": [
{
"parameters": {
"operation": "getAllPeople",
"limit": 10
},
"id": "39cd80ce-5a8f-4339-b3d5-c4af969dd330",
"name": "Customer Datastore (n8n training)",
"type": "n8n-nodes-base.n8nTrainingCustomerDatastore",
"typeVersion": 1,
"position": [940, 680]
},
{
"parameters": {
"values": {
"number": [
{
"name": "objectValue.prop1",
"value": 123
}
],
"string": [
{
"name": "objectValue.prop2",
"value": "someText"
}
]
},
"options": {
"dotNotation": true
}
},
"id": "6e4490f6-ba95-4400-beec-2caefdd4895a",
"name": "Set",
"type": "n8n-nodes-base.set",
"typeVersion": 1,
"position": [1300, 680]
},
{
"parameters": {},
"id": "58512a93-dabf-4584-817f-27c608c1bdd5",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [720, 680]
}
],
"pinData": {},
"connections": {
"Customer Datastore (n8n training)": {
"main": [
[
{
"node": "Set",
"type": "main",
"index": 0
}
]
]
},
"When clicking Execute workflow": {
"main": [
[
{
"node": "Customer Datastore (n8n training)",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {},
"versionId": "4a4f292a-92be-427c-848a-9582527f5ed3",
"id": "8",
"meta": {
"instanceId": "032eceae7493054b723340499be69ecbf4cbe28a7ec6df676b759000750b968d"
},
"tags": []
}

View File

@@ -0,0 +1,565 @@
{
"name": "My workflow",
"nodes": [
{
"parameters": {
"operation": "getAllPeople",
"limit": 10
},
"id": "441afcbf-a678-4463-bc89-7e0b6693af5c",
"name": "Customer Datastore (n8n training)",
"type": "n8n-nodes-base.n8nTrainingCustomerDatastore",
"typeVersion": 1,
"position": [720, 440]
},
{
"parameters": {
"values": {
"number": [
{
"name": "objectValue.prop1",
"value": 123
}
],
"string": [
{
"name": "objectValue.prop2",
"value": "someText"
}
]
},
"options": {
"dotNotation": true
}
},
"id": "44094a05-b3b7-49bf-bfbf-a711e6ba45d8",
"name": "Set",
"type": "n8n-nodes-base.set",
"typeVersion": 1,
"position": [1080, 440]
},
{
"parameters": {},
"id": "3dc7cf26-ff25-4437-b9fd-0e8b127ebec9",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [500, 440]
}
],
"pinData": {
"Set": [
{
"json": {
"key0": 0,
"key1": 1,
"key2": 2,
"key3": 3,
"key4": 4,
"key5": 5,
"key6": 6,
"key7": 7,
"key8": 8,
"key9": 9,
"key10": 10,
"key11": 11,
"key12": 12,
"key13": 13,
"key14": 14,
"key15": 15,
"key16": 16,
"key17": 17,
"key18": 18,
"key19": 19
}
},
{
"json": {
"key0": 0,
"key1": 1,
"key2": 2,
"key3": 3,
"key4": 4,
"key5": 5,
"key6": 6,
"key7": 7,
"key8": 8,
"key9": 9,
"key10": 10,
"key11": 11,
"key12": 12,
"key13": 13,
"key14": 14,
"key15": 15,
"key16": 16,
"key17": 17,
"key18": 18,
"key19": 19
}
},
{
"json": {
"key0": 0,
"key1": 1,
"key2": 2,
"key3": 3,
"key4": 4,
"key5": 5,
"key6": 6,
"key7": 7,
"key8": 8,
"key9": 9,
"key10": 10,
"key11": 11,
"key12": 12,
"key13": 13,
"key14": 14,
"key15": 15,
"key16": 16,
"key17": 17,
"key18": 18,
"key19": 19
}
},
{
"json": {
"key0": 0,
"key1": 1,
"key2": 2,
"key3": 3,
"key4": 4,
"key5": 5,
"key6": 6,
"key7": 7,
"key8": 8,
"key9": 9,
"key10": 10,
"key11": 11,
"key12": 12,
"key13": 13,
"key14": 14,
"key15": 15,
"key16": 16,
"key17": 17,
"key18": 18,
"key19": 19
}
},
{
"json": {
"key0": 0,
"key1": 1,
"key2": 2,
"key3": 3,
"key4": 4,
"key5": 5,
"key6": 6,
"key7": 7,
"key8": 8,
"key9": 9,
"key10": 10,
"key11": 11,
"key12": 12,
"key13": 13,
"key14": 14,
"key15": 15,
"key16": 16,
"key17": 17,
"key18": 18,
"key19": 19
}
},
{
"json": {
"key0": 0,
"key1": 1,
"key2": 2,
"key3": 3,
"key4": 4,
"key5": 5,
"key6": 6,
"key7": 7,
"key8": 8,
"key9": 9,
"key10": 10,
"key11": 11,
"key12": 12,
"key13": 13,
"key14": 14,
"key15": 15,
"key16": 16,
"key17": 17,
"key18": 18,
"key19": 19
}
},
{
"json": {
"key0": 0,
"key1": 1,
"key2": 2,
"key3": 3,
"key4": 4,
"key5": 5,
"key6": 6,
"key7": 7,
"key8": 8,
"key9": 9,
"key10": 10,
"key11": 11,
"key12": 12,
"key13": 13,
"key14": 14,
"key15": 15,
"key16": 16,
"key17": 17,
"key18": 18,
"key19": 19
}
},
{
"json": {
"key0": 0,
"key1": 1,
"key2": 2,
"key3": 3,
"key4": 4,
"key5": 5,
"key6": 6,
"key7": 7,
"key8": 8,
"key9": 9,
"key10": 10,
"key11": 11,
"key12": 12,
"key13": 13,
"key14": 14,
"key15": 15,
"key16": 16,
"key17": 17,
"key18": 18,
"key19": 19
}
},
{
"json": {
"key0": 0,
"key1": 1,
"key2": 2,
"key3": 3,
"key4": 4,
"key5": 5,
"key6": 6,
"key7": 7,
"key8": 8,
"key9": 9,
"key10": 10,
"key11": 11,
"key12": 12,
"key13": 13,
"key14": 14,
"key15": 15,
"key16": 16,
"key17": 17,
"key18": 18,
"key19": 19
}
},
{
"json": {
"key0": 0,
"key1": 1,
"key2": 2,
"key3": 3,
"key4": 4,
"key5": 5,
"key6": 6,
"key7": 7,
"key8": 8,
"key9": 9,
"key10": 10,
"key11": 11,
"key12": 12,
"key13": 13,
"key14": 14,
"key15": 15,
"key16": 16,
"key17": 17,
"key18": 18,
"key19": 19
}
},
{
"json": {
"key0": 0,
"key1": 1,
"key2": 2,
"key3": 3,
"key4": 4,
"key5": 5,
"key6": 6,
"key7": 7,
"key8": 8,
"key9": 9,
"key10": 10,
"key11": 11,
"key12": 12,
"key13": 13,
"key14": 14,
"key15": 15,
"key16": 16,
"key17": 17,
"key18": 18,
"key19": 19
}
},
{
"json": {
"key0": 0,
"key1": 1,
"key2": 2,
"key3": 3,
"key4": 4,
"key5": 5,
"key6": 6,
"key7": 7,
"key8": 8,
"key9": 9,
"key10": 10,
"key11": 11,
"key12": 12,
"key13": 13,
"key14": 14,
"key15": 15,
"key16": 16,
"key17": 17,
"key18": 18,
"key19": 19
}
},
{
"json": {
"key0": 0,
"key1": 1,
"key2": 2,
"key3": 3,
"key4": 4,
"key5": 5,
"key6": 6,
"key7": 7,
"key8": 8,
"key9": 9,
"key10": 10,
"key11": 11,
"key12": 12,
"key13": 13,
"key14": 14,
"key15": 15,
"key16": 16,
"key17": 17,
"key18": 18,
"key19": 19
}
},
{
"json": {
"key0": 0,
"key1": 1,
"key2": 2,
"key3": 3,
"key4": 4,
"key5": 5,
"key6": 6,
"key7": 7,
"key8": 8,
"key9": 9,
"key10": 10,
"key11": 11,
"key12": 12,
"key13": 13,
"key14": 14,
"key15": 15,
"key16": 16,
"key17": 17,
"key18": 18,
"key19": 19
}
},
{
"json": {
"key0": 0,
"key1": 1,
"key2": 2,
"key3": 3,
"key4": 4,
"key5": 5,
"key6": 6,
"key7": 7,
"key8": 8,
"key9": 9,
"key10": 10,
"key11": 11,
"key12": 12,
"key13": 13,
"key14": 14,
"key15": 15,
"key16": 16,
"key17": 17,
"key18": 18,
"key19": 19
}
},
{
"json": {
"key0": 0,
"key1": 1,
"key2": 2,
"key3": 3,
"key4": 4,
"key5": 5,
"key6": 6,
"key7": 7,
"key8": 8,
"key9": 9,
"key10": 10,
"key11": 11,
"key12": 12,
"key13": 13,
"key14": 14,
"key15": 15,
"key16": 16,
"key17": 17,
"key18": 18,
"key19": 19
}
},
{
"json": {
"key0": 0,
"key1": 1,
"key2": 2,
"key3": 3,
"key4": 4,
"key5": 5,
"key6": 6,
"key7": 7,
"key8": 8,
"key9": 9,
"key10": 10,
"key11": 11,
"key12": 12,
"key13": 13,
"key14": 14,
"key15": 15,
"key16": 16,
"key17": 17,
"key18": 18,
"key19": 19
}
},
{
"json": {
"key0": 0,
"key1": 1,
"key2": 2,
"key3": 3,
"key4": 4,
"key5": 5,
"key6": 6,
"key7": 7,
"key8": 8,
"key9": 9,
"key10": 10,
"key11": 11,
"key12": 12,
"key13": 13,
"key14": 14,
"key15": 15,
"key16": 16,
"key17": 17,
"key18": 18,
"key19": 19
}
},
{
"json": {
"key0": 0,
"key1": 1,
"key2": 2,
"key3": 3,
"key4": 4,
"key5": 5,
"key6": 6,
"key7": 7,
"key8": 8,
"key9": 9,
"key10": 10,
"key11": 11,
"key12": 12,
"key13": 13,
"key14": 14,
"key15": 15,
"key16": 16,
"key17": 17,
"key18": 18,
"key19": 19
}
},
{
"json": {
"key0": 0,
"key1": 1,
"key2": 2,
"key3": 3,
"key4": 4,
"key5": 5,
"key6": 6,
"key7": 7,
"key8": 8,
"key9": 9,
"key10": 10,
"key11": 11,
"key12": 12,
"key13": 13,
"key14": 14,
"key15": 15,
"key16": 16,
"key17": 17,
"key18": 18,
"key19": 19
}
}
]
},
"connections": {
"Customer Datastore (n8n training)": {
"main": [
[
{
"node": "Set",
"type": "main",
"index": 0
}
]
]
},
"When clicking Execute workflow": {
"main": [
[
{
"node": "Customer Datastore (n8n training)",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {},
"versionId": "",
"meta": {
"instanceId": "363581be2c2581d1b11e189456a090887e137f8393a4b5cb85641b1ee4fae479"
},
"tags": []
}

View File

@@ -0,0 +1,47 @@
{
"meta": {
"instanceId": "2d1cf27f75b18bb9e146336f791c37884f4fc7ddb97c2def27c0444d106778bf"
},
"nodes": [
{
"parameters": {},
"id": "8108d313-8b03-4aa4-963d-cd1c0fe8f85c",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [420, 220]
},
{
"parameters": {
"fields": {
"values": [
{
"name": "body",
"stringValue": "<?xml version=\"1.0\" encoding=\"UTF-8\"?> <library> <book> <title>Introduction to XML</title> <author>John Doe</author> <publication_year>2020</publication_year> <isbn>1234567890</isbn> </book> <book> <title>Data Science Basics</title> <author>Jane Smith</author> <publication_year>2019</publication_year> <isbn>0987654321</isbn> </book> <book> <title>Programming in Python</title> <author>Bob Johnson</author> <publication_year>2021</publication_year> <isbn>5432109876</isbn> </book> </library>"
}
]
},
"options": {}
},
"id": "45888152-7c5f-4d88-9039-660c594da084",
"name": "Edit Fields",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [640, 220]
}
],
"connections": {
"When clicking Execute workflow": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {}
}

View File

@@ -0,0 +1,89 @@
{
"name": "open_node_creator_for_connection",
"nodes": [
{
"parameters": {},
"id": "25ff0c17-7064-4e14-aec6-45c71d63201b",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [740, 520]
},
{
"parameters": {},
"id": "49f376ca-845b-4737-aac0-073d0e4fa95c",
"name": "Token Splitter",
"type": "@n8n/n8n-nodes-langchain.textSplitterTokenSplitter",
"typeVersion": 1,
"position": [1180, 540]
},
{
"parameters": {},
"id": "d1db5111-4b01-4620-8ccb-a16ea576c363",
"name": "Memory",
"type": "@n8n/n8n-nodes-langchain.memoryXata",
"typeVersion": 1.2,
"position": [940, 540],
"credentials": {
"xataApi": {
"id": "q1ckaYlHTWCYDtF0",
"name": "Xata Api account"
}
}
},
{
"parameters": {},
"id": "b08b6d3a-bef8-42ac-9cef-ec9d4e5402b1",
"name": "Output Parser",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"typeVersion": 1.1,
"position": [1060, 540]
},
{
"parameters": {},
"id": "ee557938-9cf1-4b78-afef-c783c52fd307",
"name": "Tool",
"type": "@n8n/n8n-nodes-langchain.toolWikipedia",
"typeVersion": 1,
"position": [1300, 540]
},
{
"parameters": {
"options": {}
},
"id": "814f2e9c-cc7b-4f3c-89b4-d6eb82bc24df",
"name": "Embeddings",
"type": "@n8n/n8n-nodes-langchain.embeddingsHuggingFaceInference",
"typeVersion": 1,
"position": [1420, 540]
},
{
"parameters": {
"tableName": {
"__rl": true,
"mode": "list",
"value": ""
},
"options": {}
},
"id": "e8569b0b-a580-4249-9c5e-f1feed5c644e",
"name": "Vector Store",
"type": "@n8n/n8n-nodes-langchain.vectorStoreSupabase",
"typeVersion": 1,
"position": [1540, 540]
}
],
"pinData": {},
"connections": {},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "8e90604c-f7e9-489d-8e43-1cc699b7db04",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "27cc9b56542ad45b38725555722c50a1c3fee1670bbb67980558314ee08517c4"
},
"id": "L3tgfoW660UOSuX6",
"tags": []
}