ReportScreenshot::reportScreenCombined()   A
last analyzed

Complexity

Conditions 5
Paths 7

Size

Total Lines 47
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 33
c 2
b 0
f 0
dl 0
loc 47
ccs 0
cts 34
cp 0
rs 9.0808
cc 5
nc 7
nop 2
crap 30
1
<?php
2
3
namespace LaravelDuskReporter\Generation;
4
5
use Exception;
6
use Facebook\WebDriver\Remote\RemoteWebElement;
7
use Facebook\WebDriver\WebDriverBy;
8
use Illuminate\Support\Str;
9
use Imagick;
10
use Laravel\Dusk\Browser;
11
use LaravelDuskReporter\Reporter;
12
13
/**
14
 * @method static addActionBeforeCombineScreenshot(\Closure $callback)
15
 * @method static addActionCombineScreenshotPartForOffset(\Closure $callback)
16
 * @method static addActionAfterCombineScreenshot(\Closure $callback)
17
 */
18
class ReportScreenshot implements ReportScreenshotContract
19
{
20
    use HasHooks;
21
22
    protected Reporter $reporter;
23
24
    protected string $fileExt = 'png';
25
26
    /**
27
     * ReportScreenshot constructor.
28
     *
29
     * @param Reporter $reporter
30
     */
31
    public function __construct(Reporter $reporter)
32
    {
33
        $this->reporter = $reporter;
34
    }
35
36
    /**
37
     * @inheritDoc
38
     */
39
    public function make(Browser $browser, string $filename, ?string $resize = null, ?string $suffix = null): string
40
    {
41
        $realFileName = "{$filename}.{$this->fileExt}";
42
43
        if (!$this->reporter->isReportingDisabled()) {
44
            $defaultStoreScreenshotsAt = $browser::$storeScreenshotsAt;
45
46
            $browser::$storeScreenshotsAt = $this->reporter->storeScreenshotAt();
47
48
            $filename = $this->fileName($browser, $filename, $suffix);
49
50
            $realFileName = "{$filename}.{$this->fileExt}";
51
52
            match ($resize) {
53
                static::RESIZE_COMBINE => $this->reportScreenCombined($browser, $filename),
54
                static::RESIZE_FIT     => $this->reportScreenFit($browser, $filename),
55
                default                => $browser->screenshot($filename),
56
            };
57
58
            $browser::$storeScreenshotsAt = $defaultStoreScreenshotsAt;
59
        }
60
61
        return $realFileName;
62
    }
63
64
    /**
65
     * @inheritDoc
66
     */
67
    public function fitContent(Browser $browser): Browser
68
    {
69
        try {
70
            $body        = $this->getBodyElement($browser);
71
            $currentSize = $body->getSize();
72
            $browser->resize($currentSize->getWidth(), $currentSize->getHeight());
73
        } catch (Exception) {
74
            $browser->fitContent();
75
        }
76
77
        return $browser;
78
    }
79
80
    /**
81
     * Get body element
82
     *
83
     * @param Browser $browser
84
     *
85
     * @return RemoteWebElement
86
     */
87
    protected function getBodyElement(Browser $browser): RemoteWebElement
88
    {
89
        if (is_callable(Reporter::$getBodyElementCallback)) {
90
            return call_user_func(Reporter::$getBodyElementCallback, $browser);
0 ignored issues
show
Bug introduced by
It seems like LaravelDuskReporter\Repo...:getBodyElementCallback can also be of type null; however, parameter $callback of call_user_func() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

90
            return call_user_func(/** @scrutinizer ignore-type */ Reporter::$getBodyElementCallback, $browser);
Loading history...
91
        }
92
93
        return $browser->driver->findElement(WebDriverBy::tagName('body'));
94
    }
95
96
    /**
97
     * Create fit report.
98
     *
99
     * @param Browser $browser
100
     * @param string  $filename
101
     *
102
     * @return Browser
103
     */
104
    protected function reportScreenFit(Browser $browser, string $filename): Browser
105
    {
106
        $initialSize = $browser->driver->manage()->window()->getSize();
107
108
        return $this->fitContent($browser)->screenshot($filename)->resize($initialSize->getWidth(), $initialSize->getHeight());
109
    }
110
111
    /**
112
     * Create combined report
113
     *
114
     * @param Browser $browser
115
     * @param string  $filename
116
     *
117
     * @return Browser
118
     * @throws \ImagickException
119
     */
120
    protected function reportScreenCombined(Browser $browser, string $filename): Browser
121
    {
122
        if (!extension_loaded('imagick')) {
123
            $browser->screenshot($filename);
124
125
            return $browser;
126
        }
127
128
        $windowSize   = $browser->driver->manage()->window()->getSize();
129
        $windowHeight = $windowSize->getHeight();
130
        $body         = $this->getBodyElement($browser);
131
        $fullHeight   = $body->getSize()->getHeight();
132
        $counter      = 0;
133
        $offset       = 0;
134
        $files        = [];
135
136
        $this->runHookAction('beforeCombineScreenshot', $browser);
137
        while ($offset < $fullHeight) {
138
            $this->runHookAction('combineScreenshotPartForOffset', $browser, $offset);
139
            $browser->driver->executeScript('window.scrollTo(0, ' . $offset . ');');
140
            if ($windowHeight > ($needCapture = ($fullHeight - $offset))) {
141
                $browser->resize($windowSize->getWidth(), $needCapture);
142
                $browser->driver->executeScript('window.scrollTo(0, document.body.scrollHeight);');
143
            }
144
            $browser->screenshot($screenName = "{$filename}_tmp-{$counter}");
145
            $files[] = sprintf('%s/%s.' . $this->fileExt, rtrim($browser::$storeScreenshotsAt, '/'), $screenName);
146
            $counter++;
147
            $offset += $windowHeight;
148
        }
149
        $browser->resize($windowSize->getWidth(), $windowSize->getHeight());
150
        $browser->driver->executeScript('window.scrollTo(0, 0);');
151
        $this->runHookAction('afterCombineScreenshot', $browser);
152
153
        $im = new Imagick();
154
        foreach ($files as $file) {
155
            $im->readImage($file);
156
            unlink($file);
157
        }
158
        /* Append the images into one */
159
        $im->resetIterator();
160
        $combined = $im->appendImages(true);
161
162
        /* Output the image */
163
        $combined->setImageFormat($this->fileExt);
164
        $combined->writeImage(sprintf('%s/%s.' . $this->fileExt, rtrim($browser::$storeScreenshotsAt, '/'), $filename));
165
166
        return $browser;
167
    }
168
169
    /**
170
     * Find screenshot filename without overriding
171
     *
172
     * @param Browser     $browser
173
     * @param string      $filename
174
     * @param string|null $suffix
175
     *
176
     * @return string
177
     */
178
    protected function fileName(Browser $browser, string $filename, ?string $suffix = null): string
179
    {
180
        $newFilename = $filename . ($suffix ? "_{$suffix}" : '');
181
182
        if (file_exists(sprintf('%s/%s.' . $this->fileExt, rtrim($browser::$storeScreenshotsAt, '/'), $newFilename))) {
183
            if (is_null($suffix)) {
184
                $suffix = 1;
185
            } elseif (is_numeric($suffix)) {
186
                $suffix = $suffix + 1;
187
            } else {
188
                $suffix = $suffix . '-' . Str::random();
189
            }
190
191
            return $this->fileName($browser, $filename, (string) $suffix);
192
        }
193
194
        return $newFilename;
195
    }
196
}
197