Passed
Push — 1.x ( bfdb67...14afbb )
by Kevin
08:20
created

BrowserTests::form_actions_by_field_id()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

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