Skip to main content

Protractor

Protractor is an end-to-end test framework for Angular and AngularJS applications, based on Selenium 3. Protractor runs tests against your application running in a real browser, interacting with it as a user would.

Serenity/JS revolutionises automated testing by enabling your team to write expressive, maintainable tests that align with your unique domain. Seamlessly integrating with Protractor and test runners like Mocha, Cucumber, and Jasmine, Serenity/JS also offers advanced reporting that provides clear insights into test results, helping both technical teams and business stakeholders understand the quality of the system under test.

Plus, it accommodates both classic Protractor tests and Serenity/JS Screenplay Pattern scenarios, allowing you to migrate to Screenplay gradually, and then simply switch to using Serenity/JS with a more modern web integration tool like Playwright or WebdriverIO.

Protractor is deprecated

Protractor is now officially deprecated and has not received any updates since April 2020. You should not rely on Protractor for any new test automation projects, and instead use Serenity/JS with more modern and developer-friendly integration tools like WebdriverIO or Playwright Test.

Should I use Serenity/JS with my existing Protractor project?

Yes. The most common reason why you should introduce Serenity/JS to an existing Protractor project is that it can help you to reliably migrate your codebase to a more modern integration tool like WebdriverIO or Playwright.

Using Serenity/JS Screenplay Pattern APIs will also help you future-proof your codebase and make it agnostic of the underlying integration tools.

Benefits of integrating Protractor with Serenity/JS:

In this guide, you will learn how to:

  • Integrate Serenity/JS with your Protractor test suite.
  • Enable Serenity BDD reports.
  • Start using the Screenplay Pattern.
  • Replace Protractor with WebdriverIO while keeping your tests working!

Serenity BDD Report Example

Quick start 🚀

To start testing immediately, consider using:

To see Serenity/JS reporting in action, explore the live reports generated by the Serenity/JS + Protractor Project Templates:

FrameworkProject TemplateLive Report
Serenity/JS + Cucumber + ProtractorProject TemplateLive Report
Serenity/JS + Mocha + ProtractorProject TemplateLive Report
Serenity/JS + Jasmine + ProtractorProject TemplateLive Report

Installation

To use Serenity/JS with Protractor, follow the Serenity/JS installation guide to set up your development environment and core runtime dependencies. Then, create a new Protractor project or add Serenity/JS integration and reporting modules to an existing project.

Installing Serenity/JS

To add Serenity/JS to a Protractor project, install the following modules:

npm install --save-dev @serenity-js/core @serenity-js/console-reporter @serenity-js/protractor @serenity-js/rest @serenity-js/web @serenity-js/serenity-bdd

This command installs:

Protractor offers a test runner that uses Jasmine, Mocha, or Cucumber to run your test scenarios. Since the task of running the scenarios is delegated to another tool, you'll need to follow the installation instructions to add a Serenity/JS test runner adapter for the runner you've decided to use.

See Serenity/JS test runner adapter installation instructions for:

Configuration

Protractor uses the protractor.conf.js file to configure test scenarios and reporters.

This section provides a step-by-step guide to the complete configuration setup.

Integrating Serenity/JS reporting

Serenity/JS offers automatic screenshot capture for test scenarios using the Screenplay Pattern. This is handled by the Photographer service, which takes screenshots based on interactions and assertion failures performed by the Serenity/JS actors.

To integrate Serenity/JS reporting and enable automatic screenshot capture, modify the protractor.conf.js file as follows:

protractor.conf.js
exports.config = {
// Disable Selenium promise manager
SELENIUM_PROMISE_MANAGER: false,

framework: 'custom',
frameworkPath: require.resolve('@serenity-js/protractor/adapter'),

specs: [
'./features/**/*.feature'
],

serenity: {
runner: 'cucumber',
crew: [
// Optional, print test execution results to standard output
'@serenity-js/console-reporter',

// Optional, produce Serenity BDD reports
// and living documentation (HTML)
[ '@serenity-js/serenity-bdd', {
specDirectory: './features'
} ],

[ '@serenity-js/core:ArtifactArchiver', {
outputDirectory: 'target/site/serenity'
} ],

// Optional, automatically capture screenshots
// upon interaction failure
[ '@serenity-js/web:Photographer', {
strategy: 'TakePhotosOfFailures'
} ],
]
},

cucumberOpts: {
require: [
'features/step_definitions/**/*.steps.ts', // If you're using TypeScript
'features/support/*.ts',
// 'features/step_definitions/**/*.steps.js', // If you're using JavaScript
// 'features/support/*.js'
],
requireModule: [
// Optional, if you're using TypeScript
'ts-node/register'
],
tags: ['not @wip'],
strict: false,
}
};

This configuration enables the @serenity-js/protractor test runner adapter, which in turn configures the "stage crew" of Serenity/JS reporting services:

  • Console reporter - Displays test results in the terminal.
  • Serenity BDD reporter - Produces json reports to be ingested by the Serenity BDD CLI and produce the living documentation.
  • Photographer - Automatically captures screenshots of the browser upon interactions or assertion failures when configured with TakePhotosOfInteractions or TakePhotosOfFailures, respectively.
  • Artifact Archiver - Stores the json reports and screenshots captured by the Photographer to disk.

Note that the above configuration assumes the following directory structure of your project:

  • ./spec or ./features - stores your test scenarios and is the top-most directory of your requirements hierarchy.
  • ./target/site/serenity - stores any test report artifacts, like the .json files and screenshots.

If you'd like to use different locations for your tests or the test reports, adjust the specDirectory and outputDirectory settings accordingly.

Learn more about the configuration options for your test runner:

Test RunnerConfiguration OptionsComplete protractor.conf.js
CucumberSerenity/JS Cucumber configuration optionsCucumber Protractor Template config
JasmineSerenity/JS Jasmine configuration optionsJasmine Protractor Template config
MochaSerenity/JS Mocha configuration optionsMocha Protractor Template config

Writing tests

Serenity/JS is designed to integrate seamlessly with your existing Protractor codebase, even if you are not using the Screenplay Pattern yet. Additionally, the framework enables you to mix Screenplay and non-Screenplay scenarios within the same codebase, helping your team gradually adopt the pattern where appropriate.

In this section, you will learn how to write test scenarios using Protractor and Serenity/JS APIs and how to leverage actors to structure your test interactions.

Using the Screenplay Pattern APIs

The Screenplay Pattern is an innovative, user-centred approach to writing high-quality automated acceptance tests. It promotes effective use of layers of abstraction, helps your test scenarios reflect the business vernacular of your domain, and encourages good testing and software engineering practices within your team.

To use the Screenplay Pattern APIs, import the relevant interactions and questions from the appropriate modules, and instruct your actors to perform them using the actor.attemptsTo method.

The most commonly used Screenplay Pattern APIs come from the following modules:

Using actors in test scenarios

A test scenario following the Screenplay Pattern models workflows of one or multiple actors representing people and external systems interacting with the system under test.

When you configure Serenity/JS Protractor as the Protractor framework, Serenity/JS automatically creates and makes available a default cast of actors, where every actor has the abilities to:

  • BrowseTheWebWithProtractor - Allows interaction with the browser using the global protractor.browser object.
  • TakeNotes.usingAnEmptyNotepad() - Facilitates storing and retrieving information during the test run.
  • CallAnApi - Enables interaction with RESTful APIs using an Axios HTTP client configured with configured with baseUrl.
Overriding abilities

An actor can only have one instance of each ability type at a time. Therefore, providing a new instance of the same type via the actor.whoCan method overrides any existing ability of that type

To use an actor in your test scenario, refer to it using the actorCalled or actorInTheSpotlight functions and they'll automatically use the configured cast of actors to create or retrieve the actor you need.

specs/example.spec.ts
import 'jasmine'
import { actorCalled } from '@serenity-js/core'
import { Navigate, Page } from '@serenity-js/web'
import { Ensure, equals } from '@serenity-js/assertions'

describe('My awesome website', () => {
it('can have test scenarios that follow the Screenplay Pattern', async () => {
await actorCalled('Alice').attemptsTo(
Navigate.to(`https://www.protractortest.org/`),
Ensure.that(
Page.current().title(),
equals(`Protractor - end-to-end testing for AngularJS`)
),
)
})

it('can have non-Screenplay scenarios too', async () => {
await browser.get(`https://www.protractortest.org`)
await expect(browser.getTitle())
.toBe('Protractor - end-to-end testing for AngularJS')
})
})

Since Protractor uses Jasmine, Mocha, or Cucumber to run your test scenarios, please refer to their dedicated guides to learn more about using Serenity/JS actors with:

Replacing the default actors

You can replace the default cast of actors by providing a custom implementation via serenity.actors configuration option in your protractor.conf.js.

For example, to implement a cast where every actor can BrowseTheWebWithProtractor, TakeNotes and CallAnApi, you could create a MyActors class like this:

test/MyActors.js
const { TakeNotes } = require('@serenity-js/core')
const { CallAnApi } = require('@serenity-js/rest')
const { BrowseTheWebWithProtractor } = require('@serenity-js/protractor')

exports.Actors = class Actors {
constructor(apiUrl) {
this.apiUrl = apiUrl
}

prepare(actor) {
return actor.whoCan(
BrowseTheWebWithProtractor.using(require('protractor').browser),
TakeNotes.usingAnEmptyNotepad(),
CallAnApi.at(this.apiUrl),
);
}
}
No browser in the configuration file

Protractor doesn't allow you to use the browser global variable in protractor.conf.js. That's why you need to create a custom implementation of Cast and only refer to browser in Cast.prepare method.

No TypeScript in the configuration file

Protractor doesn't allow you to use TypeScript in protractor.conf.js. That's why MyActors needs to be implemented in plain-old JavaScript.

Next, modify your Protractor configuration file to provide your custom MyActors implementation:

protractor.conf.js
const { MyActors } = require('./test/MyActors');

exports.config = {

framework: 'custom',
frameworkPath: require.resolve('@serenity-js/protractor/adapter'),

serenity: {
actors: new MyActors(),
crew: [
'@serenity-js/console-reporter',
'@serenity-js/serenity-bdd',
[ '@serenity-js/core:ArtifactArchiver', { outputDirectory: 'target/site/serenity' } ],
[ '@serenity-js/web:Photographer', { strategy: 'TakePhotosOfFailures' } ],
]
},

// other Protractor config
}

Reporting

Serenity/JS provides comprehensive reporting capabilities and integrates with the Serenity BDD reporter.

Reference Implementation

Explore the Serenity/JS + Protractor project templates to see the reporting capabilities in action.

Serenity BDD Reports

Serenity reports and living documentation are a powerful feature enabled by Serenity BDD. They aim not only to report test results, but also to document how features are tested, and what your application does.

Serenity BDD reports are generated by the Serenity BDD CLI, a Java program that ships with the @serenity-js/serenity-bdd module. These reports are based on the json reports produced by the Serenity BDD Reporter, as well as screenshots captured by the Photographer.

Example Serenity BDD report

To generate Serenity BDD HTML reports and living documentation, your test suite must:

  1. Use SerenityBDDReporter and ArtifactArchiver as per the configuration instructions.
  2. Invoke the serenity-bdd run command when the test run has finished to generate the Serenity BDD report.

All Serenity/JS Project Templates follow the same recommended pattern to generate Serenity BDD reports. This approach relies on:

  • NPM scripts to invoke the command-line tools, such as Playwright Test or the Serenity BDD CLI.
  • npm-failsafe to execute a sequence of NPM scripts.
  • rimraf to remove any test reports left over from the previous run.

