feat(Airtop Node): Implement double-click and right click variants (#18306)

This commit is contained in:
Cesar Sanchez
2025-09-04 11:36:16 +01:00
committed by GitHub
parent 56fee521f5
commit 9566f2b550
6 changed files with 91 additions and 14 deletions

View File

@@ -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( export async function execute(
@@ -39,8 +66,13 @@ export async function execute(
'Element Description', 'Element Description',
); );
const clickType = validateRequiredStringField.call(this, index, 'clickType', 'Click Type');
const request = constructInteractionRequest.call(this, index, { const request = constructInteractionRequest.call(this, index, {
elementDescription, elementDescription,
configuration: {
clickType,
},
}); });
const response = await apiRequest.call( const response = await apiRequest.call(

View File

@@ -9,7 +9,10 @@ export function constructInteractionRequest(
): IAirtopInteractionRequest { ): IAirtopInteractionRequest {
const additionalFields = this.getNodeParameter('additionalFields', index); const additionalFields = this.getNodeParameter('additionalFields', index);
const request: IAirtopInteractionRequest = { const request: IAirtopInteractionRequest = {
configuration: {}, ...parameters,
configuration: {
...(parameters.configuration ?? {}),
},
}; };
if (additionalFields.visualScope) { if (additionalFields.visualScope) {
@@ -25,7 +28,5 @@ export function constructInteractionRequest(
}; };
} }
Object.assign(request, parameters);
return request; return request;
} }

View File

@@ -23,8 +23,6 @@ export const getN8NVersion = (): string => {
export const N8N_VERSION = getN8NVersion(); export const N8N_VERSION = getN8NVersion();
export const BASE_URL = process.env.AIRTOP_BASE_URL ?? 'https://api.airtop.ai/api/v1'; 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 // Session operations
export const DEFAULT_TIMEOUT_MINUTES = 10; export const DEFAULT_TIMEOUT_MINUTES = 10;

View File

@@ -11,6 +11,7 @@ const baseNodeParameters = {
sessionId: 'test-session-123', sessionId: 'test-session-123',
windowId: 'win-123', windowId: 'win-123',
elementDescription: 'the login button', elementDescription: 'the login button',
clickType: 'click',
additionalFields: {}, additionalFields: {},
}; };
@@ -55,7 +56,9 @@ describe('Test Airtop, click operation', () => {
'/sessions/test-session-123/windows/win-123/click', '/sessions/test-session-123/windows/win-123/click',
{ {
elementDescription: 'the login button', elementDescription: 'the login button',
configuration: {}, configuration: {
clickType: 'click',
},
}, },
); );
@@ -104,6 +107,7 @@ describe('Test Airtop, click operation', () => {
visualAnalysis: { visualAnalysis: {
scope: 'viewport', scope: 'viewport',
}, },
clickType: 'click',
}, },
elementDescription: 'the login button', elementDescription: 'the login button',
}, },
@@ -125,6 +129,7 @@ describe('Test Airtop, click operation', () => {
'/sessions/test-session-123/windows/win-123/click', '/sessions/test-session-123/windows/win-123/click',
{ {
configuration: { configuration: {
clickType: 'click',
waitForNavigationConfig: { waitForNavigationConfig: {
waitUntil: 'load', 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',
},
},
);
});
}); });

View File

@@ -15,19 +15,19 @@ const defaultHeaders = {
'x-airtop-sdk-version': N8N_VERSION, 'x-airtop-sdk-version': N8N_VERSION,
}; };
export async function apiRequest( export async function apiRequest<T extends IAirtopResponse = IAirtopResponse>(
this: IExecuteFunctions | ILoadOptionsFunctions, this: IExecuteFunctions | ILoadOptionsFunctions,
method: IHttpRequestMethods, method: IHttpRequestMethods,
endpoint: string, endpoint: string,
body: IDataObject = {}, body: IDataObject = {},
query: IDataObject = {}, query: IDataObject = {},
) { ): Promise<T> {
const options: IHttpRequestOptions = { const options: IHttpRequestOptions = {
headers: defaultHeaders, headers: defaultHeaders,
method, method,
body, body,
qs: query, qs: query,
url: endpoint.startsWith('http') ? endpoint : `${BASE_URL}${endpoint}`, url: `${BASE_URL}${endpoint}`,
json: true, json: true,
}; };
@@ -35,9 +35,9 @@ export async function apiRequest(
delete options.body; delete options.body;
} }
return (await this.helpers.httpRequestWithAuthentication.call( return await this.helpers.httpRequestWithAuthentication.call<
this, IExecuteFunctions | ILoadOptionsFunctions,
'airtopApi', [string, IHttpRequestOptions],
options, Promise<T>
)) as IAirtopResponse; >(this, 'airtopApi', options);
} }

View File

@@ -61,6 +61,7 @@ export interface IAirtopInteractionRequest extends IDataObject {
waitForNavigationConfig?: { waitForNavigationConfig?: {
waitUntil: string; waitUntil: string;
}; };
clickType?: string;
}; };
} }