Trying to find unique XPath of button - html

I have a case where XPath is not unique and matches 3 elements on the page whose elementary position changes with refresh:
<div class="col-xs-12 Hover">
<button data-testid="continueCheckoutButton" ng-
class="continueDellMetricsClass" ng-click="continueButtonClick()" ng-
disabled="disableContinueButton" class="btn btn-success btn-block
continueButton" data-metrics="" type="button">Checkout</button>
Please help me finding the unique XPath or CSS path of this button element.
The other two HTML is as follows:
<div class="col-xs-12">
<button data-testid="continueCheckoutButton" ng-
class="continueDellMetricsClass" ng-click="continueButtonClick()" ng-
disabled="disableContinueButton" class="btn btn-success btn-block
continueButton" data-metrics="" type="button" style="background:
rgb(204, 136, 136); border: 2px solid red;">Checkout</button>
<div>
<button ng-class="continueDellMetricsClass" ng-
click="continueButtonClick()" ng-disabled="disableContinueButton"
class="btn btn-success btn-block continueButton" data-
testid="continueCheckoutButton" data-metrics="" type="button"
style="background: rgb(204, 136, 136); border: 2px solid
red;">Checkout</button>
</div>
This is what results in 3 elements match:
//button[#data-testid = 'continueCheckoutButton']
Please help!