You can install these additional recommended modules as follows:

npm install --save-dev npm-failsafe rimraf

Next, add the following convenience scripts to your package.json file:

  • clean - removes any test reports left over from the previous test run.
  • test - uses npm-failsafe to execute multiple NPM scripts and generate test reports.
  • test:execute - an example alias for protractor. You can extend it to include any necessary command-line arguments.
  • test:report - an alias for serenity-bdd run. You can configure it with alternative json report locations (--source) and HTML report destinations (--destination). Run npx serenity-bdd run --help to see the available options.
package.json
{
"scripts": {
"clean": "rimraf target",
"test": "failsafe clean test:execute test:report",
"test:execute": "protractor ./protractor.conf.js",
"test:report": "serenity-bdd run --source ./target/site/serenity --destination ./target/site/serenity",
}
}

To learn more about the SerenityBDDReporter, see:

Migrating from Protractor to Serenity/JS

Serenity/JS accommodates both classic Protractor tests and Serenity/JS Screenplay Pattern scenarios, allowing you to migrate to Screenplay gradually while keeping your existing test suites working and providing value to your organisation during the migration. With Serenity/JS, there's no need for a big-bang rewrite!

If you have an existing Protractor test suite and want to upgrade to a more modern web integration tool like Playwright or WebdriverIO, you'll need to:

Migrate or rewrite?

Migrating existing Protractor test scenarios to follow the Serenity/JS Screenplay Pattern can be a big undertaking, depending on the size, complexity, and stability of your current test suite. Not to mention that every team has a slightly different Protractor setup and uses the tool in different ways, which makes automating the migration process challenging.

When migrating to Serenity/JS, start with the easy scenarios so that you can focus the first steps of your migration on making sure your continuous integration and reporting infrastructure work reliably. This way you'll build a stable foundation upon which you can migrate the more complex tests.

Remember, if your current Protractor test suite is reasonably stable, still works and provides value, there's no need for a risky big bang rewrite as you can migrate it one scenario at a time while keeping the existing tests working. However, if your current Protractor test suite doesn't work and you'd rather delete it than migrate it, you might prefer to start from scratch and use Serenity/JS with Playwright or WebdriverIO straight away. The choice is yours and Serenity/JS will support you either way.

Locating elements

To identify web elements with Serenity/JS you use the Page Element Query Language.

In short:

  • PageElement represents a single HTML element,
  • PageElements represent a collection of HTML elements and lets you filter it based on your criteria.
  • By represents portable locators used to identify the elements,

For example, to identify an element <h1 id="title">My article</h1> in plain Protractor you'd say:

element(by.css('h1.title'))

Serenity/JS has a similar construct and allows you to specify a description to be used when reporting interactions with the element:

import { By, PageElement } from '@serenity-js/web'

PageElement.located(By.css('h1.title'))

However, Serenity/JS also allows you to specify a human-readable description to be used when reporting interactions with the element:

import { By, PageElement } from '@serenity-js/web'

PageElement.located(By.css('h1.title'))
.describedAs('article title')

The below table shows how you can translate common Protractor element finder expressions to Serenity/JS page element expressions:

ProtractorSerenity/JS
element(by.css('.selector'))PageElement.located(By.css('...'))
element(by.id('...'))PageElement.located(By.id('...'))
element(by.xpath('...'))PageElement.located(By.xpath('...'))
element(by.model('...'))PageElement.located(By.css('[ng-model="..."]'))
element(by.repeater('...'))PageElement.located(By.css('[ng-repeat="..."]'))
element(by.cssContainingText('.selector', 'text'))PageElement.located(By.cssContainingText('.selector', 'text'))
or for greater flexibility:
PageElements.located(By.css('.selector')).where(Text, includes('text')).first()
element(by.buttonText('Submit'))PageElements.located(By.css('button')).where(Text, equals('Submit')).first()
element.allPageElements.located(By.css('...'))

