| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -27,58 +27,103 @@ import os
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import logging
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				from changedetectionio.store import ChangeDetectionStore
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				from changedetectionio import login_optionally_required
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				browsersteps_live_ui_o = {}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				browsersteps_playwright_browser_interface = None
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				browsersteps_playwright_browser_interface_browser = None
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				browsersteps_playwright_browser_interface_context = None
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				browsersteps_playwright_browser_interface_end_time = None
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				browsersteps_playwright_browser_interface_start_time = None
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def cleanup_playwright_session():
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				browsersteps_sessions = {}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				io_interface_context = None
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    global browsersteps_live_ui_o
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    global browsersteps_playwright_browser_interface
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    global browsersteps_playwright_browser_interface_browser
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    global browsersteps_playwright_browser_interface_context
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    global browsersteps_playwright_browser_interface_end_time
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    global browsersteps_playwright_browser_interface_start_time
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    browsersteps_live_ui_o = {}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    browsersteps_playwright_browser_interface = None
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    browsersteps_playwright_browser_interface_browser = None
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    browsersteps_playwright_browser_interface_end_time = None
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    browsersteps_playwright_browser_interface_start_time = None
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    print("Cleaning up old playwright session because time was up, calling .goodbye()")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    try:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        browsersteps_playwright_browser_interface_context.goodbye()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    except Exception as e:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        print ("Got exception in shutdown, probably OK")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        print (str(e))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    browsersteps_playwright_browser_interface_context = None
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    print ("Cleaning up old playwright session because time was up - done")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def construct_blueprint(datastore: ChangeDetectionStore):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    browser_steps_blueprint = Blueprint('browser_steps', __name__, template_folder="templates")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    def start_browsersteps_session(watch_uuid):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        from . import nonContext
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        from . import browser_steps
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        import time
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        global browsersteps_sessions
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        global io_interface_context
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # We keep the playwright session open for many minutes
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        seconds_keepalive = int(os.getenv('BROWSERSTEPS_MINUTES_KEEPALIVE', 10)) * 60
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        browsersteps_start_session = {'start_time': time.time()}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # You can only have one of these running
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # This should be very fine to leave running for the life of the application
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # @idea - Make it global so the pool of watch fetchers can use it also
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if not io_interface_context:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            io_interface_context = nonContext.c_sync_playwright()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            # Start the Playwright context, which is actually a nodejs sub-process and communicates over STDIN/STDOUT pipes
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            io_interface_context = io_interface_context.start()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # keep it alive for 10 seconds more than we advertise, sometimes it helps to keep it shutting down cleanly
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        keepalive = "&timeout={}".format(((seconds_keepalive + 3) * 1000))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        try:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            browsersteps_start_session['browser'] = io_interface_context.chromium.connect_over_cdp(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                os.getenv('PLAYWRIGHT_DRIVER_URL', '') + keepalive)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        except Exception as e:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if 'ECONNREFUSED' in str(e):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                return make_response('Unable to start the Playwright Browser session, is it running?', 401)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            else:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                return make_response(str(e), 401)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        proxy_id = datastore.get_preferred_proxy_for_watch(uuid=watch_uuid)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        proxy = None
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if proxy_id:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            proxy_url = datastore.proxy_list.get(proxy_id).get('url')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if proxy_url:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                # Playwright needs separate username and password values
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                from urllib.parse import urlparse
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                parsed = urlparse(proxy_url)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                proxy = {'server': proxy_url}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                if parsed.username:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    proxy['username'] = parsed.username
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                if parsed.password:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    proxy['password'] = parsed.password
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                print("Browser Steps: UUID {} selected proxy {}".format(watch_uuid, proxy_url))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # Tell Playwright to connect to Chrome and setup a new session via our stepper interface
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        browsersteps_start_session['browserstepper'] = browser_steps.browsersteps_live_ui(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            playwright_browser=browsersteps_start_session['browser'],
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            proxy=proxy)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # For test
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        #browsersteps_start_session['browserstepper'].action_goto_url(value="http://example.com?time="+str(time.time()))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return browsersteps_start_session
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @login_optionally_required
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @browser_steps_blueprint.route("/browsersteps_update", methods=['GET', 'POST'])
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @browser_steps_blueprint.route("/browsersteps_start_session", methods=['GET'])
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    def browsersteps_start_session():
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # A new session was requested, return sessionID
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        import uuid
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        browsersteps_session_id = str(uuid.uuid4())
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        watch_uuid = request.args.get('uuid')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        global browsersteps_sessions
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        print("Starting connection with playwright")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        logging.debug("browser_steps.py connecting")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        browsersteps_sessions[browsersteps_session_id] = start_browsersteps_session()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        print("Starting connection with playwright - done")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return {'browsersteps_session_id': browsersteps_session_id}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    # A request for an action was received
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @login_optionally_required
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @browser_steps_blueprint.route("/browsersteps_update", methods=['POST'])
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    def browsersteps_ui_update():
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        import base64
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        import playwright._impl._api_types
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        import time
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        global browsersteps_sessions
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        from changedetectionio.blueprint.browser_steps import browser_steps
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        global browsersteps_live_ui_o, browsersteps_playwright_browser_interface_end_time
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        global browsersteps_playwright_browser_interface_browser
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        global browsersteps_playwright_browser_interface
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        global browsersteps_playwright_browser_interface_start_time
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        step_n = None
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        remaining =0
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        uuid = request.args.get('uuid')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -87,13 +132,9 @@ def construct_blueprint(datastore: ChangeDetectionStore):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if not browsersteps_session_id:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return make_response('No browsersteps_session_id specified', 500)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # Because we don't "really" run in a context manager ( we make the playwright interface global/long-living )
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # We need to manage the shutdown when the time is up
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if browsersteps_playwright_browser_interface_end_time:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            remaining = browsersteps_playwright_browser_interface_end_time-time.time()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if browsersteps_playwright_browser_interface_end_time and remaining <= 0:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                cleanup_playwright_session()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                return make_response('Browser session expired, please reload the Browser Steps interface', 401)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if not browsersteps_sessions.get(browsersteps_session_id):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return make_response('No session exists under that ID', 500)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # Actions - step/apply/etc, do the thing and return state
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if request.method == 'POST':
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -112,12 +153,7 @@ def construct_blueprint(datastore: ChangeDetectionStore):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            # @todo try.. accept.. nice errors not popups..
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            try:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                this_session = browsersteps_live_ui_o.get(browsersteps_session_id)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                if not this_session:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    print("Browser exited")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    return make_response('Browser session ran out of time :( Please reload this page.', 401)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                this_session.call_action(action_name=step_operation,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                browsersteps_sessions[browsersteps_session_id]['browserstepper'].call_action(action_name=step_operation,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                         selector=step_selector,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                         optional_value=step_optional_value)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -129,108 +165,43 @@ def construct_blueprint(datastore: ChangeDetectionStore):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            # Get visual selector ready/update its data (also use the current filter info from the page?)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            # When the last 'apply' button was pressed
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            # @todo this adds overhead because the xpath selection is happening twice
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            u = this_session.page.url
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            u = browsersteps_sessions[browsersteps_session_id]['browserstepper'].page.url
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if is_last_step and u:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                (screenshot, xpath_data) = this_session.request_visualselector_data()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                (screenshot, xpath_data) = browsersteps_sessions[browsersteps_session_id]['browserstepper'].request_visualselector_data()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                datastore.save_screenshot(watch_uuid=uuid, screenshot=screenshot)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                datastore.save_xpath_data(watch_uuid=uuid, data=xpath_data)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # Setup interface
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if request.method == 'GET':
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#        if not this_session.page:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#            cleanup_playwright_session()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#            return make_response('Browser session ran out of time :( Please reload this page.', 401)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if not browsersteps_playwright_browser_interface:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                print("Starting connection with playwright")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                logging.debug("browser_steps.py connecting")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # Screenshots and other info only needed on requesting a step (POST)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        try:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            state = browsersteps_sessions[browsersteps_session_id]['browserstepper'].get_current_state()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        except playwright._impl._api_types.Error as e:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return make_response("Browser session ran out of time :( Please reload this page."+str(e), 401)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                global browsersteps_playwright_browser_interface_context
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                from . import nonContext
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                browsersteps_playwright_browser_interface_context = nonContext.c_sync_playwright()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                browsersteps_playwright_browser_interface = browsersteps_playwright_browser_interface_context.start()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                # At 20 minutes, some other variable is closing it
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                # @todo find out what it is and set it
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                seconds_keepalive = int(os.getenv('BROWSERSTEPS_MINUTES_KEEPALIVE', 10)) * 60
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # Use send_file() which is way faster than read/write loop on bytes
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        import json
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        from tempfile import mkstemp
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        from flask import send_file
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        tmp_fd, tmp_file = mkstemp(text=True, suffix=".json", prefix="changedetectionio-")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                # keep it alive for 10 seconds more than we advertise, sometimes it helps to keep it shutting down cleanly
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                keepalive = "&timeout={}".format(((seconds_keepalive+3) * 1000))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                try:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    browsersteps_playwright_browser_interface_browser = browsersteps_playwright_browser_interface.chromium.connect_over_cdp(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        os.getenv('PLAYWRIGHT_DRIVER_URL', '') + keepalive)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                except Exception as e:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    if 'ECONNREFUSED' in str(e):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        return make_response('Unable to start the Playwright session properly, is it running?', 401)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        output = json.dumps({'screenshot': "data:image/jpeg;base64,{}".format(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            base64.b64encode(state[0]).decode('ascii')),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            'xpath_data': state[1],
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            'session_age_start': browsersteps_sessions[browsersteps_session_id]['browserstepper'].age_start,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            'browser_time_remaining': round(remaining)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        })
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                browsersteps_playwright_browser_interface_end_time = time.time() + (seconds_keepalive-3)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                print("Starting connection with playwright - done")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        with os.fdopen(tmp_fd, 'w') as f:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            f.write(output)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if not browsersteps_live_ui_o.get(browsersteps_session_id):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                # Boot up a new session
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                proxy_id = datastore.get_preferred_proxy_for_watch(uuid=uuid)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                proxy = None
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                if proxy_id:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    proxy_url = datastore.proxy_list.get(proxy_id).get('url')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    if proxy_url:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        # Playwright needs separate username and password values
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        from urllib.parse import urlparse
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        parsed = urlparse(proxy_url)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        proxy = {'server': proxy_url}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        if parsed.username:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            proxy['username'] = parsed.username
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        if parsed.password:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            proxy['password'] = parsed.password
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        print("Browser Steps: UUID {} Using proxy {}".format(uuid, proxy_url))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                # Begin the new "Playwright Context" that re-uses the playwright interface
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                # Each session is a "Playwright Context" as a list, that uses the playwright interface
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                browsersteps_live_ui_o[browsersteps_session_id] = browser_steps.browsersteps_live_ui(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    playwright_browser=browsersteps_playwright_browser_interface_browser,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    proxy=proxy)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                this_session = browsersteps_live_ui_o[browsersteps_session_id]
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if not this_session.page:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            cleanup_playwright_session()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return make_response('Browser session ran out of time :( Please reload this page.', 401)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        response = None
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if request.method == 'POST':
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            # Screenshots and other info only needed on requesting a step (POST)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            try:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                state = this_session.get_current_state()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            except playwright._impl._api_types.Error as e:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                return make_response("Browser session ran out of time :( Please reload this page."+str(e), 401)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            # Use send_file() which is way faster than read/write loop on bytes
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            import json
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            from tempfile import mkstemp
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            from flask import send_file
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            tmp_fd, tmp_file = mkstemp(text=True, suffix=".json", prefix="changedetectionio-")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            output = json.dumps({'screenshot': "data:image/jpeg;base64,{}".format(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                base64.b64encode(state[0]).decode('ascii')),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                'xpath_data': state[1],
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                'session_age_start': this_session.age_start,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                'browser_time_remaining': round(remaining)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            })
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            with os.fdopen(tmp_fd, 'w') as f:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                f.write(output)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            response = make_response(send_file(path_or_file=tmp_file,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                               mimetype='application/json; charset=UTF-8',
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                               etag=True))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            # No longer needed
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            os.unlink(tmp_file)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        elif request.method == 'GET':
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            # Just enough to get the session rolling, it will call for goto-site via POST next
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            response = make_response({
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                'session_age_start': this_session.age_start,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                'browser_time_remaining': round(remaining)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            })
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        response = make_response(send_file(path_or_file=tmp_file,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                           mimetype='application/json; charset=UTF-8',
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                           etag=True))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # No longer needed
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        os.unlink(tmp_file)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return response
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				 
 |