@cotestdev/ai-runner 0.0.5 → 0.0.7

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.
Files changed (164) hide show
  1. package/.github/workflows/playwright.yml +27 -0
  2. package/dist/agents/logger.d.ts.map +1 -0
  3. package/dist/agents/logger.js.map +1 -0
  4. package/dist/{ai-runner/src/agents → agents}/playwright-executor.d.ts +3 -1
  5. package/dist/agents/playwright-executor.d.ts.map +1 -0
  6. package/dist/{ai-runner/src/agents → agents}/playwright-executor.js +74 -18
  7. package/dist/agents/playwright-executor.js.map +1 -0
  8. package/dist/agents/tools/playwright-backend-adapter.d.ts.map +1 -0
  9. package/dist/{ai-runner/src/agents → agents}/tools/playwright-backend-adapter.js +2 -1
  10. package/dist/agents/tools/playwright-backend-adapter.js.map +1 -0
  11. package/dist/agents/types.d.ts.map +1 -0
  12. package/dist/{core-infra/src → agents}/types.js.map +1 -1
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/{ai-runner/src/runner.d.ts → runner.d.ts} +7 -2
  16. package/dist/runner.d.ts.map +1 -0
  17. package/dist/{ai-runner/src/runner.js → runner.js} +23 -12
  18. package/dist/runner.js.map +1 -0
  19. package/dist/tools/index.d.ts.map +1 -0
  20. package/dist/tools/index.js.map +1 -0
  21. package/dist/tools/playwright-groups.d.ts.map +1 -0
  22. package/dist/tools/playwright-groups.js.map +1 -0
  23. package/dist/types/external.d.ts.map +1 -0
  24. package/dist/types/external.js.map +1 -0
  25. package/dist/types/index.d.ts.map +1 -0
  26. package/dist/types/index.js.map +1 -0
  27. package/package.json +5 -9
  28. package/playwright.config.ts +38 -0
  29. package/src/agents/logger.ts +20 -0
  30. package/src/agents/playwright-executor.ts +284 -0
  31. package/src/agents/tools/playwright-backend-adapter.ts +135 -0
  32. package/src/agents/tools/playwright-mcp-types.d.ts +72 -0
  33. package/src/agents/types.ts +80 -0
  34. package/src/index.ts +27 -0
  35. package/src/runner.ts +237 -0
  36. package/src/tools/index.ts +48 -0
  37. package/src/tools/playwright-groups.ts +54 -0
  38. package/src/types/external.ts +7 -0
  39. package/src/types/index.ts +118 -0
  40. package/tests/agent/playwright.config.ts +32 -0
  41. package/tests/agent/test-heal-agent.spec.ts +42 -0
  42. package/tsconfig.json +26 -0
  43. package/dist/ai-runner/src/agents/logger.d.ts.map +0 -1
  44. package/dist/ai-runner/src/agents/logger.js.map +0 -1
  45. package/dist/ai-runner/src/agents/playwright-executor.d.ts.map +0 -1
  46. package/dist/ai-runner/src/agents/playwright-executor.js.map +0 -1
  47. package/dist/ai-runner/src/agents/tools/playwright-backend-adapter.d.ts.map +0 -1
  48. package/dist/ai-runner/src/agents/tools/playwright-backend-adapter.js.map +0 -1
  49. package/dist/ai-runner/src/agents/types.d.ts.map +0 -1
  50. package/dist/ai-runner/src/agents/types.js.map +0 -1
  51. package/dist/ai-runner/src/index.d.ts.map +0 -1
  52. package/dist/ai-runner/src/index.js.map +0 -1
  53. package/dist/ai-runner/src/runner.d.ts.map +0 -1
  54. package/dist/ai-runner/src/runner.js.map +0 -1
  55. package/dist/ai-runner/src/tools/index.d.ts.map +0 -1
  56. package/dist/ai-runner/src/tools/index.js.map +0 -1
  57. package/dist/ai-runner/src/tools/playwright-groups.d.ts.map +0 -1
  58. package/dist/ai-runner/src/tools/playwright-groups.js.map +0 -1
  59. package/dist/ai-runner/src/types/external.d.ts.map +0 -1
  60. package/dist/ai-runner/src/types/external.js.map +0 -1
  61. package/dist/ai-runner/src/types/index.d.ts.map +0 -1
  62. package/dist/ai-runner/src/types/index.js.map +0 -1
  63. package/dist/core-infra/src/directory-validator.d.ts +0 -29
  64. package/dist/core-infra/src/directory-validator.d.ts.map +0 -1
  65. package/dist/core-infra/src/directory-validator.js +0 -91
  66. package/dist/core-infra/src/directory-validator.js.map +0 -1
  67. package/dist/core-infra/src/index.d.ts +0 -14
  68. package/dist/core-infra/src/index.d.ts.map +0 -1
  69. package/dist/core-infra/src/index.js +0 -44
  70. package/dist/core-infra/src/index.js.map +0 -1
  71. package/dist/core-infra/src/mcp/file-mcp-manager.d.ts +0 -14
  72. package/dist/core-infra/src/mcp/file-mcp-manager.d.ts.map +0 -1
  73. package/dist/core-infra/src/mcp/file-mcp-manager.js +0 -46
  74. package/dist/core-infra/src/mcp/file-mcp-manager.js.map +0 -1
  75. package/dist/core-infra/src/mcp/index.d.ts +0 -21
  76. package/dist/core-infra/src/mcp/index.d.ts.map +0 -1
  77. package/dist/core-infra/src/mcp/index.js +0 -17
  78. package/dist/core-infra/src/mcp/index.js.map +0 -1
  79. package/dist/core-infra/src/mcp/mcp-client.d.ts +0 -128
  80. package/dist/core-infra/src/mcp/mcp-client.d.ts.map +0 -1
  81. package/dist/core-infra/src/mcp/mcp-client.js +0 -163
  82. package/dist/core-infra/src/mcp/mcp-client.js.map +0 -1
  83. package/dist/core-infra/src/mcp/mcp-manager.d.ts +0 -21
  84. package/dist/core-infra/src/mcp/mcp-manager.d.ts.map +0 -1
  85. package/dist/core-infra/src/mcp/mcp-manager.js +0 -99
  86. package/dist/core-infra/src/mcp/mcp-manager.js.map +0 -1
  87. package/dist/core-infra/src/mcp/playwright-mcp-manager.d.ts +0 -19
  88. package/dist/core-infra/src/mcp/playwright-mcp-manager.d.ts.map +0 -1
  89. package/dist/core-infra/src/mcp/playwright-mcp-manager.js +0 -121
  90. package/dist/core-infra/src/mcp/playwright-mcp-manager.js.map +0 -1
  91. package/dist/core-infra/src/model.d.ts +0 -13
  92. package/dist/core-infra/src/model.d.ts.map +0 -1
  93. package/dist/core-infra/src/model.js +0 -191
  94. package/dist/core-infra/src/model.js.map +0 -1
  95. package/dist/core-infra/src/repositories/BaseRepository.d.ts +0 -69
  96. package/dist/core-infra/src/repositories/BaseRepository.d.ts.map +0 -1
  97. package/dist/core-infra/src/repositories/BaseRepository.js +0 -213
  98. package/dist/core-infra/src/repositories/BaseRepository.js.map +0 -1
  99. package/dist/core-infra/src/repositories/DirectoryRepository.d.ts +0 -70
  100. package/dist/core-infra/src/repositories/DirectoryRepository.d.ts.map +0 -1
  101. package/dist/core-infra/src/repositories/DirectoryRepository.js +0 -336
  102. package/dist/core-infra/src/repositories/DirectoryRepository.js.map +0 -1
  103. package/dist/core-infra/src/repositories/ExplorationRepository.d.ts +0 -34
  104. package/dist/core-infra/src/repositories/ExplorationRepository.d.ts.map +0 -1
  105. package/dist/core-infra/src/repositories/ExplorationRepository.js +0 -54
  106. package/dist/core-infra/src/repositories/ExplorationRepository.js.map +0 -1
  107. package/dist/core-infra/src/repositories/FileRepository.d.ts +0 -56
  108. package/dist/core-infra/src/repositories/FileRepository.d.ts.map +0 -1
  109. package/dist/core-infra/src/repositories/FileRepository.js +0 -132
  110. package/dist/core-infra/src/repositories/FileRepository.js.map +0 -1
  111. package/dist/core-infra/src/repositories/ModelConfigRepository.d.ts +0 -38
  112. package/dist/core-infra/src/repositories/ModelConfigRepository.d.ts.map +0 -1
  113. package/dist/core-infra/src/repositories/ModelConfigRepository.js +0 -59
  114. package/dist/core-infra/src/repositories/ModelConfigRepository.js.map +0 -1
  115. package/dist/core-infra/src/repositories/ProjectRepository.d.ts +0 -32
  116. package/dist/core-infra/src/repositories/ProjectRepository.d.ts.map +0 -1
  117. package/dist/core-infra/src/repositories/ProjectRepository.js +0 -67
  118. package/dist/core-infra/src/repositories/ProjectRepository.js.map +0 -1
  119. package/dist/core-infra/src/repositories/SettingsRepository.d.ts +0 -19
  120. package/dist/core-infra/src/repositories/SettingsRepository.d.ts.map +0 -1
  121. package/dist/core-infra/src/repositories/SettingsRepository.js +0 -72
  122. package/dist/core-infra/src/repositories/SettingsRepository.js.map +0 -1
  123. package/dist/core-infra/src/repositories/TableDataRepository.d.ts +0 -22
  124. package/dist/core-infra/src/repositories/TableDataRepository.d.ts.map +0 -1
  125. package/dist/core-infra/src/repositories/TableDataRepository.js +0 -33
  126. package/dist/core-infra/src/repositories/TableDataRepository.js.map +0 -1
  127. package/dist/core-infra/src/repositories/TestCaseRepository.d.ts +0 -120
  128. package/dist/core-infra/src/repositories/TestCaseRepository.d.ts.map +0 -1
  129. package/dist/core-infra/src/repositories/TestCaseRepository.js +0 -463
  130. package/dist/core-infra/src/repositories/TestCaseRepository.js.map +0 -1
  131. package/dist/core-infra/src/repositories/TestPlanRepository.d.ts +0 -35
  132. package/dist/core-infra/src/repositories/TestPlanRepository.d.ts.map +0 -1
  133. package/dist/core-infra/src/repositories/TestPlanRepository.js +0 -80
  134. package/dist/core-infra/src/repositories/TestPlanRepository.js.map +0 -1
  135. package/dist/core-infra/src/repositories/TestResultRepository.d.ts +0 -30
  136. package/dist/core-infra/src/repositories/TestResultRepository.d.ts.map +0 -1
  137. package/dist/core-infra/src/repositories/TestResultRepository.js +0 -54
  138. package/dist/core-infra/src/repositories/TestResultRepository.js.map +0 -1
  139. package/dist/core-infra/src/repositories/index.d.ts +0 -17
  140. package/dist/core-infra/src/repositories/index.d.ts.map +0 -1
  141. package/dist/core-infra/src/repositories/index.js +0 -31
  142. package/dist/core-infra/src/repositories/index.js.map +0 -1
  143. package/dist/core-infra/src/storageService.d.ts +0 -131
  144. package/dist/core-infra/src/storageService.d.ts.map +0 -1
  145. package/dist/core-infra/src/storageService.js +0 -304
  146. package/dist/core-infra/src/storageService.js.map +0 -1
  147. package/dist/core-infra/src/types.d.ts +0 -219
  148. package/dist/core-infra/src/types.d.ts.map +0 -1
  149. package/dist/core-infra/src/types.js +0 -3
  150. /package/dist/{ai-runner/src/agents → agents}/logger.d.ts +0 -0
  151. /package/dist/{ai-runner/src/agents → agents}/logger.js +0 -0
  152. /package/dist/{ai-runner/src/agents → agents}/tools/playwright-backend-adapter.d.ts +0 -0
  153. /package/dist/{ai-runner/src/agents → agents}/types.d.ts +0 -0
  154. /package/dist/{ai-runner/src/agents → agents}/types.js +0 -0
  155. /package/dist/{ai-runner/src/index.d.ts → index.d.ts} +0 -0
  156. /package/dist/{ai-runner/src/index.js → index.js} +0 -0
  157. /package/dist/{ai-runner/src/tools → tools}/index.d.ts +0 -0
  158. /package/dist/{ai-runner/src/tools → tools}/index.js +0 -0
  159. /package/dist/{ai-runner/src/tools → tools}/playwright-groups.d.ts +0 -0
  160. /package/dist/{ai-runner/src/tools → tools}/playwright-groups.js +0 -0
  161. /package/dist/{ai-runner/src/types → types}/external.d.ts +0 -0
  162. /package/dist/{ai-runner/src/types → types}/external.js +0 -0
  163. /package/dist/{ai-runner/src/types → types}/index.d.ts +0 -0
  164. /package/dist/{ai-runner/src/types → types}/index.js +0 -0
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Playwright Executor - Uses LangChain's createAgent API to implement ReAct loop
3
+ */
4
+ /* eslint-disable @typescript-eslint/no-explicit-any */
5
+
6
+ import { AIMessage, BaseMessage, createAgent, ToolMessage } from 'langchain';
7
+ import { DynamicStructuredTool } from '@langchain/core/tools';
8
+ import type { HealContext } from '../types';
9
+ import z, { uuid } from 'zod';
10
+ import { PlaywrightAgentState } from './types';
11
+ import { uuid4 } from 'zod/v4/core/regexes.cjs';
12
+
13
+ export class PlaywrightExecutor {
14
+ private state: PlaywrightAgentState;
15
+ private signal: AbortSignal;
16
+
17
+ constructor(state: PlaywrightAgentState, signal: AbortSignal) {
18
+ this.state = state;
19
+ this.signal = signal;
20
+ }
21
+
22
+ private async createPlaywrightTools(healContext: HealContext): Promise<DynamicStructuredTool[]> {
23
+ const standardTools = this.state.playwrightGroup.getDefaultTools();
24
+ const playwrightToolGroups = this.state.playwrightGroup.getNonDefaultGroupInfo();
25
+
26
+ const getToolGroupsTool = {
27
+ name: "get_tools",
28
+ description: "Get available tools in a group.",
29
+ schema: z.object({
30
+ group_name: z.string().describe("The name of the tool group."),
31
+ }),
32
+ func: async (param: any) => {
33
+ if (!param.group_name) throw new Error('Missing group_name parameter');
34
+ return JSON.stringify(playwrightToolGroups[param.group_name]);
35
+ },
36
+ };
37
+
38
+ const callToolTool = {
39
+ name: "call_tool",
40
+ description: "Call a tool in a group by name.",
41
+ schema: z.object({
42
+ group_name: z.string().describe("Tool group name."),
43
+ tool_name: z.string().describe("Tool name."),
44
+ args: z.object().describe("Tool parameters."),
45
+ }),
46
+ func: async (param: any) => {
47
+ const { group_name, tool_name, args } = param;
48
+ if (!group_name || !tool_name) throw new Error('Missing group_name or tool_name parameter');
49
+ return await this.state.playwrightGroup.callTool(tool_name, args);
50
+ },
51
+ };
52
+
53
+ const finishTool = {
54
+ name: "finish_test",
55
+ description: "Call this tool ONLY when the step goal has been fully achieved or failed. After calling this, no more tools should be called.",
56
+ schema: z.object({
57
+ isSuccess: z.boolean().describe("Whether the step goal was successfully achieved."),
58
+ error: z.string().optional().describe("Reason if the step goal fails to fully achieve, otherwise leave empty."),
59
+ variables: z.record(z.string(), z.any()).describe(`
60
+ Key-value variables to assign in the test context to use in subsequent steps.
61
+ - The variable names should be consistent with those in the original test script
62
+ - Avoid conflicts with existing variable names
63
+ - Only include variables that have been changed or newly assigned in the fix
64
+ - The value should be NON-OBJECT types (string, number, boolean, etc.)
65
+ - If subsequent steps get a variable using 'runner.get', make sure to extract and provide the ACTUAL VALUE from test or page context
66
+ `),
67
+ repairedScript: z.string().optional().describe(`
68
+ The complete fixed test script, leave empty if the step fails:
69
+ - Keep original runner.set, runner.get, runner.reuseTest calls for the steps fixed if possible and ensure no semantic changes
70
+ - MUST return The COMPLETE fixed test script
71
+ - ONLY apply fixes relevant to the current step and any necessary previous steps
72
+ - Do NOT wrap in closures or test body - write direct code using runner API
73
+ - Add a 'FIXED' comment for the step code being fixed
74
+ - Example:
75
+ \`\`\`typescript
76
+ // FIXED
77
+ await runner.runStep('Click button', async () => {
78
+ await page.click('#button');
79
+ });
80
+ \`\`\`
81
+ `),
82
+ }),
83
+ func: async (params: any) => {
84
+ healContext.success = params.isSuccess;
85
+ healContext.healError = params.error;
86
+ if (healContext.success) {
87
+ if (params.variables) {
88
+ for (const [key, value] of Object.entries(params.variables)) {
89
+ this.state.variables.set(key, value);
90
+ }
91
+ }
92
+
93
+ this.state.originalScript = params.repairedScript;
94
+ this.updateOriginalScript(params.repairedScript);
95
+ }
96
+
97
+ return 'Test step finished.';
98
+ },
99
+ };
100
+
101
+ return [
102
+ ...standardTools,
103
+ new DynamicStructuredTool(getToolGroupsTool),
104
+ new DynamicStructuredTool(callToolTool),
105
+ new DynamicStructuredTool(finishTool)
106
+ ];
107
+ }
108
+
109
+ /**
110
+ * Update original script and save to database (heal mode)
111
+ */
112
+ private updateOriginalScript(script: string): void {
113
+ this.state.storageService.saveTestScript(this.state.testCaseId, script);
114
+ this.state.logger.log('Script fixed');
115
+ }
116
+
117
+ async execute(context: HealContext) {
118
+ const page = this.state.page;
119
+ if (!page) throw new Error('Missing page object');
120
+
121
+ // Check if already aborted
122
+ if (this.signal.aborted) {
123
+ throw new Error('Execution aborted');
124
+ }
125
+
126
+ const tools = await this.createPlaywrightTools(context);
127
+ const agent = createAgent({
128
+ model: this.state.model!,
129
+ tools: tools,
130
+ systemPrompt: this.buildSystemPrompt(),
131
+ } as any);
132
+
133
+ const snapshot = await this.getSnapshotContext();
134
+ const inputStream = {
135
+ messages: [{
136
+ role: 'user' as const,
137
+ content: this.buildUserMessage(context)
138
+ },
139
+ ...snapshot || [],
140
+ ]
141
+ };
142
+
143
+ const stream = await agent.stream(inputStream, {
144
+ recursionLimit: 25,
145
+ streamMode: 'updates',
146
+ });
147
+
148
+ for await (const event of stream) {
149
+ // Check for abort signal
150
+ if (this.signal.aborted) {
151
+ throw new Error('Execution aborted');
152
+ }
153
+
154
+ // Process AI messages (thoughts and tool calls)
155
+ const messages = event.model_request?.messages || event.messages;
156
+ if (messages?.[0]?.type === 'ai') {
157
+ const aiMsg = messages[0] as any;
158
+
159
+ const content = this.extractContent(aiMsg.content);
160
+ // Record thought
161
+ if (content) {
162
+ this.state.logger.log(`${content}`);
163
+ }
164
+ }
165
+
166
+ // Process tool results
167
+ const toolMsg = event.tools?.messages?.[0];
168
+ if (toolMsg?.type === 'tool') {
169
+ const output = this.extractContent(toolMsg.content) as string;
170
+ this.state.logger.log(`✅ Result [${toolMsg.name}]:`, output.length > 300 ? output.substring(0, 100) + '...' : output);
171
+ }
172
+ }
173
+ }
174
+
175
+ private buildSystemPrompt(): string {
176
+ return `
177
+ You are a Playwright test expert, skilled at analyzing and fixing errors in browser automation tests.
178
+
179
+ ## ** Critial Rules **
180
+ - You MUST run the step using tools first before fixing the script
181
+ - When you step finishes or fails, you MUST call 'finish_test' tool and set the 'isSuccess' parameter correctly.
182
+ - Analyze the current step code, errors and page context and what variables are being set
183
+ - Make sure the fixed code is concise and consistent with the original code in logic and style
184
+ - CRITICAL: When calling 'finish_test', analyze the full test script to identify variables defined in the current step that are used in subsequent steps
185
+
186
+ ## ** Constraints **
187
+ - NEVER set 'isSuccess' to true if the step goal is not fully achieved
188
+ - Fail the step fast and DON'T do unnecessary or unreansonable retries
189
+ - Always check if variables assigned in the current step are referenced in later steps - if so, extract their actual values and include in 'variables'
190
+ `.trim();
191
+ }
192
+
193
+ private async getSnapshotContext(): Promise<BaseMessage[] | undefined> {
194
+ const snapshot = await this.state.adapter.callTool('browser_snapshot', {});
195
+ if (snapshot.isError) {
196
+ this.state.logger.warn(`Failed to get browser snapshot: ${snapshot.error}`);
197
+ return undefined;
198
+ }
199
+
200
+ const callId = uuid.toString();
201
+ const aiMessage = new AIMessage({
202
+ tool_calls: [
203
+ {
204
+ name: 'browser_snapshot',
205
+ id: callId,
206
+ args: {},
207
+ },
208
+ ]
209
+ });
210
+ const toolMessage = new ToolMessage({
211
+ content: snapshot.content,
212
+ tool_call_id: callId,
213
+ name: 'browser_snapshot',
214
+ status: 'success',
215
+ });
216
+ return [aiMessage, toolMessage];
217
+ }
218
+
219
+ private buildUserMessage(context: HealContext): string {
220
+ const variableList = Object.keys(this.state.variables).length > 0
221
+ ? Object.keys(this.state.variables).map(v => ` - ${v}: ${JSON.stringify(this.state.variables.get(v))}`).join('\n')
222
+ : undefined;
223
+ return `
224
+ ## Step Description: ${context.stepDescription}
225
+
226
+ ${variableList ? `## Variables in Context\n${variableList}` : ''}
227
+
228
+ ## Stacktrace
229
+ ${context.stackTrace}
230
+
231
+ ## Full Test Script
232
+ \`\`\`typescript
233
+ ${this.state.originalScript}
234
+ \`\`\`
235
+ `.trim();
236
+ }
237
+
238
+ /**
239
+ * Extract and normalize content from model response
240
+ * Handles string, array (ContentBlock[]), and other types
241
+ * Following LangChain.js best practices
242
+ */
243
+ private extractContent(content: unknown): string | Record<string, unknown> {
244
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
245
+ const asAny = content as any;
246
+ if (asAny.content) {
247
+ content = asAny.content;
248
+ }
249
+ // If string, return as-is
250
+ if (typeof content === 'string') {
251
+ return content;
252
+ }
253
+
254
+ // If array (ContentBlock[]), extract text content
255
+ if (Array.isArray(content)) {
256
+ const content0 = content[0];
257
+ if (typeof content0 === 'string') {
258
+ return content0;
259
+ }
260
+ if (typeof content0 === 'object' && content0 !== null) {
261
+ // Handle text blocks
262
+ if ('text' in content0) {
263
+ return content0.text as Record<string, unknown>;
264
+ }
265
+ // Handle other block types by converting to string
266
+ return JSON.stringify(content0);
267
+ }
268
+ return String(content0);
269
+ }
270
+
271
+ // If object, try to extract text field or convert to string
272
+ if (typeof content === 'object' && content !== null) {
273
+ if ('text' in content) {
274
+ return content as Record<string, unknown>;
275
+ }
276
+ return content as Record<string, unknown>;
277
+ }
278
+
279
+ // Fallback: convert to string
280
+ return String(content);
281
+ }
282
+ }
283
+
284
+
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Playwright Backend 工具适配器 - 新方案
3
+ *
4
+ * 基于 BrowserServerBackend 的伪实例实现
5
+ * 参考:playwright/lib/mcp/test/browserBackend.js
6
+ *
7
+ * 这个方案的优势:
8
+ * 1. 直接使用 Playwright 官方的 BrowserServerBackend 类
9
+ * 2. 直接导入官方的 identityFactory,完全复用
10
+ * 3. 获得完整的官方工具功能,包括 Response、Context 等
11
+ * 4. 无需手动适配 Context 和 Response
12
+ * 5. 自动跟随 Playwright 更新
13
+ */
14
+
15
+ import { DynamicStructuredTool } from '@langchain/core/tools';
16
+ import type { BrowserContext } from 'playwright';
17
+ import * as path from 'path';
18
+
19
+ // 导入 Playwright MCP 模块的类型定义
20
+ import type { PlaywrightMCPModules, BrowserServerBackendInstance } from './playwright-mcp-types';
21
+
22
+ export class PlaywrightBackendAdapter {
23
+
24
+ private backend: BrowserServerBackendInstance | undefined;
25
+ private tools: DynamicStructuredTool[] = [];
26
+
27
+ static async create(browserContext: BrowserContext): Promise<PlaywrightBackendAdapter> {
28
+ const adapter = new PlaywrightBackendAdapter();
29
+ adapter.backend = await adapter.getBackend(browserContext);
30
+ adapter.tools = await adapter.getPlaywrightBackendToolsFromContext();
31
+ return adapter;
32
+ }
33
+
34
+ getTools(): DynamicStructuredTool[] {
35
+ return this.tools;
36
+ }
37
+
38
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
+ async callTool(name: string, params: any) {
40
+ return await this.backend!.callTool(name, params);
41
+ }
42
+
43
+ // ============================================================================
44
+ // 动态加载 Playwright 官方模块
45
+ // ============================================================================
46
+
47
+ /**
48
+ * 官方的 identityFactory 函数
49
+ * 参考:playwright/lib/mcp/test/browserBackend.js 第 94-104 行
50
+ *
51
+ * 注意:官方文件中定义了这个函数但没有导出,所以我们在这里复制一份
52
+ */
53
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
+ private identityFactory(browserContext: BrowserContext): any {
55
+ return {
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
+ createContext: async (clientInfo: any, abortSignal: any) => {
58
+ void clientInfo;
59
+ void abortSignal;
60
+ return {
61
+ browserContext,
62
+ close: async () => {
63
+ // 不关闭 context,因为它是外部的
64
+ }
65
+ };
66
+ }
67
+ };
68
+ }
69
+
70
+ private loadPlaywrightMCPModules(): PlaywrightMCPModules {
71
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
72
+ const playwrightRoot = path.dirname((require as any).resolve('playwright'));
73
+
74
+ return {
75
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
76
+ BrowserServerBackend: (require as any)(path.join(playwrightRoot, 'lib', 'mcp', 'browser', 'browserServerBackend.js')).BrowserServerBackend,
77
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
78
+ defaultConfig: (require as any)(path.join(playwrightRoot, 'lib', 'mcp', 'browser', 'config.js')).defaultConfig,
79
+ // 使用本地定义的 identityFactory(因为官方没有导出)
80
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
+ identityFactory: this.identityFactory as any,
82
+ };
83
+ }
84
+
85
+ private async getBackend(browserContext: BrowserContext): Promise<BrowserServerBackendInstance> {
86
+ // 加载 Playwright 模块(包括官方的 identityFactory)
87
+ const { BrowserServerBackend, defaultConfig, identityFactory } = this.loadPlaywrightMCPModules();
88
+
89
+ // 使用官方的 identityFactory(参考 browserBackend.js 第 34 行)
90
+ const factory = identityFactory(browserContext);
91
+
92
+ // 创建 Backend 实例
93
+ const backend = new BrowserServerBackend(
94
+ { ...defaultConfig, capabilities: ['core', 'core-tabs',] },
95
+ factory
96
+ );
97
+
98
+ // 初始化 Backend
99
+ await backend.initialize({
100
+ name: 'ai-runner',
101
+ version: '1.0.0',
102
+ roots: [],
103
+ });
104
+
105
+ return backend;
106
+ }
107
+ // ============================================================================
108
+ // 工具转换函数
109
+ // ============================================================================
110
+
111
+ // export type ToolCapability = 'core' | 'core-tabs' | 'core-install' | 'vision' | 'pdf' | 'testing' | 'tracing';
112
+
113
+ private async getPlaywrightBackendToolsFromContext(): Promise<DynamicStructuredTool[]> {
114
+ // 列出所有工具
115
+ const toolList = await this.backend!.listTools();
116
+
117
+ // 转换为 LangChain 工具
118
+ const tools: DynamicStructuredTool[] = [];
119
+
120
+ for (const mcpTool of toolList) {
121
+ const tool = new DynamicStructuredTool({
122
+ name: mcpTool.name,
123
+ description: mcpTool.description,
124
+ schema: mcpTool.inputSchema,
125
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
+ func: async (input: any) => {
127
+ return await this.backend!.callTool(mcpTool.name, input);
128
+ }
129
+ });
130
+ tools.push(tool);
131
+ }
132
+
133
+ return tools;
134
+ }
135
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Playwright MCP 模块类型定义
3
+ *
4
+ * 为动态加载的 Playwright 内部模块提供 TypeScript 类型支持
5
+ */
6
+
7
+ import type { BrowserContext } from 'playwright';
8
+ import type { Root } from '@modelcontextprotocol/sdk/types.js';
9
+
10
+ export interface BrowserContextFactory {
11
+ createContext: (
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ clientInfo: any,
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ abortSignal: any,
16
+ toolName: string
17
+ ) => Promise<{
18
+ browserContext: BrowserContext;
19
+ close: () => Promise<void>;
20
+ }>;
21
+ }
22
+
23
+ export interface BackendConfig {
24
+ capabilities?: string[];
25
+ saveSession?: boolean;
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
+ [key: string]: any;
28
+ }
29
+
30
+ export interface BrowserServerBackendInstance {
31
+ initialize(clientInfo: { name: string; version: string; roots: Root[]}): Promise<void>;
32
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
+ listTools(): Promise<Array<{ name: string; description: string; inputSchema: any }>>;
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
+ callTool(name: string, params: any): Promise<any>;
36
+ serverClosed(): void;
37
+ }
38
+
39
+ export interface BrowserServerBackend {
40
+ new (config: BackendConfig, factory: BrowserContextFactory): BrowserServerBackendInstance;
41
+ }
42
+
43
+ export interface DefaultConfig {
44
+ capabilities?: string[];
45
+ saveSession?: boolean;
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
+ [key: string]: any;
48
+ }
49
+
50
+ /**
51
+ * 官方的 identityFactory 函数类型
52
+ * 参考:playwright/lib/mcp/test/browserBackend.js 第 94-104 行
53
+ */
54
+ export type IdentityFactory = (browserContext: BrowserContext) => BrowserContextFactory;
55
+
56
+ export interface MCPTool {
57
+ schema: {
58
+ name: string;
59
+ description: string;
60
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
+ inputSchema: any;
62
+ };
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
+ handle: (context: any, params: any, response: any) => Promise<void>;
65
+ }
66
+
67
+ // 动态加载的模块类型
68
+ export interface PlaywrightMCPModules {
69
+ BrowserServerBackend: BrowserServerBackend;
70
+ defaultConfig: DefaultConfig;
71
+ identityFactory: IdentityFactory;
72
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Playwright Agent Type Definitions
3
+ */
4
+
5
+ import type { ModelConfig } from '../types/external';
6
+ import type { ReActStep } from '../types';
7
+ import { BrowserContext, Page } from '@playwright/test';
8
+ import { Logger } from './logger';
9
+ import { PlaywrightGroup } from '../tools/playwright-groups';
10
+ import { PlaywrightBackendAdapter } from './tools/playwright-backend-adapter';
11
+ import { StorageService } from '@cotestdev/core-infra';
12
+ import { BaseLanguageModel } from '@langchain/core/language_models/base';
13
+
14
+ // ============================================================================
15
+ // Agent Execution Mode
16
+ // ============================================================================
17
+
18
+ /**
19
+ * Agent execution mode
20
+ */
21
+ export type AgentMode = 'heal' | 'execute';
22
+
23
+ // ============================================================================
24
+ // Agent State
25
+ // ============================================================================
26
+
27
+ /**
28
+ * Playwright Agent State
29
+ */
30
+ export interface PlaywrightAgentState {
31
+ logger: Logger;
32
+ /** Playwright Page object */
33
+ page: Page;
34
+ /** Playwright BrowserContext object */
35
+ context: BrowserContext;
36
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
+ variables: Map<string, any>;
38
+ model?: BaseLanguageModel;
39
+ originalScript: string;
40
+ playwrightGroup: PlaywrightGroup;
41
+ adapter: PlaywrightBackendAdapter;
42
+ storageService: StorageService;
43
+ testCaseId: string;
44
+ }
45
+
46
+ // ============================================================================
47
+ // Agent Configuration
48
+ // ============================================================================
49
+
50
+ /**
51
+ * Playwright Agent Configuration
52
+ */
53
+ export interface PlaywrightAgentConfig {
54
+ /** Model configuration (for LLM Agent) */
55
+ model: ModelConfig;
56
+ }
57
+
58
+ // ============================================================================
59
+ // Agent Result
60
+ // ============================================================================
61
+
62
+ /**
63
+ * Playwright Agent Execution Result
64
+ */
65
+ export interface PlaywrightAgentResult {
66
+ /** Whether the goal was successfully completed */
67
+ success: boolean;
68
+
69
+ /** Agent's reasoning process */
70
+ reasoning?: string;
71
+
72
+ /** If failed, the error reason */
73
+ errorMessage?: string;
74
+
75
+ /** How many steps were executed */
76
+ steps?: number;
77
+
78
+ /** Detailed step records of ReAct loop */
79
+ reactSteps?: ReActStep[];
80
+ }
package/src/index.ts ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * ai-runner SDK Main Entry Point
3
+ * Provides AI-driven test execution capabilities
4
+ */
5
+
6
+ // Types
7
+ export type {
8
+ RunnerConfig,
9
+ ExecutionResult,
10
+ HealContext,
11
+ HealSummary,
12
+ HealingDetail,
13
+ RunStepOptions,
14
+ } from './types';
15
+
16
+
17
+ // Core Classes
18
+ export { Runner } from './runner';
19
+
20
+
21
+ // Agent
22
+ export type {
23
+ PlaywrightAgentState,
24
+ PlaywrightAgentResult,
25
+ PlaywrightAgentConfig,
26
+ } from './agents/types';
27
+