Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Sinnbeck's avatar
Level 102

Browsershot incredibly slow

I just got spaties browsershot working in a docker container. I have installed chrome as instructed in the puppeteer docs https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#running-puppeteer-in-docker and running the container as an arbitrary user.

Sadly it is incredibly slow. I havent used it before, so I dont know what kind response time is expected, but just grabbing google.com and outputting the image in the browser, takes 5-6 seconds.

Is it just slow in gereral? Or has someone had success getting it to work properly inside docker?

0 likes
15 replies
rodrigo.pedra's avatar

Not sure when you say you haven’t used it before you mean on docker, or the package all together.

I can confirm it is not that slow, but I never used it in a docker container.

Not in my office right now, but I can send an average runtime outside of docker when I am back (in about 2 hours) for comparison if you want.

But slow enough to me deciding defering a PDF generation to a queued job.

For reference, I used to generate a PDF from a Inertia/Vue route. So it needed to await for JavaScript and Vue to load before render.

Sinnbeck's avatar
Level 102

I mean I have never used it at all.

But i am trying to both get it working for work, and for my laravel package (laravel-served)

@rodrigo.pedra Thank you so much. Looking forwards to the results :)

rodrigo.pedra's avatar

So I finally got back to my office and found the project I told you about.

As I said, I use Browsershot to create a PDF from a route that renders a Inertia/Vue page. So it has to wait for JavaScript boot time.

The simplified script to test in isolation I wrote is this:

<?php

use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Config;
use Spatie\Browsershot\Browsershot;

Artisan::command('app:pdf', function () {
    // model I need to render the page
    $estimate = \App\Models\Estimate::query()->find(1);

    // As the route has sensitive data, I have a simple
    // token guard on it, to prevent unauthorized access
    $token = Config::get('services.pdf.token');

    $url = route('estimates.pdf', ['estimate' => $estimate->getRouteKey(), 'api_token' => $token]);

    foreach (range(1, 30) as $index) {
        $filename = storage_path("app/test-{$index}.pdf");

        $start = microtime(true);

        Browsershot::url($url)
            ->paperSize(210, 297)
            ->setOption('margin', 'none')
            ->setOption('preferCSSPageSize', true)
            ->setOption('displayHeaderFooter', false)
            ->showBackground()
            ->savePdf($filename);

        $renderTime = microtime(true) - $start;

        $this->info(str_pad($index, 2) . ': ' . number_format($renderTime, 3) . ' seconds');
    }
});

And the results are this:

$ php artisan app:pdf
1 : 0.883 seconds
2 : 0.864 seconds
3 : 0.846 seconds
4 : 0.944 seconds
5 : 0.882 seconds
6 : 0.864 seconds
7 : 0.860 seconds
8 : 0.948 seconds
9 : 0.849 seconds
10: 0.916 seconds
11: 0.870 seconds
12: 0.936 seconds
13: 0.852 seconds
14: 0.916 seconds
15: 0.856 seconds
16: 0.933 seconds
17: 0.860 seconds
18: 0.878 seconds
19: 0.856 seconds
20: 0.968 seconds
21: 0.857 seconds
22: 0.848 seconds
23: 0.855 seconds
24: 0.927 seconds
25: 0.921 seconds
26: 0.848 seconds
27: 0.899 seconds
28: 0.933 seconds
29: 0.868 seconds
30: 0.856 seconds

As you can see, all runs were less than a second.

As this project is a bit old (Laravel 6.0, last updated in 2019), here are the relevant info about package versions:

  • composer.json
        "inertiajs/inertia-laravel": "^0.1.0",
        "spatie/browsershot": "^3.32",
  • package.json
        "@inertiajs/inertia": "^0.1.3",
        "@inertiajs/inertia-vue": "^0.1.1",
        "bootstrap": "^4.3",
        "laravel-mix": "^4.0.7",
        "node-sass": "^4.11.0",
        "puppeteer": "^1.19.0",
        "vue": "^2.6",
        "vue-svg-loader": "^0.12.0",
        "vue-template-compiler": "^2.6.8",

For reference I wrote this other command to output the generated HTML:


Artisan::command('app:html', function () {
    // model I need to render the page
    $estimate = \App\Models\Estimate::query()->find(1);

    // As the route has sensitive data, I have a simple
    // token guard on it, to prevent unauthorized access
    $token = Config::get('services.pdf.token');

    $url = route('estimates.pdf', ['estimate' => $estimate->getRouteKey(), 'api_token' => $token]);

    $content = Browsershot::url($url)->bodyHtml();

    $this->info($content);
});

And this was the generated HTML

