{"record":{"id":"e0a756a5168ed736","repo":"dgtlmoon/changedetection.io","slug":"screenshotunavailable","errorCode":null,"errorMessage":"ScreenshotUnavailable","messagePattern":"ScreenshotUnavailable","errorType":"exception","errorClass":"ScreenshotUnavailable","httpStatus":null,"severity":"error","filePath":"changedetectionio/content_fetchers/playwright.py","lineNumber":420,"sourceCode":"\n\n                # Bug 3 in Playwright screenshot handling\n                # Some bug where it gives the wrong screenshot size, but making a request with the clip set first seems to solve it\n                # JPEG is better here because the screenshots can be very very large\n\n                # Screenshots also travel via the ws:// (websocket) meaning that the binary data is base64 encoded\n                # which will significantly increase the IO size between the server and client, it's recommended to use the lowest\n                # acceptable screenshot quality here\n                # The actual screenshot - this always base64 and needs decoding! horrible! huge CPU usage\n                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)\n\n                # Force aggressive memory cleanup - screenshots are large and base64 decode creates temporary buffers\n                await self.page.request_gc()\n                gc.collect()\n\n            except ScreenshotUnavailable:\n                # Re-raise screenshot unavailable exceptions\n                raise ScreenshotUnavailable(url=url, status_code=self.status_code)\n\n            finally:\n                # Clean up resources properly with timeouts to prevent hanging\n                try:\n                    if hasattr(self, 'page') and self.page:\n                        await self.page.request_gc()\n                        await asyncio.wait_for(self.page.close(), timeout=5.0)\n                        logger.debug(f\"Successfully closed page for {url}\")\n                except asyncio.TimeoutError:\n                    logger.warning(f\"Timed out closing page for {url} (5s)\")\n                except Exception as e:\n                    logger.warning(f\"Error closing page for {url}: {e}\")\n                finally:\n                    self.page = None\n\n                try:\n                    if context:\n                        await asyncio.wait_for(context.close(), timeout=5.0)","sourceCodeStart":402,"sourceCodeEnd":438,"githubUrl":"https://github.com/dgtlmoon/changedetection.io/blob/5d9c7c6da76340597243e8163c4f2439237fa0e8/changedetectionio/content_fetchers/playwright.py#L402-L438","documentation":"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().","triggerScenarios":"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.","commonSituations":"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.","solutions":["Prefix the scheme: https://example.com/path","For non-http URIs, use a different validator or extend the check to allow specific schemes","Validate client-side before submit and auto-prepend https:// when missing"],"exampleFix":"# before\nexample.com/watch\n# after\nhttps://example.com/watch","handlingStrategy":"validation","validationCode":"from urllib.parse import urlparse\n\ndef simple_url_ok(value: str) -> bool:\n    if not value:\n        return True  # pair with Optional()\n    p = urlparse(value)\n    return bool(p.scheme and p.netloc)","typeGuard":"def is_absolute_http_url(value: str) -> bool:\n    p = urlparse(value)\n    return p.scheme in ('http', 'https') and bool(p.netloc)","tryCatchPattern":null,"preventionTips":["Always include the scheme (https://) in submitted URLs","Auto-prepend https:// in the UI when the user omits it","Pair the validator with wtforms.validators.Optional() so blanks pass"],"tags":["url-validation","urlparse","form-validation"],"backgroundTag":"invalid-url-format","analyzedSha":"5d9c7c6da76340597243e8163c4f2439237fa0e8","analyzedAt":"2026-08-27T19:41:16.067Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}