0

Firstly to address the obvious

I've seen a million and one questions on this with the main dupe target being: How do I return the response from an asynchronous call? This does not suit my needs because

A: I know that I need Asynchronous java-script already and that I want to use callback functions. I need details on HOW to implement them properly with specific conditions (in Selenium using JavascriptExecutor ect) that I wasn't able to glean from the other questions

B: that question is more specific to Ajax which makes it extremely confusing for me to gain anything from it.

C: this is more about the best way to wait for asynchronous calls like this to complete

What I've done besides Stack overflow research

I also went to many websites including: https://www.sitepoint.com/callbacks-javascript/ Which gave me some basic insight on how callbacks are implemented and I've implemented one correctly I also found this website https://javapapers.com/ajax/getting-started-with-ajax-using-java/ Which gave me basic insight on one way I can think of to 'wait' for an asychronous javascript function to execute, that is to send over ajax data back to my code when the function is finally done.

What rabbit hole did you go down?

I want to make a simple script to determine whether a given web-element causes a page to reload. To do that I need a page load event. I researched awhile and found (through multiple questions on here) the JavascriptExecutor and the load event. I tried to implement them and I was naively thinking it would be an easy return statement. Originally I was trying to return the result of adding an event listener when I first tried implementing this. Then I figured out that was silly so I needed to get some sort of return value, I tried for a couple hours to return various parts of the add event listener script before I realized I was using the wrong approach and thinking about the problem wrong. Then I did this research on asynchronous JavaScript because asking questions about NPEs from my previous approach led me to it, what it is, how its used, and then I came across callback functions. After posting this and doing more research I correctly implemented a simple asynchronous callback function. Now I just need to properly wait for it to complete before moving forward so I know that the page is fully loaded.

an overarching description of what I'm doing

I have a thread off of main. This thread clicks a web-element. The clicked web-element has 3 basic cases I want to document/flag for:

  1. clicking it doesn't reload the page and doesn't move/get deleted (locators remain the same)
  2. clicking it doesn't reload the page but the element locators (think 'id' get deleted or changed)
  3. clicking the web element causes a page reload

In order to document these three cases I need flags for, the on-load event and the stale-reference exception event and I want a timeout if both those don't happen in time. If on-load happens then we know its case 3, in stale-reference happens but on-load doesn't happen.