<!DOCTYPE html><html lang="pt-BR"><head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, shrink-to-fit=no">

    <meta name="format-detection" content="telephone=no">
    <meta name="format-detection" content="date=no">
    <meta name="format-detection" content="address=no">
    <meta name="format-detection" content="email=no">

    <meta http-equiv="Cache-Control" content="max-age=0">
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Expires" content="0">
    <meta http-equiv="Expires" content="Tue, 01 Jan 1980 1:00:00 GMT">

    <meta name="robots" content="none,noarchive">
    <meta name="googlebot" content="noarchive">

    <meta name="csrf-token" content="f6l4iuXdGOiCQcv9KbSMlCzRdY36qQHTaZ4SoubE">

    <title>Project</title>

    <link rel="shortcut icon" href="http://example.test/favicon.ico" type="image/x-icon">
    <link rel="icon" href="http://example.test/favicon.ico" type="image/x-icon">

    <link rel="stylesheet" href="http://example.test/css/app.css?id=c23285a690bb9f55ad79">

    <script src="http://example.test/js/manifest.js?id=7db827d654313dce4250"></script>
    <script src="http://example.test/js/vendor.js?id=6c59242688a21c4c0ce1"></script>
    <script src="http://example.test/js/common.js?id=e48577af589747383b23"></script>


        <link rel="stylesheet" href="http://example.test/css/print.css?id=54fe4662a6cc2e457f76">
    <script src="http://example.test/js/pdf.js?id=b13e2a101e69c1bc702a"></script><style type="text/css">.modal-dialog > .modal-content[data-v-189bddbf] {
  border-radius: 2px;
  box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.5);
  background-color: white;
  color: var(--app-color);
}
.modal-dialog > .modal-content.\--dark[data-v-189bddbf] {
  background-color: var(--app-color);
  color: var(--body-bg-color);
}
.modal-dialog > .modal-content.\--dark > .modal-body > .close svg[data-v-189bddbf] {
  fill: var(--body-bg-color);
}
.modal-dialog > .modal-content.\--dark > .modal-body > .close[data-v-189bddbf]:hover {
  background-color: #414d59;
}
.modal-dialog > .modal-content > .modal-body > .close[data-v-189bddbf] {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  opacity: 1 !important;
}
.modal-dialog > .modal-content > .modal-body > .close svg[data-v-189bddbf] {
  width: 18px;
  height: 18px;
  color: var(--text-color);
}
.modal-dialog > .modal-content > .modal-body > .close[data-v-189bddbf]:hover {
  background-color: var(--light-gray);
}
</style><style type="text/css">.confirm-box[data-v-3a98e5b6] {
  text-align: center;
  padding: 14px 19px;
}
.confirm-box > .confirm-box-icon[data-v-3a98e5b6] {
  display: block;
  margin: 0 auto 25px auto;
}
.confirm-box > .confirm-box-title[data-v-3a98e5b6] {
  font-size: 16px;
  font-weight: 600;
  line-height: 1.69;
  letter-spacing: 0.5px;
  text-align: center;
  color: var(--app-color);
  margin: 0 15px;
}
.confirm-box > .alert-box-content[data-v-3a98e5b6] {
  font-size: 15px;
  line-height: 1.33;
  letter-spacing: 0.4px;
  text-align: center;
  color: var(--app-color);
  margin: 0 15px;
}
.confirm-box > .confirm-box-buttons[data-v-3a98e5b6] {
  margin-top: 40px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  text-align: center;
}
.confirm-box > .confirm-box-buttons > .btn-app[data-v-3a98e5b6] {
  width: 130px;
}
</style><style type="text/css">.cart-tag-marker {
  display: block;
  width: 23px;
  height: 23px;
  border-radius: 50%;
  text-align: center;
  vertical-align: middle;
  font-size: 15px;
  letter-spacing: 0.3px;
  line-height: 25px;
  color: white;
  background-color: #4079ab;
  cursor: inherit;
}
.cart-tag-marker.\--has-border {
  width: 26px;
  height: 26px;
  padding-left: 1px;
  border: solid 2px white;
}
.cart-tag-marker.\--has-error {
  background-color: #c83226;
}
</style><style type="text/css">.radio-checkbox {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 22px;
  border: solid 2px transparent;
  border-radius: 50%;
  background-color: var(--light-gray);
  box-shadow: 0 0 0 1px var(--medium-gray);
  cursor: pointer;
}
.radio-checkbox.\--spin {
  border-top-color: var(--heavy-gray);
}
.radio-checkbox[disabled] {
  opacity: 0.6;
  cursor: not-allowed;
}
.radio-checkbox .radio-checkbox-toggle {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background-color: var(--text-color);
}
</style><style type="text/css">.cart-details-item-description {
  width: 314px;
  height: 174px;
  padding: 15px;
  border-radius: 3px;
  box-shadow: var(--light-shadow);
  background-color: var(--body-bg-color);
}
.cart-details-item-description > header {
  margin-bottom: 10px;
  color: var(--app-color);
}
.cart-details-item-description > header > h3 {
  font-size: 16px;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: 0.5px;
  margin-bottom: 0;
}
.cart-details-item-description > header > p {
  font-size: 15px;
  font-weight: 400;
  letter-spacing: 0.5px;
  margin: 0;
}
.cart-details-item-description > .cart-details-item-description-content {
  max-height: 66px;
  overflow: hidden;
  font-size: 15px;
  line-height: 1.47;
  letter-spacing: 0.5px;
  color: var(--dark-gray);
}
</style><style type="text/css">.cart-details-items[data-v-3a181d20] {
  width: 100%;
  border-collapse: collapse;
  border-spacing: 0;
}
.cart-details-items tbody > tr[data-v-3a181d20] {
  border-bottom: solid 1px rgba(49, 58, 67, 0.3);
  height: 97px;
}
.cart-details-items thead > tr > th.cart-details-items-cell[data-v-3a181d20] {
  font-size: 17px;
  letter-spacing: 0.8px;
  color: var(--text-color);
}
.cart-details-items[data-v-3a181d20] .cart-details-items-cell {
  padding: 16px 12px;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 0.4px;
  color: var(--text-color);
}
.cart-details-items[data-v-3a181d20] .cart-details-items-cell > strong {
  display: block;
  max-width: 350px;
}
.cart-details-items[data-v-3a181d20] .cart-details-items-cell > small {
  font-size: inherit;
  font-weight: 400;
}
.cart-details-items[data-v-3a181d20] .cart-details-items-cell:first-child {
  padding-left: 0;
}
.cart-details-items[data-v-3a181d20] .cart-details-items-cell:last-child {
  padding-right: 0;
}
.cart-details-items[data-v-3a181d20] .cart-details-items-cell .cart-details-item-image {
  position: relative;
  display: inline-block;
  width: 56px;
  height: 56px;
  overflow: hidden;
  background-color: #eaeaea;
  background-size: 90%;
}
.cart-details-items[data-v-3a181d20] .cart-details-items-cell .cart-details-item-image > img {
  display: block;
  max-width: 100%;
  height: 100%;
  margin: 0 auto;
  color: transparent;
}
.cart-details-items[data-v-3a181d20] .cart-details-items-cell.\--poster {
  width: 100px;
  text-align: left;
}
.cart-details-items[data-v-3a181d20] .cart-details-items-cell.\--poster .cart-details-item-poster {
  position: relative;
  width: 56px;
  height: 56px;
  margin: 5px 0;
}
.cart-details-items[data-v-3a181d20] .cart-details-items-cell.\--poster .cart-details-item-poster .cart-details-item-tag {
  position: absolute;
  right: 0;
  bottom: 0;
  transform: translate(30%, 30%);
}
.cart-details-items[data-v-3a181d20] .cart-details-items-cell.\--details {
  position: relative;
}
.cart-details-items[data-v-3a181d20] .cart-details-items-cell.\--details .cart-details-item-description {
  position: absolute;
  top: 85%;
  left: 15px;
  z-index: 10;
}
.cart-details-items[data-v-3a181d20] .cart-details-items-cell.\--credit {
  width: 111px;
  text-align: center;
}
.cart-details-items[data-v-3a181d20] .cart-details-items-cell.\--reference {
  width: 111px;
  text-align: center;
}
.cart-details-items[data-v-3a181d20] .cart-details-items-cell.\--total {
  width: 160px;
  text-align: right;
}
.cart-details-items[data-v-3a181d20] .cart-details-items-cell.\--action {
  width: 50px;
  text-align: center;
}
.cart-details-items[data-v-3a181d20] .cart-details-items-cell.\--action .remove-button {
  position: relative;
  bottom: 4px;
}
.cart-details-items[data-v-3a181d20] .cart-details-items-cell.\--action .remove-button svg {
  width: 26px;
  height: 26px;
  fill: currentColor;
}
.cart-details-items[data-v-3a181d20] .cart-details-items-cell.\--action .credit-button.\--is-active svg {
  fill: #404040;
}
.cart-details-items[data-v-3a181d20] .cart-details-items-cell.\--action .credit-button svg {
  transition: linear all 100ms;
  fill: rgba(68, 81, 99, 0.5);
  width: 22px;
  height: 23px;
}
</style><style type="text/css">.tag-marker[data-v-b7295bca] {
  position: absolute;
}
.tag-marker .tag-marker-toggle[data-v-b7295bca] {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 25px;
  height: 25px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  transition: ease-in-out all 150ms;
  color: var(--body-bg-color);
  border: solid 2px var(--body-bg-color);
  box-shadow: 0 0 10px 0 rgba(49, 58, 67, 0.5);
  background-color: var(--indicator-color);
  background-image: linear-gradient(to bottom, var(--indicator-color), var(--dark-indicator-color));
}
.tag-marker .tag-marker-toggle.\--clickable[data-v-b7295bca] {
  cursor: pointer;
}
.tag-marker .tag-marker-toggle.\--clickable[data-v-b7295bca]:hover {
  background-image: linear-gradient(to bottom, rgba(248, 249, 250, 0.3), rgba(248, 249, 250, 0.3)), linear-gradient(to bottom, var(--indicator-color), var(--dark-indicator-color));
}
.tag-marker .tag-marker-toggle.\--is-active[data-v-b7295bca] {
  width: 35px;
  height: 35px;
  box-shadow: 0 0 20px 0 var(--app-color);
  border: solid 3px var(--body-bg-color);
  background-image: linear-gradient(to bottom, var(--indicator-color), var(--dark-indicator-color));
}
.tag-marker .tag-marker-toggle .tag-marker-label[data-v-b7295bca] {
  display: block;
  height: 16px;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.1875;
  text-align: center;
  color: var(--light-gray);
}
.tag-marker .tag-marker-toggle[data-v-b7295bca] svg {
  display: block;
  width: 20px;
  height: 20px;
  fill: var(--light-gray);
}
.tag-marker.\--has-error .tag-marker-toggle[data-v-b7295bca], .tag-marker.\--standard.\--has-error .tag-marker-toggle[data-v-b7295bca], .tag-marker.\--flat.\--has-error .tag-marker-toggle[data-v-b7295bca], .tag-marker.\--dark.\--has-error .tag-marker-toggle[data-v-b7295bca] {
  background-color: #c83226;
}
.tag-marker.\--flat .tag-marker-toggle[data-v-b7295bca] {
  width: 26px;
  height: 26px;
  background-color: #4079ab;
  background-image: none;
  box-shadow: none;
}
.tag-marker.\--flat .tag-marker-toggle[data-v-b7295bca] svg {
  position: relative;
  left: -1px;
}
.tag-marker.\--dark > .tag-marker-toggle[data-v-b7295bca] {
  width: 35px;
  height: 35px;
  color: var(--light-gray);
  box-shadow: 0 2px 5px 0 var(--app-color);
  border: solid 2px var(--body-bg-color);
  background-color: var(--text-color);
  background-image: none;
}
.tag-marker.\--dark > .tag-marker-toggle[data-v-b7295bca]:hover {
  box-shadow: 0 2px 10px 0 var(--app-color);
  border: solid 2px var(--body-bg-color);
  background-color: var(--text-color);
  background-image: linear-gradient(to bottom, rgba(248, 249, 250, 0.3), rgba(248, 249, 250, 0.3));
}
.tag-marker.\--dark > .tag-marker-toggle.\--is-active[data-v-b7295bca] {
  box-shadow: 0 2px 5px 0 var(--app-color);
  border: solid 3px var(--body-bg-color);
  background-color: var(--text-color);
  background-image: none;
}
.tag-marker.\--standard > .tag-marker-toggle[data-v-b7295bca] {
  width: 35px;
  height: 35px;
  color: var(--light-gray);
  box-shadow: 0 2px 5px 0 var(--app-color);
  border: solid 2px var(--body-bg-color);
  background-color: #29bb9c;
  background-image: none;
}
.tag-marker.\--standard > .tag-marker-toggle[data-v-b7295bca]:hover {
  box-shadow: 0 2px 10px 0 var(--app-color);
  border: solid 2px var(--body-bg-color);
  background-color: #29bb9c;
  background-image: linear-gradient(to bottom, rgba(248, 249, 250, 0.3), rgba(248, 249, 250, 0.3));
}
.tag-marker.\--standard > .tag-marker-toggle.\--is-active[data-v-b7295bca] {
  box-shadow: 0 2px 5px 0 var(--app-color);
  border: solid 3px var(--body-bg-color);
  background-color: #29bb9c;
  background-image: none;
}
</style><style type="text/css">.cart-details-tags[data-v-523b7d10] {
  position: relative;
  width: 100%;
}
.cart-details-tags > img[data-v-523b7d10] {
  display: block;
  margin: 0 auto;
  max-width: 100%;
  max-height: 523px;
  color: transparent;
}
</style><style type="text/css">.cart-details-room[data-v-18c0745a] {
  margin-bottom: 60px;
}
.cart-details-room[data-v-18c0745a] .cart-details-tags {
  margin-bottom: 50px;
}
.cart-details-room > h2[data-v-18c0745a] {
  margin: 0 0 20px 0;
  font-size: 30px;
  letter-spacing: 0.6px;
  color: var(--app-color);
}
.cart-details-room > h3[data-v-18c0745a] {
  margin: 30px 0 10px 0;
  font-size: 25px;
  letter-spacing: 0.6px;
  color: var(--app-color);
}
.cart-details-room > h4[data-v-18c0745a] {
  margin: 50px 0 10px 0;
  font-size: 18px;
  letter-spacing: 0.6px;
  color: var(--app-color);
}
.cart-details-room > .cart-details-room-subtotal[data-v-18c0745a] {
  margin: 16px 0;
  padding-right: 62px;
  font-size: 18px;
  font-weight: 600;
  letter-spacing: 0.5px;
  text-align: right;
  color: #404040;
}
</style><style type="text/css">@page {
  size: A4 portrait;
  margin: 0;
}
body {
  margin: 0 !important;
  padding: 0 !important;
}
</style><style type="text/css">#estimate-pdf-page[data-v-caace272] {
  width: 995px;
  border-collapse: collapse;
}
#estimate-pdf-page tr[data-v-caace272], #estimate-pdf-page td[data-v-caace272], #estimate-pdf-page th[data-v-caace272], #estimate-pdf-page thead[data-v-caace272], #estimate-pdf-page tbody[data-v-caace272] {
  border: none;
}
#estimate-pdf-page .content[data-v-caace272] {
  margin: 0 !important;
  padding: 1px 40px;
  page-break-inside: avoid;
}
#estimate-pdf-page thead tr:first-of-type td > div[data-v-caace272] {
  display: flex;
  align-items: center;
  padding: 20px 40px;
  background-color: var(--app-color);
  color: white;
}
#estimate-pdf-page thead tr:first-of-type td > div #logo[data-v-caace272] {
  flex: 0 0 112px;
  height: 30px;
  margin-right: 40px;
}
#estimate-pdf-page thead tr:first-of-type td > div #title[data-v-caace272] {
  flex: 1 1 auto;
  text-align: center;
  line-height: 1.1;
}
#estimate-pdf-page thead tr:last-of-type td > div[data-v-caace272] {
  display: flex;
  align-items: center;
  padding: 12px 40px;
  background-color: #e4e6f2;
  color: var(--app-color);
}
#estimate-pdf-page thead tr:last-of-type td > div[data-v-caace272] > :first-child {
  flex: 0 0 40%;
  margin-right: 40px;
  line-height: 1.2;
}
#estimate-pdf-page thead tr:last-of-type td > div[data-v-caace272] > :last-child {
  flex: 1 1 auto;
  line-height: 1.2;
}
#estimate-pdf-page tbody .body[data-v-caace272] .cart-details-room .cart-details-room-subtotal {
  padding-right: 0;
}
#estimate-pdf-page tbody .body[data-v-caace272] .cart-details-room .cart-details-items-cell.\--action {
  display: none;
}
#estimate-pdf-page tbody .body[data-v-caace272] .cart-details-tags img {
  max-height: 250px;
}
#estimate-pdf-page tbody #footer[data-v-caace272] {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
}
#estimate-pdf-page tbody #footer > div[data-v-caace272] {
  flex: 0 0 calc(50% - 20px);
  width: calc(50% - 20px);
  padding: 20px;
  margin: 10px;
}
#estimate-pdf-page tbody #footer > div.signatures[data-v-caace272] {
  margin-top: 80px;
  border-top: solid 1px var(--app-color);
  text-align: center;
}
#estimate-pdf-page tbody #footer #comments > .comments-content[data-v-caace272] {
  border: solid 1px var(--app-color);
  padding: 12px;
}
</style>

    <script src="http://example.test/js/app.js?id=dda4650ac75687d24146"></script>

    </head>
