Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

WebDriverJs

Alex Rodionov edited this pageNov 1, 2017 ·8 revisions

Introduction

The WebDriverJS library uses apromise manager to ease the pain of working with a purely asynchronous API. Rather than writing a long chain of promises, the promise manager allows you to write code as if WebDriverJS had a synchronous, blocking API (like all of the other Selenium language bindings). For instance, instead of

const{Builder, By, until}=require('selenium-webdriver');newBuilder().forBrowser('firefox').build().then(driver=>{returndriver.get('http://www.google.com/ncr').then(_=>driver.findElement(By.name('q')).sendKeys('webdriver')).then(_=>driver.findElement(By.name('btnK')).click()).then(_=>driver.wait(until.titleIs('webdriver - Google Search'),1000)).then(_=>driver.quit());});

you can write

const{Builder, By, until}=require('selenium-webdriver');letdriver=newBuilder().forBrowser('firefox').build();driver.get('http://www.google.com/ncr');driver.findElement(By.name('q')).sendKeys('webdriver');driver.findElement(By.name('btnK')).click();driver.wait(until.titleIs('webdriver - Google Search'),1000);driver.quit();

Unforutnately, there is no such thing as a free lunch. With WebDriverJS, using the promise manager comes at the cost of increased complexity (for the Selenium maintainers--thepromise module is a 3300 line beast) and reduced debuggability. For debugging, suppose you inserted adebugger statement:

driver.get('http://www.google.com/ncr');debugger;driver.findElement(By.name('q')).sendKeys('webdriver');

When is this script going to pause - after WebDriver loads google.com, or after itschedules the command to load google.com? Since the promise manager abstracts away the async nature of the API, it hides that you need to expicitly use a callback to break after a commandexecutes:

driver.get('http://www.google.com/ncr').then(()=>debugger);driver.findElement(By.name('q')).sendKeys('webdriver');

JavaScript has evolved in many ways since WebDriverJS was originally created. Not only did the community standardize the behavior and API for promises, but promises were added to the language itself. The next version of JavaScript, ES2017, adds support forasync functions, greatly simplyfing the process of writing and maintaining asynchronous code. At this point, the benefits of the promise manager no longer outweigh its costs, so it willsoon be deprecated and removed from WebDriverJS. The remainder of this guide will explain how users can migrate off the promise manager and effectively use the async constructs available today.

Moving to async/await

Step 1: Disabling the Promise Manager

As outlined inissue 2969, WebDriverJS' promise manager will be deprecated and disabled by default with the firstNode LTS release that includes native support for async functions. This feature is currently available in Node 7.x, hidden behind the--harmony_async_await flag.

Instead of waiting for the LTS, you can disable the promise manager today either by setting an environment variable,SELENIUM_PROMISE_MANAGER=0, or through the promise module's API (promise.USE_PROMISE_MANAGER = false).

When the promise manager is disabled, any attempt to create amanaged promise will generate an error, so expect your scripts to fail the first time you try running with the promise manager.

Step 2: Migrate Direct Usages of Managed Promises

Search your code for every instance where you create apromise.Promise object, either using the constructor, or theresolve/reject factories. Replace these calls with the equivalent listed in the table below. These functions will automatically switch from managed to native promises when the promise manager is disabled.

OriginalReplacement
new promise.Promise()promise.createPromise()
promise.Promise.resolve()promise.fulfilled()
promise.Promise.reject()promise.rejected()

Step 3: Migrate Off of the Promise Manager

At this point, you should be ready to migrate off of the promise manager. Unfortunately, there's no automated way to update your code; you basically have to disable the promise manager, see what fails, update some code, and try again. To understand how your code will, fail, consider a test for our Google Search example:

