Passed
Push — 1.x ( 473914...34b55f )
by Kevin
02:24
created

BrowserTests::can_attach_multiple_files()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 0
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Zenstruck\Browser\Tests;
4
5
use Symfony\Component\DomCrawler\Crawler;
6
use Symfony\Component\Filesystem\Filesystem;
7
use Symfony\Component\VarDumper\VarDumper;
8
use Zenstruck\Browser;
9
use Zenstruck\Browser\Response;
10
use Zenstruck\Browser\Response\HtmlResponse;
11
use Zenstruck\Browser\Test\HasBrowser;
12
use Zenstruck\Browser\Tests\Fixture\TestComponent1;
13
use Zenstruck\Browser\Tests\Fixture\TestComponent2;
14
use Zenstruck\Callback\Exception\UnresolveableArgument;
15
16
/**
17
 * @author Kevin Bond <[email protected]>
18
 */
19
trait BrowserTests
20
{
21
    use HasBrowser {
22
        browser as kernelBrowser;
23
    }
24
25
    /**
26
     * @test
27
     */
28
    public function multiple_browsers(): void
29
    {
30
        $browser1 = $this->browser()
31
            ->visit('/page1')
32
            ->assertOn('/page1')
33
        ;
34
35
        $browser2 = $this->browser()
0 ignored issues
show
Unused Code introduced by
The assignment to $browser2 is dead and can be removed.
Loading history...
36
            ->visit('/page2')
37
            ->assertOn('/page2')
38
        ;
39
40
        // this ensures a different browser is actually used
41
        $browser1->assertOn('/page1');
42
    }
43
44
    /**
45
     * @test
46
     */
47
    public function assert_on(): void
48
    {
49
        $this->browser()
50
            ->visit('/page1')
51
            ->assertOn('/page1')
52
            ->assertOn('http://www.example.com/page1')
53
            ->assertNotOn('/page2')
54
            ->assertNotOn('http://www.example.com/page1', ['path', 'host'])
55
            ->visit('/page1?foo=bar')
56
            ->assertOn('/page1?foo=bar')
57
            ->assertOn('/page1', ['path'])
58
            ->assertOn('/page1', ['path', 'fragment'])
59
            ->assertNotOn('/page1?foo=baz')
60
        ;
61
    }
62
63
    /**
64
     * @test
65
     * @dataProvider encodedUrlProvider
66
     */
67
    public function assert_on_encoded($url, $expected): void
68
    {
69
        $this->browser()
70
            ->visit($url)
71
            ->assertOn($expected)
72
        ;
73
    }
74
75
    public static function encodedUrlProvider(): iterable
76
    {
77
        yield ['/page1?filter[q]=value', '/page1?filter[q]=value'];
78
        yield ['/page1?filter%5Bq%5D=value', '/page1?filter[q]=value'];
79
        yield ['/page1?filter[q]=value', '/page1?filter%5Bq%5D=value'];
80
        yield ['/page1#foo bar', '/page1#foo bar'];
81
        yield ['/page1#foo%20bar', '/page1#foo bar'];
82
        yield ['/page1#foo bar', '/page1#foo%20bar'];
83
        yield ['/page1#foo+bar', '/page1#foo bar'];
84
        yield ['/page1#foo bar', '/page1#foo+bar'];
85
    }
86
87
    /**
88
     * @test
89
     */
90
    public function can_use_current_browser(): void
91
    {
92
        $browser = $this->browser();
93
94
        $browser
95
            ->use(function(Browser $b) use ($browser) {
96
                $this->assertSame($b, $browser);
0 ignored issues
show
Bug introduced by
The method assertSame() does not exist on Zenstruck\Browser\Tests\BrowserTests. Did you maybe mean assert_on()? ( Ignorable by Annotation )

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

96
                $this->/** @scrutinizer ignore-call */ 
97
                       assertSame($b, $browser);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
97
98
                $browser->visit('/redirect1');
99
            })
100
            ->assertOn('/page1')
101
            ->use(function() {
102
                $this->assertTrue(true);
0 ignored issues
show
Bug introduced by
The method assertTrue() does not exist on Zenstruck\Browser\Tests\BrowserTests. Did you maybe mean assert_on()? ( Ignorable by Annotation )

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

102
                $this->/** @scrutinizer ignore-call */ 
103
                       assertTrue(true);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
103
            })
104
        ;
105
    }
106
107
    /**
108
     * @test
109
     */
110
    public function can_use_components(): void
111
    {
112
        $this->browser()
113
            ->use(function(TestComponent1 $component) {
114
                $component->assertTitle('h1 title');
115
            })
116
            ->assertOn('/page1')
117
        ;
118
    }
119
120
    /**
121
     * @test
122
     */
123
    public function component_pre_assertions_and_actions_are_called(): void
124
    {
125
        $this->browser()
126
            ->use(function(TestComponent2 $component) {
127
                $this->assertTrue($component->preActionsCalled);
128
                $this->assertTrue($component->preAssertionsCalled);
129
            })
130
        ;
131
    }
132
133
    /**
134
     * @test
135
     */
136
    public function can_use_response(): void
137
    {
138
        $this->browser()
139
            ->visit('/page1')
140
            ->use(function(Response $response) {
141
                $this->assertStringContainsString('<h1>h1 title</h1>', $response->body());
0 ignored issues
show
Bug introduced by
It seems like assertStringContainsString() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

141
                $this->/** @scrutinizer ignore-call */ 
142
                       assertStringContainsString('<h1>h1 title</h1>', $response->body());
Loading history...
142
            })
143
            ->use(function(HtmlResponse $response) {
144
                $this->assertCount(2, $response->crawler()->filter('ul li'));
0 ignored issues
show
Bug introduced by
The method assertCount() does not exist on Zenstruck\Browser\Tests\BrowserTests. Did you maybe mean assert_on()? ( Ignorable by Annotation )

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

144
                $this->/** @scrutinizer ignore-call */ 
145
                       assertCount(2, $response->crawler()->filter('ul li'));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
145
            })
146
        ;
147
    }
148
149
    /**
150
     * @test
151
     */
152
    public function can_use_crawler(): void
153
    {
154
        $this->browser()
155
            ->visit('/page1')
156
            ->use(function(Crawler $crawler) {
157
                $this->assertSame('h1 title', $crawler->filter('h1')->text());
158
            })
159
        ;
160
    }
161
162
    /**
163
     * @test
164
     */
165
    public function with_can_accept_multiple_browsers_and_components(): void
166
    {
167
        $browser = $this->browser();
168
169
        $browser
170
            ->use(function(Browser $browser1, $browser2, TestComponent1 $component1, TestComponent2 $component2, Crawler $crawler) use ($browser) {
0 ignored issues
show
Unused Code introduced by
The parameter $crawler is not used and could be removed. ( Ignorable by Annotation )

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

170
            ->use(function(Browser $browser1, $browser2, TestComponent1 $component1, TestComponent2 $component2, /** @scrutinizer ignore-unused */ Crawler $crawler) use ($browser) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
171
                $this->assertInstanceOf(Browser::class, $browser1);
0 ignored issues
show
Bug introduced by
It seems like assertInstanceOf() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

171
                $this->/** @scrutinizer ignore-call */ 
172
                       assertInstanceOf(Browser::class, $browser1);
Loading history...
172
                $this->assertInstanceOf(Browser::class, $browser2);
173
                $this->assertInstanceOf(\get_class($browser), $browser1);
174
                $this->assertInstanceOf(\get_class($browser), $browser2);
175
                $this->assertInstanceOf(TestComponent1::class, $component1);
176
                $this->assertInstanceOf(TestComponent2::class, $component2);
177
            })
178
        ;
179
    }
180
181
    /**
182
     * @test
183
     */
184
    public function invalid_use_callback_parameter_throws_type_error(): void
185
    {
186
        $this->expectException(UnresolveableArgument::class);
0 ignored issues
show
Bug introduced by
It seems like expectException() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

186
        $this->/** @scrutinizer ignore-call */ 
187
               expectException(UnresolveableArgument::class);
Loading history...
187
188
        $this->browser()->use(function(string $invalidType) {});
0 ignored issues
show
Unused Code introduced by
The parameter $invalidType is not used and could be removed. ( Ignorable by Annotation )

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

188
        $this->browser()->use(function(/** @scrutinizer ignore-unused */ string $invalidType) {});

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
189
    }
190
191
    /**
192
     * @test
193
     */
194
    public function redirects_are_followed_by_default(): void
195
    {
196
        $this->browser()
197
            ->visit('/redirect1')
198
            ->assertOn('/page1')
199
        ;
200
    }
201
202
    /**
203
     * @test
204
     */
205
    public function content_assertions(): void
206
    {
207
        $this->browser()
208
            ->visit('/page1')
209
            ->assertContains('h1 title')
210
            ->assertNotContains('invalid text')
211
        ;
212
    }
213
214
    /**
215
     * @test
216
     */
217
    public function can_dump_response(): void
218
    {
219
        $output = self::catchVarDumperOutput(function() {
220
            $this->browser()
221
                ->visit('/page1')
222
                ->dump()
223
            ;
224
        });
225
226
        $this->assertStringContainsString('/page1', $output[0]);
227
        $this->assertStringContainsString('<html', $output[0]);
228
        $this->assertStringContainsString('<h1>h1 title</h1>', $output[0]);
229
    }
230
231
    /**
232
     * @test
233
     */
234
    public function can_save_source(): void
235
    {
236
        $contents = self::catchFileContents(__DIR__.'/../var/browser/source/source.txt', function() {
237
            $this->browser()
238
                ->visit('/page1')
239
                ->saveSource('source.txt')
240
            ;
241
        });
242
243
        $this->assertStringContainsString('/page1', $contents);
244
        $this->assertStringContainsString('<html', $contents);
245
        $this->assertStringContainsString('<h1>h1 title</h1>', $contents);
246
    }
247
248
    /**
249
     * @test
250
     */
251
    public function html_assertions(): void
252
    {
253
        $this->browser()
254
            ->visit('/page1')
255
            ->assertSee('h1 title')
256
            ->assertNotSee('invalid text')
257
            ->assertSeeIn('h1', 'title')
258
            ->assertNotSeeIn('h1', 'invalid text')
259
            ->assertSeeElement('h1')
260
            ->assertNotSeeElement('h2')
261
            ->assertElementCount('ul li', 2)
262
        ;
263
    }
264
265
    /**
266
     * @test
267
     */
268
    public function html_head_assertions(): void
269
    {
270
        $this->browser()
271
            ->visit('/page1')
272
            ->assertSeeIn('title', 'meta title')
273
            ->assertElementAttributeContains('meta[name="description"]', 'content', 'meta')
274
            ->assertElementAttributeNotContains('meta[name="description"]', 'content', 'invalid')
275
            ->assertElementAttributeContains('html', 'lang', 'en')
276
        ;
277
    }
278
279
    /**
280
     * @test
281
     */
282
    public function form_assertions(): void
283
    {
284
        $this->browser()
285
            ->visit('/page1')
286
            ->assertFieldEquals('Input 1', 'input 1')
287
            ->assertFieldEquals('input1', 'input 1')
288
            ->assertFieldEquals('input_1', 'input 1')
289
            ->assertFieldNotEquals('Input 1', 'invalid')
290
            ->assertFieldNotEquals('input1', 'invalid')
291
            ->assertFieldNotEquals('input_1', 'invalid')
292
            ->assertChecked('Input 3')
293
            ->assertChecked('input3')
294
            ->assertChecked('input_3')
295
            ->assertNotChecked('Input 2')
296
            ->assertNotChecked('input2')
297
            ->assertNotChecked('input_2')
298
            ->assertSelected('Input 4', 'option 1')
299
            ->assertSelected('input4', 'option 1')
300
            ->assertSelected('input_4', 'option 1')
301
            ->assertSelected('Input 7', 'option 1')
302
            ->assertSelected('input7', 'option 1')
303
            ->assertSelected('input_7[]', 'option 1')
304
            ->assertSelected('Input 7', 'option 3')
305
            ->assertSelected('input7', 'option 3')
306
            ->assertSelected('input_7[]', 'option 3')
307
            ->assertNotSelected('Input 4', 'option 2')
308
            ->assertNotSelected('input4', 'option 2')
309
            ->assertNotSelected('input_4', 'option 2')
310
            ->assertNotSelected('Input 7', 'option 2')
311
            ->assertNotSelected('input7', 'option 2')
312
            ->assertNotSelected('input_7[]', 'option 2')
313
            ->assertNotSelected('input_8', 'option 1')
314
            ->assertSelected('input_8', 'option 2')
315
            ->assertNotChecked('Radio 1')
316
            ->assertNotChecked('radio1')
317
            ->assertNotChecked('Radio 3')
318
            ->assertNotChecked('radio3')
319
            ->assertChecked('Radio 2')
320
            ->assertChecked('radio2')
321
        ;
322
    }
323
324
    /**
325
     * @test
326
     */
327
    public function link_action(): void
328
    {
329
        $this->browser()
330
            ->visit('/page1')
331
            ->follow('a link')
332
            ->assertOn('/page2')
333
            ->visit('/page1')
334
            ->click('a link')
335
            ->assertOn('/page2')
336
        ;
337
    }
338
339
    /**
340
     * @test
341
     */
342
    public function form_actions_by_field_label(): void
343
    {
344
        $this->browser()
345
            ->visit('/page1')
346
            ->fillField('Input 1', 'Kevin')
347
            ->checkField('Input 2')
348
            ->uncheckField('Input 3')
349
            ->selectFieldOption('Input 4', 'option 2')
350
            ->attachFile('Input 5', __FILE__)
351
            ->selectFieldOptions('Input 6', ['option 1', 'option 3'])
352
            ->checkField('Radio 3')
353
            ->click('Submit')
354
            ->assertOn('/submit-form')
355
            ->assertContains('"input_1":"Kevin"')
356
            ->assertContains('"input_2":"on"')
357
            ->assertNotContains('"input_3')
358
            ->assertContains('"input_4":"option 2"')
359
            ->assertContains(\sprintf('"input_5":"%s"', \pathinfo(__FILE__, \PATHINFO_BASENAME)))
0 ignored issues
show
Bug introduced by
It seems like pathinfo(__FILE__, PATHINFO_BASENAME) can also be of type array; however, parameter $values of sprintf() does only seem to accept double|integer|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

359
            ->assertContains(\sprintf('"input_5":"%s"', /** @scrutinizer ignore-type */ \pathinfo(__FILE__, \PATHINFO_BASENAME)))
Loading history...
360
            ->assertContains('"input_6":["option 1","option 3"]')
361
            ->assertContains('"input_8":"option 3"')
362
        ;
363
    }
364
365
    /**
366
     * @test
367
     */
368
    public function form_actions_by_field_id(): void
369
    {
370
        $this->browser()
371
            ->visit('/page1')
372
            ->fillField('input1', 'Kevin')
373
            ->checkField('input2')
374
            ->uncheckField('input3')
375
            ->selectFieldOption('input4', 'option 2')
376
            ->attachFile('input5', __FILE__)
377
            ->selectFieldOptions('input6', ['option 1', 'option 3'])
378
            ->checkField('radio3')
379
            ->click('Submit')
380
            ->assertOn('/submit-form')
381
            ->assertContains('"input_1":"Kevin"')
382
            ->assertContains('"input_2":"on"')
383
            ->assertNotContains('"input_3')
384
            ->assertContains('"input_4":"option 2"')
385
            ->assertContains(\sprintf('"input_5":"%s"', \pathinfo(__FILE__, \PATHINFO_BASENAME)))
0 ignored issues
show
Bug introduced by
It seems like pathinfo(__FILE__, PATHINFO_BASENAME) can also be of type array; however, parameter $values of sprintf() does only seem to accept double|integer|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

385
            ->assertContains(\sprintf('"input_5":"%s"', /** @scrutinizer ignore-type */ \pathinfo(__FILE__, \PATHINFO_BASENAME)))
Loading history...
386
            ->assertContains('"input_6":["option 1","option 3"]')
387
            ->assertContains('"input_8":"option 3"')
388
        ;
389
    }
390
391
    /**
392
     * @test
393
     */
394
    public function form_actions_by_field_name(): void
395
    {
396
        $this->browser()
397
            ->visit('/page1')
398
            ->fillField('input_1', 'Kevin')
399
            ->checkField('input_2')
400
            ->uncheckField('input_3')
401
            ->selectFieldOption('input_4', 'option 2')
402
            ->attachFile('input_5', __FILE__)
403
            ->selectFieldOptions('input_6[]', ['option 1', 'option 3'])
404
            ->selectFieldOption('input_8', 'option 3')
405
            ->click('Submit')
406
            ->assertOn('/submit-form')
407
            ->assertContains('"input_1":"Kevin"')
408
            ->assertContains('"input_2":"on"')
409
            ->assertNotContains('"input_3')
410
            ->assertContains('"input_4":"option 2"')
411
            ->assertContains(\sprintf('"input_5":"%s"', \pathinfo(__FILE__, \PATHINFO_BASENAME)))
0 ignored issues
show
Bug introduced by
It seems like pathinfo(__FILE__, PATHINFO_BASENAME) can also be of type array; however, parameter $values of sprintf() does only seem to accept double|integer|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

411
            ->assertContains(\sprintf('"input_5":"%s"', /** @scrutinizer ignore-type */ \pathinfo(__FILE__, \PATHINFO_BASENAME)))
Loading history...
412
            ->assertContains('"input_6":["option 1","option 3"]')
413
            ->assertContains('"input_8":"option 3"')
414
        ;
415
    }
416
417
    /**
418
     * @test
419
     */
420
    public function select_field(): void
421
    {
422
        $this->browser()
423
            ->visit('/page1')
424
            ->selectField('Input 2')
425
            ->selectField('Input 4', 'option 2')
426
            ->selectField('Input 6', ['option 1', 'option 3'])
427
            ->selectField('Radio 3')
428
            ->click('Submit')
429
            ->assertOn('/submit-form')
430
            ->assertContains('"input_2":"on"')
431
            ->assertContains('"input_4":"option 2"')
432
            ->assertContains('"input_6":["option 1","option 3"]')
433
            ->assertContains('"input_8":"option 3"')
434
        ;
435
    }
436
437
    /**
438
     * @test
439
     */
440
    public function can_submit_form_with_different_submit_buttons(): void
441
    {
442
        // Submit and Submit B, have the same field name but different values
443
        // Submit C has a different field name (and value)
444
445
        $this->browser()
446
            ->visit('/page1')
447
            ->click('Submit')
448
            ->assertOn('/submit-form')
449
            ->assertContains('"submit_1":"a"')
450
            ->assertNotContains('submit_2')
451
            ->visit('/page1')
452
            ->click('Submit B')
453
            ->assertOn('/submit-form')
454
            ->assertContains('"submit_1":"b"')
455
            ->assertNotContains('submit_2')
456
            ->visit('/page1')
457
            ->click('Submit C')
458
            ->assertOn('/submit-form')
459
            ->assertContains('"submit_2":"c"')
460
            ->assertNotContains('submit_1')
461
        ;
462
    }
463
464
    /**
465
     * @test
466
     */
467
    public function cannot_attach_file_that_does_not_exist(): void
468
    {
469
        $this->expectException(\InvalidArgumentException::class);
470
471
        $this->browser()
472
            ->visit('/page1')
473
            ->attachFile('Input 5', '/invalid/file')
474
        ;
475
    }
476
477
    /**
478
     * @test
479
     */
480
    public function can_attach_multiple_files(): void
481
    {
482
        $this->browser()
483
            ->visit('/page1')
484
            ->attachFile('Input 9', [__DIR__.'/Fixture/files/attachment.txt', __DIR__.'/Fixture/files/xml.xml'])
485
            ->click('Submit')
486
            ->assertContains('"input_9":["attachment.txt","xml.xml"]')
487
        ;
488
    }
489
490
    /**
491
     * @test
492
     */
493
    public function cannot_attach_multiple_files_to_a_non_multiple_input(): void
494
    {
495
        $this->expectException(\InvalidArgumentException::class);
496
497
        $this->browser()
498
            ->visit('/page1')
499
            ->attachFile('Input 5', [__DIR__.'/Fixture/files/attachment.txt', __DIR__.'/Fixture/files/xml.xml'])
500
        ;
501
    }
502
503
    /**
504
     * @test
505
     */
506
    public function can_dump_html_element(): void
507
    {
508
        $output = self::catchVarDumperOutput(function() {
509
            $this->browser()
510
                ->visit('/page1')
511
                ->dump('p#link')
512
            ;
513
        });
514
515
        $this->assertCount(1, $output);
516
        $this->assertSame('<p id="link"><a href="/page2">a link</a> not a link</p>', $output[0]);
517
    }
518
519
    /**
520
     * @test
521
     */
522
    public function if_dump_selector_matches_multiple_elements_all_are_dumped(): void
523
    {
524
        $output = self::catchVarDumperOutput(function() {
525
            $this->browser()
526
                ->visit('/page1')
527
                ->dump('li')
528
            ;
529
        });
530
531
        $this->assertCount(2, $output);
532
        $this->assertSame('<li>list 1</li>', $output[0]);
533
        $this->assertSame('<li>list 2</li>', $output[1]);
534
    }
535
536
    /**
537
     * @test
538
     */
539
    public function can_access_the_html_crawler(): void
540
    {
541
        $crawler = $this->browser()
542
            ->visit('/page1')
543
            ->response()
544
            ->assertHtml()
545
            ->crawler()
546
            ->filter('ul li')
547
        ;
548
549
        $this->assertCount(2, $crawler);
550
    }
551
552
    protected static function catchFileContents(string $expectedFile, callable $callback): string
553
    {
554
        (new Filesystem())->remove($expectedFile);
555
556
        $callback();
557
558
        self::assertFileExists($expectedFile);
559
560
        return \file_get_contents($expectedFile);
561
    }
562
563
    protected static function catchVarDumperOutput(callable $callback): array
564
    {
565
        $output[] = null;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$output was never initialized. Although not strictly required by PHP, it is generally a good practice to add $output = array(); before regardless.
Loading history...
566
567
        VarDumper::setHandler(function($var) use (&$output) {
568
            $output[] = $var;
569
        });
570
571
        $callback();
572
573
        // reset to default handler
574
        VarDumper::setHandler();
575
576
        // a null value is added to the beginning
577
        return \array_values(\array_filter($output));
578
    }
579
580
    abstract protected function browser(): Browser;
581
}
582