1
0

verify.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. #!/usr/bin/env python3
  2. """
  3. verify.py — Playwright封装,用于验证claude-design产出的HTML
  4. Usage:
  5. python verify.py path/to/design.html # 基础:打开+截图+抓控制台错误
  6. python verify.py design.html --viewports 1920x1080,375x667 # 多viewport
  7. python verify.py deck.html --slides 10 # 幻灯片逐页截(前10张)
  8. python verify.py design.html --output ./screenshots/ # 输出目录
  9. python verify.py design.html --show # 非headless,打开真实浏览器
  10. 依赖:
  11. pip install playwright
  12. playwright install chromium
  13. """
  14. import argparse
  15. import sys
  16. import os
  17. import time
  18. from pathlib import Path
  19. def parse_viewport(s):
  20. w, h = s.split('x')
  21. return {'width': int(w), 'height': int(h)}
  22. def verify_html(html_path, viewports=None, slides=0, output_dir=None, show=False, wait=2000):
  23. try:
  24. from playwright.sync_api import sync_playwright
  25. except ImportError:
  26. print("ERROR: playwright未安装。")
  27. print("运行: pip install playwright && playwright install chromium")
  28. sys.exit(1)
  29. html_path = Path(html_path).resolve()
  30. if not html_path.exists():
  31. print(f"ERROR: 文件不存在: {html_path}")
  32. sys.exit(1)
  33. if output_dir is None:
  34. output_dir = html_path.parent / 'screenshots'
  35. output_dir = Path(output_dir)
  36. output_dir.mkdir(parents=True, exist_ok=True)
  37. file_url = html_path.as_uri()
  38. stem = html_path.stem
  39. if viewports is None:
  40. viewports = [{'width': 1440, 'height': 900}]
  41. console_errors = []
  42. page_errors = []
  43. with sync_playwright() as p:
  44. browser = p.chromium.launch(headless=not show)
  45. for viewport in viewports:
  46. context = browser.new_context(viewport=viewport, device_scale_factor=2)
  47. page = context.new_page()
  48. page.on("console", lambda msg: console_errors.append(f"[{msg.type}] {msg.text}") if msg.type in ("error", "warning") else None)
  49. page.on("pageerror", lambda err: page_errors.append(str(err)))
  50. print(f"\n→ 打开 {file_url} @ {viewport['width']}x{viewport['height']}")
  51. page.goto(file_url, wait_until='networkidle')
  52. page.wait_for_timeout(wait)
  53. if slides > 0:
  54. for i in range(slides):
  55. screenshot_path = output_dir / f"{stem}-slide-{str(i + 1).zfill(2)}.png"
  56. page.screenshot(path=str(screenshot_path), full_page=False)
  57. print(f" ✓ slide {i+1} → {screenshot_path.name}")
  58. if i < slides - 1:
  59. page.keyboard.press('ArrowRight')
  60. page.wait_for_timeout(500)
  61. else:
  62. suffix = f"-{viewport['width']}x{viewport['height']}" if len(viewports) > 1 else ""
  63. screenshot_path = output_dir / f"{stem}{suffix}.png"
  64. page.screenshot(path=str(screenshot_path), full_page=False)
  65. print(f" ✓ 截图 → {screenshot_path.name}")
  66. full_path = output_dir / f"{stem}{suffix}-full.png"
  67. page.screenshot(path=str(full_path), full_page=True)
  68. print(f" ✓ 完整页 → {full_path.name}")
  69. if show:
  70. print(" (浏览器窗口保持打开,按Enter关闭...)")
  71. input()
  72. context.close()
  73. browser.close()
  74. print("\n" + "=" * 50)
  75. print("验证报告")
  76. print("=" * 50)
  77. if page_errors:
  78. print(f"\n❌ Page Errors ({len(page_errors)}):")
  79. for e in page_errors:
  80. print(f" - {e}")
  81. else:
  82. print("\n✅ 无JavaScript错误")
  83. if console_errors:
  84. print(f"\n⚠️ Console Errors/Warnings ({len(console_errors)}):")
  85. for e in console_errors[:20]:
  86. print(f" - {e}")
  87. if len(console_errors) > 20:
  88. print(f" ... 还有{len(console_errors) - 20}条")
  89. else:
  90. print("✅ Console干净")
  91. print(f"\n📸 截图保存至: {output_dir}")
  92. return 0 if not page_errors else 1
  93. def main():
  94. parser = argparse.ArgumentParser(
  95. description="Verify HTML design outputs with Playwright",
  96. formatter_class=argparse.RawDescriptionHelpFormatter,
  97. )
  98. parser.add_argument("html_path", help="HTML file path")
  99. parser.add_argument("--viewports", default="1440x900",
  100. help="逗号分隔的viewport列表,格式 WxH(默认 1440x900)")
  101. parser.add_argument("--slides", type=int, default=0,
  102. help="幻灯片模式:截取前N张(需要HTML支持ArrowRight翻页)")
  103. parser.add_argument("--output", default=None,
  104. help="输出目录(默认HTML所在目录的screenshots/)")
  105. parser.add_argument("--show", action="store_true",
  106. help="非headless,打开真实浏览器窗口")
  107. parser.add_argument("--wait", type=int, default=2000,
  108. help="打开页面后等待的毫秒数(默认2000)")
  109. args = parser.parse_args()
  110. viewports = [parse_viewport(v) for v in args.viewports.split(",")]
  111. return verify_html(
  112. html_path=args.html_path,
  113. viewports=viewports,
  114. slides=args.slides,
  115. output_dir=args.output,
  116. show=args.show,
  117. wait=args.wait,
  118. )
  119. if __name__ == "__main__":
  120. sys.exit(main())