I have a Selenium test suite that is running Selenium integration tests against a number of web applications, some that are written in Angular 2+, and some that are written in AngularJS.
We use a custom ExpectedCondition with WebDriverWait that we use to make test cases wait until AngularJS apps have finished loading, in order to avoid waiting an arbitrary amount of time:
private static ExpectedCondition<Boolean> angularIsFinished() {
return new ExpectedCondition<Boolean>() {
public Boolean apply(final WebDriver driver) {
Object result = null;
while(result == null || result.toString().equals("undefined")) {
result = ((JavascriptExecutor)driver).executeScript("return typeof angular;");
try {
Thread.sleep(200L);
} catch (final InterruptedException ex) {
logger.error("Error while trying to sleep", ex);
}
}
final String script = " var el = document.querySelector(\"body\");\n" +
" var callback = arguments[arguments.length - 1];\n" +
" angular.element(el).injector().get('$browser').notifyWhenNoOutstandingRequests(callback);";
((JavascriptExecutor)driver).executeAsyncScript(script);
return true;
}
public String toString() {
return "Wait for AngularJS";
}
};
}
However, return typeof angular; will always return undefined for an Angular 2+ app. Is there a similar way to AngularJS's notifyWhenNoOutstandingRequests that you can use to determine when an Angular 2+ app has finished loading?
This question mentions using NgZone as a possible solution, but how would you get a handle on that via a script executed via JavascriptExecutor?
You can check it by calling e.g. document.querySelector('app-root')? or arbitrary component selector...
Or what about calling document.readyState? It should have result 'complete' after fully loaded wep page and it doesn't matter if web page is based on angular.
Thanks to #Ardesco's answer, I was able to do something similar to what Protractor does, using the window.getAllAngularTestabilities function. Here is the script that I run to determine if the Angular 2+ page loads:
var testability = window.getAllAngularTestabilities()[0];
var callback = arguments[arguments.length - 1];
testability.whenStable(callback);
And here is what the complete ExpectedCondition looks like that works for both AngularJS and Angular 2+:
private static ExpectedCondition<Boolean> angularIsFinished() {
return new ExpectedCondition<Boolean>() {
public Boolean apply(final WebDriver driver) {
Object result = null;
boolean isAngular2Plus = false;
while(result == null || result.toString().equals("undefined")) {
result = ((JavascriptExecutor)driver).executeScript("return typeof angular;");
if (result == null || result.toString().equals("undefined")) {
result = ((JavascriptExecutor)driver).executeScript("return typeof window.getAngularTestability;");
if (result != null && !result.toString().equals("undefined")) {
isAngular2Plus = true;
}
}
try {
Thread.sleep(200L);
} catch (final InterruptedException ex) {
logger.error("Error while trying to sleep", ex);
}
}
final String script;
if (isAngular2Plus) {
script =" var testability = window.getAllAngularTestabilities()[0];\n" +
" var callback = arguments[arguments.length - 1];\n" +
" testability.whenStable(callback);";
} else {
script =" var el = document.querySelector(\"body\");\n" +
" var callback = arguments[arguments.length - 1];\n" +
" angular.element(el).injector().get('$browser').notifyWhenNoOutstandingRequests(callback);";
}
((JavascriptExecutor) driver).executeAsyncScript(script);
return true;
}
public String toString() {
return "Wait for AngularJS";
}
};
}
Looking at the Protractor code I have come up with two possible solutions:
First of all we have an option where we find a list of testability's, then add a callback to all of them, and then wait for one of them to flag the site as testable (This does mean that your script will continue after any one testability has become testable, it will not wait for all of them to become testable).
private static ExpectedCondition angular2IsTestable() {
return (ExpectedCondition<Boolean>) driver -> {
JavascriptExecutor jsexec = ((JavascriptExecutor) driver);
Object result = jsexec.executeAsyncScript("window.seleniumCallback = arguments[arguments.length -1];\n" +
"if (window.getAllAngularTestabilities()) {\n" +
" window.getAllAngularTestabilities().forEach(function (testability) {\n" +
" testability.whenStable(window.seleniumCallback(true))\n" +
" }\n" +
" );\n" +
"} else {\n" +
" window.seleniumCallback(false)\n" +
"}"
);
return Boolean.parseBoolean(result.toString());
};
}
The second option is to specifically check an angular root elements testability state:
private static ExpectedCondition angular2ElementIsTestable(final WebElement element) {
return (ExpectedCondition<Boolean>) driver -> {
JavascriptExecutor jsexec = ((JavascriptExecutor) driver);
Object result = jsexec.executeAsyncScript(
"window.seleniumCallback = arguments[arguments.length -1];\n" +
"var element = arguments[0];\n" +
"if (window.getAngularTestability && window.getAngularTestability(element)) {\n" +
" window.getAngularTestability(element).whenStable(window.seleniumCallback(true));\n" +
"} else {\n" +
" window.seleniumCallback(false)\n" +
"}"
, element);
return Boolean.parseBoolean(result.toString());
};
}
The second option is more targeted and therefore more reliable if you want to test a specific area of the site.
A third option would be to write something a bit more complicated that tracks the state of all testability's and then only fires a true callback when all of them have become true. I don't have an implementation for this yet.
I work on a project that has a string field (the name is urlOrContent) and it can be small (less than 50 character) or very long (more than 50 character), and I just want to return the first 50 characters every time based on a specific query. My database is elasticsearch and my problem is raised in this link and the questioner’s response seems to be correct (urlOrContent field is analyzed and non stored text field). It uses following script:
{
"script_fields": {
"substring": {
"script": {
"lang": "painless",
"inline": "params._source.text.substring(0, 100)"
}
}
}
}
But my main problem is that I can not find the equivalent of elasticsearch java api code. In fact, what should be added to the code below, which only returns the first 50 characters of the urlOrContent field? Note that this field may not even have 50 characters in some cases, and then the entire string should be returned.
String queryString =
EnumLinkFields.CREATE_TIME.getFieldName() + ":(>=" + dateFrom + " AND <=" + dateTo + ")";
QueryBuilder query = QueryBuilders.queryStringQuery(queryString);
SearchResponse response = TRANSPORT_CLIENT.prepareSearch(MY_INDEX)
.setTypes(MY_TYPE)
.setSearchType(SEARCH_TYPE)
.setQuery(query)
.setFetchSource(null, new String[]{EnumLinkFields.USER_ID.getFieldName()})
.setFrom(offset)
.setSize(count)
.addSort(orderByField, sortOrder)
.execute().actionGet();
I found the best answer.
String queryString =
EnumLinkFields.CREATE_TIME.getFieldName() + ":(>=" + dateFrom + " AND <=" + dateTo + ")";
QueryBuilder query = QueryBuilders.queryStringQuery(queryString);
String codeUrlOrContent = "if (" + EnumElasticScriptField.URL_OR_CONTENT.getFieldName() + ".length() > 50) {" +
"return " + EnumElasticScriptField.URL_OR_CONTENT.getFieldName() + ".substring(0, 50);" +
"} else { " +
"return " + EnumElasticScriptField.URL_OR_CONTENT.getFieldName() + "; }";
Script scriptUrlOrContent = new Script(ScriptType.INLINE, "painless",
codeUrlOrContent, Collections.emptyMap());
Script scriptIsUrl = new Script(ScriptType.INLINE, "painless",
EnumElasticScriptField.IS_URL.getFieldName(), Collections.emptyMap());
SearchResponse response = TRANSPORT_CLIENT.prepareSearch(MY_INDEX)
.setTypes(MY_TYPE)
.setSearchType(SEARCH_TYPE)
.setQuery(query)
.addScriptField(EnumLinkFields.URL_OR_CONTENT.getFieldName(),
scriptUrlOrContent)
.addScriptField(EnumLinkFields.IS_URL.getFieldName(), scriptIsUrl)
.setFrom(offset)
.setSize(count)
.addSort(orderByField, sortOrder)
.execute().actionGet();
Note that the call to the setFetchSource function must be removed and all returned fields must be returned through the script.
You can put your script_fields query in the query object, i.e. in setQuery(query).
Your query object should be looking like this right now.
"query" : {
"term" : { "user" : "kimchy" }
}
After you add the script_fields in the object, it should become:
"query" : {
"term" : { "user" : "kimchy" }
},
"script_fields": {
"urlOrContent": {
"script": {
"lang": "painless",
"inline": "if(params._source.urlOrContent.length() > 50){
params._source.urlOrContent.substring(0, 50)
}
else {
params._source.urlOrContent
}"
}
}
}
The resulting hits will have a fields array with the substring you required.
You have to enable scripting by changing the elasticsearch.yml file like so and restart the elasticsearch:
script.engine.painless.inline.aggs: on
script.engine.painless.inline.update: on
script.inline: on
script.indexed: on
I am trying to drag an element into another element using Selenium WebDriver but it's not working. I tried all the solutions which I can find on the internet but none of the solutions seems to be working for me.
WebElement sourceelement = driver.findElement(By.cssSelector("XXX"));
WebElement destelement = driver.findElement(By.cssSelector("YYY"));
Code1:-
Actions builder = new Actions( _controls.getDriver());
builder.dragAndDrop(sourceelement, destelement);
Code2:-
Actions builder = new Actions(_controls.getDriver());
Action dragAndDrop =
builder.clickAndHold(sourceelement).moveToElement(destelement).release(destelement).build();
Thread.sleep(2000);
dragAndDrop.perform()
Code3:-
Point coordinates1 = sourceelement.getLocation();
Point coordinates2 = destelement.getLocation();
Robot robot = new Robot();
robot.mouseMove(coordinates1.getX(), coordinates1.getY());
robot.mousePress(InputEvent.BUTTON1_MASK);
robot.mouseMove(coordinates2.getX(), coordinates2.getY());
robot.mouseRelease(InputEvent.BUTTON1_MASK);
Thread.sleep(2000);
Code4:-
final String java_script =
"var src=arguments[0],tgt=arguments[1];var dataTransfer={dropEffe" +
"ct:'',effectAllowed:'all',files:[],items:{},types:[],setData:fun" +
"ction(format,data){this.items[format]=data;this.types.append(for" +
"mat);},getData:function(format){return this.items[format];},clea" +
"rData:function(format){}};var emit=function(event,target){var ev" +
"t=document.createEvent('Event');evt.initEvent(event,true,false);" +
"evt.dataTransfer=dataTransfer;target.dispatchEvent(evt);};emit('" +
"dragstart',src);emit('dragenter',tgt);emit('dragover',tgt);emit(" +
"'drop',tgt);emit('dragend',src);";
((JavascriptExecutor)_controls.getDriver()).executeScript(java_script, sourceelement, destelement);
Thread.sleep(2000);
None of the above codes is working for me. All the above runs without any error but drag and drop is not happening in the application. Does anyone have any other solution? Thanks.
Can you try Java Script Executor for this
JavascriptExecutor js = (JavascriptExecutor)driver
js.executeScript("function createEvent(typeOfEvent) {\n" + "var event =document.createEvent(\"CustomEvent\");\n"
+ "event.initCustomEvent(typeOfEvent,true, true, null);\n" + "event.dataTransfer = {\n" + "data: {},\n"
+ "setData: function (key, value) {\n" + "this.data[key] = value;\n" + "},\n"
+ "getData: function (key) {\n" + "return this.data[key];\n" + "}\n" + "};\n" + "return event;\n"
+ "}\n" + "\n" + "function dispatchEvent(element, event,transferData) {\n"
+ "if (transferData !== undefined) {\n" + "event.dataTransfer = transferData;\n" + "}\n"
+ "if (element.dispatchEvent) {\n" + "element.dispatchEvent(event);\n"
+ "} else if (element.fireEvent) {\n" + "element.fireEvent(\"on\" + event.type, event);\n" + "}\n"
+ "}\n" + "\n" + "function simulateHTML5DragAndDrop(element, destination) {\n"
+ "var dragStartEvent =createEvent('dragstart');\n" + "dispatchEvent(element, dragStartEvent);\n"
+ "var dropEvent = createEvent('drop');\n"
+ "dispatchEvent(destination, dropEvent,dragStartEvent.dataTransfer);\n"
+ "var dragEndEvent = createEvent('dragend');\n"
+ "dispatchEvent(element, dragEndEvent,dropEvent.dataTransfer);\n" + "}\n" + "\n"
+ "var source = arguments[0];\n" + "var destination = arguments[1];\n"
+ "simulateHTML5DragAndDrop(source,destination);", ElementFrom, ElementTo);
Refrence: https://www.linkedin.com/pulse/javascriptexecutor-selenium-gaurav-gupta/
It works for me on web application which are angular based.
If the known cases do not work, you can try this solution
WebElement a = driver.findElement(By.cssSelector("your_selector"));
WebElement b = driver.findElement(By.cssSelector("your_selector"));
int x = b.getLocation().x;
int y = b.getLocation().y;
Actions actions = new Actions(driver);
actions.moveToElement(a)
.pause(Duration.ofSeconds(1))
.clickAndHold(a)
.pause(Duration.ofSeconds(1))
.moveByOffset(x, y)
.moveToElement(b)
.moveByOffset(x,y)
.pause(Duration.ofSeconds(1))
.release().build().perform();
If you want to drag the element in a different area(not to drag the element to another one) you can use this scenario for that:
WebElement source = driver.findElement(By.cssSelector("your_selector"));
Actions actions = new Actions(driver);
actions.moveToElement(source)
.pause(Duration.ofSeconds(1))
.clickAndHold(source)
.pause(Duration.ofSeconds(1))
.moveByOffset(0,50) //change the x, y values to be applicable for your cases
.moveByOffset(0,50)
.pause(Duration.ofSeconds(1))
.release().build().perform();
}
I have also faced the same issue. Please find below custom java-script function for drag & drop.
1) Create DragDrop.js file and paste below code in it
function customEvent(typeOfEvent) {
var event = document.createEvent("CustomEvent");
event.initCustomEvent(typeOfEvent, true, true, null);
event.dataTransfer = {
data: {},
setData: function (key, value) {
this.data[key] = value;
},
getData: function (key) {
return this.data[key];
}
};
return event;
}
function dispatchEvent(element, event, transferData) {
if (transferData !== undefined) {
event.dataTransfer = transferData;
}
if (element.dispatchEvent) {
element.dispatchEvent(event);
} else if (element.fireEvent) {
element.fireEvent("on" + event.type, event);
}
}
function executeDrageAndDrop(element, target) {
var dragStartEvent = customEvent('dragstart');
dispatchEvent(element, dragStartEvent);
var dropEvent = customEvent('drop');
dispatchEvent(target, dropEvent, dragStartEvent.dataTransfer);
var dragEndEvent = customEvent('dragend');
dispatchEvent(element, dragEndEvent, dropEvent.dataTransfer);
}
2) Using below code we can call above custom function(Below is C# code)
string script = System.IO.File.ReadAllText(#"{filepath of DragDrop.js file}");
script = script + "executeDrageAndDrop(arguments[0], arguments[1])";
IJavaScriptExecutor executor = (IJavaScriptExecutor)driver;
IWebElement source = driver.findElement(By......);
IWebElement target = driver.findElement(By......);
executor.ExecuteScript(script, source, target);
I would suggest you try the following solution:
WebElement sourceelement = driver.findElement(By.cssSelector("XXX"));
Locatable element = (Locatable)sourceelement ;
Point p= element.getCoordinates().inViewPort();
int sourceX=p.getX();
int sourceY=p.getY();
WebElement destelement = driver.findElement(By.cssSelector("YYY"));
Locatable elementTarget = (Locatable)destelement;
Point Target= elementTarget.getCoordinates().inViewPort();
int targetX=Target.getX();
int targetY=Target.getY();
You may then use Robot to drag and drop the element
Please refer this Java Script based solution with sample web application given By Dmitrii Bormotov in Medium. It's in python (With little tweak you can use it in java)
I tried using plain selenium Actions DragAndDrop method, with different combinations and it was not at all working for me.
You might want to check if webelement is enabled or displayed prior performing desired action over it. You may give it a try with below code
public void dragAndDrop(WebElement sourceElement, WebElement destinationElement) {
try {
if (sourceElement.isDisplayed() && destinationElement.isDisplayed()) {
Actions action = new Actions(driver);
action.dragAndDrop(sourceElement, destinationElement).build().perform();
} else {
System.out.println("Element was not displayed to drag");
}
} catch (StaleElementReferenceException e) {
System.out.println("Element with " + sourceElement + "or" + destinationElement + "is not attached to the page document "
+ e.getStackTrace());
} catch (NoSuchElementException e) {
System.out.println("Element " + sourceElement + "or" + destinationElement + " was not found in DOM "+ e.getStackTrace());
} catch (Exception e) {
System.out.println("Error occurred while performing drag and drop operation "+ e.getStackTrace());
}
}
public void dragAndDrop(WebElement sourceElement, WebElement destinationElement)
{
(new Actions(driver)).dragAndDrop(sourceElement, destinationElement).perform();
}
}
I would suggest you to use Touch Action to perform drag and drop.
Point coordinates1 = sourceelement.getLocation();
Point coordinates2 = destelement.getLocation();
TouchActions builder = new TouchActions(driver);
builder.longPress(coordinates1)
.move(coordinates2).release(coordinates2).perform();
I have face similar problem way back, I have used dragAndDropBy to move slider but it didn't worked for me but later I found help and below the snippet for my working code:
public static void slider(){
x=10;
WebElement slider = driver.findElement(By.id("slider"));
int width=slider.getSize().getWidth();
Actions move = new Actions(driver);
move.moveToElement(slider, ((width*x)/100), 0).click();
move.build().perform();
System.out.println("Slider moved");
}
You can refer the link here
You may try executing the following javascript to perform drag and drop
WebDriver _driver;
WebElement _sourceElement = _driver.findElement(<source>);
WebElement _targetElement = _driver.findElement(<source>);
JavascriptExecutor _js = (JavascriptExecutor) _driver;
_js.executeScript("$(arguments[0]).simulate('drag-n-drop',{dragTarget:arguments[1],interpolation:{stepWidth:100,stepDelay:50}});", _sourceElement, _targetElement);
Please find more details here.
It works perfectly for all the browsers and devices.
This is working for me :
Actions act = new Actions(driver);
act.moveToElement(element, (elementWidth / 2), elementHeight / 2).clickAndHold().build().perform();
act.moveToElement(dest, (destWidth / 2) , (destHeight / 2)).release().build().perform();
There are sometimes bugs in some version of selenium. Make sure you use the lastest one and play around with clicking. Would be easier of you can send a link of what you are trying to drag/drop
I've tried a bunch of workarounds, this one seems to work for me using macOS and chromedriver
public void dragAndDrop(WebElement source, WebElement target) throws AWTException {
new Actions(driver).dragAndDrop(source, target).release().build().perform();
Robot robot = new Robot();
robot.keyPress(KeyEvent.VK_ESCAPE);
robot.keyRelease(KeyEvent.VK_ESCAPE);
}
In your code 1:
Not calling perform() method,
it should be
Actions builder = new Actions( _controls.getDriver());
builder.dragAndDrop(sourceelement, destelement).perform();
In your code 2: I don't think you need to call release()
Please search for similar questions before posting.
Actions act = new Actions(driver);
WebElement source = driver.findElement(By.id("XXX"));
WebElement destination = driver.findElement(By.id("XXX"));
Action dragAndDrop =act.moveToElement(source,destination).build().perform();
I am trying to wait until masonry has fully loaded with this code in Java
String javaScript = "function defer() {"
+"if (window.jQuery.masonry)"
+"{ return true }"
+"else"
+"{ return false }"
+"}; defer()";
boolean isMasonryLoaded=false;
while(!isMasonryLoaded)
{
isMasonryLoaded = (boolean)(((JavascriptExecutor)driver).executeScript(javaScript));
}
However i get NullPointerException at while(!isMasonryLoaded)
Any ideas ?
May be your script function defer() does not define before calling, you should try using executeAsyncScript as below :-
String javaScript = "var callback = arguments[0];"+
"function defer() {if (window.jQuery.masonry){ "+
"callback(true) }else{ callback(false) }}; defer()";
boolean isMasonryLoaded=false;
while(!isMasonryLoaded)
{
isMasonryLoaded = (boolean)(((JavascriptExecutor)driver).executeAsyncScript(javaScript));
}
Hope it helps..:)
I am trying to implement the jQuery autocomplete custom data and display. Right now I'm using just the regular autocomplete. My problem is converting the json array of json objects to an array of javascript objects. Could someone please point me in the right direction. Here is what I currently have for the standard autocomplete.
javascript:
$('input[type=radio][name=transaction]').change(function(){
console.log($('input[type=radio][name=transaction]:checked').val());
$.ajax({
type: "post",
async: false,
url: "/autocomplete",
contenttype: "application/json; charset=utf-8",
datatype: "json",
data: "transaction="+$('input[type=radio][name=transaction]:checked').val(),
success: function (data)
{
if($('input[type=radio][name=transaction]:checked').val() == "sell")
{
theSymbols = JSON.parse(data);
}
else
{
theSymbols = data;
}
},
error: function (bad)
{
alert("Autocomplete failed.");
}
});
if($('input[type=radio][name=transaction]:checked').val() == "sell")
{
$("#symbol").autocomplete(
{
minLength: 0,
source: theSymbols,
focus: function(event, ui) {
$("#symbol").val(ui.item.value);
return false;
},
select: function(event, ui)
{
$("#symbol").val( ui.item.value);
$("#sharesOwn").html( ui.item.shares);
$("#sharesText").show();
return false;
}
})
.autocomplete("instance")._renderItem = function(ul, item)
{
return $("<li>")
.append("<a>" + item.label + "<br><span class='acFormat'>" + item.desc + "</span></a>")
.appendTo(ul);
};
}
else
{
$("#symbol").autocomplete({source: theSymbols});
}
java:
ArrayList<String> symbols = new ArrayList<String>();
ArrayList<Position> positionList = new ArrayList<Position>();
positionList = PositionDB.getPositions(user.getPortfolioID(), "O");
for (Position p : positionList)
{
String data = "";
data = "{";
data += "value: " + p.getSymbol();
data += ", label: " + p.getSymbol();
data += ", desc: " + p.getCompName();
data += ", shares: " + p.getShares();
data += "}";
symbols.add(data);
}
Gson gson = new Gson();
JsonElement syms = gson.toJsonTree(symbols, new TypeToken<List<String>>() {}.getType());
response.setContentType("application/json; charset=UTF-8");
JsonArray jArray = syms.getAsJsonArray();
PrintWriter out = response.getWriter();
System.out.println(jArray);
out.print(jArray);
out.flush();
Stock object has symbol, company name and price.
I'm having problems creating the JSON array or objects to pass to my javascript. Right now it outputs as if it were a js array of objects but keys are showing up in the textbox, along with the values so the autocomplete must be seeing it as a string, which means that I can't build it as a string in java so I need a different way. Ideas?
Thank you.