How to reuse code in Protractor / AngularJS Testing - javascript

We have several Protractor end to end tests for our AngularJS app in several JS files and they work great. But, there is a lot of duplicated code throughout the tests and we would like to DRY that up.
For instance, every time we log in, we have to click on the text elements, type in the username and password, and then click enter. And right now every single JS file has its own copy of the login function which is called before every test.
It would be nice to refactor those out into modules that we can then import. I have been searching for hours, but not found a good solution.
How should we do this?

You can create nodejs modules and include them in protractor configuration
login-helpers.js
exports.loginToPage = function () {
//nodejs code to login
};
protractor.conf.js
exports.config = {
//...
onPrepare: function () {
protractor.loginHelpers = require('./helpers/login-helpers.js');
}
//...
};
page.spec.js
it('should do smth', () => {
protractor.loginHelpers.loginToPage()
//expect(...).toBe(...);
});

Our team uses Orchid-js with Jasmine and Protractor, it's designed to do exactly this.
Your test
Describe('Login user',require('../login.js'))("username","password");
login.js
module.exports = function(username,password){
describe('login the user',function(){
it('should login the user',function(){
element(by.id('usernameField')).sendKeys(username);
element(by.id('passwordField')).sendKeys(password);
element(by.id('loginButton')).click();
});
});
}

Related

Running a test with Mocha also launches the main program

I'm trying to use Mocha to test a CLI app. The tests are running fine but, when I launch the testing procedure, it also launches the main app:
$ npm run test
> standardize-js#0.2.2 test C:\Users\Gaspard\Documents\Code\standardize-js
> mocha "./source/**/*.spec.js"
? Choose your project language or framework (Use arrow keys) //<-- THIS IS THE PROGRAM
> Javascript
Typescript
AngularJS
Main function //<-- THIS IS THE TEST
ask if the configuration is valid
Configuration is not valid, terminating program.
√ should return false if the configuration is not accepted
1 passing (29ms)
I'm kind of new to the testing world and I'm really struggling to understand what I'm doing wrong.
Here is the NPM script used to launch mocha :
"test": "mocha \"./source/**/*.spec.js\""
Here is my testing method:
/* eslint-disable func-names */
const { expect } = require("chai");
const main = require("./index").test;
describe("Main function", function() {
describe("ask if the configuration is valid", function() {
it("should return false if the configuration is not accepted", function() {
const fakeAnswer = false;
expect(main.validateConfiguration(fakeAnswer)).to.equal(false);
});
});
});
And here is my index.js file:
function validateConfiguration(answer) {
if (answer === false) {
console.log(chalk.red("Configuration is not valid, terminating program."));
return false;
}
return true;
}
const run = async () => {
//MAIN FUNCTION
};
run();
// Export functions and variables to be able to test
exports.test = {
validateConfiguration
};
It's not a problem with mocha. It is simply now node.js modules work.
When you do this:
const main = require("./index").test;
Node.js will execute index.js and then check the value of module.exports. If the module (index.js) sets or modifies module.exports then node will export it for use by require(). But note, in order for node to know that the module has exported anything it must execute the javascript file.
Node.js does not have any ability to parse and analyze javascript syntax (that's V8's job). Unlike other languages such as C or Java, modules in node.js are not implemented at the syntax level. Therefore the javascript language does not need to be modified (eg. ES6 modules) for node.js to support modules. Modules are simply implemented as a design pattern.
In your index.js file you call run:
run();
When require() loads index.js it will therefore also cause run() to be called.
Test libraries, not main
The solution to this is to implement your own logic as modules and test that, not test index.js:
mylib.js:
function validateConfiguration(answer) {
if (answer === false) {
console.log(chalk.red("Configuration is not valid, terminating program."));
return false;
}
return true;
}
// Export functions and variables to be able to test
exports.test = { validateConfiguration };
index.js:
const validateConfiguration = require("./mylib").test;
const run = async () => {
//MAIN FUNCTION
};
run();
You can now use your test script as written.
How can you not test code??
The strategy to keep index.js bug free without testing is to remove all logic from it except for the minimum amount of code to wire all your other code up together to run the app. The code should be as simple as "Hello World". That way, the code in main is so small and so simple that you can test it for bugs using your eyeballs.
Any code in index.js that causes a bug should be refactored into its own library so that it can be tested separately. There are a small handful of corner cases, such as loading environment variables or opening port 80 where you can't really separate into a library because they literally are wiring logic. For such cases you just have to be really careful.
It's calling run because you are telling it to right after defining the method.

Running javascript e2e tests on a local appium server

