{"record":{"id":"6850e7eeeac8796c","repo":"affaan-m/ECC","slug":"image-not-found-on-screen-template-path","errorCode":null,"errorMessage":"Image not found on screen: {template_path}","messagePattern":"Image not found on screen: (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"skills/windows-desktop-e2e/SKILL.md","lineNumber":780,"sourceCode":"def find_image_on_screen(template_path, confidence=0.85):\n    \"\"\"Locate a template image on screen. Returns (x, y) center or None.\"\"\"\n    screen   = np.array(pyautogui.screenshot())\n    template = np.array(Image.open(template_path))\n    result   = cv2.matchTemplate(\n        cv2.cvtColor(screen, cv2.COLOR_RGB2BGR),\n        cv2.cvtColor(template, cv2.COLOR_RGB2BGR),\n        cv2.TM_CCOEFF_NORMED,\n    )\n    _, max_val, _, max_loc = cv2.minMaxLoc(result)\n    if max_val >= confidence:\n        h, w = template.shape[:2]\n        return max_loc[0] + w // 2, max_loc[1] + h // 2\n    return None\n\ndef click_image(template_path, confidence=0.85):\n    pos = find_image_on_screen(template_path, confidence)\n    if pos is None:\n        raise RuntimeError(f\"Image not found on screen: {template_path}\")\n    pyautogui.click(*pos)\n```\n\n### DPI / Scaling Rules (screenshot mode only)\n\nScreenshot matching is brutally sensitive to Windows display scaling (100% / 125% / 150%). Three hard rules:\n\n1. **Capture templates at the same scale as the target machine.** Don't try to rescue a mismatch with `PIL.Image.resize` — `cv2.matchTemplate` is very fragile against resampling artefacts.\n2. **Pin the CI display scaling.** On `windows-latest` add a step like `Set-DisplayResolution 1920 1080 -Force` and disable per-monitor DPI scaling, so screenshot dimensions are reproducible.\n3. **Record the scale alongside each artefact.** On capture, write `GetDpiForWindow(hwnd) / 96` to `artifacts/<test>/metadata.json` — postmortems become obvious instead of guess-work.\n\n> Process-level DPI awareness (`SetProcessDpiAwarenessContext`) **can conflict with Qt's own DPI handling** when the app under test is Qt-based. Prefer \"same-scale templates + CI pin\" over flipping process-wide DPI mode in fixtures.\n\n### Debugging Match Confidence\n\nWhen tuning the `confidence` threshold, the only sane workflow is to **see** where the match landed. The helper below is diagnosis-only — do not call it from test code.\n\n```python","sourceCodeStart":762,"sourceCodeEnd":798,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/skills/windows-desktop-e2e/SKILL.md#L762-L798","documentation":"A RuntimeError raised by click_image() when find_image_on_screen() returns None — i.e. cv2.matchTemplate could not find the template image anywhere on screen at or above the configured confidence threshold. The helper uses pyautogui for the screenshot and OpenCV TM_CCOEFF_NORMED matching.","triggerScenarios":"Calling click_image(template_path, confidence) where the template does not visually match the current screen. Causes include: confidence threshold too high, template captured at a different DPI/scale than the runtime screen, the UI element is occluded or off-screen, or the template file references stale artwork.","commonSituations":"Windows display scaling mismatch (100% vs 125% vs 150%) between capture and CI machines; the app updated its icons; the window was not foregrounded; anti-aliasing/font differences between capture and run environment.","solutions":["Run the debug_match() helper to see the best score and where it landed — tune confidence against real data, not by guessing.","Re-capture the template on a machine with the same display scaling as the target (pin CI scaling with Set-DisplayResolution).","Lower confidence incrementally (e.g. 0.85 -> 0.80) only after confirming the match location is correct via debug_match.","Ensure the app window is foregrounded and fully rendered before calling click_image.","Prefer UIA-based selectors over screenshot matching where possible — screenshot matching is fragile to scaling and theme changes."],"exampleFix":"# before\npos = find_image_on_screen(template_path, confidence)\nif pos is None:\n    raise RuntimeError(f\"Image not found on screen: {template_path}\")\n\n# after: capture a diagnostic screenshot and surface the best score\nscore, pos = find_image_on_screen(template_path, confidence, return_score=True)\nif pos is None:\n    debug_match(template_path, out=f\"artifacts/{Path(template_path).stem}_miss.png\", confidence=confidence)\n    raise RuntimeError(f\"Image not found (best score {score:.3f} < {confidence}): {template_path}\")","handlingStrategy":"validation","validationCode":"def image_likely_on_screen(template_path, confidence=0.85) -> bool:\n    return find_image_on_screen(template_path, confidence) is not None\n\nif not image_likely_on_screen(template_path, confidence):\n    debug_match(template_path, out=\"artifacts/miss.png\", confidence=confidence)\n    raise RuntimeError(f\"template not visible; see artifacts/miss.png\")","typeGuard":"null","tryCatchPattern":"try:\n    click_image(template_path, confidence=0.85)\nexcept RuntimeError as e:\n    debug_match(template_path, out=\"artifacts/click_miss.png\", confidence=0.85)\n    raise","preventionTips":["Capture templates at the same DPI as the CI machine.","Run debug_match() when tuning confidence.","Prefer UIA selectors over screenshot matching where possible.","Foreground the window before matching."],"tags":["python","ui-automation","opencv","template-matching","windows"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}