Skip to content

Commit 06fca5a

Browse files
authored
Merge pull request #46 from webdriverio/feature/browserstack-provider
feat: Introduce BrowserStack provider and tools
2 parents 04a84ae + c989434 commit 06fca5a

26 files changed

Lines changed: 2064 additions & 1142 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ lib
66

77
settings.local.json
88
*.tgz
9+
10+
.mcp.json
11+
.env

CLAUDE.md

Lines changed: 30 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -15,65 +15,22 @@ npm start # Run built server from lib/server.js
1515

1616
```
1717
src/
18-
├── server.ts # MCP server entry, registers all tools + MCP resources
19-
├── session/
20-
│ ├── state.ts # Session state maps, getBrowser(), getState(), SessionMetadata
21-
│ └── lifecycle.ts # registerSession(), handleSessionTransition(), closeSession()
22-
├── providers/
23-
│ ├── types.ts # SessionProvider interface, ConnectionConfig
24-
│ ├── local-browser.provider.ts # Chrome/Firefox/Edge/Safari capability building
25-
│ └── local-appium.provider.ts # iOS/Android via appium.config.ts
26-
├── tools/
27-
│ ├── session.tool.ts # start_session (browser+mobile), close_session
28-
│ ├── tabs.tool.ts # switch_tab
29-
│ ├── launch-chrome.tool.ts # launch_chrome (remote debugging)
30-
│ ├── navigate.tool.ts # navigateAction() + navigateTool
31-
│ ├── click.tool.ts # clickAction() + clickTool
32-
│ ├── set-value.tool.ts # setValueAction() + setValueTool
33-
│ ├── scroll.tool.ts # scrollAction() + scrollTool
34-
│ ├── gestures.tool.ts # tapAction(), swipeAction(), dragAndDropAction()
35-
│ ├── context.tool.ts # switch_context (native/webview)
36-
│ ├── device.tool.ts # rotate_device, hide_keyboard
37-
│ ├── emulate-device.tool.ts # emulate_device (viewport/UA)
38-
│ ├── cookies.tool.ts # set_cookie, delete_cookies
39-
│ ├── execute-script.tool.ts # execute_script
40-
│ ├── get-elements.tool.ts # get_elements (all elements, incl. below fold)
41-
│ └── ... # Other tools follow same pattern
42-
├── resources/
43-
│ ├── index.ts # ResourceDefinition exports
44-
│ ├── sessions.resource.ts # wdio://sessions, wdio://session/*/steps, wdio://session/*/code
45-
│ ├── elements.resource.ts # wdio://session/current/elements
46-
│ ├── accessibility.resource.ts# wdio://session/current/accessibility
47-
│ ├── screenshot.resource.ts # wdio://session/current/screenshot
48-
│ ├── cookies.resource.ts # wdio://session/current/cookies
49-
│ ├── tabs.resource.ts # wdio://session/current/tabs
50-
│ ├── contexts.resource.ts # wdio://session/current/contexts
51-
│ ├── app-state.resource.ts # wdio://session/current/app-state
52-
│ └── geolocation.resource.ts # wdio://session/current/geolocation
53-
├── recording/
54-
│ ├── step-recorder.ts # withRecording HOF, appendStep, session history access
55-
│ └── code-generator.ts # SessionHistory → WebdriverIO JS code
56-
├── scripts/
57-
│ ├── get-interactable-browser-elements.ts # Browser-context element detection
58-
│ ├── get-browser-accessibility-tree.ts # Browser-context accessibility tree
59-
│ ├── get-visible-mobile-elements.ts # Mobile visible element detection
60-
│ └── get-elements.ts # Filter + paginate elements (used by tool + resource)
61-
├── locators/
62-
│ ├── element-filter.ts # Platform-specific element classification
63-
│ ├── locator-generation.ts # Multi-strategy selector generation
64-
│ ├── xml-parsing.ts # XML page source parsing for mobile
65-
│ ├── constants.ts # Shared locator constants
66-
│ ├── types.ts # Locator type definitions
67-
│ └── index.ts # Public exports
68-
├── config/
69-
│ └── appium.config.ts # iOS/Android capability builders (used by local-appium.provider)
70-
├── utils/
71-
│ ├── parse-variables.ts # URI template variable parsing (parseBool, parseNumber, etc.)
72-
│ └── zod-helpers.ts # coerceBoolean and other Zod utilities
73-
└── types/
74-
├── tool.ts # ToolDefinition interface
75-
├── resource.ts # ResourceDefinition interface
76-
└── recording.ts # RecordedStep, SessionHistory interfaces
18+
├── server.ts # MCP server entry — registers all tools + resources
19+
├── session/ # Session state (state.ts) + lifecycle (lifecycle.ts)
20+
├── providers/ # SessionProvider implementations
21+
│ ├── registry.ts # getProvider() — routes to local or cloud provider
22+
│ ├── local-browser.provider.ts # Chrome/Firefox/Edge/Safari
23+
│ ├── local-appium.provider.ts # iOS/Android via Appium
24+
│ └── cloud/
25+
│ └── browserstack.provider.ts # BrowserStack (browser + App Automate)
26+
├── tools/ # One file per MCP tool (see Tool Pattern below)
27+
├── resources/ # One file per MCP resource (see Recording below)
28+
├── recording/ # step-recorder.ts (withRecording HOF) + code-generator.ts
29+
├── scripts/ # Browser/mobile scripts executed via browser.execute() — no try/catch, raw data only
30+
├── locators/ # Element detection, selector generation, XML parsing (mobile)
31+
├── config/ # appium.config.ts — iOS/Android capability builders
32+
├── utils/ # parse-variables.ts, zod-helpers.ts (coerceBoolean)
33+
└── types/ # ToolDefinition, ResourceDefinition, RecordedStep interfaces
7734
```
7835

