Passed
Pull Request — 1.x (#27)
by Kevin
02:06
created

BrowserTests::assert_on_encoded()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

93
                $this->/** @scrutinizer ignore-call */ 
94
                       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...
94
95
                $browser->visit('/redirect1');
96
            })
97
            ->assertOn('/page1')
98
            ->use(function() {
99
                $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

99
                $this->/** @scrutinizer ignore-call */ 
100
                       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...
100
            })
101
        ;
102
    }
103
104
    /**
105
     * @test
106
     */
107
    public function can_use_components(): void
108
    {
109
        $this->browser()
110
            ->use(function(TestComponent1 $component) {
111
                $component->assertTitle('h1 title');
112
            })
113
            ->assertOn('/page1')
114
        ;
115
    }
116
117
    /**
118
     * @test
119
     */
120
    public function component_pre_assertions_and_actions_are_called(): void
121
    {
122
        $this->browser()
123
            ->use(function(TestComponent2 $component) {
124
                $this->assertTrue($component->preActionsCalled);
125
                $this->assertTrue($component->preAssertionsCalled);
126
            })
127
        ;
128
    }
129
130
    /**
131
     * @test
132
     */
133
    public function can_use_response(): void
134
    {
135
        $this->browser()
136
            ->visit('/page1')
137
            ->use(function(Response $response) {
138
                $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

138
                $this->/** @scrutinizer ignore-call */ 
139
                       assertStringContainsString('<h1>h1 title</h1>', $response->body());
Loading history...
139
            })
140
            ->use(function(HtmlResponse $response) {
141
                $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

141
                $this->/** @scrutinizer ignore-call */ 
142
                       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...
142
            })
143
        ;
144
    }
145
146
    /**
147
     * @test
148
     */
149
    public function with_can_accept_multiple_browsers_and_components(): void
150
    {
151
        $browser = $this->browser();
152
153
        $browser
154
            ->use(function(Browser $browser1, $browser2, TestComponent1 $component1, TestComponent2 $component2) use ($browser) {
155
                $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

155
                $this->/** @scrutinizer ignore-call */ 
156
                       assertInstanceOf(Browser::class, $browser1);
Loading history...
156
                $this->assertInstanceOf(Browser::class, $browser2);
157
                $this->assertInstanceOf(\get_class($browser), $browser1);
158
                $this->assertInstanceOf(\get_class($browser), $browser2);
159
                $this->assertInstanceOf(TestComponent1::class, $component1);
160
                $this->assertInstanceOf(TestComponent2::class, $component2);
161
            })
162
        ;
163
    }
164
165
    /**
166
     * @test
167
     */
168
    public function invalid_use_callback_parameter_throws_type_error(): void
169
    {
170
        $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

170
        $this->/** @scrutinizer ignore-call */ 
171
               expectException(UnresolveableArgument::class);
Loading history...
171
172
        $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

172
        $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...
173
    }
174
175
    /**
176
     * @test
177
     */
178
    public function redirects_are_followed_by_default(): void
179
    {
180
        $this->browser()
181
            ->visit('/redirect1')
182
            ->assertOn('/page1')
183
        ;
184
    }
185
186
    /**
187
     * @test
188
     */
189
    public function content_assertions(): void
190
    {
191
        $this->browser()
192
            ->visit('/page1')
193
            ->assertContains('h1 title')
194
            ->assertNotContains('invalid text')
195
        ;
196
    }
197
198
    /**
199
     * @test
200
     */
201
    public function can_dump_response(): void
202
    {
203
        $output = self::catchVarDumperOutput(function() {
204
            $this->browser()
205
                ->visit('/page1')
206
                ->dump()
207
            ;
208
        });
209
210
        $this->assertStringContainsString('/page1', $output[0]);
211
        $this->assertStringContainsString('<html', $output[0]);
212
        $this->assertStringContainsString('<h1>h1 title</h1>', $output[0]);
213
    }
214
215
    /**
216
     * @test
217
     */
218
    public function can_save_source(): void
219
    {
220
        $contents = self::catchFileContents(__DIR__.'/../var/browser/source/source.txt', function() {
221
            $this->browser()
222
                ->visit('/page1')
223
                ->saveSource('source.txt')
224
            ;
225
        });
226
227
        $this->assertStringContainsString('/page1', $contents);
228
        $this->assertStringContainsString('<html', $contents);
229
        $this->assertStringContainsString('<h1>h1 title</h1>', $contents);
230
    }