const{Builder, By, until}=require('selenium-webdriver');consttest=require('selenium-webdriver/testing');describe('Google Search',function(){letdriver;test.before(function(){driver=newBuilder().forBrowser('firefox').build();});test.it('example',functiontheTestFunction(){driver.get('http://www.google.com/ncr');// (1)driver.findElement(By.name('q')).sendKeys('webdriver');// (2)driver.findElement(By.name('btnK')).click();// (3)driver.wait(until.titleIs('webdriver - Google Search'),1000);// (4)});test.after(function(){driver.quit();});});

InsidetheTestFunction, when the promise manager is enabled, it will capture every WebDriver command and block its execution until those before it have completed. Thus, even thoughdriver.findElement() on line(2) is immediatelyscheduled, it will not start execution until the command on line(1) completes.

When you disable the promise manager, every command will start executing as soon as its scheduled. The exact behavior depends on the specific browser, but essentially, the command to find an element on line(2) will start executing before the page requested on line(1) has loaded.

You will need to update your code to explicitly link each action so it does not start until previous commands have finished. Presented below are three options for how to update your code.

Option 1: Use classic promise chaining

Your first option is to adopt classic promise chaining (yes, the very thing the promise manager was created to avoid). This will make your code far more verbose, but it will work with and without the promise manager and you won't need to use theselenium-webdriver/testing module for mocha-based tests.

const{Builder, By, until}=require('selenium-webdriver');consttest=require('selenium-webdriver/testing');describe('Google Search',function(){letdriver;before(function(){returnnewBuilder().forBrowser('firefox').build().then(d=>{driver=d;});});it('example',functiontheTestFunction(){returndriver.get('http://www.google.com/ncr').then(_=>driver.findElement(By.name('q'))).then(q=>q.sendKeys('webdriver')).then(_=>driver.findElement(By.name('btnK'))).then(b=>b.click()).then(_=>driver.wait(until.titleIs('webdriver - Google Search'),1000));});after(function(){returndriver.quit();});});

Option 2: Migrate to Generators

Your second option is to update your code to use asynchronouse generator functions. Theselenium-webdriver/testing module already handles these out of the box. You can also use third-party libraries liketask.js for the same effect. Basically, change each of your test functions to a generator, andyield on a promise. The generator wrapper will transparently wait for the promise to resolve before resuming the function. It's important tonote, however, asynchronous generators are not currently supported natively in JavaScript, so youwill have to useselenium-webdriver or another library for this to work.

const{Builder, By, until}=require('selenium-webdriver');consttest=require('selenium-webdriver/testing');describe('Google Search',function(){letdriver;test.before(function*(){driver=yieldnewBuilder().forBrowser('firefox').build();});test.it('example',function*theTestFunction(){yielddriver.get('http://www.google.com/ncr');// (1)yielddriver.findElement(By.name('q')).sendKeys('webdriver');// (2)yielddriver.findElement(By.name('btnK')).click();// (3)yielddriver.wait(until.titleIs('webdriver - Google Search'),1000);// (4)});test.after(function*(){yielddriver.quit();});});

The advantage to using generators withselenium-webdriver/testing is your code will work with and without the promise manager, so you can convert one test at a time. Another advantage to this approach is your code will work today with Node 6 & 7. When async/await support is added to Node (it's currently hidden behind a flag in Node 7), you can migrate from generators with find-and-replace, convertingfunction*() toasync function() andyield toawait.

Theselenium-webdriver/example directory contains an example of our Google Search test written with and without generators so you can compare the two side-by-side.

Option 3: Migrate to async/await

Your final option is to switch to async/await. As mentioned above, these language features are currently hidden behind a flag in Node 7, so you will have to run with--harmony_async_awaitor you will have to transpile your code withBabel (setting up Babel is left as an exercise for the reader).

Compared to generators, there is one more catch to using async/await: theydo not play well with the promise manager, so you must ensure the promise manager is disabled. There is a complete working example of a test written using async/await provided in theselenium-webdriver/example directory:

const{Builder, By, until}=require('selenium-webdriver');promise.USE_PROMISE_MANAGER=false;describe('Google Search',function(){letdriver;beforeEach(asyncfunction(){driver=awaitnewBuilder().forBrowser('firefox').build();});afterEach(asyncfunction(){awaitdriver.quit();});it('example',asyncfunction(){awaitdriver.get('https://www.google.com/ncr');awaitdriver.findElement(By.name('q')).sendKeys('webdriver');awaitdriver.findElement(By.name('btnK')).click();awaitdriver.wait(until.titleIs('webdriver - Google Search'),1000);});});

This example disables the promise manager globally. In order to migrate tests bit-by-bit, you can selectively disable the promise manager in before/after blocks:

promise.USE_PROMISE_MANAGER=false;functionlegacySuite(name,fn){describe(name,function(){before(()=>promise.USE_PROMISE_MANAGER=true);after(()=>promise.USE_PROMISE_MANAGER=false);fn();});}describe('Example',function(){legacySuite('legacy tests',function(){test.it('test 1',function(){// ...});});it('test 2',asyncfunction(){// ...});});

Miscellaneous Tips

Use Logging To Find Unmigrated Code

While the promise manager can be easily toggled through an enviornment variable, constantly running your code in the two modes can be tedious. Selenium's promise module provides logging to report every instance of unsynchronized code running through the promise manager (which, in code, is actually called theControlFlow). You can enable this logging, then work through the messages to update each instance of commands that have not be properly chained (depending on the option you chose above).

Enabling logging is only two extra lines:

const {Builder, By, logging, until} = require('selenium-webdriver');logging.installConsoleHandler();logging.getLogger('promise.ControlFlow').setLevel(logging.Level.ALL);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('btnK')).click();driver.wait(until.titleIs('webdriver - Google Search'), 1000);driver.quit();

With logging, the example above will produce messages like:

[2017-03-06T02:18:14Z] [WARNING] [promise.ControlFlow] Detected scheduling of an unchained task.    When the promise manager is disabled, unchained tasks will notwaitfor    previously scheduled tasks to finish before starting to execute.    New task: Task:WebDriver.navigate().to(http://www.google.com/ncr)        at thenableWebDriverProxy.schedule (/Users/jleyba/Development/test/node_modules/selenium-webdriver/lib/webdriver.js:816:17)        at Navigation.to (/Users/jleyba/Development/test/node_modules/selenium-webdriver/lib/webdriver.js:1140:25)        at thenableWebDriverProxy.get (/Users/jleyba/Development/test/node_modules/selenium-webdriver/lib/webdriver.js:997:28)        at Object.<anonymous> (/Users/jleyba/Development/test/log.js:10:8)        at Module._compile (module.js:571:32)        at Object.Module._extensions..js (module.js:580:10)        at Module.load (module.js:488:32)        at tryModuleLoad (module.js:447:12)        at Function.Module._load (module.js:439:3)        at Module.runMain (module.js:605:10)    Previous task: Task:WebDriver.createSession()        at Function.createSession (/Users/jleyba/Development/test/node_modules/selenium-webdriver/lib/webdriver.js:777:24)        at Function.createSession (/Users/jleyba/Development/test/node_modules/selenium-webdriver/firefox/index.js:667:55)        at createDriver (/Users/jleyba/Development/test/node_modules/selenium-webdriver/index.js:167:33)        at Builder.build (/Users/jleyba/Development/test/node_modules/selenium-webdriver/index.js:642:16)        at Object.<anonymous> (/Users/jleyba/Development/test/log.js:8:6)        at Module._compile (module.js:571:32)        at Object.Module._extensions..js (module.js:580:10)        at Module.load (module.js:488:32)        at tryModuleLoad (module.js:447:12)        at Function.Module._load (module.js:439:3)

Notice for Users

This wiki is not where you want to be!Visit the Wiki Home for more useful links

For Developers and Contributors

Getting Involved
Triaging Issues
Releasing Selenium

Binding Specific Information

Ruby Development
Python Bindings
Ruby Bindings
WebDriverJs

Being Processed

This content is being evaluated for where it belongs

Architectural Overview
Automation Atoms
HtmlUnitDriver
Lift Style API
LoadableComponent
Logging
PageFactory
RemoteWebDriver
Xpath In WebDriver

Archived

Moved toOfficial Documentation

Bot Style Tests
Buck
Continuous Integration
Crazy Fun Build
Design Patterns
Desired Capabilities
Developer Tips
Domain Driven Design
Firefox Driver
Firefox Driver Internals
Focus Stealing On Linux
Frequently Asked Questions
Google Summer Of Code
Grid Platforms
History
Internet Explorer Driver
InternetExplorerDriver Internals
Next Steps
PageObjects
RemoteWebDriverServer
Roadmap
Scaling WebDriver
SeIDE Release Notes
Selenium Emulation
Selenium Grid 4
Selenium Help
Shipping Selenium 3
The Team
TLC Meetings
Untrusted SSL Certificates
WebDriver For Mobile Browsers
Writing New Drivers

Clone this wiki locally


[8]ページ先頭

©2009-2025 Movatter.jp