7936
### Session State
@@ -157,12 +114,12 @@ MCP resources expose live session data — all at fixed URIs discoverable via Li
157114
| `src/server.ts` | MCP server init, tool + resource registration |
158115
| `src/session/state.ts` | Session state maps, `getBrowser()`, `getState()` |
159116
| `src/session/lifecycle.ts` | `registerSession()`, `closeSession()`, session transitions |
117+
| `src/providers/registry.ts` | `getProvider()` — routes to local or cloud provider |
118+
| `src/providers/cloud/browserstack.provider.ts` | BrowserStack session provider |
160119
| `src/tools/session.tool.ts` | `start_session` (browser + mobile), `close_session` |
161-
| `src/tools/tabs.tool.ts` | `switch_tab` |
162120
| `src/tools/get-elements.tool.ts` | `get_elements` — all elements with filtering + pagination |
163-
| `src/resources/` | All MCP resource definitions (10 files) |
164-
| `src/providers/local-browser.provider.ts` | Chrome/Firefox/Edge/Safari capability building |
165-
| `src/providers/local-appium.provider.ts` | iOS/Android capabilities via appium.config.ts |
121+
| `src/tools/browserstack.tool.ts` | `list_apps`, `upload_app` — BrowserStack App Automate |
122+
| `src/resources/` | All MCP resource definitions (12 files) |
166123
| `src/scripts/get-interactable-browser-elements.ts` | Browser-context element detection |
167124
| `src/locators/` | Mobile element detection + locator generation |
168125
| `src/recording/step-recorder.ts` | `withRecording(toolName, cb)` HOF — wraps tools for step logging |
@@ -234,9 +191,17 @@ catch (e) {
234191
- iOS Predicate: `-ios predicate string:label == "Login"`
235192
- XPath: `//XCUIElementTypeButton[@label="Login"]`
236193

194+
## Environment
195+
196+
| Variable | Required for |
197+
|----------|-------------|
198+
| `BROWSERSTACK_USERNAME` | BrowserStack sessions + tools |
199+
| `BROWSERSTACK_ACCESS_KEY` | BrowserStack sessions + tools |
200+
237201
## Planned Improvements
238202

239203
See `docs/architecture/` for proposals:
240204

241-
- `session-configuration-proposal.md` — Cloud provider pattern (BrowserStack, SauceLabs) — providers/types.ts is the extension point
242-
- `multi-session-proposal.md` — Parallel sessions for sub-agent coordination
205+
- `session-configuration-proposal.md` — Cloud provider pattern (SauceLabs etc.) — BrowserStack already implemented; `providers/registry.ts` + `providers/cloud/` is the extension point
206+
- `multi-session-proposal.md` — Parallel sessions for sub-agent coordination
207+
- `interaction-sequencing-proposal.md` — Sequencing model for tool interactions

README.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,117 @@ appium
216216
# Server runs at http://127.0.0.1:4723 by default
217217
```
218218

219+
## BrowserStack
220+
221+
Run browser and mobile app tests on [BrowserStack](https://www.browserstack.com/) real devices and browsers without any local setup.
222+
223+
### Prerequisites
224+
225+
Set your credentials as environment variables:
226+
227+
```bash
228+
export BROWSERSTACK_USERNAME=your_username
229+
export BROWSERSTACK_ACCESS_KEY=your_access_key
230+
```
231+
232+
Or add them to your MCP client config:
233+
234+
```json
235+
{
236+
"mcpServers": {
237+
"wdio-mcp": {
238+
"command": "npx",
239+
"args": ["-y", "@wdio/mcp@latest"],
240+
"env": {
241+
"BROWSERSTACK_USERNAME": "your_username",
242+
"BROWSERSTACK_ACCESS_KEY": "your_access_key"
243+
}
244+
}
245+
}
246+
}
247+
```
248+
249+
### Browser Sessions
250+
251+
Run a browser on a specific OS/version combination:
252+
253+
```javascript
254+
start_session({
255+
provider: 'browserstack',
256+
platform: 'browser',
257+
browser: 'chrome', // chrome | firefox | edge | safari
258+
browserVersion: 'latest', // default: latest
259+
os: 'Windows', // e.g. "Windows", "OS X"
260+
osVersion: '11', // e.g. "11", "Sequoia"
261+
reporting: {
262+
project: 'My Project',
263+
build: 'v1.2.0',
264+
session: 'Login flow'
265+
}
266+
})
267+
```
268+
269+
### Mobile App Sessions
270+
271+
Test on BrowserStack real devices. First upload your app (or use an existing `bs://` URL):
272+
273+
```javascript
274+
// Upload a local .apk or .ipa (returns a bs:// URL)
275+
upload_app({ path: '/path/to/app.apk' })
276+
277+
// Start a session with the returned URL
278+
start_session({
279+
provider: 'browserstack',
280+
platform: 'android', // android | ios
281+
app: 'bs://abc123...', // bs:// URL or custom_id from upload
282+
deviceName: 'Samsung Galaxy S23',
283+
platformVersion: '13.0',
284+
reporting: {
285+
project: 'My Project',
286+
build: 'v1.2.0',
287+
session: 'Checkout flow'
288+
}
289+
})
290+
```
291+
292+
Use `list_apps` to see previously uploaded apps:
293+
294+
```javascript
295+
list_apps() // own uploads, sorted by date
296+
list_apps({ sortBy: 'app_name' })
297+
list_apps({ organizationWide: true }) // all uploads in your org
298+
```
299+
300+
### BrowserStack Local
301+
302+
To test against URLs that are only accessible on your local machine or internal network, enable the BrowserStack Local tunnel:
303+
304+
```javascript
305+
start_session({
306+
provider: 'browserstack',
307+
platform: 'browser',
308+
browser: 'chrome',
309+
browserstackLocal: true // starts tunnel automatically
310+
})
311+
```
312+
313+
### Reporting Labels
314+
315+
All session types support `reporting` labels that appear in the BrowserStack Automate dashboard:
316+
317+
| Field | Description |
318+
|---------------------------|--------------------------------------------|
319+
| `reporting.project` | Group sessions under a project name |
320+
| `reporting.build` | Tag sessions with a build/version label |
321+
| `reporting.session` | Name for the individual test session |
322+
323+
### BrowserStack Tools
324+
325+
| Tool | Description |
326+
|---------------|----------------------------------------------------------------------------------|
327+
| `upload_app` | Upload a local `.apk` or `.ipa` to BrowserStack; returns a `bs://` URL |
328+
| `list_apps` | List apps previously uploaded to your BrowserStack account |
329+
219330
## Features
220331

221332
### Browser Automation

package.json

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,40 +37,38 @@
3737
"prebundle": "rimraf lib --glob ./*.tgz",
3838
"bundle": "tsup && shx chmod +x lib/server.js",
3939
"postbundle": "npm pack",
40-
"lint": "npm run lint:src && npm run lint:tests",
41-
"lint:src": "eslint src/ --fix && tsc --noEmit",
42-
"lint:tests": "eslint tests/ --fix && tsc -p tsconfig.test.json --noEmit",
40+
"lint": "eslint src/ tests/ --fix && tsc --noEmit",
4341
"start": "node lib/server.js",
4442
"dev": "tsx --watch src/server.ts",
4543
"prepare": "husky",
4644
"test": "vitest run"
4745
},
4846
"dependencies": {
49-
"@modelcontextprotocol/sdk": "1.27",
47+
"@modelcontextprotocol/sdk": "^1.27.1",
5048
"@toon-format/toon": "^2.1.0",
51-
"@wdio/protocols": "^9.16.2",
52-
"@xmldom/xmldom": "^0.8.11",
53-
"puppeteer-core": "^24.35.0",
49+
"@wdio/protocols": "^9.27.0",
50+
"@xmldom/xmldom": "^0.8.12",
51+
"puppeteer-core": "^24.40.0",
5452
"sharp": "^0.34.5",
55-
"webdriverio": "9.24",
53+
"webdriverio": "^9.27.0",
5654
"xpath": "^0.0.34",
57-
"zod": "^4.3.5"
55+
"zod": "^4.3.6"
5856
},
5957
"devDependencies": {
60-
"@release-it/conventional-changelog": "^10.0.4",
61-
"@types/node": "^20.11.0",
58+
"@release-it/conventional-changelog": "^10.0.6",
59+
"@types/node": "^20.19.37",
6260
"@wdio/eslint": "^0.1.3",
63-
"@wdio/types": "^9.20.0",
64-
"eslint": "^9.39.2",
65-
"happy-dom": "^20.7.0",
61+
"@wdio/types": "^9.27.0",
62+
"eslint": "^9.39.4",
63+
"happy-dom": "^20.8.9",
6664
"husky": "^9.1.7",
67-
"release-it": "^19.2.3",
68-
"rimraf": "^6.1.2",
65+
"release-it": "^19.2.4",
66+
"rimraf": "^6.1.3",
6967
"shx": "^0.4.0",
7068
"tsup": "^8.5.1",
7169
"tsx": "^4.21.0",
72-
"typescript": "5.9",
73-
"vitest": "^4.0.18"
70+
"typescript": "~5.9.3",
71+
"vitest": "^4.1.2"
7472
},
7573
"packageManager": "[email protected]"
7674
}

0 commit comments

Comments
 (0)