|
10 | 10 |
|
11 | 11 | logger = logging.getLogger(__name__) |
12 | 12 |
|
13 | | -# Global variables to cache browser initialization |
14 | | -_browser = None |
15 | | -_controller = None |
16 | | - |
17 | | - |
18 | | -def init_browser(): |
19 | | - """ |
20 | | - Initialize and cache browser and controller instances. |
21 | | -
|
22 | | - This function uses a singleton pattern to ensure we only create one browser |
23 | | - instance throughout the application lifecycle, which saves resources. |
24 | | -
|
25 | | - Returns: |
26 | | - tuple: (Browser, Controller) instances for web automation |
27 | | - """ |
28 | | - global _browser, _controller |
29 | | - |
30 | | - # Return cached instances if already initialized |
31 | | - if _browser is not None and _controller is not None: |
32 | | - return _browser, _controller |
33 | | - |
34 | | - from browser_use import Browser, BrowserConfig, BrowserContextConfig, Controller |
35 | | - from browser_use.agent.views import ActionResult |
36 | | - from browser_use.browser.context import BrowserContext |
37 | | - |
38 | | - # Set up downloads directory for browser operations |
39 | | - downloads_path = os.path.join(os.getcwd(), "downloads") |
40 | | - if not os.path.exists(downloads_path): |
41 | | - os.makedirs(downloads_path) |
42 | | - |
43 | | - context_config = BrowserContextConfig(save_downloads_path=downloads_path) |
44 | | - config = BrowserConfig(headless=True, disable_security=True, new_context_config=context_config) |
45 | | - controller = Controller() |
46 | | - |
47 | | - # Register custom action to upload files to web elements |
48 | | - @controller.action( |
49 | | - description="Upload file to interactive element with file path", |
50 | | - ) |
51 | | - async def upload_file(index: int, path: str, browser: BrowserContext): |
52 | | - """ |
53 | | - Upload a file to a file input element identified by its index. |
54 | | -
|
55 | | - Args: |
56 | | - index: The DOM element index to target |
57 | | - path: Local file path to upload |
58 | | - browser: Browser context for interaction |
59 | | -
|
60 | | - Returns: |
61 | | - ActionResult: Result of the upload operation |
62 | | - """ |
63 | | - if not os.path.exists(path): |
64 | | - return ActionResult(error=f"File {path} does not exist") |
65 | | - |
66 | | - dom_el = await browser.get_dom_element_by_index(index) |
67 | | - file_upload_dom_el = dom_el.get_file_upload_element() |
68 | | - |
69 | | - if file_upload_dom_el is None: |
70 | | - msg = f"No file upload element found at index {index}. The element may be hidden or not an input type file" |
71 | | - logger.info(msg) |
72 | | - return ActionResult(error=msg) |
73 | | - |
74 | | - file_upload_el = await browser.get_locate_element(file_upload_dom_el) |
75 | | - |
76 | | - if file_upload_el is None: |
77 | | - msg = f"No file upload element found at index {index}. The element may be hidden or not an input type file" |
78 | | - logger.info(msg) |
79 | | - return ActionResult(error=msg) |
80 | | - |
81 | | - try: |
82 | | - await file_upload_el.set_input_files(path) |
83 | | - msg = f"Successfully uploaded file to index {index}" |
84 | | - logger.info(msg) |
85 | | - return ActionResult(extracted_content=msg, include_in_memory=True) |
86 | | - except Exception as e: |
87 | | - msg = f"Failed to upload file to index {index}: {str(e)}" |
88 | | - logger.info(msg) |
89 | | - return ActionResult(error=msg) |
90 | | - |
91 | | - # Register custom action to read file contents |
92 | | - @controller.action(description="Read the file content of a file given a path") |
93 | | - async def read_file(path: str): |
94 | | - """ |
95 | | - Read and return the contents of a file at the specified path. |
96 | | -
|
97 | | - Args: |
98 | | - path: Path to the file to read |
99 | | -
|
100 | | - Returns: |
101 | | - ActionResult: File contents or error message |
102 | | - """ |
103 | | - if not os.path.exists(path): |
104 | | - return ActionResult(error=f"File {path} does not exist") |
105 | | - |
106 | | - with open(path, "r") as f: |
107 | | - content = f.read() |
108 | | - msg = f"File content: {content}" |
109 | | - logger.info(msg) |
110 | | - return ActionResult(extracted_content=msg, include_in_memory=True) |
111 | | - |
112 | | - # Cache the initialized instances |
113 | | - _browser = Browser(config=config) |
114 | | - _controller = controller |
115 | | - |
116 | | - return _browser, _controller |
117 | | - |
118 | 13 |
|
119 | 14 | class BrowserUse(Step, input_class=BrowserUseInputs, output_class=BrowserUseOutputs): |
120 | 15 | """ |
@@ -174,20 +69,29 @@ def run(self) -> dict: |
174 | 69 | dict: Results of the browser automation task |
175 | 70 | """ |
176 | 71 | from browser_use import Agent |
| 72 | + from browser_use import BrowserConfig |
| 73 | + from patchwork.common.utils.browser_initializer import BrowserInitializer |
177 | 74 |
|
178 | | - browser, controller = init_browser() |
| 75 | + browser_config = BrowserConfig(headless=self.inputs.get("headless", True), disable_security=True) |
| 76 | + browser_context = BrowserInitializer.init_browser_context(browser_config) |
| 77 | + controller = BrowserInitializer.init_controller() |
| 78 | + logger.info("Browser initialized") |
179 | 79 | agent = Agent( |
180 | | - browser=browser, |
| 80 | + browser_context=browser_context, |
181 | 81 | controller=controller, |
182 | 82 | task=mustache_render(self.inputs["task"], self.inputs["task_value"]), |
183 | 83 | llm=self.llm, |
184 | 84 | generate_gif=self.generate_gif, |
185 | 85 | validate_output=True, |
| 86 | + initial_actions=self.inputs.get("initial_actions", None), |
186 | 87 | ) |
187 | 88 |
|
188 | 89 | # Run the agent in an event loop |
189 | 90 | loop = asyncio.new_event_loop() |
190 | 91 | self.history = loop.run_until_complete(agent.run()) |
| 92 | + loop.run_until_complete(browser_context.close()) |
| 93 | + loop.run_until_complete(browser_context.browser.close()) |
| 94 | + loop.close() |
191 | 95 |
|
192 | 96 | # Format results as JSON if schema provided |
193 | 97 | if "example_json" in self.inputs: |
|
0 commit comments