-
Notifications
You must be signed in to change notification settings - Fork 853
Add utility classes to interact with checkboxes and radio buttons #545
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
lib/WebDriverCheckbox.php
Outdated
} | ||
|
||
try { | ||
// Since the mechanism of getting the text in xpath is not the same as |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that this case really occurs. But as the WebDriverSelect
already does that, I've kept this behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @dunglas , this is such a great contribution! I've done some more in-depth code-review, hope you don't mind :-). Would be fantastic if you could have look at it.
Thanks again!
lib/WebDriverCheckbox.php
Outdated
/** | ||
* Provides helper methods for checkboxes and radio buttons. | ||
* | ||
* @author Kévin Dunglas <dunglas@gmail.com> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't use these @author
annotation in other parts of php-webdriver :).
lib/WebDriverCheckbox.php
Outdated
|
||
public function __construct(WebDriverElement $element) | ||
{ | ||
if ('input' !== $tagName = $element->getTagName()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please swap all of yoda conditions used (here and on all other places)? They are not used in the codebase and are IMO making the code harder to read.
} | ||
|
||
/** | ||
* @expectedException \Facebook\WebDriver\Exception\NoSuchElementException |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please change this annotation (here and below) to $this->expectException(NoSuchElementException::class)
and also add expectedExceptionMessage
(both placed on the lines right before the exception should occur)?
The expectedException annotations are now not considered as being the best practice in PHPUnit.
lib/WebDriverCheckbox.php
Outdated
|
||
$xpath = 'ancestor::label'; | ||
$xpathNormalize = sprintf('%s[%s]', $xpath, $normalizeFilter); | ||
if (null !== $id = $element->getAttribute('id')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This kind of conditions combined with variable assigning is really hard to read - could you please expand them (also on other places)? I know it is longer, but also much more readable IMO.
lib/WebDriverCheckbox.php
Outdated
return $this->getRelatedElements(); | ||
} | ||
|
||
public function getAllSelectedOptions() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, look like the interface is a bit too specific. I may change it to something like WebDriverSelectableIterface, and rename this to getAllSelected or something like that in the future - because the keyword "option" is related to <option>
tags in selects :].
For now I suggest at least renaming the variable(s) to $selected
or $selectedElements
so that thet don't suggest you are dealing with <options>
. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I definitely agree, the interface is too specific, but it's convenient to have a common one. Introducing the new one can even be done (in another PR?) without breaking changes.
lib/WebDriverCheckbox.php
Outdated
} | ||
} | ||
|
||
throw new NoSuchElementException('No options are selected'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe rather No checkboxes are selected
? (supposing the class is split for radios and checkboxes)
lib/WebDriverCheckbox.php
Outdated
} | ||
|
||
try { | ||
$element->findElement(WebDriverBy::xpath($xpathNormalize)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please add some short comment about the logic there? (Why first trying to find element this way and why in different way inside the catch block?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #545 (comment)
It mimics the behavior of WebDriverSelect
but I don't even know if it's really necessary. I propose to remove this for now, and we'll add it back if someone reports an issue.
lib/WebDriverCheckbox.php
Outdated
* | ||
* @author Kévin Dunglas <dunglas@gmail.com> | ||
*/ | ||
class WebDriverCheckbox implements WebDriverSelectInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest splitting the class in two (Checkbox and Radio), because:
- the semantic is confusing (
$radiobutton = new WebDriverCheckbox($radibuttonElement)
) - there are lot of
$this->isMultiple()
conditionals, which separates logic for checkboxes and radiobuttons
Maybe some common abstract class could be introduced to DRY the logics. What do you think?
<input type="checkbox" name="j2" value="j2a"> | ||
|
||
<label> | ||
J2B |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make sure the normalize-space() selectors work as expected, there could also be a case where the visible text has multiple spaces inside, ie. not being just one word, like:
<label> J 2 B ...
What are you thoughts?
lib/WebDriverCheckbox.php
Outdated
} | ||
|
||
if (null === $name = $element->getAttribute('name')) { | ||
throw new WebDriverException('The input have a "name" attribute.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does not have
?
d43ed46
to
40439f0
Compare
@OndraM all comments fixed. |
@dunglas Sorry for delays, I will review this soon. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried it on some webpages, it looks really great! Just a few remaining suggestions from me.
Thank you very much :)
$this->element = $element; | ||
} | ||
|
||
public function isMultiple() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about omitting the method implementation here (should be possible, abstract class does not have to fulfill the WebDriverSelectInterface interface) and let the descendant decide (ie. return true
in WebDriverCheckbox and return false
in WebDriverRadio).
*/ | ||
abstract class AbstractWebDriverCheckboxOrRadio implements WebDriverSelectInterface | ||
{ | ||
protected $element; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please add (one-line) phpdocs? /** @var WebDriverElement */
etc.
} | ||
} | ||
|
||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the interface (and WebDriverSelect behavior), this should return WebDriverElement
or throw NoSuchElementException
.
I know this is done in descendants, but in PHP 7 this would fail static analysis. And the difference in descendants is just in the error message, so maybe this could be done here using something like throw new NoSuchElementException('No ' . $this->type . ' selected.');
?
|
||
if (!$matched) { | ||
throw new NoSuchElementException( | ||
sprintf('Cannot locate option with value: %s', $value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The option is misleading... Maybe something like "cannot locate $this->type with value ..."?
{ | ||
$elements = $this->getRelatedElements(); | ||
if (!isset($elements[$index])) { | ||
throw new NoSuchElementException(sprintf('Cannot locate option with index: %d', $index)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, I'd avoid the "option" term at least in the messages, as it belongs to domain of <select>
.
lib/WebDriverCheckbox.php
Outdated
@@ -0,0 +1,72 @@ | |||
<?php |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I was using the classes I was thinking about their names - it seems they are still kinda confusing.
Consider this code:
$checkbox = new WebDriverCheckbox($driver->findElement(...));
$checkbox->deselectAll();
$checkbox->getFirstSelectedOption();
It is confusing the class is called "checkbox" (ie. singular), however it actually represents not a single checkbox, but group of checkboxes - and also the methods semantically interacts with multiple objects (deselectAll, getFirstSelected etc.).
So I have two possible suggestions how to make this more understandable:
- Name it in plural, like "WebDriverCheckboxes", WebDriverRadios"
- Or make it obvious it is group of elements: WebDriverCheckboxGroup", "WebDriverRadioGroup"
What do you think?
@OndraM, thanks for the review! Changes made. |
FYI I was investigating the reason of CI fails, and it looks like there is a bug in HtmlUnit browser which is bundled with this version of Selenium server. The tests however seems to be working properly in Firefox & Chrome, but the XPath somehow fails in HtmlUnit. I'll try to update the Selenium server to see if it will be resolved. |
Thanks for investigating. For what it worth, tests run locally with the latest version of HtmlUnit. |
The CI tests succeeded after Selenium server upgrade, so merging now! Thanks a lot for the effort @dunglas! (And sorry the review took so long...) |
BTW I prepared Wiki page to explain how to use these helper class, would you be interested to add at least some examples for the Checboxes and Radios class (Similarly os it is for the WebDriverSelect)? https://github.com/facebook/php-webdriver/wiki/Select,-checkboxes,-radio-buttons |
Thanks for the review @OndraM! Maybe can I start with copying this PR description to the wiki? |
Yeah, maybe simplify it a bit for better readability, but it looks like a good start 👍 , thanks :) |
@OndraM the wiki is readonly, or am I missing something? |
It should be editable for contributors - this doesn't work for you? https://github.com/facebook/php-webdriver/wiki/Select%2C-checkboxes%2C-radio-buttons/_edit |
@dunglas The wiki permission should be changed, could you please have a look to see if it works for you? |
This PR adds an utility class to easily manipulate checkboxes and radio buttons.
Imagine the following form:
The proposed class allows to interact with it in a smooth way:
Features:
For consistency and convenience - especially when using libraries such as Symfony Form that allow to switch from checkboxes to selects in 1 line - the new class mimics the public API of
WebDriverSelect
and implements theWebDriverSelectInterface
.Closes #373.