{"id":49991434,"url":"https://github.com/kao273183/mk-qa-master","last_synced_at":"2026-05-27T07:02:44.505Z","repository":{"id":357291587,"uuid":"1235230186","full_name":"kao273183/mk-qa-master","owner":"kao273183","description":"AI 測試大師 — MCP server driving pytest / Jest / Cypress / Go / Maestro. Analyze, generate, run, advise. Web + Mobile (iOS/Android/BlueStacks).","archived":false,"fork":false,"pushed_at":"2026-05-25T02:18:35.000Z","size":2427,"stargazers_count":32,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-25T03:28:17.863Z","etag":null,"topics":["maestro","mcp","mcp-server","mobile-testing","model-context-protocol","playwright","pytest","qa","test-automation","testing"],"latest_commit_sha":null,"homepage":"https://mcp.chenjundigital.com","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kao273183.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"buy_me_a_coffee":"minikao"}},"created_at":"2026-05-11T06:11:39.000Z","updated_at":"2026-05-25T02:18:16.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kao273183/mk-qa-master","commit_stats":null,"previous_names":["kao273183/mcp-test-runner","kao273183/mk-qa-master"],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/kao273183/mk-qa-master","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kao273183%2Fmk-qa-master","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kao273183%2Fmk-qa-master/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kao273183%2Fmk-qa-master/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kao273183%2Fmk-qa-master/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kao273183","download_url":"https://codeload.github.com/kao273183/mk-qa-master/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kao273183%2Fmk-qa-master/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33554780,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-27T02:00:06.184Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["maestro","mcp","mcp-server","mobile-testing","model-context-protocol","playwright","pytest","qa","test-automation","testing"],"created_at":"2026-05-19T05:01:38.868Z","updated_at":"2026-05-27T07:02:44.467Z","avatar_url":"https://github.com/kao273183.png","language":"Python","funding_links":["https://buymeacoffee.com/minikao","https://www.buymeacoffee.com/minikao"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/kao273183/mk-qa-master/main/assets/logo.png\" alt=\"mk-qa-master logo\" width=\"180\" /\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eMK QA Master\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cem\u003eAI 測試大師 — your AI QA loop, from analyze to advise.\u003c/em\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eEnglish\u003c/strong\u003e · \u003ca href=\"README.zh-TW.md\"\u003e繁體中文\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://pypi.org/project/mk-qa-master/\"\u003e\u003cimg src=\"https://img.shields.io/pypi/v/mk-qa-master.svg?logo=pypi\u0026logoColor=white\u0026color=3775A9\" alt=\"PyPI\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/kao273183/mk-qa-master/actions/workflows/ci.yml\"\u003e\u003cimg src=\"https://github.com/kao273183/mk-qa-master/actions/workflows/ci.yml/badge.svg\" alt=\"CI\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://glama.ai/mcp/servers/kao273183/mk-qa-master\"\u003e\u003cimg src=\"https://glama.ai/mcp/servers/kao273183/mk-qa-master/badges/score.svg\" alt=\"Glama score\" /\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/License-MIT-yellow.svg\" alt=\"License: MIT\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.buymeacoffee.com/minikao\"\u003e\u003cimg src=\"https://img.shields.io/badge/Buy%20Me%20a%20Coffee-Support-FFDD00?logo=buy-me-a-coffee\u0026logoColor=black\" alt=\"Buy Me a Coffee\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003e Universal MCP server for running tests across pytest / Jest / Cypress / Go,\n\u003e with built-in DOM analyzer, run history, and a self-improvement coach.\n\nA **Model Context Protocol** server that lets Claude Desktop / Cursor / 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` / `pytest-playwright` / `playwright` | pytest + Playwright | Python | Web |\n| `jest` | Jest | JavaScript | Web |\n| `cypress` | Cypress | JavaScript | Web |\n| `go` / `go-test` | `go test` | Go | Backend |\n| `maestro` / `mobile` | Maestro | YAML | iOS + Android |\n| `schemathesis` / `api` | Schemathesis | OpenAPI 3.x / Swagger 2.0 | API (since v0.6.0) |\n| `newman` / `postman` | Newman | Postman collection v2.x | API (since v0.6.1) |\n\nFull design notes: [`docs/framework.md`](docs/framework.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 /\n  Android Emulator / real device; YAML flows; cross-platform without rewrites\n- **Native API testing — two runners** (since v0.6.0 / 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 / Swagger 2.0 URL or `file://` 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 / 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 / mobile runners, and\n  both feed the same `report.json` / history / flake / optimizer pipeline.\n  Existing API tests written in pytest+`httpx`, Jest+`supertest`, Cypress\n  `cy.request()`, or Go `net/http/httptest` 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 / Maestro recordings\n- **Run history**: every run snapshotted; HTML report shows a sparkline trend\n- **DOM / Screen analyzer** — `analyze_url` for web (forms / nav / dialogs /\n  CTAs + the API endpoints the page hits) and `analyze_screen` for mobile\n  (`maestro hierarchy` → form / cta / 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 / Jenkins / 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://docs.astral.sh/uv/) 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\": \"/path/to/your-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 / 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\": \"/path/to/.venv/bin/python\",\n\"args\": [\"-m\", \"mk_qa_master.server\"]\n```\n\n### Runner-specific prerequisites\n\n| `QA_RUNNER` | You also need |\n|---|---|\n| `pytest` / `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://maestro.mobile.dev/) + a booted simulator / emulator / device (or BlueStacks reachable via `adb connect`) |\n| `schemathesis` / `api` | `pip install 'mk-qa-master[api]'` (pulls in `schemathesis\u003e=3.0,\u003c4`) |\n| `newman` / `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 / 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` / history /\nflake / optimizer pipeline as your UI tests.\n\nEnd-to-end walkthrough lives in [`docs/walkthrough-api.md`](docs/walkthrough-api.md);\na self-contained 3-endpoint sample lives at\n[`examples/sample_api_project/`](examples/sample_api_project/).\n\n### 5-line config\n\n```jsonc\n\"env\": {\n  \"QA_RUNNER\": \"schemathesis\",\n  \"QA_OPENAPI_URL\": \"https://api.example.com/openapi.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)://...` for live schemas, `file://...` for local files. **Plain filesystem paths are not accepted** — they need the `file://` 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\u003e\"`. 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\" / \"api_key\" / \"secret\" / \"access_token\" / \"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` / history / flake / 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/sample_api_project/postman-collection.json`](examples/sample_api_project/postman-collection.json) —\npair it with `prism mock examples/sample_api_project/openapi.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\": \"/absolute/path/to/your-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://` 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\u003e`). Provides values for `{{var_name}}` placeholders in the collection. |\n| `QA_POSTMAN_GLOBALS` | no | — | Plain path to a Postman globals file (`-g \u003cpath\u003e`). Same shape as the environment, globally scoped. |\n| `QA_POSTMAN_ITERATIONS` | no | `1` | Replay the whole collection N times (`-n \u003cN\u003e`). 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\u003e *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 / Cursor / Gemini\n/ 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 / 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\": \"/path/to/project\",\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/walkthrough-visual-challenge.md`](docs/walkthrough-visual-challenge.md).\nPRD: [`docs/prd-v0.7-visual-challenge.md`](docs/prd-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 / Cloudflare Turnstile are permanently out of\nscope — they don't surface a visible challenge to inspect.\n\n\n## Wire into Claude Desktop\n\nCopy `examples/configs/claude_desktop_config.example.json` to:\n\n- **macOS**: `~/Library/Application Support/Claude/claude_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` / `jest` / `cypress` / `go` / `maestro` / `schemathesis` / `newman` | Selects which test framework |\n| `QA_PROJECT_ROOT` | `/path/to/your/project` | Points at the project under test |\n| `QA_ANDROID_HOST` *(optional)* | `127.0.0.1:5555` | Remote-ADB endpoint for **BlueStacks** / Genymotion / Nox / cloud Android. When set, the Maestro runner auto-runs `adb connect \u003chost\u003e` before each test / `analyze_screen` call. Requires `adb` on PATH. |\n| `QA_TIMEOUT_SECONDS` *(optional)* | `600` (default) | Hard ceiling on any single subprocess invocation (pytest / jest / cypress / go test / 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\": \"/path/to/python-project\" }\n```\n\n**Jest**:\n```json\n\"env\": { \"QA_RUNNER\": \"jest\", \"QA_PROJECT_ROOT\": \"/path/to/node-project\" }\n```\n\n**Cypress**:\n```json\n\"env\": { \"QA_RUNNER\": \"cypress\", \"QA_PROJECT_ROOT\": \"/path/to/cypress-project\" }\n```\n\n**Go test**:\n```json\n\"env\": { \"QA_RUNNER\": \"go\", \"QA_PROJECT_ROOT\": \"/path/to/go-project\" }\n```\n\n**Schemathesis (API)**:\n```json\n\"env\": {\n  \"QA_RUNNER\": \"schemathesis\",\n  \"QA_OPENAPI_URL\": \"https://api.example.com/openapi.json\"\n}\n```\n\n**Newman (Postman)**:\n```json\n\"env\": {\n  \"QA_RUNNER\": \"newman\",\n  \"QA_POSTMAN_COLLECTION\": \"/absolute/path/to/collection.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 / Cursor | `~/Library/Application Support/Claude/...json` · `~/.cursor/mcp.json` | JSON | Claude Opus / Sonnet | Best tested |\n| **Codex CLI** | `~/.codex/config.toml` | **TOML** | GPT-5 family | Strong (well-trained on tool chaining) |\n| **Gemini CLI** | `~/.gemini/settings.json` | JSON | Gemini 3.1 Pro / Flash | Works; prefers explicit prompts (\"first analyze, then write\") |\n| Cline / Continue / 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/configs/codex-config.example.toml) ·\n[`gemini-config.example.json`](examples/configs/gemini-config.example.json) ·\n[`claude_desktop_config.example.json`](examples/configs/claude_desktop_config.example.json).\n\nCodex (TOML):\n```toml\n[mcp_servers.mk-qa-master]\ncommand = \"/path/to/.venv/bin/python\"\nargs = [\"-m\", \"mk_qa_master.server\"]\ncwd = \"/path/to/mk-qa-master\"\n[mcp_servers.mk-qa-master.env]\nQA_RUNNER = \"pytest\"\nQA_PROJECT_ROOT = \"/path/to/your-test-project\"\n```\n\nGemini (JSON, same shape as Claude Desktop):\n```json\n{\n  \"mcpServers\": {\n    \"mk-qa-master\": {\n      \"command\": \"/path/to/.venv/bin/python\",\n      \"args\": [\"-m\", \"mk_qa_master.server\"],\n      \"cwd\": \"/path/to/mk-qa-master\",\n      \"env\": {\n        \"QA_RUNNER\": \"pytest\",\n        \"QA_PROJECT_ROOT\": \"/path/to/your-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 / headed / browser; last two pytest-playwright only) |\n| `run_failed` | Re-run last failures (`pytest --lf`) |\n| `get_test_report` | Summary (pass / fail / skipped / duration / flaky-in-run) |\n| `get_failure_details` | Per-failure message + screenshot / trace / video paths |\n| `generate_test` | Test skeleton; with `module` from `analyze_url`/`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) / 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 / flake debugging) |\n| `analyze_url` | **Web**: DOM probe → modules + selectors + candidate TCs + API endpoints + layout overflow warnings |\n| `analyze_screen` | **Mobile**: `maestro hierarchy` → form / cta / tab_bar modules + candidate TCs (noise-filtered) |\n| `init_qa_knowledge` / `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 / stub / fake / spy), and test data management. Domain example: [`docs/qa-knowledge-en.example.md`](docs/qa-knowledge-en.example.md) (zh-TW: [`docs/qa-knowledge.example.md`](docs/qa-knowledge.example.md)). |\n| `get_optimization_plan` | Three-layer self-improvement coach (suite / MCP / AI strategy) |\n| `inspect_visual_challenge` / `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\n### Resources\n\n| URI | What |\n|---|---|\n| `report://html` | Live-rendered HTML report (dark mode, self-contained) |\n| `report://json` | Raw pytest-json-report JSON |\n| `report://optimization` | Latest `optimization-plan.md` |\n\n---\n\n## Self-improvement loop\n\nAfter every run, `_archive_report()` snapshots `report.json` into\n`test-results/history/` 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` / `medium` / `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/\n├── pyproject.toml\n├── src/mk_qa_master/\n│   ├── server.py            # MCP entry (tool routing + telemetry wrap)\n│   ├── config.py            # Paths + env vars\n│   ├── runners/             # 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/\n│   │   └── html.py          # Self-contained HTML render\n│   └── tools/               # Thin shims + analyzer + optimizer + telemetry\n└── tests_project/           # Example project under test\n```\n\n---\n\n## Adding a runner\n\n1. Create `src/mk_qa_master/runners/your_runner.py`, subclass `TestRunner`,\n   implement the abstract methods\n2. Register the name in `runners/__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] --\u003e|analyze_url| MOD[modules\u003cbr/\u003e+ candidate TCs\u003cbr/\u003e+ API endpoints]\n    MOD --\u003e|generate_test\u003cbr/\u003emodule=...| TEST[tests/test_*.py\u003cbr/\u003erunnable skeleton]\n    TEST --\u003e|run_tests| RES[report.json\u003cbr/\u003e+ screenshots\u003cbr/\u003e+ trace.zip\u003cbr/\u003e+ junit.xml]\n    RES --\u003e|auto archive| HIST[history/ snapshot]\n    RES --\u003e|generate_html_report| HTML[HTML report\u003cbr/\u003eself-contained]\n    HIST --\u003e|auto write| PLAN[optimization-plan.md]\n    PLAN -.-\u003e|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 / Cursor session:\n\n\u003e **You**: 分析 `https://shop.example/login`，幫我寫對應測試\n\u003e\n\u003e **Claude**: [`analyze_url`] Found 1 form (`email_password_form_0`) + 3 API\n\u003e endpoints. 5 candidate TCs.\n\u003e [`generate_test` with the form module] Wrote `tests/test_login.py` —\n\u003e runnable with concrete selectors, no `# TODO` stubs.\n\n\u003e **You**: 跑\n\u003e\n\u003e **Claude**: [`run_tests`] 23 passed, 0 failed in 31s. Screenshots + step\n\u003e traces captured for every test.\n\n\u003e **You**: 下一步該做什麼？\n\u003e\n\u003e **Claude**: [opens `report://optimization`]\n\u003e Top: `tests/test_login.py::test_invalid_credentials` is flaky\n\u003e (flake_score=0.4, outcomes=PFPFP). Suggestion: add\n\u003e `wait_for_response('/api/login')` before asserting the error message.\n\nThe three optimizer lenses (suite quality / MCP usability / 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/emulator booted + your app launched (or pass `launch_app=true`\nwith `app_id`):\n\n\u003e **You**: 分析 your mobile app 首頁的條碼按鈕、寫對應測試\n\u003e\n\u003e **Claude**: [`analyze_screen`(app_id=\"com.example.app\", launch_app=true)]\n\u003e Found 15 interactive elements; matched `barcode_button`\n\u003e (text=\"條碼\", resource_id=\"barcodeButton\") + candidate TCs.\n\u003e [`generate_test` with the cta module] Wrote `maestro-flows/test_barcode.yaml` —\n\u003e `tapOn: { id: barcodeButton }` + waitForAnimationToEnd + takeScreenshot,\n\u003e ready to `maestro test`.\n\n\u003e **You**: 跑\n\u003e\n\u003e **Claude**: [`run_tests`] 5 flows pass, retry didn't fire. Screenshots\n\u003e embedded in HTML report.\n\n\u003e **You**: 上面這個按鈕有時候會 fail、為什麼？\n\u003e\n\u003e **Claude**: [`get_optimization_plan`] `barcode_button::barcode_button` flagged\n\u003e flaky (flake_score=0.4, outcomes=PFPFP, rerun_count=1). Suggestion: 加\n\u003e `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 / wifi / battery)\n  and asset-name labels (`bg_*`, `*_filled`); the result is signal-heavy.\n- Maestro's `takeScreenshot: \u003cname\u003e` 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 / 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://shop.example/`.\" | `auto_generate_tests(url=...)` — one-shot |\n| \"Analyze `https://shop.example/coupon` 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/emulator/device.\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\u003e)` |\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 / 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 / Nox / LDPlayer / 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/trace/video 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 / 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 \u003e 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://shop.example/login\",\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\": \"/api/login\",\n      \"status\": 401,\n      \"candidate_tcs\": [\n        \"POST /api/login payload 缺必填欄位應回 400 + 欄位錯誤訊息\",\n        \"POST /api/login 合法 payload 應回 2xx\",\n        \"POST /api/login 缺少 auth header 應回 401/403\"\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://shop.example/login')\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/test_login.py::test_invalid_credentials`\n- **Evidence**: flake_score=0.4, outcomes=PFPFP, rerun_count=1\n- **Suggestion**: 加 explicit wait（wait_for_response / 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://htmlpreview.github.io/?https://github.com/kao273183/mk-qa-master/blob/main/sample_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://www.atlassian.com/platform/remote-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://github.com/modelcontextprotocol/servers/tree/main/src/slack)** | 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://github.com/github/github-mcp-server)** | Read PR description / 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://github.com/getsentry/sentry-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://github.com/modelcontextprotocol/servers/tree/main/src/filesystem)** | Read a shared `qa-knowledge.md` or TC source files that live outside `QA_PROJECT_ROOT` (monorepos, multi-project setups) | `filesystem.read_file(\"~/shared/qa-knowledge.md\")` → `init_qa_knowledge` |\n\n**Honorable mention — [Google Drive MCP](https://github.com/modelcontextprotocol/servers/tree/main/src/gdrive)**: 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/mcp\"] },\n    \"slack\":           { \"command\": \"npx\", \"args\": [\"-y\", \"@modelcontextprotocol/server-slack\"] },\n    \"github\":          { \"command\": \"npx\", \"args\": [\"-y\", \"@modelcontextprotocol/server-github\"] }\n  }\n}\n```\n\nThen a single prompt walks the chain:\n\n\u003e \"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 / Slack / 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 / Slack / GitHub / Sentry / Filesystem 各自獨立 MCP server，配上 `mk-qa-master` 拼出完整測試管線。\n\n---\n\n## Publishing (maintainer-only)\n\nReleases ship to PyPI via [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) — 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/workflows/publish.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://pypi.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://img.shields.io/badge/Buy%20Me%20a%20Coffee-FFDD00?logo=buy-me-a-coffee\u0026logoColor=black\u0026style=for-the-badge)](https://www.buymeacoffee.com/minikao)\n\nYour support funds: keeping this repo free + actively maintained, more device variants for Maestro testing (real iPhones / Android tablets / 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://github.com/kao273183/mk-qa-master/issues/new/choose) or start a [Discussion](https://github.com/kao273183/mk-qa-master/discussions). 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 / 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","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkao273183%2Fmk-qa-master","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkao273183%2Fmk-qa-master","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkao273183%2Fmk-qa-master/lists"}