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

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.

Related

Why the database view show data correct but when I called it in controller show different result?

I changed the project design and created database view :
ALTER VIEW [dbo].[samplesList]
AS
SELECT DISTINCT
results.machine_id,
sample_id,
program_id,
Machines.Machine_id AS 'ID',
custid,
sys_users.user_full_name AS 'HospitalName',
Programs.name AS 'ProgramName',
machines.Machine_name AS 'MachineName',
samples.name AS 'SampleName'
FROM
results
INNER JOIN
programs ON RESULTS.program_id = Programs.id
INNER JOIN
Machines ON RESULTS.machine_id = Machines.Machine_id
INNER JOIN
sys_users ON RESULTS.custid = sys_users.user_id
INNER JOIN
samples ON RESULTS.sample_id = samples.id
This is the result in the database :
See the screenshot - it shows the correct data sample no 1, sample no 2, sample no 3 and their machines are correct.
But in the controller when I link the view with the controller its not show same result from the database this is the controller code :
public ActionResult Indexs()
{
int UserId = Convert.ToInt32(Session["UserID"]);
var samples = _context.samplesLists
.Where(x=> x.custid == UserId).ToList();
return View(samples);
}
This is the model :
namespace warehouse.Models
{
using System;
using System.Collections.Generic;
public partial class samplesList
{
public Nullable<int> machine_id { get; set; }
public Nullable<int> sample_id { get; set; }
public Nullable<int> program_id { get; set; }
public int ID { get; set; }
public Nullable<int> custid { get; set; }
public string HospitalName { get; set; }
public string ProgramName { get; set; }
public string MachineName { get; set; }
public string SampleName { get; set; }
}
}
And finally the surprise this is the output for same view in the site :
All data appears as "sample no 1":
This is the view markup:
#model IEnumerable<warehouse.Models.samplesList>
#{
ViewBag.Title = "Indexs";
Layout = "~/Views/Shared/_LayoutDashboard.cshtml";
}
<img style="margin-left:250px;" src="~/images/weblogo.png" />
<p style="margin-left:40px;">
#*<h3 style="margin-left:100px; font-family:Andalus;text-underline-position:below">
#Html.Label("Hospital Name :")
#Html.DisplayFor(model => Model.FirstOrDefault().)
</h3>*#
<table class="table table-bordered">
<tr style="background-color:hotpink">
<th>
#Html.DisplayNameFor(model => model.ProgramName)
</th>
<th>
#Html.DisplayNameFor(model => model.SampleName)
</th>
<th>
#Html.DisplayNameFor(model => model.MachineName)
</th>
#*<th></th>
<th></th>
<th></th>*#
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.ProgramName)
</td>
<td>
#Html.DisplayFor(modelItem => item.SampleName)
</td>
<td>
#Html.DisplayFor(modelItem => item.MachineName)
</td>
Please I need your help what happening why all solutions not working and what I need to change ?
EF has its share of troubles with views - because views typically don't have a defined primary key, which is crucial for EF to detect which data has already been loaded (and which hasn't).
What happens here is: since there's no primary key for your view, EF will just use all non-nullable columns from the view as a "substitute" PK.
And when EF reads the data, it will go:
read the row
check the primary key (or the "substitute" PK in this case)
if it has already read a row with that PK - it just duplicates that row that it already has - it will disregard any non-PK columns actually read from the view!
So in your case, once it's read a first row with the "substitute" PK, any further rows with those same values will just get the already read values - no matter what's stored in the database!
SOLUTION: EF needs a proper primary key to uniquely identify each individual row of data.
One solution is to add every single PK of all the underlying tables, and make sure those are marked as "non-nullable" in your model class. Another solution might be to add a custom ROW_NUMBER() OVER (....) column as an "artificial" dummy PK which gives each row a unique value - so that no two rows being read from the view are considered identical.
I solved it in different way as marc_s said there is different activity in database views and primary keys not deal like the tables primary key
What I did :
I created new TABLE (MASTER_RESULTS) and inserted the data I need without duplicate and created the controller and view based on this master table .

C# / ATATA / Selenium: Using ControlList to store multiple tables

