MCP HubMCP Hub
SKILL·D99AAE

verify-web-app-runtime

pjt222
업데이트됨 3 days ago
26
3
26
GitHub에서 보기
테스팅testingdesign

정보

이 스킬은 헤드리스 크로미움 테스트를 실행하여 실제 픽셀 출력과 콘솔 동작을 검증함으로써 WebGL 및 Canvas와 같은 시각적 웹 기능에 대한 런타임 검증을 제공합니다. 필요한 GPU 플래그를 자동으로 구성하고, 캔버스 가시성과 비검정색 콘텐츠를 검증하며, 새로고침된 페이지 로드에서 테스트가 실행되도록 보장합니다. PR에 셰이더, 렌더링 또는 오디오 관련 변경이 포함된 경우 CI 파이프라인에서 사용하여 애플리케이션이 실제로 픽셀을 올바르게 렌더링함을 입증하는 자동화된 검사를 수행할 수 있습니다.

빠른 설치

Claude Code

추천
기본
npx skills add pjt222/agent-almanac -a claude-code
플러그인 명령대체
/plugin add https://github.com/pjt222/agent-almanac
Git 클론대체
git clone https://github.com/pjt222/agent-almanac.git ~/.claude/skills/verify-web-app-runtime

Claude Code에서 이 명령을 복사하여 붙여넣어 스킬을 설치하세요

문서

Verify Web App Runtime

Prove that a web app's visual runtime actually works — not that its elements exist, not that its unit tests pass, but that real pixels land on a real canvas with the expected GPU capabilities, in a headless browser with a CI-friendly exit code. This skill encodes the gotchas that bite every cold start of headless WebGL verification, each as an explicit procedure step.

When to Use

  • A PR changes WebGL/WebGL2 shaders, a GPGPU simulation, canvas drawing, or a WebAudio graph, and "does it still render?" must be answered before merge
  • A static code review or unit test suite cannot observe the failure mode (black canvas, silent GPGPU fallback, visibility-gated render loop)
  • CI needs a one-command runtime gate: exits 0 when the app draws, 1 when not
  • A headless machine (no GPU) must verify a WebGL2 app end-to-end
  • NOT for extracting data from third-party pages — that is headless-web-scraping

