Watir-Webdriver - Parsing through table and clicking start/stop toggle - ruby

Please be gentle. I'm very new to Ruby and Watir. In general also new to UI automation testing. I'm trying to parse through a table then click a toggle that start/stops an application. The applications that can be turned on and off are not set. They can range from 1 to 'infinite' number applications. I need something that will parse through each row and click the toggle. if the application is started, I need to turn it off and vice-versa. No matter how many rows exist. The row also contains an element in the first column that allows the application to be clicked and brought to another page that gives details about said application. The toggle exists in the last column of the table. Here is the HTML I'm looking at with two applications deployed. It may be something simple that I'm missing. I'm also normally an IT admin that just got thrown into this. Thanks for all the help
<table id="apps_data" class="table table-striped table-bordered table-hover ng-scope">
<thead>
<tr>
</thead>
<tbody>
<tr class="link ng-scope" ng-repeat="app in services.data | orderBy:services.sort">
<td class="ng-binding" ng-click="click_app(app.Id)">Zenoss</td>
<td class="ng-binding deploy-success" ng-class="app.deploymentClass">
<span class="glyphicon glyphicon-ok" ng-class="app.deploymentIcon"></span>
successful
</td>
<td class="ng-binding">default</td>
<td>
<div class="toggle btn-group" data-toggle="buttons">
<label class="ng-binding btn btn-success active" ng-click="clickRunning(app, 'start', servicesService)" ng-class="app.runningClass">
<input class="ng-pristine ng-valid" type="radio" value="1" ng-model="app.Running" name="running0">
started
</label>
<label class="ng-binding btn btn-default off" ng-click="clickRunning(app, 'stop', servicesService)" ng-class="app.notRunningClass">
<input class="ng-pristine ng-valid" type="radio" value="stopped" ng-model="app.Running" name="running0">
</label>
</div>
</td>
</tr>
<tr class="link ng-scope" ng-repeat="app in services.data | orderBy:services.sort">
<td class="ng-binding" ng-click="click_app(app.Id)">Zenoss</td>
<td class="ng-binding deploy-success" ng-class="app.deploymentClass">
<span class="glyphicon glyphicon-ok" ng-class="app.deploymentIcon"></span>
successful
</td>
<td class="ng-binding">default</td>
<td>
<div class="toggle btn-group" data-toggle="buttons">
<label class="ng-binding btn btn-success active" ng-click="clickRunning(app, 'start', servicesService)" ng-class="app.runningClass">
<input class="ng-pristine ng-valid" type="radio" value="1" ng-model="app.Running" name="running1">
started
</label>
<label class="ng-binding btn btn-default off" ng-click="clickRunning(app, 'stop', servicesService)" ng-class="app.notRunningClass">
<input class="ng-pristine ng-valid" type="radio" value="stopped" ng-model="app.Running" name="running1">
</label>
</div>
</td>
</tr>
</tbody>
</table>
I have this so far, but it's just going to the first line.
if ff.div(:class, 'toggle btn-group').label(:class, 'ng-binding btn btn-success active').exist?
ff.div(:class, 'toggle btn-group').label(:class, 'ng-binding btn btn-default off').click; sleep 1
ff.div(:class, 'toggle btn-group').label(:class, 'ng-binding btn btn-default off').exist?
ff.div(:class, 'toggle btn-group').label(:class, 'ng-binding btn btn-default off').click; sleep 1
end

This might do what you want:
apps = browser.table(:id => 'apps_data').tbody.trs
apps.each do |app|
set_radios = app.radios.find_all{ |radio| !radio.set? }
if set_radios.length == 2
# Neither radio button is set, so do nothing?
else
set_radios.first.set
end
end
What this does is:
Gets a collection of all tr elements in the table body. Each tr element is assumed to represent an "app".
Iterates through each row (app):
Counts the number of radio buttons that are not set.
If neither of the 2 radio buttons is set, it does nothing.
If only one unset radio button is found, it is set.
Note that since the 2 radio buttons of the app have the same name attribute value, setting the one radio button will automatically unset the other.

Related

