class Test_WSSessionManager_wait_for_result(TestCase):
    def test_no_messages_with_result_timeout(self):

        with patch.object(_WSMessageProducer,
                          "_get_websocket",
                          new=MagicMock(return_value=_DummyWebsocket())):
            self.session_manager = WSSessionManager(1234, 1)

            with self.assertRaises(DevToolsTimeoutException):
                self.session_manager._wait_for_result(99)

    def test_message_spamming_with_result_timeout(self):

        with patch.object(_WSMessageProducer,
                          "_get_websocket",
                          new=MagicMock(return_value=_DummyWebsocket())):
            self.session_manager = WSSessionManager(1234, 1)

            with self.assertRaises(DevToolsTimeoutException):
                self.session_manager._message_producer.ws.set_recv_message({
                    "method":
                    "Network.Something",
                    "params": {}
                })
                self.session_manager._wait_for_result(99)
    def test_no_messages_with_result_timeout(self):

        with patch.object(_WSMessageProducer,
                          "_get_websocket",
                          new=MagicMock(return_value=_DummyWebsocket())):
            self.session_manager = WSSessionManager(1234, 1)

            with self.assertRaises(DevToolsTimeoutException):
                self.session_manager._wait_for_result(99)
    def test_thread_died_once(self):

        with patch.object(_WSMessageProducer,
                          "_get_websocket",
                          new=MagicMock(return_value=ExceptionThrowingWS())):

            self.session_manager = WSSessionManager(1234, 60)
            self.resetWS()
            start = time.time()
            self.session_manager.execute("Network", "enable")
            self.assertLess(time.time() - start, 10)
    def test_thread_blocks_causes_timeout(self):

        with patch.object(_WSMessageProducer,
                          "_get_websocket",
                          new=MagicMock(return_value=TimeoutBlockingWS())):

            self.session_manager = WSSessionManager(1234, 3)
            self.resetWS()
            with self.assertRaises(DevToolsTimeoutException):
                start = time.time()
                self.session_manager.execute("Network", "enable")
            self.assertLess(time.time() - start, 5)
    def test_thread_blocked_twice(self):

        with patch.object(
                _WSMessageProducer,
                "_get_websocket",
                new=MagicMock(return_value=BlockingWS(times_to_block=2))):

            self.session_manager = WSSessionManager(1234, 30)
            self.resetWS()
            start = time.time()
            self.session_manager.execute("Network", "enable")

            self.assertLess(time.time() - start, 15)
    def test_thread_died_too_many_times(self):

        with patch.object(_WSMessageProducer,
                          "_get_websocket",
                          new=MagicMock(return_value=ExceptionThrowingWS(
                              times_to_except=4))):

            self.session_manager = WSSessionManager(1234, 30)

            self.resetWS()
            start = time.time()
            with self.assertRaises(MaxRetriesException):
                self.session_manager.execute("Network", "enable")
            self.assertLess(time.time() - start, 10)
    def test_max_thread_blocks_exceeded(self):

        with patch.object(
                _WSMessageProducer,
                "_get_websocket",
                new=MagicMock(return_value=BlockingWS(times_to_block=4))):

            self.session_manager = WSSessionManager(1234, 60)
            self.resetWS()
            start = time.time()
            with self.assertRaises(MaxRetriesException):
                self.session_manager.execute("Network", "enable")

            self.assertLess(time.time() - start, 25)
