Passed
Push — 1.x ( 2b6309...50798f )
by Kevin
02:19
created

Browser::dumpCurrentState()   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
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
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\Filesystem\Filesystem;
14
use Zenstruck\Browser\Component;
15
use Zenstruck\Browser\Response;
16
use Zenstruck\Browser\Test\Constraint\UrlMatches;
17
use Zenstruck\Callback\Parameter;
18
19
/**
20
 * @author Kevin Bond <[email protected]>
21
 */
22
class Browser
23
{
24
    private const SESSION = 'app';
25
26
    private Mink $mink;
27
    private ?string $sourceDir = null;
28
    private array $savedSources = [];
29
30
    /**
31
     * @internal
32
     */
33
    public function __construct(DriverInterface $driver)
34
    {
35
        $this->mink = new Mink([self::SESSION => new Session($driver)]);
36
    }
37
38
    /**
39
     * @return static
40
     */
41
    final public function setSourceDir(string $dir): self
42
    {
43
        $this->sourceDir = $dir;
44
45
        return $this;
46
    }
47
48
    /**
49
     * @return static
50
     */
51
    final public function visit(string $uri): self
52
    {
53
        $this->minkSession()->visit($uri);
54
55
        return $this;
56
    }
57
58
    /**
59
     * @param array $parts The url parts to check (@see parse_url)
60
     *
61
     * @return static
62
     */
63
    final public function assertOn(string $expected, array $parts = ['path', 'query', 'fragment']): self
64
    {
65
        PHPUnit::assertThat($expected, new UrlMatches($this->minkSession()->getCurrentUrl(), $parts));
66
67
        return $this;
68
    }
69
70
    /**
71
     * @param array $parts The url parts to check (@see parse_url)
72
     *
73
     * @return static
74
     */
75
    final public function assertNotOn(string $expected, array $parts = ['path', 'query', 'fragment']): self
76
    {
77
        PHPUnit::assertThat(
78
            $expected,
79
            PHPUnit::logicalNot(new UrlMatches($this->minkSession()->getCurrentUrl(), $parts))
80
        );
81
82
        return $this;
83
    }
84
85
    /**
86
     * @return static
87
     */
88
    final public function assertContains(string $expected): self
89
    {
90
        return $this->wrapMinkExpectation(
91
            fn() => $this->webAssert()->responseContains($expected)
92
        );
93
    }
94
95
    /**
96
     * @return static
97
     */
98
    final public function assertNotContains(string $expected): self
99
    {
100
        return $this->wrapMinkExpectation(
101
            fn() => $this->webAssert()->responseNotContains($expected)
102
        );
103
    }
104
105
    /**
106
     * @return static
107
     */
108
    final public function use(callable $callback): self
109
    {
110
        Callback::createFor($callback)->invokeAll(
111
            Parameter::union(
112
                Parameter::untyped($this),
113
                Parameter::typed(self::class, $this),
114
                Parameter::typed(Component::class, Parameter::factory(fn(string $class) => new $class($this))),
115
                Parameter::typed(Response::class, Parameter::factory(fn() => $this->response()))
116
            )
117
        );
118
119
        return $this;
120
    }
121
122
    /**
123
     * @return static
124
     */
125
    final public function saveSource(string $filename): self
126
    {
127
        if ($this->sourceDir) {
128
            $filename = \sprintf('%s/%s', \rtrim($this->sourceDir, '/'), \ltrim($filename, '/'));
129
        }
130
131
        (new Filesystem())->dumpFile($this->savedSources[] = $filename, $this->response()->raw());
132
133
        return $this;
134
    }
135
136
    /**
137
     * @return static
138
     */
139
    final public function dump(?string $selector = null): self
140
    {
141
        $this->response()->dump($selector);
142
143
        return $this;
144
    }
145
146
    final public function dd(?string $selector = null): void
147
    {
148
        $this->dump($selector);
149
        $this->die();
150
    }
151
152
    /**
153
     * @return static
154
     */
155
    public function follow(string $link): self
156
    {
157
        $this->documentElement()->clickLink($link);
158
159
        return $this;
160
    }
161
162
    /**
163
     * @return static
164
     */
165
    final public function fillField(string $selector, string $value): self
166
    {
167
        $this->documentElement()->fillField($selector, $value);
168
169
        return $this;
170
    }
171
172
    /**
173
     * @return static
174
     */
175
    final public function checkField(string $selector): self
176
    {
177
        $field = $this->documentElement()->findField($selector);
178
179
        if ($field && 'radio' === \mb_strtolower($field->getAttribute('type'))) {
0 ignored issues
show
Bug introduced by
It seems like $field->getAttribute('type') can also be of type null; however, parameter $string of mb_strtolower() does only seem to accept string, 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

179
        if ($field && 'radio' === \mb_strtolower(/** @scrutinizer ignore-type */ $field->getAttribute('type'))) {
Loading history...
180
            $this->documentElement()->selectFieldOption($selector, $field->getAttribute('value'));
181
182
            return $this;
183
        }
184
185
        $this->documentElement()->checkField($selector);
186
187
        return $this;
188
    }
189
190
    /**
191
     * @return static
192
     */
193
    final public function uncheckField(string $selector): self
194
    {
195
        $this->documentElement()->uncheckField($selector);
196
197
        return $this;
198
    }
199
200
    /**
201
     * Select Radio, check checkbox, select single/multiple values.
202
     *
203
     * @param string|array|null $value null: check radio/checkbox
204
     *                                 string: single value
205
     *                                 array: multiple values
206
     *
207
     * @return static
208
     */
209
    final public function selectField(string $selector, $value = null): self
210
    {
211
        if (\is_array($value)) {
212
            return $this->selectFieldOptions($selector, $value);
213
        }
214
215
        if (\is_string($value)) {
216
            return $this->selectFieldOption($selector, $value);
217
        }
218
219
        return $this->checkField($selector);
220
    }
221
222
    /**
223
     * @return static
224
     */
225
    final public function selectFieldOption(string $selector, string $value): self
226
    {
227
        $this->documentElement()->selectFieldOption($selector, $value);
228
229
        return $this;
230
    }
231
232
    /**
233
     * @return static
234
     */
235
    final public function selectFieldOptions(string $selector, array $values): self
236
    {
237
        foreach ($values as $value) {
238
            $this->documentElement()->selectFieldOption($selector, $value, true);
239
        }
240
241
        return $this;
242
    }
243
244
    /**
245
     * @return static
246
     */
247
    final public function attachFile(string $selector, string $path): self
248
    {
249
        if (!\file_exists($path)) {
250
            throw new \InvalidArgumentException(\sprintf('File "%s" does not exist.', $path));
251
        }
252
253
        $this->documentElement()->attachFileToField($selector, $path);
254
255
        return $this;
256
    }
257
258
    /**
259
     * @return static
260
     */
261
    final public function click(string $selector): self
262
    {
263
        try {
264
            $this->documentElement()->pressButton($selector);
265
        } catch (ElementNotFoundException $e) {
266
            // try link
267
            $this->documentElement()->clickLink($selector);
268
        }
269
270
        return $this;
271
    }
272
273
    /**
274
     * @return static
275
     */
276
    final public function assertSee(string $expected): self
277
    {
278
        return $this->wrapMinkExpectation(
279
            fn() => $this->webAssert()->pageTextContains($expected)
280
        );
281
    }
282
283
    /**
284
     * @return static
285
     */
286
    final public function assertNotSee(string $expected): self
287
    {
288
        return $this->wrapMinkExpectation(
289
            fn() => $this->webAssert()->pageTextNotContains($expected)
290
        );
291
    }
292
293
    /**
294
     * @return static
295
     */
296
    final public function assertSeeIn(string $selector, string $expected): self
297
    {
298
        return $this->wrapMinkExpectation(
299
            fn() => $this->webAssert()->elementTextContains('css', $selector, $expected)
300
        );
301
    }
302
303
    /**
304
     * @return static
305
     */
306
    final public function assertNotSeeIn(string $selector, string $expected): self
307
    {
308
        return $this->wrapMinkExpectation(
309
            fn() => $this->webAssert()->elementTextNotContains('css', $selector, $expected)
310
        );
311
    }
312
313
    /**
314
     * @return static
315
     */
316
    final public function assertSeeElement(string $selector): self
317
    {
318
        return $this->wrapMinkExpectation(
319
            fn() => $this->webAssert()->elementExists('css', $selector)
320
        );
321
    }
322
323
    /**
324
     * @return static
325
     */
326
    final public function assertNotSeeElement(string $selector): self
327
    {
328
        return $this->wrapMinkExpectation(
329
            fn() => $this->webAssert()->elementNotExists('css', $selector)
330
        );
331
    }
332
333
    /**
334
     * @return static
335
     */
336
    final public function assertElementCount(string $selector, int $count): self
337
    {
338
        return $this->wrapMinkExpectation(
339
            fn() => $this->webAssert()->elementsCount('css', $selector, $count)
340
        );
341
    }
342
343
    /**
344
     * @return static
345
     */
346
    final public function assertFieldEquals(string $selector, string $expected): self
347
    {
348
        return $this->wrapMinkExpectation(
349
            fn() => $this->webAssert()->fieldValueEquals($selector, $expected)
350
        );
351
    }
352
353
    /**
354
     * @return static
355
     */
356
    final public function assertFieldNotEquals(string $selector, string $expected): self
357
    {
358
        return $this->wrapMinkExpectation(
359
            fn() => $this->webAssert()->fieldValueNotEquals($selector, $expected)
360
        );
361
    }
362
363
    /**
364
     * @return static
365
     */
366
    final public function assertSelected(string $selector, string $expected): self
367
    {
368
        try {
369
            $field = $this->webAssert()->fieldExists($selector);
370
            PHPUnit::assertTrue(true);
371
        } catch (ExpectationException $e) {
372
            PHPUnit::fail($e->getMessage());
373
        }
374
375
        PHPUnit::assertContains($expected, (array) $field->getValue());
376
377
        return $this;
378
    }
379
380
    /**
381
     * @return static
382
     */
383
    final public function assertNotSelected(string $selector, string $expected): self
384
    {
385
        try {
386
            $field = $this->webAssert()->fieldExists($selector);
387
            PHPUnit::assertTrue(true);
388
        } catch (ExpectationException $e) {
389
            PHPUnit::fail($e->getMessage());
390
        }
391
392
        PHPUnit::assertNotContains($expected, (array) $field->getValue());
393
394
        return $this;
395
    }
396
397
    /**
398
     * @return static
399
     */
400
    final public function assertChecked(string $selector): self
401
    {
402
        return $this->wrapMinkExpectation(
403
            fn() => $this->webAssert()->checkboxChecked($selector)
404
        );
405
    }
406
407
    /**
408
     * @return static
409
     */
410
    final public function assertNotChecked(string $selector): self
411
    {
412
        return $this->wrapMinkExpectation(
413
            fn() => $this->webAssert()->checkboxNotChecked($selector)
414
        );
415
    }
416
417
    /**
418
     * @return static
419
     */
420
    final public function assertElementAttributeContains(string $selector, string $attribute, string $expected): self
421
    {
422
        return $this->wrapMinkExpectation(
423
            fn() => $this->webAssert()->elementAttributeContains('css', $selector, $attribute, $expected)
424
        );
425
    }
426
427
    /**
428
     * @return static
429
     */
430
    final public function assertElementAttributeNotContains(string $selector, string $attribute, string $expected): self
431
    {
432
        return $this->wrapMinkExpectation(
433
            fn() => $this->webAssert()->elementAttributeNotContains('css', $selector, $attribute, $expected)
434
        );
435
    }
436
437
    /**
438
     * @internal
439
     */
440
    public function dumpCurrentState(string $filename): void
441
    {
442
        $this->saveSource("{$filename}.txt");
443
    }
444
445
    /**
446
     * @internal
447
     */
448
    public function savedArtifacts(): array
449
    {
450
        return ['Saved Source Files' => $this->savedSources];
451
    }
452
453
    public function response(): Response
454
    {
455
        return Response::createFor($this->minkSession());
456
    }
457
458
    /**
459
     * @internal
460
     */
461
    final protected function minkSession(): Session
462
    {
463
        return $this->mink->getSession(self::SESSION);
464
    }
465
466
    /**
467
     * @internal
468
     */
469
    final protected function webAssert(): WebAssert
470
    {
471
        return $this->mink->assertSession(self::SESSION);
472
    }
473
474
    /**
475
     * @internal
476
     */
477
    final protected function documentElement(): DocumentElement
478
    {
479
        return $this->minkSession()->getPage();
480
    }
481
482
    /**
483
     * @internal
484
     *
485
     * @return static
486
     */
487
    final protected function wrapMinkExpectation(callable $callback): self
488
    {
489
        try {
490
            $callback();
491
            PHPUnit::assertTrue(true);
492
        } catch (ExpectationException $e) {
493
            PHPUnit::fail($e->getMessage());
494
        }
495
496
        return $this;
497
    }
498
499
    /**
500
     * @internal
501
     */
502
    protected function die(): void
503
    {
504
        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...
505
    }
506
}
507