New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecate and remove the WebDriverJS promise manager #2969

Closed
jleyba opened this Issue Oct 19, 2016 · 47 comments

Comments

Projects
None yet
@jleyba
Contributor

jleyba commented Oct 19, 2016

This is a tracking bug for deprecating and removing the promise manager used by WebDriverJS.

Background

WebDriverJS is an asynchronous API, where every command returns a promise. This can make even the simplest WebDriver scripts very verbose. Consider the canonical "Google search" test:

let driver = new Builder().forBrowser('firefox').build();
driver.get('http://www.google.com/ncr')
    .then(_ => driver.findElement(By.name('q')))
    .then(q => q.sendKeys('webdriver'))
    .then(_ => driver.findElement(By.name('btnG')))
    .then(btnG => btnG.click())
    .then(_ => driver.wait(until.titleIs('webdriver - Google Search'), 1000))
    .then(_ => driver.quit(), e => {
      console.error(e);
      driver.quit();
    });

WebDriverJS uses a promise manager that tracks command execution, allowing tests to be written as if they were using a synchronous API:

let driver = new Builder().forBrowser('firefox').build();
driver.get('http://www.google.com/ncr');
driver.findElement(By.name('q')).sendKeys('webdriver');
driver.findElement(By.name('btnG')).click();
driver.wait(until.titleIs('webdriver - Google Search'), 1000);
driver.quit();

The original goal for the promise manager was to make it easier to read and reason about a test's intent. Unfortunately, any benefits on this front come at the cost of increased implementation complexity and reduced debuggability—in the example above, inserting a break point before the first findElement call will break before the findElement command is scheduled. To break before the command actually executes, you would have to modify the test to break in a callback to the driver.get() command:

let driver = new Builder().forBrowser('firefox').build();
driver.get('http://www.google.com/ncr').then(_ => debugger);
driver.findElement(By.name('q')).sendKeys('webdriver');

To help with debugging, tests can be written with generators and WebDriver's promise.consume function (or equivalent, from libraries like task.js). These libraries are "promise aware": if a generator yields a promise, the library will defer calling next() until the promise has resolved (if the promise is rejected, the generator's throw() function will be called with the rejection reason). While users will have to make liberal use of the yield keyword, they can use this strategy to write "synchronous" tests with predictable breakpoints.

const {Builder, By, promise, until} = require('selenium-webdriver');

let result = promise.consume(function* doGoogleSearch() {
  let driver = new Builder().forBrowser('firefox').build();
  yield driver.get('http://www.google.com/ncr');
  yield driver.findElement(By.name('q')).sendKeys('webdriver');
  yield driver.findElement(By.name('btnG')).click();
  yield driver.wait(until.titleIs('webdriver - Google Search'), 1000);
  yield driver.quit();
});

result.then(_ => console.log('SUCCESS!'), e => console.error('FAILURE: ' + e));

The need to use promise.consume/task.js will be eliminated in TC39 with the introduction of async functions:

async function doGoogleSearch() {
  let driver = new Builder().forBrowser('firefox').build();
  await driver.get('http://www.google.com/ncr');
  await driver.findElement(By.name('q')).sendKeys('webdriver');
  await driver.findElement(By.name('btnG')).click();
  await driver.wait(until.titleIs('webdriver - Google Search'), 1000);
  await driver.quit();
}

doGoogleSearch()
    .then(_ => console.log('SUCCESS!'), e => console.error('FAILURE: ' + e));

Given that the JavaScript language continues to evolve with more support for asynchronous programming (and promises in particular), the benefits to WebDriver's promise manager no longer outweigh the costs. Therefore, we will be deprecating the promise manager and eventually removing it in favor of native language constructs.

Deprecation Plan

Phase 1: allow users to opt-out of the promise manager

Estimated date: now (selenium-webdriver@3.0)

  1. Within selenium-webdriver, replace all hard dependencies on the selenium-webdriver/promise.ControlFlow class with a new interface: selenium-webdriver/promise.Scheduler.

    /** @interface */
    class Scheduler {
     execute(fn) {}
     timeout(ms) {}
     wait(condition, opt_timeout) {}
    }
  2. Add a Scheduler implementation that executes everything immediately using native Promises.

    /** @implements {Scheduler} */
    class SimpleScheduler {
     /** @override */ execute(fn) {
       return new Promise((resolve, reject) => {
         try {
           resolve(fn());
         } catch (ex) {
           reject(ex);
         }
       });
     }
    
     /** @override */ timeout(ms) {
       return new Promise(resolve => setTimeout(_ => resolve(), ms));
     }
    
     /** @override */ wait(condition, opt_timeout) {
       // Implementation omitted for brevity
     }
    }
  3. Introduce the SELENIUM_PROMISE_MANAGER environment variable. When set to 1, selenium-webdriver will use the existing ControlFlow scheduler. When set to 0, the SimpleScheduler will be used.

    When SELENIUM_PROMISE_MANAGER=0, any attempts to use the ControlFlow class will trigger an error. This will help users catch any unexpected direct dependencies. This will also impact the use of the promise.Deferred and promise.Promise classes, as well as the promise.fulfilled() and promise.rejected() functions, which all have a hard dependency on the control flow. Users should use the native equivalents.

    At this point, SELENIUM_PROMISE_MANAGER will default to 1, preserving existing functionality.

