tamash-playwright 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.
Files changed (24) hide show
  1. tamash_playwright-0.1.0/.env.example +26 -0
  2. tamash_playwright-0.1.0/.gitignore +11 -0
  3. tamash_playwright-0.1.0/LICENSE +25 -0
  4. tamash_playwright-0.1.0/PKG-INFO +173 -0
  5. tamash_playwright-0.1.0/README.md +155 -0
  6. tamash_playwright-0.1.0/pyproject.toml +34 -0
  7. tamash_playwright-0.1.0/src/tamash_playwright/__init__.py +11 -0
  8. tamash_playwright-0.1.0/src/tamash_playwright/bindings.py +168 -0
  9. tamash_playwright-0.1.0/src/tamash_playwright/cli/__init__.py +0 -0
  10. tamash_playwright-0.1.0/src/tamash_playwright/cli/__main__.py +26 -0
  11. tamash_playwright-0.1.0/src/tamash_playwright/cli/doctor.py +109 -0
  12. tamash_playwright-0.1.0/src/tamash_playwright/cli/scan_describe.py +94 -0
  13. tamash_playwright-0.1.0/src/tamash_playwright/healer/__init__.py +8 -0
  14. tamash_playwright-0.1.0/src/tamash_playwright/healer/core.py +209 -0
  15. tamash_playwright-0.1.0/src/tamash_playwright/healer/providers/__init__.py +55 -0
  16. tamash_playwright-0.1.0/src/tamash_playwright/healer/providers/anthropic_provider.py +66 -0
  17. tamash_playwright-0.1.0/src/tamash_playwright/healer/providers/gemini_provider.py +69 -0
  18. tamash_playwright-0.1.0/src/tamash_playwright/healer/providers/ollama_provider.py +81 -0
  19. tamash_playwright-0.1.0/src/tamash_playwright/healer/providers/openai_provider.py +65 -0
  20. tamash_playwright-0.1.0/src/tamash_playwright/healer/providers/prompt.py +93 -0
  21. tamash_playwright-0.1.0/src/tamash_playwright/healer/providers/types.py +32 -0
  22. tamash_playwright-0.1.0/src/tamash_playwright/plugin.py +36 -0
  23. tamash_playwright-0.1.0/tests/conftest.py +5 -0
  24. tamash_playwright-0.1.0/tests/test_sample.py +19 -0
