I'm practicing Selenium with MVC application that has partial view.
When the page is rendered, I see the form displayed:
<form action="/MyScreen/SaveData?Length=20" data-ajax="true" data-ajax-complete="handleComplete" data-ajax-failure="CheckError" data-ajax-loading="#loader" data-ajax-method="POST" data-ajax-mode="replace" data-ajax-update="#partialPlaceHolder" id="TicketDetailForm" method="post" novalidate="novalidate">
<div id="partialPlaceHolder" style="width:100%;">
<div id="Ticket-Details"></div>
</div>
</form>
This form has a button I need to click.
I'm using Wait to make sure the form is displayed before I click on the button:
WebDriverWait wait = new WebDriverWait(driver, 20);
WebElement ticketForm = wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("TicketDetailForm")));
boolean isForm = ticketForm.isDisplayed();
When executing the above code, I'm getting the following error:
Expected condition failed: waiting for visibility of element located
by By.id: TicketDetailForm.
I modified the code to get the elements gradually and was able to locate the form and a placeholder where the Button is rendered.
I'm using Wait to locate a content of the form:
List<WebElement> forms = renderBody.findElements(By.tagName("form"));
WebElement placeHolder = forms.get(0).findElement(By.xpath("//div[#id='partialPlaceHolder']"));
WebElement ticketDetail = placeHolder.findElement(By.xpath("//div[#id='Ticket_Details']"));
I cannot locate ticketDetail `WebElement
What might that be?
You can check if the form partial view is displayed by checking its element list size and then click the button which you want to click when the list size > 0
For example:
boolean partialViewDisplayed = false;
List<WebElement> partialViewElement= driver.findElements(By.id("Ticket-Details"));
while(!partialViewDisplayed){
if(partialViewElement.size()>0){
//Mark the boolean partialViewDisplayed true
partialViewDisplayed = true;
}
}
//Perform the further operations from here
Related
I am building a blazor app (WASM) where I use some <input>'s with those inputs. to both I binded a variable, for easy overview I put that in the #code part of the (index)page.
on the second <input> I also put an onclick event, and in the onclick event I fill a third variable (ThirdText) with the first (FirstText).
When I start the application, and I click on the first <input>, and fill in some characters and then click on the second <input> the value of the third variable is still empty. But when I go back to the first, change a bit, and click on the second afterwards, it directly fills the NEW value.
The last bahaviour is what I also hoped for in the first situation.
Here I have my code in index.cs:
#page "/"
<h3>#FirstText</h3> <br/>
<h3>#SecondText</h3> <br/>
<h3>#ThirdText</h3> <br/>
<input #bind="FirstText" style="width:50%" /> <br/>
<input #bind="SecondText" #onclick="OnclickHandler" style="width:50%" /> <br/>
#code{
private string FirstText { get; set; } = string.Empty;
private string SecondText { get; set; } = string.Empty;
private string ThirdText { get; set; } = string.Empty;
void OnclickHandler(MouseEventArgs mouseEventArgs)
{
ThirdText = FirstText;
}
}
This code is an easy example, the real application has a lot more logic, but this is the base of my problem.
When I make the first box empty, and click on the second box, again it does not work as expected.
I already tried to first fill the 3th variable in oninitialised() , but then the first variable is also empty so, that does not work.
I know in javascript there is a simular problem, that is because the onclick is firstly called, and the onexit or other event you want to use on the exit of the first box is then forgotten. But in that case, it goes wrong al the time. I hope that blazor has a solution for this situation. Because it goes well in Blazor if the first input was already filled.
The reason your code is not working is because of the timing of the click event:
If the button is pressed on one element and the pointer is moved
outside the element before the button is released, the event is fired
on the most specific ancestor element that contained both elements.
click fires after both the mousedown and mouseup events have fired, in
that order.
source
Long story short, by the time mouseup (or even the mousedown for that matter) occurs, the layout has changed and input element has shifted outside of the range of the mouse pointer. For example, change your code to the following and you will see it works now because the elements do not shift:
<h3>AAA #FirstText</h3> <br/>
<h3>AAA #SecondText</h3> <br/>
<h3>AAA #ThirdText</h3> <br/>
The solution to your problem would depend on what you are trying to achieve, but the basic idea here is to not rely on click events when layout changes occur.
To really prove what is happening, in Chrome Dev Tools go to Sources > Event Listener Breakpoints > Mouse > click and make sure the checkbox is checked. After that, type something in the first input and then click the second input element. The click event will fire, however notice the event target in the below screenshot! It is not the input element but the parent element <article>. This proves that the click event was never dispatched to the input element because it is not underneath the mouse pointer when the mouseup event occurs.
You describe the current behaviour but at the same time you are a bit vague about what you expect. As far as I can tell the code does what you ask for.
And what do you expect (want) to happen when a User uses the Tab key to go to the next input? Is it about mouseclicking or about completing an input?
I think you should consider one of these two changes:
<input #bind="FirstText" style="width:50%" /> <br/>
<input #bind="SecondText" #onfocus="OnUpdateHandler" style="width:50%" />
or, and this seems more logical:
<input #bind="FirstText" #onblur="OnUpdateHandler" style="width:50%" />
<input #bind="SecondText" style="width:50%" />
with
void OnUpdateHandler()
{
ThirdText = FirstText;
}
If that doesn't work then be more specific: when/why do you want ThirdText to mirror FirstText?
#Third Answer:
Here's a test page to capture the problem in it's simplest form.
I've removed most of the original code and made it a simple data entry and save form - without using EditForm.
In the current configuration SaveData isn't called when you enter your name and click on the save button.
Comment out the line <h3>#this.YourName</h3> before the form and it now works. Note there's a second <h3>#this.YourName</h3> below the form that has no impact.
Now change the line to:
<h3>Your name is: #this.YourName</h3>
and it works.
The answer lies in the answer provided by #JesseGood To quote his comment:
When you remove the single line of code, the elements do not shift down anymore so the click event occurs on the button. However when you add the line of code, the element becomes populated causing all the other elements to shift down and by the time you lift your finger off of the mouse the element is no longer under your mouse pointer causing the click event to occur on the parent element.
#page "/"
#using System.Text
<PageTitle>Index</PageTitle>
#*Comment out this line and the event triggers on first click*#
<h3>#this.YourName</h3>
<h3>Your Name is #DisplayName</h3>
<div class="m-2">
Your Name: <input value="#this.YourName" #onchange=this.NameChange style="width:50%" />
</div>
<div class="m-2">
<button class="btn btn-primary" #onclick="SaveData">Save</button> <br />
</div>
<h3>#YourName</h3>
<div>
<pre>
#log.ToString()
</pre>
</div>
#code {
private string YourName { get; set; } = string.Empty;
private StringBuilder log = new StringBuilder();
private string DisplayName = string.Empty;
void NameChange(ChangeEventArgs e)
{
this.YourName = e.Value?.ToString() ?? String.Empty;
log.AppendLine("FirstChange Called");
}
void SaveData(MouseEventArgs mouseEventArgs)
{
this.DisplayName = this.YourName;
log.AppendLine("Value Saved");
}
}
Follow on answer:
When you "bind" in Razor, the Razor compiler actually builds out the following code. In the example I've just wired it up manually so I can output some debug code when the onchange event is raised.
__builder.OpenElement(15, "input");
__builder.AddAttribute(16, "style", "width:50%");
__builder.AddAttribute(17, "value", Microsoft.AspNetCore.Components.BindConverter.FormatValue(this.FirstText));
__builder.AddAttribute(18, "onchange", Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => this.FirstText = __value, this.FirstText));
__builder.SetUpdatesAttributeName("value");
__builder.CloseElement();
First and second Answer replaced
Is it possible to allow multiple clicks on a sigle submit button of a form? I have a form where I want to perform an action on the first submission (first click) and a different action on the second submission (second click).
I am basically using ajax to populate a div in the form during the first submission and I want to submit the form on the second click.
I have tried to put by button in the div to by updated, and after the first click, I update update the div and re-creating the button in the updated div. But if I use this method, how can I set the action method of the newly created button in my controller method for Ajax?
My controller method returns something like
return Content( mystring + <input type='button' value='continue submission'/>
if i use this approach, how do I set the action method of the buttton, or is there another way of doing this?
Use two buttons with JavaScript:
Button 1 is shown initially. On click, it hides itself, shows button 2, and performs your action 1.
Button 2 is hidden initially. It is unhidden by button 1 and on click, it performs your second action.
This looks a little weird but I can tell you how to do this. Take an input type="submit" and make it hidden. Have a variable as var flag = false; When user first clicks you input type="button" call a function and do your stuff and make sure to make the flag=true; In the function itself check if flag=true; the trigger the event of your input type="submit".
Like as follows:
<input type="button" id="btn1" onclick="perfromAction()" value="submit"/>
<input type="submit" id="btn2" value="submit" style="display:none"/>
<script type="text/javascript">
var flag=false;
function performAction()
{
if(flag){
$("#btn2").trigger("click");
}
else{
//do processing
flag=true;
}
I try to made nested form with validation. All works fine, but when I remove one of nested form, validation continue to use removed form. I made jsfiddle example http://jsfiddle.net/sokolov_stas/VAyXu/
When example runs, form are valid. If click "+" button, nested form will be added and valid will be false. Then click "-" button, and valid will be false all the same.
The question is: How to remove dynamic created form from validation processing.
Well, for one thing, a <form> inside of a <form> is not valid HTML.
Second, you're not supposed to be doing DOM manipulation from inside the controller. The controller is for "business" logic. See the section on controllers here
For what you're doing, you'd probably be better off using one form, with an ng-repeat inside of it, and adding additional elements to an array:
<form name="myForm" ng-controller="FormCtrl" ng-submit="doSomething()">
<div ng-repeat="item in items">
<input ng-model="item" type="text" required/>
</div>
<a ng-click="addItem()">+</a>
<a ng-click="removeItem()">-</a>
<button type="submit">Submit</button>
<div>Form valid: {{myForm.$valid}}</div>
</form>
and the controller:
function FormCtrl($scope) {
$scope.items = [];
$scope.addItem = function() {
$scope.items.push(null);
};
$scope.removeItem = function() {
$scope.items.pop();
};
$scope.doSomething = function () {
//your submission stuff goes here.
};
}
Can i use radio button's to select two different partial view, Without using Jquery?
yes and no. a partial can only be loaded (after initial page load) via ajax, so a partial page refresh isn't possible without using ajax. however, you could submit the selected radio button (via javascript) to the controller action and then determine inside the controller which radio button had been selected. It would then just be a case of selecting the appropriate view.
As I said, you can't go down the partial route without ajax in the mix, so the answer is no. also, you'd still have to use javascript in order to use the radio button in the submit, in which case, an ajax solution might be worth thinking about.
[edit] with deference to Splash-X, here's a quick work up of the hidden div scenario:
#*use either #Html.RenderPartial() or #Html.RenderAction() as required*#
<div id="developerDiv" style="display: none">
This is the developer stuff, in reality,
this would be populated as such #*#Html.RenderPartial("DeveloperPartial")*#
</div>
<div id="testerDiv" style="display: none">
And here we have the testers, again,
this would be populated as such #*#Html.RenderPartial("TestersPartial")*#
</div>
<div>
Developer :#Html.RadioButton("team", "developer", new { onclick = "showResult(this)"})
Tester :#Html.RadioButton("team", "tester", new { onclick = "showResult(this)"})
</div>
<div id="partialContainer"></div>
<script type="text/javascript">
function showResult(radio) {
var selected = radio.value;
if (selected == "developer")
document.getElementById("partialContainer").innerHTML
= document.getElementById("developerDiv").innerHTML;
else if (selected == "tester")
document.getElementById("partialContainer").innerHTML
= document.getElementById("testerDiv").innerHTML;
}
</script>
enjoy..
I am trying to create a situation where if a user clicks on an "edit" button in a list of text items, she can edit that item. I am trying to make the "edit" button post back using ajax.
Here's my ajax code:
$(function () {
// post back edit request
$('input[name^="editItem"]').live("click", (function () {
var id = $(this).attr('id');
var sections = id.split('_');
if (sections.length == 2) {
var itemID = sections[1];
var divID = "message_" + itemID;
var form = $("#newsForm");
$.post(
form.attr("action"),
form.serialize(),
function (data) {
$("#" + divID).html(data);
}
);
}
return false;
}));
});
But the form.serialize() command is not picking up all the form controls in the form. It's ONLY picking up a hidden form field that appears for each item in the list.
Here's the code in the view, inside a loop that displays all the items:
**** this is the only control being picked up: ******
#Html.Hidden(indexItemID, j.ToString())
****
<div class="datetext" style="float: right; margin-bottom: 5px;">
#Model.newsItems[j].datePosted.Value.ToLongDateString()
</div>
#if (Model.newsItems[j].showEdit)
{
// *********** show the editor ************
<div id="#divID">
#Html.EditorFor(model => model.newsItems[j])
</div>
}
else
{
// *********** show the normal display, plus the following edit/delete buttons ***********
if (Model.newsItems[j].canEdit)
{
string editID = "editItem_" + Model.newsItems[j].itemID.ToString();
string deleteID = "deleteItem_" + Model.newsItems[j].itemID.ToString();
<div class="buttonblock">
<div style="float: right">
<input id="#editID" name="#editID" type="submit" class="smallsubmittext cancel" title="edit this item" value="Edit" />
</div>
<div style="float: right">
<input id="#deleteID" name="#deleteID" type="submit" class="smallsubmittext cancel" title="delete this item" value="Delete" />
</div>
</div>
<div class="clear"></div>
}
It's not picking up anything but the series of hidden form fields (indexItemID). Why would it not be picking up the button controls?
(The ID's of the edit button controls, by the way, are in the form "editItem_x" where x is the ID of the item. Thus the button controls are central to the whole process -- that's how I figure out which item the user wants to edit.)
UPDATE
The answer seems to be in the jquery API itself, http://api.jquery.com/serialize/:
"No submit button value is serialized since the form was not submitted using a button."
I don't know how my action is supposed to know which button was clicked, so I am manually adding the button to the serialized string, and it does seem to work, as inelegant as it seems.
UPDATE 2
I spoke too soon -- the ajax is not working to update my partial view. It's giving me an exception because one of the sections in my layout page is undefined. I give up -- I can't waste any more time on this. No Ajax for this project.
You could try:
var form = $('#newsForm *'); // note the '*'
Update
Did you change the argument to $.post() as well? I think I may have been a little too simple in my answer. Just change the second argument within $.post() while continuing to use form.attr('action')
New post should look like this:
$.post(
form.attr("action"),
$('#newsForm *').serialize(), // this line changed
function (data) {
$("#" + divID).html(data);
}
);