From 1c540eca3fee8e967c2ee298e26948e90ee36193 Mon Sep 17 00:00:00 2001 From: Franco M Date: Thu, 5 Sep 2024 10:27:25 -0300 Subject: [PATCH 1/6] Update element.py Example: - box_model: BoxModel(content=Quad([202, 679.234375, 710, 679.234375, 710, 700.703125, 202, 700.703125]), padding=Quad([189, 669.234375, 723, 669.234375, 723, 710.703125, 189, 710.703125]), border=Quad([188, 668.234375, 724, 668.234375, 724, 711.703125, 188, 711.703125]), margin=Quad([188, 668.234375, 724, 668.234375, 724, 711.703125, 188, 711.703125]), width=536, height=43, shape_outside=None) size {'height': 43, 'width': 536} location {'x': 202, 'y': 679.234375} rect {'top_left': {'x': 202, 'y': 679.234375}, 'bottom_right': {'x': 710, 'y': 700.703125}, 'width': 536, 'height': 43} - get_attribute: gameUserId3 - is_displayed: True - is_enabled: True - is_selected: None - is_clickable: True --- nodriver/core/element.py | 101 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/nodriver/core/element.py b/nodriver/core/element.py index 3ce1863..15d9b24 100644 --- a/nodriver/core/element.py +++ b/nodriver/core/element.py @@ -708,6 +708,18 @@ async def send_keys(self, text: str): await self._tab.send(cdp.input_.dispatch_key_event("char", text=char)) for char in list(text) ] + + async def write(self, text: str): + """ + write text to an input field, or any other html element. + + :param text: text to write + :return: None + """ + await self.apply("(elem) => elem.focus()") + [ + await self._tab.send(cdp.input_.insert_text(text=text)) + ] async def send_file(self, *file_paths: PathLike): """ @@ -798,6 +810,95 @@ def text_all(self): text_nodes = util.filter_recurse_all(self.node, lambda n: n.node_type == 3) return " ".join([n.node_value for n in text_nodes]) + async def box_model(self) -> cdp.dom.BoxModel: + """The box model of the element.""" + model_box = await self.tab.send(cdp.dom.get_box_model(backend_node_id=self.backend_node_id)) + return model_box + + + async def size(self) -> dict: + """The size of the element.""" + box_model = await self.box_model() + return {"height": box_model.height, "width": box_model.width} + + async def location(self) -> dict: + """The location of the element in the renderable canvas.""" + result = await self.box_model() + + # The box model is a list of 4 points, we need to find the top-left point + top_left_x = result.content[0] + top_left_y = result.content[1] + + # Return the result as a dictionary + return {"x": top_left_x, "y": top_left_y} + + async def rect(self) -> dict: + """A dictionary with the size and location of the element.""" + result = await self.box_model() + + # The box model is a list of 4 points, we need to find the top-left and bottom-right points + top_left_x = result.content[0] + top_left_y = result.content[1] + bottom_right_x = result.content[4] + bottom_right_y = result.content[5] + + # Return the result as a dictionary + return { + "top_left": {"x": top_left_x, "y": top_left_y}, + "bottom_right": {"x": bottom_right_x, "y": bottom_right_y}, + "width": result.width, + "height": result.height + } + + async def get_attribute(self, name: str) -> typing.Optional[str]: + """Gets the given attribute or property of the element. + + :param name: The name of the attribute or property to retrieve. + :return: The value of the attribute or property. + """ + return self.attrs.get(name) + + async def is_displayed(self): + """ + checks if the element is displayed on the page + + checks if the width and height are > 0 + :return: + :rtype: + """ + size = await self.size() + return not (size["height"] == 0 or size["width"] == 0) + + async def is_enabled(self): + """ + checks if the element is enabled + + checks if the element is not disabled + :return: + :rtype: + """ + return not await self.get_attribute("disabled") + + async def is_selected(self): + """ + checks if the element is selected + + checks if the element is selected + :return: + :rtype: + """ + return await self.get_attribute("selected") + + async def is_clickable(self): + """ + checks if the element is clickable + + checks if the element is displayed and enabled + :return: + :rtype: + """ + return await self.is_displayed() and await self.is_enabled() + async def query_selector_all(self, selector: str): """ like js querySelectorAll() From c01e2024c769a5b6b2bd778d88751c02f82aa88f Mon Sep 17 00:00:00 2001 From: Franco M Date: Thu, 5 Sep 2024 10:33:45 -0300 Subject: [PATCH 2/6] Update element.py --- nodriver/core/element.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nodriver/core/element.py b/nodriver/core/element.py index 15d9b24..bcb5223 100644 --- a/nodriver/core/element.py +++ b/nodriver/core/element.py @@ -877,7 +877,7 @@ async def is_enabled(self): :return: :rtype: """ - return not await self.get_attribute("disabled") + return False if await self.get_attribute("disabled") else True async def is_selected(self): """ @@ -887,7 +887,7 @@ async def is_selected(self): :return: :rtype: """ - return await self.get_attribute("selected") + return True if await self.get_attribute("checked") else False async def is_clickable(self): """ From 3950c2c2ac6e45cb0617a16c86ce13b4500a1e04 Mon Sep 17 00:00:00 2001 From: Franco M Date: Thu, 5 Sep 2024 10:35:12 -0300 Subject: [PATCH 3/6] Update element.py From 975a4b830128de2fe64504af784b81048f3254fb Mon Sep 17 00:00:00 2001 From: Franco M Date: Thu, 5 Sep 2024 14:11:46 +0000 Subject: [PATCH 4/6] Suggestions by @ilkecan --- nodriver/core/element.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nodriver/core/element.py b/nodriver/core/element.py index bcb5223..40f1c86 100644 --- a/nodriver/core/element.py +++ b/nodriver/core/element.py @@ -867,7 +867,7 @@ async def is_displayed(self): :rtype: """ size = await self.size() - return not (size["height"] == 0 or size["width"] == 0) + return (size["height"] > 0 and size["width"] > 0) async def is_enabled(self): """ @@ -877,7 +877,7 @@ async def is_enabled(self): :return: :rtype: """ - return False if await self.get_attribute("disabled") else True + return not bool(await self.get_attribute("disabled")) async def is_selected(self): """ @@ -887,7 +887,8 @@ async def is_selected(self): :return: :rtype: """ - return True if await self.get_attribute("checked") else False + return bool(await self.get_attribute("checked")) + async def is_clickable(self): """ From 257f1b6ea0de515c56ec661507f29a283acb7f48 Mon Sep 17 00:00:00 2001 From: Franco M Date: Thu, 5 Sep 2024 11:56:34 -0300 Subject: [PATCH 5/6] Update element.py --- nodriver/core/element.py | 41 +++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/nodriver/core/element.py b/nodriver/core/element.py index 40f1c86..63d0616 100644 --- a/nodriver/core/element.py +++ b/nodriver/core/element.py @@ -812,19 +812,35 @@ def text_all(self): async def box_model(self) -> cdp.dom.BoxModel: """The box model of the element.""" - model_box = await self.tab.send(cdp.dom.get_box_model(backend_node_id=self.backend_node_id)) + model_box = await self.tab.send(cdp.dom.get_box_model(backend_node_id=self.backend_node_id)) + if not model_box: + raise RuntimeError("could not get box model for %s" % self) return model_box - async def size(self) -> dict: """The size of the element.""" - box_model = await self.box_model() + try: + box_model = await self.box_model() + except Exception as e: + if 'could not get box model' in str(e): + logger.debug("could not get size for %s: %s", self, e) + return {"height": 0, "width": 0} + else: + raise e + return {"height": box_model.height, "width": box_model.width} async def location(self) -> dict: """The location of the element in the renderable canvas.""" - result = await self.box_model() - + try: + result = await self.box_model() + except Exception as e: + if 'could not get box model' in str(e): + logger.debug("could not get size for %s: %s", self, e) + return {"x": 0, "y": 0} + else: + raise e + # The box model is a list of 4 points, we need to find the top-left point top_left_x = result.content[0] top_left_y = result.content[1] @@ -834,8 +850,15 @@ async def location(self) -> dict: async def rect(self) -> dict: """A dictionary with the size and location of the element.""" - result = await self.box_model() - + try: + result = await self.box_model() + except Exception as e: + if 'could not get box model' in str(e): + logger.debug("could not get size for %s: %s", self, e) + return {"top_left": {"x": 0, "y": 0}, "bottom_right": {"x": 0, "y": 0}, "width": 0, "height": 0} + else: + raise e + # The box model is a list of 4 points, we need to find the top-left and bottom-right points top_left_x = result.content[0] top_left_y = result.content[1] @@ -877,6 +900,7 @@ async def is_enabled(self): :return: :rtype: """ + return not bool(await self.get_attribute("disabled")) async def is_selected(self): @@ -887,8 +911,7 @@ async def is_selected(self): :return: :rtype: """ - return bool(await self.get_attribute("checked")) - + return bool(await self.get_attribute("checked")) async def is_clickable(self): """ From 8bc0c49f933c1de7b0c0265cfb56d2300ef7c881 Mon Sep 17 00:00:00 2001 From: Franco M Date: Thu, 5 Sep 2024 12:11:06 -0300 Subject: [PATCH 6/6] Update element.py --- nodriver/core/element.py | 88 +++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/nodriver/core/element.py b/nodriver/core/element.py index 63d0616..e9b66a9 100644 --- a/nodriver/core/element.py +++ b/nodriver/core/element.py @@ -811,35 +811,35 @@ def text_all(self): return " ".join([n.node_value for n in text_nodes]) async def box_model(self) -> cdp.dom.BoxModel: - """The box model of the element.""" + """The box model of the element. + + :return: The box model of the element. + :rtype: cdp.dom.BoxModel, but can return None if the box model could not be retrieved. + """ + model_box = await self.tab.send(cdp.dom.get_box_model(backend_node_id=self.backend_node_id)) - if not model_box: - raise RuntimeError("could not get box model for %s" % self) return model_box - + async def size(self) -> dict: - """The size of the element.""" - try: - box_model = await self.box_model() - except Exception as e: - if 'could not get box model' in str(e): - logger.debug("could not get size for %s: %s", self, e) - return {"height": 0, "width": 0} - else: - raise e + """The size of the element. + + :return: The size of the element. + :rtype: dict + """ + box_model = await self.box_model() + return {"height": 0, "width": 0} if box_model is None else {"height": box_model.height, "width": box_model.width} - return {"height": box_model.height, "width": box_model.width} - async def location(self) -> dict: - """The location of the element in the renderable canvas.""" - try: - result = await self.box_model() - except Exception as e: - if 'could not get box model' in str(e): - logger.debug("could not get size for %s: %s", self, e) - return {"x": 0, "y": 0} - else: - raise e + """The location of the element in the renderable canvas. + + :return: The location of the element in the renderable canvas. + :rtype: dict + """ + result = await self.box_model() + + # Return 0, 0 if the result is None + if result is None: + return {"x": 0, "y": 0} # The box model is a list of 4 points, we need to find the top-left point top_left_x = result.content[0] @@ -849,15 +849,16 @@ async def location(self) -> dict: return {"x": top_left_x, "y": top_left_y} async def rect(self) -> dict: - """A dictionary with the size and location of the element.""" - try: - result = await self.box_model() - except Exception as e: - if 'could not get box model' in str(e): - logger.debug("could not get size for %s: %s", self, e) - return {"top_left": {"x": 0, "y": 0}, "bottom_right": {"x": 0, "y": 0}, "width": 0, "height": 0} - else: - raise e + """A dictionary with the size and location of the element. + + :return: A dictionary with the size and location of the element. + :rtype: dict + """ + result = await self.box_model() + + # Return 0, 0 if the result is None + if result is None: + return {"top_left": {"x": 0, "y": 0}, "bottom_right": {"x": 0, "y": 0}, "width": 0, "height": 0} # The box model is a list of 4 points, we need to find the top-left and bottom-right points top_left_x = result.content[0] @@ -886,8 +887,8 @@ async def is_displayed(self): checks if the element is displayed on the page checks if the width and height are > 0 - :return: - :rtype: + :return: True if the element is displayed, False otherwise. + :rtype: bool """ size = await self.size() return (size["height"] > 0 and size["width"] > 0) @@ -897,8 +898,9 @@ async def is_enabled(self): checks if the element is enabled checks if the element is not disabled - :return: - :rtype: + + :return: True if the element is enabled, False otherwise. + :rtype: bool """ return not bool(await self.get_attribute("disabled")) @@ -907,10 +909,12 @@ async def is_selected(self): """ checks if the element is selected - checks if the element is selected - :return: - :rtype: + checks if the element is checked + + :return: True if the element is selected, False otherwise. + :rtype: bool """ + return bool(await self.get_attribute("checked")) async def is_clickable(self): @@ -918,8 +922,8 @@ async def is_clickable(self): checks if the element is clickable checks if the element is displayed and enabled - :return: - :rtype: + :return: True if the element is clickable, False otherwise. + :rtype: bool """ return await self.is_displayed() and await self.is_enabled()