{"record":{"id":"28281855e731db63","repo":"D4Vinci/Scrapling","slug":"page-action-must-be-callable-got-type-self-page","errorCode":null,"errorMessage":"page_action must be callable, got {type(self.page_action).__name__}","messagePattern":"page_action must be callable, got (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"scrapling/engines/_browsers/_validators.py","lineNumber":99,"sourceCode":"    selector_config: Optional[Dict] = {}\n    additional_args: Optional[Dict] = {}\n    locale: str | None = None\n    real_chrome: bool = False\n    cdp_url: Optional[str] = None\n    useragent: Optional[str] = None\n    extra_flags: Optional[List[str]] = None\n    blocked_domains: Optional[Set[str]] = None\n    block_ads: bool = False\n    retries: RetriesCount = 3\n    retry_delay: Seconds = 1\n    capture_xhr: str | None = None\n    executable_path: Optional[str] = None\n    dns_over_https: bool = False\n\n    def __post_init__(self):  # pragma: no cover\n        \"\"\"Custom validation after msgspec validation\"\"\"\n        if self.page_action and not callable(self.page_action):\n            raise TypeError(f\"page_action must be callable, got {type(self.page_action).__name__}\")\n        if self.page_setup and not callable(self.page_setup):\n            raise TypeError(f\"page_setup must be callable, got {type(self.page_setup).__name__}\")\n        if self.proxy and self.proxy_rotator:\n            raise ValueError(\n                \"Cannot use 'proxy_rotator' together with 'proxy'. \"\n                \"Use either a static proxy or proxy rotation, not both.\"\n            )\n        if self.proxy:\n            self.proxy = construct_proxy_dict(self.proxy)\n        if self.cdp_url:\n            cdp_msg = _is_invalid_cdp_url(self.cdp_url)\n            if cdp_msg:\n                raise ValueError(cdp_msg)\n\n        if not self.cookies:\n            self.cookies = []\n        if not self.extra_flags:\n            self.extra_flags = []","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/D4Vinci/Scrapling/blob/5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f/scrapling/engines/_browsers/_validators.py#L81-L117","documentation":"PlaywrightConfig.__post_init__ (msgspec Struct validation) throws TypeError when the `page_action` parameter is truthy but not callable. page_action is meant to be a user-supplied callback (sync: def page_action(page), async: async def) executed on the page after load.","triggerScenarios":"Passing a function name as a string (page_action='scroll_page'), passing the result of a call page_action=my_func() instead of the reference, or passing an arbitrary object.","commonSituations":"Loading callback names from JSON/YAML config files, templating configs, or copy-pasting examples that quote the function.","solutions":["Pass the function reference itself: page_action=my_func","If loading from config, resolve the string to an actual function via a registry/dict lookup before passing","For async sessions pass a coroutine function (async def)"],"exampleFix":"// before\nsession.fetch(url, page_action='scroll_to_bottom')\n\n// after\ndef scroll_to_bottom(page):\n    page.mouse.wheel(0, 5000)\nsession.fetch(url, page_action=scroll_to_bottom)","handlingStrategy":"type-guard","validationCode":"if page_action is not None and not callable(page_action):\n    raise TypeError('page_action must be callable')\nsession.fetch(url, page_action=page_action)","typeGuard":"def is_valid_page_action(fn) -> bool:\n    return fn is None or callable(fn)","tryCatchPattern":"try:\n    session.fetch(url, page_action=cb)\nexcept TypeError as e:\n    if 'page_action must be callable' in str(e):\n        cb = resolve_callback(cb); session.fetch(url, page_action=cb)\n    else:\n        raise","preventionTips":["Always pass function references, never strings or call results (no parentheses)","When callbacks come from config files, map names to functions via an explicit registry","Add callable(cb) asserts in config-loading code to fail early"],"tags":["validation","callback","typeerror","config"],"backgroundTag":null,"analyzedSha":"5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f","analyzedAt":"2026-08-14T22:23:09.440Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}