diff --git a/examples/unit_tests/test_sb_mkdir.py b/examples/unit_tests/test_sb_mkdir.py new file mode 100644 index 00000000000..a6f7ee52fff --- /dev/null +++ b/examples/unit_tests/test_sb_mkdir.py @@ -0,0 +1,183 @@ +import os +import sys +import tempfile +import shutil +import yaml + + +def test_mkdir_with_gha_flag(): + from seleniumbase.console_scripts import sb_mkdir + import sys as sys_module + + original_argv = sys_module.argv + original_cwd = os.getcwd() + + temp_dir = tempfile.mkdtemp() + try: + os.chdir(temp_dir) + + test_dir = "test_gha_dir" + sys_module.argv = ["sbase", "mkdir", test_dir, "--gha"] + + sb_mkdir.main() + + workflow_file = os.path.join(test_dir, ".github", "workflows", "seleniumbase.yml") + assert os.path.exists(workflow_file), "Workflow file should be created" + + with open(workflow_file, "r") as f: + content = f.read() + + assert "name: SeleniumBase Tests" in content + assert "on:" in content + assert "push:" in content + assert "pull_request:" in content + assert "jobs:" in content + assert "test:" in content + assert "runs-on: ${{ matrix.os }}" in content + assert "strategy:" in content + assert "matrix:" in content + assert "python-version:" in content + assert "browser:" in content + assert "os:" in content + assert "actions/checkout@v3" in content + assert "actions/setup-python@v4" in content + assert "cache: 'pip'" in content + assert "pip install -r requirements.txt" in content + assert "pytest --browser=${{ matrix.browser }} --headless" in content + assert "actions/upload-artifact@v3" in content + assert "latest_logs/**" in content + + workflow_data = yaml.safe_load(content) + assert "on" in workflow_data + assert "jobs" in workflow_data + assert "test" in workflow_data["jobs"] + assert "steps" in workflow_data["jobs"]["test"] + + finally: + os.chdir(original_cwd) + sys_module.argv = original_argv + if os.path.exists(temp_dir): + shutil.rmtree(temp_dir) + + +def test_mkdir_with_gha_custom_params(): + from seleniumbase.console_scripts import sb_mkdir + import sys as sys_module + + original_argv = sys_module.argv + original_cwd = os.getcwd() + + temp_dir = tempfile.mkdtemp() + try: + os.chdir(temp_dir) + + test_dir = "test_gha_custom" + sys_module.argv = [ + "sbase", "mkdir", test_dir, "--gha", + "--gha-browsers=chrome,firefox", + "--gha-python=3.10,3.11", + "--gha-os=ubuntu-latest,windows-latest" + ] + + sb_mkdir.main() + + workflow_file = os.path.join(test_dir, ".github", "workflows", "seleniumbase.yml") + assert os.path.exists(workflow_file), "Workflow file should be created" + + with open(workflow_file, "r") as f: + content = f.read() + + assert '"chrome"' in content + assert '"firefox"' in content + assert '"3.10"' in content + assert '"3.11"' in content + assert '"ubuntu-latest"' in content + assert '"windows-latest"' in content + + workflow_data = yaml.safe_load(content) + matrix = workflow_data["jobs"]["test"]["strategy"]["matrix"] + assert "chrome" in matrix["browser"] + assert "firefox" in matrix["browser"] + assert "3.10" in matrix["python-version"] + assert "3.11" in matrix["python-version"] + assert "ubuntu-latest" in matrix["os"] + assert "windows-latest" in matrix["os"] + + finally: + os.chdir(original_cwd) + sys_module.argv = original_argv + if os.path.exists(temp_dir): + shutil.rmtree(temp_dir) + + +def test_mkdir_with_basic_and_gha(): + from seleniumbase.console_scripts import sb_mkdir + import sys as sys_module + + original_argv = sys_module.argv + original_cwd = os.getcwd() + + temp_dir = tempfile.mkdtemp() + try: + os.chdir(temp_dir) + + test_dir = "test_basic_gha" + sys_module.argv = ["sbase", "mkdir", test_dir, "--basic", "--gha"] + + sb_mkdir.main() + + workflow_file = os.path.join(test_dir, ".github", "workflows", "seleniumbase.yml") + assert os.path.exists(workflow_file), "Workflow file should be created with --basic --gha" + + requirements_file = os.path.join(test_dir, "requirements.txt") + assert os.path.exists(requirements_file), "requirements.txt should exist in basic mode" + + pytest_ini_file = os.path.join(test_dir, "pytest.ini") + assert os.path.exists(pytest_ini_file), "pytest.ini should exist in basic mode" + + test_files_exist = any( + f.endswith("_test.py") or f.startswith("test_") + for f in os.listdir(test_dir) + if os.path.isfile(os.path.join(test_dir, f)) + ) + assert not test_files_exist, "No test files should exist in basic mode" + + finally: + os.chdir(original_cwd) + sys_module.argv = original_argv + if os.path.exists(temp_dir): + shutil.rmtree(temp_dir) + + +def test_mkdir_gha_workflow_already_exists_error(): + from seleniumbase.console_scripts import sb_mkdir + import sys as sys_module + + original_argv = sys_module.argv + original_cwd = os.getcwd() + + temp_dir = tempfile.mkdtemp() + try: + os.chdir(temp_dir) + + test_dir = "test_gha_exists" + os.makedirs(test_dir) + workflow_dir = os.path.join(test_dir, ".github", "workflows") + os.makedirs(workflow_dir, exist_ok=True) + workflow_file = os.path.join(workflow_dir, "seleniumbase.yml") + with open(workflow_file, "w") as f: + f.write("existing workflow") + + sys_module.argv = ["sbase", "mkdir", test_dir, "--gha"] + try: + sb_mkdir.main() + assert False, "Should raise an error when directory already exists" + except Exception as e: + assert "already exists" in str(e).lower() or "ERROR" in str(e) + + finally: + os.chdir(original_cwd) + sys_module.argv = original_argv + if os.path.exists(temp_dir): + shutil.rmtree(temp_dir) + diff --git a/seleniumbase/console_scripts/ReadMe.md b/seleniumbase/console_scripts/ReadMe.md index b5f41750d02..a926c549d9a 100644 --- a/seleniumbase/console_scripts/ReadMe.md +++ b/seleniumbase/console_scripts/ReadMe.md @@ -50,14 +50,14 @@ COMMANDS:
diff --git a/seleniumbase/console_scripts/run.py b/seleniumbase/console_scripts/run.py
index b11305d9846..938f4ff93f2 100644
--- a/seleniumbase/console_scripts/run.py
+++ b/seleniumbase/console_scripts/run.py
@@ -246,8 +246,13 @@ def show_mkdir_usage():
print(" OR: sbase mkdir [DIRECTORY] [OPTIONS]")
print(" Example:")
print(" sbase mkdir ui_tests")
+ print(" sbase mkdir ui_tests --gha")
print(" Options:")
print(" -b / --basic (Only config files. No tests added.)")
+ print(" --gha / --github-actions (Generate GitHub Actions workflow)")
+ print(" --gha-browsers=BROWSERS (Comma-separated browsers, default: chrome)")
+ print(" --gha-python=VERSIONS (Comma-separated Python versions, default: 3.11)")
+ print(" --gha-os=OS_LIST (Comma-separated OS list, default: ubuntu-latest)")
print(" Output:")
print(" Creates a new folder for running SBase scripts.")
print(" The new folder contains default config files,")
diff --git a/seleniumbase/console_scripts/sb_mkdir.py b/seleniumbase/console_scripts/sb_mkdir.py
index 0caa4f01a35..103a99a2a32 100644
--- a/seleniumbase/console_scripts/sb_mkdir.py
+++ b/seleniumbase/console_scripts/sb_mkdir.py
@@ -23,6 +23,62 @@
import sys
+def generate_github_actions_workflow(python_versions, browsers, os_versions):
+ python_list = [v.strip() for v in python_versions.split(",")]
+ browser_list = [b.strip() for b in browsers.split(",")]
+ os_list = [o.strip() for o in os_versions.split(",")]
+
+ workflow_lines = []
+ workflow_lines.append("name: SeleniumBase Tests")
+ workflow_lines.append("")
+ workflow_lines.append("on:")
+ workflow_lines.append(" push:")
+ workflow_lines.append(" pull_request:")
+ workflow_lines.append("")
+ workflow_lines.append("jobs:")
+ workflow_lines.append(" test:")
+ workflow_lines.append(" runs-on: ${{ matrix.os }}")
+ workflow_lines.append(" strategy:")
+ workflow_lines.append(" fail-fast: false")
+ workflow_lines.append(" matrix:")
+ workflow_lines.append(" python-version:")
+ for py_ver in python_list:
+ workflow_lines.append(' - "%s"' % py_ver)
+ workflow_lines.append(" browser:")
+ for browser in browser_list:
+ workflow_lines.append(' - "%s"' % browser)
+ workflow_lines.append(" os:")
+ for os_ver in os_list:
+ workflow_lines.append(' - "%s"' % os_ver)
+ workflow_lines.append("")
+ workflow_lines.append(" steps:")
+ workflow_lines.append(" - uses: actions/checkout@v3")
+ workflow_lines.append(" - name: Set up Python ${{ matrix.python-version }}")
+ workflow_lines.append(" uses: actions/setup-python@v4")
+ workflow_lines.append(" with:")
+ workflow_lines.append(" python-version: ${{ matrix.python-version }}")
+ workflow_lines.append(" cache: 'pip'")
+ workflow_lines.append(" - name: Install dependencies")
+ workflow_lines.append(" run: |")
+ workflow_lines.append(" python -m pip install --upgrade pip")
+ workflow_lines.append(" pip install -r requirements.txt")
+ workflow_lines.append(" - name: Run tests with ${{ matrix.browser }}")
+ workflow_lines.append(" run: pytest --browser=${{ matrix.browser }} --headless")
+ workflow_lines.append(" - name: Upload artifacts on failure")
+ workflow_lines.append(" if: failure()")
+ workflow_lines.append(" uses: actions/upload-artifact@v3")
+ workflow_lines.append(" with:")
+ workflow_lines.append(" name: test-artifacts-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.browser }}")
+ workflow_lines.append(" path: |")
+ workflow_lines.append(" latest_logs/**")
+ workflow_lines.append(" logs/**")
+ workflow_lines.append(" archived_logs/**")
+ workflow_lines.append(" screenshots/**")
+ workflow_lines.append("")
+
+ return "\n".join(workflow_lines)
+
+
def invalid_run_command(msg=None):
exp = " ** mkdir **\n\n"
exp += " Usage:\n"
@@ -30,8 +86,13 @@ def invalid_run_command(msg=None):
exp += " OR sbase mkdir [DIRECTORY] [OPTIONS]\n"
exp += " Example:\n"
exp += " sbase mkdir ui_tests\n"
+ exp += " sbase mkdir ui_tests --gha\n"
exp += " Options:\n"
exp += " -b / --basic (Only config files. No tests added.)\n"
+ exp += " --gha / --github-actions (Generate GitHub Actions workflow)\n"
+ exp += " --gha-browsers=BROWSERS (Comma-separated browsers, default: chrome)\n"
+ exp += " --gha-python=VERSIONS (Comma-separated Python versions, default: 3.11)\n"
+ exp += " --gha-os=OS_LIST (Comma-separated OS list, default: ubuntu-latest)\n"
exp += " Output:\n"
exp += " Creates a new folder for running SBase scripts.\n"
exp += " The new folder contains default config files,\n"
@@ -59,6 +120,10 @@ def main():
cr = colorama.Style.RESET_ALL
basic = False
+ gha = False
+ gha_browsers = "chrome"
+ gha_python = "3.11"
+ gha_os = "ubuntu-latest"
help_me = False
error_msg = None
invalid_cmd = None
@@ -84,11 +149,19 @@ def main():
if len(command_args) >= 2:
options = command_args[1:]
for option in options:
- option = option.lower()
- if option == "-h" or option == "--help":
+ option_lower = option.lower()
+ if option_lower == "-h" or option_lower == "--help":
help_me = True
- elif option == "-b" or option == "--basic":
+ elif option_lower == "-b" or option_lower == "--basic":
basic = True
+ elif option_lower == "--gha" or option_lower == "--github-actions":
+ gha = True
+ elif option_lower.startswith("--gha-browsers="):
+ gha_browsers = option[len("--gha-browsers="):]
+ elif option_lower.startswith("--gha-python="):
+ gha_python = option[len("--gha-python="):]
+ elif option_lower.startswith("--gha-os="):
+ gha_os = option[len("--gha-os="):]
else:
invalid_cmd = "\n===> INVALID OPTION: >> %s <<\n" % option
invalid_cmd = invalid_cmd.replace(">> ", ">>" + c5 + " ")
@@ -321,6 +394,23 @@ def main():
file.writelines("\r\n".join(data))
file.close()
+ if gha:
+ workflow_dir = "%s/.github/workflows" % dir_name
+ workflow_file = "%s/seleniumbase.yml" % workflow_dir
+ if os.path.exists(workflow_file):
+ error_msg = (
+ 'Workflow file "%s" already exists!' % workflow_file
+ )
+ error_msg = c5 + "ERROR: " + error_msg + cr
+ invalid_run_command(error_msg)
+ os.makedirs(workflow_dir, exist_ok=True)
+ workflow_content = generate_github_actions_workflow(
+ gha_python, gha_browsers, gha_os
+ )
+ file = open(workflow_file, mode="w+", encoding="utf-8")
+ file.write(workflow_content)
+ file.close()
+
if basic:
data = []
data.append(" %s/" % dir_name)