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

BrowserTests::can_access_the_html_crawler()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

137
                $this->/** @scrutinizer ignore-call */ 
138
                       assertInstanceOf(Browser::class, $browser1);
Loading history...
138
                $this->assertInstanceOf(Browser::class, $browser2);
139
                $this->assertInstanceOf(\get_class($browser), $browser1);
140
                $this->assertInstanceOf(\get_class($browser), $browser2);
141
                $this->assertInstanceOf(TestComponent1::class, $component1);
142
                $this->assertInstanceOf(TestComponent2::class, $component2);
143
            })
144
        ;
145
    }
146
147
    /**
148
     * @test
149
     */
150
    public function invalid_use_callback_parameter_throws_type_error(): void
151
    {
152
        $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

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

154
        $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...
155
    }
156
157
    /**
158
     * @test
159
     */
160
    public function redirects_are_followed_by_default(): void
161
    {
162
        $this->browser()
163
            ->visit('/redirect1')
164
            ->assertOn('/page1')
165
        ;
166
    }
167
168
    /**
169
     * @test
170
     */
171
    public function content_assertions(): void
172
    {
173
        $this->browser()
174
            ->visit('/page1')
175
            ->assertContains('h1 title')
176
            ->assertNotContains('invalid text')
177
        ;
178
    }
179
180
    /**
181
     * @test
182
     */
183
    public function can_dump_response(): void
184
    {
185
        $output = self::catchVarDumperOutput(function() {
186
            $this->browser()
187
                ->visit('/page1')
188
                ->dump()
189
            ;
190
        });
191
192
        $this->assertStringContainsString('/page1', $output[0]);
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

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

316
            ->assertContains(\sprintf('"input_5":"%s"', /** @scrutinizer ignore-type */ \pathinfo(__FILE__, \PATHINFO_BASENAME)))
Loading history...
317
            ->assertContains('"input_6":["option 1","option 3"]')
318
        ;
319
    }
320
321
    /**
322
     * @test
323
     */
324
    public function form_actions_by_field_id(): void
325
    {
326
        $this->browser()
327
            ->visit('/page1')
328
            ->fillField('input1', 'Kevin')
329
            ->checkField('input2')
330
            ->uncheckField('input3')
331
            ->selectFieldOption('input4', 'option 2')
332
            ->attachFile('input5', __FILE__)
333
            ->selectFieldOptions('input6', ['option 1', 'option 3'])
334
            ->click('Submit')
335
            ->assertOn('/submit-form')
336
            ->assertContains('"input_1":"Kevin"')
337
            ->assertContains('"input_2":"on"')
338
            ->assertNotContains('"input_3')
339
            ->assertContains('"input_4":"option 2"')
340
            ->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

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

364
            ->assertContains(\sprintf('"input_5":"%s"', /** @scrutinizer ignore-type */ \pathinfo(__FILE__, \PATHINFO_BASENAME)))
Loading history...
365
            ->assertContains('"input_6":["option 1","option 3"]')
366
        ;
367
    }
368
369
    /**
370
     * @test
371
     */
372
    public function can_dump_html_element(): void
373
    {
374
        $output = self::catchVarDumperOutput(function() {
375
            $this->browser()
376
                ->visit('/page1')
377
                ->dump('p#link')
378
            ;
379
        });
380
381
        $this->assertCount(1, $output);
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

381
        $this->/** @scrutinizer ignore-call */ 
382
               assertCount(1, $output);

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...
382
        $this->assertSame('<a href="/page2">a link</a> not a link', $output[0]);
383
    }
384
385
    /**
386
     * @test
387
     */
388
    public function if_dump_selector_matches_multiple_elements_all_are_dumped(): void
389
    {
390
        $output = self::catchVarDumperOutput(function() {
391
            $this->browser()
392
                ->visit('/page1')
393
                ->dump('li')
394
            ;
395
        });
396
397
        $this->assertCount(2, $output);
398
        $this->assertSame('list 1', $output[0]);
399
        $this->assertSame('list 2', $output[1]);
400
    }
401
402
    /**
403
     * @test
404
     */
405
    public function can_access_the_html_crawler(): void
406
    {
407
        $crawler = $this->browser()
408
            ->visit('/page1')
409
            ->crawler()
410
            ->filter('ul li')
411
        ;
412
413
        $this->assertCount(2, $crawler);
414
    }
415
416
    protected static function catchFileContents(string $expectedFile, callable $callback): string
417
    {
418
        (new Filesystem())->remove($expectedFile);
419
420
        $callback();
421
422
        self::assertFileExists($expectedFile);
423
424
        return \file_get_contents($expectedFile);
425
    }
426
427
    protected static function catchVarDumperOutput(callable $callback): array
428
    {
429
        $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...
430
431
        VarDumper::setHandler(function($var) use (&$output) {
432
            $output[] = $var;
433
        });
434
435
        $callback();
436
437
        // reset to default handler
438
        VarDumper::setHandler();
439
440
        // a null value is added to the beginning
441
        return \array_values(\array_filter($output));
442
    }
443
444
    abstract protected function browser(): Browser;
445
}
446