k11-selenium-qa-mcp 0.1.0__tar.gz

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.
@@ -0,0 +1,9 @@
1
+ .venv/
2
+ venv/
3
+ __pycache__/
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .pytest_cache/
8
+ .tmp*
9
+ .qa-mcp/auth/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 K11 Software Solutions
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.
@@ -0,0 +1,209 @@
1
+ Metadata-Version: 2.5
2
+ Name: k11-selenium-qa-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server and Claude Code plugin for Selenium QA: verified locators, user story to pytest, and self-healing locators with an audit trail.
5
+ Project-URL: Homepage, https://github.com/K11-Software-Solutions/k11techlab-selenium-qa-mcp
6
+ Project-URL: Issues, https://github.com/K11-Software-Solutions/k11techlab-selenium-qa-mcp/issues
7
+ Author: K11 Software Solutions
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: claude,mcp,pytest,qa,selenium,self-healing,test-automation
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Framework :: Pytest
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Software Development :: Testing
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: mcp<2,>=1.20
18
+ Requires-Dist: selenium>=4.27
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest-timeout>=2; extra == 'dev'
21
+ Requires-Dist: pytest>=8; extra == 'dev'
22
+ Description-Content-Type: text/markdown
23
+
24
+ # k11 Selenium QA — MCP server & Claude Code plugin
25
+
26
+ <!-- mcp-name: io.github.K11-Software-Solutions/selenium-qa -->
27
+
28
+ **Self-healing Selenium tests with an audit trail. No vendor lock-in, no rewrite to Playwright.**
29
+
30
+ Most Selenium suites break the same way: someone renames an id, and dozens of `find_element(By.ID, ...)` calls fail overnight. AI assistants can write Selenium tests, but they guess locators, and healing tools usually patch things at runtime without telling you what changed. This MCP server gives the assistant facts instead of guesses, and gives your team a reviewable record of every fix:
31
+
32
+ - **Verified locators.** It opens the real page in Selenium and returns locators that match exactly one element, most stable first: `data-testid`, a stable `id`, `name`, link text, `aria-label`, then text-based XPath.
33
+ - **Story → pytest.** It turns "As a… I want… so that…", Gherkin scenarios or acceptance-criteria bullets into a pytest + Selenium skeleton: one test per scenario, explicit waits, and a `driver` fixture if you don't have one.
34
+ - **Structured test runs.** It runs your pytest suite and reports, for each failure, the exact locator Selenium could not find, the line that asked for it, and the page the browser was on.
35
+ - **Self-healing with evidence.** When the UI changes, it finds the element the test meant and suggests a verified replacement, with a confidence score, the reasons and the runner-up.
36
+ - **Healing in CI.** `heal-ci` applies only confident fixes, edits your tests *and Page Objects*, re-runs them, reverts anything that still fails, and opens a pull request whose description is the audit report.
37
+
38
+ Works with **Claude Code** (as a plugin with `/story-to-test` and `/heal-tests`), **Claude Desktop**, and any MCP client. Python 3.10+, Selenium 4.
39
+
40
+ ---
41
+
42
+ ## See it heal in CI
43
+
44
+ The demo app's account page was redesigned: ids renamed, a "View returns" button added next to "View orders", "Edit profile" renamed to "Account settings", the cart message reworded and the page title changed. The Selenium suite uses a Page Object:
45
+
46
+ ```python
47
+ class AccountPage:
48
+ VIEW_ORDERS = (By.ID, "view-orders")
49
+ OPEN_CART = (By.ID, "cart-btn")
50
+ ```
51
+
52
+ `k11-selenium-qa-mcp heal-ci --apply` produced this pull-request description (trimmed):
53
+
54
+ > **1 healed and verified**, 0 suggested, 1 reverted, 1 needs review, 0 not found, 1 failure not caused by locators.
55
+ >
56
+ > | Test | Where | Old locator | New locator | Confidence | Runner-up | Re-run |
57
+ > |---|---|---|---|---|---|---|
58
+ > | test_shows_my_orders | tests/pages/account_page.py:7 | `(By.ID, "view-orders")` | `(By.ID, "orders-toggle")` | 82% | `(By.ID, "returns-toggle")` (59%) | passed |
59
+ >
60
+ > Why: name matches, role matches, text matches, type matches, tag matches; classes similar (43%).
61
+ >
62
+ > **Needs a person:** "Edit profile" → best candidate "Account settings" at 45%, not confident enough to change. `(By.ID, "cart-btn")` healed at 85%, but the test still failed because the cart message changed too, so the change was **reverted**.
63
+ >
64
+ > **Not caused by locators:** `test_has_the_account_title` (the page title changed). Untouched: it may be a real bug.
65
+
66
+ It picked "View orders" over the look-alike "View returns", fixed it where it lives (the Page Object, not the test), and refused to guess where a person should decide.
67
+
68
+ ## Tools
69
+
70
+ | Tool | What it does |
71
+ |---|---|
72
+ | `explore_page` | Opens a URL and lists elements with Selenium locators verified to match exactly one element. |
73
+ | `check_locator` | Counts what a locator matches on a page (1 = good) and whether it's visible. |
74
+ | `parse_user_story` | User story → scenarios + a pytest/Selenium skeleton (and a `conftest.py` if you need one). |
75
+ | `write_test` | Writes `test_*.py`, `*_test.py` or `conftest.py` into `tests/`. Refuses other paths; won't overwrite unless asked. |
76
+ | `run_tests` | Runs pytest and returns per-test results with the failing locator, its line and the page. |
77
+ | `capture_baseline` | While tests pass, fingerprints the element each locator matches (`.qa-mcp/baselines.json`). |
78
+ | `heal_locator` | Finds the intended element after a UI change and suggests a verified replacement. |
79
+ | `save_login_state` | Logs in once with a few steps and saves cookies + localStorage for the other tools. |
80
+
81
+ Locators are written the way your code writes them: `(By.ID, "email")`, `driver.find_element(By.XPATH, "//button[...]")`, or `(By.CSS_SELECTOR, "[data-testid='save']")`.
82
+
83
+ Prompt: **`story_to_selenium`**, a guided workflow from story to passing test.
84
+
85
+ ## How self-healing works
86
+
87
+ 1. **Baseline.** While tests pass, `capture_baseline` records each element's fingerprint: role, accessible name, test id, id, `name`, type, label, placeholder, classes, text and href.
88
+ 2. **Detect.** `run_tests` loads a small pytest plugin that notes the last locator Selenium could not find, including inside `WebDriverWait` and expected conditions, and the page the browser was on.
89
+ 3. **Match.** `heal_locator` scores every visible element on the current page against the fingerprint, with exact matching for stable attributes and fuzzy, synonym-aware matching for text and ids ("Sign in" ≈ "Log in", `btn` ≈ `button`).
90
+ 4. **Verify.** The best match gets the most stable Selenium locator that matches exactly one element on the live page.
91
+ 5. **Decide.** `healed` only when confidence is at least 0.55 and clearly ahead of the runner-up; otherwise `needs_review` with candidates, or `not_found`. Without a baseline it falls back to what the locator implies, and says so.
92
+
93
+ Commit `.qa-mcp/baselines.json`. Do **not** commit `.qa-mcp/auth/` (saved logins); `save_login_state` adds it to `.gitignore`.
94
+
95
+ ## Pages behind a login and multi-step flows
96
+
97
+ Every page tool accepts a `session`:
98
+
99
+ ```json
100
+ {
101
+ "url": "https://shop.example.com/account",
102
+ "session": {
103
+ "storage_state": ".qa-mcp/auth/default.json",
104
+ "steps": [
105
+ { "action": "click", "locator": "(By.LINK_TEXT, \"My orders\")" },
106
+ { "action": "wait_for", "locator": "(By.CSS_SELECTOR, \"[data-testid='order-history']\")" }
107
+ ]
108
+ }
109
+ }
110
+ ```
111
+
112
+ - **`storage_state`**: cookies and localStorage saved by `save_login_state`, in the same JSON format Playwright uses. It must be inside the project.
113
+ - **`steps`**: `goto`, `click`, `fill`, `press`, `select`, `check`, `uncheck`, `wait_for`, up to 25, with explicit waits.
114
+ - **Secrets stay out of prompts and logs.** Write `"value": "${ENV:APP_PASSWORD}"`. Results show the placeholder; literal fill values are masked as `•••`.
115
+
116
+ ## Healing in CI, with an audit trail
117
+
118
+ ```bash
119
+ k11-selenium-qa-mcp heal-ci --apply
120
+ ```
121
+
122
+ 1. Runs your pytest suite with the locator-recording plugin.
123
+ 2. For each locator failure, opens the page the test was on (logging in first if `.qa-mcp/heal.config.json` says how) and heals against the committed baseline.
124
+ 3. Applies a change only when confidence is at least **0.7** (`--min-confidence`) and clearly ahead of the runner-up. It edits every place the locator literal appears: tests, Page Objects, helpers.
125
+ 4. **Re-runs the affected tests and reverts any change whose test still fails**, keeping the others.
126
+ 5. Leaves failures that aren't about locators alone.
127
+ 6. Writes `heal-report.md` (the pull-request body) and `heal-report.json` (full evidence), plus GitHub Actions outputs.
128
+
129
+ Without `--apply` it's a dry run. Copy [`docs/heal-locators.yml`](docs/heal-locators.yml) into `.github/workflows/` to run it nightly and open the pull request, and [`docs/heal.config.example.json`](docs/heal.config.example.json) to `.qa-mcp/heal.config.json`.
130
+
131
+ ## Install
132
+
133
+ Requires Python 3.10+ and Chrome (or Edge/Firefox with `QA_MCP_BROWSER=edge|firefox`). Selenium Manager downloads the matching driver automatically.
134
+
135
+ ### Claude Code: plugin (recommended)
136
+
137
+ Adds the MCP tools plus `/story-to-test` and `/heal-tests` (needs [uv](https://docs.astral.sh/uv/)):
138
+
139
+ ```text
140
+ /plugin marketplace add K11-Software-Solutions/k11techlab-selenium-qa-mcp
141
+ /plugin install selenium-qa@k11-selenium-qa
142
+ ```
143
+
144
+ ### Claude Code: MCP server only
145
+
146
+ From your test project's folder:
147
+
148
+ ```bash
149
+ claude mcp add selenium-qa -- uvx --from git+https://github.com/K11-Software-Solutions/k11techlab-selenium-qa-mcp k11-selenium-qa-mcp
150
+ ```
151
+
152
+ Or with pip: `pip install git+https://github.com/K11-Software-Solutions/k11techlab-selenium-qa-mcp`, then `claude mcp add selenium-qa -- k11-selenium-qa-mcp`.
153
+
154
+ ### Claude Desktop and other MCP clients
155
+
156
+ ```json
157
+ {
158
+ "mcpServers": {
159
+ "selenium-qa": {
160
+ "command": "uvx",
161
+ "args": ["--from", "git+https://github.com/K11-Software-Solutions/k11techlab-selenium-qa-mcp", "k11-selenium-qa-mcp"],
162
+ "env": { "QA_MCP_ROOT": "/path/to/your/tests/project" }
163
+ }
164
+ }
165
+ }
166
+ ```
167
+
168
+ The server works on the project in `QA_MCP_ROOT`, or the current directory. Tests run with the project's own interpreter: `QA_MCP_PYTHON`, else `.venv`/`venv` in the project, else the server's Python. That environment needs `pytest` and `selenium`.
169
+
170
+ ## Try the demo
171
+
172
+ ```bash
173
+ python demo/server.py --version v1 # http://127.0.0.1:8765/login.html (alex@example.com / correct-horse)
174
+ python demo/server.py --version v2 # the redesign
175
+ ```
176
+
177
+ ## Safety
178
+
179
+ - **Locators are parsed, never `eval`'d.** Only `By.<STRATEGY>, "<string>"` forms (and the WebDriver strategy names) are accepted.
180
+ - **Writes are confined to `tests/`** (`write_test`) or to the exact locator literal (`heal-ci`), which is found with Python's tokenizer, so comments and other code are never touched. Line endings are preserved.
181
+ - **Tests run without a shell,** with the project's interpreter.
182
+ - **Healing only suggests** in the MCP tools. `heal-ci --apply` changes code only for confident, verified fixes, and every change is in a pull request for review.
183
+
184
+ ## How it compares
185
+
186
+ | | Runtime self-healing tools | k11 Selenium QA |
187
+ |---|---|---|
188
+ | Where the fix goes | Patched in memory during the run | Your source (tests and Page Objects), in a pull request |
189
+ | Evidence | Logs | Confidence, reasons and runner-up in a report |
190
+ | If the fix is wrong | Test passes against the wrong element | Re-run must pass, or the change is reverted |
191
+ | Lock-in | Proxy, agent or SDK in your test run | A pytest plugin used only while healing; nothing in your code depends on it |
192
+
193
+ ## Development
194
+
195
+ ```bash
196
+ python -m venv .venv && .venv/Scripts/pip install -e ".[dev]" # bin/pip on macOS/Linux
197
+ pytest # unit, real-browser, MCP protocol and heal-ci tests (27)
198
+ ```
199
+
200
+ ## Roadmap
201
+
202
+ - Java (JUnit 5 / TestNG) and C# (NUnit) test projects
203
+ - Healing for chained `find_element` calls and `find_elements(...)[n]`
204
+ - Visual (screenshot) fingerprints as an extra signal
205
+ - PyPI release (`uvx k11-selenium-qa-mcp`)
206
+
207
+ ---
208
+
209
+ Built by [K11 Software Solutions](https://k11softwaresolutions.com). Part of the K11 Tech Lab agentic QA series, alongside [k11 Playwright QA](https://github.com/K11-Software-Solutions/k11techlab-playwright-qa-mcp). MIT licensed.
@@ -0,0 +1,186 @@
1
+ # k11 Selenium QA — MCP server & Claude Code plugin
2
+
3
+ <!-- mcp-name: io.github.K11-Software-Solutions/selenium-qa -->
4
+
5
+ **Self-healing Selenium tests with an audit trail. No vendor lock-in, no rewrite to Playwright.**
6
+
7
+ Most Selenium suites break the same way: someone renames an id, and dozens of `find_element(By.ID, ...)` calls fail overnight. AI assistants can write Selenium tests, but they guess locators, and healing tools usually patch things at runtime without telling you what changed. This MCP server gives the assistant facts instead of guesses, and gives your team a reviewable record of every fix:
8
+
9
+ - **Verified locators.** It opens the real page in Selenium and returns locators that match exactly one element, most stable first: `data-testid`, a stable `id`, `name`, link text, `aria-label`, then text-based XPath.
10
+ - **Story → pytest.** It turns "As a… I want… so that…", Gherkin scenarios or acceptance-criteria bullets into a pytest + Selenium skeleton: one test per scenario, explicit waits, and a `driver` fixture if you don't have one.
11
+ - **Structured test runs.** It runs your pytest suite and reports, for each failure, the exact locator Selenium could not find, the line that asked for it, and the page the browser was on.
12
+ - **Self-healing with evidence.** When the UI changes, it finds the element the test meant and suggests a verified replacement, with a confidence score, the reasons and the runner-up.
13
+ - **Healing in CI.** `heal-ci` applies only confident fixes, edits your tests *and Page Objects*, re-runs them, reverts anything that still fails, and opens a pull request whose description is the audit report.
14
+
15
+ Works with **Claude Code** (as a plugin with `/story-to-test` and `/heal-tests`), **Claude Desktop**, and any MCP client. Python 3.10+, Selenium 4.
16
+
17
+ ---
18
+
19
+ ## See it heal in CI
20
+
21
+ The demo app's account page was redesigned: ids renamed, a "View returns" button added next to "View orders", "Edit profile" renamed to "Account settings", the cart message reworded and the page title changed. The Selenium suite uses a Page Object:
22
+
23
+ ```python
24
+ class AccountPage:
25
+ VIEW_ORDERS = (By.ID, "view-orders")
26
+ OPEN_CART = (By.ID, "cart-btn")
27
+ ```
28
+
29
+ `k11-selenium-qa-mcp heal-ci --apply` produced this pull-request description (trimmed):
30
+
31
+ > **1 healed and verified**, 0 suggested, 1 reverted, 1 needs review, 0 not found, 1 failure not caused by locators.
32
+ >
33
+ > | Test | Where | Old locator | New locator | Confidence | Runner-up | Re-run |
34
+ > |---|---|---|---|---|---|---|
35
+ > | test_shows_my_orders | tests/pages/account_page.py:7 | `(By.ID, "view-orders")` | `(By.ID, "orders-toggle")` | 82% | `(By.ID, "returns-toggle")` (59%) | passed |
36
+ >
37
+ > Why: name matches, role matches, text matches, type matches, tag matches; classes similar (43%).
38
+ >
39
+ > **Needs a person:** "Edit profile" → best candidate "Account settings" at 45%, not confident enough to change. `(By.ID, "cart-btn")` healed at 85%, but the test still failed because the cart message changed too, so the change was **reverted**.
40
+ >
41
+ > **Not caused by locators:** `test_has_the_account_title` (the page title changed). Untouched: it may be a real bug.
42
+
43
+ It picked "View orders" over the look-alike "View returns", fixed it where it lives (the Page Object, not the test), and refused to guess where a person should decide.
44
+
45
+ ## Tools
46
+
47
+ | Tool | What it does |
48
+ |---|---|
49
+ | `explore_page` | Opens a URL and lists elements with Selenium locators verified to match exactly one element. |
50
+ | `check_locator` | Counts what a locator matches on a page (1 = good) and whether it's visible. |
51
+ | `parse_user_story` | User story → scenarios + a pytest/Selenium skeleton (and a `conftest.py` if you need one). |
52
+ | `write_test` | Writes `test_*.py`, `*_test.py` or `conftest.py` into `tests/`. Refuses other paths; won't overwrite unless asked. |
53
+ | `run_tests` | Runs pytest and returns per-test results with the failing locator, its line and the page. |
54
+ | `capture_baseline` | While tests pass, fingerprints the element each locator matches (`.qa-mcp/baselines.json`). |
55
+ | `heal_locator` | Finds the intended element after a UI change and suggests a verified replacement. |
56
+ | `save_login_state` | Logs in once with a few steps and saves cookies + localStorage for the other tools. |
57
+
58
+ Locators are written the way your code writes them: `(By.ID, "email")`, `driver.find_element(By.XPATH, "//button[...]")`, or `(By.CSS_SELECTOR, "[data-testid='save']")`.
59
+
60
+ Prompt: **`story_to_selenium`**, a guided workflow from story to passing test.
61
+
62
+ ## How self-healing works
63
+
64
+ 1. **Baseline.** While tests pass, `capture_baseline` records each element's fingerprint: role, accessible name, test id, id, `name`, type, label, placeholder, classes, text and href.
65
+ 2. **Detect.** `run_tests` loads a small pytest plugin that notes the last locator Selenium could not find, including inside `WebDriverWait` and expected conditions, and the page the browser was on.
66
+ 3. **Match.** `heal_locator` scores every visible element on the current page against the fingerprint, with exact matching for stable attributes and fuzzy, synonym-aware matching for text and ids ("Sign in" ≈ "Log in", `btn` ≈ `button`).
67
+ 4. **Verify.** The best match gets the most stable Selenium locator that matches exactly one element on the live page.
68
+ 5. **Decide.** `healed` only when confidence is at least 0.55 and clearly ahead of the runner-up; otherwise `needs_review` with candidates, or `not_found`. Without a baseline it falls back to what the locator implies, and says so.
69
+
70
+ Commit `.qa-mcp/baselines.json`. Do **not** commit `.qa-mcp/auth/` (saved logins); `save_login_state` adds it to `.gitignore`.
71
+
72
+ ## Pages behind a login and multi-step flows
73
+
74
+ Every page tool accepts a `session`:
75
+
76
+ ```json
77
+ {
78
+ "url": "https://shop.example.com/account",
79
+ "session": {
80
+ "storage_state": ".qa-mcp/auth/default.json",
81
+ "steps": [
82
+ { "action": "click", "locator": "(By.LINK_TEXT, \"My orders\")" },
83
+ { "action": "wait_for", "locator": "(By.CSS_SELECTOR, \"[data-testid='order-history']\")" }
84
+ ]
85
+ }
86
+ }
87
+ ```
88
+
89
+ - **`storage_state`**: cookies and localStorage saved by `save_login_state`, in the same JSON format Playwright uses. It must be inside the project.
90
+ - **`steps`**: `goto`, `click`, `fill`, `press`, `select`, `check`, `uncheck`, `wait_for`, up to 25, with explicit waits.
91
+ - **Secrets stay out of prompts and logs.** Write `"value": "${ENV:APP_PASSWORD}"`. Results show the placeholder; literal fill values are masked as `•••`.
92
+
93
+ ## Healing in CI, with an audit trail
94
+
95
+ ```bash
96
+ k11-selenium-qa-mcp heal-ci --apply
97
+ ```
98
+
99
+ 1. Runs your pytest suite with the locator-recording plugin.
100
+ 2. For each locator failure, opens the page the test was on (logging in first if `.qa-mcp/heal.config.json` says how) and heals against the committed baseline.
101
+ 3. Applies a change only when confidence is at least **0.7** (`--min-confidence`) and clearly ahead of the runner-up. It edits every place the locator literal appears: tests, Page Objects, helpers.
102
+ 4. **Re-runs the affected tests and reverts any change whose test still fails**, keeping the others.
103
+ 5. Leaves failures that aren't about locators alone.
104
+ 6. Writes `heal-report.md` (the pull-request body) and `heal-report.json` (full evidence), plus GitHub Actions outputs.
105
+
106
+ Without `--apply` it's a dry run. Copy [`docs/heal-locators.yml`](docs/heal-locators.yml) into `.github/workflows/` to run it nightly and open the pull request, and [`docs/heal.config.example.json`](docs/heal.config.example.json) to `.qa-mcp/heal.config.json`.
107
+
108
+ ## Install
109
+
110
+ Requires Python 3.10+ and Chrome (or Edge/Firefox with `QA_MCP_BROWSER=edge|firefox`). Selenium Manager downloads the matching driver automatically.
111
+
112
+ ### Claude Code: plugin (recommended)
113
+
114
+ Adds the MCP tools plus `/story-to-test` and `/heal-tests` (needs [uv](https://docs.astral.sh/uv/)):
115
+
116
+ ```text
117
+ /plugin marketplace add K11-Software-Solutions/k11techlab-selenium-qa-mcp
118
+ /plugin install selenium-qa@k11-selenium-qa
119
+ ```
120
+
121
+ ### Claude Code: MCP server only
122
+
123
+ From your test project's folder:
124
+
125
+ ```bash
126
+ claude mcp add selenium-qa -- uvx --from git+https://github.com/K11-Software-Solutions/k11techlab-selenium-qa-mcp k11-selenium-qa-mcp
127
+ ```
128
+
129
+ Or with pip: `pip install git+https://github.com/K11-Software-Solutions/k11techlab-selenium-qa-mcp`, then `claude mcp add selenium-qa -- k11-selenium-qa-mcp`.
130
+
131
+ ### Claude Desktop and other MCP clients
132
+
133
+ ```json
134
+ {
135
+ "mcpServers": {
136
+ "selenium-qa": {
137
+ "command": "uvx",
138
+ "args": ["--from", "git+https://github.com/K11-Software-Solutions/k11techlab-selenium-qa-mcp", "k11-selenium-qa-mcp"],
139
+ "env": { "QA_MCP_ROOT": "/path/to/your/tests/project" }
140
+ }
141
+ }
142
+ }
143
+ ```
144
+
145
+ The server works on the project in `QA_MCP_ROOT`, or the current directory. Tests run with the project's own interpreter: `QA_MCP_PYTHON`, else `.venv`/`venv` in the project, else the server's Python. That environment needs `pytest` and `selenium`.
146
+
147
+ ## Try the demo
148
+
149
+ ```bash
150
+ python demo/server.py --version v1 # http://127.0.0.1:8765/login.html (alex@example.com / correct-horse)
151
+ python demo/server.py --version v2 # the redesign
152
+ ```
153
+
154
+ ## Safety
155
+
156
+ - **Locators are parsed, never `eval`'d.** Only `By.<STRATEGY>, "<string>"` forms (and the WebDriver strategy names) are accepted.
157
+ - **Writes are confined to `tests/`** (`write_test`) or to the exact locator literal (`heal-ci`), which is found with Python's tokenizer, so comments and other code are never touched. Line endings are preserved.
158
+ - **Tests run without a shell,** with the project's interpreter.
159
+ - **Healing only suggests** in the MCP tools. `heal-ci --apply` changes code only for confident, verified fixes, and every change is in a pull request for review.
160
+
161
+ ## How it compares
162
+
163
+ | | Runtime self-healing tools | k11 Selenium QA |
164
+ |---|---|---|
165
+ | Where the fix goes | Patched in memory during the run | Your source (tests and Page Objects), in a pull request |
166
+ | Evidence | Logs | Confidence, reasons and runner-up in a report |
167
+ | If the fix is wrong | Test passes against the wrong element | Re-run must pass, or the change is reverted |
168
+ | Lock-in | Proxy, agent or SDK in your test run | A pytest plugin used only while healing; nothing in your code depends on it |
169
+
170
+ ## Development
171
+
172
+ ```bash
173
+ python -m venv .venv && .venv/Scripts/pip install -e ".[dev]" # bin/pip on macOS/Linux
174
+ pytest # unit, real-browser, MCP protocol and heal-ci tests (27)
175
+ ```
176
+
177
+ ## Roadmap
178
+
179
+ - Java (JUnit 5 / TestNG) and C# (NUnit) test projects
180
+ - Healing for chained `find_element` calls and `find_elements(...)[n]`
181
+ - Visual (screenshot) fingerprints as an extra signal
182
+ - PyPI release (`uvx k11-selenium-qa-mcp`)
183
+
184
+ ---
185
+
186
+ Built by [K11 Software Solutions](https://k11softwaresolutions.com). Part of the K11 Tech Lab agentic QA series, alongside [k11 Playwright QA](https://github.com/K11-Software-Solutions/k11techlab-playwright-qa-mcp). MIT licensed.
@@ -0,0 +1,43 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "k11-selenium-qa-mcp"
7
+ version = "0.1.0"
8
+ description = "MCP server and Claude Code plugin for Selenium QA: verified locators, user story to pytest, and self-healing locators with an audit trail."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ authors = [{ name = "K11 Software Solutions" }]
13
+ requires-python = ">=3.10"
14
+ keywords = ["mcp", "selenium", "self-healing", "test-automation", "pytest", "claude", "qa"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Framework :: Pytest",
18
+ "Intended Audience :: Developers",
19
+ "Programming Language :: Python :: 3",
20
+ "Topic :: Software Development :: Testing",
21
+ ]
22
+ dependencies = ["mcp>=1.20,<2", "selenium>=4.27"]
23
+
24
+ [project.optional-dependencies]
25
+ dev = ["pytest>=8", "pytest-timeout>=2"]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/K11-Software-Solutions/k11techlab-selenium-qa-mcp"
29
+ Issues = "https://github.com/K11-Software-Solutions/k11techlab-selenium-qa-mcp/issues"
30
+
31
+ [project.scripts]
32
+ k11-selenium-qa-mcp = "k11_selenium_qa.cli:main"
33
+
34
+ [tool.hatch.build.targets.wheel]
35
+ packages = ["src/k11_selenium_qa"]
36
+
37
+ [tool.hatch.build.targets.sdist]
38
+ include = ["src", "README.md", "LICENSE"]
39
+
40
+ [tool.pytest.ini_options]
41
+ testpaths = ["tests"]
42
+ norecursedirs = ["fixtures", ".tmp*"]
43
+ timeout = 300
@@ -0,0 +1,5 @@
1
+ """k11 Selenium QA: MCP server for Selenium test generation and self-healing locators."""
2
+
3
+ from .version import VERSION
4
+
5
+ __all__ = ["VERSION"]
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1,115 @@
1
+ """pytest plugin loaded by k11 Selenium QA when it runs your tests (``-p k11_qa_pytest_plugin``).
2
+
3
+ It records, for each failing test, the last Selenium locator that could not be found,
4
+ the line in your code that asked for it, and the page the browser was on. Results are
5
+ written as JSON to the path in K11_QA_RESULTS. It changes nothing about how tests run.
6
+ Self-contained on purpose: it is put on PYTHONPATH for the project's own interpreter.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import os
13
+ import sys
14
+
15
+ import pytest
16
+
17
+ _results: list[dict] = []
18
+ _current: dict = {"file": None, "failed": None}
19
+
20
+
21
+ def _record_failure(driver, by, value):
22
+ line = None
23
+ f = sys._getframe(2)
24
+ test_file = _current.get("file")
25
+ first_user = None
26
+ while f is not None:
27
+ fn = os.path.normcase(os.path.abspath(f.f_code.co_filename))
28
+ if test_file and fn == test_file:
29
+ line = f.f_lineno
30
+ break
31
+ if first_user is None and "site-packages" not in fn and "selenium" not in fn and "k11_qa_pytest_plugin" not in fn:
32
+ first_user = (fn, f.f_lineno)
33
+ f = f.f_back
34
+ try:
35
+ url = driver.current_url
36
+ except Exception: # noqa: BLE001
37
+ url = None
38
+ _current["failed"] = {"by": by, "value": value, "line": line, "url": url,
39
+ "where": f"{first_user[0]}:{first_user[1]}" if first_user else None}
40
+
41
+
42
+ def _install() -> None:
43
+ try:
44
+ from selenium.common.exceptions import NoSuchElementException
45
+ from selenium.webdriver.remote.webdriver import WebDriver
46
+ from selenium.webdriver.remote.webelement import WebElement
47
+ except ImportError:
48
+ return
49
+ if getattr(WebDriver.find_element, "_k11", False):
50
+ return
51
+
52
+ def wrap(orig, is_driver):
53
+ def find_element(self, by="id", value=None):
54
+ try:
55
+ el = orig(self, by, value)
56
+ except NoSuchElementException:
57
+ _record_failure(self if is_driver else self.parent, by, value)
58
+ raise
59
+ failed = _current.get("failed")
60
+ if failed and failed["by"] == by and failed["value"] == value:
61
+ _current["failed"] = None # found after waiting: not the cause
62
+ return el
63
+
64
+ find_element._k11 = True
65
+ return find_element
66
+
67
+ WebDriver.find_element = wrap(WebDriver.find_element, True)
68
+ WebElement.find_element = wrap(WebElement.find_element, False)
69
+
70
+
71
+ _install()
72
+
73
+
74
+ def pytest_runtest_setup(item):
75
+ _current["file"] = os.path.normcase(os.path.abspath(str(item.fspath)))
76
+ _current["failed"] = None
77
+
78
+
79
+ @pytest.hookimpl(hookwrapper=True)
80
+ def pytest_runtest_makereport(item, call):
81
+ outcome = yield
82
+ rep = outcome.get_result()
83
+ if rep.when == "call" or (rep.failed and rep.when == "setup") or (rep.skipped and rep.when == "setup"):
84
+ entry = {
85
+ "nodeid": item.nodeid,
86
+ "name": item.name,
87
+ "file": os.path.abspath(str(item.fspath)),
88
+ "line": (item.location[1] or 0) + 1,
89
+ "outcome": rep.outcome if rep.when == "call" else ("error" if rep.failed else "skipped"),
90
+ "duration": round(rep.duration, 3),
91
+ }
92
+ if rep.failed and call.excinfo is not None:
93
+ exc = call.excinfo.value
94
+ msg = (getattr(exc, "msg", None) or str(exc) or "").strip()
95
+ entry["error"] = f"{call.excinfo.typename}: {msg.splitlines()[0] if msg else ''}".strip().rstrip(":")
96
+ entry["errorType"] = call.excinfo.typename
97
+ tb = call.excinfo.traceback
98
+ for e in reversed(tb):
99
+ if os.path.normcase(os.path.abspath(str(e.path))) == _current["file"]:
100
+ entry["errorLine"] = e.lineno + 1
101
+ break
102
+ failed = _current.get("failed")
103
+ if failed and call.excinfo.typename in ("NoSuchElementException", "TimeoutException"):
104
+ entry["failingLocator"] = {"by": failed["by"], "value": failed["value"]}
105
+ entry["page"] = failed["url"]
106
+ if failed.get("line"):
107
+ entry["locatorLine"] = failed["line"]
108
+ _results[:] = [r for r in _results if r["nodeid"] != item.nodeid] + [entry]
109
+
110
+
111
+ def pytest_sessionfinish(session, exitstatus):
112
+ path = os.environ.get("K11_QA_RESULTS")
113
+ if path:
114
+ with open(path, "w", encoding="utf8") as fh:
115
+ json.dump({"tests": _results, "exitstatus": int(exitstatus)}, fh, indent=2)