From bb0fcafd9497bae3f3f81e011e56f5a5a5c0fad3 Mon Sep 17 00:00:00 2001 From: UltrafunkAmsterdam Date: Tue, 12 Mar 2024 09:56:18 +0100 Subject: [PATCH] 0.27rc4 --- nodriver/core/connection.py | 20 +++++---- nodriver/core/tab.py | 82 +++++++++++++++++++++---------------- pyproject.toml | 2 +- 3 files changed, 58 insertions(+), 46 deletions(-) diff --git a/nodriver/core/connection.py b/nodriver/core/connection.py index 9e1eb12..1ed1470 100644 --- a/nodriver/core/connection.py +++ b/nodriver/core/connection.py @@ -324,9 +324,8 @@ async def sleep(self, t: Union[int, float] = 0.25): async def wait(self, t: Union[int, float] = None): """ - waits until the event listener - reports idle (which is when no new events had been received in .5 seconds - or, 1 second when in interactive mode) + waits until the event listener reports idle (no new events received in certain timespan). + when `t` is provided, ensures waiting for `t` seconds, no matter what. :param t: :type t: @@ -334,20 +333,23 @@ async def wait(self, t: Union[int, float] = None): :rtype: """ await self.update_target() - + loop = asyncio.get_running_loop() + start_time = loop.time() try: - - await asyncio.wait_for(self.listener.idle.wait(), timeout=t) + if isinstance(t, (int, float)): + await asyncio.wait_for(self.listener.idle.wait(), timeout=t) + while (loop.time() - start_time ) < t: + await asyncio.sleep(.1) + else: + await self.listener.idle.wait() except asyncio.TimeoutError: - if t is not None: + if isinstance(t, (int, float)): # explicit time is given, which is now passed # so bail out early return except AttributeError: # no listener created yet pass - if t is not None: - await self.sleep(t) def __getattr__(self, item): """:meta private:""" diff --git a/nodriver/core/tab.py b/nodriver/core/tab.py index b989710..951ff78 100644 --- a/nodriver/core/tab.py +++ b/nodriver/core/tab.py @@ -195,7 +195,9 @@ async def find( :type timeout: float,int """ loop = asyncio.get_running_loop() - now = loop.time() + start_time = loop.time() + + text = text.strip() item = await self.find_element_by_text( text, best_match, return_enclosing_element @@ -205,7 +207,7 @@ async def find( item = await self.find_element_by_text( text, best_match, return_enclosing_element ) - if loop.time() - now > timeout: + if loop.time() - start_time > timeout: raise asyncio.TimeoutError( "time ran out while waiting for text: %s" % text ) @@ -229,13 +231,15 @@ async def select( """ loop = asyncio.get_running_loop() - now = loop.time() + start_time = loop.time() + selector = selector.strip() item = await self.query_selector(selector) + while not item: await self item = await self.query_selector(selector) - if loop.time() - now > timeout: + if loop.time() - start_time > timeout: raise asyncio.TimeoutError( "time ran out while waiting for %s" % selector ) @@ -260,8 +264,10 @@ async def find_all( loop = asyncio.get_running_loop() now = loop.time() - results = await self.find_elements_by_text(text) - while not results: + text = text.strip() + items = await self.find_elements_by_text(text) + + while not items: await self results = await self.find_elements_by_text(text) if loop.time() - now > timeout: @@ -269,7 +275,7 @@ async def find_all( "time ran out while waiting for text: %s" % text ) await self.sleep(0.5) - return results + return items async def select_all( self, @@ -288,16 +294,18 @@ async def select_all( loop = asyncio.get_running_loop() now = loop.time() - results = await self.query_selector_all(selector) - while not results: + selector = selector.strip() + + items = await self.query_selector_all(selector) + while not items: await self - results = await self.query_selector_all(selector) + items = await self.query_selector_all(selector) if loop.time() - now > timeout: raise asyncio.TimeoutError( "time ran out while waiting for %s" % selector ) await self.sleep(0.5) - return results + return items async def get( self, url="chrome://welcome", new_tab: bool = False, new_window: bool = False @@ -377,7 +385,7 @@ async def query_selector_all( raise if not node_ids: return [] - results = [] + items = [] for nid in node_ids: node = util.filter_recurse(doc, lambda n: n.node_id == nid) @@ -386,9 +394,9 @@ async def query_selector_all( if not node: continue elem = element.create(node, self, doc) - results.append(elem) + items.append(elem) - return results + return items async def query_selector( self, @@ -403,6 +411,7 @@ async def query_selector( :return: :rtype: """ + selector = selector.strip() if not _node: doc: cdp.dom.Node = await self.send(cdp.dom.get_document(-1, True)) @@ -455,7 +464,7 @@ async def find_elements_by_text( :return: :rtype: """ - + text = text.strip() doc = await self.send(cdp.dom.get_document(-1, True)) search_id, nresult = await self.send(cdp.dom.perform_search(text, True)) if nresult: @@ -467,7 +476,7 @@ async def find_elements_by_text( await self.send(cdp.dom.discard_search_results(search_id)) - results = [] + items = [] for nid in node_ids: node = util.filter_recurse(doc, lambda n: n.node_id == nid) if not node: @@ -489,13 +498,13 @@ async def find_elements_by_text( # check if parent actually has a parent and update it to be absolutely sure await elem.update() - results.append( + items.append( elem.parent or elem ) # when it really has no parent, use the text node itself continue else: # just add the element itself - results.append(elem) + items.append(elem) # since we already fetched the entire doc, including shadow and frames # let's also search through the iframes @@ -517,11 +526,11 @@ async def find_elements_by_text( element.create(text_node, self, iframe_elem.tree) for text_node in iframe_text_nodes ] - results.extend( + items.extend( text_node.parent for text_node in iframe_text_elems ) await self.send(cdp.dom.disable()) - return results or [] + return items or [] async def find_element_by_text( self, @@ -547,14 +556,14 @@ async def find_element_by_text( """ doc = await self.send(cdp.dom.get_document(-1, True)) search_id, nresult = await self.send(cdp.dom.perform_search(text, True)) - # if nresult == 0: - # return + text = text.strip() + node_ids = await self.send(cdp.dom.get_search_results(search_id, 0, nresult)) await self.send(cdp.dom.discard_search_results(search_id)) if not node_ids: node_ids = [] - results = [] + items = [] for nid in node_ids: node = util.filter_recurse(doc, lambda n: n.node_id == nid) try: @@ -570,13 +579,13 @@ async def find_element_by_text( # check if parent actually has a parent and update it to be absolutely sure await elem.update() - results.append( + items.append( elem.parent or elem ) # when it really has no parent, use the text node itself continue else: # just add the element itself - results.append(elem) + items.append(elem) # since we already fetched the entire doc, including shadow and frames # let's also search through the iframes @@ -597,20 +606,20 @@ async def find_element_by_text( element.create(text_node, self, iframe_elem.tree) for text_node in iframe_text_nodes ] - results.extend(text_node.parent for text_node in iframe_text_elems) + items.extend(text_node.parent for text_node in iframe_text_elems) try: - if not results: + if not items: return if best_match: closest_by_length = min( - results, key=lambda el: abs(len(text) - len(el.text_all)) + items, key=lambda el: abs(len(text) - len(el.text_all)) ) - elem = closest_by_length or results[0] + elem = closest_by_length or items[0] return elem else: # naively just return the first result - for elem in results: + for elem in items: if elem: return elem finally: @@ -947,7 +956,10 @@ async def set_window_state( self, left=0, top=0, width=1280, height=720, state="normal" ): """ - sets the window size and state. + sets the window size or state. + + for state you can provide the full name like minimized, maximized, normal, fullscreen, or + something which leads to either of those, like min, mini, mi, max, ma, maxi, full, fu, no, nor in case state is set other than "normal", the left, top, width, and height are ignored. :param left: @@ -995,11 +1007,9 @@ async def set_window_state( if window_state == cdp.browser.WindowState.NORMAL: bounds = cdp.browser.Bounds(left, top, width, height, window_state) else: - window_id, current_bounds = await self.get_window() - if current_bounds.window_state != cdp.browser.WindowState.NORMAL: - # min, max, full can only be used when current state == NORMAL - # therefore we first switch to NORMAL - await self.set_window_state(state="normal") + # min, max, full can only be used when current state == NORMAL + # therefore we first switch to NORMAL + await self.set_window_state(state="normal") bounds = cdp.browser.Bounds(window_state=window_state) await self.send(cdp.browser.set_window_bounds(window_id, bounds=bounds)) diff --git a/pyproject.toml b/pyproject.toml index f35874c..dd8325f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "nodriver" # Required -version = "0.27rc3" # Required +version = "0.27rc4" # Required description = """ * Official successor of Undetected Chromedriver * Can be made to work for for all chromium based browsers.