Update multiple cart items quantity. Spring Boot - Thymeleaf

I need help with updating a "quantity" of Product in my cart from a submit button, I'm clueless. I have successfully added the Product on the Carts page, I have an Update Button that should update the new quantity input value on the UI, just help and tips with updating the List of Cart Items when changes are been made to the quantity of the individual items. Thanks
//Iterating over the cartItems to be displayed on the carts page
<tr th:each="cartItem : ${cart.cartItems}">
<td class="product-name">
<h2 class="h5 text-black" th:text="${cartItem.product.productName}">Polo Shirt</h2>
</td>
<td>
<div class="input-group mb-3" style="max-width: 120px;">
<input type="number" class="form-control quantity text-center" th:value="${cartItem.quantity}" placeholder="" aria-label="Example text with button addon" aria-describedby="button-addon1">
</div>
</td>
<td th:text="${'₦' + #numbers.formatDecimal(cartItem.price, 0, 'COMMA', 0, 'POINT')}"></td>
<td><a th:href="${'/cart/removeCartItem/' + cartItem.cartItemId}" class="btn btn-primary height-auto btn-sm">X</a></td>
</tr>
//The update button
<div class="col-md-6 mb-3 mb-md-0">
<button class="btn btn-primary btn-sm btn-block">Update Cart</button>
</div>

Trying to delete data using modal popup, not getting correct id of selected item

I have a table view with many records and I want to have a delete button for each record, which will prompt a modal box for the user to confirm a delete. I am able to have the correct buttons (viewed from the table) to prompt the modal, but the button within the modal, which is the button to confirm a delete doesnt pass the right id. For example, I have 3 records in my table that is shown, with an ID of 1, 2 and 3. But the way I have code my table (will show code below) makes it where the user clicks any of the delete button on the desired record, the modal box pops up, with the "Delete(confirm)" button with an ID of the newest amongst all records. In this situation would be an ID of 3. I want it to pass the correct ID. Meaning if I want to delete record 1 it will pass ID 1 and record 2 and so on
I have tried researching what I did wrongly with my JavaScript but it seems ok and nothing seems to help for now. I have tried putting the whole modal into my table but it still has the same problem...
<!-- DataTables Example -->
<div class="card mb-3">
<div class="card-header">
<i class="fas fa-table"></i>
Advertisements
<div></div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered" id="dataTable" width="100%" cellspacing="0">
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Image</th>
<th>Status</th>
<th>Sort No.</th>
<th>Updated By</th>
<th>Expired At</th>
<th>Updated At</th>
<th> </th>
<th> </th>
</tr>
</thead>
<tbody>
#foreach ($advertisements as $advertisement)
<tr>
<td>{{ $advertisement->media->title }}</td>
<td>{{ $advertisement->media->description }}</td>
<td><img src="{{asset('/storage/uploads/images/').'/'.$advertisement->image}}" height="65" width="100"></td>
#php
if ($advertisement->media->status == 1){
$current_status = 'Active';
} elseif ($advertisement->media->status == 0){
$current_status = 'Inactive';
}
#endphp
<td>{{ $current_status }}</td>
<td>{{ $advertisement->media->sort}}</td>
<td>{{ $advertisement->media->admin->username}}</td>
<td>{{ $advertisement->expired_at}}</td>
<td>{{ $advertisement->media->updated_at}}</td>
<td><a href="/advertisements/{{$advertisement->id}}/edit" class="btn btn-success">Edit</td>
<td>
<button class="btn btn-danger delete-record" data-toggle="modal" data-target="#deleteModal" data-id="{{$advertisement->id}}" data-url="/advertisements/{{$advertisement->id}}">Delete , {{$advertisement->id}}</button>
</td>
</tr>
#endforeach
</tbody>
</table>
</div>
</div>
<div class="card-footer small text-muted">Updated yesterday at 11:59 PM</div>
</div>
Above code is the table view showing my data, and each record will have a delete button next to it.
<form action="" method="post" id="deleteForm">
#method('DELETE')
#csrf
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLongTitle">Modal title</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
Are you sure you want to delete this record?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="delete" class="btn btn-danger">Delete</button>
</div>
</div>
</div>
</div>
</form>
The code above is my modal box code, with a form that creates a delete request.
The code below is the javascript that is handling the delete and the passing of id and the action:-
$(document).ready(function () {
$('.delete-record').click(function () {
var url = $(this).attr('data-url');
$("#deleteForm").attr("action", url);
});
});
I want it to pass the correct ID. Meaning if I want to delete record 1 it will pass ID 1 and record 2 and so on.
You try to send your form via POST method, but there is no post data such as advertising id in it.
Can you please show your web.php route file ?
Because if it's POST method here, you should to add hidden input in your form.
Try with this,
Instead of url you should use route,
<button class="btn btn-danger delete-record" data-toggle="modal" data-target="#deleteModal" data-id="{{$advertisement->id}}" data-action="{{ route('advertisements.destroy', $advertisement->id) }}">Delete , {{$advertisement->id}}</button>
In your JS code
$("#deleteForm").attr("action", $(this).data('action'));
And if you don't put your modal code outside foreach then put it in at end of file
Hope this helps :)