To distinguish target button from two other you can try:
//button[not(#style) and .="Checkout"]
P.S. If HTML for all nodes seem to be identical, you can use index of required one:
(//button[not(#style) and .="Checkout"])[1]
Note that in XPath indexation starts from 1, so index for the first node will be [1].
You can also use find_elements...() instead of find_element...() to get a list of elements and select required with [index].

To click on the button with text as Checkout you have to induce wait through WebDriverWait and you can use either of the following code block (Python) :
CSS_SELECTOR :
button.btn.btn-success.btn-block.continueButton[ng-class='continueDellMetricsClass'][data-testid='continueCheckoutButton']
XPATH :
//button[#class='btn btn-success btn-block continueButton' and contains(.,'Checkout')]
Note : As the element is a Angular element you have to induce proper WebDriverWait through your respective Selenium Language Binding Art.
Update
As per your updated HTML you can use the following Locator Strategy :
XPATH :
//button[#class='btn btn-success btn-block continueButton' and contains(.,'Checkout') and not (#style='background')]

//div[#class='col-xs-12']/button[#class='btn btn-success btn-block continueButton' and contains(.,'Checkout')]
Try this.

Related

Function change text does not work if I add font awesome

This is my function that takes the id from my button and changes the text:
var modal_button_update = document.getElementById("modal_button_text");
if (modal_button_update.innerHTML === "Add") { modal_button_update.innerHTML = "Update"; }
It's working fine with this button (Add becomes Update)
<button type="button" class="btn btn-primary save_button add_group" id="modal_button_text">Add</button>
But for some reason it does not change anymore if I try to add a font awesome icon:
<button type="button" class="btn btn-primary save_button add_group" id="modal_button_text"><i class="fas fa-save" aria-hidden="true"></i>Add</button>
What could be the cause?
I ran your code. It seems the problem is that you are checking if your modal_button_update button's innerHTML is equal to Add. The original button:
<button type="button" class="btn btn-primary save_button add_group" id="modal_button_text">Add</button>
works because the innerHTML of your modal_button_update is exactly 'Add', but your modified modal_button_update: <button type="button" class="btn btn-primary save_button add_group" id="modal_button_text"><i class="fas fa-save" aria-hidden="true"></i>Add</button> does not work because the innerHTML is <i class="fas fa-save" aria-hidden="true"></i>>Add which is not equal (===) to 'Add'.
Here's three different ways I chose to solve this issue:
Remove the <i class="fas fa-save" aria-hidden="true"> element and place the font-awesome icon inside the button class like: <button type="button" class="btn btn-primary save_button add_group fas fa-save" id="modal_button_text">Add</button>
`
Alter your Javascript to look something like: var modal_button_update = document.getElementById("modal_button_text"); if (modal_button_update.innerHTML.includes("Add")) { modal_button_update.innerHTML = "Update"; }
Here the button checks to see if the innerHTML contains 'Add'.
Keeping the font-awesome icon:
In the two solutions above, your Javascript code will strip the button element of the font-awesome class when successfully changing the innerHTML to 'Update'. It is, imo, easier to use jQuery to alter the value of the button to solve this issue like:
---HTML---
<button type="button" class="btn btn-primary save_button add_group" id="modal_button_text"><i class="fas fa-save" aria-hidden="true"></i>Add</button>
---Javascript--
if ($('#modal_button_text').html().includes("Add")) {
$('#modal_button_text').html("Update");
}
If your intent is to keep the icon no matter the state of the text (i.e., Add or Update), I would personally enclose the word "Add" in a span as follows:
HTML:
<button type="button" class="btn btn-primary save_button add_group" id="modal_button_text"><i class="fas fa-save" aria-hidden="true"></i><span id="modal_button_span">Add</span></button>
Then it is just a matter of whether you use jQuery or not as to which of the following will work for you.
JavaScript:
Change value based on current text:
if(document.getElementById("modal_button_span").innerHTML === "Add") document.getElementById("modal_button_span").innerHTML = "Update";
Toggle value when the button is clicked based on current text:
document.getElementById("modal_button_span").innerHTML = document.getElementById("modal_button_span").innerHTML === "Add" ? "Update" : "Add";
jQuery:
Change value based on current text:
if($("#modal_button_span").html() === "Add") $("#modal_button_span").html("Update");
Toggle value when the button is clicked based on current text:
$("#modal_button_span").html($("#modal_button_span").html() === "Add" ? "Update" : "Add");
By enclosing the word Add in a span, you are free to do whatever you need to the text without disrupting the rest of the button or save icon.

How to pass parameters to a modal using thymeleaf?

I just starting in a project where we're using thymeleaf and I'm new to the technology.
It's a simple modal to confirm an object to be deleted. So they want me to add the name of the item we're deleting on the message instead of a generic message like: "Are you sure you want to delete?"
They want: "Are you you want to delete Item_Name?"
So I need to pass this name as parameter.
That's the code I have the display the modal:
<button id="btnDelete" value="delete" type="button" class="btn-link" data-toggle="modal" data-object-id="12345" data-object-name="NNN" th:attr="data-object-id=''+${assignment.assignmentId}+'', data-object-name='/nonInstructionalWorkload/deleteAssignment?asssignmentId='+${assignment.assignmentId}+'', data-target='#deleteAssignmentModal'">
And that's the code I have on the modal html:
<form role="form" th:action="#{/deleteAssignment}"
name="assignmentDeleteForm" id="assignmentDeleteForm" method="post">
<div class="modal-header">
<h4 class="modal-title" id="deleteWorkItemabel">Confirm Assignment Delete</h4>
</div>
<div class="modal-body">
<div class="form-group">
<p>Assignment will be deleted, are you sure you want to proceed?</p>
<input type="hidden" id="deleteId" name="deleteId" value="nnnn"/>
</div>
</div>
<div class="modal-footer">
<button type="submit" id="btnDelConfirm" class="btn btn-primary">
Yes
</button>
<button type="button" id="btnDelCancel" class="btn btn-default" data-dismiss="modal">
No
</button>
</div>
</form>
At this point is where I need to add more information on the confirmation message about the assignment item being deleted.
Already tried some approached to get the parameters but didn't work so I'm looking for more option.
Thank you!
Thymeleaf is just template engine that takes your templeate and generate static html out of it. So passing dynamic values to your modal is a javascript work (unless you generate separate modal for each item, but this would be silly).
Using thymeleaf you have to generate a data- attribute containing desired item's name inside the button which opens modal, and that's all. You've already did such thing within th:attr:
th:attr="data-object-id=''+${assignment.assignmentId}+'', data-object-name='/nonInstructionalWorkload/deleteAssignment?asssignmentId='+${assignment.assignmentId}+'', data-target='#deleteAssignmentModal'"
The code above will generate attributes data-object-id,data-object-name and data-target inside the button. I assume that data-object-name is the one you want to use (however it looks more like an URL).
Now you can customize your modal's content using javascript. I can see, that you are using bootstrap as your frontend library, so you should take a look at this example, to have an idea how to accomplish that.
Javascript code below should work fine for you:
$('#deleteAssignmentModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget) // Button that triggered the modal
var objectName = button.data('object-name') // Extract info from data-object-name attribute
// Update the modal's content. We'll use jQuery here, but you could use a data binding library or other methods instead.
var modal = $(this)
modal.find('.modal-body p').text('Do you want to delete ' + objectName + '?')
})
The sample project on GitHub you can clone and test it. Full video under the readme.
https://github.com/ibrahimkarayel/todoBoot/blob/master/src/main/resources/templates/home.html
<div class="modal modal-delete" th:id="modal-delete+${task.id }">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-hidden="true">×
</button>
<h3 id="delModalLabel">Delete Confirmation</h3>
</div>
<div class="modal-body">
<p class="error-text"><strong>Are you sure you want to
delete this task ?</strong></p>
</div>
<div class="modal-footer">
<button class="btn " data-dismiss="modal" aria-hidden="true">
Cancel
</button>
<a th:href="#{/task/delete/{id}(id=${task.id})}">
<span class="btn btn-danger" value="delete">Delete</span></a>
</div>
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
<!--end delete modal-->
<script>
$('#modal-delete').on('show.bs.modal', function (e) {
$(this).find('.btn-ok').attr('href', $(e.relatedTarget).data('href'));
$('#modal-deleteHiddenId').val($(this).find('.btn-ok').attr('href'));
});
</script>

Print html code with angular2

I'm trying to print a button in html passing it as a parameter in angular2 but angular2 never translate it.
modal.component.ts
this.footer = `<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>`
modal.component.html
<div class="modal-footer" [innerHTML]="footer"></div>
output html
<div class="modal-footer" ng-reflect-inner-h-t-m-l="Close">Close</div>
Should be innerHtml instead of innerHTML. It is case sensitive.
For example :
<div class="modal-footer" [innerHtml]="footer"></div>

HTML inserts space between the first occurrence of quotes

This drives me crazy. I want to pass a string literal as a parameter to a function in HTML onclick property containing a double quote.
My HTML element looks like this:
<button onclick = "ok_button_click(""Harry Potter "")" type="button" class="btn btn-default">ok</button>
But when I load the page and open it by Inspect Element, I see a space inserted between the first quote resulting in this:
<button onclick = "ok_button_click(" "Harry Potter"")" type="button" class="btn btn-default">bad</button>
Why does the browser insert a space ???
If you are trying to pass a string value with quotes then you have to use " like this:
<button onclick = "ok_button_click('"Harry Potter"')" type="button" class="btn btn-default">bad</button>
If you just want to pass in a string literal you can just use a single quote (or the opposite of what the attribute started with) like this:
<button onclick = "ok_button_click('Harry Potter')" type="button" class="btn btn-default">bad</button>
That is because when the DOM is being parsed the browser uses " as delimiters, so in your case it is assigning ok_button_click( to the attribute onclick and Harry Potter as a separate (and unknown) attribute.
A better way of writing this code would be mixing single and double quotes as in:
<button onclick="ok_button_click('Harry Potter')" type="button" class="btn btn-default">ok</button>
A good start on HTML debugging is to run it through a validator, like in https://validator.w3.org/#validate_by_input
Try :
<button onclick = 'ok_button_click("Harry Potter")' type="button" class="btn btn-default">ok</button>

Change class with AngularJS

I have this piece of code in a ASP.NET MVC view:
<div ng-repeat="it in (#source)">
<button class="btn hvr-glow btn-success" >
{{it.name}}
<p ng-if="it.check==true"><i class="fa fa-check-circle-o" ng-click="it.check=false"></i></p>
<p ng-if="it.check==false'"><i class="fa fa-circle-o" ng-click="it.check=true"></i></p>
</button>
</div>
source is a list of objects of type ApplicationStatus, which contain a boolean field named check. I want to change the type of the font awesome icon on click. But I don't see the icons. Source is correctly loaded. Where is the problem ?
You can use ng-class for that. For example:
<i ng-class="{'fa fa-check-circle-o': check, 'fa fa-circle-o': !check}"
Here's another question related to ng-class
Note - a ternary operator would probably be best in your case
<div ng-repeat="it in source">
<button class="btn hvr-glow btn-success" ng-click="it.check = !it.check">
{{it.name}}
<p>
<i ng-class="{'fa fa-check-circle-o': it.check, 'fa fa-circle-o': !it.check}"></i>
</p>
</button>
</div>