Atata – How to check hidden checkbox within table? - c#

I am using Atata Framework and working on the following scenario—There's a table with checkbox within TD element. I want to be able to invoke Click() method on the checkbox, but couldn't get it work correctly.
The truncated HTML is as following:
<table data-v-c4547572="" class="invGrid">
<tr data-v-c4547572="" row-id="3ed5bcf4-473d-43ae-991a-ffe36d5e0a53" class="row-index-0">
<td data-v-c4547572="" class="column-index-0 checkbox-col">
<input data-v-c4547572="" type="checkbox" element-id="3ed5bcf4-473d-43ae-991a-ffe36d5e0a53" class="">
<label data-v-c4547572="" for="3ed5bcf4-473d-43ae-991a-ffe36d5e0a53"></label>
</td>
<td data-v-c4547572="" class="column-index-1">
<span data-v-c4547572="" class="val-name">Some text</span>
<span data-v-c4547572="" class="arrow pull-right dsc"></span>
</td>
</tr>
</tbody>
</table>
The code I'm using is:
// The page class:
[FindByCss(".invGrid")]
public Table<GroupsRow, Page> Inventory { get; set; }
// The row class:
public class GroupsRow : TableRow<Page>
{
[FindByIndex(0)]
public CheckBox<Page> CheckBox { get; set; }
[FindByCss(".val-name")]
public Text<Page> Text { get; set; }
}
As an additional note, invoking Exists() on the checkbox yields false:
inv.CheckBox.Exists(); // false
Any idea how to make the checkbox to be operational?

I can guess that your check box is actually hidden, and <label> is used as a wrapper for custom rendering. As almost all controls in Atata are looking for visible elements by default, you can specify Visibility:
[FindByIndex(0, Visibility = Visibility.Any)]
public CheckBox<Page> CheckBox { get; private set; }
It should find the check box. But if click on it will not work (as it can be hidden), you can add a property for label and click it:
[FindFirst]
public Label<Page> CheckBoxLabel { get; private set; }

Related

(Blazor) Select value not updating