I am authoring a Page Object for my company that represents a HTML page which contains many tables (and is badly structured). I am only interested in certain tables on this page, and would like to have a single table to reference on my Page Object for simplicity.
Problems
This page is dynamic and loads various amounts of tables.
The "displayed table" of a single workflow is split into 3 tables in HTML.
Table 1 contains the unique identifier.
Table 2 contains buttons I am not concerned with.
Table 3 (wrapped in a div) contains the actual table data I need to retrieve.
Tables are not organized, grouped, or nested in any fashion.
Only organization is the repeating flat structure of the "displayed table". Structure does not change (generated from ASP.Net)
Goals
Have a ControlList that represents each of the "displayed tables".
Stick with ATATA (I have a Selenium solution for this, but the majority of our Page Objects use ATATA and do not want to deviate)
Store the Name of the Workflow as a variable on each of the Table objects (WorkflowName variable)
Here is an abstraction of the HTML code I am working with.
<div>
<table> <!-- Start of Displayed Table. Shows as a single row header -->
<tbody>
<tr>
<td>
<h2 id='WorkflowHeader'> Workflow Identifier </h2>
</td>
</tr>
</tbody>
</table>
<table>
<!-- This table contains buttons that I am not concerned with -->
</table>
<div>
<table> <!-- Start of multi row table that contains data to be retrieved -->
<tr>
<td>Value I want in a table</td>
<td>Value I want in a table</td>
</tr>
</table>
<br /> <!-- End of "Displayed Table" -->
<!-- The above structure repeats for every Workflow type. basic structure below -->
<table></table>
<table></table>
<div>
<table></table>
</div>
<br />
<!-- Basic repeating table structure above -->
</div>
In my ATATA Page Object, I have the following:
using Atata;
using _ = ProjectNameSpace.WorkflowPageObject;
namespace ProjectNameSpace
{
public class WorkflowPageObject : Page<_>
{
public ControlList<WorkflowTable, _> WorkflowTables { get; private set; }
[ControlDefinition("h2[contains(#id, 'WorkflowHeader')]/../../../../following-sibling::div/table", ComponentTypeName = "table")]
public class WorkflowTable: Table<WorkflowRow, _>
{
[FindByXPath("h2[contains(#id, 'WorkflowHeader')]")]
public H2<_> WorkflowName { get; private set; }
}
[ControlDefinition("h2[contains(#id, 'WorkflowHeader')]/../../../../following-sibling::div/table/tbody/tr"), ComponentTypeName = "row")]
public class WorkflowRow: TableRow<_>
{
[FindByColumnHeader(HeaderName1)]
public Content<string, _> TableData1 { get; private set; }
[FindByColumnHeader(HeaderName2)]
public Content<string, _> TableData2 { get; private set; }
[FindByColumnHeader(HeaderName3)]
public Content<string, _> TableData3 { get; private set; }
[FindByColumnHeader(HeaderName4)]
public Content<string, _> TableData4 { get; private set; }
[FindByColumnHeader(HeaderName5)]
public Content<string, _> TableData5 { get; private set; }
[FindByColumnHeader(HeaderName6)]
public Content<string, _> TableData { get; private set; }
}
}
}
When I get to this page and attempt to access any of the TableData, I get the following error:
{"Unable to locate element: By.XPath: (.//h2[contains(#id,
'WorkflowHeader')]/../../../../following-sibling::div/table/tbody/tr)
[1]\r\nContext element:\r\nTag: table\r\nLocation: {X=X,Y=Y}\r\nSize:
{Width=Width, Height=Height}\r\nText: HeaderName1 HeaderName2 HeaderName3
HeaderName4 HeaderName5 HeaderName6\r\nTableData1 TableData2 TableData3
TableData4 TableData5 TableData6"}
I feel like I am not using the ControlDefinitions correctly. My XPath is sound and is returning multiple elements. If I extract the XPath that is being used to find the element and use AtataContext.Current.Driver.FindElementsByXPath(".//h2[contains(#id, 'WorkflowHeader')]/../../../../following-sibling::div/table/tbody/tr")[1] the correct rows are returned.
Note: This code was obfuscated and any misspellings of variables or typos are most likely due to hand typing portions of code in this post. The code builds and runs.
Any help is greatly appreciated.
I assume that you don't need ControlDefinition at WorkflowRow class. Just remove it and try. When you do find rows of table it is already scoped to appropriate <table> element and looks for the children (rows) inside that element, not the whole page.
I can also recommend you to update ControlDefinition of WorkflowTable to the following:
[ControlDefinition("table[.//h2[contains(#id, 'WorkflowHeader')]]", ComponentTypeName = "table")]

