Selenium - Determine whether web page is finished loading in Angular 2+ - java

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.

Related

Implement a global listener for Selenium tests

I use this code to click on a series of dialog windows with unique button ID. It was working fine till now because the order was strict:
clickConfirmWindow(driver, SURVEY_EXIT_BUTTON_ID_LOCATOR, "Survey exit window");
... and many mode windows with unique ID with random order
protected void clickConfirmWindow(WebDriver driver, String elementId, String name) {
// Check if warning window is displayed using button ID
System.out.println("Searching " + name + " using " + elementId);
if (isClickable(driver, elementId, 1)) {
System.out.println("Found " + name + " using " + elementId);
driver.findElement(By.id(elementId)).click();
}
}
protected void clickConfirmWindow(WebDriver driver, String elementId, String name) {
// Check if warning window is displayed using button ID
System.out.println("Searching " + name + " using " + elementId);
if (isClickable(driver, elementId, 1)) {
System.out.println("Found " + name + " using " + elementId);
driver.findElement(By.id(elementId)).click();
}
}
private Boolean isClickable(WebDriver driver, String elementId, int timeOut) {
try {
new WebDriverWait(driver, timeOut).until(ExpectedConditions.visibilityOfElementLocated(By.id(elementId)));
return true;
} catch (TimeoutException e) {
e.printStackTrace();
return false;
}
}
But now the order of the windows is random. Is it possible to implement some kind of global listener which listens for web element ID thru out the entire application and clicks it if it's present?
not sure about your specific situation but in vba I would write an if statement to see if it was true,
If driver.findelementsbyid("your Id").count > 0 then
driver.findelementbyid("your Id").click
Else
End if
Searching if the element exists should help you
WebElement elementName = driver.findElement(By.id("idElement"));
if(elementName != null){
//Loaded Element
}

Why drag and drop is not working in Selenium Webdriver?

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();

Selenium wait for jQuery.Masonry

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..:)

Code is getting into an infinite search when executed via TestNG

The below code is getting into an infinite search when running via TestNG, otherwise giving correct results when executed directly in Main method via Java Application.
Boolean iselementpresent = driver.findElements(By.linkText("Foreign exchange1")).size()!= 0;
Infinite search-->
public boolean checkLinkPresence(String linkName){
Boolean iselementpresent = driver.findElements(By.linkText("Foreign exchange1")).size()!= 0;
if (iselementpresent == true)
return true;
else{
System.out.print("Element " + linkName + " not Present");
APP_LOGS.debug("Element " + linkName + " not Present");
return false;
}
}
You should not be using main method in TestNG as everything works with annotations. However make sure your implicit wait time is short so that the command findElements timeouts after that. If you have not given an implicit wait, try giving one and then try running your method. Here's an example -
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS); //set this before you get the url using driver.get()
Once your implicit wait time is set, try running your method.
public boolean checkLinkPresence(String linkName){
List elements = driver.findElements(By.linkText("Foreign exchange1"));
if (elements.size() != 0)
return true;
else{
System.out.print("Element " + linkName + " not Present");
APP_LOGS.debug("Element " + linkName + " not Present");
return false;
}
}
Hope it helps.
This is how I would write this function...
public boolean checkLinkPresence(String linkName)
{
if (driver.findElements(By.linkText(linkName)).isEmpty())
{
System.out.print("Element " + linkName + " not Present");
APP_LOGS.debug("Element " + linkName + " not Present");
return false;
}
return true;
}
Your locator had a hard coded string in it By.linkText("Foreign exchange1"), so no matter what string you passed the function, it was always looking for that one string.

"arg 1 could not unmarshal" during JSON-RPC call

I have the unenviable task of editing a 2000 line javascript file inorder to maintain and add some new feature to a web app written in JSP, Json-RPC, jQuery and Java. I do not possess any deeper knowledge of jQuery and Json-RPC except basic Javascript knowledge and the original developer is not there anymore.
There is a JS function which accepts a few params, and calls a Json-RPC and here I am getting the error
arg 1 could not unmarshal
Can someone please tell me what this error means?
Here is my code
function distributeQuantityNew(pReportId, pDecimalPlaces, pRun) {
try {
alert('distributeQuantityNew: ' + pReportId + ', ' + pDecimalPlaces + ', ' + pRun);
var fieldValue = $("#distribution_quantity_" + pReportId).val();
if (fieldValue.length == 0) {
showErrorDialog(resourceBundleMap["error.no.distribution.quantity"]);
return;
} else {
$("#distribution_quantity_" + pReportId).val("");
}
var affectedRowIds = [];
var rows = $("#tableBody_" + pReportId + " tr:visible").has("input[type=text]").filter(function(index) {
var voucherType = this.cells[getVoucherColumnIndex()].innerHTML;
if ((voucherType == 'TRANSFER_CS') || (voucherType == 'PAYOUT_CS') || (voucherType == 'SOURCE_BON') || (voucherType == 'PAYOUT_BON')) {
return false;
}
affectedRowIds.push(parseInt(this.id.split("_")[3]));
return true;
}
);
var affectedReportRows = $.extend(true, {}, foreignReportMap[pReportId]);
$.each(affectedReportRows.map, function(i, row) {
if ($.inArray(row.partnerReportBillNr, affectedRowIds) == -1) {
delete affectedReportRows.map["row_" + row.partnerReportBillNr];
}
});
var report = getLoadedReportByRunId(pReportId);
var productType = report.partnerProductType;
SessionManager.extend();
var resultRows = jsonrpc.foreignReportObject.distributeQuantity(affectedReportRows, fieldValue, pDecimalPlaces, pRun);
alert('back after RPC');
$.each(resultRows.map, function(i, row) {
foreignReportMap[pReportId].map["row_" + row.partnerReportBillNr] = row;
updateForeignReportRow(row, true, productType);
});
updateSummaryRow(pReportId);
toggleApproveAllLink(pReportId);
sortForeignReportTable(pReportId, true);
} catch (e) {
handleError("Failed to distribute quantity: ", e);
}
}
I have peppered it with alerts so that I know whether RPC call was succesful, but I get the error arg 1 could not unmarshal before that from the catch block. Thanks for any hints
OK, got it solved. The first parameter to the remote function is expecting a list of Map<String, SomeBO>. SomeBO is a bean with several BigDecimals. I had another JS function which had set the values passed into the Map. This function was setting a BigNumber where I had a setter of String only. I wish the error I had gotten back from JSON unmarshaller was a bit more descriptive...Below is the code where I added .toString() to solve the issue
foreignReportMap[pReportId].map["row_" + pRowId].clientQuantity = clientQuantity.toString();
foreignReportMap[pReportId].map["row_" + pRowId].totalClientQuantity = totalClientQuantity.toString();

Categories

Resources