first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,182 @@
import nock from 'nock';
import * as click from '../../../actions/interaction/click.operation';
import { ERROR_MESSAGES } from '../../../constants';
import * as transport from '../../../transport';
import { createMockExecuteFunction } from '../helpers';
const baseNodeParameters = {
resource: 'interaction',
operation: 'click',
sessionId: 'test-session-123',
windowId: 'win-123',
elementDescription: 'the login button',
clickType: 'click',
additionalFields: {},
};
const mockResponse = {
success: true,
message: 'Click executed successfully',
};
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(async function () {
return {
status: 'success',
data: mockResponse,
};
}),
};
});
describe('Test Airtop, click operation', () => {
beforeAll(() => {
nock.disableNetConnect();
});
afterAll(() => {
nock.restore();
jest.unmock('../../../transport');
});
afterEach(() => {
jest.clearAllMocks();
});
it('should execute click with minimal parameters', async () => {
const result = await click.execute.call(createMockExecuteFunction(baseNodeParameters), 0);
expect(transport.apiRequest).toHaveBeenCalledTimes(1);
expect(transport.apiRequest).toHaveBeenCalledWith(
'POST',
'/sessions/test-session-123/windows/win-123/click',
{
elementDescription: 'the login button',
configuration: {
clickType: 'click',
},
},
);
expect(result).toEqual([
{
json: {
sessionId: baseNodeParameters.sessionId,
windowId: baseNodeParameters.windowId,
status: 'success',
data: mockResponse,
},
},
]);
});
it("should throw error when 'elementDescription' parameter is empty", async () => {
const nodeParameters = {
...baseNodeParameters,
elementDescription: '',
};
const errorMessage = ERROR_MESSAGES.REQUIRED_PARAMETER.replace(
'{{field}}',
'Element Description',
);
await expect(click.execute.call(createMockExecuteFunction(nodeParameters), 0)).rejects.toThrow(
errorMessage,
);
});
it("should include 'visualScope' parameter when specified", async () => {
const nodeParameters = {
...baseNodeParameters,
additionalFields: {
visualScope: 'viewport',
},
};
await click.execute.call(createMockExecuteFunction(nodeParameters), 0);
expect(transport.apiRequest).toHaveBeenCalledWith(
'POST',
'/sessions/test-session-123/windows/win-123/click',
{
configuration: {
visualAnalysis: {
scope: 'viewport',
},
clickType: 'click',
},
elementDescription: 'the login button',
},
);
});
it("should include 'waitForNavigation' parameter when specified", async () => {
const nodeParameters = {
...baseNodeParameters,
additionalFields: {
waitForNavigation: 'load',
},
};
await click.execute.call(createMockExecuteFunction(nodeParameters), 0);
expect(transport.apiRequest).toHaveBeenCalledWith(
'POST',
'/sessions/test-session-123/windows/win-123/click',
{
configuration: {
clickType: 'click',
waitForNavigationConfig: {
waitUntil: 'load',
},
},
waitForNavigation: true,
elementDescription: 'the login button',
},
);
});
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',
},
},
);
});
});
@@ -0,0 +1,147 @@
import * as fill from '../../../actions/interaction/fill.operation';
import { ERROR_MESSAGES } from '../../../constants';
import * as transport from '../../../transport';
import { createMockExecuteFunction } from '../helpers';
const baseNodeParameters = {
resource: 'interaction',
operation: 'fill',
sessionId: 'test-session-123',
windowId: 'win-123',
formData: 'Name: John Doe, Email: john@example.com',
};
const mockAsyncResponse = {
requestId: 'req-123',
status: 'pending',
};
const mockCompletedResponse = {
status: 'completed',
data: {
success: true,
message: 'Form filled successfully',
},
};
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(),
};
});
describe('Test Airtop, fill form operation', () => {
afterAll(() => {
jest.unmock('../../../transport');
});
afterEach(() => {
jest.clearAllMocks();
});
it('should execute fill operation successfully', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
// Mock the initial async request
apiRequestMock.mockResolvedValueOnce(mockAsyncResponse);
// Mock the status check to return completed after first pending
apiRequestMock
.mockResolvedValueOnce({ ...mockAsyncResponse })
.mockResolvedValueOnce(mockCompletedResponse);
const result = await fill.execute.call(createMockExecuteFunction(baseNodeParameters), 0);
expect(apiRequestMock).toHaveBeenCalledWith(
'POST',
'/async/sessions/test-session-123/windows/win-123/execute-automation',
{
automationId: 'auto',
parameters: {
customData: 'Name: John Doe, Email: john@example.com',
},
},
);
expect(apiRequestMock).toHaveBeenCalledWith('GET', '/requests/req-123/status');
expect(result).toEqual([
{
json: {
sessionId: baseNodeParameters.sessionId,
windowId: baseNodeParameters.windowId,
status: 'completed',
data: {
success: true,
message: 'Form filled successfully',
},
},
},
]);
});
it("should throw error when 'formData' parameter is empty", async () => {
const nodeParameters = {
...baseNodeParameters,
formData: '',
};
await expect(fill.execute.call(createMockExecuteFunction(nodeParameters), 0)).rejects.toThrow(
ERROR_MESSAGES.REQUIRED_PARAMETER.replace('{{field}}', 'Form Data'),
);
});
it('should throw error when operation times out after 2 sec', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const nodeParameters = {
...baseNodeParameters,
};
const timeout = 2000;
// Mock the initial async request
apiRequestMock.mockResolvedValueOnce(mockAsyncResponse);
// Return pending on all requests
apiRequestMock.mockResolvedValue({ ...mockAsyncResponse });
// should throw NodeApiError
await expect(
fill.execute.call(createMockExecuteFunction(nodeParameters), 0, timeout),
).rejects.toThrow('The service was not able to process your request');
});
it('should handle error status in response', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const errorResponse = {
status: 'error',
error: {
message: 'Failed to fill form',
},
};
// Mock the initial async request
apiRequestMock.mockResolvedValueOnce(mockAsyncResponse);
// Mock the status check to return error
apiRequestMock
.mockResolvedValueOnce({ ...mockAsyncResponse })
.mockResolvedValueOnce(errorResponse);
const result = await fill.execute.call(createMockExecuteFunction(baseNodeParameters), 0);
expect(result).toEqual([
{
json: {
sessionId: baseNodeParameters.sessionId,
windowId: baseNodeParameters.windowId,
status: 'error',
error: {
message: 'Failed to fill form',
},
},
},
]);
});
});
@@ -0,0 +1,77 @@
import { constructInteractionRequest } from '../../../actions/interaction/helpers';
import { createMockExecuteFunction } from '../helpers';
describe('Test Airtop interaction helpers', () => {
describe('constructInteractionRequest', () => {
it('should construct basic request with default values', () => {
const mockExecute = createMockExecuteFunction({
additionalFields: {},
});
const request = constructInteractionRequest.call(mockExecute, 0);
expect(request).toEqual({
configuration: {},
});
});
it("should include 'visualScope' parameter when specified", () => {
const mockExecute = createMockExecuteFunction({
additionalFields: {
visualScope: 'viewport',
},
});
const request = constructInteractionRequest.call(mockExecute, 0);
expect(request).toEqual({
configuration: {
visualAnalysis: {
scope: 'viewport',
},
},
});
});
it("should include 'waitForNavigation' parameter when specified", () => {
const mockExecute = createMockExecuteFunction({
additionalFields: {
waitForNavigation: 'load',
},
});
const request = constructInteractionRequest.call(mockExecute, 0);
expect(request).toEqual({
configuration: {
waitForNavigationConfig: {
waitUntil: 'load',
},
},
waitForNavigation: true,
});
});
it('should merge additional parameters', () => {
const mockExecute = createMockExecuteFunction({
additionalFields: {
waitForNavigation: 'load',
},
});
const request = constructInteractionRequest.call(mockExecute, 0, {
elementDescription: 'test element',
});
expect(request).toEqual({
configuration: {
waitForNavigationConfig: {
waitUntil: 'load',
},
},
waitForNavigation: true,
elementDescription: 'test element',
});
});
});
});
@@ -0,0 +1,137 @@
import nock from 'nock';
import * as hover from '../../../actions/interaction/hover.operation';
import { ERROR_MESSAGES } from '../../../constants';
import * as transport from '../../../transport';
import { createMockExecuteFunction } from '../helpers';
const baseNodeParameters = {
resource: 'interaction',
operation: 'hover',
sessionId: 'test-session-123',
windowId: 'win-123',
elementDescription: 'the user profile image',
additionalFields: {},
};
const mockResponse = {
success: true,
message: 'Hover interaction executed successfully',
};
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(async function () {
return {
status: 'success',
data: mockResponse,
};
}),
};
});
describe('Test Airtop, hover operation', () => {
beforeAll(() => {
nock.disableNetConnect();
});
afterAll(() => {
nock.restore();
jest.unmock('../../../transport');
});
afterEach(() => {
jest.clearAllMocks();
});
it('should execute hover with minimal parameters', async () => {
const result = await hover.execute.call(createMockExecuteFunction(baseNodeParameters), 0);
expect(transport.apiRequest).toHaveBeenCalledTimes(1);
expect(transport.apiRequest).toHaveBeenCalledWith(
'POST',
'/sessions/test-session-123/windows/win-123/hover',
{
configuration: {},
elementDescription: 'the user profile image',
},
);
expect(result).toEqual([
{
json: {
sessionId: 'test-session-123',
windowId: 'win-123',
status: 'success',
data: mockResponse,
},
},
]);
});
it("should throw error when 'elementDescription' parameter is empty", async () => {
const nodeParameters = {
...baseNodeParameters,
elementDescription: '',
};
const errorMessage = ERROR_MESSAGES.REQUIRED_PARAMETER.replace(
'{{field}}',
'Element Description',
);
await expect(hover.execute.call(createMockExecuteFunction(nodeParameters), 0)).rejects.toThrow(
errorMessage,
);
});
it("should include 'visualScope' parameter when specified", async () => {
const nodeParameters = {
...baseNodeParameters,
additionalFields: {
visualScope: 'viewport',
},
};
await hover.execute.call(createMockExecuteFunction(nodeParameters), 0);
expect(transport.apiRequest).toHaveBeenCalledWith(
'POST',
'/sessions/test-session-123/windows/win-123/hover',
{
configuration: {
visualAnalysis: {
scope: 'viewport',
},
},
elementDescription: 'the user profile image',
},
);
});
it("should include 'waitForNavigation' parameter when specified", async () => {
const nodeParameters = {
...baseNodeParameters,
additionalFields: {
waitForNavigation: 'load',
},
};
await hover.execute.call(createMockExecuteFunction(nodeParameters), 0);
expect(transport.apiRequest).toHaveBeenCalledWith(
'POST',
'/sessions/test-session-123/windows/win-123/hover',
{
configuration: {
waitForNavigationConfig: {
waitUntil: 'load',
},
},
waitForNavigation: true,
elementDescription: 'the user profile image',
},
);
});
});
@@ -0,0 +1,170 @@
import * as scroll from '../../../actions/interaction/scroll.operation';
import { ERROR_MESSAGES } from '../../../constants';
import * as transport from '../../../transport';
import { createMockExecuteFunction } from '../helpers';
const baseNodeParameters = {
resource: 'interaction',
operation: 'scroll',
sessionId: 'test-session-123',
windowId: 'win-123',
additionalFields: {},
};
const baseAutomaticNodeParameters = {
...baseNodeParameters,
scrollingMode: 'automatic',
scrollToElement: 'the bottom of the page',
scrollWithin: '',
};
const baseManualNodeParameters = {
...baseNodeParameters,
scrollingMode: 'manual',
scrollToEdge: {
edgeValues: {
yAxis: 'bottom',
xAxis: '',
},
},
scrollBy: {
scrollValues: {
yAxis: '200px',
xAxis: '',
},
},
};
const mockResponse = {
success: true,
message: 'Scrolled successfully',
};
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(),
};
});
describe('Test Airtop, scroll operation', () => {
afterAll(() => {
jest.unmock('../../../transport');
});
afterEach(() => {
jest.clearAllMocks();
});
it('should execute automatic scroll operation successfully', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
apiRequestMock.mockResolvedValueOnce(mockResponse);
const result = await scroll.execute.call(
createMockExecuteFunction(baseAutomaticNodeParameters),
0,
);
expect(apiRequestMock).toHaveBeenCalledWith(
'POST',
'/sessions/test-session-123/windows/win-123/scroll',
{
scrollToElement: 'the bottom of the page',
configuration: {},
},
);
expect(result).toEqual([
{
json: {
sessionId: baseAutomaticNodeParameters.sessionId,
windowId: baseAutomaticNodeParameters.windowId,
success: true,
message: 'Scrolled successfully',
},
},
]);
});
it('should execute manual scroll operation successfully', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
apiRequestMock.mockResolvedValueOnce(mockResponse);
const result = await scroll.execute.call(
createMockExecuteFunction(baseManualNodeParameters),
0,
);
expect(apiRequestMock).toHaveBeenCalledWith(
'POST',
'/sessions/test-session-123/windows/win-123/scroll',
{
configuration: {},
scrollToEdge: {
yAxis: 'bottom',
xAxis: '',
},
scrollBy: {
yAxis: '200px',
xAxis: '',
},
},
);
expect(result).toEqual([
{
json: {
sessionId: baseManualNodeParameters.sessionId,
windowId: baseManualNodeParameters.windowId,
success: true,
message: 'Scrolled successfully',
},
},
]);
});
it("should throw error when scrollingMode is 'automatic' and 'scrollToElement' parameter is empty", async () => {
const nodeParameters = {
...baseAutomaticNodeParameters,
scrollToElement: '',
};
await expect(scroll.execute.call(createMockExecuteFunction(nodeParameters), 0)).rejects.toThrow(
ERROR_MESSAGES.REQUIRED_PARAMETER.replace('{{field}}', 'Element Description'),
);
});
it("should validate scroll amount formats when scrollingMode is 'manual'", async () => {
const invalidNodeParameters = {
...baseManualNodeParameters,
scrollBy: {
scrollValues: {
yAxis: 'one hundred pixels',
xAxis: '',
},
},
};
await expect(
scroll.execute.call(createMockExecuteFunction(invalidNodeParameters), 0),
).rejects.toThrow(ERROR_MESSAGES.SCROLL_BY_AMOUNT_INVALID);
});
it('should throw an error when the API returns an error response', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const errorResponse = {
errors: [
{
message: 'Failed to scroll',
},
],
};
apiRequestMock.mockResolvedValueOnce(errorResponse);
await expect(
scroll.execute.call(createMockExecuteFunction(baseAutomaticNodeParameters), 0),
).rejects.toThrow('Failed to scroll');
});
});
@@ -0,0 +1,181 @@
import nock from 'nock';
import * as type from '../../../actions/interaction/type.operation';
import { ERROR_MESSAGES } from '../../../constants';
import * as transport from '../../../transport';
import { createMockExecuteFunction } from '../helpers';
const baseNodeParameters = {
resource: 'interaction',
operation: 'type',
sessionId: 'test-session-123',
windowId: 'win-123',
text: 'Hello World',
pressEnterKey: false,
elementDescription: '',
additionalFields: {},
};
const mockResponse = {
success: true,
message: 'Text typed successfully',
};
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(async function () {
return {
status: 'success',
data: mockResponse,
};
}),
};
});
describe('Test Airtop, type operation', () => {
beforeAll(() => {
nock.disableNetConnect();
});
afterAll(() => {
nock.restore();
jest.unmock('../../../transport');
});
afterEach(() => {
jest.clearAllMocks();
});
it('should execute type with minimal parameters', async () => {
const result = await type.execute.call(createMockExecuteFunction(baseNodeParameters), 0);
expect(transport.apiRequest).toHaveBeenCalledTimes(1);
expect(transport.apiRequest).toHaveBeenCalledWith(
'POST',
'/sessions/test-session-123/windows/win-123/type',
{
configuration: {},
text: 'Hello World',
pressEnterKey: false,
elementDescription: '',
},
);
expect(result).toEqual([
{
json: {
sessionId: baseNodeParameters.sessionId,
windowId: baseNodeParameters.windowId,
status: 'success',
data: mockResponse,
},
},
]);
});
it("should throw error when 'text' parameter is empty", async () => {
const nodeParameters = {
...baseNodeParameters,
text: '',
};
await expect(type.execute.call(createMockExecuteFunction(nodeParameters), 0)).rejects.toThrow(
ERROR_MESSAGES.REQUIRED_PARAMETER.replace('{{field}}', 'Text'),
);
});
it("should include 'elementDescription' parameter when specified", async () => {
const nodeParameters = {
...baseNodeParameters,
elementDescription: 'the search box',
};
await type.execute.call(createMockExecuteFunction(nodeParameters), 0);
expect(transport.apiRequest).toHaveBeenCalledWith(
'POST',
'/sessions/test-session-123/windows/win-123/type',
{
configuration: {},
text: 'Hello World',
pressEnterKey: false,
elementDescription: 'the search box',
},
);
});
it("should include 'pressEnterKey' parameter when specified", async () => {
const nodeParameters = {
...baseNodeParameters,
pressEnterKey: true,
};
await type.execute.call(createMockExecuteFunction(nodeParameters), 0);
expect(transport.apiRequest).toHaveBeenCalledWith(
'POST',
'/sessions/test-session-123/windows/win-123/type',
{
configuration: {},
text: 'Hello World',
pressEnterKey: true,
elementDescription: '',
},
);
});
it("should include 'waitForNavigation' parameter when specified", async () => {
const nodeParameters = {
...baseNodeParameters,
additionalFields: {
waitForNavigation: 'load',
},
};
await type.execute.call(createMockExecuteFunction(nodeParameters), 0);
expect(transport.apiRequest).toHaveBeenCalledWith(
'POST',
'/sessions/test-session-123/windows/win-123/type',
{
configuration: {
waitForNavigationConfig: {
waitUntil: 'load',
},
},
waitForNavigation: true,
text: 'Hello World',
pressEnterKey: false,
elementDescription: '',
},
);
});
it("should include 'visualScope' parameter when specified", async () => {
const nodeParameters = {
...baseNodeParameters,
additionalFields: {
visualScope: 'viewport',
},
};
await type.execute.call(createMockExecuteFunction(nodeParameters), 0);
expect(transport.apiRequest).toHaveBeenCalledWith(
'POST',
'/sessions/test-session-123/windows/win-123/type',
{
configuration: {
visualAnalysis: {
scope: 'viewport',
},
},
text: 'Hello World',
pressEnterKey: false,
elementDescription: '',
},
);
});
});