Skip to content

Commit 0fbfc12

Browse files
committed
Initial commit
0 parents  commit 0fbfc12

8 files changed

Lines changed: 340 additions & 0 deletions

File tree

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Auto detect text files and perform LF normalization
2+
* text=auto

.project

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<projectDescription>
3+
<name>PyDesktopBrowserRecorder</name>
4+
<comment></comment>
5+
<projects>
6+
</projects>
7+
<buildSpec>
8+
<buildCommand>
9+
<name>org.python.pydev.PyDevBuilder</name>
10+
<arguments>
11+
</arguments>
12+
</buildCommand>
13+
</buildSpec>
14+
<natures>
15+
<nature>org.python.pydev.pythonNature</nature>
16+
</natures>
17+
</projectDescription>

.pydevproject

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+
<?eclipse-pydev version="1.0"?><pydev_project>
3+
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
4+
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python interpreter</pydev_property>
5+
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
6+
<path>/${PROJECT_DIR_NAME}/videoRecorder</path>
7+
</pydev_pathproperty>
8+
</pydev_project>

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# PyDesktopBrowserRecorder
2+
This project allows you to record your desktop or the browser during an automated test using selenium's webdriver
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'''
2+
Created on 29 feb. 2020
3+
4+
@author: ingov
5+
6+
This module allows you to check if you have installed all packages/modules needed to run this
7+
project.
8+
You can whether run this module or use the function created here.
9+
'''
10+
#We need to import pathlib to get the path of our current project
11+
import pathlib
12+
13+
def checkConfiguration():
14+
"""
15+
This function loads the file importedModules.txt where the modules needed to use this
16+
project are listed. We try to import them and if they are not installed we print a message.
17+
18+
Parameters:
19+
None
20+
21+
Returns:
22+
None
23+
"""
24+
#We build the path for the file where we listed the modules needed for this project
25+
configFile = str(pathlib.Path(__file__).parent.absolute())+"/importedModules.txt"
26+
#We open this file
27+
file = open(configFile)
28+
#We go through every line in the file because each module is listed in a new line
29+
for line in file.read().splitlines():
30+
try:
31+
#We try to import that module
32+
__import__(line)
33+
except ImportError:
34+
#If the module was not installed, we print a message indicating it and asking you
35+
#to install it
36+
print("Module {0} is not install".format(line))
37+
print("Try in command line: pip install {0}".format(line))
38+
39+
#We execute this method so you can run this module and get which modules you need
40+
checkConfiguration()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
imageio_ffmpeg
2+
imageio
3+
selenium
4+
pyautogui