231
232
    /**
233
     * @test
234
     */
235
    public function html_assertions(): void
236
    {
237
        $this->browser()
238
            ->visit('/page1')
239
            ->assertSee('h1 title')
240
            ->assertNotSee('invalid text')
241
            ->assertSeeIn('h1', 'title')
242
            ->assertNotSeeIn('h1', 'invalid text')
243
            ->assertSeeElement('h1')
244
            ->assertNotSeeElement('h2')
245
            ->assertElementCount('ul li', 2)
246
        ;
247
    }
248
249
    /**
250
     * @test
251
     */
252
    public function html_head_assertions(): void
253
    {
254
        $this->browser()
255
            ->visit('/page1')
256
            ->assertSeeIn('title', 'meta title')
257
            ->assertElementAttributeContains('meta[name="description"]', 'content', 'meta')
258
            ->assertElementAttributeNotContains('meta[name="description"]', 'content', 'invalid')
259
            ->assertElementAttributeContains('html', 'lang', 'en')
260
        ;
261
    }
262
263
    /**
264
     * @test
265
     */
266
    public function form_assertions(): void
267
    {
268
        $this->browser()
269
            ->visit('/page1')
270
            ->assertFieldEquals('Input 1', 'input 1')
271
            ->assertFieldEquals('input1', 'input 1')
272
            ->assertFieldEquals('input_1', 'input 1')
273
            ->assertFieldNotEquals('Input 1', 'invalid')
274
            ->assertFieldNotEquals('input1', 'invalid')
275
            ->assertFieldNotEquals('input_1', 'invalid')
276
            ->assertChecked('Input 3')
277
            ->assertChecked('input3')
278
            ->assertChecked('input_3')
279
            ->assertNotChecked('Input 2')
280
            ->assertNotChecked('input2')
281
            ->assertNotChecked('input_2')
282
            ->assertSelected('Input 4', 'option 1')
283
            ->assertSelected('input4', 'option 1')
284
            ->assertSelected('input_4', 'option 1')
285
            ->assertSelected('Input 7', 'option 1')
286
            ->assertSelected('input7', 'option 1')
287
            ->assertSelected('input_7[]', 'option 1')
288
            ->assertSelected('Input 7', 'option 3')
289
            ->assertSelected('input7', 'option 3')
290
            ->assertSelected('input_7[]', 'option 3')
291
            ->assertNotSelected('Input 4', 'option 2')
292
            ->assertNotSelected('input4', 'option 2')
293
            ->assertNotSelected('input_4', 'option 2')
294
            ->assertNotSelected('Input 7', 'option 2')
295
            ->assertNotSelected('input7', 'option 2')
296
            ->assertNotSelected('input_7[]', 'option 2')
297
        ;
298
    }
299
300
    /**
301
     * @test
302
     */
303
    public function link_action(): void
304
    {
305
        $this->browser()
306
            ->visit('/page1')
307
            ->follow('a link')
308
            ->assertOn('/page2')
309
            ->visit('/page1')
310
            ->click('a link')
311
            ->assertOn('/page2')
312
        ;
313
    }
314
315
    /**
316
     * @test
317
     */
318
    public function form_actions_by_field_label(): void
319
    {
320
        $this->browser()
321
            ->visit('/page1')
322
            ->fillField('Input 1', 'Kevin')
323
            ->checkField('Input 2')
324
            ->uncheckField('Input 3')
325
            ->selectFieldOption('Input 4', 'option 2')
326
            ->attachFile('Input 5', __FILE__)
327
            ->selectFieldOptions('Input 6', ['option 1', 'option 3'])
328
            ->click('Submit')
329
            ->assertOn('/submit-form')
330
            ->assertContains('"input_1":"Kevin"')
331
            ->assertContains('"input_2":"on"')
332
            ->assertNotContains('"input_3')
333
            ->assertContains('"input_4":"option 2"')
334
            ->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

334
            ->assertContains(\sprintf('"input_5":"%s"', /** @scrutinizer ignore-type */ \pathinfo(__FILE__, \PATHINFO_BASENAME)))
Loading history...
335
            ->assertContains('"input_6":["option 1","option 3"]')
336
        ;
337
    }