<body>

<table data-v-caace272="" id="estimate-pdf-page"><thead data-v-caace272=""><tr data-v-caace272=""><td data-v-caace272=""><div data-v-caace272="" class="content"><img data-v-caace272="" id="logo" src="data:image/png;base64,redacted==" alt=""> <div data-v-caace272="" id="title">
                        ANEXO I - Orçamento
                        Building Name
                    </div></div></td></tr> <tr data-v-caace272=""><td data-v-caace272=""><div data-v-caace272="" class="content"><div data-v-caace272=""><strong data-v-caace272="">Orçamento:</strong>
                        000000000 <br data-v-caace272=""> <strong data-v-caace272="">Unidade:</strong>
                        00000 <br data-v-caace272=""></div> <div data-v-caace272=""><strong data-v-caace272="">Data:</strong>
                        10/12/19 <br data-v-caace272=""> <strong data-v-caace272="">Cliente:</strong>
                        CUSTOMER NAME <br data-v-caace272=""></div></div></td></tr></thead> <tbody data-v-caace272=""><tr data-v-caace272=""><td data-v-caace272=""><div data-v-caace272="" class="body content"><section data-v-18c0745a="" data-v-caace272="" class="cart-details-room"><h3 data-v-18c0745a="">Lavabo</h3> <div data-v-523b7d10="" data-v-18c0745a="" class="cart-details-tags"><img data-v-523b7d10="" alt="Lavabo" class="no-select"> <div data-v-b7295bca="" data-v-523b7d10="" class="tag-marker no-select --flat" style="left: 0px; top: 0px;"><span data-v-b7295bca="" draggable="true" class="tag-marker-toggle"><span data-v-b7295bca="" class="tag-marker-label">1</span></span> </div></div> <table data-v-3a181d20="" data-v-18c0745a="" class="cart-details-items"><thead data-v-3a181d20=""><tr data-v-3a181d20=""><th data-v-3a181d20="" class="cart-details-items-cell --poster">
                &nbsp;
            </th> <th data-v-3a181d20="" class="cart-details-items-cell --details">
                &nbsp;
            </th> <th data-v-3a181d20="" class="cart-details-items-cell --reference">
                Referência
            </th> <th data-v-3a181d20="" class="cart-details-items-cell --total">
                Preço
            </th> <th data-v-3a181d20="" class="cart-details-items-cell --action"><button data-v-3a181d20="" type="button" class="credit-button reset-button"><svg data-v-3a181d20="" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" class=""><path data-v-3a181d20="" d="M9.9758 3.6105c-4.4935 0-8.3161 2.7544-9.8875 6.6653 1.5714 3.9108 5.394 6.6652 9.8875 6.6652s8.316-2.7544 9.8875-6.6652c-1.5714-3.9109-5.394-6.6653-9.8875-6.6653zm0 11.1102c-2.4719 0-4.4935-1.9995-4.4935-4.445s2.0216-4.4449 4.4935-4.4449 4.4935 1.9996 4.4935 4.445-2.0216 4.445-4.4935 4.445zm0-7.111c-1.4831 0-2.697 1.2006-2.697 2.666s1.2139 2.6662 2.697 2.6662 2.697-1.2007 2.697-2.6661-1.2139-2.6661-2.697-2.6661z"></path></svg></button></th></tr></thead> <tbody data-v-3a181d20=""><tr data-v-3a181d20=""><td class="cart-details-items-cell --poster"><div class="cart-details-item-poster"><div class="cart-details-item-image placeholder-image"><!----></div> <span class="cart-tag-marker no-select cart-details-item-tag">1</span></div></td> <td class="cart-details-items-cell --details"><strong title="RALO LINEAR T2" class="text-truncate">RALO LINEAR T2</strong> <button type="button" class="btn-app btn-app-link d-print-none">
            Ver descrição
        </button> <!----></td> <td class="cart-details-items-cell --reference"><div class="cart-details-item-image placeholder-image"><!----></div></td> <td class="cart-details-items-cell --total">
        R$ 16,84
    </td> <td class="cart-details-items-cell --action"><button type="button" class="remove-button reset-button"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" class=""><path d="M20 3.6768a.739.739 0 0 1-.2453.5495.8074.8074 0 0 1-.579.2358h-.4452v13.9684c0 .4337-.1558.8032-.4673 1.1095a1.5337 1.5337 0 0 1-1.1137.46H3.0737c-.4316 0-.8021-.1537-1.1137-.46a1.499 1.499 0 0 1-.4674-1.1095V4.4621H.8021a.7705.7705 0 0 1-.5684-.2358.7684.7684 0 0 1-.2337-.56.7684.7684 0 0 1 .2337-.561.7705.7705 0 0 1 .5684-.2358h4.12v-1.3c0-.4337.1526-.8032.4568-1.1095A1.5232 1.5232 0 0 1 6.5032 0h6.9936c.4306 0 .8021.1537 1.1137.46.3116.3053.4674.6758.4674 1.1095v1.3h4.0979c.2379 0 .4347.079.5895.2358A.779.779 0 0 1 20 3.6768zM6.481 1.5926v1.2769h7.0158v-1.278H6.4811zm10.6685 16.838V4.462H3.0737v13.9684h14.0758zM10.5347 7.3988c.1632.1495.2453.3369.2453.5611v7.4884a.781.781 0 0 1-.2347.5716.7579.7579 0 0 1-.5569.2358.7579.7579 0 0 1-.5568-.2358.781.781 0 0 1-.2337-.5716V7.96c0-.2242.0779-.4147.2337-.5726a.7579.7579 0 0 1 .5579-.2348c.2147 0 .3968.0821.5452.2463zm-4.321 0c.1631.1495.2452.3369.2452.5611v7.4884a.781.781 0 0 1-.2336.5716.7705.7705 0 0 1-.5685.2358.7705.7705 0 0 1-.5684-.2358.781.781 0 0 1-.2337-.5716V7.96c0-.2242.079-.4147.2348-.5726a.7705.7705 0 0 1 .5684-.2348c.221 0 .4074.0821.5558.2463zm8.619 0c.163.1495.2452.3369.2452.5611v7.4884a.781.781 0 0 1-.2337.5716.7705.7705 0 0 1-.5684.2358.7705.7705 0 0 1-.5684-.2358.781.781 0 0 1-.2327-.5716V7.96c0-.2242.078-.4147.2337-.5726a.7705.7705 0 0 1 .5684-.2348c.221 0 .4074.0821.5558.2463z"></path></svg></button></td></tr></tbody></table> <!----> <p data-v-18c0745a="" class="cart-details-room-subtotal">
        Subtotal Lavabo (um item) : R$ 16,84
    </p></section></div></td></tr></tbody> <tbody data-v-caace272=""><tr data-v-caace272=""><td data-v-caace272=""><div data-v-caace272="" id="footer" class="content"><div data-v-caace272="" id="comments" class=""><strong data-v-caace272="">Comentários</strong> <div data-v-caace272="" class="comments-content small">Teste</div></div> <div data-v-caace272=""><strong data-v-caace272="">Resumo Orçamentário</strong> <table data-v-caace272="" class="w-100"><tbody data-v-caace272=""><tr data-v-caace272=""><td data-v-caace272="">Valor:</td> <th data-v-caace272="" class="text-right">
                                        R$ 16,84
                                    </th></tr> <tr data-v-caace272=""><td data-v-caace272="">Desconto:</td> <th data-v-caace272="" class="text-right">
                                        R$ 0,00
                                    </th></tr> <tr data-v-caace272=""><td data-v-caace272="">Valor do orçamento:</td> <th data-v-caace272="" class="text-right">
                                        R$ 16,84
                                    </th></tr></tbody></table></div> <div data-v-caace272="" class="signatures">
                        CUSTOMER NAME
                    </div> <div data-v-caace272="" class="signatures">
                        Building Name
                    </div> <div data-v-caace272="" class="signatures">
                        Testemunha 1
                    </div> <div data-v-caace272="" class="signatures">
                        Testemunha 2
                    </div></div></td></tr></tbody></table>


