""" Launches the SeleniumBase Case Plans Generator. Usage: seleniumbase caseplans [OPTIONAL PATH or TEST FILE] sbase caseplans [OPTIONAL PATH or TEST FILE] Examples: sbase caseplans sbase caseplans -k agent sbase caseplans -m marker2 sbase caseplans test_suite.py sbase caseplans offline_examples/ Output: Launches the SeleniumBase Case Plans Generator. """ import codecs import colorama import os import subprocess import sys if sys.version_info <= (3, 7): current_version = ".".join(str(ver) for ver in sys.version_info[:3]) raise Exception( "\n* SBase Case Plans Generator requires Python 3.7 or newer!" "\n** You are currently using Python %s" % current_version ) from seleniumbase.fixtures import shared_utils import tkinter as tk # noqa: E402 from tkinter import messagebox # noqa: E402 from tkinter.scrolledtext import ScrolledText # noqa: E402 def set_colors(use_colors): c0 = "" c1 = "" c2 = "" c3 = "" c4 = "" c5 = "" cr = "" if use_colors: if ( shared_utils.is_windows() and hasattr(colorama, "just_fix_windows_console") ): colorama.just_fix_windows_console() else: colorama.init(autoreset=True) c0 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX c1 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX c2 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX c3 = colorama.Fore.BLACK + colorama.Back.LIGHTCYAN_EX c4 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX c5 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX cr = colorama.Style.RESET_ALL return c0, c1, c2, c3, c4, c5, cr def send_window_to_front(root): root.lift() root.attributes("-topmost", True) root.after_idle(root.attributes, "-topmost", False) def show_no_case_plans_warning(): messagebox.showwarning( "No existing Case Plans found!", "\nNo existing Case Plans found!!\n\nCreate some boilerplates first!", ) def get_test_id(display_id): """The id used in various places such as the test log path.""" return ( display_id.replace(".py::", ".").replace("::", ".").replace(" ", "_") ) def generate_case_plan_boilerplates( root, tests, selected_tests, tests_with_case_plan, tests_without_case_plan, ): total_tests = len(tests) total_selected_tests = 0 for selected_test in selected_tests: if selected_tests[selected_test].get(): total_selected_tests += 1 test_cases = [] case_plans_to_create = [] if total_selected_tests == 0: messagebox.showwarning( "No tests were selected!", "\nℹ️ No tests were selected!\nSelect tests for Case Plans!", ) send_window_to_front(root) return elif total_tests == total_selected_tests: for test in tests: test_cases.append(test) else: for test_number, test in enumerate(tests): if selected_tests[test_number].get(): test_cases.append(test) for test_case in test_cases: if ( test_case in tests_without_case_plan and test_case not in tests_with_case_plan ): case_plans_to_create.append(test_case) new_plans = 0 for case_plan in case_plans_to_create: parts = case_plan.split("/") test_address = None folder_path = None if len(parts) == 1: test_address = parts[0] if len(parts) > 1: test_address = parts[-1] folder_path = "/".join(parts[0:-1]) test_id = get_test_id(test_address) case_id = test_id + ".md" full_folder_path = None if len(parts) == 1: full_folder_path = "case_plans" if not os.path.exists(full_folder_path): os.makedirs(full_folder_path) else: full_folder_path = os.path.join(folder_path, "case_plans") if not os.path.exists(full_folder_path): os.makedirs(full_folder_path) data = [] data.append("``%s``" % test_address) data.append("---") data.append("| # | Step Description | Expected Result |") data.append("| - | ---------------- | --------------- |") data.append("| 1 | Perform Action 1 | Verify Action 1 |") data.append("| 2 | Perform Action 2 | Verify Action 2 |") data.append("") file_name = case_id file_path = os.path.join(full_folder_path, file_name) if not os.path.exists(file_path): out_file = codecs.open(file_path, "w+", "utf-8") out_file.writelines("\r\n".join(data)) out_file.close() new_plans += 1 print("Created %s" % file_path) if new_plans == 1: messagebox.showinfo( "A new Case Plan was generated!", '\n✅ %s new boilerplate Case Plan was generated!' % new_plans, ) elif new_plans >= 2: messagebox.showinfo( "New Case Plans were generated!", '\n✅ %s new boilerplate Case Plans were generated!' % new_plans, ) else: messagebox.showwarning( "No new Case Plans were generated!", "\nℹ️ No new boilerplates were generated!\n\n" "The selected tests already have Case Plans!", ) send_window_to_front(root) def view_summary_of_existing_case_plans(root, tests): case_data_storage = [] case_to_test_hash = {} full_t = [] for test_index, test in enumerate(tests): full_t.append(test) parts = test.strip().split("/") test_address = None folder_path = None if len(parts) == 1: test_address = parts[0] folder_path = "." if len(parts) > 1: test_address = parts[-1] folder_path = "/".join(parts[0:-1]) test_id = get_test_id(test_address) case_id = test_id + ".md" case_path = None if len(parts) == 1: case_path = os.path.join("case_plans", case_id) else: case_path = os.path.join(folder_path, "case_plans", case_id) if os.path.exists(case_path): f = open(case_path, "r") case_data = f.read() f.close() case_data_storage.append(case_data) case_to_test_hash[len(case_data_storage) - 1] = test_index full_plan = [] if len(case_data_storage) > 0: full_plan.append( "