GRAILS Placement of g:form affects params passed to controller

I have a HTML table that has a form field that can be updated for each row. In each row, I have created a form and when you change the editable field, a button will appear and you can save the change. This happens remotely using the GRAILS g:submitToRemote tag.
I have some hidden fields that also need to be passed.
When I do the following, none of the fields (the 3 hidden ones, nor the single visible ones) are passed the controller:
<g:form action="updatebaseprice" id="${obj.id}" name="form_${obj.id}">
<tr>
<td>${prod.product.name}</td>
<td class="text-center">${prod.product.type}</td>
<td class="text-center"><g:formatNumber number="${prod.product.unitCost}" type="currency" currencyCode="USD" /></td>
<td class="text-center">
<div class="row collapse">
<div class="small-3 columns">
<span class="prefix radius">$</span>
</div>
<div class="small-9 columns">
<g:field type="text" class="bemt_small_input_field" value="${prod.basePrice}" id="basePrice_${prod.id}" name="basePrice" onchange="Project.updateBasePrice(${prod.id});" maxlength="6"/>
</div>
</div>
</td>
<td class="text-center"><span id="display_recoveryPercent_${prod.id}"><g:formatNumber number="${prod.recoveryPercent}" type="number" minFractionDigits="2" maxFractionDigits="2"/></span>%</td>
<td class="text-center">
<g:hiddenField name="projectProduct" value="${prod.id}"></g:hiddenField>
<g:hiddenField name="unitCost" id="unitCost_${prod.id}" value="${prod.product.unitCost}"></g:hiddenField>
<g:hiddenField name="recoveryPercent" id="recoveryPercent_${prod.id}" value="${prod.recoveryPercent}"></g:hiddenField>
<g:submitToRemote id="save_button_${prod.id}" class="hidden button alert save_unitprice radius" id="save_button_${prod.id}" url="[action: 'updatebaseprice']" onSuccess="Project.saveBasePrice();" value="Save"/>
</td>
</tr></g:form>
Using a println params in the controller, nothing in printed.
When I do the following:
<tr>
<td>${prod.product.name}</td>
<td class="text-center">${prod.product.type}</td>
<td class="text-center"><g:formatNumber number="${prod.product.unitCost}" type="currency" currencyCode="USD" /></td>
<td class="text-center">
<div class="row collapse">
<div class="small-3 columns">
<span class="prefix radius">$</span>
</div>
<div class="small-9 columns">
<g:field type="text" class="bemt_small_input_field" value="${prod.basePrice}" id="basePrice_${prod.id}" name="basePrice" onchange="Project.updateBasePrice(${prod.id});" maxlength="6"/>
</div>
</div>
</td>
<td class="text-center"><span id="display_recoveryPercent_${prod.id}"><g:formatNumber number="${prod.recoveryPercent}" type="number" minFractionDigits="2" maxFractionDigits="2"/></span>%</td>
<td class="text-center">
<g:form action="updatebaseprice" id="${obj.id}" name="form_${obj.id}">
<g:hiddenField name="projectProduct" value="${prod.id}"></g:hiddenField>
<g:hiddenField name="unitCost" id="unitCost_${prod.id}" value="${prod.product.unitCost}"></g:hiddenField>
<g:hiddenField name="recoveryPercent" id="recoveryPercent_${prod.id}" value="${prod.recoveryPercent}"></g:hiddenField>
<g:submitToRemote id="save_button_${prod.id}" class="hidden button alert save_unitprice radius" id="save_button_${prod.id}" url="[action: 'updatebaseprice']" onSuccess="Project.saveBasePrice();" value="Save"/>
</g:form>
</td>
I get the three hidden params (as excepted), but not the input that is above the form (I don't expect that, given it is outside the form). The question I have is, why is the first way not returning anything, when I expect it to return the three hidden and one visible field values as params?
Thanks
The problem is that you're trying to nest a form inside the html table structure.
As you figured out, you can have a form inside a single table cell but trying to put a table row inside a form won't work.
See this question for some more info: Form inside a table

xpath preceding-sibling not working

I am trying to click on the first button which deletes the item which is the second button.
<tr>
<td>
<div class="btn-group">
<button class="btn btn btn-danger" name="delete" type="button">
</div>
</td>
<td>
<span class="class"></span>
</td>
<td>
<button class="btn" name="item" type="button">
</td>
</tr>
XPath
//button[contains(.,'${ITEM}')]/preceding-sibling::button[#name='delete']
I'm going to assume you're just not showing us the button text in your example HTML, since neither of your buttons seems to have any content.
preceding-sibling would not work here, since the two buttons are not siblings. However preceding::button should work in this case. Note the [1] at the end which is needed in order to select the closest match:
//button[contains(.,'${ITEM}')]/preceding::button[#name='delete'][1]
The following should also work, and is in my opinion a bit cleaner:
//tr[.//button[contains(., '${ITEM}')]]//button[#name ='delete']

Improve page performance by reducing number of binding with AngularJS?

Context
I'm building a list a results where each result can be edited by users.
Approach
Currently I'm repeating the visible <span> tag that displays a result as well as the hidden <input> tag used to edit this same result. See the last <td> :
<tr ng-repeat="entry in paginateDict | orderBy:predicate:reverse" class="form-inline" role="form">
<!-- Edit buttons -->
<td>
<div class="radio">
<label ng-hide="editMode" title="edit">
<input class="sr-only" type="radio" name="edit"
data-ng-click="editMode=true">
<span tooltip="edit" class="fa fa-pencil fa-lg"></span>
</label>
<label ng-show="editMode" title="save">
<input class="sr-only" type="radio" name="edit"
ng-model="editMode" ng-click="submitEntry(true)">
<span tooltip="save" class="fa fa-save fa-lg text-primary"></span>
</label>
<label ng-show="editMode" title="cancel">
<input class="sr-only" type="radio" name="edit"
ng-model="editMode" ng-click="submitEntry(false)">
<span tooltip="undo" class="fa fa-times fa-lg text-danger"></span>
</label>
</div>
</td>
<!-- Content -->
<td ng-show="pyn" lang="cmn-py" data-id="{{entry.cid}}">
<span ng-hide="editMode">{{entry.pyn | pinyin }}</span>
<input type="text" placeholder="{{entry.pyn}}" ng-model="entry.pyn" ng-show="editMode" class="form-control">
</td>
</tr>
Each line as 5 editable columns and each column as two binding (on <span> and <input>).
Question
In order to improve page performance, by reducing the number of binding, is there an Angular way to dynamically create and attach the <input> to a row when the edit radio button is click ?
You can do that using ngIf directive instead of ng-show
The ngIf directive removes or recreates a portion of the DOM tree based on an {expression}.
<td ng-show="pyn" lang="cmn-py" data-id="{{entry.cid}}">
<span ng-hide="editMode">{{entry.pyn | pinyin }}</span>
<input type="text" placeholder="{{entry.pyn}}" ng-model="entry.pyn" ng-if="editMode" class="form-control">
</td>

Resources