Skip to content

Commit e238faa

Browse files
committed
Moved the escaping of the xpath locator to the NamedSelector
The NamedSelector has no valid reason to expect receiving an escaped value for the XPath locator. Calling code has no reason to know that the locator will be inserted in an XPath query. The NamedSelector still support passing an escaped value for BC reasons but this is deprecated. The method SelectorsHandler::xpathLiteral and Element::getSelectorsHandler have been deprecated as well.
1 parent 1125c2f commit e238faa

File tree

12 files changed

+67
-39
lines changed

12 files changed

+67
-39
lines changed

driver-testsuite/tests/Form/SelectTest.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ public function testElementSelectedStateCheck($selectName, $optionValue, $option
6666
$session->visit($this->pathTo('/multiselect_form.html'));
6767
$select = $webAssert->fieldExists($selectName);
6868

69-
$optionValueEscaped = $session->getSelectorsHandler()->xpathLiteral($optionValue);
70-
$option = $webAssert->elementExists('named', array('option', $optionValueEscaped));
69+
$option = $webAssert->elementExists('named', array('option', $optionValue));
7170

7271
$this->assertFalse($option->isSelected());
7372
$select->selectOption($optionText);

driver-testsuite/tests/TestCase.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,6 @@ protected function getAssertSession()
102102
*/
103103
protected function findById($id)
104104
{
105-
$id = $this->getSession()->getSelectorsHandler()->xpathLiteral($id);
106-
107105
return $this->getAssertSession()->elementExists('named', array('id', $id));
108106
}
109107

src/Element/DocumentElement.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ public function getContent()
4646
*/
4747
public function hasContent($content)
4848
{
49-
return $this->has('named', array(
50-
'content', $this->getSelectorsHandler()->xpathLiteral($content),
51-
));
49+
return $this->has('named', array('content', $content));
5250
}
5351
}

src/Element/Element.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ protected function getDriver()
8585
* Returns selectors handler.
8686
*
8787
* @return SelectorsHandler
88+
*
89+
* @deprecated Accessing the selectors handler in the element is deprecated as of 1.7 and will be impossible in 2.0.
8890
*/
8991
protected function getSelectorsHandler()
9092
{
@@ -156,7 +158,7 @@ public function findAll($selector, $locator)
156158
return $items;
157159
}
158160

159-
$xpath = $this->getSelectorsHandler()->selectorToXpath($selector, $locator);
161+
$xpath = $this->selectorsHandler->selectorToXpath($selector, $locator);
160162
$xpath = $this->xpathManipulator->prepend($xpath, $this->getXpath());
161163

162164
return $this->getDriver()->find($xpath);

src/Element/NodeElement.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,7 @@ public function selectOption($option, $multiple = false)
228228
return;
229229
}
230230

231-
$opt = $this->find('named', array(
232-
'option', $this->getSelectorsHandler()->xpathLiteral($option),
233-
));
231+
$opt = $this->find('named', array('option', $option));
234232

235233
if (null === $opt) {
236234
throw $this->elementNotFound('select option', 'value|text', $option);

src/Element/TraversableElement.php

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ abstract class TraversableElement extends Element
2828
*/
2929
public function findById($id)
3030
{
31-
$id = $this->getSelectorsHandler()->xpathLiteral($id);
32-
3331
return $this->find('named', array('id', $id));
3432
}
3533

@@ -54,9 +52,7 @@ public function hasLink($locator)
5452
*/
5553
public function findLink($locator)
5654
{
57-
return $this->find('named', array(
58-
'link', $this->getSelectorsHandler()->xpathLiteral($locator),
59-
));
55+
return $this->find('named', array('link', $locator));
6056
}
6157

6258
/**
@@ -98,9 +94,7 @@ public function hasButton($locator)
9894
*/
9995
public function findButton($locator)
10096
{
101-
return $this->find('named', array(
102-
'button', $this->getSelectorsHandler()->xpathLiteral($locator),
103-
));
97+
return $this->find('named', array('button', $locator));
10498
}
10599

106100
/**
@@ -142,9 +136,7 @@ public function hasField($locator)
142136
*/
143137
public function findField($locator)
144138
{
145-
return $this->find('named', array(
146-
'field', $this->getSelectorsHandler()->xpathLiteral($locator),
147-
));
139+
return $this->find('named', array('field', $locator));
148140
}
149141

150142
/**
@@ -245,9 +237,7 @@ public function uncheckField($locator)
245237
*/
246238
public function hasSelect($locator)
247239
{
248-
return $this->has('named', array(
249-
'select', $this->getSelectorsHandler()->xpathLiteral($locator),
250-
));
240+
return $this->has('named', array('select', $locator));
251241
}
252242

253243
/**
@@ -281,9 +271,7 @@ public function selectFieldOption($locator, $value, $multiple = false)
281271
*/
282272
public function hasTable($locator)
283273
{
284-
return $this->has('named', array(
285-
'table', $this->getSelectorsHandler()->xpathLiteral($locator),
286-
));
274+
return $this->has('named', array('table', $locator));
287275
}
288276

289277
/**

src/Selector/NamedSelector.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
namespace Behat\Mink\Selector;
1212

13+
use Behat\Mink\Selector\Xpath\Escaper;
14+
1315
/**
1416
* Named selectors engine. Uses registered XPath selectors to create new expressions.
1517
*
@@ -157,12 +159,15 @@ class NamedSelector implements SelectorInterface
157159
.//*[%idOrNameMatch%]
158160
XPATH
159161
);
162+
private $xpathEscaper;
160163

161164
/**
162165
* Creates selector instance.
163166
*/
164167
public function __construct()
165168
{
169+
$this->xpathEscaper = new Escaper();
170+
166171
foreach ($this->replacements as $from => $to) {
167172
$this->replacements[$from] = strtr($to, $this->replacements);
168173
}
@@ -217,7 +222,7 @@ public function translateToXPath($locator)
217222
$xpath = $this->selectors[$selector];
218223

219224
if (null !== $locator) {
220-
$xpath = strtr($xpath, array('%locator%' => $locator));
225+
$xpath = strtr($xpath, array('%locator%' => $this->escapeLocator($locator)));
221226
}
222227

223228
return $xpath;
@@ -235,4 +240,24 @@ protected function registerReplacement($from, $to)
235240
{
236241
$this->replacements[$from] = $to;
237242
}
243+
244+
private function escapeLocator($locator)
245+
{
246+
// If the locator looks like an escaped one, don't escape it again for BC reasons.
247+
if (
248+
preg_match('/^\'[^\']*+\'$/', $locator)
249+
|| (false !== strpos($locator, '\'') && preg_match('/^"[^"]*+"$/', $locator))
250+
|| ((8 < $length = strlen($locator)) && 'concat(' === substr($locator, 0, 7) && ')' === $locator[$length - 1])
251+
) {
252+
trigger_error(
253+
'Passing an excaped locator to the named selector is deprecated as of 1.7 and will be removed in 2.0.'
254+
.' Pass the raw value instead.',
255+
E_USER_DEPRECATED
256+
);
257+
258+
return $locator;
259+
}
260+
261+
return $this->xpathEscaper->escapeLiteral($locator);
262+
}
238263
}

src/Selector/SelectorsHandler.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,22 @@ public function selectorToXpath($selector, $locator)
114114
/**
115115
* Translates string to XPath literal.
116116
*
117+
* @deprecated since Mink 1.7. Use \Behat\Mink\Selector\Xpath\Escaper::escapeLiteral when building Xpath
118+
* or pass the unescaped value when using the named selector.
119+
*
117120
* @param string $s
118121
*
119122
* @return string
120123
*/
121124
public function xpathLiteral($s)
122125
{
126+
trigger_error(
127+
'The '.__METHOD__.' method is deprecated as of 1.7 and will be removed in 2.0.'
128+
.' Use \Behat\Mink\Selector\Xpath\Escaper::escapeLiteral instead when building Xpath'
129+
.' or pass the unescaped value when using the named selector.',
130+
E_USER_DEPRECATED
131+
);
132+
123133
return $this->escaper->escapeLiteral($s);
124134
}
125135
}

tests/Element/ElementTest.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,6 @@ protected function setUp()
3636

3737
$this->selectors = $this->getMockBuilder('Behat\Mink\Selector\SelectorsHandler')->getMock();
3838
$this->session = new Session($this->driver, $this->selectors);
39-
40-
$this->selectors
41-
->expects($this->any())
42-
->method('xpathLiteral')
43-
->will($this->returnArgument(0));
4439
}
4540

4641
protected function mockNamedFinder($xpath, array $results, $locator, $times = 2)

tests/Selector/NamedSelectorTest.php

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
namespace Behat\Mink\Tests\Selector;
44

55
use Behat\Mink\Selector\NamedSelector;
6-
use Behat\Mink\Selector\SelectorsHandler;
6+
use Behat\Mink\Selector\Xpath\Escaper;
77

88
abstract class NamedSelectorTest extends \PHPUnit_Framework_TestCase
99
{
@@ -42,10 +42,6 @@ public function testSelectors($fixtureFile, $selector, $locator, $expectedExactC
4242
$dom = new \DOMDocument('1.0', 'UTF-8');
4343
$dom->loadHTML(file_get_contents(__DIR__.'/fixtures/'.$fixtureFile));
4444

45-
// Escape the locator as Mink 1.x expects the caller of the NamedSelector to handle it
46-
$selectorsHandler = new SelectorsHandler();
47-
$locator = $selectorsHandler->xpathLiteral($locator);
48-
4945
$namedSelector = $this->getSelector();
5046

5147
$xpath = $namedSelector->translateToXPath(array($selector, $locator));
@@ -56,6 +52,20 @@ public function testSelectors($fixtureFile, $selector, $locator, $expectedExactC
5652
$this->assertEquals($expectedCount, $nodeList->length);
5753
}
5854

55+
/**
56+
* @dataProvider getSelectorTests
57+
*/
58+
public function testEscapedSelectors($fixtureFile, $selector, $locator, $expectedExactCount, $expectedPartialCount = null)
59+
{
60+
// Escape the locator as Mink 1.x expects the caller of the NamedSelector to handle it
61+
$escaper = new Escaper();
62+
$locator = $escaper->escapeLiteral($locator);
63+
64+
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
65+
66+
$this->testSelectors($fixtureFile, $selector, $locator, $expectedExactCount, $expectedPartialCount);
67+
}
68+
5969
public function getSelectorTests()
6070
{
6171
$fieldCount = 8; // fields without `type` attribute
@@ -122,6 +132,7 @@ public function getSelectorTests()
122132

123133
// 3 matches, because matches every HTML node in path: html > body > div
124134
'content' => array('test.html', 'content', 'content-text', 1, 4),
135+
'content with quotes' => array('test.html', 'content', 'some "quoted" content', 1, 3),
125136

126137
'select (name/label)' => array('test.html', 'select', 'the-field', 3),
127138
'select (with-id)' => array('test.html', 'select', 'the-field-select', 1),

0 commit comments

Comments
 (0)