I have a <select> element in my page, of which I want to get the value. To do that, I've added the #bind= property. Here's the code:
<form target="#">
<label for="subject">Vak</label>
<select #bind="#selectedSubject" id="subject">
#foreach (Subject subject in Auth.User.Subjects)
{
<option value="#subject.Uuid">#subject.Name</option>
}
</select><br />
<!-- (more input fields) -->
<button class="button btn-primary mt-3" #onclick="addNote">Toevoegen</button>
</form>
#code {
private String selectedSubject { get; set; }
private String inputTitle { get; set; }
private String inputContent { get; set; }
private void addNote()
{
Console.WriteLine(selectedSubject);
}
}
This doesn't output anything, and trying to use selectedSubject results in a NullReferenceError.
The other values (inputTitle, inputContent) work as expected.
Yes, the <select> is properly being filled with <option>s.
I've tried switching to whatever <EditForm> is, but it kept giving me warnings and failed to build.
First of all, you don't need a form at all. The main purpose of EditForm is to validate inputs-- like, making sure a password is the correct format.
Also, unless you want to programmatically SET the value of the dropdown, you don't need to bind to it-- instead, use #onchange:
<select #onchange=ProcessSelection>
. . .
#code {
Subject SelectedSubject {get; set;}
async Task ProcessSelection (ChangeEventArgs args){
SelectedSubject = Auth.User.Subjects.Single(s=>s.uuid = args.Value.ToString();
// Process the selection.
}
}
This will (1) give you a place to breakpoint for debugging. (2) Let you do useful things with the SelectedSubject object more easily (like add / remove it from a list, pass it as a parameter to a display component, etc.).
My approach to the problem is different from the other answer.
Here's everything in one Razor component.
Separate your data out into a class. Note that the fields can be null (you may not want them to be).
Use EditForm with normal binding. You can then do validation.
Logic in select list to determine if no value is selected and display a select message.
Separate out the Select code into a separate RenderFragment block to keep the code cleaner. Not necessary if you like all your markup in one place.
#page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
<EditForm EditContext="editContext">
<div class="col-12 mb-3">
<label class="form-label small muted">Subject</label>
<select class="form-select" #bind="model.Subject">
#this.SubjectList
</select>
</div>
<div class="col-12 mb-3">
<label class="form-label small muted">Title</label>
<input class="form-control" #bind="model.Title">
</div>
</EditForm>
<div class="alert alert-info m-3">
<div class="m-2">
Subject : #model.Subject
</div>
<div class="m-2">
Title : #model.Title
</div>
<div class="m-2">
Content : #model.Content
</div>
</div>
#code {
//private Note model = new() { Subject="Portugal"};
private Note model = new();
private EditContext? editContext;
protected override void OnInitialized()
=> this.editContext = new EditContext(model);
private IEnumerable<string> Subjects = new List<string> { "France", "Portugal", "Spain" };
private bool noSubjectSelected => !Subjects.Any(item => item == model.Subject);
// Separates out the code into a separate render block
private RenderFragment SubjectList => (__builder) =>
{
if (noSubjectSelected)
{
<option disabled selected value="">-- Select a Subject --</option>
}
foreach (var subject in Subjects)
{
<option value="#subject">#subject</option>
}
};
// Normally separate out into own class file
public class Note
{
public string? Subject { get; set; }
public string? Title { get; set; }
public string? Content { get; set; }
}
}
I've left the subject as a string as that's what you have it as. If it's a guid you will need to adjust the Select Code to handle a Guid/String pair.
Bear in mind that source code is an instruction of rendering intent so it isn't always clear how your code will react in all cases of operation.
You have to decide whether there will be a selection at all times, or whether no material selection is also valid. For example, if you want an option of "Select a Subject" at the top of the list which is the default, so set am option with that text an a value of Guid.Empty (all zeros). When processing values - just check that the selected Guid is not equal to Guid.Empty if you want to know if a "real" subject option is selected
I use the following two approaches with InputSelect. Note that the Microsoft renderer may set the text of the select to your bound option but may not set that option to "selected"
<InputSelect #bind-Value=MyData>
<option selected value="#Guid.Empty">Select a Subject</option>
#foreach (var i in Subjects)
{
<option value=#i.Id>#i.SubjectName</option>
}
</InputSelect>
If you want finer grained control over the select, there is this binding option. As well as a value to which you bind, you also need a function which is called on change - SelectChanged - where you can run other code. you should set the bound value within this method. If the InputSelect is inside a component, the bound value should also have an EventHandler of the same name with the "Changed" text appended - you should Invoke this in your SelectChanged function. Any parent control binding to the child will have its data automatically updated and the SetStateChanged is called for you
<InputSelect Value="#MyValue" ValueChanged="#((Guid value) => SelectChanged(value))" ValueExpression="#(() => MyValue)" >
#foreach (var i in MyValues)
{
if (TheSelectedValue == i.TheValue)
{
<option selected value=#i.TheValue>#i.ValueDisplay</option>
}
else
{
<option value=#i.TheValue>#i.ValueDisplay</option>
}
}
</InputSelect>
To solve your issue with things not changing, there are two ways you can do it:
Start with a null object for your SelectedSubject, do a null check in your click, and have a dummy select option that forces you to select an item:
.
<select #onchange=ProcessChange>
<option selected disabled>Pick a subject. . . </option>
#foreach (var subject in Auth.User.Subjects) {
<option>. . . </option>
}
</select>
<button #onclick=AddSubject />
#code {
List<Subject> SelectedSubjects = new();
Subject SelectedSubject {get; set; } = null;
async Task ProcessChange (ChangeEventArgs args){. . . }
async Task AddSubject{
if (Subject is not null) SelectedSubjects.Add (SelectedSubject);
}
}
Preselect your first option in your data initialization, and then you don't NEED the dummy <option> or the null check.
.
#code{
async Task OnInitializedAsync (bool IsFirstRender){
Auth.User.Subjects = await LoadSubjectsForUser (Auth.User);
SelectedSubject = Auth.User.Subjects.First();
}
}
In your use case, I think #1 is probably better.

Unable to locate element using ID, XPath and CSS Selector

