Capture server JSON response in end-to-end test - angularjs

I'm writing an end-to-end test that simulates user authentication with Protractor. A user feels in her credentials and clicks a Submit button. As a result, the server returns an access token in a JSON response that can be used for other REST API calls. I'd like to save this token to a file.
There's a similar question on capturing a response of a GET request here, but I'm not sure it's a good idea to send another request after I click the button.
How can I capture the response after a button click?

Here is my idea about how to catch HTTP responses. Protractor provides a method browser.addMockModule() (docs) - it is used to add custom Angular modules to a page, which are usually used to mock outcoming requests and provide custom response. But we do not need to mock requests, it would be enough to just listen for whatever comes from a server. It can be achieved with the help of Angular HTTP interceptors. Interceptors are used to catch a request or a response and modify it for whatever needs before in gets to it's endpoint. We can use them to collect information about what is coming from the server, store it, and then let response go forward without changes. Since this custom module and spec tests will run on the same page, information about responses can be stored in some global property. Then, when button is clicked, it would be possible to inject custom script to a page to retrieve required responses via browser.executeScript() (docs). Here is the source:
it('should intercept requests', function () {
// Inject custom Angular module on a page
// Script should be injected before you "browser.get()" the page
browser.addMockModule('e2eHttp', function () {
// Note: This function scope is not a Protractor environment
angular
.module('e2eHttp', [])
.config(function ($httpProvider) {
// add custom interceptor to all requests
$httpProvider.interceptors.push('e2eHttpInterceptor');
})
.factory('e2eHttpInterceptor', function () {
return {
response: function (response) {
// name of the global property to store responses
var global = 'e2eHttpResponses';
// responses will be grouped by url
// but you can use data from "response.config" to adapt it
// it has a lot of info about response headers, method, etc
var url = response.config.url;
window[global] = window[global] || {};
window[global][url] = window[global][url] || [];
window[global][url].push(response); // store response
// proceed without changing response
return response;
}
};
});
});
// Load the page
browser.get('#/auth/login');
$('#submit').click();
// If we are sure that response has come, then extract it
browser.executeScript(function () {
// Note: This function scope is not a Protractor environment
var global = 'e2eHttpResponses';
var uri = 'api/auth/login.json';
// extract array of stored responses for required uri
var responses = (window[global] && window[global][uri]) || [];
// return responses to spec
return responses;
}).then(function (responses) {
// and finally, we are now able to get all information we need
// about response, and in your case, save it to a file
console.log(responses);
var data = responses[0].data; // first response body as a string
});
// remove injected module not to break another specs
browser.removeMockModule('e2eHttp');
});
You can move setup and injection calls to some utility modules, so test specs would be clean.

Related

Unable to get a JSON response from my express router using node-Fetch module

So I'm making a post request to my Express search router where I'm using the node-fetch module to call a remote api:
var fetch = require('node-fetch');
router.route('/search')
//Performs Job Search
.post(function(req, res){
var area = req.body.area;
fetch('https://api.indeed.com/ads/apisearch?publisher=*********&l=' + area)
.then(function(res) {
return res.json();
}).then(function(json) {
console.log(json);
});
})
I'm using angular 1 on the client side to call the router and parse the returned json:
$scope.search = function(){
$http.post('/api/search', $scope.newSearch).success(function(data){
if(data.state == 'success'){
//perform JSON parsing here
}
else{
$scope.error_message = data.message;
}
});
}
I'm just starting out with the MEAN stack and only have a vague idea of how promises work. So my issue is that my angular search function is not getting the JSON string I want to return from my remote API call. But rather the data parameter is getting set to my page html. Eventually the breakpoints I've set in the .then() clauses are hit and my json is returned. So my question is how can I use Anguular to get the JSON values when they are finally returned????
Can you try something like this?
router.route('/search')
//Performs Job Search
.post(function(req, res){
var area = req.body.area;
fetch('https://api.indeed.com/ads/apisearch?publisher=*********&l=' + area)
.then(function(result) {
res.json(result);
//or possibly res.send(result), depending on what indeed responds with
})
})
Turns out I had forgotten that I had middleware in place where if the user was not authenticated when performing the search they were redirected to the login page. So I was getting a bunch of html returned for this login page, rather then my json. What still confuses me is why my breakpoints in search function were ever hit if I was being redirected before ever reaching this function.

How to post some data and get results using MEAN stack and ngResource

