mendwright 0.3.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 (128) hide show
  1. mendwright-0.3.0/CHANGELOG.md +28 -0
  2. mendwright-0.3.0/CODE_OF_CONDUCT.md +68 -0
  3. mendwright-0.3.0/CONTRIBUTING.md +260 -0
  4. mendwright-0.3.0/LICENSE +21 -0
  5. mendwright-0.3.0/MANIFEST.in +9 -0
  6. mendwright-0.3.0/PKG-INFO +282 -0
  7. mendwright-0.3.0/README.md +223 -0
  8. mendwright-0.3.0/SECURITY.md +146 -0
  9. mendwright-0.3.0/examples/demo/README.md +115 -0
  10. mendwright-0.3.0/examples/demo/demo.py +85 -0
  11. mendwright-0.3.0/examples/demo/v1.html +32 -0
  12. mendwright-0.3.0/examples/demo/v2.html +37 -0
  13. mendwright-0.3.0/examples/heal_gateway.mjs +263 -0
  14. mendwright-0.3.0/examples/wrangler.toml +22 -0
  15. mendwright-0.3.0/mendwright/__init__.py +92 -0
  16. mendwright-0.3.0/mendwright/__main__.py +6 -0
  17. mendwright-0.3.0/mendwright/aio.py +686 -0
  18. mendwright-0.3.0/mendwright/capture.py +162 -0
  19. mendwright-0.3.0/mendwright/cli.py +689 -0
  20. mendwright-0.3.0/mendwright/client.py +216 -0
  21. mendwright-0.3.0/mendwright/coordinator.py +289 -0
  22. mendwright-0.3.0/mendwright/demo/__init__.py +35 -0
  23. mendwright-0.3.0/mendwright/demo/_page.py +171 -0
  24. mendwright-0.3.0/mendwright/demo/_story.py +470 -0
  25. mendwright-0.3.0/mendwright/demo/v1.html +29 -0
  26. mendwright-0.3.0/mendwright/demo/v2.html +33 -0
  27. mendwright-0.3.0/mendwright/drivers/__init__.py +25 -0
  28. mendwright-0.3.0/mendwright/drivers/base.py +316 -0
  29. mendwright-0.3.0/mendwright/drivers/playwright_async.py +148 -0
  30. mendwright-0.3.0/mendwright/drivers/playwright_sync.py +193 -0
  31. mendwright-0.3.0/mendwright/drivers/registry.py +37 -0
  32. mendwright-0.3.0/mendwright/drivers/selenium.py +596 -0
  33. mendwright-0.3.0/mendwright/exceptions.py +10 -0
  34. mendwright-0.3.0/mendwright/facade.py +783 -0
  35. mendwright-0.3.0/mendwright/gate.py +62 -0
  36. mendwright-0.3.0/mendwright/journal.py +180 -0
  37. mendwright-0.3.0/mendwright/limits.py +236 -0
  38. mendwright-0.3.0/mendwright/prompt.py +67 -0
  39. mendwright-0.3.0/mendwright/providers/__init__.py +24 -0
  40. mendwright-0.3.0/mendwright/providers/_errors.py +376 -0
  41. mendwright-0.3.0/mendwright/providers/anthropic.py +177 -0
  42. mendwright-0.3.0/mendwright/providers/base.py +25 -0
  43. mendwright-0.3.0/mendwright/providers/bedrock.py +126 -0
  44. mendwright-0.3.0/mendwright/providers/detect.py +170 -0
  45. mendwright-0.3.0/mendwright/providers/gateway.py +69 -0
  46. mendwright-0.3.0/mendwright/providers/gemini.py +171 -0
  47. mendwright-0.3.0/mendwright/providers/litellm.py +176 -0
  48. mendwright-0.3.0/mendwright/providers/openai_compat.py +255 -0
  49. mendwright-0.3.0/mendwright/publish.py +74 -0
  50. mendwright-0.3.0/mendwright/py.typed +0 -0
  51. mendwright-0.3.0/mendwright/pytest_plugin.py +505 -0
  52. mendwright-0.3.0/mendwright/resolver.py +133 -0
  53. mendwright-0.3.0/mendwright/roles.py +232 -0
  54. mendwright-0.3.0/mendwright/selectors.py +181 -0
  55. mendwright-0.3.0/mendwright/store.py +722 -0
  56. mendwright-0.3.0/mendwright/types.py +56 -0
  57. mendwright-0.3.0/mendwright/validation.py +98 -0
  58. mendwright-0.3.0/mendwright.egg-info/PKG-INFO +282 -0
  59. mendwright-0.3.0/mendwright.egg-info/SOURCES.txt +126 -0
  60. mendwright-0.3.0/mendwright.egg-info/dependency_links.txt +1 -0
  61. mendwright-0.3.0/mendwright.egg-info/entry_points.txt +5 -0
  62. mendwright-0.3.0/mendwright.egg-info/requires.txt +38 -0
  63. mendwright-0.3.0/mendwright.egg-info/top_level.txt +1 -0
  64. mendwright-0.3.0/pyproject.toml +94 -0
  65. mendwright-0.3.0/setup.cfg +4 -0
  66. mendwright-0.3.0/tests/browser/conftest.py +99 -0
  67. mendwright-0.3.0/tests/browser/mw_engines.py +612 -0
  68. mendwright-0.3.0/tests/browser/test_aio_browser.py +336 -0
  69. mendwright-0.3.0/tests/browser/test_browser_drivers.py +211 -0
  70. mendwright-0.3.0/tests/browser/test_browser_healing.py +258 -0
  71. mendwright-0.3.0/tests/conftest.py +181 -0
  72. mendwright-0.3.0/tests/fixtures/terms_moved.html +14 -0
  73. mendwright-0.3.0/tests/test_action_proxy.py +170 -0
  74. mendwright-0.3.0/tests/test_aio.py +814 -0
  75. mendwright-0.3.0/tests/test_apply_retired.py +148 -0
  76. mendwright-0.3.0/tests/test_attention_messages.py +205 -0
  77. mendwright-0.3.0/tests/test_auto_provider.py +313 -0
  78. mendwright-0.3.0/tests/test_capture_clean.py +197 -0
  79. mendwright-0.3.0/tests/test_cli.py +820 -0
  80. mendwright-0.3.0/tests/test_core_correctness.py +96 -0
  81. mendwright-0.3.0/tests/test_demo.py +94 -0
  82. mendwright-0.3.0/tests/test_destructive_flags.py +122 -0
  83. mendwright-0.3.0/tests/test_driver_act_on.py +333 -0
  84. mendwright-0.3.0/tests/test_driver_describe_inventory.py +257 -0
  85. mendwright-0.3.0/tests/test_driver_detection.py +170 -0
  86. mendwright-0.3.0/tests/test_driver_protocol.py +123 -0
  87. mendwright-0.3.0/tests/test_driver_selector_kinds.py +277 -0
  88. mendwright-0.3.0/tests/test_driver_selenium.py +238 -0
  89. mendwright-0.3.0/tests/test_driver_selenium_waits_frames.py +194 -0
  90. mendwright-0.3.0/tests/test_drivers.py +98 -0
  91. mendwright-0.3.0/tests/test_facade.py +109 -0
  92. mendwright-0.3.0/tests/test_facade_code_wins.py +162 -0
  93. mendwright-0.3.0/tests/test_facade_roles.py +264 -0
  94. mendwright-0.3.0/tests/test_failure_capture.py +52 -0
  95. mendwright-0.3.0/tests/test_frame_hint.py +75 -0
  96. mendwright-0.3.0/tests/test_gateway_worker.py +197 -0
  97. mendwright-0.3.0/tests/test_heal_client.py +93 -0
  98. mendwright-0.3.0/tests/test_heal_coordinator.py +139 -0
  99. mendwright-0.3.0/tests/test_healer_hooks.py +67 -0
  100. mendwright-0.3.0/tests/test_http_useragent.py +36 -0
  101. mendwright-0.3.0/tests/test_journal.py +203 -0
  102. mendwright-0.3.0/tests/test_limits.py +239 -0
  103. mendwright-0.3.0/tests/test_live_gate.py +168 -0
  104. mendwright-0.3.0/tests/test_logging.py +226 -0
  105. mendwright-0.3.0/tests/test_open_config.py +211 -0
  106. mendwright-0.3.0/tests/test_prompt.py +37 -0
  107. mendwright-0.3.0/tests/test_provider_anthropic.py +114 -0
  108. mendwright-0.3.0/tests/test_provider_base_url.py +92 -0
  109. mendwright-0.3.0/tests/test_provider_bedrock.py +123 -0
  110. mendwright-0.3.0/tests/test_provider_errors.py +132 -0
  111. mendwright-0.3.0/tests/test_provider_gemini.py +262 -0
  112. mendwright-0.3.0/tests/test_provider_last_error.py +627 -0
  113. mendwright-0.3.0/tests/test_provider_litellm.py +145 -0
  114. mendwright-0.3.0/tests/test_provider_openai_compat.py +144 -0
  115. mendwright-0.3.0/tests/test_providers.py +54 -0
  116. mendwright-0.3.0/tests/test_public_api.py +41 -0
  117. mendwright-0.3.0/tests/test_publish_selector.py +63 -0
  118. mendwright-0.3.0/tests/test_pytest_plugin.py +625 -0
  119. mendwright-0.3.0/tests/test_readme.py +675 -0
  120. mendwright-0.3.0/tests/test_selector_kinds.py +110 -0
  121. mendwright-0.3.0/tests/test_selector_resolver.py +105 -0
  122. mendwright-0.3.0/tests/test_selector_validation.py +68 -0
  123. mendwright-0.3.0/tests/test_selectors_store.py +145 -0
  124. mendwright-0.3.0/tests/test_store_meta.py +191 -0
  125. mendwright-0.3.0/tests/test_store_robustness.py +358 -0
  126. mendwright-0.3.0/tests/test_success_heal.py +192 -0
  127. mendwright-0.3.0/tests/test_units.py +89 -0
  128. mendwright-0.3.0/tests/test_version.py +29 -0
