@biggora/claude-plugins 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/.claude/settings.local.json +13 -0
  2. package/CLAUDE.md +55 -0
  3. package/LICENSE +1 -1
  4. package/README.md +208 -39
  5. package/bin/cli.js +39 -0
  6. package/package.json +30 -17
  7. package/registry/registry.json +166 -1
  8. package/registry/schema.json +10 -0
  9. package/src/commands/skills/add.js +194 -0
  10. package/src/commands/skills/list.js +52 -0
  11. package/src/commands/skills/remove.js +27 -0
  12. package/src/commands/skills/update.js +74 -0
  13. package/src/config.js +5 -0
  14. package/src/skills/codex-cli/SKILL.md +265 -0
  15. package/src/skills/commafeed-api/SKILL.md +1012 -0
  16. package/src/skills/gemini-cli/SKILL.md +379 -0
  17. package/src/skills/gemini-cli/references/commands.md +145 -0
  18. package/src/skills/gemini-cli/references/configuration.md +182 -0
  19. package/src/skills/gemini-cli/references/headless-and-scripting.md +181 -0
  20. package/src/skills/gemini-cli/references/mcp-and-extensions.md +254 -0
  21. package/src/skills/n8n-api/SKILL.md +623 -0
  22. package/src/skills/notebook-lm/SKILL.md +217 -0
  23. package/src/skills/notebook-lm/references/artifact-options.md +168 -0
  24. package/src/skills/notebook-lm/references/auth.md +58 -0
  25. package/src/skills/notebook-lm/references/workflows.md +144 -0
  26. package/src/skills/screen-recording/SKILL.md +309 -0
  27. package/src/skills/screen-recording/references/approach1-programmatic.md +311 -0
  28. package/src/skills/screen-recording/references/approach2-xvfb.md +232 -0
  29. package/src/skills/screen-recording/references/design-patterns.md +168 -0
  30. package/src/skills/test-mobile-app/SKILL.md +212 -0
  31. package/src/skills/test-mobile-app/references/report-template.md +95 -0
  32. package/src/skills/test-mobile-app/references/setup-appium.md +154 -0
  33. package/src/skills/test-mobile-app/scripts/analyze_apk.py +164 -0
  34. package/src/skills/test-mobile-app/scripts/check_environment.py +116 -0
  35. package/src/skills/test-mobile-app/scripts/generate_report.py +250 -0
  36. package/src/skills/test-mobile-app/scripts/run_tests.py +326 -0
  37. package/src/skills/test-web-ui/SKILL.md +232 -0
  38. package/src/skills/test-web-ui/references/test_case_schema.md +102 -0
  39. package/src/skills/test-web-ui/scripts/discover.py +176 -0
  40. package/src/skills/test-web-ui/scripts/generate_report.py +237 -0
  41. package/src/skills/test-web-ui/scripts/run_tests.py +296 -0
  42. package/src/skills/text-to-speech/SKILL.md +236 -0
  43. package/src/skills/text-to-speech/references/espeak-cli.md +277 -0
  44. package/src/skills/text-to-speech/references/kokoro-onnx.md +124 -0
  45. package/src/skills/text-to-speech/references/online-engines.md +128 -0
  46. package/src/skills/text-to-speech/references/pyttsx3-espeak.md +143 -0
  47. package/src/skills/tm-search/SKILL.md +240 -0
  48. package/src/skills/tm-search/references/field-guide.md +79 -0
  49. package/src/skills/tm-search/references/scraping-fallback.md +140 -0
  50. package/src/skills/tm-search/scripts/tm_search.py +375 -0
  51. package/src/skills/wp-rest-api/SKILL.md +114 -0
  52. package/src/skills/wp-rest-api/references/authentication.md +18 -0
  53. package/src/skills/wp-rest-api/references/custom-content-types.md +20 -0
  54. package/src/skills/wp-rest-api/references/discovery-and-params.md +20 -0
  55. package/src/skills/wp-rest-api/references/responses-and-fields.md +30 -0
  56. package/src/skills/wp-rest-api/references/routes-and-endpoints.md +36 -0
  57. package/src/skills/wp-rest-api/references/schema.md +22 -0
  58. package/src/skills/youtube-search/SKILL.md +412 -0
  59. package/src/skills/youtube-search/references/parsing-examples.md +159 -0
  60. package/src/skills/youtube-search/references/youtube-api-quota.md +85 -0
  61. package/src/skills/youtube-thumbnail/SKILL.md +1060 -0
  62. package/tests/commands/info.test.js +49 -0
  63. package/tests/commands/install.test.js +36 -0
  64. package/tests/commands/list.test.js +66 -0
  65. package/tests/commands/publish.test.js +182 -0
  66. package/tests/commands/search.test.js +45 -0
  67. package/tests/commands/uninstall.test.js +29 -0
  68. package/tests/commands/update.test.js +59 -0
  69. package/tests/functional/skills-lifecycle.test.js +293 -0
  70. package/tests/helpers/fixtures.js +63 -0
  71. package/tests/integration/cli.test.js +83 -0
  72. package/tests/skills/add.test.js +138 -0
  73. package/tests/skills/list.test.js +63 -0
  74. package/tests/skills/remove.test.js +38 -0
  75. package/tests/skills/update.test.js +60 -0
  76. package/tests/unit/config.test.js +31 -0
  77. package/tests/unit/registry.test.js +79 -0
  78. package/tests/unit/utils.test.js +150 -0
  79. package/tests/validation/registry-schema.test.js +112 -0
  80. package/tests/validation/skills-validation.test.js +96 -0