I'm wanting to run e2e tests written in javascript with mocha on an Appium server instance running a local android emulator. The app on test is an apk originally written in react-native.
On Windows I have the server up and running with an Android Studio emulator through using the Appium desktop app. The server all looks good and has the apk of the native app I want to test working fine. I also have a basic describe/assert test written in mocha that I want to apply to the app.
My question is what do I need to include (presumably in the test file) to make the tests actually test the emulator application? I'm finding the documentation pretty confusing and the sample code seems pretty specific to a different use case.
Many thanks for your help!
There are at least 2 good js client libraries to use for Appium based project: webdriverio and wd. Personally, I'm using the second one so I can advice you how write tests with it and mocha:
my test file looks like this:
'use strict'
require(path.resolve('hooks', 'hooks'))
describe('Suite name', function () {
before('Start new auction', async function () {
//do before all the tests in this file, e.g. generate test data
})
after('Cancel auction', async function () {
//do after all the tests in this file, e.g. remove test data
})
it('test1', async () => {
// test steps and checks are here
})
it('test2', async () => {
// test steps and checks are here
})
it('test3', async () => {
// test steps and checks are here
})
})
where hooks.js contains global before/after for all the tests:
const hooks = {}
before(async () => {
// before all the tests, e.g. start Appium session
})
after(async () => {
// after all the tests, e.g. close session
})
beforeEach(async () => {
// before each test, e.g. restart app
})
afterEach(async function () {
// e.g. take screenshot if test failed
})
module.exports = hooks
I'm not saying its the best practice of designing tests, but its one of multiple ways.
Cool so I managed to get it working to a degree. I was checking through the Appium console logs as I was trying to run stuff and noticed that the session id was missing from my requests. All that was needed was to attach the driver using the session id. My code looks a bit like this:
"use strict";
var wd = require("wd")
var assert = require("assert")
var serverConfig = {
host: "localhost",
port: 4723,
}
var driver = wd.remote(serverConfig)
driver.attach("0864a299-dd7a-4b2d-b3a0-e66226817761", function() {
it("should be true", function() {
const action = new wd.TouchAction()
action
.press({x: 210, y: 130})
.wait(3000)
.release()
driver.performTouchAction(action)
assert.equal(true, true)
})
})
The equals true assert is just there as a placeholder sanity check. The only problem with this currently is that I'm copy-pasting the alpha-numeric session id inside the attach method each time I restart the Appium server so I need to find a way to automate that.

Jasmine testing with external javascript

Well I am trying to fire up my first Jasmine test with Karma. I want to test my main javascript file declared functions, but I'm stuck on it... In my specRunner.html I included every files which needed, in karma.conf.js I added both my test specification and main js files also files: ['src/scripts/main.js','src/tests/mySpec.js'].
In my main.js there is the function I want to use
function testFunctionOne(a, b) {
return a * b;
}
So in mySpec.js file there is the test:
describe('testFunctionOne function', function () {
it('should return a * b', function () {
expect(testFunctionOne(2,4).toEqual(8));
});
});
My question is what am I forgot? What am I making wrong? Keep in mind, this is my first attempt for Jasmine testing.
expect(testFunctionOne(2,4)).toEqual(8); not expect(testFunctionOne(2,4).toEqual(8)); I think

protractor + Angular + requireJs

