xlavm-lib 1.1.1__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.
@@ -0,0 +1,100 @@
1
+ Metadata-Version: 2.4
2
+ Name: xlavm-lib
3
+ Version: 1.1.1
4
+ Summary: Visual Testing and Get Ids utilities for Selenium & Appium
5
+ Author-email: Luis Angel Vanegas Martinez <angelvamart@hotmail.com>
6
+ License: MIT
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: pillow
10
+ Requires-Dist: opencv-python
11
+ Requires-Dist: easyocr
12
+ Requires-Dist: jinja2
13
+ Requires-Dist: numpy
14
+ Requires-Dist: lxml
15
+ Requires-Dist: imageio-ffmpeg
16
+ Requires-Dist: allure-pytest
17
+
18
+ # xlavm-lib
19
+
20
+ Librería orientada a pruebas automatizadas de Python (Pytest) con Selenium y Appium.
21
+
22
+ Esta libreria se enfoca en 3 partes fundamentales:
23
+
24
+ - Pruebas Visuales
25
+ - Obtencion de accessibility id o xpath (Sin necesidad de inspeccionar elementos)
26
+ - Reportes en Allure (Local y en Browserstack)
27
+
28
+ ## Instalación
29
+
30
+ ```bash
31
+ pip install xlavm-lib
32
+ ```
33
+
34
+ ## Visual Testing
35
+
36
+ 1. En conftest.py:
37
+
38
+ ```PY
39
+ @pytest.fixture
40
+ def driver():
41
+ driver.quit()
42
+ from xlavm_lib import VisualTestReport
43
+ VisualTestReport().exec()
44
+ ```
45
+
46
+ 2. En cualquier parte donde se quiera realizar una prueba visual:
47
+
48
+ ```PY
49
+ from xlavm_lib import VisualTest
50
+ VisualTest(self.driver, 'nombre_pagina').exec()
51
+ ```
52
+
53
+ ## Get Ids
54
+
55
+ 1. En cualquier parte donde se quiera obtener los elements ids de una pagina:
56
+
57
+ ```PY
58
+ from xlavm_lib import GetIds
59
+ GetIds(self.driver, 'nombre_pagina').exec()
60
+ ```
61
+
62
+ ## Allure Reports
63
+
64
+ 1. En conftest.py:
65
+
66
+ ```PY
67
+ @pytest.fixture
68
+ def driver(request):
69
+ request.node.report = AllureReport(driver)
70
+ yield driver
71
+
72
+ from xlavm_lib import AllureReport
73
+ # HOOKS
74
+ def pytest_runtest_call(item):
75
+ report = getattr(item, "report", None)
76
+ if report:
77
+ report.start_record_video()
78
+
79
+ def pytest_runtest_teardown(item):
80
+ report = getattr(item, "report", None)
81
+ if report:
82
+ report.stop_record_video()
83
+ ```
84
+
85
+ 2. En env.py:
86
+
87
+ ```PY
88
+ import os
89
+ os.environ["BROWSERSTACK_USERNAME"] = "tu_username"
90
+ os.environ["BROWSERSTACK_ACCESS_KEY"] = "tu_access_key"
91
+ ```
92
+
93
+ 3. En cualquier parte donde se quiera usar el reporte:
94
+
95
+ ```PY
96
+ from xlavm_lib import AllureReport
97
+ AllureReport(driver).step("1. Ingreso credenciales validas", request.node.name) #toma pantallazo por cada paso
98
+ AllureReport(driver).info("este es un mensaje infomativo")
99
+ AllureReport(driver).error("este es un mensaje de error")
100
+ ```
@@ -0,0 +1,83 @@
1
+ # xlavm-lib
2
+
3
+ Librería orientada a pruebas automatizadas de Python (Pytest) con Selenium y Appium.
4
+
5
+ Esta libreria se enfoca en 3 partes fundamentales:
6
+
7
+ - Pruebas Visuales
8
+ - Obtencion de accessibility id o xpath (Sin necesidad de inspeccionar elementos)
9
+ - Reportes en Allure (Local y en Browserstack)
10
+
11
+ ## Instalación
12
+
13
+ ```bash
14
+ pip install xlavm-lib
15
+ ```
16
+
17
+ ## Visual Testing
18
+
19
+ 1. En conftest.py:
20
+
21
+ ```PY
22
+ @pytest.fixture
23
+ def driver():
24
+ driver.quit()
25
+ from xlavm_lib import VisualTestReport
26
+ VisualTestReport().exec()
27
+ ```
28
+
29
+ 2. En cualquier parte donde se quiera realizar una prueba visual:
30
+
31
+ ```PY
32
+ from xlavm_lib import VisualTest
33
+ VisualTest(self.driver, 'nombre_pagina').exec()
34
+ ```
35
+
36
+ ## Get Ids
37
+
38
+ 1. En cualquier parte donde se quiera obtener los elements ids de una pagina:
39
+
40
+ ```PY
41
+ from xlavm_lib import GetIds
42
+ GetIds(self.driver, 'nombre_pagina').exec()
43
+ ```
44
+
45
+ ## Allure Reports
46
+
47
+ 1. En conftest.py:
48
+
49
+ ```PY
50
+ @pytest.fixture
51
+ def driver(request):
52
+ request.node.report = AllureReport(driver)
53
+ yield driver
54
+
55
+ from xlavm_lib import AllureReport
56
+ # HOOKS
57
+ def pytest_runtest_call(item):
58
+ report = getattr(item, "report", None)
59
+ if report:
60
+ report.start_record_video()
61
+
62
+ def pytest_runtest_teardown(item):
63
+ report = getattr(item, "report", None)
64
+ if report:
65
+ report.stop_record_video()
66
+ ```
67
+
68
+ 2. En env.py:
69
+
70
+ ```PY
71
+ import os
72
+ os.environ["BROWSERSTACK_USERNAME"] = "tu_username"
73
+ os.environ["BROWSERSTACK_ACCESS_KEY"] = "tu_access_key"
74
+ ```
75
+
76
+ 3. En cualquier parte donde se quiera usar el reporte:
77
+
78
+ ```PY
79
+ from xlavm_lib import AllureReport
80
+ AllureReport(driver).step("1. Ingreso credenciales validas", request.node.name) #toma pantallazo por cada paso
81
+ AllureReport(driver).info("este es un mensaje infomativo")
82
+ AllureReport(driver).error("este es un mensaje de error")
83
+ ```
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "xlavm-lib"
7
+ version = "1.1.1"
8
+ description = "Visual Testing and Get Ids utilities for Selenium & Appium"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ authors = [
12
+ { name="Luis Angel Vanegas Martinez", email="angelvamart@hotmail.com" }
13
+ ]
14
+ license = { text = "MIT" }
15
+ dependencies = [
16
+ "pillow",
17
+ "opencv-python",
18
+ "easyocr",
19
+ "jinja2",
20
+ "numpy",
21
+ "lxml",
22
+ "imageio-ffmpeg",
23
+ "allure-pytest"
24
+ ]
25
+
26
+ [tool.setuptools]
27
+ package-dir = {"" = "src"}
28
+
29
+ [tool.setuptools.packages.find]
30
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,13 @@
1
+ from xlavm_lib.visual_test.visual_test import VisualTest
2
+ from xlavm_lib.visual_test.visual_test_report import VisualTestReport
3
+ from xlavm_lib.get_ids.get_ids import GetIds
4
+ from xlavm_lib.allure_report.allure_report import AllureReport
5
+
6
+ __all__ = [
7
+ "VisualTest",
8
+ "VisualTestReport",
9
+ "GetIds",
10
+ "AllureReport"
11
+ ]
12
+
13
+ # Asi el usuario usa: from xlavm_lib import VisualTest, VisualTestReport, GetIds, AllureReport
@@ -0,0 +1,153 @@
1
+ import os
2
+ import allure
3
+ import requests
4
+ from xlavm_lib.allure_report.browserstack import Browserstack
5
+ from xlavm_lib.allure_report.video_recorder_mobile import VideoRecorderMobile
6
+ from xlavm_lib.allure_report.video_recorder_web import VideoRecorderWeb
7
+ import env
8
+ import random
9
+
10
+ class AllureReport():
11
+ """
12
+ Crea un reporte en Allure ya sea para ejecuciones locales o de Browserstack
13
+
14
+ Prerrequisitos:
15
+ En el conftest.py se crean estos hooks:
16
+ from xlavm_lib import AllureReport
17
+ # HOOKS
18
+ def pytest_runtest_call(item):
19
+ report = getattr(item, "report", None)
20
+ if report:
21
+ report.start_record_video()
22
+
23
+ def pytest_runtest_teardown(item):
24
+ report = getattr(item, "report", None)
25
+ if report:
26
+ report.stop_record_video()
27
+
28
+ En el conftest.py dentro de la funcion driver(request) y y antes del yield driver, se
29
+ debe ubicar:
30
+ request.node.report = AllureReport(driver)
31
+
32
+ Se debe crear el archivo env.py en la raiz del proyecto con los siguientes valores (si no usas Browserstack, dejalos vacio)
33
+ import os
34
+ os.environ["BROWSERSTACK_USERNAME"] = "tu_username"
35
+ os.environ["BROWSERSTACK_ACCESS_KEY"] = "tu_access_key"
36
+
37
+ Uso:
38
+ from xlavm_lib import AllureReport
39
+ AllureReport(driver).step("1. Ingreso credenciales validas", request.node.name) #toma pantallazo por cada paso
40
+ AllureReport(driver).info("este es un mensaje infomativo")
41
+ AllureReport(driver).error("este es un mensaje de error")
42
+
43
+ Args:
44
+ driver (driver): Driver web o mobile
45
+ """
46
+
47
+ video_path_web = f"videos/evidencia.mp4"
48
+ web_recorder = VideoRecorderWeb(video_path_web)
49
+
50
+ def __init__(self, driver):
51
+ self.driver = driver
52
+ self.mobile_recorder = None
53
+ # BrowserStack credentials (desde ENV)
54
+ self.BS_username = os.getenv("BROWSERSTACK_USERNAME")
55
+ self.BS_access_key = os.getenv("BROWSERSTACK_ACCESS_KEY")
56
+ self.BS_session_id = None
57
+ if not self.BS_username or not self.BS_access_key:
58
+ raise EnvironmentError("Variables de entorno BROWSERSTACK_USERNAME / BROWSERSTACK_ACCESS_KEY no definidas")
59
+
60
+ def info(self, description):
61
+ with allure.step(description):
62
+ pass
63
+ if not self.is_local_execution():
64
+ browserstack = Browserstack(self.driver)
65
+ browserstack.log_debug(description)
66
+
67
+ def error(self, description):
68
+ if not self.is_local_execution():
69
+ browserstack = Browserstack(self.driver)
70
+ browserstack.log_error(description)
71
+ with allure.step(description):
72
+ assert False, description
73
+
74
+ def step(self, description, TC):
75
+ """
76
+ - Use:
77
+ report.step(description, request.node.name)
78
+ """
79
+ SC_PATH = f'screenshots/evidences/{TC}/{description}.png'
80
+ with allure.step(description):
81
+ with allure.step("Evidencia"):
82
+ os.makedirs(os.path.dirname(SC_PATH), exist_ok=True)
83
+ self.driver.save_screenshot(SC_PATH)
84
+ with open(SC_PATH, "rb") as image_file:
85
+ allure.attach(image_file.read(), name="Evidencia", attachment_type=allure.attachment_type.PNG)
86
+
87
+ """
88
+ Metodo que se ejecuta antes del 'yield driver' para grabar evidencias
89
+ """
90
+ def start_record_video(self):
91
+ if self.is_local_execution():
92
+ if self.driver.platform in ["android", "ios"]:
93
+ self.mobile_recorder = VideoRecorderMobile(self.driver)
94
+ self.mobile_recorder.start()
95
+ else:
96
+ self.web_recorder.start()
97
+ else:
98
+ """Uso BS, entonces guardo el session id para obtener el video"""
99
+ self.BS_session_id = self.driver.session_id
100
+
101
+ """
102
+ Metodo que se ejecuta despues del 'driver.quit' para detener la grabacion y guardar evidencias
103
+ """
104
+ def stop_record_video(self):
105
+ if self.is_local_execution():
106
+ if self.driver.platform in ["android", "ios"]:
107
+ if self.mobile_recorder:
108
+ self.mobile_recorder.stop()
109
+ else:
110
+ self.web_recorder.stop()
111
+ if os.path.exists(self.video_path_web):
112
+ with open(self.video_path_web, "rb") as video:
113
+ allure.attach(
114
+ video.read(),
115
+ name="Video Evidencia de la Prueba",
116
+ attachment_type=allure.attachment_type.MP4
117
+ )
118
+ else:
119
+ video_url = self.get_browserstack_video_url(self.BS_session_id, self.BS_username, self.BS_access_key)
120
+ if video_url:
121
+ allure.attach(
122
+ video_url,
123
+ name="Video Evidencia de la Prueba en BrowserStack",
124
+ attachment_type=allure.attachment_type.URI_LIST
125
+ )
126
+ # tomo pantallazo despues de la grabacion
127
+ SC_PATH = f'screenshots/evidences/post_record/post-{random.random()}.png'
128
+ os.makedirs(os.path.dirname(SC_PATH), exist_ok=True)
129
+ self.driver.save_screenshot(SC_PATH)
130
+ with open(SC_PATH, "rb") as image_file:
131
+ allure.attach(
132
+ image_file.read(),
133
+ name="Screenshot Final",
134
+ attachment_type=allure.attachment_type.PNG
135
+ )
136
+
137
+ """
138
+ Este metodo verifica si la ejecucion es local o desde Browserstack
139
+ """
140
+ def is_local_execution(self):
141
+ return "hub-use.browserstack.com" not in str(self.driver.capabilities)
142
+
143
+ """
144
+ Este metodo obtiene la url del video que se graba en Browserstack
145
+ """
146
+ def get_browserstack_video_url(self, session_id, user, key):
147
+ if self.driver.platform in ["android", "ios"]:
148
+ url = f"https://api.browserstack.com/app-automate/sessions/{session_id}.json" # para mobile
149
+ else:
150
+ url = f"https://api.browserstack.com/automate/sessions/{session_id}.json" # para web
151
+ response = requests.get(url, auth=(user, key))
152
+ response.raise_for_status()
153
+ return response.json()["automation_session"]["video_url"]
@@ -0,0 +1,25 @@
1
+ class Browserstack():
2
+
3
+ def __init__(self, driver):
4
+ self.driver = driver
5
+
6
+ def log_debug(self, message):
7
+ self.driver.execute_script('browserstack_executor: {"action": "annotate", "arguments": {"data":"'+ message +'", "level": "debug"}}')
8
+
9
+ def log_error(self, message):
10
+ self.driver.execute_script('browserstack_executor: {"action": "annotate", "arguments": {"data":"'+ message +'", "level": "error"}}')
11
+
12
+ def log_info(self, message):
13
+ self.driver.execute_script('browserstack_executor: {"action": "annotate", "arguments": {"data":"'+ message +'", "level": "info"}}')
14
+
15
+ def network_offline(self):
16
+ self.driver.execute_script('browserstack_executor: {"action": "setNetworkProfile", "arguments": {"profile": "offline"}}')
17
+
18
+ def network_online(self):
19
+ self.driver.execute_script('browserstack_executor: {"action": "setNetworkProfile", "arguments": {"profile": "full"}}')
20
+
21
+ def mark_failed(self, message):
22
+ self.driver.execute_script('browserstack_executor: {"action": "setSessionStatus", "arguments": {"status": "failed", "reason": "'+ message +'"}}')
23
+
24
+ def mark_passed(self, message):
25
+ self.driver.execute_script('browserstack_executor: {"action": "setSessionStatus", "arguments": {"status": "passed", "reason": "'+ message +'"}}')
@@ -0,0 +1,55 @@
1
+ import time
2
+ import base64
3
+ import threading
4
+ import allure
5
+
6
+ class VideoRecorderMobile:
7
+ """
8
+ 170 porque el recording_screen del driver para mobiles graba solo 3 minutos, entonces
9
+ cuando este en 170, haga otra grabacion parte 2
10
+ """
11
+ def __init__(self, driver, max_seconds=170, check_interval=5):
12
+ self.driver = driver
13
+ self.max_seconds = max_seconds
14
+ self.check_interval = check_interval
15
+ self.segment = 1
16
+ self.last_start = None
17
+ self.running = False
18
+ self.thread = None
19
+
20
+ def start(self):
21
+ self.driver.start_recording_screen()
22
+ self.last_start = time.time()
23
+ self.running = True
24
+
25
+ self.thread = threading.Thread(
26
+ target=self._watchdog,
27
+ daemon=True
28
+ )
29
+ self.thread.start()
30
+
31
+ def _watchdog(self):
32
+ while self.running:
33
+ time.sleep(self.check_interval)
34
+ self.rotate_if_needed()
35
+
36
+ def rotate_if_needed(self):
37
+ elapsed = time.time() - self.last_start
38
+ if elapsed >= self.max_seconds:
39
+ self._stop_and_attach()
40
+ self.driver.start_recording_screen()
41
+ self.last_start = time.time()
42
+ self.segment += 1
43
+
44
+ def stop(self):
45
+ self.running = False
46
+ time.sleep(0.2) # deja cerrar el hilo
47
+ self._stop_and_attach()
48
+
49
+ def _stop_and_attach(self):
50
+ video = self.driver.stop_recording_screen()
51
+ allure.attach(
52
+ base64.b64decode(video),
53
+ name=f"Video Evidencia Parte {self.segment}",
54
+ attachment_type=allure.attachment_type.MP4
55
+ )
@@ -0,0 +1,44 @@
1
+ import subprocess
2
+ import os
3
+ import time
4
+ import imageio_ffmpeg
5
+
6
+ class VideoRecorderWeb:
7
+ def __init__(self, output_path):
8
+ self.output_path = output_path
9
+ self.process = None
10
+ self.ffmpeg = imageio_ffmpeg.get_ffmpeg_exe()
11
+
12
+ def start(self):
13
+ os.makedirs(os.path.dirname(self.output_path), exist_ok=True)
14
+ self.process = subprocess.Popen(
15
+ [
16
+ self.ffmpeg,
17
+ "-y",
18
+ "-f", "gdigrab",
19
+ "-framerate", "30",
20
+ "-video_size", "1920x1080",
21
+ "-i", "desktop",
22
+ "-vcodec", "libx264",
23
+ "-preset", "ultrafast",
24
+ "-pix_fmt", "yuv420p",
25
+ self.output_path
26
+ ],
27
+ stdin=subprocess.PIPE,
28
+ stdout=subprocess.DEVNULL,
29
+ stderr=subprocess.DEVNULL,
30
+ creationflags=subprocess.CREATE_NO_WINDOW
31
+ )
32
+ time.sleep(1) # deja que FFmpeg escriba headers
33
+
34
+ def stop(self):
35
+ if not self.process:
36
+ return
37
+ try:
38
+ # CIERRE CORRECTO
39
+ self.process.stdin.write(b"q\n")
40
+ self.process.stdin.flush()
41
+ self.process.wait(timeout=3) # esperar máximo 3s
42
+ except Exception:
43
+ # fallback si ffmpeg no responde
44
+ self.process.kill()