@shiplightai/mcp 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/README.md +401 -0
- package/dist/index.d.ts +96 -0
- package/dist/index.js +541 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
# Shiplight MCP Server
|
|
2
|
+
|
|
3
|
+
**AI-powered web testing through the Model Context Protocol.**
|
|
4
|
+
|
|
5
|
+
Shiplight lets AI coding agents — Claude Code, Cursor, Windsurf, and any MCP-compatible tool — browse, interact with, and test web applications. Write tests in natural language, run them in the cloud, and get detailed results — all from your IDE.
|
|
6
|
+
|
|
7
|
+
## Why Shiplight
|
|
8
|
+
|
|
9
|
+
Other browser MCP servers (like [Playwright MCP](https://github.com/microsoft/playwright-mcp)) give your coding agent basic browser control. Shiplight goes further:
|
|
10
|
+
|
|
11
|
+
| Capability | Playwright MCP | Shiplight |
|
|
12
|
+
|------------|---------------|-----------|
|
|
13
|
+
| Browser automation (click, type, scroll) | Yes | Yes |
|
|
14
|
+
| AI-powered assertions (`verify`) | No | Yes * |
|
|
15
|
+
| AI data extraction (`ai_extract`) | No | Yes * |
|
|
16
|
+
| Natural language test flows (YAML DSL) | No | Yes |
|
|
17
|
+
| Conditional logic (`IF`/`ELSE`) | No | Yes * |
|
|
18
|
+
| Loops (`WHILE`) | No | Yes * |
|
|
19
|
+
| Cloud test execution & results | No | Yes |
|
|
20
|
+
| Test case management (CRUD, folders) | No | Yes |
|
|
21
|
+
| Enrichment workflow (DRAFT to ACTION) | No | Yes |
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
\* Uses the web agent (secondary LLM)
|
|
25
|
+
|
|
26
|
+
Shiplight also uses **set-of-mark** technology for better identifying interactive elements and handles **cross-frame elements transparently** — no manual iframe switching needed.
|
|
27
|
+
|
|
28
|
+
Shiplight is not just a browser driver — it's a complete test automation platform accessible through MCP.
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
### Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install -g @shiplightai/mcp
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Claude Code
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
claude mcp add shiplight -- shiplight-mcp \
|
|
42
|
+
-e GOOGLE_API_KEY=your-google-api-key \
|
|
43
|
+
-e WEB_AGENT_MODEL=gemini-2.5-pro \
|
|
44
|
+
-e PWDEBUG=console
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Claude Desktop
|
|
48
|
+
|
|
49
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"mcpServers": {
|
|
54
|
+
"shiplight": {
|
|
55
|
+
"command": "shiplight-mcp",
|
|
56
|
+
"env": {
|
|
57
|
+
"GOOGLE_API_KEY": "your-google-api-key",
|
|
58
|
+
"WEB_AGENT_MODEL": "gemini-2.5-pro",
|
|
59
|
+
"PWDEBUG": "console"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Cursor
|
|
67
|
+
|
|
68
|
+
Add to `.cursor/mcp.json` in your project:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"mcpServers": {
|
|
73
|
+
"shiplight": {
|
|
74
|
+
"command": "shiplight-mcp",
|
|
75
|
+
"env": {
|
|
76
|
+
"GOOGLE_API_KEY": "your-google-api-key",
|
|
77
|
+
"WEB_AGENT_MODEL": "gemini-2.5-pro",
|
|
78
|
+
"PWDEBUG": "console"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The MCP server runs a **web agent** — a secondary LLM that looks at the browser page and decides how to act. This agent powers:
|
|
86
|
+
|
|
87
|
+
- **AI-powered actions**: `verify`, `ai_extract`, `ai_wait_until`
|
|
88
|
+
- **Natural language test steps**: DRAFT statements and VERIFY assertions in test flows
|
|
89
|
+
- **Conditional evaluation**: IF/ELSE and WHILE conditions written in natural language
|
|
90
|
+
|
|
91
|
+
Browser actions like `click`, `input_text`, `scroll` etc. do **not** require the web agent — they execute deterministically.
|
|
92
|
+
|
|
93
|
+
You can use either a Google or Anthropic API key. Set `WEB_AGENT_MODEL` to match your provider:
|
|
94
|
+
|
|
95
|
+
| Provider | API Key | Supported Models |
|
|
96
|
+
|----------|---------|-----------------|
|
|
97
|
+
| Google | `GOOGLE_API_KEY` | `gemini-2.5-pro`, `gemini-3-pro-preview` |
|
|
98
|
+
| Anthropic | `ANTHROPIC_API_KEY` | `claude-haiku-4-5`, `claude-sonnet-4-5`, `claude-opus-4-5` |
|
|
99
|
+
|
|
100
|
+
To unlock cloud test management, add your Shiplight API token:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"env": {
|
|
105
|
+
"GOOGLE_API_KEY": "your-google-api-key",
|
|
106
|
+
"WEB_AGENT_MODEL": "gemini-2.5-pro",
|
|
107
|
+
"PWDEBUG": "console",
|
|
108
|
+
"API_TOKEN": "your-shiplight-api-token-here"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Free: Browser Automation & UI Verification
|
|
114
|
+
|
|
115
|
+
No Shiplight account needed. Launch a browser, interact with any web app, and verify UI state — all driven by your AI coding agent.
|
|
116
|
+
|
|
117
|
+
**Your coding agent can verify UI features by:**
|
|
118
|
+
|
|
119
|
+
- Opening a browser and navigating to your app
|
|
120
|
+
- Inspecting page state via screenshots and DOM structure
|
|
121
|
+
- Interacting with elements: click, type, select, scroll, upload files
|
|
122
|
+
- Asserting UI state with natural language (e.g., "the login form should be visible")
|
|
123
|
+
- Capturing console errors and network requests
|
|
124
|
+
|
|
125
|
+
**Example — prompt your coding agent:**
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
Add a "Forgot Password?" link below the login form. After implementing,
|
|
129
|
+
Use Shiplight to verify your implementation in the browser.
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Free Tools
|
|
133
|
+
|
|
134
|
+
| Tool | Description |
|
|
135
|
+
|------|-------------|
|
|
136
|
+
| `new_session` | Create a browser session with optional device emulation and auto-login |
|
|
137
|
+
| `close_session` | Close a browser session |
|
|
138
|
+
| `close_all` | Close all browser sessions |
|
|
139
|
+
| `get_session_state` | Get current URL and session info |
|
|
140
|
+
| `save_storage_state` | Save cookies/localStorage for fast session restore |
|
|
141
|
+
| `navigate` | Navigate to a URL |
|
|
142
|
+
| `get_page_info` | Get current page URL and title |
|
|
143
|
+
| `inspect_page` | Screenshot + DOM with interactive element indices |
|
|
144
|
+
| `act` | Perform browser actions (click, type, scroll, verify, etc.) |
|
|
145
|
+
| `get_locator` | Extract Playwright locator/xpath for an element |
|
|
146
|
+
| `update_variables` | Set session variables for use in actions |
|
|
147
|
+
| `clear_execution_history` | Reset session action history |
|
|
148
|
+
| `get_browser_console_logs` | Get browser console output with filtering |
|
|
149
|
+
| `get_browser_network_logs` | Get network requests with status filtering |
|
|
150
|
+
| `clear_logs` | Clear console and network logs |
|
|
151
|
+
| `get_artifact` | Retrieve a saved screenshot or DOM snapshot |
|
|
152
|
+
|
|
153
|
+
### Browser Actions
|
|
154
|
+
|
|
155
|
+
The `act` tool supports the following actions for interacting with the page:
|
|
156
|
+
|
|
157
|
+
| Action | Description |
|
|
158
|
+
|--------|-------------|
|
|
159
|
+
| `click` | Click an element |
|
|
160
|
+
| `double_click` | Double-click an element |
|
|
161
|
+
| `right_click` | Right-click an element |
|
|
162
|
+
| `hover` | Hover over an element |
|
|
163
|
+
| `input_text` | Type text into an input field |
|
|
164
|
+
| `clear_input` | Clear an input field |
|
|
165
|
+
| `press` | Press a key or key combination (e.g., `Enter`, `Control+A`) |
|
|
166
|
+
| `send_keys_on_element` | Send keys to a specific element |
|
|
167
|
+
| `select_dropdown_option` | Select from a dropdown by value or label |
|
|
168
|
+
| `get_dropdown_options` | List all options in a dropdown |
|
|
169
|
+
| `set_date_for_native_date_picker` | Set date on a native date picker input |
|
|
170
|
+
| `scroll` | Scroll up or down by number of pages |
|
|
171
|
+
| `scroll_to_text` | Scroll until specific text is visible |
|
|
172
|
+
| `scroll_on_element` | Scroll within a specific scrollable element |
|
|
173
|
+
| `go_to_url` | Navigate to a URL |
|
|
174
|
+
| `go_back` | Go back in browser history |
|
|
175
|
+
| `reload_page` | Reload the current page |
|
|
176
|
+
| `switch_tab` | Switch to a different browser tab |
|
|
177
|
+
| `close_tab` | Close a browser tab |
|
|
178
|
+
| `upload_file` | Upload a file via file input |
|
|
179
|
+
|
|
180
|
+
### Utility Actions
|
|
181
|
+
|
|
182
|
+
| Action | Description |
|
|
183
|
+
|--------|-------------|
|
|
184
|
+
| `wait` | Wait for a specified duration |
|
|
185
|
+
| `wait_for_page_ready` | Wait until the page finishes loading |
|
|
186
|
+
| `wait_for_download_complete` | Wait for a file download to complete |
|
|
187
|
+
| `save_variable` | Save a value to a session variable |
|
|
188
|
+
| `generate_2fa_code` | Generate a TOTP 2FA code from a secret key |
|
|
189
|
+
|
|
190
|
+
### AI-Powered Actions *
|
|
191
|
+
|
|
192
|
+
These actions use the AI model specified by `WEB_AGENT_MODEL` to reason about the page:
|
|
193
|
+
|
|
194
|
+
| Action | Description |
|
|
195
|
+
|--------|-------------|
|
|
196
|
+
| `verify` * | Assert page state with a natural language statement |
|
|
197
|
+
| `ai_extract` * | Extract data from the page into a variable |
|
|
198
|
+
| `ai_wait_until` * | Wait until an AI-evaluated condition is true |
|
|
199
|
+
|
|
200
|
+
## Writing Tests in Natural Language
|
|
201
|
+
|
|
202
|
+
Shiplight tests are written in YAML using natural language. Start with plain English, then optionally enrich with locators for speed.
|
|
203
|
+
|
|
204
|
+
**No lock-in**: Test flows are exported as pure Playwright + [Shiplight Agent SDK](https://github.com/ShiplightAI/sdk-examples) code for execution. The YAML DSL is an authoring format — what actually runs is standard Playwright with an AI agent layer on top. You can eject at any time.
|
|
205
|
+
|
|
206
|
+
### Basic Test (all natural language)
|
|
207
|
+
|
|
208
|
+
```yaml
|
|
209
|
+
goal: Verify user can create a new project
|
|
210
|
+
url: https://app.example.com/projects
|
|
211
|
+
statements:
|
|
212
|
+
- Click the "New Project" button
|
|
213
|
+
- Enter "My Test Project" in the project name field
|
|
214
|
+
- Select "Public" from the visibility dropdown
|
|
215
|
+
- Click "Create"
|
|
216
|
+
- "VERIFY: Project page shows title 'My Test Project'"
|
|
217
|
+
teardown:
|
|
218
|
+
- Delete the created project
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Every line is a plain English instruction. The AI resolves each one at runtime by looking at the page and performing the right action.
|
|
222
|
+
|
|
223
|
+
### Enriched Test (with locator)
|
|
224
|
+
|
|
225
|
+
After exploring the UI with the free browser tools, you can add locators for deterministic, fast replay:
|
|
226
|
+
|
|
227
|
+
```yaml
|
|
228
|
+
goal: Verify user can create a new project
|
|
229
|
+
url: https://app.example.com/projects
|
|
230
|
+
statements:
|
|
231
|
+
- STEP: Create project
|
|
232
|
+
statements:
|
|
233
|
+
- description: Click the New Project button
|
|
234
|
+
action_entity:
|
|
235
|
+
action_data:
|
|
236
|
+
action_name: click
|
|
237
|
+
locator: "getByRole('button', { name: 'New Project' })"
|
|
238
|
+
- description: Enter project name
|
|
239
|
+
action_entity:
|
|
240
|
+
action_data:
|
|
241
|
+
action_name: input_text
|
|
242
|
+
kwargs:
|
|
243
|
+
text: "My Test Project"
|
|
244
|
+
locator: "getByRole('textbox', { name: 'Project name' })"
|
|
245
|
+
- description: Click Create
|
|
246
|
+
action_entity:
|
|
247
|
+
action_data:
|
|
248
|
+
action_name: click
|
|
249
|
+
locator: "getByRole('button', { name: 'Create' })"
|
|
250
|
+
- "VERIFY: Project page shows title 'My Test Project'"
|
|
251
|
+
teardown:
|
|
252
|
+
- Delete the created project
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Natural language statements** (~10-15s each): AI reads the page and figures out what to do.
|
|
256
|
+
**Action statements with locator** (~1s each): Replay deterministically without AI.
|
|
257
|
+
**VERIFY statements**: Always use AI — just provide a clear assertion in plain English.
|
|
258
|
+
|
|
259
|
+
**Locators are a cache, not a hard dependency.** When the UI changes and a locator becomes stale, Shiplight's agentic layer [auto-heals](https://docs.shiplight.ai/sdk/self-healing.html) by falling back to the natural language description. When running on the Shiplight cloud, the platform automatically updates the cached locator after a successful self-heal — so future runs replay at full speed without manual maintenance.
|
|
260
|
+
|
|
261
|
+
### Statement Types
|
|
262
|
+
|
|
263
|
+
| Type | Syntax | Description |
|
|
264
|
+
|------|--------|-------------|
|
|
265
|
+
| Natural language * | `- Click the login button` | AI resolves at runtime |
|
|
266
|
+
| Action with locator | `- description: ...` + `action_entity: ...` | Deterministic replay |
|
|
267
|
+
| Verify * | `- "VERIFY: page shows welcome message"` | AI-powered assertion |
|
|
268
|
+
| Step group | `- STEP: Login` + `statements: [...]` | Group related actions |
|
|
269
|
+
| Conditional * | `- IF: cookie banner is visible` + `THEN: [...]` | Conditional execution |
|
|
270
|
+
| Loop * | `- WHILE: more items to load` + `DO: [...]` | Repeat until condition |
|
|
271
|
+
|
|
272
|
+
### Conditional Logic & Loops
|
|
273
|
+
|
|
274
|
+
Shiplight tests support branching and looping — handle real-world UI variability without separate test cases.
|
|
275
|
+
|
|
276
|
+
**IF / ELSE** — handle optional UI elements:
|
|
277
|
+
|
|
278
|
+
```yaml
|
|
279
|
+
statements:
|
|
280
|
+
- IF: cookie consent dialog is visible
|
|
281
|
+
THEN:
|
|
282
|
+
- Click "Accept All"
|
|
283
|
+
- IF: user is logged in
|
|
284
|
+
THEN:
|
|
285
|
+
- Click the logout button
|
|
286
|
+
ELSE:
|
|
287
|
+
- Click the login button
|
|
288
|
+
- Enter credentials and submit
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**WHILE** — repeat until a condition is met:
|
|
292
|
+
|
|
293
|
+
```yaml
|
|
294
|
+
statements:
|
|
295
|
+
- WHILE: "Load More" button is visible
|
|
296
|
+
DO:
|
|
297
|
+
- Click the "Load More" button
|
|
298
|
+
- Wait for new items to appear
|
|
299
|
+
- "VERIFY: all items are loaded"
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Conditions are evaluated by AI at runtime using the current page state. You can also use JavaScript conditions with the `js:` prefix:
|
|
303
|
+
|
|
304
|
+
```yaml
|
|
305
|
+
- IF: "js: document.querySelectorAll('.item').length < 10"
|
|
306
|
+
THEN:
|
|
307
|
+
- Click "Load More"
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### The Enrichment Workflow
|
|
311
|
+
|
|
312
|
+
1. **Draft** — Write tests in plain English
|
|
313
|
+
2. **Explore** — Use `inspect_page` and `act` to walk through the UI
|
|
314
|
+
3. **Collect** — Use `get_locator` to capture element locators
|
|
315
|
+
4. **Enrich** — Replace natural language with action_entity + locator
|
|
316
|
+
5. **Result** — Tests run 10x faster with deterministic replay
|
|
317
|
+
|
|
318
|
+
You can mix natural language and enriched statements in the same test. Start with all natural language, then selectively enrich the most-used flows.
|
|
319
|
+
|
|
320
|
+
## Cloud: Test Case Management & Execution
|
|
321
|
+
|
|
322
|
+
*Requires a Shiplight API token.*
|
|
323
|
+
|
|
324
|
+
Store test cases in the cloud, trigger runs, and analyze results with full runner logs, screenshots, and trace files.
|
|
325
|
+
|
|
326
|
+
**What you can do:**
|
|
327
|
+
|
|
328
|
+
- Create and update test cases from YAML flows
|
|
329
|
+
- Trigger cloud test runs across environments
|
|
330
|
+
- Get detailed results: step-by-step status, screenshots, runner logs
|
|
331
|
+
- Manage test infrastructure: environments, test accounts, folders
|
|
332
|
+
|
|
333
|
+
**Example conversation:**
|
|
334
|
+
|
|
335
|
+
```
|
|
336
|
+
You: Create a test case from my login-test.yaml and run it on staging
|
|
337
|
+
|
|
338
|
+
Claude: [reads YAML, creates test case, triggers run, polls for results]
|
|
339
|
+
Test case #502 created. Cloud run completed in 1m 23s — all 8 steps passed.
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Cloud Tools
|
|
343
|
+
|
|
344
|
+
| Tool | Description |
|
|
345
|
+
|------|-------------|
|
|
346
|
+
| `create_test_case` | Create a test case from a YAML flow or JSON object |
|
|
347
|
+
| `update_test_case` | Update an existing test case flow |
|
|
348
|
+
| `get_test_case` | Get test case details (supports YAML output) |
|
|
349
|
+
| `run_test_case` | Trigger a cloud test run |
|
|
350
|
+
| `list_test_runs` | List test runs with filtering |
|
|
351
|
+
| `get_test_run_details` | Get run status and test case results |
|
|
352
|
+
| `get_test_case_result` | Get detailed result with runner logs (stdout/stderr) |
|
|
353
|
+
| `get_test_case_result_steps` | Get step-by-step execution details |
|
|
354
|
+
| `get_step_artifacts` | Download screenshots and artifacts for a step |
|
|
355
|
+
| `list_environments` | List testing environments |
|
|
356
|
+
| `list_test_accounts` | List test accounts for an environment |
|
|
357
|
+
| `get_test_account` | Get test account details |
|
|
358
|
+
| `create_test_account` | Create a test account with login config |
|
|
359
|
+
| `list_folders` | List test case folders |
|
|
360
|
+
| `create_folder` | Create a folder for organizing test cases |
|
|
361
|
+
| `get_folder` | Get folder details with full path |
|
|
362
|
+
|
|
363
|
+
## Workflow Examples
|
|
364
|
+
|
|
365
|
+
Shiplight MCP fits naturally into AI-driven development. Your coding agent can verify UI changes in a live browser as you code, and automatically create enriched test cases from the session — no context switching needed. See the [workflow guide](https://docs.shiplight.ai/mcp/workflows) for detailed examples, including how to set up a Claude Code custom agent for fully autonomous test creation.
|
|
366
|
+
|
|
367
|
+
## Coming Soon
|
|
368
|
+
|
|
369
|
+
- **Coordinate-based actions** — `click_by_coordinates`, `drag_drop` for pixel-precise interactions (canvas, maps, visual editors)
|
|
370
|
+
|
|
371
|
+
## Configuration
|
|
372
|
+
|
|
373
|
+
| Variable | Required | Description | Default |
|
|
374
|
+
|----------|----------|-------------|---------|
|
|
375
|
+
| `GOOGLE_API_KEY` | One of these | Google AI API key | — |
|
|
376
|
+
| `ANTHROPIC_API_KEY` | required | Anthropic API key | — |
|
|
377
|
+
| `WEB_AGENT_MODEL` | Yes | AI model for browser automation | — |
|
|
378
|
+
| `PWDEBUG` | No | Set to `console` to enable Playwright debug logging | — |
|
|
379
|
+
| `API_TOKEN` | For cloud features | Shiplight API token | — |
|
|
380
|
+
| `API_BASE_URL` | No | Shiplight API URL | `https://api.shiplight.ai` |
|
|
381
|
+
|
|
382
|
+
## Resources & Prompts
|
|
383
|
+
|
|
384
|
+
The server exposes MCP resources with schema documentation:
|
|
385
|
+
|
|
386
|
+
| Resource | Description |
|
|
387
|
+
|----------|-------------|
|
|
388
|
+
| `shiplight://schemas/testflow-v1.2.0` | TestFlow YAML/JSON format, statement types, examples |
|
|
389
|
+
| `shiplight://schemas/action-entity` | Browser action parameters for the `act` tool |
|
|
390
|
+
|
|
391
|
+
## Getting an API Token
|
|
392
|
+
|
|
393
|
+
To unlock cloud test management:
|
|
394
|
+
|
|
395
|
+
1. Visit [shiplight.ai](https://shiplight.ai) to create an account
|
|
396
|
+
2. Generate an API token from your account settings
|
|
397
|
+
3. Set `API_TOKEN` in your MCP server configuration
|
|
398
|
+
|
|
399
|
+
## License
|
|
400
|
+
|
|
401
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP Server Logger
|
|
5
|
+
*
|
|
6
|
+
* For STDIO-based MCP servers, we must NEVER write to stdout.
|
|
7
|
+
* All logging output goes to stderr to avoid corrupting JSON-RPC messages.
|
|
8
|
+
*/
|
|
9
|
+
declare class MCPLogger {
|
|
10
|
+
private static _instance;
|
|
11
|
+
private _logLevel;
|
|
12
|
+
private constructor();
|
|
13
|
+
static getInstance(): MCPLogger;
|
|
14
|
+
setLevel(level: number): void;
|
|
15
|
+
/**
|
|
16
|
+
* Log debug messages (only shown when LOG_LEVEL=DEBUG)
|
|
17
|
+
*/
|
|
18
|
+
debug(...args: unknown[]): void;
|
|
19
|
+
/**
|
|
20
|
+
* Log informational messages
|
|
21
|
+
*/
|
|
22
|
+
info(...args: unknown[]): void;
|
|
23
|
+
/**
|
|
24
|
+
* Log warning messages
|
|
25
|
+
*/
|
|
26
|
+
warn(...args: unknown[]): void;
|
|
27
|
+
/**
|
|
28
|
+
* Log error messages (always shown)
|
|
29
|
+
*/
|
|
30
|
+
error(...args: unknown[]): void;
|
|
31
|
+
/**
|
|
32
|
+
* Alias for info (for compatibility)
|
|
33
|
+
*/
|
|
34
|
+
log(...args: unknown[]): void;
|
|
35
|
+
}
|
|
36
|
+
declare const logger: MCPLogger;
|
|
37
|
+
|
|
38
|
+
declare const LogLevel: {
|
|
39
|
+
ERROR: number;
|
|
40
|
+
WARN: number;
|
|
41
|
+
INFO: number;
|
|
42
|
+
DEBUG: number;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Axios Retry Utility
|
|
47
|
+
*
|
|
48
|
+
* Provides retry logic for axios requests with exponential backoff
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
interface RetryConfig {
|
|
52
|
+
maxRetries?: number;
|
|
53
|
+
initialDelayMs?: number;
|
|
54
|
+
maxDelayMs?: number;
|
|
55
|
+
backoffMultiplier?: number;
|
|
56
|
+
retryableStatuses?: number[];
|
|
57
|
+
onRetry?: (attempt: number, error: AxiosError) => void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Retry an axios request with exponential backoff
|
|
61
|
+
*/
|
|
62
|
+
declare function retryAxios<T = unknown>(requestFn: () => Promise<AxiosResponse<T>>, config?: RetryConfig): Promise<AxiosResponse<T>>;
|
|
63
|
+
/**
|
|
64
|
+
* Create a retry wrapper for axios instance
|
|
65
|
+
*/
|
|
66
|
+
declare function createRetryAxios(retryConfig?: RetryConfig): {
|
|
67
|
+
get: <T = unknown>(url: string, config?: AxiosRequestConfig) => Promise<AxiosResponse<T, any, {}>>;
|
|
68
|
+
post: <T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<AxiosResponse<T, any, {}>>;
|
|
69
|
+
put: <T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<AxiosResponse<T, any, {}>>;
|
|
70
|
+
delete: <T = unknown>(url: string, config?: AxiosRequestConfig) => Promise<AxiosResponse<T, any, {}>>;
|
|
71
|
+
patch: <T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<AxiosResponse<T, any, {}>>;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Shiplight MCP Server
|
|
76
|
+
*
|
|
77
|
+
* MCP server implementation for Shiplight AI test automation platform.
|
|
78
|
+
* Uses shared tools from mcp-tools with default backends.
|
|
79
|
+
*/
|
|
80
|
+
declare class ShiplightMCPServer {
|
|
81
|
+
private server;
|
|
82
|
+
private registry;
|
|
83
|
+
private sessionBackend;
|
|
84
|
+
private apiClient;
|
|
85
|
+
private sessionTools;
|
|
86
|
+
private browserTools;
|
|
87
|
+
private debugTools;
|
|
88
|
+
private testCaseTools;
|
|
89
|
+
private testResultTools;
|
|
90
|
+
private dataTools;
|
|
91
|
+
constructor();
|
|
92
|
+
private setupHandlers;
|
|
93
|
+
run(): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export { LogLevel, type RetryConfig, ShiplightMCPServer, createRetryAxios, logger, retryAxios };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{Server as us}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as ds}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as hs,ListToolsRequestSchema as ps,ListResourcesRequestSchema as gs,ReadResourceRequestSchema as ms,ListPromptsRequestSchema as fs,GetPromptRequestSchema as _s,ErrorCode as Re,McpError as Fe}from"@modelcontextprotocol/sdk/types.js";import ws from"axios";import Ss from"dotenv";import P from"fs";import C from"path";import Ht from"crypto";import{BrowserManager as Gt,Agent as qe,createAgentContext as Bt,DomService as $t,getActionEntityLocatorInfo as Kt}from"sdk-internal";var Ue=112;var Tt=1080-Ue;var He={"Blackberry PlayBook":{name:"Blackberry PlayBook",userAgent:"Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/26.0 Safari/536.2+",screen:{width:600,height:1024},viewport:{width:600,height:1024},deviceScaleFactor:1,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"BlackBerry Z30":{name:"BlackBerry Z30",userAgent:"Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/26.0 Mobile Safari/537.10+",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"Galaxy Note 3":{name:"Galaxy Note 3",userAgent:"Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/26.0 Mobile Safari/534.30",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"Galaxy Note II":{name:"Galaxy Note II",userAgent:"Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/26.0 Mobile Safari/534.30",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"Galaxy S III":{name:"Galaxy S III",userAgent:"Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/26.0 Mobile Safari/534.30",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"Galaxy S5":{name:"Galaxy S5",userAgent:"Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Galaxy S8":{name:"Galaxy S8",userAgent:"Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:360,height:740},viewport:{width:360,height:740},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Galaxy S9+":{name:"Galaxy S9+",userAgent:"Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:320,height:658},viewport:{width:320,height:658},deviceScaleFactor:4.5,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Galaxy S24":{name:"Galaxy S24",userAgent:"Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:360,height:780},viewport:{width:360,height:780},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Galaxy A55":{name:"Galaxy A55",userAgent:"Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:480,height:1040},viewport:{width:480,height:1040},deviceScaleFactor:2.25,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Galaxy Tab S4":{name:"Galaxy Tab S4",userAgent:"Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:712,height:1138},viewport:{width:712,height:1138},deviceScaleFactor:2.25,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Galaxy Tab S9":{name:"Galaxy Tab S9",userAgent:"Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:640,height:1024},viewport:{width:640,height:1024},deviceScaleFactor:2.5,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"iPad (gen 5)":{name:"iPad (gen 5)",userAgent:"Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:768,height:1024},viewport:{width:768,height:1024},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPad (gen 6)":{name:"iPad (gen 6)",userAgent:"Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:768,height:1024},viewport:{width:768,height:1024},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPad (gen 7)":{name:"iPad (gen 7)",userAgent:"Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:810,height:1080},viewport:{width:810,height:1080},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPad (gen 11)":{name:"iPad (gen 11)",userAgent:"Mozilla/5.0 (iPad; CPU OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/19E241 Safari/604.1",screen:{width:656,height:944},viewport:{width:656,height:944},deviceScaleFactor:2.5,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPad Mini":{name:"iPad Mini",userAgent:"Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:768,height:1024},viewport:{width:768,height:1024},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPad Pro 11":{name:"iPad Pro 11",userAgent:"Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:834,height:1194},viewport:{width:834,height:1194},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 6":{name:"iPhone 6",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/26.0 Mobile/15A372 Safari/604.1",screen:{width:375,height:667},viewport:{width:375,height:667},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 6 Plus":{name:"iPhone 6 Plus",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/26.0 Mobile/15A372 Safari/604.1",screen:{width:414,height:736},viewport:{width:414,height:736},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 7":{name:"iPhone 7",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/26.0 Mobile/15A372 Safari/604.1",screen:{width:375,height:667},viewport:{width:375,height:667},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 7 Plus":{name:"iPhone 7 Plus",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/26.0 Mobile/15A372 Safari/604.1",screen:{width:414,height:736},viewport:{width:414,height:736},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 8":{name:"iPhone 8",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/26.0 Mobile/15A372 Safari/604.1",screen:{width:375,height:667},viewport:{width:375,height:667},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 8 Plus":{name:"iPhone 8 Plus",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/26.0 Mobile/15A372 Safari/604.1",screen:{width:414,height:736},viewport:{width:414,height:736},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone SE":{name:"iPhone SE",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/26.0 Mobile/14E304 Safari/602.1",screen:{width:320,height:568},viewport:{width:320,height:568},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone SE (3rd gen)":{name:"iPhone SE (3rd gen)",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/26.0 Mobile/19E241 Safari/602.1",screen:{width:375,height:667},viewport:{width:375,height:667},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone X":{name:"iPhone X",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/26.0 Mobile/15A372 Safari/604.1",screen:{width:375,height:812},viewport:{width:375,height:812},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone XR":{name:"iPhone XR",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:414,height:896},viewport:{width:414,height:896},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 11":{name:"iPhone 11",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:414,height:896},viewport:{width:414,height:715},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 11 Pro":{name:"iPhone 11 Pro",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:375,height:812},viewport:{width:375,height:635},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 11 Pro Max":{name:"iPhone 11 Pro Max",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:414,height:896},viewport:{width:414,height:715},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 12":{name:"iPhone 12",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:390,height:844},viewport:{width:390,height:664},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 12 Pro":{name:"iPhone 12 Pro",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:390,height:844},viewport:{width:390,height:664},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 12 Pro Max":{name:"iPhone 12 Pro Max",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:428,height:926},viewport:{width:428,height:746},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 12 Mini":{name:"iPhone 12 Mini",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:375,height:812},viewport:{width:375,height:629},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 13":{name:"iPhone 13",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:390,height:844},viewport:{width:390,height:664},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 13 Pro":{name:"iPhone 13 Pro",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:390,height:844},viewport:{width:390,height:664},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 13 Pro Max":{name:"iPhone 13 Pro Max",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:428,height:926},viewport:{width:428,height:746},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 13 Mini":{name:"iPhone 13 Mini",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:375,height:812},viewport:{width:375,height:629},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 14":{name:"iPhone 14",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:390,height:844},viewport:{width:390,height:664},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 14 Plus":{name:"iPhone 14 Plus",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:428,height:926},viewport:{width:428,height:746},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 14 Pro":{name:"iPhone 14 Pro",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:393,height:852},viewport:{width:393,height:660},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 14 Pro Max":{name:"iPhone 14 Pro Max",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:430,height:932},viewport:{width:430,height:740},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 15":{name:"iPhone 15",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:393,height:852},viewport:{width:393,height:659},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 15 Plus":{name:"iPhone 15 Plus",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:430,height:932},viewport:{width:430,height:739},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 15 Pro":{name:"iPhone 15 Pro",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:393,height:852},viewport:{width:393,height:659},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 15 Pro Max":{name:"iPhone 15 Pro Max",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:430,height:932},viewport:{width:430,height:739},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"Kindle Fire HDX":{name:"Kindle Fire HDX",userAgent:"Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true",screen:{width:800,height:1280},viewport:{width:800,height:1280},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"LG Optimus L70":{name:"LG Optimus L70",userAgent:"Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:384,height:640},viewport:{width:384,height:640},deviceScaleFactor:1.25,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Microsoft Lumia 550":{name:"Microsoft Lumia 550",userAgent:"Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36 Edge/14.14263",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Microsoft Lumia 950":{name:"Microsoft Lumia 950",userAgent:"Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36 Edge/14.14263",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:4,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nexus 10":{name:"Nexus 10",userAgent:"Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:800,height:1280},viewport:{width:800,height:1280},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nexus 4":{name:"Nexus 4",userAgent:"Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:384,height:640},viewport:{width:384,height:640},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nexus 5":{name:"Nexus 5",userAgent:"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nexus 5X":{name:"Nexus 5X",userAgent:"Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:412,height:732},viewport:{width:412,height:732},deviceScaleFactor:2.625,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nexus 6":{name:"Nexus 6",userAgent:"Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:412,height:732},viewport:{width:412,height:732},deviceScaleFactor:3.5,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nexus 6P":{name:"Nexus 6P",userAgent:"Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:412,height:732},viewport:{width:412,height:732},deviceScaleFactor:3.5,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nexus 7":{name:"Nexus 7",userAgent:"Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:600,height:960},viewport:{width:600,height:960},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nokia Lumia 520":{name:"Nokia Lumia 520",userAgent:"Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)",screen:{width:320,height:533},viewport:{width:320,height:533},deviceScaleFactor:1.5,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nokia N9":{name:"Nokia N9",userAgent:"Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13",screen:{width:480,height:854},viewport:{width:480,height:854},deviceScaleFactor:1,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"Pixel 2":{name:"Pixel 2",userAgent:"Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:411,height:731},viewport:{width:411,height:731},deviceScaleFactor:2.625,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Pixel 2 XL":{name:"Pixel 2 XL",userAgent:"Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:411,height:823},viewport:{width:411,height:823},deviceScaleFactor:3.5,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Pixel 3":{name:"Pixel 3",userAgent:"Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:393,height:786},viewport:{width:393,height:786},deviceScaleFactor:2.75,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Pixel 4":{name:"Pixel 4",userAgent:"Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:353,height:745},viewport:{width:353,height:745},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Pixel 4a (5G)":{name:"Pixel 4a (5G)",userAgent:"Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:412,height:892},viewport:{width:412,height:765},deviceScaleFactor:2.63,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Pixel 5":{name:"Pixel 5",userAgent:"Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:393,height:851},viewport:{width:393,height:727},deviceScaleFactor:2.75,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Pixel 7":{name:"Pixel 7",userAgent:"Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:412,height:915},viewport:{width:412,height:839},deviceScaleFactor:2.625,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Moto G4":{name:"Moto G4",userAgent:"Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Desktop Chrome HiDPI":{name:"Desktop Chrome HiDPI",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:1792,height:1120},viewport:{width:1280,height:720},deviceScaleFactor:2,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium"},"Desktop Edge HiDPI":{name:"Desktop Edge HiDPI",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36 Edg/141.0.7390.16",screen:{width:1792,height:1120},viewport:{width:1280,height:720},deviceScaleFactor:2,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium"},"Desktop Firefox HiDPI":{name:"Desktop Firefox HiDPI",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:142.0.1) Gecko/20100101 Firefox/142.0.1",screen:{width:1792,height:1120},viewport:{width:1280,height:720},deviceScaleFactor:2,isMobile:!1,hasTouch:!1,defaultBrowserType:"firefox"},"Desktop Safari":{name:"Desktop Safari",userAgent:"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Safari/605.1.15",screen:{width:1792,height:1120},viewport:{width:1280,height:720},deviceScaleFactor:2,isMobile:!1,hasTouch:!1,defaultBrowserType:"webkit"},"Desktop Chrome":{name:"Desktop Chrome",displayName:"Playwright Chromium",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:1920,height:1080},viewport:{width:1920,height:1080},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium"},"Desktop Chrome Medium Resolution":{name:"Desktop Chrome Medium Resolution",displayName:"Playwright Chromium",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:1280,height:720},viewport:{width:1280,height:720},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium"},"Desktop Chrome (Branded)":{name:"Desktop Chrome (Branded)",displayName:"Google Chrome",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:1920,height:1080},viewport:{width:1920,height:1080},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium",channel:"chrome"},"Desktop Chrome Medium Resolution (Branded)":{name:"Desktop Chrome Medium Resolution (Branded)",displayName:"Google Chrome",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:1280,height:720},viewport:{width:1280,height:720},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium",channel:"chrome"},"Desktop Edge":{name:"Desktop Edge",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36 Edg/141.0.7390.16",screen:{width:1920,height:1080},viewport:{width:1280,height:720},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium"},"Desktop Edge (Branded)":{name:"Desktop Edge (Branded)",displayName:"Microsoft Edge",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36 Edg/141.0.7390.16",screen:{width:1920,height:1080},viewport:{width:1920,height:1080},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium",channel:"msedge"},"Desktop Edge Medium Resolution (Branded)":{name:"Desktop Edge Medium Resolution (Branded)",displayName:"Microsoft Edge",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36 Edg/141.0.7390.16",screen:{width:1280,height:720},viewport:{width:1280,height:720},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium",channel:"msedge"},"Desktop Firefox":{name:"Desktop Firefox",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:142.0.1) Gecko/20100101 Firefox/142.0.1",screen:{width:1920,height:1080},viewport:{width:1280,height:720},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"firefox"}};var Ge={desktop:["Desktop Chrome","Desktop Chrome Medium Resolution","Desktop Chrome (Branded)","Desktop Chrome Medium Resolution (Branded)","Desktop Edge (Branded)","Desktop Edge Medium Resolution (Branded)","Desktop Safari"],mobile:["iPhone 15 Pro Max","iPhone 15 Pro","iPhone 15 Plus","iPhone 15","iPhone 14 Pro Max","iPhone 14 Pro","iPhone 14 Plus","iPhone 14","iPhone 13 Pro Max","iPhone 13 Pro","iPhone 13","iPhone 13 Mini","iPhone 12 Pro Max","iPhone 12 Pro","iPhone 12","iPhone 12 Mini","iPhone 11 Pro Max","iPhone 11 Pro","iPhone 11","iPhone XR","iPhone X","iPhone SE (3rd gen)","iPhone SE","iPhone 8 Plus","iPhone 8","iPhone 7 Plus","iPhone 7","iPhone 6 Plus","iPhone 6","Galaxy S24","Galaxy A55","Galaxy S9+","Galaxy S8","Galaxy S5","Galaxy Note 3","Galaxy Note II","Galaxy S III","Pixel 7","Pixel 5","Pixel 4a (5G)","Pixel 4","Pixel 3","Pixel 2 XL","Pixel 2","Nexus 6P","Nexus 6","Nexus 5X","Nexus 5","Nexus 4","Moto G4","LG Optimus L70","Microsoft Lumia 950","Microsoft Lumia 550","Nokia Lumia 520","Nokia N9","BlackBerry Z30"]},X=(t,e=!1)=>{let s=["chromium"];return e&&s.push("webkit"),Ge[t].map(i=>He[i]).filter(i=>i.defaultBrowserType&&s.includes(i.defaultBrowserType))};var vt={desktop:{label:"Desktop",type:"desktop",devices:X("desktop")},mobile:{label:"Mobile Web",type:"mobile",devices:X("mobile")}},At={desktop:{label:"Desktop",type:"desktop",devices:X("desktop",!0)},mobile:{label:"Mobile Web",type:"mobile",devices:X("mobile",!0)}};import{z as l}from"zod";var Be=l.enum(["JS_CODE","AI_MODE"]),me=l.object({type:Be,expression:l.string()}),$e=l.enum(["DRAFT","STEP","ACTION","IF_ELSE","WHILE_LOOP"]),U=l.object({uid:l.string(),type:$e}),ee=l.object({action_data:l.object({action_name:l.string(),kwargs:l.record(l.any()).optional(),args:l.array(l.any()).optional()}),action_description:l.string().optional(),url:l.string().optional(),xpath:l.string().nullable().optional(),locator:l.string().nullable().optional(),css_selector:l.string().nullable().optional(),unique_selector:l.string().nullable().optional(),element_index:l.number().nullable().optional(),frame_path:l.array(l.any()).optional(),artifacts:l.record(l.any()).optional(),feedback:l.string().optional(),original_browser_use_action:l.any().optional()}).passthrough(),Ke=U.extend({type:l.literal("DRAFT"),description:l.string()}),Ve=U.extend({type:l.literal("ACTION"),description:l.string(),action_entity:ee.optional(),locator:l.string().optional(),use_pure_vision:l.boolean().optional()}),k=l.lazy(()=>l.union([Ke,Ve,U.extend({type:l.literal("STEP"),description:l.string().optional().default(""),statements:l.array(k),reference_id:l.number().optional()}),U.extend({type:l.literal("IF_ELSE"),description:l.string().optional(),condition:me,then:l.array(k),else:l.array(k).optional()}),U.extend({type:l.literal("WHILE_LOOP"),description:l.string().optional(),condition:me,body:l.array(k),timeout_ms:l.number().optional()})])),M=l.object({version:l.string().optional(),goal:l.string(),url:l.string(),final_feedback:l.string().optional(),completed:l.boolean().optional(),success:l.boolean().optional(),statements:l.array(k),teardown:l.array(k).optional(),last_modified_at:l.string().optional()});import{stringify as Et,parse as Mt}from"yaml";import{v4 as I}from"uuid";function fe(t){let e={goal:t.goal,url:t.url,statements:t.statements.map(H)};return t.final_feedback&&(e.final_feedback=t.final_feedback),t.teardown&&t.teardown.length>0&&(e.teardown=t.teardown.map(H)),Et(e,{lineWidth:120,defaultKeyType:"PLAIN",defaultStringType:"PLAIN"})}function H(t){switch(t.type){case"DRAFT":return Pt(t);case"ACTION":return kt(t);case"STEP":return Ct(t);case"IF_ELSE":return Ot(t);case"WHILE_LOOP":return Lt(t)}}function Pt(t){return t.description}function kt(t){if((t.action_entity?.action_data?.action_name??t.action_entity?.action?.action_name)==="verify"){let r=(t.action_entity?.action_data?.kwargs??t.action_entity?.action?.kwargs)?.statement;if(typeof r=="string"&&!t.action_entity?.locator&&!t.action_entity?.xpath)return`VERIFY: ${r}`}if(!t.action_entity)return t.description;let s={description:t.description},i=xt(t.action_entity);return i&&(s.action_entity=i),t.locator&&(s.locator=t.locator),t.use_pure_vision&&(s.use_pure_vision=!0),s}function xt(t){let e={},s=!1,i=t.action_data??t.action;return i&&(e.action_data={action_name:i.action_name},i.kwargs&&Object.keys(i.kwargs).length>0&&(e.action_data.kwargs=i.kwargs),i.args&&i.args.length>0&&(e.action_data.args=i.args),s=!0),t.locator&&(e.locator=t.locator,s=!0),t.xpath&&(e.xpath=t.xpath,s=!0),s?e:void 0}function Ct(t){let e={STEP:t.description,statements:t.statements.map(H)};return t.reference_id!==void 0&&(e.reference_id=t.reference_id),e}function Ot(t){let e={IF:je(t.condition),THEN:t.then.map(H)};return t.else&&t.else.length>0&&(e.ELSE=t.else.map(H)),e}function Lt(t){let e={WHILE:je(t.condition),DO:t.body.map(H)};return t.timeout_ms!==void 0&&(e.timeout_ms=t.timeout_ms),e}function je(t){return t.type==="JS_CODE"?`js:${t.expression}`:t.expression}var ze=1024*1024;function _e(t){if(t.length>ze)throw new Error(`YAML input too large (${t.length} bytes, max ${ze})`);let e=Mt(t);if(!e||typeof e!="object")throw new Error("Invalid YAML: expected an object at root level");let s={version:"1.2.0",goal:e.goal,url:e.url,statements:G(e.statements??[])};e.final_feedback&&(s.final_feedback=e.final_feedback),e.teardown&&Array.isArray(e.teardown)&&(s.teardown=G(e.teardown));let i=M.safeParse(s);if(!i.success)throw new Error(`Invalid TestFlow after YAML conversion: ${JSON.stringify(i.error.errors)}`);return i.data}function G(t){if(!Array.isArray(t))throw new Error("Expected an array of statements");return t.map(It)}function It(t){if(typeof t=="string")return Dt(t);if(typeof t!="object"||t===null)throw new Error(`Invalid statement: expected string or object, got ${typeof t}`);let e=t;if("IF"in e)return Rt(e);if("WHILE"in e)return Ft(e);if("STEP"in e)return Nt(e);if("action_entity"in e)return Wt(e);if("description"in e&&typeof e.description=="string")return{uid:I(),type:"DRAFT",description:e.description};throw new Error(`Cannot infer statement type from object: ${JSON.stringify(e)}`)}function Dt(t){let e=t.match(/^VERIFY:\s*(.+)$/i);return e?{uid:I(),type:"ACTION",description:`Verify: ${e[1]}`,action_entity:{action_description:`Verify: ${e[1]}`,action_data:{action_name:"verify",kwargs:{statement:e[1]}}}}:{uid:I(),type:"DRAFT",description:t}}function Je(t){if(typeof t!="string")throw new Error(`Condition must be a string, got ${typeof t}`);return t.startsWith("js:")?{type:"JS_CODE",expression:t.slice(3)}:{type:"AI_MODE",expression:t}}function Rt(t){let e=Je(t.IF),s=t.THEN;if(!Array.isArray(s))throw new Error("IF_ELSE requires a THEN array");let i={uid:I(),type:"IF_ELSE",condition:e,then:G(s)};return"ELSE"in t&&Array.isArray(t.ELSE)&&(i.else=G(t.ELSE)),i}function Ft(t){let e=Je(t.WHILE),s=t.DO;if(!Array.isArray(s))throw new Error("WHILE_LOOP requires a DO array");let i={uid:I(),type:"WHILE_LOOP",condition:e,body:G(s)};return typeof t.timeout_ms=="number"&&(i.timeout_ms=t.timeout_ms),i}function Nt(t){let e=typeof t.STEP=="string"?t.STEP:"";if(!Array.isArray(t.statements))throw new Error("STEP requires a statements array");let s={uid:I(),type:"STEP",description:e,statements:G(t.statements)};return typeof t.reference_id=="number"&&(s.reference_id=t.reference_id),s}function Wt(t){let e=typeof t.description=="string"?t.description:"",s=t.action_entity,i;s&&(i={action_description:e,...s});let o={uid:I(),type:"ACTION",description:e};return i&&(o.action_entity=i),typeof t.locator=="string"&&(o.locator=t.locator),typeof t.use_pure_vision=="boolean"&&(o.use_pure_vision=t.use_pure_vision),o}var te=t=>{let e=[];switch(t.type){case"STEP":t.statements&&e.push({key:"statements",statements:t.statements});break;case"IF_ELSE":t.then&&e.push({key:"then",statements:t.then}),t.else&&e.push({key:"else",statements:t.else});break;case"WHILE_LOOP":t.body&&e.push({key:"body",statements:t.body});break}return e};var v=(t=>(t.DRAFT="DRAFT",t.STEP="STEP",t.ACTION="ACTION",t.IF_ELSE="IF_ELSE",t.WHILE_LOOP="WHILE_LOOP",t))(v||{});var we=18e4;var Se=class Xe{data={};sensitive=new Set;get(e){return this.data[e]}set(e,s,i=!1){this.data[e]=s,i?this.sensitive.add(e):this.sensitive.has(e)&&this.sensitive.delete(e)}getAll(){return{...this.data}}isSensitive(e){return this.sensitive.has(e)}getAllSensitiveKeys(){return new Set(this.sensitive)}delete(e){return this.sensitive.delete(e),delete this.data[e]}clear(){this.data={},this.sensitive.clear()}has(e){return e in this.data}get size(){return Object.keys(this.data).length}merge(e){for(let[s,i]of Object.entries(e.getAll()))this.set(s,i,e.isSensitive(s))}toJSON(){return{data:{...this.data},sensitiveKeys:Array.from(this.sensitive)}}static fromJSON(e){let s=new Xe;if(e.data){let i=new Set(e.sensitiveKeys||[]);for(let[o,r]of Object.entries(e.data))s.set(o,r,i.has(o))}return s}};import zt from"axios";function Vt(){let e=new Date().toISOString().replace(/T/,"-").replace(/:/g,"-").replace(/\..+/,""),s=Ht.randomBytes(2).toString("hex");return`${e}-${s}`}var ye=class{sessions=new Map;browserManager=null;baseDebugPort=9222;runDir;constructor(t={}){this.runDir=t.runDir||C.join(process.cwd(),"mcp-runs"),P.mkdirSync(this.runDir,{recursive:!0})}ensureBrowserManager(){this.browserManager||(this.browserManager=new Gt({testDir:this.runDir}))}async createSession(t){this.ensureBrowserManager();let e=Vt(),s=this.baseDebugPort+this.sessions.size,i=t.browserOptions||{},o,r;try{o=await this.browserManager.launchBrowser({debugPort:s,deviceName:i.deviceName,headless:i.headless,disableSecurity:i.disableSecurity,recordVideo:i.recordVideo,enableCamera:i.enableCamera,enableMicrophone:i.enableMicrophone,locale:i.locale,proxy:i.proxy,localStorageStatePath:i.storageStatePath}),r=await o.context.newPage(),t.startingUrl&&t.startingUrl!=="about:blank"&&await r.goto(t.startingUrl,{waitUntil:"domcontentloaded"});let n=await this.createSessionData(e,r,o.context,t.webAgentConfig,t.maxActions,o);return this.sessions.set(e,n),{sessionId:e,url:r.url()}}catch(n){throw r&&await r.close().catch(()=>{}),o&&this.browserManager&&await this.browserManager.terminateBrowser(o).catch(()=>{}),console.error("[SessionManager.createSession] Failed:",n),n}}async createSessionWithPage(t,e={}){let s=this.generateSessionId(),i=e.runDir||this.runDir;i!==this.runDir&&P.mkdirSync(i,{recursive:!0});let o=await this.createSessionData(s,t,t.context(),e.webAgentConfig,e.maxActions,void 0);return this.sessions.set(s,o),{sessionId:s}}generateSessionId(){let t=new Date,e=t.toISOString().slice(0,10),s=t.toTimeString().slice(0,8).replace(/:/g,"-"),i=Math.random().toString(36).substring(2,6);return`${e}_${s}_${i}`}async createSessionData(t,e,s,i,o,r){let n=[];e.on("console",c=>{n.push({type:c.type(),text:c.text(),timestamp:Date.now()})});let a=[];e.on("response",c=>{a.push({url:c.url(),method:c.request().method(),status:c.status(),timestamp:Date.now()})});let p=new Se;if(!i?.model)throw new Error("WebAgentConfig.model is required");let u=Bt({model:i.model,variableStore:p,executionHistory:[],testDataDir:i?.testDataDir||C.join(this.runDir,"files"),downloadDir:i?.downloadDir,organizationId:i?.organizationId,organizationSettings:i?.organizationSettings}),d=new qe(u);return d.agentServices.setupPageTracking(s),i?.testDataDownloader&&d.agentServices.setTestDataDownloader(i.testDataDownloader,i.testDataFileNames),{sessionId:t,browserInstance:r,page:e,webAgent:d,agentContext:u,variableStore:p,consoleLogs:n,networkLogs:a,createdAt:new Date,lastAccessedAt:new Date,abortController:new AbortController,maxActions:o,totalActions:0}}async closeSession(t){let e=this.sessions.get(t);e&&(e.page.removeAllListeners("console"),e.page.removeAllListeners("response"),e.browserInstance&&this.browserManager&&await this.browserManager.terminateBrowser(e.browserInstance),this.sessions.delete(t))}async closeAllSessions(){let t=Array.from(this.sessions.keys());for(let e of t)await this.closeSession(e)}getSessionCount(){return this.sessions.size}getSession(t){let e=this.sessions.get(t);return e?(e.lastAccessedAt=new Date,{sessionId:e.sessionId,sessionType:"browser",createdAt:e.createdAt,lastAccessedAt:e.lastAccessedAt,currentUrl:this.getPageForSession(e).url()}):null}getSessionData(t){return this.sessions.get(t)||null}getPage(t){let e=this.sessions.get(t);if(!e)throw new Error(`Session ${t} not found`);return this.getPageForSession(e)}getPageForSession(t){let e=t.webAgent.agentServices.getCurrentPage();return e&&!e.isClosed()?e:t.webAgent.agentServices.validatePage(t.page)}getWebAgent(t){let e=this.sessions.get(t);if(!e)throw new Error(`Session ${t} not found`);return e.webAgent}getVariableStore(t){let e=this.sessions.get(t);if(!e)throw new Error(`Session ${t} not found`);return e.variableStore}async getCurrentUrl(t){let e=this.sessions.get(t);if(!e)throw new Error(`Session ${t} not found`);return this.getPageForSession(e).url()}async navigate(t,e){let s=this.sessions.get(t);if(!s)throw new Error(`Session ${t} not found`);await this.getPageForSession(s).goto(e,{waitUntil:"domcontentloaded"})}async loginWithTestAccount(t,e){let s=this.sessions.get(t);if(!s)throw new Error(`Session ${t} not found`);if(!e.loginConfig||!e.username||!e.password)return!1;try{let i=this.getPageForSession(s),o=e.loginConfig.account?.type,r={site_url:i.url(),account:{type:o,username:e.username,password:e.password,two_factor_auth_config:e.loginConfig.account?.two_factor_auth_config},additional_prompt:e.loginConfig.additional_prompt,verification_hint:e.loginConfig.verification_hint,num_verification_exprs:0};return(await s.webAgent.loginPage(i,r)).success}catch(i){return console.error("Login failed:",i),!1}}async getPageInfo(t){let e=this.sessions.get(t);if(!e)throw new Error(`Session ${t} not found`);let s=this.getPageForSession(e);return{url:s.url(),title:await s.title()}}async inspectPage(t){let e=this.sessions.get(t);if(!e)throw new Error(`Session ${t} not found`);let{v4:s}=await import("uuid"),i=C.join(this.runDir,t);P.mkdirSync(i,{recursive:!0});let o=Date.now(),r=this.getPageForSession(e),n=await r.screenshot({fullPage:!1}),a=C.join(i,`screenshot-${o}.png`);P.writeFileSync(a,n);let p=new $t,{domState:u}=await p.getClickableElementsWithScreenshot(r),d=u.elementTree.clickableElementsToString(),c=s();e.domState=u,e.domStateId=c;let f=C.join(i,`dom-${o}.txt`);return P.writeFileSync(f,d),{screenshotPath:a,domText:d,domFilePath:f,domStateId:c,currentUrl:r.url()}}async getLocator(t,e){let s=this.sessions.get(t);if(!s)throw new Error(`Session ${t} not found`);if(!s.domState)throw new Error(`No DOM state cached for session ${t}. Call inspectPage first.`);let i=s.domState.selectorMap.get(e);if(!i)throw new Error(`Element index ${e} not found in DOM state. Available indices: ${Array.from(s.domState.selectorMap.keys()).join(", ")}`);let o=this.getPageForSession(s),r=await Kt(o,i),n=i.getAllTextTillNextClickableElement(2);return{element_index:e,locator:r.locator||null,xpath:r.xpath||i.xpath,frame_path:r.frame_path||[],tag_name:i.tagName,text:n.trim()}}getConsoleLogs(t,e){let s=this.sessions.get(t);if(!s)throw new Error(`Session ${t} not found`);let i=s.consoleLogs;return e?.sinceTimestamp!==void 0&&(i=i.filter(o=>o.timestamp>=e.sinceTimestamp)),e?.logTypes&&e.logTypes.length>0&&(i=i.filter(o=>e.logTypes.includes(o.type))),i}getNetworkLogs(t,e){let s=this.sessions.get(t);if(!s)throw new Error(`Session ${t} not found`);let i=s.networkLogs;return e?.sinceTimestamp!==void 0&&(i=i.filter(o=>o.timestamp>=e.sinceTimestamp)),e?.statusFilter&&(e.statusFilter==="errors"?i=i.filter(o=>o.status&&o.status>=400):e.statusFilter==="success"&&(i=i.filter(o=>o.status&&o.status>=200&&o.status<300))),i}clearLogs(t){let e=this.sessions.get(t);if(!e)throw new Error(`Session ${t} not found`);e.consoleLogs.length=0,e.networkLogs.length=0}async runTask(t,e,s){let i=this.sessions.get(t);if(!i)throw new Error(`Session ${t} not found`);i.abortController.signal.aborted&&(i.abortController=new AbortController);let o=s?.onEvent||this.createDefaultEventLogger();try{let r=this.getPageForSession(i),a=await(await import("sdk-internal")).runTask(e,r,i.webAgent.agentServices,o,{abortSignal:s?.abortSignal||i.abortController.signal,maxSteps:s?.maxSteps,executionHistory:i.agentContext.executionHistory,variables:i.variableStore.getAll?.()||{},sensitiveKeys:i.variableStore.getAllSensitiveKeys?.()||[]});return{success:a.status==="success"&&a.completed,actions:a.actionEntities||[],details:a.explanation||a.error,executionHistory:i.agentContext.executionHistory}}catch(r){return{success:!1,details:r instanceof Error?r.message:String(r)}}}createDefaultEventLogger(){return t=>{console.log("[run_task event]",JSON.stringify(t,null,2))}}async stopTask(t){let e=this.sessions.get(t);if(!e)throw new Error(`Session ${t} not found`);return e.abortController.signal.aborted?!1:(e.abortController.abort(),!0)}async act(t,e,s){let i=this.sessions.get(t);if(!i)throw new Error(`Session ${t} not found`);if(!i.domState)throw new Error(`No DOM state cached for session ${t}. Call inspectPage first.`);let o=await import("sdk-internal"),{toolRegistry:r,DomService:n}=o,a=s?.stopOnError??!0,p=this.getPageForSession(i),u=[],d=!0;for(let c of e){let f=Object.keys(c)[0],y=c[f]||{};try{let T={page:p,agentServices:i.webAgent.agentServices,domService:new n,domState:i.domState,actionDescription:y.description},b=await r.execute(f,y,T);if(u.push({success:b.success,action_entity:b.actionEntity,message:b.message,error:b.error}),!b.success&&(d=!1,a))break}catch(T){let b=T instanceof Error?T.message:String(T);if(u.push({success:!1,error:b}),d=!1,a)break}}return{success:d,results:u}}updateVariables(t,e,s){let i=this.sessions.get(t);if(!i)throw new Error(`Session ${t} not found`);let o=new Set(s||[]);for(let[r,n]of Object.entries(e)){let a=o.has(r);i.variableStore.set(r,n,a)}}getVariables(t){let e=this.sessions.get(t);if(!e)throw new Error(`Session ${t} not found`);return e.variableStore.getAll()}getVariableNames(t){let e=this.sessions.get(t);if(!e)throw new Error(`Session ${t} not found`);return Object.keys(e.variableStore.getAll())}clearExecutionHistory(t){let e=this.sessions.get(t);if(!e)throw new Error(`Session ${t} not found`);e.agentContext.executionHistory=[]}addExecutionHistory(t,e,s){let i=this.sessions.get(t);if(!i)throw new Error(`Session ${t} not found`);i.agentContext.executionHistory?.push([e,s])}getExecutionHistory(t){let e=this.sessions.get(t);if(!e)throw new Error(`Session ${t} not found`);return e.agentContext.executionHistory||[]}getRemainingActions(t){let e=this.sessions.get(t);if(!e)throw new Error(`Session ${t} not found`);let s=e.maxActions??1/0;return Math.max(0,s-e.totalActions)}addActions(t,e){let s=this.sessions.get(t);if(!s)throw new Error(`Session ${t} not found`);s.totalActions+=e}isMaxActionsReached(t){return this.getRemainingActions(t)<=0}getTotalActions(t){let e=this.sessions.get(t);if(!e)throw new Error(`Session ${t} not found`);return e.totalActions}createAbortController(t){let e=this.sessions.get(t);if(!e)throw new Error(`Session ${t} not found`);let s=new AbortController;return e.abortController.signal.aborted?s.abort():e.abortController.signal.addEventListener("abort",()=>{s.abort()},{once:!0}),s}abortTask(t){let e=this.sessions.get(t);if(!e)throw new Error(`Session ${t} not found`);return e.abortController.signal.aborted?!1:(e.abortController.abort(),!0)}resetAbortController(t){let e=this.sessions.get(t);if(!e)throw new Error(`Session ${t} not found`);e.abortController=new AbortController}isAborted(t){let e=this.sessions.get(t);if(!e)throw new Error(`Session ${t} not found`);return e.abortController.signal.aborted}getAbortSignal(t){let e=this.sessions.get(t);if(!e)throw new Error(`Session ${t} not found`);return e.abortController.signal}getAbortController(t){let e=this.sessions.get(t);if(!e)throw new Error(`Session ${t} not found`);return e.abortController}async getArtifact(t){if(!P.existsSync(t))throw new Error(`Artifact not found: ${t}`);let e=P.statSync(t),s=C.extname(t).toLowerCase();return[".png",".jpg",".jpeg",".gif",".webp"].includes(s)?{data:P.readFileSync(t).toString("base64"),format:"base64",size:e.size}:{data:P.readFileSync(t,"utf-8"),format:"text",size:e.size}}async saveStorageState(t,e){let s=this.sessions.get(t);if(!s)throw new Error(`Session ${t} not found`);let i=C.dirname(e);P.mkdirSync(i,{recursive:!0}),await this.getPageForSession(s).context().storageState({path:e,indexedDB:!0})}getSessionDir(t){return C.join(this.runDir,t)}getRunDir(){return this.runDir}},be=class{http;getApiToken;maxRetries;initialDelayMs;maxDelayMs;constructor(t){this.getApiToken=t.getApiToken,this.maxRetries=t.maxRetries??3,this.initialDelayMs=t.initialDelayMs??500,this.maxDelayMs=t.maxDelayMs??5e3,this.http=zt.create({baseURL:t.apiBaseUrl})}async getAuthHeaders(){return{Authorization:`Bearer ${await this.getApiToken()}`}}async retryRequest(t){let e,s=this.initialDelayMs;for(let i=0;i<=this.maxRetries;i++)try{return await t()}catch(o){if(e=o,i<this.maxRetries){let r=o;if(r.code==="ECONNRESET"||r.code==="ETIMEDOUT"||r.response&&r.response.status>=500){await new Promise(n=>setTimeout(n,s)),s=Math.min(s*2,this.maxDelayMs);continue}}throw o}throw e}async getTestAccount(t){let e=await this.getAuthHeaders();return(await this.retryRequest(()=>this.http.get(`/v1/test-accounts/${t}`,{headers:e}))).data}async listTestAccounts(t,e){let s=await this.getAuthHeaders(),i=t;if(!i&&e){let a=(await this.listEnvironments()).find(p=>p.url===e);if(!a)throw new Error(`No environment found with URL: ${e}`);i=a.id}let o="/v1/test-accounts";return i!==void 0&&(o+=`?environmentId=${i}`),(await this.retryRequest(()=>this.http.get(o,{headers:s}))).data}async createTestAccount(t){let e=await this.getAuthHeaders();return(await this.retryRequest(()=>this.http.post("/v1/test-accounts",{username:t.username,password:t.password,name:t.name,environmentId:t.environmentId,loginConfig:t.loginConfig},{headers:e}))).data}async getEnvironment(t){let e=await this.getAuthHeaders();return(await this.retryRequest(()=>this.http.get(`/v1/environments/${t}`,{headers:e}))).data}async listEnvironments(){let t=await this.getAuthHeaders();return(await this.retryRequest(()=>this.http.get("/v1/environments",{headers:t}))).data}async getFolder(t){let e=await this.getAuthHeaders(),s=await this.retryRequest(()=>this.http.get(`/v1/test-folders/${t}`,{headers:e}));return s.data.data||s.data}async listFolders(t,e){let s=await this.getAuthHeaders(),i,o=new URLSearchParams;t!==void 0?(i="/v1/test-folders",o.append("parentId",t===null?"null":String(t))):i="/v1/test-folders/all",e&&o.append("search",e);let r=o.toString();r&&(i+=`?${r}`);let n=await this.retryRequest(()=>this.http.get(i,{headers:s}));return n.data.data||n.data}async createFolder(t){let e=await this.getAuthHeaders(),s=await this.retryRequest(()=>this.http.post("/v1/test-folders",{name:t.name,description:t.description,parentId:t.parentId},{headers:e}));return s.data.data||s.data}async getTestCase(t){let e=await this.getAuthHeaders();return(await this.retryRequest(()=>this.http.get(`/v1/test-cases/${t}`,{headers:e}))).data}async createTestCase(t){let e=await this.getAuthHeaders();return(await this.retryRequest(()=>this.http.post("/v1/test-cases",{title:t.title,test_flow:t.testFlow,folder_id:t.folderId,environment_configs:t.environmentConfigs},{headers:e}))).data}async updateTestCase(t,e){let s=await this.getAuthHeaders();return(await this.retryRequest(()=>this.http.put(`/v1/test-cases/${t}`,{title:e.title,test_flow:e.testFlow},{headers:s}))).data}async generateTest(t){let e=await this.getAuthHeaders();return(await this.retryRequest(()=>this.http.post("/v1/test-batch-gen-tasks/create",{title:t.title,startingUrl:t.startingUrl,goal:t.goal,environmentId:t.environmentId,testAccountGroup:t.testAccountGroup,folderId:t.folderId,creation_mode:"SINGLE"},{headers:e}))).data.testCase}async listTestRuns(t){let e=await this.getAuthHeaders(),s=new URLSearchParams;t?.testPlanId!==void 0&&s.append("testPlanId",String(t.testPlanId)),t?.trigger&&s.append("trigger",t.trigger),t?.result&&s.append("result",t.result),t?.limit!==void 0&&s.append("limit",String(t.limit));let i=s.toString(),o=i?`/v1/test-runs?${i}`:"/v1/test-runs";return(await this.retryRequest(()=>this.http.get(o,{headers:e}))).data}async getTestRunDetails(t){let e=await this.getAuthHeaders();return(await this.retryRequest(()=>this.http.get(`/run-results/${t}`,{headers:e}))).data}async getTestCaseResult(t){let e=await this.getAuthHeaders();return(await this.retryRequest(()=>this.http.get(`/test-case-results/${t}`,{headers:e}))).data}async runTestCase(t,e){let s=await this.getAuthHeaders(),i={trigger:"API"};return e&&(i.environment={id:e}),(await this.retryRequest(()=>this.http.post(`/v1/test-run/test-case/${t}`,i,{headers:s}))).data}async getS3FileContents(t){try{let e=await this.getAuthHeaders(),s=await this.retryRequest(()=>this.http.get("/v1/s3/file",{headers:e,params:{uri:t}}));return typeof s.data=="string"?s.data:JSON.stringify(s.data)}catch(e){return console.error("Failed to get S3 file contents:",e),null}}};var Te={"browser-session-basics":{name:"browser-session-basics",description:"Basic browser session management for UI verification",content:`# Browser Session Basics
|
|
3
|
+
|
|
4
|
+
1. Create: new_session(starting_url) - Launch browser session
|
|
5
|
+
2. Inspect: inspect_page(session_id) - Get screenshot and DOM with element indices
|
|
6
|
+
3. Act: act(session_id, actions) - Execute actions using element indices
|
|
7
|
+
4. Close: close_session(session_id) - Close the browser session
|
|
8
|
+
|
|
9
|
+
Workflow: inspect_page \u2192 act \u2192 inspect_page \u2192 act \u2192 ... until done
|
|
10
|
+
|
|
11
|
+
act returns: { success, actions: [{ action_entity, success, error? }] }
|
|
12
|
+
|
|
13
|
+
**Action parameters:** See resource \`shiplight://schemas/action-entity\`
|
|
14
|
+
`}};function ve(t){return Te[t]?.content}function Ae(){return Object.values(Te).map(t=>({name:t.name,description:t.description}))}var Ee=class{tools=new Map;registerAll(t,e){for(let s of t.toolDefinitions){let i=this.findMethodForTool(e,s.name);i&&this.tools.set(s.name,{definition:s,handler:e[i].bind(e)})}return this}registerTools(t,e,s){for(let i of s){let o=this.findDefinitionForMethod(t.toolDefinitions,i);o&&this.tools.set(o.name,{definition:o,handler:e[i].bind(e)})}return this}registerTool(t,e){return this.tools.set(t.name,{definition:t,handler:e}),this}build(){return{tools:Array.from(this.tools.values()).map(s=>s.definition),handleToolCall:async(s,i,o)=>{let r=this.tools.get(s);if(!r)throw new Error(`Unknown tool: ${s}`);return r.handler(i,o)}}}getRegisteredToolNames(){return Array.from(this.tools.keys())}hasToolRegistered(t){return this.tools.has(t)}findMethodForTool(t,e){let i={new_session:"newSession",save_storage_state:"saveStorageState",close_session:"closeSession",close_all:"closeAllSessions",get_session_state:"getCurrentState",navigate:"navigate",get_page_info:"getPageInfo",inspect_page:"inspectPage",act:"act",get_locator:"getLocator",update_variables:"updateVariables",clear_execution_history:"clearExecutionHistory",get_browser_console_logs:"getConsoleLogs",get_browser_network_logs:"getNetworkLogs",clear_logs:"clearLogs",get_artifact:"getArtifact",list_environments:"listEnvironments",list_test_accounts:"listTestAccounts",get_test_account:"getTestAccount",create_test_account:"createTestAccount",list_folders:"listFolders",create_folder:"createFolder",get_folder:"getFolder",create_test_case:"createTestCase",update_test_case:"updateTestCase",get_test_case:"getTestCase",run_test_case:"runTestCase",run_test_flow:"runTestFlow",list_test_runs:"listTestRuns",get_test_run_details:"getTestRunDetails",get_test_case_result:"getTestCaseResult",get_test_case_result_steps:"getTestCaseResultSteps",get_step_artifacts:"getStepArtifacts"}[e];return i&&typeof t[i]=="function"?i:null}findDefinitionForMethod(t,e){let i={newSession:["new_session"],saveStorageState:["save_storage_state"],closeSession:["close_session"],closeAllSessions:["close_all"],getCurrentState:["get_session_state"],navigate:["navigate"],getPageInfo:["get_page_info"],inspectPage:["inspect_page"],act:["act"],getLocator:["get_locator"],updateVariables:["update_variables"],clearExecutionHistory:["clear_execution_history"],getConsoleLogs:["get_browser_console_logs"],getNetworkLogs:["get_browser_network_logs"],clearLogs:["clear_logs"],getArtifact:["get_artifact"],listEnvironments:["list_environments"],listTestAccounts:["list_test_accounts"],getTestAccount:["get_test_account"],createTestAccount:["create_test_account"],listFolders:["list_folders"],createFolder:["create_folder"],getFolder:["get_folder"],createTestCase:["create_test_case"],updateTestCase:["update_test_case"],getTestCase:["get_test_case"],runTestCase:["run_test_case"],runTestFlow:["run_test_flow"],listTestRuns:["list_test_runs"],getTestRunDetails:["get_test_run_details"],getTestCaseResult:["get_test_case_result"],getTestCaseResultSteps:["get_test_case_result_steps"],getStepArtifacts:["get_step_artifacts"]}[e];if(!i)return null;for(let o of i){let r=t.find(n=>n.name===o);if(r)return r}return null}};var B=["click","double_click","right_click","hover","input_text","clear_input","press","send_keys_on_element","select_dropdown_option","get_dropdown_options","set_date_for_native_date_picker","scroll","scroll_to_text","scroll_on_element","go_to_url","go_back","reload_page","switch_tab","close_tab","wait","wait_for_page_ready","wait_for_download_complete","upload_file","save_variable","verify","ai_extract","ai_wait_until","generate_2fa_code"];import{zodToJsonSchema as jt}from"zod-to-json-schema";import{getToolRegistry as Jt,ensureToolsRegistered as Xt}from"sdk-internal";function qt(t,e){let s=[];s.push(`## Supported Actions
|
|
15
|
+
`);for(let i of e){let o=t.get(i);if(!o)continue;let r=jt(o.schema,{$refStrategy:"none"});s.push(`#### ${o.name}`),s.push(o.description),s.push("```json"),s.push(JSON.stringify(r,null,2)),s.push("```\n")}return s.join(`
|
|
16
|
+
`)}var Ye=[{uri:"shiplight://schemas/testflow-v1.2.0",name:"TestFlow Schema v1.2.0",description:"Complete schema documentation for TestFlow v1.2.0 format with examples and conversion guide",mimeType:"text/markdown"},{uri:"shiplight://schemas/action-entity",name:"ActionEntity Schema & Action Parameters",description:"Detailed parameter documentation for supported browser actions",mimeType:"text/markdown"}];function Ze(){return`# TestFlow Schema v1.2.0
|
|
17
|
+
|
|
18
|
+
## Overview
|
|
19
|
+
|
|
20
|
+
TestFlow is the format for defining test cases in Shiplight. It is a tree-structured format supporting conditional logic, loops, and variables.
|
|
21
|
+
|
|
22
|
+
**IMPORTANT - Local File Storage:** When saving test flows to local files, always use YAML format (.yaml extension). Use \`get_test_case\` with \`output_format: "yaml"\` to retrieve flows as YAML. The \`create_test_case\` and \`update_test_case\` tools accept YAML strings directly. YAML is the standard format for local test flow files \u2014 do NOT save raw JSON test flows to local files.
|
|
23
|
+
|
|
24
|
+
## Interface: TestFlow
|
|
25
|
+
|
|
26
|
+
\`\`\`typescript
|
|
27
|
+
interface TestFlow {
|
|
28
|
+
version?: string; // Schema version, use "1.2.0"
|
|
29
|
+
goal: string; // Test objective description
|
|
30
|
+
url: string; // Starting URL
|
|
31
|
+
final_feedback: string; // Final test execution feedback
|
|
32
|
+
completed: boolean; // Whether test completed
|
|
33
|
+
success: boolean; // Whether test passed
|
|
34
|
+
statements: Statement[]; // Test steps/actions (tree structure)
|
|
35
|
+
teardown?: Statement[]; // Cleanup steps (optional)
|
|
36
|
+
last_modified_at?: string; // ISO timestamp (optional)
|
|
37
|
+
}
|
|
38
|
+
\`\`\`
|
|
39
|
+
|
|
40
|
+
## Statement Types
|
|
41
|
+
|
|
42
|
+
Statements form a tree structure. Each statement must have:
|
|
43
|
+
- \`uid\`: Unique identifier (you can use simple strings like "action-1", server will regenerate with UUID4)
|
|
44
|
+
- \`type\`: One of "DRAFT", "ACTION", "STEP", "IF_ELSE", "WHILE_LOOP"
|
|
45
|
+
|
|
46
|
+
### 1. DRAFT (Transient)
|
|
47
|
+
|
|
48
|
+
A draft is a transient statement that converts to ACTION or STEP after execution.
|
|
49
|
+
Use DRAFT when you're not sure if an instruction needs one or multiple steps.
|
|
50
|
+
|
|
51
|
+
\`\`\`typescript
|
|
52
|
+
interface Draft {
|
|
53
|
+
uid: string; // Will be regenerated as UUID4
|
|
54
|
+
type: "DRAFT"; // Must be exactly "DRAFT"
|
|
55
|
+
description: string; // Natural language instruction
|
|
56
|
+
}
|
|
57
|
+
\`\`\`
|
|
58
|
+
|
|
59
|
+
**DRAFT Example:**
|
|
60
|
+
\`\`\`json
|
|
61
|
+
{
|
|
62
|
+
"uid": "draft-1",
|
|
63
|
+
"type": "DRAFT",
|
|
64
|
+
"description": "Fill out the contact form and submit"
|
|
65
|
+
}
|
|
66
|
+
\`\`\`
|
|
67
|
+
|
|
68
|
+
After execution:
|
|
69
|
+
- If 1 action is produced \u2192 converts to ACTION
|
|
70
|
+
- If 2+ actions are produced \u2192 converts to STEP with child ACTIONs
|
|
71
|
+
|
|
72
|
+
### 3. ACTION (Leaf Node)
|
|
73
|
+
|
|
74
|
+
Represents a single browser action. Generated automatically by the AI agent.
|
|
75
|
+
|
|
76
|
+
\`\`\`typescript
|
|
77
|
+
interface Action {
|
|
78
|
+
uid: string; // Will be regenerated as UUID4
|
|
79
|
+
type: "ACTION"; // Must be exactly "ACTION"
|
|
80
|
+
description: string; // Human-readable description
|
|
81
|
+
action_entity?: ActionEntity; // Agent-generated action details (DO NOT construct manually)
|
|
82
|
+
locator?: string; // Optional Playwright locator
|
|
83
|
+
use_pure_vision?: boolean; // Optional vision mode flag
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface ActionEntity {
|
|
87
|
+
action_description: string; // Action description
|
|
88
|
+
url: string; // Page URL where action occurred
|
|
89
|
+
action_data: {
|
|
90
|
+
action_name: string; // Name of the action
|
|
91
|
+
kwargs: Record<string, any>; // Action parameters
|
|
92
|
+
};
|
|
93
|
+
// Additional fields populated by agent (feedback, locator, frame_path, etc.)
|
|
94
|
+
}
|
|
95
|
+
\`\`\`
|
|
96
|
+
|
|
97
|
+
**ACTION Example (Click):**
|
|
98
|
+
\`\`\`json
|
|
99
|
+
{
|
|
100
|
+
"uid": "action-1",
|
|
101
|
+
"type": "ACTION",
|
|
102
|
+
"description": "Click login button",
|
|
103
|
+
"action_entity": {
|
|
104
|
+
"action_description": "Click login button",
|
|
105
|
+
"url": "https://example.com/login",
|
|
106
|
+
"action_data": {
|
|
107
|
+
"action_name": "click",
|
|
108
|
+
"kwargs": {}
|
|
109
|
+
},
|
|
110
|
+
"locator": "getByRole('button', { name: 'Login' })",
|
|
111
|
+
"xpath": "html/body/div/button[1]"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
\`\`\`
|
|
115
|
+
|
|
116
|
+
**ACTION Example (Verification):**
|
|
117
|
+
For verification steps, use \`action_name: "verify"\` with a \`statement\` in kwargs:
|
|
118
|
+
\`\`\`json
|
|
119
|
+
{
|
|
120
|
+
"uid": "action-2",
|
|
121
|
+
"type": "ACTION",
|
|
122
|
+
"description": "Verify dashboard is displayed",
|
|
123
|
+
"action_entity": {
|
|
124
|
+
"action_description": "Verify dashboard is displayed",
|
|
125
|
+
"url": "https://example.com/dashboard",
|
|
126
|
+
"action_data": {
|
|
127
|
+
"action_name": "verify",
|
|
128
|
+
"kwargs": {
|
|
129
|
+
"statement": "The dashboard page is displayed with the welcome message"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
\`\`\`
|
|
135
|
+
|
|
136
|
+
**IMPORTANT:** Do NOT use vague descriptions like "Verify X works". Instead, use the \`verify\` action with a clear statement describing what should be true.
|
|
137
|
+
|
|
138
|
+
### 4. STEP (Container)
|
|
139
|
+
|
|
140
|
+
Groups multiple related statements together for organization.
|
|
141
|
+
|
|
142
|
+
**IMPORTANT:** Only use STEP when grouping 2 or more related actions. Do NOT wrap a single action in a STEP - just use the ACTION directly at the top level.
|
|
143
|
+
|
|
144
|
+
\`\`\`typescript
|
|
145
|
+
interface Step {
|
|
146
|
+
uid: string; // Will be regenerated as UUID4
|
|
147
|
+
type: "STEP"; // Must be exactly "STEP"
|
|
148
|
+
description: string; // Human-readable description
|
|
149
|
+
statements: Statement[]; // Nested statements (must have 2+ items)
|
|
150
|
+
reference_id?: number; // Optional reference to another test
|
|
151
|
+
}
|
|
152
|
+
\`\`\`
|
|
153
|
+
|
|
154
|
+
**STEP Example:**
|
|
155
|
+
\`\`\`json
|
|
156
|
+
{
|
|
157
|
+
"uid": "step-1",
|
|
158
|
+
"type": "STEP",
|
|
159
|
+
"description": "Complete login process",
|
|
160
|
+
"statements": [
|
|
161
|
+
{
|
|
162
|
+
"uid": "action-1",
|
|
163
|
+
"type": "ACTION",
|
|
164
|
+
"description": "Fill username field",
|
|
165
|
+
"action_entity": { /* ... */ }
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"uid": "action-2",
|
|
169
|
+
"type": "ACTION",
|
|
170
|
+
"description": "Fill password field",
|
|
171
|
+
"action_entity": { /* ... */ }
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"uid": "action-3",
|
|
175
|
+
"type": "ACTION",
|
|
176
|
+
"description": "Click login button",
|
|
177
|
+
"action_entity": { /* ... */ }
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
\`\`\`
|
|
182
|
+
|
|
183
|
+
### 3. IF_ELSE (Conditional)
|
|
184
|
+
|
|
185
|
+
Conditional execution based on a condition.
|
|
186
|
+
|
|
187
|
+
\`\`\`typescript
|
|
188
|
+
enum ConditionType {
|
|
189
|
+
JS_CODE = "JS_CODE", // JavaScript code, evaluates to a boolean or unknown
|
|
190
|
+
AI_MODE = "AI_MODE" // AI mode, a natural language description that evaluates to a boolean or unknown
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
interface Condition {
|
|
194
|
+
type: ConditionType; // Either "JS_CODE" or "AI_MODE"
|
|
195
|
+
expression: string; // JavaScript code (for JS_CODE) or natural language (for AI_MODE)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
interface IfElse {
|
|
199
|
+
uid: string; // Will be regenerated as UUID4
|
|
200
|
+
type: "IF_ELSE"; // Must be exactly "IF_ELSE"
|
|
201
|
+
condition: Condition; // Condition object with type and expression
|
|
202
|
+
then: Statement[]; // Execute if condition true
|
|
203
|
+
else?: Statement[]; // Execute if condition false (optional)
|
|
204
|
+
}
|
|
205
|
+
\`\`\`
|
|
206
|
+
|
|
207
|
+
**Condition Examples:**
|
|
208
|
+
\`\`\`json
|
|
209
|
+
// AI_MODE - natural language condition (recommended)
|
|
210
|
+
{
|
|
211
|
+
"type": "AI_MODE",
|
|
212
|
+
"expression": "FAQ section is not visible on the page"
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// JS_CODE - JavaScript expression
|
|
216
|
+
{
|
|
217
|
+
"type": "JS_CODE",
|
|
218
|
+
"expression": "document.querySelector('.faq-section') === null"
|
|
219
|
+
}
|
|
220
|
+
\`\`\`
|
|
221
|
+
|
|
222
|
+
**IF_ELSE Example:**
|
|
223
|
+
\`\`\`json
|
|
224
|
+
{
|
|
225
|
+
"uid": "if-else-1",
|
|
226
|
+
"type": "IF_ELSE",
|
|
227
|
+
"condition": {
|
|
228
|
+
"type": "AI_MODE",
|
|
229
|
+
"expression": "user is logged in"
|
|
230
|
+
},
|
|
231
|
+
"then": [
|
|
232
|
+
{
|
|
233
|
+
"uid": "action-1",
|
|
234
|
+
"type": "ACTION",
|
|
235
|
+
"description": "Click logout button",
|
|
236
|
+
"action_entity": { /* ... */ }
|
|
237
|
+
}
|
|
238
|
+
],
|
|
239
|
+
"else": [
|
|
240
|
+
{
|
|
241
|
+
"uid": "action-2",
|
|
242
|
+
"type": "ACTION",
|
|
243
|
+
"description": "Click login button",
|
|
244
|
+
"action_entity": { /* ... */ }
|
|
245
|
+
}
|
|
246
|
+
]
|
|
247
|
+
}
|
|
248
|
+
\`\`\`
|
|
249
|
+
|
|
250
|
+
### 4. WHILE_LOOP
|
|
251
|
+
|
|
252
|
+
Loop while a condition is true.
|
|
253
|
+
|
|
254
|
+
\`\`\`typescript
|
|
255
|
+
interface WhileLoop {
|
|
256
|
+
uid: string; // Will be regenerated as UUID4
|
|
257
|
+
type: "WHILE_LOOP"; // Must be exactly "WHILE_LOOP"
|
|
258
|
+
condition: Condition; // Condition object with type and expression
|
|
259
|
+
body: Statement[]; // Execute while condition true
|
|
260
|
+
timeout_ms?: number; // Optional timeout in milliseconds
|
|
261
|
+
}
|
|
262
|
+
\`\`\`
|
|
263
|
+
|
|
264
|
+
**WHILE_LOOP Example:**
|
|
265
|
+
\`\`\`json
|
|
266
|
+
{
|
|
267
|
+
"uid": "while-1",
|
|
268
|
+
"type": "WHILE_LOOP",
|
|
269
|
+
"condition": {
|
|
270
|
+
"type": "AI_MODE",
|
|
271
|
+
"expression": "FAQ section is not visible on the page"
|
|
272
|
+
},
|
|
273
|
+
"body": [
|
|
274
|
+
{
|
|
275
|
+
"uid": "action-1",
|
|
276
|
+
"type": "ACTION",
|
|
277
|
+
"description": "Scroll down one page",
|
|
278
|
+
"action_entity": {
|
|
279
|
+
"action_description": "Scroll down one page",
|
|
280
|
+
"url": "https://example.com",
|
|
281
|
+
"action_data": {
|
|
282
|
+
"action_name": "scroll",
|
|
283
|
+
"kwargs": {"down": true, "num_pages": 1}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
],
|
|
288
|
+
"timeout_ms": 30000
|
|
289
|
+
}
|
|
290
|
+
\`\`\`
|
|
291
|
+
|
|
292
|
+
## Complete TestFlow Example
|
|
293
|
+
|
|
294
|
+
A typical TestFlow structure:
|
|
295
|
+
|
|
296
|
+
\`\`\`json
|
|
297
|
+
{
|
|
298
|
+
"version": "1.2.0",
|
|
299
|
+
"goal": "Verify user can login and view dashboard",
|
|
300
|
+
"url": "https://app.example.com",
|
|
301
|
+
"final_feedback": "Test passed - user successfully logged in and dashboard displayed",
|
|
302
|
+
"completed": true,
|
|
303
|
+
"success": true,
|
|
304
|
+
"statements": [
|
|
305
|
+
{
|
|
306
|
+
"uid": "step-1",
|
|
307
|
+
"type": "STEP",
|
|
308
|
+
"description": "Complete login process",
|
|
309
|
+
"statements": [
|
|
310
|
+
{
|
|
311
|
+
"uid": "action-1",
|
|
312
|
+
"type": "ACTION",
|
|
313
|
+
"description": "Fill username field",
|
|
314
|
+
"action_entity": { /* Agent-generated details */ }
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
"uid": "action-2",
|
|
318
|
+
"type": "ACTION",
|
|
319
|
+
"description": "Fill password field",
|
|
320
|
+
"action_entity": { /* Agent-generated details */ }
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"uid": "action-3",
|
|
324
|
+
"type": "ACTION",
|
|
325
|
+
"description": "Click login button",
|
|
326
|
+
"action_entity": { /* Agent-generated details */ }
|
|
327
|
+
}
|
|
328
|
+
]
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
"uid": "action-4",
|
|
332
|
+
"type": "ACTION",
|
|
333
|
+
"description": "Verify dashboard heading is visible",
|
|
334
|
+
"action_entity": { /* Agent-generated details */ }
|
|
335
|
+
}
|
|
336
|
+
]
|
|
337
|
+
}
|
|
338
|
+
\`\`\`
|
|
339
|
+
|
|
340
|
+
**Note:** The agent automatically populates all action_entity fields with proper selectors,
|
|
341
|
+
action data, and metadata. You don't need to understand or modify this structure.
|
|
342
|
+
|
|
343
|
+
## YAML Format
|
|
344
|
+
|
|
345
|
+
TestFlows can also be represented in a clean YAML format for local file storage.
|
|
346
|
+
\`get_test_case\` with \`output_format: "yaml"\` returns YAML; \`create_test_case\` and \`update_test_case\` accept YAML strings directly.
|
|
347
|
+
|
|
348
|
+
### Simple YAML (all DRAFTs):
|
|
349
|
+
\`\`\`yaml
|
|
350
|
+
goal: Login and verify dashboard
|
|
351
|
+
url: https://app.example.com
|
|
352
|
+
statements:
|
|
353
|
+
- Enter username admin@test.com
|
|
354
|
+
- Enter password
|
|
355
|
+
- Click login button
|
|
356
|
+
- "VERIFY: dashboard heading is visible"
|
|
357
|
+
\`\`\`
|
|
358
|
+
|
|
359
|
+
### YAML with structure:
|
|
360
|
+
\`\`\`yaml
|
|
361
|
+
goal: Login with cookie handling
|
|
362
|
+
url: https://app.example.com
|
|
363
|
+
statements:
|
|
364
|
+
- IF: cookie consent dialog is visible
|
|
365
|
+
THEN:
|
|
366
|
+
- Accept all cookies
|
|
367
|
+
- STEP: Complete login
|
|
368
|
+
statements:
|
|
369
|
+
- Enter username
|
|
370
|
+
- Enter password
|
|
371
|
+
- Click login button
|
|
372
|
+
- "VERIFY: dashboard heading is visible"
|
|
373
|
+
teardown:
|
|
374
|
+
- Click logout button
|
|
375
|
+
\`\`\`
|
|
376
|
+
|
|
377
|
+
### YAML with action_entity:
|
|
378
|
+
\`\`\`yaml
|
|
379
|
+
goal: Login test
|
|
380
|
+
url: https://app.example.com
|
|
381
|
+
final_feedback: Login flow completed successfully
|
|
382
|
+
statements:
|
|
383
|
+
- description: Click login button
|
|
384
|
+
action_entity:
|
|
385
|
+
action_data:
|
|
386
|
+
action_name: click
|
|
387
|
+
kwargs: {}
|
|
388
|
+
locator: "getByRole('button', { name: 'Login' })"
|
|
389
|
+
\`\`\`
|
|
390
|
+
|
|
391
|
+
### YAML type inference rules:
|
|
392
|
+
- Plain string \u2192 DRAFT (description = the string)
|
|
393
|
+
- \`VERIFY: <text>\` \u2192 ACTION with action_name="verify", kwargs.statement=text
|
|
394
|
+
- \`IF: <condition>\` + \`THEN:\` [+ \`ELSE:\`] \u2192 IF_ELSE
|
|
395
|
+
- \`WHILE: <condition>\` + \`DO:\` [+ \`timeout_ms:\`] \u2192 WHILE_LOOP
|
|
396
|
+
- \`STEP: <description>\` + \`statements:\` \u2192 STEP
|
|
397
|
+
- Object with \`action_entity\` \u2192 ACTION
|
|
398
|
+
- Object with \`description\` only \u2192 DRAFT
|
|
399
|
+
- Condition prefix \`js:\` \u2192 JS_CODE condition type (default: AI_MODE)
|
|
400
|
+
|
|
401
|
+
## Key Points
|
|
402
|
+
|
|
403
|
+
1. **TestFlow Structure**: Contains version, goal, url, statements, completion status, and feedback
|
|
404
|
+
2. **Statement Types**: ACTION, STEP, IF_ELSE, WHILE_LOOP
|
|
405
|
+
3. **Tree Structure**: STEPs can contain other STEPs and statements for hierarchical organization
|
|
406
|
+
4. **UIDs**: Use simple strings like "action-1" - they will be regenerated as UUID4 when saved
|
|
407
|
+
5. **STEP Usage**: Only use STEP to group 2+ related actions. Single actions should be ACTION at top level.
|
|
408
|
+
6. **Verification Actions**: Use \`action_name: "verify"\` with \`kwargs: { statement: "..." }\` for assertions
|
|
409
|
+
7. **YAML Format**: \`create_test_case\` and \`update_test_case\` accept YAML strings directly; \`get_test_case\` with \`output_format: "yaml"\` returns YAML
|
|
410
|
+
`}async function Qe(){await Xt();let t=Jt();return`# ActionEntity Schema & Action Parameters
|
|
411
|
+
|
|
412
|
+
## Overview
|
|
413
|
+
|
|
414
|
+
This document describes the ActionEntity schema and parameters for supported browser actions.
|
|
415
|
+
|
|
416
|
+
**IMPORTANT:** All actions require a \`description\` field with a semantic, human-readable description.
|
|
417
|
+
|
|
418
|
+
## ActionEntity Structure
|
|
419
|
+
|
|
420
|
+
\`\`\`typescript
|
|
421
|
+
interface ActionEntity {
|
|
422
|
+
action_description: string; // Human-readable description
|
|
423
|
+
action_data: {
|
|
424
|
+
action_name: string; // Name of the action
|
|
425
|
+
kwargs: Record<string, any>; // Action-specific parameters
|
|
426
|
+
};
|
|
427
|
+
locator?: string; // Playwright locator (auto-generated)
|
|
428
|
+
xpath?: string; // XPath selector (auto-generated)
|
|
429
|
+
frame_path?: string[]; // Frame path for iframes (auto-generated)
|
|
430
|
+
}
|
|
431
|
+
\`\`\`
|
|
432
|
+
|
|
433
|
+
${qt(t,B)}
|
|
434
|
+
|
|
435
|
+
## Examples
|
|
436
|
+
|
|
437
|
+
### Click a button
|
|
438
|
+
\`\`\`json
|
|
439
|
+
{"click": {"element_index": 5, "description": "Click the Submit button"}}
|
|
440
|
+
\`\`\`
|
|
441
|
+
|
|
442
|
+
### Type in a field
|
|
443
|
+
\`\`\`json
|
|
444
|
+
{"input_text": {"element_index": 3, "text": "john@example.com", "description": "Enter email address"}}
|
|
445
|
+
\`\`\`
|
|
446
|
+
|
|
447
|
+
### Press Enter
|
|
448
|
+
\`\`\`json
|
|
449
|
+
{"press": {"keys": "Enter", "description": "Submit the form"}}
|
|
450
|
+
\`\`\`
|
|
451
|
+
|
|
452
|
+
### Select dropdown option
|
|
453
|
+
\`\`\`json
|
|
454
|
+
{"select_dropdown_option": {"element_index": 7, "option": "United States", "description": "Select country"}}
|
|
455
|
+
\`\`\`
|
|
456
|
+
|
|
457
|
+
### Navigate to URL
|
|
458
|
+
\`\`\`json
|
|
459
|
+
{"go_to_url": {"url": "https://example.com/login", "description": "Navigate to login page"}}
|
|
460
|
+
\`\`\`
|
|
461
|
+
|
|
462
|
+
### Scroll down
|
|
463
|
+
\`\`\`json
|
|
464
|
+
{"scroll": {"down": true, "num_pages": 1, "description": "Scroll down one page"}}
|
|
465
|
+
\`\`\`
|
|
466
|
+
|
|
467
|
+
### Verify something
|
|
468
|
+
\`\`\`json
|
|
469
|
+
{"verify": {"statement": "The success message is displayed", "description": "Verify submission succeeded"}}
|
|
470
|
+
\`\`\`
|
|
471
|
+
`}function Me(){return Ye}async function Pe(t){switch(t){case"shiplight://schemas/testflow-v1.2.0":return Ze();case"shiplight://schemas/action-entity":return await Qe();default:return}}import{z as h}from"zod";import{zodToJsonSchema as q}from"zod-to-json-schema";import{z as S}from"zod";import{z as se}from"zod";import{zodToJsonSchema as F}from"zod-to-json-schema";import{toolRegistry as Yt}from"sdk-internal";import{z as w}from"zod";import{zodToJsonSchema as re}from"zod-to-json-schema";import{z as g}from"zod";import{zodToJsonSchema as D}from"zod-to-json-schema";import{z as m}from"zod";import{zodToJsonSchema as ne}from"zod-to-json-schema";import{v4 as Zt}from"uuid";import{z as _}from"zod";import{zodToJsonSchema as Y}from"zod-to-json-schema";import R from"fs";import ce from"path";import Qt from"os";import es from"axios";var le=class ${constructor(e,s,i){this.backend=e,this.apiClient=s,this.webAgentModel=i}static newSessionTool={name:"new_session",description:"Create a new browser session with optional device emulation and auto-login. Returns a session_id for subsequent operations. Use storage_state_path to restore a previously saved session (cookies, localStorage, IndexedDB). After creating a session, IMMEDIATELY read resource 'shiplight://schemas/action-entity' for action parameter formats before calling any other tools.",inputSchema:q(h.object({starting_url:h.string().optional().describe("Starting URL (defaults to about:blank)"),test_account_id:h.number().optional().describe("Test account ID for auto-login. If provided, will navigate to the environment URL and login."),storage_state_path:h.string().optional().describe("Path to a storage state file to restore cookies, localStorage, and IndexedDB into the session."),browser_options:h.object({device_type:h.enum(["browser","android"]).optional().describe("Device type. Default: browser"),device_name:h.string().optional().describe("Device name for emulation (e.g., 'iPhone 14 Pro')"),disable_security:h.boolean().optional().describe("Disable web security (CORS, CSP). Default: true"),record_video:h.boolean().optional().describe("Enable video recording"),enable_camera:h.boolean().optional().describe("Enable camera permission (Chromium-only)"),enable_microphone:h.boolean().optional().describe("Enable microphone permission (Chromium-only)"),headless:h.boolean().optional().describe("Run browser in headless mode"),locale:h.string().optional().describe("Browser locale (e.g., 'en-US')"),proxy:h.object({server:h.string(),username:h.string().optional(),password:h.string().optional()}).optional().describe("Proxy configuration")}).optional().describe("Browser launch options")}),{$refStrategy:"none"})};async newSession(e){console.log("[SessionTools.newSession] Starting with args:",JSON.stringify(e));let{starting_url:s,test_account_id:i,storage_state_path:o,browser_options:r}=h.object({starting_url:h.string().optional(),test_account_id:h.number().optional(),storage_state_path:h.string().optional(),browser_options:h.object({device_type:h.enum(["browser","android"]).optional(),device_name:h.string().optional(),disable_security:h.boolean().optional(),record_video:h.boolean().optional(),enable_camera:h.boolean().optional(),enable_microphone:h.boolean().optional(),headless:h.boolean().optional(),locale:h.string().optional(),proxy:h.object({server:h.string(),username:h.string().optional(),password:h.string().optional()}).optional()}).optional()}).parse(e);if(i&&o)throw new Error("test_account_id and storage_state_path are mutually exclusive. Use one or the other.");let n=null,a=s||"about:blank";if(i){if(!this.apiClient)throw new Error("test_account_id requires a Shiplight account. Use storage_state_path for login without an account.");console.log("[SessionTools.newSession] Fetching test account:",i);try{let c=await this.apiClient.getTestAccount(i);console.log("[SessionTools.newSession] Got test account:",c.id,c.username);let f=c.environmentId||c.environment_id,y=s;if(f){console.log("[SessionTools.newSession] Fetching environment:",f);let A=await this.apiClient.getEnvironment(f);console.log("[SessionTools.newSession] Got environment URL:",A.url),y=A.url,s||(a=A.url)}let T=c.loginConfig||c.login_config||{},b={...T,site_url:y||a,account:{...T.account,username:c.username,password:c.password}};n={id:c.id,username:c.username,password:c.password||"",environmentId:f,loginConfig:b},console.log("[SessionTools.newSession] Built testAccount with loginConfig.site_url:",b.site_url)}catch(c){throw console.error("[SessionTools.newSession] Error fetching test account or environment:",c),c}}console.log("[SessionTools.newSession] Creating session with URL:",a);let p;try{p=(await this.backend.createSession({startingUrl:a,testAccount:n,browserOptions:{deviceType:r?.device_type||"browser",deviceName:r?.device_name,disableSecurity:r?.disable_security,recordVideo:r?.record_video,enableCamera:r?.enable_camera,enableMicrophone:r?.enable_microphone,headless:r?.headless??!1,locale:r?.locale,proxy:r?.proxy,storageStatePath:o},webAgentConfig:{model:this.webAgentModel}})).sessionId,console.log("[SessionTools.newSession] Session created:",p)}catch(c){throw console.error("[SessionTools.newSession] Error creating session:",c),c}let u=!1;if(n&&n.username&&n.password){console.log("[SessionTools.newSession] Performing login for user:",n.username);try{u=await this.backend.loginWithTestAccount(p,n),console.log("[SessionTools.newSession] Login result:",u)}catch(c){console.error("[SessionTools.newSession] Auto-login failed:",c)}}console.log("[SessionTools.newSession] Getting current URL");let d=await this.backend.getCurrentUrl(p);return console.log("[SessionTools.newSession] Current URL:",d),JSON.stringify({session_id:p,current_url:d,login_performed:u,storage_state_loaded:!!o,message:u?`Session created and logged in at ${d}`:o?`Session created with restored storage state at ${d}`:`Session created at ${d}`,do_this_now:"You MUST read resource 'shiplight://schemas/action-entity' before calling any other shiplight tools."})}static saveStorageStateTool={name:"save_storage_state",description:"Save the browser session's storage state (cookies, localStorage, IndexedDB) to a file. Use this after logging in to cache the session for fast restores via new_session's storage_state_path.",inputSchema:q(h.object({session_id:h.string().describe("Session ID"),path:h.string().describe("File path to save the storage state to (e.g., ~/.shiplight/storage-states/my-app.json)")}),{$refStrategy:"none"})};async saveStorageState(e){let{session_id:s,path:i}=h.object({session_id:h.string(),path:h.string()}).parse(e);return await this.backend.saveStorageState(s,i),JSON.stringify({path:i,message:`Storage state saved to ${i}`})}static closeSessionTool={name:"close_session",description:"Close a browser session",inputSchema:q(h.object({session_id:h.string().describe("Session ID to close")}),{$refStrategy:"none"})};async closeSession(e){let{session_id:s}=h.object({session_id:h.string()}).parse(e);return await this.backend.closeSession(s),JSON.stringify({message:`Session ${s} closed successfully`})}static closeAllSessionsTool={name:"close_all",description:"Close all browser sessions",inputSchema:q(h.object({}),{$refStrategy:"none"})};async closeAllSessions(){let e=this.backend.getSessionCount();return await this.backend.closeAllSessions(),JSON.stringify({message:`Closed ${e} session(s)`})}static getCurrentStateTool={name:"get_session_state",description:"Get current state of a session (URL, session type)",inputSchema:q(h.object({session_id:h.string().describe("Session ID")}),{$refStrategy:"none"})};async getCurrentState(e){let{session_id:s}=h.object({session_id:h.string()}).parse(e),i=this.backend.getSession(s);if(!i)throw new Error(`Session ${s} not found`);let o=await this.backend.getCurrentUrl(s);return JSON.stringify({session_id:s,current_url:o,session_type:i.sessionType})}static toolDefinitions=[$.newSessionTool,$.saveStorageStateTool,$.closeSessionTool,$.closeAllSessionsTool,$.getCurrentStateTool]},ke=S.object({session_id:S.string().describe("Session ID from new_session"),url:S.string().optional().describe("URL to navigate to (optional, stays at current URL if not provided)")}),et={name:"navigate",description:`Navigate to a URL in an existing browser session.
|
|
472
|
+
|
|
473
|
+
Examples:
|
|
474
|
+
- Navigate to a specific page: url="https://example.com/login"
|
|
475
|
+
- Stay at current page and get URL: (no url parameter)
|
|
476
|
+
|
|
477
|
+
The tool returns the current URL after navigation.`};async function tt(t,e){let{session_id:s,url:i}=e;if(!t.getSession(s))throw new Error(`Session ${s} not found`);i&&await t.navigate(s,i);let r=await t.getCurrentUrl(s);return{session_id:s,current_url:r,message:i?`Navigated to ${r}`:`Current URL: ${r}`}}var st=S.object({session_id:S.string().describe("Session ID"),actions:S.array(S.record(S.any())).describe("List of actions to execute"),stop_on_error:S.boolean().optional().describe("Stop execution on first error (default: true)")});function it(){return{name:"act",description:`Execute browser actions using element indices from inspect_page.
|
|
478
|
+
|
|
479
|
+
CRITICAL: Before your first act call, you MUST read resource 'shiplight://schemas/action-entity' to learn action parameter formats. Each action has specific required parameters (e.g., press uses "keys" not "key").
|
|
480
|
+
|
|
481
|
+
Workflow:
|
|
482
|
+
1. inspect_page \u2192 get screenshot + DOM with element indices [0], [1], etc.
|
|
483
|
+
2. act \u2192 execute actions using element indices
|
|
484
|
+
3. Repeat until task is complete
|
|
485
|
+
|
|
486
|
+
Supported actions: ${B.join(", ")}
|
|
487
|
+
|
|
488
|
+
Each action is an object with the action name as key containing:
|
|
489
|
+
- element_index: Element index from DOM (for element-based actions)
|
|
490
|
+
- description: REQUIRED - Semantic, human-readable description of the action
|
|
491
|
+
- Action-specific params: MUST read 'shiplight://schemas/action-entity' resource
|
|
492
|
+
|
|
493
|
+
CRITICAL: The 'description' field must be semantic and grounded - DO NOT reference element indices.
|
|
494
|
+
- BAD: "Click element 5", "Input text to element 3"
|
|
495
|
+
- GOOD: "Click the Submit button", "Enter 'john@example.com' in email field"
|
|
496
|
+
|
|
497
|
+
Response fields:
|
|
498
|
+
- success: Whether all actions completed successfully
|
|
499
|
+
- actions: Array of { action_entity, success, error? } for each executed action`}}async function ot(t,e){let{session_id:s,actions:i,stop_on_error:o=!0}=e;if(!t.getSession(s))throw new Error(`Session ${s} not found`);try{let n=await t.act(s,i,{stopOnError:o});return{success:n.success,actions:n.results.map(a=>({action_entity:a.action_entity,success:a.success,error:a.error}))}}catch(n){let a=n instanceof Error?n.message:String(n);return{success:!1,actions:[],error:a}}}var xe=S.object({session_id:S.string().describe("Session ID"),variables:S.record(S.any()).describe('Variables as key-value pairs (e.g., {username: "user", password: "secret"})'),sensitive_keys:S.array(S.string()).optional().describe('Keys to treat as sensitive - values will be masked in logs (e.g., ["password", "token"])')}),rt={name:"update_variables",description:`Update session variables for use in subsequent browser operations.
|
|
500
|
+
|
|
501
|
+
Variables are key-value pairs that can be referenced in browser tasks.
|
|
502
|
+
Mark sensitive values (passwords, tokens) with sensitive_keys to prevent logging.
|
|
503
|
+
|
|
504
|
+
Example:
|
|
505
|
+
variables: { username: "john", password: "secret123" }
|
|
506
|
+
sensitive_keys: ["password"]`};function nt(t,e){let{session_id:s,variables:i,sensitive_keys:o}=e;return t.updateVariables(s,i,o),{session_id:s,message:"Variables updated successfully",variable_count:Object.keys(i).length,sensitive_keys_count:o?.length||0}}var Ce=S.object({session_id:S.string().describe("Session ID")}),at={name:"clear_execution_history",description:`Clear the execution history for a session.
|
|
507
|
+
|
|
508
|
+
The execution history tracks past browser operations within a session.
|
|
509
|
+
Clear it to start fresh or reduce memory usage.`};function ct(t,e){let{session_id:s}=e;return t.clearExecutionHistory(s),{session_id:s,message:"Execution history cleared successfully"}}var Oe=S.object({session_id:S.string().describe("Session ID")}),lt={name:"get_page_info",description:`Get basic page information (URL and title) without taking a screenshot.
|
|
510
|
+
|
|
511
|
+
Use this as a lightweight way to check what page you're on.
|
|
512
|
+
For visual inspection, use inspect_page.`};async function ut(t,e){let{session_id:s}=e;if(!t.getSession(s))throw new Error(`Session ${s} not found`);let o=await t.getPageInfo(s);return{session_id:s,url:o.url,title:o.title}}var Le=S.object({session_id:S.string().describe("Session ID")}),dt={name:"inspect_page",description:`Get a comprehensive view of the current page state.
|
|
513
|
+
|
|
514
|
+
Combines screenshot and DOM inspection in a single call.
|
|
515
|
+
Use this before calling act to get element indices.
|
|
516
|
+
|
|
517
|
+
Returns:
|
|
518
|
+
- screenshot_path: Path to the captured screenshot
|
|
519
|
+
- dom_file_path: Path to file containing full DOM text with element indices
|
|
520
|
+
- dom_state_id: ID of cached DOM state (for staleness detection)
|
|
521
|
+
- current_url: The current page URL
|
|
522
|
+
|
|
523
|
+
The DOM text shows interactive elements with indices like [0], [1], etc.
|
|
524
|
+
Use these indices with act to perform actions.`};async function ht(t,e){let{session_id:s}=e;if(!t.getSession(s))throw new Error(`Session ${s} not found`);let o=await t.inspectPage(s);return{session_id:s,screenshot_path:o.screenshotPath,dom_file_path:o.domFilePath,dom_state_id:o.domStateId,current_url:o.currentUrl}}var Ie=S.object({session_id:S.string().describe("Session ID"),element_index:S.number().describe("Element index from inspect_page DOM output")}),pt={name:"get_locator",description:`Extract locator info for a DOM element without performing any action.
|
|
525
|
+
|
|
526
|
+
Requires inspect_page to be called first to populate the DOM state.
|
|
527
|
+
Returns the Playwright locator, xpath, frame path, tag name, and text content
|
|
528
|
+
for the element at the given index.
|
|
529
|
+
|
|
530
|
+
Use this to collect locator information for building test flows without
|
|
531
|
+
actually interacting with the element.`};async function gt(t,e){let{session_id:s,element_index:i}=e;if(!t.getSession(s))throw new Error(`Session ${s} not found`);return t.getLocator(s,i)}var ie=null,oe=null;function mt(){return ie||(ie=Yt.buildActionUnionSchemaForTools([...B],!0),ie)}function ft(){if(oe)return oe;let t=mt(),e=se.array(t),s=it();return oe={name:s.name,description:s.description,inputSchema:F(se.object({session_id:se.string().describe("Session ID"),actions:e.describe("List of actions to execute"),stop_on_error:se.boolean().optional().describe("Stop execution on first error (default: true)")}),{$refStrategy:"none"})},oe}var ue=class O{constructor(e){this.backend=e}static navigateTool={...et,inputSchema:F(ke,{$refStrategy:"none"})};async navigate(e){let s=ke.parse(e),i=await tt(this.backend,s);return JSON.stringify(i)}static get actTool(){return ft()}async act(e){let s=st.parse(e),i=await ot(this.backend,s);return JSON.stringify(i)}static updateVariablesTool={...rt,inputSchema:F(xe,{$refStrategy:"none"})};async updateVariables(e){let s=xe.parse(e),i=nt(this.backend,s);return JSON.stringify(i)}static clearExecutionHistoryTool={...at,inputSchema:F(Ce,{$refStrategy:"none"})};async clearExecutionHistory(e){let s=Ce.parse(e),i=ct(this.backend,s);return JSON.stringify(i)}static getPageInfoTool={...lt,inputSchema:F(Oe,{$refStrategy:"none"})};async getPageInfo(e){let s=Oe.parse(e),i=await ut(this.backend,s);return JSON.stringify(i)}static inspectPageTool={...dt,inputSchema:F(Le,{$refStrategy:"none"})};async inspectPage(e){let s=Le.parse(e),i=await ht(this.backend,s);return JSON.stringify(i)}static getLocatorTool={...pt,inputSchema:F(Ie,{$refStrategy:"none"})};async getLocator(e){let s=Ie.parse(e),i=await gt(this.backend,s);return JSON.stringify(i)}static get toolDefinitions(){return[O.navigateTool,O.getPageInfoTool,O.inspectPageTool,O.actTool,O.getLocatorTool,O.updateVariablesTool,O.clearExecutionHistoryTool]}},de=class Z{constructor(e){this.backend=e}static getConsoleLogsTool={name:"get_browser_console_logs",description:"Get console logs from the browser (errors, warnings, etc.)",inputSchema:re(w.object({session_id:w.string().describe("Browser session ID"),since_timestamp:w.number().optional().describe("Only return logs after this timestamp"),log_types:w.array(w.string()).optional().describe("Filter by log types (e.g., ['error', 'warning'])")}),{$refStrategy:"none"})};async getConsoleLogs(e){let{session_id:s,since_timestamp:i,log_types:o}=w.object({session_id:w.string(),since_timestamp:w.number().optional(),log_types:w.array(w.string()).optional()}).parse(e);if(!this.backend.getSession(s))throw new Error(`Session ${s} not found`);let n=this.backend.getConsoleLogs(s,{sinceTimestamp:i,logTypes:o});return JSON.stringify({session_id:s,log_count:n.length,logs:n.slice(-100)})}static getNetworkLogsTool={name:"get_browser_network_logs",description:"Get network request logs from the browser",inputSchema:re(w.object({session_id:w.string().describe("Browser session ID"),since_timestamp:w.number().optional().describe("Only return logs after this timestamp"),status_filter:w.enum(["errors","success"]).optional().describe("Filter by HTTP status (errors: 4xx/5xx, success: 2xx)")}),{$refStrategy:"none"})};async getNetworkLogs(e){let{session_id:s,since_timestamp:i,status_filter:o}=w.object({session_id:w.string(),since_timestamp:w.number().optional(),status_filter:w.enum(["errors","success"]).optional()}).parse(e);if(!this.backend.getSession(s))throw new Error(`Session ${s} not found`);let n=this.backend.getNetworkLogs(s,{sinceTimestamp:i,statusFilter:o});return JSON.stringify({session_id:s,log_count:n.length,logs:n.slice(-100)})}static clearLogsTool={name:"clear_logs",description:"Clear console and network logs for a session",inputSchema:re(w.object({session_id:w.string().describe("Browser session ID")}),{$refStrategy:"none"})};async clearLogs(e){let{session_id:s}=w.object({session_id:w.string()}).parse(e);if(!this.backend.getSession(s))throw new Error(`Session ${s} not found`);return this.backend.clearLogs(s),JSON.stringify({session_id:s,message:"Logs cleared successfully"})}static getArtifactTool={name:"get_artifact",description:"Get artifact file (screenshot, DOM snapshot, etc.) from a previous run",inputSchema:re(w.object({path:w.string().describe("Artifact file path from previous tool output"),return_format:w.enum(["base64","text"]).optional().describe("Return format (auto-detected if not specified)")}),{$refStrategy:"none"})};async getArtifact(e){let{path:s}=w.object({path:w.string(),return_format:w.enum(["base64","text"]).optional()}).parse(e),i=await this.backend.getArtifact(s);return JSON.stringify({path:s,format:i.format,size:i.size,data:i.data})}static toolDefinitions=[Z.getConsoleLogsTool,Z.getNetworkLogsTool,Z.clearLogsTool,Z.getArtifactTool]},he=class L{constructor(e){this.apiClient=e}static listEnvironmentsTool={name:"list_environments",description:"List all testing environments (staging, production, etc.)",inputSchema:D(g.object({}),{$refStrategy:"none"})};async listEnvironments(e){let s=await this.apiClient.listEnvironments();return JSON.stringify({environments:s.map(i=>({id:i.id,name:i.name,url:i.url}))})}static listTestAccountsTool={name:"list_test_accounts",description:"List test accounts for a specific environment. Requires either environment_id or environment_url.",inputSchema:D(g.object({environment_id:g.number().optional().describe("Environment ID to filter by"),environment_url:g.string().optional().describe("Environment URL to filter by (alternative to environment_id)")}),{$refStrategy:"none"})};async listTestAccounts(e){let{environment_id:s,environment_url:i}=g.object({environment_id:g.number().optional(),environment_url:g.string().optional()}).parse(e),o=await this.apiClient.listTestAccounts(s,i);return JSON.stringify({test_accounts:o.map(r=>({id:r.id,name:r.name,username:r.username,environment_id:r.environmentId||r.environment_id}))})}static getTestAccountTool={name:"get_test_account",description:"Get a specific test account by ID",inputSchema:D(g.object({test_account_id:g.number().describe("Test account ID")}),{$refStrategy:"none"})};async getTestAccount(e){let{test_account_id:s}=g.object({test_account_id:g.number()}).parse(e),i=await this.apiClient.getTestAccount(s);return JSON.stringify({test_account:i})}static createTestAccountTool={name:"create_test_account",description:"Create a new test account",inputSchema:D(g.object({environment_id:g.number().describe("Environment ID"),username:g.string().describe("Username"),password:g.string().describe("Password"),login_config:g.any().optional().describe("Login configuration")}),{$refStrategy:"none"})};async createTestAccount(e){let{environment_id:s,username:i,password:o,login_config:r}=g.object({environment_id:g.number(),username:g.string(),password:g.string(),login_config:g.any().optional()}).parse(e),n=await this.apiClient.createTestAccount({environmentId:s,username:i,password:o,loginConfig:r});return JSON.stringify({test_account_id:n.id,message:"Test account created successfully"})}static listFoldersTool={name:"list_folders",description:"List test case folders",inputSchema:D(g.object({parent_id:g.number().nullable().optional().describe("Parent folder ID, null for root"),search:g.string().optional().describe("Search query")}),{$refStrategy:"none"})};async listFolders(e){let{parent_id:s,search:i}=g.object({parent_id:g.number().nullable().optional(),search:g.string().optional()}).parse(e),o=await this.apiClient.listFolders(s,i);return JSON.stringify({folders:o.map(r=>({id:r.id,name:r.name,parent_id:r.parentId||r.parent_id,path:r.pathIds}))})}static createFolderTool={name:"create_folder",description:"Create a new test case folder",inputSchema:D(g.object({name:g.string().describe("Folder name"),description:g.string().optional().describe("Folder description"),parent_id:g.number().nullable().optional().describe("Parent folder ID")}),{$refStrategy:"none"})};async createFolder(e){let{name:s,description:i,parent_id:o}=g.object({name:g.string(),description:g.string().optional(),parent_id:g.number().nullable().optional()}).parse(e),r=await this.apiClient.createFolder({name:s,description:i,parentId:o});return JSON.stringify({folder_id:r.id,message:"Folder created successfully"})}static getFolderTool={name:"get_folder",description:"Get folder details with full hierarchical path",inputSchema:D(g.object({folder_id:g.number().describe("Folder ID")}),{$refStrategy:"none"})};async getFolder(e){let{folder_id:s}=g.object({folder_id:g.number()}).parse(e),i=await this.apiClient.getFolder(s);return JSON.stringify({folder:i})}static toolDefinitions=[L.listEnvironmentsTool,L.listTestAccountsTool,L.getTestAccountTool,L.createTestAccountTool,L.listFoldersTool,L.createFolderTool,L.getFolderTool]},pe=class Q{constructor(e){this.apiClient=e}parseFlowInput(e){return typeof e=="string"?M.parse(_e(e)):M.parse(e)}static createTestCaseTool={name:"create_test_case",description:"Create a test case from a flow (test flow JSON object or YAML string). IMPORTANT: Read 'shiplight://schemas/testflow-v1.2.0' for the required flow format. Accepts a test flow as JSON object or YAML string. Include action_entity with locator/xpath on ACTION statements when available - this speeds up test execution.",inputSchema:ne(m.object({flow:m.union([m.string(),m.any()]).describe("Test flow as JSON object or YAML string"),folder_id:m.number().optional().describe("Folder ID"),name:m.string().optional().describe("Test case name"),environment_id:m.number().optional().describe("Environment ID for test execution"),test_account_id:m.number().optional().describe("Optional test account ID for login")}),{$refStrategy:"none"})};async createTestCase(e){let{flow:s,folder_id:i,name:o,environment_id:r,test_account_id:n}=m.object({flow:m.any(),folder_id:m.number().optional(),name:m.string().optional(),environment_id:m.number().optional(),test_account_id:m.number().optional()}).parse(e),a;try{a=this.parseFlowInput(s)}catch(f){throw f instanceof m.ZodError?new Error(`Invalid TestFlow schema: ${JSON.stringify(f.errors)}`):f}let p={...a,version:"1.2.0",statements:this.regenerateUids(a.statements),teardown:a.teardown?this.regenerateUids(a.teardown):void 0},u;r!==void 0&&(u=[{environment_id:r,test_account_group:n?{type:"Specific",account_ids:[n]}:{type:"None",account_ids:[]},path:""}]);let d=await this.apiClient.createTestCase({title:o||a.goal,testFlow:p,folderId:i,environmentConfigs:u}),c=this.computeFlowStats(a.statements,a.teardown);return JSON.stringify({...d,flow_stats:c})}static updateTestCaseTool={name:"update_test_case",description:"Update a test case from a flow (test flow JSON object or YAML string). IMPORTANT: Read 'shiplight://schemas/testflow-v1.2.0' for the required flow format. Accepts a test flow as JSON object or YAML string. Include action_entity with locator/xpath on ACTION statements when available - this speeds up test execution.",inputSchema:ne(m.object({test_case_id:m.number().describe("Test case ID"),flow:m.union([m.string(),m.any()]).describe("Test flow as JSON object or YAML string"),title:m.string().optional().describe("New title")}),{$refStrategy:"none"})};async updateTestCase(e){let{test_case_id:s,flow:i,title:o}=m.object({test_case_id:m.number(),flow:m.any(),title:m.string().optional()}).parse(e),r;try{r=this.parseFlowInput(i)}catch(u){throw u instanceof m.ZodError?new Error(`Invalid TestFlow schema: ${JSON.stringify(u.errors)}`):u}let n={...r,version:"1.2.0",statements:this.regenerateUids(r.statements),teardown:r.teardown?this.regenerateUids(r.teardown):void 0},a=await this.apiClient.updateTestCase(s,{title:o||r.goal,testFlow:n}),p=this.computeFlowStats(r.statements,r.teardown);return JSON.stringify({...a,flow_stats:p})}static getTestCaseTool={name:"get_test_case",description:"Get a test case by ID. Use output_format 'yaml' to get the flow as a YAML string.",inputSchema:ne(m.object({test_case_id:m.number().describe("Test case ID"),output_format:m.enum(["json","yaml"]).optional().describe("Output format for the test flow (default: json)")}),{$refStrategy:"none"})};async getTestCase(e){let{test_case_id:s,output_format:i}=m.object({test_case_id:m.number(),output_format:m.enum(["json","yaml"]).optional()}).parse(e),o=await this.apiClient.getTestCase(s);if(i==="yaml"&&o.test_flow){let r=fe(o.test_flow);return JSON.stringify({test_case:{id:o.id,title:o.title,folder_id:o.folder_id},yaml:r})}return JSON.stringify({test_case:o})}static runTestCaseTool={name:"run_test_case",description:"Trigger cloud execution of a test case. Returns a test_run_id that can be used with get_test_run_details to poll for completion and results.",inputSchema:ne(m.object({test_case_id:m.number().describe("Test case ID to run"),environment_id:m.number().optional().describe("Environment ID to run against (uses test case default if not specified)")}),{$refStrategy:"none"})};async runTestCase(e){let{test_case_id:s,environment_id:i}=m.object({test_case_id:m.number(),environment_id:m.number().optional()}).parse(e),o=await this.apiClient.runTestCase(s,i);return JSON.stringify({test_run_id:o.id,status:o.status,result:o.result,test_case_result_ids:o.test_case_result_ids,message:`Test run ${o.id} triggered. Use get_test_run_details(${o.id}) to check status and results.`})}computeFlowStats(e,s){let i={total:0,drafts:0,actions_with_locator:0,actions_without_locator:0,verifies:0,steps:0},o=n=>{for(let a of n)if(i.total++,a.type==="DRAFT")i.drafts++;else if(a.type==="ACTION"){let p=a.action_entity;p?.action_data?.action_name==="verify"?i.verifies++:p?.locator||p?.xpath?i.actions_with_locator++:i.actions_without_locator++}else a.type==="STEP"?(i.steps++,a.statements&&o(a.statements)):a.type==="IF_ELSE"?(a.then&&o(a.then),a.else&&o(a.else)):a.type==="WHILE_LOOP"&&a.body&&o(a.body)};o(e),s&&o(s);let r={...i};return i.drafts>0&&i.actions_with_locator===0?r.hint=`${i.drafts} DRAFT statements with no action_entities (~10-15s each at runtime). Include action_entities with locator/xpath for ~1s deterministic replay.`:i.drafts>0&&(r.hint=`${i.drafts} DRAFT statements remaining (~10-15s each at runtime vs ~1s with action_entities). Consider converting DRAFTs that don't depend on runtime state to ACTIONs.`),r}regenerateUids(e){return e.map(s=>{let i={...s,uid:Zt()};return s.type==="STEP"&&s.statements?i.statements=this.regenerateUids(s.statements):s.type==="IF_ELSE"?(s.then&&(i.then=this.regenerateUids(s.then)),s.else&&(i.else=this.regenerateUids(s.else))):s.type==="WHILE_LOOP"&&s.body&&(i.body=this.regenerateUids(s.body)),i})}static toolDefinitions=[Q.createTestCaseTool,Q.updateTestCaseTool,Q.getTestCaseTool,Q.runTestCaseTool]},ts=3600*1e3,ae=ce.join(Qt.tmpdir(),"shiplight-artifacts"),ge=class K{constructor(e,s){this.apiClient=e,this.options=s||{}}options;getProxyUrl(e){if(!this.options.artifactProxyBaseUrl)return null;let s=e.match(/^s3:\/\/[^/]+\/(.+)$/);if(!s)return null;let i=s[1];return`${this.options.artifactProxyBaseUrl}/api/artifacts/${i}`}cleanupOldArtifacts(){try{if(!R.existsSync(ae))return;let e=Date.now(),s=R.readdirSync(ae,{withFileTypes:!0});for(let i of s){if(!i.isDirectory())continue;let o=ce.join(ae,i.name);try{let r=R.statSync(o);e-r.mtimeMs>ts&&R.rmSync(o,{recursive:!0,force:!0})}catch{}}}catch{}}static listTestRunsTool={name:"list_test_runs",description:"List test runs with optional filtering by test plan, trigger type, or result status",inputSchema:Y(_.object({test_plan_id:_.number().optional().describe("Filter by test plan ID"),trigger:_.string().optional().describe("Filter by trigger type (Manual, Scheduled, API, GITHUB_ACTION, Webhook, Automation)"),result:_.string().optional().describe("Filter by result (Passed, Failed, Pending, Skipped, Queued)"),limit:_.number().optional().describe("Maximum number of results (default 20)")}),{$refStrategy:"none"})};async listTestRuns(e){let{test_plan_id:s,trigger:i,result:o,limit:r}=_.object({test_plan_id:_.number().optional(),trigger:_.string().optional(),result:_.string().optional(),limit:_.number().optional()}).parse(e),n=await this.apiClient.listTestRuns({testPlanId:s,trigger:i,result:o,limit:r??20});return JSON.stringify({test_runs:n.map(a=>({id:a.id,status:a.status,result:a.result,trigger:a.trigger,start_time:a.startTime||a.start_time,end_time:a.endTime||a.end_time,duration_ms:a.duration,total_test_case_count:a.totalTestCaseCount||a.total_test_case_count,passed_test_case_count:a.passedTestCaseCount||a.passed_test_case_count,failed_test_case_count:a.failedTestCaseCount||a.failed_test_case_count,skipped_test_case_count:a.skippedTestCaseCount||a.skipped_test_case_count,test_plan_id:a.testPlanId||a.test_plan_id})),count:n.length})}static getTestRunDetailsTool={name:"get_test_run_details",description:"Get detailed information about a test run including all test case results",inputSchema:Y(_.object({test_run_id:_.number().describe("Test run ID")}),{$refStrategy:"none"})};async getTestRunDetails(e){let{test_run_id:s}=_.object({test_run_id:_.number()}).parse(e),i=await this.apiClient.getTestRunDetails(s),o=i.testRun||i,r=i.testCaseResults||[];return JSON.stringify({test_run:{id:o.id,status:o.status,result:o.result,trigger:o.trigger,start_time:o.startTime||o.start_time,end_time:o.endTime||o.end_time,duration_ms:o.duration,total_test_case_count:o.totalTestCaseCount||o.total_test_case_count,passed_test_case_count:o.passedTestCaseCount||o.passed_test_case_count,failed_test_case_count:o.failedTestCaseCount||o.failed_test_case_count,skipped_test_case_count:o.skippedTestCaseCount||o.skipped_test_case_count,test_plan_id:o.testPlanId||o.test_plan_id},test_case_results:r.map(n=>({id:n.id,test_case_id:n.testCaseId||n.test_case_id,result:n.result,status:n.status,duration_ms:n.duration,environment_name:n.environmentName||n.environment_name,environment_url:n.environmentUrl||n.environment_url,error:n.error}))})}static getTestCaseResultTool={name:"get_test_case_result",description:"Get detailed test case result including status, duration, and error information",inputSchema:Y(_.object({test_case_result_id:_.number().describe("Test case result ID"),include_report:_.boolean().optional().describe("Include full report with step details (default false)")}),{$refStrategy:"none"})};async getTestCaseResult(e){let{test_case_result_id:s,include_report:i}=_.object({test_case_result_id:_.number(),include_report:_.boolean().optional()}).parse(e),o=await this.apiClient.getTestCaseResult(s),r=Array.isArray(o.report)?o.report[0]:o.report,n=r?.resultJson||{},a=Object.entries(n),p=a.length,u=null;for(let c=0;c<a.length;c++){let[,f]=a[c];if(f.status==="failed"||f.status==="failure"){u=c;break}}let d={id:o.id,test_case_id:o.testCaseId||o.test_case_id,test_run_id:o.testRunId||o.test_run_id,result:o.result,status:o.status,start_time:o.startTime||o.start_time,end_time:o.endTime||o.end_time,duration_ms:o.duration,environment_name:o.environmentName||o.environment_name,environment_id:o.environmentId||o.environment_id,environment_url:o.environmentUrl||o.environment_url,device:o.device,total_steps:p,failed_step_index:u,video_s3_uri:o.video,trace_s3_uri:o.trace,report_s3_uri:o.reportS3Uri||o.report_s3_uri};if(i){let c=o.reportS3Uri||o.report_s3_uri,f=null;if(c&&this.apiClient.getS3FileContents)try{let T=await this.apiClient.getS3FileContents(c);if(T){let b=JSON.parse(T);f=Array.isArray(b)?b[0]:b}}catch(T){console.error("Failed to fetch S3 report, falling back to DB report:",T)}let y=f||r;d.report={stdout:y?.stdout,stderr:y?.stderr,consoleLogs:y?.consoleLogs,flaky:y?.flaky,error:y?.error,step_results:y?.resultJson,video_s3_uri:y?.videoS3Uri,trace_s3_uri:y?.traceS3Uri,source_s3_uri:y?.sourceS3Uri}}return JSON.stringify(d)}static getTestCaseResultStepsTool={name:"get_test_case_result_steps",description:"Get step-by-step execution details for a range of steps. Use get_test_case_result first to find the failed_step_index, then request the range you need.",inputSchema:Y(_.object({test_case_result_id:_.number().describe("Test case result ID"),from_step:_.number().describe("Start step index (0-based, inclusive)"),to_step:_.number().describe("End step index (0-based, inclusive)")}),{$refStrategy:"none"})};async getTestCaseResultSteps(e){let{test_case_result_id:s,from_step:i,to_step:o}=_.object({test_case_result_id:_.number(),from_step:_.number(),to_step:_.number()}).parse(e),r=await this.apiClient.getTestCaseResult(s),a=(Array.isArray(r.report)?r.report[0]:r.report)?.resultJson||{},p=Object.entries(a).map(([d,c])=>{let f=c.artifacts?.[0];return{step_id:d,description:c.description,status:c.status,message:c.message,start_time:c.startTime,duration_ms:c.duration,has_screenshot:!!(f?.screenshot_s3_path||c.screenshotS3Uri),has_messages:!!f?.messages_s3_path,has_response:!!f?.response_s3_path,has_system_prompt:!!f?.system_prompt_s3_path}}),u=p.slice(i,o+1);return JSON.stringify({test_case_result_id:s,total_steps:p.length,from_step:i,to_step:o,steps:u})}static getStepArtifactsTool={name:"get_step_artifacts",description:"Download all artifacts for a step to local files. Returns local file paths - client decides which to read.",inputSchema:Y(_.object({test_case_result_id:_.number().describe("Test case result ID"),step_index:_.number().describe("Step index (0-based)")}),{$refStrategy:"none"})};async getStepArtifacts(e){let{test_case_result_id:s,step_index:i}=_.object({test_case_result_id:_.number(),step_index:_.number()}).parse(e),o=await this.apiClient.getTestCaseResult(s),n=(Array.isArray(o.report)?o.report[0]:o.report)?.resultJson||{},a=Object.values(n);if(i<0||i>=a.length)throw new Error(`Step index ${i} out of range (0-${a.length-1})`);let p=a[i],u=p.artifacts?.[0],d={},c=u?.screenshot_s3_path||p.screenshotS3Uri;c&&(d.screenshot={s3Uri:c,filename:`step_${i}_screenshot.png`}),u?.messages_s3_path&&(d.messages={s3Uri:u.messages_s3_path,filename:`step_${i}_messages.json`}),u?.response_s3_path&&(d.response={s3Uri:u.response_s3_path,filename:`step_${i}_response.txt`}),u?.system_prompt_s3_path&&(d.system_prompt={s3Uri:u.system_prompt_s3_path,filename:`step_${i}_system_prompt.txt`});let f=!!this.apiClient.getS3FileContents,y=!!this.options.artifactProxyBaseUrl,T=!!this.apiClient.getArtifactPresignedUrl;if(!f&&!y&&!T)return JSON.stringify({test_case_result_id:s,step_index:i,error:"Artifact download not supported by this API client",s3_uris:Object.fromEntries(Object.entries(d).map(([W,{s3Uri:J}])=>[W,J]))});this.cleanupOldArtifacts();let b=ce.join(ae,`tcr-${s}`);R.existsSync(b)||R.mkdirSync(b,{recursive:!0});let A={},wt=!!this.apiClient.downloadArtifact;return await Promise.all(Object.entries(d).map(async([W,{s3Uri:J,filename:St}])=>{try{let E=null;if(!E&&f){let x=await this.apiClient.getS3FileContents(J);x&&(E=x)}if(!E&&y&&wt){let x=this.getProxyUrl(J);x&&(E=await this.apiClient.downloadArtifact(x))}if(!E&&T){let x=await this.apiClient.getArtifactPresignedUrl(J);if(x){let yt=await es.get(x,{responseType:"arraybuffer"});E=Buffer.from(yt.data)}}if(!E){A[W]=null;return}let We=ce.join(b,St);R.writeFileSync(We,E),A[W]=We}catch(E){console.error(`Failed to download ${W} artifact:`,E),A[W]=null}})),JSON.stringify({test_case_result_id:s,step_index:i,screenshot_path:A.screenshot,messages_path:A.messages,response_path:A.response,system_prompt_path:A.system_prompt})}static toolDefinitions=[K.listTestRunsTool,K.getTestRunDetailsTool,K.getTestCaseResultTool,K.getTestCaseResultStepsTool,K.getStepArtifactsTool]};import{z as N}from"zod";import{zodToJsonSchema as ss}from"zod-to-json-schema";var _r=class _t{constructor(e){this.backend=e}static runTestFlowTool={name:"run_test_flow",description:`Execute a complete TestFlow in an existing browser session, returning per-statement pass/fail results. No test case is created. Use this to validate a test flow before saving it.
|
|
532
|
+
|
|
533
|
+
The flow is executed statement by statement:
|
|
534
|
+
- ACTION with action_entity: replays using cached locator (fast)
|
|
535
|
+
- ACTION without action_entity: uses AI to find and interact with elements
|
|
536
|
+
- DRAFT: uses multi-step AI execution
|
|
537
|
+
- STEP: executes child statements sequentially
|
|
538
|
+
- IF_ELSE: evaluates condition, then executes the matching branch
|
|
539
|
+
- WHILE_LOOP: loops while condition is true (respects timeout_ms)
|
|
540
|
+
|
|
541
|
+
IMPORTANT: Read 'shiplight://schemas/testflow-v1.2.0' resource for the flow format.`,inputSchema:ss(N.object({session_id:N.string().describe("Session ID from new_session"),flow:N.any().describe("Test flow JSON object (must match TestFlow schema)")}),{$refStrategy:"none"})};async runTestFlow(e){let{session_id:s,flow:i}=N.object({session_id:N.string(),flow:N.any()}).parse(e);if(!this.backend.getSession(s))throw new Error(`Session ${s} not found`);let r;try{r=M.parse(i)}catch(f){throw f instanceof N.ZodError?new Error(`Invalid TestFlow schema: ${JSON.stringify(f.errors)}`):f}let n=this.backend.getPage(s),a=this.backend.getWebAgent(s);r.url&&await n.goto(r.url,{waitUntil:"domcontentloaded"});let p=[],u=await this.executeStatements(r.statements,n,a,p),d=this.countResults(p),c={success:u,url:n.url(),results:p,statements_passed:d.passed,statements_failed:d.failed,statements_total:d.total};return JSON.stringify(c)}async executeStatements(e,s,i,o){let r=!0;for(let n of e){let a=await this.executeStatement(n,s,i);if(o.push(a),!a.success){r=!1;break}}return r}async executeStatement(e,s,i){let o=this.getStatementDescription(e),r={uid:e.uid,type:e.type,description:o,success:!1};try{switch(e.type){case v.DRAFT:return await this.executeDraft(e,s,i,r);case v.ACTION:return await this.executeAction(e,s,i,r);case v.STEP:return await this.executeStep(e,s,i,r);case v.IF_ELSE:return await this.executeIfElse(e,s,i,r);case v.WHILE_LOOP:return await this.executeWhileLoop(e,s,i,r);default:return{...r,error:`Unknown statement type: ${e.type}`}}}catch(n){return{...r,error:n instanceof Error?n.message:String(n)}}}async executeDraft(e,s,i,o){let r=await i.run(s,e.description);return{...o,success:r.success,error:r.success?void 0:r.details||"Draft execution failed"}}async executeAction(e,s,i,o){if(e.action_entity){let r=e.action_entity.action_data?.action_name;return r?(await i.execAction(r,s,e.action_entity),{...o,success:!0}):{...o,error:"action_entity missing action_data.action_name"}}else{let r=await i.execute(s,e.description);return{...o,success:r.success,error:r.success?void 0:r.details||"Action execution failed"}}}async executeStep(e,s,i,o){let r=te(e),n=[],a=!0;for(let p of r)if(!await this.executeStatements(p.statements,s,i,n)){a=!1;break}return{...o,success:a,children:n,error:a?void 0:"One or more child statements failed"}}async executeIfElse(e,s,i,o){let r=e.condition.expression,n=await i.evaluate(s,r),a=[],p=!0;return n&&e.then&&e.then.length>0?p=await this.executeStatements(e.then,s,i,a):!n&&e.else&&e.else.length>0&&(p=await this.executeStatements(e.else,s,i,a)),{...o,success:p,children:a,description:`${o.description} (condition=${n})`}}async executeWhileLoop(e,s,i,o){let n=e.condition.expression,a=e.timeout_ms??we,p=Date.now(),u=[],d=0;for(;;){if(Date.now()-p>a)return{...o,success:!1,children:u,error:`While loop timed out after ${a}ms (${d} iterations)`};if(d>=100)return{...o,success:!1,children:u,error:"While loop exceeded max iterations (100)"};if(!await i.evaluate(s,n))break;if(e.body&&e.body.length>0){let f=[],y=await this.executeStatements(e.body,s,i,f);if(u.push(...f),!y)return{...o,success:!1,children:u,error:`While loop body failed on iteration ${d+1}`}}d++}return{...o,success:!0,children:u,description:`${o.description} (${d} iterations)`}}getStatementDescription(e){switch(e.type){case v.DRAFT:case v.ACTION:case v.STEP:return e.description||e.type;case v.IF_ELSE:return`IF ${e.condition.expression}`;case v.WHILE_LOOP:return`WHILE ${e.condition.expression}`}}countResults(e){let s=0,i=0,o=0;for(let r of e)if(o++,r.success?s++:i++,r.children){let n=this.countResults(r.children);s+=n.passed,i+=n.failed,o+=n.total}return{passed:s,failed:i,total:o}}static toolDefinitions=[_t.runTestFlowTool]};var De=class t{static _instance;_logLevel;constructor(){switch(process.env.LOG_LEVEL?.toUpperCase()){case"ERROR":this._logLevel=1;break;case"WARN":this._logLevel=2;break;case"INFO":this._logLevel=3;break;case"DEBUG":this._logLevel=4;break;default:this._logLevel=3}}static getInstance(){return t._instance||(t._instance=new t),t._instance}setLevel(e){this._logLevel=e}debug(...e){this._logLevel>=4&&console.error("[DEBUG]",...e)}info(...e){this._logLevel>=3&&console.error("[INFO]",...e)}warn(...e){this._logLevel>=2&&console.error("[WARN]",...e)}error(...e){this._logLevel>=1&&console.error("[ERROR]",...e)}log(...e){this.info(...e)}},is=De.getInstance(),V=is,os={ERROR:1,WARN:2,INFO:3,DEBUG:4};import z from"axios";var rs={maxRetries:3,initialDelayMs:1e3,maxDelayMs:1e4,backoffMultiplier:2,retryableStatuses:[408,429,500,502,503,504],onRetry:()=>{}};function ns(t,e){return!!(!t.response||t.response.status&&e.includes(t.response.status))}function as(t){return new Promise(e=>setTimeout(e,t))}function cs(t,e,s,i){let o=e*Math.pow(i,t);return Math.min(o,s)}async function j(t,e={}){let{maxRetries:s,initialDelayMs:i,maxDelayMs:o,backoffMultiplier:r,retryableStatuses:n,onRetry:a}={...rs,...e},p;for(let u=0;u<=s;u++)try{return await t()}catch(d){if(!z.isAxiosError(d)||(p=d,u>=s||!ns(d,n)))throw d;let c=cs(u,i,o,r);V.debug(`Retry attempt ${u+1}/${s} after ${c}ms - ${d.code||d.message}`),a(u+1,d),await as(c)}throw p}function ls(t={}){return{get:(e,s)=>j(()=>z.get(e,s),t),post:(e,s,i)=>j(()=>z.post(e,s,i),t),put:(e,s,i)=>j(()=>z.put(e,s,i),t),delete:(e,s)=>j(()=>z.delete(e,s),t),patch:(e,s,i)=>j(()=>z.patch(e,s,i),t)}}process.env.PWDEBUG="console";Ss.config();var ys=process.env.API_BASE_URL||"https://api.shiplight.ai",bs=process.env.WEB_AGENT_MODEL||"gemini-2.5-pro",Ts=process.env.TERMINATION_TIMEOUT?parseInt(process.env.TERMINATION_TIMEOUT,10):null,Ne=class{server;registry;sessionBackend;apiClient;sessionTools;browserTools;debugTools;testCaseTools;testResultTools;dataTools;constructor(){this.server=new us({name:"shiplight-mcp",version:"1.0.0"},{capabilities:{tools:{},resources:{},prompts:{}}}),this.sessionBackend=new ye({runDir:void 0,terminationTimeout:Ts}),this.apiClient=new be({apiBaseUrl:ys,getApiToken:()=>process.env.API_TOKEN||""}),this.sessionTools=new le(this.sessionBackend,this.apiClient,bs),this.browserTools=new ue(this.sessionBackend),this.debugTools=new de(this.sessionBackend),this.testCaseTools=new pe(this.apiClient),this.testResultTools=new ge(this.apiClient),this.dataTools=new he(this.apiClient),this.registry=new Ee,this.registry.registerAll(le,this.sessionTools).registerAll(ue,this.browserTools).registerAll(de,this.debugTools).registerAll(pe,this.testCaseTools).registerAll(ge,this.testResultTools).registerAll(he,this.dataTools),this.setupHandlers()}setupHandlers(){let{tools:e,handleToolCall:s}=this.registry.build();this.server.setRequestHandler(ps,async()=>({tools:e})),this.server.setRequestHandler(hs,async i=>{try{let{name:o,arguments:r}=i.params;return{content:[{type:"text",text:await s(o,r)}]}}catch(o){let r=o instanceof Error?o.message:String(o);throw ws.isAxiosError(o)&&o.response?.status===401&&(r="Authentication required. Contact info@shiplight.ai to get access to cloud services and advanced features."),new Fe(Re.InternalError,r)}}),this.server.setRequestHandler(gs,async()=>({resources:Me()})),this.server.setRequestHandler(ms,async i=>{let{uri:o}=i.params,r=await Pe(o);if(!r)throw new Fe(Re.InvalidRequest,`Unknown resource: ${o}`);let n=(o.includes("action-entity"),"text/markdown");return{contents:[{uri:o,mimeType:n,text:r}]}}),this.server.setRequestHandler(fs,async()=>({prompts:Ae().map(o=>({name:o.name,description:o.description}))})),this.server.setRequestHandler(_s,async i=>{let{name:o}=i.params,r=ve(o);if(!r)throw new Fe(Re.InvalidRequest,`Unknown prompt: ${o}`);return{messages:[{role:"user",content:{type:"text",text:r}}]}})}async run(){let e=new ds;await this.server.connect(e),V.info("Shiplight MCP Server running on stdio")}},vs=new Ne;vs.run().catch(t=>{V.error("Fatal error starting MCP server:",t),process.exit(1)});export{os as LogLevel,Ne as ShiplightMCPServer,ls as createRetryAxios,V as logger,j as retryAxios};
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shiplightai/mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for Shiplight AI test automation platform",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"shiplight-mcp": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@modelcontextprotocol/sdk": "^0.5.0",
|
|
13
|
+
"axios": "^1.6.0",
|
|
14
|
+
"dotenv": "^16.0.3",
|
|
15
|
+
"uuid": "^11.1.0",
|
|
16
|
+
"yaml": "^2.8.0",
|
|
17
|
+
"zod": "^3.22.0",
|
|
18
|
+
"zod-to-json-schema": "^3.24.6",
|
|
19
|
+
"mcp-tools": "1.0.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^24.0.0",
|
|
23
|
+
"tsup": "^8.3.5",
|
|
24
|
+
"tsx": "^4.20.3",
|
|
25
|
+
"typescript": "^5.3.0",
|
|
26
|
+
"shiplight-types": "0.1.0"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsup",
|
|
34
|
+
"dev": "tsx watch src/index.ts",
|
|
35
|
+
"start": "tsx src/index.ts",
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"clean": "rm -rf dist",
|
|
38
|
+
"inspector": "npx @modelcontextprotocol/inspector tsx src/index.ts"
|
|
39
|
+
}
|
|
40
|
+
}
|