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.
- mendwright-0.3.0/CHANGELOG.md +28 -0
- mendwright-0.3.0/CODE_OF_CONDUCT.md +68 -0
- mendwright-0.3.0/CONTRIBUTING.md +260 -0
- mendwright-0.3.0/LICENSE +21 -0
- mendwright-0.3.0/MANIFEST.in +9 -0
- mendwright-0.3.0/PKG-INFO +282 -0
- mendwright-0.3.0/README.md +223 -0
- mendwright-0.3.0/SECURITY.md +146 -0
- mendwright-0.3.0/examples/demo/README.md +115 -0
- mendwright-0.3.0/examples/demo/demo.py +85 -0
- mendwright-0.3.0/examples/demo/v1.html +32 -0
- mendwright-0.3.0/examples/demo/v2.html +37 -0
- mendwright-0.3.0/examples/heal_gateway.mjs +263 -0
- mendwright-0.3.0/examples/wrangler.toml +22 -0
- mendwright-0.3.0/mendwright/__init__.py +92 -0
- mendwright-0.3.0/mendwright/__main__.py +6 -0
- mendwright-0.3.0/mendwright/aio.py +686 -0
- mendwright-0.3.0/mendwright/capture.py +162 -0
- mendwright-0.3.0/mendwright/cli.py +689 -0
- mendwright-0.3.0/mendwright/client.py +216 -0
- mendwright-0.3.0/mendwright/coordinator.py +289 -0
- mendwright-0.3.0/mendwright/demo/__init__.py +35 -0
- mendwright-0.3.0/mendwright/demo/_page.py +171 -0
- mendwright-0.3.0/mendwright/demo/_story.py +470 -0
- mendwright-0.3.0/mendwright/demo/v1.html +29 -0
- mendwright-0.3.0/mendwright/demo/v2.html +33 -0
- mendwright-0.3.0/mendwright/drivers/__init__.py +25 -0
- mendwright-0.3.0/mendwright/drivers/base.py +316 -0
- mendwright-0.3.0/mendwright/drivers/playwright_async.py +148 -0
- mendwright-0.3.0/mendwright/drivers/playwright_sync.py +193 -0
- mendwright-0.3.0/mendwright/drivers/registry.py +37 -0
- mendwright-0.3.0/mendwright/drivers/selenium.py +596 -0
- mendwright-0.3.0/mendwright/exceptions.py +10 -0
- mendwright-0.3.0/mendwright/facade.py +783 -0
- mendwright-0.3.0/mendwright/gate.py +62 -0
- mendwright-0.3.0/mendwright/journal.py +180 -0
- mendwright-0.3.0/mendwright/limits.py +236 -0
- mendwright-0.3.0/mendwright/prompt.py +67 -0
- mendwright-0.3.0/mendwright/providers/__init__.py +24 -0
- mendwright-0.3.0/mendwright/providers/_errors.py +376 -0
- mendwright-0.3.0/mendwright/providers/anthropic.py +177 -0
- mendwright-0.3.0/mendwright/providers/base.py +25 -0
- mendwright-0.3.0/mendwright/providers/bedrock.py +126 -0
- mendwright-0.3.0/mendwright/providers/detect.py +170 -0
- mendwright-0.3.0/mendwright/providers/gateway.py +69 -0
- mendwright-0.3.0/mendwright/providers/gemini.py +171 -0
- mendwright-0.3.0/mendwright/providers/litellm.py +176 -0
- mendwright-0.3.0/mendwright/providers/openai_compat.py +255 -0
- mendwright-0.3.0/mendwright/publish.py +74 -0
- mendwright-0.3.0/mendwright/py.typed +0 -0
- mendwright-0.3.0/mendwright/pytest_plugin.py +505 -0
- mendwright-0.3.0/mendwright/resolver.py +133 -0
- mendwright-0.3.0/mendwright/roles.py +232 -0
- mendwright-0.3.0/mendwright/selectors.py +181 -0
- mendwright-0.3.0/mendwright/store.py +722 -0
- mendwright-0.3.0/mendwright/types.py +56 -0
- mendwright-0.3.0/mendwright/validation.py +98 -0
- mendwright-0.3.0/mendwright.egg-info/PKG-INFO +282 -0
- mendwright-0.3.0/mendwright.egg-info/SOURCES.txt +126 -0
- mendwright-0.3.0/mendwright.egg-info/dependency_links.txt +1 -0
- mendwright-0.3.0/mendwright.egg-info/entry_points.txt +5 -0
- mendwright-0.3.0/mendwright.egg-info/requires.txt +38 -0
- mendwright-0.3.0/mendwright.egg-info/top_level.txt +1 -0
- mendwright-0.3.0/pyproject.toml +94 -0
- mendwright-0.3.0/setup.cfg +4 -0
- mendwright-0.3.0/tests/browser/conftest.py +99 -0
- mendwright-0.3.0/tests/browser/mw_engines.py +612 -0
- mendwright-0.3.0/tests/browser/test_aio_browser.py +336 -0
- mendwright-0.3.0/tests/browser/test_browser_drivers.py +211 -0
- mendwright-0.3.0/tests/browser/test_browser_healing.py +258 -0
- mendwright-0.3.0/tests/conftest.py +181 -0
- mendwright-0.3.0/tests/fixtures/terms_moved.html +14 -0
- mendwright-0.3.0/tests/test_action_proxy.py +170 -0
- mendwright-0.3.0/tests/test_aio.py +814 -0
- mendwright-0.3.0/tests/test_apply_retired.py +148 -0
- mendwright-0.3.0/tests/test_attention_messages.py +205 -0
- mendwright-0.3.0/tests/test_auto_provider.py +313 -0
- mendwright-0.3.0/tests/test_capture_clean.py +197 -0
- mendwright-0.3.0/tests/test_cli.py +820 -0
- mendwright-0.3.0/tests/test_core_correctness.py +96 -0
- mendwright-0.3.0/tests/test_demo.py +94 -0
- mendwright-0.3.0/tests/test_destructive_flags.py +122 -0
- mendwright-0.3.0/tests/test_driver_act_on.py +333 -0
- mendwright-0.3.0/tests/test_driver_describe_inventory.py +257 -0
- mendwright-0.3.0/tests/test_driver_detection.py +170 -0
- mendwright-0.3.0/tests/test_driver_protocol.py +123 -0
- mendwright-0.3.0/tests/test_driver_selector_kinds.py +277 -0
- mendwright-0.3.0/tests/test_driver_selenium.py +238 -0
- mendwright-0.3.0/tests/test_driver_selenium_waits_frames.py +194 -0
- mendwright-0.3.0/tests/test_drivers.py +98 -0
- mendwright-0.3.0/tests/test_facade.py +109 -0
- mendwright-0.3.0/tests/test_facade_code_wins.py +162 -0
- mendwright-0.3.0/tests/test_facade_roles.py +264 -0
- mendwright-0.3.0/tests/test_failure_capture.py +52 -0
- mendwright-0.3.0/tests/test_frame_hint.py +75 -0
- mendwright-0.3.0/tests/test_gateway_worker.py +197 -0
- mendwright-0.3.0/tests/test_heal_client.py +93 -0
- mendwright-0.3.0/tests/test_heal_coordinator.py +139 -0
- mendwright-0.3.0/tests/test_healer_hooks.py +67 -0
- mendwright-0.3.0/tests/test_http_useragent.py +36 -0
- mendwright-0.3.0/tests/test_journal.py +203 -0
- mendwright-0.3.0/tests/test_limits.py +239 -0
- mendwright-0.3.0/tests/test_live_gate.py +168 -0
- mendwright-0.3.0/tests/test_logging.py +226 -0
- mendwright-0.3.0/tests/test_open_config.py +211 -0
- mendwright-0.3.0/tests/test_prompt.py +37 -0
- mendwright-0.3.0/tests/test_provider_anthropic.py +114 -0
- mendwright-0.3.0/tests/test_provider_base_url.py +92 -0
- mendwright-0.3.0/tests/test_provider_bedrock.py +123 -0
- mendwright-0.3.0/tests/test_provider_errors.py +132 -0
- mendwright-0.3.0/tests/test_provider_gemini.py +262 -0
- mendwright-0.3.0/tests/test_provider_last_error.py +627 -0
- mendwright-0.3.0/tests/test_provider_litellm.py +145 -0
- mendwright-0.3.0/tests/test_provider_openai_compat.py +144 -0
- mendwright-0.3.0/tests/test_providers.py +54 -0
- mendwright-0.3.0/tests/test_public_api.py +41 -0
- mendwright-0.3.0/tests/test_publish_selector.py +63 -0
- mendwright-0.3.0/tests/test_pytest_plugin.py +625 -0
- mendwright-0.3.0/tests/test_readme.py +675 -0
- mendwright-0.3.0/tests/test_selector_kinds.py +110 -0
- mendwright-0.3.0/tests/test_selector_resolver.py +105 -0
- mendwright-0.3.0/tests/test_selector_validation.py +68 -0
- mendwright-0.3.0/tests/test_selectors_store.py +145 -0
- mendwright-0.3.0/tests/test_store_meta.py +191 -0
- mendwright-0.3.0/tests/test_store_robustness.py +358 -0
- mendwright-0.3.0/tests/test_success_heal.py +192 -0
- mendwright-0.3.0/tests/test_units.py +89 -0
- 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.
|
mendwright-0.3.0/LICENSE
ADDED
|
@@ -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
|