@@ -0,0 +1,26 @@
1
+ # Master on/off switch for self-healing. Unset or anything other than false/0 leaves it on.
2
+ HEALER_ENABLED=true
3
+
4
+ # Which AI provider backs self-healing: ollama | openai | anthropic | gemini
5
+ # Unset, unknown, or a provider whose key/model below is missing disables self-healing —
6
+ # a failed action just fails normally, since there's no AI model configured to recover it.
7
+ HEALER_PROVIDER=ollama
8
+
9
+ # --- Ollama Cloud (https://ollama.com) ---
10
+ OLLAMA_MODEL=gpt-oss:120b
11
+ OLLAMA_API_KEY=
12
+ # OLLAMA_BASE_URL=https://ollama.com
13
+
14
+ # --- OpenAI ---
15
+ # OPENAI_MODEL=gpt-4.1-mini
16
+ # OPENAI_API_KEY=
17
+ # OPENAI_BASE_URL=https://api.openai.com/v1
18
+
19
+ # --- Anthropic (requires: pip install tamash-playwright[anthropic]) ---
20
+ # ANTHROPIC_MODEL=claude-haiku-4-5
21
+ # ANTHROPIC_API_KEY=
22
+
23
+ # --- Google Gemini ---
24
+ # GEMINI_MODEL= # check https://ai.google.dev/gemini-api/docs/models for the current model id
25
+ # GEMINI_API_KEY=
26
+ # GEMINI_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .pytest_cache/
4
+ .venv/
5
+ venv/
6
+ *.egg-info/
7
+ dist/
8
+ build/
9
+ .env
10
+ test-results/
11
+ playwright-report/
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2026 QtpSudhakar / VibeTestQ
2
+
3
+ All rights reserved.
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 use
7
+ the Software for any purpose, including commercial use, subject to the
8
+ following conditions:
9
+
10
+ 1. The Software may not be copied, modified, merged, published, distributed,
11
+ sublicensed, or sold, in whole or in part, without prior written
12
+ permission from the copyright holder.
13
+
14
+ 2. The above copyright notice and this permission notice shall be included
15
+ in all copies of the Software that are permitted under these terms.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23
+ DEALINGS IN THE SOFTWARE.
24
+
25
+ For questions or concerns, contact us at support@vibetestq.com.
@@ -0,0 +1,173 @@
1
+ Metadata-Version: 2.5
2
+ Name: tamash-playwright
3
+ Version: 0.1.0
4
+ Summary: Self-healing bindings for Playwright + pytest: automatically recovers broken selectors using an AI model (Ollama, OpenAI, Anthropic, or Gemini).
5
+ Project-URL: Homepage, https://github.com/qtpsudhakarproducts/tamash-playwright-python
6
+ Project-URL: Repository, https://github.com/qtpsudhakarproducts/tamash-playwright-python
7
+ Project-URL: License, https://github.com/qtpsudhakarproducts/tamash-playwright-python/blob/main/LICENSE
8
+ Author: QtpSudhakar / VibeTestQ
9
+ License: SEE LICENSE IN LICENSE
10
+ License-File: LICENSE
11
+ Keywords: anthropic,gemini,llm,ollama,openai,playwright,pytest,self-healing,test-automation
12
+ Requires-Python: >=3.9
13
+ Requires-Dist: pytest-playwright>=0.4.0
14
+ Requires-Dist: python-dotenv>=1.0.0
15
+ Provides-Extra: anthropic
16
+ Requires-Dist: anthropic>=0.115.0; extra == 'anthropic'
17
+ Description-Content-Type: text/markdown
18
+
19
+ # tamash-playwright
20
+
21
+ `tamash-playwright` is a plug and play self-healing solution for Playwright + pytest. Install it, add your AI API key details, and wire in one fixture override.
22
+
23
+ That's it. No changes needed to your actual test functions if you're following standard Playwright/pytest best practices.
24
+
25
+ > Also available for TypeScript/`@playwright/test` as [`tamash-playwright` on npm](https://www.npmjs.com/package/tamash-playwright) — same name, same idea, separate package per ecosystem.
26
+
27
+ ### Why you need this
28
+
29
+ Websites change often. A button gets renamed or moved, and your test can't find it anymore — even though the app still works fine for real users. Normally, that just means a broken test.
30
+
31
+ `tamash-playwright` fixes this automatically. When a test action can't find an element, it asks an AI model to find it on the current page and tries again. If it succeeds, your test keeps going. If not, it fails normally, just like before.
32
+
33
+ Here are the detailed steps to use this package.
34
+
35
+ ## Step 1: Install it
36
+
37
+ ```sh
38
+ pip install tamash-playwright
39
+ ```
40
+
41
+ This pulls in `pytest-playwright` as a dependency, so if you're starting fresh you'll also need the Playwright browsers:
42
+
43
+ ```sh
44
+ playwright install
45
+ ```
46
+
47
+ Using Anthropic (Claude) as your provider needs one extra install:
48
+
49
+ ```sh
50
+ pip install "tamash-playwright[anthropic]"
51
+ ```
52
+
53
+ ## Step 2: Connect an AI model
54
+
55
+ `tamash-playwright` needs an AI model to decide where a broken element actually went. Pick one of Ollama, OpenAI, Anthropic (Claude), or Google Gemini, and give it an API key.
56
+
57
+ Create a file named `.env` in your project folder:
58
+
59
+ ```sh
60
+ # Master on/off switch. Leave this as true, or remove the line entirely.
61
+ HEALER_ENABLED=true
62
+
63
+ # Pick one: ollama | openai | anthropic | gemini
64
+ HEALER_PROVIDER=ollama
65
+
66
+ # --- Ollama Cloud (https://ollama.com) ---
67
+ OLLAMA_MODEL=gpt-oss:120b
68
+ OLLAMA_API_KEY=
69
+
70
+ # --- OpenAI ---
71
+ # OPENAI_MODEL=gpt-4.1-mini
72
+ # OPENAI_API_KEY=
73
+
74
+ # --- Anthropic (Claude) ---
75
+ # ANTHROPIC_MODEL=claude-haiku-4-5
76
+ # ANTHROPIC_API_KEY=
77
+
78
+ # --- Google Gemini ---
79
+ # GEMINI_MODEL=
80
+ # GEMINI_API_KEY=
81
+ ```
82
+
83
+ Just fill in the API key and model for whichever one you want to use, and leave the rest as-is (or delete them).
84
+
85
+ ### Getting a free Ollama key (fastest way to get started)
86
+
87
+ Ollama Cloud is a quick, free way to get an API key without signing up for OpenAI/Anthropic/Gemini billing.
88
+
89
+ 1. Go to [ollama.com](https://ollama.com/) and create an account.
90
+ 2. Once signed in, go to [ollama.com/settings/keys](https://ollama.com/settings/keys).
91
+ 3. Create a new API key and copy it.
92
+ 4. Paste it into your `.env` file:
93
+
94
+ ```sh
95
+ HEALER_ENABLED=true
96
+ HEALER_PROVIDER=ollama
97
+ OLLAMA_MODEL=gpt-oss:120b
98
+ OLLAMA_API_KEY=paste_your_key_here
99
+ ```
100
+
101
+ That's all you need — no other variables required.
102
+
103
+ ## Step 3: Wire it in
104
+
105
+ Unlike the JS/TS version of this package (where you swap one `import` line per test file), pytest's plugin model means the reliable way to activate self-healing is **one line in your project's `conftest.py`**, added once — not per test file:
106
+
107
+ ```python
108
+ # conftest.py
109
+ from tamash_playwright.plugin import page # noqa: F401
110
+ ```
111
+
112
+ Why this line, and not nothing at all: `tamash-playwright` registers itself as a pytest plugin automatically on install, and its `page` fixture *may* already override `pytest-playwright`'s own `page` fixture depending on plugin load order — but that order isn't something pytest guarantees across environments. A `conftest.py` fixture, on the other hand, is *always* preferred by pytest over a same-named fixture from an installed plugin, so re-exporting it there is the one setup step that's guaranteed to work everywhere, every time.
113
+
114
+ With that line in place, every test using the `page` fixture — no matter how many test files you have — automatically gets self-healing. Nothing else changes:
115
+
116
+ ```python
117
+ def test_login(page):
118
+ page.goto("/")
119
+ page.get_by_placeholder("Username").fill("Admin") # healed automatically if this breaks
120
+ page.get_by_role("button", name="Login").click()
121
+ from playwright.sync_api import expect
122
+ expect(page.get_by_role("heading", name="Dashboard")).to_be_visible()
123
+ ```
124
+
125
+ ## Step 4: Check your setup
126
+
127
+ Run the built-in doctor command to confirm everything's wired up correctly:
128
+
129
+ ```sh
130
+ tamash-playwright doctor
131
+ ```
132
+
133
+ It checks three things:
134
+
135
+ 1. **AI connectivity** — confirms `HEALER_ENABLED`/`HEALER_PROVIDER` are set correctly and actually calls your configured provider to make sure the API key and model work.
136
+ 2. **Missing `.describe()` labels** — scans your test files (`tests/` by default, or pass `--dir <path>`) for locators that don't have a `.describe('...')` label, flagging the ones most worth fixing (raw CSS/XPath selectors first).
137
+ 3. **Locators written directly in test files** — flags any locator defined inline in a test rather than inside a Page Object class, a Playwright best practice regardless of self-healing.
138
+
139
+ If it finds issues, the fastest fix is to open the project in an AI coding assistant (Claude Code, Cursor, GitHub Copilot, etc.) and ask it to address what it flagged. You can also add a standing rule to that assistant's instructions/skill file (e.g. `CLAUDE.md`, `.cursor/rules`, `.github/copilot-instructions.md`) so it follows both practices automatically on any new test code going forward.
140
+
141
+ ### A quick tip for better results
142
+
143
+ If you're using plain CSS selectors (like `page.locator('input[name="username"]')`) rather than Playwright's more descriptive locators (`get_by_role`, `get_by_placeholder`, etc.), it helps to add a short, human-readable label so the healer knows what it's actually looking for. Chain `.describe('...')` right onto the locator:
144
+
145
+ ```python
146
+ def test_login_using_css_selectors(page):
147
+ page.goto("https://example.com/auth/login")
148
+
149
+ txt_username = page.locator('input[name="username"]').describe("User Name Textbox")
150
+ txt_username.fill("testadmin")
151
+
152
+ txt_password = page.locator('input[placeholder="Password"]').describe("Password Textbox")
153
+ txt_password.fill("secret")
154
+
155
+ btn_login = page.locator('button[type="submit"]').describe("Login Button")
156
+ btn_login.click()
157
+ ```
158
+
159
+ This step is optional, but recommended — without it, the healer has to guess purely from a broken CSS selector, which gives it a lot less to work with.
160
+
161
+ ## What gets healed (and what doesn't)
162
+
163
+ Only real Playwright actions that can be safely retried are healed: `click`, `fill`, `check`, `hover`, `press`, `select_option`, `set_input_files`, `focus`, `blur`, `dblclick`, `tap`, `clear`, `uncheck`. `drag_to` and anything unlisted is intentionally left alone rather than guessed at.
164
+
165
+ `expect(...)` assertions (`to_have_text`, `to_be_visible`, etc.) are **not** healed — they use Playwright's own built-in auto-retrying assertions, which are a separate mechanism this package doesn't touch. If a locator only ever appears inside an `expect(...)` call and never in an action, `.describe()` on it is a readability nicety, not something that affects healing.
166
+
167
+ ## License
168
+
169
+ Free to use, including commercially. The source code may not be copied, modified, redistributed, or resold without prior written permission. See the LICENSE file included in this package for the full terms.
170
+
171
+ ## Support
172
+
173
+ For questions or concerns, contact us at support@vibetestq.com.
@@ -0,0 +1,155 @@
1
+ # tamash-playwright
2
+
3
+ `tamash-playwright` is a plug and play self-healing solution for Playwright + pytest. Install it, add your AI API key details, and wire in one fixture override.
4
+
5
+ That's it. No changes needed to your actual test functions if you're following standard Playwright/pytest best practices.
6
+
7
+ > Also available for TypeScript/`@playwright/test` as [`tamash-playwright` on npm](https://www.npmjs.com/package/tamash-playwright) — same name, same idea, separate package per ecosystem.
8
+
9
+ ### Why you need this
10
+
11
+ Websites change often. A button gets renamed or moved, and your test can't find it anymore — even though the app still works fine for real users. Normally, that just means a broken test.
12
+
13
+ `tamash-playwright` fixes this automatically. When a test action can't find an element, it asks an AI model to find it on the current page and tries again. If it succeeds, your test keeps going. If not, it fails normally, just like before.
14
+
15
+ Here are the detailed steps to use this package.
16
+
17
+ ## Step 1: Install it
18
+
19
+ ```sh
20
+ pip install tamash-playwright
21
+ ```
22
+
23
+ This pulls in `pytest-playwright` as a dependency, so if you're starting fresh you'll also need the Playwright browsers:
24
+
25
+ ```sh
26
+ playwright install
27
+ ```
28
+
29
+ Using Anthropic (Claude) as your provider needs one extra install:
30
+
31
+ ```sh
32
+ pip install "tamash-playwright[anthropic]"
33
+ ```
34
+
35
+ ## Step 2: Connect an AI model
36
+
37
+ `tamash-playwright` needs an AI model to decide where a broken element actually went. Pick one of Ollama, OpenAI, Anthropic (Claude), or Google Gemini, and give it an API key.
38
+
39
+ Create a file named `.env` in your project folder:
40
+
41
+ ```sh
42
+ # Master on/off switch. Leave this as true, or remove the line entirely.
43
+ HEALER_ENABLED=true
44
+
45
+ # Pick one: ollama | openai | anthropic | gemini
46
+ HEALER_PROVIDER=ollama
47
+
48
+ # --- Ollama Cloud (https://ollama.com) ---
49
+ OLLAMA_MODEL=gpt-oss:120b
50
+ OLLAMA_API_KEY=
51
+
52
+ # --- OpenAI ---
53
+ # OPENAI_MODEL=gpt-4.1-mini
54
+ # OPENAI_API_KEY=
55
+
56
+ # --- Anthropic (Claude) ---
57
+ # ANTHROPIC_MODEL=claude-haiku-4-5
58
+ # ANTHROPIC_API_KEY=
59
+
60
+ # --- Google Gemini ---
61
+ # GEMINI_MODEL=
62
+ # GEMINI_API_KEY=
63
+ ```
64
+
65
+ Just fill in the API key and model for whichever one you want to use, and leave the rest as-is (or delete them).
66
+
67
+ ### Getting a free Ollama key (fastest way to get started)
68
+
69
+ Ollama Cloud is a quick, free way to get an API key without signing up for OpenAI/Anthropic/Gemini billing.
70
+
71
+ 1. Go to [ollama.com](https://ollama.com/) and create an account.
72
+ 2. Once signed in, go to [ollama.com/settings/keys](https://ollama.com/settings/keys).
73
+ 3. Create a new API key and copy it.
74
+ 4. Paste it into your `.env` file:
75
+
76
+ ```sh
77
+ HEALER_ENABLED=true
78
+ HEALER_PROVIDER=ollama
79
+ OLLAMA_MODEL=gpt-oss:120b
80
+ OLLAMA_API_KEY=paste_your_key_here
81
+ ```
82
+
83
+ That's all you need — no other variables required.
84
+
85
+ ## Step 3: Wire it in
86
+
87
+ Unlike the JS/TS version of this package (where you swap one `import` line per test file), pytest's plugin model means the reliable way to activate self-healing is **one line in your project's `conftest.py`**, added once — not per test file:
88
+
89
+ ```python
90
+ # conftest.py
91
+ from tamash_playwright.plugin import page # noqa: F401
92
+ ```
93
+
94
+ Why this line, and not nothing at all: `tamash-playwright` registers itself as a pytest plugin automatically on install, and its `page` fixture *may* already override `pytest-playwright`'s own `page` fixture depending on plugin load order — but that order isn't something pytest guarantees across environments. A `conftest.py` fixture, on the other hand, is *always* preferred by pytest over a same-named fixture from an installed plugin, so re-exporting it there is the one setup step that's guaranteed to work everywhere, every time.
95
+
96
+ With that line in place, every test using the `page` fixture — no matter how many test files you have — automatically gets self-healing. Nothing else changes:
97
+
98
+ ```python
99
+ def test_login(page):
100
+ page.goto("/")
101
+ page.get_by_placeholder("Username").fill("Admin") # healed automatically if this breaks
102
+ page.get_by_role("button", name="Login").click()
103
+ from playwright.sync_api import expect
104
+ expect(page.get_by_role("heading", name="Dashboard")).to_be_visible()
105
+ ```
106
+
107
+ ## Step 4: Check your setup
108
+
109
+ Run the built-in doctor command to confirm everything's wired up correctly:
110
+
111
+ ```sh
112
+ tamash-playwright doctor
113
+ ```
114
+
115
+ It checks three things:
116
+
117
+ 1. **AI connectivity** — confirms `HEALER_ENABLED`/`HEALER_PROVIDER` are set correctly and actually calls your configured provider to make sure the API key and model work.
118
+ 2. **Missing `.describe()` labels** — scans your test files (`tests/` by default, or pass `--dir <path>`) for locators that don't have a `.describe('...')` label, flagging the ones most worth fixing (raw CSS/XPath selectors first).
119
+ 3. **Locators written directly in test files** — flags any locator defined inline in a test rather than inside a Page Object class, a Playwright best practice regardless of self-healing.
120
+
121
+ If it finds issues, the fastest fix is to open the project in an AI coding assistant (Claude Code, Cursor, GitHub Copilot, etc.) and ask it to address what it flagged. You can also add a standing rule to that assistant's instructions/skill file (e.g. `CLAUDE.md`, `.cursor/rules`, `.github/copilot-instructions.md`) so it follows both practices automatically on any new test code going forward.
122
+
123
+ ### A quick tip for better results
124
+
125
+ If you're using plain CSS selectors (like `page.locator('input[name="username"]')`) rather than Playwright's more descriptive locators (`get_by_role`, `get_by_placeholder`, etc.), it helps to add a short, human-readable label so the healer knows what it's actually looking for. Chain `.describe('...')` right onto the locator:
126
+
127
+ ```python
128
+ def test_login_using_css_selectors(page):
129
+ page.goto("https://example.com/auth/login")
130
+
131
+ txt_username = page.locator('input[name="username"]').describe("User Name Textbox")
132
+ txt_username.fill("testadmin")
133
+
134
+ txt_password = page.locator('input[placeholder="Password"]').describe("Password Textbox")
135
+ txt_password.fill("secret")
136
+
137
+ btn_login = page.locator('button[type="submit"]').describe("Login Button")
138
+ btn_login.click()
139
+ ```
140
+
141
+ This step is optional, but recommended — without it, the healer has to guess purely from a broken CSS selector, which gives it a lot less to work with.
142
+
143
+ ## What gets healed (and what doesn't)
144
+
145
+ Only real Playwright actions that can be safely retried are healed: `click`, `fill`, `check`, `hover`, `press`, `select_option`, `set_input_files`, `focus`, `blur`, `dblclick`, `tap`, `clear`, `uncheck`. `drag_to` and anything unlisted is intentionally left alone rather than guessed at.
146
+
147
+ `expect(...)` assertions (`to_have_text`, `to_be_visible`, etc.) are **not** healed — they use Playwright's own built-in auto-retrying assertions, which are a separate mechanism this package doesn't touch. If a locator only ever appears inside an `expect(...)` call and never in an action, `.describe()` on it is a readability nicety, not something that affects healing.
148
+
149
+ ## License
150
+
151
+ Free to use, including commercially. The source code may not be copied, modified, redistributed, or resold without prior written permission. See the LICENSE file included in this package for the full terms.
152
+
153
+ ## Support
154
+
155
+ For questions or concerns, contact us at support@vibetestq.com.
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "tamash-playwright"
7
+ version = "0.1.0"
8
+ description = "Self-healing bindings for Playwright + pytest: automatically recovers broken selectors using an AI model (Ollama, OpenAI, Anthropic, or Gemini)."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "SEE LICENSE IN LICENSE" }
12
+ authors = [{ name = "QtpSudhakar / VibeTestQ" }]
13
+ keywords = ["playwright", "pytest", "self-healing", "test-automation", "llm", "ollama", "openai", "anthropic", "gemini"]
14
+ dependencies = [
15
+ "pytest-playwright>=0.4.0",
16
+ "python-dotenv>=1.0.0",
17
+ ]
18
+
19
+ [project.optional-dependencies]
20
+ anthropic = ["anthropic>=0.115.0"]
21
+
22
+ [project.urls]
23
+ Homepage = "https://github.com/qtpsudhakarproducts/tamash-playwright-python"
24
+ Repository = "https://github.com/qtpsudhakarproducts/tamash-playwright-python"
25
+ License = "https://github.com/qtpsudhakarproducts/tamash-playwright-python/blob/main/LICENSE"
26
+
27
+ [project.entry-points.pytest11]
28
+ tamash_playwright = "tamash_playwright.plugin"
29
+
30
+ [project.scripts]
31
+ tamash-playwright = "tamash_playwright.cli.__main__:main"
32
+
33
+ [tool.hatch.build.targets.wheel]
34
+ packages = ["src/tamash_playwright"]
@@ -0,0 +1,11 @@
1
+ from .bindings import bind_frame, bind_locator, bind_page
2
+ from .healer.core import SelfHealingReport, get_healing_reports, heal_action_failure
3
+
4
+ __all__ = [
5
+ "bind_page",
6
+ "bind_frame",
7
+ "bind_locator",
8
+ "heal_action_failure",
9
+ "get_healing_reports",
10
+ "SelfHealingReport",
11
+ ]
@@ -0,0 +1,168 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Optional
4
+
5
+ from playwright.sync_api import Locator
6
+
7
+ from .healer.core import heal_action_failure
8
+
9
+ # `drag_to` is included so a failure is at least reported (see healer/core.py's
10
+ # _REPLAYABLE_ACTIONS for why it can never actually be healed).
11
+ _ACTIONS = {
12
+ "click",
13
+ "dblclick",
14
+ "tap",
15
+ "hover",
16
+ "fill",
17
+ "clear",
18
+ "press",
19
+ "check",
20
+ "uncheck",
21
+ "select_option",
22
+ "set_input_files",
23
+ "drag_to",
24
+ "focus",
25
+ "blur",
26
+ }
27
+
28
+ _LOCATOR_FACTORY_METHODS = {
29
+ "locator",
30
+ "get_by_role",
31
+ "get_by_label",
32
+ "get_by_placeholder",
33
+ "get_by_text",
34
+ "get_by_alt_text",
35
+ "get_by_title",
36
+ "get_by_test_id",
37
+ }
38
+
39
+ # Playwright's Locator.and_()/or_() use a trailing underscore since `and`/`or` are reserved
40
+ # keywords in Python.
41
+ _CHAIN_METHODS = {"describe", "first", "last", "nth", "filter", "and_", "or_"}
42
+
43
+ _DESCRIPTION_ATTR = "_tamash_description"
44
+
45
+
46
+ def _describe_factory_call(method: str, args: tuple, kwargs: dict) -> Optional[str]:
47
+ first = args[0] if args else None
48
+
49
+ if method == "get_by_role":
50
+ name = kwargs.get("name") or (args[1] if len(args) > 1 else None)
51
+ return f'role:{first} "{name}"' if name else f"role:{first}"
52
+ if method == "get_by_label":
53
+ return f'label "{first}"'
54
+ if method == "get_by_placeholder":
55
+ return f'placeholder "{first}"'
56
+ if method == "get_by_text":
57
+ return f'text "{first}"'
58
+ if method == "get_by_alt_text":
59
+ return f'alt text "{first}"'
60
+ if method == "get_by_title":
61
+ return f'title "{first}"'
62
+ if method == "get_by_test_id":
63
+ return f'testid "{first}"'
64
+ if method == "locator":
65
+ return f'selector "{first}"'
66
+ return None
67
+
68
+
69
+ # Python has no equivalent of JS's Proxy, and — unlike a __getattr__-based wrapper class — that
70
+ # actually matters here: Playwright's own `expect()` does `isinstance(actual, Locator)` /
71
+ # `isinstance(actual, Page)` internally and reaches into `actual._impl_obj` directly. A wrapper
72
+ # class fails those checks outright (`expect()` raises "Unsupported type"), since it was never
73
+ # really a Locator/Page to begin with. So instead of wrapping, this monkey-patches the specific
74
+ # action/factory/chain methods directly onto the *real* Page/Frame/Locator instance in place —
75
+ # it's still a genuine Locator afterwards, isinstance and expect() both keep working, and only
76
+ # the patched method names' behavior changes.
77
+
78
+
79
+ def _patch_actions(obj: Any, kind: str) -> None:
80
+ for name in _ACTIONS:
81
+ original = getattr(obj, name, None)
82
+ if not callable(original):
83
+ continue
84
+
85
+ def make_wrapper(action_name: str, real_method: Any) -> Any:
86
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
87
+ try:
88
+ return real_method(*args, **kwargs)
89
+ except Exception as error: # noqa: BLE001 - re-raised below if not recovered
90
+ description = getattr(obj, _DESCRIPTION_ATTR, None)
91
+ _report, recovered, result = heal_action_failure(
92
+ action=action_name,
93
+ kind=kind,
94
+ description=description or f"{kind} action {action_name}",
95
+ error=error,
96
+ target=obj,
97
+ args=args,
98
+ kwargs=kwargs,
99
+ )
100
+ if recovered:
101
+ return result
102
+ raise
103
+
104
+ return wrapper
105
+
106
+ setattr(obj, name, make_wrapper(name, original))
107
+
108
+
109
+ def _patch_locator_factories(obj: Any, kind: str) -> None:
110
+ for name in _LOCATOR_FACTORY_METHODS:
111
+ original = getattr(obj, name, None)
112
+ if not callable(original):
113
+ continue
114
+
115
+ def make_wrapper(method_name: str, real_factory: Any) -> Any:
116
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
117
+ child = real_factory(*args, **kwargs)
118
+ if isinstance(child, Locator):
119
+ _patch_locator(child, _describe_factory_call(method_name, args, kwargs))
120
+ return child
121
+
122
+ return wrapper
123
+
124
+ setattr(obj, name, make_wrapper(name, original))
125
+
126
+
127
+ def _patch_chain_methods(locator: Any, description: Optional[str]) -> None:
128
+ for name in _CHAIN_METHODS:
129
+ original = getattr(locator, name, None)
130
+ if not callable(original):
131
+ continue
132
+
133
+ def make_wrapper(method_name: str, real_method: Any) -> Any:
134
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
135
+ result = real_method(*args, **kwargs)
136
+ next_description = str(args[0]) if method_name == "describe" and args else description
137
+ if isinstance(result, Locator):
138
+ _patch_locator(result, next_description)
139
+ return result
140
+
141
+ return wrapper
142
+
143
+ setattr(locator, name, make_wrapper(name, original))
144
+
145
+
146
+ def _patch_locator(locator: Any, description: Optional[str]) -> None:
147
+ if description is not None:
148
+ setattr(locator, _DESCRIPTION_ATTR, description)
149
+ _patch_actions(locator, "locator")
150
+ _patch_locator_factories(locator, "locator")
151
+ _patch_chain_methods(locator, description)
152
+
153
+
154
+ def bind_page(page: Any) -> Any:
155
+ _patch_actions(page, "page")
156
+ _patch_locator_factories(page, "page")
157
+ return page
158
+
159
+
160
+ def bind_frame(frame: Any) -> Any:
161
+ _patch_actions(frame, "frame")
162
+ _patch_locator_factories(frame, "frame")
163
+ return frame
164
+
165
+
166
+ def bind_locator(locator: Any) -> Any:
167
+ _patch_locator(locator, None)
168
+ return locator
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import sys
5
+
6
+ from .doctor import run_doctor
7
+
8
+
9
+ def main() -> None:
10
+ parser = argparse.ArgumentParser(prog="tamash-playwright")
11
+ subparsers = parser.add_subparsers(dest="command")
12
+
13
+ doctor_parser = subparsers.add_parser("doctor", help="Check AI connectivity and locator best practices.")
14
+ doctor_parser.add_argument("--dir", default="tests", help="Test directory to scan (default: tests)")
15
+
16
+ args = parser.parse_args()
17
+
18
+ if args.command == "doctor":
19
+ run_doctor(args.dir)
20
+ else:
21
+ parser.print_help()
22
+ sys.exit(1 if args.command else 0)
23
+
24
+
25
+ if __name__ == "__main__":
26
+ main()