I am using atata framework with C#. I am trying to locate web element to select all rows but neither Id, CSS Path or XPath are able to find.
I have used ID, XPath and CSS Selector
[FindById("com.kronos.wfc.ngui.genies.selectall")]
public Button<_> SelectAllRows { get; private set; }
[FindByXPath("div[#id=com.kronos.wfc.ngui.genies.selectall]")]
public Button<_> SelectAllRows { get; private set; }
My page object should be located. Details of paths are:
Element:
<div class="widget-button btn-group margin-mini shrinkable" title="Select All Rows"
id="com.kronos.wfc.ngui.genies.selectall" style="display: inline-block;">
<div class="top-bar"><span></span></div>
<button type="button" class="btn btn-rounded widget-button-icon" id="com.kronos.wfc.ngui.genies.selectall_btn">
<i class="icon-k-select-all"></i></button><div class="icon-label"><span>Select All Rows</span></div></div>
Selector: #com.kronos.wfc.ngui.genies.selectall
XPath: //*[#id="com.kronos.wfc.ngui.genies.selectall"]
For the first <div> element:
<div class="widget-button btn-group margin-mini shrinkable" title="Select All Rows"
id="com.kronos.wfc.ngui.genies.selectall" style="display: inline-block;">
As it is a div, not a button element then use general puprose Control type:
[FindById("com.kronos.wfc.ngui.genies.selectall")]
public Control<_> SelectAllRows { get; private set; }
For the second <button> element:
<button type="button" class="btn btn-rounded widget-button-icon" id="com.kronos.wfc.ngui.genies.selectall_btn">
The following should find the element if it's actually visible:
[FindById("com.kronos.wfc.ngui.genies.selectall_btn")]
public Button<_> SelectAllRows { get; private set; }
If the element is not visible:
[FindById("com.kronos.wfc.ngui.genies.selectall_btn", Visibility = Visibility.Any)]
public Button<_> SelectAllRows { get; private set; }
Anyway, figure out which element is actually visible and should be interacted with.
I think you should perform the click action on the button element instead of the div element. Try the below code:
[FindById("com.kronos.wfc.ngui.genies.selectall_btn")]
public Button<_> SelectAllRows { get; private set; }

Atata - Unable to locate element, using Table<> class

When I trying to refer to some element in table, using Table<> class, I get this error:
Message: OpenQA.Selenium.NoSuchElementException : Unable to locate element: By.XPath: .//td[1]/descendant-or-self::a
Context element:
Tag: tr
Location: {X=62,Y=273}
Size: {Width=1140, Height=37}
Text: Order Date User Address Origin Address Destination My Reference POD Status
A table source:
<table class="table table-striped">
<tr class="text-nowrap">
<th>Order</th>
<th>Date</th>
<th>Customer</th>
<th>User</th>
<th>Address Origin</th>
<th>Address Destination</th>
<th>My Reference</th>
<th>POD</th>
<th>Status</th>
</tr>
<tr>
<td class="text-nowrap">
180305-NQHHGU
</td>
<td>05.03.2018</td>
<td>Merchant Advance (2M7)</td>
<td>Barry Manilow</td>
<td>757 RUE GUY MONTREAL</td>
<td>242 LAVERENDRYE AVE CHURCHILL</td>
<td></td>
<td>
</td>
<td class="text-nowrap">…</td>
</tr>
Page object source:
public class OrdersPage : BasePage<_>
{
public Table<OrdersTableRow, _> Orders { get; private set; }
public class OrdersTableRow : TableRow<_>
{
[FindByColumnHeader("Order")]
public LinkDelegate<ShipmentOrderPage, _> Order { get; private set; }
public Date<_> Date { get; private set; }
public Text<_> Customer { get; private set; }
public Text<_> User { get; private set; }
…
…
}
}
And I'm trying to do something like that in test:
Go.To<OrdersPage>().
Orders.Rows[x => x.Order.Content().Value == order.OrderNumber].Order();
I think it's about my table haven't <thead> tag. Have any Idea?
You are right. Out of the box Table control works by default with <table> that contains <th> elements inside thead/tr. Such row is skipped when Atata handles regular/data rows.
You can check that TableRow class contains the following control definition:
[ControlDefinition("tr[parent::table or parent::tbody]", ComponentTypeName = "row")]
In your case the first row with headers was considered like a regular row, and Atata tried to find a link in this row, which is missing there.
But in Atata you can reconfigure such things easily. Just overwrite [ControlDefinition] of OrdersTableRow class as follows:
[ControlDefinition("tr[td]", ComponentTypeName = "row")]
public class OrdersTableRow : TableRow<_>
{
//...
}
This way Orders.Rows will process only <tr> elements that have <td> element inside, skipping the first row.

