accessibility-testing
Use this skill when you need to design accessibility testing against WCAG, keyboard navigation, and assistive technology scenarios; triggers include accessibility testing and a11y testing.
Install
npx skills add https://github.com/naodeng/awesome-qa-skills/tree/main/skills/en/testing-types/accessibility-testing
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install naodeng-awesome-qa-skills@llmmart
git clone https://github.com/naodeng/awesome-qa-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole naodeng/awesome-qa-skills collection as a plugin from our marketplace. Git is the plain clone.
README
Accessibility Testing
Skill Overview
Need help with accessibility testing in a real project context; Need an output that can be used directly for execution, review, or follow-up.
How to Use
- Open
SKILL.mdin this folder and confirm this skill fits your task. - In your AI tool, call
@skill accessibility-testing, then add your real project context and goal. - If you need a specific output format (table, checklist, report), include it directly in your request.
One-Click Install Script
Run from the repository root:
macOS / Linux
bash ./scripts/install-skills-mac.sh --tool codex --lang en --skill accessibility-testing
Windows PowerShell
powershell -ExecutionPolicy Bypass -File .\scripts\install-skills-windows.ps1 -Tool codex -Lang en -Skill accessibility-testing
Skill manifest
Accessibility Testing (English)
Chinese version: See the corresponding Chinese skill.
When to Use
- Need help with accessibility testing in a real project context.
- Need an output that can be used directly for execution, review, or follow-up.
Workflow
- Read and follow the main prompt listed under Progressive disclosure (coverage, structure, quality bar).
- Add only project context that changes the result: scope, environment, constraints, risks, dependencies, expected deliverable.
- If input is incomplete, return a usable first draft and explicitly mark assumptions and gaps.
- Default to Markdown; switch formats only when the user asks.
Core Constraints
- Prioritize by risk / business impact — do not treat everything equally.
- Separate confirmed facts from current assumptions.
- Do not invent endpoints, fields, environments, or root causes the user did not provide.
- Keep output executable: concrete scenarios, clear priority, clear next steps.
Progressive Disclosure
- Before producing output, read and follow
prompts/accessibility-testing.md(minimum coverage, output structure, quality bar). - When Excel/CSV/JSON/Word is requested: read
output-formats.mdand honor the format. - When a ready-made template fits: use matching files under
output-templates/. - For deep framework/troubleshoot/schema notes: read only the relevant file(s) under
references/, do not load the whole directory. - For format conversion or helper checks: prefer existing
scripts/over reinventing. - For evaluating/regressing this skill: use
evals/with skill-up.
Pre-delivery Checklist
- Followed the main prompt's output structure
- Minimum coverage focus: scope and target users, keyboard access, focus order and visible focus, screen reader semantics and labels, headings and landmarks, form errors and validation feedback, color contrast and non-color cues, images, icons, and alternative text, ... (details in main prompt)
- Covered the minimum checklist, or explained omissions
- High-risk items have explicit priority
- Did not invent details the user did not provide
- Assumptions and gaps are marked
Common Pitfalls
- Do not pretend completeness when scope/context is missing.
- Do not treat every item as equally important.
- Do not skip assumptions and information gaps.
- Do not dump generic theory unrelated to the current toolchain.
Files (awesome-qa-skills)
-
agents
-
openai.yaml 477 B
version: 1 metadata: key: "accessibility-testing" interface: display_name: "Accessibility Testing (English)" short_description: "Use this skill when you need to design accessibility testing against WCAG, keyboard navigation, and assistive technology scenarios; triggers include accessibil…" default_prompt: "Use the accessibility-testing skill to complete this testing task with structured outputs and practical next steps." policy: allow_implicit_invocation: true
-
-
evals
-
cases
-
basic-success.yaml 729 B
id: basic-success title: "Accessibility: barriers and fix priority on critical journeys" description: | Return keyboard/screen-reader focused checks and fix priority—not a WCAG lecture. input: prompt: | Use accessibility-testing. Page: checkout (address form + pay button). Goal: keyboard and screen-reader usable; WCAG-minded but practical. Platform: Web Chrome/Safari. Produce an a11y plan with fix priority. expect: must_contain: - "keyboard" - "screen reader" - "WCAG" must_not_contain: - "TODO" - "I cannot" judge: type: rule_based success: - output_contains: all: - "Task Understanding" - "Priority" - "WCAG" - "keyboard" -
edge-domain-boundary.yaml 770 B
id: edge-domain-boundary title: "Accessibility: not a functional regression suite" description: | When asked for full functional cases, stay a11y-focused and clarify the boundary. input: prompt: | Use accessibility-testing. A tester rewrote the ask as 'regress every checkout functional point, and glance at a11y'. Deliver only high-risk accessibility checks and fix priority; call out what belongs to functional testing. expect: must_contain: - "accessibility" - "functional" - "scope" - "automation" - "manual check" must_not_contain: - "TODO" - "I cannot" judge: type: rule_based success: - output_contains: all: - "keyboard" - "Priority" - "scope" - "automation" -
edge-incomplete-input.yaml 697 B
id: edge-incomplete-input title: "Accessibility: draft gaps without prototypes" description: | One-line asks still get a draft checklist plus information gaps. input: prompt: | Use accessibility-testing. Only sentence: new campaign page needs accessibility. No designs, no AT scope. Give a first-pass checklist and list must-have information. expect: must_contain: - "Open Questions" - "assumption" - "gaps" - "assumptions" - "confirm" must_not_contain: - "TODO" - "I cannot" judge: type: rule_based success: - output_contains: all: - "Information Gaps" - "assumption" - "gaps" - "assumptions"
-
-
eval.yaml 559 B
schema_version: v1alpha1 environment: type: none skills: - source: local_path path: . engine: name: claude_code # model is optional; omit to use engine default # model: # provider: anthropic # name: claude-sonnet-4-6 cases: files: - evals/cases/basic-success.yaml - evals/cases/edge-incomplete-input.yaml - evals/cases/edge-domain-boundary.yaml defaults: timeout_seconds: 180 max_turns: 8 expect: exit_code: 0 must_not_contain: - "TODO" - "I cannot" report: formats: [json]
-
-
output-templates
-
template-csv.csv 206 B · in bundle
-
template-excel.tsv 358 B · in bundle
-
template-json.json 393 B
{ "meta": { "skill": "", "scope": "", "environment": "", "priority": "" }, "inputs": { "requirement": "", "constraints": [], "risks": [] }, "execution": [ { "step": 1, "action": "", "expected": "" } ], "results": { "status": "", "evidence": [], "defects": [] }, "next_actions": [ { "owner": "", "eta": "", "action": "" } ] } -
template-markdown.md 250 B
# QA Output Template ## Summary - Skill: - Scope: - Environment: - Priority: ## Inputs - Requirement: - Constraints: - Risks: ## Execution 1. Step 1 2. Step 2 3. Step 3 ## Results - Status: - Evidence: - Defects: ## Next Actions - Owner: - ETA: -
template-word.md 302 B
QA Report ========= 1. Basic Information - Skill: - Scope: - Environment: - Priority: 2. Requirement and Constraints - Requirement: - Constraints: - Risks: 3. Test/Review Process - Step 1: - Step 2: - Step 3: 4. Outcome - Status: - Evidence: - Defects: 5. Follow-up Plan - Owner: - ETA: - Action: -
template-xmind.md 317 B
# QA Output Mindmap - QA Output - Meta - Skill - Scope - Environment - Priority - Inputs - Requirement - Constraints - Risks - Execution - Step 1 - Step 2 - Step 3 - Results - Status - Evidence - Defects - Next Actions - Owner - ETA - Action
-
-
prompts
-
accessibility-testing.md 1.9 KB
# Accessibility Testing Prompt Assess product accessibility risks and produce a practical testing plan or review output that the team can act on directly. ## Role - Act as a senior QA and accessibility testing expert with strong product-risk judgment and practical remediation awareness. ## Input - product pages, flows, prototypes, requirements, or design notes - target platforms, browsers, devices, and assistive technology scope - existing issues, standards targets, release scope, and constraints ## What to do 1. Understand the target users, target flows, and accessibility expectations. 2. Identify the highest-risk barriers first instead of treating every page equally. 3. Focus on checks that can block access, completion, understanding, or confidence. ## Execution Rules - Use WCAG-based thinking, but keep the output practical instead of turning it into a standards lecture. - Separate confirmed issues, likely risks, and items that still need manual verification. - If the release scope is small, focus on the in-scope journeys, forms, navigation, and critical states. ## Minimum Coverage Checklist Unless the user explicitly narrows the scope, make sure the result addresses these items: - scope and target users - keyboard access - focus order and visible focus - screen reader semantics and labels - headings and landmarks - form errors and validation feedback - color contrast and non-color cues - images, icons, and alternative text - dynamic content and announcements - responsive and zoom behavior - severity and user impact - missing information and assumptions ## Output Return the result in this order: ### 1. Task Understanding ### 2. Information Gaps ### 3. Top Accessibility Risks ### 4. Core Checks or Findings ### 5. Execution or Fix Priority ### 6. Next-Step Recommendations ## Quality Bar - Be specific about the barrier and affected users. - Do not list low-value checks before blockers. - Do not claim full compliance without enough evidence.
-
-
references
-
local
-
FAQ.md 537 B
# Archived Local Reference This file is a lightweight legacy note. The previous long snapshot was removed to avoid duplicate and outdated guidance. ## Use Instead - Main prompt: `prompts/accessibility-testing.md` - Main entry: `SKILL.md` - Output format guide: `output-formats.md` ## Notes - Keep using the current prompt and `SKILL.md` as the source of truth. - Load `references/`, `examples/`, or `scripts/` only when the task really needs extra detail. - Do not rely on this file for the latest prompt wording or workflow rules. -
Users-nao.deng-awsomeCode-awesome-qa-skills-skills-testing-types-accessibility-testing-examples-axe-playwright.md 599 B
# Local Example Collection Reference This file points to an example collection that used to be stored as a larger snapshot. ## What It Is - A local note for example-related materials. - Useful only when you need extra example context beyond the main prompt. ## Prefer These Files - `SKILL.md` - `prompts/accessibility-testing.md` - `references/` ## Notes - This file is now a short local reference instead of a full snapshot copy. - Use it only when you need extra background beyond the main prompt and `SKILL.md`. - If the current prompt conflicts with this file, follow the current prompt. -
Users-nao.deng-awsomeCode-awesome-qa-skills-skills-testing-types-accessibility-testing-examples.md 599 B
# Local Example Collection Reference This file points to an example collection that used to be stored as a larger snapshot. ## What It Is - A local note for example-related materials. - Useful only when you need extra example context beyond the main prompt. ## Prefer These Files - `SKILL.md` - `prompts/accessibility-testing.md` - `references/` ## Notes - This file is now a short local reference instead of a full snapshot copy. - Use it only when you need extra background beyond the main prompt and `SKILL.md`. - If the current prompt conflicts with this file, follow the current prompt.
-
-
troubleshooting.md 5.1 KB
## Troubleshooting ### Issue 1: axe-core Reports Too Many False Positives **Symptoms:** Automated test reports many issues, but they are actually false positives **Solution:** 1. Configure rules to exclude known false positives: ```typescript await new AxeBuilder({ page }) .disableRules(['color-contrast']) // Exclude specific rules .analyze(); ``` 2. Use tag filtering: ```typescript await new AxeBuilder({ page }) .withTags(['wcag2a', 'wcag2aa']) // Only check WCAG 2.1 A/AA .analyze(); ``` 3. Manually verify suspicious issues 4. Update to latest version of axe-core ### Issue 2: Cannot Detect Accessibility Issues in Dynamic Content **Symptoms:** Issues in modals, dropdowns, etc. are not detected **Solution:** 1. Run tests after triggering dynamic content: ```typescript await page.click('[data-testid="open-modal"]'); await page.waitForSelector('[role="dialog"]'); const results = await new AxeBuilder({ page }).analyze(); ``` 2. Test specific regions: ```typescript await new AxeBuilder({ page }) .include('[role="dialog"]') .analyze(); ``` 3. Test accessibility in different states ### Issue 3: Keyboard Navigation Tests Fail **Symptoms:** Some elements cannot be accessed via keyboard **Solution:** 1. Check tabindex attribute: ```html <!-- ✅ Recommended: Use tabindex="0" to make element focusable --> <div role="button" tabindex="0">Click me</div> <!-- ❌ Avoid: tabindex > 0 breaks natural Tab order --> <div tabindex="5">Bad practice</div> ``` 2. Use semantic HTML: ```html <!-- ✅ Recommended: Native button is automatically focusable --> <button>Click me</button> <!-- ❌ Not recommended: Requires additional accessibility attributes --> <div onclick="...">Click me</div> ``` 3. Add keyboard event handlers: ```typescript element.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { // Handle activation } }); ``` ### Issue 4: Insufficient Color Contrast **Symptoms:** axe-core reports color contrast issues **Solution:** 1. Use contrast checking tools: - Chrome DevTools Lighthouse - WebAIM Contrast Checker - Colour Contrast Analyser 2. Ensure contrast ratio at least: - Normal text: 4.5:1 - Large text (18pt+): 3:1 - UI components: 3:1 3. Adjust color scheme: ```css /* ❌ Insufficient contrast */ color: #999; /* Gray text */ background: #fff; /* White background */ /* ✅ Sufficient contrast */ color: #595959; /* Dark gray text */ background: #fff; /* White background */ ``` ### Issue 5: Screen Reader Cannot Read Content Correctly **Symptoms:** Content is confusing or missing when using screen reader **Solution:** 1. Use semantic HTML: ```html <!-- ✅ Recommended --> <nav> <ul> <li><a href="/">Home</a></li> </ul> </nav> <!-- ❌ Not recommended --> <div class="nav"> <div class="item">Home</div> </div> ``` 2. Use ARIA attributes correctly: ```html <!-- ✅ Recommended: Provide meaningful label --> <button aria-label="Close dialog">×</button> <!-- ❌ Not recommended: Screen reader only reads "times" --> <button>×</button> ``` 3. Use aria-live to announce dynamic changes: ```html <div aria-live="polite" aria-atomic="true"> <!-- Dynamic content --> </div> ``` ### Issue 6: Form Accessibility Issues **Symptoms:** Form controls lack labels or error messages are unclear **Solution:** 1. Associate label with input: ```html <!-- ✅ Recommended: Explicit association --> <label for="email">Email</label> <input id="email" type="email" /> <!-- ✅ Recommended: Implicit association --> <label> Email <input type="email" /> </label> ``` 2. Provide clear error messages: ```html <input id="email" type="email" aria-invalid="true" aria-describedby="email-error" /> <span id="email-error" role="alert"> Please enter a valid email address </span> ``` 3. Use fieldset and legend for grouping: ```html <fieldset> <legend>Contact Information</legend> <!-- Form controls --> </fieldset> ``` ### Issue 7: Tests Fail in CI Environment **Symptoms:** Tests pass locally but fail in CI environment **Solution:** 1. Ensure browsers are installed in CI environment: ```yaml - name: Install Playwright run: npx playwright install --with-deps ``` 2. Check environment differences (fonts, rendering) 3. Save screenshots and reports on failure: ```typescript if (violations.length > 0) { await page.screenshot({ path: 'a11y-violations.png' }); fs.writeFileSync('a11y-report.json', JSON.stringify(violations)); } ``` 4. Use Docker containers to ensure environment consistency ### Get More Help If the issue persists: 1. Check [FAQ.md](local/FAQ.md) 2. Review example README.md files 3. Reference WCAG 2.1 official documentation 4. Use WebAIM resources 5. Submit a new Issue with detailed information **Related skills:** functional-testing, manual-testing, automation-testing, test-case-writing.
-
-
scripts
-
batch_convert_templates.py 2.7 KB
#!/usr/bin/env python3 import argparse import subprocess import sys from pathlib import Path def detect_from(file: Path) -> str: ext = file.suffix.lower() if file.name.endswith('.word.md'): return 'markdown' return { '.md': 'markdown', '.markdown': 'markdown', '.json': 'json', '.csv': 'csv', '.tsv': 'excel', '.docx': 'word', '.xlsx': 'excel', '.xmind': 'xmind', }.get(ext, 'markdown') def run_convert(convert_script: Path, src: Path, to_fmt: str, out: Path) -> int: cmd = [sys.executable, str(convert_script), str(src), '--from', detect_from(src), '--to', to_fmt, '--output', str(out)] return subprocess.call(cmd) def main() -> None: parser = argparse.ArgumentParser(description='Batch convert all template files into target formats.') parser.add_argument('--templates-dir', type=Path, default=Path('output-templates')) parser.add_argument('--artifacts-dir', type=Path, default=Path('artifacts')) parser.add_argument('--targets', default='word,excel,xmind,json,csv,markdown', help='comma-separated target formats') parser.add_argument('--skip-same', action='store_true', help='skip conversion when source format equals target format') args = parser.parse_args() cwd = Path.cwd() templates_dir = (cwd / args.templates_dir).resolve() artifacts_dir = (cwd / args.artifacts_dir).resolve() artifacts_dir.mkdir(parents=True, exist_ok=True) local_convert = (Path(__file__).resolve().parent / 'convert_formats.py').resolve() targets = [t.strip() for t in args.targets.split(',') if t.strip()] if not templates_dir.exists(): raise SystemExit(f'templates directory not found: {templates_dir}') files = [p for p in sorted(templates_dir.iterdir()) if p.is_file()] total = 0 failed = 0 for src in files: src_fmt = detect_from(src) for to_fmt in targets: if args.skip_same and src_fmt == to_fmt: continue out_ext = { 'json': '.json', 'csv': '.csv', 'excel': '.tsv', 'markdown': '.md', 'word': '.word.md', 'xmind': '.xmind.md', }[to_fmt] out = artifacts_dir / f"{src.stem}.to-{to_fmt}{out_ext}" total += 1 rc = run_convert(local_convert, src, to_fmt, out) if rc != 0: failed += 1 print(f'[FAILED] {src.name} -> {to_fmt}') else: print(f'[OK] {src.name} -> {out.name}') print(f'\nDone. total={total}, failed={failed}, artifacts={artifacts_dir}') if failed: raise SystemExit(1) if __name__ == '__main__': main() -
convert_formats.py 10.4 KB
#!/usr/bin/env python3 import argparse import csv import json import re import zipfile from pathlib import Path from typing import Any from xml.etree import ElementTree as ET # ---- parsing ---- def parse_markdown(path: Path) -> dict[str, Any]: text = path.read_text(encoding="utf-8", errors="ignore") lines = text.splitlines() headings: list[dict[str, Any]] = [] for line in lines: m = re.match(r"^(#{1,6})\s+(.*)$", line.strip()) if m: headings.append({"level": len(m.group(1)), "title": m.group(2).strip()}) return {"title": headings[0]["title"] if headings else path.stem, "headings": headings, "text": text} def parse_json(path: Path) -> dict[str, Any]: data = json.loads(path.read_text(encoding="utf-8", errors="ignore")) return {"title": path.stem, "data": data} def parse_csv_file(path: Path) -> dict[str, Any]: with path.open("r", encoding="utf-8", errors="ignore", newline="") as f: reader = csv.DictReader(f) rows = list(reader) return {"title": path.stem, "columns": reader.fieldnames or [], "rows": rows} def parse_docx(path: Path) -> dict[str, Any]: paragraphs: list[str] = [] with zipfile.ZipFile(path) as zf: with zf.open("word/document.xml") as f: root = ET.fromstring(f.read()) ns = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"} for p in root.findall(".//w:p", ns): texts = [t.text for t in p.findall(".//w:t", ns) if t.text] s = "".join(texts).strip() if s: paragraphs.append(s) return {"title": path.stem, "paragraphs": paragraphs} def _shared_strings(zf: zipfile.ZipFile) -> list[str]: out: list[str] = [] try: with zf.open("xl/sharedStrings.xml") as f: root = ET.fromstring(f.read()) ns = {"a": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"} for si in root.findall(".//a:si", ns): out.append("".join((t.text or "") for t in si.findall(".//a:t", ns))) except KeyError: pass return out def parse_xlsx(path: Path) -> dict[str, Any]: rows: list[list[str]] = [] with zipfile.ZipFile(path) as zf: shared = _shared_strings(zf) with zf.open("xl/worksheets/sheet1.xml") as f: root = ET.fromstring(f.read()) ns = {"a": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"} for row in root.findall(".//a:sheetData/a:row", ns): vals = [] for c in row.findall("a:c", ns): t = c.attrib.get("t") v = c.find("a:v", ns) if v is None or v.text is None: vals.append("") elif t == "s": idx = int(v.text) vals.append(shared[idx] if 0 <= idx < len(shared) else "") else: vals.append(v.text) rows.append(vals) return {"title": path.stem, "rows": rows} def parse_xmind(path: Path) -> dict[str, Any]: with zipfile.ZipFile(path) as zf: names = set(zf.namelist()) topics: list[str] = [] if "content.json" in names: data = json.loads(zf.read("content.json").decode("utf-8", errors="ignore")) def walk(node: Any): if isinstance(node, dict): t = node.get("title") if isinstance(t, str) and t.strip(): topics.append(t.strip()) for k in ("children", "topics", "rootTopic", "attached"): walk(node.get(k)) elif isinstance(node, list): for i in node: walk(i) walk(data) elif "content.xml" in names: root = ET.fromstring(zf.read("content.xml")) topics = [e.text.strip() for e in root.findall(".//title") if e.text and e.text.strip()] else: raise ValueError("Unsupported XMind package structure") return {"title": topics[0] if topics else path.stem, "topics": topics} def detect_in_format(path: Path, forced: str | None) -> str: if forced and forced != "auto": return forced return { ".md": "markdown", ".markdown": "markdown", ".json": "json", ".csv": "csv", ".docx": "word", ".xlsx": "excel", ".xmind": "xmind", ".tsv": "excel", }.get(path.suffix.lower(), "markdown") def normalize(parsed: dict[str, Any]) -> dict[str, Any]: title = parsed.get("title") or "QA Output" sections: list[dict[str, Any]] = [] if "text" in parsed: sections.append({"name": "content", "items": [{"key": "text", "value": parsed["text"]}]}) if "headings" in parsed: sections.append({"name": "headings", "items": [{"key": "heading", "value": h.get("title", "")} for h in parsed["headings"]]}) if "data" in parsed: data = parsed["data"] if isinstance(data, dict): items = [{"key": k, "value": v} for k, v in list(data.items())[:100]] sections.append({"name": "json_object", "items": items}) elif isinstance(data, list): sections.append({"name": "json_array", "items": [{"key": "row", "value": v} for v in data[:200]]}) else: sections.append({"name": "json_value", "items": [{"key": "value", "value": data}]}) if "rows" in parsed: rows = parsed["rows"] sections.append({"name": "rows", "items": [{"key": f"row_{i+1}", "value": r} for i, r in enumerate(rows[:200])]}) if "columns" in parsed: sections.append({"name": "columns", "items": [{"key": "column", "value": c} for c in parsed["columns"]]}) if "paragraphs" in parsed: sections.append({"name": "paragraphs", "items": [{"key": f"p{i+1}", "value": p} for i, p in enumerate(parsed["paragraphs"][:200])]}) if "topics" in parsed: sections.append({"name": "topics", "items": [{"key": "topic", "value": t} for t in parsed["topics"][:300]]}) return {"title": title, "sections": sections} # ---- writers ---- def write_json(model: dict[str, Any], output: Path) -> None: output.write_text(json.dumps(model, ensure_ascii=False, indent=2), encoding="utf-8") def _scalar(v: Any) -> str: if isinstance(v, (dict, list)): return json.dumps(v, ensure_ascii=False) return str(v) def write_csv(model: dict[str, Any], output: Path) -> None: with output.open("w", encoding="utf-8", newline="") as f: writer = csv.writer(f) writer.writerow(["section", "key", "value"]) for s in model.get("sections", []): for item in s.get("items", []): writer.writerow([s.get("name", ""), item.get("key", ""), _scalar(item.get("value", ""))]) def write_excel_tsv(model: dict[str, Any], output: Path) -> None: lines = ["Section\tKey\tValue"] for s in model.get("sections", []): for item in s.get("items", []): lines.append(f"{s.get('name','')}\t{item.get('key','')}\t{_scalar(item.get('value','')).replace(chr(9), ' ')}") output.write_text("\n".join(lines) + "\n", encoding="utf-8") def write_markdown(model: dict[str, Any], output: Path) -> None: lines = [f"# {model.get('title', 'QA Output')}", ""] for s in model.get("sections", []): lines.append(f"## {s.get('name', 'section')}") for item in s.get("items", []): lines.append(f"- **{item.get('key','key')}**: {_scalar(item.get('value',''))}") lines.append("") output.write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8") def write_word_md(model: dict[str, Any], output: Path) -> None: lines = [model.get("title", "QA Output"), "=" * len(model.get("title", "QA Output")), ""] idx = 1 for s in model.get("sections", []): lines.append(f"{idx}. {s.get('name', 'section').replace('_', ' ').title()}") for item in s.get("items", []): lines.append(f"- {item.get('key','key')}: {_scalar(item.get('value',''))}") lines.append("") idx += 1 output.write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8") def write_xmind_md(model: dict[str, Any], output: Path) -> None: lines = [f"# {model.get('title', 'QA Output')}", "", f"- {model.get('title', 'QA Output')}"] for s in model.get("sections", []): lines.append(f" - {s.get('name', 'section')}") for item in s.get("items", []): lines.append(f" - {item.get('key','key')}: {_scalar(item.get('value',''))}") output.write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8") def default_output(input_path: Path, to_fmt: str) -> Path: ext = { "json": ".json", "csv": ".csv", "excel": ".tsv", "markdown": ".md", "word": ".word.md", "xmind": ".xmind.md", }[to_fmt] return input_path.with_name(input_path.stem + ".converted" + ext) def main() -> None: parser = argparse.ArgumentParser(description="Convert QA output files between common formats") parser.add_argument("input", type=Path, help="Input file path") parser.add_argument("--from", dest="from_fmt", default="auto", choices=["auto", "word", "excel", "xmind", "json", "csv", "markdown"]) parser.add_argument("--to", required=True, choices=["word", "excel", "xmind", "json", "csv", "markdown"]) parser.add_argument("--output", type=Path, help="Output file path") args = parser.parse_args() in_fmt = detect_in_format(args.input, args.from_fmt) if in_fmt == "word": parsed = parse_docx(args.input) elif in_fmt == "excel": if args.input.suffix.lower() == ".tsv": rows = [line.rstrip("\n").split("\t") for line in args.input.read_text(encoding="utf-8", errors="ignore").splitlines() if line] parsed = {"title": args.input.stem, "rows": rows} else: parsed = parse_xlsx(args.input) elif in_fmt == "xmind": parsed = parse_xmind(args.input) elif in_fmt == "json": parsed = parse_json(args.input) elif in_fmt == "csv": parsed = parse_csv_file(args.input) else: parsed = parse_markdown(args.input) model = normalize(parsed) output = args.output or default_output(args.input, args.to) if args.to == "json": write_json(model, output) elif args.to == "csv": write_csv(model, output) elif args.to == "excel": write_excel_tsv(model, output) elif args.to == "markdown": write_markdown(model, output) elif args.to == "word": write_word_md(model, output) else: write_xmind_md(model, output) print(str(output)) if __name__ == "__main__": main() -
convert_output_formats.py 255 B
#!/usr/bin/env python3 import os import subprocess import sys LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'convert_formats.py')) if __name__ == '__main__': raise SystemExit(subprocess.call([sys.executable, LOCAL] + sys.argv[1:])) -
convert_to_csv.py 270 B
#!/usr/bin/env python3 import os import subprocess import sys LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'convert_formats.py')) if __name__ == '__main__': raise SystemExit(subprocess.call([sys.executable, LOCAL, '--to', 'csv'] + sys.argv[1:])) -
convert_to_excel.py 272 B
#!/usr/bin/env python3 import os import subprocess import sys LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'convert_formats.py')) if __name__ == '__main__': raise SystemExit(subprocess.call([sys.executable, LOCAL, '--to', 'excel'] + sys.argv[1:])) -
convert_to_json.py 271 B
#!/usr/bin/env python3 import os import subprocess import sys LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'convert_formats.py')) if __name__ == '__main__': raise SystemExit(subprocess.call([sys.executable, LOCAL, '--to', 'json'] + sys.argv[1:])) -
convert_to_markdown.py 275 B
#!/usr/bin/env python3 import os import subprocess import sys LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'convert_formats.py')) if __name__ == '__main__': raise SystemExit(subprocess.call([sys.executable, LOCAL, '--to', 'markdown'] + sys.argv[1:])) -
convert_to_word.py 271 B
#!/usr/bin/env python3 import os import subprocess import sys LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'convert_formats.py')) if __name__ == '__main__': raise SystemExit(subprocess.call([sys.executable, LOCAL, '--to', 'word'] + sys.argv[1:])) -
convert_to_xmind.py 272 B
#!/usr/bin/env python3 import os import subprocess import sys LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'convert_formats.py')) if __name__ == '__main__': raise SystemExit(subprocess.call([sys.executable, LOCAL, '--to', 'xmind'] + sys.argv[1:])) -
parse_csv.py 272 B
#!/usr/bin/env python3 import os import subprocess import sys LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'parse_formats.py')) if __name__ == '__main__': raise SystemExit(subprocess.call([sys.executable, LOCAL, '--format', 'csv'] + sys.argv[1:])) -
parse_excel.py 274 B
#!/usr/bin/env python3 import os import subprocess import sys LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'parse_formats.py')) if __name__ == '__main__': raise SystemExit(subprocess.call([sys.executable, LOCAL, '--format', 'excel'] + sys.argv[1:])) -
parse_formats.py 5.9 KB
#!/usr/bin/env python3 import argparse import csv import json import re import zipfile from pathlib import Path from typing import Any from xml.etree import ElementTree as ET def parse_markdown(path: Path) -> dict[str, Any]: text = path.read_text(encoding="utf-8", errors="ignore") headings = [] for line in text.splitlines(): m = re.match(r"^(#{1,6})\s+(.*)$", line.strip()) if m: headings.append({"level": len(m.group(1)), "title": m.group(2).strip()}) return {"format": "markdown", "headings": headings, "preview": text[:500]} def parse_json(path: Path) -> dict[str, Any]: data = json.loads(path.read_text(encoding="utf-8", errors="ignore")) if isinstance(data, dict): shape = {"type": "object", "keys": list(data.keys())[:50]} elif isinstance(data, list): shape = {"type": "array", "size": len(data)} else: shape = {"type": type(data).__name__} return {"format": "json", "shape": shape, "data": data} def parse_csv_file(path: Path) -> dict[str, Any]: with path.open("r", encoding="utf-8", errors="ignore", newline="") as f: reader = csv.DictReader(f) rows = list(reader) return { "format": "csv", "columns": reader.fieldnames or [], "row_count": len(rows), "sample_rows": rows[:10], } def parse_docx(path: Path) -> dict[str, Any]: paragraphs = [] with zipfile.ZipFile(path) as zf: with zf.open("word/document.xml") as f: root = ET.fromstring(f.read()) ns = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"} for p in root.findall(".//w:p", ns): texts = [t.text for t in p.findall(".//w:t", ns) if t.text] joined = "".join(texts).strip() if joined: paragraphs.append(joined) return {"format": "word", "paragraph_count": len(paragraphs), "paragraphs": paragraphs[:100]} def _read_shared_strings(zf: zipfile.ZipFile) -> list[str]: strings = [] try: with zf.open("xl/sharedStrings.xml") as f: root = ET.fromstring(f.read()) ns = {"a": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"} for si in root.findall(".//a:si", ns): parts = [t.text or "" for t in si.findall(".//a:t", ns)] strings.append("".join(parts)) except KeyError: pass return strings def parse_xlsx(path: Path) -> dict[str, Any]: rows_out: list[list[str]] = [] with zipfile.ZipFile(path) as zf: shared = _read_shared_strings(zf) with zf.open("xl/worksheets/sheet1.xml") as f: root = ET.fromstring(f.read()) ns = {"a": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"} for row in root.findall(".//a:sheetData/a:row", ns): vals = [] for c in row.findall("a:c", ns): cell_type = c.attrib.get("t") v = c.find("a:v", ns) if v is None or v.text is None: vals.append("") continue if cell_type == "s": idx = int(v.text) vals.append(shared[idx] if 0 <= idx < len(shared) else "") else: vals.append(v.text) rows_out.append(vals) return {"format": "excel", "row_count": len(rows_out), "sample_rows": rows_out[:20]} def parse_xmind(path: Path) -> dict[str, Any]: with zipfile.ZipFile(path) as zf: names = set(zf.namelist()) if "content.json" in names: data = json.loads(zf.read("content.json").decode("utf-8", errors="ignore")) titles: list[str] = [] def walk(node: Any): if isinstance(node, dict): title = node.get("title") if isinstance(title, str) and title.strip(): titles.append(title.strip()) for k in ("children", "topics", "rootTopic", "attached"): walk(node.get(k)) elif isinstance(node, list): for i in node: walk(i) walk(data) return {"format": "xmind", "topic_count": len(titles), "topics": titles[:200]} if "content.xml" in names: root = ET.fromstring(zf.read("content.xml")) titles = [el.text.strip() for el in root.findall(".//title") if el.text and el.text.strip()] return {"format": "xmind", "topic_count": len(titles), "topics": titles[:200]} raise ValueError("Unsupported XMind package structure") def detect_format(path: Path, forced: str | None) -> str: if forced and forced != "auto": return forced ext = path.suffix.lower() return { ".md": "markdown", ".markdown": "markdown", ".json": "json", ".csv": "csv", ".docx": "word", ".xlsx": "excel", ".xmind": "xmind", }.get(ext, "markdown") def main() -> None: parser = argparse.ArgumentParser(description="Parse common QA output formats into normalized JSON") parser.add_argument("input", type=Path, help="Input file path") parser.add_argument("--format", default="auto", choices=["auto", "word", "excel", "xmind", "json", "csv", "markdown"]) parser.add_argument("--output", type=Path, help="Output JSON path (default: stdout)") args = parser.parse_args() fmt = detect_format(args.input, args.format) if fmt == "word": result = parse_docx(args.input) elif fmt == "excel": result = parse_xlsx(args.input) elif fmt == "xmind": result = parse_xmind(args.input) elif fmt == "json": result = parse_json(args.input) elif fmt == "csv": result = parse_csv_file(args.input) else: result = parse_markdown(args.input) result["source"] = str(args.input) out = json.dumps(result, ensure_ascii=False, indent=2) if args.output: args.output.write_text(out, encoding="utf-8") else: print(out) if __name__ == "__main__": main() -
parse_json.py 273 B
#!/usr/bin/env python3 import os import subprocess import sys LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'parse_formats.py')) if __name__ == '__main__': raise SystemExit(subprocess.call([sys.executable, LOCAL, '--format', 'json'] + sys.argv[1:])) -
parse_markdown.py 277 B
#!/usr/bin/env python3 import os import subprocess import sys LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'parse_formats.py')) if __name__ == '__main__': raise SystemExit(subprocess.call([sys.executable, LOCAL, '--format', 'markdown'] + sys.argv[1:])) -
parse_output_formats.py 253 B
#!/usr/bin/env python3 import os import subprocess import sys LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'parse_formats.py')) if __name__ == '__main__': raise SystemExit(subprocess.call([sys.executable, LOCAL] + sys.argv[1:])) -
parse_word.py 273 B
#!/usr/bin/env python3 import os import subprocess import sys LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'parse_formats.py')) if __name__ == '__main__': raise SystemExit(subprocess.call([sys.executable, LOCAL, '--format', 'word'] + sys.argv[1:])) -
parse_xmind.py 274 B
#!/usr/bin/env python3 import os import subprocess import sys LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'parse_formats.py')) if __name__ == '__main__': raise SystemExit(subprocess.call([sys.executable, LOCAL, '--format', 'xmind'] + sys.argv[1:]))
-
-
output-formats.md 401 B
# Output Format Options This skill **defaults to Markdown**. To get **Excel**, **CSV**, or **JSON**, add at the **end** of your request: - **Excel:** "Please output as tab-separated table for pasting into Excel." - **CSV:** "Please output as CSV (comma-separated, header row first)." - **JSON:** "Please output as JSON." See repo `skills/testing-types/_output-formats-template-en.md` for examples. -
quick-start.md 2.2 KB
# Accessibility Testing: Quick Start Use the `accessibility-testing` Skill to evaluate product accessibility against user needs and applicable WCAG expectations. ## 1. Prepare the Minimum Context Provide what is available from this list: - product scope - target platforms - known assistive-technology users - designs or running pages - scope, version, environment, time budget, and prohibited actions - known failures, existing tests, and evidence links If some information is unavailable, say so explicitly. The Skill should still return a bounded first pass with assumptions and open questions. ## 2. Invoke the Skill ```text @skill accessibility-testing Scenario: A checkout form must work with keyboard-only navigation and a screen reader. Prioritize the highest risks, distinguish facts from assumptions, and provide an executable result with evidence requirements and open questions. ``` ## 3. Follow the Execution Flow 1. Confirm the objective, subject, scope, and success criteria. 2. Audit the completeness and credibility of the supplied material. 3. Identify failure modes and rank them by impact, likelihood, and detectability. 4. Produce concrete scenarios, checks, or decisions with expected results. 5. Record evidence gaps, residual risk, and the smallest useful next action. ## 4. Minimum Coverage Unless the request narrows scope, cover: - keyboard access - focus order - semantic structure - names and labels - contrast - zoom and reflow - screen readers - error recovery - confirmed facts, working assumptions, and open questions - P0/P1 priorities and the rationale for lower-priority deferral - blockers, stop conditions, and residual risk ## 5. Review the Result - Every high-risk item has a concrete failure mode and expected behavior. - Priorities are justified rather than evenly distributed. - No field, API, metric, environment, or execution result is invented. - The artifact can be executed or reviewed without guessing the next step. - Production, security, and privacy work uses least privilege and sanitized data. ## 6. Continue Add missing evidence and ask the Skill to refine the same artifact. Keep confirmed facts separate from newly introduced assumptions so changes remain reviewable. -
README.md 835 B
# Accessibility Testing ## Skill Overview Need help with accessibility testing in a real project context; Need an output that can be used directly for execution, review, or follow-up. ## How to Use 1. Open `SKILL.md` in this folder and confirm this skill fits your task. 2. In your AI tool, call `@skill accessibility-testing`, then add your real project context and goal. 3. If you need a specific output format (table, checklist, report), include it directly in your request. ## One-Click Install Script Run from the repository root: ### macOS / Linux ```bash bash ./scripts/install-skills-mac.sh --tool codex --lang en --skill accessibility-testing ``` ### Windows PowerShell ```powershell powershell -ExecutionPolicy Bypass -File .\scripts\install-skills-windows.ps1 -Tool codex -Lang en -Skill accessibility-testing ``` -
SKILL.md 2.6 KB
--- name: accessibility-testing description: Use this skill when you need to design accessibility testing against WCAG, keyboard navigation, and assistive technology scenarios; triggers include accessibility testing and a11y testing. --- # Accessibility Testing (English) **Chinese version:** See the corresponding Chinese skill. ## When to Use - Need help with accessibility testing in a real project context. - Need an output that can be used directly for execution, review, or follow-up. ## Workflow 1. Read and follow the main prompt listed under Progressive disclosure (coverage, structure, quality bar). 2. Add only project context that changes the result: scope, environment, constraints, risks, dependencies, expected deliverable. 3. If input is incomplete, return a usable first draft and explicitly mark assumptions and gaps. 4. Default to Markdown; switch formats only when the user asks. ## Core Constraints - Prioritize by risk / business impact — do not treat everything equally. - Separate confirmed facts from current assumptions. - Do not invent endpoints, fields, environments, or root causes the user did not provide. - Keep output executable: concrete scenarios, clear priority, clear next steps. ## Progressive Disclosure - Before producing output, read and follow `prompts/accessibility-testing.md` (minimum coverage, output structure, quality bar). - When Excel/CSV/JSON/Word is requested: read `output-formats.md` and honor the format. - When a ready-made template fits: use matching files under `output-templates/`. - For deep framework/troubleshoot/schema notes: read only the relevant file(s) under `references/`, do not load the whole directory. - For format conversion or helper checks: prefer existing `scripts/` over reinventing. - For evaluating/regressing this skill: use `evals/` with skill-up. ## Pre-delivery Checklist - [ ] Followed the main prompt's output structure - [ ] Minimum coverage focus: scope and target users, keyboard access, focus order and visible focus, screen reader semantics and labels, headings and landmarks, form errors and validation feedback, color contrast and non-color cues, images, icons, and alternative text, ... (details in main prompt) - [ ] Covered the minimum checklist, or explained omissions - [ ] High-risk items have explicit priority - [ ] Did not invent details the user did not provide - [ ] Assumptions and gaps are marked ## Common Pitfalls - Do not pretend completeness when scope/context is missing. - Do not treat every item as equally important. - Do not skip assumptions and information gaps. - Do not dump generic theory unrelated to the current toolchain.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.