autoflex 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,26 @@
1
+ Metadata-Version: 2.4
2
+ Name: autoflex
3
+ Version: 1.0.0
4
+ Summary: Web and System Automation Testing Framework
5
+ Home-page: https://github.com/LZYEIL/AutoFlex
6
+ Author: Zhiyuan Li
7
+ Author-email: zhiyuanjeremy@gmail.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: selenium>=4.24.0
14
+ Requires-Dist: pyautogui>=0.9.54
15
+ Requires-Dist: pynput>=1.8.1
16
+ Dynamic: author
17
+ Dynamic: author-email
18
+ Dynamic: classifier
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: home-page
22
+ Dynamic: requires-dist
23
+ Dynamic: requires-python
24
+ Dynamic: summary
25
+
26
+ # This is AutoFlex README Template
@@ -0,0 +1 @@
1
+ # This is AutoFlex README Template
File without changes
File without changes
@@ -0,0 +1,446 @@
1
+ from .selenium_actions import (
2
+ element_actions as selenium_element,
3
+ dropdown_actions as selenium_dropdown,
4
+ page_actions as selenium_page,
5
+ mouse_actions as selenium_mouse,
6
+ wait_actions as selenium_wait,
7
+ script_actions as selenium_script,
8
+ screenshot_actions as selenium_screenshot
9
+ )
10
+ from .pyautogui_actions import (
11
+ mouse_actions as pyauto_mouse,
12
+ keyboard_actions as pyauto_keyboard,
13
+ screen_actions as pyauto_screen,
14
+ dialog_actions as pyauto_dialog
15
+ )
16
+ from .pynput_actions import (
17
+ keyboard_controller as pynput_keyboard,
18
+ keyboard_listener as pynput_kb_listener,
19
+ mouse_listener as pynput_mouse_listener
20
+ )
21
+ from .logger import logger
22
+ from .exceptions import OperationTimeoutError
23
+
24
+
25
+
26
+
27
+ class Actions:
28
+
29
+
30
+ # Here are the functions from the Selenium:
31
+ @staticmethod
32
+ def find_element(locator, timeout=10):
33
+ element = selenium_element.ElementActions.find_element(locator, timeout)
34
+ return element
35
+
36
+
37
+
38
+
39
+ # Click: Supports switching to PyAutoGUI
40
+ @staticmethod
41
+ def click(locator=None, x=None, y=None, timeout=10):
42
+ if locator:
43
+ try:
44
+ selenium_element.ElementActions.click(locator, timeout)
45
+ logger.info(f"Click via Selenium: {locator}")
46
+ return
47
+ except Exception as e:
48
+ logger.warning(f"Selenium click failed: {e}, fallback to PyAutoGUI.")
49
+ if x is not None and y is not None:
50
+ try:
51
+ pyauto_mouse.MouseActions.click_at(x, y)
52
+ logger.info(f"Click at ({x},{y}) via PyAutoGUI")
53
+ return
54
+ except Exception as e:
55
+ logger.error(f"PyAutoGUI click failed: {e}")
56
+ raise OperationTimeoutError("Click failed.")
57
+
58
+
59
+
60
+
61
+ @staticmethod
62
+ def input_text(locator, text, timeout=10):
63
+ try:
64
+ selenium_element.ElementActions.input_text(locator, text, timeout)
65
+ logger.info(f"Input text via Selenium on {locator}: {text}")
66
+ except Exception as e:
67
+ logger.error(f"Input text on {locator} failed: {e}")
68
+ raise OperationTimeoutError("Input text failed.")
69
+
70
+
71
+
72
+
73
+ @staticmethod
74
+ def get_text(locator, timeout=10):
75
+ try:
76
+ text = selenium_element.ElementActions.get_text(locator, timeout)
77
+ logger.info(f"Get text via Selenium from {locator}: {text}")
78
+ return text
79
+ except Exception as e:
80
+ logger.error(f"Get text from {locator} failed: {e}")
81
+ raise OperationTimeoutError("Get text failed.")
82
+
83
+
84
+
85
+
86
+ @staticmethod
87
+ def get_attribute(locator, attr, timeout=10):
88
+ try:
89
+ value = selenium_element.ElementActions.get_attribute(locator, attr, timeout)
90
+ logger.info(f"Get attribute '{attr}' from {locator}: {value}")
91
+ return value
92
+ except Exception as e:
93
+ logger.error(f"Get attribute {attr} from {locator} failed: {e}")
94
+ raise OperationTimeoutError("Get attribute failed.")
95
+
96
+
97
+
98
+
99
+ @staticmethod
100
+ def is_visible(locator, timeout=10):
101
+ visible = selenium_element.ElementActions.is_visible(locator, timeout)
102
+ return visible
103
+
104
+
105
+
106
+
107
+ @staticmethod
108
+ def move_to_element(locator, timeout=10):
109
+ try:
110
+ selenium_mouse.MouseActions.move_to_element(locator, timeout)
111
+ logger.info(f"Moved to element via Selenium: {locator}")
112
+ except Exception as e:
113
+ logger.error(f"Move to {locator} failed: {e}")
114
+ raise OperationTimeoutError("Move to element failed.")
115
+
116
+
117
+
118
+
119
+ # double_click supports switching to PyAutoGUI
120
+ @staticmethod
121
+ def double_click(locator=None, x=None, y=None, timeout=10):
122
+ if locator:
123
+ try:
124
+ selenium_mouse.MouseActions.double_click(locator, timeout)
125
+ logger.info(f"Double click via Selenium: {locator}")
126
+ return
127
+ except Exception as e:
128
+ logger.warning(f"Selenium double click failed: {e}, fallback.")
129
+ if x is not None and y is not None:
130
+ try:
131
+ pyauto_mouse.MouseActions.double_click_at(x, y)
132
+ logger.info(f"Double click at ({x},{y}) via PyAutoGUI")
133
+ return
134
+ except Exception as e:
135
+ logger.error(f"PyAutoGUI double click failed: {e}")
136
+ raise OperationTimeoutError("Double click failed.")
137
+
138
+
139
+
140
+
141
+ # right_click supports switching to PyAutoGUI
142
+ @staticmethod
143
+ def right_click(locator=None, x=None, y=None, timeout=10):
144
+ if locator:
145
+ try:
146
+ selenium_mouse.MouseActions.right_click(locator, timeout)
147
+ logger.info(f"Right click via Selenium: {locator}")
148
+ return
149
+ except Exception as e:
150
+ logger.warning(f"Selenium right click failed: {e}, fallback to PyAutoGUI.")
151
+ if x is not None and y is not None:
152
+ try:
153
+ pyauto_mouse.MouseActions.right_click_at(x, y)
154
+ logger.info(f"Right click at ({x},{y}) via PyAutoGUI")
155
+ return
156
+ except Exception as e:
157
+ logger.error(f"PyAutoGUI right click failed: {e}")
158
+ raise OperationTimeoutError("Right click action failed.")
159
+
160
+
161
+
162
+
163
+
164
+ @staticmethod
165
+ def drag_and_drop(source_locator, target_locator, timeout=10):
166
+ try:
167
+ selenium_mouse.MouseActions.drag_and_drop(source_locator, target_locator, timeout)
168
+ except Exception as e:
169
+ logger.error(f"Drag and drop from {source_locator} to {target_locator} failed: {e}")
170
+ raise OperationTimeoutError("Drag and drop failed.")
171
+
172
+
173
+
174
+ @staticmethod
175
+ def select_by_visible_text(locator, text, timeout=10):
176
+ try:
177
+ selenium_dropdown.DropdownActions.select_by_visible_text(locator, text, timeout)
178
+ except Exception as e:
179
+ logger.error(f"Select by text in {locator} failed: {e}")
180
+ raise OperationTimeoutError("Select by visible text failed.")
181
+
182
+
183
+
184
+
185
+ @staticmethod
186
+ def select_by_value(locator, value, timeout=10):
187
+ try:
188
+ selenium_dropdown.DropdownActions.select_by_value(locator, value, timeout)
189
+ except Exception as e:
190
+ logger.error(f"Select by value in {locator} failed: {e}")
191
+ raise OperationTimeoutError("Select by value failed.")
192
+
193
+
194
+
195
+
196
+ @staticmethod
197
+ def select_by_index(locator, index, timeout=10):
198
+ try:
199
+ selenium_dropdown.DropdownActions.select_by_index(locator, index, timeout)
200
+ except Exception as e:
201
+ logger.error(f"Select by index in {locator} failed: {e}")
202
+ raise OperationTimeoutError("Select by index failed.")
203
+
204
+
205
+
206
+
207
+ @staticmethod
208
+ def switch_to_window(window_name):
209
+ selenium_page.PageActions.switch_to_window(window_name)
210
+
211
+
212
+
213
+
214
+ @staticmethod
215
+ def switch_to_frame(frame_reference):
216
+ selenium_page.PageActions.switch_to_frame(frame_reference)
217
+
218
+
219
+
220
+
221
+ @staticmethod
222
+ def switch_to_default_content():
223
+ try:
224
+ selenium_page.PageActions.switch_to_default_content()
225
+ except Exception as e:
226
+ logger.error(f"Switch to default content failed: {e}")
227
+ raise OperationTimeoutError("Switch to default content failed.")
228
+
229
+
230
+
231
+
232
+ @staticmethod
233
+ def accept_alert():
234
+ selenium_page.PageActions.accept_alert()
235
+
236
+
237
+
238
+
239
+ @staticmethod
240
+ def dismiss_alert():
241
+ selenium_page.PageActions.dismiss_alert()
242
+
243
+
244
+
245
+
246
+ # take_screenshot supports switching to PyAutoGUI
247
+ @staticmethod
248
+ def take_screenshot(file_path):
249
+ try:
250
+ selenium_screenshot.ScreenshotActions.take_screenshot(file_path)
251
+ logger.info(f"Screenshot via Selenium saved to {file_path}")
252
+ return
253
+ except Exception as e:
254
+ logger.warning(f"Selenium screenshot failed: {e}, fallback to PyAutoGUI.")
255
+ try:
256
+ pyauto_screen.ScreenActions.take_screenshot(file_path)
257
+ logger.info(f"Screenshot via PyAutoGUI saved to {file_path}")
258
+ except Exception as e:
259
+ logger.error(f"PyAutoGUI screenshot failed: {e}")
260
+ raise OperationTimeoutError("Screenshot action failed.")
261
+
262
+
263
+
264
+
265
+ @staticmethod
266
+ def execute_script(script):
267
+ result = selenium_script.ScriptActions.execute_script(script)
268
+ return result
269
+
270
+
271
+
272
+
273
+ @staticmethod
274
+ def execute_async_script(script):
275
+ result = selenium_script.ScriptActions.execute_async_script(script)
276
+ return result
277
+
278
+
279
+
280
+
281
+ @staticmethod
282
+ def wait_for_element_visible(locator, timeout=10):
283
+ try:
284
+ selenium_wait.WaitActions.wait_for_element_visible(locator, timeout)
285
+ logger.info(f"Element {locator} is visible.")
286
+ except Exception as e:
287
+ logger.error(f"Wait for element {locator} visible failed: {e}")
288
+ raise OperationTimeoutError("Wait for element visible failed.")
289
+
290
+
291
+
292
+
293
+ @staticmethod
294
+ def wait_for_element_clickable(locator, timeout=10):
295
+ try:
296
+ selenium_wait.WaitActions.wait_for_element_clickable(locator, timeout)
297
+ logger.info(f"Element {locator} is clickable.")
298
+ except Exception as e:
299
+ logger.error(f"Wait for element {locator} clickable failed: {e}")
300
+ raise OperationTimeoutError("Wait for element clickable failed.")
301
+
302
+
303
+
304
+
305
+ # Below are the functions from the PyAutogGUI:
306
+ # Dialog Actions:
307
+ @staticmethod
308
+ def show_alert(message):
309
+ pyauto_dialog.DialogActions.show_alert(message)
310
+
311
+
312
+
313
+
314
+
315
+ @staticmethod
316
+ def show_confirm(message):
317
+ result = pyauto_dialog.DialogActions.show_confirm(message)
318
+ return result
319
+
320
+
321
+
322
+
323
+ @staticmethod
324
+ def show_prompt(message):
325
+ result = pyauto_dialog.DialogActions.show_prompt(message)
326
+ return result
327
+
328
+
329
+ # Keyboard Actions:
330
+ @staticmethod
331
+ def press_key(key):
332
+ pyauto_keyboard.KeyboardActions.press_key(key)
333
+
334
+
335
+
336
+ @staticmethod
337
+ def press_hotkey(*keys):
338
+ pyauto_keyboard.KeyboardActions.press_hotkey(*keys)
339
+
340
+
341
+
342
+ @staticmethod
343
+ def hold_and_release(keys, hold_time=0.5):
344
+ pyauto_keyboard.KeyboardActions.hold_and_release(keys, hold_time)
345
+
346
+
347
+
348
+ @staticmethod
349
+ def type_text(text, interval=0.05):
350
+ pyauto_keyboard.KeyboardActions.type_text(text, interval)
351
+
352
+
353
+ # Mouse Actions:
354
+ @staticmethod
355
+ def move_to(x, y, duration=0.5):
356
+ pyauto_mouse.MouseActions.move_to(x, y, duration)
357
+
358
+
359
+
360
+ @staticmethod
361
+ def drag_to(x, y, duration=0.5):
362
+ pyauto_mouse.MouseActions.drag_to(x, y, duration)
363
+
364
+
365
+
366
+ @staticmethod
367
+ def scroll(amount):
368
+ pyauto_mouse.MouseActions.scroll(amount)
369
+
370
+
371
+
372
+ @staticmethod
373
+ def get_mouse_position():
374
+ pos = pyauto_mouse.MouseActions.get_position()
375
+ return pos
376
+
377
+
378
+
379
+ # Screen Actions:
380
+ @staticmethod
381
+ def get_pixel_color(x, y):
382
+ color = pyauto_screen.ScreenActions.get_pixel_color(x, y)
383
+ return color
384
+
385
+
386
+
387
+ @staticmethod
388
+ def pixel_matches_color(x, y, expected_color):
389
+ match = pyauto_screen.ScreenActions.pixel_matches_color(x, y, expected_color)
390
+ return match
391
+
392
+
393
+
394
+
395
+ @staticmethod
396
+ def locate_image_on_screen(image_path, confidence=0.9):
397
+ pos = pyauto_screen.ScreenActions.locate_image_on_screen(image_path, confidence)
398
+ return pos
399
+
400
+
401
+
402
+ # Below are the functions from the PynPut:
403
+ @staticmethod
404
+ def press_key_pynput(key):
405
+ pynput_keyboard.KeyboardController.press_key(key)
406
+
407
+
408
+
409
+
410
+ @staticmethod
411
+ def release_key_pynput(key):
412
+ pynput_keyboard.KeyboardController.release_key(key)
413
+
414
+
415
+
416
+
417
+ @staticmethod
418
+ def press_and_hold_pynput(keys, hold_time=0.5):
419
+ pynput_keyboard.KeyboardController.press_and_hold(keys, hold_time)
420
+
421
+
422
+
423
+
424
+ @staticmethod
425
+ def start_keyboard_listener(on_press_func):
426
+ try:
427
+ pynput_kb_listener.KeyboardListener.start_listener(on_press_func)
428
+ logger.info(f"Pynput keyboard listener started.")
429
+ except Exception as e:
430
+ logger.error(f"Pynput keyboard listener failed: {e}")
431
+ raise OperationTimeoutError("Pynput keyboard listener failed.")
432
+
433
+
434
+
435
+
436
+ @staticmethod
437
+ def start_mouse_listener(on_click_func):
438
+ try:
439
+ pynput_mouse_listener.MouseListener.start_listener(on_click_func)
440
+ logger.info(f"Pynput mouse listener started.")
441
+ except Exception as e:
442
+ logger.error(f"Pynput mouse listener failed: {e}")
443
+ raise OperationTimeoutError("Pynput mouse listener failed.")
444
+
445
+
446
+
@@ -0,0 +1,28 @@
1
+ import yaml
2
+ import os
3
+ from .logger import logger
4
+ from .exceptions import ConfigLoadError
5
+
6
+
7
+
8
+ def load_config(config_path=None):
9
+ """
10
+ Load YAML configurations, return a dict
11
+ """
12
+ default_path = os.path.join(os.path.dirname(__file__), "../config/config.yaml")
13
+ final_path = config_path if config_path else default_path
14
+ try:
15
+ # Check if the path exists:
16
+ if not os.path.exists(final_path):
17
+ raise FileNotFoundError(f"No Configuration File Found: {final_path}")
18
+
19
+ # Open and analyze YAML file:
20
+ with open(final_path, "r", encoding="utf-8") as f:
21
+ config = yaml.safe_load(f)
22
+
23
+ logger.info(f"Successfully Loaded The Configuration File: {final_path}")
24
+ return config
25
+
26
+ except Exception as e:
27
+ logger.error(f"Failed To Load The Configuration File: {str(e)}")
28
+ raise ConfigLoadError("Failed To Load Configuration File.", e)
@@ -0,0 +1,83 @@
1
+
2
+
3
+ class BrowserLaunchError(Exception):
4
+ """
5
+ Custom exception for browser launch failures.
6
+
7
+ Attributes:
8
+ message (str): Human-readable error message.
9
+ original_exception (Exception, optional): Original exception instance.
10
+ """
11
+
12
+ def __init__(self, message, original_exception=None):
13
+ super().__init__(message)
14
+ self.original_exception = original_exception
15
+
16
+
17
+ def __str__(self):
18
+ if self.original_exception:
19
+ return f"{self.args[0]} (Caused by: {repr(self.original_exception)})"
20
+ return self.args[0]
21
+
22
+
23
+
24
+ class ConfigLoadError(Exception):
25
+ """
26
+ Custom exception for configuration loading failures.
27
+
28
+ Attributes:
29
+ message (str): Human-readable error message.
30
+ original_exception (Exception, optional): Original exception instance.
31
+ """
32
+
33
+ def __init__(self, message, original_exception=None):
34
+ super().__init__(message)
35
+ self.original_exception = original_exception
36
+
37
+ def __str__(self):
38
+ if self.original_exception:
39
+ return f"{self.args[0]} (Caused by: {repr(self.original_exception)})"
40
+ return self.args[0]
41
+
42
+
43
+
44
+
45
+
46
+ class ElementNotFoundError(Exception):
47
+ """
48
+ Raised when a web element cannot be found.
49
+
50
+ Attributes:
51
+ message (str): Human-readable error message.
52
+ original_exception (Exception, optional): Original exception instance.
53
+ """
54
+
55
+ def __init__(self, message="Element Not Found", original_exception=None):
56
+ super().__init__(message)
57
+ self.original_exception = original_exception
58
+
59
+ def __str__(self):
60
+ if self.original_exception:
61
+ return f"{self.args[0]} (Caused by: {repr(self.original_exception)})"
62
+ return self.args[0]
63
+
64
+
65
+
66
+
67
+ class OperationTimeoutError(Exception):
68
+ """
69
+ Raised when a wait or operation times out.
70
+
71
+ Attributes:
72
+ message (str): Human-readable error message.
73
+ original_exception (Exception, optional): Original exception instance.
74
+ """
75
+
76
+ def __init__(self, message="Operation Timed Out", original_exception=None):
77
+ super().__init__(message)
78
+ self.original_exception = original_exception
79
+
80
+ def __str__(self):
81
+ if self.original_exception:
82
+ return f"{self.args[0]} (Caused by: {repr(self.original_exception)})"
83
+ return self.args[0]
@@ -0,0 +1,31 @@
1
+ import logging
2
+ import os
3
+
4
+
5
+ # The path for the logger file:
6
+ log_dir = "logs"
7
+ os.makedirs(log_dir, exist_ok=True)
8
+ log_file = os.path.join(log_dir, "autoflex.log")
9
+
10
+ # Create a logger from logging:
11
+ logger = logging.getLogger("AutoFlexLogger")
12
+ logger.setLevel(logging.DEBUG) # All levels (DEBUG -> INFO -> WARNING -> ERROR -> CRITICAL)
13
+
14
+ # Log format:
15
+ formatter = logging.Formatter(
16
+ "%(asctime)s - %(levelname)s - %(filename)s - %(funcName)s - %(lineno)d - %(message)s"
17
+ )
18
+
19
+ # File logger handler:
20
+ file_handler = logging.FileHandler(log_file, encoding="utf-8")
21
+ file_handler.setLevel(logging.DEBUG)
22
+ file_handler.setFormatter(formatter)
23
+
24
+ # Console logger handler:
25
+ console_handler = logging.StreamHandler()
26
+ console_handler.setLevel(logging.INFO)
27
+ console_handler.setFormatter(formatter)
28
+
29
+ # Add both handlers to the logger instance:
30
+ logger.addHandler(file_handler)
31
+ logger.addHandler(console_handler)
@@ -0,0 +1,115 @@
1
+ # Imports
2
+ from selenium import webdriver
3
+ from selenium.webdriver.chrome.service import Service as ChromeService
4
+ from selenium.webdriver.edge.service import Service as EdgeService
5
+ from selenium.webdriver.firefox.service import Service as FirefoxService
6
+
7
+ from selenium.webdriver.chrome.options import Options as ChromeOptions
8
+ from selenium.webdriver.edge.options import Options as EdgeOptions
9
+ from selenium.webdriver.firefox.options import Options as FirefoxOptions
10
+
11
+ from .logger import logger
12
+ from .exceptions import BrowserLaunchError
13
+ from .config_loader import load_config
14
+
15
+
16
+ # The Browser Manager class:
17
+ class Browser:
18
+ _driver = None # Class variable (private)
19
+
20
+
21
+
22
+ @classmethod
23
+ def start(cls, browser_type="chrome", remote_url=None, config_path=None): # The config_path here is for user's customizable path
24
+ try:
25
+ logger.info(f"Starting The Browser: {browser_type}")
26
+
27
+ config = load_config(config_path)
28
+ options = None # Initialize the options and service, will update later
29
+ service = None
30
+
31
+ # Chrome logic:
32
+ if browser_type.lower() == "chrome":
33
+ options = ChromeOptions()
34
+ driver_path = config.get("chrome")
35
+ if not driver_path:
36
+ raise FileNotFoundError("Chrome Driver Path Not Specified In config.yaml")
37
+ service = ChromeService(executable_path=driver_path)
38
+
39
+ for arg in config.get("chrome_args", []):
40
+ options.add_argument(arg)
41
+
42
+ # Edge logic:
43
+ elif browser_type.lower() == "edge":
44
+ options = EdgeOptions()
45
+ driver_path = config.get("edge")
46
+ if not driver_path:
47
+ raise FileNotFoundError("Edge Driver Path Not Specified In config.yaml")
48
+ service = EdgeService(executable_path=driver_path)
49
+
50
+ for arg in config.get("edge_args", []):
51
+ options.add_argument(arg)
52
+
53
+ # Firefox logic:
54
+ elif browser_type.lower() == "firefox":
55
+ options = FirefoxOptions()
56
+ driver_path = config.get("firefox")
57
+ if not driver_path:
58
+ raise FileNotFoundError("Firefox Driver Path Not Specified In config.yaml")
59
+ service = FirefoxService(executable_path=driver_path)
60
+
61
+
62
+ for key, value in config.get("firefox_prefs", {}).items():
63
+ options.set_preference(key, value)
64
+
65
+ else:
66
+ raise ValueError(f"Not Supported Browser Type: {browser_type}")
67
+
68
+ # Logic of dealing with remote url:
69
+ if remote_url:
70
+ logger.info(f"Utilizing Remote WebDriver: {remote_url}")
71
+ cls._driver = webdriver.Remote(command_executor=remote_url, options=options)
72
+ else:
73
+ if browser_type.lower() == "chrome":
74
+ cls._driver = webdriver.Chrome(service=service, options=options)
75
+ elif browser_type.lower() == "edge":
76
+ cls._driver = webdriver.Edge(service=service, options=options)
77
+ elif browser_type.lower() == "firefox":
78
+ cls._driver = webdriver.Firefox(service=service, options=options)
79
+
80
+ cls._driver.maximize_window()
81
+
82
+ logger.info(f"{browser_type} Successfully Running")
83
+ return cls._driver
84
+
85
+ except Exception as e:
86
+ logger.error(f"Error Occured When Trying To Start The Browser: {str(e)}")
87
+ raise BrowserLaunchError(f"Failed To Start The Browser: {str(e)}")
88
+
89
+
90
+
91
+
92
+ '''
93
+ This method acts as a getter,
94
+ it returns the current driver object
95
+ '''
96
+ @classmethod
97
+ def get_driver(cls):
98
+ if cls._driver is None:
99
+ raise BrowserLaunchError("Not Starting The Browser Yet, Please call Browser.start()")
100
+ return cls._driver
101
+
102
+
103
+
104
+
105
+ @classmethod
106
+ def quit(cls):
107
+ try:
108
+ if cls._driver:
109
+ logger.info("Closing the browser")
110
+ cls._driver.quit()
111
+ cls._driver = None
112
+ except Exception as e:
113
+ logger.error(f"Failed to close the browser: {str(e)}")
114
+
115
+
File without changes
@@ -0,0 +1,50 @@
1
+ from autoflex.core.web_manager import Browser
2
+ from autoflex.core.actions import Actions
3
+ from autoflex.core.exceptions import BrowserLaunchError
4
+ import time
5
+
6
+
7
+ def on_key_press(key):
8
+ print(f"[监听] 键盘按下: {key}")
9
+ if str(key) == "'q'":
10
+ print("监听到 Q,停止监听")
11
+ return False
12
+
13
+
14
+ def on_mouse_click(x, y, button, pressed):
15
+ if pressed:
16
+ print(f"[监听] 鼠标点击: ({x},{y}) {button}")
17
+
18
+
19
+ try:
20
+ # 启动浏览器
21
+ driver = Browser.start("edge")
22
+ Actions.take_screenshot("startup.png")
23
+
24
+ # 打开百度并执行点击+输入+截图
25
+ driver.get("https://www.baidu.com")
26
+ Actions.take_screenshot("baidu_home.png")
27
+ Actions.click(locator=("css selector", "#kw"))
28
+ Actions.type_text("AutoFlex 调度器测试")
29
+ Actions.click(locator=("css selector", "#su"))
30
+
31
+ # 鼠标移动+截图
32
+ Actions.move_to(500, 200)
33
+ Actions.take_screenshot("after_move.png")
34
+
35
+ # 执行 Pynput 键盘监听
36
+ print("按 Q 键停止键盘监听:")
37
+ Actions.start_keyboard_listener(on_key_press)
38
+
39
+ # 执行 Pynput 鼠标监听
40
+ print("鼠标点击事件监听中,单击停止监听:")
41
+ Actions.start_mouse_listener(on_mouse_click)
42
+
43
+ input("测试完成,按 Enter 退出")
44
+ Browser.quit()
45
+
46
+ except BrowserLaunchError as e:
47
+ print(f"Failed to launch browser: {e}")
48
+ except Exception as e:
49
+ print(f"执行异常: {e}")
50
+
@@ -0,0 +1,26 @@
1
+ Metadata-Version: 2.4
2
+ Name: autoflex
3
+ Version: 1.0.0
4
+ Summary: Web and System Automation Testing Framework
5
+ Home-page: https://github.com/LZYEIL/AutoFlex
6
+ Author: Zhiyuan Li
7
+ Author-email: zhiyuanjeremy@gmail.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: selenium>=4.24.0
14
+ Requires-Dist: pyautogui>=0.9.54
15
+ Requires-Dist: pynput>=1.8.1
16
+ Dynamic: author
17
+ Dynamic: author-email
18
+ Dynamic: classifier
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: home-page
22
+ Dynamic: requires-dist
23
+ Dynamic: requires-python
24
+ Dynamic: summary
25
+
26
+ # This is AutoFlex README Template
@@ -0,0 +1,17 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.py
4
+ autoflex/__init__.py
5
+ autoflex.egg-info/PKG-INFO
6
+ autoflex.egg-info/SOURCES.txt
7
+ autoflex.egg-info/dependency_links.txt
8
+ autoflex.egg-info/requires.txt
9
+ autoflex.egg-info/top_level.txt
10
+ autoflex/core/__init__.py
11
+ autoflex/core/actions.py
12
+ autoflex/core/config_loader.py
13
+ autoflex/core/exceptions.py
14
+ autoflex/core/logger.py
15
+ autoflex/core/web_manager.py
16
+ autoflex/tests/__init__.py
17
+ autoflex/tests/test_browser.py
@@ -0,0 +1,3 @@
1
+ selenium>=4.24.0
2
+ pyautogui>=0.9.54
3
+ pynput>=1.8.1
@@ -0,0 +1 @@
1
+ autoflex
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42", "wheel"]
3
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,24 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name='autoflex',
5
+ version='1.0.0',
6
+ description='Web and System Automation Testing Framework',
7
+ long_description=open('README.md', encoding='utf-8').read(),
8
+ long_description_content_type='text/markdown',
9
+ author='Zhiyuan Li',
10
+ author_email='zhiyuanjeremy@gmail.com',
11
+ url='https://github.com/LZYEIL/AutoFlex',
12
+ packages=find_packages(),
13
+ install_requires=[
14
+ 'selenium>=4.24.0',
15
+ 'pyautogui>=0.9.54',
16
+ 'pynput>=1.8.1',
17
+ ],
18
+ classifiers=[
19
+ 'Programming Language :: Python :: 3',
20
+ 'License :: OSI Approved :: MIT License',
21
+ 'Operating System :: OS Independent',
22
+ ],
23
+ python_requires='>=3.8',
24
+ )