openclaw-cascade-plugin 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PHASE1_SUMMARY.md +191 -0
- package/PHASE3_SUMMARY.md +195 -0
- package/README.md +43 -0
- package/dist/a2a-client.d.ts +17 -0
- package/dist/a2a-client.d.ts.map +1 -0
- package/dist/a2a-client.js +47 -0
- package/dist/a2a-client.js.map +1 -0
- package/dist/cascade-client.d.ts +53 -0
- package/dist/cascade-client.d.ts.map +1 -0
- package/dist/cascade-client.js +179 -0
- package/dist/cascade-client.js.map +1 -0
- package/dist/config.d.ts +26 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +116 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +136 -0
- package/dist/index.js.map +1 -0
- package/dist/python-manager.d.ts +59 -0
- package/dist/python-manager.d.ts.map +1 -0
- package/dist/python-manager.js +190 -0
- package/dist/python-manager.js.map +1 -0
- package/dist/test-utils/helpers.d.ts +20 -0
- package/dist/test-utils/helpers.d.ts.map +1 -0
- package/dist/test-utils/helpers.js +89 -0
- package/dist/test-utils/helpers.js.map +1 -0
- package/dist/test-utils/index.d.ts +3 -0
- package/dist/test-utils/index.d.ts.map +1 -0
- package/dist/test-utils/index.js +19 -0
- package/dist/test-utils/index.js.map +1 -0
- package/dist/test-utils/mocks.d.ts +51 -0
- package/dist/test-utils/mocks.d.ts.map +1 -0
- package/dist/test-utils/mocks.js +84 -0
- package/dist/test-utils/mocks.js.map +1 -0
- package/dist/tools/a2a-tools.d.ts +9 -0
- package/dist/tools/a2a-tools.d.ts.map +1 -0
- package/dist/tools/a2a-tools.js +147 -0
- package/dist/tools/a2a-tools.js.map +1 -0
- package/dist/tools/api-tools.d.ts +9 -0
- package/dist/tools/api-tools.d.ts.map +1 -0
- package/dist/tools/api-tools.js +102 -0
- package/dist/tools/api-tools.js.map +1 -0
- package/dist/tools/desktop-automation.d.ts +10 -0
- package/dist/tools/desktop-automation.d.ts.map +1 -0
- package/dist/tools/desktop-automation.js +330 -0
- package/dist/tools/desktop-automation.js.map +1 -0
- package/dist/tools/index.d.ts +12 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +35 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/response-helpers.d.ts +25 -0
- package/dist/tools/response-helpers.d.ts.map +1 -0
- package/dist/tools/response-helpers.js +71 -0
- package/dist/tools/response-helpers.js.map +1 -0
- package/dist/tools/sandbox-tools.d.ts +9 -0
- package/dist/tools/sandbox-tools.d.ts.map +1 -0
- package/dist/tools/sandbox-tools.js +79 -0
- package/dist/tools/sandbox-tools.js.map +1 -0
- package/dist/tools/tool-registry.d.ts +34 -0
- package/dist/tools/tool-registry.d.ts.map +1 -0
- package/dist/tools/tool-registry.js +50 -0
- package/dist/tools/tool-registry.js.map +1 -0
- package/dist/tools/web-automation.d.ts +9 -0
- package/dist/tools/web-automation.d.ts.map +1 -0
- package/dist/tools/web-automation.js +471 -0
- package/dist/tools/web-automation.js.map +1 -0
- package/dist/types/index.d.ts +111 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +38 -0
- package/dist/types/index.js.map +1 -0
- package/jest.setup.js +19 -0
- package/openclaw-cascade-plugin-1.0.0.tgz +0 -0
- package/openclaw.plugin.json +116 -0
- package/package.json +74 -0
- package/src/a2a-client.ts +66 -0
- package/src/cascade-client.test.ts +400 -0
- package/src/cascade-client.ts +198 -0
- package/src/config.test.ts +194 -0
- package/src/config.ts +135 -0
- package/src/index.ts +164 -0
- package/src/python-manager.test.ts +187 -0
- package/src/python-manager.ts +230 -0
- package/src/test-utils/helpers.ts +107 -0
- package/src/test-utils/index.ts +2 -0
- package/src/test-utils/mocks.ts +101 -0
- package/src/tools/a2a-tools.ts +162 -0
- package/src/tools/api-tools.ts +110 -0
- package/src/tools/desktop-automation.test.ts +305 -0
- package/src/tools/desktop-automation.ts +366 -0
- package/src/tools/index.ts +13 -0
- package/src/tools/response-helpers.ts +78 -0
- package/src/tools/sandbox-tools.ts +83 -0
- package/src/tools/tool-registry.ts +51 -0
- package/src/tools/web-automation.test.ts +177 -0
- package/src/tools/web-automation.ts +518 -0
- package/src/types/index.ts +132 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base tool response helpers
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ToolResponse } from '../types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create a successful text response
|
|
9
|
+
*/
|
|
10
|
+
export function successResponse(text: string): ToolResponse {
|
|
11
|
+
return {
|
|
12
|
+
content: [{ type: 'text', text }]
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create a successful JSON response
|
|
18
|
+
*/
|
|
19
|
+
export function jsonResponse(data: any): ToolResponse {
|
|
20
|
+
return {
|
|
21
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }]
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create an image response with optional text
|
|
27
|
+
*/
|
|
28
|
+
export function imageResponse(
|
|
29
|
+
base64Data: string,
|
|
30
|
+
format: 'png' | 'jpeg' = 'png',
|
|
31
|
+
text?: string
|
|
32
|
+
): ToolResponse {
|
|
33
|
+
const content: any[] = [
|
|
34
|
+
{
|
|
35
|
+
type: 'image',
|
|
36
|
+
source: {
|
|
37
|
+
type: 'base64',
|
|
38
|
+
media_type: `image/${format}`,
|
|
39
|
+
data: base64Data
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
if (text) {
|
|
45
|
+
content.push({ type: 'text', text });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { content };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Create an error response
|
|
53
|
+
*/
|
|
54
|
+
export function errorResponse(message: string, suggestion?: string): ToolResponse {
|
|
55
|
+
const response: any = {
|
|
56
|
+
success: false,
|
|
57
|
+
error: message
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
if (suggestion) {
|
|
61
|
+
response.suggestion = suggestion;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
|
|
66
|
+
isError: true
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Format a successful tool result
|
|
72
|
+
*/
|
|
73
|
+
export function formatSuccess(result: any): ToolResponse {
|
|
74
|
+
return jsonResponse({
|
|
75
|
+
success: true,
|
|
76
|
+
...result
|
|
77
|
+
});
|
|
78
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox Tools
|
|
3
|
+
*
|
|
4
|
+
* 1 tool for executing Python code in sandbox
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ToolRegistry } from './tool-registry';
|
|
8
|
+
import { CascadeMcpClient } from '../cascade-client';
|
|
9
|
+
import { ToolResponse } from '../types';
|
|
10
|
+
import { errorResponse, formatSuccess } from './response-helpers';
|
|
11
|
+
|
|
12
|
+
export function registerSandboxTools(registry: ToolRegistry, client: CascadeMcpClient): void {
|
|
13
|
+
registry.register({
|
|
14
|
+
name: 'cascade_execute_sandbox_skill',
|
|
15
|
+
description: 'Execute a Python sandbox skill for programmatic file automation',
|
|
16
|
+
inputSchema: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
properties: {
|
|
19
|
+
skill_id: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
description: 'Skill ID to execute'
|
|
22
|
+
},
|
|
23
|
+
task: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'Task description to guide execution'
|
|
26
|
+
},
|
|
27
|
+
inputs: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
description: 'Runtime inputs for the operation',
|
|
30
|
+
additionalProperties: true
|
|
31
|
+
},
|
|
32
|
+
files: {
|
|
33
|
+
type: 'array',
|
|
34
|
+
description: 'Files to copy in/out of sandbox',
|
|
35
|
+
items: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
properties: {
|
|
38
|
+
name: { type: 'string' },
|
|
39
|
+
local_path: { type: 'string' },
|
|
40
|
+
sandbox_path: { type: 'string' },
|
|
41
|
+
mode: { type: 'string', enum: ['input', 'output', 'inout'] }
|
|
42
|
+
},
|
|
43
|
+
required: ['name', 'local_path', 'mode']
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
local_path: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
description: 'Primary file path (convenience when using single file)'
|
|
49
|
+
},
|
|
50
|
+
python_code: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
description: 'Optional Python code to execute (auto-generated if not provided)'
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
required: ['skill_id', 'task']
|
|
56
|
+
},
|
|
57
|
+
handler: async (args): Promise<ToolResponse> => {
|
|
58
|
+
try {
|
|
59
|
+
if (!args.skill_id) {
|
|
60
|
+
return errorResponse('skill_id is required');
|
|
61
|
+
}
|
|
62
|
+
if (!args.task) {
|
|
63
|
+
return errorResponse('task is required');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const result = await client.callTool('execute_sandbox_skill', {
|
|
67
|
+
skill_id: args.skill_id,
|
|
68
|
+
task: args.task,
|
|
69
|
+
inputs: args.inputs || {},
|
|
70
|
+
files: args.files || [],
|
|
71
|
+
local_path: args.local_path,
|
|
72
|
+
python_code: args.python_code
|
|
73
|
+
});
|
|
74
|
+
return formatSuccess(result);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return errorResponse(
|
|
77
|
+
error instanceof Error ? error.message : 'Failed to execute sandbox skill',
|
|
78
|
+
'Check that the skill exists and E2B_API_KEY is set'
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Registry for managing OpenClaw tools
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ToolSchema, ToolHandler, ToolResponse } from '../types';
|
|
6
|
+
|
|
7
|
+
export class ToolRegistry {
|
|
8
|
+
private tools = new Map<string, ToolSchema & { handler: ToolHandler }>();
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Register a tool with the registry
|
|
12
|
+
*/
|
|
13
|
+
register(tool: ToolSchema & { handler: ToolHandler }): void {
|
|
14
|
+
if (this.tools.has(tool.name)) {
|
|
15
|
+
throw new Error(`Tool ${tool.name} is already registered`);
|
|
16
|
+
}
|
|
17
|
+
this.tools.set(tool.name, tool);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get all registered tools
|
|
22
|
+
*/
|
|
23
|
+
getAll(): Array<ToolSchema & { handler: ToolHandler }> {
|
|
24
|
+
return Array.from(this.tools.values());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get a specific tool by name
|
|
29
|
+
*/
|
|
30
|
+
get(name: string): (ToolSchema & { handler: ToolHandler }) | undefined {
|
|
31
|
+
return this.tools.get(name);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if a tool exists
|
|
36
|
+
*/
|
|
37
|
+
has(name: string): boolean {
|
|
38
|
+
return this.tools.has(name);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Call a tool by name with arguments
|
|
43
|
+
*/
|
|
44
|
+
async call(name: string, args: Record<string, any>): Promise<ToolResponse> {
|
|
45
|
+
const tool = this.get(name);
|
|
46
|
+
if (!tool) {
|
|
47
|
+
throw new Error(`Tool ${name} not found`);
|
|
48
|
+
}
|
|
49
|
+
return tool.handler(args);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Web Automation Tools (Playwright)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { registerWebTools } from './web-automation';
|
|
6
|
+
import { ToolRegistry } from './tool-registry';
|
|
7
|
+
import { MockCascadeMcpClient } from '../test-utils';
|
|
8
|
+
|
|
9
|
+
describe('Web Automation Tools', () => {
|
|
10
|
+
let registry: ToolRegistry;
|
|
11
|
+
let mockClient: MockCascadeMcpClient;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
registry = new ToolRegistry();
|
|
15
|
+
mockClient = new MockCascadeMcpClient();
|
|
16
|
+
registerWebTools(registry, mockClient as any);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('Navigation Tools', () => {
|
|
20
|
+
test('cascade_pw_goto should navigate to URL', async () => {
|
|
21
|
+
mockClient.registerMockTool('pw_goto', () => ({ success: true }));
|
|
22
|
+
await registry.call('cascade_pw_goto', { url: 'https://example.com' });
|
|
23
|
+
expect(mockClient.callTool).toHaveBeenCalledWith('pw_goto', {
|
|
24
|
+
url: 'https://example.com',
|
|
25
|
+
wait_until: 'networkidle'
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('cascade_pw_back should go back', async () => {
|
|
30
|
+
mockClient.registerMockTool('pw_back', () => ({ success: true }));
|
|
31
|
+
await registry.call('cascade_pw_back', {});
|
|
32
|
+
expect(mockClient.callTool).toHaveBeenCalledWith('pw_back', {});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('cascade_pw_forward should go forward', async () => {
|
|
36
|
+
mockClient.registerMockTool('pw_forward', () => ({ success: true }));
|
|
37
|
+
await registry.call('cascade_pw_forward', {});
|
|
38
|
+
expect(mockClient.callTool).toHaveBeenCalledWith('pw_forward', {});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('cascade_pw_reload should reload page', async () => {
|
|
42
|
+
mockClient.registerMockTool('pw_reload', () => ({ success: true }));
|
|
43
|
+
await registry.call('cascade_pw_reload', {});
|
|
44
|
+
expect(mockClient.callTool).toHaveBeenCalledWith('pw_reload', { wait_until: 'networkidle' });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('cascade_pw_wait_for_url should wait for URL', async () => {
|
|
48
|
+
mockClient.registerMockTool('pw_wait_for_url', () => ({ success: true }));
|
|
49
|
+
await registry.call('cascade_pw_wait_for_url', { url: '**/success' });
|
|
50
|
+
expect(mockClient.callTool).toHaveBeenCalledWith('pw_wait_for_url', {
|
|
51
|
+
url: '**/success',
|
|
52
|
+
timeout_ms: 10000
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('Locator Tools', () => {
|
|
58
|
+
test('cascade_pw_locator_count should count elements', async () => {
|
|
59
|
+
mockClient.registerMockTool('pw_locator_count', () => ({ count: 5 }));
|
|
60
|
+
const result = await registry.call('cascade_pw_locator_count', { selector: '.item' });
|
|
61
|
+
expect(mockClient.callTool).toHaveBeenCalledWith('pw_locator_count', { selector: '.item' });
|
|
62
|
+
expect(result.content[0].text).toContain('5');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('cascade_pw_locator_text should get element text', async () => {
|
|
66
|
+
mockClient.registerMockTool('pw_locator_text', () => ({ text: 'Hello World' }));
|
|
67
|
+
const result = await registry.call('cascade_pw_locator_text', { selector: 'h1' });
|
|
68
|
+
expect(result.content[0].text).toContain('Hello World');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('Interaction Tools', () => {
|
|
73
|
+
test('cascade_pw_click should click element', async () => {
|
|
74
|
+
mockClient.registerMockTool('pw_click', () => ({ success: true }));
|
|
75
|
+
await registry.call('cascade_pw_click', { selector: '#submit' });
|
|
76
|
+
expect(mockClient.callTool).toHaveBeenCalledWith('pw_click', {
|
|
77
|
+
selector: '#submit',
|
|
78
|
+
timeout_ms: 8000
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('cascade_pw_fill should fill form field', async () => {
|
|
83
|
+
mockClient.registerMockTool('pw_fill', () => ({ success: true }));
|
|
84
|
+
await registry.call('cascade_pw_fill', { selector: '#email', text: 'test@example.com' });
|
|
85
|
+
expect(mockClient.callTool).toHaveBeenCalledWith('pw_fill', {
|
|
86
|
+
selector: '#email',
|
|
87
|
+
text: 'test@example.com',
|
|
88
|
+
timeout_ms: 8000
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('cascade_pw_fill should require text', async () => {
|
|
93
|
+
const result = await registry.call('cascade_pw_fill', { selector: '#email' });
|
|
94
|
+
expect(result.isError).toBe(true);
|
|
95
|
+
expect(result.content[0].text).toContain('text');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('cascade_pw_press should press key', async () => {
|
|
99
|
+
mockClient.registerMockTool('pw_press', () => ({ success: true }));
|
|
100
|
+
await registry.call('cascade_pw_press', { selector: '#search', key: 'Enter' });
|
|
101
|
+
expect(mockClient.callTool).toHaveBeenCalledWith('pw_press', {
|
|
102
|
+
selector: '#search',
|
|
103
|
+
key: 'Enter',
|
|
104
|
+
timeout_ms: 8000
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('cascade_pw_select_option should select dropdown option', async () => {
|
|
109
|
+
mockClient.registerMockTool('pw_select_option', () => ({ selected: ['option1'] }));
|
|
110
|
+
const result = await registry.call('cascade_pw_select_option', {
|
|
111
|
+
selector: '#country',
|
|
112
|
+
values: ['USA', 'Canada']
|
|
113
|
+
});
|
|
114
|
+
expect(result.content[0].text).toContain('selected');
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('Evaluation Tools', () => {
|
|
119
|
+
test('cascade_pw_eval should evaluate JavaScript', async () => {
|
|
120
|
+
mockClient.registerMockTool('pw_eval', () => ({ result: { title: 'Page Title' } }));
|
|
121
|
+
const result = await registry.call('cascade_pw_eval', {
|
|
122
|
+
expression: 'document.title'
|
|
123
|
+
});
|
|
124
|
+
expect(result.content[0].text).toContain('Page Title');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('cascade_pw_eval_on_selector should evaluate on element', async () => {
|
|
128
|
+
mockClient.registerMockTool('pw_eval_on_selector', () => ({ result: 'Hello' }));
|
|
129
|
+
const result = await registry.call('cascade_pw_eval_on_selector', {
|
|
130
|
+
selector: 'h1',
|
|
131
|
+
expression: 'el => el.textContent'
|
|
132
|
+
});
|
|
133
|
+
expect(result.content[0].text).toContain('Hello');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('cascade_pw_list_frames should list frames', async () => {
|
|
137
|
+
mockClient.registerMockTool('pw_list_frames', () => ({
|
|
138
|
+
frames: [
|
|
139
|
+
{ name: 'main', url: 'https://example.com' },
|
|
140
|
+
{ name: 'iframe1', url: 'https://example.com/frame' }
|
|
141
|
+
]
|
|
142
|
+
}));
|
|
143
|
+
const result = await registry.call('cascade_pw_list_frames', {});
|
|
144
|
+
expect(result.content[0].text).toContain('main');
|
|
145
|
+
expect(result.content[0].text).toContain('iframe1');
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('Storage Tools', () => {
|
|
150
|
+
test('cascade_pw_get_cookies should return cookies', async () => {
|
|
151
|
+
mockClient.registerMockTool('pw_get_cookies', () => ({
|
|
152
|
+
cookies: [
|
|
153
|
+
{ name: 'session', value: 'abc123', domain: '.example.com' }
|
|
154
|
+
]
|
|
155
|
+
}));
|
|
156
|
+
const result = await registry.call('cascade_pw_get_cookies', {});
|
|
157
|
+
expect(result.content[0].text).toContain('session');
|
|
158
|
+
expect(result.content[0].text).toContain('abc123');
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('Error Handling', () => {
|
|
163
|
+
test('should handle element not found', async () => {
|
|
164
|
+
mockClient.simulateError(new Error('Element not found'));
|
|
165
|
+
const result = await registry.call('cascade_pw_click', { selector: '#missing' });
|
|
166
|
+
expect(result.isError).toBe(true);
|
|
167
|
+
expect(result.content[0].text).toContain('Element not found');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('should handle timeout', async () => {
|
|
171
|
+
mockClient.simulateError(new Error('Timeout exceeded'));
|
|
172
|
+
const result = await registry.call('cascade_pw_wait_for_url', { url: '**/slow' });
|
|
173
|
+
expect(result.isError).toBe(true);
|
|
174
|
+
expect(result.content[0].text).toContain('Timeout exceeded');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|