This article describes how we can use Protractor and Browserstack to create a good environment and development process to quickly get feedback about the responsive design of a web application during development.
Challenge
We want to develop a responsive web application. Therefore it should be able to be used in a user-friendly manner at various resolutions and in different environments.
So we want to make sure that the application is properly displayed and operable in different browsers under Windows, OSX, IOS
When viewing the app on the desktop, we distinguish between a maximized window and a reduced window, which may require a different layout. On mobile devices, we differentiate between portrait and landscape mode.
Approach
During development, we want to get feedback on the presentation of our web application in the different environments as quickly and efficiently as possible.
To do this we want to create an environment in which we can verify the current state of our locally hosted app at any time from the different browsers of different operating systems.
Environment
Own devices
We could now design our environment to be e.g. on a Mac and address the app hosted there from a virtualized or real Windows system. In addition, however, we still need mobile Android and IOS devices in our network to test our app from these as well.
However, this solution would require that we have to invest a lot of money in hardware. And this hardware would always have to carry with us and we may have to take big hurdles in order to operate all devices in our corporate network.
If we work with multiple developers in parallel or remotely, this solution is also unattractive.
Browserstack
Browserstack provides a virtual way of accessing web pages from various browsers, operating systems and devices. This can be used to manually or automatically test web applications.
Key feature for us is that we can also access our local environment from the Browserstack instances.
The license costs are not cheap depending on the needs, but they are still significantly lower than the hardware acquisition and configuration costs. It also allows us to develop efficiently and flexibly.
Development process
Next we want to get feedback on how the application looks in a variety of environments. For example, if we’re initially developing against Windows Chrome, we’d like to know how the app is displayed maximized and small in OSX Safari, IE11, Android Chrome and iPhone Safari.
For this we put the app via a protractor test in the state, whose representation we want to verify. Subsequently, a screenshot is created within the test and stored locally.
These semi-automated tests are run on Browserstack by using a respective protractor configuration. The result is a screenshot per desktop browser of the maximized window and a smaller window of defined size. For mobile devices, screenshots are taken in portrait and landscape mode.
This gives us automated and immediate feedback on these very different environments.
Problems with the presentation can then be analyzed and fixed manually via Browserstack Live. Within the Browserstack instances we are supported by the individual dev tools of the browser, as usual.
When the app is properly displayed in all screenshots, we finally have to verify the remaining environments by hand.
We could also always make screenshots of all variants, but this would extend the capturing and viewing of screenshots accordingly.
If the app is neatly displayed in the most diverse environments, my experience is that there are rarely any problems with the rest of the environment.
Note on the duration of the screenshots:
Depending on the license, the different environments can be tested in parallel in different instances and the tests can also be executed in parallel within the individual environments. Accordingly, the duration of the screenshot depends very much on how many parallel test executions
Configuration
Browserstack
First we create an account on the Browserstack page with a license that allows you to run automated tests.
Logged in, we find in our username and the associated key in our settings.
We do not want to write this information as plain text in our configuration, so we put them in the environment variables of the OS as BS_USERNAME and BS_KEY.
For Browserstack to access our local instance we need the NPM package „browserstack-local“. We install this as described on the GitHub page.
Screenshoter
For capturing and saving screenshots
Protractor configuration
Dependencies
In out protractor configuration we need browserstack-local and the Screenshoter.
const { Screenshoter } = require('./screenshoter');
const browserstack = require('browserstack-local');
Capabilities
What I have referred as the environment is called capability in the context of protractor and Browserstack. Each capability defines an environment and can be created with the support of the Browserstack website.
Some properties of the capabilities are the same across. We define them globally and then inject them into the individual capabilities.
const commonCapabilityProperties = {
'browserstack.user': process.env.BS_USERNAME,
'browserstack.key': process.env.BS_KEY,
'browserstack.selenium_version': '3.13.0',
'browserstack.local': 'true',
'browserstack.video': 'true',
'browserstack.debug': 'false',
'browserstack.timezone': 'UTC',
};
BS_USERNAME and BS_KEY are our browser stacks username and key, which we have previously defined as the environment variable of the operating system. We pass them from the process at runtime.
Now we define our capabilities. This can look like this, for example:
multiCapabilities: [
{
logName: 'OSX_Safari_1920',
...commonCapabilityProperties,
os: 'OS X',
os_version: 'High Sierra',
browserName: 'Safari',
browser_version: '11.1',
resolution: '1920x1080',
'browserstack.safari.driver': '2.48'
},
{
logName: 'Win10_FF_720',
...commonCapabilityProperties,
os: 'Windows',
os_version: '10',
browserName: 'Firefox',
browser_version: '64.0',
resolution: '1920x1080',
customWindowSize: {
width: 600,
height: 800
},
'browserstack.geckodriver': '0.22.0',
},
{
logName: 'GalaxyS9_Chrome_landscape',
...commonCapabilityProperties,
os_version: '8.0',
device: 'Samsung Galaxy S9',
browserName: 'Chrome',
browser_version: '71.0',
real_mobile: 'true',
deviceOrientation: 'landscape',
'browserstack.appium_version': '1.9.1'
},
{
logName: 'IPhoneX_Safari_portrait',
...commonCapabilityProperties ,
device: 'iPhone X',
browserName: 'Safari',
real_mobile: 'true',
'browserstack.appium_version': '1.9.1'
}
}
„CustomWindowSize“ is not a standard property. We use it if we do not want to run the tests in a maximized browser
Settings
We now have to make some smaller settings.
The SeleniumAdresse:
seleniumAddress: 'http://hub-cloud.browserstack.com/wd/hub',
The maximum number of Browserstack instances used:
maxSessions: 2,
Normally Browserstack instances can access the locally hosted instance via localhost. Unfortunately there are problems with this kind of redirection on the IOS devices. This has been resolved by redirecting to the local instance via „bs-local.com“.
Furthermore, there is a limitation in the possible ports that can be used. Information can be found on the Browserstack FAQs.
In our example, the app is hosted locally on port 5000.
baseUrl: 'http://bs-local.com:5000/',
Access to the Screenshoter
In order to be able to access the screenshoter in the tests, we provide this as a protractor parameter via a function.
To do this, we create a global map and define the protractor parameters:
const screenshoters = {};
exports.config = {
//...
params: {
getScreenshoter: () => screenshoters[this.config.params.logName],
logName: ''
},
//...
}
Connection to Browserstack
When starting Protractor we connect to the browser stack and propagate our session. In case of an error, the start should be aborted.
beforeLaunch() {
return new Promise((resolve, reject) => {
console.log('Connecting local');
exports.bs_local = new browserstack.Local();
exports.bs_local.start({ key: commonCapabilityProperties['browserstack.key'] },
function(error) {
if (error) return reject(error);
console.log('Connected to browserstack');
resolve();
});
});
},
Before we finish Protractor, we close the connection properly.
afterLaunch() {
return new Promise(resolve => exports.bs_local.stop(resolve));
}
Before the environment is getting started…
For each individual environment, a screenshoter should be instantiated and the window should be maximized or adjusted.
async onPrepare() {
//...
return browser.getProcessedConfig().then(config => {
exports.config.params.logName = config.capabilities.logName;
screenshoters[config.capabilities.logName] = new
Screenshoter(config.capabilities.logName);
return resizeWindow(config);
});
},
const resizeWindow = async function(config) {
const isDesktop = config.capabilities.os === 'Windows' || config.capabilities.os === 'OS X';
const customWindowSize = config.capabilities.customWindowSize ?
config.capabilities.customWindowSize : null;
if (isDesktop === true) {
if (customWindowSize !== null) {
await browser.driver
.manage()
.window()
.setSize(customWindowSize.width, customWindowSize.height);
console.log('Resized window to ' + customWindowSize.width + 'x' +
customWindowSize.height);
} else {
await browser.driver
.manage()
.window()
.maximize();
console.log('Maximized window');
}
browser.switchTo();
}
};
After all tests of a single environment have been executed…
After all tests have been executed, the captured screenshots a saved locally.
async onComplete() {
return new Promise(resolve => {
const screenshotDir = `${__dirname}/screenshots/`;
exports.config.params.getScreenshoter().saveScreenshots(screenshotDir);
resolve();
});
},
Specification of the semi-automated tests
We have to write a test spec that puts the app in the desired state and finally captures the screenshot.
A spec for making a screenshot might look like this.
describe('SomeResponsiveComponent', () => {
const page = new Page();
it('should display a responsive dialog', async () => {
await page.open();
await page.openResponsiveDialog();
await browser.params.getScreenshoter().takeScreenshot('ResponsiveDialog');
});
});
The screenshot is saved in the project´s subfolder „screenshots/ResponsiveDialog/<Logname der Capability>.png“.
Conclusion
Browser stack can greatly facilitate and save costs during the development for multiple browsers and operating systems.
By creating screenshots, working hours can be saved and feedback can be generated immediately without manually and monotonously changing the app again and again in various browsers to a defined state.