Skip to content

Commit d2bb997

Browse files
committed
Make improvements to Recorder Mode and the GUI
1 parent 05afcbe commit d2bb997

File tree

4 files changed

+219
-103
lines changed

4 files changed

+219
-103
lines changed

seleniumbase/console_scripts/sb_recorder.py

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@
1414
import os
1515
import subprocess
1616
import sys
17+
from seleniumbase import config as sb_config
1718
from seleniumbase.fixtures import page_utils
1819

20+
sb_config.rec_subprocess_p = None
21+
sb_config.rec_subprocess_used = False
1922
if sys.version_info <= (3, 7):
2023
current_version = ".".join(str(ver) for ver in sys.version_info[:3])
2124
raise Exception(
@@ -50,6 +53,14 @@ def send_window_to_front(window):
5053
window.after_idle(window.attributes, "-topmost", False)
5154

5255

56+
def show_already_recording_warning():
57+
messagebox.showwarning(
58+
"SeleniumBase Recorder: Already Running!",
59+
"Please finalize the active recording from the terminal:\n"
60+
'Type "c" and press Enter/Return there.'
61+
)
62+
63+
5364
def file_name_error(file_name):
5465
error_msg = None
5566
if not file_name.endswith(".py"):
@@ -64,6 +75,18 @@ def file_name_error(file_name):
6475

6576

6677
def do_recording(file_name, url, overwrite_enabled, use_chrome, window):
78+
poll = None
79+
if sb_config.rec_subprocess_used:
80+
poll = sb_config.rec_subprocess_p.poll()
81+
if not sb_config.rec_subprocess_used or poll is not None:
82+
pass
83+
else:
84+
show_already_recording_warning()
85+
send_window_to_front(window)
86+
poll = sb_config.rec_subprocess_p.poll()
87+
if poll is None:
88+
return
89+
6790
file_name = file_name.strip()
6891
error_msg = file_name_error(file_name)
6992
if error_msg:
@@ -98,7 +121,14 @@ def do_recording(file_name, url, overwrite_enabled, use_chrome, window):
98121
command = "sbase mkrec %s --url=%s --gui" % (file_name, url)
99122
if not use_chrome:
100123
command += " --edge"
101-
subprocess.Popen(command, shell=True)
124+
poll = None
125+
if sb_config.rec_subprocess_used:
126+
poll = sb_config.rec_subprocess_p.poll()
127+
if not sb_config.rec_subprocess_used or poll is not None:
128+
sb_config.rec_subprocess_p = subprocess.Popen(command, shell=True)
129+
sb_config.rec_subprocess_used = True
130+
else:
131+
show_already_recording_warning()
102132
send_window_to_front(window)
103133

104134

@@ -123,8 +153,18 @@ def do_playback(file_name, use_chrome, window, demo_mode=False):
123153
command += " --edge"
124154
if demo_mode:
125155
command += " --demo"
126-
print(command)
127-
subprocess.Popen(command, shell=True)
156+
poll = None
157+
if sb_config.rec_subprocess_used:
158+
poll = sb_config.rec_subprocess_p.poll()
159+
if not sb_config.rec_subprocess_used or poll is not None:
160+
print(command)
161+
subprocess.Popen(command, shell=True)
162+
else:
163+
messagebox.showwarning(
164+
"SeleniumBase Recorder: Already Running!",
165+
"Please finalize the active recording from the terminal:\n"
166+
'Type "c" and press Enter/Return there.'
167+
)
128168
send_window_to_front(window)
129169

130170

@@ -201,6 +241,38 @@ def create_tkinter_gui():
201241
decoy.destroy()
202242
# Start tkinter
203243
window.mainloop()
244+
end_program()
245+
246+
247+
def recorder_still_running():
248+
poll = None
249+
if sb_config.rec_subprocess_used:
250+
try:
251+
poll = sb_config.rec_subprocess_p.poll()
252+
except Exception:
253+
return False
254+
else:
255+
return False
256+
if poll is not None:
257+
return False
258+
return True
259+
260+
261+
def show_still_running_warning():
262+
"""Give the user a chance to end the recording safely via the
263+
pytest ipdb Debug Mode so that processes such as chromedriver
264+
and Python don't remain open and hanging in the background."""
265+
messagebox.showwarning(
266+
"SeleniumBase Recorder: Still Running!",
267+
"Please end the active recording from the TERMINAL/PROMPT:\n"
268+
'Type "c" and press Enter/Return there.\n'
269+
"(Then you can safely close this alert.)"
270+
)
271+
272+
273+
def end_program():
274+
if recorder_still_running():
275+
show_still_running_warning()
204276

205277

206278
def main():

seleniumbase/extensions/recorder.zip

244 Bytes
Binary file not shown.

seleniumbase/fixtures/base_case.py

Lines changed: 61 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3469,6 +3469,17 @@ def save_recorded_actions(self):
34693469
origin = self.get_origin()
34703470
self.__origins_to_save.append(origin)
34713471
tab_actions = self.__get_recorded_actions_on_active_tab()
3472+
for n in range(len(tab_actions)):
3473+
if (
3474+
n > 2
3475+
and tab_actions[n - 2][0] == "sw_fr"
3476+
and tab_actions[n - 1][0] == "sk_fo"
3477+
and tab_actions[n][0] != "_url_"
3478+
):
3479+
origin = tab_actions[n - 2][2]
3480+
time_stamp = str(int(tab_actions[n][3]) - 1)
3481+
new_action = ["sw_pf", "", origin, time_stamp]
3482+
tab_actions.append(new_action)
34723483
self.__actions_to_save.append(tab_actions)
34733484

34743485
def __get_recorded_actions_on_active_tab(self):
@@ -3478,6 +3489,7 @@ def __get_recorded_actions_on_active_tab(self):
34783489
or url.startswith("chrome:") or url.startswith("edge:")
34793490
):
34803491
return []
3492+
self.__origins_to_save.append(self.get_origin())
34813493
actions = self.get_session_storage_item("recorded_actions")
34823494
if actions:
34833495
actions = json.loads(actions)
@@ -3492,34 +3504,43 @@ def __process_recorded_actions(self):
34923504
srt_actions = []
34933505
cleaned_actions = []
34943506
sb_actions = []
3495-
used_actions = []
34963507
action_dict = {}
34973508
for window in self.driver.window_handles:
34983509
self.switch_to_window(window)
34993510
tab_actions = self.__get_recorded_actions_on_active_tab()
3511+
for n in range(len(tab_actions)):
3512+
if (
3513+
n > 2
3514+
and tab_actions[n - 2][0] == "sw_fr"
3515+
and tab_actions[n - 1][0] == "sk_fo"
3516+
and tab_actions[n][0] != "_url_"
3517+
):
3518+
origin = tab_actions[n - 2][2]
3519+
time_stamp = str(int(tab_actions[n][3]) - 1)
3520+
new_action = ["sw_pf", "", origin, time_stamp]
3521+
tab_actions.append(new_action)
35003522
for action in tab_actions:
3501-
if action not in used_actions:
3502-
used_actions.append(action)
3523+
if action not in raw_actions:
35033524
raw_actions.append(action)
35043525
for tab_actions in self.__actions_to_save:
35053526
for action in tab_actions:
3506-
if action not in used_actions:
3507-
used_actions.append(action)
3527+
if action not in raw_actions:
35083528
raw_actions.append(action)
35093529
for action in self.__extra_actions:
3510-
if action not in used_actions:
3511-
used_actions.append(action)
3530+
if action not in raw_actions:
35123531
raw_actions.append(action)
35133532
for action in raw_actions:
3514-
if self._reuse_session:
3515-
if int(action[3]) < int(self.__js_start_time):
3516-
continue
3533+
if int(action[3]) < int(self.__js_start_time):
3534+
continue
35173535
# Use key for sorting and preventing duplicates
35183536
key = str(action[3]) + "-" + str(action[0])
35193537
action_dict[key] = action
35203538
for key in sorted(action_dict):
35213539
# print(action_dict[key]) # For debugging purposes
35223540
srt_actions.append(action_dict[key])
3541+
for n in range(len(srt_actions)):
3542+
if srt_actions[n][0] == "sk_fo":
3543+
srt_actions[n][0] = "sk_op"
35233544
for n in range(len(srt_actions)):
35243545
if (
35253546
(srt_actions[n][0] == "begin" or srt_actions[n][0] == "_url_")
@@ -3722,6 +3743,7 @@ def __process_recorded_actions(self):
37223743
ext_actions.append("s_c_d")
37233744
ext_actions.append("sh_fc")
37243745
ext_actions.append("c_l_s")
3746+
ext_actions.append("c_s_s")
37253747
ext_actions.append("e_mfa")
37263748
ext_actions.append("ss_tl")
37273749
for n in range(len(srt_actions)):
@@ -4088,6 +4110,8 @@ def __process_recorded_actions(self):
40884110
sb_actions.append("self.%s()" % method)
40894111
elif action[0] == "c_l_s":
40904112
sb_actions.append("self.clear_local_storage()")
4113+
elif action[0] == "c_s_s":
4114+
sb_actions.append("self.clear_session_storage()")
40914115
elif action[0] == "c_box":
40924116
method = "check_if_unchecked"
40934117
if action[2] == "no":
@@ -5812,9 +5836,7 @@ def enter_mfa_code(
58125836
action = ["e_mfa", sel_key, origin, time_stamp]
58135837
self.__extra_actions.append(action)
58145838
# Sometimes Sign-In leaves the origin... Save work first.
5815-
self.__origins_to_save.append(origin)
5816-
tab_actions = self.__get_recorded_actions_on_active_tab()
5817-
self.__actions_to_save.append(tab_actions)
5839+
self.save_recorded_actions()
58185840
mfa_code = self.get_mfa_code(totp_key)
58195841
self.update_text(selector, mfa_code + "\n", by=by, timeout=timeout)
58205842

@@ -6715,7 +6737,24 @@ def remove_session_storage_item(self, key):
67156737

67166738
def clear_session_storage(self):
67176739
self.__check_scope()
6718-
self.execute_script("window.sessionStorage.clear();")
6740+
if not self.recorder_mode:
6741+
self.execute_script("window.sessionStorage.clear();")
6742+
else:
6743+
recorder_keys = [
6744+
"recorder_mode",
6745+
"recorded_actions",
6746+
"recorder_title",
6747+
"pause_recorder",
6748+
"recorder_activated",
6749+
]
6750+
keys = self.get_session_storage_keys()
6751+
for key in keys:
6752+
if key not in recorder_keys:
6753+
self.remove_session_storage_item(key)
6754+
time_stamp = self.execute_script("return Date.now();")
6755+
origin = self.get_origin()
6756+
action = ["c_s_s", "", origin, time_stamp]
6757+
self.__extra_actions.append(action)
67196758

67206759
def get_session_storage_keys(self):
67216760
self.__check_scope()
@@ -11634,9 +11673,9 @@ def setUp(self, masterqa_mode=False):
1163411673
self._dash_initialized = True
1163511674
self.__process_dashboard(False, init=True)
1163611675

11637-
# Set the JS start time for Recorder Mode if reusing the session.
11676+
# Set the JS start time for Recorder Mode.
1163811677
# Use this to skip saving recorded actions from previous tests.
11639-
if self.recorder_mode and self._reuse_session:
11678+
if self.recorder_mode:
1164011679
self.__js_start_time = int(time.time() * 1000.0)
1164111680

1164211681
has_url = False
@@ -12417,18 +12456,23 @@ def has_exception(self):
1241712456
def save_teardown_screenshot(self):
1241812457
"""(Should ONLY be used at the start of custom tearDown() methods.)
1241912458
This method takes a screenshot of the current web page for a
12420-
failing test (or when running your tests with --save-screenshot).
12459+
FAILING test (or when using "--screenshot" / "--save-screenshot").
1242112460
That way your tearDown() method can navigate away from the last
1242212461
page where the test failed, and still get the correct screenshot
1242312462
before performing tearDown() steps on other pages. If this method
1242412463
is not included in your custom tearDown() method, a screenshot
1242512464
will still be taken after the last step of your tearDown(), where
1242612465
you should be calling "super(SubClassOfBaseCase, self).tearDown()"
12466+
or "super().tearDown()".
12467+
This method also saves recorded actions when using Recorder Mode.
1242712468
"""
1242812469
try:
1242912470
self.__check_scope()
1243012471
except Exception:
1243112472
return
12473+
if self.recorder_mode:
12474+
# In case tearDown() leaves the origin, save actions first.
12475+
self.save_recorded_actions()
1243212476
if self.__has_exception() or self.save_screenshot_after_test:
1243312477
test_logpath = os.path.join(self.log_path, self.__get_test_id())
1243412478
self.__create_log_path_as_needed(test_logpath)

0 commit comments

Comments
 (0)