<script>
+(function (window, undefined) {
    'use strict';

    var init = function () {
        toastr.options.progressBar = true;
        toastr.options.newestOnTop = true;
        toastr.options.closeButton = true;
        toastr.options.positionClass = 'toast-bottom-right';
        toastr.options.timeOut = 3500;
        toastr.options.extendedTimeOut = 3500;






                };

    window.document.addEventListener('DOMContentLoaded', init);
}(window));
</script>



</body></html>

You can see it has the scoped CSS data- attributes generated by Vue.

Let me know if you need any additional info.

Hope it helps.

1 like
rodrigo.pedra's avatar

One more thing: here are my local node and npm versions:

$ node -v
v12.18.3

$ npm -v
6.14.6

As you are packaging in a docker container, this might be useful.

Sinnbeck's avatar
Level 102

@rodrigo.pedra Thank you so much. I will use this to do some more benchmarking. Sadly I am quite sure that they will all be waaay to slow. My main suspect at the moment, is that it needs to start the browser completely before taking the snapshot, leading to the long response time.

Edit: Testing with rendering google.com

1 : 3.118 seconds
2 : 1.729 seconds
3 : 1.680 seconds
4 : 1.739 seconds
5 : 1.687 seconds
6 : 1.764 seconds
7 : 1.674 seconds
8 : 1.778 seconds
9 : 1.716 seconds
10: 1.711 seconds

