mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 18:18:43 +00:00
test(browser): 补充 Phase 6B 边界覆盖并同步文档
补齐 fulfill 参数校验与页面关闭后的状态清理回归测试,确保 mock response 行为在边界条件下可验证。 同步 Browser Automation 文档,明确 Phase 6B 的 fulfill 能力与 session-local 语义。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
f133deb539
commit
447b5c8bc1
@@ -57,12 +57,15 @@ Phase 5 capability details:
|
|||||||
- existing tools still honor explicit `pageIndex`; when omitted, they resolve through the selected page
|
- existing tools still honor explicit `pageIndex`; when omitted, they resolve through the selected page
|
||||||
- selected page state is session-local MCP runtime state and is not persisted across runtime restarts
|
- selected page state is session-local MCP runtime state and is not persisted across runtime restarts
|
||||||
|
|
||||||
Phase 6A capability details:
|
Phase 6 capability details:
|
||||||
|
|
||||||
- interception rules are session-local and bind to the concrete page selected when the rule is created
|
- interception rules are session-local and bind to the concrete page selected when the rule is created
|
||||||
- Phase 6A supports minimal request matching by `urlIncludes` and `method`
|
- Phase 6 supports minimal request matching by `urlIncludes` and `method`
|
||||||
- Phase 6A actions are limited to `continue` and `fail`
|
- Phase 6A actions are limited to `continue` and `fail`
|
||||||
|
- Phase 6B adds `fulfill` mock responses on top of the existing session-local interception model
|
||||||
|
- fulfill rules can return a custom status code, optional headers, and a UTF-8 response body
|
||||||
- `browser_list_requests` returns recent request summaries, not full bodies
|
- `browser_list_requests` returns recent request summaries, not full bodies
|
||||||
|
- response bodies are applied only to the matched request and are not persisted beyond the current MCP session
|
||||||
|
|
||||||
Minimal multi-tab workflow examples:
|
Minimal multi-tab workflow examples:
|
||||||
|
|
||||||
@@ -88,9 +91,12 @@ Minimal multi-tab workflow examples:
|
|||||||
{
|
{
|
||||||
"name": "browser_add_intercept_rule",
|
"name": "browser_add_intercept_rule",
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"urlIncludes": "/api",
|
"urlIncludes": "/api/mock/users",
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"action": "fail"
|
"action": "fulfill",
|
||||||
|
"statusCode": 200,
|
||||||
|
"contentType": "application/json",
|
||||||
|
"body": "{\"users\":[1]}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -107,7 +113,7 @@ Scoped selector notes:
|
|||||||
|
|
||||||
- `browser_click`, `browser_hover`, `browser_query`, `browser_wait_for`, and `browser_take_element_screenshot` accept optional `frameSelector` for same-origin iframes whose `contentDocument` is accessible
|
- `browser_click`, `browser_hover`, `browser_query`, `browser_wait_for`, and `browser_take_element_screenshot` accept optional `frameSelector` for same-origin iframes whose `contentDocument` is accessible
|
||||||
- the same selector-based tools accept optional `pierceShadow: true` for open shadow-root traversal
|
- the same selector-based tools accept optional `pierceShadow: true` for open shadow-root traversal
|
||||||
- closed shadow roots, frame-index routing, response mocking, and download acceptance controls are still out of scope
|
- closed shadow roots, frame-index routing, richer request matching, and download acceptance controls are still out of scope
|
||||||
|
|
||||||
Example event wait:
|
Example event wait:
|
||||||
|
|
||||||
|
|||||||
@@ -2199,6 +2199,155 @@ describe('ccs-browser MCP server', () => {
|
|||||||
expect(addText).toContain('statusCode: 204');
|
expect(addText).toContain('statusCode: 204');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('rejects fulfill rules when statusCode is out of range', async () => {
|
||||||
|
const responses = await runMcpRequests(
|
||||||
|
[{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
id: 968,
|
||||||
|
method: 'tools/call',
|
||||||
|
params: {
|
||||||
|
name: 'browser_add_intercept_rule',
|
||||||
|
arguments: {
|
||||||
|
urlIncludes: '/api/mock',
|
||||||
|
action: 'fulfill',
|
||||||
|
statusCode: 99,
|
||||||
|
body: 'x',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = responses.find((message) => message.id === 968);
|
||||||
|
expect((response?.result as { isError?: boolean }).isError).toBe(true);
|
||||||
|
expect(getResponseText(response)).toContain('Browser MCP failed: statusCode must be an integer between 100 and 599');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects fulfill rules when headers is not an array', async () => {
|
||||||
|
const responses = await runMcpRequests(
|
||||||
|
[{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
id: 969,
|
||||||
|
method: 'tools/call',
|
||||||
|
params: {
|
||||||
|
name: 'browser_add_intercept_rule',
|
||||||
|
arguments: {
|
||||||
|
urlIncludes: '/api/mock',
|
||||||
|
action: 'fulfill',
|
||||||
|
headers: 'x',
|
||||||
|
body: 'x',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = responses.find((message) => message.id === 969);
|
||||||
|
expect((response?.result as { isError?: boolean }).isError).toBe(true);
|
||||||
|
expect(getResponseText(response)).toContain('Browser MCP failed: headers must be an array');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects fulfill rules when a header entry is missing name', async () => {
|
||||||
|
const responses = await runMcpRequests(
|
||||||
|
[{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
id: 970,
|
||||||
|
method: 'tools/call',
|
||||||
|
params: {
|
||||||
|
name: 'browser_add_intercept_rule',
|
||||||
|
arguments: {
|
||||||
|
urlIncludes: '/api/mock',
|
||||||
|
action: 'fulfill',
|
||||||
|
headers: [{ value: 'x' }],
|
||||||
|
body: 'x',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = responses.find((message) => message.id === 970);
|
||||||
|
expect((response?.result as { isError?: boolean }).isError).toBe(true);
|
||||||
|
expect(getResponseText(response)).toContain('Browser MCP failed: headers.name is required');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects fulfill rules when body is not a string', async () => {
|
||||||
|
const responses = await runMcpRequests(
|
||||||
|
[{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
id: 971,
|
||||||
|
method: 'tools/call',
|
||||||
|
params: {
|
||||||
|
name: 'browser_add_intercept_rule',
|
||||||
|
arguments: {
|
||||||
|
urlIncludes: '/api/mock',
|
||||||
|
action: 'fulfill',
|
||||||
|
body: 123,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = responses.find((message) => message.id === 971);
|
||||||
|
expect((response?.result as { isError?: boolean }).isError).toBe(true);
|
||||||
|
expect(getResponseText(response)).toContain('Browser MCP failed: body must be a string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes fulfill rules and request summaries after the bound page is closed', async () => {
|
||||||
|
const responses = await runMcpRequests(
|
||||||
|
[
|
||||||
|
{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' },
|
||||||
|
{
|
||||||
|
id: 'page-2',
|
||||||
|
title: 'Mocked',
|
||||||
|
currentUrl: 'https://example.com/mocked',
|
||||||
|
intercept: {
|
||||||
|
pausedRequests: [
|
||||||
|
{ requestId: 'req-fulfill-close', url: 'https://example.com/api/mock/close', method: 'GET' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ jsonrpc: '2.0', id: 972, method: 'tools/call', params: { name: 'browser_select_page', arguments: { pageIndex: 1 } } },
|
||||||
|
{
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
id: 973,
|
||||||
|
method: 'tools/call',
|
||||||
|
params: {
|
||||||
|
name: 'browser_add_intercept_rule',
|
||||||
|
arguments: {
|
||||||
|
urlIncludes: '/api/mock/close',
|
||||||
|
action: 'fulfill',
|
||||||
|
statusCode: 200,
|
||||||
|
body: 'done',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ jsonrpc: '2.0', id: 974, method: 'tools/call', params: { name: 'browser_list_requests', arguments: {} } },
|
||||||
|
{ jsonrpc: '2.0', id: 975, method: 'tools/call', params: { name: 'browser_close_page', arguments: { pageId: 'page-2' } } },
|
||||||
|
{ jsonrpc: '2.0', id: 976, method: 'tools/call', params: { name: 'browser_list_intercept_rules', arguments: {} } },
|
||||||
|
{ jsonrpc: '2.0', id: 977, method: 'tools/call', params: { name: 'browser_list_requests', arguments: {} } },
|
||||||
|
],
|
||||||
|
{
|
||||||
|
responseTimeoutMs: 12000,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getResponseText(responses.find((message) => message.id === 974))).toContain('requestId: req-fulfill-close');
|
||||||
|
expect(getResponseText(responses.find((message) => message.id === 976))).not.toContain('pageId: page-2');
|
||||||
|
expect(getResponseText(responses.find((message) => message.id === 977))).not.toContain('requestId: req-fulfill-close');
|
||||||
|
});
|
||||||
|
|
||||||
it('returns only the requested number of recent requests', async () => {
|
it('returns only the requested number of recent requests', async () => {
|
||||||
const responses = await runMcpRequests(
|
const responses = await runMcpRequests(
|
||||||
[
|
[
|
||||||
|
|||||||
Reference in New Issue
Block a user