Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion spring-ai-alibaba-jmanus/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@

<webdrivermanager.version>6.1.0</webdrivermanager.version>

<!-- Playwright Configuration -->
<playwright.version>1.53.0</playwright.version>

<!-- CheckStyle Maven Plugin -->
<maven-checkstyle-plugin.version>3.5.0</maven-checkstyle-plugin.version>
<maven-checkstyle-plugin.failsOnError>true</maven-checkstyle-plugin.failsOnError>
Expand Down Expand Up @@ -168,7 +171,7 @@
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.52.0</version>
<version>${playwright.version}</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -57,6 +59,9 @@ public class ChromeDriverService implements IChromeDriverService {

private final ObjectMapper objectMapper;

@Autowired(required = false)
private SpringBootPlaywrightInitializer playwrightInitializer;

/**
* Shared directory for storing cookies
*/
Expand Down Expand Up @@ -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()) {
Expand All @@ -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) {
Expand All @@ -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<String> 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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}

}
Loading