1

I'm in Eclipse, programming a Java GUI/script, running a headless chromedriver and I'm having issues with trying to load the html2canvas.js [1] [2] library in the driver, then calling a function I wrote on that library in the browser; I'm getting an undefined error with this code:

String ss1ScriptLoc = "C:\\Users\\me\\Desktop\\resources\\html2canvas.min.js";
String ss2ScriptLoc = "C:\\Users\\me\\Desktop\\resources\\takeScreenShot.js";

je.executeScript(
        "var headID1 = document.getElementsByTagName('head')[0]; "
        + "var newScript1 = document.createElement('script'); "
        + "newScript1.type = 'text/javascript'; "
        + "newScript1.src = '" + ss1ScriptLoc + "'; "
        + "headID1.appendChild(newScript1); "
        + "var headID2 = document.getElementsByTagName('head')[0]; "
        + "var newScript2 = document.createElement('script'); "
        + "newScript2.type = 'text/javascript'; "
        + "newScript2.src = '" + ss2ScriptLoc + "'; "
        + "headID2.appendChild(newScript2); "
        + "$(document).ready( function () { takeScreenShot(); });"
    );

This results in an error for not defining the function "takeScreenShot();" which I thought I defined already in the local .js files.

Starting ChromeDriver 75.0.3770.140 (-refs/branch-heads/3770@{#1155}) on port 48415
Only local connections are allowed.
Please protect ports used by ChromeDriver and related test frameworks to prevent access by malicious code.
Aug 05, 2019 9:36:53 AM org.openqa.selenium.remote.ProtocolHandshake createSession
INFO: Detected dialect: W3C
java.util.concurrent.ExecutionException: org.openqa.selenium.JavascriptException: javascript error: takeScreenShot is not defined
  (Session info: headless chrome=75.0.3770.142)
Caused by: org.openqa.selenium.JavascriptException: javascript error: takeScreenShot is not defined
  (Session info: headless chrome=75.0.3770.142)
Build info: version: '3.141.59', revision: 'e82be7d358', time: '2018-11-14T08:25:48'
System info: host: '', ip: '', os.name: 'Windows 10', os.arch: 'amd64', os.version: '10.0', java.version: '1.8.0_191'
Driver info: org.openqa.selenium.chrome.ChromeDriver
Capabilities {acceptInsecureCerts: false, browserName: chrome, browserVersion: 75.0.3770.142, chrome: {chromedriverVersion: 75.0.3770.140 (2d9f97485c7b..., userDataDir: C:\Users\me\AppData\Lo...}, goog:chromeOptions: {debuggerAddress: localhost:55906}, javascriptEnabled: true, networkConnectionEnabled: false, pageLoadStrategy: normal, platform: XP, platformName: XP, proxy: Proxy(), setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify}
Session ID: 
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at org.openqa.selenium.remote.http.W3CHttpResponseCodec.createException(W3CHttpResponseCodec.java:187)
    at org.openqa.selenium.remote.http.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:122)
    at org.openqa.selenium.remote.http.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:49)
    at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:158)
    at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:83)
    at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:552)
    at org.openqa.selenium.remote.RemoteWebDriver.executeScript(RemoteWebDriver.java:489)
    at App.getScreenShot(App.java:170)
    at javax.swing.SwingWorker$1.call(SwingWorker.java:295)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at javax.swing.SwingWorker.run(SwingWorker.java:334)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

edit1: added this snippet in, tested, works, but doesn't appear to actually do anything.

je.executeScript("");
je.executeScript("document.body.onload = myCustomFunc();"
    + "function myCustomFunc() { console.log('hello world'); }"
);

I took the two library .js files and read them into List then made one big StringBuilder that had all the elements appended to the StringBuilder, then added the takeScreenShot() function inplace of myCustomFunc(). Doesn't throw any errors, but doesn't produce a downloaded screenshot file either.


edit2: my loading function