To get a flag for the stale-reference case was easy, its already in Selenium. The problem is the flag for the on-load event. To flag this I need something I can run in JavaScriptExecutor that then transfers data via asynchronous callback functions and possibly ajax (or some other method I'm open to suggestions) to java. This data can be as simple as a number, as long as I can get some message from the javascript telling my java that it's done doing its asynchronous stuff. I've implemented a callback function in my code but I need to get data from the javascript to the java. Additionally it is really hard to test for syntax/errors in the javascript because I can't just test the JavaScript script string itself as its own program easily (So far I've had to use free online editors that aren't that good).

The code in question

//initialize a wait object and set a default timeout (the value is ten 
//seconds)
WebDriverWait wait = new WebDriverWait(driver,timeoutSeconds);
        try{
            //wait until stalereference exception is thrown (unless timeout 
            //is exceeded)
            wait.until(ExpectedConditions.stalenessOf(webElement));

            //define the test function with a callback inside it. Prints 2 
            //to a page on page reload event only if the webelement went 
            //stale
            String script = "function test(arg,callback) {callback(arg);}" +
                    "function Return(arg){document.write(arg);}" +
                    "var a = 2;" +
                    "window.addEventListener('load', test(a,Return));";
            //TODO change this fucntionality to use ajax or some other good 
            //method to say "hey I'm done executing!" to the java code

            //execute the script to add the event listener
            JavascriptExecutor js = (JavascriptExecutor)driver;
            js.executeScript(script);

            //flag the state of this Thread
        }
        //Timeout was exceeded so the page doesn't change on click
        catch (TimeoutException e){
            //here I set a 'page didn't change' flag
        }

Notes on the code/what I need codewise

The only things required to run this should be a web element, a driver object and maybe some print statements to be the 'flags'. I used the chrome-driver driver because I like chrome and I'm using the question box as the webElement (and of course deleting my comments). My styling and syntax is probably messy but this is just test code at this point I want to get it functional. What I want is basically a

InsideCallback.POST(somedata) to java

But in code that works with (or replaces) my current test code. I know that since its asynchronous I don't want to poll the return result (that defeats the purpose of the whole being asynchronous thing). I just don't know how to say 'when this function in another language returns its stuff' in code. Unfortunately I don't have a specific error with a stack-trace because its more about whats NOT in the code. I feel like I'm on the right path but I need to know if Ajax is the right way to go. The problem is, the workable examples of this seem complicated and might take awhile so I want to know what I'm getting into.

EDIT 1

Updated code so it shouldn't have syntax errors that cause stacktrace/failure, it ran fine when I tried it, just some parenthesis mixup and argument syntax that was wrong. I still am missing some way to actually callback the callback function but I'm going to continue trying to research it.

Edit 2

OK I finally figured out javascript syntax enough and got it so that the eventlistener callback function stuff is working. I have it printing 2 to a page on a reload. Simple and easy. Now I just need to change that functionality to link to my javacode and set a simple flag of some sort. (updated code so when ran it should work much better). Updated code ordering to more closely match desired behavior. staleReferenceException should be aited for BEFORE the eventListener for onload event is added Otherwise the onload events fires prematurely for desired behavior.

Edit 3

Updated question to more accuractely reflect title/current status of research.

Redacted
  • 613
  • 6
  • 23
  • what are you trying to achieve? for example simple way to wait for asynch evet is if your `EventListener` adds some visible element on the page, which selenium could wait for using standard `ExpectedConditions`. But what's the goal of all of it? say you found the way to wait for that asynch call, and then what? – timbre timbre Sep 18 '18 at 15:13
  • Then I'd use that information to flag that the webelement I clicked on to get that event causes a page reload. Another useful thing this allows me to do is wait fully until a page is loaded and not have any stale reference exception weirdness – Redacted Sep 18 '18 at 15:19
  • so in both cases you basically want to improve consistency and behavior with app reloading/refreshing the page (because reason for stale reference is also page reload most likely). How about traditional solution: implementing custom expected condition with `js.executeScript("return document.readyState").equals("complete"))` - did you try that? – timbre timbre Sep 18 '18 at 16:19
  • I've heard that sometimes that doesn't catch everything but I might try it that way. Thing is now I still need a way to tell when the async call is done unless I want to not do any of my other checks and just wait in the javascript. I could do that, but I'd rather be able to classify webelements that cause page reloads and webelements that delete themselves because I think it'd be highly useful for Selenium tests – Redacted Sep 18 '18 at 16:22

1 Answers1

0

So whats the solution?

I found out That the way to execute an async script in java is to use the Selenium provided method: executeAsyncScript instead of executeScript. The reason this is important is because I was trying to use executeScript before and the script worked as intended but not as I wanted. It installed a javascript listener and then returned. What I wanted though was something to return to the java code when the Async Callback function was done, NOT the original listener function. That functionality is neatly provided by executeAsyncScript and the process of how I found it is addressed in this question of mine:

write to Java console when my Javascript callback (made in Selenium) returns

Why did your previous approach fail?

The reason my previous approach didn't work is because I was trying to return data when the event listener was installed rather than when the callback returned. As discussed in my question, the callback DID run, but I wasn't able to link it to java because I was returning too early. When I use executeAsync script it returns when the callback is done. You then can use reflection (return a string in javascript that corresponds to a class you'd like to call or even a method) and then you can use

String classToCall = (String) js.executeAsyncScript(script);
Class.forname("the string your callback returned");

to get the Class you want. Then you can make a static method and call it to run javacode. I made a simple static method example here:

public static void JavascriptWorking(){
    System.out.println("Call back function returned");
}

And then used reflection to obtain that method from the class we got earlier by using:

callbackMethod = clazz.getMethod("JavascriptWorking");
callbackMethod.invoke(null,null);

which then prints out to console as desired. The one thing about this though is that the java code waits for the async callback to be done. This can lead to 'infinite hangs' (callback won't ever run because of some interruption) so you need to set a scriptTimeout property (which is easy in Selenium):

driver.manage().timeouts().setScriptTimeout(timeoutSeconds, TimeUnit.SECONDS);

Where timeoutSeconds (for me) represented the number 10 as an int. TimeUnit.SECONDS is a predefined constant thing that comes with the class/Selenium itself for specifying the units of measure for the timeout.

alternatively you can use some sort of other timeout so that you don't stop the whole application permanently when a callback doesn't run.

Doesn't this interfere with the whole purpose of callbacks?

The whole point of callbacks is that your javascript code doesn't wait or poll until the javascript is done, instead the callback is ran when it needs to be. Unfortunately this solution just makes the java code wait till that happens because the program flow can't continue until executeAsycScript returns. You could just create a new thread to run that specific part of the code and have that new thread wait for the callback so that the rest of the application isn't held up by the call to executeAsycScript.

Something fundamental to understanding this:

something thing I learned thats relevant to this, the javascript I create here is Client-side because its ran in MY browser. This means that I can't/wouldn't use the typical Client <-> Serverside methods of returning something to java from javascript as everything here is Clientside. This effectively means that my rambling on about AJAX in my question is irrelevant to the actual solution because I don't need to use it to return a request to the java code or anything like that. Those methods are only used in server/client interactions.

Community
  • 1
  • 1
Redacted
  • 613
  • 6
  • 23