And the local laravel welcome/splash page

1 : 2.742 seconds
2 : 1.303 seconds
3 : 1.167 seconds
4 : 1.310 seconds
5 : 1.396 seconds
6 : 1.333 seconds
7 : 1.164 seconds
8 : 1.359 seconds
9 : 1.116 seconds
10: 1.163 seconds

I hope someone else have gotten this working on docker, and can give me some ideas on what to change :)

1 like
Sinnbeck's avatar
Level 102

So I just tried creating a brand new laravel project, and running it all on the linux cli (no docker).. The results were the same 🤯

I am using

"laravel/framework": "^8.12",
"spatie/browsershot": "^3.40"
"dependencies": {
        "puppeteer": "^5.4.1"
    }

Node: v14.15.0 Npm: 6.14.8

rodrigo.pedra's avatar

One thing that just crossed my mind:

I tested with a local URL, so my "benchmark" had:

  • no network latency
  • no SSL negotiation
  • no redirects were made (as Google would redirect me to the local Brazilian Google)

So I changed my tests to take a URL argument:

Artisan::command('app:pdf {url}', function () {
    $url = $this->argument('url');

    $this->info('URL: ' . $url);

    foreach (range(1, 30) as $index) {
        $filename = storage_path("app/test-{$index}.pdf");

        $start = microtime(true);

        Browsershot::url($url)
            ->paperSize(210, 297)
            ->setOption('margin', 'none')
            ->setOption('preferCSSPageSize', true)
            ->setOption('displayHeaderFooter', false)
            ->showBackground()
            ->savePdf($filename);

        $renderTime = microtime(true) - $start;

        $this->info(str_pad($index, 2) . ': ' . number_format($renderTime, 3) . ' seconds');
    }
});

