Skip to content

Commit aeda2f1

Browse files
authored
Added support for matches() and closest() in Crawler (#524)
* Added support for `matches()` and `closest()` in Crawler * cs
1 parent dc57282 commit aeda2f1

File tree

3 files changed

+89
-0
lines changed

3 files changed

+89
-0
lines changed

src/DomCrawler/Crawler.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace Symfony\Component\Panther\DomCrawler;
1515

16+
use function array_merge;
1617
use Facebook\WebDriver\Exception\NoSuchElementException;
1718
use Facebook\WebDriver\WebDriver;
1819
use Facebook\WebDriver\WebDriverBy;
@@ -132,6 +133,33 @@ public function siblings(): static
132133
return $this->createSubCrawlerFromXpath('(preceding-sibling::* | following-sibling::*)');
133134
}
134135

136+
public function matches(string $selector): bool
137+
{
138+
$converter = $this->createCssSelectorConverter();
139+
$xpath = $converter->toXPath($selector, 'self::');
140+
141+
return $this->filterXPath($xpath)->count() > 0;
142+
}
143+
144+
public function closest(string $selector): ?self
145+
{
146+
$converter = $this->createCssSelectorConverter();
147+
$xpath = WebDriverBy::xpath($converter->toXPath($selector, 'self::'));
148+
149+
/** @var WebDriverElement[] $elements */
150+
$elements = [...$this->elements, ...$this->ancestors()->elements];
151+
foreach ($elements as $element) {
152+
try {
153+
$element->findElement($xpath);
154+
155+
return $this->createSubCrawler([$element]);
156+
} catch (NoSuchElementException) {
157+
}
158+
}
159+
160+
return null;
161+
}
162+
135163
public function nextAll(): static
136164
{
137165
return $this->createSubCrawlerFromXpath('following-sibling::*');

tests/DomCrawler/CrawlerTest.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,50 @@ public function testSiblings(callable $clientFactory): void
156156
$this->assertSame(['Main', 'Sibling 2', 'Sibling 3'], $texts);
157157
}
158158

159+
/**
160+
* @dataProvider clientFactoryProvider
161+
*/
162+
public function testMatches(callable $clientFactory): void
163+
{
164+
$crawler = $this->request($clientFactory, '/basic.html');
165+
$p = $crawler->filter('#a-sibling');
166+
167+
$this->assertTrue($p->matches('#a-sibling'));
168+
$this->assertTrue($p->matches('p'));
169+
$this->assertTrue($p->matches('.foo'));
170+
$this->assertFalse($p->matches('#other-id'));
171+
$this->assertFalse($p->matches('div'));
172+
$this->assertFalse($p->matches('.bar'));
173+
}
174+
175+
/**
176+
* @dataProvider clientFactoryProvider
177+
*/
178+
public function testClosest(callable $clientFactory): void
179+
{
180+
$crawler = $this->request($clientFactory, '/closest.html');
181+
182+
$foo = $crawler->filter('#foo');
183+
184+
$newFoo = $foo->closest('#foo');
185+
$this->assertNotNull($newFoo);
186+
$this->assertSame('newFoo ok', $newFoo->attr('class'));
187+
188+
$lorem1 = $foo->closest('.lorem1');
189+
$this->assertNotNull($lorem1);
190+
$this->assertSame('lorem1 ok', $lorem1->attr('class'));
191+
192+
$lorem2 = $foo->closest('.lorem2');
193+
$this->assertNotNull($lorem2);
194+
$this->assertSame('lorem2 ok', $lorem2->attr('class'));
195+
196+
$lorem3 = $foo->closest('.lorem3');
197+
$this->assertNull($lorem3);
198+
199+
$notFound = $foo->closest('.not-found');
200+
$this->assertNull($notFound);
201+
}
202+
159203
/**
160204
* @dataProvider clientFactoryProvider
161205
*/

tests/fixtures/closest.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<body>
4+
<div class="lorem2 ok">
5+
<div>
6+
<div class="lorem3 ko"></div>
7+
</div>
8+
<div class="lorem1 ok">
9+
<div id="foo" class="newFoo ok">
10+
<div class="lorem1 ko"></div>
11+
</div>
12+
</div>
13+
</div>
14+
<div class="lorem2 ko">
15+
</div>
16+
</body>
17+
</html>

0 commit comments

Comments
 (0)