Repository: apify/crawlee-python
File: docs/guides/code_examples/playwright_crawler_stagehand/support_classes.py (Python)

Code before:
        wait_until: str | None = None,
    ) -> Any:
        """Navigate to the specified URL."""
        # Override goto to return navigation result that `PlaywrightCrawler` expects
        return await self._page._page.goto(  # noqa: SLF001
            url,
            referer=referer,
            timeout=timeout,
            wait_until=wait_until,
        )

    def __getattr__(self, name: str) -> Any:
        """Delegate all other methods to the underlying StagehandPage."""
        if name == 'goto':
            # This case will never be called since goto is defined above
            return self.goto
        return getattr(self._page, name)

    async def __aenter__(self) -> CrawleeStagehandPage:
        """Enter the context manager."""
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_value: BaseException | None,
        exc_traceback: TracebackType | None,
    ) -> None:
        await self._page.close()


class CrawleeStagehand(Stagehand):
    """Stagehand wrapper for Crawlee to disable the launch of Playwright."""

    async def init(self) -> None:
        # Skip Stagehand's own Playwright initialization
        # Let Crawlee's PlaywrightBrowserPlugin manage the browser lifecycle
        self._initialized = True


Review comment (suggestion):
self._page is an instance of `StagehandPage`, right? I don't think it has a close method.

I guess it is supposed to be self._page._page and this was supposed to rely on `__getattr__` to do the delegation, but since `_page` exists on `self`, the `__getattr__` will not be called.

There are different ways to handle this, but maybe you can use `self._stagehand_page` to avoid shadowing the `_page` that is supposed to be delegated and to be more explicit about which page is which.

```
class CrawleeStagehandPage:
    """StagehandPage wrapper for Crawlee."""

    def __init__(self, page: StagehandPage) -> None:
        self._stagehand_page = page
```




Code after:
        wait_until: str | None = None,
    ) -> Any:
        """Navigate to the specified URL."""
        # Override goto to return navigation result that `PlaywrightCrawler` expects
        return await self._page._page.goto(  # noqa: SLF001
            url,
            referer=referer,
            timeout=timeout,
            wait_until=wait_until,
        )

    def __getattr__(self, name: str) -> Any:
        """Delegate all other methods to the underlying StagehandPage."""
        return getattr(self._page, name)

    async def __aenter__(self) -> CrawleeStagehandPage:
        """Enter the context manager."""
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_value: BaseException | None,
        exc_traceback: TracebackType | None,
    ) -> None:
        await self._page.close()


class CrawleeStagehand(Stagehand):
    """Stagehand wrapper for Crawlee to disable the launch of Playwright."""

    async def init(self) -> None:
        # Skip Stagehand's own Playwright initialization
        # Let Crawlee's PlaywrightBrowserPlugin manage the browser lifecycle
        self._initialized = True