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(
@@ -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(

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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',
},
},
);
});
});

View File

@@ -15,19 +15,19 @@ const defaultHeaders = {
'x-airtop-sdk-version': N8N_VERSION,
};
export async function apiRequest(
export async function apiRequest<T extends IAirtopResponse = IAirtopResponse>(
this: IExecuteFunctions | ILoadOptionsFunctions,
method: IHttpRequestMethods,
endpoint: string,
body: IDataObject = {},
query: IDataObject = {},
) {
): Promise<T> {
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<T>
>(this, 'airtopApi', options);
}

View File

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