[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-80942":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":13,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":13,"stars7d":16,"stars30d":16,"stars90d":15,"forks30d":15,"starsTrendScore":17,"compositeScore":18,"rankGlobal":10,"rankLanguage":10,"license":19,"archived":20,"fork":20,"defaultBranch":21,"hasWiki":22,"hasPages":20,"topics":23,"createdAt":10,"pushedAt":10,"updatedAt":34,"readmeContent":35,"aiSummary":36,"trendingCount":15,"starSnapshotCount":15,"syncStatus":16,"lastSyncTime":37,"discoverSource":38},80942,"mk-qa-master","kao273183\u002Fmk-qa-master","kao273183","AI 測試大師 — MCP server driving pytest \u002F Jest \u002F Cypress \u002F Go \u002F Maestro. Analyze, generate, run, advise. Web + Mobile (iOS\u002FAndroid\u002FBlueStacks).","https:\u002F\u002Fmcp.chenjundigital.com",null,"Python",34,1,32,0,2,3,0.9,"MIT License",false,"main",true,[24,25,26,27,28,29,30,31,32,33],"maestro","mcp","mcp-server","mobile-testing","model-context-protocol","playwright","pytest","qa","test-automation","testing","2026-06-12 02:04:08","\u003Cp align=\"center\">\n  \u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002Fkao273183\u002Fmk-qa-master\u002Fmain\u002Fassets\u002Flogo.png\" alt=\"mk-qa-master logo\" width=\"180\" \u002F>\n\u003C\u002Fp>\n\n\u003Ch1 align=\"center\">MK QA Master\u003C\u002Fh1>\n\n\u003Cp align=\"center\">\n  \u003Cem>AI 測試大師 — your AI QA loop, from analyze to advise.\u003C\u002Fem>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Cstrong>English\u003C\u002Fstrong> · \u003Ca href=\"README.zh-TW.md\">繁體中文\u003C\u002Fa>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Ca href=\"https:\u002F\u002Fpypi.org\u002Fproject\u002Fmk-qa-master\u002F\">\u003Cimg src=\"https:\u002F\u002Fimg.shields.io\u002Fpypi\u002Fv\u002Fmk-qa-master.svg?logo=pypi&logoColor=white&color=3775A9\" alt=\"PyPI\" \u002F>\u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fkao273183\u002Fmk-qa-master\u002Factions\u002Fworkflows\u002Fci.yml\">\u003Cimg src=\"https:\u002F\u002Fgithub.com\u002Fkao273183\u002Fmk-qa-master\u002Factions\u002Fworkflows\u002Fci.yml\u002Fbadge.svg\" alt=\"CI\" \u002F>\u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fglama.ai\u002Fmcp\u002Fservers\u002Fkao273183\u002Fmk-qa-master\">\u003Cimg src=\"https:\u002F\u002Fglama.ai\u002Fmcp\u002Fservers\u002Fkao273183\u002Fmk-qa-master\u002Fbadges\u002Fscore.svg\" alt=\"Glama score\" \u002F>\u003C\u002Fa>\n  \u003Ca href=\"LICENSE\">\u003Cimg src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FLicense-MIT-yellow.svg\" alt=\"License: MIT\" \u002F>\u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fwww.buymeacoffee.com\u002Fminikao\">\u003Cimg src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FBuy%20Me%20a%20Coffee-Support-FFDD00?logo=buy-me-a-coffee&logoColor=black\" alt=\"Buy Me a Coffee\" \u002F>\u003C\u002Fa>\n\u003C\u002Fp>\n\n> Universal MCP server for running tests across pytest \u002F Jest \u002F Cypress \u002F Go,\n> with built-in DOM analyzer, run history, and a self-improvement coach.\n\nA **Model Context Protocol** server that lets Claude Desktop \u002F Cursor \u002F any\nMCP client drive your test suite end-to-end: run tests, inspect failures\n(screenshot + video + trace), analyze a live URL to draft test cases, and —\nafter each run — produce a prioritized action plan telling you exactly what\nto fix or write next.\n\n| `QA_RUNNER` | Framework | Language | Target |\n|---|---|---|---|\n| `pytest` \u002F `pytest-playwright` \u002F `playwright` | pytest + Playwright | Python | Web |\n| `jest` | Jest | JavaScript | Web |\n| `cypress` | Cypress | JavaScript | Web |\n| `go` \u002F `go-test` | `go test` | Go | Backend |\n| `maestro` \u002F `mobile` | Maestro | YAML | iOS + Android |\n| `schemathesis` \u002F `api` | Schemathesis | OpenAPI 3.x \u002F Swagger 2.0 | API (since v0.6.0) |\n| `newman` \u002F `postman` | Newman | Postman collection v2.x | API (since v0.6.1) |\n\nFull design notes: [`docs\u002Fframework.md`](docs\u002Fframework.md).\n\n---\n\n## What's in the box\n\n- **Run tests** across multiple frameworks (web + mobile + API) via a single MCP surface\n- **Mobile via Maestro** (since v0.3.0): same MCP tools, iOS Simulator \u002F\n  Android Emulator \u002F real device; YAML flows; cross-platform without rewrites\n- **Native API testing — two runners** (since v0.6.0 \u002F v0.6.1): two peers\n  now share the API testing slot, each fed by the artifact your team\n  already maintains.\n  - **Schemathesis** (`QA_RUNNER=schemathesis`, since v0.6.0): point at an\n    OpenAPI 3.x \u002F Swagger 2.0 URL or `file:\u002F\u002F` schema and get property-based\n    fuzzed tests covering status codes, response schemas, content types,\n    and `5xx`-on-fuzz violations.\n  - **Newman** (`QA_RUNNER=newman`, since v0.6.1): point at an exported\n    Postman 2.x collection (plus optional environment \u002F globals files) and\n    Newman replays every request, runs the embedded `pm.test(...)`\n    assertions, and returns one mk-qa-master nodeid per assertion. Newman\n    is a **system prerequisite** (`npm install -g newman`) — it's an npm\n    package, not pip, so it doesn't ship as a Python extra.\n\n  Both drop into the same MCP tool surface as the web \u002F mobile runners, and\n  both feed the same `report.json` \u002F history \u002F flake \u002F optimizer pipeline.\n  Existing API tests written in pytest+`httpx`, Jest+`supertest`, Cypress\n  `cy.request()`, or Go `net\u002Fhttp\u002Fhttptest` still ride their existing\n  runners — no migration needed. Pact provider verification stays on the\n  v0.7.0 conditional roadmap.\n- **Failure artifacts**: screenshot (base64-inlined), video, Playwright\n  trace.zip \u002F Maestro recordings\n- **Run history**: every run snapshotted; HTML report shows a sparkline trend\n- **DOM \u002F Screen analyzer** — `analyze_url` for web (forms \u002F nav \u002F dialogs \u002F\n  CTAs + the API endpoints the page hits) and `analyze_screen` for mobile\n  (`maestro hierarchy` → form \u002F cta \u002F tab_bar modules)\n- **Smart test generation** (`generate_test`): hand it an analyzer module\n  and it writes a runnable Playwright `.py` or Maestro `.yaml` with concrete\n  selectors, not `# TODO` stubs\n- **Auto-retry flakes** — pytest side via `pytest-rerunfailures`; Maestro\n  side via custom retry wrapper (no native `--reruns`); flaky tests\n  surfaced separately from real failures\n- **Self-improvement coach** (`get_optimization_plan`): post-run analysis\n  across three lenses — suite quality, MCP usability, AI generation effectiveness\n- **JUnit XML output** for CI integrations (GitHub Actions \u002F Jenkins \u002F GitLab)\n\n---\n\n## Install\n\nTwo paths — pick the one that matches how you'll use it.\n\n### A. Run via `uvx` (zero install, recommended for end users)\n\nAdd `mk-qa-master` to your client config without installing anything globally; [`uv`](https:\u002F\u002Fdocs.astral.sh\u002Fuv\u002F) fetches and runs it in an ephemeral environment per session:\n\n```json\n{\n  \"mcpServers\": {\n    \"mk-qa-master\": {\n      \"command\": \"uvx\",\n      \"args\": [\"mk-qa-master\"],\n      \"env\": { \"QA_RUNNER\": \"pytest\", \"QA_PROJECT_ROOT\": \"\u002Fpath\u002Fto\u002Fyour-test-project\" }\n    }\n  }\n}\n```\n\nThat's the whole setup. First call downloads the package; subsequent calls are cached. Switching versions: `uvx mk-qa-master@0.4.1 ...`.\n\n### B. Install into a project venv (for contributors \u002F hacking)\n\n```bash\npip install mk-qa-master       # or: pip install -e . from a clone\nplaywright install                # only if you use pytest-playwright\npip install pytest-rerunfailures  # optional, enables auto-retry\n```\n\nThen point your client config at the same Python interpreter:\n\n```json\n\"command\": \"\u002Fpath\u002Fto\u002F.venv\u002Fbin\u002Fpython\",\n\"args\": [\"-m\", \"mk_qa_master.server\"]\n```\n\n### Runner-specific prerequisites\n\n| `QA_RUNNER` | You also need |\n|---|---|\n| `pytest` \u002F `pytest-playwright` | `pip install pytest-playwright` + `playwright install chromium` |\n| `jest` | A Node project with `jest` installed (`npm i -D jest`) |\n| `cypress` | A Node project with `cypress` installed (`npm i -D cypress`) |\n| `go` | Go toolchain on PATH |\n| `maestro` | [Maestro CLI](https:\u002F\u002Fmaestro.mobile.dev\u002F) + a booted simulator \u002F emulator \u002F device (or BlueStacks reachable via `adb connect`) |\n| `schemathesis` \u002F `api` | `pip install 'mk-qa-master[api]'` (pulls in `schemathesis>=3.0,\u003C4`) |\n| `newman` \u002F `postman` | `npm install -g newman` (Newman is an npm package, not pip — no extra to install) |\n\n\n## API testing (`QA_RUNNER=schemathesis`)\n\nPoint the runner at any OpenAPI 3.x \u002F Swagger 2.0 schema and Schemathesis\ngenerates property-based test cases per operation — covering response\nschema conformance, status code conformance, content-type checks, and\n`5xx`-on-fuzz. Results flow through the same `report.json` \u002F history \u002F\nflake \u002F optimizer pipeline as your UI tests.\n\nEnd-to-end walkthrough lives in [`docs\u002Fwalkthrough-api.md`](docs\u002Fwalkthrough-api.md);\na self-contained 3-endpoint sample lives at\n[`examples\u002Fsample_api_project\u002F`](examples\u002Fsample_api_project\u002F).\n\n### 5-line config\n\n```jsonc\n\"env\": {\n  \"QA_RUNNER\": \"schemathesis\",\n  \"QA_OPENAPI_URL\": \"https:\u002F\u002Fapi.example.com\u002Fopenapi.json\"\n}\n```\n\n### Environment variables\n\n| Variable | Required | Default | What it does |\n|---|---|---|---|\n| `QA_OPENAPI_URL` | yes | — | OpenAPI URL. `http(s):\u002F\u002F...` for live schemas, `file:\u002F\u002F...` for local files. **Plain filesystem paths are not accepted** — they need the `file:\u002F\u002F` prefix. |\n| `QA_SCHEMATHESIS_CHECKS` | no | `all` | Comma-separated subset: `response_schema_conformance,status_code_conformance,not_a_server_error,content_type_conformance,response_headers_conformance`. |\n| `QA_SCHEMATHESIS_AUTH` | no | — | Authorization header value. Sent as `-H \"Authorization: \u003Cvalue>\"`. Never logged; redacted from archived reports. |\n| `QA_SCHEMATHESIS_MAX_EXAMPLES` | no | `20` | Hypothesis examples per operation. Higher = deeper fuzz, slower run. |\n| `QA_SCHEMATHESIS_DRY_RUN` | no | `0` | Set to `1` to plan-without-HTTP — useful for safety preview against production, or CI smoke against a schema-only artifact. |\n| `QA_NO_REDACT` | no | `0` | Disables secret redaction in archived reports. Default redacts `Authorization: Bearer …`, `\"password\": …`, `\"token\" \u002F \"api_key\" \u002F \"secret\" \u002F \"access_token\" \u002F \"refresh_token\": …`. |\n\nStandard `QA_TIMEOUT_SECONDS` still applies (default 600s).\n\n\n## API testing (`QA_RUNNER=newman`)\n\nPoint the runner at any exported Postman 2.x collection and Newman 6.x\nreplays every request, runs the embedded `pm.test(...)` assertions, and\nreturns one mk-qa-master \"test\" per assertion. Results flow through the\nsame `report.json` \u002F history \u002F flake \u002F optimizer pipeline as the\nSchemathesis and UI runners.\n\n**System prerequisite**: Newman ships via npm, not pip. Install once:\n\n```bash\nnpm install -g newman\n```\n\nThere's no `pip install 'mk-qa-master[postman]'` extra — the runner\njust shells out to the `newman` binary on PATH. If it's missing, the\nrunner raises a clear `ImportError` pointing at the npm install line.\n\nThe same 3-endpoint **Library API** that the OpenAPI sample targets\nships as a Postman collection at\n[`examples\u002Fsample_api_project\u002Fpostman-collection.json`](examples\u002Fsample_api_project\u002Fpostman-collection.json) —\npair it with `prism mock examples\u002Fsample_api_project\u002Fopenapi.yaml` for\na fully self-contained dev loop, or point at your own staging server.\n\n### 5-line config\n\n```jsonc\n\"env\": {\n  \"QA_RUNNER\": \"newman\",\n  \"QA_POSTMAN_COLLECTION\": \"\u002Fabsolute\u002Fpath\u002Fto\u002Fyour-collection.json\"\n}\n```\n\n### Environment variables\n\n| Variable | Required | Default | What it does |\n|---|---|---|---|\n| `QA_POSTMAN_COLLECTION` | yes | — | Plain filesystem path to a Postman 2.x collection JSON. **No `file:\u002F\u002F` prefix** — Newman doesn't need scheme disambiguation since collections are always local artifacts. |\n| `QA_POSTMAN_ENVIRONMENT` | no | — | Plain path to a Postman environment file (`-e \u003Cpath>`). Provides values for `{{var_name}}` placeholders in the collection. |\n| `QA_POSTMAN_GLOBALS` | no | — | Plain path to a Postman globals file (`-g \u003Cpath>`). Same shape as the environment, globally scoped. |\n| `QA_POSTMAN_ITERATIONS` | no | `1` | Replay the whole collection N times (`-n \u003CN>`). Useful for soak tests and flake detection. |\n| `QA_POSTMAN_FOLDER` | no | — | CSV of Postman folder names to restrict the run to (repeated `--folder` flags). `run_failed` also uses folder-scoping when failures cluster in known folders. |\n| `QA_POSTMAN_TIMEOUT_REQUEST_MS` | no | `30000` | Per-request HTTP timeout in milliseconds (`--timeout-request`). Distinct from `QA_TIMEOUT_SECONDS`, which caps the whole subprocess. |\n| `QA_NO_REDACT` | no | `0` | Same redaction policy as the Schemathesis runner — disable only for short debug sessions. |\n\nStandard `QA_TIMEOUT_SECONDS` still applies (default 600s).\n\n\n## AI Visual Challenge Solver (v0.7.0)\n\n> *When backend bypass isn't an option: Claude looks at the CAPTCHA, mk-qa-master does the clicks.*\n\nSupports reCAPTCHA v2 (since v0.7.0) and hCaptcha (since v0.7.1).\n\nThe first capability in the family where the AI client's vision is\nload-bearing, not optional. Two new MCP tools\n(`inspect_visual_challenge` + `solve_visual_challenge`) detect a\nreCAPTCHA v2 or hCaptcha image-grid challenge on the active Playwright\npage, screenshot it for the multimodal AI client, accept the\ntile-selection the AI returns, and execute the click chain. The\nrunner is the eyes and hands; the AI client (Claude \u002F Cursor \u002F Gemini\n\u002F GPT-4o) is the actual solver.\n\n### When to use this — Tier 1 vs Tier 3\n\nThe built-in QA knowledge layer (`get_qa_context section=\"CAPTCHA\"`)\ncodifies three tiers. Reach for them in order:\n\n| Tier | Approach | When |\n|---|---|---|\n| **1 — bypass** | reCAPTCHA test keys, feature flags, IP allowlist, test-mode headers | Default. Covers ~90% of cases. |\n| **2 — degrade** | Mark as `external_dependency`, skip downstream assertions | When you can't change the backend but the test isn't about the CAPTCHA itself. |\n| **3 — AI visual judgment** | This feature. | Only when 1 + 2 don't fit (client sites with authorization but no backend access, staging that mirrors prod CAPTCHA, mobile webviews where IP allowlist isn't reachable). |\n\n### Consent gate\n\nThe solver does nothing until you explicitly opt in. Two env vars drive\nit:\n\n| Variable | Required | Default | What it does |\n|---|---|---|---|\n| `QA_VISUAL_CHALLENGE_CONSENT` | yes | `false` | Must be set to `true` for either tool to function. Without it, both tools return a `consent_required` error carrying the full legal disclaimer (the AI client surfaces this to the user). |\n| `QA_VISUAL_CHALLENGE_AUTHORIZED_DOMAINS` | no (recommended) | — | Comma-separated allowlist of domains where the tool may operate. When SET, refuses any other domain. When UNSET, warn-only — proceeds but stamps the response with a warning telling you to set one. **Recommended** for shared CI \u002F multi-tenant environments. |\n| `QA_VISUAL_CHALLENGE_TIMEOUT` | no | `120` | Wall-clock budget in seconds for the inspect→solve cycle. Honors `QA_TIMEOUT_SECONDS` as a hard ceiling. |\n\n### Quick start\n\n```jsonc\n\"env\": {\n  \"QA_RUNNER\": \"pytest\",\n  \"QA_PROJECT_ROOT\": \"\u002Fpath\u002Fto\u002Fproject\",\n  \"QA_VISUAL_CHALLENGE_CONSENT\": \"true\",\n  \"QA_VISUAL_CHALLENGE_AUTHORIZED_DOMAINS\": \"client-staging.example.com\"\n}\n```\n\nThen, when a `run_tests` call surfaces an `external_dependency`\nfailure that points at a CAPTCHA, the AI client can escalate:\n\n```\nmk-qa-master.inspect_visual_challenge()  # screenshot + tile grid\n→ AI vision picks tiles [0, 4, 7]\nmk-qa-master.solve_visual_challenge(\n    challenge_id=\"...\", selected_tile_indices=[0, 4, 7], confirm=true,\n)\n→ status: \"passed\", token: \"...\", hint: \"CAPTCHA verified. Resume your test.\"\n```\n\nFull walkthrough lives in [`docs\u002Fwalkthrough-visual-challenge.md`](docs\u002Fwalkthrough-visual-challenge.md).\nPRD: [`docs\u002Fprd-v0.7-visual-challenge.md`](docs\u002Fprd-v0.7-visual-challenge.md).\n\n### Hard-stop domains\n\nRegardless of consent or allowlist, the solver refuses to operate on\nknown third-party identity providers (`accounts.google.com`,\n`login.microsoftonline.com`, `id.apple.com`, `facebook.com`,\n`login.live.com`, etc.). No legitimate QA scenario justifies a\nCAPTCHA solver against someone else's login portal.\n\n### Privacy\n\nNo screenshot retention beyond the active inspect→solve cycle.\nTelemetry logs the boolean outcome only — never the screenshot, never\nthe challenge text, never the tile selection. The 5-minute LRU cache\nholds at most 10 outstanding challenges per process and never touches\ndisk.\n\n### Success rate caveat\n\nThe AI client's vision model does the actual judging — Claude Sonnet\n4, GPT-4o, and Gemini 2.5 all ship with native vision but their\naccuracy on a 3x3 reCAPTCHA varies. Plan for at least one retry per\nchallenge (reCAPTCHA gives you three before locking out). `get_telemetry`\nwill eventually surface aggregate pass-rate so you can size that\nexpectation per-client.\n\n**Scope**: reCAPTCHA v2 image-grid only in v0.7.0. hCaptcha lands in\nv0.7.1. reCAPTCHA v3 \u002F Cloudflare Turnstile are permanently out of\nscope — they don't surface a visible challenge to inspect.\n\n\n## OWASP API Security scanning (v0.8.0)\n\n> *Schemathesis catches correctness drift. v0.8.0 adds the layer that\n> catches the security drift hiding behind a passing schema.*\n\nv0.8.0 ships an **OWASP API Security Top 10 (2023) rule-based scanner**\nas a new MCP tool: `run_api_security_scan`. It loads an OpenAPI 3.x\nspec, walks each (path × method), and dispatches five purely-HTTP-\nobservable rules:\n\n| OWASP # | Rule | Severity when triggered |\n|---|---|---|\n| API1 | **BOLA \u002F IDOR** — alice's token reads bob's object via path-id tampering | CRITICAL |\n| API2 | **Broken Authentication** — server accepts `alg:none`, malformed, or wrong-signature JWTs | MEDIUM \u002F HIGH \u002F CRITICAL by probe |\n| API3 | **Mass Assignment** — server persists dangerous extra fields like `role: admin`, `is_verified: true` | HIGH |\n| API5 | **Function-Level Authz** — non-admin user accesses admin-shaped endpoints | HIGH |\n| API8 | **Security Misconfiguration** — missing HSTS\u002FCSP\u002FX-Frame headers, wildcard CORS with credentials | LOW \u002F MEDIUM \u002F HIGH |\n\nAPI4 (rate limit DoS risk), API6 (business flow modeling), API7\n(SSRF callback infra), API9 (prod recon), API10 (upstream APIs) are\n**deferred** — see [`docs\u002Fprd-v0.8-api-security.md`](docs\u002Fprd-v0.8-api-security.md) §3.\n\n### Consent + authorization gates\n\nMirrors the v0.7 visual-challenge consent model:\n\n| Variable | Required | What it does |\n|---|---|---|\n| `QA_API_SECURITY_CONSENT` | yes | Must be `true`. Without it, returns `consent_required`. |\n| `QA_API_SECURITY_AUTHORIZED_DOMAINS` | yes for external hosts | Comma-separated allowlist. Localhost \u002F 127.0.0.1 are implicitly authorized. |\n\nThe `mass_assignment` rule mutates server state — it's **excluded from\ndefault categories**. Callers must opt in:\n`categories=[\"headers\", \"broken_auth\", \"bola\", \"function_authz\", \"mass_assignment\"]`.\n\n### Quick start\n\n```jsonc\n\"env\": {\n  \"QA_RUNNER\": \"pytest\",\n  \"QA_PROJECT_ROOT\": \"\u002Fpath\u002Fto\u002Fproject\",\n  \"QA_API_SECURITY_CONSENT\": \"true\",\n  \"QA_API_SECURITY_AUTHORIZED_DOMAINS\": \"api.staging.example.com\"\n}\n```\n\nThen ask the AI client to scan:\n\n```\nmk-qa-master.run_api_security_scan(\n    spec_url=\"https:\u002F\u002Fapi.staging.example.com\u002Fopenapi.yaml\",\n    auth={\n        \"token\": \"alice's bearer token\",\n        \"alt_user_token\": \"bob's bearer token\",\n        \"bola_test_ids\": {\"user_a\": [101, 103], \"user_b\": [202]}\n    },\n    severity_threshold=\"medium\"\n)\n```\n\nReturns the v0.8 security report block:\n\n```jsonc\n{\n  \"scan_id\": \"a3f8d1c9b7e2\",\n  \"spec_url\": \"...\",\n  \"base_url\": \"https:\u002F\u002Fapi.staging.example.com\",\n  \"categories_run\": [\"headers\", \"broken_auth\", \"bola\", \"function_authz\"],\n  \"rules_ran\": [\"OWASP-API8-Headers\", \"OWASP-API2-BrokenAuth\", ...],\n  \"ops_scanned\": 23,\n  \"severity_threshold\": \"medium\",\n  \"findings\": [\n    {\n      \"rule_id\": \"OWASP-API1-BOLA-CrossUserDataExposure\",\n      \"severity\": \"critical\",\n      \"endpoint\": \"GET \u002Forders\u002F{id}\",\n      \"title\": \"user_a can read user_b's object id=202 — missing object-level authorization check\",\n      \"evidence\": {\"actor\": \"user_a\", \"target_owner\": \"user_b\", \"target_id\": 202, \"probed_path\": \"\u002Forders\u002F202\", \"status_code\": 200, ...},\n      \"remediation_hint\": \"Compare the caller's identity to the object's owner before returning...\"\n    },\n    ...\n  ],\n  \"summary\": {\"total\": 7, \"by_severity\": {\"critical\": 2, \"high\": 4, \"medium\": 1, \"low\": 0, \"info\": 0}}\n}\n```\n\n### The Tier 1 ground truth\n\n`examples\u002Fsample_vulnerable_api\u002F` ships a deliberately-vulnerable\nFlask app where every in-scope OWASP category has a vuln\u002Fsafe\nendpoint pair. Run it locally to see what each rule looks like in\naction:\n\n```bash\ncd examples\u002Fsample_vulnerable_api\npip install -r requirements.txt\npython app.py  # binds 127.0.0.1:5099\n# Then from another shell, point run_api_security_scan at\n# http:\u002F\u002F127.0.0.1:5099 + the bundled openapi.yaml\n```\n\nThe scanner finds all 5 categories on `\u002Fvuln\u002F*` and produces zero\nfalse positives on `\u002Fsafe\u002F*`. That property is enforced by the\n[Tier 1 dogfood tests](examples\u002Fsample_vulnerable_api\u002Ftests\u002F) on\nevery PR.\n\n### Security note\n\nThe scanner runs adversarial test cases. Do not point it at\nproduction systems you don't own, and do not point it at any system\nwhere you don't have authorization. The two env vars above are the\ncontract.\n\nPRD: [`docs\u002Fprd-v0.8-api-security.md`](docs\u002Fprd-v0.8-api-security.md).\nThe earlier v0.8 mobile attempt was parked — see\n[`docs\u002Fv0.8-mobile-postmortem.md`](docs\u002Fv0.8-mobile-postmortem.md) for\nwhat we learned and how it shaped the API-security PRD's testing\ngates.\n\n\n## Use as a Claude Code \u002F Codex \u002F Hermes \u002F OpenClaw skill (v0.9.0)\n\n> *Same skill folder loads in four different agent hosts via the\n> [agentskills.io](https:\u002F\u002Fagentskills.io) convention.*\n\nv0.9.0 packages mk-qa-master as a **cross-host agent skill** in addition\nto its MCP-server form. The `skills\u002Fmk-qa-master\u002F` folder is the single\nsource of truth — the same `SKILL.md`, slash commands, and reference\ndocs load into:\n\n- **Claude Code** — via `.claude-plugin\u002Fplugin.json` (this repo is a\n  plugin marketplace).\n- **OpenAI Codex** — via `.codex-plugin\u002Fplugin.json` (Codex reads Claude-\n  style marketplaces).\n- **OpenClaw** — install from local checkout: `openclaw plugins install\n  \u002Fpath\u002Fto\u002Fmk-qa-master`.\n- **Hermes Agent** — symlink the skill folder into `~\u002F.hermes\u002Fskills\u002F`.\n\n### Quick install (Claude Code)\n\n```text\n# Inside Claude Code:\n\u002Fplugin marketplace add kao273183\u002Fmk-qa-master\n\u002Fplugin install mk-qa-master@mk-qa-master\n```\n\nRestart Claude Code so the skill registers. Then any QA testing prompt\nauto-activates the skill — or explicitly invoke a slash command:\n\n```\n\u002Fmk-qa-master:run-tests login\n\u002Fmk-qa-master:generate https:\u002F\u002Fstaging.example.com\n\u002Fmk-qa-master:api-security https:\u002F\u002Fapi.staging.example.com\u002Fopenapi.yaml\n```\n\n### What the skill does\n\nThe skill is a single-file operating contract that teaches the host how\nto drive mk-qa-master's 19 MCP tools coherently. It encodes:\n\n- **When to auto-activate** — phrases like \"run my tests\", \"why did this\n  test fail\", \"scan this API for OWASP issues\" trigger it.\n- **Five flows** — run tests \u002F generate tests \u002F debug failures \u002F solve\n  CAPTCHAs \u002F scan APIs.\n- **Hard rules** — surface consent errors verbatim, don't silently\n  re-run with relaxed filters, confirm before destructive runs.\n\nFull reference at [`skills\u002Fmk-qa-master\u002FSKILL.md`](skills\u002Fmk-qa-master\u002FSKILL.md).\n\n### Why a skill on top of an MCP server?\n\nThe MCP server makes the 19 tools **callable** by any client. The skill\nmakes them **discoverable + governed**: it gives the host's skill router\nenough context to decide *when* to use the tools and *which flow* to\nfollow. Inspired by [microsoft\u002FWebwright](https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FWebwright),\nwhich uses the same pattern.\n\n\n## Wire into Claude Desktop (legacy MCP-only path)\n\nIf you prefer the bare MCP-server wiring (no plugin\u002Fskill layer), copy\n`examples\u002Fconfigs\u002Fclaude_desktop_config.example.json` to:\n\n- **macOS**: `~\u002FLibrary\u002FApplication Support\u002FClaude\u002Fclaude_desktop_config.json`\n- **Windows**: `%APPDATA%\\Claude\\claude_desktop_config.json`\n\nTwo environment variables drive the runtime:\n\n| Variable | Example | What it does |\n|---|---|---|\n| `QA_RUNNER` | `pytest` \u002F `jest` \u002F `cypress` \u002F `go` \u002F `maestro` \u002F `schemathesis` \u002F `newman` | Selects which test framework |\n| `QA_PROJECT_ROOT` | `\u002Fpath\u002Fto\u002Fyour\u002Fproject` | Points at the project under test |\n| `QA_ANDROID_HOST` *(optional)* | `127.0.0.1:5555` | Remote-ADB endpoint for **BlueStacks** \u002F Genymotion \u002F Nox \u002F cloud Android. When set, the Maestro runner auto-runs `adb connect \u003Chost>` before each test \u002F `analyze_screen` call. Requires `adb` on PATH. |\n| `QA_TIMEOUT_SECONDS` *(optional)* | `600` (default) | Hard ceiling on any single subprocess invocation (pytest \u002F jest \u002F cypress \u002F go test \u002F maestro). Returns `exit_code=124` with a `[TIMEOUT…]` tag in stderr when exceeded, so the AI client can react cleanly instead of hanging the MCP server forever. |\n\n### Per-runner snippet\n\n**pytest-playwright**:\n```json\n\"env\": { \"QA_RUNNER\": \"pytest\", \"QA_PROJECT_ROOT\": \"\u002Fpath\u002Fto\u002Fpython-project\" }\n```\n\n**Jest**:\n```json\n\"env\": { \"QA_RUNNER\": \"jest\", \"QA_PROJECT_ROOT\": \"\u002Fpath\u002Fto\u002Fnode-project\" }\n```\n\n**Cypress**:\n```json\n\"env\": { \"QA_RUNNER\": \"cypress\", \"QA_PROJECT_ROOT\": \"\u002Fpath\u002Fto\u002Fcypress-project\" }\n```\n\n**Go test**:\n```json\n\"env\": { \"QA_RUNNER\": \"go\", \"QA_PROJECT_ROOT\": \"\u002Fpath\u002Fto\u002Fgo-project\" }\n```\n\n**Schemathesis (API)**:\n```json\n\"env\": {\n  \"QA_RUNNER\": \"schemathesis\",\n  \"QA_OPENAPI_URL\": \"https:\u002F\u002Fapi.example.com\u002Fopenapi.json\"\n}\n```\n\n**Newman (Postman)**:\n```json\n\"env\": {\n  \"QA_RUNNER\": \"newman\",\n  \"QA_POSTMAN_COLLECTION\": \"\u002Fabsolute\u002Fpath\u002Fto\u002Fcollection.json\"\n}\n```\n\n---\n\n## Other MCP clients\n\nMCP is an open protocol — this server isn't Claude-only. The same Python\nprocess talks to any MCP client over JSON-RPC stdio. What differs across\nclients is (1) the config file format and (2) how reliably the underlying\nmodel auto-chains tool calls.\n\n| Client | Config | Format | Model | Tool-chain quality |\n|---|---|---|---|---|\n| Claude Desktop \u002F Cursor | `~\u002FLibrary\u002FApplication Support\u002FClaude\u002F...json` · `~\u002F.cursor\u002Fmcp.json` | JSON | Claude Opus \u002F Sonnet | Best tested |\n| **Codex CLI** | `~\u002F.codex\u002Fconfig.toml` | **TOML** | GPT-5 family | Strong (well-trained on tool chaining) |\n| **Gemini CLI** | `~\u002F.gemini\u002Fsettings.json` | JSON | Gemini 3.1 Pro \u002F Flash | Works; prefers explicit prompts (\"first analyze, then write\") |\n| Cline \u002F Continue \u002F Zed | each has its own MCP config slot | varies | varies | depends on configured model |\n\nExample configs ship in the repo:\n[`codex-config.example.toml`](examples\u002Fconfigs\u002Fcodex-config.example.toml) ·\n[`gemini-config.example.json`](examples\u002Fconfigs\u002Fgemini-config.example.json) ·\n[`claude_desktop_config.example.json`](examples\u002Fconfigs\u002Fclaude_desktop_config.example.json).\n\nCodex (TOML):\n```toml\n[mcp_servers.mk-qa-master]\ncommand = \"\u002Fpath\u002Fto\u002F.venv\u002Fbin\u002Fpython\"\nargs = [\"-m\", \"mk_qa_master.server\"]\ncwd = \"\u002Fpath\u002Fto\u002Fmk-qa-master\"\n[mcp_servers.mk-qa-master.env]\nQA_RUNNER = \"pytest\"\nQA_PROJECT_ROOT = \"\u002Fpath\u002Fto\u002Fyour-test-project\"\n```\n\nGemini (JSON, same shape as Claude Desktop):\n```json\n{\n  \"mcpServers\": {\n    \"mk-qa-master\": {\n      \"command\": \"\u002Fpath\u002Fto\u002F.venv\u002Fbin\u002Fpython\",\n      \"args\": [\"-m\", \"mk_qa_master.server\"],\n      \"cwd\": \"\u002Fpath\u002Fto\u002Fmk-qa-master\",\n      \"env\": {\n        \"QA_RUNNER\": \"pytest\",\n        \"QA_PROJECT_ROOT\": \"\u002Fpath\u002Fto\u002Fyour-test-project\"\n      }\n    }\n  }\n}\n```\n\nTool descriptions already nudge the recommended chains\n(`analyze_url → generate_test`, `get_qa_context` before generating\ndomain tests). Clients with weaker tool-selection benefit most from\nexplicit prompts that name the steps.\n\n---\n\n## Tool surface\n\nShared across all runners (some tools degrade gracefully on non-pytest runners):\n\n| Tool | Purpose |\n|---|---|\n| `get_runner_info` | Which runner is active + all available ones |\n| `list_tests` | Enumerate tests in the project |\n| `run_tests` | Run tests (filter \u002F headed \u002F browser; last two pytest-playwright only) |\n| `run_failed` | Re-run last failures (`pytest --lf`) |\n| `get_test_report` | Summary (pass \u002F fail \u002F skipped \u002F duration \u002F flaky-in-run) |\n| `get_failure_details` | Per-failure message + screenshot \u002F trace \u002F video paths |\n| `generate_test` | Test skeleton; with `module` from `analyze_url`\u002F`analyze_screen`, a *runnable* one (Playwright `.py` or Maestro `.yaml`) |\n| `auto_generate_tests` | One-shot: analyze URL → generate one test per discovered module |\n| `codegen` | Launch Playwright codegen (web) \u002F hint to `maestro studio` (mobile) |\n| `generate_html_report` | Render the latest run as self-contained HTML |\n| `get_test_history` | Last N archived run summaries (for trend \u002F flake debugging) |\n| `analyze_url` | **Web**: DOM probe → modules + selectors + candidate TCs + API endpoints + layout overflow warnings |\n| `analyze_screen` | **Mobile**: `maestro hierarchy` → form \u002F cta \u002F tab_bar modules + candidate TCs (noise-filtered) |\n| `init_qa_knowledge` \u002F `get_qa_context` | Scaffold + read the project's QA knowledge layer (methodology + domain). **Bilingual since v0.6.2** — methodology ships in English by default (`QA_LANG=en`) or Traditional Chinese (`QA_LANG=zh-tw`); same 13 sections in both, the four newest cover API testing methodology, flakiness root-cause taxonomy, test doubles (mock \u002F stub \u002F fake \u002F spy), and test data management. Domain example: [`docs\u002Fqa-knowledge-en.example.md`](docs\u002Fqa-knowledge-en.example.md) (zh-TW: [`docs\u002Fqa-knowledge.example.md`](docs\u002Fqa-knowledge.example.md)). |\n| `get_optimization_plan` | Three-layer self-improvement coach (suite \u002F MCP \u002F AI strategy) |\n| `inspect_visual_challenge` \u002F `solve_visual_challenge` | **v0.7.0** AI Visual Challenge Solver — detect a reCAPTCHA v2 image-grid challenge, screenshot it, accept the AI client's tile selection, execute the click chain. Gated by `QA_VISUAL_CHALLENGE_CONSENT=true` + per-call `confirm=true`. See the dedicated section above. |\n| `run_api_security_scan` | **v0.8.0** OWASP API Security Top 10 (2023) rule-based scanner — load an OpenAPI 3.x spec, walk path × method, dispatch 5 in-scope rules (API1 BOLA, API2 Broken Auth, API3 Mass Assignment, API5 Function-Level Authz, API8 Misconfig). Gated by `QA_API_SECURITY_CONSENT=true` + `QA_API_SECURITY_AUTHORIZED_DOMAINS`. See the dedicated section above. |\n\n### Resources\n\n| URI | What |\n|---|---|\n| `report:\u002F\u002Fhtml` | Live-rendered HTML report (dark mode, self-contained) |\n| `report:\u002F\u002Fjson` | Raw pytest-json-report JSON |\n| `report:\u002F\u002Foptimization` | Latest `optimization-plan.md` |\n\n---\n\n## Self-improvement loop\n\nAfter every run, `_archive_report()` snapshots `report.json` into\n`test-results\u002Fhistory\u002F` and writes a fresh `optimization-plan.md` covering:\n\n1. **Suite quality** — outcomes string per test (`PFPFP`); transitions → flake\n   score; 3+ identical-signature fails → broken; rerun-passed → flaky-in-run\n2. **MCP usability** — top tools, error rates, repeat-arg patterns, common\n   A→B chains (from telemetry JSONL logs)\n3. **AI strategy** — adoption rate of `generate_test` outputs, coverage gaps\n   from `analyze_url` modules with no matching test files\n\nThe plan emits prioritized actions (`high` \u002F `medium` \u002F `low`) each with\ntarget + evidence + suggestion + optional `auto_action_hint` the MCP client\ncan chain into the next tool call.\n\n---\n\n## Project layout\n\n```\nmk-qa-master\u002F\n├── pyproject.toml\n├── src\u002Fmk_qa_master\u002F\n│   ├── server.py            # MCP entry (tool routing + telemetry wrap)\n│   ├── config.py            # Paths + env vars\n│   ├── runners\u002F             # Per-framework plugins\n│   │   ├── base.py          # TestRunner abstract interface\n│   │   ├── pytest_playwright.py\n│   │   ├── jest.py\n│   │   ├── cypress.py\n│   │   └── go_test.py\n│   ├── reporters\u002F\n│   │   └── html.py          # Self-contained HTML render\n│   └── tools\u002F               # Thin shims + analyzer + optimizer + telemetry\n└── tests_project\u002F           # Example project under test\n```\n\n---\n\n## Adding a runner\n\n1. Create `src\u002Fmk_qa_master\u002Frunners\u002Fyour_runner.py`, subclass `TestRunner`,\n   implement the abstract methods\n2. Register the name in `runners\u002F__init__.py`'s `REGISTRY`\n3. Done\n\n---\n\n## End-to-end workflow\n\nThe intended pipeline — from a URL to \"what should I improve next time\":\n\n```mermaid\nflowchart LR\n    URL[URL] -->|analyze_url| MOD[modules\u003Cbr\u002F>+ candidate TCs\u003Cbr\u002F>+ API endpoints]\n    MOD -->|generate_test\u003Cbr\u002F>module=...| TEST[tests\u002Ftest_*.py\u003Cbr\u002F>runnable skeleton]\n    TEST -->|run_tests| RES[report.json\u003Cbr\u002F>+ screenshots\u003Cbr\u002F>+ trace.zip\u003Cbr\u002F>+ junit.xml]\n    RES -->|auto archive| HIST[history\u002F snapshot]\n    RES -->|generate_html_report| HTML[HTML report\u003Cbr\u002F>self-contained]\n    HIST -->|auto write| PLAN[optimization-plan.md]\n    PLAN -.->|next session reads| URL\n```\n\nThe loop is the point: every run feeds the optimizer, the optimizer\npoints at the weakest link, the next run hits that link first.\n\n### Walkthrough — testing a login page\n\nIn a Claude \u002F Cursor session:\n\n> **You**: 分析 `https:\u002F\u002Fshop.example\u002Flogin`，幫我寫對應測試\n>\n> **Claude**: [`analyze_url`] Found 1 form (`email_password_form_0`) + 3 API\n> endpoints. 5 candidate TCs.\n> [`generate_test` with the form module] Wrote `tests\u002Ftest_login.py` —\n> runnable with concrete selectors, no `# TODO` stubs.\n\n> **You**: 跑\n>\n> **Claude**: [`run_tests`] 23 passed, 0 failed in 31s. Screenshots + step\n> traces captured for every test.\n\n> **You**: 下一步該做什麼？\n>\n> **Claude**: [opens `report:\u002F\u002Foptimization`]\n> Top: `tests\u002Ftest_login.py::test_invalid_credentials` is flaky\n> (flake_score=0.4, outcomes=PFPFP). Suggestion: add\n> `wait_for_response('\u002Fapi\u002Flogin')` before asserting the error message.\n\nThe three optimizer lenses (suite quality \u002F MCP usability \u002F AI generation\neffectiveness) make every \"下一步\" answer data-driven, not gut feel.\n\n### Walkthrough — testing a mobile app (Maestro)\n\nSame shape, different runner. Requires Maestro CLI installed +\nsimulator\u002Femulator booted + your app launched (or pass `launch_app=true`\nwith `app_id`):\n\n> **You**: 分析 your mobile app 首頁的條碼按鈕、寫對應測試\n>\n> **Claude**: [`analyze_screen`(app_id=\"com.example.app\", launch_app=true)]\n> Found 15 interactive elements; matched `barcode_button`\n> (text=\"條碼\", resource_id=\"barcodeButton\") + candidate TCs.\n> [`generate_test` with the cta module] Wrote `maestro-flows\u002Ftest_barcode.yaml` —\n> `tapOn: { id: barcodeButton }` + waitForAnimationToEnd + takeScreenshot,\n> ready to `maestro test`.\n\n> **You**: 跑\n>\n> **Claude**: [`run_tests`] 5 flows pass, retry didn't fire. Screenshots\n> embedded in HTML report.\n\n> **You**: 上面這個按鈕有時候會 fail、為什麼？\n>\n> **Claude**: [`get_optimization_plan`] `barcode_button::barcode_button` flagged\n> flaky (flake_score=0.4, outcomes=PFPFP, rerun_count=1). Suggestion: 加\n> `waitForAnimationToEnd` 或 `extendedWaitUntil` 等動畫穩定後再 tap。\n\nMobile-specific notes:\n- The same `qa-knowledge.md` (built-in methodology + your domain) feeds\n  both web and mobile runs — write your business rules once.\n- `analyze_screen` filters out iOS status bar (signal \u002F wifi \u002F battery)\n  and asset-name labels (`bg_*`, `*_filled`); the result is signal-heavy.\n- Maestro's `takeScreenshot: \u003Cname>` directive controls which screens\n  show up as inline images in the HTML report.\n\n---\n\n## Prompting cookbook\n\nEach row shows a phrase you can paste into a Claude \u002F Cursor session and\nthe underlying MCP tool call it should trigger. Use as a reference for\n\"how do I get the AI to do X without naming the tool myself.\"\n\n### One-time setup\n| You say | Claude calls |\n|---|---|\n| \"Initialize the QA knowledge file.\" | `init_qa_knowledge` → writes `qa-knowledge.md` to your project root |\n| \"Show me the current QA knowledge.\" | `get_qa_context` → methodology + your domain sections |\n| \"Open the ISTQB principles section.\" | `get_qa_context(section=\"ISTQB\")` |\n\n### Day-to-day testing\n| You say | Claude calls |\n|---|---|\n| \"Run all tests.\" | `run_tests` |\n| \"Run only login-related tests.\" | `run_tests(filter=\"login\")` |\n| \"Re-run just the failures.\" | `run_failed` |\n| \"Show me the summary.\" | `get_test_report` |\n| \"Which ones failed? Give me screenshots and trace.\" | `get_failure_details` |\n| \"Generate the HTML report.\" | `generate_html_report` |\n\n### Building tests from a URL (web)\n| You say | Claude calls |\n|---|---|\n| \"Auto-generate tests for `https:\u002F\u002Fshop.example\u002F`.\" | `auto_generate_tests(url=...)` — one-shot |\n| \"Analyze `https:\u002F\u002Fshop.example\u002Fcoupon` first, then write one test per module.\" | `analyze_url` → `generate_test` × N |\n| \"Analyze coupon page and write a regression test for our past idempotency bug.\" | `get_qa_context(section=\"Bug\")` → `analyze_url` → `generate_test(business_context=...)` |\n| \"Just record a checkout flow as a baseline.\" | `codegen(url=...)` |\n\n### Building tests from a mobile screen (Maestro)\nRequires `QA_RUNNER=maestro`, Maestro CLI, and a booted simulator\u002Femulator\u002Fdevice.\n\n| You say | Claude calls |\n|---|---|\n| \"Analyze the current your mobile app screen and write a test for the barcode button.\" | `analyze_screen(app_id=\"com.example.app\", launch_app=true)` → `generate_test(module=\u003Ccta>)` |\n| \"Test the login form on this app.\" | `analyze_screen(launch_app=true)` → pick `form` module → `generate_test` |\n| \"Cover the tab bar — write one flow per tab.\" | `analyze_screen` → take the `tab_bar` module → `generate_test` |\n| \"Use Maestro Studio to record a flow.\" | `codegen(url=...)` returns a hint pointing at `maestro studio` (record + save manually) |\n\n**BlueStacks \u002F remote Android instances**: set `QA_ANDROID_HOST=127.0.0.1:5555`\n(or whatever host:port BlueStacks exposes — see *Settings → Advanced → Android\nDebug Bridge*). The Maestro runner will `adb connect` before each test and\n`analyze_screen`, and bumps the `hierarchy` timeout to 60s to absorb the\nslower TCP-ADB path. Genymotion \u002F Nox \u002F LDPlayer \u002F WSA work the same way;\nany `host:port` that responds to `adb connect` is fine.\n\n### Continuous improvement\n| You say | Claude calls |\n|---|---|\n| \"What should I fix next?\" | `get_optimization_plan` |\n| \"Has `test_login_invalid` been flaky lately?\" | `get_test_history` + plan lookup |\n| \"Why did it fail? Show me the trace.\" | `get_failure_details` (returns screenshot\u002Ftrace\u002Fvideo paths) |\n\n### Tips — getting Claude to pick the right tool\n\n- **Mention QA knowledge explicitly** — \"**reference qa knowledge** when testing coupon\" pushes Claude to call `get_qa_context` first; saying just \"test coupon\" may skip it.\n- **State the order** — \"**analyze first**, then write\" forces `analyze_url` before `generate_test`; \"just write a test for X\" skips analysis.\n- **Batch vs precise** — \"auto-generate the whole page\" → `auto_generate_tests`; \"write one test per candidate_tc\" → manual chain.\n- **Failure debugging** — Asking \"why did it fail \u002F show me the screenshot\" reliably triggers `get_failure_details` (which now returns screenshot + trace + video paths).\n\n### Anti-patterns\n- ❌ \"Run it 5 times to see if it's flaky\" — the runner has auto-retry + history; just ask \"is it flaky\" and let `get_optimization_plan` answer.\n- ❌ \"Generate 100 tests\" — noise > signal. Use `get_optimization_plan` first to find what's missing.\n- ❌ \"Test all edge cases\" — too vague. Phrase as \"test every `candidate_tc` for this form\" — concrete, bounded, traceable.\n\n---\n\n## Sample outputs\n\n### `analyze_url` (excerpt)\n\n```json\n{\n  \"url\": \"https:\u002F\u002Fshop.example\u002Flogin\",\n  \"page_title\": \"Login\",\n  \"module_count\": 3,\n  \"modules\": [\n    {\n      \"kind\": \"form\",\n      \"name\": \"email_password_form_0\",\n      \"selectors\": {\n        \"container\": \"#login\",\n        \"fields\": [\n          {\"label\": \"Email\", \"selector\": \"#email\", \"type\": \"email\", \"required\": true},\n          {\"label\": \"Password\", \"selector\": \"#password\", \"type\": \"password\", \"required\": true}\n        ],\n        \"submit\": \"button[type='submit']\"\n      },\n      \"candidate_tcs\": [\n        \"所有必填欄位為空時送出，應顯示必填錯誤\",\n        \"Email 欄位填入格式錯誤的字串（無 @），應顯示格式錯誤\",\n        \"Password 欄位輸入後應預設遮蔽（type=password）\",\n        \"全部填入合法值後送出，應觸發成功流程\"\n      ]\n    }\n  ],\n  \"api_endpoints\": [\n    {\n      \"method\": \"POST\",\n      \"path\": \"\u002Fapi\u002Flogin\",\n      \"status\": 401,\n      \"candidate_tcs\": [\n        \"POST \u002Fapi\u002Flogin payload 缺必填欄位應回 400 + 欄位錯誤訊息\",\n        \"POST \u002Fapi\u002Flogin 合法 payload 應回 2xx\",\n        \"POST \u002Fapi\u002Flogin 缺少 auth header 應回 401\u002F403\"\n      ]\n    }\n  ]\n}\n```\n\n### `generate_test` output (smart, with module)\n\n```python\n\"\"\"\nLogin happy path\n\nAuto-generated from analyze_url module: email_password_form_0 (kind=form)\n\"\"\"\nfrom playwright.sync_api import Page, expect\n\n\ndef test_login(page: Page):\n    page.goto('https:\u002F\u002Fshop.example\u002Flogin')\n    page.locator('#email').fill('test@example.com')\n    page.locator('#password').fill('TestPass123!')\n    page.locator(\"button[type='submit']\").click()\n    # TC: Email 欄位填入格式錯誤的字串（無 @），應顯示格式錯誤\n    # TC: Password 欄位輸入後應預設遮蔽\n    # TC: 正確 Email + 正確密碼 → 導向 dashboard\n    # TODO: 補上實際斷言，例如：\n    # expect(page).to_have_url(...)\n    # expect(page.get_by_text(\"成功\")).to_be_visible()\n```\n\n### `optimization-plan.md` (excerpt)\n\n```markdown\n# Optimization Plan — 2026-05-12T14:03:40\n\n_Based on 6 archived runs._\n\n## Prioritized Actions\n\n### 1. 🔴 HIGH — flaky\n- **Target**: `tests\u002Ftest_login.py::test_invalid_credentials`\n- **Evidence**: flake_score=0.4, outcomes=PFPFP, rerun_count=1\n- **Suggestion**: 加 explicit wait（wait_for_response \u002F locator wait）\n\n### 2. 🟡 MEDIUM — coverage_gap\n- **Target**: `register_form`\n- **Evidence**: 由 analyze_url 偵測但 repo 內找不到對應 test_*.py\n- **Suggestion**: `call generate_test(description=\"...\", filename=\"test_register_form.py\")`\n```\n\n### HTML report\n\n[**Open the live rendered demo →**](https:\u002F\u002Fhtmlpreview.github.io\u002F?https:\u002F\u002Fgithub.com\u002Fkao273183\u002Fmk-qa-master\u002Fblob\u002Fmain\u002Fsample_report.html)\n(served via GitHub Pages — clicking the link in GitHub's UI to\n[`sample_report.html`](sample_report.html) would only show source).\n\nThe demo shows the stats grid, trend sparkline, failure cards with embedded\nscreenshots + step lists, and the collapsed Passed section.\n\n---\n\n## Integrations\n\n`mk-qa-master` doesn't bundle third-party SDKs — it stays a pure\ntest-execution + analysis layer. Real QA workflows are composed by\nrunning multiple MCP servers side-by-side in the same client config;\n**Claude orchestrates the chain across servers**. There's no MCP-to-MCP\nRPC — each server is independent, the AI client is the conductor.\n\nThe pairings below are the ones that complete the loop most often:\n\n| Pair with | Why | Example chain |\n|---|---|---|\n| **[Atlassian MCP](https:\u002F\u002Fwww.atlassian.com\u002Fplatform\u002Fremote-mcp-server)** *(JIRA + Confluence)* | Auto-open bug tickets from failures; sync `optimization-plan.md` to a team Confluence page | `run_tests` → `get_failure_details` → `atlassian.createJiraIssue` *(attaches screenshot + trace path)* |\n| **[Slack MCP](https:\u002F\u002Fgithub.com\u002Fmodelcontextprotocol\u002Fservers\u002Ftree\u002Fmain\u002Fsrc\u002Fslack)** | Notify channels on failure, share the rendered HTML report, mention oncall for flaky tests | `generate_html_report` → `slack.send_message(channel=\"#qa-bots\", attachments=...)` |\n| **[GitHub MCP](https:\u002F\u002Fgithub.com\u002Fgithub\u002Fgithub-mcp-server)** | Read PR description \u002F linked issues for *business context* before generating tests; post results back as PR comments | `github.get_pull_request` → `analyze_url` → `generate_test(business_context=PR body)` → `github.create_issue_comment` |\n| **[Sentry MCP](https:\u002F\u002Fgithub.com\u002Fgetsentry\u002Fsentry-mcp)** | Production errors drive regression priority: top crashes → matching regression tests | `sentry.list_issues(sort=\"frequency\")` → `generate_test(business_context=stack trace)` → `run_tests` |\n| **[Filesystem MCP](https:\u002F\u002Fgithub.com\u002Fmodelcontextprotocol\u002Fservers\u002Ftree\u002Fmain\u002Fsrc\u002Ffilesystem)** | Read a shared `qa-knowledge.md` or TC source files that live outside `QA_PROJECT_ROOT` (monorepos, multi-project setups) | `filesystem.read_file(\"~\u002Fshared\u002Fqa-knowledge.md\")` → `init_qa_knowledge` |\n\n**Honorable mention — [Google Drive MCP](https:\u002F\u002Fgithub.com\u002Fmodelcontextprotocol\u002Fservers\u002Ftree\u002Fmain\u002Fsrc\u002Fgdrive)**: pairs with Google-Sheet-based TC management (read TCs from a sheet → `generate_test` → write status back).\n\n### Composing in your client config\n\nAll five run as separate processes alongside `mk-qa-master`:\n\n```json\n{\n  \"mcpServers\": {\n    \"mk-qa-master\": { \"command\": \"python\", \"args\": [\"-m\", \"mk_qa_master.server\"], \"env\": { \"QA_RUNNER\": \"maestro\" } },\n    \"atlassian\":       { \"command\": \"npx\", \"args\": [\"-y\", \"@atlassian\u002Fmcp\"] },\n    \"slack\":           { \"command\": \"npx\", \"args\": [\"-y\", \"@modelcontextprotocol\u002Fserver-slack\"] },\n    \"github\":          { \"command\": \"npx\", \"args\": [\"-y\", \"@modelcontextprotocol\u002Fserver-github\"] }\n  }\n}\n```\n\nThen a single prompt walks the chain:\n\n> \"Run the checkout suite. For each failure, open a JIRA in project QA with the RIDER format and the screenshot attached. Post the HTML report to #qa-bots when done.\"\n\nWhy this matters: `mk-qa-master` stays focused on the test loop\n(analyze → generate → run → coach). JIRA \u002F Slack \u002F Sentry are entire\ndomains with their own dedicated servers — bolting them into this one\nwould dilute the scope, duplicate auth handling, and force every user\nto inherit dependencies they may not want.\n\n本 repo 不打包任何第三方 SDK——維持「測試執行 + 分析」單一職責。實務上 QA 工作流是**多個 MCP server 並存、由 Claude 編排跨 server 的 tool chain**達成的。範例配套：JIRA \u002F Slack \u002F GitHub \u002F Sentry \u002F Filesystem 各自獨立 MCP server，配上 `mk-qa-master` 拼出完整測試管線。\n\n---\n\n## Publishing (maintainer-only)\n\nReleases ship to PyPI via [Trusted Publishing](https:\u002F\u002Fdocs.pypi.org\u002Ftrusted-publishers\u002F) — no API tokens stored in the repo. The flow:\n\n1. Bump `version = \"x.y.z\"` in `pyproject.toml` (via a normal PR — main is branch-protected).\n2. After merge, tag main and push:\n   ```bash\n   git tag -a vX.Y.Z -m \"vX.Y.Z — short summary\"\n   git push origin vX.Y.Z\n   ```\n3. Create a GitHub Release for that tag (`gh release create vX.Y.Z ...`).\n4. The release event fires `.github\u002Fworkflows\u002Fpublish.yml` → builds sdist + wheel → uploads to PyPI.\n\nOne-time PyPI setup (must be done once before the first publish works):\n\n- Sign in at https:\u002F\u002Fpypi.org → enable 2FA.\n- Project page → *Settings → Publishing* → add a **pending publisher** with:\n  - Owner: `kao273183`\n  - Repository: `mk-qa-master`\n  - Workflow filename: `publish.yml`\n  - Environment name: `pypi`\n\nAfter the first successful run, PyPI auto-promotes the pending publisher to a trusted one and subsequent releases authenticate via OIDC.\n\nThe workflow refuses to publish if the release tag doesn't match `pyproject.version`, which catches \"tagged but forgot to bump\" mistakes before they hit PyPI.\n\n---\n\n## Support the project ☕\n\n`mk-qa-master` is built and maintained solo on nights and weekends. If it saved you time or shaped how your team thinks about AI-driven QA, a coffee keeps the late-night Maestro debugging sessions going:\n\n[![Buy Me a Coffee](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FBuy%20Me%20a%20Coffee-FFDD00?logo=buy-me-a-coffee&logoColor=black&style=for-the-badge)](https:\u002F\u002Fwww.buymeacoffee.com\u002Fminikao)\n\nYour support funds: keeping this repo free + actively maintained, more device variants for Maestro testing (real iPhones \u002F Android tablets \u002F BlueStacks), recorded tutorials for the QA community, and the next 2am bug hunt.\n\nNo ads, no sponsorships, no enterprise upsell — just the work.\n\n---\n\n## Contributing\n\nThis repo is **maintained solo**. Ideas and bug reports are very welcome — please open an [Issue](https:\u002F\u002Fgithub.com\u002Fkao273183\u002Fmk-qa-master\u002Fissues\u002Fnew\u002Fchoose) or start a [Discussion](https:\u002F\u002Fgithub.com\u002Fkao273183\u002Fmk-qa-master\u002Fdiscussions). I read every one and will implement what fits the project's direction.\n\n**External pull requests are auto-closed.** Not because contributions aren't appreciated, but because keeping the codebase coherent under a single voice matters more here than the throughput a multi-contributor model would bring. If you really want a specific change, an Issue describing the problem gets you further than a PR.\n\n本 repo 由我一人維護。歡迎透過 Issue \u002F Discussion 提想法或回報問題，我會親自評估並實作。**外部 PR 會自動關閉**——不是不歡迎貢獻，而是想保持程式碼風格與走向一致。\n\n---\n\n## License\n\nMIT © 2026 Jack Kao — see [`LICENSE`](LICENSE)\n(中文翻譯參考: [`LICENSE.zh-TW.md`](LICENSE.zh-TW.md); the English version is\nauthoritative).\n\nIn plain English: you can use this for anything (personal projects, commercial\nwork, modifications, redistribution). The only ask is that you keep the\ncopyright + license notice in any copy you ship. There's no warranty — use\nat your own risk.\n","MK QA Master 是一个基于Model Context Protocol的AI测试平台，支持通过单一界面运行多种框架（如pytest、Jest、Cypress、Go等）下的测试用例。其核心功能包括跨平台测试执行、内置DOM分析器、运行历史记录以及自我改进建议生成，能够帮助开发者从分析到优化整个测试流程。该项目特别适合需要进行Web、移动应用及后端API自动化测试的场景，尤其是对于希望利用AI辅助提高测试效率和质量的团队来说是一个强大的工具。","2026-06-11 04:02:55","CREATED_QUERY"]