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..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,8 @@ 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; import org.slf4j.Logger; @@ -57,6 +59,9 @@ public class ChromeDriverService implements IChromeDriverService { private final ObjectMapper objectMapper; + @Autowired(required = false) + private SpringBootPlaywrightInitializer playwrightInitializer; + /** * Shared directory for storing cookies */ @@ -197,21 +202,45 @@ public void closeDriverForPlan(String planId) { } private DriverWrapper createNewDriver() { + log.info("Creating new browser driver"); + return createDriverInstance(); + } + + /** + * Create browser driver instance + */ + 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 + 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"); + + // Try to create Playwright instance using Spring Boot initializer Playwright playwright = null; try { - - if (playwright == null) { + 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,10 +252,20 @@ 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 - return new DriverWrapper(playwright, browser, browser.newPage(), this.sharedDir, objectMapper); + Browser browser = browserType.launch(options); + 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, page, this.sharedDir, objectMapper); } catch (Exception e) { if (playwright != null) { @@ -237,11 +276,30 @@ 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); } } + /** + * 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(); + } + } + private String getRandomUserAgent() { List userAgents = Arrays.asList( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", 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..5f747722be --- /dev/null +++ b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/browser/SpringBootPlaywrightInitializer.java @@ -0,0 +1,191 @@ +/* + * 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) + 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 + 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() { + // 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); + + // 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); + + // 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("=========================================="); + } + + /** + * 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; + } + } + +}