From a4fc24371ddf80881e233127f7bf2c8fb6da2f0e Mon Sep 17 00:00:00 2001 From: Artem Sorokin <38620398+seemewalkin@users.noreply.github.com> Date: Tue, 16 Sep 2025 16:11:51 +0200 Subject: [PATCH] test: Migrate cypress tests batch 1 to playwright (#19569) --- .../e2e/30-editor-after-route-changes.cy.ts | 71 -- cypress/e2e/39-import-workflow.cy.ts | 74 -- cypress/e2e/40-manual-partial-execution.cy.ts | 91 -- .../playwright/composables/CanvasComposer.ts | 60 + .../composables/PartialExecutionComposer.ts | 112 ++ .../composables/WorkflowComposer.ts | 33 + .../testing/playwright/pages/CanvasPage.ts | 75 +- .../playwright/pages/NodeDetailsViewPage.ts | 20 + .../playwright/pages/NotificationsPage.ts | 16 + packages/testing/playwright/pages/n8nPage.ts | 3 + .../playwright/tests/ui/2-credentials.spec.ts | 2 +- .../ui/30-editor-after-route-changes.spec.ts | 42 + .../tests/ui/39-import-workflow.spec.ts | 81 ++ .../ui/40-manual-partial-execution.spec.ts | 45 + .../tests/ui/security-notifications.spec.ts | 2 +- .../playwright/workflows/Lots_of_nodes.json | 913 +++++++++++++++ .../workflows/Onboarding_workflow.json | 1003 +++++++++++++++++ .../Test_workflow_partial_execution_v2.json | 74 ++ .../workflows/manual-partial-execution.json | 95 ++ 19 files changed, 2573 insertions(+), 239 deletions(-) delete mode 100644 cypress/e2e/30-editor-after-route-changes.cy.ts delete mode 100644 cypress/e2e/39-import-workflow.cy.ts delete mode 100644 cypress/e2e/40-manual-partial-execution.cy.ts create mode 100644 packages/testing/playwright/composables/PartialExecutionComposer.ts create mode 100644 packages/testing/playwright/tests/ui/30-editor-after-route-changes.spec.ts create mode 100644 packages/testing/playwright/tests/ui/39-import-workflow.spec.ts create mode 100644 packages/testing/playwright/tests/ui/40-manual-partial-execution.spec.ts create mode 100644 packages/testing/playwright/workflows/Lots_of_nodes.json create mode 100644 packages/testing/playwright/workflows/Onboarding_workflow.json create mode 100644 packages/testing/playwright/workflows/Test_workflow_partial_execution_v2.json create mode 100644 packages/testing/playwright/workflows/manual-partial-execution.json diff --git a/cypress/e2e/30-editor-after-route-changes.cy.ts b/cypress/e2e/30-editor-after-route-changes.cy.ts deleted file mode 100644 index cdd550b000..0000000000 --- a/cypress/e2e/30-editor-after-route-changes.cy.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { getWorkflowHistoryCloseButton } from '../composables/workflow'; -import { WorkflowPage as WorkflowPageClass } from '../pages'; - -const workflowPage = new WorkflowPageClass(); - -const switchBetweenEditorAndHistory = () => { - workflowPage.getters.workflowHistoryButton().click(); - cy.wait(['@getHistory']); - cy.wait(['@getVersion']); - - cy.intercept('GET', '/rest/workflows/*').as('workflowGet'); - getWorkflowHistoryCloseButton().click(); - cy.wait(['@workflowGet']); - cy.wait(1000); - - workflowPage.getters.canvasNodes().first().should('be.visible'); - workflowPage.getters.canvasNodes().last().should('be.visible'); -}; - -const switchBetweenEditorAndWorkflowlist = () => { - cy.getByTestId('menu-item').first().click(); - cy.wait(['@getUsers', '@getWorkflows', '@getActiveWorkflows', '@getProjects']); - - cy.getByTestId('resources-list-item-workflow').first().click(); - - workflowPage.getters.canvasNodes().first().should('be.visible'); - workflowPage.getters.canvasNodes().last().should('be.visible'); -}; - -const zoomInAndCheckNodes = () => { - cy.getByTestId('zoom-in-button').click(); - cy.getByTestId('zoom-in-button').click(); - cy.getByTestId('zoom-in-button').click(); - cy.getByTestId('zoom-in-button').click(); - - workflowPage.getters.canvasNodes().first().should('not.be.visible'); - workflowPage.getters.canvasNodes().last().should('not.be.visible'); -}; - -describe('Editor zoom should work after route changes', () => { - beforeEach(() => { - cy.enableFeature('debugInEditor'); - cy.enableFeature('workflowHistory'); - cy.signinAsOwner(); - workflowPage.actions.visit(); - cy.createFixtureWorkflow('Lots_of_nodes.json'); - workflowPage.actions.saveWorkflowOnButtonClick(); - }); - - it('after switching between Editor and Workflow history and Workflow list', () => { - cy.intercept('GET', '/rest/workflow-history/workflow/*/version/*').as('getVersion'); - cy.intercept('GET', '/rest/workflow-history/workflow/*').as('getHistory'); - cy.intercept('GET', '/rest/users?*').as('getUsers'); - cy.intercept('GET', '/rest/workflows?*').as('getWorkflows'); - cy.intercept('GET', '/rest/active-workflows').as('getActiveWorkflows'); - cy.intercept('GET', '/rest/projects').as('getProjects'); - - switchBetweenEditorAndHistory(); - zoomInAndCheckNodes(); - switchBetweenEditorAndHistory(); - switchBetweenEditorAndHistory(); - zoomInAndCheckNodes(); - switchBetweenEditorAndWorkflowlist(); - zoomInAndCheckNodes(); - switchBetweenEditorAndWorkflowlist(); - switchBetweenEditorAndWorkflowlist(); - zoomInAndCheckNodes(); - switchBetweenEditorAndHistory(); - switchBetweenEditorAndWorkflowlist(); - }); -}); diff --git a/cypress/e2e/39-import-workflow.cy.ts b/cypress/e2e/39-import-workflow.cy.ts deleted file mode 100644 index 1d31c0c611..0000000000 --- a/cypress/e2e/39-import-workflow.cy.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { WorkflowPage } from '../pages'; -import { errorToast, successToast } from '../pages/notifications'; - -const workflowPage = new WorkflowPage(); - -before(() => { - cy.fixture('Onboarding_workflow.json').then((data) => { - cy.intercept('GET', '/rest/workflows/from-url*', { - body: { data }, - }).as('downloadWorkflowFromURL'); - }); -}); - -describe('Import workflow', () => { - describe('From URL', () => { - it('should import workflow', () => { - workflowPage.actions.visit(true); - workflowPage.getters.workflowMenu().click(); - workflowPage.getters.workflowMenuItemImportFromURLItem().click(); - - workflowPage.getters.inputURLImportWorkflowFromURL().should('be.visible'); - - workflowPage.getters - .inputURLImportWorkflowFromURL() - .type('https://fakepage.com/workflow.json'); - - workflowPage.getters.confirmActionImportWorkflowFromURL().click(); - - workflowPage.actions.zoomToFit(); - - workflowPage.getters.canvasNodes().should('have.length', 4); - - errorToast().should('not.exist'); - - successToast().should('not.exist'); - }); - - it('clicking outside modal should not show error toast', () => { - workflowPage.actions.visit(true); - workflowPage.getters.workflowMenu().click(); - workflowPage.getters.workflowMenuItemImportFromURLItem().click(); - - cy.get('body').click(0, 0); - - errorToast().should('not.exist'); - }); - - it('canceling modal should not show error toast', () => { - workflowPage.actions.visit(true); - - workflowPage.getters.workflowMenu().click(); - workflowPage.getters.workflowMenuItemImportFromURLItem().click(); - workflowPage.getters.cancelActionImportWorkflowFromURL().click(); - - errorToast().should('not.exist'); - }); - }); - - describe('From File', () => { - it('should import workflow', () => { - workflowPage.actions.visit(true); - - workflowPage.getters.workflowMenu().click(); - workflowPage.getters.workflowMenuItemImportFromFile().click(); - workflowPage.getters - .workflowImportInput() - .selectFile('fixtures/Test_workflow-actions_paste-data.json', { force: true }); - cy.waitForLoad(false); - workflowPage.actions.zoomToFit(); - workflowPage.getters.canvasNodes().should('have.length', 5); - workflowPage.getters.nodeConnections().should('have.length', 5); - }); - }); -}); diff --git a/cypress/e2e/40-manual-partial-execution.cy.ts b/cypress/e2e/40-manual-partial-execution.cy.ts deleted file mode 100644 index 88f1e386bb..0000000000 --- a/cypress/e2e/40-manual-partial-execution.cy.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { - clickAssignmentCollectionAdd, - clickGetBackToCanvas, - getNodeRunInfoStale, - getOutputTbodyCell, -} from '../composables/ndv'; -import { - clickExecuteWorkflowButton, - getNodeByName, - getZoomToFitButton, - navigateToNewWorkflowPage, - openNode, -} from '../composables/workflow'; -import { NDV, WorkflowPage } from '../pages'; - -const canvas = new WorkflowPage(); -const ndv = new NDV(); - -describe('Manual partial execution', () => { - it('should not execute parent nodes with no run data', () => { - canvas.actions.visit(); - - cy.fixture('manual-partial-execution.json').then((data) => { - cy.get('body').paste(JSON.stringify(data)); - }); - - canvas.actions.zoomToFit(); - - canvas.actions.openNode('Edit Fields'); - - cy.get('button').contains('Execute step').click(); // create run data - cy.get('button').contains('Execute step').click(); // use run data - - ndv.actions.close(); - - canvas.actions.openNode('Webhook1'); - - ndv.getters.nodeRunSuccessIndicator().should('not.exist'); - ndv.getters.nodeRunTooltipIndicator().should('not.exist'); - ndv.getters.outputRunSelector().should('not.exist'); - }); - - describe('partial execution v2', () => { - beforeEach(() => { - cy.window().then((win) => { - win.localStorage.setItem('PartialExecution.version', '2'); - }); - navigateToNewWorkflowPage(); - }); - - it('should execute from the first dirty node up to the current node', () => { - cy.createFixtureWorkflow('Test_workflow_partial_execution_v2.json'); - - getZoomToFitButton().click(); - - // First, execute the whole workflow - clickExecuteWorkflowButton(); - - getNodeByName('A').findChildByTestId('canvas-node-status-success').should('be.visible'); - getNodeByName('B').findChildByTestId('canvas-node-status-success').should('be.visible'); - getNodeByName('C').findChildByTestId('canvas-node-status-success').should('be.visible'); - openNode('A'); - getOutputTbodyCell(1, 0).invoke('text').as('before', { type: 'static' }); - clickGetBackToCanvas(); - - // Change parameter of the node in the middle - openNode('B'); - clickAssignmentCollectionAdd(); - getNodeRunInfoStale().should('be.visible'); - clickGetBackToCanvas(); - - getNodeByName('A').findChildByTestId('canvas-node-status-success').should('be.visible'); - getNodeByName('B').findChildByTestId('canvas-node-status-warning').should('be.visible'); - getNodeByName('C').findChildByTestId('canvas-node-status-success').should('be.visible'); - - // Partial execution - getNodeByName('C').findChildByTestId('execute-node-button').click(); - - getNodeByName('A').findChildByTestId('canvas-node-status-success').should('be.visible'); - getNodeByName('B').findChildByTestId('canvas-node-status-success').should('be.visible'); - getNodeByName('C').findChildByTestId('canvas-node-status-success').should('be.visible'); - openNode('A'); - getOutputTbodyCell(1, 0).invoke('text').as('after', { type: 'static' }); - - // Assert that 'A' ran only once by comparing its output - cy.get('@before').then((before) => - cy.get('@after').then((after) => expect(before).to.equal(after)), - ); - }); - }); -}); diff --git a/packages/testing/playwright/composables/CanvasComposer.ts b/packages/testing/playwright/composables/CanvasComposer.ts index 5816409a63..61f18f2bba 100644 --- a/packages/testing/playwright/composables/CanvasComposer.ts +++ b/packages/testing/playwright/composables/CanvasComposer.ts @@ -1,3 +1,5 @@ +import { expect } from '@playwright/test'; + import type { n8nPage } from '../pages/n8nPage'; export class CanvasComposer { @@ -37,4 +39,62 @@ export class CanvasComposer { await this.n8n.canvas.selectAll(); await this.copySelectedNodesWithToast(); } + + /** + * Switch between editor and workflow history and back + */ + async switchBetweenEditorAndHistory(): Promise { + await this.n8n.page.getByTestId('workflow-history-button').click(); + await this.n8n.page.getByTestId('workflow-history-close-button').click(); + await this.n8n.page.waitForLoadState(); + await expect(this.n8n.canvas.getCanvasNodes().first()).toBeVisible(); + await expect(this.n8n.canvas.getCanvasNodes().last()).toBeVisible(); + } + + /** + * Switch between editor and workflow list and back + */ + async switchBetweenEditorAndWorkflowList(): Promise { + await this.n8n.page.getByTestId('menu-item').first().click(); + await this.n8n.page.getByTestId('resources-list-item-workflow').first().click(); + await expect(this.n8n.canvas.getCanvasNodes().first()).toBeVisible(); + await expect(this.n8n.canvas.getCanvasNodes().last()).toBeVisible(); + } + + /** + * Zoom in and validate that zoom functionality works + */ + async zoomInAndCheckNodes(): Promise { + await this.n8n.canvas.getCanvasNodes().first().waitFor(); + + const initialNodeSize = await this.n8n.page.evaluate(() => { + const firstNode = document.querySelector('[data-test-id="canvas-node"]'); + if (!firstNode) { + throw new Error('Canvas node not found during initial measurement'); + } + return firstNode.getBoundingClientRect().width; + }); + + for (let i = 0; i < 4; i++) { + await this.n8n.canvas.clickZoomInButton(); + } + + const finalNodeSize = await this.n8n.page.evaluate(() => { + const firstNode = document.querySelector('[data-test-id="canvas-node"]'); + if (!firstNode) { + throw new Error('Canvas node not found during final measurement'); + } + return firstNode.getBoundingClientRect().width; + }); + + // Validate zoom increased node sizes by at least 50% + const zoomWorking = finalNodeSize > initialNodeSize * 1.5; + + if (!zoomWorking) { + throw new Error( + "Zoom functionality not working: nodes didn't scale properly. " + + `Initial: ${initialNodeSize.toFixed(1)}px, Final: ${finalNodeSize.toFixed(1)}px`, + ); + } + } } diff --git a/packages/testing/playwright/composables/PartialExecutionComposer.ts b/packages/testing/playwright/composables/PartialExecutionComposer.ts new file mode 100644 index 0000000000..033b2203af --- /dev/null +++ b/packages/testing/playwright/composables/PartialExecutionComposer.ts @@ -0,0 +1,112 @@ +import { expect } from '@playwright/test'; + +import type { n8nPage } from '../pages/n8nPage'; + +/** + * A class for partial execution testing workflows that involve + * complex multi-step scenarios across pages. + */ +export class PartialExecutionComposer { + constructor(private readonly n8n: n8nPage) {} + + /** + * Sets up partial execution version 2 in localStorage + * This enables the v2 partial execution feature + */ + async enablePartialExecutionV2(): Promise { + await this.n8n.page.evaluate(() => { + window.localStorage.setItem('PartialExecution.version', '2'); + }); + } + + /** + * Executes a full workflow and verifies all nodes show success status + * @param nodeNames - Array of node names to verify + */ + async executeFullWorkflowAndVerifySuccess(nodeNames: string[]): Promise { + await this.n8n.canvas.clickExecuteWorkflowButton(); + + // Verify all nodes show success status + for (const nodeName of nodeNames) { + await expect(this.n8n.canvas.getNodeSuccessStatusIndicator(nodeName)).toBeVisible(); + } + } + + /** + * Captures output data from a node for later comparison + * @param nodeName - The node to capture data from + * @returns The captured text content + */ + async captureNodeOutputData(nodeName: string): Promise { + await this.n8n.canvas.openNode(nodeName); + await this.n8n.ndv.outputPanel.getTable().waitFor(); + // Note: Using row 0 for tbody (equivalent to row 1 in Cypress which includes header) + const cell = this.n8n.ndv.outputPanel.getTbodyCell(0, 0); + await expect(cell).toHaveText(/.+/); + const beforeText = await cell.textContent(); + await this.n8n.ndv.close(); + + return beforeText!; + } + + /** + * Modifies a node parameter to trigger stale state + * @param nodeName - The node to modify + */ + async modifyNodeToTriggerStaleState(nodeName: string): Promise { + await this.n8n.canvas.openNode(nodeName); + await this.n8n.ndv.clickAssignmentCollectionDropArea(); + + // Verify stale node indicator appears after parameter change + await expect(this.n8n.ndv.getStaleNodeIndicator()).toBeVisible(); + await this.n8n.ndv.close(); + } + + /** + * Verifies node states after parameter change for partial execution v2 + * @param unchangedNodes - Nodes that should still show success + * @param modifiedNodes - Nodes that should show warning (need re-execution) + */ + async verifyNodeStatesAfterChange( + unchangedNodes: string[], + modifiedNodes: string[], + ): Promise { + // Verify unchanged nodes still show success + for (const nodeName of unchangedNodes) { + await expect(this.n8n.canvas.getNodeSuccessStatusIndicator(nodeName)).toBeVisible(); + } + + // Verify modified nodes show warning status + for (const nodeName of modifiedNodes) { + await expect(this.n8n.canvas.getNodeWarningStatusIndicator(nodeName)).toBeVisible(); + } + } + + /** + * Performs partial execution on a node and verifies all nodes return to success + * @param targetNodeName - The node to execute from + * @param allNodeNames - All nodes that should show success after partial execution + */ + async performPartialExecutionAndVerifySuccess( + targetNodeName: string, + allNodeNames: string[], + ): Promise { + // Perform partial execution by clicking execute button on target node + await this.n8n.canvas.executeNode(targetNodeName); + + // Verify all nodes show success status after partial execution + for (const nodeName of allNodeNames) { + await expect(this.n8n.canvas.getNodeSuccessStatusIndicator(nodeName)).toBeVisible(); + } + } + + /** + * Opens a node for data verification (test should handle the assertion) + * @param nodeName - The node to open for verification + * @returns Promise that resolves when node is open and ready for verification + */ + async openNodeForDataVerification(nodeName: string): Promise { + await this.n8n.canvas.openNode(nodeName); + await this.n8n.ndv.outputPanel.getTable().waitFor(); + } +} diff --git a/packages/testing/playwright/composables/WorkflowComposer.ts b/packages/testing/playwright/composables/WorkflowComposer.ts index 921748d5f1..41f8a94911 100644 --- a/packages/testing/playwright/composables/WorkflowComposer.ts +++ b/packages/testing/playwright/composables/WorkflowComposer.ts @@ -62,4 +62,37 @@ export class WorkflowComposer { await this.n8n.canvas.importWorkflow(fileName, workflowName); return { workflowName }; } + + /** + * Creates a new workflow by importing from a URL + * @param url - The URL to import the workflow from + * @returns Promise that resolves when the import is complete + */ + async importWorkflowFromURL(url: string): Promise { + await this.n8n.workflows.clickAddWorkflowButton(); + await this.n8n.canvas.clickWorkflowMenu(); + await this.n8n.canvas.clickImportFromURL(); + await this.n8n.canvas.fillImportURLInput(url); + await this.n8n.canvas.clickConfirmImportURL(); + } + + /** + * Opens the import from URL dialog and then dismisses it by clicking outside + */ + async openAndDismissImportFromURLDialog(): Promise { + await this.n8n.workflows.clickAddWorkflowButton(); + await this.n8n.canvas.clickWorkflowMenu(); + await this.n8n.canvas.clickImportFromURL(); + await this.n8n.canvas.clickOutsideModal(); + } + + /** + * Opens the import from URL dialog and then cancels it + */ + async openAndCancelImportFromURLDialog(): Promise { + await this.n8n.workflows.clickAddWorkflowButton(); + await this.n8n.canvas.clickWorkflowMenu(); + await this.n8n.canvas.clickImportFromURL(); + await this.n8n.canvas.clickCancelImportURL(); + } } diff --git a/packages/testing/playwright/pages/CanvasPage.ts b/packages/testing/playwright/pages/CanvasPage.ts index 48618fd248..0e18b7f07f 100644 --- a/packages/testing/playwright/pages/CanvasPage.ts +++ b/packages/testing/playwright/pages/CanvasPage.ts @@ -228,7 +228,7 @@ export class CanvasPage extends BasePage { const [fileChooser] = await Promise.all([ this.page.waitForEvent('filechooser'), - this.clickByText('Import from File...'), + this.clickByTestId('workflow-menu-item-import-from-file'), ]); await fileChooser.setFiles(resolveFromRoot('workflows', fixtureKey)); @@ -237,6 +237,40 @@ export class CanvasPage extends BasePage { await this.page.getByTestId('inline-edit-input').press('Enter'); } + // Import workflow locators + getImportURLInput(): Locator { + return this.page.getByTestId('workflow-url-import-input'); + } + + // Import workflow actions + async clickWorkflowMenu(): Promise { + await this.clickByTestId('workflow-menu'); + } + + async clickImportFromURL(): Promise { + await this.clickByTestId('workflow-menu-item-import-from-url'); + } + + async clickImportFromFile(): Promise { + await this.clickByTestId('workflow-menu-item-import-from-file'); + } + + async fillImportURLInput(url: string): Promise { + await this.getImportURLInput().fill(url); + } + + async clickConfirmImportURL(): Promise { + await this.clickByTestId('confirm-workflow-import-url-button'); + } + + async clickCancelImportURL(): Promise { + await this.clickByTestId('cancel-workflow-import-url-button'); + } + + async clickOutsideModal(): Promise { + await this.page.locator('body').click({ position: { x: 0, y: 0 } }); + } + getWorkflowTags() { return this.page.getByTestId('workflow-tags').locator('.el-tag'); } @@ -660,7 +694,46 @@ export class CanvasPage extends BasePage { await this.page.getByTestId('radio-button-executions').click(); } + async clickZoomInButton(): Promise { + await this.clickByTestId('zoom-in-button'); + } + + async clickZoomOutButton(): Promise { + await this.clickByTestId('zoom-out-button'); + } + + /** + * Get the current zoom level of the canvas + * @returns The current zoom/scale factor as a number + */ + async getCanvasZoomLevel(): Promise { + return await this.page.evaluate(() => { + const canvasViewport = document.querySelector('.vue-flow__viewport'); + if (canvasViewport) { + const transform = window.getComputedStyle(canvasViewport).transform; + if (transform && transform !== 'none') { + const matrix = transform.match(/matrix\(([^)]+)\)/); + if (matrix) { + const values = matrix[1].split(',').map((v) => v.trim()); + return parseFloat(values[0]); // First value is scaleX + } + } + } + + // Fallback: return default zoom level + return 1.0; + }); + } + waitingForTriggerEvent() { return this.getExecuteWorkflowButton().getByText('Waiting for trigger event'); } + + getNodeSuccessStatusIndicator(nodeName: string): Locator { + return this.nodeByName(nodeName).getByTestId('canvas-node-status-success'); + } + + getNodeWarningStatusIndicator(nodeName: string): Locator { + return this.nodeByName(nodeName).getByTestId('canvas-node-status-warning'); + } } diff --git a/packages/testing/playwright/pages/NodeDetailsViewPage.ts b/packages/testing/playwright/pages/NodeDetailsViewPage.ts index fb215f6855..8f5a96edd6 100644 --- a/packages/testing/playwright/pages/NodeDetailsViewPage.ts +++ b/packages/testing/playwright/pages/NodeDetailsViewPage.ts @@ -166,6 +166,14 @@ export class NodeDetailsViewPage extends BasePage { .getByTestId('assignment-collection-drop-area'); } + getAssignmentCollectionDropArea() { + return this.page.getByTestId('assignment-collection-drop-area'); + } + + async clickAssignmentCollectionDropArea() { + await this.getAssignmentCollectionDropArea().click(); + } + getAssignmentValue(paramName: string) { return this.page .getByTestId(`assignment-collection-${paramName}`) @@ -723,6 +731,18 @@ export class NodeDetailsViewPage extends BasePage { return this.page.getByTestId('node-run-info'); } + getStaleNodeIndicator() { + return this.page.getByTestId('node-run-info-stale'); + } + + getExecuteStepButton() { + return this.page.getByRole('button').filter({ hasText: 'Execute step' }); + } + + async clickExecuteStep() { + await this.getExecuteStepButton().click(); + } + async openSettings() { await this.page.getByTestId('tab-settings').click(); } diff --git a/packages/testing/playwright/pages/NotificationsPage.ts b/packages/testing/playwright/pages/NotificationsPage.ts index eb81b34782..121b0abe16 100644 --- a/packages/testing/playwright/pages/NotificationsPage.ts +++ b/packages/testing/playwright/pages/NotificationsPage.ts @@ -265,4 +265,20 @@ export class NotificationsPage { // Silent fail } } + + getErrorNotifications(): Locator { + return this.page.locator('.el-notification:has(.el-notification--error)'); + } + + getSuccessNotifications(): Locator { + return this.page.locator('.el-notification:has(.el-notification--success)'); + } + + getWarningNotifications(): Locator { + return this.page.locator('.el-notification:has(.el-notification--warning)'); + } + + getInfoNotifications(): Locator { + return this.page.locator('.el-notification:has(.el-notification--info)'); + } } diff --git a/packages/testing/playwright/pages/n8nPage.ts b/packages/testing/playwright/pages/n8nPage.ts index 275014aa8f..7a64b74dcf 100644 --- a/packages/testing/playwright/pages/n8nPage.ts +++ b/packages/testing/playwright/pages/n8nPage.ts @@ -25,6 +25,7 @@ import { WorkflowSharingModal } from './WorkflowSharingModal'; import { WorkflowsPage } from './WorkflowsPage'; import { CanvasComposer } from '../composables/CanvasComposer'; import { CredentialsComposer } from '../composables/CredentialsComposer'; +import { PartialExecutionComposer } from '../composables/PartialExecutionComposer'; import { ProjectComposer } from '../composables/ProjectComposer'; import { TestEntryComposer } from '../composables/TestEntryComposer'; import { WorkflowComposer } from '../composables/WorkflowComposer'; @@ -68,6 +69,7 @@ export class n8nPage { readonly projectComposer: ProjectComposer; readonly canvasComposer: CanvasComposer; readonly credentialsComposer: CredentialsComposer; + readonly partialExecutionComposer: PartialExecutionComposer; readonly start: TestEntryComposer; // Helpers @@ -109,6 +111,7 @@ export class n8nPage { this.projectComposer = new ProjectComposer(this); this.canvasComposer = new CanvasComposer(this); this.credentialsComposer = new CredentialsComposer(this); + this.partialExecutionComposer = new PartialExecutionComposer(this); this.start = new TestEntryComposer(this); // Helpers diff --git a/packages/testing/playwright/tests/ui/2-credentials.spec.ts b/packages/testing/playwright/tests/ui/2-credentials.spec.ts index 1d65440183..bf67d0cfb2 100644 --- a/packages/testing/playwright/tests/ui/2-credentials.spec.ts +++ b/packages/testing/playwright/tests/ui/2-credentials.spec.ts @@ -351,7 +351,7 @@ test.describe('Credentials', () => { const saveBtn = n8n.canvas.credentialModal.getSaveButton(); await saveBtn.click(); - const errorNotification = page.locator('.el-notification:has(.el-notification--error)'); + const errorNotification = n8n.notifications.getErrorNotifications(); await expect(errorNotification).toBeVisible(); await expect(n8n.canvas.credentialModal.getModal()).toBeVisible(); diff --git a/packages/testing/playwright/tests/ui/30-editor-after-route-changes.spec.ts b/packages/testing/playwright/tests/ui/30-editor-after-route-changes.spec.ts new file mode 100644 index 0000000000..d836adfbd7 --- /dev/null +++ b/packages/testing/playwright/tests/ui/30-editor-after-route-changes.spec.ts @@ -0,0 +1,42 @@ +import { test, expect } from '../../fixtures/base'; + +const NOTIFICATIONS = { + WORKFLOW_CREATED: 'Workflow successfully created', +}; + +test.describe('Editor zoom should work after route changes', () => { + test.beforeEach(async ({ n8n, api }) => { + await api.enableFeature('debugInEditor'); + await api.enableFeature('workflowHistory'); + + await n8n.workflowComposer.createWorkflowFromJsonFile( + 'Lots_of_nodes.json', + 'Lots of nodes test', + ); + await n8n.notifications.waitForNotificationAndClose(NOTIFICATIONS.WORKFLOW_CREATED); + }); + + test('should maintain zoom functionality after switching between Editor and Workflow history and Workflow list', async ({ + n8n, + }) => { + const initialNodeCount = await n8n.canvas.getCanvasNodes().count(); + expect(initialNodeCount).toBeGreaterThan(0); + + await n8n.canvasComposer.switchBetweenEditorAndHistory(); + await n8n.canvasComposer.zoomInAndCheckNodes(); + + await n8n.canvasComposer.switchBetweenEditorAndHistory(); + await n8n.canvasComposer.switchBetweenEditorAndHistory(); + await n8n.canvasComposer.zoomInAndCheckNodes(); + + await n8n.canvasComposer.switchBetweenEditorAndWorkflowList(); + await n8n.canvasComposer.zoomInAndCheckNodes(); + + await n8n.canvasComposer.switchBetweenEditorAndWorkflowList(); + await n8n.canvasComposer.switchBetweenEditorAndWorkflowList(); + await n8n.canvasComposer.zoomInAndCheckNodes(); + + await n8n.canvasComposer.switchBetweenEditorAndHistory(); + await n8n.canvasComposer.switchBetweenEditorAndWorkflowList(); + }); +}); diff --git a/packages/testing/playwright/tests/ui/39-import-workflow.spec.ts b/packages/testing/playwright/tests/ui/39-import-workflow.spec.ts new file mode 100644 index 0000000000..c8840e0716 --- /dev/null +++ b/packages/testing/playwright/tests/ui/39-import-workflow.spec.ts @@ -0,0 +1,81 @@ +import { test, expect } from '../../fixtures/base'; +import onboardingWorkflow from '../../workflows/Onboarding_workflow.json'; + +test.describe('Import workflow', () => { + test.describe('From URL', () => { + test.beforeEach(async ({ page }) => { + await page.route('**/rest/workflows/from-url*', async (route) => { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ data: onboardingWorkflow }), + }); + }); + }); + + test('should import workflow', async ({ n8n }) => { + await n8n.navigate.toWorkflow('new'); + await n8n.page.waitForLoadState('load'); + + await n8n.canvas.clickWorkflowMenu(); + await n8n.canvas.clickImportFromURL(); + + await expect(n8n.canvas.getImportURLInput()).toBeVisible(); + + await n8n.canvas.fillImportURLInput('https://fakepage.com/workflow.json'); + await n8n.canvas.clickConfirmImportURL(); + + await n8n.canvas.clickZoomToFitButton(); + + await expect(n8n.canvas.getCanvasNodes()).toHaveCount(4); + + await expect(n8n.notifications.getErrorNotifications()).toHaveCount(0); + await expect(n8n.notifications.getSuccessNotifications()).toHaveCount(0); + }); + + test('clicking outside modal should not show error toast', async ({ n8n }) => { + await n8n.navigate.toWorkflow('new'); + await n8n.page.waitForLoadState('load'); + + await n8n.canvas.clickWorkflowMenu(); + await n8n.canvas.clickImportFromURL(); + + await n8n.canvas.clickOutsideModal(); + + await expect(n8n.notifications.getErrorNotifications()).toHaveCount(0); + }); + + test('canceling modal should not show error toast', async ({ n8n }) => { + await n8n.navigate.toWorkflow('new'); + await n8n.page.waitForLoadState('load'); + + await n8n.canvas.clickWorkflowMenu(); + await n8n.canvas.clickImportFromURL(); + + await n8n.canvas.clickCancelImportURL(); + + await expect(n8n.notifications.getErrorNotifications()).toHaveCount(0); + }); + }); + + test.describe('From File', () => { + test('should import workflow', async ({ n8n }) => { + await n8n.navigate.toWorkflow('new'); + await n8n.page.waitForLoadState('load'); + + await n8n.canvas.importWorkflow( + 'Test_workflow-actions_paste-data.json', + 'Import Test Workflow', + ); + + await n8n.page.waitForLoadState('load'); + + await n8n.canvas.clickZoomToFitButton(); + + await expect(n8n.canvas.getCanvasNodes()).toHaveCount(5); + + const connections = n8n.page.getByTestId('edge'); + await expect(connections).toHaveCount(5); + }); + }); +}); diff --git a/packages/testing/playwright/tests/ui/40-manual-partial-execution.spec.ts b/packages/testing/playwright/tests/ui/40-manual-partial-execution.spec.ts new file mode 100644 index 0000000000..6be0281a2a --- /dev/null +++ b/packages/testing/playwright/tests/ui/40-manual-partial-execution.spec.ts @@ -0,0 +1,45 @@ +import { test, expect } from '../../fixtures/base'; + +test.describe('Manual partial execution', () => { + test('should not execute parent nodes with no run data', async ({ n8n }) => { + await n8n.start.fromImportedWorkflow('manual-partial-execution.json'); + await n8n.canvas.clickZoomToFitButton(); + + await n8n.canvas.openNode('Edit Fields'); + + await n8n.ndv.clickExecuteStep(); + + await n8n.ndv.close(); + + await n8n.canvas.openNode('Webhook1'); + + await expect(n8n.ndv.getNodeRunSuccessIndicator()).toBeHidden(); + await expect(n8n.ndv.getNodeRunTooltipIndicator()).toBeHidden(); + await expect(n8n.ndv.outputPanel.getRunSelector()).toBeHidden(); + }); + + test.describe('partial execution v2', () => { + test('should execute from the first dirty node up to the current node', async ({ n8n }) => { + const nodeNames = ['A', 'B', 'C']; + + await n8n.navigate.toWorkflow('new'); + await n8n.partialExecutionComposer.enablePartialExecutionV2(); + await n8n.start.fromImportedWorkflow('Test_workflow_partial_execution_v2.json'); + await n8n.canvas.clickZoomToFitButton(); + + await n8n.partialExecutionComposer.executeFullWorkflowAndVerifySuccess(nodeNames); + + const beforeText = await n8n.partialExecutionComposer.captureNodeOutputData('A'); + + await n8n.partialExecutionComposer.modifyNodeToTriggerStaleState('B'); + + await n8n.partialExecutionComposer.verifyNodeStatesAfterChange(['A', 'C'], ['B']); + + await n8n.partialExecutionComposer.performPartialExecutionAndVerifySuccess('C', nodeNames); + + await n8n.partialExecutionComposer.openNodeForDataVerification('A'); + + await expect(n8n.ndv.outputPanel.getTbodyCell(0, 0)).toHaveText(beforeText); + }); + }); +}); diff --git a/packages/testing/playwright/tests/ui/security-notifications.spec.ts b/packages/testing/playwright/tests/ui/security-notifications.spec.ts index a2fe00c7cd..78894c2c90 100644 --- a/packages/testing/playwright/tests/ui/security-notifications.spec.ts +++ b/packages/testing/playwright/tests/ui/security-notifications.spec.ts @@ -135,7 +135,7 @@ test.describe('Security Notifications', () => { await expect(notification).toContainText('More info'); // Verify warning styling - await expect(notification.locator('.el-notification--warning')).toBeVisible(); + await expect(n8n.notifications.getWarningNotifications()).toBeVisible(); // Close the notification await n8n.notifications.closeNotificationByText('Critical update available'); diff --git a/packages/testing/playwright/workflows/Lots_of_nodes.json b/packages/testing/playwright/workflows/Lots_of_nodes.json new file mode 100644 index 0000000000..a7bdab7909 --- /dev/null +++ b/packages/testing/playwright/workflows/Lots_of_nodes.json @@ -0,0 +1,913 @@ +{ + "name": "Lots of nodes", + "nodes": [ + { + "parameters": {}, + "id": "369fe424-dd3b-4399-9de3-50bd4ce1f75b", + "name": "When clicking ‘Execute workflow’", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [860, 740] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "dce967a7-8c5e-43cc-ba2b-e0fb0c9cf14c", + "name": "Code", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [1080, 740] + }, + { + "parameters": { + "options": {} + }, + "id": "df7a719e-b25a-43e3-b941-7091a7d9a1a8", + "name": "Edit Fields", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [1300, 740] + }, + { + "parameters": {}, + "id": "32968b79-6a8b-43ed-b884-eb906b597661", + "name": "IF", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [1520, 740] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "e9a72745-6dbb-4be1-b286-aaa679b95e36", + "name": "Code1", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [1820, 80] + }, + { + "parameters": { + "options": {} + }, + "id": "f831d21b-c3a9-4bd8-9fc3-6daef12bd43f", + "name": "Edit Fields1", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [2040, 80] + }, + { + "parameters": {}, + "id": "6e6b2a4f-9e61-4245-8502-ca01e851fcbe", + "name": "IF1", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [2260, 80] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "535b9786-ead9-44f9-bff2-ef2e019a4cf9", + "name": "Code3", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [2560, -260] + }, + { + "parameters": { + "options": {} + }, + "id": "6a181d75-f2f2-4ad1-be3c-81ebe077ccc8", + "name": "Edit Fields3", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [2780, -260] + }, + { + "parameters": {}, + "id": "4b45828e-4e2b-4046-b9ae-24b373a81863", + "name": "IF7", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [3000, -260] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "059534cb-820c-4fb7-933c-eeed2ae74f1c", + "name": "Code7", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [3260, -400] + }, + { + "parameters": { + "options": {} + }, + "id": "4f5c0d94-b69d-4ad3-aa8f-f1dd5824ec4a", + "name": "Edit Fields7", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [3480, -400] + }, + { + "parameters": {}, + "id": "cd74f840-7b0f-425d-8ecd-e247a7d8abf5", + "name": "IF8", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [3700, -400] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "3c97fd14-9c23-45e2-a1ac-934d743e9a01", + "name": "Code8", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [3260, -80] + }, + { + "parameters": { + "options": {} + }, + "id": "9e7bd7e9-5142-4751-b132-735d27007d82", + "name": "Edit Fields8", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [3480, -80] + }, + { + "parameters": {}, + "id": "8d3968b6-16d4-4e03-9026-eeaf70b17805", + "name": "IF9", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [3700, -80] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "141edef3-ea0f-4e90-9b6a-09f5d5551195", + "name": "Code4", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [2560, 440] + }, + { + "parameters": { + "options": {} + }, + "id": "b5b93cd7-9448-4290-91b7-c3c8429925fd", + "name": "Edit Fields4", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [2780, 440] + }, + { + "parameters": {}, + "id": "79d2c11c-0378-4ff5-b166-ae1bf773f53a", + "name": "IF14", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [3000, 440] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "8483e962-24e7-4495-9c8e-481481ebe897", + "name": "Code13", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [3260, 300] + }, + { + "parameters": { + "options": {} + }, + "id": "74dfb8f9-6d14-493e-97d5-729e1f44856b", + "name": "Edit Fields13", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [3480, 300] + }, + { + "parameters": {}, + "id": "0c2e8e54-958d-4932-91b5-b23979460c97", + "name": "IF15", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [3700, 300] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "bfed29c6-c453-4850-8acf-7aa11b1d0d8e", + "name": "Code14", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [3260, 620] + }, + { + "parameters": { + "options": {} + }, + "id": "d8415057-c597-40a9-95f6-bafbe3fafac0", + "name": "Edit Fields14", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [3480, 620] + }, + { + "parameters": {}, + "id": "51ed9040-bb6c-4f77-9740-74b54ac56a00", + "name": "IF16", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [3700, 620] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "5864e701-eb16-4412-ae8b-be1f2a1f16a5", + "name": "Code2", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [1820, 1480] + }, + { + "parameters": { + "options": {} + }, + "id": "4b7de291-f1c7-4ae8-a545-81aaa2ebd1fb", + "name": "Edit Fields2", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [2040, 1480] + }, + { + "parameters": {}, + "id": "328aa16f-82ed-465e-b548-9436f21eb519", + "name": "IF2", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [2260, 1480] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "90aaf0b0-57b6-4a08-b000-abb2956ba640", + "name": "Code5", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [2560, 1140] + }, + { + "parameters": { + "options": {} + }, + "id": "7d327c87-da3b-4f4b-9f9a-51c9c622990d", + "name": "Edit Fields5", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [2780, 1140] + }, + { + "parameters": {}, + "id": "fa2a3b1b-53de-454e-a16d-e2bf62cb05ec", + "name": "IF21", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [3000, 1140] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "8efaa5a3-982e-41b4-af6e-28e35c64093d", + "name": "Code19", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [3260, 1000] + }, + { + "parameters": { + "options": {} + }, + "id": "987e27fd-778a-4562-85a9-369b1ec232de", + "name": "Edit Fields19", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [3480, 1000] + }, + { + "parameters": {}, + "id": "b3f4e9b3-9995-4019-9b0f-dadd64e036b4", + "name": "IF22", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [3700, 1000] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "681c1b30-063d-4c1e-b550-942a9dd3eb9a", + "name": "Code20", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [3260, 1320] + }, + { + "parameters": { + "options": {} + }, + "id": "024770b6-7bf4-44f6-9675-d4f7dc73d6ac", + "name": "Edit Fields20", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [3480, 1320] + }, + { + "parameters": {}, + "id": "24699015-3ccf-4ffa-b52f-8ba4c4853963", + "name": "IF23", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [3700, 1320] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "f4b2d116-2fda-4a3a-9509-0e8c64e7796e", + "name": "Code6", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [2560, 1840] + }, + { + "parameters": { + "options": {} + }, + "id": "535e5b12-6743-4c01-9fc5-e27b10421423", + "name": "Edit Fields6", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [2780, 1840] + }, + { + "parameters": {}, + "id": "3dcbecdf-686b-445f-9c77-2902d0dc1f56", + "name": "IF28", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [3000, 1840] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "7223c6ef-664b-426a-8d08-eca1b34e6b23", + "name": "Code25", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [3260, 1700] + }, + { + "parameters": { + "options": {} + }, + "id": "496414a6-384a-4f94-97ec-d2e5ad646f82", + "name": "Edit Fields25", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [3480, 1700] + }, + { + "parameters": {}, + "id": "82f9562d-e4a8-49f3-924d-983effb4b6c6", + "name": "IF29", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [3700, 1700] + }, + { + "parameters": { + "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();" + }, + "id": "c91d4bc5-3c60-4c22-aa31-44e84e0816ec", + "name": "Code26", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [3260, 2020] + }, + { + "parameters": { + "options": {} + }, + "id": "49b61f23-bf3f-474d-8bba-a3b7de6f6441", + "name": "Edit Fields26", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [3480, 2020] + }, + { + "parameters": {}, + "id": "1cad6ae3-1064-4f30-a9ec-502891868332", + "name": "IF30", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [3700, 2020] + } + ], + "pinData": {}, + "connections": { + "When clicking ‘Execute workflow’": { + "main": [ + [ + { + "node": "Code", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code": { + "main": [ + [ + { + "node": "Edit Fields", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields": { + "main": [ + [ + { + "node": "IF", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF": { + "main": [ + [ + { + "node": "Code1", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Code2", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code1": { + "main": [ + [ + { + "node": "Edit Fields1", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields1": { + "main": [ + [ + { + "node": "IF1", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code3": { + "main": [ + [ + { + "node": "Edit Fields3", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields3": { + "main": [ + [ + { + "node": "IF7", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF1": { + "main": [ + [ + { + "node": "Code3", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Code4", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF7": { + "main": [ + [ + { + "node": "Code7", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Code8", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code7": { + "main": [ + [ + { + "node": "Edit Fields7", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields7": { + "main": [ + [ + { + "node": "IF8", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code8": { + "main": [ + [ + { + "node": "Edit Fields8", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields8": { + "main": [ + [ + { + "node": "IF9", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code4": { + "main": [ + [ + { + "node": "Edit Fields4", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields4": { + "main": [ + [ + { + "node": "IF14", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF14": { + "main": [ + [ + { + "node": "Code13", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Code14", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code13": { + "main": [ + [ + { + "node": "Edit Fields13", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields13": { + "main": [ + [ + { + "node": "IF15", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code14": { + "main": [ + [ + { + "node": "Edit Fields14", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields14": { + "main": [ + [ + { + "node": "IF16", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code2": { + "main": [ + [ + { + "node": "Edit Fields2", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields2": { + "main": [ + [ + { + "node": "IF2", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF2": { + "main": [ + [ + { + "node": "Code5", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Code6", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code5": { + "main": [ + [ + { + "node": "Edit Fields5", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields5": { + "main": [ + [ + { + "node": "IF21", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF21": { + "main": [ + [ + { + "node": "Code19", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Code20", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code19": { + "main": [ + [ + { + "node": "Edit Fields19", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields19": { + "main": [ + [ + { + "node": "IF22", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code20": { + "main": [ + [ + { + "node": "Edit Fields20", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields20": { + "main": [ + [ + { + "node": "IF23", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code6": { + "main": [ + [ + { + "node": "Edit Fields6", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields6": { + "main": [ + [ + { + "node": "IF28", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF28": { + "main": [ + [ + { + "node": "Code25", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Code26", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code25": { + "main": [ + [ + { + "node": "Edit Fields25", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields25": { + "main": [ + [ + { + "node": "IF29", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code26": { + "main": [ + [ + { + "node": "Edit Fields26", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields26": { + "main": [ + [ + { + "node": "IF30", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "d38289e0-49d3-4e1d-8e4b-46e4eb85a2c9", + "id": "iKlx4AGIjCNJSu9M", + "meta": { + "instanceId": "8a47b83b4479b11330fdf21ccc96d4a8117035a968612e452b4c87bfd09c16c7" + }, + "tags": [] +} diff --git a/packages/testing/playwright/workflows/Onboarding_workflow.json b/packages/testing/playwright/workflows/Onboarding_workflow.json new file mode 100644 index 0000000000..10bfa7b00c --- /dev/null +++ b/packages/testing/playwright/workflows/Onboarding_workflow.json @@ -0,0 +1,1003 @@ +{ + "name": "DEMO: Create a new record in Google Sheets when something happens in Hubspot", + "nodes": [ + { + "parameters": { + "eventsUi": { + "eventValues": [{}] + }, + "additionalFields": {} + }, + "id": "78395fdf-2e8b-4064-a102-c1c0335e0d94", + "name": "HubSpot Trigger", + "type": "n8n-nodes-base.hubspotTrigger", + "typeVersion": 1, + "position": [580, 320], + "webhookId": "25833e56-c646-4af0-8bbe-2eea8bda4c00", + "notesInFlow": true, + "notes": "On Contact Created" + }, + { + "parameters": { + "conditions": { + "string": [ + { + "value1": "={{ $json['identity-profiles'][0].identities[0].value }}", + "operation": "contains", + "value2": "@gmail" + } + ] + } + }, + "id": "3888d918-c140-47a1-8024-d50fddb3f8f0", + "name": "IF", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [820, 320], + "notesInFlow": true, + "notes": "Is Gmail Email?" + }, + { + "parameters": {}, + "id": "416a8876-f496-499c-a089-aad243daabc6", + "name": "Is Gmail, Don't Add to Sheet", + "type": "n8n-nodes-base.noOp", + "typeVersion": 1, + "position": [1140, 240] + }, + { + "parameters": { + "content": "## Demo: Creating Google Sheets records when something happens in HubSpot\nThis workflow runs each time a new Contact is added in HubSpot. It filters out Contacts with Gmail email addresses then pushes the remaining new Contacts to [this Google Sheet](https://docs.google.com/spreadsheets/d/1GeWRcu5cvNVA-0hpHZHtatjnFtyunbgWUgRur5uT08A/edit?usp=sharing).", + "height": 160.1450000000002, + "width": 480.31999999999596 + }, + "id": "cf69cda9-ba96-468f-990c-6c3ad5242053", + "name": "Sticky Note1", + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [520, 100] + }, + { + "parameters": { + "operation": "append", + "documentId": { + "__rl": true, + "value": "1GeWRcu5cvNVA-0hpHZHtatjnFtyunbgWUgRur5uT08A", + "mode": "list", + "cachedResultName": "New HubSpot Contacts", + "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1GeWRcu5cvNVA-0hpHZHtatjnFtyunbgWUgRur5uT08A/edit?usp=drivesdk" + }, + "sheetName": { + "__rl": true, + "value": "gid=0", + "mode": "list", + "cachedResultName": "New Contacts", + "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1GeWRcu5cvNVA-0hpHZHtatjnFtyunbgWUgRur5uT08A/edit#gid=0" + }, + "columns": { + "mappingMode": "defineBelow", + "value": { + "Name": "={{ $json.properties.num_unique_conversion_events.versions[0]['source-type'] }}", + "Email": "={{ $json.properties.num_unique_conversion_events.versions[0]['source-label'] }}" + }, + "matchingColumns": [], + "schema": [ + { + "id": "Name", + "displayName": "Name", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "Email", + "displayName": "Email", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "Sync timestamp", + "displayName": "Sync timestamp", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + } + ] + }, + "options": {} + }, + "id": "1e4084bd-b7fb-41f1-a340-1414ef134468", + "name": "Google Sheets", + "type": "n8n-nodes-base.googleSheets", + "typeVersion": 4, + "position": [1140, 440], + "notesInFlow": true, + "credentials": { + "googleSheetsOAuth2Api": { + "id": "FrRoXgPJOrFwkeN4", + "name": "Replace me with your own Sheets credential" + } + }, + "notes": "Append new contact to sheet" + } + ], + "pinData": { + "HubSpot Trigger": [ + { + "json": { + "vid": 51, + "canonical-vid": 51, + "merged-vids": [], + "portal-id": 8924380, + "is-contact": true, + "properties": { + "hs_latest_source_data_2": { + "value": "sample-contact", + "versions": [ + { + "value": "sample-contact", + "source-type": "MIGRATION", + "source-id": "BackfillContactUpdatesKafka", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1639158301358, + "selected": false + } + ] + }, + "hs_latest_source_data_1": { + "value": "API", + "versions": [ + { + "value": "API", + "source-type": "MIGRATION", + "source-id": "BackfillContactUpdatesKafka", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1639158301358, + "selected": false + } + ] + }, + "hs_is_unworked": { + "value": "true", + "versions": [ + { + "value": "true", + "source-type": "CALCULATED", + "source-id": null, + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045782, + "selected": false + } + ] + }, + "firstname": { + "value": "Brian", + "versions": [ + { + "value": "Brian", + "source-type": "CONTACTS_WEB", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + }, + "associatedcompanyid": { + "value": "4931550080", + "versions": [ + { + "value": "4931550080", + "source-type": "CALCULATED", + "source-id": "RollupProperties", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827057264, + "selected": false + } + ] + }, + "city": { + "value": "Cambridge", + "versions": [ + { + "value": "Cambridge", + "source-type": "CONTACTS_WEB", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + }, + "num_unique_conversion_events": { + "value": "0", + "versions": [ + { + "value": "0", + "source-type": "MIGRATION", + "source-id": "BackfillReadtimeCalculatedPropertiesJob", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1629469311146, + "selected": false + } + ] + }, + "hs_latest_source": { + "value": "OFFLINE", + "versions": [ + { + "value": "OFFLINE", + "source-type": "MIGRATION", + "source-id": "BackfillContactUpdatesKafka", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1639158301358, + "selected": false + } + ] + }, + "hs_pipeline": { + "value": "contacts-lifecycle-pipeline", + "versions": [ + { + "value": "contacts-lifecycle-pipeline", + "source-type": "MIGRATION", + "source-id": "BackfillHsPipelineForContacts", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1628846625829, + "selected": false + } + ] + }, + "hs_analytics_revenue": { + "value": "0.0", + "versions": [ + { + "value": "0.0", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "hs_social_num_broadcast_clicks": { + "value": "0", + "versions": [ + { + "value": "0", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "createdate": { + "value": "1606827045698", + "versions": [ + { + "value": "1606827045698", + "source-type": "API", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045698, + "selected": false + } + ] + }, + "hs_analytics_num_visits": { + "value": "0", + "versions": [ + { + "value": "0", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "hs_social_linkedin_clicks": { + "value": "0", + "versions": [ + { + "value": "0", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "hs_marketable_until_renewal": { + "value": "true", + "versions": [ + { + "value": "true", + "source-type": "API", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045876, + "selected": false + } + ] + }, + "hs_marketable_status": { + "value": "true", + "versions": [ + { + "value": "true", + "source-type": "API", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045876, + "selected": false + } + ] + }, + "hs_analytics_source": { + "value": "OFFLINE", + "versions": [ + { + "value": "OFFLINE", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "hs_email_domain": { + "value": "hubspot.com", + "versions": [ + { + "value": "hubspot.com", + "source-type": "MIGRATION", + "source-id": "BackfillReadtimeCalculatedPropertiesJob", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1629469311146, + "selected": false + } + ] + }, + "hs_analytics_num_page_views": { + "value": "0", + "versions": [ + { + "value": "0", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "hs_marketable_reason_id": { + "value": "Sample Contact", + "versions": [ + { + "value": "Sample Contact", + "source-type": "API", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045876, + "selected": false + } + ] + }, + "company": { + "value": "HubSpot", + "versions": [ + { + "value": "HubSpot", + "source-type": "CONTACTS_WEB", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + }, + "state": { + "value": "MA", + "versions": [ + { + "value": "MA", + "source-type": "CONTACTS_WEB", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + }, + "email": { + "value": "bh@hubspot.com", + "versions": [ + { + "value": "bh@hubspot.com", + "source-type": "API", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + }, + "hs_latest_source_timestamp": { + "value": "1606827045720", + "versions": [ + { + "value": "1606827045720", + "source-type": "MIGRATION", + "source-id": "BackfillContactUpdatesKafka", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1651750884919, + "selected": false + } + ] + }, + "website": { + "value": "http://www.HubSpot.com", + "versions": [ + { + "value": "http://www.HubSpot.com", + "source-type": "CONTACTS_WEB", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + }, + "hs_marketable_reason_type": { + "value": "SAMPLE_CONTACT", + "versions": [ + { + "value": "SAMPLE_CONTACT", + "source-type": "API", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045876, + "selected": false + } + ] + }, + "jobtitle": { + "value": "CEO", + "versions": [ + { + "value": "CEO", + "source-type": "CONTACTS_WEB", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + }, + "lastmodifieddate": { + "value": "1651750891986", + "versions": [ + { + "value": "1651750891986", + "source-type": "CALCULATED", + "source-id": null, + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1651750891986, + "selected": false + }, + { + "value": "1639158305597", + "source-type": "CALCULATED", + "source-id": null, + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1639158305597, + "selected": false + }, + { + "value": "1628846625829", + "source-type": "CALCULATED", + "source-id": "BackfillHsPipelineForContacts", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1628846625829, + "selected": false + } + ] + }, + "hs_analytics_first_timestamp": { + "value": "1606827045444", + "versions": [ + { + "value": "1606827045444", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "hs_social_google_plus_clicks": { + "value": "0", + "versions": [ + { + "value": "0", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "hs_analytics_average_page_views": { + "value": "0", + "versions": [ + { + "value": "0", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "lastname": { + "value": "Halligan (Sample Contact)", + "versions": [ + { + "value": "Halligan (Sample Contact)", + "source-type": "CONTACTS_WEB", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + }, + "hs_all_contact_vids": { + "value": "51", + "versions": [ + { + "value": "51", + "source-type": "MIGRATION", + "source-id": "BackfillReadtimeCalculatedPropertiesJob", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1629469311146, + "selected": false + } + ] + }, + "twitterhandle": { + "value": "bhalligan", + "versions": [ + { + "value": "bhalligan", + "source-type": "CONTACTS_WEB", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + }, + "hs_social_facebook_clicks": { + "value": "0", + "versions": [ + { + "value": "0", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "hs_is_contact": { + "value": "true", + "versions": [ + { + "value": "true", + "source-type": "CALCULATED", + "source-id": null, + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045698, + "selected": false + } + ] + }, + "num_conversion_events": { + "value": "0", + "versions": [ + { + "value": "0", + "source-type": "MIGRATION", + "source-id": "BackfillReadtimeCalculatedPropertiesJob", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1629469311146, + "selected": false + } + ] + }, + "twitterprofilephoto": { + "value": "https://pbs.twimg.com/profile_images/3491742741/212e42c07d3348251da10872e85aa6b0.jpeg", + "versions": [ + { + "value": "https://pbs.twimg.com/profile_images/3491742741/212e42c07d3348251da10872e85aa6b0.jpeg", + "source-type": "CONTACTS_WEB", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + }, + "hs_object_id": { + "value": "51", + "versions": [ + { + "value": "51", + "source-type": "MIGRATION", + "source-id": "BackfillReadtimeCalculatedPropertiesJob", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1629469311146, + "selected": false + } + ] + }, + "hs_analytics_num_event_completions": { + "value": "0", + "versions": [ + { + "value": "0", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "hs_social_twitter_clicks": { + "value": "0", + "versions": [ + { + "value": "0", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827047569, + "selected": false + } + ] + }, + "hs_analytics_source_data_2": { + "value": "sample-contact", + "versions": [ + { + "value": "sample-contact", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827054762, + "selected": false + } + ] + }, + "hs_lifecyclestage_lead_date": { + "value": "1606827045444", + "versions": [ + { + "value": "1606827045444", + "source-type": "CONTACTS_WEB", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + }, + "hs_analytics_source_data_1": { + "value": "API", + "versions": [ + { + "value": "API", + "source-type": "ANALYTICS", + "source-id": "ContactAnalyticsDetailsUpdateWorker", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827054762, + "selected": false + } + ] + }, + "lifecyclestage": { + "value": "lead", + "versions": [ + { + "value": "lead", + "source-type": "CONTACTS_WEB", + "source-id": "sample-contact", + "source-label": null, + "updated-by-user-id": null, + "timestamp": 1606827045444, + "selected": false + } + ] + } + }, + "form-submissions": [], + "list-memberships": [], + "identity-profiles": [ + { + "vid": 51, + "saved-at-timestamp": 1606827045720, + "deleted-changed-timestamp": 0, + "identities": [ + { + "type": "EMAIL", + "value": "bh@hubspot.com", + "timestamp": 1606827045444, + "is-primary": true + }, + { + "type": "LEAD_GUID", + "value": "d3749acc-06e1-4511-84fd-7b0d847f6eff", + "timestamp": 1606827045717 + } + ] + } + ], + "merge-audits": [], + "associated-company": { + "company-id": 4931550080, + "portal-id": 8924380, + "properties": { + "country": { + "value": "United States" + }, + "city": { + "value": "Cambridge" + }, + "num_associated_contacts": { + "value": "2" + }, + "timezone": { + "value": "America/New_York" + }, + "facebook_company_page": { + "value": "https://www.facebook.com/hubspot" + }, + "createdate": { + "value": "1606827053844" + }, + "description": { + "value": "HubSpot is an American developer and marketer of software products for inbound marketing, sales, and customer service." + }, + "hs_analytics_latest_source_data_2": { + "value": "sample-contact" + }, + "hs_analytics_latest_source_data_1": { + "value": "API" + }, + "hs_num_blockers": { + "value": "0" + }, + "industry": { + "value": "COMPUTER_SOFTWARE" + }, + "total_money_raised": { + "value": "100.5M" + }, + "web_technologies": { + "value": "unbounce;instagram;app_nexus;piwik;google_analytics;mixpanel;google_tag_manager;facebook_advertiser;salesforce;cloud_flare;dstillery;twitter_button;hubspot;vidyard;facebook_connect;crazy_egg;amazon__cloudfront;wistia;optimizely" + }, + "numberofemployees": { + "value": "5000" + }, + "hs_analytics_num_visits": { + "value": "0" + }, + "linkedin_company_page": { + "value": "https://www.linkedin.com/company/hubspot" + }, + "hs_analytics_latest_source_timestamp": { + "value": "1606827045720" + }, + "hs_analytics_source": { + "value": "OFFLINE" + }, + "annualrevenue": { + "value": "250000000" + }, + "founded_year": { + "value": "2006" + }, + "hs_annual_revenue_currency_code": { + "value": "USD" + }, + "hs_analytics_num_page_views": { + "value": "0" + }, + "state": { + "value": "MA" + }, + "linkedinbio": { + "value": "HubSpot is an American developer and marketer of software products for inbound marketing, sales, and customer service." + }, + "hs_num_open_deals": { + "value": "0" + }, + "zip": { + "value": "02141" + }, + "website": { + "value": "hubspot.com" + }, + "address": { + "value": "25 First Street" + }, + "hs_analytics_first_timestamp": { + "value": "1606827045444" + }, + "first_contact_createdate": { + "value": "1606827045444" + }, + "twitterhandle": { + "value": "HubSpot" + }, + "hs_target_account_probability": { + "value": "0.49565839767456055" + }, + "hs_lastmodifieddate": { + "value": "1653392172246" + }, + "hs_num_decision_makers": { + "value": "0" + }, + "phone": { + "value": "+1 888-482-7768" + }, + "domain": { + "value": "hubspot.com" + }, + "hs_num_child_companies": { + "value": "0" + }, + "hs_num_contacts_with_buying_roles": { + "value": "0" + }, + "hs_object_id": { + "value": "4931550080" + }, + "is_public": { + "value": "true" + }, + "name": { + "value": "HubSpot, Inc." + }, + "hs_analytics_source_data_2": { + "value": "sample-contact" + }, + "hs_analytics_latest_source": { + "value": "OFFLINE" + }, + "hs_analytics_source_data_1": { + "value": "API" + } + } + } + } + } + ] + }, + "connections": { + "HubSpot Trigger": { + "main": [ + [ + { + "node": "IF", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF": { + "main": [ + [ + { + "node": "Is Gmail, Don't Add to Sheet", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Google Sheets", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "4748dbbc-75dd-400e-98d1-41bbd82c7208", + "id": "cGYp8fpTdh3LAgP5", + "meta": { + "instanceId": "dbd43d88d26a9e30d8aadc002c9e77f1400c683dd34efe3778d43d27250dde50" + }, + "tags": [ + { + "createdAt": "2023-09-21T09:36:34.726Z", + "updatedAt": "2023-09-21T09:36:52.231Z", + "id": "vh6ctEIEfFztmSF2", + "name": "release-template-version" + } + ] +} diff --git a/packages/testing/playwright/workflows/Test_workflow_partial_execution_v2.json b/packages/testing/playwright/workflows/Test_workflow_partial_execution_v2.json new file mode 100644 index 0000000000..c3c8ecc7ae --- /dev/null +++ b/packages/testing/playwright/workflows/Test_workflow_partial_execution_v2.json @@ -0,0 +1,74 @@ +{ + "nodes": [ + { + "parameters": { + "rule": { + "interval": [{}] + } + }, + "type": "n8n-nodes-base.scheduleTrigger", + "typeVersion": 1.2, + "position": [0, 0], + "id": "dcc1c5e1-c6c1-45f8-80d5-65c88d66d56e", + "name": "A" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "3d8f0810-84f0-41ce-a81b-0e7f04fd88cb", + "name": "", + "value": "", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [220, 0], + "id": "097ffa30-d37b-4de6-bd5c-ccd945f31df1", + "name": "B" + }, + { + "parameters": { + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [440, 0], + "id": "dc44e635-916f-4f76-a745-1add5762f730", + "name": "C" + } + ], + "connections": { + "A": { + "main": [ + [ + { + "node": "B", + "type": "main", + "index": 0 + } + ] + ] + }, + "B": { + "main": [ + [ + { + "node": "C", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": {}, + "meta": { + "instanceId": "b0d9447cff9c96796e4ac4f00fcd899b03cfac3ab3d4f748ae686d34881eae0c" + } +} diff --git a/packages/testing/playwright/workflows/manual-partial-execution.json b/packages/testing/playwright/workflows/manual-partial-execution.json new file mode 100644 index 0000000000..4afbb57cc5 --- /dev/null +++ b/packages/testing/playwright/workflows/manual-partial-execution.json @@ -0,0 +1,95 @@ +{ + "meta": { + "templateCredsSetupCompleted": true + }, + "nodes": [ + { + "parameters": { + "options": {} + }, + "id": "f4467143-fdb9-46fa-8020-6417cc5eea7d", + "name": "Edit Fields", + "type": "n8n-nodes-base.set", + "typeVersion": 3.3, + "position": [1140, 260] + }, + { + "parameters": { + "path": "30ff316d-405f-4288-a0ac-e713546c9d4e", + "options": {} + }, + "id": "4760aafb-5d56-4633-99d3-7a97c576a216", + "name": "Webhook1", + "type": "n8n-nodes-base.webhook", + "typeVersion": 2, + "position": [680, 340], + "webhookId": "30ff316d-405f-4288-a0ac-e713546c9d4e" + }, + { + "parameters": { + "articleId": "123", + "additionalFields": {} + }, + "id": "8c811eca-8978-44d9-b8f7-ef2c7725784c", + "name": "Hacker News", + "type": "n8n-nodes-base.hackerNews", + "typeVersion": 1, + "position": [920, 260] + }, + { + "parameters": { + "path": "4a3398e4-1388-4e10-9d21-add90b804955", + "options": {} + }, + "id": "1c2c2d06-45c9-4712-9fa0-c655bef8d0e5", + "name": "Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 2, + "position": [680, 180], + "webhookId": "4a3398e4-1388-4e10-9d21-add90b804955" + } + ], + "connections": { + "Webhook1": { + "main": [ + [ + { + "node": "Hacker News", + "type": "main", + "index": 0 + } + ] + ] + }, + "Hacker News": { + "main": [ + [ + { + "node": "Edit Fields", + "type": "main", + "index": 0 + } + ] + ] + }, + "Webhook": { + "main": [ + [ + { + "node": "Hacker News", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": { + "Webhook": [ + { + "name": "First item", + "code": 1 + } + ] + } +}