1<?php 2 3namespace Facebook\WebDriver; 4 5use Facebook\WebDriver\Exception\NoSuchElementException; 6use Facebook\WebDriver\Exception\UnexpectedTagNameException; 7use Facebook\WebDriver\Exception\UnsupportedOperationException; 8use Facebook\WebDriver\Support\XPathEscaper; 9 10/** 11 * Models a default HTML `<select>` tag, providing helper methods to select and deselect options. 12 */ 13class WebDriverSelect implements WebDriverSelectInterface 14{ 15 /** @var WebDriverElement */ 16 private $element; 17 /** @var bool */ 18 private $isMulti; 19 20 public function __construct(WebDriverElement $element) 21 { 22 $tag_name = $element->getTagName(); 23 24 if ($tag_name !== 'select') { 25 throw new UnexpectedTagNameException('select', $tag_name); 26 } 27 $this->element = $element; 28 $value = $element->getAttribute('multiple'); 29 $this->isMulti = $value === 'true'; 30 } 31 32 public function isMultiple() 33 { 34 return $this->isMulti; 35 } 36 37 public function getOptions() 38 { 39 return $this->element->findElements(WebDriverBy::tagName('option')); 40 } 41 42 public function getAllSelectedOptions() 43 { 44 $selected_options = []; 45 foreach ($this->getOptions() as $option) { 46 if ($option->isSelected()) { 47 $selected_options[] = $option; 48 49 if (!$this->isMultiple()) { 50 return $selected_options; 51 } 52 } 53 } 54 55 return $selected_options; 56 } 57 58 public function getFirstSelectedOption() 59 { 60 foreach ($this->getOptions() as $option) { 61 if ($option->isSelected()) { 62 return $option; 63 } 64 } 65 66 throw new NoSuchElementException('No options are selected'); 67 } 68 69 public function selectByIndex($index) 70 { 71 foreach ($this->getOptions() as $option) { 72 if ($option->getAttribute('index') === (string) $index) { 73 $this->selectOption($option); 74 75 return; 76 } 77 } 78 79 throw new NoSuchElementException(sprintf('Cannot locate option with index: %d', $index)); 80 } 81 82 public function selectByValue($value) 83 { 84 $matched = false; 85 $xpath = './/option[@value = ' . XPathEscaper::escapeQuotes($value) . ']'; 86 $options = $this->element->findElements(WebDriverBy::xpath($xpath)); 87 88 foreach ($options as $option) { 89 $this->selectOption($option); 90 if (!$this->isMultiple()) { 91 return; 92 } 93 $matched = true; 94 } 95 96 if (!$matched) { 97 throw new NoSuchElementException( 98 sprintf('Cannot locate option with value: %s', $value) 99 ); 100 } 101 } 102 103 public function selectByVisibleText($text) 104 { 105 $matched = false; 106 $xpath = './/option[normalize-space(.) = ' . XPathEscaper::escapeQuotes($text) . ']'; 107 $options = $this->element->findElements(WebDriverBy::xpath($xpath)); 108 109 foreach ($options as $option) { 110 $this->selectOption($option); 111 if (!$this->isMultiple()) { 112 return; 113 } 114 $matched = true; 115 } 116 117 // Since the mechanism of getting the text in xpath is not the same as 118 // webdriver, use the expensive getText() to check if nothing is matched. 119 if (!$matched) { 120 foreach ($this->getOptions() as $option) { 121 if ($option->getText() === $text) { 122 $this->selectOption($option); 123 if (!$this->isMultiple()) { 124 return; 125 } 126 $matched = true; 127 } 128 } 129 } 130 131 if (!$matched) { 132 throw new NoSuchElementException( 133 sprintf('Cannot locate option with text: %s', $text) 134 ); 135 } 136 } 137 138 public function selectByVisiblePartialText($text) 139 { 140 $matched = false; 141 $xpath = './/option[contains(normalize-space(.), ' . XPathEscaper::escapeQuotes($text) . ')]'; 142 $options = $this->element->findElements(WebDriverBy::xpath($xpath)); 143 144 foreach ($options as $option) { 145 $this->selectOption($option); 146 if (!$this->isMultiple()) { 147 return; 148 } 149 $matched = true; 150 } 151 152 if (!$matched) { 153 throw new NoSuchElementException( 154 sprintf('Cannot locate option with text: %s', $text) 155 ); 156 } 157 } 158 159 public function deselectAll() 160 { 161 if (!$this->isMultiple()) { 162 throw new UnsupportedOperationException('You may only deselect all options of a multi-select'); 163 } 164 165 foreach ($this->getOptions() as $option) { 166 $this->deselectOption($option); 167 } 168 } 169 170 public function deselectByIndex($index) 171 { 172 if (!$this->isMultiple()) { 173 throw new UnsupportedOperationException('You may only deselect options of a multi-select'); 174 } 175 176 foreach ($this->getOptions() as $option) { 177 if ($option->getAttribute('index') === (string) $index) { 178 $this->deselectOption($option); 179 180 return; 181 } 182 } 183 } 184 185 public function deselectByValue($value) 186 { 187 if (!$this->isMultiple()) { 188 throw new UnsupportedOperationException('You may only deselect options of a multi-select'); 189 } 190 191 $xpath = './/option[@value = ' . XPathEscaper::escapeQuotes($value) . ']'; 192 $options = $this->element->findElements(WebDriverBy::xpath($xpath)); 193 foreach ($options as $option) { 194 $this->deselectOption($option); 195 } 196 } 197 198 public function deselectByVisibleText($text) 199 { 200 if (!$this->isMultiple()) { 201 throw new UnsupportedOperationException('You may only deselect options of a multi-select'); 202 } 203 204 $xpath = './/option[normalize-space(.) = ' . XPathEscaper::escapeQuotes($text) . ']'; 205 $options = $this->element->findElements(WebDriverBy::xpath($xpath)); 206 foreach ($options as $option) { 207 $this->deselectOption($option); 208 } 209 } 210 211 public function deselectByVisiblePartialText($text) 212 { 213 if (!$this->isMultiple()) { 214 throw new UnsupportedOperationException('You may only deselect options of a multi-select'); 215 } 216 217 $xpath = './/option[contains(normalize-space(.), ' . XPathEscaper::escapeQuotes($text) . ')]'; 218 $options = $this->element->findElements(WebDriverBy::xpath($xpath)); 219 foreach ($options as $option) { 220 $this->deselectOption($option); 221 } 222 } 223 224 /** 225 * Mark option selected 226 * @param WebDriverElement $option 227 */ 228 protected function selectOption(WebDriverElement $option) 229 { 230 if (!$option->isSelected()) { 231 $option->click(); 232 } 233 } 234 235 /** 236 * Mark option not selected 237 * @param WebDriverElement $option 238 */ 239 protected function deselectOption(WebDriverElement $option) 240 { 241 if ($option->isSelected()) { 242 $option->click(); 243 } 244 } 245} 246