private String fileToJSString(final String inScriptFile) {
        Path p = Paths.get(inScriptFile);
        if (!Files.exists(p)) {
            System.err.println("fileToJSString : error, file does not exist");
            return null;
        }
        List<String> lines = null;
        try {
            lines = Files.readAllLines(Paths.get(inScriptFile));
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (lines == null) {
            System.err.println("fileToJSString : error, no lines read from file");
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (String s : lines)
            sb.append(s.trim() + " ");
        return sb.toString();
    }

edit3: as per the suggestion by @jay-mattinson I attempted to load the scripts, however the runtime String variable doesn't work with the html2canvas.js library (too many special characters - see the link to the actual file earlier/above in this post)

String ss1ScriptLoc = "C:\\Users\\me\\Desktop\\resources\\html2canvas.min.js";
String ss2ScriptLoc = "C:\\Users\\me\\Desktop\\resources\\takeScreenShot.js";
String jsScript1 = fileToJSString(ss1ScriptLoc);
String jsScript2 = fileToJSString(ss2ScriptLoc);
JavascriptExecutor je = (JavascriptExecutor) this.driver;
je.executeScript(
    "var headID = document.getElementsByTagName('head')[0]; "
    + "var newScript = document.createElement('script'); "
    + "newScript.type = 'text/javascript'; "
    + "var code = {" + jsScript1 + " " + jsScript2 + "};"
    + "newScript.appendChild(document.createTextNode(code));"
    + "headID.appendChild(newScript); "
    + "$(document).ready( function () { takeScreenShot(); });"
);

Specific error thrown during runtime;

Caused by: org.openqa.selenium.WebDriverException: unknown error: Runtime.evaluate threw exception: SyntaxError: Unexpected token !

edit4: here's the html2canvas based function I wrote to grab an image of a table on a webpage;

function takeScreenShot() {
    if (document.getElementsByClassName("abc-top") == null) {
        console.log('not on the right web page with the right content. stopping script');
        return;
    }

    var hiddenLinkObj = document.createElement('a');
    hiddenLinkObj.setAttribute('href', '');
    hiddenLinkObj.setAttribute('id', '');
    document.body.insertBefore(hiddenLinkObj, document.body.childNodes[0]);

    var s1 = window.location.href;
    var idx = s1.lastIndexOf('/');
    var res = s1.substring(idx + 1, s1.length);
    hiddenLinkObj.setAttribute('download', 's' + res + '-s.png');

    var partA = document.querySelector('[abc-show="showInfo"]');
    var partB = partA.getElementsByClassName('row');
    var partC = partB[3].childNodes[0];
    partC.setAttribute('style', 'background-color:#fafafa');
    var result = partC;

    html2canvas(
        result, {
            onrendered: function(canvas) {
                var myImage = canvas.toDataURL("image/png");
                hiddenLinkObj.setAttribute('href', myImage.replace("image/png", "image/octet-stream"));
                hiddenLinkObj.click();
            }
        }
    );
}

So my question/issue: why is there an undefined function error when it's being defined and loaded in the takeScreenShot.js file? - How do I load a decent size html2canvas JS library and a second/short library containing a single JS function I wrote based off the html2canvas library and execute my function inside selenium? -

I'm guessing it lies in my lack of JavaScript knowledge and I'm missing an obvious error. Any and all help appreciated.

Nick Bell
  • 516
  • 3
  • 16
  • You are passing a local path into the page's script SRC. Also, you don't need to create markup for the scripts. Selenium will do that for you. As a side note, Selenium can take the screenshots for you: File full_scrn = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE); – pcalkins Aug 05 '19 at 19:55

2 Answers2

0

Here's what I use to execute/inject a locally stored ".js" file using Selenium:

  File newJSFile = new File(path_to_local_js_file);
        if (newJSFile.exists())
        {
            try
            {
                  Scanner sc = new Scanner(new FileInputStream(newJSFile));
        String js_TxtFile = ""; 
            while (sc.hasNext()) {          
                String[] s = sc.next().split("\r\n");   
                for (int i = 0; i < s.length; i++) {
                    js_TxtFile += s[i];
                    js_TxtFile += " ";
                }   

            }
              try
                {
                ((JavascriptExecutor)driver).executeScript(js_TxtFile);
                }
                catch (Exception ex)
                {
                     System.out.println ("Exception when running Javascript: " + ex.toString());
                }

            }
            catch (Exception ex)
            {
            System.out.println(ex.toString());
            }
        }
pcalkins
  • 1,188
  • 13
  • 20
  • I don't think it's a loading issue like the code your exampled. I loaded the JS code a similar way, but this doesn't appear to resolve the issue. (edited in my loading func to original post). – Nick Bell Aug 05 '19 at 21:08
0

Chrome doesn't allow you to load local resources for security reasons. You will either need to place the file somewhere it is accessible to the driver (network drive/server/repo etc.), or to read the file and pass it in as an argument.

Here is how the second option would work: Instead of specifying the src on your script element, create and append a text node with your file text, and you should be good to go:

je.executeScript(
    $"var headID1 = document.getElementsByTagName('head')[0]; "
    + "var newScript1 = document.createElement('script'); "
    + "newScript1.type = 'text/javascript'; "
    + "var code = {fileText}; "
    + "newScript1.appendChild(document.createTextNode(code)); "
    + "headID1.appendChild(newScript1); "
    + "takeScreenShot();"
);
  • Thanks, this is good information that I didn't know. I wrote in a loading function for the scripts into a string, however, I am not sure if this approach is going to cut it, as I don't think the html2canvas.js library will follow the String variable stipulations during run time in the Java code. (see edit3 in OP) – Nick Bell Aug 05 '19 at 21:14
  • I'm guessing it may be easier to load the first script via a web link resource due to the sheer amount of complexity / special characters and load the second file containing the single function directly this way? What are your thoughts? – Nick Bell Aug 05 '19 at 21:26
  • Maybe try the "min" version and remove the comment sections at the top first. Also, don't use document.ready... that won't fire as the page has already loaded by the time Selenium injects the script. – pcalkins Aug 05 '19 at 21:36
  • pcalkins Yes I meant to remove the document.Ready call, I have edited my answer to reflect this. Nick Bell, may I ask what your goal is in using this js library? A simpler solution might be to do as pcalkins suggested and use the built-in screenshot tool, and modify as needed. If you are working on this github project and want to get it running for selenium, I might suggest making a plugin and loading it onto your driver. – MentallyRecursive Aug 05 '19 at 21:46
  • Thanks, I'll note the removal of the document ready line. I haven't researched the selenium TakesScreenshot approach, but that is looking simpler to accomplish at this point. the html2canvas library I already have working to take a png image of a table on a web page (see edit4 in OP with addition of the html2canvas code). -- Also I tried loading the html2canvas library as a web url resource and the custom download function (contained in edit4) however it throws a selenium syntax error during run time. – Nick Bell Aug 05 '19 at 22:00
  • My initial issue with a selenium screenshot (like this - https://stackoverflow.com/a/13834607/6393165 ) is that my table can be larger than the default selenium browser screen size (scroll bars cut off full window/table) – Nick Bell Aug 05 '19 at 22:08
  • I know this furthers the workaround, but if you are able to use Firefox driver, the screenshots will not be limited by screen size, as they are in the Chrome driver, I believe. – MentallyRecursive Aug 05 '19 at 22:17
  • There are several resources on the web too about how to stitch screenshots together to get what you need out of the Chrome driver - see https://stackoverflow.com/questions/16795458/selenium-webdriver-c-sharp-full-website-screenshots-with-chromedriver-and-firefo – MentallyRecursive Aug 05 '19 at 22:22
  • Does the Firefox driver screen shot/cap the entire page, e.g. if the page has a table that scrolls down (meaning the whole table isn't viewable on a standard desktop screen), will it screen shot/cap the whole page (making a tall single image)? Also, thank you for the chrome driver stitch link, will look into that. – Nick Bell Aug 05 '19 at 22:22
  • I haven't used the Firefox driver for a few months, but back then it did take the entire screen as the screenshot- vertically and horizontally. We decided to go with Chrome mostly for the dev tools it offered, but the screenshots were better/easier on Firefox. We ended up having to do the stitch solution which is what we run now. – MentallyRecursive Aug 05 '19 at 22:38