From 9566f2b5500a9e35f5e33e206996d45346412456 Mon Sep 17 00:00:00 2001 From: Cesar Sanchez Date: Thu, 4 Sep 2025 11:36:16 +0100 Subject: [PATCH] feat(Airtop Node): Implement double-click and right click variants (#18306) --- .../actions/interaction/click.operation.ts | 32 +++++++++++++ .../Airtop/actions/interaction/helpers.ts | 7 +-- packages/nodes-base/nodes/Airtop/constants.ts | 2 - .../test/node/interaction/click.test.ts | 47 ++++++++++++++++++- .../nodes/Airtop/transport/index.ts | 16 +++---- .../nodes/Airtop/transport/types.ts | 1 + 6 files changed, 91 insertions(+), 14 deletions(-) diff --git a/packages/nodes-base/nodes/Airtop/actions/interaction/click.operation.ts b/packages/nodes-base/nodes/Airtop/actions/interaction/click.operation.ts index 6aa4e7283f..fd8f490422 100644 --- a/packages/nodes-base/nodes/Airtop/actions/interaction/click.operation.ts +++ b/packages/nodes-base/nodes/Airtop/actions/interaction/click.operation.ts @@ -25,6 +25,33 @@ export const description: INodeProperties[] = [ }, }, }, + { + displayName: 'Click Type', + name: 'clickType', + type: 'options', + default: 'click', + description: 'The type of click to perform. Defaults to left click.', + options: [ + { + name: 'Left Click', + value: 'click', + }, + { + name: 'Double Click', + value: 'doubleClick', + }, + { + name: 'Right Click', + value: 'rightClick', + }, + ], + displayOptions: { + show: { + resource: ['interaction'], + operation: ['click'], + }, + }, + }, ]; export async function execute( @@ -39,8 +66,13 @@ export async function execute( 'Element Description', ); + const clickType = validateRequiredStringField.call(this, index, 'clickType', 'Click Type'); + const request = constructInteractionRequest.call(this, index, { elementDescription, + configuration: { + clickType, + }, }); const response = await apiRequest.call( diff --git a/packages/nodes-base/nodes/Airtop/actions/interaction/helpers.ts b/packages/nodes-base/nodes/Airtop/actions/interaction/helpers.ts index bdcbd1dec1..2b5e9ea0f6 100644 --- a/packages/nodes-base/nodes/Airtop/actions/interaction/helpers.ts +++ b/packages/nodes-base/nodes/Airtop/actions/interaction/helpers.ts @@ -9,7 +9,10 @@ export function constructInteractionRequest( ): IAirtopInteractionRequest { const additionalFields = this.getNodeParameter('additionalFields', index); const request: IAirtopInteractionRequest = { - configuration: {}, + ...parameters, + configuration: { + ...(parameters.configuration ?? {}), + }, }; if (additionalFields.visualScope) { @@ -25,7 +28,5 @@ export function constructInteractionRequest( }; } - Object.assign(request, parameters); - return request; } diff --git a/packages/nodes-base/nodes/Airtop/constants.ts b/packages/nodes-base/nodes/Airtop/constants.ts index 5b7dccbb84..a7f8476c9d 100644 --- a/packages/nodes-base/nodes/Airtop/constants.ts +++ b/packages/nodes-base/nodes/Airtop/constants.ts @@ -23,8 +23,6 @@ export const getN8NVersion = (): string => { export const N8N_VERSION = getN8NVersion(); export const BASE_URL = process.env.AIRTOP_BASE_URL ?? 'https://api.airtop.ai/api/v1'; -export const INTEGRATION_URL = - process.env.AIRTOP_INTEGRATION_URL ?? 'https://portal-api.airtop.ai/integrations/v1/no-code'; // Session operations export const DEFAULT_TIMEOUT_MINUTES = 10; diff --git a/packages/nodes-base/nodes/Airtop/test/node/interaction/click.test.ts b/packages/nodes-base/nodes/Airtop/test/node/interaction/click.test.ts index 5cada782be..aef86651f7 100644 --- a/packages/nodes-base/nodes/Airtop/test/node/interaction/click.test.ts +++ b/packages/nodes-base/nodes/Airtop/test/node/interaction/click.test.ts @@ -11,6 +11,7 @@ const baseNodeParameters = { sessionId: 'test-session-123', windowId: 'win-123', elementDescription: 'the login button', + clickType: 'click', additionalFields: {}, }; @@ -55,7 +56,9 @@ describe('Test Airtop, click operation', () => { '/sessions/test-session-123/windows/win-123/click', { elementDescription: 'the login button', - configuration: {}, + configuration: { + clickType: 'click', + }, }, ); @@ -104,6 +107,7 @@ describe('Test Airtop, click operation', () => { visualAnalysis: { scope: 'viewport', }, + clickType: 'click', }, elementDescription: 'the login button', }, @@ -125,6 +129,7 @@ describe('Test Airtop, click operation', () => { '/sessions/test-session-123/windows/win-123/click', { configuration: { + clickType: 'click', waitForNavigationConfig: { waitUntil: 'load', }, @@ -134,4 +139,44 @@ describe('Test Airtop, click operation', () => { }, ); }); + + it("should execute double click when 'clickType' is 'doubleClick'", async () => { + const nodeParameters = { + ...baseNodeParameters, + clickType: 'doubleClick', + }; + + await click.execute.call(createMockExecuteFunction(nodeParameters), 0); + + expect(transport.apiRequest).toHaveBeenCalledWith( + 'POST', + '/sessions/test-session-123/windows/win-123/click', + { + elementDescription: 'the login button', + configuration: { + clickType: 'doubleClick', + }, + }, + ); + }); + + it("should execute right click when 'clickType' is 'rightClick'", async () => { + const nodeParameters = { + ...baseNodeParameters, + clickType: 'rightClick', + }; + + await click.execute.call(createMockExecuteFunction(nodeParameters), 0); + + expect(transport.apiRequest).toHaveBeenCalledWith( + 'POST', + '/sessions/test-session-123/windows/win-123/click', + { + elementDescription: 'the login button', + configuration: { + clickType: 'rightClick', + }, + }, + ); + }); }); diff --git a/packages/nodes-base/nodes/Airtop/transport/index.ts b/packages/nodes-base/nodes/Airtop/transport/index.ts index 94e5997043..cf2db03e4a 100644 --- a/packages/nodes-base/nodes/Airtop/transport/index.ts +++ b/packages/nodes-base/nodes/Airtop/transport/index.ts @@ -15,19 +15,19 @@ const defaultHeaders = { 'x-airtop-sdk-version': N8N_VERSION, }; -export async function apiRequest( +export async function apiRequest( this: IExecuteFunctions | ILoadOptionsFunctions, method: IHttpRequestMethods, endpoint: string, body: IDataObject = {}, query: IDataObject = {}, -) { +): Promise { const options: IHttpRequestOptions = { headers: defaultHeaders, method, body, qs: query, - url: endpoint.startsWith('http') ? endpoint : `${BASE_URL}${endpoint}`, + url: `${BASE_URL}${endpoint}`, json: true, }; @@ -35,9 +35,9 @@ export async function apiRequest( delete options.body; } - return (await this.helpers.httpRequestWithAuthentication.call( - this, - 'airtopApi', - options, - )) as IAirtopResponse; + return await this.helpers.httpRequestWithAuthentication.call< + IExecuteFunctions | ILoadOptionsFunctions, + [string, IHttpRequestOptions], + Promise + >(this, 'airtopApi', options); } diff --git a/packages/nodes-base/nodes/Airtop/transport/types.ts b/packages/nodes-base/nodes/Airtop/transport/types.ts index 2990529b83..24ab9858e0 100644 --- a/packages/nodes-base/nodes/Airtop/transport/types.ts +++ b/packages/nodes-base/nodes/Airtop/transport/types.ts @@ -61,6 +61,7 @@ export interface IAirtopInteractionRequest extends IDataObject { waitForNavigationConfig?: { waitUntil: string; }; + clickType?: string; }; }