Ejemplo n.º 8
0
    def __init__(self, port, timeout=30, domains=None):
        """ Initialises the interface starting the websocket connection and enabling
            a series of domains.

        :param port: remote-debugging-port to connect.
        :param timeout: Timeout between executing a command and receiving a result.
        :param domains: Dictionary of dictionaries where the Key is the domain string and the Value
        is a dictionary of the arguments passed with the domain upon enabling.
        """
        # type: WSSessionManager
        self._session_manager = WSSessionManager(port,
                                                 timeout,
                                                 domains=domains)
        self._dom_manager = _DOMManager(self._session_manager)
    def test_thread_blocked_once(self):

        with patch.object(
                _WSMessageProducer,
                "_get_websocket",
                new=MagicMock(return_value=BlockingWS(times_to_block=1))):

            self.session_manager = WSSessionManager(1234, 30)

            start = time.time()
            self.session_manager.execute("Network", "enable")

            # We should find the execution result after 5 seconds because
            # we give the thread 5 seconds to poll before we consider it blocked,
            self.assertLess(time.time() - start, 10)
    def test_locked_get_events(self):
        self.ws = FullWebSocket()
        with patch.object(_WSMessageProducer,
                          "_get_websocket",
                          new=MagicMock(return_value=self.ws)):
            self.session_manager = WSSessionManager(1234, 1, {"Network": {}})

            events = list(
                reversed(self.session_manager.get_events("Network",
                                                         clear=True)))

            while self.session_manager._message_producer.ws.queue:
                # Wait until all messages have been processed
                pass

            # make sure we don't lose any
            last_event_collected = events[0]
            next_event = {
                "method": "Network.Something",
                "params": {
                    "index": -1
                }
            }
            first_event = {
                "method": "Network.Something",
                "params": {
                    "index": -1
                }
            }
            if self.session_manager._events["Network"]:
                first_event = self.session_manager._events["Network"][0]

            assert (last_event_collected["params"]["index"]
                    == next_event["params"]["index"] - 1
                    or last_event_collected["params"]["index"]
                    == first_event["params"]["index"] - 1)
    def test_no_dupe_ids(self):
        with patch.object(_WSMessageProducer,
                          "_get_websocket",
                          new=MagicMock(return_value=_DummyWebsocket())):
            self.session_manager = WSSessionManager(1234, 1, {"Network": {}})
            self.ids = []

            def _send(message):
                self.ids.append(message["id"])

            self.session_manager._send = _send

            self.pool.map(self.continually_send, range(10))
            time.sleep(5)

            self.assertEqual(len(set(self.ids)), len(self.ids))
class Test_WSSessionManager_execute(TestCase):
    def resetWS(self):
        """ flush_messages runs async so we only want the socket to start blocking when we're
            ready
        """
        ExceptionThrowingWS.exceptions = 0
        BlockingWS.blocked = 0

    def setUp(self):
        """ We don't want the socket to block before we call execute()
        """
        ExceptionThrowingWS.exceptions = 2
        BlockingWS.blocked = 2

    def tearDown(self):
        self.session_manager._message_producer.ws.unblock()
        self.session_manager.close()

    def test_thread_blocked_once(self):

        with patch.object(
                _WSMessageProducer,
                "_get_websocket",
                new=MagicMock(return_value=BlockingWS(times_to_block=1))):

            self.session_manager = WSSessionManager(1234, 30)

            start = time.time()
            self.session_manager.execute("Network", "enable")

            # We should find the execution result after 5 seconds because
            # we give the thread 5 seconds to poll before we consider it blocked,
            self.assertLess(time.time() - start, 10)

    def test_thread_blocked_twice(self):

        with patch.object(
                _WSMessageProducer,
                "_get_websocket",
                new=MagicMock(return_value=BlockingWS(times_to_block=2))):

            self.session_manager = WSSessionManager(1234, 30)
            self.resetWS()
            start = time.time()
            self.session_manager.execute("Network", "enable")

            self.assertLess(time.time() - start, 15)

    def test_thread_blocks_causes_timeout(self):

        with patch.object(_WSMessageProducer,
                          "_get_websocket",
                          new=MagicMock(return_value=TimeoutBlockingWS())):

            self.session_manager = WSSessionManager(1234, 3)
            self.resetWS()
            with self.assertRaises(DevToolsTimeoutException):
                start = time.time()
                self.session_manager.execute("Network", "enable")
            self.assertLess(time.time() - start, 5)

    def test_max_thread_blocks_exceeded(self):

        with patch.object(
                _WSMessageProducer,
                "_get_websocket",
                new=MagicMock(return_value=BlockingWS(times_to_block=4))):

            self.session_manager = WSSessionManager(1234, 60)
            self.resetWS()
            start = time.time()
            with self.assertRaises(MaxRetriesException):
                self.session_manager.execute("Network", "enable")

            self.assertLess(time.time() - start, 25)

    def test_thread_died_once(self):

        with patch.object(_WSMessageProducer,
                          "_get_websocket",
                          new=MagicMock(return_value=ExceptionThrowingWS())):

            self.session_manager = WSSessionManager(1234, 60)
            self.resetWS()
            start = time.time()
            self.session_manager.execute("Network", "enable")
            self.assertLess(time.time() - start, 10)

    def test_thread_died_twice(self):

        with patch.object(_WSMessageProducer,
                          "_get_websocket",
                          new=MagicMock(return_value=ExceptionThrowingWS(
                              times_to_except=2))):

            self.session_manager = WSSessionManager(1234, 30)
            self.resetWS()
            start = time.time()
            self.session_manager.execute("Network", "enable")
            self.assertLess(time.time() - start, 10)

    def test_thread_died_too_many_times(self):

        with patch.object(_WSMessageProducer,
                          "_get_websocket",
                          new=MagicMock(return_value=ExceptionThrowingWS(
                              times_to_except=4))):

            self.session_manager = WSSessionManager(1234, 30)

            self.resetWS()
            start = time.time()
            with self.assertRaises(MaxRetriesException):
                self.session_manager.execute("Network", "enable")
            self.assertLess(time.time() - start, 10)
