zelium 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zelium-0.1.0/MANIFEST.in +2 -0
- zelium-0.1.0/PKG-INFO +51 -0
- zelium-0.1.0/README.md +39 -0
- zelium-0.1.0/pyproject.toml +29 -0
- zelium-0.1.0/setup.cfg +4 -0
- zelium-0.1.0/src/zelium/__init__.py +55 -0
- zelium-0.1.0/src/zelium/alarm.py +51 -0
- zelium-0.1.0/src/zelium/config.py +98 -0
- zelium-0.1.0/src/zelium/helpers.py +26 -0
- zelium-0.1.0/src/zelium/js.py +115 -0
- zelium-0.1.0/src/zelium/tools.py +10 -0
- zelium-0.1.0/src/zelium/xpath.py +149 -0
- zelium-0.1.0/src/zelium.egg-info/PKG-INFO +51 -0
- zelium-0.1.0/src/zelium.egg-info/SOURCES.txt +16 -0
- zelium-0.1.0/src/zelium.egg-info/dependency_links.txt +1 -0
- zelium-0.1.0/src/zelium.egg-info/requires.txt +1 -0
- zelium-0.1.0/src/zelium.egg-info/top_level.txt +1 -0
- zelium-0.1.0/tests/test.py +21 -0
zelium-0.1.0/MANIFEST.in
ADDED
zelium-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zelium
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Intelligent abstraction layer over Selenium
|
|
5
|
+
Author-email: Iván <yupivangh@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yup-Ivan/Zelium
|
|
8
|
+
Project-URL: Repository, https://github.com/yup-Ivan/Zelium
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: selenium>=4.0
|
|
12
|
+
|
|
13
|
+
# ZELIUM
|
|
14
|
+
|
|
15
|
+
**ZELIUM** is a web automation framework based on **Selenium**, written in **Python**, designed to **simplify, standardize, and accelerate** the creation of web automation scripts.
|
|
16
|
+
|
|
17
|
+
It is designed to:
|
|
18
|
+
- Reduce repetitive code
|
|
19
|
+
- Centralize common patterns (waits, scrolls, JS fallbacks…)
|
|
20
|
+
- Provide a clear and expressive API (in Spanish and English)
|
|
21
|
+
- Make maintenance of complex automation scripts easier
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 👤 Author
|
|
26
|
+
|
|
27
|
+
- 💻 GitHub: https://github.com/yup-Ivan
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 🎯 Main Goals
|
|
32
|
+
|
|
33
|
+
- 🧠 Intelligent abstraction over Selenium
|
|
34
|
+
- 🔁 Reuse of common logic via helpers
|
|
35
|
+
- 🛡️ Robustness against dynamic elements (React, Vue, etc.)
|
|
36
|
+
- 🧩 Clean, readable, and consistent API
|
|
37
|
+
- 🌍 Multilanguage support (aliases in Spanish / English)
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 📁 Project Structure
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
Zelium/
|
|
45
|
+
├── __init__.py # Public API of the framework (exports and aliases)
|
|
46
|
+
├── alarm.py # Handling browser alerts, confirms, and prompts
|
|
47
|
+
├── config.py # Global initialization and configuration (driver, options…)
|
|
48
|
+
├── helpers.py # Internal helpers (wait, find, scroll, js_click, etc.)
|
|
49
|
+
├── js.py # JavaScript utilities (scroll, set_value, remove readonly…)
|
|
50
|
+
├── tools.py # Reusable helper functions
|
|
51
|
+
└── xpath.py # XPath element actions (click, send_keys, select…)
|
zelium-0.1.0/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# ZELIUM
|
|
2
|
+
|
|
3
|
+
**ZELIUM** is a web automation framework based on **Selenium**, written in **Python**, designed to **simplify, standardize, and accelerate** the creation of web automation scripts.
|
|
4
|
+
|
|
5
|
+
It is designed to:
|
|
6
|
+
- Reduce repetitive code
|
|
7
|
+
- Centralize common patterns (waits, scrolls, JS fallbacks…)
|
|
8
|
+
- Provide a clear and expressive API (in Spanish and English)
|
|
9
|
+
- Make maintenance of complex automation scripts easier
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 👤 Author
|
|
14
|
+
|
|
15
|
+
- 💻 GitHub: https://github.com/yup-Ivan
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 🎯 Main Goals
|
|
20
|
+
|
|
21
|
+
- 🧠 Intelligent abstraction over Selenium
|
|
22
|
+
- 🔁 Reuse of common logic via helpers
|
|
23
|
+
- 🛡️ Robustness against dynamic elements (React, Vue, etc.)
|
|
24
|
+
- 🧩 Clean, readable, and consistent API
|
|
25
|
+
- 🌍 Multilanguage support (aliases in Spanish / English)
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 📁 Project Structure
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
Zelium/
|
|
33
|
+
├── __init__.py # Public API of the framework (exports and aliases)
|
|
34
|
+
├── alarm.py # Handling browser alerts, confirms, and prompts
|
|
35
|
+
├── config.py # Global initialization and configuration (driver, options…)
|
|
36
|
+
├── helpers.py # Internal helpers (wait, find, scroll, js_click, etc.)
|
|
37
|
+
├── js.py # JavaScript utilities (scroll, set_value, remove readonly…)
|
|
38
|
+
├── tools.py # Reusable helper functions
|
|
39
|
+
└── xpath.py # XPath element actions (click, send_keys, select…)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "zelium"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Intelligent abstraction layer over Selenium"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Iván", email = "yupivangh@gmail.com" }
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
dependencies = [
|
|
18
|
+
"selenium>=4.0"
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://github.com/yup-Ivan/Zelium"
|
|
23
|
+
Repository = "https://github.com/yup-Ivan/Zelium"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools]
|
|
26
|
+
package-dir = {"" = "src"}
|
|
27
|
+
|
|
28
|
+
[tool.setuptools.packages.find]
|
|
29
|
+
where = ["src"]
|
zelium-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Zelium/__init__.py
|
|
2
|
+
from .js import JS
|
|
3
|
+
from .alarm import Alarm
|
|
4
|
+
from .tools import open
|
|
5
|
+
from .config import start
|
|
6
|
+
from .xpath import (
|
|
7
|
+
exist, get_text, click, send_keys,
|
|
8
|
+
select, force_select_combobox, clear,
|
|
9
|
+
delDisable,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__version__ = "0.1.0"
|
|
13
|
+
|
|
14
|
+
# ─────────────────────────────
|
|
15
|
+
# Alias (multi-idioma / semánticos)
|
|
16
|
+
# ─────────────────────────────
|
|
17
|
+
JavaScript = JS
|
|
18
|
+
Alarma = Alarm
|
|
19
|
+
|
|
20
|
+
empezar = start
|
|
21
|
+
abrir = open
|
|
22
|
+
|
|
23
|
+
existe = exist
|
|
24
|
+
obtener_texto = get_text
|
|
25
|
+
pulsar = click
|
|
26
|
+
enviar = send_keys
|
|
27
|
+
seleccionar = select
|
|
28
|
+
forzar_combobox = force_select_combobox
|
|
29
|
+
limpiar = clear
|
|
30
|
+
quitar_disable = delDisable
|
|
31
|
+
|
|
32
|
+
# ─────────────────────────────
|
|
33
|
+
# API
|
|
34
|
+
# ─────────────────────────────
|
|
35
|
+
__all__ = [
|
|
36
|
+
"JS", "JavaScript",
|
|
37
|
+
"Alarm", "Alarma",
|
|
38
|
+
"start", "empezar",
|
|
39
|
+
"open", "abrir",
|
|
40
|
+
"exist", "existe",
|
|
41
|
+
"get_text", "obtener_texto",
|
|
42
|
+
"click", "pulsar",
|
|
43
|
+
"send_keys", "enviar",
|
|
44
|
+
"select", "seleccionar",
|
|
45
|
+
"force_select_combobox", "forzar_combobox",
|
|
46
|
+
"clear", "limpiar",
|
|
47
|
+
"noDisable", "quitar_disable",
|
|
48
|
+
"alarm", "js",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
# ─────────────────────────────
|
|
52
|
+
# Instancias
|
|
53
|
+
# ─────────────────────────────
|
|
54
|
+
alarm = Alarm
|
|
55
|
+
js = JS
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Zelium/alarm.py
|
|
2
|
+
from selenium.common.exceptions import NoAlertPresentException
|
|
3
|
+
from selenium.webdriver.remote.webdriver import WebDriver
|
|
4
|
+
|
|
5
|
+
class Alarm:
|
|
6
|
+
_driver: WebDriver = None
|
|
7
|
+
|
|
8
|
+
@classmethod
|
|
9
|
+
def set_driver(cls, driver: WebDriver):
|
|
10
|
+
cls._driver = driver
|
|
11
|
+
|
|
12
|
+
@classmethod
|
|
13
|
+
def _get_alert(cls):
|
|
14
|
+
if cls._driver is None:
|
|
15
|
+
raise RuntimeError("Driver no asignado. Usa Zelium.alarm.set_driver(driver)")
|
|
16
|
+
try:
|
|
17
|
+
return cls._driver.switch_to.alert
|
|
18
|
+
except NoAlertPresentException:
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def accept(cls):
|
|
23
|
+
alert = cls._get_alert()
|
|
24
|
+
if alert:
|
|
25
|
+
alert.accept()
|
|
26
|
+
return True
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def deny(cls):
|
|
31
|
+
alert = cls._get_alert()
|
|
32
|
+
if alert:
|
|
33
|
+
alert.dismiss()
|
|
34
|
+
return True
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def delete(cls):
|
|
39
|
+
alert = cls._get_alert()
|
|
40
|
+
if alert:
|
|
41
|
+
text = alert.text
|
|
42
|
+
alert.accept()
|
|
43
|
+
return text
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def text(cls):
|
|
48
|
+
alert = cls._get_alert()
|
|
49
|
+
if alert:
|
|
50
|
+
return alert.text
|
|
51
|
+
return None
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Zelium/config.py
|
|
2
|
+
import os
|
|
3
|
+
from seleniumwire import webdriver as sw_webdriver
|
|
4
|
+
from selenium import webdriver as selenium_normal
|
|
5
|
+
from selenium.webdriver.chrome.options import Options
|
|
6
|
+
|
|
7
|
+
from .alarm import Alarm
|
|
8
|
+
from .js import JS
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def start(modo="normal", proxy=None, headless=False, usar_seleniumwire=True, perfil=None,):
|
|
12
|
+
"""
|
|
13
|
+
Inicia un navegador Chrome con distintas opciones.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
modo (str): "normal", "limpio" o "extension"
|
|
17
|
+
proxy (str | None): proxy en formato host:puerto
|
|
18
|
+
headless (bool): ejecutar Chrome en modo headless
|
|
19
|
+
usar_seleniumwire (bool): usar Selenium Wire (necesario si hay proxy)
|
|
20
|
+
perfil (str | None): ruta a perfil de Chrome (modo extension)
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
webdriver.Chrome: instancia del driver
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
chrome_options = Options()
|
|
27
|
+
chrome_options.add_argument("--start-maximized")
|
|
28
|
+
chrome_options.add_argument("--disable-notifications")
|
|
29
|
+
chrome_options.add_argument("--ignore-certificate-errors")
|
|
30
|
+
chrome_options.add_argument("--disable-infobars")
|
|
31
|
+
chrome_options.add_argument("--log-level=3")
|
|
32
|
+
chrome_options.add_argument("--remote-debugging-port=0")
|
|
33
|
+
chrome_options.add_argument("--disable-extensions")
|
|
34
|
+
chrome_options.add_argument("--blink-settings=imagesEnabled=false")
|
|
35
|
+
chrome_options.add_experimental_option(
|
|
36
|
+
"excludeSwitches", ["enable-logging"]
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
chrome_options.add_argument(
|
|
40
|
+
"--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
|
41
|
+
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
|
42
|
+
"Chrome/115.0.0.0 Safari/537.36"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if headless:
|
|
46
|
+
chrome_options.add_argument("--headless=new")
|
|
47
|
+
|
|
48
|
+
# ─────────────────────────────
|
|
49
|
+
# Modo de ejecución
|
|
50
|
+
# ─────────────────────────────
|
|
51
|
+
if modo == "extension":
|
|
52
|
+
perfil_path = perfil or r"C:\SeleniumProfile"
|
|
53
|
+
os.makedirs(perfil_path, exist_ok=True)
|
|
54
|
+
chrome_options.add_argument(
|
|
55
|
+
f"--user-data-dir={os.path.abspath(perfil_path)}"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
elif modo == "limpio":
|
|
59
|
+
chrome_options.add_argument("--incognito")
|
|
60
|
+
|
|
61
|
+
# ─────────────────────────────
|
|
62
|
+
# Proxy
|
|
63
|
+
# ─────────────────────────────
|
|
64
|
+
seleniumwire_options = {}
|
|
65
|
+
|
|
66
|
+
if proxy:
|
|
67
|
+
if usar_seleniumwire:
|
|
68
|
+
seleniumwire_options = {
|
|
69
|
+
"proxy": {
|
|
70
|
+
"http": f"http://{proxy}",
|
|
71
|
+
"https": f"http://{proxy}",
|
|
72
|
+
"no_proxy": "localhost,127.0.0.1",
|
|
73
|
+
},
|
|
74
|
+
"verify_ssl": False,
|
|
75
|
+
}
|
|
76
|
+
else:
|
|
77
|
+
chrome_options.add_argument(
|
|
78
|
+
f"--proxy-server=http://{proxy}"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# ─────────────────────────────
|
|
82
|
+
# Inicializar driver
|
|
83
|
+
# ─────────────────────────────
|
|
84
|
+
if usar_seleniumwire and proxy:
|
|
85
|
+
driver = sw_webdriver.Chrome(
|
|
86
|
+
options=chrome_options,
|
|
87
|
+
seleniumwire_options=seleniumwire_options,
|
|
88
|
+
)
|
|
89
|
+
else:
|
|
90
|
+
driver = selenium_normal.Chrome(options=chrome_options)
|
|
91
|
+
|
|
92
|
+
# ─────────────────────────────
|
|
93
|
+
# Inyectar driver en módulos
|
|
94
|
+
# ─────────────────────────────
|
|
95
|
+
Alarm.set_driver(driver)
|
|
96
|
+
JS.set_driver(driver)
|
|
97
|
+
|
|
98
|
+
return driver
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Zelium/helpers.py
|
|
2
|
+
from selenium.webdriver.common.by import By
|
|
3
|
+
from selenium.webdriver.support.ui import WebDriverWait
|
|
4
|
+
from selenium.webdriver.support import expected_conditions as EC
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def wait(driver, condition, timeout=5):
|
|
8
|
+
return WebDriverWait(driver, timeout).until(condition)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def find(xpath, driver, timeout=5, visible=False):
|
|
12
|
+
condition = (
|
|
13
|
+
EC.visibility_of_element_located
|
|
14
|
+
if visible else EC.presence_of_element_located
|
|
15
|
+
)
|
|
16
|
+
return wait(driver, condition((By.XPATH, xpath)), timeout)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def scroll(elem, driver):
|
|
20
|
+
driver.execute_script(
|
|
21
|
+
"arguments[0].scrollIntoView({block:'center'});", elem
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def js_click(elem, driver):
|
|
26
|
+
driver.execute_script("arguments[0].click();", elem)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Zelium/js.py
|
|
2
|
+
from selenium.common.exceptions import JavascriptException, NoSuchElementException
|
|
3
|
+
from selenium.webdriver.remote.webdriver import WebDriver
|
|
4
|
+
from selenium.webdriver.common.by import By
|
|
5
|
+
from selenium.webdriver.support.ui import WebDriverWait
|
|
6
|
+
from selenium.webdriver.support import expected_conditions as EC
|
|
7
|
+
from selenium.common.exceptions import JavascriptException, TimeoutException
|
|
8
|
+
|
|
9
|
+
class JS:
|
|
10
|
+
_driver: WebDriver = None
|
|
11
|
+
|
|
12
|
+
@classmethod
|
|
13
|
+
def set_driver(cls, driver: WebDriver):
|
|
14
|
+
cls._driver = driver
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def execute(cls, script: str, *args):
|
|
18
|
+
if cls._driver is None:
|
|
19
|
+
raise RuntimeError("Driver no asignado. Usa JS.set_driver(driver)")
|
|
20
|
+
try:
|
|
21
|
+
return cls._driver.execute_script(script, *args)
|
|
22
|
+
except JavascriptException as e:
|
|
23
|
+
print(f"[Error JS] {e}")
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
# ─────────────────────────────
|
|
27
|
+
# Sub funciones predeterminadas
|
|
28
|
+
# ─────────────────────────────
|
|
29
|
+
@classmethod
|
|
30
|
+
def quitar_readonly(cls, xpath: str):
|
|
31
|
+
if cls._driver is None:
|
|
32
|
+
raise RuntimeError("Driver no asignado. Usa JS.set_driver(driver)")
|
|
33
|
+
try:
|
|
34
|
+
elemento = cls._driver.find_element(By.XPATH, xpath)
|
|
35
|
+
cls._driver.execute_script("arguments[0].removeAttribute('readonly')", elemento)
|
|
36
|
+
except NoSuchElementException:
|
|
37
|
+
print(f"[Advertencia] No se encontró el elemento con XPath: {xpath}")
|
|
38
|
+
except JavascriptException as e:
|
|
39
|
+
print(f"[Error] No se pudo eliminar el atributo 'readonly': {e}")
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def set_value(cls, xpath: str, value):
|
|
43
|
+
if cls._driver is None:
|
|
44
|
+
raise RuntimeError("Driver no asignado. Usa JS.set_driver(driver)")
|
|
45
|
+
try:
|
|
46
|
+
elemento = cls._driver.find_element(By.XPATH, xpath)
|
|
47
|
+
cls._driver.execute_script("arguments[0].value = arguments[1];", elemento, value)
|
|
48
|
+
except NoSuchElementException:
|
|
49
|
+
print(f"[Advertencia] No se encontró el elemento con XPath: {xpath}")
|
|
50
|
+
except JavascriptException as e:
|
|
51
|
+
print(f"[Error] No se pudo establecer el valor: {e}")
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def establecer_fecha(cls, xpath: str, valor: str):
|
|
55
|
+
if cls._driver is None:
|
|
56
|
+
raise RuntimeError("Driver no asignado. Usa JS.set_driver(driver)")
|
|
57
|
+
try:
|
|
58
|
+
elemento = cls._driver.find_element(By.XPATH, xpath)
|
|
59
|
+
cls._driver.execute_script("arguments[0].removeAttribute('readonly')", elemento)
|
|
60
|
+
cls._driver.execute_script("arguments[0].value = arguments[1];", elemento, valor)
|
|
61
|
+
cls._driver.execute_script(
|
|
62
|
+
"arguments[0].dispatchEvent(new Event('change', { bubbles: true }));", elemento
|
|
63
|
+
)
|
|
64
|
+
except NoSuchElementException:
|
|
65
|
+
print(f"[Advertencia] No se encontró el elemento con XPath: {xpath}")
|
|
66
|
+
except JavascriptException as e:
|
|
67
|
+
print(f"[Error] No se pudo establecer la fecha: {e}")
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def agregar_clase_valid(cls, xpath=None, xpaths=None, timeout=5):
|
|
71
|
+
if cls._driver is None:
|
|
72
|
+
raise RuntimeError("Driver no asignado. Usa JS.set_driver(driver)")
|
|
73
|
+
|
|
74
|
+
if not xpaths and not xpath:
|
|
75
|
+
print("[Error] agregar_clase_valid: No se proporcionó ni 'xpath' ni 'xpaths'.")
|
|
76
|
+
return 0
|
|
77
|
+
|
|
78
|
+
if xpaths is None:
|
|
79
|
+
xpaths = [xpath]
|
|
80
|
+
|
|
81
|
+
if not isinstance(xpaths, (list, tuple)):
|
|
82
|
+
print(f"[Error] agregar_clase_valid: 'xpaths' no es iterable ({type(xpaths)})")
|
|
83
|
+
return 0
|
|
84
|
+
|
|
85
|
+
modificados = 0
|
|
86
|
+
js = "if (arguments[0]) { arguments[0].classList.add('valid'); return true; } return false;"
|
|
87
|
+
|
|
88
|
+
for xp in xpaths:
|
|
89
|
+
try:
|
|
90
|
+
elemento = WebDriverWait(cls._driver, timeout).until(
|
|
91
|
+
EC.presence_of_element_located((By.XPATH, xp))
|
|
92
|
+
)
|
|
93
|
+
result = cls._driver.execute_script(js, elemento)
|
|
94
|
+
if result:
|
|
95
|
+
modificados += 1
|
|
96
|
+
except TimeoutException:
|
|
97
|
+
print(f"[Advertencia] No se encontró el elemento con XPath (timeout {timeout}s): {xp}")
|
|
98
|
+
except JavascriptException as e:
|
|
99
|
+
print(f"[Error JS en {xp}] {e}")
|
|
100
|
+
except Exception as e:
|
|
101
|
+
print(f"[Error inesperado en {xp}] {type(e).__name__}: {e}")
|
|
102
|
+
|
|
103
|
+
return modificados
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def scroll(cls, valor):
|
|
107
|
+
if cls._driver is None:
|
|
108
|
+
raise RuntimeError("Driver no asignado. Usa JS.set_driver(driver)")
|
|
109
|
+
try:
|
|
110
|
+
cls._driver.execute_script(f"window.scrollBy(0, {valor});")
|
|
111
|
+
|
|
112
|
+
except Exception as e:
|
|
113
|
+
print(e)
|
|
114
|
+
except JavascriptException as e:
|
|
115
|
+
print(f"[Error JS] No se pudo hacer scroll: {e}")
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Zelium/tools.py
|
|
2
|
+
from selenium.common.exceptions import WebDriverException
|
|
3
|
+
|
|
4
|
+
def open(url, driver):
|
|
5
|
+
try:
|
|
6
|
+
driver.get(url)
|
|
7
|
+
except WebDriverException as e:
|
|
8
|
+
print(f"[WebDriverError] No se pudo abrir {url}: {e}")
|
|
9
|
+
except Exception as e:
|
|
10
|
+
print(f"[Error inesperado] No se pudo abrir {url}: {type(e).__name__}: {e}")
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from selenium.webdriver.common.by import By
|
|
2
|
+
from selenium.webdriver.support.ui import Select
|
|
3
|
+
from selenium.webdriver.support import expected_conditions as EC
|
|
4
|
+
from selenium.common.exceptions import (
|
|
5
|
+
JavascriptException,
|
|
6
|
+
TimeoutException,
|
|
7
|
+
ElementNotInteractableException,
|
|
8
|
+
)
|
|
9
|
+
from .helpers import wait, find, scroll, js_click
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def exist(xpath, driver, timeout=5):
|
|
14
|
+
try:
|
|
15
|
+
find(xpath, driver, timeout)
|
|
16
|
+
return True
|
|
17
|
+
except TimeoutException:
|
|
18
|
+
return False
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_text(xpath, driver, timeout=5):
|
|
22
|
+
try:
|
|
23
|
+
elem = find(xpath, driver, timeout)
|
|
24
|
+
return elem.text
|
|
25
|
+
except TimeoutException:
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def click(xpath, driver, timeout=5):
|
|
30
|
+
try:
|
|
31
|
+
elem = wait(
|
|
32
|
+
driver,
|
|
33
|
+
EC.element_to_be_clickable((By.XPATH, xpath)),
|
|
34
|
+
timeout,
|
|
35
|
+
)
|
|
36
|
+
scroll(elem, driver)
|
|
37
|
+
elem.click()
|
|
38
|
+
return True
|
|
39
|
+
|
|
40
|
+
except (TimeoutException, ElementNotInteractableException):
|
|
41
|
+
try:
|
|
42
|
+
elem = find(xpath, driver, timeout)
|
|
43
|
+
scroll(elem, driver)
|
|
44
|
+
js_click(elem, driver)
|
|
45
|
+
return True
|
|
46
|
+
except Exception:
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def send_keys(xpath, value, driver, timeout=5):
|
|
51
|
+
if value is None:
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
elem = find(xpath, driver, timeout, visible=True)
|
|
56
|
+
scroll(elem, driver)
|
|
57
|
+
elem.clear()
|
|
58
|
+
elem.send_keys(value)
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
except (TimeoutException, ElementNotInteractableException):
|
|
62
|
+
try:
|
|
63
|
+
elem = find(xpath, driver, timeout)
|
|
64
|
+
driver.execute_script(
|
|
65
|
+
"""
|
|
66
|
+
arguments[0].value = arguments[1];
|
|
67
|
+
arguments[0].dispatchEvent(new Event('input', {bubbles:true}));
|
|
68
|
+
arguments[0].dispatchEvent(new Event('change', {bubbles:true}));
|
|
69
|
+
""",
|
|
70
|
+
elem,
|
|
71
|
+
value,
|
|
72
|
+
)
|
|
73
|
+
return True
|
|
74
|
+
except Exception:
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def select(xpath, buscar, driver, attr="value", timeout=5):
|
|
79
|
+
try:
|
|
80
|
+
elem = find(xpath, driver, timeout)
|
|
81
|
+
scroll(elem, driver)
|
|
82
|
+
|
|
83
|
+
sel = Select(elem)
|
|
84
|
+
buscar = str(buscar)
|
|
85
|
+
|
|
86
|
+
if attr == "value":
|
|
87
|
+
sel.select_by_value(buscar)
|
|
88
|
+
elif attr == "text":
|
|
89
|
+
sel.select_by_visible_text(buscar)
|
|
90
|
+
else:
|
|
91
|
+
for option in sel.options:
|
|
92
|
+
if option.get_attribute(attr) == buscar:
|
|
93
|
+
option.click()
|
|
94
|
+
break
|
|
95
|
+
else:
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
return True
|
|
99
|
+
|
|
100
|
+
except Exception:
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def force_select_combobox(input_xpath, option_text, driver, timeout=5):
|
|
105
|
+
try:
|
|
106
|
+
input_elem = find(input_xpath, driver, timeout)
|
|
107
|
+
scroll(input_elem, driver)
|
|
108
|
+
input_elem.click()
|
|
109
|
+
|
|
110
|
+
option_xpath = (
|
|
111
|
+
f"//li[@role='option' and normalize-space(text())='{option_text}']"
|
|
112
|
+
)
|
|
113
|
+
option_elem = find(option_xpath, driver, timeout)
|
|
114
|
+
|
|
115
|
+
scroll(option_elem, driver)
|
|
116
|
+
js_click(option_elem, driver)
|
|
117
|
+
|
|
118
|
+
return True
|
|
119
|
+
|
|
120
|
+
except Exception:
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def clear(xpath, driver, timeout=5):
|
|
125
|
+
try:
|
|
126
|
+
elem = find(xpath, driver, timeout)
|
|
127
|
+
elem.clear()
|
|
128
|
+
return True
|
|
129
|
+
except Exception:
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def delDisable(xpath, driver, timeout=5):
|
|
134
|
+
try:
|
|
135
|
+
elem = find(xpath, driver, timeout)
|
|
136
|
+
driver.execute_script(
|
|
137
|
+
"""
|
|
138
|
+
arguments[0].removeAttribute('readonly');
|
|
139
|
+
arguments[0].removeAttribute('disabled');
|
|
140
|
+
try { arguments[0].readOnly = false; } catch(e) {}
|
|
141
|
+
""",
|
|
142
|
+
elem,
|
|
143
|
+
)
|
|
144
|
+
time.sleep(0.3)
|
|
145
|
+
elem.click()
|
|
146
|
+
return True
|
|
147
|
+
|
|
148
|
+
except (TimeoutException, JavascriptException):
|
|
149
|
+
return False
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zelium
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Intelligent abstraction layer over Selenium
|
|
5
|
+
Author-email: Iván <yupivangh@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yup-Ivan/Zelium
|
|
8
|
+
Project-URL: Repository, https://github.com/yup-Ivan/Zelium
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: selenium>=4.0
|
|
12
|
+
|
|
13
|
+
# ZELIUM
|
|
14
|
+
|
|
15
|
+
**ZELIUM** is a web automation framework based on **Selenium**, written in **Python**, designed to **simplify, standardize, and accelerate** the creation of web automation scripts.
|
|
16
|
+
|
|
17
|
+
It is designed to:
|
|
18
|
+
- Reduce repetitive code
|
|
19
|
+
- Centralize common patterns (waits, scrolls, JS fallbacks…)
|
|
20
|
+
- Provide a clear and expressive API (in Spanish and English)
|
|
21
|
+
- Make maintenance of complex automation scripts easier
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 👤 Author
|
|
26
|
+
|
|
27
|
+
- 💻 GitHub: https://github.com/yup-Ivan
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 🎯 Main Goals
|
|
32
|
+
|
|
33
|
+
- 🧠 Intelligent abstraction over Selenium
|
|
34
|
+
- 🔁 Reuse of common logic via helpers
|
|
35
|
+
- 🛡️ Robustness against dynamic elements (React, Vue, etc.)
|
|
36
|
+
- 🧩 Clean, readable, and consistent API
|
|
37
|
+
- 🌍 Multilanguage support (aliases in Spanish / English)
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 📁 Project Structure
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
Zelium/
|
|
45
|
+
├── __init__.py # Public API of the framework (exports and aliases)
|
|
46
|
+
├── alarm.py # Handling browser alerts, confirms, and prompts
|
|
47
|
+
├── config.py # Global initialization and configuration (driver, options…)
|
|
48
|
+
├── helpers.py # Internal helpers (wait, find, scroll, js_click, etc.)
|
|
49
|
+
├── js.py # JavaScript utilities (scroll, set_value, remove readonly…)
|
|
50
|
+
├── tools.py # Reusable helper functions
|
|
51
|
+
└── xpath.py # XPath element actions (click, send_keys, select…)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/zelium/__init__.py
|
|
5
|
+
src/zelium/alarm.py
|
|
6
|
+
src/zelium/config.py
|
|
7
|
+
src/zelium/helpers.py
|
|
8
|
+
src/zelium/js.py
|
|
9
|
+
src/zelium/tools.py
|
|
10
|
+
src/zelium/xpath.py
|
|
11
|
+
src/zelium.egg-info/PKG-INFO
|
|
12
|
+
src/zelium.egg-info/SOURCES.txt
|
|
13
|
+
src/zelium.egg-info/dependency_links.txt
|
|
14
|
+
src/zelium.egg-info/requires.txt
|
|
15
|
+
src/zelium.egg-info/top_level.txt
|
|
16
|
+
tests/test.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
selenium>=4.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
zelium
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import Zelium
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
driver = Zelium.start()
|
|
5
|
+
|
|
6
|
+
Zelium.open("http://127.0.0.1:5000", driver)
|
|
7
|
+
time.sleep(2)
|
|
8
|
+
|
|
9
|
+
Zelium.alarm.accept()
|
|
10
|
+
time.sleep(2)
|
|
11
|
+
|
|
12
|
+
Zelium.js.quitar_readonly("//input[@id='fecha']")
|
|
13
|
+
time.sleep(2)
|
|
14
|
+
|
|
15
|
+
Zelium.js.set_value("//input[@id='nombre']", "Iván")
|
|
16
|
+
|
|
17
|
+
Zelium.js.scroll(300)
|
|
18
|
+
|
|
19
|
+
time.sleep(5)
|
|
20
|
+
|
|
21
|
+
driver.quit()
|