Phase 2: opt-in to the promise manager

Estimated date: October 2017, Node 8.0

Following the release of a Node LTS that contains async functions, change the SELENIUM_PROMISE_MANAGER default value to 0. Users must explicitly set this environment variable to continue using the ControlFlow scheduler.

Phase 3: removing the promise manager

Estimated date: October 2018, Node 10.0

On the release of the second major Node LTS with async functions, the ControlFlow class and all related code will be removed. The SELENIUM_PROMISE_MANAGER environment variable will no longer have any effect.

@mekdev

This comment has been minimized.

Show comment
Hide comment
@mekdev

mekdev Nov 1, 2016

@jleyba I am happy to help out with documentation for this.
We just swapped out the promise manager to async/await entirely with babel and its working well for us!

For other people following up:

There is some sample code we put on github for reference using Mocha and Async / Await
https://github.com/airware/webdriver-mocha-async-await-example

The technical talk we gave at SF Selenium Meetup in Oct 2016 on the move to Async/Await can be found here: Fullstack End-to-end test automation with Node.js, one year later

mekdev commented Nov 1, 2016

@jleyba I am happy to help out with documentation for this.
We just swapped out the promise manager to async/await entirely with babel and its working well for us!

For other people following up:

There is some sample code we put on github for reference using Mocha and Async / Await
https://github.com/airware/webdriver-mocha-async-await-example

The technical talk we gave at SF Selenium Meetup in Oct 2016 on the move to Async/Await can be found here: Fullstack End-to-end test automation with Node.js, one year later

jleyba added a commit that referenced this issue Nov 2, 2016

[js] Introduce the SELENIUM_PROMISE_MANAGER environment variable, whi…
…ch can be

set to 0 to disable use of the promise manager.

Full details and motivation behind this change can be found in #2969

jleyba added a commit that referenced this issue Nov 2, 2016

[js] Builder.build() now returns a thenable WebDriver object. Users c…
…an issue

commands directly, or through a standard promise callback. This is the same
pattern used for WebElements with WebDriver.findElement().

Removed Builder.buildAsync() as it was redundant with the change above.

This change is part of #2969, to support awaiting the build result:

async function demo(url); {
  let driver = await new Builder().forBrowser('firefox').build();
  return driver.get(�url);
}

jleyba added a commit that referenced this issue Nov 2, 2016

[js] Allow disabling the promise manager through a property setter.
Using the property setter will always take precedence over the environment
variable.

#2969
@jleyba

This comment has been minimized.

Show comment
Hide comment
@jleyba

jleyba Nov 3, 2016

Contributor

Phase 1 is complete: 3.0.0 has been published to npm.

Contributor

jleyba commented Nov 3, 2016

Phase 1 is complete: 3.0.0 has been published to npm.

@wcdeich4

This comment has been minimized.

Show comment
Hide comment
@wcdeich4

wcdeich4 Feb 3, 2017

After the promise manager is removed, how will people be able to call WebDriver functions in NodeJS imperatively (one after another)?

wcdeich4 commented Feb 3, 2017

After the promise manager is removed, how will people be able to call WebDriver functions in NodeJS imperatively (one after another)?

@kidmillions

This comment has been minimized.

Show comment
Hide comment

kidmillions commented Feb 3, 2017

@jleyba

This comment has been minimized.

Show comment
Hide comment
@jleyba

jleyba Feb 3, 2017

Contributor

There's an example of using async/await in the background section of the initial write up for this issue.

Contributor

jleyba commented Feb 3, 2017

There's an example of using async/await in the background section of the initial write up for this issue.

@wcdeich4

This comment has been minimized.

Show comment
Hide comment
@wcdeich4

wcdeich4 Feb 3, 2017

Ok. I tried this, but I still get an error:

driver = new webdriver.Builder().forBrowser('chrome').build();
await driver.get(url);

await driver.get(url);
^^^^^^
SyntaxError: Unexpected identifier

driver = await new webdriver.Builder().forBrowser('chrome').build(); has a similar exception