Atata – How to check hidden checkbox within table?

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; }

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.

Rendering partial view in ASP.Net MVC3 Razor using Ajax

The base functionality I wish to achive is that the contents of a table are updated when a dropdownlist item is selected. This will update when the user makes a new selection and retrieve new information from the database and repopulate the table.
It's also worth noting that the DropDownListFor that I want the .change() to work with is not contained within the AjaxForm but appears elsewhere on the page (admittedly in another form)
To achieve this I looked at this question: Rendering partial view dynamically in ASP.Net MVC3 Razor using Ajax call to Action which does a good job of going part the way of what I want to do.
So far, I have a controller method which handles populating a customized viewmodel for the partial view:
[HttpPost]
public ActionResult CompanyBillingBandDetails(int id = 0)
{
var viewModel = new BillingGroupDetailsViewModel();
var billingGroupBillingBands =
_model.GetAllRecordsWhere<BillingGroupBillingBand>(x => x.BillingGroupId == id).ToList();
foreach (var band in billingGroupBillingBands)
{
viewModel.BillingBands.Add(band.BillingBand);
}
return PartialView("BillingGroupDetailsPartial", viewModel);
}
The ViewModel I wish to populate each call:
public class BillingGroupDetailsViewModel
{
public List<BillingBand> BillingBands { get; set; }
}
The strongly typed model I'm using as a model for the partial view
public class BillingBandsObject
{
public int BillingBandId { get; set; }
public int RangeFrom { get; set; }
public int RangeTo { get; set; }
public Decimal Charge { get; set; }
public int BillingTypeId { get; set; }
public bool Delete { get; set; }
}
The partial view it populates and returns:
#model xxx.xxx.DTO.Objects.BillingBandsObject
<tr>
<td>
#Html.DisplayTextFor(x => x.RangeFrom)
</td>
</tr>
<tr>
<td>
#Html.DisplayTextFor(x => x.RangeTo)
</td>
</tr>
<tr>
<td>
#Html.DisplayTextFor(x => x.Charge)
</td>
</tr>
The Razor code for this section of the page:
<table>
<thead>
<tr>
<th>
Range From
</th>
<th>
Range To
</th>
<th>
Charge
</th>
</tr>
</thead>
<tbody>
#using (Ajax.BeginForm("CompanyBillingBandDetails", new AjaxOptions() { UpdateTargetId = "details", id = "ajaxform" }))
{
<div id="details">
#Html.Action("CompanyBillingBandDetails", new { id = 1 })
</div>
}
</tbody>
</table>
and finally the function I lifted almost straight from Darin's answer:
$(function () {
$('#billinggrouplist').change(function () {
// This event will be triggered when the dropdown list selection changes
// We start by fetching the form element. Note that if you have
// multiple forms on the page it would be better to provide it
// an unique id in the Ajax.BeginForm helper and then use id selector:
var form = $('#ajaxform');
// finally we send the AJAX request:
$.ajax({
url: form.attr('action'),
type: form.attr('method'),
data: form.serialize(),
success: function (result) {
// The AJAX request succeeded and the result variable
// will contain the partial HTML returned by the action
// we inject it into the div:
$('#details').html(result);
}
});
});
});
At the moment I have fought through a number of errors, currently I am faced with :
"Error executing child request for handler 'System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerAsyncWrapper'."
However, i feel my understanding of the problem as a whole may be lacking.
Any help appreciated!
This error means that there was an exception while rendering your child view. Probably something related to your data, ie. NulLReferenceException.
Just attach your debugger and set to to break when an exception is thrown.

Categories

Resources