@@ -0,0 +1,28 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and version numbers follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+ While the version starts with 0, a minor release can change the public API.
7
+
8
+ ## [0.3.0] - 2026-10-11
9
+
10
+ First public release. Earlier versions were developed privately as selfheal and never published.
11
+
12
+ - Drivers for Playwright (sync and async, including patchright and camoufox) and Selenium, behind one `mendwright.open(page)`. `heal.locator(key, selector)` takes XPath, CSS or Playwright selectors. A heal is always an XPath.
13
+ - Providers for Anthropic, OpenAI and compatible APIs, Gemini, Bedrock, LiteLLM, Ollama and your own gateway. `auto_provider()` picks one from the environment, and there is no default model. `ANTHROPIC_BASE_URL` and `OPENAI_BASE_URL` are honoured for proxies and compatible hosts.
14
+ - Local checks before a heal is used: not an index-only path, not quarantined or superseded for the key, one match in the captured HTML, the step's role, and one visible match on the live page that still has the role. A heal whose action fails is quarantined, and the quarantine blocks the same element, not just the same selector string.
15
+ - Keys you mark as destructive are never healed and never sent to a model: `destructive=True` in `heal.locator()`, `mendwright.open(destructive_keys=...)`, or `"destructive": true` in a seed file. mendwright doesn't guess this from the key or the page.
16
+ - Page capture removes scripts, styles, comments and head metadata, and redacts values, URLs and long numbers, including in broken selectors and expected text. Direct providers have an hourly rate limit and an optional spend cap.
17
+ - A pytest plugin: the `heal` fixture, an overridable `mendwright_provider` fixture, and `--mendwright-strict`, which also fails reruns from pytest-rerunfailures. Heals from a passing test are confirmed.
18
+ - The `mendwright` command: `demo` (offline, or `--live` with the model from your environment), `doctor` and `report`.
19
+ - Heals are saved in `.mendwright/overrides.json`, with a journal next to it. A corrupt store raises instead of being overwritten. Each applied heal prints one line to stderr.
20
+ - Python 3.10 to 3.14, with lxml as the only dependency.
21
+
22
+ ### Changed
23
+
24
+ From the private builds. If you never used them under the name selfheal, you can skip this.
25
+
26
+ - On Selenium, the handle for a candidate inside an iframe is a `FramedElement`. `driver.execute_script()` and `ActionChains` need `el.in_frame(lambda e: ...)` to get an element that still works in the frame (see the [guide](https://github.com/saadbuilt/mendwright/blob/main/docs/guide.md#selenium)).
27
+
28
+ [0.3.0]: https://github.com/saadbuilt/mendwright/releases/tag/v0.3.0
@@ -0,0 +1,68 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a
6
+ harassment-free experience for everyone, regardless of age, body size, visible or
7
+ invisible disability, ethnicity, sex characteristics, gender identity and expression,
8
+ level of experience, education, socio-economic status, nationality, personal appearance,
9
+ race, religion, or sexual identity and orientation.
10
+
11
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse,
12
+ inclusive, and healthy community.
13
+
14
+ ## Our Standards
15
+
16
+ Examples of behavior that contributes to a positive environment include:
17
+
18
+ - Demonstrating empathy and kindness toward other people
19
+ - Being respectful of differing opinions, viewpoints, and experiences
20
+ - Giving and gracefully accepting constructive feedback
21
+ - Accepting responsibility and apologizing to those affected by our mistakes, and
22
+ learning from the experience
23
+ - Focusing on what is best for the overall community
24
+
25
+ Examples of unacceptable behavior include:
26
+
27
+ - The use of sexualized language or imagery, and sexual attention or advances of any kind
28
+ - Trolling, insulting or derogatory comments, and personal or political attacks
29
+ - Public or private harassment
30
+ - Publishing others' private information, such as a physical or email address, without
31
+ their explicit permission
32
+ - Other conduct which could reasonably be considered inappropriate in a professional
33
+ setting
34
+
35
+ ## Enforcement Responsibilities
36
+
37
+ Community leaders are responsible for clarifying and enforcing our standards of
38
+ acceptable behavior and will take appropriate and fair corrective action in response to
39
+ any behavior that they deem inappropriate, threatening, offensive, or harmful.
40
+
41
+ Community leaders have the right and responsibility to remove, edit, or reject comments,
42
+ commits, code, wiki edits, issues, and other contributions that are not aligned to this
43
+ Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
44
+
45
+ ## Scope
46
+
47
+ This Code of Conduct applies within all community spaces, and also applies when an
48
+ individual is officially representing the community in public spaces.
49
+
50
+ ## Enforcement
51
+
52
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to
53
+ the community leaders responsible for enforcement through the project's private
54
+ vulnerability or contact channels on the repository. All complaints will be reviewed and
55
+ investigated promptly and fairly. All community leaders are obligated to respect the
56
+ privacy and security of the reporter of any incident.
57
+
58
+ ## Attribution
59
+
60
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1,
61
+ available at
62
+ https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
63
+
64
+ [homepage]: https://www.contributor-covenant.org
65
+
66
+ For answers to common questions about this code of conduct, see the FAQ at
67
+ https://www.contributor-covenant.org/faq. Translations are available at
68
+ https://www.contributor-covenant.org/translations.
@@ -0,0 +1,260 @@
1
+ # Contributing to mendwright
2
+
3
+ mendwright has one runtime dependency (lxml), and the core runs without a browser library or a vendor SDK installed.
4
+ Changes that keep it that way are the easiest to merge.
5
+ New drivers and new providers are the most useful contributions; their contracts are below.
6
+
7
+ ## Where to start
8
+
9
+ - Pick an issue labelled [good first issue](https://github.com/saadbuilt/mendwright/labels/good%20first%20issue).
10
+ - Report a heal that picked the wrong element, with the "A heal picked the wrong element" issue template. These reports are the only accuracy data the project has.
11
+ - Add a driver for another browser library ([Writing a driver](#writing-a-driver)) or a provider for another model API ([Writing a provider](#writing-a-provider)).
12
+
13
+ ## Setup
14
+
15
+ Python 3.10 or later.
16
+
17
+ ```bash
18
+ git clone https://github.com/saadbuilt/mendwright
19
+ cd mendwright
20
+ python -m venv .venv
21
+ . .venv/bin/activate # Windows: .venv\Scripts\activate
22
+ pip install -e ".[dev]"
23
+ ```
24
+
25
+ `[dev]` installs pytest, ruff, build and twine.
26
+ Keep the quotes around `".[dev]"`; zsh treats the brackets as a glob otherwise.
27
+
28
+ For the real-browser tests, also install the browser libraries and Playwright's browsers:
29
+
30
+ ```bash
31
+ pip install -e ".[dev,playwright,selenium]"
32
+ python -m playwright install chromium firefox webkit
33
+ ```
34
+
35
+ Selenium drives the Chrome and Firefox already installed on your machine.
36
+ Selenium Manager downloads a matching driver the first time.
37
+ patchright and camoufox are optional: `pip install patchright` then `python -m patchright install chromium`, and `pip install camoufox` then `python -m camoufox fetch`.
38
+
39
+ ## Running the tests
40
+
41
+ ### Offline suite
42
+
43
+ ```bash
44
+ python -m pytest
45
+ ```
46
+
47
+ This needs no network, API key or browser, and takes about 10 seconds.
48
+ One or two tests skip on purpose: one needs a live gateway, and a POSIX-only one skips on Windows.
49
+
50
+ `pyproject.toml` sets `addopts = "-m 'not browser' -p no:mendwright"`.
51
+ The first part leaves out the browser tests.
52
+ The second turns off mendwright's own pytest plugin, because this repo's tests call `mendwright.open()` hundreds of times and the plugin would print a heal summary at the end. `tests/test_pytest_plugin.py` loads the plugin explicitly through pytester.
53
+
54
+ New tests must not reach the network, a real model or a real browser, and must not depend on the clock or on randomness.
55
+ The fixtures in `tests/conftest.py` cover most cases:
56
+
57
+ - `make_page`: a `FakePage`, an lxml-backed page shaped like a Playwright page.
58
+ - `stub_provider`: a `StubProvider` that returns a fixed proposal and records what it was sent.
59
+ - Autouse fixtures that unset `MENDWRIGHT_STORE`, `MENDWRIGHT_TIMEOUT_MS`, `MENDWRIGHT_DRY_RUN` and `MENDWRIGHT_DISABLE`, and reset the shared rate limiters.
60
+
61
+ Providers take an injected `http=` transport; `tests/test_providers.py` shows the pattern.
62
+
63
+ ### Browser suite
64
+
65
+ ```bash
66
+ python -m pytest -m browser
67
+ ```
68
+
69
+ A `-m` on the command line replaces the default one.
70
+ The suite is in `tests/browser/`:
71
+
72
+ - The sync tests run on 7 engines: `pw-chromium`, `pw-firefox`, `pw-webkit`, `patchright`, `camoufox`, `sel-chrome` and `sel-firefox`.
73
+ - `test_aio_browser.py` runs `mendwright.aio` on Chromium, Firefox and WebKit.
74
+ - An engine that isn't installed is skipped, and the skip message names the install command.
75
+
76
+ Run one engine with `-k`, for example `python -m pytest -m browser -k pw-webkit`.
77
+ Run one scenario on every engine with, for example, `-k heal_after_a_redesign`.
78
+ The last full local run passed on all 7 engines, plus the async tests on all 3 browsers (Windows 11, Python 3.12). The sync suite took a little over 2 minutes.
79
+
80
+ `MENDWRIGHT_REQUIRE_ENGINES` takes a comma-separated list of engine ids that must start.
81
+ If one of them fails to launch, its tests fail instead of being skipped. CI sets it to `pw-chromium,pw-firefox,pw-webkit,sel-chrome,sel-firefox`.
82
+ Only the fixture in `tests/browser/conftest.py` reads it. `test_aio_browser.py` has its own `engine` fixture, which still skips an engine that can't launch.
83
+
84
+ Rules for browser tests:
85
+
86
+ - Mark them with `pytestmark = pytest.mark.browser`.
87
+ - Do not import playwright or selenium at module level or in a conftest. The default run still collects `tests/browser/` in CI jobs that have neither installed. Use `pytest.importorskip()` or import inside the test.
88
+ - A test that only applies to one family takes `@pytest.mark.engine_family('playwright')` or `('selenium')`. It is deselected on the other family, so the skip count only shows real gaps.
89
+
90
+ ### Gateway parity test
91
+
92
+ `tests/test_gateway_worker.py` runs `examples/heal_gateway.mjs` under node, with `fetch` stubbed and no network.
93
+ It checks that the worker's `buildPrompt()` and `parseProposal()` give the same results as `build_prompt()` and `parse_proposal()` in `mendwright/prompt.py`, and that the worker reads its model id from `MODEL`.
94
+ It's skipped when `node` isn't on PATH.
95
+
96
+ If you change `mendwright/prompt.py`, make the same change in `examples/heal_gateway.mjs` and run:
97
+
98
+ ```bash
99
+ python -m pytest tests/test_prompt.py tests/test_gateway_worker.py
100
+ ```
101
+
102
+ ## Lint
103
+
104
+ ```bash
105
+ ruff check .
106
+ ```
107
+
108
+ The configuration is in `pyproject.toml`: rules E, F, W and B, line length 112.
109
+
110
+ ## Architecture
111
+
112
+ What happens in one call to `heal.locator(key, selector).click()`:
113
+
114
+ ```
115
+ heal.locator(key, selector) facade.py (sync), aio.py (async)
116
+ resolve() resolver.py, through a Driver (drivers/)
117
+ on a miss: HealCoordinator.try_heal() coordinator.py
118
+ destructive, guard, role, rate checks
119
+ build_bundle() capture.py
120
+ provider.propose(bundle) providers/, prompt.py
121
+ apply(): validate(), live_match() client.py, validation.py, gate.py
122
+ save the new candidate store.py
123
+ resolve() again
124
+ .click() ActionProxy in facade.py, through driver.act_on()
125
+ on failure: quarantine the heal coordinator.confirm_pending()
126
+ ```
127
+
128
+ The modules:
129
+
130
+ - `selectors.py`: `classify()` (XPath, CSS or Playwright engine selector), `check_syntax()`, `normalize()` and `InvalidSelector`.
131
+ - `drivers/`: `base.py` holds the `Driver` protocol, the shared describe and inventory scripts, and the fallback helpers. `registry.py` has `wrap()` and `register_driver()`. The built-in drivers are `playwright_sync.py`, `playwright_async.py` (used by `mendwright.aio`) and `selenium.py`, which handles frames, implicit waits and the verb mapping.
132
+ - `resolver.py`: `resolve()` and `present()`. Tries the candidates in order and polls until the timeout, then raises `AttentionRequired`, or `InvalidSelector` when no candidate can run.
133
+ - `facade.py`: `mendwright.open()`, `Healer` and `ActionProxy`. Puts the selectors from your code first, reads the role from the page, never acts through a saved heal on a key marked destructive, and runs actions, quarantining a heal whose action fails.
134
+ - `aio.py`: `mendwright.aio.open()` and `AsyncHealer`. A heal runs the sync coordinator in a worker thread, which reaches the page through `SyncBridge`.
135
+ - `coordinator.py`: `HealCoordinator` and `resolve_with_heal()`. Runs the checks before any model call, builds the bundle, asks the provider (with one re-prompt on rejection), applies, journals, and keeps the reason for `explain()`.
136
+ - `validation.py` and `gate.py`: `validate()` checks a proposal against the captured HTML with lxml; `live_match()` checks it against the page through the driver. `client.apply()` runs both.
137
+ - `store.py` and `roles.py`: the seed and override layers, candidate status, per-key metadata, the lock file and the merge; and the role vocabulary.
138
+ - `capture.py`: `build_bundle()`, `redact()` and `clean_html()`.
139
+ - `providers/`: the `HealProvider` protocol (`base.py`), the built-in providers, `auto_provider()` (`detect.py`) and the error text (`_errors.py`). `prompt.py` holds the one prompt every provider uses.
140
+ - `limits.py` and `journal.py`: the rate limit and spend cap for direct providers, and the JSONL journal.
141
+ - `publish.py`: writes a confirmed selector into a seed JSON file.
142
+ - `cli.py` and `demo/`: the `mendwright` command and the demo that ships in the wheel.
143
+ - `pytest_plugin.py`: the `heal` fixture, confirmation from test results, strict mode and the summary.
144
+
145
+ ## Writing a driver
146
+
147
+ A driver wraps one live page or session.
148
+ `wrap()` accepts any object that has `count`, `is_visible` and `handle`; anything else goes through the registered detectors.
149
+
150
+ | Method | Status | Contract |
151
+ |---|---|---|
152
+ | `count(selector, frame_chain=())` | required | Number of matches. |
153
+ | `is_visible(selector, frame_chain=())` | required | Whether the match is visible. |
154
+ | `handle(selector, frame_chain=())` | required | The native locator or element, so user code can act on it. |
155
+ | `scroll_into_view(selector, frame_chain=())` | expected | |
156
+ | `act(selector, verb, *args, frame_chain=())` | expected | Perform a verb on the match. |
157
+ | `url()` | expected | The current URL. |
158
+ | `outer_html(scope=None)` | expected | The HTML the checks run against. |
159
+ | `act_on(native, verb, *args, **kwargs)` | optional | Run a Playwright-style verb on a `handle()` result. Without it, `driver_act_on()` calls `getattr(native, verb)(*args, **kwargs)`. |
160
+ | `describe(selector, frame_chain=())` | optional | `{'tag', 'role', 'text', 'outer_html'}` for a single match, else `None`. Without it, `driver_describe()` returns `None` and the role is guessed from the selector text. |
161
+ | `inventory(limit=200)` | optional | Up to `limit` rows of `{'tag', 'role', 'id', 'name', 'text', 'aria_label'}` for buttons, links, inputs, selects, textareas and `[role]` elements. Without it, `driver_inventory()` returns `[]`. |
162
+
163
+ Rules:
164
+
165
+ - `frame_chain` is a tuple of frame selectors, outermost first.
166
+ - A selector can be XPath, CSS or a Playwright engine selector (see `mendwright/selectors.py`). Raise `InvalidSelector` for a kind your library can't run, or one the browser rejects. For any other error, the read methods return `0`, `False` or `None`, so the resolver keeps polling.
167
+ - `outer_html(scope)` must capture the same document or frame that resolution runs against. Otherwise a selector can be unique in the snapshot and not on the page.
168
+ - `describe()` must compute `role` and `text` the way `validate()` does: `validation._role_of()` on the element, and its whitespace-normalised text, cut to 200 characters. The built-in drivers do this with `DESCRIBE_JS` and `summary_from_js()` in `drivers/base.py`.
169
+ - `inventory()` must not read input values.
170
+ - Detect your page by its methods, not its module name. camoufox and patchright pages aren't in the `playwright` module, and a wrapper has no stable module name. Register with `register_driver(detect, make)`, or `register_driver_front(detect, make)` to be checked before the built-in drivers.
171
+ - For `mendwright.aio`, a custom driver whose `count`, `is_visible` and `handle` are coroutine functions is accepted by `mendwright.aio.open()`.
172
+
173
+ ```python
174
+ from mendwright import register_driver
175
+
176
+ register_driver(my_detect, MyDriver) # my_detect(obj) -> bool, MyDriver(obj) -> driver
177
+ ```
178
+
179
+ The tests to copy from are `tests/test_driver_protocol.py`, `tests/test_driver_describe_inventory.py` and `tests/test_driver_act_on.py`.
180
+
181
+ ## Writing a provider
182
+
183
+ A provider turns a capture bundle into a proposal:
184
+
185
+ ```
186
+ propose(bundle) -> {'xpath': str, 'fallbacks': [str, ...], 'reasoning': str} | None
187
+ report_outcome(key, xpath, success) -> None
188
+ last_error: str | None
189
+ ```
190
+
191
+ `HealProvider` in `mendwright/providers/base.py` is the protocol.
192
+ `last_error` is optional for providers outside this repo (the coordinator reads it with `getattr`), and required for built-in ones.
193
+
194
+ Rules:
195
+
196
+ - Build the request with `mendwright.prompt.build_prompt(bundle)` and parse the reply with `parse_proposal(text)`. Do not write a second prompt.
197
+ - Set `last_error = None` in `__init__` and at the start of `propose()`. When `propose()` returns `None`, set it to one or two sentences: what happened, then a next step that names a real setting. It must not contain the API key, an auth header, the request body or a URL query string. `Describer` in `mendwright/providers/_errors.py` does this for the built-in providers.
198
+ - Import the vendor SDK inside the method that needs it, with a stdlib `urllib` fallback, so `import mendwright` works without the SDK. Bedrock is the exception: its requests are SigV4-signed, so it needs boto3, and only constructing a `BedrockProvider` without it raises.
199
+ - Never hardcode a model id. Take it from a `model=` argument or a `<PROVIDER>_MODEL` environment variable, and raise an error that names the variable when neither is set.
200
+ - Accept an `http=` transport so tests can run offline.
201
+ - A provider only proposes. It never writes to the store or touches the page; the coordinator checks every proposal locally before it is used.
202
+
203
+ The tests to copy from are `tests/test_provider_last_error.py` and the `tests/test_provider_*.py` file for a similar provider.
204
+
205
+ ## Pull requests
206
+
207
+ Before you open one, check:
208
+
209
+ - [ ] `python -m pytest` passes, and new behaviour has offline tests.
210
+ - [ ] `ruff check .` passes.
211
+ - [ ] No new required dependency. Vendor SDKs stay optional and are imported lazily.
212
+ - [ ] No model id in library code.
213
+ - [ ] The checks aren't weakened: a provider only proposes, and `validate()`, `live_match()`, the destructive-control refusal and `guard` still apply.
214
+ - [ ] If `mendwright/prompt.py` changed, `examples/heal_gateway.mjs` changed to match, and `tests/test_gateway_worker.py` passes with node installed.
215
+ - [ ] If the change is user-visible, `CHANGELOG.md` has an entry under the next version.
216
+ - [ ] If a driver changed, `python -m pytest -m browser` passes for the engines you have.
217
+
218
+ Keep pull requests small and about one thing.
219
+
220
+ ## Releases
221
+
222
+ See [docs/RELEASING.md](docs/RELEASING.md).
223
+
224
+ ## Reference
225
+
226
+ ### Live gateway test
227
+
228
+ `tests/test_success_heal.py` has one test against a deployed gateway. It runs only when `MENDWRIGHT_GATEWAY` is set:
229
+
230
+ ```bash
231
+ export MENDWRIGHT_GATEWAY=https://heal.example.workers.dev
232
+ export MENDWRIGHT_KEY=<your client key>
233
+ python -m pytest tests/test_success_heal.py
234
+ ```
235
+
236
+ ### What CI runs
237
+
238
+ `.github/workflows/ci.yml` has these jobs:
239
+
240
+ | Job | What it runs |
241
+ |---|---|
242
+ | lint | `ruff check .` on Python 3.12. Findings fail the job. |
243
+ | test | `python -m pytest -q` on Ubuntu with Python 3.10, 3.11, 3.12, 3.13 and 3.14, on Windows with 3.10 and 3.14, and on macOS with 3.12. |
244
+ | test-next | The same on the Python 3.15 prerelease. Failures are reported but don't fail the run. |
245
+ | lowest-deps | The offline suite on Python 3.10 with lxml 5.0.0 and pytest 7. |
246
+ | browsers | `python -m pytest -m browser -q -rs` on Ubuntu with Python 3.12. It installs Playwright's three browsers, uses the runner image's Chrome and Firefox for Selenium, and sets `MENDWRIGHT_REQUIRE_ENGINES`. patchright and camoufox aren't installed there, so their tests skip. |
247
+ | package | Builds the sdist and wheel, runs `twine check --strict`, installs the wheel into a fresh venv, runs `mendwright --version`, `python -m mendwright doctor` and `mendwright demo`, then runs the sdist's tests against the installed wheel. |
248
+
249
+ ### Charts
250
+
251
+ The comparison charts in `docs/assets/` are generated from `docs/charts/data.json`:
252
+
253
+ ```bash
254
+ python docs/charts/build_charts.py # rewrite the 12 SVGs
255
+ python docs/charts/build_charts.py --check # exit 1 if an SVG is out of date, 2 if data.json is invalid
256
+ ```
257
+
258
+ The script uses only the standard library and gives the same bytes on every run.
259
+ `docs/assets/demo.svg`, the terminal recording in the README, is built from the real output of `mendwright demo` with `python docs/charts/build_demo_svg.py` (also `--check`). Run it again after any change to the demo's output.
260
+ CI doesn't run either one, so run `--check` before a pull request that touches `data.json` or the demo.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mendwright contributors
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,9 @@
1
+ # The sdist carries what it needs to run the offline test suite from the tarball.
2
+ # Explicit patterns rather than graft, so __pycache__ and .pyc files never get in.
3
+ # Each pattern must match at least one file, or the build prints a warning.
4
+ include CHANGELOG.md CONTRIBUTING.md SECURITY.md CODE_OF_CONDUCT.md
5
+ recursive-include tests *.py *.html
6
+ recursive-include examples/demo *.py *.html *.md
7
+ # The reference gateway and its config: tests/test_gateway_worker.py checks the worker
8
+ # against mendwright/prompt.py.
9
+ include examples/heal_gateway.mjs examples/wrangler.toml