I am working on a application and I am using MEAN stack as technology. In AngularJS, I am using ngResource to CRUD operations. Can any one suggest how to send username and password to server and get response back to check if the credentials are valid. I need help in ngResource and mongoose code. Thanks.
Check out the mean.js boilerplate:
https://github.com/meanjs/mean
You'll see how they do it pretty quickly:
moduleName.client.controller.js will make an http call, using the injected http. Here is an example of the call being made from /modules/users/client/controllers/authentication.client.controller.js (with some edits to the code to make it easier to see what you're looking for):
AuthenticationController.$inject = ['$scope', '$state', '$http', 'Authentication'];
function AuthenticationController($scope, $state, $http, Authentication, ) {
...
vm.authentication = Authentication;
$http.post('/api/auth/signup', vm.credentials).success(function (response) {
// If successful we assign the response to the global user model
vm.authentication.user = response;
}).error(function (response) {
vm.error = response.message;
});
}
Now, this call is posted to '/api/auth/signup'. The file that handles this route is located in /modules/users/server/routes/auth.server.routes.js:
modules.exports = function(app) {
var users = require('../controllers/users.server.controller');
...
app.route('/api/auth/signup').post(users.signup);
}
As you can see, the route (the url) matches the one you called from the client controller. As that $http call from the controller was a $http.post(), the route entry must match. You can see that it does above.
The parameter users.signup passed above refers to a function in yet another file: /modules/users/server/controllers/users/users.authentication.server.controller.js. This is your main controller for the authentication part of the users module. Now, within this file we can see the signup function is exported:
/* note: there are global variables here, see below */
exports.signup = function (req, res) {
// For security measurement we remove the roles from the req.body object
delete req.body.roles;
// Init user and add missing fields
var user = new User(req.body);
user.provider = 'local';
user.displayName = user.firstName + ' ' + user.lastName;
// Then save the user
user.save(function (err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
// Remove sensitive data before login
user.password = undefined;
user.salt = undefined;
req.login(user, function (err) {
if (err) {
res.status(400).send(err);
} else {
res.json(user);
}
});
}
});
};
Now, a lot is going on here, but we can break it down.
The req variable is the post request as passed by $http. The res variable is the response the client expects to receive back.
Notice how vm.credentials was passed in $http.post('/api/auth/signup/', vm.credentials)? This is bound to req.body and may be accessed by your server controller from that.
So in this example, the req.body is the required data to create a new user on the server. This is done using mongoose, which has a schema called User. Access this by declaring globals at the top of your controller:
var mongoose = require('mongoose'),
User = mongoose.model('User');
You can see that a new user is instantiated above. It is saved via the mongoose call .save().
Lastly, your server functions should response to the client's request using the res variable passed to the function. See how once the user is created successfully, the function calls
res.jsonp(user);
This is success() to the client, which accepts the response and binds it to a local variable vm.authentication.user
Hope this helps!

Complete E2E test of form in AngularJs 1.5.5

my goal is to create an automatic E2E test that fills a form and clicks on the "send" button. After that I need to wait the mock api response before set the test as Passed or not.
First question: Is this possible in the same E2E test?
I'm using AngularJs 1.5.5, ngMocks and Protractor\Jasmine.
Right now I'm able to fill the form using
element(by.model('xxxx')).sendKeys('xxxx');
and click on the "Send" button using
element(by.css('input[type="button"]')).click();
I created also an api mock for this test in a new js file that I included in page. The code is the following:
var testAppDEV = angular.module('testAppDEV', ['testApp', 'ngMockE2E']);
testAppDEV.run(function ($httpBackend) {
// Api to save the form
$httpBackend.whenPOST('/api/save').respond(function (method, url, data) {
var formData = angular.fromJson(data);
if (Object.keys(formData).length == 3) {
// url, data, headers
return [200, { errorCode: null }, {}];
}
else
{
return [400, { errorCode: '1' }, {}];
}
});
});
Now I need to add the code to my Jasmine test that wait for the mock api response and according to response, set the test as Passed or Failed.
Can you help me?
This is a first step because the next will be to integrate this test with Jenkins, like I did with Unit tests..
Every hint is welcome :)

AngularJs watch for change in variable inside a service

