Within your web app's UI there are areas that your tests interact with. A Page Object simply models these as objects within the test code. This reduces the amount of duplicated code and means that if the UI changes, the fix need only be applied in one place.
Developed in collaboration with the Chromium team, ChromeDriver is a standalone server which implements WebDriver's wire protocol. Visit the full ChromeDriver site View all ChromeDriver downloads. The ChromeDriver consists of three separate pieces. Introducing WebDriver¶. The primary new feature in Selenium 2.0 is the integration of the WebDriver API. WebDriver is designed to provide a simpler, more concise programming interface in addition to addressing some limitations in the Selenium-RC API.
Implementation Notes
PageObjects can be thought of as facing in two directions simultaneously. Facing towards the developer of a test, they represent the services offered by a particular page. Facing away from the developer, they should be the only thing that has a deep knowledge of the structure of the HTML of a page (or part of a page) It's simplest to think of the methods on a Page Object as offering the 'services' that a page offers rather than exposing the details and mechanics of the page. As an example, think of the inbox of any web-based email system. Amongst the services that it offers are typically the ability to compose a new email, to choose to read a single email, and to list the subject lines of the emails in the inbox. How these are implemented shouldn't matter to the test.
Because we're encouraging the developer of a test to try and think about the services that they're interacting with rather than the implementation, PageObjects should seldom expose the underlying WebDriver instance. To facilitate this, methods on the PageObject should return other PageObjects. This means that we can effectively model the user's journey through our application. It also means that should the way that pages relate to one another change (like when the login page asks the user to change their password the first time they log into a service, when it previously didn't do that) simply changing the appropriate method's signature will cause the tests to fail to compile. Put another way, we can tell which tests would fail without needing to run them when we change the relationship between pages and reflect this in the PageObjects.
One consequence of this approach is that it may be necessary to model (for example) both a successful and unsuccessful login, or a click could have a different result depending on the state of the app. When this happens, it is common to have multiple methods on the PageObject:
The code presented above shows an important point: the tests, not the PageObjects, should be responsible for making assertions about the state of a page. For example:
could be re-written as:
Of course, as with every guideline there are exceptions, and one that is commonly seen with PageObjects is to check that the WebDriver is on the correct page when we instantiate the PageObject. This is done in the example below.
Finally, a PageObject need not represent an entire page. It may represent a section that appears many times within a site or page, such as site navigation. The essential principle is that there is only one place in your test suite with knowledge of the structure of the HTML of a particular (part of a) page.
Summary
- The public methods represent the services that the page offers
- Try not to expose the internals of the page
- Generally don't make assertions
- Methods return other PageObjects
- Need not represent an entire page
- Different results for the same action are modelled as different methods
Example
Selenium Java Api
Support in WebDriver
There is a PageFactory in the support package that provides support for this pattern, and helps to remove some boiler-plate code from your Page Objects at the same time.
Introduction
Selenium Browser Drivers
The WebDriverJS library uses a promise 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
you can write
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--the promise module is a 3300 line beast) and reduced debuggability. For debugging, suppose you inserted a
debugger
statement:When is this script going to pause - after WebDriver loads google.com, or after it schedules 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 command executes:
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 for async 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 will soon 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 in issue 2969, WebDriverJS' promise manager will be deprecated and disabled by default with the first Node 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 a managed 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 a
promise.Promise
object, either using the constructor, or the resolve
/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.Original | Replacement |
---|---|
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:
Inside
theTestFunction
, when the promise manager is enabled, it will capture every WebDriver command and block its execution until those before it have completed. Thus, even though driver.findElement()
on line (2)
is immediately scheduled, 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 the
selenium-webdriver/testing
module for mocha-based tests.Option 2: Migrate to Generators
Your second option is to update your code to use asynchronouse generator functions. The
selenium-webdriver/testing
module already handles these out of the box. You can also use third-party libraries like task.js for the same effect. Basically, change each of your test functions to a generator, and yield
on a promise. The generator wrapper will transparently wait for the promise to resolve before resuming the function. It's important to note, however, asynchronous generators are not currently supported natively in JavaScript, so you will have to use selenium-webdriver
or another library for this to work.The advantage to using generators with
selenium-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, converting function*()
to async function()
and yield
to await
.The
selenium-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_await
or you will have to transpile your code with Babel (setting up Babel is left as an exercise for the reader).Compared to generators, there is one more catch to using async/await: they do 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 the
selenium-webdriver/example
directory: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:
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 the
ControlFlow
). 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:
Selenium Webdriver Wikipedia
With logging, the example above will produce messages like: