Files
mcp/test/autogui/autogui-tools.test.ts
zeekay c724ae360a Let the tests run again
Every suite had stopped executing. ts-jest peers on `typescript >=4.3 <7`
and this repo builds against 7, so all twenty files died inside ConfigSet
with "Cannot read properties of undefined (reading 'fileExists')" — twenty
failures, zero tests, and nothing to read them by because the push gate
deliberately runs typecheck and build rather than jest.

swc transforms instead: it strips types rather than asking the compiler
to, so it has no TypeScript version to disagree with, and `tsc --noEmit`
already answers the question ts-jest was answering twice.

Running as real ESM then exposed what the CommonJS fallback had hidden.
jest.mock does not hoist there, so a static import binds the real module
and the suite fails on `mockReturnValue is not a function` — an absent
mock reported as a broken one. The search, autogui and ui suites now
register through unstable_mockModule and import after it. One of them had
never mocked fs at all: it cast fs.promises to a mocked type and called
mockImplementation on the real module.

384 tests, twenty suites, all passing.
2026-09-01 09:42:54 -07:00

330 lines
12 KiB
TypeScript

import { describe, test, expect, beforeEach, afterEach, jest } from '@jest/globals';
// Mock external dependencies
// unstable_mockModule + await import, NOT jest.mock + a static import: ESM has no
// hoisting for jest.mock to rely on, so a static import binds the REAL module and
// the suite dies on `mockReturnValue is not a function` — an absent mock reported
// as a broken one.
jest.unstable_mockModule('../../src/autogui/factory.js', () => ({
createAutoGUI: jest.fn(),
getAvailableAutoGUIImplementations: jest.fn(),
getAutoGUIImplementationStatus: jest.fn(),
}));
const factory = await import('../../src/autogui/factory.js');
const { autoguiTools } = await import('../../src/autogui/tools/autogui-tools.js');
describe('AutoGUI Tools', () => {
beforeEach(() => {
jest.clearAllMocks();
// Mock factory functions
const { createAutoGUI, getAvailableAutoGUIImplementations, getAutoGUIImplementationStatus } = factory;
getAvailableAutoGUIImplementations.mockResolvedValue(['base', 'mock']);
getAutoGUIImplementationStatus.mockResolvedValue({
base: { available: true, status: 'ready' },
mock: { available: true, status: 'ready' }
});
createAutoGUI.mockResolvedValue({
getImplementation: () => 'mock',
isAvailable: () => true,
getScreenSize: jest.fn<() => Promise<any>>().mockResolvedValue({ width: 1920, height: 1080 }),
screenshot: jest.fn<() => Promise<any>>().mockResolvedValue('base64-image-data'),
click: jest.fn<() => Promise<any>>().mockResolvedValue(undefined),
doubleClick: jest.fn<() => Promise<any>>().mockResolvedValue(undefined),
rightClick: jest.fn<() => Promise<any>>().mockResolvedValue(undefined),
type: jest.fn<() => Promise<any>>().mockResolvedValue(undefined),
key: jest.fn<() => Promise<any>>().mockResolvedValue(undefined),
scroll: jest.fn<() => Promise<any>>().mockResolvedValue(undefined),
moveMouse: jest.fn<() => Promise<any>>().mockResolvedValue(undefined),
findElement: jest.fn<() => Promise<any>>().mockResolvedValue({ x: 100, y: 100, width: 50, height: 30 }),
});
});
describe('autoguiTools array', () => {
test('should export AutoGUI tools array', () => {
expect(Array.isArray(autoguiTools)).toBe(true);
expect(autoguiTools.length).toBeGreaterThan(0);
});
test('should have valid tool structures', () => {
autoguiTools.forEach(tool => {
expect(tool).toHaveProperty('name');
expect(tool).toHaveProperty('description');
expect(tool).toHaveProperty('inputSchema');
expect(tool).toHaveProperty('handler');
expect(typeof tool.name).toBe('string');
expect(typeof tool.description).toBe('string');
expect(typeof tool.handler).toBe('function');
expect(tool.inputSchema).toHaveProperty('type');
expect(tool.inputSchema.type).toBe('object');
});
});
test('should have unique tool names', () => {
const names = autoguiTools.map(tool => tool.name);
const uniqueNames = new Set(names);
expect(uniqueNames.size).toBe(autoguiTools.length);
});
test('should include core AutoGUI functionality', () => {
const toolNames = autoguiTools.map(tool => tool.name);
// Should include status tool
expect(toolNames.some(name => name.includes('status'))).toBe(true);
// Should include basic interaction tools if available
const expectedTools = ['status', 'screenshot', 'click', 'type'];
expectedTools.forEach(expectedTool => {
expect(toolNames.some(name => name.includes(expectedTool))).toBe(true);
});
});
});
describe('autogui_status tool', () => {
test('should have correct metadata', () => {
const statusTool = autoguiTools.find(tool => tool.name === 'autogui_status');
expect(statusTool).toBeDefined();
expect(statusTool!.description).toContain('status');
expect(statusTool!.inputSchema.required).toEqual([]);
});
test('should return implementation status', async () => {
const statusTool = autoguiTools.find(tool => tool.name === 'autogui_status');
const result = await statusTool!.handler({});
expect(result.isError).toBeFalsy();
expect(result.content).toHaveLength(1);
expect(result.content[0].type).toBe('text');
expect(result.content[0].text).toBeTruthy();
});
});
describe('Screenshot functionality', () => {
test('should have screenshot tool', () => {
const screenshotTool = autoguiTools.find(tool =>
tool.name.includes('screenshot') || tool.description.toLowerCase().includes('screenshot')
);
expect(screenshotTool).toBeDefined();
});
test('should handle screenshot requests', async () => {
const screenshotTool = autoguiTools.find(tool =>
tool.name.includes('screenshot') || tool.description.toLowerCase().includes('screenshot')
);
if (screenshotTool) {
try {
const result = await screenshotTool.handler({});
expect(result).toHaveProperty('content');
expect(Array.isArray(result.content)).toBe(true);
} catch (error) {
// Acceptable if AutoGUI implementation is not available
expect(error).toBeDefined();
}
}
});
});
describe('Mouse interaction tools', () => {
test('should have click tool', () => {
const clickTool = autoguiTools.find(tool =>
tool.name.includes('click') || tool.description.toLowerCase().includes('click')
);
expect(clickTool).toBeDefined();
});
test('should validate click coordinates', () => {
const clickTool = autoguiTools.find(tool =>
tool.name.includes('click') || tool.description.toLowerCase().includes('click')
);
if (clickTool) {
const schema = clickTool.inputSchema;
expect(schema.properties).toHaveProperty('x');
expect(schema.properties).toHaveProperty('y');
// x and y are optional for the click tool (clicks at current position
// when coordinates are omitted), so required is an empty array
expect(schema.required).toEqual([]);
}
});
test('should handle click actions', async () => {
const clickTool = autoguiTools.find(tool =>
tool.name.includes('click') || tool.description.toLowerCase().includes('click')
);
if (clickTool) {
try {
const result = await clickTool.handler({ x: 100, y: 100 });
expect(result).toHaveProperty('content');
} catch (error) {
// Acceptable if AutoGUI implementation is not available
expect(error).toBeDefined();
}
}
});
});
describe('Keyboard interaction tools', () => {
test('should have typing tool', () => {
const typeTool = autoguiTools.find(tool =>
tool.name.includes('type') || tool.description.toLowerCase().includes('type')
);
expect(typeTool).toBeDefined();
});
test('should validate text input', () => {
const typeTool = autoguiTools.find(tool =>
tool.name.includes('type') || tool.description.toLowerCase().includes('type')
);
if (typeTool) {
const schema = typeTool.inputSchema;
expect(schema.properties).toHaveProperty('text');
expect(schema.required).toContain('text');
}
});
test('should handle typing actions', async () => {
const typeTool = autoguiTools.find(tool =>
tool.name.includes('type') || tool.description.toLowerCase().includes('type')
);
if (typeTool) {
try {
const result = await typeTool.handler({ text: 'Hello World' });
expect(result).toHaveProperty('content');
} catch (error) {
// Acceptable if AutoGUI implementation is not available
expect(error).toBeDefined();
}
}
});
});
describe('Error handling', () => {
test('should handle AutoGUI initialization failures', async () => {
const { createAutoGUI } = factory;
createAutoGUI.mockRejectedValue(new Error('AutoGUI not available'));
const statusTool = autoguiTools.find(tool => tool.name === 'autogui_status');
try {
const result = await statusTool!.handler({});
// Should handle initialization failure gracefully
expect(result).toHaveProperty('content');
} catch (error) {
// Acceptable for AutoGUI tools when implementation is not available
expect(error).toBeDefined();
}
});
test('should handle missing implementations', async () => {
const { getAvailableAutoGUIImplementations } = factory;
getAvailableAutoGUIImplementations.mockResolvedValue([]);
const statusTool = autoguiTools.find(tool => tool.name === 'autogui_status');
try {
const result = await statusTool!.handler({});
expect(result).toHaveProperty('content');
} catch (error) {
expect(error).toBeDefined();
}
});
test('should handle invalid coordinates', async () => {
const clickTool = autoguiTools.find(tool =>
tool.name.includes('click') || tool.description.toLowerCase().includes('click')
);
if (clickTool) {
try {
// Test with negative coordinates
const result = await clickTool.handler({ x: -1, y: -1 });
if (!result.isError) {
// Tool might handle invalid coordinates gracefully
expect(result).toHaveProperty('content');
} else {
// Or it might return an error, which is also acceptable
expect(result.isError).toBe(true);
}
} catch (error) {
// Validation error is acceptable
expect(error).toBeDefined();
}
}
});
});
describe('Factory integration', () => {
test('should interact with factory functions', () => {
const { createAutoGUI, getAvailableAutoGUIImplementations, getAutoGUIImplementationStatus } = factory;
// Verify mocks are set up (indicates proper factory integration)
expect(createAutoGUI).toBeDefined();
expect(getAvailableAutoGUIImplementations).toBeDefined();
expect(getAutoGUIImplementationStatus).toBeDefined();
});
test('should cache AutoGUI adapter instance', async () => {
const statusTool = autoguiTools.find(tool => tool.name === 'autogui_status');
if (statusTool) {
// The status tool calls getAvailableAutoGUIImplementations and
// getAutoGUIImplementationStatus from the factory, which are mocked.
// Verify that repeated calls produce consistent results, confirming
// the factory integration works and mocks are properly wired.
const result1 = await statusTool.handler({});
const result2 = await statusTool.handler({});
expect(result1.content[0].text).toBe(result2.content[0].text);
const { getAvailableAutoGUIImplementations } = factory;
expect(getAvailableAutoGUIImplementations).toHaveBeenCalled();
}
});
});
describe('Tool categories', () => {
test('should provide system information tools', () => {
const systemTools = autoguiTools.filter(tool =>
tool.name.includes('status') ||
tool.description.toLowerCase().includes('status') ||
tool.description.toLowerCase().includes('info')
);
expect(systemTools.length).toBeGreaterThan(0);
});
test('should provide screen interaction tools', () => {
const screenTools = autoguiTools.filter(tool =>
tool.name.includes('screenshot') ||
tool.name.includes('click') ||
tool.name.includes('mouse') ||
tool.description.toLowerCase().includes('screen') ||
tool.description.toLowerCase().includes('mouse')
);
expect(screenTools.length).toBeGreaterThan(0);
});
test('should provide keyboard interaction tools', () => {
const keyboardTools = autoguiTools.filter(tool =>
tool.name.includes('type') ||
tool.name.includes('key') ||
tool.description.toLowerCase().includes('keyboard') ||
tool.description.toLowerCase().includes('type')
);
expect(keyboardTools.length).toBeGreaterThan(0);
});
});
});