And ran this test:

$ php artisan app:pdf https://www.google.com
URL: https://www.google.com
1 : 1.588 seconds
2 : 1.418 seconds
3 : 0.978 seconds
4 : 0.953 seconds
5 : 1.034 seconds
6 : 1.367 seconds
7 : 0.972 seconds
8 : 1.056 seconds
9 : 0.997 seconds
10: 0.994 seconds
11: 1.321 seconds
12: 1.405 seconds
13: 1.185 seconds
14: 1.497 seconds
15: 1.795 seconds
16: 3.383 seconds
17: 1.979 seconds
18: 1.803 seconds
19: 1.741 seconds
20: 1.651 seconds
21: 1.753 seconds
22: 1.603 seconds
23: 1.109 seconds
24: 1.583 seconds
25: 4.050 seconds
26: 0.996 seconds
27: 1.604 seconds
28: 1.057 seconds
29: 1.035 seconds
30: 1.429 seconds

As you can time elapsed is higher than using a local project route, but not that much higher in average.

I don't know if fetching an external URL from a docker container could have additional overhead.

Maybe that hints to some direction you can look for (host/guest network configuration).

But as I don't use docker much, I can't think on anything else.

I see you got similar results on you machine with and without docker. On that matter I got a brand new computer recently (core i9-9900, with NVME storage), so maybe that explains my times being a bit lower than yours.

