Skip to content

Commit 282dd7b

Browse files
authored
Merge pull request #85 from Integration-Automation/dev
Extended capabilities batch + MCP server
2 parents 70c8363 + 0df8426 commit 282dd7b

106 files changed

Lines changed: 7474 additions & 153 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test_dev.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ jobs:
4646
matrix:
4747
python-version: ["3.10", "3.11", "3.12", "3.13"]
4848

49+
env:
50+
# webdriver-manager calls api.github.com to find geckodriver/chromedriver
51+
# releases; the unauthenticated quota (60/h per IP) gets hammered in CI.
52+
# Pass the workflow token so requests use the 5000/h authenticated limit.
53+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
54+
4955
steps:
5056
- uses: actions/checkout@v4
5157

.github/workflows/test_stable.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ jobs:
4646
matrix:
4747
python-version: ["3.10", "3.11", "3.12", "3.13"]
4848

49+
env:
50+
# webdriver-manager hits api.github.com for geckodriver/chromedriver
51+
# release lookups; share the workflow token to use the authenticated
52+
# 5000/h quota instead of the 60/h per-IP limit.
53+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
54+
4955
steps:
5056
- uses: actions/checkout@v4
5157

README.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,98 @@ Companion APIs — `WR_run_for_users` (multi-user matrix), `WR_run_ab` (A/B mode
536536
- **HAR diff**`WR_diff_har` / `WR_diff_har_files` show added / removed / status-changed requests between two runs.
537537
- **Arbitrary-script gate**`executor.set_allow_arbitrary_script(False)` blocks `WR_execute_script` / `WR_execute_async_script` / `WR_pw_evaluate` / `WR_cdp` / `WR_pw_cdp` for untrusted action JSON.
538538

539+
## Extended Capabilities
540+
541+
Reliability & flake reduction:
542+
543+
- **Adaptive retry**`je_web_runner.utils.adaptive_retry.run_with_retry(fn, policy=...)` replays only failures the classifier marks transient / flaky / environment; real bugs short-circuit.
544+
- **Locator strength scorer**`linter.locator_strength.score_locator(strategy, value)` ranks locators 0–100; `assert_strength` fails CI on fragile XPath / TAG_NAME picks.
545+
- **Smart wait**`smart_wait.wait_for_fetch_idle` and `wait_for_spa_route_stable` patch `window.fetch` and `history.pushState` to detect SPA quiescence — no more `time.sleep`.
546+
- **Service throttler**`throttler.throttle("payments-api")` is a file-semaphore that caps cross-shard concurrency on a shared service.
547+
548+
Debugging & observability:
549+
550+
- **Timeline merger**`observability.timeline.build(spans=, console=, responses=)` merges OTel spans, console messages, and network responses into one chronologically-sorted event list.
551+
- **Failure bundle**`failure_bundle.FailureBundle("login_test", error_repr).add_screenshot(...).write("bundle.zip")` packages screenshots / DOM / network / console / trace into a single replayable zip with manifest.
552+
- **Memory leak detector**`memory_leak.detect_growth(driver, action, iterations=10, growth_bytes_per_iter_budget=...)` polls `performance.memory.usedJSHeapSize` and fails on linear-fit growth above budget.
553+
- **Playwright trace recorder**`trace_recorder.TraceRecorder(output_dir="trace-out").start(context, name); …; .stop(context)` always writes a `.zip` viewable with `playwright show-trace`.
554+
- **CSP reporter**`csp_reporter.CspViolationCollector` injects a `securitypolicyviolation` listener and exposes `assert_none()` / `assert_no_directive("script-src")`.
555+
556+
Test data & determinism:
557+
558+
- **Record/replay fixture**`snapshot.fixture_record.FixtureRecorder("fx.json", mode="auto")` saves the producer's output the first time, replays it forever after.
559+
- **DB fixture loader**`database.fixtures.load_fixture_file("seed.json")` + `load_into_connection(conn, fixture)` seeds testcontainers Postgres / MySQL / SQLite from a `{table: [rows]}` JSON.
560+
561+
API & contract testing:
562+
563+
- **API mocking**`api_mock.MockRouter().add("GET", "/api/users/*", body={"id": 1}).attach_to_page(page)` intercepts Playwright routes; URL globs and `re:` regex patterns supported.
564+
- **Contract testing**`contract_testing.validate_response(body, schema)` runs a JSON-Schema subset; `validate_against_openapi(body, doc, "/users/{id}", "GET", 200)` resolves `$ref` and checks the right schema for the response status.
565+
- **GraphQL helper**`graphql.GraphQLClient("https://api/graphql").execute("{ me { id } }")`; `extract_field(payload, "me.id")` plucks values via dotted path.
566+
- **In-process mock services**`mock_services.MockOAuthServer().start()` issues fake bearer tokens, `MockSmtpServer` captures sent mails, `MockS3Storage` is a memory KV.
567+
568+
Security probes:
569+
570+
- **Header tampering**`header_tampering.HeaderTampering().set_header("X-Forwarded-For", "192.0.2.1").attach_to_page(page)` mutates outbound requests so testers can probe missing-CSRF / wrong-origin / stripped-auth handling.
571+
- **License scanner**`license_scanner.scan_text(bundle_text)` finds SPDX identifiers and known license phrases (AGPL/GPL/MIT/Apache-2.0/MPL/ISC/BSD) so SBOM gates can `assert_allowed_licenses`.
572+
573+
Browser & locale:
574+
575+
- **Device emulation presets**`device_emulation.playwright_kwargs("iPhone 15 Pro")` and `apply_to_chrome_options(opts, "Desktop 1080p")`; viewport + DPR + UA + touch in one call.
576+
- **Geo / TZ / locale**`geo_locale.GeoOverride(latitude=51.5, longitude=-0.13, timezone="Europe/London", locale="en-GB")` produces both CDP commands and Playwright `new_context` kwargs.
577+
- **Multi-tab choreographer**`multi_tab.TabChoreographer().open_new(driver, "side", url=...)` registers tabs by alias so action JSON can `WR_switch_tab("side")`.
578+
- **WebAuthn virtual authenticator**`webauthn.enable_virtual_authenticator(driver)` uses CDP `WebAuthn.*` to simulate passkey / FIDO2 sign-in flows.
579+
- **Cookie consent dismisser**`cookie_consent.ConsentDismisser().dismiss(driver)` clicks the first matching OneTrust / TrustArc / Cookiebot / Didomi / Quantcast button; selector list extensible via `register_selector`.
580+
581+
Reporting & CI:
582+
583+
- **PR comment poster**`pr_comment.post_or_update_comment("owner/repo", 42, body, token=...)` is idempotent via a hidden HTML marker so retried CI runs don't pile up.
584+
- **Trend dashboard**`trend_dashboard.compute_trend("ledger.json")` buckets the ledger by day; `render_html(trend)` produces a self-contained SVG line chart + table.
585+
586+
Orchestration & developer experience:
587+
588+
- **Action template library**`action_templates.render_template("login_basic", {...})` substitutes `{{placeholders}}` in built-in flows (login, accept-cookies, switch-locale, close-modal).
589+
- **Diff-aware shard**`sharding.diff_shard.select_for_changed(candidates, base_ref="main")` filters candidates to those touched by the current branch's `git diff`.
590+
- **Watch mode**`watch_mode.watch_loop(directory, on_change=callback, interval=0.5)` re-runs a callback whenever JSON files change.
591+
- **Kubernetes runner**`k8s_runner.render_job_manifests(ShardJobConfig(name_prefix="run", image=..., total_shards=8, actions_dir="/actions"))` produces one `batch/v1 Job` per shard.
592+
- **Per-route perf budgets**`perf_metrics.budgets.evaluate_metrics("/checkout", {"lcp_ms": 2300}, budgets)` plus `assert_within_budget(result)` enforce route-specific thresholds.
593+
594+
AI assistance:
595+
596+
- **Failure RCA**`ai_assist.llm_assist.explain_failure(test_name, error_repr, console=, network=, steps=)` asks the registered LLM for `{likely_cause, evidence, next_steps, confidence}`.
597+
598+
## MCP Server
599+
600+
WebRunner ships a [Model Context Protocol](https://modelcontextprotocol.io/) server so any MCP-aware client (Claude, IDE plugins, etc.) can drive WebRunner over JSON-RPC stdio.
601+
602+
```bash
603+
python -m je_web_runner.mcp_server
604+
```
605+
606+
The default tool list exposes:
607+
608+
- `webrunner_lint_action`, `webrunner_locator_strength`
609+
- `webrunner_render_template`, `webrunner_compute_trend`
610+
- `webrunner_validate_response`, `webrunner_summary_markdown`
611+
- `webrunner_diff_shard`, `webrunner_render_k8s`, `webrunner_partition_shard`
612+
613+
```python
614+
from je_web_runner.mcp_server import McpServer, Tool, build_default_tools, serve_stdio
615+
616+
# Or build a custom server
617+
server = McpServer()
618+
for tool in build_default_tools():
619+
server.register(tool)
620+
server.register(Tool(
621+
name="my_custom_tool",
622+
description="",
623+
input_schema={"type": "object", "properties": {"x": {"type": "string"}}},
624+
handler=lambda args: f"hello {args['x']}",
625+
))
626+
serve_stdio(server=server)
627+
```
628+
629+
The server speaks MCP `2024-11-05`: `initialize`, `tools/list`, `tools/call`, `resources/list`, `ping`, `shutdown`.
630+
539631
## Browser Internals
540632

541633
```python

docs/source/Eng/doc/extended_features/extended_features_doc.rst

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,3 +262,130 @@ registers any ``Callable[[str], str]`` and powers:
262262
self-healing locator flow.
263263
* ``generate_actions_from_prompt(request)`` — natural language → action
264264
JSON draft.
265+
* ``explain_failure(test_name, error_repr, console=, network=, steps=)``
266+
— produces a JSON RCA: ``{likely_cause, evidence, next_steps,
267+
confidence}``.
268+
269+
Reliability helpers
270+
===================
271+
272+
* ``adaptive_retry.run_with_retry(fn, policy=...)`` — retries only when
273+
the failure classifier labels the exception transient / flaky /
274+
environment; ``RetryPolicy`` exposes per-category budgets and history.
275+
* ``linter.locator_strength.score_locator(strategy, value)`` — scores a
276+
locator on a 0–100 scale; ``score_action_locators`` runs across an
277+
action JSON list.
278+
* ``smart_wait.wait_for_fetch_idle`` / ``wait_for_spa_route_stable`` —
279+
inject window.fetch and history hooks to detect SPA quiescence.
280+
* ``throttler.throttle("payments-api")`` — file-semaphore for cross-shard
281+
concurrency limits.
282+
283+
Observability
284+
=============
285+
286+
* ``observability.timeline.build(spans=, console=, responses=)`` —
287+
merges three event sources into a chronological list.
288+
* ``failure_bundle.FailureBundle("test", error_repr).write("bundle.zip")``
289+
— replayable zip with manifest (``screenshot`` / ``dom`` / ``console``
290+
/ ``network`` / ``trace`` / arbitrary text & files).
291+
* ``memory_leak.detect_growth(driver, action, iterations=10)`` —
292+
performance.memory linear-fit slope; ``growth_bytes_per_iter_budget``
293+
raises on regression.
294+
* ``trace_recorder.TraceRecorder().start(context, name) / .stop(context)``
295+
— Playwright tracing wrapper that always emits a ``.zip``.
296+
* ``csp_reporter.CspViolationCollector`` — securitypolicyviolation
297+
listener with ``assert_none`` / ``assert_no_directive``.
298+
299+
Test data & determinism
300+
=======================
301+
302+
* ``snapshot.fixture_record.FixtureRecorder("fx.json", mode="auto")`` —
303+
record once, replay forever; modes ``record`` / ``replay`` / ``auto``.
304+
* ``database.fixtures.load_fixture_file("seed.json")`` +
305+
``load_into_connection(conn, fixture)`` — seed Postgres / MySQL /
306+
SQLite from ``{table: [rows]}`` JSON.
307+
308+
API & contract testing
309+
======================
310+
311+
* ``api_mock.MockRouter().add(method, url_pattern, body=, status=, times=)``
312+
— supports literal, glob, and ``re:`` regex URL patterns; attach to a
313+
Playwright page with ``attach_to_page(page)``.
314+
* ``contract_testing.validate_response(body, schema)`` — JSON-Schema
315+
subset (type / properties / required / items / enum / oneOf /
316+
additionalProperties); ``validate_against_openapi`` resolves
317+
``$ref`` and looks up ``paths[…].responses[…]``.
318+
* ``graphql.GraphQLClient(endpoint).execute(query, variables=)`` +
319+
``extract_field(payload, "users[0].name")``.
320+
* ``mock_services`` — ``MockOAuthServer``, ``MockSmtpServer``,
321+
``MockS3Storage`` for offline CI runs.
322+
323+
Security probes
324+
===============
325+
326+
* ``header_tampering.HeaderTampering()`` — rule list + Playwright
327+
``page.route()`` integration to set / remove / append headers.
328+
* ``license_scanner.scan_text(bundle_text)`` — find SPDX identifiers and
329+
known license phrases; ``assert_allowed_licenses(findings, allow=,
330+
deny=)`` for SBOM gates.
331+
* ``cookie_consent.ConsentDismisser().dismiss(driver)`` — auto-click
332+
OneTrust / TrustArc / Cookiebot / Didomi / Quantcast accept buttons.
333+
334+
Browser & locale
335+
================
336+
337+
* ``device_emulation`` — ``available_presets`` /
338+
``playwright_kwargs("iPhone 15 Pro")`` /
339+
``apply_to_chrome_options(opts, "Desktop 1080p")`` /
340+
``cdp_emulation_command(name)``.
341+
* ``geo_locale.GeoOverride`` — yields both
342+
``cdp_payloads(override)`` and ``playwright_context_kwargs(override)``.
343+
* ``multi_tab.TabChoreographer`` — track tabs by alias;
344+
``register_current`` / ``open_new`` / ``switch_to`` / ``with_tab`` /
345+
``close``.
346+
* ``webauthn.enable_virtual_authenticator(driver)`` — CDP
347+
``WebAuthn.addVirtualAuthenticator`` for passkey simulation.
348+
349+
Reporting & CI
350+
==============
351+
352+
* ``pr_comment.post_or_update_comment(repo, pr_number, body, token=)``
353+
— idempotent via a hidden HTML marker.
354+
* ``trend_dashboard.compute_trend("ledger.json")`` +
355+
``render_html(trend)`` — daily pass-rate / duration / SVG chart.
356+
357+
Orchestration & DX
358+
==================
359+
360+
* ``action_templates.render_template("login_basic", {...})`` —
361+
built-in templates: ``login_basic``, ``accept_cookies``,
362+
``switch_locale``, ``close_modal``; ``register_template`` for custom.
363+
* ``sharding.diff_shard.select_for_changed(candidates, base_ref="main")``
364+
— git-diff-aware test selection.
365+
* ``watch_mode.watch_loop(directory, on_change=callback)`` — polled file
366+
watcher with snapshot diff.
367+
* ``k8s_runner.render_job_manifests(ShardJobConfig(...))`` /
368+
``render_job_yaml(config)`` — one ``batch/v1 Job`` per shard.
369+
* ``perf_metrics.budgets`` — ``load_budgets("budgets.json")`` +
370+
``evaluate_metrics(route, metrics, budgets)`` +
371+
``assert_within_budget(result)``.
372+
373+
MCP server
374+
==========
375+
376+
WebRunner ships a Model Context Protocol server so MCP-aware clients can
377+
drive it over JSON-RPC stdio:
378+
379+
.. code-block:: shell
380+
381+
python -m je_web_runner.mcp_server
382+
383+
Default tools registered: ``webrunner_lint_action``,
384+
``webrunner_locator_strength``, ``webrunner_render_template``,
385+
``webrunner_compute_trend``, ``webrunner_validate_response``,
386+
``webrunner_summary_markdown``, ``webrunner_diff_shard``,
387+
``webrunner_render_k8s``, ``webrunner_partition_shard``.
388+
389+
Custom tools register via ``McpServer.register(Tool(...))``; the server
390+
implements MCP ``2024-11-05`` (``initialize`` / ``tools/list`` /
391+
``tools/call`` / ``resources/list`` / ``ping`` / ``shutdown``).

docs/source/Zh/doc/extended_features/extended_features_doc.rst

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,84 @@ WebRunner 不打包任何 LLM client。透過 ``set_llm_callable(fn)`` 註冊任
189189

190190
* ``suggest_locator`` — 自我修復定位的 LLM 後援
191191
* ``generate_actions_from_prompt`` — 自然語言生成 action 草稿
192+
* ``explain_failure`` — 從失敗素材生成 RCA:``{likely_cause, evidence,
193+
next_steps, confidence}``
194+
195+
可靠度
196+
======
197+
198+
* ``adaptive_retry.run_with_retry`` — 依 classifier 結果決定是否重試
199+
* ``linter.locator_strength.score_locator`` — locator 0–100 分強度評估
200+
* ``smart_wait.wait_for_fetch_idle`` / ``wait_for_spa_route_stable`` —
201+
比 ``time.sleep`` 智慧的 SPA 等待
202+
* ``throttler.throttle("svc")`` — 跨 shard 的檔案信號量
203+
204+
可觀測性
205+
========
206+
207+
* ``observability.timeline.build`` — 合併 OTel span / console / 網路回應
208+
* ``failure_bundle.FailureBundle`` — 失敗素材打包成可重現的 zip
209+
* ``memory_leak.detect_growth`` — heap 線性回歸找洩漏
210+
* ``trace_recorder.TraceRecorder`` — Playwright tracing 包裝
211+
* ``csp_reporter.CspViolationCollector`` — CSP 違規監聽
212+
213+
測試資料 / 確定性
214+
=================
215+
216+
* ``snapshot.fixture_record.FixtureRecorder`` — 第一次跑記錄、之後重放
217+
* ``database.fixtures`` — YAML/JSON → SQLAlchemy 連線 seed
218+
219+
API 與合約
220+
==========
221+
222+
* ``api_mock.MockRouter`` — Playwright route() 上層的宣告式 mock
223+
* ``contract_testing`` — JSON Schema 子集 + OpenAPI ``$ref`` 解析
224+
* ``graphql.GraphQLClient`` — GraphQL HTTP client + ``extract_field``
225+
* ``mock_services`` — SMTP / OAuth / S3 in-process mock
226+
227+
安全測試
228+
========
229+
230+
* ``header_tampering.HeaderTampering`` — 改 cookie/referer/origin
231+
* ``license_scanner`` — SPDX / 已知授權字樣偵測
232+
* ``cookie_consent.ConsentDismisser`` — 自動關閉 GDPR 彈窗
233+
234+
裝置 / 區域
235+
===========
236+
237+
* ``device_emulation`` — iPhone / Pixel / iPad / Desktop 預設
238+
* ``geo_locale`` — geolocation / timezone / locale 一次設定
239+
* ``multi_tab.TabChoreographer`` — 多分頁腳本連動
240+
* ``webauthn.enable_virtual_authenticator`` — passkey / FIDO2 模擬
241+
242+
報告 / CI
243+
=========
244+
245+
* ``pr_comment.post_or_update_comment`` — GitHub PR 自動留言(idempotent)
246+
* ``trend_dashboard.compute_trend`` — ledger 日趨勢 + SVG 圖表
247+
248+
編排 / 開發者體驗
249+
=================
250+
251+
* ``action_templates`` — login_basic / accept_cookies / switch_locale /
252+
close_modal 等可重用樣板
253+
* ``sharding.diff_shard`` — 只跑 git diff 影響到的測試
254+
* ``watch_mode.watch_loop`` — 檔案變動監看
255+
* ``k8s_runner.render_job_manifests`` — 每個 shard 一個 batch/v1 Job
256+
* ``perf_metrics.budgets`` — 每路由 FCP/LCP/CLS 預算
257+
258+
MCP server
259+
==========
260+
261+
提供 Model Context Protocol stdio JSON-RPC server:
262+
263+
.. code-block:: shell
264+
265+
python -m je_web_runner.mcp_server
266+
267+
預設工具:``webrunner_lint_action`` / ``webrunner_locator_strength`` /
268+
``webrunner_render_template`` / ``webrunner_compute_trend`` /
269+
``webrunner_validate_response`` / ``webrunner_summary_markdown`` /
270+
``webrunner_diff_shard`` / ``webrunner_render_k8s`` /
271+
``webrunner_partition_shard``。可透過 ``McpServer.register(Tool(...))``
272+
擴充自訂工具,協定版本 ``2024-11-05``。
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""WebRunner MCP server: expose WR_* actions over the Model Context Protocol."""
2+
from je_web_runner.mcp_server.server import (
3+
McpServer,
4+
McpServerError,
5+
build_default_tools,
6+
serve_stdio,
7+
)
8+
9+
__all__ = ["McpServer", "McpServerError", "build_default_tools", "serve_stdio"]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""Entry point so ``python -m je_web_runner.mcp_server`` starts the stdio server."""
2+
from je_web_runner.mcp_server.server import serve_stdio
3+
4+
5+
if __name__ == "__main__":
6+
serve_stdio()

0 commit comments

Comments
 (0)