From 703fc9c0747be37402029b7fac6f9de43eb842c2 Mon Sep 17 00:00:00 2001
From: JeffreyChen
Date: Sat, 4 Apr 2026 22:16:15 +0800
Subject: [PATCH 1/2] Update docs
Update docs
---
README.md | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/README.md b/README.md
index d232a11..130f00a 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
-
+
@@ -701,4 +701,3 @@ WebRunner uses a rotating file handler for logging.
This project is licensed under the [MIT License](LICENSE).
-Copyright (c) 2021~2023 JE-Chen
From c965b5dc32843e728164c815ed369029c6e4a320 Mon Sep 17 00:00:00 2001
From: JeffreyChen
Date: Sat, 4 Apr 2026 22:37:39 +0800
Subject: [PATCH 2/2] Update docs
Update docs
---
docs/source/API/api_index.rst | 4 +-
docs/source/API/utils/assert.rst | 80 +-
docs/source/API/utils/callback.rst | 57 +-
docs/source/API/utils/executor.rst | 315 +++-----
docs/source/API/utils/file_process.rst | 20 +-
docs/source/API/utils/generate_report.rst | 71 +-
docs/source/API/utils/json.rst | 50 +-
docs/source/API/utils/package_manager.rst | 129 ++--
docs/source/API/utils/project.rst | 36 +-
docs/source/API/utils/scheduler.rst | 192 -----
docs/source/API/utils/socket_server.rst | 90 ++-
docs/source/API/utils/test_object.rst | 71 +-
docs/source/API/utils/test_record.rst | 61 ++
docs/source/API/utils/test_reocrd.rst | 10 -
docs/source/API/utils/xml.rst | 54 ++
docs/source/API/wrapper/element.rst | 125 ++--
docs/source/API/wrapper/manager.rst | 70 +-
docs/source/API/wrapper/utils.rst | 119 +--
docs/source/API/wrapper/webdriver.rst | 708 +++++-------------
.../action_executor/action_executor_doc.rst | 401 ++++++++++
.../Eng/doc/assertion/assertion_doc.rst | 98 +++
.../callback_function_doc.rst | 128 +++-
docs/source/Eng/doc/cli/cli_doc.rst | 75 +-
.../doc/create_project/create_project_doc.rst | 81 +-
.../Eng/doc/exception/exception_doc.rst | 84 +++
.../generate_report/generate_report_doc.rst | 119 ++-
.../Eng/doc/installation/installation_doc.rst | 88 ++-
docs/source/Eng/doc/logging/logging_doc.rst | 54 ++
.../package_manager/package_manager_doc.rst | 89 +++
.../Eng/doc/quick_start/quick_start_doc.rst | 101 +++
.../Eng/doc/scheduler/scheduler_doc.rst | 19 -
.../doc/socket_driver/socket_driver_doc.rst | 111 ++-
.../Eng/doc/test_object/test_object_doc.rst | 90 +++
.../Eng/doc/test_record/test_record_doc.rst | 107 +++
.../Eng/doc/web_element/web_element_doc.rst | 162 ++++
.../webdriver_manager_doc.rst | 102 +++
.../webdriver_options_doc.rst | 93 +++
.../webdriver_wrapper_doc.rst | 198 +++++
docs/source/Eng/eng_index.rst | 15 +-
.../action_executor/action_executor_doc.rst | 96 +++
.../source/Zh/doc/assertion/assertion_doc.rst | 61 ++
.../callback_function_doc.rst | 98 ++-
docs/source/Zh/doc/cli/cli_doc.rst | 58 +-
.../doc/create_project/create_project_doc.rst | 63 +-
.../source/Zh/doc/exception/exception_doc.rst | 83 ++
.../generate_report/generate_report_doc.rst | 105 ++-
.../Zh/doc/installation/installation_doc.rst | 88 ++-
docs/source/Zh/doc/logging/logging_doc.rst | 51 ++
.../package_manager/package_manager_doc.rst | 61 ++
.../Zh/doc/quick_start/quick_start_doc.rst | 101 +++
.../source/Zh/doc/scheduler/scheduler_doc.rst | 19 -
.../doc/socket_driver/socket_driver_doc.rst | 82 +-
.../Zh/doc/test_object/test_object_doc.rst | 63 ++
.../Zh/doc/test_record/test_record_doc.rst | 71 ++
.../Zh/doc/web_element/web_element_doc.rst | 142 ++++
.../webdriver_manager_doc.rst | 100 +++
.../webdriver_options_doc.rst | 61 ++
.../webdriver_wrapper_doc.rst | 192 +++++
docs/source/Zh/zh_index.rst | 17 +-
docs/source/conf.py | 26 +-
docs/source/index.rst | 20 +-
61 files changed, 4535 insertions(+), 1600 deletions(-)
delete mode 100644 docs/source/API/utils/scheduler.rst
create mode 100644 docs/source/API/utils/test_record.rst
delete mode 100644 docs/source/API/utils/test_reocrd.rst
create mode 100644 docs/source/Eng/doc/action_executor/action_executor_doc.rst
create mode 100644 docs/source/Eng/doc/assertion/assertion_doc.rst
create mode 100644 docs/source/Eng/doc/exception/exception_doc.rst
create mode 100644 docs/source/Eng/doc/logging/logging_doc.rst
create mode 100644 docs/source/Eng/doc/package_manager/package_manager_doc.rst
create mode 100644 docs/source/Eng/doc/quick_start/quick_start_doc.rst
delete mode 100644 docs/source/Eng/doc/scheduler/scheduler_doc.rst
create mode 100644 docs/source/Eng/doc/test_object/test_object_doc.rst
create mode 100644 docs/source/Eng/doc/test_record/test_record_doc.rst
create mode 100644 docs/source/Eng/doc/web_element/web_element_doc.rst
create mode 100644 docs/source/Eng/doc/webdriver_manager/webdriver_manager_doc.rst
create mode 100644 docs/source/Eng/doc/webdriver_options/webdriver_options_doc.rst
create mode 100644 docs/source/Eng/doc/webdriver_wrapper/webdriver_wrapper_doc.rst
create mode 100644 docs/source/Zh/doc/action_executor/action_executor_doc.rst
create mode 100644 docs/source/Zh/doc/assertion/assertion_doc.rst
create mode 100644 docs/source/Zh/doc/exception/exception_doc.rst
create mode 100644 docs/source/Zh/doc/logging/logging_doc.rst
create mode 100644 docs/source/Zh/doc/package_manager/package_manager_doc.rst
create mode 100644 docs/source/Zh/doc/quick_start/quick_start_doc.rst
delete mode 100644 docs/source/Zh/doc/scheduler/scheduler_doc.rst
create mode 100644 docs/source/Zh/doc/test_object/test_object_doc.rst
create mode 100644 docs/source/Zh/doc/test_record/test_record_doc.rst
create mode 100644 docs/source/Zh/doc/web_element/web_element_doc.rst
create mode 100644 docs/source/Zh/doc/webdriver_manager/webdriver_manager_doc.rst
create mode 100644 docs/source/Zh/doc/webdriver_options/webdriver_options_doc.rst
create mode 100644 docs/source/Zh/doc/webdriver_wrapper/webdriver_wrapper_doc.rst
diff --git a/docs/source/API/api_index.rst b/docs/source/API/api_index.rst
index dd7393b..03d217a 100644
--- a/docs/source/API/api_index.rst
+++ b/docs/source/API/api_index.rst
@@ -12,11 +12,11 @@ WebRunner API Documentation
utils/generate_report.rst
utils/json.rst
utils/package_manager.rst
+ utils/project.rst
utils/socket_server.rst
utils/test_object.rst
- utils/test_reocrd.rst
+ utils/test_record.rst
utils/xml.rst
- utils/scheduler.rst
wrapper/element.rst
wrapper/manager.rst
wrapper/utils.rst
diff --git a/docs/source/API/utils/assert.rst b/docs/source/API/utils/assert.rst
index e0cb67f..0e8d8e3 100644
--- a/docs/source/API/utils/assert.rst
+++ b/docs/source/API/utils/assert.rst
@@ -1,78 +1,92 @@
Assert API
-----
+==========
+
+``je_web_runner.utils.assert_value.result_check``
+
+Functions for validating WebDriver and WebElement properties.
+Raises ``WebRunnerAssertException`` on validation failure.
.. code-block:: python
def _make_webdriver_check_dict(webdriver_to_check: WebDriver) -> dict:
"""
- use to check webdriver current info
- :param webdriver_to_check: what webdriver we want to check
- :return: webdriver detail dict
+ Build a dictionary of the WebDriver's current state for validation.
+
+ :param webdriver_to_check: WebDriver instance to inspect
+ :return: dict of WebDriver properties (name, title, current_url, etc.)
"""
.. code-block:: python
def _make_web_element_check_dict(web_element_to_check: WebElement) -> dict:
"""
- use to check web element current info
- :param web_element_to_check: what web element we want to check
- :return: web element detail dict
+ Build a dictionary of the WebElement's current state for validation.
+
+ :param web_element_to_check: WebElement instance to inspect
+ :return: dict of WebElement properties (tag_name, text, enabled, displayed, etc.)
"""
.. code-block:: python
- def check_value(element_name: str, element_value: typing.Any, result_check_dict: dict) -> None:
+ def check_value(element_name: str, element_value: Any, result_check_dict: dict) -> None:
"""
- use to check state
- :param element_name: the name of element we want to check
- :param element_value: what value element should be
- :param result_check_dict: the dict include data name and value to check check_dict is valid or not
- :return: None
+ Check a single value against a result dictionary.
+
+ :param element_name: key to look up in result_check_dict
+ :param element_value: expected value
+ :param result_check_dict: dictionary of actual values
+ :raises WebRunnerAssertException: if values don't match
"""
.. code-block:: python
def check_values(check_dict: dict, result_check_dict: dict) -> None:
"""
- :param check_dict: dict include data name and value to check
- :param result_check_dict: the dict include data name and value to check check_dict is valid or not
- :return: None
+ Check multiple key-value pairs against a result dictionary.
+
+ :param check_dict: dict of {name: expected_value} pairs
+ :param result_check_dict: dictionary of actual values
+ :raises WebRunnerAssertException: if any value doesn't match
"""
.. code-block:: python
- def check_webdriver_value(element_name: str, element_value: typing.Any, webdriver_to_check: WebDriver) -> None:
+ def check_webdriver_value(element_name: str, element_value: Any, webdriver_to_check: WebDriver) -> None:
"""
- :param element_name: the name of element we want to check
- :param element_value: what value element should be
- :param webdriver_to_check: the dict include data name and value to check result_dict is valid or not
- :return: None
+ Check a single WebDriver property against an expected value.
+
+ :param element_name: property name (e.g., "name", "title")
+ :param element_value: expected value
+ :param webdriver_to_check: WebDriver instance to validate
"""
.. code-block:: python
def check_webdriver_details(webdriver_to_check: WebDriver, result_check_dict: dict) -> None:
"""
- :param webdriver_to_check: what webdriver we want to check
- :param result_check_dict: the dict include data name and value to check result_dict is valid or not
- :return: None
+ Validate multiple WebDriver properties.
+
+ :param webdriver_to_check: WebDriver instance to validate
+ :param result_check_dict: dict of {property: expected_value}
"""
.. code-block:: python
- def check_web_element_value(element_name: str, element_value: typing.Any, web_element_to_check: WebElement) -> None:
+ def check_web_element_value(element_name: str, element_value: Any, web_element_to_check: WebElement) -> None:
"""
- :param element_name: the name of element we want to check
- :param element_value: what value element should be
- :param web_element_to_check: the dict include data name and value to check result_dict is valid or not
- :return: None
+ Check a single WebElement property against an expected value.
+
+ :param element_name: property name (e.g., "tag_name", "enabled")
+ :param element_value: expected value
+ :param web_element_to_check: WebElement instance to validate
"""
.. code-block:: python
def check_web_element_details(web_element_to_check: WebElement, result_check_dict: dict) -> None:
"""
- :param web_element_to_check: what web element we want to check
- :param result_check_dict: the dict include data name and value to check result_dict is valid or not
- :return: None
- """
\ No newline at end of file
+ Validate multiple WebElement properties.
+
+ :param web_element_to_check: WebElement instance to validate
+ :param result_check_dict: dict of {property: expected_value}
+ """
diff --git a/docs/source/API/utils/callback.rst b/docs/source/API/utils/callback.rst
index 1b728a7..d246f80 100644
--- a/docs/source/API/utils/callback.rst
+++ b/docs/source/API/utils/callback.rst
@@ -1,21 +1,44 @@
Callback API
-----
+============
+
+``je_web_runner.utils.callback.callback_function_executor``
+
+Class: CallbackFunctionExecutor
+-------------------------------
+
+Executes trigger functions with callback support.
+Shares the same ``event_dict`` command mapping as the standard Executor.
+
+.. code-block:: python
+
+ class CallbackFunctionExecutor:
+
+ event_dict: dict
+ # Same command mapping as Executor (WR_* commands)
+
+ def callback_function(
+ self,
+ trigger_function_name: str,
+ callback_function: Callable,
+ callback_function_param: Union[dict, list, None] = None,
+ callback_param_method: str = "kwargs",
+ **kwargs
+ ):
+ """
+ Execute a trigger function, then execute a callback function.
+
+ :param trigger_function_name: function name to trigger (must exist in event_dict)
+ :param callback_function: callback function to execute after trigger
+ :param callback_function_param: parameters for callback (dict for kwargs, list for args)
+ :param callback_param_method: "kwargs" or "args"
+ :param kwargs: parameters passed to the trigger function
+ :return: return value of the trigger function
+ :raises CallbackExecutorException: if trigger function not found or invalid param method
+ """
+
+Global Instance
+---------------
.. code-block:: python
- def callback_function(
- self,
- trigger_function_name: str,
- callback_function: typing.Callable,
- callback_function_param: [dict, None] = None,
- callback_param_method: str = "kwargs",
- **kwargs
- ):
- """
- :param trigger_function_name: what function we want to trigger only accept function in event_dict
- :param callback_function: what function we want to callback
- :param callback_function_param: callback function's param only accept dict
- :param callback_param_method: what type param will use on callback function only accept kwargs and args
- :param kwargs: trigger_function's param
- :return: trigger_function_name return value
- """
\ No newline at end of file
+ callback_executor = CallbackFunctionExecutor()
diff --git a/docs/source/API/utils/executor.rst b/docs/source/API/utils/executor.rst
index 2537787..b3d43e5 100644
--- a/docs/source/API/utils/executor.rst
+++ b/docs/source/API/utils/executor.rst
@@ -1,215 +1,144 @@
Executor API
-----
+============
+
+``je_web_runner.utils.executor.action_executor``
+
+Class: Executor
+---------------
+
+The action execution engine that maps command strings to callable functions.
.. code-block:: python
- import builtins
- import sys
- import time
- import types
- from inspect import getmembers, isbuiltin
-
- from je_web_runner.je_web_runner.manager.webrunner_manager import web_runner
- from je_web_runner.utils.exception.exception_tags import add_command_exception_tag
- from je_web_runner.utils.exception.exception_tags import executor_data_error, executor_list_error
- from je_web_runner.utils.exception.exceptions import WebRunnerExecuteException, WebRunnerAddCommandException
- from je_web_runner.utils.generate_report.generate_html_report import generate_html_report
- from je_web_runner.utils.generate_report.generate_html_report import generate_html
- from je_web_runner.utils.generate_report.generate_json_report import generate_json
- from je_web_runner.utils.generate_report.generate_json_report import generate_json_report
- from je_web_runner.utils.generate_report.generate_xml_report import generate_xml
- from je_web_runner.utils.generate_report.generate_xml_report import generate_xml_report
- from je_web_runner.utils.json.json_file.json_file import read_action_json
- from je_web_runner.utils.package_manager.package_manager_class import package_manager
- from je_web_runner.utils.test_object.test_object_record.test_object_record_class import test_object_record
- from je_web_runner.utils.test_record.test_record_class import test_record_instance
-
-
- class Executor(object):
-
- def __init__(self):
- self.event_dict = {
- # webdriver manager
- "WR_get_webdriver_manager": web_runner.new_driver,
- "WR_change_index_of_webdriver": web_runner.change_webdriver,
- "WR_quit": web_runner.quit,
- # test object
- "WR_SaveTestObject": test_object_record.save_test_object,
- "WR_CleanTestObject": test_object_record.clean_record,
- # webdriver wrapper
- "WR_set_driver": web_runner.webdriver_wrapper.set_driver,
- "WR_set_webdriver_options_capability": web_runner.webdriver_wrapper.set_driver,
- "WR_find_element": web_runner.webdriver_wrapper.find_element_with_test_object_record,
- "WR_find_elements": web_runner.webdriver_wrapper.find_elements_with_test_object_record,
- "WR_implicitly_wait": web_runner.webdriver_wrapper.implicitly_wait,
- "WR_explict_wait": web_runner.webdriver_wrapper.explict_wait,
- "WR_to_url": web_runner.webdriver_wrapper.to_url,
- "WR_forward": web_runner.webdriver_wrapper.forward,
- "WR_back": web_runner.webdriver_wrapper.back,
- "WR_refresh": web_runner.webdriver_wrapper.refresh,
- "WR_switch": web_runner.webdriver_wrapper.switch,
- "WR_set_script_timeout": web_runner.webdriver_wrapper.set_script_timeout,
- "WR_set_page_load_timeout": web_runner.webdriver_wrapper.set_page_load_timeout,
- "WR_get_cookies": web_runner.webdriver_wrapper.get_cookies,
- "WR_get_cookie": web_runner.webdriver_wrapper.get_cookie,
- "WR_add_cookie": web_runner.webdriver_wrapper.add_cookie,
- "WR_delete_cookie": web_runner.webdriver_wrapper.delete_cookie,
- "WR_delete_all_cookies": web_runner.webdriver_wrapper.delete_all_cookies,
- "WR_execute": web_runner.webdriver_wrapper.execute,
- "WR_execute_script": web_runner.webdriver_wrapper.execute_script,
- "WR_execute_async_script": web_runner.webdriver_wrapper.execute_async_script,
- "WR_move_to_element": web_runner.webdriver_wrapper.move_to_element_with_test_object,
- "WR_move_to_element_with_offset": web_runner.webdriver_wrapper.move_to_element_with_offset_and_test_object,
- "WR_drag_and_drop": web_runner.webdriver_wrapper.drag_and_drop_with_test_object,
- "WR_drag_and_drop_offset": web_runner.webdriver_wrapper.drag_and_drop_offset_with_test_object,
- "WR_perform": web_runner.webdriver_wrapper.perform,
- "WR_reset_actions": web_runner.webdriver_wrapper.reset_actions,
- "WR_left_click": web_runner.webdriver_wrapper.left_click_with_test_object,
- "WR_left_click_and_hold": web_runner.webdriver_wrapper.left_click_and_hold_with_test_object,
- "WR_right_click": web_runner.webdriver_wrapper.right_click_with_test_object,
- "WR_left_double_click": web_runner.webdriver_wrapper.left_double_click_with_test_object,
- "WR_release": web_runner.webdriver_wrapper.release_with_test_object,
- "WR_press_key": web_runner.webdriver_wrapper.press_key_with_test_object,
- "WR_release_key": web_runner.webdriver_wrapper.release_key_with_test_object,
- "WR_move_by_offset": web_runner.webdriver_wrapper.move_by_offset,
- "WR_pause": web_runner.webdriver_wrapper.pause,
- "WR_send_keys": web_runner.webdriver_wrapper.send_keys,
- "WR_send_keys_to_element": web_runner.webdriver_wrapper.send_keys_to_element_with_test_object,
- "WR_scroll": web_runner.webdriver_wrapper.scroll,
- "WR_check_current_webdriver": web_runner.webdriver_wrapper.check_current_webdriver,
- "WR_maximize_window": web_runner.webdriver_wrapper.maximize_window,
- "WR_fullscreen_window": web_runner.webdriver_wrapper.fullscreen_window,
- "WR_minimize_window": web_runner.webdriver_wrapper.minimize_window,
- "WR_set_window_size": web_runner.webdriver_wrapper.set_window_size,
- "WR_set_window_position": web_runner.webdriver_wrapper.set_window_position,
- "WR_get_window_position": web_runner.webdriver_wrapper.get_window_position,
- "WR_get_window_rect": web_runner.webdriver_wrapper.get_window_rect,
- "WR_set_window_rect": web_runner.webdriver_wrapper.set_window_rect,
- "WR_get_screenshot_as_png": web_runner.webdriver_wrapper.get_screenshot_as_png,
- "WR_get_screenshot_as_base64": web_runner.webdriver_wrapper.get_screenshot_as_base64,
- "WR_get_log": web_runner.webdriver_wrapper.get_log,
- "WR_single_quit": web_runner.webdriver_wrapper.quit,
- # web element
- "WR_element_submit": web_runner.webdriver_element.submit,
- "WR_element_clear": web_runner.webdriver_element.clear,
- "WR_element_get_property": web_runner.webdriver_element.get_property,
- "WR_element_get_dom_attribute": web_runner.webdriver_element.get_dom_attribute,
- "WR_element_get_attribute": web_runner.webdriver_element.get_attribute,
- "WR_element_is_selected": web_runner.webdriver_element.is_selected,
- "WR_element_is_enabled": web_runner.webdriver_element.is_enabled,
- "WR_input_to_element": web_runner.webdriver_element.input_to_element,
- "WR_click_element": web_runner.webdriver_element.click_element,
- "WR_element_is_displayed": web_runner.webdriver_element.is_displayed,
- "WR_element_value_of_css_property": web_runner.webdriver_element.value_of_css_property,
- "WR_element_screenshot": web_runner.webdriver_element.screenshot,
- "WR_element_change_web_element": web_runner.webdriver_element.change_web_element,
- "WR_element_check_current_web_element": web_runner.webdriver_element.check_current_web_element,
- "WR_element_get_select": web_runner.webdriver_element.get_select,
- # init test record
- "WR_set_record_enable": test_record_instance.set_record_enable,
- # generate report
- "WR_generate_html": generate_html,
- "WR_generate_html_report": generate_html_report,
- "WR_generate_json": generate_json,
- "WR_generate_json_report": generate_json_report,
- "WR_generate_xml": generate_xml,
- "WR_generate_xml_report": generate_xml_report,
- # execute
- "WR_execute_action": self.execute_action,
- "WR_execute_files": self.execute_files,
- # Add package
- "WR_add_package_to_executor": package_manager.add_package_to_executor,
- "WR_add_package_to_callback_executor": package_manager.add_package_to_callback_executor,
- }
- # get all builtin function and add to event dict
- for function in getmembers(builtins, isbuiltin):
- self.event_dict.update({str(function[0]): function[1]})
+ class Executor:
- def _execute_event(self, action: list):
+ event_dict: dict
+ # Maps command names (str) to callable functions.
+ # Includes all WR_* commands and Python built-in functions.
+
+ def execute_action(self, action_list: Union[list, dict]) -> dict:
"""
- :param action: execute action
- :return: what event return
+ Execute a sequence of actions.
+
+ :param action_list: list of actions or dict with "webdriver_wrapper" key
+ Format: [["command", {params}], ["command"], ...]
+ :return: dict mapping each action description to its return value
+ :raises WebRunnerExecuteException: if action_list is empty or invalid
"""
- event = self.event_dict.get(action[0])
- if len(action) == 2:
- if isinstance(action[1], dict):
- return event(**action[1])
- else:
- return event(*action[1])
- elif len(action) == 1:
- return event()
- else:
- raise WebRunnerExecuteException(executor_data_error + " " + str(action))
-
- def execute_action(self, action_list: [list, dict]) -> dict:
+
+ def execute_files(self, execute_files_list: list) -> list:
"""
- use to execute action on list
- :param action_list: like this structure
- [
- ["WR_get_webdriver_manager", {"webdriver_name": "firefox"}],
- ["WR_to_url", {"url": "https://www.google.com"}],
- ["WR_quit"]
- ]
- for loop and use execute_event function to execute
- :return: recode string, response as list
+ Execute actions from JSON files.
+
+ :param execute_files_list: list of file paths to JSON action files
+ :return: list of execution result dicts (one per file)
"""
- if type(action_list) is dict:
- action_list = action_list.get("web_runner", None)
- if action_list is None:
- raise WebRunnerExecuteException(executor_list_error)
- execute_record_dict = dict()
- try:
- if len(action_list) > 0 or type(action_list) is not list:
- pass
- else:
- raise WebRunnerExecuteException(executor_list_error)
- except Exception as error:
- print(repr(error), file=sys.stderr)
- for action in action_list:
- try:
- event_response = self._execute_event(action)
- execute_record = "execute: " + str(action)
- execute_record_dict.update({execute_record: event_response})
- except Exception as error:
- print(repr(error), file=sys.stderr)
- print(action, file=sys.stderr)
- execute_record = "execute: " + str(action)
- execute_record_dict.update({execute_record: repr(error)})
- for key, value in execute_record_dict.items():
- print(key)
- print(value)
- return execute_record_dict
- def execute_files(self, execute_files_list: list) -> list:
+ def _execute_event(self, action: list):
"""
- :param execute_files_list: list include execute files path
- :return: every execute detail as list
+ Execute a single action from the event dictionary.
+
+ :param action: ["command_name"] or ["command_name", {kwargs}] or ["command_name", [args]]
+ :return: return value of the executed function
+ :raises WebRunnerExecuteException: if command not found or invalid format
"""
- execute_detail_list = list()
- for file in execute_files_list:
- execute_detail_list.append(self.execute_action(read_action_json(file)))
- return execute_detail_list
+Registered Commands (event_dict)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- executor = Executor()
- package_manager.executor = executor
+**WebDriver Manager:**
+``WR_get_webdriver_manager``, ``WR_change_index_of_webdriver``, ``WR_quit``
+**Test Object:**
+``WR_SaveTestObject``, ``WR_CleanTestObject``
- def add_command_to_executor(command_dict: dict):
- """
- :param command_dict: command dict to add into executor command dict
- :return:None
+**WebDriver Wrapper (Navigation):**
+``WR_to_url``, ``WR_forward``, ``WR_back``, ``WR_refresh``, ``WR_switch``
+
+**WebDriver Wrapper (Element Finding):**
+``WR_find_element``, ``WR_find_elements``
+
+**WebDriver Wrapper (Wait):**
+``WR_implicitly_wait``, ``WR_explict_wait``, ``WR_set_script_timeout``, ``WR_set_page_load_timeout``
+
+**WebDriver Wrapper (Cookies):**
+``WR_get_cookies``, ``WR_get_cookie``, ``WR_add_cookie``, ``WR_delete_cookie``, ``WR_delete_all_cookies``
+
+**WebDriver Wrapper (JavaScript):**
+``WR_execute``, ``WR_execute_script``, ``WR_execute_async_script``
+
+**WebDriver Wrapper (Mouse):**
+``WR_left_click``, ``WR_right_click``, ``WR_left_double_click``,
+``WR_left_click_and_hold``, ``WR_release``,
+``WR_move_to_element``, ``WR_move_to_element_with_offset``, ``WR_move_by_offset``,
+``WR_drag_and_drop``, ``WR_drag_and_drop_offset``
+
+**WebDriver Wrapper (Keyboard):**
+``WR_press_key``, ``WR_release_key``, ``WR_send_keys``, ``WR_send_keys_to_element``
+
+**WebDriver Wrapper (Actions):**
+``WR_perform``, ``WR_reset_actions``, ``WR_pause``, ``WR_scroll``
+
+**WebDriver Wrapper (Window):**
+``WR_maximize_window``, ``WR_fullscreen_window``, ``WR_minimize_window``,
+``WR_set_window_size``, ``WR_set_window_position``, ``WR_get_window_position``,
+``WR_get_window_rect``, ``WR_set_window_rect``
+
+**WebDriver Wrapper (Screenshot/Log):**
+``WR_get_screenshot_as_png``, ``WR_get_screenshot_as_base64``, ``WR_get_log``
+
+**WebDriver Wrapper (Other):**
+``WR_set_driver``, ``WR_set_webdriver_options_capability``,
+``WR_check_current_webdriver``, ``WR_single_quit``
+
+**Web Element:**
+``WR_click_element``, ``WR_input_to_element``,
+``WR_element_submit``, ``WR_element_clear``,
+``WR_element_get_property``, ``WR_element_get_dom_attribute``, ``WR_element_get_attribute``,
+``WR_element_is_selected``, ``WR_element_is_enabled``, ``WR_element_is_displayed``,
+``WR_element_value_of_css_property``, ``WR_element_screenshot``,
+``WR_element_change_web_element``, ``WR_element_check_current_web_element``,
+``WR_element_get_select``
+
+**Test Record:**
+``WR_set_record_enable``
+
+**Report Generation:**
+``WR_generate_html``, ``WR_generate_html_report``,
+``WR_generate_json``, ``WR_generate_json_report``,
+``WR_generate_xml``, ``WR_generate_xml_report``
+
+**Nested Execution:**
+``WR_execute_action``, ``WR_execute_files``
+
+**Package Management:**
+``WR_add_package_to_executor``, ``WR_add_package_to_callback_executor``
+
+**Python Built-ins:**
+All Python built-in functions (``print``, ``len``, ``type``, etc.)
+
+Module-level Functions
+----------------------
+
+.. code-block:: python
+
+ def add_command_to_executor(command_dict: dict) -> None:
"""
- for command_name, command in command_dict.items():
- if isinstance(command, (types.MethodType, types.FunctionType)):
- executor.event_dict.update({command_name: command})
- else:
- raise WebRunnerAddCommandException(add_command_exception_tag)
+ Dynamically add commands to the global Executor.
+ :param command_dict: {command_name: function}
+ :raises WebRunnerAddCommandException: if value is not a function/method
+ """
def execute_action(action_list: list) -> dict:
- return executor.execute_action(action_list)
-
+ """Global convenience function to execute actions."""
def execute_files(execute_files_list: list) -> list:
- return executor.execute_files(execute_files_list)
+ """Global convenience function to execute actions from files."""
+
+Global Instance
+---------------
+
+.. code-block:: python
+
+ executor = Executor()
diff --git a/docs/source/API/utils/file_process.rst b/docs/source/API/utils/file_process.rst
index cd193cf..0ffc9e9 100644
--- a/docs/source/API/utils/file_process.rst
+++ b/docs/source/API/utils/file_process.rst
@@ -1,12 +1,18 @@
File Process API
-----
+================
+
+``je_web_runner.utils.file_process.get_dir_file_list``
.. code-block:: python
- def get_dir_files_as_list(dir_path: str = getcwd(), default_search_file_extension: str = ".json") -> List[str]:
+ def get_dir_files_as_list(
+ dir_path: str = getcwd(),
+ default_search_file_extension: str = ".json"
+ ) -> List[str]:
+ """
+ Recursively walk a directory and return files matching the given extension.
+
+ :param dir_path: directory to search (default: current working directory)
+ :param default_search_file_extension: file extension to filter (default: ".json")
+ :return: list of absolute file paths, or empty list if none found
"""
- get dir file when end with default_search_file_extension
- :param dir_path: which dir we want to walk and get file list
- :param default_search_file_extension: which extension we want to search
- :return: [] if nothing searched or [file1, file2.... files] file was searched
- """
\ No newline at end of file
diff --git a/docs/source/API/utils/generate_report.rst b/docs/source/API/utils/generate_report.rst
index 2f8664e..6743f95 100644
--- a/docs/source/API/utils/generate_report.rst
+++ b/docs/source/API/utils/generate_report.rst
@@ -1,45 +1,76 @@
-Generate report API
-----
+Generate Report API
+===================
+
+``je_web_runner.utils.generate_report``
+
+HTML Report
+-----------
+
+``je_web_runner.utils.generate_report.generate_html_report``
.. code-block:: python
def generate_html() -> str:
"""
- :return: html_string
- """
+ Generate a complete HTML report string from test_record_instance.
-.. code-block:: python
+ The report contains color-coded tables:
+ - aqua (event_table_head) for successful actions
+ - red (failure_table_head) for failed actions
- def generate_html_report(html_name: str = "default_name"):
+ :return: complete HTML document string
+ :raises WebRunnerHTMLException: if no test records exist
"""
- this function will create and save html report on current folder
- :param html_name: save html file name
+
+ def generate_html_report(html_name: str = "default_name") -> None:
+ """
+ Generate and save an HTML report file.
+ Thread-safe (uses threading.Lock).
+
+ :param html_name: output file name without extension (creates {html_name}.html)
"""
+JSON Report
+-----------
+
+``je_web_runner.utils.generate_report.generate_json_report``
+
.. code-block:: python
- def generate_json():
- """
- :return: success test dict and failure test dict
+ def generate_json() -> tuple:
"""
+ Generate JSON report data from test_record_instance.
-.. code-block:: python
+ :return: tuple of (success_dict, failure_dict)
+ """
- def generate_json_report(json_file_name: str = "default_name"):
+ def generate_json_report(json_file_name: str = "default_name") -> None:
"""
- :param json_file_name: save json file's name
+ Generate and save JSON report files.
+ Creates two files: {json_file_name}_success.json and {json_file_name}_failure.json.
+ Thread-safe (uses threading.Lock).
+
+ :param json_file_name: base file name without extension
"""
+XML Report
+----------
+
+``je_web_runner.utils.generate_report.generate_xml_report``
+
.. code-block:: python
- def generate_xml():
+ def generate_xml() -> tuple:
"""
- :return: success test dict and failure test dict
+ Generate XML report data from test_record_instance.
+
+ :return: tuple of (success_records, failure_records)
"""
-.. code-block:: python
+ def generate_xml_report(xml_file_name: str = "default_name") -> None:
+ """
+ Generate and save XML report files.
+ Creates two files: {xml_file_name}_success.xml and {xml_file_name}_failure.xml.
- def generate_xml_report(xml_file_name: str = "default_name"):
+ :param xml_file_name: base file name without extension
"""
- :param xml_file_name: save xml use xml_file_name
- """
\ No newline at end of file
diff --git a/docs/source/API/utils/json.rst b/docs/source/API/utils/json.rst
index d48a756..f4a2a0f 100644
--- a/docs/source/API/utils/json.rst
+++ b/docs/source/API/utils/json.rst
@@ -1,37 +1,45 @@
-Json File API
-----
+JSON Utilities API
+==================
+
+JSON File Operations
+--------------------
+
+``je_web_runner.utils.json.json_file.json_file``
.. code-block:: python
def read_action_json(json_file_path: str) -> list:
"""
- read the action json
- :param json_file_path json file's path to read
- """
+ Read an action JSON file and return its contents.
+ Thread-safe (uses threading.Lock).
-.. code-block:: python
+ :param json_file_path: path to the JSON file
+ :return: list of actions parsed from the file
+ :raises WebRunnerJsonException: if reading or parsing fails
+ """
- def write_action_json(json_save_path: str, action_json: list):
+ def write_action_json(json_save_path: str, action_json: list) -> None:
"""
- write action json
- :param json_save_path json save path
- :param action_json the json str include action to write
+ Write an action list to a JSON file with indentation.
+ Thread-safe (uses threading.Lock).
+
+ :param json_save_path: path to save the JSON file
+ :param action_json: list of actions to write
"""
-.. code-block:: python
+JSON Formatting
+---------------
- def __process_json(json_string: str, **kwargs) -> str:
- """
- :param json_string: full json str (not json type)
- :param kwargs: any another kwargs for dumps
- :return: reformat str
- """
+``je_web_runner.utils.json.json_format.json_process``
.. code-block:: python
def reformat_json(json_string: str, **kwargs) -> str:
"""
- :param json_string: Valid json string
- :param kwargs: __process_json params
- :return: reformat json string
- """
\ No newline at end of file
+ Reformat a JSON string with indentation (pretty-print).
+
+ :param json_string: valid JSON string to reformat
+ :param kwargs: additional kwargs passed to json.dumps
+ :return: reformatted JSON string
+ :raises WebRunnerJsonException: if the string is not valid JSON
+ """
diff --git a/docs/source/API/utils/package_manager.rst b/docs/source/API/utils/package_manager.rst
index 2460401..b31b1d6 100644
--- a/docs/source/API/utils/package_manager.rst
+++ b/docs/source/API/utils/package_manager.rst
@@ -1,101 +1,70 @@
Package Manager API
-----
+===================
+
+``je_web_runner.utils.package_manager.package_manager_class``
+
+Class: PackageManager
+---------------------
+
+Dynamically imports Python packages and registers their functions/classes
+into the Executor or CallbackExecutor event dictionaries.
.. code-block:: python
- from importlib import import_module
- from importlib.util import find_spec
- from inspect import getmembers, isfunction, isbuiltin, isclass
- from sys import stderr
+ class PackageManager:
+ installed_package_dict: dict
+ # Cache of imported packages {name: module}
- class PackageManager(object):
+ executor: Executor
+ # Reference to the global Executor instance
- def __init__(self):
- self.installed_package_dict = {
- }
- self.executor = None
- self.callback_executor = None
+ callback_executor: CallbackFunctionExecutor
+ # Reference to the global CallbackFunctionExecutor instance
def check_package(self, package: str):
"""
- :param package: package to check exists or not
- :return: package if find else None
+ Check if a package exists and import it.
+
+ :param package: package name to check
+ :return: imported module if found, None otherwise
"""
- if self.installed_package_dict.get(package, None) is None:
- found_spec = find_spec(package)
- if found_spec is not None:
- try:
- installed_package = import_module(found_spec.name)
- self.installed_package_dict.update(
- {found_spec.name: installed_package})
- except ModuleNotFoundError as error:
- print(repr(error), file=stderr)
- return self.installed_package_dict.get(package, None)
-
- def add_package_to_executor(self, package):
+
+ def add_package_to_executor(self, package: str) -> None:
"""
- :param package: package's function will add to executor
+ Add all functions and classes from a package to the Executor's event_dict.
+ Functions are registered as "{package}_{function_name}".
+
+ :param package: package name to import and register
"""
- self.add_package_to_target(
- package=package,
- target=self.executor
- )
- def add_package_to_callback_executor(self, package):
+ def add_package_to_callback_executor(self, package: str) -> None:
"""
- :param package: package's function will add to callback_executor
+ Add all functions and classes from a package to the CallbackExecutor's event_dict.
+
+ :param package: package name to import and register
"""
- self.add_package_to_target(
- package=package,
- target=self.callback_executor
- )
- def get_member(self, package, predicate, target):
+ def get_member(self, package: str, predicate, target) -> None:
"""
- :param package: package we want to get member
- :param predicate: predicate
- :param target: which event_dict will be added
+ Extract members matching a predicate and add them to a target executor.
+
+ :param package: package name
+ :param predicate: inspect predicate (isfunction, isbuiltin, isclass)
+ :param target: executor instance with event_dict attribute
"""
- installed_package = self.check_package(package)
- if installed_package is not None and target is not None:
- for member in getmembers(installed_package, predicate):
- target.event_dict.update(
- {str(package) + "_" + str(member[0]): member[1]})
- elif installed_package is None:
- print(repr(ModuleNotFoundError(f"Can't find package {package}")),
- file=stderr)
- else:
- print(f"Executor error {self.executor}", file=stderr)
-
- def add_package_to_target(self, package, target):
+
+ def add_package_to_target(self, package: str, target) -> None:
"""
- :param package: package we want to get member
- :param target: which event_dict will be added
+ Add functions, builtins, and classes from a package to a target executor.
+
+ :param package: package name
+ :param target: executor instance with event_dict attribute
"""
- try:
- self.get_member(
- package=package,
- predicate=isfunction,
- target=target
- )
- self.get_member(
- package=package,
- predicate=isbuiltin,
- target=target
- )
- self.get_member(
- package=package,
- predicate=isfunction,
- target=target
- )
- self.get_member(
- package=package,
- predicate=isclass,
- target=target
- )
- except Exception as error:
- print(repr(error), file=stderr)
-
-
- package_manager: PackageManager = PackageManager()
+
+Global Instance
+---------------
+
+.. code-block:: python
+
+ package_manager = PackageManager()
diff --git a/docs/source/API/utils/project.rst b/docs/source/API/utils/project.rst
index 9a6f2e0..9198f78 100644
--- a/docs/source/API/utils/project.rst
+++ b/docs/source/API/utils/project.rst
@@ -1,33 +1,31 @@
Project API
-----
+===========
+
+``je_web_runner.utils.project.create_project_structure``
.. code-block:: python
def create_dir(dir_name: str) -> None:
"""
- :param dir_name: create dir use dir name
- :return: None
- """
- Path(dir_name).mkdir(
- parents=True,
- exist_ok=True
- )
+ Create a directory (with parents, no error if exists).
-.. code-block:: python
+ :param dir_name: directory path to create
+ """
def create_template(parent_name: str, project_path: str = None) -> None:
"""
- create template ob project dir
- :param parent_name: project name
- :param project_path: project create path
- :return: None
- """
+ Create template files (keyword JSONs and executor Python scripts)
+ in the project directory.
-.. code-block:: python
+ :param parent_name: project name (subdirectory name)
+ :param project_path: project base path (default: current working directory)
+ """
def create_project_dir(project_path: str = None, parent_name: str = "WebRunner") -> None:
"""
- :param parent_name: project name
- :param project_path: project create path
- :return: None
- """
\ No newline at end of file
+ Create project directory structure and generate template files.
+ Creates keyword/ and executor/ subdirectories with sample files.
+
+ :param project_path: path where the project will be created (default: cwd)
+ :param parent_name: top-level project directory name (default: "WebRunner")
+ """
diff --git a/docs/source/API/utils/scheduler.rst b/docs/source/API/utils/scheduler.rst
deleted file mode 100644
index 7ee025f..0000000
--- a/docs/source/API/utils/scheduler.rst
+++ /dev/null
@@ -1,192 +0,0 @@
-Scheduler API
-----
-
-.. code-block:: python
-
- def add_blocking_job(
- self, func: Callable, trigger: str = None, args: Union[list, tuple] = None,
- kwargs: dict = None, id: str = None, name: str = None,
- misfire_grace_time: int = undefined, coalesce: bool = undefined, max_instances: int = undefined,
- next_run_time: datetime = undefined, jobstore: str = 'default', executor: str = 'default',
- replace_existing: bool = False, **trigger_args: Any) -> Job:
- """
- Just an apscheduler add job wrapper.
- :param func: callable (or a textual reference to one) to run at the given time
- :param str|apscheduler.triggers.base.BaseTrigger trigger: trigger that determines when
- ``func`` is called
- :param list|tuple args: list of positional arguments to call func with
- :param dict kwargs: dict of keyword arguments to call func with
- :param str|unicode id: explicit identifier for the job (for modifying it later)
- :param str|unicode name: textual description of the job
- :param int misfire_grace_time: seconds after the designated runtime that the job is still
- allowed to be run (or ``None`` to allow the job to run no matter how late it is)
- :param bool coalesce: run once instead of many times if the scheduler determines that the
- job should be run more than once in succession
- :param int max_instances: maximum number of concurrently running instances allowed for this
- job
- :param datetime next_run_time: when to first run the job, regardless of the trigger (pass
- ``None`` to add the job as paused)
- :param str|unicode jobstore: alias of the job store to store the job in
- :param str|unicode executor: alias of the executor to run the job with
- :param bool replace_existing: ``True`` to replace an existing job with the same ``id``
- (but retain the number of runs from the existing one)
- :return: Job
- """
-
-.. code-block:: python
-
- def add_nonblocking_job(
- self, func: Callable, trigger: str = None, args: Union[list, tuple] = None,
- kwargs: dict = None, id: str = None, name: str = None,
- misfire_grace_time: int = undefined, coalesce: bool = undefined, max_instances: int = undefined,
- next_run_time: datetime = undefined, jobstore: str = 'default', executor: str = 'default',
- replace_existing: bool = False, **trigger_args: Any) -> Job:
- """
- Just an apscheduler add job wrapper.
- :param func: callable (or a textual reference to one) to run at the given time
- :param str|apscheduler.triggers.base.BaseTrigger trigger: trigger that determines when
- ``func`` is called
- :param list|tuple args: list of positional arguments to call func with
- :param dict kwargs: dict of keyword arguments to call func with
- :param str|unicode id: explicit identifier for the job (for modifying it later)
- :param str|unicode name: textual description of the job
- :param int misfire_grace_time: seconds after the designated runtime that the job is still
- allowed to be run (or ``None`` to allow the job to run no matter how late it is)
- :param bool coalesce: run once instead of many times if the scheduler determines that the
- job should be run more than once in succession
- :param int max_instances: maximum number of concurrently running instances allowed for this
- job
- :param datetime next_run_time: when to first run the job, regardless of the trigger (pass
- ``None`` to add the job as paused)
- :param str|unicode jobstore: alias of the job store to store the job in
- :param str|unicode executor: alias of the executor to run the job with
- :param bool replace_existing: ``True`` to replace an existing job with the same ``id``
- (but retain the number of runs from the existing one)
- :return: Job
- """
-
-.. code-block:: python
-
- def get_blocking_scheduler(self) -> BlockingScheduler:
- """
- Return self blocking scheduler
- :return: BlockingScheduler
- """
-
-.. code-block:: python
-
- def get_nonblocking_scheduler(self) -> BackgroundScheduler:
- """
- Return self background scheduler
- :return: BackgroundScheduler
- """
-
-.. code-block:: python
-
- def start_block_scheduler(self, *args: Any, **kwargs: Any) -> None:
- """
- Start blocking scheduler
- :return: None
- """
-
-.. code-block:: python
-
- def start_nonblocking_scheduler(self, *args: Any, **kwargs: Any) -> None:
- """
- Start background scheduler
- :return: None
- """
-
-.. code-block:: python
-
- def start_all_scheduler(self, *args: Any, **kwargs: Any) -> None:
- """
- Start background and blocking scheduler
- :return: None
- """
-
-.. code-block:: python
-
- def add_interval_blocking_secondly(
- self, function: Callable, id: str = None, args: Union[list, tuple] = None,
- kwargs: dict = None, seconds: int = 1, **trigger_args: Any) -> Job:
-
-.. code-block:: python
-
- def add_interval_blocking_minutely(
- self, function: Callable, id: str = None, args: Union[list, tuple] = None,
- kwargs: dict = None, minutes: int = 1, **trigger_args: Any) -> Job:
-
-.. code-block:: python
-
- def add_interval_blocking_hourly(
- self, function: Callable, id: str = None, args: Union[list, tuple] = None,
- kwargs: dict = None, hours: int = 1, **trigger_args: Any) -> Job:
-
-.. code-block:: python
-
- def add_interval_blocking_daily(
- self, function: Callable, id: str = None, args: Union[list, tuple] = None,
- kwargs: dict = None, days: int = 1, **trigger_args: Any) -> Job:
-
-.. code-block:: python
-
- def add_interval_blocking_weekly(
- self, function: Callable, id: str = None, args: Union[list, tuple] = None,
- kwargs: dict = None, weeks: int = 1, **trigger_args: Any) -> Job:
-
-.. code-block:: python
-
- def add_interval_nonblocking_secondly(
- self, function: Callable, id: str = None, args: list = None,
- kwargs: dict = None, seconds: int = 1, **trigger_args: Any) -> Job:
-
-.. code-block:: python
-
- def add_interval_nonblocking_minutely(
- self, function: Callable, id: str = None, args: list = None,
- kwargs: dict = None, minutes: int = 1, **trigger_args: Any) -> Job:
-
-.. code-block:: python
-
- def add_interval_nonblocking_hourly(
- self, function: Callable, id: str = None, args: Union[list, tuple] = None,
- kwargs: dict = None, hours: int = 1, **trigger_args: Any) -> Job:
-
-.. code-block:: python
-
- def add_interval_nonblocking_daily(
- self, function: Callable, id: str = None, args: Union[list, tuple] = None,
- kwargs: dict = None, days: int = 1, **trigger_args: Any) -> Job:
-
-.. code-block:: python
-
- def add_interval_nonblocking_weekly(
- self, function: Callable, id: str = None, args: Union[list, tuple] = None,
- kwargs: dict = None, weeks: int = 1, **trigger_args: Any) -> Job:
-
-.. code-block:: python
-
- def add_cron_blocking(
- self, function: Callable, id: str = None, **trigger_args: Any) -> Job:
-
-.. code-block:: python
-
- def add_cron_nonblocking(
- self, function: Callable, id: str = None, **trigger_args: Any) -> Job:
-
-.. code-block:: python
-
- def remove_blocking_job(self, id: str, jobstore: str = 'default') -> Any:
-
-.. code-block:: python
-
- def remove_nonblocking_job(self, id: str, jobstore: str = 'default') -> Any:
-
-.. code-block:: python
-
- def shutdown_blocking_scheduler(self, wait: bool = False) -> None:
-
-.. code-block:: python
-
- def shutdown_nonblocking_scheduler(self, wait: bool = False) -> None:
diff --git a/docs/source/API/utils/socket_server.rst b/docs/source/API/utils/socket_server.rst
index 913e2e8..38db36b 100644
--- a/docs/source/API/utils/socket_server.rst
+++ b/docs/source/API/utils/socket_server.rst
@@ -1,56 +1,54 @@
-.. code-block:: python
+Socket Server API
+=================
- import json
- import socketserver
- import sys
- import threading
+``je_web_runner.utils.socket_server.web_runner_socket_server``
- from je_web_runner.utils.executor.action_executor import execute_action
+Class: TCPServerHandler
+-----------------------
+.. code-block:: python
class TCPServerHandler(socketserver.BaseRequestHandler):
+ """
+ Request handler for the WebRunner TCP server.
+
+ Receives UTF-8 encoded JSON action strings (max 8192 bytes),
+ executes them via execute_action(), and returns results.
- def handle(self):
- command_string = str(self.request.recv(8192).strip(), encoding="utf-8")
- socket = self.request
- print("command is: " + command_string, flush=True)
- if command_string == "quit_server":
- self.server.shutdown()
- self.server.close_flag = True
- print("Now quit server", flush=True)
- else:
- try:
- execute_str = json.loads(command_string)
- for execute_function, execute_return in execute_action(execute_str).items():
- socket.sendto(str(execute_return).encode("utf-8"), self.client_address)
- socket.sendto("\n".encode("utf-8"), self.client_address)
- socket.sendto("Return_Data_Over_JE".encode("utf-8"), self.client_address)
- socket.sendto("\n".encode("utf-8"), self.client_address)
- except Exception as error:
- try:
- socket.sendto(str(error).encode("utf-8"), self.client_address)
- socket.sendto("\n".encode("utf-8"), self.client_address)
- socket.sendto("Return_Data_Over_JE".encode("utf-8"), self.client_address)
- socket.sendto("\n".encode("utf-8"), self.client_address)
- except Exception as error:
- print(repr(error))
+ Special command: "quit_server" shuts down the server.
+ Response protocol:
+ - Each result is sent as UTF-8 followed by newline
+ - Final message: "Return_Data_Over_JE\n"
+ """
+
+Class: TCPServer
+----------------
+
+.. code-block:: python
class TCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
+ """
+ Multi-threaded TCP server.
+
+ Attributes:
+ close_flag (bool): Set to True when server receives shutdown command.
+ """
+
+Function: start_web_runner_socket_server
+----------------------------------------
+
+.. code-block:: python
+
+ def start_web_runner_socket_server(host: str = "localhost", port: int = 9941) -> TCPServer:
+ """
+ Start the WebRunner TCP Socket Server in a background daemon thread.
+
+ Host and port can be overridden via sys.argv:
+ - 1 arg: host
+ - 2 args: host, port
- def __init__(self, server_address, RequestHandlerClass):
- super().__init__(server_address, RequestHandlerClass)
- self.close_flag: bool = False
-
-
- def start_web_runner_socket_server(host: str = "localhost", port: int = 9941):
- if len(sys.argv) == 2:
- host = sys.argv[1]
- elif len(sys.argv) == 3:
- host = sys.argv[1]
- port = int(sys.argv[2])
- server = TCPServer((host, port), TCPServerHandler)
- server_thread = threading.Thread(target=server.serve_forever)
- server_thread.daemon = True
- server_thread.start()
- return server
+ :param host: server host (default: "localhost")
+ :param port: server port (default: 9941)
+ :return: TCPServer instance (already serving)
+ """
diff --git a/docs/source/API/utils/test_object.rst b/docs/source/API/utils/test_object.rst
index 8769b3b..af50514 100644
--- a/docs/source/API/utils/test_object.rst
+++ b/docs/source/API/utils/test_object.rst
@@ -1,15 +1,76 @@
+Test Object API
+===============
+
+``je_web_runner.utils.test_object.test_object_class``
+
+Class: TestObject
+-----------------
+
.. code-block:: python
- def create_test_object(object_type, test_object_name: str) -> TestObject:
+ class TestObject:
"""
- :param object_type: test object type should in type_list
- :param test_object_name: this test object name
- :return: test object
+ Encapsulates element locator information.
+
+ Attributes:
+ test_object_type (str): locator strategy (e.g., "name", "id", "xpath")
+ test_object_name (str): locator value (e.g., "search", "//div[@id='main']")
"""
+ def __init__(self, test_object_name: str, test_object_type: str):
+ ...
+
+Functions
+---------
+
.. code-block:: python
+ def create_test_object(object_type: str, test_object_name: str) -> TestObject:
+ """
+ Factory function to create a TestObject.
+
+ :param object_type: locator strategy (must be in type_list)
+ :param test_object_name: locator value
+ :return: TestObject instance
+ """
+
def get_test_object_type_list() -> list:
"""
- :return: list include what type should be used to create test object
+ Get available locator types from selenium.webdriver.common.by.By.
+
+ :return: list of type strings
+ ['ID', 'NAME', 'XPATH', 'CSS_SELECTOR', 'CLASS_NAME',
+ 'TAG_NAME', 'LINK_TEXT', 'PARTIAL_LINK_TEXT']
+ """
+
+TestObjectRecord
+----------------
+
+``je_web_runner.utils.test_object.test_object_record.test_object_record_class``
+
+.. code-block:: python
+
+ class TestObjectRecord:
+ """
+ Stores TestObject instances by name for later retrieval.
+ Used by _with_test_object methods and the Action Executor.
+
+ Attributes:
+ test_object_record_dict (dict): {name: TestObject} storage
"""
+
+ def save_test_object(self, test_object_name: str, object_type: str) -> None:
+ """Save a TestObject by name."""
+
+ def remove_test_object(self, test_object_name: str) -> Union[TestObject, bool]:
+ """Remove and return a TestObject by name. Returns False if not found."""
+
+ def clean_record(self) -> None:
+ """Clear all stored TestObjects."""
+
+Global Instance
+~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+ test_object_record = TestObjectRecord()
diff --git a/docs/source/API/utils/test_record.rst b/docs/source/API/utils/test_record.rst
new file mode 100644
index 0000000..a6b8774
--- /dev/null
+++ b/docs/source/API/utils/test_record.rst
@@ -0,0 +1,61 @@
+Test Record API
+===============
+
+``je_web_runner.utils.test_record.test_record_class``
+
+Class: TestRecord
+-----------------
+
+.. code-block:: python
+
+ class TestRecord:
+ """
+ Manages recording of test actions for audit and report generation.
+
+ Attributes:
+ test_record_list (list): list of recorded action dicts
+ init_record (bool): whether recording is enabled (default: False)
+ """
+
+ def set_record_enable(self, set_enable: bool = True) -> None:
+ """
+ Enable or disable test recording.
+
+ :param set_enable: True to enable, False to disable
+ """
+
+ def clean_record(self) -> None:
+ """Clear all recorded actions."""
+
+Function: record_action_to_list
+-------------------------------
+
+.. code-block:: python
+
+ def record_action_to_list(
+ function_name: str,
+ local_param: Union[dict, None],
+ program_exception: Union[Exception, None] = None
+ ) -> None:
+ """
+ Record a function execution to the global test_record_instance.
+
+ Each record is a dict:
+ {
+ "function_name": str,
+ "local_param": dict or None,
+ "time": str (timestamp),
+ "program_exception": str ("None" or exception repr)
+ }
+
+ :param function_name: name of the executed function
+ :param local_param: parameters passed to the function
+ :param program_exception: exception that occurred (None if success)
+ """
+
+Global Instance
+---------------
+
+.. code-block:: python
+
+ test_record_instance = TestRecord()
diff --git a/docs/source/API/utils/test_reocrd.rst b/docs/source/API/utils/test_reocrd.rst
deleted file mode 100644
index a158d2a..0000000
--- a/docs/source/API/utils/test_reocrd.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-.. code-block:: python
-
- def record_action_to_list(function_name: str, local_param: Union[vars, None],
- program_exception: Union[Exception, None] = None):
- """
- :param function_name: what function call this method
- :param local_param: what param used
- :param program_exception: what exception happened
- :return: None
- """
\ No newline at end of file
diff --git a/docs/source/API/utils/xml.rst b/docs/source/API/utils/xml.rst
index e69de29..df2c41e 100644
--- a/docs/source/API/utils/xml.rst
+++ b/docs/source/API/utils/xml.rst
@@ -0,0 +1,54 @@
+XML Utilities API
+=================
+
+``je_web_runner.utils.xml.xml_file.xml_file``
+
+Class: XMLParser
+----------------
+
+.. code-block:: python
+
+ class XMLParser:
+ """
+ XML parser that supports parsing from string or file.
+
+ Attributes:
+ element_tree: xml.etree.ElementTree module
+ tree: parsed ElementTree
+ xml_root: root Element of the parsed XML
+ xml_from_type (str): source type ("string" or "file")
+ """
+
+ def __init__(self, xml_string: str, xml_type: str = "string"):
+ """
+ :param xml_string: XML content string or file path
+ :param xml_type: "string" to parse from string, "file" to parse from file
+ :raises XMLTypeException: if xml_type is not "string" or "file"
+ """
+
+ def xml_parser_from_string(self, **kwargs) -> Element:
+ """
+ Parse XML from a string.
+
+ :return: root Element
+ """
+
+ def xml_parser_from_file(self, **kwargs) -> Element:
+ """
+ Parse XML from a file.
+
+ :return: root Element
+ """
+
+Function: reformat_xml_file
+---------------------------
+
+.. code-block:: python
+
+ def reformat_xml_file(xml_string: str) -> str:
+ """
+ Pretty-print an XML string with indentation.
+
+ :param xml_string: XML string to format
+ :return: formatted XML string
+ """
diff --git a/docs/source/API/wrapper/element.rst b/docs/source/API/wrapper/element.rst
index 1d6f76f..9bf35dd 100644
--- a/docs/source/API/wrapper/element.rst
+++ b/docs/source/API/wrapper/element.rst
@@ -1,95 +1,74 @@
Element API
-----
+===========
-.. code-block:: python
+``je_web_runner.element.web_element_wrapper``
- def submit(self) -> None:
- """
- current web element submit
- :return: None
- """
+Class: WebElementWrapper
+------------------------
- def clear(self) -> None:
- """
- current web element clear
- :return: None
- """
+Wraps Selenium WebElement with convenience methods for interaction and validation.
- def get_property(self, name: str) -> str:
- """
- :param name: name of property
- :return: property value as str
- """
+**Attributes:**
- def get_dom_attribute(self, name: str) -> str:
- """
- :param name: name of dom
- :return: dom attribute value as str
- """
+- ``current_web_element`` (``WebElement | None``): currently active element
+- ``current_web_element_list`` (``List[WebElement] | None``): list of found elements
- def get_attribute(self, name: str) -> str:
- """
- :param name: name of web element
- :return:web element attribute value as str
- """
+**Methods:**
- def is_selected(self) -> bool:
- """
- check current web element is selected or not
- :return: True or False
- """
+.. code-block:: python
- def is_enabled(self) -> bool:
- """
- check current web element is enable or not
- :return: True or False
- """
+ def click_element(self) -> None:
+ """Click the current WebElement."""
def input_to_element(self, input_value: str) -> None:
- """
- input value to current web element
- :param input_value: what value we want to input to current web element
- :return: None
- """
+ """Type text into the current WebElement via send_keys."""
- def click_element(self) -> None:
- """
- click current web element
- :return: None
- """
+ def clear(self) -> None:
+ """Clear the content of the current WebElement."""
- def is_displayed(self) -> bool:
- """
- check current web element is displayed or not
- :return: True or False
- """
+ def submit(self) -> None:
+ """Submit the current WebElement's form."""
- def value_of_css_property(self, property_name: str) -> str:
- """
- :param property_name: name of property
- :return: css property value as str
- """
+ def get_attribute(self, name: str) -> str | None:
+ """Get an HTML attribute value."""
- def screenshot(self, filename: str) -> bool:
- """
- :param filename: full file name not need .png extension
- :return: Save True or not
- """
+ def get_property(self, name: str) -> None | str | bool | WebElement | dict:
+ """Get a JavaScript property value."""
+
+ def get_dom_attribute(self, name: str) -> str | None:
+ """Get a DOM attribute value."""
+
+ def is_displayed(self) -> bool | None:
+ """Check if the element is visible."""
+
+ def is_enabled(self) -> bool | None:
+ """Check if the element is enabled."""
+
+ def is_selected(self) -> bool | None:
+ """Check if the element is selected (checkbox/radio)."""
+
+ def value_of_css_property(self, property_name: str) -> str | None:
+ """Get a CSS property value."""
+
+ def screenshot(self, filename: str) -> bool | None:
+ """Take a screenshot of the element. Saves as {filename}.png."""
def change_web_element(self, element_index: int) -> None:
- """
- :param element_index: change to web element index
- :return: web element list [element_index]
- """
+ """Switch active element to one from current_web_element_list by index."""
def check_current_web_element(self, check_dict: dict) -> None:
"""
- :param check_dict: check web element dict {name: should be value}
- :return: None
+ Validate the current WebElement's properties.
+ :param check_dict: {property_name: expected_value}
+ :raises WebRunnerAssertException: if validation fails
"""
- def get_select(self) -> Select:
- """
- get Select(current web element)
- :return: Select(current web element)
- """
+ def get_select(self) -> Select | None:
+ """Get a Selenium Select wrapper for dropdown elements."""
+
+Global Instance
+---------------
+
+.. code-block:: python
+
+ web_element_wrapper = WebElementWrapper()
diff --git a/docs/source/API/wrapper/manager.rst b/docs/source/API/wrapper/manager.rst
index e2c0d31..dfb1518 100644
--- a/docs/source/API/wrapper/manager.rst
+++ b/docs/source/API/wrapper/manager.rst
@@ -1,46 +1,72 @@
Manager API
-----
+===========
+
+``je_web_runner.manager.webrunner_manager``
+
+Class: WebdriverManager
+-----------------------
+
+Manages multiple WebDriver instances for parallel browser automation.
+
+**Attributes:**
+
+- ``webdriver_wrapper`` (``WebDriverWrapper``): WebDriver operations wrapper
+- ``webdriver_element`` (``WebElementWrapper``): element operations wrapper
+- ``current_webdriver`` (``WebDriver | None``): currently active WebDriver
+
+**Methods:**
.. code-block:: python
- def new_driver(self, webdriver_name: str, **kwargs) -> None:
+ def new_driver(self, webdriver_name: str, options: List[str] = None, **kwargs) -> None:
"""
- use to create new webdriver instance
- :param webdriver_name: which webdriver we want to use [chrome, chromium, firefox, edge, ie]
- :param kwargs: webdriver download manager param
- :return: None
+ Create a new WebDriver instance and add it to the managed list.
+
+ :param webdriver_name: browser name [chrome, chromium, firefox, edge, ie]
+ :param options: browser startup arguments (e.g., ["--headless"])
+ :param kwargs: additional parameters passed to set_driver()
"""
def change_webdriver(self, index_of_webdriver: int) -> None:
"""
- change to target webdriver
- :param index_of_webdriver: change current webdriver to choose index webdriver
- :return: None
+ Switch the active WebDriver to the one at the given index.
+
+ :param index_of_webdriver: index in the internal WebDriver list
"""
def close_current_webdriver(self) -> None:
- """
- close current webdriver
- :return: None
- """
+ """Close and remove the currently active WebDriver."""
def close_choose_webdriver(self, webdriver_index: int) -> None:
"""
- close choose webdriver
- :param webdriver_index: close choose webdriver on current webdriver list
- :return: None
+ Close a specific WebDriver by index.
+
+ :param webdriver_index: index in the internal WebDriver list
"""
def quit(self) -> None:
"""
- close and quit all webdriver instance
- :return: None
+ Close and quit ALL managed WebDriver instances.
+ Also cleans up all TestObjectRecord entries.
"""
+Factory Function
+----------------
+
+.. code-block:: python
+
def get_webdriver_manager(webdriver_name: str, **kwargs) -> WebdriverManager:
"""
- use to get webdriver instance
- :param webdriver_name: which webdriver we want to use [chrome, chromium, firefox, edge, ie]
- :param kwargs: webdriver download manager param
- :return: Webdriver manager instance
+ Get the global WebdriverManager and create a new driver.
+
+ :param webdriver_name: browser name [chrome, chromium, firefox, edge, ie]
+ :param kwargs: additional parameters (e.g., options)
+ :return: global WebdriverManager instance
"""
+
+Global Instance
+---------------
+
+.. code-block:: python
+
+ web_runner = WebdriverManager()
diff --git a/docs/source/API/wrapper/utils.rst b/docs/source/API/wrapper/utils.rst
index 8513992..087858d 100644
--- a/docs/source/API/wrapper/utils.rst
+++ b/docs/source/API/wrapper/utils.rst
@@ -1,100 +1,51 @@
-Utils API
-----
+Selenium Utilities API
+======================
+
+Desired Capabilities
+--------------------
-* Desired Capabilities
+``je_web_runner.utils.selenium_utils_wrapper.desired_capabilities.desired_capabilities``
.. code-block:: python
- def get_desired_capabilities_keys() -> Union[str, Any]:
+ def get_desired_capabilities_keys() -> dict_keys:
"""
- :return: return all webdriver you can get desired capabilities
+ Get available WebDriver/browser names.
+
+ :return: dict_keys of available browsers (e.g., 'CHROME', 'FIREFOX', 'EDGE', ...)
"""
- def get_desired_capabilities(webdriver_name: str) -> \
- [
- DesiredCapabilities.FIREFOX.copy(),
- DesiredCapabilities.CHROME.copy(),
- DesiredCapabilities.EDGE.copy(),
- DesiredCapabilities.SAFARI.copy(),
- ]:
+ def get_desired_capabilities(webdriver_name: str) -> dict:
"""
- choose webdriver to get desired capabilities
- :param webdriver_name: name to get desired capabilities
- :return: desired capabilities
+ Get DesiredCapabilities for a specific browser.
+
+ :param webdriver_name: browser name (e.g., "CHROME", "FIREFOX")
+ :return: capabilities dict
"""
-* keys
+Keys
+----
-.. code-block:: python
+``je_web_runner.utils.selenium_utils_wrapper.keys.selenium_keys``
+
+Re-export of ``selenium.webdriver.common.keys.Keys``.
- class Keys:
- """Set of special keys codes."""
+Provides constants for special keyboard keys:
- NULL = "\ue000"
- CANCEL = "\ue001" # ^break
- HELP = "\ue002"
- BACKSPACE = "\ue003"
- BACK_SPACE = BACKSPACE
- TAB = "\ue004"
- CLEAR = "\ue005"
- RETURN = "\ue006"
- ENTER = "\ue007"
- SHIFT = "\ue008"
- LEFT_SHIFT = SHIFT
- CONTROL = "\ue009"
- LEFT_CONTROL = CONTROL
- ALT = "\ue00a"
- LEFT_ALT = ALT
- PAUSE = "\ue00b"
- ESCAPE = "\ue00c"
- SPACE = "\ue00d"
- PAGE_UP = "\ue00e"
- PAGE_DOWN = "\ue00f"
- END = "\ue010"
- HOME = "\ue011"
- LEFT = "\ue012"
- ARROW_LEFT = LEFT
- UP = "\ue013"
- ARROW_UP = UP
- RIGHT = "\ue014"
- ARROW_RIGHT = RIGHT
- DOWN = "\ue015"
- ARROW_DOWN = DOWN
- INSERT = "\ue016"
- DELETE = "\ue017"
- SEMICOLON = "\ue018"
- EQUALS = "\ue019"
+**Standard keys:**
+``NULL``, ``CANCEL``, ``HELP``, ``BACKSPACE``, ``TAB``, ``CLEAR``,
+``RETURN``, ``ENTER``, ``SHIFT``, ``LEFT_SHIFT``, ``CONTROL``, ``LEFT_CONTROL``,
+``ALT``, ``LEFT_ALT``, ``PAUSE``, ``ESCAPE``, ``SPACE``,
+``PAGE_UP``, ``PAGE_DOWN``, ``END``, ``HOME``,
+``LEFT``, ``UP``, ``RIGHT``, ``DOWN``,
+``INSERT``, ``DELETE``, ``SEMICOLON``, ``EQUALS``
- NUMPAD0 = "\ue01a" # number pad keys
- NUMPAD1 = "\ue01b"
- NUMPAD2 = "\ue01c"
- NUMPAD3 = "\ue01d"
- NUMPAD4 = "\ue01e"
- NUMPAD5 = "\ue01f"
- NUMPAD6 = "\ue020"
- NUMPAD7 = "\ue021"
- NUMPAD8 = "\ue022"
- NUMPAD9 = "\ue023"
- MULTIPLY = "\ue024"
- ADD = "\ue025"
- SEPARATOR = "\ue026"
- SUBTRACT = "\ue027"
- DECIMAL = "\ue028"
- DIVIDE = "\ue029"
+**Number pad:**
+``NUMPAD0`` through ``NUMPAD9``,
+``MULTIPLY``, ``ADD``, ``SEPARATOR``, ``SUBTRACT``, ``DECIMAL``, ``DIVIDE``
- F1 = "\ue031" # function keys
- F2 = "\ue032"
- F3 = "\ue033"
- F4 = "\ue034"
- F5 = "\ue035"
- F6 = "\ue036"
- F7 = "\ue037"
- F8 = "\ue038"
- F9 = "\ue039"
- F10 = "\ue03a"
- F11 = "\ue03b"
- F12 = "\ue03c"
+**Function keys:**
+``F1`` through ``F12``
- META = "\ue03d"
- COMMAND = "\ue03d"
- ZENKAKU_HANKAKU = "\ue040"
+**Special:**
+``META``, ``COMMAND``, ``ZENKAKU_HANKAKU``
diff --git a/docs/source/API/wrapper/webdriver.rst b/docs/source/API/wrapper/webdriver.rst
index 015fc64..af1c705 100644
--- a/docs/source/API/wrapper/webdriver.rst
+++ b/docs/source/API/wrapper/webdriver.rst
@@ -1,528 +1,206 @@
WebDriver API
-----
+=============
+
+Options Configuration
+---------------------
+
+``je_web_runner.webdriver.webdriver_with_options``
-* With options
.. code-block:: python
- def set_webdriver_options_argument(webdriver_name: str, argument_iterable: [list, set]) -> \
- Union[
- webdriver.chrome.options.Options, webdriver.chrome.options.Options,
- webdriver.edge.options.Options, webdriver.ie.options.Options
- ]:
+ def set_webdriver_options_argument(
+ webdriver_name: str,
+ argument_iterable: Union[List[str], Set[str]]
+ ) -> Options:
"""
- :param webdriver_name: use name to open webdriver manager and download webdriver
- :param argument_iterable: start webdriver argument
- :return: webdriver
+ Set browser startup arguments.
+
+ :param webdriver_name: browser name (chrome, firefox, edge, etc.)
+ :param argument_iterable: list of arguments (e.g., ["--headless", "--disable-gpu"])
+ :return: configured Options object
+ :raises WebRunnerOptionsWrongTypeException: if argument_iterable is wrong type
+ """
+
+ def set_webdriver_options_capability_wrapper(
+ webdriver_name: str,
+ key_and_vale_dict: dict
+ ) -> Options:
"""
+ Set browser capabilities.
+
+ :param webdriver_name: browser name
+ :param key_and_vale_dict: capabilities dict (e.g., {"acceptInsecureCerts": True})
+ :return: configured Options object
+ """
+
+WebDriverWrapper
+----------------
+
+``je_web_runner.webdriver.webdriver_wrapper``
+
+Class: WebDriverWrapper
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Core WebDriver wrapper with 80+ methods.
+
+**Attributes:**
+
+- ``current_webdriver`` (``WebDriver | None``): active WebDriver
+- ``_webdriver_name`` (``str | None``): name of current driver
+- ``_action_chain`` (``ActionChains | None``): action chains instance
+
+**Driver Setup:**
+
+.. code-block:: python
+
+ def set_driver(self, webdriver_name: str, webdriver_manager_option_dict: dict = None,
+ options: List[str] = None, **kwargs) -> WebDriver:
+ """Start a new WebDriver instance."""
+
+ def set_webdriver_options_capability(self, key_and_vale_dict: dict) -> Options:
+ """Set capabilities on current WebDriver's options."""
+
+**Element Finding:**
+
+.. code-block:: python
+
+ def find_element(self, test_object: TestObject) -> WebElement | None:
+ """Find a single element using TestObject locator."""
+
+ def find_elements(self, test_object: TestObject) -> list[WebElement] | None:
+ """Find multiple elements using TestObject locator."""
+
+ def find_element_with_test_object_record(self, element_name: str) -> WebElement | None:
+ """Find element using a saved TestObjectRecord name."""
+
+ def find_elements_with_test_object_record(self, element_name: str) -> list[WebElement] | None:
+ """Find multiple elements using a saved TestObjectRecord name."""
+
+**Wait:**
+
+.. code-block:: python
+
+ def implicitly_wait(self, time_to_wait: int) -> None:
+ def explict_wait(self, wait_condition, timeout: int = 10, poll_frequency: float = 0.5):
+ def set_script_timeout(self, timeout: int) -> None:
+ def set_page_load_timeout(self, timeout: int) -> None:
+
+**Navigation:**
+
+.. code-block:: python
+
+ def to_url(self, url: str) -> None:
+ def forward(self) -> None:
+ def back(self) -> None:
+ def refresh(self) -> None:
+
+**Context Switching:**
- def set_webdriver_options_capability_wrapper(webdriver_name: str, key_and_vale_dict: dict) -> \
- Union[
- webdriver.chrome.options.Options, webdriver.chrome.options.Options,
- webdriver.edge.options.Options, webdriver.ie.options.Options
- ]:
+.. code-block:: python
+
+ def switch(self, switch_type: str, switch_value: str = None) -> None:
"""
- :param webdriver_name: use name to open webdriver manager and download webdriver
- :param key_and_vale_dict: set webdriver options capability
- :return: webdriver
+ Switch context. Supported types:
+ active_element, default_content, frame, parent_frame, window, alert
"""
-* WebDriverWrapper
+**Cookie Management:**
+
+.. code-block:: python
+
+ def get_cookies(self) -> list:
+ def get_cookie(self, name: str) -> dict:
+ def add_cookie(self, cookie_dict: dict) -> None:
+ def delete_cookie(self, name: str) -> None:
+ def delete_all_cookies(self) -> None:
+
+**JavaScript:**
+
+.. code-block:: python
+
+ def execute(self, script: str, *args) -> Any:
+ def execute_script(self, script: str, *args) -> Any:
+ def execute_async_script(self, script: str, *args) -> Any:
+
+**Mouse Actions:**
+
+.. code-block:: python
+
+ def left_click(self) -> None:
+ def left_click_with_test_object(self, element_name: str) -> None:
+ def right_click(self) -> None:
+ def right_click_with_test_object(self, element_name: str) -> None:
+ def left_double_click(self) -> None:
+ def left_double_click_with_test_object(self, element_name: str) -> None:
+ def left_click_and_hold(self) -> None:
+ def left_click_and_hold_with_test_object(self, element_name: str) -> None:
+ def release(self) -> None:
+ def release_with_test_object(self, element_name: str) -> None:
+ def move_to_element(self, element: WebElement) -> None:
+ def move_to_element_with_test_object(self, element_name: str) -> None:
+ def move_to_element_with_offset(self, element, offset_x: int, offset_y: int) -> None:
+ def move_to_element_with_offset_and_test_object(self, element_name: str, offset_x: int, offset_y: int) -> None:
+ def drag_and_drop(self, source, target) -> None:
+ def drag_and_drop_with_test_object(self, source_name: str, target_name: str) -> None:
+ def drag_and_drop_offset(self, element, offset_x: int, offset_y: int) -> None:
+ def drag_and_drop_offset_with_test_object(self, element_name: str, offset_x: int, offset_y: int) -> None:
+ def move_by_offset(self, offset_x: int, offset_y: int) -> None:
+
+**Keyboard:**
+
+.. code-block:: python
+
+ def press_key(self, key) -> None:
+ def press_key_with_test_object(self, key) -> None:
+ def release_key(self, key) -> None:
+ def release_key_with_test_object(self, key) -> None:
+ def send_keys(self, keys: str) -> None:
+ def send_keys_to_element(self, element, keys: str) -> None:
+ def send_keys_to_element_with_test_object(self, element_name: str, keys: str) -> None:
+
+**Action Chain:**
+
+.. code-block:: python
+
+ def perform(self) -> None:
+ def reset_actions(self) -> None:
+ def pause(self, duration: int) -> None:
+ def scroll(self, offset_x: int, offset_y: int) -> None:
+
+**Window Management:**
+
+.. code-block:: python
+
+ def maximize_window(self) -> None:
+ def fullscreen_window(self) -> None:
+ def minimize_window(self) -> None:
+ def set_window_size(self, width: int, height: int) -> None:
+ def set_window_position(self, x: int, y: int) -> None:
+ def get_window_position(self) -> dict:
+ def get_window_rect(self) -> dict:
+ def set_window_rect(self, x=None, y=None, width=None, height=None) -> None:
+
+**Screenshots & Logging:**
+
+.. code-block:: python
+
+ def get_screenshot_as_png(self) -> bytes:
+ def get_screenshot_as_base64(self) -> str:
+ def get_log(self, log_type: str) -> list:
+
+**Validation & Quit:**
+
+.. code-block:: python
+
+ def check_current_webdriver(self, check_dict: dict) -> None:
+ """Validate WebDriver properties against expected values."""
+
+ def quit(self) -> None:
+ """Quit the current single WebDriver."""
+
+Global Instance
+~~~~~~~~~~~~~~~
.. code-block:: python
- def set_driver(self, webdriver_name: str,
- webdriver_manager_option_dict: dict = None, **kwargs) -> \
- Union[
- webdriver.Chrome,
- webdriver.Chrome,
- webdriver.Firefox,
- webdriver.Edge,
- webdriver.Ie,
- webdriver.Safari,
- ]:
- """
- :param webdriver_name: which webdriver we want to use
- :param webdriver_manager_option_dict: if you want to set webdriver download manager
- :param kwargs: used to catch var
- :return: current use webdriver
- """
-
- def set_webdriver_options_capability(self, key_and_vale_dict: dict) -> \
- Union[
- webdriver.Chrome,
- webdriver.Chrome,
- webdriver.Firefox,
- webdriver.Edge,
- webdriver.Ie,
- webdriver.Safari,
- ]:
- """
- :param key_and_vale_dict: use to set webdriver capability
- :return: current webdriver
- """
-
- def find_element(self, test_object: TestObject) -> WebElement:
- """
- :param test_object: use test object to find element
- :return: fined web element
- """
-
- def find_elements(self, test_object: TestObject) -> List[WebElement]:
- """
- :param test_object: use test object to find elements
- :return: list include fined web element
- """
-
- def find_element_with_test_object_record(self, element_name: str) -> WebElement:
- """
- this is executor use but still can normally use
- :param element_name: test object name
- :return: fined web element
- """
-
- def find_elements_with_test_object_record(self, element_name: str) -> List[WebElement]:
- """
- this is executor use but still can normally use
- :param element_name: test object name
- :return: list include fined web element
- """
-
- def implicitly_wait(self, time_to_wait: int) -> None:
- """
- selenium implicitly_wait
- :param time_to_wait: how much time we want to wait
- :return: None
- """
-
- def explict_wait(self, wait_time: int, method: typing.Callable, until_type: bool = True):
- """
- selenium explict_wait
- :param wait_time: how much time we want to wait if over-time will raise an exception
- :param method: a program statement should be return True or False
- :param until_type: what type until wait True is until False is until_not
- :return:
- """
-
- def to_url(self, url: str) -> None:
- """
- to url
- :param url: what url we want redirect to
- :return: None
- """
-
- def forward(self) -> None:
- """
- forward current page
- :return: None
- """
-
- def back(self) -> None:
- """
- back current page
- :return: None
- """
-
- def refresh(self) -> None:
- """
- refresh current page
- :return: None
- """
-
- def switch(self, switch_type: str, switch_target_name: str = None):
- """
- switch to target element
- :param switch_type: what type switch? one of [active_element, default_content, frame,
- parent_frame, window, alert]
- :param switch_target_name: what target we want to switch use name to search
- :return: what we switch to
- """
-
- def set_script_timeout(self, time_to_wait: int) -> None:
- """
- set max script execute time
- :param time_to_wait: how much time we want to wait if over-time will raise an exception
- :return: None
- """
-
- def set_page_load_timeout(self, time_to_wait: int) -> None:
- """
- set page load max wait time
- :param time_to_wait: how much time we want to wait if over-time will raise an exception
- :return: None
- """
-
- def get_cookies(self) -> List[dict]:
- """
- get current page cookies
- :return: cookies as list
- """
-
- def get_cookie(self, name: str) -> dict:
- """
- use to get current page cookie
- :param name: use cookie name to find cookie
- :return: {cookie_name: value}
- """
-
- def add_cookie(self, cookie_dict: dict) -> None:
- """
- use to add cookie to current page
- :param cookie_dict: {cookie_name: value}
- :return: None
- """
-
- def delete_cookie(self, name) -> None:
- """
- use to delete current page cookie
- :param name: use name to find cookie
- :return: None
- """
-
- def delete_all_cookies(self) -> None:
- """
- delete current page all cookies
- :return: None
- """
-
- def execute(self, driver_command: str, params: dict = None) -> dict:
- """
- :param driver_command: webdriver command
- :param params: webdriver command params
- :return: after execute dict
- """
-
- def execute_script(self, script: str, *args) -> None:
- """
- execute script
- :param script: script to execute
- :param args: script args
- :return: None
- """
-
- def execute_async_script(self, script: str, *args):
- """
- execute script async
- :param script:script to execute
- :param args: script args
- :return: None
- """
-
- def move_to_element(self, targe_element: WebElement) -> None:
- """
- move mouse to target web element
- :param targe_element: target web element
- :return: None
- """
-
- def move_to_element_with_test_object(self, element_name: str):
- """
- move mouse to target web element use test object
- :param element_name: test object name
- :return: None
- """
-
- def move_to_element_with_offset(self, target_element: WebElement, offset_x: int, offset_y: int) -> None:
- """
- move to target element with offset
- :param target_element: what target web element we want to move to
- :param offset_x: offset x
- :param offset_y: offset y
- :return: None
- """
-
- def move_to_element_with_offset_and_test_object(self, element_name: str, offset_x: int, offset_y: int) -> None:
- """
- move to target element with offset use test object
- :param element_name: test object name
- :param offset_x: offset x
- :param offset_y: offset y
- :return: None
- """
-
- def drag_and_drop(self, web_element: WebElement, targe_element: WebElement) -> None:
- """
- drag web element to target element then drop
- :param web_element: which web element we want to drag and drop
- :param targe_element: target web element to drop
- :return: None
- """
-
- def drag_and_drop_with_test_object(self, element_name: str, target_element_name: str):
- """
- drag web element to target element then drop use testobject
- :param element_name: which web element we want to drag and drop use name to find
- :param target_element_name: target web element to drop use name to find
- :return: None
- """
-
- def drag_and_drop_offset(self, web_element: WebElement, target_x: int, target_y: int) -> None:
- """
- drag web element to target element then drop with offset
- :param web_element: which web element we want to drag and drop with offset
- :param target_x: offset x
- :param target_y: offset y
- :return: None
- """
-
- def drag_and_drop_offset_with_test_object(self, element_name: str, offset_x: int, offset_y: int) -> None:
- """
- drag web element to target element then drop with offset and test object
- :param element_name: test object name
- :param offset_x: offset x
- :param offset_y: offset y
- :return: None
- """
-
- def perform(self) -> None:
- """
- perform actions
- :return: None
- """
-
- def reset_actions(self) -> None:
- """
- clear actions
- :return: None
- """
-
- def left_click(self, on_element: WebElement = None) -> None:
- """
- left click mouse on current mouse position or click on web element
- :param on_element: can be None or web element
- :return: None
- """
-
- def left_click_with_test_object(self, element_name: str = None) -> None:
- """
- left click mouse on current mouse position or click on web element
- find use test object name
- :param element_name: test object name
- :return: None
- """
-
- def left_click_and_hold(self, on_element: WebElement = None) -> None:
- """
- left click and hold on current mouse position or left click and hold on web element
- :param on_element: can be None or web element
- :return: None
- """
-
- def left_click_and_hold_with_test_object(self, element_name: str = None) -> None:
- """
- left click and hold on current mouse position or left click and hold on web element
- find use test object name
- :param element_name: test object name
- :return: None
- """
-
- def right_click(self, on_element: WebElement = None) -> None:
- """
- right click mouse on current mouse position or click on web element
- :param on_element: can be None or web element
- :return: None
- """
-
- def right_click_with_test_object(self, element_name: str = None) -> None:
- """
- right click mouse on current mouse position or click on web element
- find use test object name
- :param element_name: test object name
- :return: None
- """
-
- def left_double_click(self, on_element: WebElement = None) -> None:
- """
- double left click mouse on current mouse position or double click on web element
- :param on_element: can be None or web element
- :return: None
- """
-
- def left_double_click_with_test_object(self, element_name: str = None) -> None:
- """
- double left click mouse on current mouse position or double click on web element
- find use test object name
- :param element_name: test object name
- :return: None
- """
-
- def release(self, on_element: WebElement = None) -> None:
- """
- release mouse or web element
- :param on_element: can be None or web element
- :return: None
- """
-
- def release_with_test_object(self, element_name: str = None) -> None:
- """
- release mouse or web element find use test object name
- :param element_name: test object name
- :return: None
- """
- def press_key(self, keycode_on_key_class, on_element: WebElement = None) -> None:
- """
- press key or press key on web element key should be in Key
- :param keycode_on_key_class: which key code to press
- :param on_element: can be None or web element
- :return: None
- """
-
- def press_key_with_test_object(self, keycode_on_key_class, element_name: str = None) -> None:
- """
- press key or press key on web element key should be in Key find web element use test object name
- :param keycode_on_key_class: which key code to press
- :param element_name: test object name
- :return: None
- """
-
- def release_key(self, keycode_on_key_class, on_element: WebElement = None) -> None:
- """
- release key or release key on web element key should be in Key
- :param keycode_on_key_class: which key code to release
- :param on_element: can be None or web element
- :return: None
- """
-
- def release_key_with_test_object(self, keycode_on_key_class, element_name: str = None) -> None:
- """
- release key or release key on web element key should be in Key
- find use test object
- :param keycode_on_key_class: which key code to release
- :param element_name: test object name
- :return: None
- """
-
- def move_by_offset(self, offset_x: int, offset_y: int) -> None:
- """
- move mouse use offset
- :param offset_x: offset x
- :param offset_y: offset y
- :return: None
- """
-
- def pause(self, seconds: int) -> None:
- """
- pause seconds time (this many be let selenium raise some exception)
- :param seconds: seconds to pause
- :return: None
- """
-
- def send_keys(self, keys_to_send) -> None:
- """
- send(press and release) keyboard key
- :param keys_to_send: which key on keyboard we want to send
- :return: None
- """
-
- def send_keys_to_element(self, element: WebElement, keys_to_send) -> None:
- """
- :param element: which element we want send key to
- :param keys_to_send: which key on keyboard we want to send
- :return: None
- """
-
- def send_keys_to_element_with_test_object(self, element_name: str, keys_to_send) -> None:
- """
- :param element_name: test object name
- :param keys_to_send: which key on keyboard we want to send find use test object
- :return: None
- """
-
- def scroll(self, scroll_x: int, scroll_y: int, delta_x: int, delta_y: int,
- duration: int = 0, origin: str = "viewport") -> None:
- """
- :param scroll_x: starting x coordinate
- :param scroll_y: starting y coordinate
- :param delta_x: the distance the mouse will scroll on the x axis
- :param delta_y: the distance the mouse will scroll on the y axis
- :param duration: delay to wheel
- :param origin: what is origin to scroll
- :return:
- """
-
- def maximize_window(self) -> None:
- """
- maximize current window
- :return: None
- """
-
- def full_screen_window(self) -> None:
- """
- full-screen current window
- :return: None
- """
-
- def minimize_window(self) -> None:
- """
- minimize current window
- :return: None
- """
-
- def set_window_size(self, width, height, window_handle='current') -> dict:
- """
- :param width: window width (pixel)
- :param height: window height (pixel)
- :param window_handle: normally is "current" (w3c) if not "current" will make exception
- :return: size
- """
-
- def set_window_position(self, x, y, window_handle='current') -> dict:
- """
- :param x: position x
- :param y: position y
- :param window_handle: normally is "current" (w3c) if not "current" will make exception
- :return: execute(Command.SET_WINDOW_RECT,
- {"x": x, "y": y, "width": width, "height": height})['value']
- """
-
- def get_window_position(self, window_handle='current') -> dict:
- """
- :param window_handle: normally is "current" (w3c) if not "current" will make exception
- :return: window position dict
- """
-
- def get_window_rect(self) -> dict:
- """
- :return: execute(Command.GET_WINDOW_RECT)['value']
- """
-
- def set_window_rect(self, x: int = None, y: int = None, width: int = None, height: int = None) -> dict:
- """
- only supported for w3c compatible another browsers need use set_window_position or set_window_size
- :param x: set x coordinates
- :param y: set y coordinates
- :param width: set window width
- :param height: set window height
- :return: execute(Command.SET_WINDOW_RECT,
- {"x": x, "y": y, "width": width, "height": height})['value']
- """
-
- def get_screenshot_as_png(self) -> bytes:
- """
- get current page screenshot as png
- :return: screenshot as bytes
- """
-
- def get_screenshot_as_base64(self) -> str:
- """
- get current page screenshot as base64 str
- :return: screenshot as str
- """
-
- def get_log(self, log_type: str):
- """
- :param log_type: ["browser", "driver", client", "server]
- :return: execute(Command.GET_LOG, {'type': log_type})['value']
- """
-
- def check_current_webdriver(self, check_dict: dict) -> None:
- """
- if check failure will raise an exception
- :param check_dict: use to check current webdriver state
- :return: None
- """
-
- def quit(self) -> None:
- """
- quit this webdriver
- :return: None
- """
+ webdriver_wrapper_instance = WebDriverWrapper()
diff --git a/docs/source/Eng/doc/action_executor/action_executor_doc.rst b/docs/source/Eng/doc/action_executor/action_executor_doc.rst
new file mode 100644
index 0000000..a2764f5
--- /dev/null
+++ b/docs/source/Eng/doc/action_executor/action_executor_doc.rst
@@ -0,0 +1,401 @@
+Action Executor
+===============
+
+Overview
+--------
+
+The Action Executor is a powerful engine that maps command strings to callable functions.
+It allows you to define automation scripts as JSON action lists, enabling data-driven automation workflows.
+
+The executor also includes all Python built-in functions, so you can call ``print``, ``len``, etc. from action lists.
+
+Action Format
+-------------
+
+Each action is a list with the command name and optional parameters:
+
+.. code-block:: python
+
+ ["command_name"] # No parameters
+ ["command_name", {"key": "value"}] # Keyword arguments
+ ["command_name", [arg1, arg2]] # Positional arguments
+
+Basic Usage
+-----------
+
+.. code-block:: python
+
+ from je_web_runner import execute_action
+
+ actions = [
+ ["WR_get_webdriver_manager", {"webdriver_name": "chrome"}],
+ ["WR_to_url", {"url": "https://www.google.com"}],
+ ["WR_implicitly_wait", {"time_to_wait": 2}],
+ ["WR_SaveTestObject", {"test_object_name": "q", "object_type": "name"}],
+ ["WR_find_element", {"element_name": "q"}],
+ ["WR_click_element"],
+ ["WR_input_to_element", {"input_value": "WebRunner"}],
+ ["WR_quit"]
+ ]
+
+ result = execute_action(actions)
+
+The ``execute_action()`` function returns a dict mapping each action to its return value.
+
+Available Commands
+------------------
+
+**WebDriver Manager:**
+
+.. list-table::
+ :header-rows: 1
+ :widths: 40 60
+
+ * - Command
+ - Description
+ * - ``WR_get_webdriver_manager``
+ - Create a new WebDriver (params: ``webdriver_name``, ``options``)
+ * - ``WR_change_index_of_webdriver``
+ - Switch to a WebDriver by index
+ * - ``WR_quit``
+ - Quit all WebDrivers
+ * - ``WR_single_quit``
+ - Quit the current single WebDriver
+
+**Navigation:**
+
+.. list-table::
+ :header-rows: 1
+ :widths: 40 60
+
+ * - Command
+ - Description
+ * - ``WR_to_url``
+ - Navigate to URL (param: ``url``)
+ * - ``WR_forward``
+ - Go forward
+ * - ``WR_back``
+ - Go back
+ * - ``WR_refresh``
+ - Refresh the page
+
+**Element Finding:**
+
+.. list-table::
+ :header-rows: 1
+ :widths: 40 60
+
+ * - Command
+ - Description
+ * - ``WR_find_element``
+ - Find single element by saved test object name (param: ``element_name``)
+ * - ``WR_find_elements``
+ - Find multiple elements by saved test object name
+
+**Wait:**
+
+.. list-table::
+ :header-rows: 1
+ :widths: 40 60
+
+ * - Command
+ - Description
+ * - ``WR_implicitly_wait``
+ - Set implicit wait (param: ``time_to_wait``)
+ * - ``WR_explict_wait``
+ - Explicit wait with condition
+ * - ``WR_set_script_timeout``
+ - Set script timeout
+ * - ``WR_set_page_load_timeout``
+ - Set page load timeout
+
+**Mouse Actions:**
+
+.. list-table::
+ :header-rows: 1
+ :widths: 40 60
+
+ * - Command
+ - Description
+ * - ``WR_left_click``
+ - Left click (on saved test object or current position)
+ * - ``WR_right_click``
+ - Right click
+ * - ``WR_left_double_click``
+ - Double click
+ * - ``WR_left_click_and_hold``
+ - Click and hold
+ * - ``WR_release``
+ - Release held button
+ * - ``WR_drag_and_drop``
+ - Drag from source to target (test object names)
+ * - ``WR_drag_and_drop_offset``
+ - Drag to offset
+ * - ``WR_move_to_element``
+ - Hover over element (test object name)
+ * - ``WR_move_to_element_with_offset``
+ - Hover with offset
+ * - ``WR_move_by_offset``
+ - Move mouse by offset
+
+**Keyboard:**
+
+.. list-table::
+ :header-rows: 1
+ :widths: 40 60
+
+ * - Command
+ - Description
+ * - ``WR_press_key``
+ - Press a key
+ * - ``WR_release_key``
+ - Release a key
+ * - ``WR_send_keys``
+ - Send keys globally
+ * - ``WR_send_keys_to_element``
+ - Send keys to saved test object
+
+**Action Chain:**
+
+.. list-table::
+ :header-rows: 1
+ :widths: 40 60
+
+ * - Command
+ - Description
+ * - ``WR_perform``
+ - Execute queued actions
+ * - ``WR_reset_actions``
+ - Clear action queue
+ * - ``WR_pause``
+ - Pause in chain
+ * - ``WR_scroll``
+ - Scroll page
+
+**Cookies:**
+
+.. list-table::
+ :header-rows: 1
+ :widths: 40 60
+
+ * - Command
+ - Description
+ * - ``WR_get_cookies``
+ - Get all cookies
+ * - ``WR_get_cookie``
+ - Get a specific cookie
+ * - ``WR_add_cookie``
+ - Add a cookie
+ * - ``WR_delete_cookie``
+ - Delete a cookie
+ * - ``WR_delete_all_cookies``
+ - Delete all cookies
+
+**JavaScript:**
+
+.. list-table::
+ :header-rows: 1
+ :widths: 40 60
+
+ * - Command
+ - Description
+ * - ``WR_execute``
+ - Execute JavaScript
+ * - ``WR_execute_script``
+ - Execute script
+ * - ``WR_execute_async_script``
+ - Execute async script
+
+**Window:**
+
+.. list-table::
+ :header-rows: 1
+ :widths: 40 60
+
+ * - Command
+ - Description
+ * - ``WR_maximize_window``
+ - Maximize window
+ * - ``WR_minimize_window``
+ - Minimize window
+ * - ``WR_fullscreen_window``
+ - Full screen
+ * - ``WR_set_window_size``
+ - Set window size
+ * - ``WR_set_window_position``
+ - Set window position
+ * - ``WR_get_window_position``
+ - Get window position
+ * - ``WR_get_window_rect``
+ - Get window rect
+ * - ``WR_set_window_rect``
+ - Set window rect
+
+**Screenshot & Logging:**
+
+.. list-table::
+ :header-rows: 1
+ :widths: 40 60
+
+ * - Command
+ - Description
+ * - ``WR_get_screenshot_as_png``
+ - Get screenshot as PNG bytes
+ * - ``WR_get_screenshot_as_base64``
+ - Get screenshot as base64
+ * - ``WR_get_log``
+ - Get browser logs
+
+**Element Interaction:**
+
+.. list-table::
+ :header-rows: 1
+ :widths: 40 60
+
+ * - Command
+ - Description
+ * - ``WR_click_element``
+ - Click the current element
+ * - ``WR_input_to_element``
+ - Type text into element
+ * - ``WR_element_clear``
+ - Clear element content
+ * - ``WR_element_submit``
+ - Submit form
+ * - ``WR_element_get_attribute``
+ - Get element attribute
+ * - ``WR_element_get_property``
+ - Get element property
+ * - ``WR_element_get_dom_attribute``
+ - Get DOM attribute
+ * - ``WR_element_is_displayed``
+ - Check if element is visible
+ * - ``WR_element_is_enabled``
+ - Check if element is enabled
+ * - ``WR_element_is_selected``
+ - Check if element is selected
+ * - ``WR_element_value_of_css_property``
+ - Get CSS property
+ * - ``WR_element_screenshot``
+ - Take element screenshot
+ * - ``WR_element_change_web_element``
+ - Switch active element
+ * - ``WR_element_check_current_web_element``
+ - Validate element properties
+ * - ``WR_element_get_select``
+ - Get Select for dropdown
+
+**Test Object:**
+
+.. list-table::
+ :header-rows: 1
+ :widths: 40 60
+
+ * - Command
+ - Description
+ * - ``WR_SaveTestObject``
+ - Save a test object (params: ``test_object_name``, ``object_type``)
+ * - ``WR_CleanTestObject``
+ - Clear all saved test objects
+
+**Report:**
+
+.. list-table::
+ :header-rows: 1
+ :widths: 40 60
+
+ * - Command
+ - Description
+ * - ``WR_generate_html``
+ - Generate HTML report string
+ * - ``WR_generate_html_report``
+ - Save HTML report to file
+ * - ``WR_generate_json``
+ - Generate JSON report dicts
+ * - ``WR_generate_json_report``
+ - Save JSON report to file
+ * - ``WR_generate_xml``
+ - Generate XML report
+ * - ``WR_generate_xml_report``
+ - Save XML report to file
+
+**Other:**
+
+.. list-table::
+ :header-rows: 1
+ :widths: 40 60
+
+ * - Command
+ - Description
+ * - ``WR_switch``
+ - Switch context (frame, window, alert)
+ * - ``WR_check_current_webdriver``
+ - Validate WebDriver properties
+ * - ``WR_set_record_enable``
+ - Enable/disable test recording
+ * - ``WR_execute_action``
+ - Nested execute action list
+ * - ``WR_execute_files``
+ - Execute from JSON files
+ * - ``WR_add_package_to_executor``
+ - Add a Python package to executor
+ * - ``WR_add_package_to_callback_executor``
+ - Add a Python package to callback executor
+
+Execute from JSON Files
+-----------------------
+
+.. code-block:: python
+
+ from je_web_runner import execute_files
+
+ # Execute actions from multiple JSON files
+ results = execute_files(["actions1.json", "actions2.json"])
+
+JSON file format:
+
+.. code-block:: json
+
+ [
+ ["WR_get_webdriver_manager", {"webdriver_name": "chrome"}],
+ ["WR_to_url", {"url": "https://example.com"}],
+ ["WR_quit"]
+ ]
+
+Dict Format Input
+-----------------
+
+The executor also accepts a dict with a ``"webdriver_wrapper"`` key:
+
+.. code-block:: python
+
+ execute_action({
+ "webdriver_wrapper": [
+ ["WR_get_webdriver_manager", {"webdriver_name": "chrome"}],
+ ["WR_quit"]
+ ]
+ })
+
+Add Custom Commands
+-------------------
+
+Register your own functions as executor commands:
+
+.. code-block:: python
+
+ from je_web_runner import add_command_to_executor
+
+ def my_custom_function(param1, param2):
+ print(f"Custom: {param1}, {param2}")
+
+ add_command_to_executor({"my_command": my_custom_function})
+
+ # Now use it in action lists
+ execute_action([
+ ["my_command", {"param1": "hello", "param2": "world"}]
+ ])
+
+.. note::
+
+ Only ``types.MethodType`` and ``types.FunctionType`` are accepted.
+ Passing other callable types will raise ``WebRunnerAddCommandException``.
diff --git a/docs/source/Eng/doc/assertion/assertion_doc.rst b/docs/source/Eng/doc/assertion/assertion_doc.rst
new file mode 100644
index 0000000..a0ed9fe
--- /dev/null
+++ b/docs/source/Eng/doc/assertion/assertion_doc.rst
@@ -0,0 +1,98 @@
+Assertion and Validation
+========================
+
+Overview
+--------
+
+WebRunner provides assertion utilities for validating WebDriver and WebElement states
+during automation. If a check fails, a ``WebRunnerAssertException`` is raised.
+
+WebDriver Validation
+--------------------
+
+Validate properties of the current WebDriver:
+
+.. code-block:: python
+
+ # Via WebDriverWrapper
+ wrapper.check_current_webdriver({"name": "chrome"})
+
+.. code-block:: python
+
+ # Via utility functions
+ from je_web_runner.utils.assert_value.result_check import (
+ check_webdriver_value,
+ check_webdriver_values,
+ )
+
+ check_webdriver_value("name", "chrome", webdriver_instance)
+ check_webdriver_values({"name": "chrome"}, webdriver_instance)
+
+WebElement Validation
+---------------------
+
+Validate properties of the current WebElement:
+
+.. code-block:: python
+
+ # Via WebElementWrapper
+ web_element_wrapper.check_current_web_element({
+ "tag_name": "input",
+ "enabled": True
+ })
+
+.. code-block:: python
+
+ # Via utility functions
+ from je_web_runner.utils.assert_value.result_check import check_web_element_details
+
+ check_web_element_details(element, {
+ "tag_name": "input",
+ "enabled": True
+ })
+
+General Value Checking
+----------------------
+
+.. code-block:: python
+
+ from je_web_runner.utils.assert_value.result_check import check_value, check_values
+
+ # Check a single value against a result dictionary
+ check_value("element_name", "expected_value", result_check_dict)
+
+ # Check multiple values
+ check_values({"name": "expected"}, result_check_dict)
+
+Via Action Executor
+-------------------
+
+.. code-block:: python
+
+ from je_web_runner import execute_action
+
+ execute_action([
+ ["WR_get_webdriver_manager", {"webdriver_name": "chrome"}],
+ ["WR_check_current_webdriver", {"check_dict": {"name": "chrome"}}],
+ ["WR_quit"],
+ ])
+
+Functions Reference
+-------------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 35 65
+
+ * - Function
+ - Description
+ * - ``check_value(element_name, element_value, result_check_dict)``
+ - Check a single value against a result dictionary
+ * - ``check_values(check_dict, result_check_dict)``
+ - Check multiple key-value pairs
+ * - ``check_webdriver_value(element_name, element_value, webdriver)``
+ - Check a single WebDriver property
+ * - ``check_webdriver_values(check_dict, webdriver)``
+ - Check multiple WebDriver properties (alias: ``check_webdriver_details``)
+ * - ``check_web_element_details(element, check_dict)``
+ - Validate WebElement properties against expected values
diff --git a/docs/source/Eng/doc/callback_function/callback_function_doc.rst b/docs/source/Eng/doc/callback_function/callback_function_doc.rst
index e13e792..986256a 100644
--- a/docs/source/Eng/doc/callback_function/callback_function_doc.rst
+++ b/docs/source/Eng/doc/callback_function/callback_function_doc.rst
@@ -1,57 +1,109 @@
-Callback Function
-----
+Callback Executor
+=================
-In AutoControl, callback functions are supported by the Callback Executor.
-Below is a simple example of using the Callback Executor:
+Overview
+--------
+
+The Callback Executor allows you to execute automation commands with callback functions
+triggered on completion. It wraps the standard executor's event dictionary and provides
+an event-driven execution model.
+
+The global instance ``callback_executor`` is imported from ``je_web_runner``.
+
+Basic Usage
+-----------
.. code-block:: python
from je_web_runner import callback_executor
- # trigger_function will first to execute, but return value need to wait everything done
- # so this test will first print("test") then print(size_function_return_value)
+
+ def on_complete():
+ print("Navigation complete!")
+
callback_executor.callback_function(
- trigger_function_name="WR_get_webdriver_manager",
- callback_function=print,
- callback_param_method="args",
- callback_function_param={"": "open driver"},
- **{
- "webdriver_name": "edge"
- }
+ trigger_function_name="WR_to_url",
+ callback_function=on_complete,
+ url="https://example.com"
)
-* Note that if the "name: function" pair in the callback_executor event_dict is different from the executor, it is a bug.
-* Of course, like the executor, it can be expanded by adding external functions. Please see the example below.
+The trigger function (``WR_to_url``) executes first, then the callback (``on_complete``) runs.
-In this example, we use callback_executor to execute the "size" function defined in AutoControl.
-After executing the "size" function, the function passed to callback_function will be executed.
-The delivery method can be determined by the callback_param_method parameter.
-If it is "args", please pass in {"value1", "value2", ...}.
-Here, the ellipsis (...) represents multiple inputs.
- If it is "kwargs", please pass in {"actually_param_name": value, ...}.
-Here, the ellipsis (...) again represents multiple inputs.
- If you want to use the return value,
-since the return value will only be returned after all functions are executed,
-you will actually see the "print" statement
-before the "print(size_function_return_value)" statement in this example,
-even though the order of size -> print is correct.
-This is because the "size" function only returns the value itself without printing it.
+Callback with kwargs
+--------------------
-This code will load all built-in functions, methods, and classes of the time module into the callback executor.
-To use the loaded functions, we need to use the package_function name,
-for example, time.sleep will become time_sleep.
+Pass keyword arguments to the callback using ``callback_param_method="kwargs"``:
-If we want to add functions in the callback_executor, we can use the following code:
+.. code-block:: python
-This code will add all the functions of the time module to the executor (interpreter).
+ def on_element_found(result=None):
+ print(f"Element found: {result}")
+
+ callback_executor.callback_function(
+ trigger_function_name="WR_find_element",
+ callback_function=on_element_found,
+ callback_function_param={"result": "search_box"},
+ callback_param_method="kwargs",
+ element_name="search_box"
+ )
+
+Callback with args
+------------------
+
+Pass positional arguments using ``callback_param_method="args"``:
.. code-block:: python
- from je_web_runner import package_manager
- package_manager.add_package_to_callback_executor("time")
+ def on_done(msg):
+ print(f"Done: {msg}")
-If you need to check the updated event_dict, you can use:
+ callback_executor.callback_function(
+ trigger_function_name="WR_quit",
+ callback_function=on_done,
+ callback_function_param=["All browsers closed"],
+ callback_param_method="args"
+ )
+
+Parameters
+----------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 20 55
+
+ * - Parameter
+ - Type
+ - Description
+ * - ``trigger_function_name``
+ - ``str``
+ - Name of the function to trigger (must exist in ``event_dict``)
+ * - ``callback_function``
+ - ``Callable``
+ - The callback function to execute after the trigger
+ * - ``callback_function_param``
+ - ``dict | list | None``
+ - Parameters to pass to the callback
+ * - ``callback_param_method``
+ - ``str``
+ - How to pass callback params: ``"kwargs"`` or ``"args"``
+ * - ``**kwargs``
+ -
+ - Parameters passed to the trigger function
+
+Return Value
+------------
+
+The ``callback_function()`` method returns the return value of the **trigger function**
+(not the callback).
+
+Adding Packages
+---------------
+
+You can add external packages to the callback executor:
.. code-block:: python
- from je_web_runner import callback_executor
- print(callback_executor.event_dict)
\ No newline at end of file
+ from je_web_runner.utils.package_manager.package_manager_class import package_manager
+
+ package_manager.add_package_to_callback_executor("time")
+
+ # Now "time_sleep" is available as a trigger function name
diff --git a/docs/source/Eng/doc/cli/cli_doc.rst b/docs/source/Eng/doc/cli/cli_doc.rst
index ccc4a48..0968598 100644
--- a/docs/source/Eng/doc/cli/cli_doc.rst
+++ b/docs/source/Eng/doc/cli/cli_doc.rst
@@ -1,19 +1,74 @@
-CLI
-----
+CLI Usage
+=========
-We can use the CLI mode to execute the keyword.
-json file or execute the folder containing the Keyword.json files.
+Overview
+--------
-The following example is to execute the specified path of the keyword JSON file.
+WebRunner can be executed directly from the command line using the ``je_web_runner`` module.
-.. code-block::
+Commands
+--------
- python je_web_runner --execute_file "your_file_path"
+**Execute a single JSON action file:**
+.. code-block:: bash
+ python -m je_web_runner -e actions.json
+ python -m je_web_runner --execute_file actions.json
-The following example is to run all keyword JSON files in a specified folder:
+**Execute all JSON files in a directory:**
-.. code-block::
+.. code-block:: bash
- python je_web_runner --execute_dir "your_dir_path"
\ No newline at end of file
+ python -m je_web_runner -d ./actions/
+ python -m je_web_runner --execute_dir ./actions/
+
+**Execute a JSON action string directly:**
+
+.. code-block:: bash
+
+ python -m je_web_runner --execute_str '[["WR_get_webdriver_manager", {"webdriver_name": "chrome"}], ["WR_quit"]]'
+
+Command Reference
+-----------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 15 60
+
+ * - Flag
+ - Short
+ - Description
+ * - ``--execute_file``
+ - ``-e``
+ - Execute a single JSON action file
+ * - ``--execute_dir``
+ - ``-d``
+ - Execute all JSON files in a directory
+ * - ``--execute_str``
+ -
+ - Execute a JSON action string directly
+
+JSON File Format
+----------------
+
+The JSON file should contain an array of action lists:
+
+.. code-block:: json
+
+ [
+ ["WR_get_webdriver_manager", {"webdriver_name": "chrome"}],
+ ["WR_to_url", {"url": "https://example.com"}],
+ ["WR_quit"]
+ ]
+
+.. note::
+
+ On Windows, the ``--execute_str`` option may require double JSON parsing
+ due to shell escaping. WebRunner handles this automatically.
+
+Error Handling
+--------------
+
+If no arguments are provided, WebRunner raises a ``WebRunnerExecuteException``.
+All errors are printed to stderr and the process exits with code 1.
diff --git a/docs/source/Eng/doc/create_project/create_project_doc.rst b/docs/source/Eng/doc/create_project/create_project_doc.rst
index 002b4f7..361834b 100644
--- a/docs/source/Eng/doc/create_project/create_project_doc.rst
+++ b/docs/source/Eng/doc/create_project/create_project_doc.rst
@@ -1,23 +1,80 @@
Create Project
-----
+==============
-In WebRunner, you can create a project which will automatically generate sample files once the project is created.
-These sample files include a Python executor file and a keyword.json file.
+Overview
+--------
-To create a project, you can use the following method:
+WebRunner can generate a quick-start project structure with sample files,
+including example JSON keyword files and Python executor scripts.
+
+Usage
+-----
.. code-block:: python
from je_web_runner import create_project_dir
- # create on current workdir
+
+ # Create on current working directory
create_project_dir()
- # create project on project_path
- create_project_dir("project_path")
- # create project on project_path and dir name is My First Project
- create_project_dir("project_path", "My First Project")
-Or using CLI, this will generate a project at the project_path location.
+ # Create at a specific path
+ create_project_dir(project_path="./my_project")
+
+ # Create with a custom parent name
+ create_project_dir(project_path="./my_project", parent_name="MyTest")
+
+CLI Method
+----------
+
+.. code-block:: bash
+
+ python -m je_web_runner --create_project ./my_project
+
+Generated Structure
+-------------------
+
+.. code-block:: text
+
+ my_project/WebRunner/
+ ├── keyword/
+ │ ├── keyword1.json # Sample action file (success case)
+ │ ├── keyword2.json # Sample action file (success case)
+ │ └── bad_keyword_1.json # Sample action file (failure case)
+ └── executor/
+ ├── executor_one_file.py # Execute a single JSON file
+ ├── executor_folder.py # Execute all JSON files in a folder
+ └── executor_bad_file.py # Execute failure case file
+
+Template Details
+----------------
+
+**keyword1.json / keyword2.json**: Sample action lists that demonstrate correct usage
+of WebRunner commands (launching a browser, navigating, quitting).
+
+**bad_keyword_1.json**: An intentionally broken action list to demonstrate error handling.
+
+**executor_one_file.py**: Reads and executes a single keyword JSON file using
+``execute_action(read_action_json(path))``.
+
+**executor_folder.py**: Uses ``execute_files(get_dir_files_as_list(path))`` to
+execute all ``.json`` files in the ``keyword/`` directory.
+
+**executor_bad_file.py**: Executes the bad keyword file to demonstrate
+how errors are captured and reported.
+
+Parameters
+----------
-.. code-block:: console
+.. list-table::
+ :header-rows: 1
+ :widths: 25 25 50
- python -m je_web_runner --create_project project_path
\ No newline at end of file
+ * - Parameter
+ - Default
+ - Description
+ * - ``project_path``
+ - Current working directory
+ - Path where the project will be created
+ * - ``parent_name``
+ - ``"WebRunner"``
+ - Name of the top-level project directory
diff --git a/docs/source/Eng/doc/exception/exception_doc.rst b/docs/source/Eng/doc/exception/exception_doc.rst
new file mode 100644
index 0000000..b3ecb37
--- /dev/null
+++ b/docs/source/Eng/doc/exception/exception_doc.rst
@@ -0,0 +1,84 @@
+Exception Handling
+==================
+
+Overview
+--------
+
+WebRunner provides a hierarchy of custom exceptions for specific error scenarios.
+All exceptions inherit from ``WebRunnerException``.
+
+Exception Hierarchy
+-------------------
+
+.. code-block:: text
+
+ WebRunnerException (base)
+ ├── WebRunnerWebDriverNotFoundException
+ ├── WebRunnerOptionsWrongTypeException
+ ├── WebRunnerArgumentWrongTypeException
+ ├── WebRunnerWebDriverIsNoneException
+ ├── WebRunnerExecuteException
+ ├── WebRunnerAssertException
+ ├── WebRunnerHTMLException
+ ├── WebRunnerAddCommandException
+ ├── WebRunnerJsonException
+ │ └── WebRunnerGenerateJsonReportException
+ ├── XMLException
+ │ └── XMLTypeException
+ └── CallbackExecutorException
+
+Exception Reference
+-------------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 35 65
+
+ * - Exception
+ - Description
+ * - ``WebRunnerException``
+ - Base exception for all WebRunner errors
+ * - ``WebRunnerWebDriverNotFoundException``
+ - WebDriver not found or browser name not supported
+ * - ``WebRunnerOptionsWrongTypeException``
+ - Invalid options type provided (must be list or set)
+ * - ``WebRunnerArgumentWrongTypeException``
+ - Invalid argument type provided
+ * - ``WebRunnerWebDriverIsNoneException``
+ - WebDriver is None (not initialized)
+ * - ``WebRunnerExecuteException``
+ - Error during action execution (unknown command, invalid format)
+ * - ``WebRunnerJsonException``
+ - JSON processing error
+ * - ``WebRunnerGenerateJsonReportException``
+ - JSON report generation error
+ * - ``WebRunnerAssertException``
+ - Assertion validation failure
+ * - ``WebRunnerHTMLException``
+ - HTML report generation error (e.g., no test records)
+ * - ``WebRunnerAddCommandException``
+ - Error registering custom command (not a function/method)
+ * - ``XMLException``
+ - XML processing error
+ * - ``XMLTypeException``
+ - Invalid XML type specified (must be ``"string"`` or ``"file"``)
+ * - ``CallbackExecutorException``
+ - Callback execution error (bad trigger function or method)
+
+Example
+-------
+
+.. code-block:: python
+
+ from je_web_runner import get_webdriver_manager
+ from je_web_runner.utils.exception.exceptions import (
+ WebRunnerException,
+ WebRunnerWebDriverNotFoundException,
+ )
+
+ try:
+ manager = get_webdriver_manager("unsupported_browser")
+ except WebRunnerWebDriverNotFoundException as e:
+ print(f"Browser not supported: {e}")
+ except WebRunnerException as e:
+ print(f"WebRunner error: {e}")
diff --git a/docs/source/Eng/doc/generate_report/generate_report_doc.rst b/docs/source/Eng/doc/generate_report/generate_report_doc.rst
index 050a411..4603ca9 100644
--- a/docs/source/Eng/doc/generate_report/generate_report_doc.rst
+++ b/docs/source/Eng/doc/generate_report/generate_report_doc.rst
@@ -1,25 +1,122 @@
-Generate Report
-----
+Report Generation
+=================
-* Generate Report can generate reports in the following formats:
- * HTML
- * JSON
- * XML
+Overview
+--------
-* Generate Report is mainly used to record and confirm which steps were executed and whether they were successful or not.
-* The following example is used with keywords and an executor. If you don't understand, please first take a look at the executor.
+WebRunner can automatically record all actions and generate reports in three formats:
+**HTML**, **JSON**, and **XML**. Reports include detailed information about each executed action,
+including function name, parameters, timestamp, and any exceptions.
-Here's an example of generating an HTML report.
+.. note::
+
+ Test recording must be enabled before generating reports.
+ See the :doc:`../test_record/test_record_doc` section for details.
+
+Enable Recording
+----------------
.. code-block:: python
+ from je_web_runner import test_record_instance
+
+ test_record_instance.set_record_enable(True)
+
+HTML Report
+-----------
-Here's an example of generating an JSON report.
+HTML reports include color-coded tables:
+
+* **Aqua** background for successful actions
+* **Red** background for failed actions
+
+Each row shows: function name, parameters, timestamp, and exception (if any).
.. code-block:: python
+ from je_web_runner import generate_html, generate_html_report
+
+ # Generate HTML string (returns complete HTML document)
+ html_content = generate_html()
+
+ # Save to file (creates test_results.html)
+ generate_html_report("test_results")
+
+JSON Report
+-----------
+
+JSON reports produce separate files for success and failure records.
+
+.. code-block:: python
+
+ from je_web_runner import generate_json, generate_json_report
+
+ # Generate dicts (returns tuple of success_dict, failure_dict)
+ success_dict, failure_dict = generate_json()
+
+ # Save to files:
+ # - test_results_success.json
+ # - test_results_failure.json
+ generate_json_report("test_results")
-Here's an example of generating an XML report.
+XML Report
+----------
.. code-block:: python
+ from je_web_runner import generate_xml, generate_xml_report
+
+ # Generate XML structures
+ success_xml, failure_xml = generate_xml()
+
+ # Save to files:
+ # - test_results_success.xml
+ # - test_results_failure.xml
+ generate_xml_report("test_results")
+
+Report via Action Executor
+--------------------------
+
+Reports can also be generated from action lists:
+
+.. code-block:: python
+
+ from je_web_runner import execute_action
+
+ execute_action([
+ ["WR_set_record_enable", {"set_enable": True}],
+ ["WR_get_webdriver_manager", {"webdriver_name": "chrome"}],
+ ["WR_to_url", {"url": "https://example.com"}],
+ ["WR_quit"],
+ ["WR_generate_html_report", {"html_name": "my_report"}],
+ ])
+
+Record Data Format
+------------------
+
+Each record in the report contains:
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 25 50
+
+ * - Field
+ - Type
+ - Description
+ * - ``function_name``
+ - ``str``
+ - Name of the executed function
+ * - ``local_param``
+ - ``dict | None``
+ - Parameters passed to the function
+ * - ``time``
+ - ``str``
+ - Timestamp of execution
+ * - ``program_exception``
+ - ``str``
+ - Exception message or ``"None"``
+
+Thread Safety
+-------------
+
+All report generation methods use ``threading.Lock`` to ensure thread-safe file writing.
diff --git a/docs/source/Eng/doc/installation/installation_doc.rst b/docs/source/Eng/doc/installation/installation_doc.rst
index 156f263..bd44a4e 100644
--- a/docs/source/Eng/doc/installation/installation_doc.rst
+++ b/docs/source/Eng/doc/installation/installation_doc.rst
@@ -1,15 +1,85 @@
Installation
-----
+============
-.. code-block:: python
+Install via pip
+---------------
+
+**Stable version:**
+
+.. code-block:: bash
pip install je_web_runner
-* Python & pip require version
- * Python 3.7 & up
- * pip 19.3 & up
+**Development version:**
+
+.. code-block:: bash
+
+ pip install je_web_runner_dev
+
+Requirements
+------------
+
+* Python **3.10** or later
+* pip **19.3** or later
+
+Dependencies (installed automatically):
+
+* ``selenium>=4.0.0``
+* ``requests``
+* ``python-dotenv``
+* ``webdriver-manager``
+
+Supported Platforms
+-------------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 70
+
+ * - Platform
+ - Notes
+ * - Windows 11
+ - Fully supported
+ * - macOS
+ - Fully supported
+ * - Ubuntu / Linux
+ - Fully supported
+ * - Raspberry Pi
+ - Fully supported
+
+Supported Browsers
+------------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 20 50
+
+ * - Browser
+ - Key
+ - Notes
+ * - Google Chrome
+ - ``chrome``
+ - Most commonly used, auto-managed via webdriver-manager
+ * - Chromium
+ - ``chromium``
+ - Open-source Chrome variant
+ * - Mozilla Firefox
+ - ``firefox``
+ - Full support via GeckoDriver
+ * - Microsoft Edge
+ - ``edge``
+ - Chromium-based Edge
+ * - Internet Explorer
+ - ``ie``
+ - Legacy support
+ * - Apple Safari
+ - ``safari``
+ - macOS only, no auto driver management
+
+Verify Installation
+-------------------
+
+.. code-block:: python
-* Dev env
- * windows 11
- * osx 11 big sur
- * ubuntu 20.0.4
+ import je_web_runner
+ print(je_web_runner.__all__)
diff --git a/docs/source/Eng/doc/logging/logging_doc.rst b/docs/source/Eng/doc/logging/logging_doc.rst
new file mode 100644
index 0000000..32ba103
--- /dev/null
+++ b/docs/source/Eng/doc/logging/logging_doc.rst
@@ -0,0 +1,54 @@
+Logging
+=======
+
+Overview
+--------
+
+WebRunner uses Python's ``logging`` module with a rotating file handler
+for logging automation events, errors, and warnings.
+
+Configuration
+-------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 70
+
+ * - Property
+ - Value
+ * - Log file
+ - ``WEBRunner.log``
+ * - Log level
+ - ``WARNING`` and above
+ * - Max file size
+ - 1 GB (rotating)
+ * - Log format
+ - ``%(asctime)s | %(name)s | %(levelname)s | %(message)s``
+ * - Handler
+ - ``RotatingFileHandler`` (custom ``WebRunnerLoggingHandler``)
+
+Log Output
+----------
+
+The log file is created in the current working directory as ``WEBRunner.log``.
+When the file reaches 1 GB, it is rotated.
+
+Example log entries:
+
+.. code-block:: text
+
+ 2025-01-01 12:00:00 | je_web_runner | WARNING | WebDriverWrapper find_element failed: ...
+ 2025-01-01 12:00:01 | je_web_runner | ERROR | WebdriverManager quit, failed: ...
+
+Logger Instance
+---------------
+
+The global logger is accessible as ``web_runner_logger``:
+
+.. code-block:: python
+
+ from je_web_runner.utils.logging.loggin_instance import web_runner_logger
+
+ web_runner_logger.warning("Custom warning message")
+
+All WebRunner components use this logger internally to record their operations.
diff --git a/docs/source/Eng/doc/package_manager/package_manager_doc.rst b/docs/source/Eng/doc/package_manager/package_manager_doc.rst
new file mode 100644
index 0000000..397af30
--- /dev/null
+++ b/docs/source/Eng/doc/package_manager/package_manager_doc.rst
@@ -0,0 +1,89 @@
+Package Manager
+===============
+
+Overview
+--------
+
+The Package Manager dynamically loads external Python packages into the executor at runtime.
+This allows you to extend the executor's capabilities without modifying the source code.
+
+When a package is added, all its public functions and classes are extracted and
+registered in the executor's event dictionary with the naming convention ``{package}_{function}``.
+
+Usage via Action Executor
+-------------------------
+
+.. code-block:: python
+
+ from je_web_runner import execute_action
+
+ actions = [
+ # Load the 'time' package into executor
+ ["WR_add_package_to_executor", {"package": "time"}],
+
+ # Now you can use time.sleep as "time_sleep"
+ ["time_sleep", [2]],
+ ]
+
+ execute_action(actions)
+
+Direct API Usage
+----------------
+
+.. code-block:: python
+
+ from je_web_runner.utils.package_manager.package_manager_class import package_manager
+
+ # Check if a package exists and import it
+ module = package_manager.check_package("os")
+
+ # Add all functions from a package to the executor
+ package_manager.add_package_to_executor("math")
+
+ # Add to callback executor instead
+ package_manager.add_package_to_callback_executor("time")
+
+Methods
+-------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 30 40
+
+ * - Method
+ - Parameters
+ - Description
+ * - ``check_package()``
+ - ``package: str``
+ - Check and import a package, returns the module or None
+ * - ``add_package_to_executor()``
+ - ``package: str``
+ - Add package members to the Executor's event_dict
+ * - ``add_package_to_callback_executor()``
+ - ``package: str``
+ - Add package members to the CallbackExecutor's event_dict
+ * - ``get_member()``
+ - ``package, predicate, target``
+ - Extract and add specific members from a package
+ * - ``add_package_to_target()``
+ - ``package: str, target``
+ - Add functions/classes to a target executor
+
+Naming Convention
+-----------------
+
+When a package is added, functions are registered with the prefix ``{package_name}_``.
+
+For example, adding the ``time`` package registers:
+
+* ``time_sleep`` -> ``time.sleep``
+* ``time_time`` -> ``time.time``
+* ``time_monotonic`` -> ``time.monotonic``
+* etc.
+
+Package Caching
+---------------
+
+The ``installed_package_dict`` attribute caches imported packages to avoid
+re-importing them. If a package has already been loaded, subsequent calls
+to ``check_package()`` return the cached module.
diff --git a/docs/source/Eng/doc/quick_start/quick_start_doc.rst b/docs/source/Eng/doc/quick_start/quick_start_doc.rst
new file mode 100644
index 0000000..49fc883
--- /dev/null
+++ b/docs/source/Eng/doc/quick_start/quick_start_doc.rst
@@ -0,0 +1,101 @@
+Quick Start
+===========
+
+This guide demonstrates the three main ways to use WebRunner.
+
+Example 1: Direct Python API
+-----------------------------
+
+Use WebRunner's classes and functions directly in Python code.
+
+.. code-block:: python
+
+ from je_web_runner import TestObject
+ from je_web_runner import get_webdriver_manager
+ from je_web_runner import web_element_wrapper
+
+ # Create a WebDriver manager (using Chrome)
+ manager = get_webdriver_manager("chrome")
+
+ # Navigate to a URL
+ manager.webdriver_wrapper.to_url("https://www.google.com")
+
+ # Set implicit wait
+ manager.webdriver_wrapper.implicitly_wait(2)
+
+ # Create a test object to locate the search box by name
+ search_box = TestObject("q", "name")
+
+ # Find the element
+ manager.webdriver_wrapper.find_element(search_box)
+
+ # Click and type into the element
+ web_element_wrapper.click_element()
+ web_element_wrapper.input_to_element("WebRunner automation")
+
+ # Close the browser
+ manager.quit()
+
+Example 2: JSON Action List
+-----------------------------
+
+Define automation scripts as JSON action lists and execute them through the Action Executor.
+
+.. code-block:: python
+
+ from je_web_runner import execute_action
+
+ actions = [
+ ["WR_get_webdriver_manager", {"webdriver_name": "chrome"}],
+ ["WR_to_url", {"url": "https://www.google.com"}],
+ ["WR_implicitly_wait", {"time_to_wait": 2}],
+ ["WR_SaveTestObject", {"test_object_name": "q", "object_type": "name"}],
+ ["WR_find_element", {"element_name": "q"}],
+ ["WR_click_element"],
+ ["WR_input_to_element", {"input_value": "WebRunner automation"}],
+ ["WR_quit"]
+ ]
+
+ result = execute_action(actions)
+
+Example 3: Execute from JSON File
+-----------------------------------
+
+Create an ``actions.json`` file:
+
+.. code-block:: json
+
+ [
+ ["WR_get_webdriver_manager", {"webdriver_name": "chrome"}],
+ ["WR_to_url", {"url": "https://example.com"}],
+ ["WR_quit"]
+ ]
+
+Execute it in Python:
+
+.. code-block:: python
+
+ from je_web_runner import execute_files
+
+ results = execute_files(["actions.json"])
+
+Or via CLI:
+
+.. code-block:: bash
+
+ python -m je_web_runner -e actions.json
+
+Example 4: Headless Mode
+--------------------------
+
+Run browsers without a visible GUI window using headless mode.
+
+.. code-block:: python
+
+ from je_web_runner import get_webdriver_manager
+
+ manager = get_webdriver_manager("chrome", options=["--headless", "--disable-gpu"])
+ manager.webdriver_wrapper.to_url("https://example.com")
+ title = manager.webdriver_wrapper.execute_script("return document.title")
+ print(f"Page title: {title}")
+ manager.quit()
diff --git a/docs/source/Eng/doc/scheduler/scheduler_doc.rst b/docs/source/Eng/doc/scheduler/scheduler_doc.rst
deleted file mode 100644
index 13e3939..0000000
--- a/docs/source/Eng/doc/scheduler/scheduler_doc.rst
+++ /dev/null
@@ -1,19 +0,0 @@
-Scheduler
-----
-
-You can use scheduling to perform repetitive tasks, either by using a simple wrapper for APScheduler or by consulting the API documentation to use it yourself.
-
-.. code-block:: python
-
- from je_web_runner import SchedulerManager
-
-
- def test_scheduler():
- print("Test Scheduler")
- scheduler.remove_blocking_job(id="test")
- scheduler.shutdown_blocking_scheduler()
-
-
- scheduler = SchedulerManager()
- scheduler.add_interval_blocking_secondly(function=test_scheduler, id="test")
- scheduler.start_block_scheduler()
diff --git a/docs/source/Eng/doc/socket_driver/socket_driver_doc.rst b/docs/source/Eng/doc/socket_driver/socket_driver_doc.rst
index 29ec49f..7ee10ab 100644
--- a/docs/source/Eng/doc/socket_driver/socket_driver_doc.rst
+++ b/docs/source/Eng/doc/socket_driver/socket_driver_doc.rst
@@ -1,25 +1,96 @@
-Socket Driver
-----
-
-* This is an experimental feature.
-* The Socket Server is mainly used to allow other programming languages to use AutoControl.
-* It processes received strings and performs actions through the underlying executor.
-* Tests can be performed remotely through this feature.
-* Currently, Java and C# support are experimental.
-* Return_Data_Over_JE should be transmitted at the end of each paragraph.
-* UTF-8 encoding is used.
-* Sending quit_server will shut down the server.
+Remote Automation (Socket Server)
+=================================
+
+Overview
+--------
+
+WebRunner includes a multi-threaded TCP socket server for remote automation control.
+This enables cross-language support -- any language that supports TCP sockets
+(Java, C#, Go, etc.) can send automation commands to WebRunner.
+
+Starting the Server
+-------------------
+
+.. code-block:: python
+
+ from je_web_runner import start_web_runner_socket_server
+
+ server = start_web_runner_socket_server(host="localhost", port=9941)
+
+The server starts in a background daemon thread and is ready to accept connections immediately.
+
+You can also override the host and port via command-line arguments:
+
+.. code-block:: bash
+
+ python your_script.py localhost 9941
+
+Client Connection Example
+-------------------------
.. code-block:: python
- import sys
+ import socket
+ import json
+
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.connect(("localhost", 9941))
+
+ # Send actions as JSON (UTF-8 encoded)
+ actions = [
+ ["WR_get_webdriver_manager", {"webdriver_name": "chrome"}],
+ ["WR_to_url", {"url": "https://example.com"}],
+ ["WR_quit"]
+ ]
+ sock.send(json.dumps(actions).encode("utf-8"))
+
+ # Receive results (ends with "Return_Data_Over_JE\n")
+ response = sock.recv(4096).decode("utf-8")
+ print(response)
+
+ # Shutdown server
+ sock.send("quit_server".encode("utf-8"))
+
+Protocol
+--------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 70
+
+ * - Property
+ - Value
+ * - Default host
+ - ``localhost``
+ * - Default port
+ - ``9941``
+ * - Encoding
+ - UTF-8
+ * - Message format
+ - JSON array of actions
+ * - Max receive buffer
+ - 8192 bytes
+ * - Response terminator
+ - ``Return_Data_Over_JE``
+ * - Shutdown command
+ - ``quit_server``
+ * - Threading model
+ - Multi-threaded (``socketserver.ThreadingMixIn``)
+
+Response Format
+---------------
+
+After executing actions, the server sends results back one by one.
+Each result is followed by a newline (``\n``).
+The final message is always ``Return_Data_Over_JE\n``.
+
+If an error occurs, the error message is sent to the client followed by the same terminator.
+
+Server Classes
+--------------
- from je_web_runner import start_autocontrol_socket_server
+**TCPServerHandler** (extends ``socketserver.BaseRequestHandler``):
+Handles incoming requests, parses JSON, executes actions, and returns results.
- try:
- server = start_autocontrol_socket_server()
- while not server.close_flag:
- pass
- sys.exit(0)
- except Exception as error:
- print(repr(error))
\ No newline at end of file
+**TCPServer** (extends ``socketserver.ThreadingMixIn, socketserver.TCPServer``):
+Multi-threaded TCP server with a ``close_flag`` attribute for shutdown control.
diff --git a/docs/source/Eng/doc/test_object/test_object_doc.rst b/docs/source/Eng/doc/test_object/test_object_doc.rst
new file mode 100644
index 0000000..540954c
--- /dev/null
+++ b/docs/source/Eng/doc/test_object/test_object_doc.rst
@@ -0,0 +1,90 @@
+Test Object
+===========
+
+Overview
+--------
+
+``TestObject`` encapsulates element locator information (strategy + value) for reusable element definitions.
+``TestObjectRecord`` stores ``TestObject`` instances by name for later retrieval by the ``_with_test_object`` methods.
+
+Creating Test Objects
+---------------------
+
+.. code-block:: python
+
+ from je_web_runner import TestObject, create_test_object, get_test_object_type_list
+
+ # Constructor: TestObject(test_object_name, test_object_type)
+ obj1 = TestObject("search", "name")
+
+ # Factory function: create_test_object(object_type, test_object_name)
+ obj2 = create_test_object("id", "submit-btn")
+
+Available Locator Types
+-----------------------
+
+.. code-block:: python
+
+ print(get_test_object_type_list())
+ # ['ID', 'NAME', 'XPATH', 'CSS_SELECTOR', 'CLASS_NAME',
+ # 'TAG_NAME', 'LINK_TEXT', 'PARTIAL_LINK_TEXT']
+
+These map directly to Selenium's ``By`` class constants.
+
+TestObject Attributes
+---------------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 30 40
+
+ * - Attribute
+ - Type
+ - Description
+ * - ``test_object_type``
+ - ``str``
+ - Locator strategy (e.g., ``"name"``, ``"xpath"``)
+ * - ``test_object_name``
+ - ``str``
+ - Locator value (e.g., ``"search"``, ``"//div[@id='main']"``)
+
+TestObjectRecord
+----------------
+
+``TestObjectRecord`` stores ``TestObject`` instances by name. This is used by the Action Executor
+to reference elements by string names (e.g., ``WR_SaveTestObject`` and ``WR_find_element``).
+
+.. code-block:: python
+
+ from je_web_runner.utils.test_object.test_object_record.test_object_record_class import test_object_record
+
+ # Save a test object
+ test_object_record.save_test_object("search_box", "name")
+
+ # Remove a test object
+ test_object_record.remove_test_object("search_box")
+
+ # Clear all records
+ test_object_record.clean_record()
+
+Usage in Action Executor
+------------------------
+
+.. code-block:: python
+
+ from je_web_runner import execute_action
+
+ execute_action([
+ # Save a test object with name "search" and locator type "name"
+ ["WR_SaveTestObject", {"test_object_name": "search", "object_type": "name"}],
+
+ # Find the element by its saved name
+ ["WR_find_element", {"element_name": "search"}],
+
+ # Interact with the found element
+ ["WR_click_element"],
+ ["WR_input_to_element", {"input_value": "hello"}],
+
+ # Clean all saved test objects
+ ["WR_CleanTestObject"],
+ ])
diff --git a/docs/source/Eng/doc/test_record/test_record_doc.rst b/docs/source/Eng/doc/test_record/test_record_doc.rst
new file mode 100644
index 0000000..2890797
--- /dev/null
+++ b/docs/source/Eng/doc/test_record/test_record_doc.rst
@@ -0,0 +1,107 @@
+Test Record
+===========
+
+Overview
+--------
+
+All WebRunner actions can be automatically recorded for audit trails and report generation.
+The global instance ``test_record_instance`` manages recording state and stores records.
+
+Enable Recording
+----------------
+
+Recording is disabled by default. Enable it before running actions:
+
+.. code-block:: python
+
+ from je_web_runner import test_record_instance
+
+ test_record_instance.set_record_enable(True)
+
+Or via the action executor:
+
+.. code-block:: python
+
+ from je_web_runner import execute_action
+
+ execute_action([
+ ["WR_set_record_enable", {"set_enable": True}],
+ # ... your actions ...
+ ])
+
+Accessing Records
+-----------------
+
+.. code-block:: python
+
+ from je_web_runner import test_record_instance
+
+ test_record_instance.set_record_enable(True)
+
+ # ... perform automation ...
+
+ # Access records
+ records = test_record_instance.test_record_list
+
+ for record in records:
+ print(record)
+
+Record Format
+-------------
+
+Each record is a dictionary with the following fields:
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 25 50
+
+ * - Field
+ - Type
+ - Description
+ * - ``function_name``
+ - ``str``
+ - Name of the executed function
+ * - ``local_param``
+ - ``dict | None``
+ - Parameters passed to the function
+ * - ``time``
+ - ``str``
+ - Timestamp of execution (e.g., ``"2025-01-01 12:00:00"``)
+ * - ``program_exception``
+ - ``str``
+ - Exception message or ``"None"`` if successful
+
+Example record:
+
+.. code-block:: python
+
+ {
+ "function_name": "to_url",
+ "local_param": {"url": "https://example.com"},
+ "time": "2025-01-01 12:00:00",
+ "program_exception": "None"
+ }
+
+Clearing Records
+----------------
+
+.. code-block:: python
+
+ test_record_instance.clean_record()
+
+Attributes
+----------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 25 50
+
+ * - Attribute
+ - Type
+ - Description
+ * - ``test_record_list``
+ - ``list``
+ - List of all recorded actions
+ * - ``init_record``
+ - ``bool``
+ - Whether recording is currently enabled
diff --git a/docs/source/Eng/doc/web_element/web_element_doc.rst b/docs/source/Eng/doc/web_element/web_element_doc.rst
new file mode 100644
index 0000000..6ab833e
--- /dev/null
+++ b/docs/source/Eng/doc/web_element/web_element_doc.rst
@@ -0,0 +1,162 @@
+Web Element Wrapper
+===================
+
+Overview
+--------
+
+``WebElementWrapper`` provides methods for interacting with located elements.
+It operates on the currently active element set by ``find_element()`` or ``find_elements()``.
+
+The global instance ``web_element_wrapper`` is imported from ``je_web_runner``.
+
+Basic Interactions
+------------------
+
+.. code-block:: python
+
+ from je_web_runner import web_element_wrapper
+
+ web_element_wrapper.click_element() # Click element
+ web_element_wrapper.input_to_element("Hello World") # Type text
+ web_element_wrapper.clear() # Clear content
+ web_element_wrapper.submit() # Submit form
+
+Attribute and Property Inspection
+---------------------------------
+
+.. code-block:: python
+
+ web_element_wrapper.get_attribute("href") # Get HTML attribute
+ web_element_wrapper.get_property("checked") # Get JS property
+ web_element_wrapper.get_dom_attribute("data-id") # Get DOM attribute
+
+State Checks
+------------
+
+.. code-block:: python
+
+ web_element_wrapper.is_displayed() # Check visibility
+ web_element_wrapper.is_enabled() # Check if enabled
+ web_element_wrapper.is_selected() # Check if selected (checkbox/radio)
+
+CSS Property
+------------
+
+.. code-block:: python
+
+ web_element_wrapper.value_of_css_property("color")
+
+Dropdown (Select) Handling
+--------------------------
+
+.. code-block:: python
+
+ select = web_element_wrapper.get_select()
+ # Now use Selenium's Select API:
+ # select.select_by_visible_text("Option 1")
+ # select.select_by_value("opt1")
+ # select.select_by_index(0)
+
+Element Screenshot
+------------------
+
+.. code-block:: python
+
+ web_element_wrapper.screenshot("element") # Saves as element.png
+
+Switching Elements
+------------------
+
+When ``find_elements()`` returns multiple elements, use ``change_web_element()``
+to switch the active element:
+
+.. code-block:: python
+
+ # After find_elements, switch to the 3rd element (index 2)
+ web_element_wrapper.change_web_element(2)
+ web_element_wrapper.click_element()
+
+Element Validation
+------------------
+
+Validate element properties against expected values:
+
+.. code-block:: python
+
+ web_element_wrapper.check_current_web_element({
+ "tag_name": "input",
+ "enabled": True
+ })
+
+Key Attributes
+--------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 30 40
+
+ * - Attribute
+ - Type
+ - Description
+ * - ``current_web_element``
+ - ``WebElement | None``
+ - Currently active element
+ * - ``current_web_element_list``
+ - ``List[WebElement] | None``
+ - List of found elements (from ``find_elements``)
+
+Methods Reference
+-----------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 35 40
+
+ * - Method
+ - Parameters
+ - Description
+ * - ``click_element()``
+ -
+ - Click the current element
+ * - ``input_to_element()``
+ - ``input_value: str``
+ - Type text into the element
+ * - ``clear()``
+ -
+ - Clear element content
+ * - ``submit()``
+ -
+ - Submit the form
+ * - ``get_attribute()``
+ - ``name: str``
+ - Get HTML attribute value
+ * - ``get_property()``
+ - ``name: str``
+ - Get JavaScript property value
+ * - ``get_dom_attribute()``
+ - ``name: str``
+ - Get DOM attribute value
+ * - ``is_displayed()``
+ -
+ - Check if element is visible
+ * - ``is_enabled()``
+ -
+ - Check if element is enabled
+ * - ``is_selected()``
+ -
+ - Check if element is selected
+ * - ``value_of_css_property()``
+ - ``property_name: str``
+ - Get CSS property value
+ * - ``screenshot()``
+ - ``filename: str``
+ - Take element screenshot
+ * - ``change_web_element()``
+ - ``element_index: int``
+ - Switch active element by index
+ * - ``check_current_web_element()``
+ - ``check_dict: dict``
+ - Validate element properties
+ * - ``get_select()``
+ -
+ - Get Select object for dropdown
diff --git a/docs/source/Eng/doc/webdriver_manager/webdriver_manager_doc.rst b/docs/source/Eng/doc/webdriver_manager/webdriver_manager_doc.rst
new file mode 100644
index 0000000..c8abfcf
--- /dev/null
+++ b/docs/source/Eng/doc/webdriver_manager/webdriver_manager_doc.rst
@@ -0,0 +1,102 @@
+WebDriver Manager
+=================
+
+Overview
+--------
+
+``WebdriverManager`` manages multiple WebDriver instances for parallel browser automation.
+It maintains a list of active WebDriver instances and provides methods to create, switch between, and close them.
+
+The global instance ``web_runner`` is used internally. Access it via the factory function ``get_webdriver_manager()``.
+
+Creating a Manager
+------------------
+
+.. code-block:: python
+
+ from je_web_runner import get_webdriver_manager
+
+ # Create with Chrome
+ manager = get_webdriver_manager("chrome")
+
+ # Create with Firefox and options
+ manager = get_webdriver_manager("firefox", options=["--headless"])
+
+The factory function creates a new WebDriver and returns the global ``WebdriverManager`` instance.
+
+Managing Multiple Browsers
+--------------------------
+
+.. code-block:: python
+
+ manager = get_webdriver_manager("chrome")
+
+ # Add a second browser instance
+ manager.new_driver("firefox")
+
+ # Switch to Chrome (index 0)
+ manager.change_webdriver(0)
+ manager.webdriver_wrapper.to_url("https://example.com")
+
+ # Switch to Firefox (index 1)
+ manager.change_webdriver(1)
+ manager.webdriver_wrapper.to_url("https://google.com")
+
+ # Close Firefox only
+ manager.close_choose_webdriver(1)
+
+ # Close the current browser
+ manager.close_current_webdriver()
+
+ # Close and quit ALL browsers
+ manager.quit()
+
+Key Attributes
+--------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 30 40
+
+ * - Attribute
+ - Type
+ - Description
+ * - ``webdriver_wrapper``
+ - ``WebDriverWrapper``
+ - Wrapper for WebDriver operations
+ * - ``webdriver_element``
+ - ``WebElementWrapper``
+ - Wrapper for element operations
+ * - ``current_webdriver``
+ - ``WebDriver | None``
+ - Currently active WebDriver instance
+
+Methods
+-------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 40 30
+
+ * - Method
+ - Parameters
+ - Description
+ * - ``new_driver()``
+ - ``webdriver_name: str, options: List[str] = None, **kwargs``
+ - Create a new WebDriver instance
+ * - ``change_webdriver()``
+ - ``index_of_webdriver: int``
+ - Switch to a WebDriver by index
+ * - ``close_current_webdriver()``
+ -
+ - Close the current WebDriver
+ * - ``close_choose_webdriver()``
+ - ``webdriver_index: int``
+ - Close a WebDriver by index
+ * - ``quit()``
+ -
+ - Close and quit all WebDrivers
+
+.. note::
+
+ When ``quit()`` is called, it also cleans up all saved ``TestObjectRecord`` entries.
diff --git a/docs/source/Eng/doc/webdriver_options/webdriver_options_doc.rst b/docs/source/Eng/doc/webdriver_options/webdriver_options_doc.rst
new file mode 100644
index 0000000..7472391
--- /dev/null
+++ b/docs/source/Eng/doc/webdriver_options/webdriver_options_doc.rst
@@ -0,0 +1,93 @@
+WebDriver Options Configuration
+================================
+
+Overview
+--------
+
+Configure browser options and capabilities before launching a WebDriver instance.
+This is useful for headless mode, disabling GPU, setting window size, etc.
+
+Browser Arguments
+-----------------
+
+.. code-block:: python
+
+ from je_web_runner import set_webdriver_options_argument, get_webdriver_manager
+
+ # Set browser arguments (returns Options object)
+ options = set_webdriver_options_argument("chrome", [
+ "--headless",
+ "--disable-gpu",
+ "--no-sandbox",
+ "--window-size=1920,1080"
+ ])
+
+ # Or pass options directly when creating the manager
+ manager = get_webdriver_manager("chrome", options=["--headless", "--disable-gpu"])
+
+Common Chrome Arguments
+~~~~~~~~~~~~~~~~~~~~~~~
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 70
+
+ * - Argument
+ - Description
+ * - ``--headless``
+ - Run without visible GUI
+ * - ``--disable-gpu``
+ - Disable GPU hardware acceleration
+ * - ``--no-sandbox``
+ - Disable sandbox (required in some Linux environments)
+ * - ``--window-size=W,H``
+ - Set initial window size
+ * - ``--incognito``
+ - Open in incognito mode
+ * - ``--disable-extensions``
+ - Disable browser extensions
+ * - ``--start-maximized``
+ - Start with maximized window
+
+DesiredCapabilities
+-------------------
+
+.. code-block:: python
+
+ from je_web_runner import get_desired_capabilities, get_desired_capabilities_keys
+
+ # View available capability keys (browser names)
+ keys = get_desired_capabilities_keys()
+ # dict_keys(['CHROME', 'FIREFOX', 'EDGE', ...])
+
+ # Get capabilities for a specific browser
+ caps = get_desired_capabilities("CHROME")
+
+Setting Capabilities via Wrapper
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+ from je_web_runner.webdriver.webdriver_with_options import set_webdriver_options_capability_wrapper
+
+ options = set_webdriver_options_capability_wrapper("chrome", {
+ "acceptInsecureCerts": True
+ })
+
+Functions Reference
+-------------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 35 65
+
+ * - Function
+ - Description
+ * - ``set_webdriver_options_argument(webdriver_name, argument_iterable)``
+ - Set browser startup arguments; returns ``Options`` object
+ * - ``set_webdriver_options_capability_wrapper(webdriver_name, key_and_vale_dict)``
+ - Set browser capabilities; returns ``Options`` object
+ * - ``get_desired_capabilities_keys()``
+ - Get available browser names
+ * - ``get_desired_capabilities(webdriver_name)``
+ - Get DesiredCapabilities dict for a browser
diff --git a/docs/source/Eng/doc/webdriver_wrapper/webdriver_wrapper_doc.rst b/docs/source/Eng/doc/webdriver_wrapper/webdriver_wrapper_doc.rst
new file mode 100644
index 0000000..fa2c61b
--- /dev/null
+++ b/docs/source/Eng/doc/webdriver_wrapper/webdriver_wrapper_doc.rst
@@ -0,0 +1,198 @@
+WebDriver Wrapper
+=================
+
+Overview
+--------
+
+``WebDriverWrapper`` is the central component that wraps Selenium WebDriver with comprehensive methods.
+It provides a unified interface for browser control, element finding, mouse/keyboard actions,
+cookie management, JavaScript execution, window management, and more.
+
+The global instance ``webdriver_wrapper_instance`` is used internally by the manager and executor.
+
+Navigation
+----------
+
+.. code-block:: python
+
+ wrapper = manager.webdriver_wrapper
+
+ wrapper.to_url("https://example.com") # Navigate to URL
+ wrapper.forward() # Go forward
+ wrapper.back() # Go back
+ wrapper.refresh() # Refresh the page
+
+Element Finding
+---------------
+
+Find elements using ``TestObject`` locators:
+
+.. code-block:: python
+
+ from je_web_runner import TestObject
+
+ # Locator strategies: id, name, xpath, css selector,
+ # class name, tag name, link text, partial link text
+ element = TestObject("search-input", "id")
+
+ wrapper.find_element(element) # Find single element
+ wrapper.find_elements(element) # Find multiple elements
+
+Find elements using saved ``TestObjectRecord`` names:
+
+.. code-block:: python
+
+ # Using saved test objects (stored by name in TestObjectRecord)
+ wrapper.find_element_with_test_object_record("search-input")
+ wrapper.find_elements_with_test_object_record("search-input")
+
+Wait Methods
+------------
+
+.. code-block:: python
+
+ wrapper.implicitly_wait(5) # Implicit wait (seconds)
+ wrapper.explict_wait( # Explicit WebDriverWait
+ wait_condition,
+ timeout=10,
+ poll_frequency=0.5
+ )
+ wrapper.set_script_timeout(30) # Async script timeout
+ wrapper.set_page_load_timeout(60) # Page load timeout
+
+Mouse Actions
+-------------
+
+**Basic clicks (at current position):**
+
+.. code-block:: python
+
+ wrapper.left_click()
+ wrapper.right_click()
+ wrapper.left_double_click()
+ wrapper.left_click_and_hold()
+ wrapper.release()
+
+**Clicks on saved test objects:**
+
+.. code-block:: python
+
+ wrapper.left_click_with_test_object("button_name")
+ wrapper.right_click_with_test_object("button_name")
+ wrapper.left_double_click_with_test_object("button_name")
+ wrapper.left_click_and_hold_with_test_object("button_name")
+ wrapper.release_with_test_object("button_name")
+
+**Drag and drop:**
+
+.. code-block:: python
+
+ wrapper.drag_and_drop(source_element, target_element)
+ wrapper.drag_and_drop_offset(element, x=100, y=50)
+
+ # Using saved test objects
+ wrapper.drag_and_drop_with_test_object("source_name", "target_name")
+ wrapper.drag_and_drop_offset_with_test_object("element_name", offset_x=100, offset_y=50)
+
+**Mouse movement:**
+
+.. code-block:: python
+
+ wrapper.move_to_element(element) # Hover over element
+ wrapper.move_to_element_with_test_object("element_name")
+ wrapper.move_to_element_with_offset(element, offset_x=10, offset_y=10)
+ wrapper.move_to_element_with_offset_and_test_object("name", offset_x=10, offset_y=10)
+ wrapper.move_by_offset(100, 200) # Move from current position
+
+Keyboard Actions
+----------------
+
+.. code-block:: python
+
+ wrapper.press_key(keycode)
+ wrapper.press_key_with_test_object(keycode)
+ wrapper.release_key(keycode)
+ wrapper.release_key_with_test_object(keycode)
+ wrapper.send_keys("text")
+ wrapper.send_keys_to_element(element, "text")
+ wrapper.send_keys_to_element_with_test_object("element_name", "text")
+
+Action Chain Control
+--------------------
+
+.. code-block:: python
+
+ wrapper.perform() # Execute queued actions
+ wrapper.reset_actions() # Clear action queue
+ wrapper.pause(2) # Pause in action chain (seconds)
+ wrapper.scroll(0, 500) # Scroll page by offset
+
+Cookie Management
+-----------------
+
+.. code-block:: python
+
+ wrapper.get_cookies() # Get all cookies
+ wrapper.get_cookie("session_id") # Get specific cookie
+ wrapper.add_cookie({"name": "key", "value": "val"})
+ wrapper.delete_cookie("session_id")
+ wrapper.delete_all_cookies()
+
+JavaScript Execution
+--------------------
+
+.. code-block:: python
+
+ wrapper.execute("document.title")
+ wrapper.execute_script("return document.title")
+ wrapper.execute_async_script("arguments[0]('done')", callback)
+
+Window Management
+-----------------
+
+.. code-block:: python
+
+ wrapper.maximize_window()
+ wrapper.minimize_window()
+ wrapper.fullscreen_window()
+ wrapper.set_window_size(1920, 1080)
+ wrapper.set_window_position(0, 0)
+ wrapper.get_window_position() # Returns dict
+ wrapper.get_window_rect() # Returns dict
+ wrapper.set_window_rect(x=0, y=0, width=1920, height=1080)
+
+Screenshots
+-----------
+
+.. code-block:: python
+
+ wrapper.get_screenshot_as_png() # Returns bytes
+ wrapper.get_screenshot_as_base64() # Returns base64 string
+
+Frame / Window / Alert Switching
+--------------------------------
+
+The ``switch()`` method supports the following context types:
+
+.. code-block:: python
+
+ wrapper.switch("frame", "frame_name")
+ wrapper.switch("window", "window_handle")
+ wrapper.switch("default_content")
+ wrapper.switch("parent_frame")
+ wrapper.switch("active_element")
+ wrapper.switch("alert")
+
+Browser Logs
+------------
+
+.. code-block:: python
+
+ wrapper.get_log("browser")
+
+WebDriver Validation
+--------------------
+
+.. code-block:: python
+
+ wrapper.check_current_webdriver({"name": "chrome"})
diff --git a/docs/source/Eng/eng_index.rst b/docs/source/Eng/eng_index.rst
index b737ef5..1fa7c80 100644
--- a/docs/source/Eng/eng_index.rst
+++ b/docs/source/Eng/eng_index.rst
@@ -6,9 +6,20 @@ WebRunner English Documentation
:maxdepth: 4
doc/installation/installation_doc.rst
+ doc/quick_start/quick_start_doc.rst
+ doc/webdriver_manager/webdriver_manager_doc.rst
+ doc/webdriver_wrapper/webdriver_wrapper_doc.rst
+ doc/web_element/web_element_doc.rst
+ doc/test_object/test_object_doc.rst
+ doc/action_executor/action_executor_doc.rst
doc/create_project/create_project_doc.rst
doc/generate_report/generate_report_doc.rst
doc/callback_function/callback_function_doc.rst
doc/cli/cli_doc.rst
- doc/scheduler/scheduler_doc.rst
- doc/socket_driver/socket_driver_doc.rst
\ No newline at end of file
+ doc/socket_driver/socket_driver_doc.rst
+ doc/package_manager/package_manager_doc.rst
+ doc/webdriver_options/webdriver_options_doc.rst
+ doc/assertion/assertion_doc.rst
+ doc/test_record/test_record_doc.rst
+ doc/exception/exception_doc.rst
+ doc/logging/logging_doc.rst
diff --git a/docs/source/Zh/doc/action_executor/action_executor_doc.rst b/docs/source/Zh/doc/action_executor/action_executor_doc.rst
new file mode 100644
index 0000000..a047c26
--- /dev/null
+++ b/docs/source/Zh/doc/action_executor/action_executor_doc.rst
@@ -0,0 +1,96 @@
+動作執行器
+==========
+
+概述
+----
+
+動作執行器是一個強大的引擎,將指令字串對應到可呼叫的函式。
+它允許您將自動化腳本定義為 JSON 動作列表,實現資料驅動的自動化工作流程。
+
+執行器還包含所有 Python 內建函式,因此可以在動作列表中呼叫 ``print``、``len`` 等。
+
+動作格式
+--------
+
+每個動作是一個包含指令名稱和可選參數的列表:
+
+.. code-block:: python
+
+ ["command_name"] # 無參數
+ ["command_name", {"key": "value"}] # 關鍵字參數
+ ["command_name", [arg1, arg2]] # 位置參數
+
+基本用法
+--------
+
+.. code-block:: python
+
+ from je_web_runner import execute_action
+
+ actions = [
+ ["WR_get_webdriver_manager", {"webdriver_name": "chrome"}],
+ ["WR_to_url", {"url": "https://www.google.com"}],
+ ["WR_implicitly_wait", {"time_to_wait": 2}],
+ ["WR_SaveTestObject", {"test_object_name": "q", "object_type": "name"}],
+ ["WR_find_element", {"element_name": "q"}],
+ ["WR_click_element"],
+ ["WR_input_to_element", {"input_value": "WebRunner"}],
+ ["WR_quit"]
+ ]
+
+ result = execute_action(actions)
+
+``execute_action()`` 回傳一個字典,將每個動作對應到其回傳值。
+
+可用指令
+--------
+
+完整的指令列表請參閱英文文件的 Action Executor 頁面。
+
+主要指令分類:
+
+* **WebDriver 管理**:``WR_get_webdriver_manager``, ``WR_change_index_of_webdriver``, ``WR_quit``
+* **導航**:``WR_to_url``, ``WR_forward``, ``WR_back``, ``WR_refresh``
+* **元素尋找**:``WR_find_element``, ``WR_find_elements``
+* **等待**:``WR_implicitly_wait``, ``WR_explict_wait``, ``WR_set_script_timeout``, ``WR_set_page_load_timeout``
+* **滑鼠**:``WR_left_click``, ``WR_right_click``, ``WR_left_double_click``, ``WR_drag_and_drop``
+* **鍵盤**:``WR_press_key``, ``WR_release_key``, ``WR_send_keys``, ``WR_send_keys_to_element``
+* **Cookie**:``WR_get_cookies``, ``WR_add_cookie``, ``WR_delete_cookie``, ``WR_delete_all_cookies``
+* **JavaScript**:``WR_execute``, ``WR_execute_script``, ``WR_execute_async_script``
+* **視窗**:``WR_maximize_window``, ``WR_minimize_window``, ``WR_set_window_size``
+* **截圖**:``WR_get_screenshot_as_png``, ``WR_get_screenshot_as_base64``
+* **元素操作**:``WR_click_element``, ``WR_input_to_element``, ``WR_element_clear``, ``WR_element_submit``
+* **測試物件**:``WR_SaveTestObject``, ``WR_CleanTestObject``
+* **報告**:``WR_generate_html_report``, ``WR_generate_json_report``, ``WR_generate_xml_report``
+* **套件**:``WR_add_package_to_executor``, ``WR_add_package_to_callback_executor``
+
+從 JSON 檔案執行
+-----------------
+
+.. code-block:: python
+
+ from je_web_runner import execute_files
+
+ results = execute_files(["actions1.json", "actions2.json"])
+
+新增自訂指令
+------------
+
+.. code-block:: python
+
+ from je_web_runner import add_command_to_executor
+
+ def my_custom_function(param1, param2):
+ print(f"自訂: {param1}, {param2}")
+
+ add_command_to_executor({"my_command": my_custom_function})
+
+ # 在動作列表中使用
+ execute_action([
+ ["my_command", {"param1": "hello", "param2": "world"}]
+ ])
+
+.. note::
+
+ 僅接受 ``types.MethodType`` 和 ``types.FunctionType``。
+ 傳入其他可呼叫類型會引發 ``WebRunnerAddCommandException``。
diff --git a/docs/source/Zh/doc/assertion/assertion_doc.rst b/docs/source/Zh/doc/assertion/assertion_doc.rst
new file mode 100644
index 0000000..fb902e1
--- /dev/null
+++ b/docs/source/Zh/doc/assertion/assertion_doc.rst
@@ -0,0 +1,61 @@
+斷言與驗證
+==========
+
+概述
+----
+
+WebRunner 提供斷言工具,用於在自動化過程中驗證 WebDriver 和 WebElement 的狀態。
+驗證失敗時會引發 ``WebRunnerAssertException``。
+
+WebDriver 驗證
+---------------
+
+.. code-block:: python
+
+ # 透過 WebDriverWrapper
+ wrapper.check_current_webdriver({"name": "chrome"})
+
+.. code-block:: python
+
+ # 透過工具函式
+ from je_web_runner.utils.assert_value.result_check import (
+ check_webdriver_value,
+ check_webdriver_values,
+ )
+
+ check_webdriver_value("name", "chrome", webdriver_instance)
+ check_webdriver_values({"name": "chrome"}, webdriver_instance)
+
+WebElement 驗證
+----------------
+
+.. code-block:: python
+
+ # 透過 WebElementWrapper
+ web_element_wrapper.check_current_web_element({
+ "tag_name": "input",
+ "enabled": True
+ })
+
+.. code-block:: python
+
+ # 透過工具函式
+ from je_web_runner.utils.assert_value.result_check import check_web_element_details
+
+ check_web_element_details(element, {
+ "tag_name": "input",
+ "enabled": True
+ })
+
+透過 Action Executor 使用
+--------------------------
+
+.. code-block:: python
+
+ from je_web_runner import execute_action
+
+ execute_action([
+ ["WR_get_webdriver_manager", {"webdriver_name": "chrome"}],
+ ["WR_check_current_webdriver", {"check_dict": {"name": "chrome"}}],
+ ["WR_quit"],
+ ])
diff --git a/docs/source/Zh/doc/callback_function/callback_function_doc.rst b/docs/source/Zh/doc/callback_function/callback_function_doc.rst
index e6fb018..4962e4e 100644
--- a/docs/source/Zh/doc/callback_function/callback_function_doc.rst
+++ b/docs/source/Zh/doc/callback_function/callback_function_doc.rst
@@ -1,51 +1,83 @@
-回調函數
+回調執行器
+==========
+
+概述
----
-在 AutoControl 裡,Callback function 是由 Callback Executor 提供支援,
-以下是簡易的使用 Callback Executor 的範例,
+回調執行器允許您執行自動化指令,並在完成後觸發回調函式。
+它包裝了標準執行器的事件字典,提供事件驅動的執行模型。
+
+全域實例 ``callback_executor`` 從 ``je_web_runner`` 匯入。
+
+基本用法
+--------
.. code-block:: python
from je_web_runner import callback_executor
- # trigger_function will first to execute, but return value need to wait everything done
- # so this test will first print("test") then print(size_function_return_value)
+
+ def on_complete():
+ print("導航完成!")
+
callback_executor.callback_function(
- trigger_function_name="WR_get_webdriver_manager",
- callback_function=print,
- callback_param_method="args",
- callback_function_param={"": "open driver"},
- **{
- "webdriver_name": "edge"
- }
+ trigger_function_name="WR_to_url",
+ callback_function=on_complete,
+ url="https://example.com"
)
-* ( 注意!,如果 callback_executor event_dict 裡面包含的 name: function 需與 executor 一樣,不一樣則是 Bug)
-* (當然跟 executor 一樣可以藉由添加外部 function 來擴充,請看下面例子)
+使用 kwargs 的回調
+-------------------
-在這個範例裡,我們使用 callback_executor 執行定義在 AutoControl 的 size function,
-然後執行完 size function 後,會去執行傳遞給 callback_function 的 function,
-可以由 callback_param_method 參數來決定要使用的傳遞方法,
-如果是 "args" 請傳入 {"value1", "value2", ...} 這裡 ... 代表可以複數傳入,
-如果是 "kwargs" 請傳入 {"actually_param_name": value, ...} 這裡 ... 代表可以複數傳入,
-然後如果要使用回傳值的話,由於回傳值會在所有 function 執行完後才回傳,
-實際上 size -> print 順序沒錯,但此例會先看到 print 之後才是 print(size_function_return_value),
-因為 size function 只有回傳值本身沒有 print 的動作。
+.. code-block:: python
-如果我們想要在 callback_executor 裡面添加 function,可以使用如下:
-這段程式碼會把所有 time module 的 builtin, function, method, class
-載入到 callback executor,然後要使用被載入的 function 需要使用 package_function 名稱,
-例如 time.sleep 會變成 time_sleep
+ def on_element_found(result=None):
+ print(f"找到元素: {result}")
-這段程式碼會把 time 所有的 function 加入 executor(直譯器裡)。
+ callback_executor.callback_function(
+ trigger_function_name="WR_find_element",
+ callback_function=on_element_found,
+ callback_function_param={"result": "search_box"},
+ callback_param_method="kwargs",
+ element_name="search_box"
+ )
+
+使用 args 的回調
+-----------------
.. code-block:: python
- from je_web_runner import package_manager
- package_manager.add_package_to_callback_executor("time")
+ def on_done(msg):
+ print(f"完成: {msg}")
-如果你需要查看被更新的 event_dict 可以使用
+ callback_executor.callback_function(
+ trigger_function_name="WR_quit",
+ callback_function=on_done,
+ callback_function_param=["所有瀏覽器已關閉"],
+ callback_param_method="args"
+ )
-.. code-block:: python
+參數
+----
- from je_web_runner import callback_executor
- print(callback_executor.event_dict)
\ No newline at end of file
+.. list-table::
+ :header-rows: 1
+ :widths: 25 20 55
+
+ * - 參數
+ - 型別
+ - 說明
+ * - ``trigger_function_name``
+ - ``str``
+ - 要觸發的函式名稱(必須存在於 ``event_dict``)
+ * - ``callback_function``
+ - ``Callable``
+ - 觸發後要執行的回調函式
+ * - ``callback_function_param``
+ - ``dict | list | None``
+ - 傳遞給回調的參數
+ * - ``callback_param_method``
+ - ``str``
+ - 回調參數傳遞方式:``"kwargs"`` 或 ``"args"``
+ * - ``**kwargs``
+ -
+ - 傳遞給觸發函式的參數
diff --git a/docs/source/Zh/doc/cli/cli_doc.rst b/docs/source/Zh/doc/cli/cli_doc.rst
index d8b5b2e..64bac6c 100644
--- a/docs/source/Zh/doc/cli/cli_doc.rst
+++ b/docs/source/Zh/doc/cli/cli_doc.rst
@@ -1,17 +1,61 @@
命令列介面
+==========
+
+概述
+----
+
+WebRunner 可以直接從命令列使用 ``je_web_runner`` 模組執行。
+
+指令
----
-我們可以使用 CLI 模式去執行 keyword.json 檔案或執行包含 Keyword.json files 的資料夾,
-以下這個範例是去執行指定路徑的關鍵字 json 檔
+**執行單一 JSON 動作檔案:**
+
+.. code-block:: bash
+
+ python -m je_web_runner -e actions.json
+ python -m je_web_runner --execute_file actions.json
+
+**執行目錄中所有 JSON 檔案:**
+
+.. code-block:: bash
+
+ python -m je_web_runner -d ./actions/
+ python -m je_web_runner --execute_dir ./actions/
+
+**直接執行 JSON 動作字串:**
+
+.. code-block:: bash
+
+ python -m je_web_runner --execute_str '[["WR_get_webdriver_manager", {"webdriver_name": "chrome"}], ["WR_quit"]]'
-.. code-block::
+指令參考
+--------
- python je_web_runner --execute_file "your_file_path"
+.. list-table::
+ :header-rows: 1
+ :widths: 25 15 60
+ * - 旗標
+ - 簡寫
+ - 說明
+ * - ``--execute_file``
+ - ``-e``
+ - 執行單一 JSON 動作檔案
+ * - ``--execute_dir``
+ - ``-d``
+ - 執行目錄中所有 JSON 檔案
+ * - ``--execute_str``
+ -
+ - 直接執行 JSON 動作字串
+.. note::
-以下這個範例是去執行指定路徑資料夾下所有的 keyword json 檔
+ 在 Windows 上,``--execute_str`` 選項可能需要雙重 JSON 解析(因 shell 跳脫字元)。
+ WebRunner 會自動處理此情況。
-.. code-block::
+錯誤處理
+--------
- python je_web_runner --execute_dir "your_dir_path"
\ No newline at end of file
+若未提供任何參數,WebRunner 會引發 ``WebRunnerExecuteException``。
+所有錯誤會輸出到 stderr,程序以代碼 1 退出。
diff --git a/docs/source/Zh/doc/create_project/create_project_doc.rst b/docs/source/Zh/doc/create_project/create_project_doc.rst
index b9023f9..be3cbcd 100644
--- a/docs/source/Zh/doc/create_project/create_project_doc.rst
+++ b/docs/source/Zh/doc/create_project/create_project_doc.rst
@@ -1,23 +1,62 @@
-創建專案
+建立專案
+========
+
+概述
----
-在 WebRunner 裡可以創建專案,創建專案後將會自動生成範例文件,
-範例文件包含 python executor 檔案以及 keyword.json 檔案。
+WebRunner 可以產生快速啟動的專案結構,包含範例 JSON 關鍵字檔案和 Python 執行器腳本。
-要創建專案可以用以下方式:
+用法
+----
.. code-block:: python
from je_web_runner import create_project_dir
- # create on current workdir
+
+ # 在當前工作目錄建立
create_project_dir()
- # create project on project_path
- create_project_dir("project_path")
- # create project on project_path and dir name is My First Project
- create_project_dir("project_path", "My First Project")
-或是這個方式將會在 project_path 路徑產生專案
+ # 在指定路徑建立
+ create_project_dir(project_path="./my_project")
+
+ # 自訂父目錄名稱
+ create_project_dir(project_path="./my_project", parent_name="MyTest")
+
+命令列方式
+----------
+
+.. code-block:: bash
+
+ python -m je_web_runner --create_project ./my_project
+
+產生的結構
+----------
+
+.. code-block:: text
+
+ my_project/WebRunner/
+ ├── keyword/
+ │ ├── keyword1.json # 範例動作檔案(成功案例)
+ │ ├── keyword2.json # 範例動作檔案(成功案例)
+ │ └── bad_keyword_1.json # 範例動作檔案(失敗案例)
+ └── executor/
+ ├── executor_one_file.py # 執行單一 JSON 檔案
+ ├── executor_folder.py # 執行資料夾中所有 JSON 檔案
+ └── executor_bad_file.py # 執行失敗案例檔案
+
+參數
+----
-.. code-block:: console
+.. list-table::
+ :header-rows: 1
+ :widths: 25 25 50
- python -m je_web_runner --create_project project_path
\ No newline at end of file
+ * - 參數
+ - 預設值
+ - 說明
+ * - ``project_path``
+ - 當前工作目錄
+ - 專案建立路徑
+ * - ``parent_name``
+ - ``"WebRunner"``
+ - 頂層專案目錄名稱
diff --git a/docs/source/Zh/doc/exception/exception_doc.rst b/docs/source/Zh/doc/exception/exception_doc.rst
new file mode 100644
index 0000000..f840422
--- /dev/null
+++ b/docs/source/Zh/doc/exception/exception_doc.rst
@@ -0,0 +1,83 @@
+例外處理
+========
+
+概述
+----
+
+WebRunner 提供自訂例外階層結構。所有例外繼承自 ``WebRunnerException``。
+
+例外階層
+--------
+
+.. code-block:: text
+
+ WebRunnerException(基底)
+ ├── WebRunnerWebDriverNotFoundException
+ ├── WebRunnerOptionsWrongTypeException
+ ├── WebRunnerArgumentWrongTypeException
+ ├── WebRunnerWebDriverIsNoneException
+ ├── WebRunnerExecuteException
+ ├── WebRunnerAssertException
+ ├── WebRunnerHTMLException
+ ├── WebRunnerAddCommandException
+ ├── WebRunnerJsonException
+ │ └── WebRunnerGenerateJsonReportException
+ ├── XMLException
+ │ └── XMLTypeException
+ └── CallbackExecutorException
+
+例外參考
+--------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 35 65
+
+ * - 例外
+ - 說明
+ * - ``WebRunnerException``
+ - 所有 WebRunner 錯誤的基底例外
+ * - ``WebRunnerWebDriverNotFoundException``
+ - 找不到 WebDriver 或不支援的瀏覽器名稱
+ * - ``WebRunnerOptionsWrongTypeException``
+ - 提供了無效的選項型別
+ * - ``WebRunnerArgumentWrongTypeException``
+ - 提供了無效的參數型別
+ * - ``WebRunnerWebDriverIsNoneException``
+ - WebDriver 為 None(未初始化)
+ * - ``WebRunnerExecuteException``
+ - 動作執行錯誤(未知指令、無效格式)
+ * - ``WebRunnerJsonException``
+ - JSON 處理錯誤
+ * - ``WebRunnerGenerateJsonReportException``
+ - JSON 報告產生錯誤
+ * - ``WebRunnerAssertException``
+ - 斷言驗證失敗
+ * - ``WebRunnerHTMLException``
+ - HTML 報告產生錯誤
+ * - ``WebRunnerAddCommandException``
+ - 註冊自訂指令錯誤(非函式/方法)
+ * - ``XMLException``
+ - XML 處理錯誤
+ * - ``XMLTypeException``
+ - 無效的 XML 類型
+ * - ``CallbackExecutorException``
+ - 回調執行錯誤
+
+範例
+----
+
+.. code-block:: python
+
+ from je_web_runner import get_webdriver_manager
+ from je_web_runner.utils.exception.exceptions import (
+ WebRunnerException,
+ WebRunnerWebDriverNotFoundException,
+ )
+
+ try:
+ manager = get_webdriver_manager("unsupported_browser")
+ except WebRunnerWebDriverNotFoundException as e:
+ print(f"不支援的瀏覽器: {e}")
+ except WebRunnerException as e:
+ print(f"WebRunner 錯誤: {e}")
diff --git a/docs/source/Zh/doc/generate_report/generate_report_doc.rst b/docs/source/Zh/doc/generate_report/generate_report_doc.rst
index f45eab4..e3daa36 100644
--- a/docs/source/Zh/doc/generate_report/generate_report_doc.rst
+++ b/docs/source/Zh/doc/generate_report/generate_report_doc.rst
@@ -1,23 +1,110 @@
報告產生
+========
+
+概述
----
-Generate Report 可以生成以下格式的報告
+WebRunner 可以自動記錄所有動作,並產生三種格式的報告:
+**HTML**、**JSON** 和 **XML**。報告包含每個執行動作的詳細資訊,
+包括函式名稱、參數、時間戳記和例外資訊。
+
+.. note::
+
+ 產生報告前必須先啟用測試記錄。
+
+啟用記錄
+--------
+
+.. code-block:: python
+
+ from je_web_runner import test_record_instance
+
+ test_record_instance.set_record_enable(True)
+
+HTML 報告
+---------
+
+HTML 報告包含顏色編碼的表格:
+
+* **水藍色** 背景表示成功的動作
+* **紅色** 背景表示失敗的動作
+
+.. code-block:: python
+
+ from je_web_runner import generate_html, generate_html_report
+
+ # 產生 HTML 字串
+ html_content = generate_html()
-* HTML
-* JSON
-* XML
-* Generate Report 主要用來記錄與確認有哪些步驟執行,執行是否成功,
-* 下面的範例有搭配 keyword and executor 如果看不懂可以先去看看 executor
+ # 儲存為檔案(建立 test_results.html)
+ generate_html_report("test_results")
-以下是產生 HTML 的範例。
+JSON 報告
+---------
+
+JSON 報告分別產生成功和失敗的檔案。
.. code-block:: python
-以下是產生 JSON 的範例。
+ from je_web_runner import generate_json, generate_json_report
+
+ # 產生字典(回傳 success_dict, failure_dict 元組)
+ success_dict, failure_dict = generate_json()
+
+ # 儲存為檔案:
+ # - test_results_success.json
+ # - test_results_failure.json
+ generate_json_report("test_results")
+
+XML 報告
+---------
.. code-block:: python
-以下是產生 XML 的範例。
+ from je_web_runner import generate_xml, generate_xml_report
+
+ # 產生 XML 結構
+ success_xml, failure_xml = generate_xml()
+
+ # 儲存為檔案:
+ # - test_results_success.xml
+ # - test_results_failure.xml
+ generate_xml_report("test_results")
+
+透過 Action Executor 產生報告
+------------------------------
.. code-block:: python
+ from je_web_runner import execute_action
+
+ execute_action([
+ ["WR_set_record_enable", {"set_enable": True}],
+ ["WR_get_webdriver_manager", {"webdriver_name": "chrome"}],
+ ["WR_to_url", {"url": "https://example.com"}],
+ ["WR_quit"],
+ ["WR_generate_html_report", {"html_name": "my_report"}],
+ ])
+
+記錄資料格式
+------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 25 50
+
+ * - 欄位
+ - 型別
+ - 說明
+ * - ``function_name``
+ - ``str``
+ - 執行的函式名稱
+ * - ``local_param``
+ - ``dict | None``
+ - 傳遞給函式的參數
+ * - ``time``
+ - ``str``
+ - 執行時間戳記
+ * - ``program_exception``
+ - ``str``
+ - 例外訊息或 ``"None"``
diff --git a/docs/source/Zh/doc/installation/installation_doc.rst b/docs/source/Zh/doc/installation/installation_doc.rst
index 51c239e..f065618 100644
--- a/docs/source/Zh/doc/installation/installation_doc.rst
+++ b/docs/source/Zh/doc/installation/installation_doc.rst
@@ -1,15 +1,85 @@
安裝
-----
+====
-.. code-block:: python
+透過 pip 安裝
+--------------
+
+**穩定版:**
+
+.. code-block:: bash
pip install je_web_runner
-* Python & pip require version
- * Python 3.7 & up
- * pip 19.3 & up
+**開發版:**
+
+.. code-block:: bash
+
+ pip install je_web_runner_dev
+
+系統需求
+--------
+
+* Python **3.10** 或更新版本
+* pip **19.3** 或更新版本
+
+相依套件(自動安裝):
+
+* ``selenium>=4.0.0``
+* ``requests``
+* ``python-dotenv``
+* ``webdriver-manager``
+
+支援平台
+--------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 70
+
+ * - 平台
+ - 備註
+ * - Windows 11
+ - 完整支援
+ * - macOS
+ - 完整支援
+ * - Ubuntu / Linux
+ - 完整支援
+ * - Raspberry Pi
+ - 完整支援
+
+支援瀏覽器
+----------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 20 50
+
+ * - 瀏覽器
+ - 代碼
+ - 備註
+ * - Google Chrome
+ - ``chrome``
+ - 最常使用,透過 webdriver-manager 自動管理
+ * - Chromium
+ - ``chromium``
+ - 開源 Chrome 變體
+ * - Mozilla Firefox
+ - ``firefox``
+ - 透過 GeckoDriver 完整支援
+ * - Microsoft Edge
+ - ``edge``
+ - 基於 Chromium 的 Edge
+ * - Internet Explorer
+ - ``ie``
+ - 舊版支援
+ * - Apple Safari
+ - ``safari``
+ - 僅 macOS,無自動驅動程式管理
+
+驗證安裝
+--------
+
+.. code-block:: python
-* Dev env
- * windows 11
- * osx 11 big sur
- * ubuntu 20.0.4
+ import je_web_runner
+ print(je_web_runner.__all__)
diff --git a/docs/source/Zh/doc/logging/logging_doc.rst b/docs/source/Zh/doc/logging/logging_doc.rst
new file mode 100644
index 0000000..27e6259
--- /dev/null
+++ b/docs/source/Zh/doc/logging/logging_doc.rst
@@ -0,0 +1,51 @@
+日誌
+====
+
+概述
+----
+
+WebRunner 使用 Python 的 ``logging`` 模組搭配旋轉檔案處理器來記錄自動化事件、錯誤和警告。
+
+設定
+----
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 70
+
+ * - 屬性
+ - 值
+ * - 日誌檔案
+ - ``WEBRunner.log``
+ * - 日誌等級
+ - ``WARNING`` 及以上
+ * - 最大檔案大小
+ - 1 GB(旋轉)
+ * - 日誌格式
+ - ``%(asctime)s | %(name)s | %(levelname)s | %(message)s``
+ * - 處理器
+ - ``RotatingFileHandler``(自訂 ``WebRunnerLoggingHandler``)
+
+日誌輸出
+--------
+
+日誌檔案在當前工作目錄中建立為 ``WEBRunner.log``。
+當檔案達到 1 GB 時會進行旋轉。
+
+日誌範例:
+
+.. code-block:: text
+
+ 2025-01-01 12:00:00 | je_web_runner | WARNING | WebDriverWrapper find_element failed: ...
+ 2025-01-01 12:00:01 | je_web_runner | ERROR | WebdriverManager quit, failed: ...
+
+日誌實例
+--------
+
+全域日誌可透過 ``web_runner_logger`` 存取:
+
+.. code-block:: python
+
+ from je_web_runner.utils.logging.loggin_instance import web_runner_logger
+
+ web_runner_logger.warning("自訂警告訊息")
diff --git a/docs/source/Zh/doc/package_manager/package_manager_doc.rst b/docs/source/Zh/doc/package_manager/package_manager_doc.rst
new file mode 100644
index 0000000..c7af6a3
--- /dev/null
+++ b/docs/source/Zh/doc/package_manager/package_manager_doc.rst
@@ -0,0 +1,61 @@
+套件管理器
+==========
+
+概述
+----
+
+套件管理器可在執行期間動態載入外部 Python 套件到執行器中。
+當套件被加入後,其所有公開函式和類別會以 ``{套件名}_{函式名}`` 的命名慣例註冊到事件字典。
+
+透過 Action Executor 使用
+--------------------------
+
+.. code-block:: python
+
+ from je_web_runner import execute_action
+
+ actions = [
+ # 將 'time' 套件載入執行器
+ ["WR_add_package_to_executor", {"package": "time"}],
+
+ # 現在可以使用 time.sleep(名稱為 "time_sleep")
+ ["time_sleep", [2]],
+ ]
+
+ execute_action(actions)
+
+直接 API 使用
+--------------
+
+.. code-block:: python
+
+ from je_web_runner.utils.package_manager.package_manager_class import package_manager
+
+ # 檢查套件是否存在並匯入
+ module = package_manager.check_package("os")
+
+ # 將套件的所有函式加入執行器
+ package_manager.add_package_to_executor("math")
+
+ # 加入回調執行器
+ package_manager.add_package_to_callback_executor("time")
+
+方法
+----
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 30 40
+
+ * - 方法
+ - 參數
+ - 說明
+ * - ``check_package()``
+ - ``package: str``
+ - 檢查並匯入套件,回傳模組或 None
+ * - ``add_package_to_executor()``
+ - ``package: str``
+ - 將套件成員加入 Executor 的 event_dict
+ * - ``add_package_to_callback_executor()``
+ - ``package: str``
+ - 將套件成員加入 CallbackExecutor 的 event_dict
diff --git a/docs/source/Zh/doc/quick_start/quick_start_doc.rst b/docs/source/Zh/doc/quick_start/quick_start_doc.rst
new file mode 100644
index 0000000..fec6164
--- /dev/null
+++ b/docs/source/Zh/doc/quick_start/quick_start_doc.rst
@@ -0,0 +1,101 @@
+快速開始
+========
+
+本指南示範使用 WebRunner 的三種主要方式。
+
+範例一:直接使用 Python API
+-----------------------------
+
+直接在 Python 程式碼中使用 WebRunner 的類別和函式。
+
+.. code-block:: python
+
+ from je_web_runner import TestObject
+ from je_web_runner import get_webdriver_manager
+ from je_web_runner import web_element_wrapper
+
+ # 建立 WebDriver 管理器(使用 Chrome)
+ manager = get_webdriver_manager("chrome")
+
+ # 導航到網址
+ manager.webdriver_wrapper.to_url("https://www.google.com")
+
+ # 設定隱式等待
+ manager.webdriver_wrapper.implicitly_wait(2)
+
+ # 建立測試物件來定位搜尋框
+ search_box = TestObject("q", "name")
+
+ # 尋找元素
+ manager.webdriver_wrapper.find_element(search_box)
+
+ # 點擊並輸入文字
+ web_element_wrapper.click_element()
+ web_element_wrapper.input_to_element("WebRunner automation")
+
+ # 關閉瀏覽器
+ manager.quit()
+
+範例二:JSON 動作列表
+----------------------
+
+將自動化腳本定義為 JSON 動作列表,透過 Action Executor 執行。
+
+.. code-block:: python
+
+ from je_web_runner import execute_action
+
+ actions = [
+ ["WR_get_webdriver_manager", {"webdriver_name": "chrome"}],
+ ["WR_to_url", {"url": "https://www.google.com"}],
+ ["WR_implicitly_wait", {"time_to_wait": 2}],
+ ["WR_SaveTestObject", {"test_object_name": "q", "object_type": "name"}],
+ ["WR_find_element", {"element_name": "q"}],
+ ["WR_click_element"],
+ ["WR_input_to_element", {"input_value": "WebRunner automation"}],
+ ["WR_quit"]
+ ]
+
+ result = execute_action(actions)
+
+範例三:從 JSON 檔案執行
+--------------------------
+
+建立 ``actions.json`` 檔案:
+
+.. code-block:: json
+
+ [
+ ["WR_get_webdriver_manager", {"webdriver_name": "chrome"}],
+ ["WR_to_url", {"url": "https://example.com"}],
+ ["WR_quit"]
+ ]
+
+在 Python 中執行:
+
+.. code-block:: python
+
+ from je_web_runner import execute_files
+
+ results = execute_files(["actions.json"])
+
+或使用命令列:
+
+.. code-block:: bash
+
+ python -m je_web_runner -e actions.json
+
+範例四:無頭模式
+-----------------
+
+使用無頭模式在沒有可見 GUI 視窗的情況下執行瀏覽器。
+
+.. code-block:: python
+
+ from je_web_runner import get_webdriver_manager
+
+ manager = get_webdriver_manager("chrome", options=["--headless", "--disable-gpu"])
+ manager.webdriver_wrapper.to_url("https://example.com")
+ title = manager.webdriver_wrapper.execute_script("return document.title")
+ print(f"頁面標題: {title}")
+ manager.quit()
diff --git a/docs/source/Zh/doc/scheduler/scheduler_doc.rst b/docs/source/Zh/doc/scheduler/scheduler_doc.rst
deleted file mode 100644
index 0213e54..0000000
--- a/docs/source/Zh/doc/scheduler/scheduler_doc.rst
+++ /dev/null
@@ -1,19 +0,0 @@
-Scheduler
-----
-
-可以使用排程來執行重複的任務,可以使用對 APScheduler 的簡易包裝或是觀看 API 文件自行使用
-
-.. code-block:: python
-
- from je_web_runner import SchedulerManager
-
-
- def test_scheduler():
- print("Test Scheduler")
- scheduler.remove_blocking_job(id="test")
- scheduler.shutdown_blocking_scheduler()
-
-
- scheduler = SchedulerManager()
- scheduler.add_interval_blocking_secondly(function=test_scheduler, id="test")
- scheduler.start_block_scheduler()
diff --git a/docs/source/Zh/doc/socket_driver/socket_driver_doc.rst b/docs/source/Zh/doc/socket_driver/socket_driver_doc.rst
index 6c17aa1..37d6340 100644
--- a/docs/source/Zh/doc/socket_driver/socket_driver_doc.rst
+++ b/docs/source/Zh/doc/socket_driver/socket_driver_doc.rst
@@ -1,26 +1,72 @@
-Socket Driver
+遠端自動化(Socket 伺服器)
+============================
+
+概述
----
-* 實驗性的功能。
-* Socket Server 主要用來讓其他程式語言也可以使用 AutoControl。
-* 透過底層的 executor 處理接收到字串並進行執行動作。
-* 可以透過遠端來執行測試的動作。
+WebRunner 包含一個多執行緒 TCP Socket 伺服器,用於遠端自動化控制。
+這使得跨語言支援成為可能 -- 任何支援 TCP Socket 的語言
+(Java、C#、Go 等)都可以向 WebRunner 發送自動化指令。
+
+啟動伺服器
+----------
+
+.. code-block:: python
+
+ from je_web_runner import start_web_runner_socket_server
+
+ server = start_web_runner_socket_server(host="localhost", port=9941)
+
+伺服器在背景常駐執行緒中啟動,立即準備接受連線。
-* 目前有實驗性的 Java 與 C# 支援。
-* 每個段落結束都應該傳輸 Return_Data_Over_JE。
-* 使用 UTF-8 encoding。
-* 傳送 quit_server 將會關閉伺服器。
+客戶端連線範例
+--------------
.. code-block:: python
- import sys
+ import socket
+ import json
+
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.connect(("localhost", 9941))
+
+ # 發送 JSON 格式的動作(UTF-8 編碼)
+ actions = [
+ ["WR_get_webdriver_manager", {"webdriver_name": "chrome"}],
+ ["WR_to_url", {"url": "https://example.com"}],
+ ["WR_quit"]
+ ]
+ sock.send(json.dumps(actions).encode("utf-8"))
+
+ # 接收結果(以 "Return_Data_Over_JE\n" 結尾)
+ response = sock.recv(4096).decode("utf-8")
+ print(response)
+
+ # 關閉伺服器
+ sock.send("quit_server".encode("utf-8"))
+
+協定
+----
- from je_web_runner import start_autocontrol_socket_server
+.. list-table::
+ :header-rows: 1
+ :widths: 30 70
- try:
- server = start_autocontrol_socket_server()
- while not server.close_flag:
- pass
- sys.exit(0)
- except Exception as error:
- print(repr(error))
\ No newline at end of file
+ * - 屬性
+ - 值
+ * - 預設主機
+ - ``localhost``
+ * - 預設埠號
+ - ``9941``
+ * - 編碼
+ - UTF-8
+ * - 訊息格式
+ - JSON 動作陣列
+ * - 最大接收緩衝
+ - 8192 bytes
+ * - 回應終止符
+ - ``Return_Data_Over_JE``
+ * - 關閉指令
+ - ``quit_server``
+ * - 執行緒模型
+ - 多執行緒(``socketserver.ThreadingMixIn``)
diff --git a/docs/source/Zh/doc/test_object/test_object_doc.rst b/docs/source/Zh/doc/test_object/test_object_doc.rst
new file mode 100644
index 0000000..32b3880
--- /dev/null
+++ b/docs/source/Zh/doc/test_object/test_object_doc.rst
@@ -0,0 +1,63 @@
+測試物件
+========
+
+概述
+----
+
+``TestObject`` 封裝元素定位資訊(策略+值),用於可重複使用的元素定義。
+``TestObjectRecord`` 以名稱儲存 ``TestObject`` 實例,供 ``_with_test_object`` 方法使用。
+
+建立測試物件
+------------
+
+.. code-block:: python
+
+ from je_web_runner import TestObject, create_test_object, get_test_object_type_list
+
+ # 建構子:TestObject(test_object_name, test_object_type)
+ obj1 = TestObject("search", "name")
+
+ # 工廠函式:create_test_object(object_type, test_object_name)
+ obj2 = create_test_object("id", "submit-btn")
+
+可用定位類型
+------------
+
+.. code-block:: python
+
+ print(get_test_object_type_list())
+ # ['ID', 'NAME', 'XPATH', 'CSS_SELECTOR', 'CLASS_NAME',
+ # 'TAG_NAME', 'LINK_TEXT', 'PARTIAL_LINK_TEXT']
+
+這些直接對應 Selenium 的 ``By`` 類別常數。
+
+TestObjectRecord
+----------------
+
+.. code-block:: python
+
+ from je_web_runner.utils.test_object.test_object_record.test_object_record_class import test_object_record
+
+ # 儲存測試物件
+ test_object_record.save_test_object("search_box", "name")
+
+ # 移除測試物件
+ test_object_record.remove_test_object("search_box")
+
+ # 清除所有紀錄
+ test_object_record.clean_record()
+
+在 Action Executor 中使用
+--------------------------
+
+.. code-block:: python
+
+ from je_web_runner import execute_action
+
+ execute_action([
+ ["WR_SaveTestObject", {"test_object_name": "search", "object_type": "name"}],
+ ["WR_find_element", {"element_name": "search"}],
+ ["WR_click_element"],
+ ["WR_input_to_element", {"input_value": "hello"}],
+ ["WR_CleanTestObject"],
+ ])
diff --git a/docs/source/Zh/doc/test_record/test_record_doc.rst b/docs/source/Zh/doc/test_record/test_record_doc.rst
new file mode 100644
index 0000000..fe3c176
--- /dev/null
+++ b/docs/source/Zh/doc/test_record/test_record_doc.rst
@@ -0,0 +1,71 @@
+測試記錄
+========
+
+概述
+----
+
+所有 WebRunner 動作都可以自動記錄,用於稽核追蹤和報告產生。
+全域實例 ``test_record_instance`` 管理記錄狀態和儲存記錄。
+
+啟用記錄
+--------
+
+記錄預設為停用。在執行動作前啟用:
+
+.. code-block:: python
+
+ from je_web_runner import test_record_instance
+
+ test_record_instance.set_record_enable(True)
+
+或透過動作執行器:
+
+.. code-block:: python
+
+ from je_web_runner import execute_action
+
+ execute_action([
+ ["WR_set_record_enable", {"set_enable": True}],
+ ])
+
+存取記錄
+--------
+
+.. code-block:: python
+
+ records = test_record_instance.test_record_list
+
+ for record in records:
+ print(record)
+
+記錄格式
+--------
+
+每筆記錄是一個字典,包含以下欄位:
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 25 50
+
+ * - 欄位
+ - 型別
+ - 說明
+ * - ``function_name``
+ - ``str``
+ - 執行的函式名稱
+ * - ``local_param``
+ - ``dict | None``
+ - 傳遞給函式的參數
+ * - ``time``
+ - ``str``
+ - 執行時間戳記
+ * - ``program_exception``
+ - ``str``
+ - 例外訊息或 ``"None"``
+
+清除記錄
+--------
+
+.. code-block:: python
+
+ test_record_instance.clean_record()
diff --git a/docs/source/Zh/doc/web_element/web_element_doc.rst b/docs/source/Zh/doc/web_element/web_element_doc.rst
new file mode 100644
index 0000000..c8b0ed0
--- /dev/null
+++ b/docs/source/Zh/doc/web_element/web_element_doc.rst
@@ -0,0 +1,142 @@
+Web 元素包裝器
+===============
+
+概述
+----
+
+``WebElementWrapper`` 提供與已定位元素互動的方法。
+它操作由 ``find_element()`` 或 ``find_elements()`` 設定的當前活動元素。
+
+全域實例 ``web_element_wrapper`` 從 ``je_web_runner`` 匯入。
+
+基本互動
+--------
+
+.. code-block:: python
+
+ from je_web_runner import web_element_wrapper
+
+ web_element_wrapper.click_element() # 點擊元素
+ web_element_wrapper.input_to_element("Hello World") # 輸入文字
+ web_element_wrapper.clear() # 清除內容
+ web_element_wrapper.submit() # 提交表單
+
+屬性檢查
+--------
+
+.. code-block:: python
+
+ web_element_wrapper.get_attribute("href") # 取得 HTML 屬性
+ web_element_wrapper.get_property("checked") # 取得 JS 屬性
+ web_element_wrapper.get_dom_attribute("data-id") # 取得 DOM 屬性
+
+狀態檢查
+--------
+
+.. code-block:: python
+
+ web_element_wrapper.is_displayed() # 檢查是否可見
+ web_element_wrapper.is_enabled() # 檢查是否可用
+ web_element_wrapper.is_selected() # 檢查是否被選取(核取方塊/單選)
+
+CSS 屬性
+---------
+
+.. code-block:: python
+
+ web_element_wrapper.value_of_css_property("color")
+
+下拉選單處理
+------------
+
+.. code-block:: python
+
+ select = web_element_wrapper.get_select()
+ # 使用 Selenium 的 Select API:
+ # select.select_by_visible_text("選項一")
+ # select.select_by_value("opt1")
+ # select.select_by_index(0)
+
+元素截圖
+--------
+
+.. code-block:: python
+
+ web_element_wrapper.screenshot("element") # 儲存為 element.png
+
+切換元素
+--------
+
+當 ``find_elements()`` 回傳多個元素時,使用 ``change_web_element()`` 切換活動元素:
+
+.. code-block:: python
+
+ # 切換到第 3 個元素(索引 2)
+ web_element_wrapper.change_web_element(2)
+ web_element_wrapper.click_element()
+
+元素驗證
+--------
+
+.. code-block:: python
+
+ web_element_wrapper.check_current_web_element({
+ "tag_name": "input",
+ "enabled": True
+ })
+
+方法參考
+--------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 35 40
+
+ * - 方法
+ - 參數
+ - 說明
+ * - ``click_element()``
+ -
+ - 點擊當前元素
+ * - ``input_to_element()``
+ - ``input_value: str``
+ - 輸入文字到元素
+ * - ``clear()``
+ -
+ - 清除元素內容
+ * - ``submit()``
+ -
+ - 提交表單
+ * - ``get_attribute()``
+ - ``name: str``
+ - 取得 HTML 屬性值
+ * - ``get_property()``
+ - ``name: str``
+ - 取得 JavaScript 屬性值
+ * - ``get_dom_attribute()``
+ - ``name: str``
+ - 取得 DOM 屬性值
+ * - ``is_displayed()``
+ -
+ - 檢查元素是否可見
+ * - ``is_enabled()``
+ -
+ - 檢查元素是否可用
+ * - ``is_selected()``
+ -
+ - 檢查元素是否被選取
+ * - ``value_of_css_property()``
+ - ``property_name: str``
+ - 取得 CSS 屬性值
+ * - ``screenshot()``
+ - ``filename: str``
+ - 對元素截圖
+ * - ``change_web_element()``
+ - ``element_index: int``
+ - 透過索引切換活動元素
+ * - ``check_current_web_element()``
+ - ``check_dict: dict``
+ - 驗證元素屬性
+ * - ``get_select()``
+ -
+ - 取得下拉選單的 Select 物件
diff --git a/docs/source/Zh/doc/webdriver_manager/webdriver_manager_doc.rst b/docs/source/Zh/doc/webdriver_manager/webdriver_manager_doc.rst
new file mode 100644
index 0000000..d3fa9aa
--- /dev/null
+++ b/docs/source/Zh/doc/webdriver_manager/webdriver_manager_doc.rst
@@ -0,0 +1,100 @@
+WebDriver 管理器
+=================
+
+概述
+----
+
+``WebdriverManager`` 管理多個 WebDriver 實例以支援並行瀏覽器自動化。
+它維護一個活動 WebDriver 清單,並提供建立、切換和關閉的方法。
+
+透過工廠函式 ``get_webdriver_manager()`` 取得管理器。
+
+建立管理器
+----------
+
+.. code-block:: python
+
+ from je_web_runner import get_webdriver_manager
+
+ # 使用 Chrome 建立
+ manager = get_webdriver_manager("chrome")
+
+ # 使用 Firefox 搭配選項建立
+ manager = get_webdriver_manager("firefox", options=["--headless"])
+
+管理多個瀏覽器
+--------------
+
+.. code-block:: python
+
+ manager = get_webdriver_manager("chrome")
+
+ # 新增第二個瀏覽器實例
+ manager.new_driver("firefox")
+
+ # 切換到 Chrome(索引 0)
+ manager.change_webdriver(0)
+ manager.webdriver_wrapper.to_url("https://example.com")
+
+ # 切換到 Firefox(索引 1)
+ manager.change_webdriver(1)
+ manager.webdriver_wrapper.to_url("https://google.com")
+
+ # 僅關閉 Firefox
+ manager.close_choose_webdriver(1)
+
+ # 關閉當前瀏覽器
+ manager.close_current_webdriver()
+
+ # 關閉並退出所有瀏覽器
+ manager.quit()
+
+關鍵屬性
+--------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 30 40
+
+ * - 屬性
+ - 型別
+ - 說明
+ * - ``webdriver_wrapper``
+ - ``WebDriverWrapper``
+ - WebDriver 操作包裝器
+ * - ``webdriver_element``
+ - ``WebElementWrapper``
+ - 元素操作包裝器
+ * - ``current_webdriver``
+ - ``WebDriver | None``
+ - 當前活動的 WebDriver 實例
+
+方法
+----
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 40 30
+
+ * - 方法
+ - 參數
+ - 說明
+ * - ``new_driver()``
+ - ``webdriver_name: str, options: List[str] = None, **kwargs``
+ - 建立新的 WebDriver 實例
+ * - ``change_webdriver()``
+ - ``index_of_webdriver: int``
+ - 透過索引切換 WebDriver
+ * - ``close_current_webdriver()``
+ -
+ - 關閉當前 WebDriver
+ * - ``close_choose_webdriver()``
+ - ``webdriver_index: int``
+ - 透過索引關閉指定 WebDriver
+ * - ``quit()``
+ -
+ - 關閉並退出所有 WebDriver
+
+.. note::
+
+ 呼叫 ``quit()`` 時,會同時清除所有已儲存的 ``TestObjectRecord`` 項目。
diff --git a/docs/source/Zh/doc/webdriver_options/webdriver_options_doc.rst b/docs/source/Zh/doc/webdriver_options/webdriver_options_doc.rst
new file mode 100644
index 0000000..626400e
--- /dev/null
+++ b/docs/source/Zh/doc/webdriver_options/webdriver_options_doc.rst
@@ -0,0 +1,61 @@
+WebDriver 選項設定
+==================
+
+概述
+----
+
+在啟動 WebDriver 之前設定瀏覽器選項和功能。
+適用於無頭模式、停用 GPU、設定視窗大小等。
+
+瀏覽器參數
+----------
+
+.. code-block:: python
+
+ from je_web_runner import set_webdriver_options_argument, get_webdriver_manager
+
+ # 設定瀏覽器參數(回傳 Options 物件)
+ options = set_webdriver_options_argument("chrome", [
+ "--headless",
+ "--disable-gpu",
+ "--no-sandbox",
+ "--window-size=1920,1080"
+ ])
+
+ # 或在建立管理器時直接傳入
+ manager = get_webdriver_manager("chrome", options=["--headless", "--disable-gpu"])
+
+常用 Chrome 參數
+~~~~~~~~~~~~~~~~
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 70
+
+ * - 參數
+ - 說明
+ * - ``--headless``
+ - 不顯示 GUI 執行
+ * - ``--disable-gpu``
+ - 停用 GPU 硬體加速
+ * - ``--no-sandbox``
+ - 停用沙箱(某些 Linux 環境需要)
+ * - ``--window-size=W,H``
+ - 設定初始視窗大小
+ * - ``--incognito``
+ - 以無痕模式開啟
+ * - ``--start-maximized``
+ - 以最大化視窗啟動
+
+DesiredCapabilities
+-------------------
+
+.. code-block:: python
+
+ from je_web_runner import get_desired_capabilities, get_desired_capabilities_keys
+
+ # 檢視可用的功能鍵(瀏覽器名稱)
+ keys = get_desired_capabilities_keys()
+
+ # 取得特定瀏覽器的功能
+ caps = get_desired_capabilities("CHROME")
diff --git a/docs/source/Zh/doc/webdriver_wrapper/webdriver_wrapper_doc.rst b/docs/source/Zh/doc/webdriver_wrapper/webdriver_wrapper_doc.rst
new file mode 100644
index 0000000..0e49c96
--- /dev/null
+++ b/docs/source/Zh/doc/webdriver_wrapper/webdriver_wrapper_doc.rst
@@ -0,0 +1,192 @@
+WebDriver 包裝器
+=================
+
+概述
+----
+
+``WebDriverWrapper`` 是核心元件,包裝了 Selenium WebDriver 並提供全面的方法。
+涵蓋瀏覽器控制、元素尋找、滑鼠/鍵盤操作、Cookie 管理、JavaScript 執行、視窗管理等功能。
+
+導航
+----
+
+.. code-block:: python
+
+ wrapper = manager.webdriver_wrapper
+
+ wrapper.to_url("https://example.com") # 導航至網址
+ wrapper.forward() # 前進
+ wrapper.back() # 後退
+ wrapper.refresh() # 重新整理
+
+元素尋找
+--------
+
+使用 ``TestObject`` 定位器尋找元素:
+
+.. code-block:: python
+
+ from je_web_runner import TestObject
+
+ # 定位策略:id, name, xpath, css selector,
+ # class name, tag name, link text, partial link text
+ element = TestObject("search-input", "id")
+
+ wrapper.find_element(element) # 尋找單一元素
+ wrapper.find_elements(element) # 尋找多個元素
+
+使用已儲存的 ``TestObjectRecord`` 名稱尋找:
+
+.. code-block:: python
+
+ wrapper.find_element_with_test_object_record("search-input")
+ wrapper.find_elements_with_test_object_record("search-input")
+
+等待方法
+--------
+
+.. code-block:: python
+
+ wrapper.implicitly_wait(5) # 隱式等待(秒)
+ wrapper.explict_wait( # 顯式等待
+ wait_condition,
+ timeout=10,
+ poll_frequency=0.5
+ )
+ wrapper.set_script_timeout(30) # 非同步腳本逾時
+ wrapper.set_page_load_timeout(60) # 頁面載入逾時
+
+滑鼠操作
+--------
+
+**基本點擊(於當前位置):**
+
+.. code-block:: python
+
+ wrapper.left_click()
+ wrapper.right_click()
+ wrapper.left_double_click()
+ wrapper.left_click_and_hold()
+ wrapper.release()
+
+**對已儲存的測試物件點擊:**
+
+.. code-block:: python
+
+ wrapper.left_click_with_test_object("button_name")
+ wrapper.right_click_with_test_object("button_name")
+ wrapper.left_double_click_with_test_object("button_name")
+ wrapper.left_click_and_hold_with_test_object("button_name")
+ wrapper.release_with_test_object("button_name")
+
+**拖放:**
+
+.. code-block:: python
+
+ wrapper.drag_and_drop(source_element, target_element)
+ wrapper.drag_and_drop_offset(element, x=100, y=50)
+
+ # 使用已儲存的測試物件
+ wrapper.drag_and_drop_with_test_object("source_name", "target_name")
+ wrapper.drag_and_drop_offset_with_test_object("element_name", offset_x=100, offset_y=50)
+
+**滑鼠移動:**
+
+.. code-block:: python
+
+ wrapper.move_to_element(element) # 懸停
+ wrapper.move_to_element_with_test_object("element_name")
+ wrapper.move_to_element_with_offset(element, offset_x=10, offset_y=10)
+ wrapper.move_to_element_with_offset_and_test_object("name", offset_x=10, offset_y=10)
+ wrapper.move_by_offset(100, 200) # 從當前位置移動
+
+鍵盤操作
+--------
+
+.. code-block:: python
+
+ wrapper.press_key(keycode)
+ wrapper.press_key_with_test_object(keycode)
+ wrapper.release_key(keycode)
+ wrapper.release_key_with_test_object(keycode)
+ wrapper.send_keys("text")
+ wrapper.send_keys_to_element(element, "text")
+ wrapper.send_keys_to_element_with_test_object("element_name", "text")
+
+動作鏈控制
+----------
+
+.. code-block:: python
+
+ wrapper.perform() # 執行佇列中的動作
+ wrapper.reset_actions() # 清除動作佇列
+ wrapper.pause(2) # 在動作鏈中暫停(秒)
+ wrapper.scroll(0, 500) # 捲動頁面
+
+Cookie 管理
+-----------
+
+.. code-block:: python
+
+ wrapper.get_cookies() # 取得所有 Cookie
+ wrapper.get_cookie("session_id") # 取得指定 Cookie
+ wrapper.add_cookie({"name": "key", "value": "val"})
+ wrapper.delete_cookie("session_id")
+ wrapper.delete_all_cookies()
+
+JavaScript 執行
+----------------
+
+.. code-block:: python
+
+ wrapper.execute("document.title")
+ wrapper.execute_script("return document.title")
+ wrapper.execute_async_script("arguments[0]('done')", callback)
+
+視窗管理
+--------
+
+.. code-block:: python
+
+ wrapper.maximize_window()
+ wrapper.minimize_window()
+ wrapper.fullscreen_window()
+ wrapper.set_window_size(1920, 1080)
+ wrapper.set_window_position(0, 0)
+ wrapper.get_window_position() # 回傳 dict
+ wrapper.get_window_rect() # 回傳 dict
+ wrapper.set_window_rect(x=0, y=0, width=1920, height=1080)
+
+截圖
+----
+
+.. code-block:: python
+
+ wrapper.get_screenshot_as_png() # 回傳 bytes
+ wrapper.get_screenshot_as_base64() # 回傳 base64 字串
+
+Frame/視窗/Alert 切換
+------------------------
+
+.. code-block:: python
+
+ wrapper.switch("frame", "frame_name")
+ wrapper.switch("window", "window_handle")
+ wrapper.switch("default_content")
+ wrapper.switch("parent_frame")
+ wrapper.switch("active_element")
+ wrapper.switch("alert")
+
+瀏覽器日誌
+----------
+
+.. code-block:: python
+
+ wrapper.get_log("browser")
+
+WebDriver 驗證
+---------------
+
+.. code-block:: python
+
+ wrapper.check_current_webdriver({"name": "chrome"})
diff --git a/docs/source/Zh/zh_index.rst b/docs/source/Zh/zh_index.rst
index faf0d78..77e4f81 100644
--- a/docs/source/Zh/zh_index.rst
+++ b/docs/source/Zh/zh_index.rst
@@ -1,14 +1,25 @@
====================================
-WebRunner 繁體中文 文件
+WebRunner 繁體中文文件
====================================
.. toctree::
:maxdepth: 4
doc/installation/installation_doc.rst
+ doc/quick_start/quick_start_doc.rst
+ doc/webdriver_manager/webdriver_manager_doc.rst
+ doc/webdriver_wrapper/webdriver_wrapper_doc.rst
+ doc/web_element/web_element_doc.rst
+ doc/test_object/test_object_doc.rst
+ doc/action_executor/action_executor_doc.rst
doc/create_project/create_project_doc.rst
doc/generate_report/generate_report_doc.rst
doc/callback_function/callback_function_doc.rst
doc/cli/cli_doc.rst
- doc/scheduler/scheduler_doc.rst
- doc/socket_driver/socket_driver_doc.rst
\ No newline at end of file
+ doc/socket_driver/socket_driver_doc.rst
+ doc/package_manager/package_manager_doc.rst
+ doc/webdriver_options/webdriver_options_doc.rst
+ doc/assertion/assertion_doc.rst
+ doc/test_record/test_record_doc.rst
+ doc/exception/exception_doc.rst
+ doc/logging/logging_doc.rst
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 534b6df..ec36d7a 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -1,15 +1,8 @@
# Configuration file for the Sphinx documentation builder.
#
-# This file only contains a selection of the most common options. For a full
-# list see the documentation:
+# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
-# -- Path setup --------------------------------------------------------------
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-#
import os
import sys
@@ -18,34 +11,19 @@
# -- Project information -----------------------------------------------------
project = 'WebRunner'
-copyright = '2020 ~ 2023, JE-Chen'
+copyright = '2021 ~ 2025, JE-Chen'
author = 'JE-Chen'
# -- General configuration ---------------------------------------------------
-# Add any Sphinx extension module names here, as strings. They can be
-# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
-# ones.
extensions = [
"sphinx.ext.autosectionlabel"
]
-# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = []
# -- Options for HTML output -------------------------------------------------
-# The theme to use for HTML and HTML Help pages. See the documentation for
-# a list of builtin themes.
-#
html_theme = 'sphinx_rtd_theme'
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 70e0b78..88a2fa1 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -2,8 +2,20 @@
WebRunner
==================
+**A cross-platform web automation framework built on Selenium**
+
+WebRunner (``je_web_runner``) simplifies browser automation with multi-browser support,
+parallel execution, automatic driver management, JSON-driven action scripts, and detailed report generation.
+
+* **PyPI**: https://pypi.org/project/je-web-runner/
+* **GitHub**: https://github.com/Intergration-Automation-Testing/WebRunner
+* **License**: MIT
+
+----
+
.. toctree::
:maxdepth: 4
+ :caption: Documentation
Eng/eng_index.rst
Zh/zh_index.rst
@@ -12,10 +24,6 @@ WebRunner
----
RoadMap
+-------
-----
-
-* Project Kanban
-* https://github.com/orgs/Integrated-Testing-Environment/projects/4/views/1
-
-----
\ No newline at end of file
+* Project Kanban: https://github.com/orgs/Integrated-Testing-Environment/projects/4/views/1