Passed
Pull Request — 1.x (#74)
by Kevin
02:19
created

PantherBrowser::useParameters()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Zenstruck\Browser;
4
5
use Symfony\Component\BrowserKit\CookieJar;
6
use Symfony\Component\Filesystem\Filesystem;
7
use Symfony\Component\Panther\Client;
8
use Symfony\Component\VarDumper\VarDumper;
9
use Zenstruck\Assert;
10
use Zenstruck\Browser;
11
use Zenstruck\Browser\Mink\PantherDriver;
12
use Zenstruck\Browser\Response\PantherResponse;
13
use Zenstruck\Callback\Parameter;
14
15
/**
16
 * @author Kevin Bond <[email protected]>
17
 *
18
 * @experimental in 1.0
19
 */
20
class PantherBrowser extends Browser
21
{
22
    private Client $client;
23
    private ?string $screenshotDir = null;
24
    private ?string $consoleLogDir = null;
25
26
    /** @var string[] */
27
    private array $savedScreenshots = [];
28
29
    /** @var string[] */
30
    private array $savedConsoleLogs = [];
31
32
    final public function __construct(Client $client)
33
    {
34
        parent::__construct(new PantherDriver($this->client = $client));
35
    }
36
37
    final public function client(): Client
38
    {
39
        return $this->client;
40
    }
41
42
    final public function setScreenshotDir(string $dir): self
43
    {
44
        $this->screenshotDir = $dir;
45
46
        return $this;
47
    }
48
49
    final public function setConsoleLogDir(string $dir): self
50
    {
51
        $this->consoleLogDir = $dir;
52
53
        return $this;
54
    }
55
56
    /**
57
     * @return static
58
     */
59
    final public function follow(string $link): self
60
    {
61
        if (!$element = $this->documentElement()->findLink($link)) {
62
            Assert::fail('Link "%s" not found.', [$link]);
63
        }
64
65
        if (!$element->isVisible()) {
66
            Assert::fail('Link "%s" is not visible.', [$link]);
67
        }
68
69
        $this->documentElement()->clickLink($link);
70
71
        return $this;
72
    }
73
74
    /**
75
     * @return static
76
     */
77
    final public function assertVisible(string $selector): self
78
    {
79
        return $this->wrapMinkExpectation(function() use ($selector) {
80
            $element = $this->webAssert()->elementExists('css', $selector);
81
82
            Assert::true($element->isVisible(), 'Expected element "%s" to be visible but it isn\'t.', [$selector]);
83
        });
84
    }
85
86
    /**
87
     * @return static
88
     */
89
    final public function assertNotVisible(string $selector): self
90
    {
91
        $element = $this->documentElement()->find('css', $selector);
92
93
        if (!$element) {
94
            Assert::pass();
95
96
            return $this;
97
        }
98
99
        Assert::false($element->isVisible(), 'Expected element "%s" to not be visible but it is.', [$selector]);
100
101
        return $this;
102
    }
103
104
    /**
105
     * @return static
106
     */
107
    final public function wait(int $milliseconds): self
108
    {
109
        \usleep($milliseconds * 1000);
110
111
        return $this;
112
    }
113
114
    /**
115
     * @return static
116
     */
117
    final public function waitUntilVisible(string $selector): self
118
    {
119
        $this->client->waitForVisibility($selector);
120
121
        return $this;
122
    }
123
124
    /**
125
     * @return static
126
     */
127
    final public function waitUntilNotVisible(string $selector): self
128
    {
129
        $this->client->waitForInvisibility($selector);
130
131
        return $this;
132
    }
133
134
    /**
135
     * @return static
136
     */
137
    final public function waitUntilSeeIn(string $selector, string $expected): self
138
    {
139
        $this->client->waitForElementToContain($selector, $expected);
140
141
        return $this;
142
    }
143
144
    /**
145
     * @return static
146
     */
147
    final public function waitUntilNotSeeIn(string $selector, string $expected): self
148
    {
149
        $this->client->waitForElementToNotContain($selector, $expected);
150
151
        return $this;
152
    }
153
154
    /**
155
     * @return static
156
     */
157
    final public function inspect(): self
158
    {
159
        if (!($_SERVER['PANTHER_NO_HEADLESS'] ?? false)) {
160
            throw new \RuntimeException('The "PANTHER_NO_HEADLESS" env variable must be set to inspect.');
161
        }
162
163
        \fwrite(\STDIN, "\n\nInspecting the browser.\n\nPress enter to continue...");
164
        \fgets(\STDIN);
165
166
        return $this;
167
    }
168
169
    /**
170
     * @return static
171
     */
172
    final public function takeScreenshot(string $filename): self
173
    {
174
        if ($this->screenshotDir) {
175
            $filename = \sprintf('%s/%s', \rtrim($this->screenshotDir, '/'), \ltrim($filename, '/'));
176
        }
177
178
        $this->client->takeScreenshot($this->savedScreenshots[] = $filename);
179
180
        return $this;
181
    }
182
183
    final public function saveConsoleLog(string $filename): self
184
    {
185
        if ($this->consoleLogDir) {
186
            $filename = \sprintf('%s/%s', \rtrim($this->consoleLogDir, '/'), \ltrim($filename, '/'));
187
        }
188
189
        $log = $this->client->manage()->getLog('browser');
190
        $log = \json_encode($log, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_THROW_ON_ERROR);
191
192
        (new Filesystem())->dumpFile($this->savedConsoleLogs[] = $filename, $log);
193
194
        return $this;
195
    }
196
197
    final public function dumpConsoleLog(): self
198
    {
199
        VarDumper::dump($this->client->manage()->getLog('browser'));
200
201
        return $this;
202
    }
203
204
    final public function ddConsoleLog(): void
205
    {
206
        $this->dumpConsoleLog();
207
        $this->die();
208
    }
209
210
    final public function ddScreenshot(string $filename = 'screenshot.png'): void
211
    {
212
        $this->takeScreenshot($filename);
213
214
        echo \sprintf("\n\nScreenshot saved as \"%s\".\n\n", \end($this->savedScreenshots));
215
216
        $this->die();
217
    }
218
219
    /**
220
     * @internal
221
     */
222
    final public function dumpCurrentState(string $filename): void
223
    {
224
        parent::dumpCurrentState($filename);
225
226
        $this->takeScreenshot("{$filename}.png");
227
        $this->saveConsoleLog("{$filename}.log");
228
    }
229
230
    /**
231
     * @internal
232
     */
233
    final public function savedArtifacts(): array
234
    {
235
        return \array_merge(
236
            parent::savedArtifacts(),
237
            ['Saved Console Logs' => $this->savedConsoleLogs, 'Saved Screenshots' => $this->savedScreenshots]
238
        );
239
    }
240
241
    final public function response(): PantherResponse
242
    {
243
        return new PantherResponse($this->minkSession());
244
    }
245
246
    /**
247
     * @internal
248
     */
249
    final protected function die(): void
250
    {
251
        $this->client->quit();
252
        parent::die();
253
    }
254
255
    protected function useParameters(): array
256
    {
257
        return [
0 ignored issues
show
introduced by
The expression return array(parent::use...n(...) { /* ... */ }))) returns an array which contains values of type array<integer,Zenstruck\...meter\UntypedParameter> which are incompatible with the return type Zenstruck\Callback\Parameter mandated by Zenstruck\Browser::useParameters().
Loading history...
258
            ...parent::useParameters(),
259
            Parameter::typed(CookieJar::class, Parameter::factory(fn() => $this->client->getCookieJar())),
260
        ];
261
    }
262
}
263