I am trying to work with Selenium in Java with Angular 5 based website.
Selenium does not support it directly, but JavascriptExecutor can help validating the page components finished loading.
The problem is, I do not know how to implement the JavaScript to validate this.
I am using:
return window.getAngularTestability === undefined
to validate Angular 5 exists in the current page, but the next part of the implementation is a mystery to me.
I know I have to use return window.getAngularTestability somehow.
You can create a generic java method for running any javascript within your Java code. Refer below block of code:-
public void executeJavascript(String script) {
((JavascriptExecutor) driver).executeScript(script);
}
You can pass your return javascript statements as parameters to this method.
I found an answer after a lot of research and searching the web.
The solution is not mine, so i do not deserve the credit.
ExpectedCondition<Boolean> expectation = driver -> ((JavascriptExecutor) driver).executeAsyncScript(
"var callback = arguments[arguments.length - 1];" +
"if (document.readyState !== 'complete') {" +
" callback('document not ready');" +
"} else {" +
" try {" +
" var testabilities = window.getAllAngularTestabilities();" +
" var count = testabilities.length;" +
" var decrement = function() {" +
" count--;" +
" if (count === 0) {" +
" callback('complete');" +
" }" +
" };" +
" testabilities.forEach(function(testability) {" +
" testability.whenStable(decrement);" +
" });" +
" } catch (err) {" +
" callback(err.message);" +
" }" +
"}"
).toString().equals("complete");
try {
WebDriverWait wait = new WebDriverWait(webDriver(), 15);
wait.until(expectation);
} catch (Throwable error) {
new Exception("Timeout waiting for Page Load Request to complete.");
}
Related
I recently posted a question about an issue I have white testing an angular based application (ref: wait until Angular has finished loading issue )
Turns out that the check done was valid for angular 1.x apps, while our application runs on angular 6.x.
I've then found out that post: Detecting that Angular 2 is done running
which explains how to do a similar check but for angular 2+ apps. I've set up the check in a similar fashion to "Michal Filip" explained.
I've also tried to use the ngWebdriver solution proposed further down in the post.
Both suffers of the same problem: the check will always return true as in its done loading which isn't true.
tried to inverse the check, it didn't help (state never changed)
// Will check if Angular still has pending http_requests ongoing and wait if required
public void untilAngular2HasFinishedProcessing()
{
until(() ->
{
log.info("Waiting on angular 2+ to finish processing");
final boolean isReady = ((JavascriptExecutor) driver).executeAsyncScript(
"var callback = arguments[arguments.length - 1];" +
"if (document.readyState !== 'complete') {" +
" callback('document not ready');" +
"} else {" +
" try {" +
" var testabilities = window.getAllAngularTestabilities();" +
" var count = testabilities.length;" +
" var decrement = function() {" +
" count--;" +
" if (count === 0) {" +
" callback('complete');" +
" }" +
" };" +
" testabilities.forEach(function(testability) {" +
" testability.whenStable(decrement);" +
" });" +
" } catch (err) {" +
" callback(err.message);" +
" }" +
"}"
).toString().equals("complete");
log.info("Is angular 2+ ready? " + isReady);
return isReady;
}
);
}
// sample call would be
untilAngular2HasFinishedProcessing();
Excpected: the test would wait until Angular is done loading before returning true
Actual: Returns true from the start, which I know isn't the case.
Possible duplicate? No, because this is a problem question based on the implementation proposed in the linked question.
Here's the solution I ended up using:
public boolean untilAngular2HasFinishedProcessing()
{
until(() ->
{
log.info("Waiting on angular 2+ to finish processing");
final boolean isReady = (Boolean.valueOf(((JavascriptExecutor) driver)
.executeScript(
"try{" +
"return (window.getAllAngularTestabilities()[0]._ngZone.hasPendingMicrotasks == " +
"false && " +
"window.getAllAngularTestabilities()[0]._ngZone.hasPendingMacrotasks == false && " +
"window.getAllAngularTestabilities()[0]._ngZone._nesting == 0 &&" +
"window.getAllAngularTestabilities()[0]._ngZone.isStable == true)" +
"}" +
"catch(err) {" +
"if (err.message == ('window.getAllAngularTestabilities is not a function'))" +
"{" +
"return true" +
"}" +
"}")
.toString()));
log.info("Is Angular 2+ ready? " + isReady);
return isReady;
}
);
return true;
}
That worked on a consistent fashion so far.
I've got an application that executes a lot of javascript server side and I'm trying to convert from Rhino to Nashorn but I am running into trouble with my scripts. Using Rhino I would always convert whatever arguments I had for a function into a JSON string but that's really slow. With Nashorn I'm trying to just pass in the arguments as Java objects but they don't seem to inherit functions from Javascript's Object type. Here is a sample method that illustrates my problem where hasOwnProperty is not available on my array:
public String printArrayValues() throws ScriptException, NoSuchMethodException {
String script =
"function printArrayValues(objArray) {\n" +
" var result = '';\n" +
" for(var obj in objArray) {\n" +
" if(objArray.hasOwnProperty(obj)) {\n" +
" result = result + ' ' + objArray[obj];\n" +
" }\n" +
" }\n" +
" return result;\n" +
"}";
List<String> data = Arrays.asList(new String[]{ "one", "two", "three"});
ScriptEngine scriptEngine = new NashornScriptEngineFactory().getScriptEngine();
scriptEngine.eval(script);
String result = (String) ((Invocable) scriptEngine).invokeFunction("printArrayValues", data);
}
Here the call to invokeFunction throws an exception:
javax.script.ScriptException: TypeError: [one, two, three] has no such function "hasOwnProperty" in <eval> at line number 4
If I call the same function in a browser, I get what I would expect:
> printArrayValues(["one", "two", "three"]);
> " one two three"
Is there some way I can accomplish this so I really can use these Java objects without turning them into a JSON string and then eval'ing it into a Javascript object?
You can't use Java arrays in this way. Java arrays are "hardwired" objects. Unlike ordinary objects, they don't have methods and they support the [] operator, which objects can't.
This Article about Nashorn at Oracle explains that you need to use the Java.to and Java.from methods in your javascript in order to change a Java array to a Javascript array.
use Java.from() to transform the Java List to Javascript Array, and then operate on it.
String script =
"function printArrayValues(objArray) {\n" +
" var result = '';\n var temp = Java.from(objArray);" +
" for(var obj in temp ) {\n" +
" if(temp .hasOwnProperty(obj)) {\n" +
" result = result + ' ' + temp [obj];\n" +
" }\n" +
" }\n" +
" return result;\n" +
"}";
I'm writing a play framework 2.1.5 access log, code like this
class AccessLog extends Filter {
def apply(next: (RequestHeader) => Result)(rh: RequestHeader) = {
val start = System.currentTimeMillis
def logTime(result: PlainResult): Result = {
val time = System.currentTimeMillis - start
val contentLength: String = result.header.headers getOrElse("Content-Length", "-")
play.Logger.info("" + rh.method +" " + rh.uri + " took " + " " + time + " ms and returned " + result.header.status + " " + contentLength)
result
}
next(rh) match {
case plain: PlainResult => logTime(plain)
case async: AsyncResult => async.transform(logTime)
}
}
}
I found that many people found an access log for the play framework and this works any way with an issue that
it can always get the response size, when you press CTRL + F5, if you just press F5, you cannot get the "Content-Length" even the "Content-Length" does not exist in the map.
any one konw this?
UPDATE 3
Final working code below. YOU NEED THE ace.js FROM THE src FOLDER! It will not work from the libs, you need the pre-packaged version from their site.
WText *editor = new WText(root());
editor->setText("function(){\n hello.abc();\n}\n");
editor->setInline(false);
The above code can set the contents of the ACE window.
MyClass::MyClass(const WEnvironment& env)
: WApplication(env)
{
wApp->require("ace-builds/src/ace.js");
// A WContainerWidget is rendered as a div
WContainerWidget *editor = new WContainerWidget(root());
editor->resize(500, 500);
std::string editor_ref = editor->jsRef(); // is a text string that will be the element when executed in JS
std::string command =
editor_ref + ".editor = ace.edit(" + editor_ref + ");" +
editor_ref + ".editor.setTheme(\"ace/theme/monokai\");" +
editor_ref + ".editor.getSession().setMode(\"ace/mode/javascript\");";
editor->doJavaScript(command);
JSignal <std::string> *jsignal = new JSignal<std::string>(editor, "textChanged");
jsignal->connect(this, &MyClass::textChanged);
WPushButton *b = new WPushButton("Save", root());
command = "function(object, event) {" +
jsignal->createCall(editor_ref + ".editor.getValue()") +
";}";
b->clicked().connect(command);
}
void MyClass::textChanged(std::string incoming)
{
}
UPDATE 2
Here is what my project looks like atm, still getting a white screen with a red "Loading..." message from WT in the top right hand corner. More notes below.
MyClass::MyClass(const WEnvironment& env)
: WApplication(env)
{
wApp->require("lib/ace/ace.js");
// A WContainerWidget is rendered as a div
WContainerWidget *editor = new WContainerWidget(root());
editor->resize(500, 500);
std::string editor_ref = editor->jsRef(); // is a text string that will be the element when executed in JS
std::string command =
editor_ref + ".editor = ace.edit(" + editor_ref + ");" +
editor_ref + ".editor.setTheme(\"ace/theme/monokai\");" +
editor_ref + ".editor.getSession().setMode(\"ace/mode/javascript\");";
editor->doJavaScript(command);
JSignal <std::string> *jsignal = new JSignal<std::string>(editor, "textChanged");
jsignal->connect(this, &MyClass::textChanged);
WPushButton *b = new WPushButton("Save", root());
command = "function(object, event) {" +
jsignal->createCall(editor_ref + ".editor.getValue()") +
";}";
b->clicked().connect(command);
}
void MyClass::textChanged(std::string incoming)
{
}
"command" variable is equal to the following when it is used for editor->doJavaScript(command)
"Wt3_3_0.$('oy4ycjy').editor = ace.edit(Wt3_3_0.$('oy4ycjy'));Wt3_3_0.$('oy4ycjy').editor.setTheme('ace/theme/monokai');Wt3_3_0.$('oy4ycjy').editor.getSession().setMode('ace/mode/javascript');"
"command" variable is equal to the following when it is used for b->clicked().connect(command);
"function(object, event) {Wt.emit('oy4ycjy','textChanged',Wt3_3_0.$('oy4ycjy').editor.getValue());;}"
UPDATE 1
Added the suggested code to my constructor, however the page does not change from a solid white screen. I am doing nothing else in this WT project, only this code is running.
wApp->require("lib/ace/ace.js");
// A WContainerWidget is rendered as a div
WContainerWidget *editor = new WContainerWidget(root());
std::string editor_ref = editor->jsRef(); // is a text string that will be the element when executed in JS
editor->doJavaScript(
editor_ref + ".editor = ace.edit('" + editor_ref + "');" +
editor_ref + ".editor.setTheme('ace/theme/monokai');" +
editor_ref + ".editor.getSession().setMode('ace/mode/javascript');"
);
The value of editor_ref is "Wt3_3_0.$('oumvrgm')" minus the quotes.
Also tried adding the code below, and the page is still blanked out.
JSignal <std::string> *jsignal = new JSignal<std::string>(editor, "textChanged");
jsignal->connect(this, &MyClass::textChanged);
WPushButton *b = new WPushButton("Save", root());
b->clicked().connect("function(object, event) {" +
jsignal->createCall(editor->jsRef() + ".editor.getValue()") +
";}");
I have also found that commenting out
editor_ref + ".editor = ace.edit('" + editor_ref + "');" +
makes the button show up, but there is a red "Loading..." note at the top right of the screen so WT is waiting on something.
I have textChanged as a do nothing function at the moment.
ORIGINAL POST
So, my problem is this. How can I get ACE http://ace.ajax.org/#nav=about in WT http://www.webtoolkit.eu/wt. More specifically, ACE in a WT Wt::WTextArea or Wt::WTabWidget, the text area would be preferred. I have been trying to do this for a few days now and have not had much success.
I've been able to embed ACE in an HTML page no problem, as their site says "just copy and paste it into your page" and it really is that simple. However, I need to load it locally through WT and into a container. I downloaded their repos from GIT to my machine and have tried using
require("lib/ace/ace.js");
and
doJavaScript(...);
with various commands to no success... I am not nearly as strong in Java and HTML as C++ so I will ask for as much detail as possible if this involves a lot of Java/HTML. Thanks in advance mates!
Maybe this puts you in the right direction:
wApp->require("lib/ace/ace.js")
// A WContainerWidget is rendered as a div
WContainerWidget *editor = new WContainerWidget(parent);
// editor->jsRef() is a text string that will be the element when executed in JS
editor->doJavaScript(
editor->jsRef() + ".editor = ace.edit(" + editor->jsRef() + ");" +
editor->jsRef() + ".editor.setTheme('ace/theme/monokai');" +
editor->jsRef() + ".editor.getSession().setMode('ace/mode/javascript');"
);
That should decorate the editor. Wt does not automatically send the modifications to a div to the server, so you do this manually through a JSignal (emits a signal from JS to C++):
JSignal <std::string> *jsignal = new JSignal<std::string>(editor, "textChanged");
jsignal->connect(this, MyClass::textChanged);
WPushButton *b = new WPushButton("Save", parent);
b->clicked().connect("function(object, event) {" +
jsignal->createCall(editor->jsRef() + ".editor.getValue()") +
";}");
(code above is not tested so you may need to adjust a bit)
I have integrated CodeMirror in an earlier JWt (java) project like this:
import eu.webtoolkit.jwt.WApplication;
import eu.webtoolkit.jwt.WContainerWidget;
import eu.webtoolkit.jwt.WTextArea;
public class CodeMirrorTextArea extends WContainerWidget {
private WTextArea textArea;
public CodeMirrorTextArea(WContainerWidget parent) {
super(parent);
textArea = new WTextArea(this);
WApplication app = WApplication.getInstance();
app.require(app.resolveRelativeUrl("codemirror-2.32/lib/codemirror.js"));
app.require(app.resolveRelativeUrl("codemirror-2.32/mode/groovy/groovy.js"));
//TODO:
//We save the editor state to the text area on each key stroke,
//it appears to be not a performance issue,
//however it might very well become one when editing larger fragments of code.
//A better solution would be to save this state to the text area only when
//the form is submitted, currently this is not yet possible in Wt???.
String js =
"var e = " + textArea.getJsRef() + ";" +
"var cm = CodeMirror.fromTextArea(e, {" +
" onKeyEvent : function (editor, event) {" +
" editor.save();" +
" }," +
" lineNumbers: true" +
" });" +
"var self = " + getJsRef() + ";" +
"self.cm = cm;";
this.doJavaScript(js);
}
public CodeMirrorTextArea() {
this(null);
}
public void setText(String text) {
textArea.setText(text);
}
public String getText() {
return textArea.getText();
}
public void setMarker(int line, String htmlMarker) {
String js =
"var self = " + getJsRef() + ";" +
"self.cm.setMarker(" + line + ", " + jsStringLiteral(htmlMarker +
"%N%") + ");";
this.doJavaScript(js);
}
public void clearMarker(int line) {
String js =
"var self = " + getJsRef() + ";" +
"self.cm.clearMarker(" + line + ");";
this.doJavaScript(js);
}
}
i'm trying to use https://github.com/thegrubbsian/jquery.ganttView
in play 1.2.4
already check JQuery GanttChart - Apply Data
but my problem is how to renderJSON to the view,
i have try a simple one that is renderJSON("Hello"); and capture in the view, the problem is that i only manage to download a file like 4fporLKs.part1 that have inside Hello :(
can some one explain me how to do it
thanks
other guy is working in the same as i, but haven't got it
<script type="text/javascript">
$(function ()
{
$("#ganttChart").ganttView({
data : 'dataUrl: "data.json"',
slideWidth: 900,
behavior: {
onClick: function (data) {
var msg = "You clicked on an event: { start: " + data.start.toString("M/d/yyyy") + ", end: " + data.end.toString("M/d/yyyy") + " }";
$("#eventMessage").text(msg);
},
onResize: function (data) {
var msg = "You resized an event: { start: " + data.start.toString("M/d/yyyy") + ", end: " + data.end.toString("M/d/yyyy") + " }";
$("#eventMessage").text(msg);
},
onDrag: function (data) {
var msg = "You dragged an event: { start: " + data.start.toString("M/d/yyyy") + ", end: " + data.end.toString("M/d/yyyy") + " }";
$("#eventMessage").text(msg);
}
}
});
//$("#ganttChart").ganttView("setSlideWidth", 600);
});
</script>
"hello" is not well formated json string, try:
renderJSON("{\"hello\":\"world\"}");
Also check Play's documentation for controllers to see how render ie. json from List (paragraph: Return a JSON String)