Passing collection to the controller from view

I have a model bind to view. I would like to add a checkbox which allows user to change select and submit the selected items for another process. User also can change the value of NumberOfCopies if needed.
I am passing the ManufacturingJobEditModel to the controller. I can see all the items in the PrintErrors collection in the controller. However, I have 2 problems here
ManufacturingJob always NULL in ManufacturingJobEditModel in the controller
Only IsSelected and NumberOfCopies have values. The rest of the properties show NULL values.
Is that anything that I am missing here?
Model
public class ManufacturingJobProductEditModel
{
public ManufacturingJob ManufacturingJob{ get; set;}
public IList<PrintError> PrintErrors { get; set; }
}
public class PrintError
{
public bool IsSelected { get; set; }
public int ProductId { get; set; }
public string ISBN { get; set; }
public string ProductName { get; set; }
public int Sequence { get; set; }
public int NumberofCopies { get; set; }
}
MainView
<table>
<tr>
<td class="display-label valign-top">Products</td>
<td class="display-field white-space-reset"
colspan="3">
<table class="formDisplayTable">
<colgroup>
<col class="width05" />
<col class="width10" />
<col class="width10" />
<col class="width35" />
<col class="width05" />
<col class="width20" />
</colgroup>
<thead>
<tr>
<th></th>
<th>ISBN</th>
<th>Product ID</th>
<th>ProductName</th>
<th>Sequence Number</th>
<th>No of Copies</th>
</tr>
</thead>
<tbody>#foreach (var product in Model.ManufacturingJob.ManufacturingJobProducts.OrderBy(c => c.Sequence))
{
Html.RenderPartial("_PrintErrorDetails", product);
}</tbody>
</table>
</td>
</tr>
</table>
_PrintErrorDetails.cshtml
#model Bolinda.Matrix.Data.Domain.ManufacturingJobProduct
#{Html.RegisterFormContextForValidation();}
<tr class="valign-top">
#using (Html.BeginCollectionItem("PrintErrors"))
{
<td>
<div class="editor-field">#Html.CheckBox("IsSelected")</div>
</td>
<td>
<div class="table-display-field">#Html.Display("ISBN")</div>
</td>
<td>
<div class="table-display-field">#Html.Display("ManufacturingProduct.Product.ProductId")</div>
</td>
<td>
<div class="table-display-field">#Html.Display("ManufacturingProduct.Product.Name")</div>
</td>
<td>
<div class="table-display-field">#Html.Display("Sequence")</div>
</td>
<td>
<div class="table-editor-field">#Html.Editor("NumberOfCopies")</div>
</td>
}
</tr>
Controller
[HttpPost]
public ActionResult PrintError(ManufacturingJobProductEditModel editModel)
{
var id = editModel.ManufacturingJob.ManufacturingJobId;
ManufacturingJob manufacturingJob = _unitOfWork.ManufacturingJob
.GetWhere(j => j.ManufacturingJobId == id, null, "ManufacturingJobProducts")
.FirstOrDefault();
if (manufacturingJob == null)
{
return new HttpNotFoundResult(String.Format("Manufacturing Job with id {0} was not found.", id));
}
// _service.RequeueErrorCorrection(manufacturingJob, printErrorCorrection, autoCdErrorCorrection, manualCdErrorCorrectionSequenceNumbers);
return RedirectToAction("Details", new { id = manufacturingJob.ManufacturingJobId });
}
ManufacturingJob always NULL in ManufacturingJobEditModel in the controller
The view you have shown does not generate any form controls for any properties so no values are posted back and bound to your model. From the code in your POST method, you appear to only need the ManufacturingJobId property so you need to include
#Html.HiddenFor(m => m.ManufacturingJob.ManufacturingJobId)
Only IsSelected and NumberOfCopies have values. The rest of the properties show NULL values
Again, you have not included form controls for any properties other than the IsSelected and NumberOfCopies of each PrintError object in the collection. If you want the other properties to be bound, use
<td>
<div class="table-display-field">#Html.Display("ISBN")</div>
#Html.HiddenFor(m => m.ISBN)
</td>
or
<td>
<div class="table-display-field">#Html.TextboxFor(m => m.ISBN, new { #readonly = "readonly" })</div>
</td>
Side note: Since you are not dynamically adding or deleting PrintError items in the view, there is no need to use the extra overhead of BeginCollectionItem(). Either use a for loop or a custom EditorTemplate for type of PrintError and in the main view use #Html.EditorFor(m => m.PrintErrors) (refer this answer for an example of using an EditorTemplate). I would also recommend that you populate your models PrintError collection on the server before you pass it to the view (including the .Order() clause) rather that trying to 'fake' it as you are doing.
This is because you are not rendering html input controls for the rest of the model properties other than "IsSelected" and "NumberOfCopies".
"#Html. Display" just render data without any html input control. You can check using page view source.
To render these control you can use below html helper methods. #Html. TextBox, #Html. DropDown, #Html. TextArea and others.
To submit all properties that you required for further processing, you must need to render html input control corresponding to that property. Only then you can able to submit those properties.
Please let me know if problem still persist.