@@ -0,0 +1,326 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ run_tests.py — Execute mobile test scenarios via Appium or in static mode.
4
+
5
+ Usage:
6
+ python3 run_tests.py --apk app.apk --tests tests.json --output results/
7
+ python3 run_tests.py --static --tests tests.json --output results/
8
+ """
9
+ import argparse
10
+ import json
11
+ import os
12
+ import time
13
+ import datetime
14
+ import traceback
15
+ from pathlib import Path
16
+
17
+
18
+ # ─────────────────────────────────────────
19
+ # Data structures
20
+ # ─────────────────────────────────────────
21
+
22
+ class TestResult:
23
+ def __init__(self, test_id: str, title: str):
24
+ self.test_id = test_id
25
+ self.title = title
26
+ self.status = "PENDING" # PASS | FAIL | SKIP | MANUAL_REQUIRED | ERROR
27
+ self.steps_log = []
28
+ self.assertions = []
29
+ self.error = None
30
+ self.screenshot_path = None
31
+ self.duration_ms = 0
32
+ self.issues_found = []
33
+
34
+ def to_dict(self):
35
+ return {
36
+ "test_id": self.test_id,
37
+ "title": self.title,
38
+ "status": self.status,
39
+ "steps_log": self.steps_log,
40
+ "assertions": self.assertions,
41
+ "error": self.error,
42
+ "screenshot_path": self.screenshot_path,
43
+ "duration_ms": self.duration_ms,
44
+ "issues_found": self.issues_found,
45
+ }
46
+
47
+
48
+ # ─────────────────────────────────────────
49
+ # Appium driver helpers
50
+ # ─────────────────────────────────────────
51
+
52
+ def create_driver(apk_path: str, device_id: str = None):
53
+ """Create Appium WebDriver for Android."""
54
+ try:
55
+ from appium import webdriver
56
+ from appium.options import AppiumOptions
57
+ except ImportError:
58
+ raise RuntimeError("appium-python-client not installed. Run: pip install Appium-Python-Client --break-system-packages")
59
+
60
+ options = AppiumOptions()
61
+ options.platform_name = "Android"
62
+ options.automation_name = "UiAutomator2"
63
+ options.app = os.path.abspath(apk_path)
64
+ options.no_reset = False
65
+ options.full_reset = False
66
+
67
+ if device_id:
68
+ options.udid = device_id
69
+
70
+ driver = webdriver.Remote("http://localhost:4723/wd/hub", options=options)
71
+ driver.implicitly_wait(10)
72
+ return driver
73
+
74
+
75
+ def safe_find(driver, by, value, timeout=10):
76
+ """Find element with explicit wait."""
77
+ from selenium.webdriver.support.ui import WebDriverWait
78
+ from selenium.webdriver.support import expected_conditions as EC
79
+ return WebDriverWait(driver, timeout).until(
80
+ EC.presence_of_element_located((by, value))
81
+ )
82
+
83
+
84
+ def check_for_crash(driver) -> str | None:
85
+ """Returns crash message if a crash dialog is detected, else None."""
86
+ try:
87
+ # Look for common crash dialog text
88
+ from appium.webdriver.common.appiumby import AppiumBy
89
+ crash_indicators = [
90
+ "has stopped",
91
+ "keeps stopping",
92
+ "isn't responding",
93
+ "Unfortunately",
94
+ ]
95
+ page_source = driver.page_source
96
+ for indicator in crash_indicators:
97
+ if indicator.lower() in page_source.lower():
98
+ return f"Crash dialog detected: '{indicator}'"
99
+ return None
100
+ except Exception:
101
+ return None
102
+
103
+
104
+ def take_screenshot(driver, output_dir: str, test_id: str, step: int) -> str:
105
+ """Take screenshot and save to output dir."""
106
+ Path(output_dir).mkdir(parents=True, exist_ok=True)
107
+ path = os.path.join(output_dir, f"{test_id}_step{step}.png")
108
+ try:
109
+ driver.save_screenshot(path)
110
+ return path
111
+ except Exception:
112
+ return None
113
+
114
+
115
+ # ─────────────────────────────────────────
116
+ # Core test runner
117
+ # ─────────────────────────────────────────
118
+
119
+ def run_test_automated(driver, test: dict, output_dir: str) -> TestResult:
120
+ """Execute a single test case via Appium driver."""
121
+ from appium.webdriver.common.appiumby import AppiumBy
122
+
123
+ result = TestResult(test["id"], test["title"])
124
+ start = time.time()
125
+
126
+ try:
127
+ steps = test.get("steps", [])
128
+
129
+ for i, step in enumerate(steps):
130
+ action = step.get("action", "")
131
+ result.steps_log.append(f"Step {i+1}: {action}")
132
+
133
+ # Crash check after each step
134
+ crash = check_for_crash(driver)
135
+ if crash:
136
+ result.status = "FAIL"
137
+ result.error = crash
138
+ result.issues_found.append({"severity": "Critical", "description": crash})
139
+ result.screenshot_path = take_screenshot(driver, output_dir, test["id"], i)
140
+ return result
141
+
142
+ # Execute action
143
+ try:
144
+ act_type = step.get("type", "")
145
+
146
+ if act_type == "tap" and step.get("locator"):
147
+ el = safe_find(driver, AppiumBy.ACCESSIBILITY_ID, step["locator"])
148
+ el.click()
149
+
150
+ elif act_type == "tap_id" and step.get("locator"):
151
+ el = safe_find(driver, AppiumBy.ID, step["locator"])
152
+ el.click()
153
+
154
+ elif act_type == "type" and step.get("locator"):
155
+ el = safe_find(driver, AppiumBy.ACCESSIBILITY_ID, step["locator"])
156
+ el.clear()
157
+ el.send_keys(step.get("value", ""))
158
+
159
+ elif act_type == "type_id" and step.get("locator"):
160
+ el = safe_find(driver, AppiumBy.ID, step["locator"])
161
+ el.clear()
162
+ el.send_keys(step.get("value", ""))
163
+
164
+ elif act_type == "scroll_down":
165
+ driver.swipe(500, 1000, 500, 300, 500)
166
+
167
+ elif act_type == "back":
168
+ driver.back()
169
+
170
+ elif act_type == "wait":
171
+ time.sleep(step.get("seconds", 1))
172
+
173
+ elif act_type == "assert_visible":
174
+ locator = step.get("locator", "")
175
+ try:
176
+ el = safe_find(driver, AppiumBy.ACCESSIBILITY_ID, locator, timeout=5)
177
+ result.assertions.append({"check": f"Element visible: {locator}", "passed": True})
178
+ except Exception:
179
+ result.assertions.append({"check": f"Element visible: {locator}", "passed": False})
180
+ result.status = "FAIL"
181
+ result.screenshot_path = take_screenshot(driver, output_dir, test["id"], i)
182
+
183
+ elif act_type == "assert_text":
184
+ expected = step.get("expected", "")
185
+ page_source = driver.page_source
186
+ found = expected.lower() in page_source.lower()
187
+ result.assertions.append({"check": f"Text present: '{expected}'", "passed": found})
188
+ if not found:
189
+ result.status = "FAIL"
190
+ result.screenshot_path = take_screenshot(driver, output_dir, test["id"], i)
191
+
192
+ elif act_type == "assert_no_error":
193
+ page = driver.page_source.lower()
194
+ error_words = ["error", "ошибка", "failed", "exception"]
195
+ has_error = any(w in page for w in error_words)
196
+ result.assertions.append({"check": "No error on screen", "passed": not has_error})
197
+ if has_error:
198
+ result.status = "FAIL"
199
+ result.issues_found.append({"severity": "Major", "description": "Error text found on screen"})
200
+
201
+ except Exception as step_err:
202
+ result.steps_log.append(f" ⚠️ Step failed: {step_err}")
203
+ result.screenshot_path = take_screenshot(driver, output_dir, test["id"], i)
204
+
205
+ # Final crash check
206
+ crash = check_for_crash(driver)
207
+ if crash:
208
+ result.status = "FAIL"
209
+ result.error = crash
210
+ result.issues_found.append({"severity": "Critical", "description": crash})
211
+ elif result.status == "PENDING":
212
+ result.status = "PASS"
213
+
214
+ except Exception as e:
215
+ result.status = "ERROR"
216
+ result.error = traceback.format_exc()
217
+ finally:
218
+ result.duration_ms = int((time.time() - start) * 1000)
219
+
220
+ return result
221
+
222
+
223
+ def run_test_static(test: dict) -> TestResult:
224
+ """Mark test as requiring manual execution (no emulator available)."""
225
+ result = TestResult(test["id"], test["title"])
226
+ result.status = "MANUAL_REQUIRED"
227
+ result.steps_log = [f"Step {i+1}: {s.get('action', s)}" for i, s in enumerate(test.get("steps", []))]
228
+ result.assertions = [{"check": a, "passed": None} for a in test.get("assertions", [])]
229
+ result.duration_ms = 0
230
+ return result
231
+
232
+
233
+ # ─────────────────────────────────────────
234
+ # Main
235
+ # ─────────────────────────────────────────
236
+
237
+ def main():
238
+ parser = argparse.ArgumentParser()
239
+ parser.add_argument("--apk", help="Path to APK file")
240
+ parser.add_argument("--tests", help="Path to tests.json", default="tests.json")
241
+ parser.add_argument("--output", help="Output directory", default="results")
242
+ parser.add_argument("--device", help="Device/emulator ID", default=None)
243
+ parser.add_argument("--suite", help="Test suite filter: smoke|functional|all", default="all")
244
+ parser.add_argument("--static", action="store_true", help="Static mode — no emulator")
245
+ args = parser.parse_args()
246
+
247
+ Path(args.output).mkdir(parents=True, exist_ok=True)
248
+
249
+ # Load tests
250
+ if not os.path.exists(args.tests):
251
+ print(f"❌ Tests file not found: {args.tests}")
252
+ print(" Generate tests first with Claude's Phase 2-3 workflow.")
253
+ return
254
+
255
+ with open(args.tests) as f:
256
+ tests_data = json.load(f)
257
+
258
+ tests = tests_data.get("tests", tests_data) if isinstance(tests_data, dict) else tests_data
259
+
260
+ # Filter by suite
261
+ if args.suite != "all":
262
+ tests = [t for t in tests if t.get("type", "").lower() == args.suite.lower()]
263
+
264
+ print(f"\n🚀 Running {len(tests)} tests | Mode: {'STATIC' if args.static else 'AUTOMATED'}")
265
+ print("=" * 60)
266
+
267
+ results = []
268
+ driver = None
269
+
270
+ if not args.static and args.apk:
271
+ try:
272
+ print("📱 Connecting to device via Appium...")
273
+ driver = create_driver(args.apk, args.device)
274
+ print("✅ Device connected\n")
275
+ except Exception as e:
276
+ print(f"⚠️ Could not connect to device: {e}")
277
+ print(" Falling back to STATIC mode.\n")
278
+ driver = None
279
+
280
+ for test in tests:
281
+ test_id = test.get("id", "?")
282
+ title = test.get("title", "Untitled")
283
+ print(f" {'▶' if driver else '📋'} {test_id}: {title}", end=" ", flush=True)
284
+
285
+ if driver:
286
+ result = run_test_automated(driver, test, args.output)
287
+ else:
288
+ result = run_test_static(test)
289
+
290
+ results.append(result)
291
+
292
+ status_icon = {"PASS": "✅", "FAIL": "❌", "ERROR": "💥",
293
+ "SKIP": "⏭️", "MANUAL_REQUIRED": "📋"}.get(result.status, "?")
294
+ print(f"→ {status_icon} {result.status} ({result.duration_ms}ms)")
295
+
296
+ if driver:
297
+ try:
298
+ driver.quit()
299
+ except Exception:
300
+ pass
301
+
302
+ # Save raw results
303
+ results_path = os.path.join(args.output, "results.json")
304
+ summary = {
305
+ "timestamp": datetime.datetime.now().isoformat(),
306
+ "mode": "static" if not driver else "automated",
307
+ "apk": args.apk,
308
+ "total": len(results),
309
+ "passed": sum(1 for r in results if r.status == "PASS"),
310
+ "failed": sum(1 for r in results if r.status == "FAIL"),
311
+ "errors": sum(1 for r in results if r.status == "ERROR"),
312
+ "manual": sum(1 for r in results if r.status == "MANUAL_REQUIRED"),
313
+ "tests": [r.to_dict() for r in results],
314
+ }
315
+
316
+ with open(results_path, "w") as f:
317
+ json.dump(summary, f, indent=2, ensure_ascii=False)
318
+
319
+ print("\n" + "=" * 60)
320
+ print(f"📊 RESULTS: {summary['passed']} passed | {summary['failed']} failed | {summary['errors']} errors | {summary['manual']} manual")
321
+ print(f"💾 Saved to: {results_path}")
322
+ print(f"\n📄 Generate report: python3 scripts/generate_report.py --results {args.output}/ --output test_report.html")
323
+
324
+
325
+ if __name__ == "__main__":
326
+ main()
@@ -0,0 +1,232 @@
1
+ ---
2
+ name: test-web-ui
3
+ description: >
4
+ Automated web QA skill: analyzes a website or project, generates end-user use cases,
5
+ derives a structured test plan, executes tests via Playwright browser automation, and
6
+ produces a full HTML/Markdown QA report with screenshots and pass/fail results.
7
+
8
+ TRIGGER this skill whenever the user asks to: test a website, run QA on a web app,
9
+ check if a site works, find bugs on a site, validate a web project, create a test plan
10
+ for a website, run functional tests, check a landing page, audit a web app for issues,
11
+ test user flows — or any variation of "проверить сайт", "протестировать сайт",
12
+ "QA сайта", "тест веб-приложения", "найти баги на сайте". Even if the user just says
13
+ "посмотри работает ли всё нормально на сайте" — use this skill.
14
+ ---
15
+
16
+ # Web Tester Skill
17
+
18
+ Transforms any website or project description into a structured QA run:
19
+ **Discover → Plan → Execute → Report**
20
+
21
+ ---
22
+
23
+ ## Overview of Phases
24
+
25
+ ```
26
+ Phase 1 → DISCOVERY : Explore the site, understand its purpose and features
27
+ Phase 2 → USE CASES : Generate end-user use case list
28
+ Phase 3 → TEST PLAN : Convert use cases into concrete, executable test cases
29
+ Phase 4 → EXECUTION : Run tests with Playwright, capture screenshots
30
+ Phase 5 → REPORT : Compile HTML + Markdown report with all results
31
+ ```
32
+
33
+ ---
34
+
35
+ ## Phase 1: Discovery
36
+
37
+ ### What to gather
38
+ - **URL** — if the user provides a live URL, use Playwright to crawl it
39
+ - **Project files** — if no live URL, inspect source files in `/mnt/user-data/uploads/`
40
+ - **Purpose** — what does the site do? (landing page, e-commerce, dashboard, blog, etc.)
41
+ - **Key pages** — home, auth, main feature pages, forms, checkout, etc.
42
+ - **Tech stack** — optional but helpful for targeted checks
43
+
44
+ ### Discovery script
45
+ Run `scripts/discover.py` (see below) to auto-detect pages, links, forms, interactive elements.
46
+
47
+ **If no live URL is available** (e.g., network blocked or local project):
48
+ - Read source files (HTML, JSX, Vue, etc.) in uploads directory
49
+ - Extract routes, components, visible text, form fields, navigation links
50
+ - Use Playwright with `page.set_content()` to render and inspect local HTML files
51
+
52
+ ---
53
+
54
+ ## Phase 2: Generate Use Cases
55
+
56
+ After discovery, produce a use case list **from the perspective of an end user**.
57
+
58
+ ### Format
59
+ ```
60
+ UC-01: [Actor] can [action] so that [goal]
61
+ UC-02: [Actor] can [action] so that [goal]
62
+ ...
63
+ ```
64
+
65
+ ### Categories to cover
66
+ - **Navigation** — user browses pages, menu works, links resolve
67
+ - **Authentication** — sign up, log in, password reset, logout
68
+ - **Core feature flows** — the main thing the site does (purchase, search, submit form, etc.)
69
+ - **Content validation** — required text, images, prices, labels appear correctly
70
+ - **Forms** — fill in, submit, validate errors, success states
71
+ - **Responsiveness** — works on mobile viewport
72
+ - **Error handling** — 404 pages, empty states, invalid inputs
73
+ - **Performance / visual** — no broken images, no console errors, reasonable load
74
+
75
+ Aim for **10–25 use cases** depending on site complexity.
76
+
77
+ ---
78
+
79
+ ## Phase 3: Test Plan
80
+
81
+ Convert each use case into a concrete test case:
82
+
83
+ ```
84
+ TC-01 [UC-01]: Homepage Navigation
85
+ Given: User opens the site root URL
86
+ When: Page finishes loading
87
+ Then: - Page title is not empty
88
+ - Navigation menu is visible
89
+ - Logo/brand element is present
90
+ - No 404/500 status code
91
+ Checks: title, nav links count > 0, hero text present
92
+ ```
93
+
94
+ ### Test case types to include
95
+ | Type | Examples |
96
+ |------|---------|
97
+ | **Presence checks** | Element exists, text is visible, image loads |
98
+ | **Functional checks** | Button clickable, form submits, menu expands |
99
+ | **Data validation** | Price format, phone format, required fields |
100
+ | **Navigation checks** | Links don't 404, routing works |
101
+ | **Form validation** | Empty submit shows errors, valid submit succeeds |
102
+ | **Responsiveness** | Mobile viewport renders without overflow |
103
+ | **Console errors** | No JS errors on page load |
104
+ | **Accessibility basics** | Images have alt text, headings hierarchy |
105
+
106
+ ---
107
+
108
+ ## Phase 4: Test Execution
109
+
110
+ ### Use the execution script
111
+ ```bash
112
+ python3 /mnt/skills/user/web-tester/scripts/run_tests.py \
113
+ --url <URL_OR_LOCAL_PATH> \
114
+ --test-plan /home/claude/test_plan.json \
115
+ --output /home/claude/test_results/
116
+ ```
117
+
118
+ ### What the script does
119
+ 1. Launches headless Chromium (no-sandbox mode for container)
120
+ 2. For each test case:
121
+ - Navigates to the target page
122
+ - Captures a **before screenshot**
123
+ - Executes assertions (element presence, text content, clicks, form fills)
124
+ - Captures an **after screenshot** if interaction occurred
125
+ - Records PASS / FAIL / SKIP + error message
126
+ 3. Saves all screenshots to `test_results/screenshots/`
127
+ 4. Writes `test_results/results.json`
128
+
129
+ ### Manual execution (when no URL — code project)
130
+ If only source files are available:
131
+ 1. Serve them locally: `python3 -m http.server 8080 --directory /home/claude/project/`
132
+ 2. Run tests against `http://localhost:8080`
133
+
134
+ ### Key Playwright patterns to use
135
+
136
+ ```python
137
+ # Launch
138
+ from playwright.sync_api import sync_playwright
139
+ browser = p.chromium.launch(args=['--no-sandbox', '--disable-dev-shm-usage'])
140
+ context = browser.new_context(viewport={'width': 1280, 'height': 800})
141
+ page = context.new_page()
142
+
143
+ # Console error collection
144
+ errors = []
145
+ page.on('console', lambda msg: errors.append(msg.text) if msg.type == 'error' else None)
146
+
147
+ # Mobile viewport test
148
+ mobile = browser.new_context(viewport={'width': 390, 'height': 844}, is_mobile=True)
149
+
150
+ # Screenshot
151
+ page.screenshot(path='screenshots/tc01_home.png', full_page=True)
152
+
153
+ # Assertions
154
+ page.wait_for_load_state('networkidle')
155
+ assert page.locator('nav').is_visible()
156
+ assert page.title() != ''
157
+ count = page.locator('a').count()
158
+ text = page.locator('h1').text_content()
159
+ ```
160
+
161
+ ---
162
+
163
+ ## Phase 5: Report Generation
164
+
165
+ After execution, generate report using `scripts/generate_report.py`:
166
+
167
+ ```bash
168
+ python3 /mnt/skills/user/web-tester/scripts/generate_report.py \
169
+ --results /home/claude/test_results/results.json \
170
+ --screenshots /home/claude/test_results/screenshots/ \
171
+ --output /mnt/user-data/outputs/qa_report.html
172
+ ```
173
+
174
+ ### Report contents
175
+ - **Summary dashboard** — total tests, pass/fail/skip counts, pass rate %, tested URL, timestamp
176
+ - **Use case list** with traceability to test cases
177
+ - **Test results table** — TC ID, name, status badge, duration, error message
178
+ - **Screenshot gallery** — inline base64 screenshots per test case
179
+ - **Console errors log** — any JS errors captured
180
+ - **Recommendations** — auto-generated improvement suggestions based on failures
181
+
182
+ ### Report format
183
+ - Primary: **HTML** (self-contained, with embedded screenshots)
184
+ - Secondary: **Markdown** summary for quick reading
185
+
186
+ ---
187
+
188
+ ## Workflow Summary (step-by-step)
189
+
190
+ ```
191
+ 1. Receive URL or project files from user
192
+ 2. Run discovery → understand pages, structure, features
193
+ 3. Generate use case list → show to user, get approval or continue
194
+ 4. Generate test plan JSON → structured test cases
195
+ 5. Run Playwright tests → collect results + screenshots
196
+ 6. Generate HTML report → save to /mnt/user-data/outputs/
197
+ 7. Present report to user using present_files tool
198
+ ```
199
+
200
+ ---
201
+
202
+ ## Handling Common Situations
203
+
204
+ **No internet access (container network restrictions)**
205
+ → Ask user to provide HTML files, or serve local project
206
+ → Use `page.set_content()` for static HTML testing
207
+ → Use `python3 -m http.server` for multi-page projects
208
+
209
+ **Site requires authentication**
210
+ → Ask user for test credentials OR
211
+ → Test only the public-facing pages
212
+ → Mark auth-gated tests as SKIP with note
213
+
214
+ **Single-page app (React/Vue/Angular)**
215
+ → Wait for `networkidle` state
216
+ → Use `page.wait_for_selector()` before asserting dynamic content
217
+ → Check that JS bundle loads without console errors
218
+
219
+ **Large site (many pages)**
220
+ → Focus on critical user paths first
221
+ → Limit to top 5–10 most important flows
222
+ → Mention in report which pages were NOT covered
223
+
224
+ ---
225
+
226
+ ## Reference Files
227
+
228
+ - `references/test_case_schema.md` — JSON schema for test_plan.json
229
+ - `references/report_template.md` — Report structure reference
230
+ - `scripts/discover.py` — Site discovery automation
231
+ - `scripts/run_tests.py` — Test execution engine
232
+ - `scripts/generate_report.py` — HTML report generator
@@ -0,0 +1,102 @@
1
+ # Test Plan JSON Schema
2
+
3
+ ## Structure
4
+
5
+ ```json
6
+ {
7
+ "meta": {
8
+ "site_name": "My Site",
9
+ "url": "https://example.com",
10
+ "created_at": "2025-01-01T12:00:00",
11
+ "description": "QA test plan for homepage and auth flows"
12
+ },
13
+ "use_cases": [
14
+ {
15
+ "id": "UC-01",
16
+ "actor": "Visitor",
17
+ "action": "views the homepage",
18
+ "goal": "understand what the product offers",
19
+ "test_cases": ["TC-01", "TC-02"]
20
+ }
21
+ ],
22
+ "test_cases": [
23
+ {
24
+ "id": "TC-01",
25
+ "use_case": "UC-01",
26
+ "name": "Homepage loads and shows key content",
27
+ "path": "/",
28
+ "mobile": false,
29
+ "steps": [
30
+ { "action": "scroll", "selector": "", "value": "" }
31
+ ],
32
+ "assertions": [
33
+ { "type": "title_not_empty", "description": "Page has a title" },
34
+ { "type": "visible", "selector": "nav", "description": "Navigation is visible" },
35
+ { "type": "visible", "selector": "h1", "description": "Main heading exists" },
36
+ { "type": "count_gt", "selector": "a", "expected": "2", "description": "Multiple links exist" },
37
+ { "type": "images_loaded", "description": "All images loaded" },
38
+ { "type": "no_console_errors", "description": "No JS console errors" }
39
+ ]
40
+ }
41
+ ]
42
+ }
43
+ ```
44
+
45
+ ## Assertion Types
46
+
47
+ | type | required fields | description |
48
+ |------|----------------|-------------|
49
+ | `visible` | selector | Element is visible on page |
50
+ | `not_visible` | selector | Element is NOT visible |
51
+ | `text_contains` | selector, expected | Element text contains string |
52
+ | `title_not_empty` | — | Page title is not blank |
53
+ | `title_contains` | expected | Page title contains string |
54
+ | `count_gt` | selector, expected | Count of elements > N |
55
+ | `count_eq` | selector, expected | Count of elements == N |
56
+ | `url_contains` | expected | Current URL contains string |
57
+ | `no_console_errors` | — | No JS errors in console |
58
+ | `images_loaded` | — | All img elements loaded (naturalWidth > 0) |
59
+ | `has_alt_text` | — | All images have alt attributes |
60
+ | `clickable` | selector | Element is visible and enabled |
61
+ | `attribute_equals` | selector, attribute, expected | Element attribute == value |
62
+
63
+ ## Step Actions
64
+
65
+ | action | selector | value | description |
66
+ |--------|----------|-------|-------------|
67
+ | `click` | CSS selector | — | Click element |
68
+ | `fill` | CSS selector | text | Fill input with text |
69
+ | `wait_for` | CSS selector | — | Wait until element appears |
70
+ | `scroll` | — | — | Scroll to bottom of page |
71
+ | `hover` | CSS selector | — | Hover over element |
72
+
73
+ ## Common Selectors Cheatsheet
74
+
75
+ ```css
76
+ /* Navigation */
77
+ nav, header nav, [role=navigation]
78
+
79
+ /* Buttons */
80
+ button, [role=button], input[type=submit]
81
+
82
+ /* Forms */
83
+ form, input, textarea, select
84
+
85
+ /* Images */
86
+ img
87
+
88
+ /* Headings */
89
+ h1, h2, h3
90
+
91
+ /* Links */
92
+ a[href]
93
+
94
+ /* Footer */
95
+ footer
96
+
97
+ /* Error messages */
98
+ .error, [role=alert], .alert-danger
99
+
100
+ /* Success messages */
101
+ .success, .alert-success, [role=status]
102
+ ```