190 lines
7.3 KiB
Python
190 lines
7.3 KiB
Python
from typing import TYPE_CHECKING, Optional
|
|
import random
|
|
import asyncio
|
|
import json
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
from .Tab import Tab
|
|
|
|
class ElementoWeb:
|
|
def __init__(self, tab: "Tab", object_id: Optional[str]):
|
|
self.tab = tab
|
|
self.object_id = object_id
|
|
self._node_id = None # Lazy resolved
|
|
|
|
@classmethod
|
|
def from_node(cls, tab: "Tab", node_id: int) -> "ElementoWeb":
|
|
inst = cls(tab, object_id=None)
|
|
inst._node_id = node_id
|
|
return inst
|
|
|
|
async def _asegurar_object_id(self):
|
|
if not self.object_id and self._node_id:
|
|
try:
|
|
resolved = await self.tab._enviar("DOM.resolveNode", {"nodeId": self._node_id})
|
|
self.object_id = resolved["object"]["objectId"]
|
|
except Exception as e:
|
|
print(f"⚠️ No se pudo resolver objectId desde nodeId: {e}")
|
|
|
|
async def scroll_into_view(self):
|
|
try:
|
|
await self._asegurar_object_id()
|
|
await self.tab._enviar("Runtime.callFunctionOn", {
|
|
"objectId": self.object_id,
|
|
"functionDeclaration": "function() { this.scrollIntoView({block: 'center'}); }",
|
|
"awaitPromise": True
|
|
})
|
|
if self.tab.verbose:
|
|
print("📜 Elemento desplazado a la vista.")
|
|
except Exception as e:
|
|
print(f"⚠️ Error al hacer scroll hacia el elemento: {e}")
|
|
|
|
async def click(self):
|
|
try:
|
|
await self.scroll_into_view()
|
|
await self._asegurar_object_id()
|
|
if not self.object_id:
|
|
raise ValueError("No se puede obtener objectId del elemento para hacer click.")
|
|
|
|
# Intenta obtener coordenadas del nodo
|
|
node_result = await self.tab._enviar("DOM.describeNode", {
|
|
"objectId": self.object_id
|
|
})
|
|
node_id = node_result["node"]["nodeId"]
|
|
|
|
try:
|
|
box_model = await self.tab._enviar("DOM.getBoxModel", {"nodeId": node_id})
|
|
content = box_model["model"]["content"]
|
|
x = (content[0] + content[2]) / 2
|
|
y = (content[1] + content[5]) / 2
|
|
except:
|
|
quads_result = await self.tab._enviar("DOM.getContentQuads", {"nodeId": node_id})
|
|
quad = quads_result["quads"][0]
|
|
x = (quad[0] + quad[4]) / 2
|
|
y = (quad[1] + quad[5]) / 2
|
|
|
|
# 🧠 Enfocar el elemento antes de clickear
|
|
await self.tab._enviar("DOM.focus", {
|
|
"objectId": self.object_id
|
|
})
|
|
|
|
# 🎯 Movimiento humanoide opcional
|
|
start_x, start_y = x + random.uniform(-100, 100), y + random.uniform(-100, 100)
|
|
steps = random.randint(5, 12)
|
|
for i in range(1, steps + 1):
|
|
curr_x = start_x + (x - start_x) * i / steps + random.uniform(-1, 1)
|
|
curr_y = start_y + (y - start_y) * i / steps + random.uniform(-1, 1)
|
|
await self.tab._enviar("Input.dispatchMouseEvent", {
|
|
"type": "mouseMoved",
|
|
"x": curr_x,
|
|
"y": curr_y,
|
|
})
|
|
await asyncio.sleep(random.uniform(0.01, 0.05))
|
|
|
|
# 👆 Mouse Down
|
|
await self.tab._enviar("Input.dispatchMouseEvent", {
|
|
"type": "mousePressed",
|
|
"x": x,
|
|
"y": y,
|
|
"button": "left",
|
|
"clickCount": 1
|
|
})
|
|
|
|
await asyncio.sleep(random.uniform(0.05, 0.15))
|
|
|
|
# 👇 Mouse Up
|
|
await self.tab._enviar("Input.dispatchMouseEvent", {
|
|
"type": "mouseReleased",
|
|
"x": x,
|
|
"y": y,
|
|
"button": "left",
|
|
"clickCount": 1
|
|
})
|
|
|
|
await asyncio.sleep(random.uniform(0.01, 0.05))
|
|
|
|
# 🖱️ Click manual adicional
|
|
await self.tab._enviar("Input.dispatchMouseEvent", {
|
|
"type": "mouseClicked",
|
|
"x": x,
|
|
"y": y,
|
|
"button": "left",
|
|
"clickCount": 1
|
|
})
|
|
|
|
if self.tab.verbose:
|
|
print(f"🖱️ Click humano simulado en ({x:.1f}, {y:.1f})")
|
|
|
|
except Exception as e:
|
|
print(f"⚠️ Error al hacer click físico: {e}")
|
|
print("🧪 Intentando fallback con JavaScript click()...")
|
|
await self.click_js()
|
|
|
|
async def click_js(self):
|
|
try:
|
|
await self._asegurar_object_id()
|
|
if not self.object_id:
|
|
print("⚠️ No se puede hacer click JS: objectId no disponible.")
|
|
return
|
|
await self.tab._enviar("Runtime.callFunctionOn", {
|
|
"objectId": self.object_id,
|
|
"functionDeclaration": "function() { this.click(); }",
|
|
"awaitPromise": True
|
|
})
|
|
if self.tab.verbose:
|
|
print("🖱️ Click simulado por JavaScript (element.click())")
|
|
except Exception as e:
|
|
print(f"⚠️ Error al ejecutar click en JS: {e}")
|
|
|
|
async def obtener_texto(self) -> Optional[str]:
|
|
try:
|
|
await self._asegurar_object_id()
|
|
result = await self.tab._enviar("Runtime.callFunctionOn", {
|
|
"objectId": self.object_id,
|
|
"functionDeclaration": "function() { return this.textContent; }",
|
|
"returnByValue": True
|
|
})
|
|
return result.get("result", {}).get("value")
|
|
except Exception as e:
|
|
print(f"⚠️ Error al obtener texto del elemento: {e}")
|
|
return None
|
|
|
|
async def escribir_texto(self, texto: str):
|
|
try:
|
|
await self._asegurar_object_id()
|
|
await self.tab._enviar("Runtime.callFunctionOn", {
|
|
"objectId": self.object_id,
|
|
"functionDeclaration": f"function() {{ this.value = {json.dumps(texto)}; this.dispatchEvent(new Event('input')); }}",
|
|
"awaitPromise": True
|
|
})
|
|
if self.tab.verbose:
|
|
print(f"⌨️ Texto escrito en elemento: '{texto}'")
|
|
except Exception as e:
|
|
print(f"⚠️ Error al escribir texto: {e}")
|
|
|
|
|
|
async def encontrar_hijo_clickeable(self) -> Optional["ElementoWeb"]:
|
|
try:
|
|
await self._asegurar_object_id()
|
|
resultado = await self.tab._enviar("Runtime.callFunctionOn", {
|
|
"objectId": self.object_id,
|
|
"functionDeclaration": """
|
|
function() {
|
|
const candidatos = this.querySelectorAll("span, div, a, button");
|
|
for (const el of candidatos) {
|
|
const style = window.getComputedStyle(el);
|
|
const visible = style.display !== "none" && style.visibility !== "hidden";
|
|
const interactivo = style.pointerEvents !== "none";
|
|
if (visible && interactivo) return el;
|
|
}
|
|
return this;
|
|
}
|
|
""",
|
|
"returnByValue": False
|
|
})
|
|
if "result" in resultado and "objectId" in resultado["result"]:
|
|
return ElementoWeb(self.tab, resultado["result"]["objectId"])
|
|
except Exception as e:
|
|
print(f"⚠️ No se pudo encontrar hijo clickeable: {e}")
|
|
return None |