I have a service named loginManager which stores objects called is_logged_in & api_token along with few others. My various controllers make ajax calls using $http using the api_token.
If the api_token is reset/expired on server, response is sent as auth_error, at this point I set is_logged_in = false
What i want to achieve is, whenever is_logged_in is changed, the service redirects to /login using $location.path('/login'), i.e. to say, I want to watch the object inside the service, and invoke callback on change from service itself.
I just want the service to take care of login and corresponding routing, without any controller worrying about weather user is logged in or not.
I believe Pankaj Pakar's answer could work but you should use angular's interceptors for that. They intercept all messages. You could add hook for response or responseError and when you recieve auth_error you do any action you like. For example $location.path('/login'), display error to user, etc.
If you want to separate logic you could inject your service with all code inside and just call some method on it.
I'd suggest you to put that watcher in run phase on the angular application which will be there at a single place, by which you could check the value is_logged_in flag of service & if user is not login then redirect him/her to login page directily.
Code
app.run(function($rootScope, loginManager, $location){
$rootScope.$watch(function(){
return loginManager.is_logged_in;
}, function(newValue){
if(angular.isDefine(newValue) && !newValue)
$location.path('/login');
//$state.go('login'); //if you are using ui.router
})
})
Edit
Really curious part of your question is, from where you are changing is_logged_in flag of your service as #JBNizet asked? If any code is there is JavaScript then you should directly redirect to login page from there.
I feel the need to answer something more, Mior is quite right, but his answer needs more meat.
Here I show you how I managed to handle ALL server XHR requests with response 401 unauthorized.
First of all you need a service:
'use strict';
angular.module('theModule')
.factory('interceptorService', ['$q', '$location', function ($q, $location) {
return {
response: function (response) {
return response || $q.when(response);
},
responseError: function (rejection) {
var returnTo = $location.path().replace(/^\/|\/$/g, '');
if (returnTo === 'login') {
return;
}
if (rejection.status === 401) {
console.log('Unauthorized');
$location.path('/login').search('returnTo', returnTo);
}
return $q.reject(rejection);
}
};
}]);
This will be used to intercept all XHR calls and to change the location every time a 401 error is found.
I've also added an improvement that is the "returnTo" parameter, you will be able to use it after login to return to the previous page.
To bind it to each request you have to call the config method, this is my main javascript.
'use strict';
/**
* #author Gianmarco Laggia
*
* Main module of the application an configurations.
*/
angular
.module('theModule', [])
.config(['$httpProvider', function ($httpProvider) {
//Http Interceptor to check auth failures for xhr requests
$httpProvider.interceptors.push('interceptorService');
}]);
This is pretty much what you need to intercept every request, working on the rejection.status you can also intercept events such as server down (status is -1), internal server error (500+), success status (in the response part, status 200+) etc.

AngularJS : Service for data between controllers

I have a page with a main controller and a nested controller for showing details about a product. I want to use a a service in angular to call the server, retrieve a data object and hold that data object. The main controller will call the service to fetch the data and the details controller needs to know it was updated and then access the data. My service looks like this:
.service("productService", function ($http, $q) {
var product = {};
//interface that is returned
return ({
fetchProduct: fetchProduct,
clearProduct: clearProduct,
product: product
});
function fetchProduct(ID) {
var request = $http({
method: "get",
url: "/online/productdata.ashx?itemID=" + ID,
params: {
action: "get"
}
});
return (request.then(handleSuccess, handleError));
};
function clearProduct() {
product = {};
};
// Transform the error response, unwrapping the application dta from
// the API response payload.
function handleError(response) {
// The API response from the server should be returned in a
// nomralized format. However, if the request was not handled by the
// server (or what not handles properly - ex. server error), then we
// may have to normalize it on our end, as best we can.
if (
!angular.isObject(response.data) ||
!response.data.message
) {
return ($q.reject("An unknown error occurred."));
}
// Otherwise, use expected error message.
return ($q.reject(response.data.message));
};
// I attempt to transform the successful response and unwrap the application data
// from the API response payload.
function handleSuccess(response) {
product = response.data;
console.log("Found Data: " + angular.toJson(response.data))
return (response.data);
};
})
In my main controller I set a scope object to the service like this:
$scope.SelectedProduct = productService;
When the user clicks the button to show the product it is called via the $scope handle:
$scope.SelectedProduct.fetchProduct(ID);
The details controller has the same assignment for the $scope.SelectedProduct. I am new to using services but what I understood is that angular would bind to the service object and changes to the property product would trigger binding to any updates. That is not happening - in fact I do see the data after the fetch operation. In the service I have a console.log on the returned data and it is showing the correct data. However the product property is not getting updated. Can someone tell me what I am doing wrong please? Neither controller has access to the data after it is fetched. I understand that I am getting back a promise but the data is never there even after a timeout check.
Try it with a factory instead of a service.
AngularJS: Factory vs Service vs Provider

Resources