videoRecorder/test/demo.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from videoRecorder import desktopBrowserRecorder
2+
from selenium import webdriver
3+
import time
4+
5+
import os
6+
dir_path = os.path.dirname(os.path.realpath(__file__))
7+
recorder = desktopBrowserRecorder.DesktopBrowserRecorder(dir_path,".mp4")
8+
recorder.startRecordingSession()
9+
driver = webdriver.Chrome()
10+
recorder2 = desktopBrowserRecorder.DesktopBrowserRecorder(dir_path,".mp4",driver)
11+
driver.get("http://www.google.es")
12+
i = 0
13+
while i < 60:
14+
time.sleep(1)
15+
if i == 30:
16+
recorder2.startRecordingSession()
17+
i+=1
18+
recorder.stopRecordingSession()
19+
20+
i = 0
21+
while i < 30:
22+
time.sleep(1)
23+
i+=1
24+
25+
recorder2.stopRecordingSession()
26+
driver.quit()
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
'''
2+
Created on 28 feb. 2020
3+
4+
@author: ingov
5+
'''
6+
from selenium import webdriver
7+
import imageio
8+
import pyautogui
9+
import time
10+
import threading
11+
from datetime import datetime
12+
import os
13+
14+
class DesktopBrowserRecorder:
15+
"""
16+
This is a class that allows you to record your desktop or a webdriver-driven browser for
17+
automatic testing.
18+
19+
Attributes:
20+
driver (wedriver): A webdriver to get screenshots in case we want to record a selenium.
21+
webdriver-driven automatic test. By default is None and if we don't provide it in __init__
22+
we will the desktop.
23+
__folder (str): The __folder where we want the videos to be saved.
24+
__encoding (str): The __encoding of the video, .mp4 recommended and tested.
25+
__keeprecording (bool): Flag that indicated the algorithm if it has to keep recording or stop.
26+
frame (obj): Current frame taken from the desktop or the browser that will be added to the video.
27+
__thread (theard): As this process will be all performed in another threard, this is where
28+
we will store that __thread so we can start it or stop it and that way start or stop the recording
29+
session.
30+
"""
31+
def __init__(self,folder,encoding,driver = None):
32+
"""
33+
The constructor for DesktopBrowserRecorder class.
34+
35+
Parameters:
36+
__folder (str): The __folder where we want the videos to be saved.
37+
__encoding (str): The __encoding of the video, .mp4 recommended and tested.
38+
driver (wedriver): A webdriver to get screenshots in case we want to record a selenium.
39+
By default is None, in that case we will record the desktop and not the browser opened
40+
by this webdriver
41+
"""
42+
self.driver = driver
43+
self.__folder = folder
44+
self.__sessionPath = ""
45+
self.__encoding = encoding
46+
#To start __keeprecording will be False for starters
47+
self.__keeprecording = False
48+
#To start our frame will be None until we capture an image to use for the video
49+
self.frame = None
50+
#Until we start the recording session this will be None
51+
self.__thread = None
52+
53+
def __threatTakeScreenshot(self):
54+
"""
55+
This function takes the screenshot that will be added to the video in a different __thread
56+
than the main __thread. The screenshot will be of the webdriver or the desktop depending
57+
on what we set in the constructor.
58+
59+
Parameters:
60+
None
61+
62+
Returns:
63+
None
64+
"""
65+
#This parameter contains the path to the file where we will save the frame
66+
frameFileName = self.__sessionPath+"/frame.png"
67+
#We will keep iterating until the recording session is stopped
68+
while self.__keeprecording:
69+
#If self.driver is not None, means we are recording a webdriver browser
70+
if self.driver is not None:
71+
#We save our browser screenshot in the file for the frame
72+
self.driver.save_screenshot(frameFileName)
73+
else:
74+
#Otherwise, we are recording the desktop and take the screenshot from
75+
#the desktop and save it too to the frame file
76+
pyautogui.screenshot(frameFileName)
77+
#We use imageio to open that file and assign it to the attribute frame
78+
#that we will use to add to the video in the main threat
79+
self.frame = imageio.imread(frameFileName)
80+
#As we are using a 20fps configuration, we sleep for 1/25 seconds
81+
time.sleep(1/25)
82+
83+
def __buildWriter(self):
84+
"""
85+
This function builders the writer for the class imageio that we will use
86+
to builder the video. We will use this to build the video writer for our
87+
current video.
88+
89+
Parameters:
90+
folderPath (str): Folder where we want to save the videos for our current
91+
recording session.
92+
93+
Returns:
94+
writer (obj): The object we will use to write our current video.
95+
"""
96+
#We get the current date and time
97+
now = datetime.now()
98+
#We use the current date and time and the class attribute __encoding to create the
99+
#video file name
100+
fileName = now.strftime("%d-%m-%Y_%H-%M-%S") + self.__encoding
101+
#We use the recording session __folder and file name to builder the imageio video
102+
#writer that we will use to create our video
103+
writer = imageio.get_writer(self.__sessionPath+"/"+fileName, fps=20)
104+
#We return this object
105+
return writer
106+
107+
def __mainThreadRecordingSession(self):
108+
"""
109+
As the recording process should be done in a parallel __thread to the execution of
110+
the program where we want to start a recording, we have to define this function
111+
that contains the main parallel __thread to the program that will be started
112+
when we start the recording session (using the class method start recording
113+
session).
114+
115+
Parameters:
116+
None
117+
118+
Returns:
119+
None
120+
"""
121+
#We get the current instant to the second to build the __folder which
122+
#will contain the videos resulting of our recording session
123+
now = datetime.now()
124+
#We build that path to contain the videos resulting from our recording session
125+
self.__sessionPath = self.__folder + "\\"+now.strftime("%d-%m-%Y_%H-%M-%S")
126+
try:
127+
#We create that __folder
128+
os.mkdir(self.__sessionPath)
129+
except Exception as e:
130+
print(e)
131+
#We create the secondary thread that will start taking the screenshots that will
132+
#build our video
133+
x = threading.Thread(target=self.__threatTakeScreenshot, args=())
134+
#We use the method __buildWriter to get the video writer that will create and build
135+
#our current video
136+
writer = self.__buildWriter()
137+
#We start the secondary __thread that will start taking the screenshots
138+
x.start()
139+
#While the flag __keeprecording is True, we will keep adding frames to our video
140+
#or create a new video if case something happens to our current video
141+
while self.__keeprecording:
142+
try:
143+
#If the frame is not None, if we already have a frame, we add it to the video
144+
if self.frame is not None:
145+
writer.append_data(self.frame)
146+
except Exception:
147+
#In case there is an exception when trying to add the frame to the video
148+
#we close the writer
149+
writer.close()
150+
#We create a new one
151+
writer = self.__buildWriter()
152+
if self.frame is not None:
153+
#Again we try to add the frame to the new video
154+
writer.append_data(self.frame)
155+
#NOTE: This is the best way to prevent problems when the size of a browser changes
156+
#in that case, an exception occurs because all frame in a video must have the same size
157+
#so we create a new video in the same __folder so the session continues and no information
158+
#is lost. It will be just distributed in several videos in case the window changes sizes
159+
#several times
160+
#As our video has the property 20fps, we have to sleep our __thread 1/25. It is not 1/20
161+
#because processing some of these instructions take time so we need this __thread to keep
162+
#running faster
163+
time.sleep(1/25)
164+
#Once the flag __keeprecording is False we stop our recording session, so we close our video writer
165+
writer.close()
166+
#And stop the secondary __thread and takes the screenshots
167+
x.join()
168+
169+
def startRecordingSession(self):
170+
"""
171+
This method starts the recording session. It starts a new __thread that runs the method
172+
__mainThreadRecordingSession
173+
174+
Parameters:
175+
None
176+
177+
Returns:
178+
None
179+
"""
180+
#If __keeprecording is already True is because we already have a recording session running
181+
if self.__keeprecording == True:
182+
#In that case, we have to raise a custom made Exception
183+
raise SessionStartedException("There is an existing running recording session for this object")
184+
print("Started our recording session")
185+
#Otherwise, we don't have a recording session going on, so we change the flag
186+
#__keeprecording to True
187+
self.__keeprecording = True
188+
#We create the main __thread that runs the algorithm and assign it to attribute __thread
189+
#so we can start and stop it
190+
self.__thread = threading.Thread(target=self.__mainThreadRecordingSession, args=())
191+
#We start that __thread
192+
self.__thread.start()
193+
194+
def stopRecordingSession(self):
195+
"""
196+
This method stops the recording session. It stops the threads that create the video
197+
198+
Parameters:
199+
None
200+
201+
Returns:
202+
None
203+
"""
204+
#If the flag __keeprecording is already False is because we don't have a session running
205+
if self.__keeprecording == False:
206+
#In that case, we raise a custom made Exception
207+
raise NoSessionStartedException("There is no current recording session")
208+
#We set the flag keeprecoring to false, to stop the recording process
209+
self.__keeprecording = False
210+
#We stop the main __thread
211+
self.__thread.join()
212+
#We set the attribute __thread to None, because the __thread is finished and the
213+
#recording session is done
214+
self.__thread = None
215+
try:
216+
os.remove(self.__sessionPath+"/frame.png")
217+
except Exception:
218+
print("There was an issue when trying to delete the file where we stored our frames")
219+
print("Our recording session ended")
220+
221+
class SessionStartedException(Exception):
222+
"""
223+
This is a class that inherits from Exception to create a custom made Exception to be raised
224+
in the case that we try to start a new recording session when there is a current session
225+
running.
226+
227+
Attributes:
228+
None
229+
"""
230+
pass
231+
232+
class NoSessionStartedException(Exception):
233+
"""
234+
This is a class that inherits from Exception to create a custom made Exception to be raised
235+
in the case that we try to stop a recording session when we have not started a recording
236+
session.
237+
238+
Attributes:
239+
None
240+
"""
241+
pass

0 commit comments

Comments
 (0)