Selenium: Cómo generar un video del caso de prueba
Requerimientos
- Pillow: utilizada para "abrir" la imagen capturada por el controlador del navegador.
- OpenCV: encargada de generar el video
- Numpy: convierte la imagen generada por PIL a un array para luego pasársela a OpenCV
- Selenium: recrea el caso de uso como si fuese un usuario real de la plataforma y genera
python -m pip install --upgrade pillow opencv-python numpy selenium
Consideraciones
El video generado por el script no es 100% fluido, esto se debe a una limitación que tiene la API de captura de pantalla, que por algún motivo comienza a arrojar errores cuando se elimina el tiempo de espera
sleep(0.01)
en el métodorun()
del hilo. De igual manera, este tiempo de espera se utiliza con el propósito de no generar un sobreconsumo de los recursos de la CPU, sobre todo cuando se ejecuta a nivel de pipeline.Existen otros métodos para generar un video del caso de uso, como, por ejemplo, utilizando la librería mss. En este caso el archivo generado es mucho más fluido, pero el único inconveniente con el que nos podemos llegar a topar es que el sistema donde se está ejecutando la prueba debe tener un entorno de escritorio y, además, no funciona bajo el modo headless, ya que captura toda el área de trabajo visible.
Código
"""
Registra la evidencia de ejecución del caso de uso en un video
Requerimientos:
python -m pip install --upgrade pillow opencv-python numpy selenium
Ejecución:
python selenium.record.py
"""
from os import getcwd
from os import path
from io import BytesIO
from random import choice
from time import sleep
from uuid import uuid4
from threading import Event
from threading import Thread
import cv2
import numpy
from PIL import Image
from selenium.webdriver.common.by import By
from selenium.webdriver import Chrome
from selenium.webdriver.chrome.options import Options as ChromeOptions
from selenium.webdriver.chrome.webdriver import WebDriver as ChromeDriver
class RecordThread(Thread):
"""
Hilo encargado de grabar la pantalla mientras se ejecuta el caso de uso
"""
def __init__(self, event: Event, driver: ChromeDriver, *args, **kwargs) -> None:
"""Inicializa una nueva instancia del hilo
Args:
event (Event): objeto tipo `Event` utilizado para manipular la ejecución del hilo
driver (ChromeDriver): controlador del navegador
args (tuple): lista o tupla de argumentos
kwargs (dict): diccionario de argumentos de palabras clave
"""
super().__init__(*args, **kwargs)
self.event: Event = event
self.driver: ChromeDriver = driver
# Generar nombre del archivo
# [!] La carpeta "recordings" debe existir, de lo contrario no se creará el archivo
# ni aparecerá un error en la ejecución
filename: str = path.join(getcwd(), "recordings", uuid4().hex + ".mp4")
print(f"Output file: {filename}")
# Obtener la resolución del contenido visible del navegador
image_size: tuple[int, int] = Image.open(
BytesIO(self.driver.get_screenshot_as_png())
).size
self.output = cv2.VideoWriter(
filename=filename,
fourcc=cv2.VideoWriter_fourcc(*"mp4v"),
fps=5.0,
frameSize=image_size,
)
def run(self) -> None:
"""
Captura una imagen del contenido visible del navegador y la concatena al video.
"""
while True:
with Image.open(BytesIO(self.driver.get_screenshot_as_png())) as image:
image_array = numpy.array(image)
frame = cv2.cvtColor(image_array, cv2.COLOR_RGB2BGR)
self.output.write(frame)
# Romper el ciclo si el estado del evento cambió
if self.event.is_set():
break
sleep(0.01)
# Cerrar el escritor de video
self.output.release()
if __name__ == "__main__":
options: ChromeOptions = ChromeOptions()
options.add_argument("--start-maximized")
options.add_argument("--headless") # También funciona en modo headless
driver: Chrome = Chrome(options=options)
record_event: Event = Event()
record_thread: RecordThread = RecordThread(record_event, driver)
record_thread.start()
driver.get("https://blazedemo.com/")
driver.implicitly_wait(10)
select_from = driver.find_element(By.CSS_SELECTOR, "select[name='fromPort']")
select_to = driver.find_element(By.CSS_SELECTOR, "select[name='toPort']")
options_from = select_from.find_elements(By.TAG_NAME, "option")
options_to = select_to.find_elements(By.TAG_NAME, "option")
option_from = choice(options_from)
select_from.click()
option_from.click()
option_to = choice(options_to)
select_to.click()
option_to.click()
find_flights = driver.find_element(By.CSS_SELECTOR, "input[type='submit']")
find_flights.click()
sleep(1)
record_event.set()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128