338
339
    /**
340
     * @test
341
     */
342
    public function form_actions_by_field_id(): void
343
    {
344
        $this->browser()
345
            ->visit('/page1')
346
            ->fillField('input1', 'Kevin')
347
            ->checkField('input2')
348
            ->uncheckField('input3')
349
            ->selectFieldOption('input4', 'option 2')
350
            ->attachFile('input5', __FILE__)
351
            ->selectFieldOptions('input6', ['option 1', 'option 3'])
352
            ->click('Submit')
353
            ->assertOn('/submit-form')
354
            ->assertContains('"input_1":"Kevin"')
355
            ->assertContains('"input_2":"on"')
356
            ->assertNotContains('"input_3')
357
            ->assertContains('"input_4":"option 2"')
358
            ->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

358
            ->assertContains(\sprintf('"input_5":"%s"', /** @scrutinizer ignore-type */ \pathinfo(__FILE__, \PATHINFO_BASENAME)))
Loading history...
359
            ->assertContains('"input_6":["option 1","option 3"]')
360
        ;
361
    }
362
363
    /**
364
     * @test
365
     */
366
    public function form_actions_by_field_name(): void
367
    {
368
        $this->browser()
369
            ->visit('/page1')
370
            ->fillField('input_1', 'Kevin')
371
            ->checkField('input_2')
372
            ->uncheckField('input_3')
373
            ->selectFieldOption('input_4', 'option 2')
374
            ->attachFile('input_5', __FILE__)
375
            ->selectFieldOptions('input_6[]', ['option 1', 'option 3'])
376
            ->click('Submit')
377
            ->assertOn('/submit-form')
378
            ->assertContains('"input_1":"Kevin"')
379
            ->assertContains('"input_2":"on"')
380
            ->assertNotContains('"input_3')
381
            ->assertContains('"input_4":"option 2"')
382
            ->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

382
            ->assertContains(\sprintf('"input_5":"%s"', /** @scrutinizer ignore-type */ \pathinfo(__FILE__, \PATHINFO_BASENAME)))
Loading history...
383
            ->assertContains('"input_6":["option 1","option 3"]')
384
        ;
385
    }
386
387
    /**
388
     * @test
389
     */
390
    public function can_dump_html_element(): void
391
    {
392
        $output = self::catchVarDumperOutput(function() {
393
            $this->browser()
394
                ->visit('/page1')
395
                ->dump('p#link')
396
            ;
397
        });
398
399
        $this->assertCount(1, $output);
400
        $this->assertSame('<a href="/page2">a link</a> not a link', $output[0]);
401
    }
402
403
    /**
404
     * @test
405
     */
406
    public function if_dump_selector_matches_multiple_elements_all_are_dumped(): void
407
    {
408
        $output = self::catchVarDumperOutput(function() {
409
            $this->browser()
410
                ->visit('/page1')
411
                ->dump('li')
412
            ;
413
        });
414
415
        $this->assertCount(2, $output);
416
        $this->assertSame('list 1', $output[0]);
417
        $this->assertSame('list 2', $output[1]);
418
    }
419
420
    /**
421
     * @test
422
     */
423
    public function can_access_the_html_crawler(): void
424
    {
425
        $crawler = $this->browser()
426
            ->visit('/page1')
427
            ->response()
428
            ->assertHtml()
429
            ->crawler()
430
            ->filter('ul li')
431
        ;
432
433
        $this->assertCount(2, $crawler);
434
    }
435
436
    protected static function catchFileContents(string $expectedFile, callable $callback): string
437
    {
438
        (new Filesystem())->remove($expectedFile);
439
440
        $callback();
441
442
        self::assertFileExists($expectedFile);
443
444
        return \file_get_contents($expectedFile);
445
    }
446
447
    protected static function catchVarDumperOutput(callable $callback): array
448
    {
449
        $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...
450
451
        VarDumper::setHandler(function($var) use (&$output) {
452
            $output[] = $var;
453
        });
454
455
        $callback();
456
457
        // reset to default handler
458
        VarDumper::setHandler();
459
460
        // a null value is added to the beginning
461
        return \array_values(\array_filter($output));
462
    }
463
464
    abstract protected function browser(): Browser;
465
}
466