Search code examples
javascriptrubycucumbercapybaraappium

How to call async javascript function from ruby with capybara?


I have some E2E tests for a cross platform Ionic application. The tests are written in Ruby and they use appium_capybara, capybara and selenium-webdriver.

The Ionic application has an async javascript function that can be used from the developer console to sign in to the app. I want to invoke this from the tests and I managed to do this from Ruby with a function called evaluate_async_script.

However, while I have proof that the async function is actually executed, i get this error from Ruby:

Timed out waiting for asyncrhonous script result after 10033 ms (Selenium::WebDriver::Error::ScriptTimeoutError)

Why does this happen? Any suggestions on how to make it work?

The async function looks something like this:

export async function signin(user: string, pass: string,
                             callback: (result: object) => object): 
Promise<object> {
    if (typeof(window.authService) === 'object') {
        const credentials: ICredentials = {
            email: user,
            password: pass,
            rememberMe: false
        };

        await window.authService.authenticate(credentials);

        const result = { message: 'YES!!' };
        callback(result);
        return result;
    }
    return { message: 'EMPTY STRING!!' };
}

The call from Ruby looks like this:

result = Capybara.current_session
    .evaluate_async_script("window.signin('#{user.email}',
        '#{user.password}', (result) => { return result; })
            .then(function(value) { alert(value.message); })")

The alert pops up showing the 'YES!!' message, so I know that the function is executed. But for some reason Ruby never notices that the function has finished.


Solution

  • From the docs for Session#evaluate_async_script - https://www.rubydoc.info/gems/capybara/Capybara/Session#evaluate_async_script-instance_method - the important part is "from a callback function which will be passed as the last argument to the script". Your script isn't calling the callback function which would be available as arguments[0] so Capybara has no way of knowing it is done.

    For it to work you would need something along the lines of (untested)

    result = Capybara.current_session.evaluate_async_script("
      var cb = arguments[0];
      window.signin('#{user.email}','#{user.password}', cb);")
    

    You could also pass the email and password in as arguments too if desired

    result = Capybara.current_session.evaluate_async_script('
      var cb = arguments[2];
      window.signin(arguments[0], arguments[1], cb);', user.email, user.password)
    

    Note this moves the callback function to arguments[2], since you're passing 2 other arguments first.

    The bigger question is whether or not you even need to be using evaluate_async_script. It's only needed if you actually need the response from the async function, if not you can just call it with execute_script and wait for the visible page changes as normal.