From 59db4e55aa56c541c4f1622e27f6db53da6de7ad Mon Sep 17 00:00:00 2001 From: IMayBeABitShy Date: Thu, 12 Jan 2023 17:18:41 +0100 Subject: [PATCH 1/3] Add a method for deleting/discarding a specific request by its id --- README.rst | 6 ++++++ seleniumwire/inspect.py | 14 +++++++++++++ seleniumwire/storage.py | 32 ++++++++++++++++++++++++++++++ tests/seleniumwire/test_inspect.py | 5 +++++ tests/seleniumwire/test_storage.py | 25 +++++++++++++++++++++++ 5 files changed, 82 insertions(+) diff --git a/README.rst b/README.rst index ec3e3bc..5016624 100644 --- a/README.rst +++ b/README.rst @@ -263,6 +263,12 @@ To clear previously captured requests and HAR entries, use ``del``: del driver.requests +Alternatively, you can discard a specific captured request using ``.delete_request()``: + +.. code:: python + + driver.delete_request(request.id) + .. [1] Selenium Wire ignores OPTIONS requests by default, as these are typically uninteresting and just add overhead. If you want to capture OPTIONS requests, you need to set the ``ignore_http_methods`` `option`_ to ``[]``. .. _`option`: #all-options diff --git a/seleniumwire/inspect.py b/seleniumwire/inspect.py index 7e03f89..5ecb27f 100644 --- a/seleniumwire/inspect.py +++ b/seleniumwire/inspect.py @@ -82,6 +82,20 @@ def wait_for_request(self, pat: str, timeout: Union[int, float] = 10) -> Request raise TimeoutException('Timed out after {}s waiting for request matching {}'.format(timeout, pat)) + def delete_request(self, request_id) -> None: + """Clear a specific request. + + Args: + request_id: id of request to clear. + Raises: + KeyError if no request matching the specified id could be found + """ + # I believe that changing the 'requests* property to return an + # instance supporting __delitem__ would provide a better API for + # users, but this may break some backwards compatibilty or lead + # to unexpected behavior. + self.backend.storage.clear_request_by_id(request_id) + @property def har(self) -> str: """Get a HAR archive of HTTP transactions that have taken place. diff --git a/seleniumwire/storage.py b/seleniumwire/storage.py index 1be95da..1ec89d4 100644 --- a/seleniumwire/storage.py +++ b/seleniumwire/storage.py @@ -286,6 +286,25 @@ def clear_requests(self) -> None: for indexed_request in index: shutil.rmtree(self._get_request_dir(indexed_request.id), ignore_errors=True) + def clear_request_by_id(self, request_id: str) -> None: + """Clear a specific request. + + Args: + request_id: id of request to clear. + Raises: + KeyError if no request matching the specified id could be found + """ + with self._lock: + for i, indexed_request in enumerate(self._index): + if indexed_request.id == request_id: + request_index = i + break + else: + raise KeyError("Could not find any request with the specified id '{}'!".format(request_id)) + del self._index[i] + + shutil.rmtree(self._get_request_dir(indexed_request.id), ignore_errors=True) + def find(self, pat: str, check_response: bool = True) -> Optional[Request]: """Find the first request that matches the specified pattern. @@ -498,6 +517,19 @@ def clear_requests(self) -> None: with self._lock: self._requests.clear() + def clear_request_by_id(self, request_id: str) -> None: + """Clear a specific request. + + Args: + request_id: id of request to clear. + Raises: + KeyError if no request matching the specified id could be found + """ + with self._lock: + if request_id not in self._requests: + raise KeyError("Could not find any request with the specified id '{}'!".format(request_id)) + del self._requests[request_id] + def find(self, pat: str, check_response: bool = True) -> Optional[Request]: """Find the first request that matches the specified pattern. diff --git a/tests/seleniumwire/test_inspect.py b/tests/seleniumwire/test_inspect.py index 376e859..dd351f0 100644 --- a/tests/seleniumwire/test_inspect.py +++ b/tests/seleniumwire/test_inspect.py @@ -32,6 +32,11 @@ def test_delete_requests(self): self.mock_backend.storage.clear_requests.assert_called_once_with() + def test_delete_request(self): + self.driver.delete_request("foo") + + self.mock_backend.storage.clear_request_by_id.assert_called_once_with("foo") + def test_iter_requests(self): self.mock_backend.storage.iter_requests.return_value = iter([Mock()]) diff --git a/tests/seleniumwire/test_storage.py b/tests/seleniumwire/test_storage.py index 67f83a8..a04907f 100644 --- a/tests/seleniumwire/test_storage.py +++ b/tests/seleniumwire/test_storage.py @@ -283,6 +283,19 @@ def test_clear_requests(self): self.assertFalse(requests) self.assertFalse(glob.glob(os.path.join(self.base_dir, '.seleniumwire', 'storage-*', '*'))) + def test_clear_request_by_id(self): + request_1 = self._create_request() + request_2 = self._create_request() + self.storage.save_request(request_1) + self.storage.save_request(request_2) + + self.storage.clear_request_by_id(request_2.id) + requests = self.storage.load_requests() + + self.assertEqual(len(requests), 1) # only request_2 should have been cleared + self.assertEqual(requests[0].id, request_1.id) + self.assertEqual(len(glob.glob(os.path.join(self.base_dir, '.seleniumwire', 'storage-*', '*'))), 1) + def test_get_home_dir(self): self.assertEqual(os.path.join(self.base_dir, '.seleniumwire'), self.storage.home_dir) @@ -490,6 +503,18 @@ def test_clear_requests(self): self.assertFalse(requests) + def test_clear_request_by_id(self): + request_1 = self._create_request() + request_2 = self._create_request() + self.storage.save_request(request_1) + self.storage.save_request(request_2) + + self.storage.clear_request_by_id(request_2.id) + requests = self.storage.load_requests() + + self.assertEqual(len(requests), 1) # only request_2 should have been cleared + self.assertEqual(requests[0].id, request_1.id) + def test_cleanup(self): request_1 = self._create_request() request_2 = self._create_request() From 2f45364e3f1eeabd2e98d51065e858ecb1ddf637 Mon Sep 17 00:00:00 2001 From: IMayBeABitShy Date: Thu, 12 Jan 2023 17:33:33 +0100 Subject: [PATCH 2/3] wait_for_request: expose check_response --- README.rst | 4 ++-- seleniumwire/inspect.py | 5 +++-- tests/seleniumwire/test_inspect.py | 10 +++++++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 5016624..ed7f04e 100644 --- a/README.rst +++ b/README.rst @@ -230,8 +230,8 @@ Selenium Wire captures all HTTP/HTTPS traffic made by the browser [1]_. The foll ``driver.last_request`` Convenience attribute for retrieving the most recently captured request. This is more efficient than using ``driver.requests[-1]``. -``driver.wait_for_request(pat, timeout=10)`` - This method will wait until it sees a request matching a pattern. The ``pat`` attribute will be matched within the request URL. ``pat`` can be a simple substring or a regular expression. Note that ``driver.wait_for_request()`` doesn't *make* a request, it just *waits* for a previous request made by some other action and it will return the first request it finds. Also note that since ``pat`` can be a regular expression, you must escape special characters such as question marks with a slash. A ``TimeoutException`` is raised if no match is found within the timeout period. +``driver.wait_for_request(pat, timeout=10, check_response=False)`` + This method will wait until it sees a request matching a pattern. The ``pat`` attribute will be matched within the request URL. ``pat`` can be a simple substring or a regular expression. Note that ``driver.wait_for_request()`` doesn't *make* a request, it just *waits* for a previous request made by some other action and it will return the first request it finds. Also note that since ``pat`` can be a regular expression, you must escape special characters such as question marks with a slash. The ``check_response`` parameter controlls whether this method should wait until a request has received a response. A ``TimeoutException`` is raised if no match is found within the timeout period. For example, to wait for an AJAX request to return after a button is clicked: diff --git a/seleniumwire/inspect.py b/seleniumwire/inspect.py index 5ecb27f..c4a712d 100644 --- a/seleniumwire/inspect.py +++ b/seleniumwire/inspect.py @@ -48,7 +48,7 @@ def last_request(self) -> Optional[Request]: """ return self.backend.storage.load_last_request() - def wait_for_request(self, pat: str, timeout: Union[int, float] = 10) -> Request: + def wait_for_request(self, pat: str, timeout: Union[int, float] = 10, check_response: bool = False) -> Request: """Wait up to the timeout period for a request matching the specified pattern to be seen. @@ -63,6 +63,7 @@ def wait_for_request(self, pat: str, timeout: Union[int, float] = 10) -> Request Args: pat: The pat of the request to look for. A regex can be supplied. timeout: The maximum time to wait in seconds. Default 10s. + check_response: If nonzero, only match requests with a response. Returns: The request. @@ -73,7 +74,7 @@ def wait_for_request(self, pat: str, timeout: Union[int, float] = 10) -> Request start = time.time() while time.time() - start < timeout: - request = self.backend.storage.find(pat) + request = self.backend.storage.find(pat, check_response=check_response) if request is None: time.sleep(1 / 5) diff --git a/tests/seleniumwire/test_inspect.py b/tests/seleniumwire/test_inspect.py index dd351f0..29be399 100644 --- a/tests/seleniumwire/test_inspect.py +++ b/tests/seleniumwire/test_inspect.py @@ -64,7 +64,15 @@ def test_wait_for_request(self): request = self.driver.wait_for_request('/some/path') self.assertIsNotNone(request) - self.mock_backend.storage.find.assert_called_once_with('/some/path') + self.mock_backend.storage.find.assert_called_once_with('/some/path', check_response=False) + + def test_wait_for_request_with_response(self): + self.mock_backend.storage.find.return_value = Mock() + + request = self.driver.wait_for_request('/some/path', check_response=True) + + self.assertIsNotNone(request) + self.mock_backend.storage.find.assert_called_once_with('/some/path', check_response=True) def test_wait_for_request_timeout(self): self.mock_backend.storage.find.return_value = None From ae460fe12f0c56d6dc16acd7812c67a3f28d0b10 Mon Sep 17 00:00:00 2001 From: IMayBeABitShy Date: Thu, 12 Jan 2023 20:46:23 +0100 Subject: [PATCH 3/3] remove unused variable --- seleniumwire/storage.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/seleniumwire/storage.py b/seleniumwire/storage.py index 1ec89d4..bbe23d0 100644 --- a/seleniumwire/storage.py +++ b/seleniumwire/storage.py @@ -297,11 +297,10 @@ def clear_request_by_id(self, request_id: str) -> None: with self._lock: for i, indexed_request in enumerate(self._index): if indexed_request.id == request_id: - request_index = i break else: raise KeyError("Could not find any request with the specified id '{}'!".format(request_id)) - del self._index[i] + del self._index[i] # i will be index at break shutil.rmtree(self._get_request_dir(indexed_request.id), ignore_errors=True)