I want to test my app with protracor but test fails with this errors :
Failed: Error while waiting for Protractor to sync with the page: "root element (html) has no injector. this may mean it is not inside ng-app."
it seems that angular doesn't load completely, and browser.waitForAngular(); not working.
how can I setup protractor to continue test after RequireJs load dependencies ?
also adding this :
onPrepare:function(){
browser.manage().timeouts().pageLoadTimeout(40000);
browser.manage().timeouts().implicitlyWait(25000);
}
to ocnfig file(As mentioned here) cause this error:
Failed: Error while waiting for Protractor to sync with the page: "angular could not be found on the window"
You will need a manual way to know that Angular has bootstrapped from within your specs. Here's the basic run-down of how I have this set up with Angular, RequireJS and Protractor. This works for me with jasmine2 and old jasmine.
We want to add a class of ng-app to the element that Angular bootstraps. For example:
index.html
<html lang="en" class="ng-app">
But rather than putting this in the HTML file, we want to add the class using the same RequireJS module that is manually bootstrapping your Angular App. Eg:
ng-bootstrap.js
require(['angular'], function (angular, otherdeps) {
// Start the Angular App
angular.bootstrap(document, ['MyApp']);
// Set the ng-app class for Angular Protractor tests
var root = document.documentElement;
angular.element(root).addClass('ng-app');
});
Check that your page adds this class after bootstrapping. then set up your protractor.conf exports to run the onprepare test. This spec is executed each time Protractor is launched and we will use it to check for the class you added in the ng-bootstrap.js module.
protractor-conf.js
exports.config = {
// Every time protractor is launched:
onPrepare: 'onprepare.e2e.js',
};
In your onprepare.e2e.js spec file, you can trigger the load of the home page. Then ask Protractor to wait until the class .ng-app is found on the root element, Ie: Angular has bootstrapped and is ready to run Protractor tests.
onprepare.e2e.js
describe("On prepare", function () {
// Replace with your own URL
var baseUrl = 'http://127.0.0.1:8001/#/';
// Begin fetching the page
browser.driver.get(baseUrl);
// Wait until `.ng-app` is found on the root elem
browser.driver.wait(function () {
return browser.driver.getCurrentUrl().then(function (url) {
return browser.driver.isElementPresent(by.className('ng-app')).then(function () {
return true;
});
});
});
});
Keep in mind that if you a running lots of spec files together, your page could is being re-loaded when a new test starts. Your page also may be being reloaded if your Angular router is using a reload: true param.
This means that the app has to bootstrap again; And you will need to wait for the bootstrap class again before you can use Protractor.
Add a helper for this and include it in your protractor-conf.js.
helpers.js
module.exports = {
get: function (url) {
browser.driver.get(url);
browser.driver.wait(function () {
return browser.driver.getCurrentUrl().then(function (url) {
return browser.driver.isElementPresent(by.className('ng-app')).then(function () {
return true;
});
});
});
},
};
protractor-conf.js
helpers = require('helpers.js');
exports.config = {
onPrepare: 'onprepare.e2e.js',
specs: [
'my-spec.js'
]
};
Now your helper is globally visible to your specs and you can use your new helper.get(url) method instead of browser.driver.get(url). Example:
my-spec.js
describe("Users", function() {
it('should login', function () {
// Wait for Angular and RequireJS to finish
helpers.get('http://127.0.0.1:8001/#/login');
// ...tests here...
});
});
I had some similar problem, maybe it is because the way our app is loaded, but you can try having some custom wait:
browser.driver.wait(function() {
return browser.driver.isElementPresent(by.css('.ng-scope'));
}, 50000);// ^^or some other locator for your angular
});
inside your beforeEach() for example.
Edit:
Also for someone it helps to change browser windows size:
browser.manage().window().setSize(1280, 1024);
in onPrepare()
I can run test by adding browser.driver.sleep(3000) to beforeEach.
but this isn't the right solution.

Setting up Screenshot Reporter for Protractor

Since I'm a newbie with automated tests and protractor, I'm having some trouble setting this up in my tests.
According to the guide, every time that I create a new instance of screenshot reporter, I have to pass a directory path. Right, this means that every time I create a new instance in my spec file?
Also, there are functions to take screenshots of my skipped and my failed tests. Where i supposed to use takeScreenShotsForSkippedSpecs and takeScreenShotsOnlyForFailedSpecs? In my config file?
This is my onPrepare:
onPrepare: function () {
browser.driver.manage().window().maximize();
global.dvr = browser.driver;
global.isAngularSite = function (flag) {
browser.ignoreSynchronization = !flag;
}
jasmine.getEnv().addReporter(new ScreenShotReporter({
baseDirectory: '/tmp/screenshots',
takeScreenShotsForSkippedSpecs: true,
takeScreenShotsOnlyForFailedSpecs: true
}));
Note: If you are using jasmine2, use protractor-jasmine2-screenshot-reporter.
For jasmine1:
I've been using successfully using protractor-html-screenshot-reporterpackage. It is based on protractor-screenshot-reporter, but also provides a nice HTML report.
Here is what I have in the protractor config:
var HtmlReporter = require("protractor-html-screenshot-reporter");
exports.config = {
...
onPrepare: function () {
// screenshot reporter
jasmine.getEnv().addReporter(new HtmlReporter({
baseDirectory: "test-results/screenshots"
}));
},
...
}
After running tests, you would get an HTML file containing (example):
You can click "view" to see the test-case specific screenshot in the browser.
The readme in the library is pretty self explanatory. After installing the library, add it onto protractor's onPrepare in your protractor config file.
i.e.
protractorConf.js:
var ScreenShotReporter = require('protractor-screenshot-reporter');
exports.config = {
// your config here ...
onPrepare: function() {
// Add a screenshot reporter and store screenshots to `/tmp/screnshots`:
jasmine.getEnv().addReporter(new ScreenShotReporter({
baseDirectory: '/tmp/screenshots',
takeScreenShotsForSkippedSpecs: true
}));
}
}
then protractor protractorConf.js to run protractor.
Just recently I published a brand new plugin called protractor-screenshoter-plugin that captures for each browser instance a screenshot and console logs. The snapshot is made optionally for each expect or spec. It comes with a beautiful angular and bootstrap based analytics tool to visually check and fix tests results.
Check it out at https://github.com/azachar/protractor-screenshoter-plugin.
Also, I created a list of all available alternatives, so if you find something else, please do not hesitate to add it there.

Categories