rodrigo.pedra's avatar
Level 56

One thing that just crossed my mind : What if we keep chrome headless running? So puppeteer doesn't have to spin up a new chrome instance on every run.

People seem to be trying newing up chrome as root with --no-sandbox. But doing that does not sound safe, and some people said it wasn't working properly.

So I found this stackoverflow answer:

https://stackoverflow.com/a/53975412

Which recommends changing chrome seccomp configuration.

It links to docker site to explain what a seccomp is and how it works. But it also has a link to a seccomp file for running chrome properly on a docker image.

This seccomp file is from Jess Frazelle's dotfile repo:

https://github.com/jessfraz/dotfiles/blob/master/etc/docker/seccomp/chrome.json

Jess is a well-know consultant (check out her twitter). So I would at least consider taking a look into this file.

Not sure if all this would help you lowering those numbers, as I said I am not a docker expert, not even a regular user. But it sounded something worth sharing.

Good luck!

Sinnbeck's avatar
Level 102

@rodrigo.pedra I cannot get it to start chrome from container start, as it complains about Unable to open X display. This seems to be only fixable by setting the privileged flag (which I am unsure if a good idea).

Now that post was a nice find! After loading that chrome.json file, it actually works without the privileged flag (just running headless when needed). It now runs a screenshot of google.com in around 1.5-2 seconds (the first screenshot is always the slowest), but I can live with that for now. Maybe some docker/puppeteer genious will make a PR later on.

Thank you again for all you help! You are awesome!

1 like
rodrigo.pedra's avatar

You're welcome!

Didn't have the chance to test your laravel/served project yet, but from what I read in its readme I will sure use it in future projects.

Glad those findings helped anyhow. Have a nice day =)

Sinnbeck's avatar
Level 102

@rodrigo.pedra Hope you find it useful :) Be aware that it currently works best on linux/windows. Mac is still really slow when running docker. I have a workaround in the works, but it isnt very pretty (still hoping that docker/apple can find a proper solution together)

1 like
rodrigo.pedra's avatar

No worries then, I am on the Linux side of the Force for some years now.

But thanks for the heads up!

1 like
Loots's avatar

I'm trying to get Browsershot working in a docker container but at the moment it's not working. How did you manage to get it working @sinnbeck ? I'm using laravel sail.

I thought I had to create another dockerfile with the instructions you provided in your post (https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#running-puppeteer-in-docker). And then let laravel.test depend on this container while setting the port to 9222 (default port that Browsershot uses).

Please or to participate in this conversation.