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