Skip to content

Commit f02a91d

Browse files
authored
Merge pull request #27 from kbond/response-public
Make Browser::response() public (crawler access option 1)
2 parents b6efad4 + 038ee12 commit f02a91d

12 files changed

+196
-41
lines changed

README.md

+28
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,34 @@ $browser
224224
->dd('foo') // if json response, array key
225225
->dd('foo.*.baz') // if json response, JMESPath notation can be used
226226
;
227+
228+
// "response" object
229+
$response = $browser->response();
230+
$response->statusCode(); // ie 200
231+
$response->body(); // the raw body
232+
233+
// html response
234+
$response->assertHtml()->crawler(); // Symfony\Component\DomCrawler\Crawler
235+
236+
// xml response (not available with PantherBrowser)
237+
$response->assertXml()->crawler(); // Symfony\Component\DomCrawler\Crawler
238+
239+
// json response (not available with PantherBrowser)
240+
$response->assertJson()->json(); // response content json decoded
241+
$response->assertJson()->search('some.selector'); // search the json using JMESPath expression
242+
243+
// use the response without breaking the fluid browser session
244+
$browser
245+
->visit('/some/page')
246+
->use(function(\Zenstruck\Browser\Response $response) {
247+
$response->statusCode(); // ie 200
248+
$response->assertJson()->json(); // response content json decoded
249+
})
250+
->use(function(\Zenstruck\Browser\Response\JsonResponse $response) {
251+
// inject the expected response type
252+
$response->json(); // response content json decoded
253+
})
254+
;
227255
```
228256

229257
### KernelBrowser/HttpBrowser

src/Browser.php

+7-6
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ final public function use(callable $callback): self
111111
Parameter::union(
112112
Parameter::untyped($this),
113113
Parameter::typed(self::class, $this),
114-
Parameter::typed(Component::class, Parameter::factory(fn(string $class) => new $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()))
115116
)
116117
);
117118

@@ -415,6 +416,11 @@ public function savedArtifacts(): array
415416
return ['Saved Source Files' => $this->savedSources];
416417
}
417418

419+
public function response(): Response
420+
{
421+
return Response::createFor($this->minkSession());
422+
}
423+
418424
/**
419425
* @internal
420426
*/
@@ -439,11 +445,6 @@ final protected function documentElement(): DocumentElement
439445
return $this->minkSession()->getPage();
440446
}
441447

442-
protected function response(): Response
443-
{
444-
return Response::createFor($this->minkSession());
445-
}
446-
447448
/**
448449
* @internal
449450
*

src/Browser/BrowserKitBrowser.php

+1-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use Symfony\Component\HttpKernel\Profiler\Profile;
88
use Zenstruck\Browser;
99
use Zenstruck\Browser\Mink\BrowserKitDriver;
10-
use Zenstruck\Browser\Response\JsonResponse;
1110

1211
/**
1312
* @author Kevin Bond <[email protected]>
@@ -220,11 +219,7 @@ final public function assertJson(string $expectedContentType = 'json'): self
220219
*/
221220
final public function assertJsonMatches(string $expression, $expected): self
222221
{
223-
if (!$this->response() instanceof JsonResponse) {
224-
PHPUnit::fail('Not a json response.');
225-
}
226-
227-
PHPUnit::assertSame($expected, $this->response()->search($expression));
222+
PHPUnit::assertSame($expected, $this->response()->assertJson()->search($expression));
228223

229224
return $this;
230225
}

src/Browser/PantherBrowser.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ final public function savedArtifacts(): array
232232
);
233233
}
234234

235-
final protected function response(): PantherResponse
235+
final public function response(): PantherResponse
236236
{
237237
return new PantherResponse($this->minkSession());
238238
}

src/Browser/Response.php

+55
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Zenstruck\Browser;
44

55
use Behat\Mink\Session;
6+
use PHPUnit\Framework\Assert as PHPUnit;
67
use Symfony\Component\VarDumper\VarDumper;
78
use Zenstruck\Browser\Response\HtmlResponse;
89
use Zenstruck\Browser\Response\JsonResponse;
@@ -15,6 +16,9 @@ class Response
1516
{
1617
private Session $session;
1718

19+
/**
20+
* @internal
21+
*/
1822
final public function __construct(Session $session)
1923
{
2024
$this->session = $session;
@@ -25,6 +29,9 @@ final public function statusCode(): int
2529
return $this->session->getStatusCode();
2630
}
2731

32+
/**
33+
* @internal
34+
*/
2835
final public static function createFor(Session $session): self
2936
{
3037
$contentType = (string) $session->getResponseHeader('content-type');
@@ -49,21 +56,60 @@ final public function body(): string
4956
return $this->session->getPage()->getContent();
5057
}
5158

59+
final public function assertJson(): JsonResponse
60+
{
61+
if (!$this instanceof JsonResponse) {
62+
PHPUnit::fail('Not a json response.');
63+
}
64+
65+
return $this;
66+
}
67+
68+
final public function assertXml(): XmlResponse
69+
{
70+
if (!$this instanceof XmlResponse) {
71+
PHPUnit::fail('Not an xml response.');
72+
}
73+
74+
return $this;
75+
}
76+
77+
final public function assertHtml(): HtmlResponse
78+
{
79+
if (!$this instanceof HtmlResponse) {
80+
PHPUnit::fail('Not an html response.');
81+
}
82+
83+
return $this;
84+
}
85+
86+
/**
87+
* @internal
88+
*/
5289
final public function raw(): string
5390
{
5491
return "{$this->rawMetadata()}\n{$this->rawBody()}";
5592
}
5693

94+
/**
95+
* @internal
96+
*/
5797
final public function isSuccessful(): bool
5898
{
5999
return $this->statusCode() >= 200 && $this->statusCode() < 300;
60100
}
61101

102+
/**
103+
* @internal
104+
*/
62105
final public function isRedirect(): bool
63106
{
64107
return $this->statusCode() >= 300 && $this->statusCode() < 400;
65108
}
66109

110+
/**
111+
* @internal
112+
*/
67113
public function dump(?string $selector = null): void
68114
{
69115
if (null !== $selector) {
@@ -73,11 +119,17 @@ public function dump(?string $selector = null): void
73119
VarDumper::dump($this->raw());
74120
}
75121

122+
/**
123+
* @internal
124+
*/
76125
final protected function session(): Session
77126
{
78127
return $this->session;
79128
}
80129

130+
/**
131+
* @internal
132+
*/
81133
protected function rawMetadata(): string
82134
{
83135
$ret = "URL: {$this->session->getCurrentUrl()} ({$this->statusCode()})\n\n";
@@ -91,6 +143,9 @@ protected function rawMetadata(): string
91143
return $ret;
92144
}
93145

146+
/**
147+
* @internal
148+
*/
94149
protected function rawBody(): string
95150
{
96151
return $this->body();

src/Browser/Response/HtmlResponse.php

+18-8
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Zenstruck\Browser\Response;
44

5-
use Behat\Mink\Element\NodeElement;
5+
use Symfony\Component\DomCrawler\Crawler;
66
use Symfony\Component\VarDumper\VarDumper;
77
use Zenstruck\Browser\Response;
88

@@ -11,23 +11,33 @@
1111
*/
1212
class HtmlResponse extends Response
1313
{
14-
public function dump(?string $selector = null): void
14+
public function crawler(): Crawler
15+
{
16+
$crawler = new Crawler();
17+
$crawler->addHtmlContent($this->body());
18+
19+
return $crawler;
20+
}
21+
22+
/**
23+
* @internal
24+
*/
25+
final public function dump(?string $selector = null): void
1526
{
1627
if (null === $selector) {
1728
parent::dump();
1829

1930
return;
2031
}
2132

22-
$elements = $this->session()->getPage()->findAll('css', $selector);
23-
$elements = \array_map(static fn(NodeElement $node) => $node->getHtml(), $elements);
33+
$elements = $this->crawler()->filter($selector);
2434

25-
if (empty($elements)) {
35+
if (0 === $elements->count()) {
2636
throw new \RuntimeException("Element \"{$selector}\" not found.");
2737
}
2838

29-
foreach ($elements as $element) {
30-
VarDumper::dump($element);
31-
}
39+
$elements->each(function(Crawler $node) {
40+
VarDumper::dump($node->html());
41+
});
3242
}
3343
}

src/Browser/Response/JsonResponse.php

+6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ public function json()
2020
return \json_decode($this->body(), true, 512, \JSON_THROW_ON_ERROR);
2121
}
2222

23+
/**
24+
* @internal
25+
*/
2326
public function dump(?string $selector = null): void
2427
{
2528
if (null === $selector) {
@@ -34,6 +37,9 @@ public function search(string $selector)
3437
return search($selector, $this->json());
3538
}
3639

40+
/**
41+
* @internal
42+
*/
3743
protected function rawBody(): string
3844
{
3945
return \json_encode($this->json(), \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_THROW_ON_ERROR);

src/Browser/Response/PantherResponse.php

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
/**
66
* @author Kevin Bond <[email protected]>
7+
*
8+
* @internal
79
*/
810
final class PantherResponse extends HtmlResponse
911
{

src/Browser/Response/XmlResponse.php

+12-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@
1111
*/
1212
final class XmlResponse extends Response
1313
{
14+
public function crawler(): Crawler
15+
{
16+
$dom = new \DOMDocument();
17+
$dom->loadXML($this->body());
18+
19+
return new Crawler($dom);
20+
}
21+
22+
/**
23+
* @internal
24+
*/
1425
public function dump(?string $selector = null): void
1526
{
1627
if (null === $selector) {
@@ -19,10 +30,7 @@ public function dump(?string $selector = null): void
1930
return;
2031
}
2132

22-
$dom = new \DOMDocument();
23-
$dom->loadXML($this->body());
24-
25-
$elements = (new Crawler($dom))->filter($selector);
33+
$elements = $this->crawler()->filter($selector);
2634

2735
if (!\count($elements)) {
2836
throw new \RuntimeException("Element \"{$selector}\" not found.");

tests/BrowserKitBrowserTests.php

+32
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,22 @@ public function can_save_formatted_json_source(): void
367367
$this->assertStringContainsString(' "content": "",', $contents);
368368
}
369369

370+
/**
371+
* @test
372+
*/
373+
public function can_access_json_response(): void
374+
{
375+
$response = $this->browser()
376+
->post('/json', ['json' => $expected = ['foo' => 'bar']])
377+
->assertSuccessful()
378+
->response()
379+
->assertJson()
380+
;
381+
382+
$this->assertSame($expected, $response->json());
383+
$this->assertSame('bar', $response->search('foo'));
384+
}
385+
370386
/**
371387
* @test
372388
*/
@@ -396,4 +412,20 @@ public function can_dump_xml_selector(): void
396412
$this->assertSame('https://www.example.com/page1', $output[0]);
397413
$this->assertSame('https://www.example.com/page2', $output[1]);
398414
}
415+
416+
/**
417+
* @test
418+
*/
419+
public function can_access_the_xml_crawler(): void
420+
{
421+
$crawler = $this->browser()
422+
->visit('/xml')
423+
->response()
424+
->assertXml()
425+
->crawler()
426+
->filter('url loc')
427+
;
428+
429+
$this->assertCount(2, $crawler);
430+
}
399431
}

0 commit comments

Comments
 (0)