How to click an element based on another element's text - automated-tests

Given a multiple row user table...
<tr>
<td class="cell--select">
<input class="choice__input" type="checkbox">
</td>
<td>
<div class="user">
<ul class="user-info">
<li class="name">Jane Doe</li>
</ul>
</div>
</td>
</tr><tr>
...
I want to select the row with a given username and click the checkbox on that row. I've tried a number of ways to do this including withText and/or parent() and/or find() etc... but nothing works.
Typically, I would grab all the li.names, check for the correct name and use the index to check the correct checkbox but I also can't figure out a way to accomplish that.
Stuck... ideas?

There is a bit simpler way to achieve the desired behavior. You can use the withText method to identify a table row:
const checkboxToClick = await Selector('tr')
.withText('Jane Doe')
.find(".choice__input");
await t.click(checkboxToClick);

Okay, I found a way. This is a bit more brittle than I'd like but it works. Please do add an answer if there's a better solution!
const checkboxToClick = await Selector('.name')
.withText('Jane Doe')
.parent("tr")
.find(".choice__input");
await t.click(checkboxToClick);

Related

How to effectively use foreach in laravel-livewire in a table when the data dynamically changes?

I've been struggling with the foreach blade directive when combined with Laravel-Livewire and dynamically changing data rendered in an html table.
Specific scenario:
A table is presented to the user
Each row of the table includes a clickable icon that toggles a variable associated with that row
Toggling this icon visibly removes the row from the table
It sounds simple enough but I cannot get it to work correctly.
<?php
namespace App\Http\Livewire;
use Livewire\Component;
class UnreconciledServices extends Component
{
public $services;
public function render()
{
return view('livewire.unreconciled-services');
}
//
// receives an invoice number and changes the "reconciled" value for the $aaaServices record to true
//
public function test($a)
{
}
}
This is pretty simple - there is a function called "test" that accepts a variable (the function does nothing at all) and there is a collection of records called $services that has been passed in from the parent.
The blade file is as follows:
<div>
<table style="width:100%">
<thead>
<tr>
<th width="5%">
</th>
<th>
Service Date
</th>
<th>
Call #
</th>
<th>
Payment
</th>
</tr>
</thead>
<tbody>
#foreach ($services as $service)
{{-- <span wire:key="{{$service->invoice_number}}"> --}}
{{-- #if ($service->reconciled == false) --}}
<tr id="{{$service->invoice_number}}" class="text-center" style="{{ $service->reconciled == 1 ? 'display:none' : 'show'}}">
<td>
{{-- <input class="m-2" type="checkbox"> --}}
{{-- <span class="m-2"><i class="far fa-trash-alt text-red-500"></i></span> --}}
<span wire:key="{{$service->invoice_number}}" wire:click="test('{{$service->invoice_number}}')" class="m-2 cursor-pointer"><i class="lm-2 rm-2 fas fa-plus text-green-500"></i></span>
</td>
<td>
{{$service->invoice_date}}
</td>
<td>
{{$service->call_number}}
</td>
<td>
{{$service->service_price}}
</td>
</tr>
{{-- #endif --}}
{{-- </span> --}}
#endforeach
</tbody>
</table>
</div>
This is a simple table with 4 columns. The 1st is a clickable icon, the other 3 are data. Rows should only be visible if the "reconciled" boolean is 0.
When initially rendered, the appropriate records are displayed (there are 2 that qualify and 177 that do not). However, as soon as I click the icon - even when the function it links to has zero actual content - all records are suddenly visible except for the one that I've just hidden.
Looking at the html change when I click the icon, the tag changes from
<tr id="21031251674" class="text-center" style="display:none">
to
<tr id="21031253205" class="text-center" style="show">.
I have tried using livewire keys, though I'm not sure how this would affect the functionality since I'm not rendering components, but rather data driven html.
Surely someone else has encountered this and overcome. Please point me in the right direction.
Sorry for the delay. This whole thread should be deleted as bone-headed. But it was written in earnest...
When all was said and done, I was unclear on the differences b/w an html / ajax - based approach such as livewire and something more javascript-based such as Vue. I was expecting functionality that didn't exist in the framework as I was attempting to use it.
To solve the problem, I moved towards individual row items being components in and of themselves, using emit to communicate between components and adjust the backend data source.
Once I embraced the html / ajax approach and stopped trying to force / expect javascript functionality & dom manipulation then all was well. Just a mindset difference from what I am familiar with, and it took me some time to turn the corner.

Thymeleaf - How to dynamically add a tooltip to a table cell upon a condition

We are building a dynamic table with Thymeleaf.
Some cells, of null values, are holding "-" sign, while cells with values hold some data from the object:
<td th:text="${person.getAddress()} ? ${person.getAddress().getCity() : '-'"}
So the current state is this:
<table border=1>
<tr><td>John</td><td>London</td></tr>
<tr><td>Paul</td><td>-</td></tr>
</table>
Now, we like to add a tooltip, that when hovering the relevant table cell, more data can be seen (e.g. the person's full address).
We found this CSS example for tooltip and we figure out our final result should be something like that:
<td class="tooltip">London
<div class="tooltiptext">
<div>Street: Green</div>
<div>Number: 123</div>
</div>
</td>
But when trying to implement it in Thymeleaf we got stuck.
This is what we tried:
<div th:switch="${person.getAddress()}">
<div th:case="null"><td>-</td></div>
<div th:case="*">
<td> // now what? how to both inject value and the sub divs? </td>
</div>
</div>
Another option we thought of is to create by concatenation the full HTML within a td th:text=...
But both of the ways seems very cumbersome.
You can use the safe navigation operator in combination with the elvis operator instead of your null check.
No need for switch or any logic like this. Create a couple extra tags and move your logic deeper into the html.
Don't use .getAddress(), you can just use .address for properties with correctly named getters/setters.
For example:
<td>
<span th:text="${person.address?.city} ?: '-'" />
<div th:unless="${person.address == null}" class="tooltiptext">
<div>Street: Green</div>
<div>Number: 123</div>
</div>
</td>
Without all the fancy stuff, you could also simply do something like this:
<td th:if="${person.address == null}">-</td>
<td th:if="${person.address != null}">
<span th:text="${person.address.city}" />
<div class="tooltiptext">
<div>Street: Green</div>
<div>Number: 123</div>
</div>
</td>

Robot Framework - Selenium2Library - Input text into field according to the text before it

Due to every ID being randomly generated after each refresh, I am having to use other identifiers. I basically want Robot to input text into a field next to my indicated identifier.
https://vgy.me/3YBIgl.png
I want text to be inputted to the user: field and using the "user:" as the locator to work with.
It works with xpaths, but I would rather use another method which wont be so brittle.
Here is the HTML used to generate the form
<tbody id="m8DPe" class="z-rows">
<tr id="m8DPf" style="background:#FFFFFF;" class="z-row">
<td id="m8DPg-chdextr" style="background:#FFFFFF;text-align:left;" class="z-row-inner">
<div id="m8DPg-cell" class="z-row-content">
<span id="m8DPg" class="z-label">User:</span>
</div>
</td>
<td id="m8DPh-chdextr" style="background:#FFFFFF;text-align:left;" class="z-row-inner">
<div id="m8DPh-cell" class="z-row-content">
<input id="m8DPh" class="z-textbox" value="" type="text" name="j_username">
</div>
</td>
</tr>
<tr id="m8DPi" style="background:#FFFFFF;" class="z-row z-grid-odd">
<td id="m8DPj-chdextr" style="background:#FFFFFF;text-align:left;" class="z-row-inner">
<div id="m8DPj-cell" class="z-row-content">
<span id="m8DPj" class="z-label">Password:</span>
</div>
</td>
<td id="m8DPk-chdextr" style="background:#FFFFFF;text-align:left;" class="z-row-inner">
<div id="m8DPk-cell" class="z-row-content">
<input id="m8DPk" class="z-textbox" value="" type="password" name="j_password">
</div>
</td>
</tr>
I know it would be something similar to this:
Input Text //tr[contains(text(), 'Example') and ...] ${USERNAME}
But I honestly do not know. Is there somewhere I could read up on this?
I am extremely new to Robot Framework. Sorry for the noobness.
Looking at the sample, the name attribute for the input doesn'the look randomly generated. If that's really so, you could use the most trivial Selenium locator strategy by name, e.g.:
Input Text name=j_username ${USERNAME}
If that's not the case, this can be accomplished through an xpath:
//tr[//span[text()="User:"]/td//input
That reads (right-to-left for clearness, though it's evaluated LTR): return the input, which is inside a td (a cell), which itself is a direct child of a tr (a table row), having a span with that text (exact match here).
Thus, the locator will find the row having cell with "User:" in it, and will return the input in it.
*** settings ***
Library Selenium Library
*** Test Cases ***
Input text [Xpath- where you have to enter the text] python interpreter

asp.net razor Model Binding Dynamic List in Form submission

I am trying to create an order form for input.
I need the user to be able to add multiple line items then update. I was trying to
use the shopping cart class (where user creates a cart item and adds a
list item, multiple lines can be added to the list item).
I didn't get through with that. I am using asp.net razor am using
webmatrix to build site. Webmatrix is saying that it doesnt recognise Cart().
#{
if (Session["cart"] == null)
{
Session["cart"] = new Cart();
}
Cart cart = (Cart)Session["cart"];
}
<table id="cartTable">
<tr>
<th class="product">Product</th>
<th class="size">Size</th>
<th class="price">Price</th>
</tr>
#foreach (var item in cart.Items)
{
<tr>
<td class="product">#item.ProductID</td>
<td class="size">#item.Size</td>
<td class="price">£#item.Price</td>
</tr>
}
</table>
Is there a better way to do this? All help is greatly appreciated
There IS a way to bind dynamic list elements
#foreach (i=0; i< cart.Items.count; i++)
{
<tr>
<td class="product"> <input type="hidden" name="cart.item[#i].ProductID"> </td>
<td class="size"> <input type="text" name="cart.item[#i].Size"> </td>
<td class="price">£ <input type="text" name="cart.item[#i].Price"> </td>
</tr>
}
Webmatrix is saying that it doesnt recognise Cart()
I strongly recommend you place your Models in the Models folder and make them part of the Models namespace. Then they should be available automatically. Otherwise, you may have to reference cart by it's complete reference path (if it is not in your Models folder). example
Datalayer.Entities.Cart cart = (Datalayer.Entities.Cart)Session["cart"];
Final Note:
You are not passing your Cart as a Model to your View
example
#model {Project}.Entities.Cart
This is a better practice using the MVC 3 framework. You would have discovered whatever reference problem earlier and would have the option to use tightly bound Helpers
I recommend using the Visual Studio 2012 Express over WebMatrix for MVC development. Furthermore, I would also suggest using jQuery (javascript) if you wish to let users add line items on the fly via the same page. I can share an example with you if you like.
One more note: You tagged this with both MVC and WebForms, which are two very different platforms.
Edit:
I think Dave A's solution may be better, but to do this with jQuery:
1 Put your add button and hidden div in a form
<form action="/MyController/MyAction" method="post" id="addListItemForm">
<button id="addListItemButton">Add List Item</button>
<div style="hidden">
<input type="text" name="product" id="product" />
<button id="addButton">Add</button>
</div>
<input type="submit" text="Submit" />
</form>
2 Show the form fields on button click
$('#addListItemButton').click(function(){
$('#addListItemForm div').show();
});
3 Add hidden field on add button click
$('#addButton').click(function(){
var product = $('#addListItemForm #product').val();
$("input").attr({
name : "productListItems[]",
type : "hidden",
value : product
}).after('#addListItemForm');
});
4 When the form submits, you will have various product names in the productListItems array passed via POST method.
Note: You'll have to play with this a little bit, but it would be a good learning exercise... I am not exactly sure what you are trying to do, but this is my best guess.

Binding not working for collections in knockoutjs

I am trying to use knockout.js to show a specification object on the UI. The specification has a name and it has a few parameterInfo rows. Each ParameterInfo row has a ParameterPartNumber and a bunch of SignalInputs. Each SignalInput has just one property called Name. I am able to show the specification name, the parameterInfo rows and ParameterPartNumber but am not able to show the bunch of SignalInput Names that I have even though the SpecificationModel has the values. I am using the following code:
HTML code:
<div id="specificationHeader">
Name : <input data-bind='value: Name' />
<br />
<br />
</div>
<table>
<thead>
<tr>
<th>
Parameter Part
</th>
<th>
Signal Inputs
</th>
</tr>
</thead>
<tbody data-bind="foreach: ParameterInfos">
<tr>
<td>
<input data-bind='value: ParameterPartNumber' />
</td>
<td>
<ul data-bind="foreach: SignalInputs">
<li><span data-bind='text: Name' /></li>
</ul>
</td>
</tr>
</tbody>
</table>
Javascript/Knockout code:
<script type="text/javascript">
var SpecificationModel = function (specification) {
var self = this;
self.Name = ko.observable(specification.Name);
self.ParameterInfos = ko.observableArray(ko.utils.arrayMap(specification.ParameterInfos, function (ParameterInfo) {
return { ParameterPartNumber: ParameterInfo.ParameterPartNumber, SignalInputs: ko.observableArray(ParameterInfo.SignalInputs) };
}));
};
var specificationData = '#Html.Raw(ViewBag.SpecificationData)';
var viewModel = new SpecificationModel($.parseJSON(specificationData))
ko.applyBindings(viewModel);
</script>
When I run the program in debug mode, I can see the following values:
var specificationData = '{"Name":"Specification One",
"ParameterInfos": [{"ParameterPartNumber":"26-20700-002", "SignalInputs":[{"Name":"Park Brake"},{"Name":"Neutral"}]} ]}';
It's strange because I was able to get an almost similar example working thanks to the answers for the following question:
Need to pass initial viewmodel data from ASP.NET MVC to knockout.js
Still, somehow the binding code is not working. What am I missing?
EDIT:
Ok, the following lines work:
<td data-bind="foreach: SignalInputs">
<ul >
<li><span data-bind='text: Name' /></li>
</ul>
</td>
But, the following lines don't
<td>
<ul data-bind="foreach: SignalInputs">
<li><span data-bind='text: Name' /></li>
</ul>
</td>
Any idea why? The latter site of lines work in the other stackoverflow example question I cited.
In my experience when you run into weird binding errors, it often stems from the for-each binding. Because I've had so many issues with it, I pretty much just go the "containerless" route:
<!-- ko foreach: myItems -->
<li>Item <span data-bind="text: $data"></span></li>
<!-- /ko -->
I was having problems binding, but my issue turned out to be because I was using an early version of (knockout 1.2.1) that didn't support the foreach binding. It didn't throw an error for an unrecognised binding function though, just when I tried to reference an element within the collection I was supposedly binding to making it hard to debug.
In the end I used a template binding as per the answer here: Stackoverflow: knockout javascript foreach binding
I remember reading in the KnockoutJS documentation that the bindings run into issues with empty elements like <span .... /> and they suggest using <span ...></span> or even <span ...> </span> (the latter being a countermeasure to an IE6 bug).
Not sure this is relevant since this answer was posted so long ago but I was having an issue with foreach as well. I noticed that the error was coming from a jquery call in knockout (i was using knockout-2.0.0) and jquery-2.1.1. When I updated to knockout-3.1.0, this problem was solved.
2.0.0 was installed when I installed knockout.mapping via nuget and it probably depends on 2.0.0 at least. Hope this helps someone...

Resources