Note how with Serenity/JS you can easily express complex queries using intuitive syntax. For example, you can locate the last <button /> which text meets some expectation:

import { By, PageElements, Text } from '@serenity-js/web'
import { equals } from '@serenity-js/assertions'

PageElements.located(By.css('button'))
.where(Text, equals('Submit'))
.last()

You can create aliases for expressions you use frequently:

import { By, PageElements, Text } from '@serenity-js/web'
import { equals } from '@serenity-js/assertions'

const approveButton = () =>
PageElements.located(By.css('button'))
.where(Text, equals('Approve'))
.first()

And you can also compose page elements, making them reusable:

import { actorCalled, Expectation } from '@serenity-js/core'
import { equals } from '@serenity-js/assertions'
import { By, Click, PageElement, PageElements } from '@serenity-js/web'

const clientNameField = () =>
PageElement.located(By.css('[data-test-id="client-name"]'))
.describedAs('client name field')

const invoiceRecordForClientWhereName = (expectation: Expectation<string>) =>
PageElements.located(By.css('li.invoice'))
.where(Text.of(clientNameField()), expectation)
.first()

await actorCalled('Alice').attemptsTo(
Click.on(
approveButton().of(invoiceRecordForClientWhereName(equals('Acme')))
)
)

Learn more:

Retrieving information

To retrieve information about the current web page, use Page.current().

ProtractorSerenity/JS
browser.getCurrentUrl()Page.current().url().href
browser.getTitle()Page.current().title()

For example, to navigate to a page and then assert on its title and URL you can say:

import { actorCalled } from '@serenity-js/core'
import { Ensure, endsWith, equals } from '@serenity-js/assertions'
import { Navigate, Page } from '@serenity-js/web'

await actorCalled('Amanda').attemptsTo(
Navigate.to('https://serenity-js.org'),
Ensure.that(
Page.current().title(),
endsWith('Serenity/JS')
),
Ensure.that(
Page.current().url().href,
equals('https://serenity-js.org')
),
)

To retrieve information about a page element, use Serenity/JS web questions:

ProtractorSerenity/JSNotes
element(by.id('...')).getAttribute('class')CssClasses.of(pageElement)Serenity/JS returns an array of CSS classes to make assertions easier, instead of a single string like plain Protractor
element(by.id('...')).getAttribute('value')Value.of(pageElement)Serenity/JS has a dedicated question for retrieving the value attribute of <input /> elements
element(by.id('...')).getText()Text.of(pageElement) and Text.ofAll(pageElements)Serenity/JS makes it easier to retrieve text content of all elements in a collection

For example, to navigate to a page and assert on the CSS classes of an element, you could say:

import { actorCalled } from '@serenity-js/core'
import { By, CssClasses, Navigate, PageElement } from '@serenity-js/web'
import { Ensure, include } from '@serenity-js/assertions'

const startAutomatingButton = () =>
PageElement.located(By.cssContainingText('.button', 'Start automating'))
.describedAs('"Start automating" button')

await actorCalled('Amanda').attemptsTo(
Navigate.to('https://serenity-js.org'),
Ensure.that(
CssClasses.of(startAutomatingButton()),
include('button--primary')
),
)

Performing interactions

To interact with a web page or a page element, instruct an actor to perform a desired interaction. Note that Serenity/JS also consistently models all assertion, synchronisation logging, debugging, and control flow statements as interactions too.

For example, to navigate to a page, click on a button and perform an assertion, you could say:

import { actorCalled } from '@serenity-js/core'
import { By, Click, Navigate, Page, PageElement } from '@serenity-js/web'
import { Ensure, equals } from '@serenity-js/assertions'

const startAutomatingButton = () =>
PageElement.located(By.cssContainingText('.button', 'Start automating'))
.describedAs('"Start automating" button')