await driver.manage().window().maximize(); has a similar exception.

process.env.SELENIUM_PROMISE_MANAGER=0; did not stop the errors

wcdeich4 commented Feb 3, 2017

Ok. I tried this, but I still get an error:

driver = new webdriver.Builder().forBrowser('chrome').build();
await driver.get(url);

await driver.get(url);
^^^^^^
SyntaxError: Unexpected identifier

driver = await new webdriver.Builder().forBrowser('chrome').build(); has a similar exception

await driver.manage().window().maximize(); has a similar exception.

process.env.SELENIUM_PROMISE_MANAGER=0; did not stop the errors

@mekdev

This comment has been minimized.

Show comment
Hide comment
@mekdev

mekdev Feb 3, 2017

@wcdeich4

What node version are you using ? async await is supported in Node 7 and above with a feature flag. Else you can use babel to transpile to older versions of Node.

mekdev commented Feb 3, 2017

@wcdeich4

What node version are you using ? async await is supported in Node 7 and above with a feature flag. Else you can use babel to transpile to older versions of Node.

@mekdev

This comment has been minimized.

Show comment
Hide comment
@mekdev

mekdev Feb 3, 2017

Also for people jumping on board async await, this thread in the Nodejs project tracks the implementation roll out.

nodejs/promises#4

mekdev commented Feb 3, 2017

Also for people jumping on board async await, this thread in the Nodejs project tracks the implementation roll out.

nodejs/promises#4

@seadb

This comment has been minimized.

Show comment
Hide comment
@seadb

seadb Dec 22, 2017

So has the promise manager been removed?

seadb commented Dec 22, 2017

So has the promise manager been removed?

@donaldpipowitch

This comment has been minimized.

Show comment
Hide comment
@donaldpipowitch

donaldpipowitch Jan 3, 2018

Contributor

Would it be possible to publish a prerelease for 4.0.0-dev to npm? 😃 Would be nice for some easy tests and experiments.

Contributor

donaldpipowitch commented Jan 3, 2018

Would it be possible to publish a prerelease for 4.0.0-dev to npm? 😃 Would be nice for some easy tests and experiments.

cwolfes added a commit to cloudogu/jenkins that referenced this issue Feb 28, 2018

updates selenium webdriver which removes promise manager
the selenium driver now returns a promise instead of doing some crazy
stack-magic.

see this issue for more information:
SeleniumHQ/selenium#2969

cwolfes added a commit to cloudogu/redmine that referenced this issue Feb 28, 2018

updates selenium webdriver which removes promise manager
the selenium driver now returns a promise instead of doing some crazy
stack-magic.

see this issue for more information:
SeleniumHQ/selenium#2969
@johnjbarton

This comment has been minimized.

Show comment
Hide comment
@johnjbarton

johnjbarton May 3, 2018

Contributor

Since this is a breaking change that impacts almost every line of test code, what is the time line for a complete supported version 4.0 npm module publication?

In other words how much time do we have to prepare?

Contributor

johnjbarton commented May 3, 2018

Since this is a breaking change that impacts almost every line of test code, what is the time line for a complete supported version 4.0 npm module publication?

In other words how much time do we have to prepare?

@jleyba

This comment has been minimized.

Show comment
Hide comment
@jleyba

jleyba May 4, 2018

Contributor

npm already has 4.0.0-alpha.1 which includes the removal of the promise manager. It's tagged as "alpha" because the Selenium project as a whole wasn't ready to move to 4.0 and I couldn't release it as part of 3.X given the scope of changes. It's still considered a fully supported release, just like anything else published to npm

Contributor

jleyba commented May 4, 2018

npm already has 4.0.0-alpha.1 which includes the removal of the promise manager. It's tagged as "alpha" because the Selenium project as a whole wasn't ready to move to 4.0 and I couldn't release it as part of 3.X given the scope of changes. It's still considered a fully supported release, just like anything else published to npm

@awarecan

This comment has been minimized.

Show comment
Hide comment
@awarecan

awarecan May 4, 2018

@jleyba any plan to do another 3.x release?

awarecan commented May 4, 2018

@jleyba any plan to do another 3.x release?

@johnjbarton

This comment has been minimized.

Show comment
Hide comment
@johnjbarton

johnjbarton May 4, 2018

Contributor

@jleyba Thanks! Sorry I was not clear. I need to know when selenium-webdriver will release 4.0.

We support multiple languages and we would really prefer to use the same version across all of them. Thus we can't switch to 4.0 alpha, and yet we need a long lead time to prepare for it.

I'm only looking for a ballpark figure, "in the next few weeks", "early fall 2018".

Contributor

johnjbarton commented May 4, 2018