Summary of existing Case Plans

" ) full_plan.append("") full_plan.append("| | |") full_plan.append("| - | - |") full_plan.append("| 🔵 | Plans with customized step definitions. |") full_plan.append("| ⭕ | Plans using default boilerplate code. |") full_plan.append("| 🚧 | Plans under construction with no table. |") full_plan.append("") full_plan.append("--------") else: show_no_case_plans_warning() send_window_to_front(root) return full_plan.append("") full_plan.append("

🔎 (Click rows to expand) 🔍

") full_plan.append("") full_plan = [] num_ready_cases = 0 num_boilerplate = 0 num_in_progress = 0 for case_index, case_data in enumerate(case_data_storage): icon = "🔵" table_missing = False if "| 1 | Perform Action 1 | Verify Action 1 |" in case_data: # Still using raw boilerplate code. (Missing real test steps) icon = "⭕" if case_data.count("|") < 9 or case_data.count("-") < 3: # Not enough characters for a minimal Markdown case plan file. # The dash(es) on line 2, and the Markdown table are required. # This is what a minimal case plan file might look like: """ TEST_ADDRESS - | Steps | Results | | - | - | | Step1 | Result1 | """ icon = "🚧" table_missing = True lines = case_data.split("\n") if len(lines) >= 3 and not table_missing: first_line = lines[0] first_line = first_line.strip() if not (first_line.startswith("``") and first_line.endswith("``")): first_line = "``%s``" % tests[case_to_test_hash[case_index]] lines.insert(0, first_line) else: first_line = "``%s``" % tests[case_to_test_hash[case_index]] lines[0] = first_line lines.insert(0, "
") lines[1] = ( " %s " % icon + first_line[2:-2] + "" ) if ( lines[2].strip().startswith("-") and lines[2].strip().endswith("-") ): lines[2] = "" elif lines[2].strip() != "": lines.insert(2, "") if lines[-1].strip() != "": lines.append("") lines.append("
") full_plan.append("\r\n".join(lines)) else: # No existing Case Plan found. / File is missing boilerplate. icon = "🚧" lines = [] first_line = tests[case_to_test_hash[case_index]] first_line = "%s %s" % (icon, first_line) lines.insert(0, first_line) full_plan.append("\r\n".join(lines)) full_plan.append("") if icon == "🔵": num_ready_cases += 1 elif icon == "⭕": num_boilerplate += 1 elif icon == "🚧": num_in_progress += 1 msg_ready_cases = "%s Case Plans with customized tables" % num_ready_cases if num_ready_cases == 1: msg_ready_cases = "1 Case Plan with a customized table" msg_boilerplate = "%s Case Plans using boilerplate code" % num_boilerplate if num_boilerplate == 1: msg_boilerplate = "1 Case Plan using boilerplate code" msg_in_progress = "%s Case Plans that are missing tables" % num_in_progress if num_in_progress == 1: msg_in_progress = "1 Case Plan that is missing a table" msg_r = " ".join(msg_ready_cases.split(" ")[1:]) msg_b = " ".join(msg_boilerplate.split(" ")[1:]) msg_i = " ".join(msg_in_progress.split(" ")[1:]) plan_head = [] if len(case_data_storage) > 0: plan_head.append( "

Summary of existing Case Plans

" ) plan_head.append("") plan_head.append("| | | |") plan_head.append("| - | -: | - |") plan_head.append("| 🔵 | %s | %s |" % (num_ready_cases, msg_r)) plan_head.append("| ⭕ | %s | %s |" % (num_boilerplate, msg_b)) plan_head.append("| 🚧 | %s | %s |" % (num_in_progress, msg_i)) plan_head.append("") plan_head.append("--------") else: show_no_case_plans_warning() send_window_to_front(root) return plan_head.append("") plan_head.append("

🔎 (Click rows to expand) 🔍

") plan_head.append("") for row in full_plan: plan_head.append(row) full_plan = plan_head file_path = "case_summary.md" file = codecs.open(file_path, "w+", "utf-8") file.writelines("\r\n".join(full_plan)) file.close() if num_ready_cases < 10: msg_ready_cases = " %s" % msg_ready_cases if num_ready_cases < 100: msg_ready_cases = " %s" % msg_ready_cases if num_boilerplate < 10: msg_boilerplate = " %s" % msg_boilerplate if num_boilerplate < 100: msg_boilerplate = " %s" % msg_boilerplate if num_in_progress < 10: msg_in_progress = " %s" % msg_in_progress if num_in_progress < 100: msg_in_progress = " %s" % msg_in_progress gen_message = ( '🗂️ Summary generated at "case_summary.md":' '\n🔵 %s' '\n⭕ %s' '\n🚧 %s' % (msg_ready_cases, msg_boilerplate, msg_in_progress) ) print(gen_message) if num_ready_cases < 10: msg_ready_cases = " %s" % msg_ready_cases if num_ready_cases < 100: msg_ready_cases = " %s" % msg_ready_cases if num_boilerplate < 10: msg_boilerplate = " %s" % msg_boilerplate if num_boilerplate < 100: msg_boilerplate = " %s" % msg_boilerplate if num_in_progress < 10: msg_in_progress = " %s" % msg_in_progress if num_in_progress < 100: msg_in_progress = " %s" % msg_in_progress messagebox.showinfo( "Case Plans Summary generated!", '\nSummary generated at "case_summary.md"' '\n🔵 %s' '\n⭕ %s' '\n🚧 %s' % (msg_ready_cases, msg_boilerplate, msg_in_progress) ) send_window_to_front(root) def create_tkinter_gui(tests, command_string): root = tk.Tk() root.title("SeleniumBase Case Plans Generator") if shared_utils.is_windows(): root.minsize(820, 618) else: root.minsize(820, 652) tk.Label(root, text="").pack() run_display = ( "Select from %s tests found: " "(Boilerplate Case Plans will be generated as needed)" % len(tests) ) if len(tests) == 1: run_display = ( "Select from 1 test found: " "(Boilerplate Case Plans will be generated as needed)" ) run_display_2 = "(Tests with existing Case Plans are already checked)" tk.Label(root, text=run_display, bg="yellow", fg="green").pack() tk.Label(root, text=run_display_2, bg="yellow", fg="magenta").pack() text_area = ScrolledText( root, width=100, height=12, wrap="word", state=tk.DISABLED ) text_area.pack(side=tk.TOP, fill=tk.BOTH, expand=True) count = 0 ara = {} tests_with_case_plan = [] tests_without_case_plan = [] for row in tests: row += " " * 200 ara[count] = tk.IntVar() cb = None if shared_utils.is_windows(): cb = tk.Checkbutton( text_area, text=(row), bg="white", fg="black", anchor="w", pady=0, borderwidth=1, highlightthickness=1, variable=ara[count], ) else: cb = tk.Checkbutton( text_area, text=(row), bg="white", fg="black", anchor="w", pady=0, variable=ara[count], ) parts = row.strip().split("/") test_address = None folder_path = None if len(parts) == 1: test_address = parts[0] if len(parts) > 1: test_address = parts[-1] folder_path = "/".join(parts[0:-1]) test_id = get_test_id(test_address) case_id = test_id + ".md" case_path = None if len(parts) == 1: case_path = os.path.join("case_plans", case_id) else: case_path = os.path.join(folder_path, "case_plans", case_id) if os.path.exists(case_path): cb.select() tests_with_case_plan.append(row.strip()) else: tests_without_case_plan.append(row.strip()) text_area.window_create("end", window=cb) text_area.insert("end", "\n") count += 1 tk.Label(root, text="").pack() tk.Button( root, text=( "Generate boilerplate Case Plans " "for selected tests missing them"), fg="green", command=lambda: generate_case_plan_boilerplates( root, tests, ara, tests_with_case_plan, tests_without_case_plan, ), ).pack() tk.Label(root, text="").pack() try: tk.Button( root, text=("Generate Summary of existing Case Plans"), fg="teal", command=lambda: view_summary_of_existing_case_plans(root, tests), ).pack() except Exception: tk.Button( root, text=("Generate Summary of existing Case Plans"), fg="green", command=lambda: view_summary_of_existing_case_plans(root, tests), ).pack() tk.Label(root, text="\n").pack() # Bring form window to front send_window_to_front(root) # Use decoy to set correct focus on main window decoy = tk.Tk() decoy.geometry("1x1") decoy.iconify() decoy.update() decoy.deiconify() decoy.destroy() # Start tkinter root.mainloop() def main(): use_colors = True if shared_utils.is_linux(): use_colors = False c0, c1, c2, c3, c4, c5, cr = set_colors(use_colors) command_args = sys.argv[2:] command_string = " ".join(command_args) message = "" message += c2 message += "*" message += c4 message += " Starting the " message += c0 message += "Selenium" message += c1 message += "Base" message += c2 message += " " message += c3 message += "Case Plans" message += c4 message += " Generator" message += c2 message += "..." message += cr print(message) proc = subprocess.Popen( '"%s" -m pytest --collect-only -q --rootdir="./" %s' % (sys.executable, command_string), stdout=subprocess.PIPE, shell=True, ) (output, error) = proc.communicate() if error: error_msg = "Error collecting tests: %s" % str(error) error_msg = c5 + error_msg + cr print(error_msg) return tests = [] if shared_utils.is_windows(): output = output.decode("latin1") else: output = output.decode("utf-8") for row in output.replace("\r", "").split("\n"): if ("::") in row: tests.append(row) if not tests: error_msg = "No tests found! Exiting the Case Plans Generator..." error_msg = c5 + "ERROR: " + error_msg + cr print(error_msg) return create_tkinter_gui(tests, command_string) if __name__ == "__main__": print('To open the Case Plans Generator, type "sbase caseplans"')