await actorCalled('Amanda').attemptsTo(
Navigate.to('https://serenity-js.org'),
Click.on(startAutomatingButton()),
Ensure.that(
Page.current().url().path,
equals('/handbook/tutorials/your-first-web-scenario/')
)
)

Note that one of Serenity/JS super-powers is the ability to compose interactions into tasks, like startTutorial() below, which makes your code easy to share and reuse:

import { actorCalled, Task } from '@serenity-js/core'
import { Click, Navigate, Page } from '@serenity-js/web'
import { Ensure, equals } from '@serenity-js/assertions'

// ...

const startTutorial = () =>
Task.where(`#actor starts the Serenity/JS tutorial`,
Click.on(startAutomatingButton()),
Ensure.that(
Page.current().url().path,
equals('/handbook/tutorials/your-first-web-scenario/')
)
)

await actorCalled('Amanda').attemptsTo(
Navigate.to('https://serenity-js.org'),
startTutorial(),
)

Below table shows how you can replace Protractor-specific API calls with Serenity/JS web interactions:

ProtractorSerenity/JS
element(locator).click()Click.on(pageElement)
element(locator).clear()Clear.theValueOf(pageElement)
element(locator).sendKeys(text)Enter.theValue(text).into(pageElement)

Waiting and synchronisation

To express a synchronisation statement, use Wait.until or Ensure.eventually.

ProtractorSerenity/JS
element(locator).isPresent()Wait.until(pageElement, isPresent())
element(locator).isEnabled()Wait.until(pageElement, isEnabled())
element(locator).isSelected()Wait.until(pageElement, isSelected())
element(locator).isDisplayed()Wait.until(pageElement, isVisible())

Replacing Protractor with WebdriverIO

In many ways, WebdriverIO is very similar to Protractor:

  • it integrates with the same test runners, such as Cucumber, Mocha, and Jasmine
  • it works well with Serenity/JS
  • it works with local browsers and remote Selenium Grids
  • it supports multi-tab and multi-window test scenarios
  • it supports mobile testing
  • its configuration file follows a similar structure

Once you've migrated your test scenarios to follow the Screenplay Pattern and there are no Protractor API calls left in your code, follow WebdriverIO Protractor migration guide to:

Next, install Serenity/JS WebdriverIO module:

npm install @serenity-js/webdriverio --save-dev

Update the script section in your package.json to use wdio instead of protractor:

package.json
{
"scripts": {
"clean": "rimraf target",
"test": "failsafe clean test:execute test:report",
- "test:execute": "protractor ./protractor.conf.js",
+ "test:execute": "wdio ./wdio.conf.ts",
"test:report": "serenity-bdd run"
}
}

Once your test suite works, remove Protractor and its related Serenity/JS module:

npm uninstall protractor @serenity-js/protractor --save

Learn more:

Integration architecture

To recap, Serenity/JS integrates with Protractor through the @serenity-js/protractor module, which acts as a test runner adapter and:

This modular architecture enables Serenity/JS to enhance both classic Protractor scenarios and those following the Screenplay Pattern with advanced reporting capabilities.

To enable this integration, you need to:

  1. Configure Serenity/JS test runner adapter and reporting services in your protractor.conf.js file
  2. Optionally, use the Serenity/JS Screenplay Pattern APIs in your test scenarios
Serenity/JS + Protractor integration architecture

Next steps

Well done, your Protractor test suite is now integrated with Serenity/JS! 🎉🎉🎉

To take things further, check out:

Remember, new features, tutorials, and demos are coming soon! Follow Serenity/JS on LinkedIn, subscribe to Serenity/JS channel on YouTube and join the Serenity/JS Community Chat to stay up to date!

Don't forget to ⭐️ Serenity/JS on GitHub to help others discover the framework!

Follow Serenity/JS on LinkedIn Watch Serenity/JS on YouTube Join Serenity/JS Community Chat GitHub stars