Passed
Pull Request — 1.x (#28)
by Kevin
02:58 queued 48s
created

Browser::crawler()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace Zenstruck;
4
5
use Behat\Mink\Driver\DriverInterface;
6
use Behat\Mink\Element\DocumentElement;
7
use Behat\Mink\Exception\ElementNotFoundException;
8
use Behat\Mink\Exception\ExpectationException;
9
use Behat\Mink\Mink;
10
use Behat\Mink\Session;
11
use Behat\Mink\WebAssert;
12
use PHPUnit\Framework\Assert as PHPUnit;
0 ignored issues
show
Bug introduced by
The type PHPUnit\Framework\Assert was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
13
use Symfony\Component\DomCrawler\Crawler;
14
use Symfony\Component\Filesystem\Filesystem;
15
use Zenstruck\Browser\Component;
16
use Zenstruck\Browser\Response;
17
use Zenstruck\Browser\Test\Constraint\UrlMatches;
18
use Zenstruck\Callback\Parameter;
19
20
/**
21
 * @author Kevin Bond <[email protected]>
22
 */
23
class Browser
24
{
25
    private const SESSION = 'app';
26
27
    private Mink $mink;
28
    private ?string $sourceDir = null;
29
    private array $savedSources = [];
30
31
    /**
32
     * @internal
33
     */
34
    public function __construct(DriverInterface $driver)
35
    {
36
        $this->mink = new Mink([self::SESSION => new Session($driver)]);
37
    }
38
39
    /**
40
     * @internal
41
     *
42
     * @return static
43
     */
44
    final public static function create(callable $factory): self
45
    {
46
        $browser = $factory();
47
48
        if (!$browser instanceof self) {
49
            throw new \RuntimeException(\sprintf('The factory callable must return an instance of "%s".', self::class));
50
        }
51
52
        return $browser;
53
    }
54
55
    /**
56
     * @return static
57
     */
58
    final public function setSourceDir(string $dir): self
59
    {
60
        $this->sourceDir = $dir;
61
62
        return $this;
63
    }
64
65
    /**
66
     * @return static
67
     */
68
    final public function visit(string $uri): self
69
    {
70
        $this->minkSession()->visit($uri);
71
72
        return $this;
73
    }
74
75
    /**
76
     * @param array $parts The url parts to check (@see parse_url)
77
     *
78
     * @return static
79
     */
80
    final public function assertOn(string $expected, array $parts = ['path', 'query', 'fragment']): self
81
    {
82
        PHPUnit::assertThat($expected, new UrlMatches($this->minkSession()->getCurrentUrl(), $parts));
83
84
        return $this;
85
    }
86
87
    /**
88
     * @param array $parts The url parts to check (@see parse_url)
89
     *
90
     * @return static
91
     */
92
    final public function assertNotOn(string $expected, array $parts = ['path', 'query', 'fragment']): self
93
    {
94
        PHPUnit::assertThat(
95
            $expected,
96
            PHPUnit::logicalNot(new UrlMatches($this->minkSession()->getCurrentUrl(), $parts))
97
        );
98
99
        return $this;
100
    }
101
102
    /**
103
     * @return static
104
     */
105
    final public function assertContains(string $expected): self
106
    {
107
        return $this->wrapMinkExpectation(
108
            fn() => $this->webAssert()->responseContains($expected)
109
        );
110
    }
111
112
    /**
113
     * @return static
114
     */
115
    final public function assertNotContains(string $expected): self
116
    {
117
        return $this->wrapMinkExpectation(
118
            fn() => $this->webAssert()->responseNotContains($expected)
119
        );
120
    }
121
122
    /**
123
     * @return static
124
     */
125
    final public function use(callable $callback): self
126
    {
127
        Callback::createFor($callback)->invokeAll(
128
            Parameter::union(
129
                Parameter::untyped($this),
130
                Parameter::typed(self::class, $this),
131
                Parameter::typed(Component::class, Parameter::factory(fn(string $class) => new $class($this)))
132
            )
133
        );
134
135
        return $this;
136
    }
137
138
    /**
139
     * @return static
140
     */
141
    final public function saveSource(string $filename): self
142
    {
143
        if ($this->sourceDir) {
144
            $filename = \sprintf('%s/%s', \rtrim($this->sourceDir, '/'), \ltrim($filename, '/'));
145
        }
146
147
        (new Filesystem())->dumpFile($this->savedSources[] = $filename, $this->response()->raw());
148
149
        return $this;
150
    }
151
152
    /**
153
     * @return static
154
     */
155
    final public function dump(?string $selector = null): self
156
    {
157
        $this->response()->dump($selector);
158
159
        return $this;
160
    }
161
162
    final public function dd(?string $selector = null): void
163
    {
164
        $this->dump($selector);
165
        $this->die();
166
    }
167
168
    /**
169
     * @return static
170
     */
171
    public function follow(string $link): self
172
    {
173
        $this->documentElement()->clickLink($link);
174
175
        return $this;
176
    }
177
178
    /**
179
     * @return static
180
     */
181
    final public function fillField(string $selector, string $value): self
182
    {
183
        $this->documentElement()->fillField($selector, $value);
184
185
        return $this;
186
    }
187
188
    /**
189
     * @return static
190
     */
191
    final public function checkField(string $selector): self
192
    {
193
        $this->documentElement()->checkField($selector);
194
195
        return $this;
196
    }
197
198
    /**
199
     * @return static
200
     */
201
    final public function uncheckField(string $selector): self
202
    {
203
        $this->documentElement()->uncheckField($selector);
204
205
        return $this;
206
    }
207
208
    /**
209
     * @return static
210
     */
211
    final public function selectFieldOption(string $selector, string $value): self
212
    {
213
        $this->documentElement()->selectFieldOption($selector, $value);
214
215
        return $this;
216
    }
217
218
    /**
219
     * @return static
220
     */
221
    final public function selectFieldOptions(string $selector, array $values): self
222
    {
223
        foreach ($values as $value) {
224
            $this->documentElement()->selectFieldOption($selector, $value, true);
225
        }
226
227
        return $this;
228
    }
229
230
    /**
231
     * @return static
232
     */
233
    final public function attachFile(string $selector, string $path): self
234
    {
235
        $this->documentElement()->attachFileToField($selector, $path);
236
237
        return $this;
238
    }
239
240
    /**
241
     * @return static
242
     */
243
    final public function click(string $selector): self
244
    {
245
        try {
246
            $this->documentElement()->pressButton($selector);
247
        } catch (ElementNotFoundException $e) {
248
            // try link
249
            $this->documentElement()->clickLink($selector);
250
        }
251
252
        return $this;
253
    }
254
255
    /**
256
     * @return static
257
     */
258
    final public function assertSee(string $expected): self
259
    {
260
        return $this->wrapMinkExpectation(
261
            fn() => $this->webAssert()->pageTextContains($expected)
262
        );
263
    }
264
265
    /**
266
     * @return static
267
     */
268
    final public function assertNotSee(string $expected): self
269
    {
270
        return $this->wrapMinkExpectation(
271
            fn() => $this->webAssert()->pageTextNotContains($expected)
272
        );
273
    }
274
275
    /**
276
     * @return static
277
     */
278
    final public function assertSeeIn(string $selector, string $expected): self
279
    {
280
        return $this->wrapMinkExpectation(
281
            fn() => $this->webAssert()->elementTextContains('css', $selector, $expected)
282
        );
283
    }
284
285
    /**
286
     * @return static
287
     */
288
    final public function assertNotSeeIn(string $selector, string $expected): self
289
    {
290
        return $this->wrapMinkExpectation(
291
            fn() => $this->webAssert()->elementTextNotContains('css', $selector, $expected)
292
        );
293
    }
294
295
    /**
296
     * @return static
297
     */
298
    final public function assertSeeElement(string $selector): self
299
    {
300
        return $this->wrapMinkExpectation(
301
            fn() => $this->webAssert()->elementExists('css', $selector)
302
        );
303
    }
304
305
    /**
306
     * @return static
307
     */
308
    final public function assertNotSeeElement(string $selector): self
309
    {
310
        return $this->wrapMinkExpectation(
311
            fn() => $this->webAssert()->elementNotExists('css', $selector)
312
        );
313
    }
314
315
    /**
316
     * @return static
317
     */
318
    final public function assertElementCount(string $selector, int $count): self
319
    {
320
        return $this->wrapMinkExpectation(
321
            fn() => $this->webAssert()->elementsCount('css', $selector, $count)
322
        );
323
    }
324
325
    /**
326
     * @return static
327
     */
328
    final public function assertFieldEquals(string $selector, string $expected): self
329
    {
330
        return $this->wrapMinkExpectation(
331
            fn() => $this->webAssert()->fieldValueEquals($selector, $expected)
332
        );
333
    }
334
335
    /**
336
     * @return static
337
     */
338
    final public function assertFieldNotEquals(string $selector, string $expected): self
339
    {
340
        return $this->wrapMinkExpectation(
341
            fn() => $this->webAssert()->fieldValueNotEquals($selector, $expected)
342
        );
343
    }
344
345
    /**
346
     * @return static
347
     */
348
    final public function assertSelected(string $selector, string $expected): self
349
    {
350
        try {
351
            $field = $this->webAssert()->fieldExists($selector);
352
            PHPUnit::assertTrue(true);
353
        } catch (ExpectationException $e) {
354
            PHPUnit::fail($e->getMessage());
355
        }
356
357
        PHPUnit::assertContains($expected, (array) $field->getValue());
358
359
        return $this;
360
    }
361
362
    /**
363
     * @return static
364
     */
365
    final public function assertNotSelected(string $selector, string $expected): self
366
    {
367
        try {
368
            $field = $this->webAssert()->fieldExists($selector);
369
            PHPUnit::assertTrue(true);
370
        } catch (ExpectationException $e) {
371
            PHPUnit::fail($e->getMessage());
372
        }
373
374
        PHPUnit::assertNotContains($expected, (array) $field->getValue());
375
376
        return $this;
377
    }
378
379
    /**
380
     * @return static
381
     */
382
    final public function assertChecked(string $selector): self
383
    {
384
        return $this->wrapMinkExpectation(
385
            fn() => $this->webAssert()->checkboxChecked($selector)
386
        );
387
    }
388
389
    /**
390
     * @return static
391
     */
392
    final public function assertNotChecked(string $selector): self
393
    {
394
        return $this->wrapMinkExpectation(
395
            fn() => $this->webAssert()->checkboxNotChecked($selector)
396
        );
397
    }
398
399
    /**
400
     * @return static
401
     */
402
    final public function assertElementAttributeContains(string $selector, string $attribute, string $expected): self
403
    {
404
        return $this->wrapMinkExpectation(
405
            fn() => $this->webAssert()->elementAttributeContains('css', $selector, $attribute, $expected)
406
        );
407
    }
408
409
    /**
410
     * @return static
411
     */
412
    final public function assertElementAttributeNotContains(string $selector, string $attribute, string $expected): self
413
    {
414
        return $this->wrapMinkExpectation(
415
            fn() => $this->webAssert()->elementAttributeNotContains('css', $selector, $attribute, $expected)
416
        );
417
    }
418
419
    final public function crawler(): Crawler
420
    {
421
        return $this->response()->crawler();
422
    }
423
424
    /**
425
     * @internal
426
     */
427
    public function dumpCurrentState(string $filename): void
428
    {
429
        $this->saveSource("{$filename}.txt");
430
    }
431
432
    /**
433
     * @internal
434
     */
435
    public function savedArtifacts(): array
436
    {
437
        return ['Saved Source Files' => $this->savedSources];
438
    }
439
440
    /**
441
     * @internal
442
     */
443
    final protected function minkSession(): Session
444
    {
445
        return $this->mink->getSession(self::SESSION);
446
    }
447
448
    /**
449
     * @internal
450
     */
451
    final protected function webAssert(): WebAssert
452
    {
453
        return $this->mink->assertSession(self::SESSION);
454
    }
455
456
    /**
457
     * @internal
458
     */
459
    final protected function documentElement(): DocumentElement
460
    {
461
        return $this->minkSession()->getPage();
462
    }
463
464
    /**
465
     * @internal
466
     */
467
    protected function response(): Response
468
    {
469
        return Response::createFor($this->minkSession());
470
    }
471
472
    /**
473
     * @internal
474
     *
475
     * @return static
476
     */
477
    final protected function wrapMinkExpectation(callable $callback): self
478
    {
479
        try {
480
            $callback();
481
            PHPUnit::assertTrue(true);
482
        } catch (ExpectationException $e) {
483
            PHPUnit::fail($e->getMessage());
484
        }
485
486
        return $this;
487
    }
488
489
    /**
490
     * @internal
491
     */
492
    protected function die(): void
493
    {
494
        exit(1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
495
    }
496
}
497