dgtlmoon/changedetection.io · error · ScreenshotUnavailable

ScreenshotUnavailable

Error message

ScreenshotUnavailable

What it means

ValidateSimpleURL.__call__ parses the submitted value with urllib.parse.urlparse and requires both a scheme and a netloc; otherwise it raises ValidationError('Invalid URL.'). Empty values pass through so the validator can be paired with validators.Optional().

Source

Thrown at changedetectionio/content_fetchers/playwright.py:420


                # Bug 3 in Playwright screenshot handling
                # Some bug where it gives the wrong screenshot size, but making a request with the clip set first seems to solve it
                # JPEG is better here because the screenshots can be very very large

                # Screenshots also travel via the ws:// (websocket) meaning that the binary data is base64 encoded
                # which will significantly increase the IO size between the server and client, it's recommended to use the lowest
                # acceptable screenshot quality here
                # The actual screenshot - this always base64 and needs decoding! horrible! huge CPU usage
                self.screenshot = await capture_full_page_async(page=self.page, screenshot_format=self.screenshot_format, watch_uuid=watch_uuid, lock_viewport_elements=self.lock_viewport_elements)

                # Force aggressive memory cleanup - screenshots are large and base64 decode creates temporary buffers
                await self.page.request_gc()
                gc.collect()

            except ScreenshotUnavailable:
                # Re-raise screenshot unavailable exceptions
                raise ScreenshotUnavailable(url=url, status_code=self.status_code)

            finally:
                # Clean up resources properly with timeouts to prevent hanging
                try:
                    if hasattr(self, 'page') and self.page:
                        await self.page.request_gc()
                        await asyncio.wait_for(self.page.close(), timeout=5.0)
                        logger.debug(f"Successfully closed page for {url}")
                except asyncio.TimeoutError:
                    logger.warning(f"Timed out closing page for {url} (5s)")
                except Exception as e:
                    logger.warning(f"Error closing page for {url}: {e}")
                finally:
                    self.page = None

                try:
                    if context:
                        await asyncio.wait_for(context.close(), timeout=5.0)

View on GitHub (pinned to 5d9c7c6da7)

Solutions

  1. Prefix the scheme: https://example.com/path
  2. For non-http URIs, use a different validator or extend the check to allow specific schemes
  3. Validate client-side before submit and auto-prepend https:// when missing

Example fix

# before
example.com/watch
# after
https://example.com/watch
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlparse

def simple_url_ok(value: str) -> bool:
    if not value:
        return True  # pair with Optional()
    p = urlparse(value)
    return bool(p.scheme and p.netloc)

Type guard

def is_absolute_http_url(value: str) -> bool:
    p = urlparse(value)
    return p.scheme in ('http', 'https') and bool(p.netloc)

Prevention

When it happens

Trigger: Submitting values like 'example.com/path' (no scheme), 'mailto:someone@x.com' (scheme but no netloc), 'javascript:void(0)', or random text to a field using ValidateSimpleURL. Only strings like 'https://example.com/x' pass.

Common situations: Users omitting https:// when entering a watch URL; entering local paths or hostnames without protocol; passing URIs (urn:, mailto:) that legitimately lack a netloc into a field designed for absolute http(s) URLs.

Related errors


AI-assisted analysis of dgtlmoon/changedetection.io@5d9c7c6da7 (2026-08-27). Data as JSON: /api/errors/e0a756a5168ed736. Report an issue: GitHub.