I have multiple "quantity" textboxes, how do I post values of all textboxes

How do I get the entered values of textboxes to post to my controller with values. Thought of using arrays but have no idea where to start. Textboxes are dynamic due to each have counter number
#{
var counter = 0;
foreach (var addedProduct in Model)
{
counter++;
<tr class="default" data-ng-controller="deleteItemsController">
<td>#addedProduct.name</td>
<td>#addedProduct.price</td>
<td>
<input type="text" id="quantity_#counter" name="quantity_#counter"/>
</td>
</tr>
}
}
<tr>
<td><b>Total:</b>{{#ViewBag.total}}</td>
<td>
#using (Html.BeginForm("Index", "Payment"))
{
//how do i get quantity values//
<input type="submit" value="pay">
}
</td>
my model:
public class ProductVar
{
public int productID { get; set; }
public string name { get; set; }
public int price { get; set; }
public int quantity { get; set; }
public int total { get; set; }
}
I have not implemented method yet, trying to figure out how to post all values first.
You are pretty close. Read this blog post from Phil Haack and things will be clear how your input fields should be named. You will understand how model binding works in ASP.NET MVC as well.
Also you should place your <input> fields inside the HTML <form> or nothing will ever be sent to the server when this form gets submitted.
So start by modifying your view code so that you use strongly typed HTML helpers for generating your input fields. They will take car of properly naming them:
#using (Html.BeginForm("Index", "Payment"))
{
for (var i = 0; i < Model.Count; i++)
{
<tr class="default" data-ng-controller="deleteItemsController">
<td>#Html.DisplayFor(x => x[i].name)</td>
<td>#Html.DisplayFor(x => x[i].price)</td>
<td>
#Html.EditorFor(x => x[i].quantity)
</td>
</tr>
}
<tr>
<td>
<b>Total:</b>
#ViewBag.total
</td>
<td>
<input type="submit" value="pay" />
</td>
</tr>
}
Now you can have your controller action that will be triggered when the form is submitted:
[HttpPost]
public ActionResult Index(ProductVar[] model)
{
...
}
In this example only the quantity field of the model will be sent to the server because that's the only input field present inside the HTML form and so the only values sent to the server. If you want to bind the other values you might include them as hidden fields as well:
#Html.HiddenFor(x => x[i].name)
#Html.HiddenFor(x => x[i].price)
Of course in most cases this is bad practice because the user can manipulate those values. The best approach is to retrieve them from the db in your POST action using the same id that you used to retrieve them in the GET action that you used to render this form.

Categories

Resources