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.
- xlavm_lib-1.1.1/PKG-INFO +100 -0
- xlavm_lib-1.1.1/README.md +83 -0
- xlavm_lib-1.1.1/pyproject.toml +30 -0
- xlavm_lib-1.1.1/setup.cfg +4 -0
- xlavm_lib-1.1.1/src/xlavm_lib/__init__.py +13 -0
- xlavm_lib-1.1.1/src/xlavm_lib/allure_report/allure_report.py +153 -0
- xlavm_lib-1.1.1/src/xlavm_lib/allure_report/browserstack.py +25 -0
- xlavm_lib-1.1.1/src/xlavm_lib/allure_report/video_recorder_mobile.py +55 -0
- xlavm_lib-1.1.1/src/xlavm_lib/allure_report/video_recorder_web.py +44 -0
- xlavm_lib-1.1.1/src/xlavm_lib/get_ids/get_ids.py +508 -0
- xlavm_lib-1.1.1/src/xlavm_lib/visual_test/visual_test.py +372 -0
- xlavm_lib-1.1.1/src/xlavm_lib/visual_test/visual_test_report.py +409 -0
- xlavm_lib-1.1.1/src/xlavm_lib.egg-info/PKG-INFO +100 -0
- xlavm_lib-1.1.1/src/xlavm_lib.egg-info/SOURCES.txt +15 -0
- xlavm_lib-1.1.1/src/xlavm_lib.egg-info/dependency_links.txt +1 -0
- xlavm_lib-1.1.1/src/xlavm_lib.egg-info/requires.txt +8 -0
- xlavm_lib-1.1.1/src/xlavm_lib.egg-info/top_level.txt +1 -0
xlavm_lib-1.1.1/PKG-INFO
ADDED
|
@@ -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,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()
|