Inputs

  • Required: URL of the running app, including any base path (e.g. http://localhost:5173/myapp/ — a missing base path 404s silently)
  • Required: Interaction steps to reach the surface under test — a JSON array of click / wait / assert_attr step objects (clicks double as the WebAudio user gesture)
  • Optional: Output directory for screenshots and reports (default: ./verify-runtime-out)
  • Optional: Luminance thresholds (default: a grayscale pixel > 25 counts as lit; >= 1% lit pixels passes)
  • Optional: Console error keywords (default: gpgpu, webgl error, nan, fallback)
  • Optional: Whether EXT_color_buffer_half_float is required (default: yes; pass --skip-half-float for apps without a GPGPU path)

The packaged verifier at scripts/verify_runtime.py implements every step below; the procedure explains what it asserts and why, so each check can also be reproduced or adapted standalone.

Procedure

Step 1: Start the app and insist on a fresh-load test surface (never HMR)

HMR does not reset GPGPU textures: a hot-reloaded tab keeps the previous simulation state (e.g. particle position textures survive the module swap), so anything observed through HMR proves nothing about a cold start. Only a fresh page load in a fresh browser is a valid test surface.

npm run dev &                       # or: npm run build && npm run preview
curl -sf http://localhost:5173/myapp/ >/dev/null && echo "server up"

Expected: The app answers on a stable URL. All verification below happens via fresh browser.launch() + page.goto() — never by observing a tab that hot-reloaded.

On failure: If the URL 404s, check the base path — Vite apps often serve under /<repo-name>/, and the bare origin silently serves nothing. If only an HMR-updated tab shows the change, restart the dev server or build a preview before verifying.

Step 2: Launch headless Chromium with the SwiftShader/ANGLE flags

Headless Chromium has no GPU; without software rendering flags, getContext('webgl2') returns null and every downstream check fails for the wrong reason.

CHROMIUM_ARGS = [
    "--use-gl=angle",
    "--use-angle=swiftshader",
    "--enable-unsafe-swiftshader",
    "--ignore-gpu-blocklist",
]
browser = playwright.chromium.launch(headless=True, args=CHROMIUM_ARGS)

Expected: A WebGL2 context is obtainable in the headless page (verified by the probe in Step 3).

On failure: Copy the four flags exactly — a misspelled flag is silently ignored by Chromium and the symptom is identical (webgl2: false). If the flags are correct and WebGL2 is still unavailable, update the browser: python -m playwright install chromium.

Step 3: Probe EXT_color_buffer_half_float before trusting a GPGPU path

GPGPU pipelines render to half-float framebuffers. If the extension is missing, well-built apps fall back silently to a non-GPGPU mode — the page looks alive, but the screenshots verify the wrong code path.

webgl = page.evaluate("""() => {
    const gl = document.createElement('canvas').getContext('webgl2');
    return gl ? {webgl2: true, halfFloat: !!gl.getExtension('EXT_color_buffer_half_float')}
              : {webgl2: false};
}""")

Expected: {"webgl2": true, "halfFloat": true}. SwiftShader supports the extension, so a headless pass here is representative.

On failure: webgl2: false means Step 2's flags did not take effect. halfFloat: false means any GPGPU verification is invalid — the app is exercising its fallback. Fail the run (the packaged script does) rather than screenshot the fallback; relax with --skip-half-float only for apps that have no GPGPU path at all.

Step 4: Assert the page is visible — rAF loops are visibility-gated

Well-behaved apps pause their requestAnimationFrame loop when document.hidden is true. A hidden page produces a black canvas that looks exactly like a rendering bug.

info = page.evaluate("() => ({visibility: document.visibilityState})")
assert info["visibility"] == "visible"

Expected: visibilityState === 'visible' in every context, checked before trusting any pixel assertion.

On failure: A black canvas with a hidden page is a test-harness artifact, not an app bug. Call page.bring_to_front() and re-check; do not run other foreground automation against the same headless browser while verifying.

Step 5: Drive interactions with real clicks (the WebAudio user gesture)

Reach the surface under test through the UI, exactly as a user would. Browsers refuse to start an AudioContext without a user gesture — real Playwright clicks count, programmatic dispatchEvent calls do not.

[
  {"action": "click", "role": "button", "name": "Switch to 3D view", "settle": 2.5},
  {"action": "click", "role": "button", "name": "Sand", "exact": true, "settle": 4},
  {"action": "assert_attr", "role": "button", "name": "Sand", "exact": true,
   "attr": "aria-pressed", "equals": "true"}
]

The "Switch to 3D view" / "Sand" names above are examples from the origin project — replace them with your app's controls. settle (seconds) lets a simulation reach a representative state before pixels are judged.

python3 scripts/verify_runtime.py --url http://localhost:5173/myapp/ \
    --steps steps.json --out /tmp/verify-out

Expected: Every click resolves its target (ARIA role + accessible name preferred, CSS selector as fallback), and every assert_attr step passes — e.g. the mode button reports aria-pressed="true", proving the app accepted the mode switch rather than ignoring the click.

On failure: A click timeout usually means a wrong accessible name — dump candidates with page.get_by_role("button").all_inner_texts(). An assert_attr mismatch means the UI ignored the interaction: check the console log for errors thrown by the click handler.

Step 6: Screenshot the canvas and assert non-black luminance

A present canvas can still be empty. Element presence, canvas dimensions, and even a running rAF loop all pass while the app draws nothing. Only sampled pixels prove rendering.

from PIL import Image
grayscale = Image.open("norm_canvas.png").convert("L")
histogram = grayscale.histogram()
total_pixels = grayscale.size[0] * grayscale.size[1]
lit = sum(histogram[26:]) / total_pixels * 100   # pixels brighter than 25
assert lit >= 1.0, f"canvas effectively black (lit={lit:.1f}%)"

Expected: More than 1% of canvas pixels brighter than grayscale 25. The origin run measured ~9% lit for a healthy particle scene — comfortably above threshold without being tuned to the content.

On failure: Screenshot the full page too and compare: if the page shows content but the canvas crop is black, the draw loop is not producing pixels (check Steps 3 and 4 first). If a legitimately sparse scene sits under 1%, raise settle so more of the scene accumulates, or lower --lit-percent-min deliberately and note why.

Step 7: Run a second reduced_motion: reduce context and confirm the settled pose

Apps that honor prefers-reduced-motion must show a calm, settled state. Verify it in a separate fresh context — never by toggling emulation on the already-running page.

context = browser.new_context(viewport={"width": 1280, "height": 900},
                              reduced_motion="reduce")

The packaged script re-runs all previous assertions in this context, then takes two canvas screenshots one second apart and requires them near-identical (<= 2% of pixels changed) — a still pose, not a paused-by-accident one.

Expected: Reduced-motion context passes the same luminance/visibility checks and the two screenshots differ by <= 2% of pixels.

On failure: A large diff means animation continues despite prefers-reduced-motion — check that the app queries the media feature and that the settled pose is reachable without animation. If the app deliberately keeps subtle motion, raise --still-max-changed-percent and document the decision.

Step 8: Scan console output for GPU error signals

Pixels can look right while the console reports a degraded path. Collect console and pageerror events for the whole run and match them against error keywords.

gpu_errors = [line for line in logs
              if any(keyword in line.lower()
                     for keyword in ["gpgpu", "webgl error", "nan", "fallback"])]

Expected: Zero matching lines. The packaged script appends any hits to the failure list and writes the full log to <out>/console.log.

On failure: Read the matched lines in console.log. nan in a shader or simulation log means numeric blow-up even if this frame looked fine; fallback means Step 3's guarantee was violated at app level. Substring matches on innocent words (e.g. "nan" inside a longer token) are tuned away with explicit --console-error-keyword flags.

Step 9: Run the packaged verifier end-to-end and read the verdict

pip install playwright pillow
python -m playwright install chromium
python3 skills/verify-web-app-runtime/scripts/verify_runtime.py \
    --url http://localhost:5173/myapp/ \
    --steps steps.json \
    --out /tmp/verify-out

Expected: Per-context summary lines, FAILURES: none, exit code 0. /tmp/verify-out/ contains norm_page.png, norm_canvas.png, rm_page.png, rm_canvas.png, rm_canvas_still.png, console.log, and report.json. The origin run against a healthy build reported: half-float present, mode active, visibility visible, ~9% pixels lit, zero GPGPU errors.

On failure: The exit code is 1 and every failed assertion is listed — work through them in procedure order (flags before probe, probe before pixels), since early failures cause misleading later ones. report.json holds the raw evidence for each context.

Validation

  • Verifier exits 0 against a known-good build of the app
  • Verifier exits 1 when pointed at a deliberately broken surface (e.g. a blank page), proving the assertions can fail
  • report.json shows "webgl2": true and "halfFloat": true (or the run explicitly used --skip-half-float)
  • visibilityState is visible in both contexts
  • Lit-pixel percentage >= 1% on the canvas crop in both contexts
  • Reduced-motion stillness diff <= 2% changed pixels
  • console.log contains no lines matching the error keywords
  • Screenshots for both contexts exist in the output directory

Common Pitfalls

  • Verifying through HMR: Hot module replacement preserves GPGPU state (position textures survive the swap), so an HMR-updated tab can render correctly while a cold start is broken — or vice versa. Always verify a fresh page load in a fresh browser.
  • Trusting element presence: expect(canvas).toBeVisible() passes on a pitch-black canvas. Only the luminance assertion (Step 6) proves rendering. Know its converse limit too: an undrawn canvas composites as transparent, so over a bright page background it screenshots as fully lit — which is why Validation requires demonstrating the verifier can also fail.
  • Missing SwiftShader flags: Without the four Step 2 flags, headless WebGL2 is simply null — and the resulting black canvas is indistinguishable from an app bug. Rule the harness out first.
  • Ignoring the half-float probe: Skipping Step 3 lets a silent GPGPU fallback masquerade as a pass — the pixels came from the wrong code path.
  • Asserting mid-animation: A simulation needs settle time before its pixels are representative; screenshots taken during a transition flake. Use per-step settle values, not fixed global sleeps.
  • Expecting audio without a gesture: AudioContext stays suspended until a user gesture; drive the UI with real Playwright clicks (Step 5) before asserting anything audio-dependent.
  • Overbroad console keywords: The default nan keyword substring-matches innocent tokens. Tune with --console-error-keyword instead of ignoring console failures wholesale.

Related Skills

  • run-copilot-review-loop — companion skill from the same review workflow: after runtime verification passes, drive the bot review of the PR to a clean pass
  • headless-web-scraping — the distinction: scraping is data extraction from (usually third-party) pages; this skill is runtime verification of your own app's pixels, GPU capabilities, and console — same headless browser, opposite purpose
  • Headless WebGL Verification guide — background on why headless WebGL needs SwiftShader/ANGLE and how the verification pattern generalizes

This skill is a core skill of the frontend-runtime-verifier agent and is exercised by the visual-pr-review team. See references/EXAMPLES.md for the full origin recipe, the steps-DSL reference, a no-GPGPU variant, and a CI integration example.

<!-- Keep under 500 lines. Extract large examples to references/EXAMPLES.md if needed. -->

GitHub 저장소

pjt222/agent-almanac
경로: i18n/de/skills/verify-web-app-runtime
0
agentsagentskillsai-assisted-developmentclaude-codeskillsteams
FAQ

Frequently asked questions

What is the verify-web-app-runtime skill?

verify-web-app-runtime is a Claude Skill by pjt222. Skills package instructions and resources that Claude loads on demand, so Claude can perform verify-web-app-runtime-related tasks without extra prompting.

How do I install verify-web-app-runtime?

Use the install commands on this page: add verify-web-app-runtime to Claude Code as a plugin, or clone its repository into your skills directory, then restart Claude so it picks up the skill.

What category does verify-web-app-runtime belong to?

verify-web-app-runtime is in the Testing category, tagged testing and design.

Is verify-web-app-runtime free to use?

Yes. verify-web-app-runtime is listed on AIMCP and free to install. It runs inside Claude, so no separate service account is required to use the skill itself.

연관 스킬

evaluating-llms-harness
테스팅

이 Claude Skill은 MMLU, GSM8K를 포함한 60개 이상의 표준화된 학술 과제에서 LLM 성능을 벤치마크하기 위해 lm-evaluation-harness를 실행합니다. 개발자들이 모델 품질을 비교하고, 학습 진행 상황을 추적하거나 학술 결과를 보고할 수 있도록 설계되었습니다. 이 도구는 HuggingFace와 vLLM 모델을 포함한 다양한 백엔드를 지원합니다.

스킬 보기
cloudflare-cron-triggers
테스팅

이 스킬은 cron 표현식을 사용하여 Worker를 스케줄링하기 위한 Cloudflare Cron Triggers 구현에 관한 포괄적인 지식을 제공합니다. 주기적 작업, 유지보수 작업, 자동화된 워크플로우 설정 방법을 다루며, 잘못된 cron 표현식이나 시간대 문제 같은 일반적인 이슈들을 해결하는 방법을 포함합니다. 개발자들은 이를 통해 스케줄된 핸들러 구성, cron 트리거 테스트, Workflows 및 Green Compute와의 연동 작업을 수행할 수 있습니다.

스킬 보기
webapp-testing
테스팅

이 Claude Skill은 Python 스크립트를 통해 로컬 웹 애플리케이션을 테스트하기 위한 Playwright 기반 툴킷을 제공합니다. 프론트엔드 검증, UI 디버깅, 스크린샷 캡처, 로그 확인 기능을 지원하며 서버 라이프사이클을 관리합니다. 브라우저 자동화 작업에 사용하되 컨텍스트 오염을 방지하기 위해 소스 코드를 읽지 않고 스크립트를 직접 실행하세요.

스킬 보기
finishing-a-development-branch
테스팅

이 스킬은 테스트 통과를 확인한 후 체계적인 통합 옵션을 제시하여 개발자가 완성된 작업을 마무리하도록 돕습니다. 구현이 완료된 후 머지, PR 생성, 브랜치 정리와 같은 워크플로우를 안내합니다. 코드가 준비되고 테스트가 완료되었을 때 개발 프로세스를 체계적으로 마무리하기 위해 사용하세요.

스킬 보기