You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

ASP.NET Core Web API集成测试中PuppeteerSharp无法调用Chromium的问题求助

ASP.NET Core Web API集成测试中PuppeteerSharp无法调用Chromium的问题求助

看起来你遇到的核心问题是测试环境和容器生产环境的Chromium路径不匹配——你的API在Docker容器里运行时,/usr/bin/chromium-browser是合法路径,但在本地Windows机器跑集成测试时,这个路径根本不存在,自然会抛出"找不到进程"的错误。

先拆解下问题根源:你在HtmlToPdfService的构造函数里硬编码了Chromium的绝对路径,而且用同步阻塞的方式初始化浏览器,这在跨环境测试时直接踩了环境不一致的坑。下面给你几个可行的解决思路,按从易到难的顺序排列:


方案1:让Puppeteer自动适配平台,移除硬编码的ExecutablePath

PuppeteerSharp自带的BrowserFetcher本来就会自动下载对应操作系统的Chromium版本,你完全没必要硬写ExecutablePath。直接修改HtmlToPdfService的构造函数:

public HtmlToPdfService()
{
    var browserFetcher = new BrowserFetcher();
    browserFetcher.DownloadAsync().GetAwaiter().GetResult();

    // 删掉硬编码的ExecutablePath,让Puppeteer自动找到对应平台的浏览器
    _browser = Puppeteer.LaunchAsync(new LaunchOptions
    {
        Headless = true,
        Args = _puppeteerArgs
    })
    .GetAwaiter()
    .GetResult();

    _pdfOptionSet = new PdfOptions
    {
        Format = PaperFormat.A4,
        PreferCSSPageSize = true,
        PrintBackground = true,
    };
}

这个方案的优势:

  • 本地Windows测试时,Puppeteer会自动下载Windows版Chromium,无需你手动找路径
  • Docker容器环境中,BrowserFetcher也会下载适配Linux的版本,或者你也可以后续通过配置指定系统自带的Chromium路径

方案2:用配置文件动态注入Chromium路径(适配多环境)

如果你的容器环境必须用系统自带的/usr/bin/chromium-browser,不想依赖Puppeteer自动下载的版本,可以把Chromium路径放到环境专属的配置文件里,通过依赖注入动态读取。

步骤1:添加多环境配置

appsettings.json(容器生产环境)中添加:

"PuppeteerSettings": {
  "ExecutablePath": "/usr/bin/chromium-browser"
}

appsettings.Development.json(本地测试环境)中添加:

"PuppeteerSettings": {
  "ExecutablePath": "" // 留空让Puppeteer自动找,也可以填本地Chrome路径,比如"C:/Program Files/Google/Chrome/Application/chrome.exe"
}

步骤2:抽象配置类

public class PuppeteerSettings
{
    public string ExecutablePath { get; set; } = string.Empty;
}

步骤3:修改服务注册逻辑

CenterHub.StartWork中添加配置绑定:

appBuilder.Services.Configure<PuppeteerSettings>(appBuilder.Configuration.GetSection("PuppeteerSettings"));
appBuilder.Services.AddSingleton<HtmlToPdfService>();

步骤4:更新HtmlToPdfService的构造函数

public class HtmlToPdfService
{
    private readonly string[] _puppeteerArgs = new[] { "--no-sandbox", "--disable-dev-shm-usage", "--disable-gpu", "--disable-setuid-sandbox", "--disable-extensions", "--disable-software-rasterizer", "--font-render-hinting=none" };
    private IBrowser _browser { get; }
    private PdfOptions _pdfOptionSet { get; }

    public HtmlToPdfService(IOptions<PuppeteerSettings> puppeteerSettings)
    {
        var browserFetcher = new BrowserFetcher();
        browserFetcher.DownloadAsync().GetAwaiter().GetResult();

        var launchOptions = new LaunchOptions
        {
            Headless = true,
            Args = _puppeteerArgs
        };

        // 只有配置里有路径时才设置,否则让Puppeteer自动适配
        if (!string.IsNullOrEmpty(puppeteerSettings.Value.ExecutablePath))
        {
            launchOptions.ExecutablePath = puppeteerSettings.Value.ExecutablePath;
        }

        _browser = Puppeteer.LaunchAsync(launchOptions)
            .GetAwaiter()
            .GetResult();

        _pdfOptionSet = new PdfOptions
        {
            Format = PaperFormat.A4,
            PreferCSSPageSize = true,
            PrintBackground = true,
        };
    }
}

这样修改后,测试环境和生产容器环境会自动读取对应配置的Chromium路径,完美解决跨环境冲突。


方案3:用TestContainers在容器内运行测试(完全模拟生产环境)

如果你的测试必须100%匹配生产环境的运行条件,可以用TestContainers.NET在测试时自动启动Docker容器,在容器内部执行测试逻辑,这样就能直接使用/usr/bin/chromium-browser路径了。

大致实现步骤:

  1. 安装NuGet包:TestContainers.Container.AbstractionsTestContainers.Dotnet(版本匹配你的.NET版本)
  2. 修改测试类,启动包含API和Chromium的容器,在容器内发起测试请求
  3. 这种方式适合对环境一致性要求极高的场景,但配置相对复杂

额外优化建议

  1. 避免在构造函数中同步阻塞异步调用GetAwaiter().GetResult()可能导致死锁,建议把浏览器初始化改成异步方式,比如实现IHostedService在应用启动时异步初始化:
public class BrowserInitializationService : IHostedService
{
    private readonly HtmlToPdfService _pdfService;

    public BrowserInitializationService(HtmlToPdfService pdfService)
    {
        _pdfService = pdfService;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        // 这里调用异步初始化方法,替代构造函数里的同步阻塞
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        // 应用停止时关闭浏览器
        return _pdfService._browser.CloseAsync();
    }
}

然后在服务注册时添加:appBuilder.Services.AddHostedService<BrowserInitializationService>();

  1. 测试时可模拟PDF服务:如果你的测试只是验证API接口逻辑,而非PDF生成细节,可以用Moq模拟HtmlToPdfService,绕开Chromium依赖:
[TestFixture]
internal class TestSpecificDocument1
{
    [Test]
    public async Task TestsRequestForDocument1()
    {
        var mockPdfService = new Mock<HtmlToPdfService>();
        // 模拟PDF生成方法,返回预设字节流
        mockPdfService.Setup(s => s.GeneratePdfAsync(It.IsAny<string>()))
                      .ReturnsAsync(new byte[0]);

        var app = new WebApplicationFactory<Program>()
            .WithWebHostBuilder(builder =>
            {
                builder.ConfigureServices(services =>
                {
                    // 替换原服务为模拟对象
                    services.Replace(ServiceDescriptor.Singleton<HtmlToPdfService>(mockPdfService.Object));
                });
            });

        var client = app.CreateClient();
        // 后续测试逻辑
    }
}

这种方式测试速度更快,也无需依赖外部环境。


总结:最推荐先尝试方案1,改动最小且能快速解决跨环境问题;如果必须保留容器环境的系统Chromium路径,就用方案2的配置注入方式;对环境一致性要求极高时再考虑方案3。

火山引擎 最新活动