I have a method in my script that I use in a loop to determine if an object is enabled and displayed before it tries to interact with it, this looks like so: -
public boolean isElementPresent(By myObject, individualThreadSession threadSesh) {
try{
if(threadSesh.driver.findElement(myObject).isEnabled() && threadSesh.driver.findElement(myObject).isDisplayed()) {
return true;
} else {
return false;
}
}
catch (NoSuchElementException e){
return(false);
}
}
The myobject
part is passed in from a line reading something like so: -
isElementPresent(By.xpath(IDString),threadSesh)
So I'm passing in the relevant By
(which is determined via data specified at runtime)
Now this has worked out very well for me for a long time, until now. I'm trying to click on an object that is inserted into the page after I click a link, the problem is that Selenium is returning false for the threadSesh.driver.findElement(myObject).isDisplayed()
portion of this even once the object is clearly visible on the screen.
I can interact with this object fine manually and if I record it using the IDE it seems to work fine also... Anyone have any idea about how I can get around this? Or forcing Selenium to think that the object is displayed?
The HTML for the area I'm working on is as follows when the objects have not been added: -
<td class="activecomment">
<div data-bind="template: { name: 'commentControlPriorities', data: $data }" style="position: relative">
<div style="margin-right: 10px" data-bind="with: $data.LatestComment, flash: $data.LatestComment">
<div data-bind="style: { display: editMode() ? 'none' : 'block' }" style="display: block;">
<!-- Highlighted comment -->
<div class="comment labelgrey highlightcomment" data-bind="css: { highlightcomment: $parent.CommentSupportsApproving() && !CommentApproved() }">
<div style="word-wrap: break-word" data-bind="text: CommentView">No comment</div>
<div style="margin-top: 5px;">
<!-- ko if: $parent.isEnabled("Edit") -->
<a class="btn btn-small btn-info" data-bind="click: toggleEditMode, clickBubble: false" title="Leave Comment" href="#">
<img class="MYIcon" src="/Content/images/edit-icon.png"/>
</a>
<!-- ko if: $parent.CommentSupportsApproving() && CommentView() != "No comment" -->
<!-- /ko -->
<!-- /ko -->
<span class="lightergrey pull-right">
<span data-bind="cutSurname: User"/>
<span data-bind="relativeDateFormat: CommentDateView"/>
</span>
</div>
</div>
</div>
<!-- Editable comment - temporarily hidden -->
<!-- ko if: $parent.isEnabled("Edit") -->
<div data-bind="template: { name: 'commentEditControl', data: $data }">
<div class="labelgrey" style="background-color: rgb(255, 255, 255); padding: 1px; margin: 5px 0px; border: 1px solid rgb(110, 121, 131); display: none;" data-bind="style: { display: editMode() ? 'block' : 'none' }">
<textarea id="editcommenttext_0bf0e2db-0662-4b13-8a3c-1840cca79ba6" class="latestComment limitedText editCommentTextArea" data-bind="value: CommentUpdate, valueUpdate: 'afterkeydown', attr: {id: 'editcommenttext_'+$parentContext.$parent.Id()}" style="font-size: 12px; line-height: 18px; width: 100%; background-color: #FFF; border: 0px; resize: none; padding: 0px; margin: 0px; min-height: 54px"/>
<span class="validationMessage" style="display: none;"/>
<div style="background-color: #6E7983; padding: 5px;">
<a id="editcommentsave_0bf0e2db-0662-4b13-8a3c-1840cca79ba6" class="btn btn-small btn-info" data-bind="event: { click: function (data, parent) { saveComment($parentContext.$parent) } }, attr: {id: 'editcommentsave_'+$parentContext.$parent.Id()}" style="margin-right: 7px;" title="Save Comment" href="#"> Save </a>
<a id="cancelComment" class="cancel" data-bind="click: cancelComment" href="#">Cancel</a>
<div style="float: right; font-size: 10px; color: #FFF;">
<span class="limitedTextCharactersCount" data-bind="text: RemainingCharacterCount">240</span>
characters
</div>
</div>
</div>
</div>
<!-- /ko -->
</div>
</div>
</td>
And once you click the object
<a class="btn btn-small btn-info" data-bind="click: toggleEditMode, clickBubble: false" title="Leave Comment" href="#">
<img class="MYIcon" src="/Content/images/edit-icon.png"/>
</a>
Its changed to this:
<td class="activecomment">
<div data-bind="template: { name: 'commentControlPriorities', data: $data }" style="position: relative">
<div style="margin-right: 10px" data-bind="with: $data.LatestComment, flash: $data.LatestComment">
<div data-bind="style: { display: editMode() ? 'none' : 'block' }" style="display: none;">
<!-- Highlighted comment -->
<div class="comment labelgrey highlightcomment" data-bind="css: { highlightcomment: $parent.CommentSupportsApproving() && !CommentApproved() }">
<div style="word-wrap: break-word" data-bind="text: CommentView">No comment</div>
<div style="margin-top: 5px;">
<!-- ko if: $parent.isEnabled("Edit") -->
<a class="btn btn-small btn-info" data-bind="click: toggleEditMode, clickBubble: false" title="Leave Comment" href="#">
<img class="MYIcon" src="/Content/images/edit-icon.png"/>
</a>
<!-- ko if: $parent.CommentSupportsApproving() && CommentView() != "No comment" -->
<!-- /ko -->
<!-- /ko -->
<span class="lightergrey pull-right">
<span data-bind="cutSurname: User"/>
<span data-bind="relativeDateFormat: CommentDateView"/>
</span>
</div>
</div>
</div>
<!-- Editable comment - temporarily hidden -->
<!-- ko if: $parent.isEnabled("Edit") -->
<div data-bind="template: { name: 'commentEditControl', data: $data }">
<div class="labelgrey" style="background-color: rgb(255, 255, 255); padding: 1px; margin: 5px 0px; border: 1px solid rgb(110, 121, 131); display: block;" data-bind="style: { display: editMode() ? 'block' : 'none' }">
<textarea id="editcommenttext_0bf0e2db-0662-4b13-8a3c-1840cca79ba6" class="latestComment limitedText editCommentTextArea" data-bind="value: CommentUpdate, valueUpdate: 'afterkeydown', attr: {id: 'editcommenttext_'+$parentContext.$parent.Id()}" style="font-size: 12px; line-height: 18px; width: 100%; background-color: #FFF; border: 0px; resize: none; padding: 0px; margin: 0px; min-height: 54px"/>
<span class="validationMessage" style="display: none;"/>
<div style="background-color: #6E7983; padding: 5px;">
<a id="editcommentsave_0bf0e2db-0662-4b13-8a3c-1840cca79ba6" class="btn btn-small btn-info" data-bind="event: { click: function (data, parent) { saveComment($parentContext.$parent) } }, attr: {id: 'editcommentsave_'+$parentContext.$parent.Id()}" style="margin-right: 7px;" title="Save Comment" href="#"> Save </a>
<a id="cancelComment" class="cancel" data-bind="click: cancelComment" href="#">Cancel</a>
<div style="float: right; font-size: 10px; color: #FFF;">
<span class="limitedTextCharactersCount" data-bind="text: RemainingCharacterCount">240</span>
characters
</div>
</div>
</div>
</div>
<!-- /ko -->
</div>
</div>
</td>
I've tried using an XPATH of .//textarea[contains(@id,'editcommenttext')]
which I've varified within firepath as targetting just the textarea I want but no success. Any suggestions? Is there something wrong with the HTML I could get the devs to alter to make selenium return true here perhaps?
It also appears that if I remove the isDisplayed
check and then move on with my code I then get an error stating .InvalidElementStateException: Element must not be hidden, disabled or read-only
when I try and type into the cell etc.
I also tried getting the devs to give it an ID so I could find the object by ID and no difference.
So something about this object isn't enabled/visible but I can't see what (its clearly visible on the screen etc (when I'm debugging I'm giving it plenty of time to load the page before proceeding.
Thanks
I THINK I KNOW WHAT THE ISSUE IS!
When I record the process using the Selenium IDE it was appending [3]
to the xpath, turns out that when I thought this object was being added at runtime it was in actual fact being added on all rows and then just hidden, so now I need to add something into my object checks to run through all the objects that match the XPATH/ID and then use whichever one it comes across first that is both enabled and visible.... I'll confirm if this is the case.
The answer to this was frustratingly simple. The XPATH I was using was not specific enough and returning multiple objects when I thought it was only returning one, it then was directing Seleniums attention to the first in the array which was not visible (as it correctly reporting).
Once I changed my method to return the array of matching objects and simply cycle through until I found one that was both enabled and displayed to use Robert was my mothers brother.
The dodgy XPATH issue comes from the fact that I checked the XPATH within firepath (in firefox) and this was correctly highlighting the object I wanted but selenium could somehow actually multiples of the same object (despite the page source not showing more than 1 object) and this wasn't highlighted to me until I used the firefox Selenium addin to record my actions.
So the lesson learned here is to not trust an XPATH generated via firepath 100% if it doesnt appear to be working without checking in the Selenium IDE first...!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With