1

I am becoming more and more frustrated at the moment and I hope to find some help on stackexchange.

First things first: I am not an experienced Javascript dev, probably not an experienced dev at all, but I guess I know how to work with basic scripting - I know a little bit of C#, Java and co.. For my current web automation script I thought it would be a good idea to catch up and try writing in Javascript, but right now I am at a point, where I consider to start from scratch with a different not so confusing language.

Please, can anyone tell me, how the heck I can let my code being executed in a synchronous way from top to bottom?

After lots and lots of hours googling, I have tryed the following so far:

  • begin line 1 with #! /usr/bin/env node and start in terminal using ./app.js
  • make every function() an async function()
  • use await on all methods()

But even now, when I run the script, it throws me a lot of UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'isDisplayed' of undefined and similar things, which let me guess, that node is running some of my methods/functions asynchronous. Those exceptions pop up in console well before the browser windows is even up and loaded.

I am using: * selenium-webdriver 3.6.0 * Firefox 60.0.2 * node 8.10.0

My code basicaly looks like this:

const {Builder, By, Key, until, manage} = require('selenium-webdriver');
const firefox   = require('selenium-webdriver/firefox');
const webdriver = require('selenium-webdriver');
const fs        = require('fs');

//
// declaring lots of global variables, I need in my functions
//

async function init(){
    let options = await new firefox.Options()
    .setProfile('/home/ruphus/.mozilla/firefox/selenium_profile.backtesting');

    let driver = await new webdriver.Builder()
        .forBrowser('firefox')
        .setFirefoxOptions(options)
        .build();
    await driver.get('https://www.someurl.com/')
        .then(openStrategySettings())
        .then(btnStrategySettings.click());

// ... defining webelements/locations to the previously created objects - using xpath

inputPeriod = await driver.findElement(By.xpath("//div[@id='header-toolbar-intervals']/div/div/div"));
}


async function openStrategySettings() {
    if (!someWebelement.isDisplayed()){
        await tabStrategyTester.click();
    }
}
async function inputValue(element, value) {
    await element.sendKeys(Key.BACK_SPACE + Key.BACK_SPACE + value.toString());
}
async function run(){
// this is the main function with a couple of for loops and array.forEach(function()... things
}

init();
run();

So as far as I understand, I am starting the webdriver and firefox with my async init() function. Here I use await for all those methods. After starting webdriver/firefox I define the Object variables to the locations (I want that to happen, when the browser is up and loaded).

But somehow, I do not get why, the script seems to run all my functions and all code it can find straight after starting the script. Actually it seems that it waits to load the browser the last. Before it has finally loaded I get several(8) UnhandledPromiseRejectionWarning..

  • UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'sendKeys' of undefined
  • UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'click' of undefined
  • UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'isDisplayed' of undefined

I would reallllyy appreciate some help here.

parrott
  • 368
  • 4
  • 12
  • I seem to have found a way, to solve a lot of problems. By calling the function `init();` within the `run()` function it runs mostly synchronous as I would expect it to run. Nevertheless `driver.sleep(1000);` does not trigger as I want it to. The script seems to fully ignore the Waiting time of one second and continue all steps of the `for` and `array.forEach(){}`loops without recognizing a 1 second sleep. Any way to force the script to wait? The site I am running on uses lots of Javascrtipt and I need to wait a little while (probably less than 1s) for the site to fully display new results. – parrott Jun 26 '18 at 06:43

3 Answers3

0

The answer to let the async/await functions to run purely synchrnous seems to be to let it only run ONE function.

By that I mean that one can not first init(); for initialization the browser and variables and stuff and after that calling run(); for the automation itself.

Rather should one build a small function main(), which is calling those two functions:

async main(){
  await init;
  await run();
}
main();

That seems to do the trick with the asynchronous runs. Nevertheless I am still troubleing with await driver.sleep(1000);, which does not seem to work as expected.

By further googling I also read here about webdriver-sync. But that is a pretty old comment on that topic. I am not too sure how up-to-date that webdriver-sync is. I will probably try it out some time.

If anyone has further information on how to solve e.g. the not working await driver.sleep(1000); method, used within

array.forEach(async function(entry){
  for(){
    for(){
      await driver.sleep(1000);
    }
  }
}

I would be really glad to read about.

parrott
  • 368
  • 4
  • 12
0

Your usage of await is almost correct. But the .then method expects a function (or two) as argument. You do function calls inside then() and are actually passing the result of that function call to then() instead of the function itself.

async function init(){
    let options = await new firefox.Options()
    .setProfile('/home/ruphus/.mozilla/firefox/selenium_profile.backtesting');

    let driver = await new webdriver.Builder()
        .forBrowser('firefox')
        .setFirefoxOptions(options)
        .build();
    await driver.get('https://www.someurl.com/')
        .then(openStrategySettings)  // <-- XXX pass function
        .then(() => btnStrategySettings.click());  // <-- XXX if the argument isn't a plain function name, arrow functions come handy 

// ... defining webelements/locations to the previously created objects - using xpath

inputPeriod = await driver.findElement(By.xpath("//div[@id='header-toolbar-intervals']/div/div/div"));
}


async function openStrategySettings() {
    if (! await someWebelement.isDisplayed()){  // <-- XXX isDisplayed() is async (returns Promise), await its settlement
        return await tabStrategyTester.click();  // <-- XXX never ignore a Promise
    }
}
async function inputValue(element, value) {
    return await element.sendKeys(Key.BACK_SPACE + Key.BACK_SPACE + value.toString());  // <-- XXX never ignore a Promise
}

When returning the result of a async function inside a async function, the await can be omitted, since a Promise inside a Promise gets automatically unwraped. So return element.sendKeys( will work the same, but using await might be less confusing.

Hägar
  • 1
0
async function surrounding_function(){
    for(value of array){
        for(){
            for(){
                await driver.sleep(1000);
            }
        }
    }
}

driver.sleep() returns a Promise object that gets fulfilled when the sleep time elapsed. The await stops execution until the Promise is settled (either fulfilled or rejected). The interpreter executes other tasks from the task queue in the meanwhile. Igoring the value of the await statement is ok since this ignores the value of the Promise, only; not the Promise itself. async functions return a Promise. The .forEach method ignores the return value of the function you are passing in as callback and thus ignores the Promise and continues with execution without waiting for the Promise to settle. Calling async functions pushes new tasks in the task queue in the first place. Without await these task get executed in an unexpected order.

Hägar
  • 1