From da751137837356bfb5d8ad9f5d58cd1d2dc4968c Mon Sep 17 00:00:00 2001 From: rainerWJY Date: Fri, 8 Aug 2025 00:23:54 +0800 Subject: [PATCH 1/4] feat(playwright): integrate Playwright for browser automation with Spring Boot support - Added Playwright dependency versioning in pom.xml. - Enhanced ChromeDriverService to support Playwright initialization in Spring Boot environments. - Implemented browser type detection and configuration options for headless mode. - Improved error handling and logging for Playwright instance creation. --- spring-ai-alibaba-jmanus/pom.xml | 5 +- .../tool/browser/ChromeDriverService.java | 129 ++++++++++++++++-- .../SpringBootPlaywrightInitializer.java | 105 ++++++++++++++ 3 files changed, 227 insertions(+), 12 deletions(-) create mode 100644 spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/browser/SpringBootPlaywrightInitializer.java diff --git a/spring-ai-alibaba-jmanus/pom.xml b/spring-ai-alibaba-jmanus/pom.xml index 0ec1bcdb84..049f9e94ef 100644 --- a/spring-ai-alibaba-jmanus/pom.xml +++ b/spring-ai-alibaba-jmanus/pom.xml @@ -28,6 +28,9 @@ 6.1.0 + + 1.53.0 + 3.5.0 true @@ -168,7 +171,7 @@ com.microsoft.playwright playwright - 1.52.0 + ${playwright.version} commons-codec diff --git a/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/browser/ChromeDriverService.java b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/browser/ChromeDriverService.java index 8f0cf22b5b..62bc285d07 100644 --- a/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/browser/ChromeDriverService.java +++ b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/browser/ChromeDriverService.java @@ -31,6 +31,7 @@ import com.microsoft.playwright.Playwright; import com.microsoft.playwright.Browser; import com.microsoft.playwright.BrowserType; +import org.springframework.beans.factory.annotation.Autowired; import jakarta.annotation.PreDestroy; import org.slf4j.Logger; @@ -57,6 +58,9 @@ public class ChromeDriverService implements IChromeDriverService { private final ObjectMapper objectMapper; + @Autowired(required = false) + private SpringBootPlaywrightInitializer playwrightInitializer; + /** * Shared directory for storing cookies */ @@ -197,21 +201,90 @@ public void closeDriverForPlan(String planId) { } private DriverWrapper createNewDriver() { - Playwright playwright = null; try { + // Check if running in Spring Boot environment + if (isSpringBootEnvironment()) { + log.info("Detected Spring Boot environment, using special handling"); + return createNewDriverForSpringBoot(); + } + + // Standard handling approach + try (Playwright playwright = Playwright.create()) { + // Get browser type, supports environment variable configuration + BrowserType browserType = getBrowserTypeFromEnv(playwright); + log.info("Using browser type: {}", browserType.name()); + + BrowserType.LaunchOptions options = new BrowserType.LaunchOptions(); + + // Basic configuration + options.setArgs(Arrays.asList("--remote-allow-origins=*", + "--disable-blink-features=AutomationControlled", "--disable-infobars", + "--disable-notifications", "--disable-dev-shm-usage", "--lang=zh-CN,zh,en-US,en", + "--user-agent=" + getRandomUserAgent(), "--window-size=1920,1080")); + + // Decide whether to use headless mode based on configuration + if (manusProperties.getBrowserHeadless()) { + log.info("Enable Playwright headless mode"); + options.setHeadless(true); + } + else { + log.info("Enable Playwright non-headless mode"); + options.setHeadless(false); + } + + // Use browserType.launch() instead of playwright.chromium().launch() + Browser browser = browserType.launch(options); + log.info("Created new Playwright Browser instance with anti-detection"); + + // Create DriverWrapper, but don't close playwright here, let + // DriverWrapper manage it + DriverWrapper wrapper = new DriverWrapper(playwright, browser, browser.newPage(), this.sharedDir, + objectMapper); + + return wrapper; + } + } + catch (Exception e) { + log.error("Failed to create Playwright Browser instance", e); + throw new RuntimeException("Failed to initialize Playwright Browser", e); + } + } + + /** + * Create browser driver for Spring Boot environment + */ + private DriverWrapper createNewDriverForSpringBoot() { + // In Spring Boot environment, we need to manually set system properties + System.setProperty("playwright.browsers.path", System.getProperty("user.home") + "/.cache/ms-playwright"); + + // Set custom driver temp directory to avoid classpath issues + System.setProperty("playwright.driver.tmpdir", System.getProperty("java.io.tmpdir")); + + // Skip browser download if browsers are already installed + System.setProperty("PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD", "1"); - if (playwright == null) { + // Try to create Playwright instance using Spring Boot initializer + Playwright playwright = null; + try { + if (playwrightInitializer != null && playwrightInitializer.canInitialize()) { + log.info("Using SpringBootPlaywrightInitializer"); + playwright = playwrightInitializer.createPlaywright(); + } + else { + log.info("Using standard Playwright initialization"); playwright = Playwright.create(); } + + // Get browser type + BrowserType browserType = getBrowserTypeFromEnv(playwright); + log.info("Using browser type: {}", browserType.name()); + BrowserType.LaunchOptions options = new BrowserType.LaunchOptions(); // Basic configuration options.setArgs(Arrays.asList("--remote-allow-origins=*", "--disable-blink-features=AutomationControlled", "--disable-infobars", "--disable-notifications", "--disable-dev-shm-usage", - "--lang=zh-CN,zh,en-US,en", "--user-agent=" + getRandomUserAgent(), "--window-size=1920,1080" // Default - // window - // size - )); + "--lang=zh-CN,zh,en-US,en", "--user-agent=" + getRandomUserAgent(), "--window-size=1920,1080")); // Decide whether to use headless mode based on configuration if (manusProperties.getBrowserHeadless()) { @@ -223,9 +296,9 @@ private DriverWrapper createNewDriver() { options.setHeadless(false); } - Browser browser = playwright.chromium().launch(options); - log.info("Created new Playwright Browser instance with anti-detection"); - // Pass the sharedDir to the DriverWrapper constructor + Browser browser = browserType.launch(options); + log.info("Created new Playwright Browser instance for Spring Boot environment"); + return new DriverWrapper(playwright, browser, browser.newPage(), this.sharedDir, objectMapper); } catch (Exception e) { @@ -237,8 +310,42 @@ private DriverWrapper createNewDriver() { log.warn("Failed to close failed Playwright instance", ex); } } - log.error("Failed to create Playwright Browser instance", e); - throw new RuntimeException("Failed to initialize Playwright Browser", e); + throw new RuntimeException("Failed to initialize Playwright Browser in Spring Boot environment", e); + } + } + + /** + * Detect if running in Spring Boot environment + */ + private boolean isSpringBootEnvironment() { + try { + // Simple check for Spring Boot application class + Class.forName("org.springframework.boot.SpringApplication"); + return true; + } + catch (ClassNotFoundException e) { + // If Spring Boot is not found, assume standard environment + return false; + } + } + + /** + * Get browser type, supports environment variable configuration + */ + private BrowserType getBrowserTypeFromEnv(Playwright playwright) { + String browserName = System.getenv("BROWSER"); + if (browserName == null) { + browserName = "chromium"; + } + + switch (browserName.toLowerCase()) { + case "webkit": + return playwright.webkit(); + case "firefox": + return playwright.firefox(); + case "chromium": + default: + return playwright.chromium(); } } diff --git a/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/browser/SpringBootPlaywrightInitializer.java b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/browser/SpringBootPlaywrightInitializer.java new file mode 100644 index 0000000000..93744ecf17 --- /dev/null +++ b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/browser/SpringBootPlaywrightInitializer.java @@ -0,0 +1,105 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.example.manus.tool.browser; + +import com.microsoft.playwright.Playwright; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Spring Boot environment Playwright initializer Handles the special requirements for + * running Playwright in Spring Boot fat jar + */ +@Component +public class SpringBootPlaywrightInitializer { + + private static final Logger log = LoggerFactory.getLogger(SpringBootPlaywrightInitializer.class); + + /** + * Initialize Playwright for Spring Boot environment + */ + public Playwright createPlaywright() { + try { + // Set up environment for Spring Boot + setupSpringBootEnvironment(); + + // Save current context class loader + ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); + + try { + // Use this class's classloader (LaunchedClassLoader in Spring Boot) + Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader()); + return Playwright.create(); + } + finally { + // Always restore original class loader + Thread.currentThread().setContextClassLoader(originalClassLoader); + } + } + catch (Exception e) { + log.error("Failed to create Playwright in Spring Boot environment", e); + throw new RuntimeException("Failed to initialize Playwright", e); + } + } + + /** + * Set up environment properties for Spring Boot + */ + private void setupSpringBootEnvironment() { + // Set browser path + String browserPath = System.getProperty("user.home") + "/.cache/ms-playwright"; + System.setProperty("playwright.browsers.path", browserPath); + + // Set driver temp directory + String tempDir = System.getProperty("java.io.tmpdir"); + System.setProperty("playwright.driver.tmpdir", tempDir); + + // Check if browsers are installed + Path browsersPath = Paths.get(browserPath); + if (Files.exists(browsersPath)) { + log.info("Playwright browsers found at: {}", browserPath); + System.setProperty("PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD", "1"); + } + else { + log.warn("Playwright browsers not found at: {}. They will be downloaded on first use.", browserPath); + } + + log.info("Spring Boot Playwright environment configured:"); + log.info(" - Browser path: {}", browserPath); + log.info(" - Temp directory: {}", tempDir); + } + + /** + * Check if Playwright can be initialized + */ + public boolean canInitialize() { + try { + // Try to find the required classes + Class.forName("com.microsoft.playwright.Playwright"); + return true; + } + catch (ClassNotFoundException e) { + log.error("Playwright classes not found in classpath", e); + return false; + } + } + +} From 3dee81df4225e54b6533c950b824972649ec974c Mon Sep 17 00:00:00 2001 From: rainerWJY Date: Fri, 8 Aug 2025 00:58:00 +0800 Subject: [PATCH 2/4] feat(playwright): enhance logging and directory checks during Playwright initialization - Added detailed logging for class loader switching and Playwright instance creation. - Implemented checks for browser and temporary directories post-creation. - Enhanced environment setup with additional logging for system properties related to Playwright. --- .../SpringBootPlaywrightInitializer.java | 90 ++++++++++++++++++- 1 file changed, 88 insertions(+), 2 deletions(-) diff --git a/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/browser/SpringBootPlaywrightInitializer.java b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/browser/SpringBootPlaywrightInitializer.java index 93744ecf17..5f747722be 100644 --- a/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/browser/SpringBootPlaywrightInitializer.java +++ b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/browser/SpringBootPlaywrightInitializer.java @@ -46,8 +46,44 @@ public Playwright createPlaywright() { try { // Use this class's classloader (LaunchedClassLoader in Spring Boot) - Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader()); - return Playwright.create(); + ClassLoader newCL = this.getClass().getClassLoader(); + log.info("Switching to ClassLoader: {} [{}]", newCL.getClass().getSimpleName(), newCL.toString()); + Thread.currentThread().setContextClassLoader(newCL); + + log.info("About to call Playwright.create()..."); + Playwright playwright = Playwright.create(); + log.info("Playwright.create() successful! Instance: {}", playwright.getClass().getName()); + + // Check what was actually downloaded after creation + log.info("=== Post-Creation Directory Check ==="); + String browserPath = System.getProperty("playwright.browsers.path"); + String tempDir = System.getProperty("playwright.driver.tmpdir"); + + try { + Path browsersPath = Paths.get(browserPath); + if (Files.exists(browsersPath)) { + log.info("Browsers directory content:"); + Files.walk(browsersPath, 2).forEach(path -> { + if (!path.equals(browsersPath)) { + log.info(" - {}", browsersPath.relativize(path)); + } + }); + } + + // Check temp directory for playwright files + Path tempPath = Paths.get(tempDir); + if (Files.exists(tempPath)) { + Files.list(tempPath) + .filter(path -> path.getFileName().toString().contains("playwright")) + .forEach(path -> log.info("Temp playwright file: {}", path)); + } + } + catch (Exception e) { + log.warn("Could not list post-creation directories: {}", e.getMessage()); + } + log.info("====================================="); + + return playwright; } finally { // Always restore original class loader @@ -64,6 +100,25 @@ public Playwright createPlaywright() { * Set up environment properties for Spring Boot */ private void setupSpringBootEnvironment() { + // Print detailed class loader information + ClassLoader currentCL = Thread.currentThread().getContextClassLoader(); + ClassLoader thisCL = this.getClass().getClassLoader(); + + log.info("=== Playwright Class Loader Analysis ==="); + log.info("Current thread context ClassLoader: {} [{}]", currentCL.getClass().getSimpleName(), + currentCL.toString()); + log.info("This class ClassLoader: {} [{}]", thisCL.getClass().getSimpleName(), thisCL.toString()); + + // Print classpath information + String classPath = System.getProperty("java.class.path"); + log.info("Java classpath: {}", classPath); + + // Print loader path if exists + String loaderPath = System.getProperty("loader.path"); + if (loaderPath != null) { + log.info("Spring Boot loader.path: {}", loaderPath); + } + // Set browser path String browserPath = System.getProperty("user.home") + "/.cache/ms-playwright"; System.setProperty("playwright.browsers.path", browserPath); @@ -85,6 +140,37 @@ private void setupSpringBootEnvironment() { log.info("Spring Boot Playwright environment configured:"); log.info(" - Browser path: {}", browserPath); log.info(" - Temp directory: {}", tempDir); + + // Print all Playwright-related system properties + log.info("=== Playwright Runtime Directories ==="); + log.info(" - playwright.browsers.path: {}", System.getProperty("playwright.browsers.path")); + log.info(" - playwright.driver.tmpdir: {}", System.getProperty("playwright.driver.tmpdir")); + log.info(" - PLAYWRIGHT_BROWSERS_PATH env: {}", System.getenv("PLAYWRIGHT_BROWSERS_PATH")); + log.info(" - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: {}", System.getProperty("PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD")); + + // Check actual directories + String[] checkPaths = { browserPath, browserPath + "/chromium-*", browserPath + "/firefox-*", + browserPath + "/webkit-*", tempDir + "/playwright-java-*" }; + + for (String path : checkPaths) { + try { + Path p = Paths.get(path.replace("*", "")); + if (Files.exists(p)) { + log.info(" ✓ Directory exists: {}", path); + if (Files.isDirectory(p)) { + Files.list(p).forEach(subPath -> log.info(" - {}", subPath.getFileName())); + } + } + else { + log.info(" ✗ Directory not found: {}", path); + } + } + catch (Exception e) { + log.warn(" ? Could not check path {}: {}", path, e.getMessage()); + } + } + + log.info("=========================================="); } /** From cedbcd3dca4106e3db9194d515afbee981ec509a Mon Sep 17 00:00:00 2001 From: rainerWJY Date: Fri, 8 Aug 2025 10:13:51 +0800 Subject: [PATCH 3/4] up --- .../manus/tool/browser/ChromeDriverService.java | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/browser/ChromeDriverService.java b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/browser/ChromeDriverService.java index 62bc285d07..ba7e21cf5a 100644 --- a/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/browser/ChromeDriverService.java +++ b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/browser/ChromeDriverService.java @@ -315,18 +315,11 @@ private DriverWrapper createNewDriverForSpringBoot() { } /** - * Detect if running in Spring Boot environment + * Detect if running in Spring Boot environment , default return true because we are + * in spring boot env . */ private boolean isSpringBootEnvironment() { - try { - // Simple check for Spring Boot application class - Class.forName("org.springframework.boot.SpringApplication"); - return true; - } - catch (ClassNotFoundException e) { - // If Spring Boot is not found, assume standard environment - return false; - } + return true; } /** From 6b13cfb54ef554499f4ef144ecf1be45ddef973d Mon Sep 17 00:00:00 2001 From: rainerWJY Date: Fri, 8 Aug 2025 10:24:58 +0800 Subject: [PATCH 4/4] =?UTF-8?q?refactor(spring-ai-alibaba-jmanus):=20?= =?UTF-8?q?=E9=87=8D=E6=9E=84=20ChromeDriverService=20=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除了特定于 Spring Boot 环境的代码 - 优化了浏览器驱动实例的创建过程 - 添加了对浏览器页面超时的配置支持 - 简化了错误处理逻辑 - 删除了未使用的 isSpringBootEnvironment 方法 --- .../tool/browser/ChromeDriverService.java | 80 +++++-------------- 1 file changed, 19 insertions(+), 61 deletions(-) diff --git a/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/browser/ChromeDriverService.java b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/browser/ChromeDriverService.java index ba7e21cf5a..c2ad0205f4 100644 --- a/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/browser/ChromeDriverService.java +++ b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/browser/ChromeDriverService.java @@ -31,6 +31,7 @@ import com.microsoft.playwright.Playwright; import com.microsoft.playwright.Browser; import com.microsoft.playwright.BrowserType; +import com.microsoft.playwright.Page; import org.springframework.beans.factory.annotation.Autowired; import jakarta.annotation.PreDestroy; @@ -201,60 +202,15 @@ public void closeDriverForPlan(String planId) { } private DriverWrapper createNewDriver() { - try { - // Check if running in Spring Boot environment - if (isSpringBootEnvironment()) { - log.info("Detected Spring Boot environment, using special handling"); - return createNewDriverForSpringBoot(); - } - - // Standard handling approach - try (Playwright playwright = Playwright.create()) { - // Get browser type, supports environment variable configuration - BrowserType browserType = getBrowserTypeFromEnv(playwright); - log.info("Using browser type: {}", browserType.name()); - - BrowserType.LaunchOptions options = new BrowserType.LaunchOptions(); - - // Basic configuration - options.setArgs(Arrays.asList("--remote-allow-origins=*", - "--disable-blink-features=AutomationControlled", "--disable-infobars", - "--disable-notifications", "--disable-dev-shm-usage", "--lang=zh-CN,zh,en-US,en", - "--user-agent=" + getRandomUserAgent(), "--window-size=1920,1080")); - - // Decide whether to use headless mode based on configuration - if (manusProperties.getBrowserHeadless()) { - log.info("Enable Playwright headless mode"); - options.setHeadless(true); - } - else { - log.info("Enable Playwright non-headless mode"); - options.setHeadless(false); - } - - // Use browserType.launch() instead of playwright.chromium().launch() - Browser browser = browserType.launch(options); - log.info("Created new Playwright Browser instance with anti-detection"); - - // Create DriverWrapper, but don't close playwright here, let - // DriverWrapper manage it - DriverWrapper wrapper = new DriverWrapper(playwright, browser, browser.newPage(), this.sharedDir, - objectMapper); - - return wrapper; - } - } - catch (Exception e) { - log.error("Failed to create Playwright Browser instance", e); - throw new RuntimeException("Failed to initialize Playwright Browser", e); - } + log.info("Creating new browser driver"); + return createDriverInstance(); } /** - * Create browser driver for Spring Boot environment + * Create browser driver instance */ - private DriverWrapper createNewDriverForSpringBoot() { - // In Spring Boot environment, we need to manually set system properties + private DriverWrapper createDriverInstance() { + // Set system properties for Playwright configuration System.setProperty("playwright.browsers.path", System.getProperty("user.home") + "/.cache/ms-playwright"); // Set custom driver temp directory to avoid classpath issues @@ -297,9 +253,19 @@ private DriverWrapper createNewDriverForSpringBoot() { } Browser browser = browserType.launch(options); - log.info("Created new Playwright Browser instance for Spring Boot environment"); + log.info("Created new Playwright Browser instance"); + + // Create new page and configure timeout + Page page = browser.newPage(); + + // Set default timeout based on configuration + Integer timeout = manusProperties.getBrowserRequestTimeout(); + if (timeout != null && timeout > 0) { + log.info("Setting browser page timeout to {} seconds", timeout); + page.setDefaultTimeout(timeout * 1000); // Convert to milliseconds + } - return new DriverWrapper(playwright, browser, browser.newPage(), this.sharedDir, objectMapper); + return new DriverWrapper(playwright, browser, page, this.sharedDir, objectMapper); } catch (Exception e) { if (playwright != null) { @@ -310,18 +276,10 @@ private DriverWrapper createNewDriverForSpringBoot() { log.warn("Failed to close failed Playwright instance", ex); } } - throw new RuntimeException("Failed to initialize Playwright Browser in Spring Boot environment", e); + throw new RuntimeException("Failed to initialize Playwright Browser", e); } } - /** - * Detect if running in Spring Boot environment , default return true because we are - * in spring boot env . - */ - private boolean isSpringBootEnvironment() { - return true; - } - /** * Get browser type, supports environment variable configuration */