Passed
Push — master ( 183777...4a21d6 )
by Yaroslav
10:56
created

ReportScreenshot::make()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 12
dl 0
loc 23
ccs 0
cts 10
cp 0
rs 9.8666
c 1
b 0
f 0
cc 2
nc 2
nop 4
crap 6
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
class ReportScreenshot implements ReportScreenshotContract
14
{
15
    protected Reporter $reporter;
16
17
    protected string $fileExt = 'png';
18
19
    /**
20
     * ReportScreenshot constructor.
21
     *
22
     * @param Reporter $reporter
23
     */
24
    public function __construct(Reporter $reporter)
25
    {
26
        $this->reporter = $reporter;
27
    }
28
29
    /**
30
     * @inheritDoc
31
     */
32
    public function make(Browser $browser, string $filename, ?string $resize = null, ?string $suffix = null): string
33
    {
34
        $realFileName = "{$filename}.{$this->fileExt}";
35
36
        if (!$this->reporter->isReportingDisabled()) {
37
            $defaultStoreScreenshotsAt = $browser::$storeScreenshotsAt;
38
39
            $browser::$storeScreenshotsAt = $this->reporter->storeScreenshotAt();
40
41
            $filename = $this->fileName($browser, $filename, $suffix);
42
43
            $realFileName = "{$filename}.{$this->fileExt}";
44
45
            match ($resize) {
46
                static::RESIZE_COMBINE => $this->reportScreenCombined($browser, $filename),
47
                static::RESIZE_FIT     => $this->reportScreenFit($browser, $filename),
48
                default                => $browser->screenshot($filename),
49
            };
50
51
            $browser::$storeScreenshotsAt = $defaultStoreScreenshotsAt;
52
        }
53
54
        return $realFileName;
55
    }
56
57
    /**
58
     * @inheritDoc
59
     */
60
    public function fitContent(Browser $browser): Browser
61
    {
62
        try {
63
            $body        = $this->getBodyElement($browser);
64
            $currentSize = $body->getSize();
65
            $browser->resize($currentSize->getWidth(), $currentSize->getHeight());
66
        } catch (Exception) {
67
            $browser->fitContent();
68
        }
69
70
        return $browser;
71
    }
72
73
    /**
74
     * Get body element
75
     *
76
     * @param Browser $browser
77
     *
78
     * @return RemoteWebElement
79
     */
80
    protected function getBodyElement(Browser $browser): RemoteWebElement
81
    {
82
        if (is_callable(Reporter::$getBodyElementCallback)) {
83
            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

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