Ejemplo n.º 13
0
class ChromeInterface(object):
    """ The Chrome Interface communicates with the browser through the remote-debugging-port using
        the Chrome DevTools Protocol.
        For a thorough reference check: https://chromedevtools.github.io/devtools-protocol/

        Usage example:

            interface = ChromeInterface(9123)
            interface.navigate(url="https://github.com/scivisum/browser-debugger-tools")
    """
    def __init__(self, port, timeout=30, domains=None):
        """ Initialises the interface starting the websocket connection and enabling
            a series of domains.

        :param port: remote-debugging-port to connect.
        :param timeout: Timeout between executing a command and receiving a result.
        :param domains: Dictionary of dictionaries where the Key is the domain string and the Value
        is a dictionary of the arguments passed with the domain upon enabling.
        """
        # type: WSSessionManager
        self._session_manager = WSSessionManager(port,
                                                 timeout,
                                                 domains=domains)
        self._dom_manager = _DOMManager(self._session_manager)

    def quit(self):
        self._session_manager.close()

    def reset(self):
        """ Clears all stored messages
        """
        self._session_manager.reset()
        self._dom_manager.reset()

    def get_events(self, domain, clear=False):
        """ Retrieves all events for a given domain
          :param domain: The domain to get the events for.
          :param clear: Removes the stored events if set to true.
          :return: List of events.
          """
        return self._session_manager.get_events(domain, clear)

    def execute(self, domain, method, params=None):
        """ Executes a command and returns the result.

        Usage example:

        self.execute("Network", "Cookies", args={"urls": ["http://www.urls.com/"]})

        https://chromedevtools.github.io/devtools-protocol/tot/Network#method-getCookies

        :param domain: Chrome DevTools Protocol Domain
        :param method: Domain specific method.
        :param params: Parameters to be executed
        :return: The result of the command
        """
        return self._session_manager.execute(domain, method, params=params)

    def enable_domain(self, domain, params=None):
        """ Enables notifications for the given domain.
        """
        self._session_manager.enable_domain(domain, parameters=params)

    def disable_domain(self, domain):
        """ Disables further notifications from the given domain. Also clears any events cached for
            that domain, it is recommended that you get events for the domain before disabling it.

        """
        self._session_manager.disable_domain(domain)

    @contextlib.contextmanager
    def set_timeout(self, value):
        """ Switches the timeout to the given value.
        """
        _timeout = self._session_manager.timeout
        self._session_manager.timeout = value
        try:
            yield
        finally:
            self._session_manager.timeout = _timeout

    def navigate(self, url):
        """ Navigates to the given url asynchronously
        """
        return self.execute("Page", "navigate", {"url": url})

    def take_screenshot(self, filepath):
        """ Takes a screenshot of the current page
        """
        response = self.execute("Page", "captureScreenshot")
        image_data = response["data"]
        with open(filepath, "wb") as f:
            f.write(b64decode(image_data))

    def stop_page_load(self):
        return self.execute("Page", "stopLoading")

    def execute_javascript(self, script):
        result = self.execute("Runtime", "evaluate", {
            "expression": script,
            "returnByValue": True
        })["result"]

        return result.get("value")

    def get_url(self):
        # type: () -> str
        """
        Consider enabling the Page domain to increase performance.

        :returns: The url of the current page.
        """
        return self._session_manager.event_handlers[
            "PageLoad"].get_current_url()

    def get_document_readystate(self):
        """ Gets the document.readyState of the page.
        """
        return self.execute_javascript("document.readyState")

    def set_user_agent_override(self, user_agent):
        """ Overriding user agent with the given string.
        :param user_agent:
        :return:
        """
        return self.execute("Network", "setUserAgentOverride",
                            {"userAgent": user_agent})

    def emulate_network_conditions(self,
                                   latency,
                                   download,
                                   upload,
                                   offline=False):
        """
        :param latency: Minimum latency from request sent to response headers (ms).
        :param download: Maximal aggregated download throughput (bytes/sec).
        :param upload: Maximal aggregated upload throughput (bytes/sec).
        :param offline: Whether to emulate network disconnection
        """

        # Note: Currently, there's a bug in the devtools protocol when disabling parameters,
        # i.e setting download to -1, therefore we enforce that all parameters must be passed with
        # a sensible value (bigger than 0)
        assert min(latency, download, upload) > 0 or offline

        network_conditions = {
            "offline": offline,
            "latency": latency,
            "downloadThroughput": download,
            "uploadThroughput": upload,
        }

        return self.execute("Network", "emulateNetworkConditions",
                            network_conditions)

    def set_basic_auth(self, username, password):
        """
        Creates a basic type Authorization header from the username and password strings
        and applies it to all requests
        """
        auth = "Basic " + b64encode("%s:%s" % (username, password))
        self.set_request_headers({"Authorization": auth})

    def set_request_headers(self, headers):
        """
        The specified headers are applied to all requests
        :param headers: A dictionary of the form {"headerKey": "headerValue"}
        """
        self.execute("Network", "setExtraHTTPHeaders", {"headers": headers})

    def get_opened_javascript_dialog(self):
        # type: () -> JavascriptDialog
        """
        Gets the opened javascript dialog.

        :raises DomainNotEnabledError: If the Page domain isn't enabled
        :raises JavascriptDialogNotFoundError: If there is currently no dialog open
        """
        return (self._session_manager.event_handlers["JavascriptDialog"].
                get_opened_javascript_dialog())

    def get_iframe_source_content(self, xpath):
        # type: (str) -> str
        """
        Returns the HTML markup for an iframe document, where the iframe node can be located in the
        DOM with the given xpath.

        :param xpath: following the spec 3.1 https://www.w3.org/TR/xpath-31/
        :return: HTML markup
        :raises IFrameNotFoundError: A matching iframe document could not be found
        :raises UnknownError: The socket handler received a message with an unknown error code
        """
        return self._dom_manager.get_iframe_html(xpath)

    def get_page_source(self):
        # type: () -> str
        """
        Returns the HTML markup of the current page. Iframe tags are included but the enclosed
        documents are not. Consider enabling the Page domain to increase performance.

        :return: HTML markup
        """

        root_node_id = self._session_manager.event_handlers[
            "PageLoad"].get_root_backend_node_id()
        return self._dom_manager.get_outer_html(root_node_id)