@jleyba Thanks! Sorry I was not clear. I need to know when selenium-webdriver will release 4.0.

We support multiple languages and we would really prefer to use the same version across all of them. Thus we can't switch to 4.0 alpha, and yet we need a long lead time to prepare for it.

I'm only looking for a ballpark figure, "in the next few weeks", "early fall 2018".

@jimevans

This comment has been minimized.

Show comment
Hide comment
@jimevans

jimevans May 5, 2018

Member

@johnjbarton Simon Stewart (@shs96c) is on record that we’ll be starting the engines on a 4.0 release once the W3C WebDriver Specification goes to Recommendation, which is likely to happen in late May/early June. Expect some beta period, then a full 4.0 release. In other words, “before Christmas” (inside joke for long-time project followers).

Member

jimevans commented May 5, 2018

@johnjbarton Simon Stewart (@shs96c) is on record that we’ll be starting the engines on a 4.0 release once the W3C WebDriver Specification goes to Recommendation, which is likely to happen in late May/early June. Expect some beta period, then a full 4.0 release. In other words, “before Christmas” (inside joke for long-time project followers).

@shanlashari

This comment has been minimized.

Show comment
Hide comment
@shanlashari

shanlashari May 14, 2018

Will we able to use Jasmine after this update implementation ?

shanlashari commented May 14, 2018

Will we able to use Jasmine after this update implementation ?

@StanislavKharchenko

This comment has been minimized.

Show comment
Hide comment
@StanislavKharchenko

StanislavKharchenko Jun 29, 2018

Hello.
What is the planned dates to release of this feature?
Does it mean that all protractor tests need to have async/await in specs and page-objects when promise manager will be completely removed?

Thank you in advance.

StanislavKharchenko commented Jun 29, 2018

Hello.
What is the planned dates to release of this feature?
Does it mean that all protractor tests need to have async/await in specs and page-objects when promise manager will be completely removed?

Thank you in advance.

@StanislavKharchenko

This comment has been minimized.

Show comment
Hide comment
@StanislavKharchenko

StanislavKharchenko Jul 12, 2018

@jleyba How we can use async/await for cases when need to fill element (or block of elements) in web page using setter? Example:

mainPage.Information = "Address information..."

Where information is setter with sendKeys, like:

set Information(value) {
   this.information.sendKeys(value);
}

Typescript is not allow async/await for setters.
So, how we can use such approach without promise manager?

Thank you.

StanislavKharchenko commented Jul 12, 2018

@jleyba How we can use async/await for cases when need to fill element (or block of elements) in web page using setter? Example:

mainPage.Information = "Address information..."

Where information is setter with sendKeys, like:

set Information(value) {
   this.information.sendKeys(value);
}

Typescript is not allow async/await for setters.
So, how we can use such approach without promise manager?

Thank you.

@StanislavKharchenko

This comment has been minimized.

Show comment
Hide comment
@StanislavKharchenko

StanislavKharchenko Jul 24, 2018

Does anyone help me with comment above?
What is the destiny with promise manager?

It is a not good situation, when something couldn't work due to deprecation of functionality.
Using a setter to fill web-elements is very laconic style and it is bad to lose it.

Thanks!

StanislavKharchenko commented Jul 24, 2018

Does anyone help me with comment above?
What is the destiny with promise manager?

It is a not good situation, when something couldn't work due to deprecation of functionality.
Using a setter to fill web-elements is very laconic style and it is bad to lose it.

Thanks!

@johnjbarton

This comment has been minimized.

Show comment
Hide comment
@johnjbarton

johnjbarton Jul 24, 2018

Contributor
await mainPage.setInformation("Address information...");
Contributor

johnjbarton commented Jul 24, 2018

await mainPage.setInformation("Address information...");
@StanislavKharchenko

This comment has been minimized.

Show comment
Hide comment
@StanislavKharchenko

StanislavKharchenko Jul 24, 2018

@johnjbarton, thank you, but it is not a solution. Its just a method, but the idea to use a setter.

StanislavKharchenko commented Jul 24, 2018

@johnjbarton, thank you, but it is not a solution. Its just a method, but the idea to use a setter.

@jleyba

This comment has been minimized.

Show comment
Hide comment
@jleyba

jleyba Jul 24, 2018

Contributor

If TypeScript won't let you use async/await with a setter, don't use a setter.

FWIW, I don't think you should try to hide expensive RPCs in a setter anyway. Bit of an anti-pattern IMO.

Contributor

jleyba commented Jul 24, 2018

If TypeScript won't let you use async/await with a setter, don't use a setter.

FWIW, I don't think you should try to hide expensive RPCs in a setter anyway. Bit of an anti-pattern IMO.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment