Skip to content

Commit 6c88535

Browse files
dunglasOndraM
authored andcommitted
Add utility classes to interact with checkboxes and radio buttons (php-webdriver#545)
* Add an utility class to manipulate checkboxes and radio buttons
1 parent a1784b2 commit 6c88535

File tree

6 files changed

+803
-0
lines changed

6 files changed

+803
-0
lines changed
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
<?php
2+
// Copyright 2004-present Facebook. All Rights Reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
namespace Facebook\WebDriver;
17+
18+
use Facebook\WebDriver\Exception\NoSuchElementException;
19+
use Facebook\WebDriver\Exception\UnexpectedTagNameException;
20+
use Facebook\WebDriver\Exception\WebDriverException;
21+
use Facebook\WebDriver\Support\XPathEscaper;
22+
23+
/**
24+
* Provides helper methods for checkboxes and radio buttons.
25+
*/
26+
abstract class AbstractWebDriverCheckboxOrRadio implements WebDriverSelectInterface
27+
{
28+
/** @var WebDriverElement */
29+
protected $element;
30+
31+
/** @var string */
32+
protected $type;
33+
34+
/** @var string */
35+
protected $name;
36+
37+
public function __construct(WebDriverElement $element)
38+
{
39+
$tagName = $element->getTagName();
40+
if ($tagName !== 'input') {
41+
throw new UnexpectedTagNameException('input', $tagName);
42+
}
43+
44+
$this->name = $element->getAttribute('name');
45+
if ($this->name === null) {
46+
throw new WebDriverException('The input does not have a "name" attribute.');
47+
}
48+
49+
$this->element = $element;
50+
}
51+
52+
public function getOptions()
53+
{
54+
return $this->getRelatedElements();
55+
}
56+
57+
public function getAllSelectedOptions()
58+
{
59+
$selectedElement = [];
60+
foreach ($this->getRelatedElements() as $element) {
61+
if ($element->isSelected()) {
62+
$selectedElement[] = $element;
63+
64+
if (!$this->isMultiple()) {
65+
return $selectedElement;
66+
}
67+
}
68+
}
69+
70+
return $selectedElement;
71+
}
72+
73+
public function getFirstSelectedOption()
74+
{
75+
foreach ($this->getRelatedElements() as $element) {
76+
if ($element->isSelected()) {
77+
return $element;
78+
}
79+
}
80+
81+
throw new NoSuchElementException(
82+
sprintf('No %s are selected', 'radio' === $this->type ? 'radio buttons' : 'checkboxes')
83+
);
84+
}
85+
86+
public function selectByIndex($index)
87+
{
88+
$this->byIndex($index);
89+
}
90+
91+
public function selectByValue($value)
92+
{
93+
$this->byValue($value);
94+
}
95+
96+
public function selectByVisibleText($text)
97+
{
98+
$this->byVisibleText($text);
99+
}
100+
101+
public function selectByVisiblePartialText($text)
102+
{
103+
$this->byVisibleText($text, true);
104+
}
105+
106+
/**
107+
* Selects or deselects a checkbox or a radio button by its value.
108+
*
109+
* @param string $value
110+
* @param bool $select
111+
* @throws NoSuchElementException
112+
*/
113+
protected function byValue($value, $select = true)
114+
{
115+
$matched = false;
116+
foreach ($this->getRelatedElements($value) as $element) {
117+
$select ? $this->selectOption($element) : $this->deselectOption($element);
118+
if (!$this->isMultiple()) {
119+
return;
120+
}
121+
122+
$matched = true;
123+
}
124+
125+
if (!$matched) {
126+
throw new NoSuchElementException(
127+
sprintf('Cannot locate %s with value: %s', $this->type, $value)
128+
);
129+
}
130+
}
131+
132+
/**
133+
* Selects or deselects a checkbox or a radio button by its index.
134+
*
135+
* @param int $index
136+
* @param bool $select
137+
* @throws NoSuchElementException
138+
*/
139+
protected function byIndex($index, $select = true)
140+
{
141+
$elements = $this->getRelatedElements();
142+
if (!isset($elements[$index])) {
143+
throw new NoSuchElementException(sprintf('Cannot locate %s with index: %d', $this->type, $index));
144+
}
145+
146+
$select ? $this->selectOption($elements[$index]) : $this->deselectOption($elements[$index]);
147+
}
148+
149+
/**
150+
* Selects or deselects a checkbox or a radio button by its visible text.
151+
*
152+
* @param string $text
153+
* @param bool $partial
154+
* @param bool $select
155+
*/
156+
protected function byVisibleText($text, $partial = false, $select = true)
157+
{
158+
foreach ($this->getRelatedElements() as $element) {
159+
$normalizeFilter = sprintf(
160+
$partial ? 'contains(normalize-space(.), %s)' : 'normalize-space(.) = %s',
161+
XPathEscaper::escapeQuotes($text)
162+
);
163+
164+
$xpath = 'ancestor::label';
165+
$xpathNormalize = sprintf('%s[%s]', $xpath, $normalizeFilter);
166+
167+
$id = $element->getAttribute('id');
168+
if ($id !== null) {
169+
$idFilter = sprintf('@for = %s', XPathEscaper::escapeQuotes($id));
170+
171+
$xpath .= sprintf(' | //label[%s]', $idFilter);
172+
$xpathNormalize .= sprintf(' | //label[%s and %s]', $idFilter, $normalizeFilter);
173+
}
174+
175+
try {
176+
$element->findElement(WebDriverBy::xpath($xpathNormalize));
177+
} catch (NoSuchElementException $e) {
178+
if ($partial) {
179+
continue;
180+
}
181+
182+
try {
183+
// Since the mechanism of getting the text in xpath is not the same as
184+
// webdriver, use the expensive getText() to check if nothing is matched.
185+
if ($text !== $element->findElement(WebDriverBy::xpath($xpath))->getText()) {
186+
continue;
187+
}
188+
} catch (NoSuchElementException $e) {
189+
continue;
190+
}
191+
}
192+
193+
$select ? $this->selectOption($element) : $this->deselectOption($element);
194+
if (!$this->isMultiple()) {
195+
return;
196+
}
197+
}
198+
}
199+
200+
/**
201+
* Gets checkboxes or radio buttons with the same name.
202+
*
203+
* @param string|null $value
204+
* @return WebDriverElement[]
205+
*/
206+
protected function getRelatedElements($value = null)
207+
{
208+
$valueSelector = $value ? sprintf(' and @value = %s', XPathEscaper::escapeQuotes($value)) : '';
209+
$formId = $this->element->getAttribute('form');
210+
if ($formId === null) {
211+
$form = $this->element->findElement(WebDriverBy::xpath('ancestor::form'));
212+
213+
$formId = $form->getAttribute('id');
214+
if ($formId === '') {
215+
return $form->findElements(WebDriverBy::xpath(
216+
sprintf('.//input[@name = %s%s]', XPathEscaper::escapeQuotes($this->name), $valueSelector)
217+
));
218+
}
219+
}
220+
221+
return $this->element->findElements(WebDriverBy::xpath(sprintf(
222+
'//form[@id = %1$s]//input[@name = %2$s%3$s] | //input[@form = %1$s and @name = %2$s%3$s]',
223+
XPathEscaper::escapeQuotes($formId),
224+
XPathEscaper::escapeQuotes($this->name),
225+
$valueSelector
226+
)));
227+
}
228+
229+
/**
230+
* Selects a checkbox or a radio button.
231+
*/
232+
protected function selectOption(WebDriverElement $element)
233+
{
234+
if (!$element->isSelected()) {
235+
$element->click();
236+
}
237+
}
238+
239+
/**
240+
* Deselects a checkbox or a radio button.
241+
*/
242+
protected function deselectOption(WebDriverElement $element)
243+
{
244+
if ($element->isSelected()) {
245+
$element->click();
246+
}
247+
}
248+
}

lib/WebDriverCheckboxes.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
// Copyright 2004-present Facebook. All Rights Reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
namespace Facebook\WebDriver;
17+
18+
use Facebook\WebDriver\Exception\WebDriverException;
19+
20+
/**
21+
* Provides helper methods for checkboxes.
22+
*/
23+
class WebDriverCheckboxes extends AbstractWebDriverCheckboxOrRadio
24+
{
25+
public function __construct(WebDriverElement $element)
26+
{
27+
parent::__construct($element);
28+
29+
$this->type = $element->getAttribute('type');
30+
if ($this->type !== 'checkbox') {
31+
throw new WebDriverException('The input must be of type "checkbox".');
32+
}
33+
}
34+
35+
public function isMultiple()
36+
{
37+
return true;
38+
}
39+
40+
public function deselectAll()
41+
{
42+
foreach ($this->getRelatedElements() as $checkbox) {
43+
$this->deselectOption($checkbox);
44+
}
45+
}
46+
47+
public function deselectByIndex($index)
48+
{
49+
$this->byIndex($index, false);
50+
}
51+
52+
public function deselectByValue($value)
53+
{
54+
$this->byValue($value, false);
55+
}
56+
57+
public function deselectByVisibleText($text)
58+
{
59+
$this->byVisibleText($text, false, false);
60+
}
61+
62+
public function deselectByVisiblePartialText($text)
63+
{
64+
$this->byVisibleText($text, true, false);
65+
}
66+
}

lib/WebDriverRadios.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
// Copyright 2004-present Facebook. All Rights Reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
namespace Facebook\WebDriver;
17+
18+
use Facebook\WebDriver\Exception\UnsupportedOperationException;
19+
use Facebook\WebDriver\Exception\WebDriverException;
20+
21+
/**
22+
* Provides helper methods for radio buttons.
23+
*/
24+
class WebDriverRadios extends AbstractWebDriverCheckboxOrRadio
25+
{
26+
public function __construct(WebDriverElement $element)
27+
{
28+
parent::__construct($element);
29+
30+
$this->type = $element->getAttribute('type');
31+
if ($this->type !== 'radio') {
32+
throw new WebDriverException('The input must be of type "radio".');
33+
}
34+
}
35+
36+
public function isMultiple()
37+
{
38+
return false;
39+
}
40+
41+
public function deselectAll()
42+
{
43+
throw new UnsupportedOperationException('You cannot deselect radio buttons');
44+
}
45+
46+
public function deselectByIndex($index)
47+
{
48+
throw new UnsupportedOperationException('You cannot deselect radio buttons');
49+
}
50+
51+
public function deselectByValue($value)
52+
{
53+
throw new UnsupportedOperationException('You cannot deselect radio buttons');
54+
}
55+
56+
public function deselectByVisibleText($text)
57+
{
58+
throw new UnsupportedOperationException('You cannot deselect radio buttons');
59+
}
60+
61+
public function deselectByVisiblePartialText($text)
62+
{
63+
throw new UnsupportedOperationException('You cannot deselect radio buttons');
64+
}
65+
}

0 commit comments

Comments
 (0)