@@ -518,6 +518,13 @@ async def _download_request_with_page(
518518
519519 start_time = time ()
520520 response , download = await self ._get_response_and_download (request , page , spider )
521+
522+ # page methods may navigate the main frame away from the original response
523+ response = await self ._maybe_apply_page_methods (
524+ page = page , request = request , spider = spider , response = response
525+ )
526+
527+ headers = Headers ()
521528 if isinstance (response , PlaywrightResponse ):
522529 await _set_redirect_meta (request = request , response = response )
523530 headers = Headers (await response .all_headers ())
@@ -534,9 +541,7 @@ async def _download_request_with_page(
534541 "scrapy_request_method" : request .method ,
535542 },
536543 )
537- headers = Headers ()
538544
539- await self ._apply_page_methods (page , request , spider )
540545 body_str = await _get_page_content (
541546 page = page ,
542547 spider = spider ,
@@ -672,7 +677,51 @@ async def _handle_response(response: PlaywrightResponse) -> None:
672677
673678 return response , download if download else None
674679
675- async def _apply_page_methods (self , page : Page , request : Request , spider : Spider ) -> None :
680+ async def _maybe_apply_page_methods (
681+ self ,
682+ page : Page ,
683+ request : Request ,
684+ spider : Spider ,
685+ response : Optional [PlaywrightResponse ],
686+ ) -> Optional [PlaywrightResponse ]:
687+ """Run the request's page methods, returning the final Playwright response to use.
688+
689+ If a page method navigates the main frame away from the original response, the final Scrapy
690+ response should have updated URL, body, status and headers. URL and body can be taken from
691+ the Playwright Page, but status and headers need to be taken from the final Playwright
692+ Response, which is not available as return value of page.goto().
693+ """
694+ if not request .meta .get ("playwright_page_methods" ):
695+ return response
696+
697+ # track the most recent main-frame document navigation triggered by the page methods
698+ last_navigation = response
699+
700+ def _track_navigation (navigation_response : PlaywrightResponse ) -> None :
701+ nonlocal last_navigation
702+ if (
703+ navigation_response .frame is page .main_frame
704+ and navigation_response .request .is_navigation_request ()
705+ and navigation_response .request .resource_type == "document"
706+ ):
707+ last_navigation = navigation_response
708+
709+ page .on ("response" , _track_navigation )
710+ try :
711+ await self ._run_page_methods (page , request , spider )
712+ finally :
713+ page .remove_listener ("response" , _track_navigation )
714+
715+ # use the final navigation response if it superseded the original one
716+ if (
717+ last_navigation is not response
718+ and isinstance (last_navigation , PlaywrightResponse )
719+ and last_navigation .url .rstrip ("/" ) == page .url .rstrip ("/" )
720+ ):
721+ return last_navigation
722+ return response
723+
724+ async def _run_page_methods (self , page : Page , request : Request , spider : Spider ) -> None :
676725 context_name = request .meta .get ("playwright_context" )
677726 page_methods = request .meta .get ("playwright_page_methods" ) or ()
678727 if isinstance (page_methods , dict ):
0 commit comments