chrome-extension-tester-mcp 2.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/LICENSE +21 -0
- package/README.md +267 -0
- package/package.json +45 -0
- package/src/index.js +67 -0
- package/src/prompts/extension-tester.js +138 -0
- package/src/prompts/index.js +9 -0
- package/src/state.js +50 -0
- package/src/tools/assertion.js +66 -0
- package/src/tools/badge.js +76 -0
- package/src/tools/context-menu.js +85 -0
- package/src/tools/dom.js +47 -0
- package/src/tools/index.js +35 -0
- package/src/tools/load-extension.js +48 -0
- package/src/tools/logs.js +28 -0
- package/src/tools/messaging.js +66 -0
- package/src/tools/network.js +84 -0
- package/src/tools/options-page.js +64 -0
- package/src/tools/popup.js +61 -0
- package/src/tools/screenshot.js +27 -0
- package/src/tools/storage.js +81 -0
- package/src/tools/tabs.js +96 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 bhuvanrj
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Chrome Extension Tester — MCP Server
|
|
2
|
+
|
|
3
|
+
An **MCP (Model Context Protocol) server** that lets Claude interactively test any unpacked Chrome extension using Playwright. Load your extension, interact with its popup and options page, inspect storage, monitor network requests, check badges, test messaging, and more — all through natural language.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Features](#features)
|
|
10
|
+
- [Requirements](#requirements)
|
|
11
|
+
- [Installation](#installation)
|
|
12
|
+
- [Setup with Claude Desktop](#setup-with-claude-desktop)
|
|
13
|
+
- [Setup with Claude Code (npx)](#setup-with-claude-code-npx)
|
|
14
|
+
- [Available Tools](#available-tools)
|
|
15
|
+
- [Testing Agent Prompt](#testing-agent-prompt)
|
|
16
|
+
- [Example Prompts](#example-prompts)
|
|
17
|
+
- [Project Structure](#project-structure)
|
|
18
|
+
- [Notes](#notes)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- Load and reload any unpacked Chrome extension
|
|
25
|
+
- Interact with popup and options pages (click, type, read content)
|
|
26
|
+
- Inspect and manipulate `chrome.storage` (local / sync / session)
|
|
27
|
+
- Read background service worker console logs
|
|
28
|
+
- Monitor and inspect network requests
|
|
29
|
+
- Check and assert badge text and color
|
|
30
|
+
- Send messages to the background script and validate responses
|
|
31
|
+
- Simulate tab open / close / switch events
|
|
32
|
+
- Test context menu registration and handler invocation
|
|
33
|
+
- Run assertions that return structured PASS / FAIL results
|
|
34
|
+
- Take screenshots at any point during testing
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Requirements
|
|
39
|
+
|
|
40
|
+
- **Node.js** 18 or higher
|
|
41
|
+
- **Claude Desktop** or **Claude Code** with MCP support
|
|
42
|
+
- A Chrome extension with a `manifest.json` (Manifest V2 or V3)
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
### Option A — npx (no install needed)
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npx chrome-extension-tester-mcp
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Option B — install globally
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm install -g chrome-extension-tester-mcp
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Option C — clone and run locally
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
git clone https://github.com/BHUVAN-RJ/chrome-extension-testing-mcp.git
|
|
64
|
+
cd chrome-extension-testing-mcp
|
|
65
|
+
npm install
|
|
66
|
+
npx playwright install chromium
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Setup with Claude Desktop
|
|
72
|
+
|
|
73
|
+
Add the following to your Claude Desktop MCP config file:
|
|
74
|
+
|
|
75
|
+
**macOS / Linux** — `~/.config/claude/claude_desktop_config.json`
|
|
76
|
+
**Windows** — `%APPDATA%\Claude\claude_desktop_config.json`
|
|
77
|
+
|
|
78
|
+
### Using npx (recommended)
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"mcpServers": {
|
|
83
|
+
"chrome-extension-tester": {
|
|
84
|
+
"command": "npx",
|
|
85
|
+
"args": ["chrome-extension-tester-mcp"]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Using a local clone
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"mcpServers": {
|
|
96
|
+
"chrome-extension-tester": {
|
|
97
|
+
"command": "node",
|
|
98
|
+
"args": ["/absolute/path/to/chrome-extension-testing-mcp/src/index.js"]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Restart Claude Desktop after saving the config.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Setup with Claude Code (npx)
|
|
109
|
+
|
|
110
|
+
Add to your project's `.mcp.json` or user-level MCP config:
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"mcpServers": {
|
|
115
|
+
"chrome-extension-tester": {
|
|
116
|
+
"command": "npx",
|
|
117
|
+
"args": ["chrome-extension-tester-mcp"]
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Available Tools
|
|
126
|
+
|
|
127
|
+
| Tool | What it does |
|
|
128
|
+
|------|-------------|
|
|
129
|
+
| `load_extension` | Launch Chromium with an unpacked extension; captures the extension ID automatically |
|
|
130
|
+
| `interact_with_popup` | Open the popup, then click elements, type text, or read content |
|
|
131
|
+
| `open_options_page` | Open the extension's options / settings page and interact with it |
|
|
132
|
+
| `inspect_dom` | Navigate to a URL, query a DOM selector, or evaluate arbitrary JavaScript |
|
|
133
|
+
| `get_service_worker_logs` | Read buffered background service worker console logs; optionally clear them |
|
|
134
|
+
| `take_screenshot` | Save a screenshot of the current page or popup |
|
|
135
|
+
| `run_assertion` | Assert that an element exists, has specific text, or a JS expression is truthy — returns PASS or FAIL |
|
|
136
|
+
| `extension_storage` | Get, set, remove, or clear keys in `chrome.storage.local`, `.sync`, or `.session` |
|
|
137
|
+
| `monitor_network` | Capture network requests during navigation; retrieve or clear the captured list |
|
|
138
|
+
| `check_badge` | Read or assert the extension action badge text and background color |
|
|
139
|
+
| `send_message_to_background` | Send `chrome.runtime.sendMessage` from the popup context and return the response |
|
|
140
|
+
| `test_context_menu` | Check `contextMenus` API availability, simulate right-click, or invoke a menu item handler directly |
|
|
141
|
+
| `simulate_tab_events` | Open, close, switch, list, or close all browser tabs |
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Testing Agent Prompt
|
|
146
|
+
|
|
147
|
+
The server includes a built-in MCP prompt called **`extension-tester-agent`** — a fully automated testing agent that validates all implemented changes and returns a structured report.
|
|
148
|
+
|
|
149
|
+
### Arguments
|
|
150
|
+
|
|
151
|
+
| Argument | Required | Description |
|
|
152
|
+
|----------|----------|-------------|
|
|
153
|
+
| `extension_path` | yes | Absolute path to the unpacked extension folder |
|
|
154
|
+
| `extension_description` | yes | What the extension does — features, UI, storage, background behaviour |
|
|
155
|
+
| `changes` | yes | Everything implemented or changed in this session |
|
|
156
|
+
|
|
157
|
+
### What it does
|
|
158
|
+
|
|
159
|
+
1. **Understands** the extension and derives a set of tests from the changes list
|
|
160
|
+
2. **Writes a test plan** — every change maps to at least one test and the right MCP tool
|
|
161
|
+
3. **Executes every test** — never skips, takes screenshots on failure
|
|
162
|
+
4. **Reports** a structured PASS / FAIL table with details on any failures
|
|
163
|
+
|
|
164
|
+
### How to invoke
|
|
165
|
+
|
|
166
|
+
After implementing changes, tell Claude:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
Use the extension-tester-agent prompt with:
|
|
170
|
+
- extension_path: /path/to/my-extension
|
|
171
|
+
- extension_description: "A tab manager that saves sessions to chrome.storage.local and restores them via a popup"
|
|
172
|
+
- changes: "Added save button; save button writes open tabs to storage.local; badge shows count of saved tabs"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Claude will write the test plan, execute every test, and return a full report.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Example Prompts
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
Load my extension from /Users/me/my-extension and open the popup
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
Click the button with selector #save and take a screenshot
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
Navigate to https://example.com and check if my content script injected a .banner element
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
Read all keys from chrome.storage.local
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
Set { "enabled": true } in chrome.storage.local and verify it was saved
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
Navigate to https://example.com, capture all network requests, then show me any that were blocked
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
Check the badge text — it should say "ON"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
Send the message { "type": "GET_STATUS" } to the background and show the response
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
Open a tab to https://news.ycombinator.com, then another to https://github.com, then list all open tabs
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
Right-click on https://example.com and trigger the context menu item with id "my-action"
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Project Structure
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
chrome-extension-testing-mcp/
|
|
227
|
+
├── src/
|
|
228
|
+
│ ├── index.js # MCP server entry point
|
|
229
|
+
│ ├── state.js # Shared browser state and helpers
|
|
230
|
+
│ ├── prompts/
|
|
231
|
+
│ │ ├── index.js # Registers MCP prompts
|
|
232
|
+
│ │ └── extension-tester.js # extension-tester-agent prompt definition
|
|
233
|
+
│ └── tools/
|
|
234
|
+
│ ├── index.js # Aggregates all tool definitions and handlers
|
|
235
|
+
│ ├── load-extension.js
|
|
236
|
+
│ ├── popup.js
|
|
237
|
+
│ ├── dom.js
|
|
238
|
+
│ ├── logs.js
|
|
239
|
+
│ ├── screenshot.js
|
|
240
|
+
│ ├── assertion.js
|
|
241
|
+
│ ├── storage.js
|
|
242
|
+
│ ├── network.js
|
|
243
|
+
│ ├── options-page.js
|
|
244
|
+
│ ├── context-menu.js
|
|
245
|
+
│ ├── badge.js
|
|
246
|
+
│ ├── messaging.js
|
|
247
|
+
│ └── tabs.js
|
|
248
|
+
├── package.json
|
|
249
|
+
└── README.md
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Notes
|
|
255
|
+
|
|
256
|
+
- The browser launches in **headed mode** (visible window) so you can watch tests run in real time
|
|
257
|
+
- Screenshots default to `./screenshot.png` unless a custom path is provided
|
|
258
|
+
- Service worker logs are buffered from the moment `load_extension` is called
|
|
259
|
+
- Call `load_extension` again at any time to get a fresh browser instance
|
|
260
|
+
- Native Chrome context menus cannot be automated by Playwright — use `test_context_menu` with `trigger_item` to invoke handlers directly
|
|
261
|
+
- Badge and storage tools communicate via the service worker, so the extension must have a background service worker (MV3)
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## License
|
|
266
|
+
|
|
267
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "chrome-extension-tester-mcp",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "MCP server for interactive Chrome extension testing via Playwright — load, interact, assert, inspect storage, network, badges, messaging, tabs, and more.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"chrome-extension-tester": "src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node src/index.js",
|
|
12
|
+
"dev": "node --watch src/index.js",
|
|
13
|
+
"postinstall": "playwright install chromium"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"src/"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"mcp",
|
|
20
|
+
"model-context-protocol",
|
|
21
|
+
"chrome-extension",
|
|
22
|
+
"testing",
|
|
23
|
+
"playwright",
|
|
24
|
+
"automation",
|
|
25
|
+
"browser-testing",
|
|
26
|
+
"claude"
|
|
27
|
+
],
|
|
28
|
+
"author": "bhuvanrj",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/BHUVAN-RJ/chrome-extension-testing-mcp"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/BHUVAN-RJ/chrome-extension-testing-mcp#readme",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/BHUVAN-RJ/chrome-extension-testing-mcp/issues"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
40
|
+
"playwright": "^1.44.0"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18.0.0"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import {
|
|
5
|
+
CallToolRequestSchema,
|
|
6
|
+
ListToolsRequestSchema,
|
|
7
|
+
ListPromptsRequestSchema,
|
|
8
|
+
GetPromptRequestSchema,
|
|
9
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
import { TOOLS, HANDLERS } from "./tools/index.js";
|
|
11
|
+
import { PROMPTS, PROMPT_HANDLERS } from "./prompts/index.js";
|
|
12
|
+
|
|
13
|
+
const server = new Server(
|
|
14
|
+
{ name: "chrome-extension-tester", version: "2.0.0" },
|
|
15
|
+
{ capabilities: { tools: {}, prompts: {} } }
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
// ── Tools ─────────────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
21
|
+
|
|
22
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
23
|
+
const { name, arguments: args } = request.params;
|
|
24
|
+
const handler = HANDLERS[name];
|
|
25
|
+
|
|
26
|
+
if (!handler) {
|
|
27
|
+
return {
|
|
28
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
29
|
+
isError: true,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
return await handler(args || {});
|
|
35
|
+
} catch (err) {
|
|
36
|
+
return {
|
|
37
|
+
content: [{ type: "text", text: `Error in ${name}: ${err.message}` }],
|
|
38
|
+
isError: true,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// ── Prompts ───────────────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: PROMPTS }));
|
|
46
|
+
|
|
47
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
48
|
+
const { name, arguments: args } = request.params;
|
|
49
|
+
const getMessages = PROMPT_HANDLERS[name];
|
|
50
|
+
|
|
51
|
+
if (!getMessages) {
|
|
52
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const definition = PROMPTS.find((p) => p.name === name);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
description: definition.description,
|
|
59
|
+
messages: getMessages(args || {}),
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// ── Start ─────────────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
const transport = new StdioServerTransport();
|
|
66
|
+
await server.connect(transport);
|
|
67
|
+
console.error("Chrome Extension Tester MCP server running (v2.0.0)...");
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
export const definition = {
|
|
2
|
+
name: "extension-tester-agent",
|
|
3
|
+
description:
|
|
4
|
+
"A testing agent that validates all implemented changes in a Chrome extension. Understands what the extension does, derives tests from the changes list, runs every test using the MCP tools, and returns a structured PASS/FAIL report.",
|
|
5
|
+
arguments: [
|
|
6
|
+
{
|
|
7
|
+
name: "extension_path",
|
|
8
|
+
description: "Absolute path to the unpacked extension folder (must contain manifest.json)",
|
|
9
|
+
required: true,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: "extension_description",
|
|
13
|
+
description:
|
|
14
|
+
"What this extension does — its purpose, key features, UI elements, storage it uses, pages it injects into, background behaviour, etc. The more detail the better.",
|
|
15
|
+
required: true,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: "changes",
|
|
19
|
+
description:
|
|
20
|
+
"Everything that was implemented or changed in this session. List each change on its own line or separated by semicolons.",
|
|
21
|
+
required: true,
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function getMessages({ extension_path, extension_description, changes }) {
|
|
27
|
+
return [
|
|
28
|
+
{
|
|
29
|
+
role: "user",
|
|
30
|
+
content: {
|
|
31
|
+
type: "text",
|
|
32
|
+
text: `
|
|
33
|
+
You are a Chrome extension testing agent. Your job is to validate that every implemented change works correctly before the result is returned to the user.
|
|
34
|
+
|
|
35
|
+
You have access to the chrome-extension-tester MCP tools:
|
|
36
|
+
- load_extension → launch Chrome with the extension loaded
|
|
37
|
+
- interact_with_popup → open popup, click, type, read text/HTML
|
|
38
|
+
- open_options_page → open and interact with the options/settings page
|
|
39
|
+
- inspect_dom → navigate to a URL, query selectors, or run JS
|
|
40
|
+
- get_service_worker_logs → read background service worker console output
|
|
41
|
+
- take_screenshot → save a screenshot (use on any failure)
|
|
42
|
+
- run_assertion → assert element exists, has text, or a JS expression is true
|
|
43
|
+
- extension_storage → get/set/remove/clear chrome.storage (local/sync/session)
|
|
44
|
+
- monitor_network → capture network requests during navigation
|
|
45
|
+
- check_badge → read or assert the extension icon badge
|
|
46
|
+
- send_message_to_background → send chrome.runtime.sendMessage and capture the response
|
|
47
|
+
- test_context_menu → verify context menu API or simulate right-click events
|
|
48
|
+
- simulate_tab_events → open, close, switch, and list browser tabs
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Extension
|
|
53
|
+
|
|
54
|
+
**Path:** ${extension_path}
|
|
55
|
+
|
|
56
|
+
**What it does:**
|
|
57
|
+
${extension_description}
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Changes to validate
|
|
62
|
+
|
|
63
|
+
${changes}
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Your process — follow this exactly
|
|
68
|
+
|
|
69
|
+
### Phase 1 — Understand
|
|
70
|
+
Read the extension description and the changes list carefully.
|
|
71
|
+
For each change, reason about:
|
|
72
|
+
- What part of the extension is affected (popup UI, background logic, content script, storage, messaging, options page, badge, context menu, network, tabs)
|
|
73
|
+
- What the correct behaviour looks like after this change
|
|
74
|
+
- Which MCP tools are the right ones to verify it
|
|
75
|
+
|
|
76
|
+
### Phase 2 — Write a test plan
|
|
77
|
+
Before running anything, output a numbered test plan:
|
|
78
|
+
- One line per test
|
|
79
|
+
- Format: [TOOL] Description of what is being verified
|
|
80
|
+
- Cover EVERY change — do not skip anything
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
1. [load_extension] Load extension from path, confirm extension ID is detected
|
|
84
|
+
2. [interact_with_popup] Open popup, assert #save-button exists
|
|
85
|
+
3. [run_assertion] Assert #save-button has text "Save"
|
|
86
|
+
4. [extension_storage] After clicking save, verify storage.local has { saved: true }
|
|
87
|
+
5. [get_service_worker_logs] Check no errors logged during save flow
|
|
88
|
+
|
|
89
|
+
### Phase 3 — Execute every test
|
|
90
|
+
- Start by calling load_extension
|
|
91
|
+
- Work through the test plan top to bottom
|
|
92
|
+
- For each test: call the tool, record the result (PASS / FAIL + detail)
|
|
93
|
+
- On any FAIL: call take_screenshot immediately, note the output path
|
|
94
|
+
- Do not stop on failure — run every test and collect all results
|
|
95
|
+
|
|
96
|
+
### Phase 4 — Report
|
|
97
|
+
Output a final report in this exact format:
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
## Test Report
|
|
101
|
+
|
|
102
|
+
**Extension:** ${extension_path}
|
|
103
|
+
**Result:** PASS ← or FAIL
|
|
104
|
+
|
|
105
|
+
### Results
|
|
106
|
+
|
|
107
|
+
| # | Test | Result | Detail |
|
|
108
|
+
|---|------|--------|--------|
|
|
109
|
+
| 1 | [load_extension] Load extension | PASS | ID: abcdef... |
|
|
110
|
+
| 2 | [interact_with_popup] #save-button exists | FAIL | Element not found |
|
|
111
|
+
| 3 | ... | ... | ... |
|
|
112
|
+
|
|
113
|
+
### Failures
|
|
114
|
+
(only if FAIL)
|
|
115
|
+
For each failed test:
|
|
116
|
+
- What was expected
|
|
117
|
+
- What was actually observed
|
|
118
|
+
- Screenshot path if taken
|
|
119
|
+
- Likely cause based on the change description
|
|
120
|
+
|
|
121
|
+
### Summary
|
|
122
|
+
X of Y tests passed.
|
|
123
|
+
(If all pass: "All changes verified. Ready to return to user.")
|
|
124
|
+
(If any fail: "Do not return to user. Fix the following before re-testing: ...")
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Rules
|
|
128
|
+
- Always load_extension first, even if the browser is already open
|
|
129
|
+
- Never mark the result PASS if any single test failed
|
|
130
|
+
- If a tool call errors, that counts as a FAIL for that test
|
|
131
|
+
- Do not invent tests for things not in the changes list
|
|
132
|
+
- Do not skip tests because they seem obvious
|
|
133
|
+
- Be concise in tool calls — don't narrate, just execute
|
|
134
|
+
`.trim(),
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as extensionTester from "./extension-tester.js";
|
|
2
|
+
|
|
3
|
+
const allPrompts = [extensionTester];
|
|
4
|
+
|
|
5
|
+
export const PROMPTS = allPrompts.map((p) => p.definition);
|
|
6
|
+
|
|
7
|
+
export const PROMPT_HANDLERS = Object.fromEntries(
|
|
8
|
+
allPrompts.map((p) => [p.definition.name, p.getMessages])
|
|
9
|
+
);
|
package/src/state.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { chromium } from "playwright";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
export const state = {
|
|
6
|
+
browser: null,
|
|
7
|
+
page: null,
|
|
8
|
+
extensionId: null,
|
|
9
|
+
swLogs: [],
|
|
10
|
+
networkCaptures: [],
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export async function ensureBrowser(extensionPath) {
|
|
14
|
+
if (state.browser) return;
|
|
15
|
+
const absPath = path.resolve(extensionPath);
|
|
16
|
+
if (!fs.existsSync(absPath)) throw new Error(`Extension path not found: ${absPath}`);
|
|
17
|
+
|
|
18
|
+
state.browser = await chromium.launchPersistentContext("", {
|
|
19
|
+
headless: false,
|
|
20
|
+
args: [
|
|
21
|
+
`--disable-extensions-except=${absPath}`,
|
|
22
|
+
`--load-extension=${absPath}`,
|
|
23
|
+
],
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
27
|
+
const workers = state.browser.serviceWorkers();
|
|
28
|
+
if (workers.length > 0) {
|
|
29
|
+
const url = workers[0].url();
|
|
30
|
+
const match = url.match(/chrome-extension:\/\/([a-z]{32})\//);
|
|
31
|
+
if (match) state.extensionId = match[1];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
state.page = await state.browser.newPage();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function ensurePage() {
|
|
38
|
+
if (!state.page || state.page.isClosed()) {
|
|
39
|
+
if (!state.browser) throw new Error("Browser not started. Call load_extension first.");
|
|
40
|
+
state.page = await state.browser.newPage();
|
|
41
|
+
}
|
|
42
|
+
return state.page;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function getServiceWorker() {
|
|
46
|
+
if (!state.browser) throw new Error("Browser not started. Call load_extension first.");
|
|
47
|
+
const workers = state.browser.serviceWorkers();
|
|
48
|
+
if (!workers.length) throw new Error("No service worker found. Extension may not have a background service worker.");
|
|
49
|
+
return workers[0];
|
|
50
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ensurePage } from "../state.js";
|
|
2
|
+
|
|
3
|
+
export const definition = {
|
|
4
|
+
name: "run_assertion",
|
|
5
|
+
description: "Run a test assertion against the current page. Checks a condition and reports PASS or FAIL.",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: "object",
|
|
8
|
+
properties: {
|
|
9
|
+
description: {
|
|
10
|
+
type: "string",
|
|
11
|
+
description: "Human-readable description of what is being tested",
|
|
12
|
+
},
|
|
13
|
+
selector: {
|
|
14
|
+
type: "string",
|
|
15
|
+
description: "CSS selector to check existence or text content of",
|
|
16
|
+
},
|
|
17
|
+
expected_text: {
|
|
18
|
+
type: "string",
|
|
19
|
+
description: "Expected text content of the element (optional, used with selector)",
|
|
20
|
+
},
|
|
21
|
+
script: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "JS expression that must return true to pass (overrides selector)",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
required: ["description"],
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export async function handler(args) {
|
|
31
|
+
const p = await ensurePage();
|
|
32
|
+
let passed = false;
|
|
33
|
+
let detail = "";
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
if (args.script) {
|
|
37
|
+
passed = !!(await p.evaluate(args.script));
|
|
38
|
+
detail = `Script: ${args.script}`;
|
|
39
|
+
} else if (args.selector) {
|
|
40
|
+
const el = await p.$(args.selector);
|
|
41
|
+
if (!el) {
|
|
42
|
+
passed = false;
|
|
43
|
+
detail = `Element "${args.selector}" not found`;
|
|
44
|
+
} else if (args.expected_text !== undefined) {
|
|
45
|
+
const actual = await el.textContent();
|
|
46
|
+
passed = actual.trim() === args.expected_text.trim();
|
|
47
|
+
detail = `Expected: "${args.expected_text}" | Got: "${actual.trim()}"`;
|
|
48
|
+
} else {
|
|
49
|
+
passed = true;
|
|
50
|
+
detail = `Element "${args.selector}" exists`;
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
return { content: [{ type: "text", text: "Provide a selector or script for the assertion." }] };
|
|
54
|
+
}
|
|
55
|
+
} catch (e) {
|
|
56
|
+
passed = false;
|
|
57
|
+
detail = `Error: ${e.message}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
content: [{
|
|
62
|
+
type: "text",
|
|
63
|
+
text: `${passed ? "PASS" : "FAIL"} — ${args.description}\n${detail}`,
|
|
64
|
+
}],
|
|
65
|
+
};
|
|
66
|
+
}
|