@@ -490,6 +490,18 @@ def export(
490490 default = 720 ,
491491 help = "The height of the screenshot." ,
492492)
493+ @click .option (
494+ "--headed/--headless" ,
495+ default = False ,
496+ is_flag = True ,
497+ help = "Whether to run the browser in headed mode (visible) or headless mode (invisible)." ,
498+ )
499+ @click .option (
500+ "--stateful/--stateless" ,
501+ default = True ,
502+ is_flag = True ,
503+ help = "Whether to run the browser in stateful mode (preserving session) or stateless mode (fresh session)." ,
504+ )
493505@click .option (
494506 "--output-dir" ,
495507 type = Path ,
@@ -503,6 +515,8 @@ def take_screenshots(
503515 full_page : bool ,
504516 width : int ,
505517 height : int ,
518+ headed : bool ,
519+ stateful : bool ,
506520 output_dir : Path ,
507521):
508522 """Take screenshots of the app in the current directory.
@@ -519,6 +533,7 @@ def take_screenshots(
519533
520534 import playwright .sync_api
521535
536+ from reflex .route import replace_brackets_with_keywords
522537 from reflex .utils .net import get , post
523538
524539 backend_url_parsed = urllib .parse .urlsplit (backend_url )
@@ -527,11 +542,6 @@ def take_screenshots(
527542 + "/"
528543 + constants .Endpoint .ALL_ROUTES .value
529544 )
530- active_connections_url = backend_url_parsed ._replace (
531- path = backend_url_parsed .path .removesuffix ("/" )
532- + "/"
533- + constants .Endpoint .ACTIVE_CONNECTIONS .value
534- )
535545
536546 endpoints : list [str ] = []
537547
@@ -547,57 +557,73 @@ def take_screenshots(
547557
548558 token = ""
549559
550- try :
551- response = get (active_connections_url .geturl ())
552- response .raise_for_status ()
553- active_connections = response .json ()
554- if active_connections :
555- token = list (active_connections .keys ())[- 1 ]
556- console .info (f"Identified active connection with token { token } ." )
557- except Exception :
558- token = ""
559-
560- if token :
561- clone_state_url = backend_url_parsed ._replace (
560+ if stateful :
561+ active_connections_url = backend_url_parsed ._replace (
562562 path = backend_url_parsed .path .removesuffix ("/" )
563563 + "/"
564- + constants .Endpoint .CLONE_STATE .value
564+ + constants .Endpoint .ACTIVE_CONNECTIONS .value
565565 )
566+
566567 try :
567- response = post ( clone_state_url .geturl (), json = token )
568+ response = get ( active_connections_url .geturl ())
568569 response .raise_for_status ()
569- token = response .json ()
570- console . info (
571- f"Cloned state from { clone_state_url . geturl () } using token { token } ."
572- )
570+ active_connections = response .json ()
571+ if active_connections :
572+ token = list ( active_connections . keys ())[ - 1 ]
573+ console . info ( f"Identified active connection with token { token } ." )
573574 except Exception :
574- console .warn (
575- f"Failed to clone state from { clone_state_url .geturl ()} using token { token } . "
576- "This may result in screenshots not being accurate."
577- )
578575 token = ""
579576
577+ if token :
578+ clone_state_url = backend_url_parsed ._replace (
579+ path = backend_url_parsed .path .removesuffix ("/" )
580+ + "/"
581+ + constants .Endpoint .CLONE_STATE .value
582+ )
583+ try :
584+ response = post (clone_state_url .geturl (), json = token )
585+ response .raise_for_status ()
586+ token = response .json ()
587+ console .info (
588+ f"Cloned state from { clone_state_url .geturl ()} using token { token } ."
589+ )
590+ except Exception :
591+ console .warn (
592+ f"Failed to clone state from { clone_state_url .geturl ()} using token { token } . "
593+ "This may result in screenshots not being accurate."
594+ )
595+ token = ""
596+
580597 frontend_url_parsed = urllib .parse .urlsplit (frontend_url )
581598
582- for endpoint in endpoints :
583- normalized_endpoint = endpoint
584- if not normalized_endpoint .startswith ("/" ):
585- normalized_endpoint = "/" + normalized_endpoint
586- if normalized_endpoint == "/index" :
587- normalized_endpoint = "/"
588- full_url = frontend_url_parsed ._replace (
589- path = frontend_url_parsed .path .removesuffix ("/" ) + normalized_endpoint
590- ).geturl ()
591- console .info (f"Taking screenshot of { full_url } " )
592-
593- with playwright .sync_api .sync_playwright () as p :
594- browser = p .chromium .launch (headless = False )
595- page = browser .new_page ()
596- page .set_viewport_size ({"width" : width , "height" : height })
597- if token :
598- page .add_init_script (
599- f"window.sessionStorage.setItem('token', '{ token } ')"
599+ with playwright .sync_api .sync_playwright () as p :
600+ browser = p .chromium .launch (headless = not headed )
601+ page = browser .new_page ()
602+ page .set_viewport_size ({"width" : width , "height" : height })
603+ if token :
604+ page .add_init_script (f"window.sessionStorage.setItem('token', '{ token } ')" )
605+
606+ for endpoint in endpoints :
607+ normalized_endpoint = endpoint
608+ if not normalized_endpoint .startswith ("/" ):
609+ normalized_endpoint = "/" + normalized_endpoint
610+ if normalized_endpoint == "/index" :
611+ normalized_endpoint = "/"
612+
613+ if (
614+ replace_brackets_with_keywords (normalized_endpoint )
615+ != normalized_endpoint
616+ ):
617+ console .warn (
618+ f"Skipping endpoint { normalized_endpoint } because it contains dynamic route args."
600619 )
620+ continue
621+
622+ full_url = frontend_url_parsed ._replace (
623+ path = frontend_url_parsed .path .removesuffix ("/" ) + normalized_endpoint
624+ ).geturl ()
625+ console .info (f"Taking screenshot of { full_url } " )
626+
601627 page .goto (full_url )
602628 page .wait_for_load_state ("networkidle" )
603629 if delay > 0 :
@@ -606,7 +632,9 @@ def take_screenshots(
606632 path = output_dir / f"{ endpoint .strip ('/' ).replace ('/' , '_' )} .png" ,
607633 full_page = full_page ,
608634 )
609- browser .close ()
635+
636+ console .success ("All screenshots taken successfully." )
637+ browser .close ()
610638
611639
612640@cli .command ()
0 commit comments