How to get tests to record fixtures with AngularJs like vcr in Rails? - angularjs

I've been looking everywhere, I tried node-replay but with protractor but it won't work with selenium.
I've also tried vcr.js and sepia.
How do I go about setting up my tests for that they make the initial calls but store them as cassettes like vcr.
Cheers.

I've been setting up sepia to be used with protractor.
It works now, here is what I did:
I assume you've already set up grunt-connect to run your protractor tests.
Then you'll need to wait for the event listening event from the connect configuration:
grunt.event.once('connect.test.listening', function)
And that's where you will configure sepia.
grunt.event.once('connect.test.listening', function(host, port) {
/**
* Configure sepia here
*/
var sepia = require('sepia').withSepiaServer();
// Use your custom configuration
sepia.configure({
verbose: true,
debug: true,
includeHeaderNames: false,
includeCookieNames: false
});
// I have some path/body content to filter configured in the vrc configuration
var bodyFilters = grunt.config('vcr.filters.body') || [];
var pathFilters = grunt.config('vcr.filters.path') || [];
var regexPath = function(string) {
var escapedString = string.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
return new RegExp(escapedString);
};
// Filter path
_.map(pathFilters, function(filter) {
sepia.filter({
url: regexPath(filter.path),
urlFilter: function(url) {
return url.replace(filter.pattern, filter.replacement);
}
});
});
// Filter body content
_.map(bodyFilters, function(filter) {
sepia.filter({
url: regexPath(filter.path),
bodyFilter: function(body) {
return body.replace(filter.pattern, filter.replacement);
}
});
});
});
});

Related

Jasmine - Restart browser between tests (it blocks)

In our Protractor+Jasmine framework trying to restart browser between tests for different login user. As we have non-angular login page and after login its navigate to angular page. Following is my main test spec file
describe('User Permissions', function() {
var docsLoginPage = require('../pages/CLM_Page.js');
var projectsPage = require('../pages/Projects_Page.js');
var contentPage = require('../pages/Content_Page.js');
beforeEach(function() {
browser.ignoreSynchronization = true;
browser.get('http://be-docs-dev.xyz.local/');
});
it('Verify permissions for Non Admin and Read only permission group user', function() {
docsLoginPage.loginToDocs("sipqa4#xyz.com","Yahoo#123");
expect(browser.getTitle()).toEqual('abc');
browser.ignoreSynchronization = false;
expect(projectsPage.checkElementExistsById('headers_nav_projects')).toBe(false);
expect(contentPage.checkElementExistsById("content_browse_options_folder_settings")).toBe(false);
expect(contentPage.checkElementExistsById('content_browse_options_new_menu')).toBe(false);
});
it('Verify permissions for Project collaborator Non Admin and Read only permission group user', function() {
/*browser.restart().then(function(){
console.log("-----------Restarted the browser---------------");
});*/
docsLoginPage.loginToDocs("sipqa2#xyz.com","Yahoo#123");
expect(browser.getTitle()).toEqual('abc');
browser.ignoreSynchronization = false;
expect(projectsPage.checkElementExistsById('headers_nav_content')).toBe(false);
expect(contentPage.checkElementExistsById("projects_browse_options_folder_settings")).toBe(false);
expect(contentPage.checkElementExistsById('projects_browse_options_new_menu')).toBe(false);
});
});
And CLM_Page.js
var CLM_page = function() {
this.userName = element(By.id('userName'));
this.password = element(By.id('password'));
this.signIn = element(By.className('btn btn-primary'));
this.loginToDocs = function(userName, password) {
browser.driver.manage().window().maximize();
browser.driver.manage().timeouts().implicitlyWait(10000);
this.userName.sendKeys(userName);
this.signIn.click();
this.password.sendKeys(password);
return this.signIn.click();
};
};
module.exports = new CLM_page();
In conf file set to
restartBrowserBetweenTests: true
First it block is executing correctly. However while executing second it block browser restarting and closing immediately. Showing following error
Failures:
1) User Permissions Verify permissions for Project collaborator Non Admin and Read only permission group user
Message:
Failed: This driver instance does not have a valid session ID (did you call WebDriver.quit()?) and may no longer be used.
Stack:
NoSuchSessionError: This driver instance does not have a valid session ID (did you call WebDriver.quit()?) and may no longer be used.
The problem here is the webdriver session is closed after starting the test.
There is no problem with restartBrowserBetweenTests: true, Since your browser is restarted after the first test.
Since you are moving from non angular to angular application. Follow as below
browser.driver.get('http://localhost:8000/login.html');
browser.driver.findElement(by.id('username')).sendKeys('Jane');
browser.driver.findElement(by.id('password')).sendKeys('1234');
browser.driver.findElement(by.id('clickme')).click();
You are using browser.ignoreSynchronization = true; which is deprectaed. Try usingwaitForAngularEnabled(true).
make the BeforEach() as
beforeEach(function() {
browser.waitForAngularEnabled(false); // to tell protractor that this is a non- anularg page
browser.get('http://be-docs-dev.xyz.local/');
});
After moving to angular page make it as browser.waitForAngularEnabled(true);.
Hope the above answer helps you...
Based on your test case design, you can achieve the same by setting restartBrowserBetweenTests: false in conf.js file
And also clear the browser cookies and session from the browser after each it block run. Please modify your existing test script by adding afterEach
add the below snippet with existing code
afterEach(function () {
browser.manage().deleteAllCookies();
browser.executeScript('window.sessionStorage.clear();window.localStorage.clear();');
});
beforeEach function should be like
beforeEach(function() {
browser.waitForAngularEnabled(false);
browser.get('http://be-docs-dev.xyz.local/');
});
In conf.js.
Note: Default value is false. So it is not mandatory to set it as false
exports.config = {
...
restartBrowserBetweenTests: false,
...
}
If you are setting restartBrowserBetweenTests: true, This will cause your tests to slow down drastically. Please refer this

NgUpgrade: Unable to use templateUrl when upgrading Angular1 components

I want to upgrade a ng1 component to be used inside an ng2 component.
If I use just a template string the ng1 component, to be upgraded, it works. However, if I switch to using a templateUrl instead, the app crashes and give me this error:
angular.js:13920 Error: loading directive templates asynchronously is not supported
at RemoteUrlComponent.UpgradeComponent.compileTemplate (upgrade-static.umd.js:720)
at RemoteUrlComponent.UpgradeComponent (upgrade-static.umd.js:521)
at new RemoteUrlComponent (remote-url.component.ts:11)
at new Wrapper_RemoteUrlComponent (wrapper.ngfactory.js:7)
at View_AppComponent1.createInternal (component.ngfactory.js:73)
at View_AppComponent1.AppView.create (core.umd.js:12262)
at TemplateRef_.createEmbeddedView (core.umd.js:9320)
at ViewContainerRef_.createEmbeddedView (core.umd.js:9552)
at eval (common.umd.js:1670)
at DefaultIterableDiffer.forEachOperation (core.umd.js:4653)
Here is a plunk demonstrating my issue:
https://plnkr.co/edit/2fXvfc?p=info
I've followed the Angular 1 -> 2 upgrade guide and it seems that this code should work. I'm not quite sure why its not working.
After trying require with requireJS and the text plugin which did not work for me, I managed to make it work using 'ng-include' as follow:
angular.module('appName').component('nameComponent', {
template: `<ng-include src="'path_to_file/file-name.html'"></ng-include>`,
I hope this helps!
I found a quite cheap solution for the issue.
Just use template: require('./remote-url.component.html') instead of templateUrl: './remote-url.component.html' and it should work just fine!
This is really frustating because the Angular upgrade documentation specifically says it's ok to use templateUrl. Never mentions this async issue. I've found a way around it by using the $templateCache. I didn't want to change my angular 1 directive because it is used my angular 1 apps and will also be used by angular 4 apps. So I had to find a way to modify it on the fly. I used $delegate, $provider, and $templateCache. My code is below. I also use this to remove the replace attribute since it is deprecated.
function upgradeDirective(moduleName, invokedName) {
/** get the invoked directive */
angular.module(moduleName).config(config);
config.$inject = ['$provide'];
decorator.$inject = ['$delegate', '$templateCache'];
function config($provide) {
$provide.decorator(invokedName + 'Directive', decorator);
}
function decorator($delegate, $templateCache) {
/** get the directive reference */
var directive = $delegate[0];
/** remove deprecated attributes */
if (directive.hasOwnProperty('replace')){
delete directive.replace;
}
/** check for templateUrl and get template from cache */
if (directive.hasOwnProperty('templateUrl')){
/** get the template key */
var key = directive.templateUrl.substring(directive.templateUrl.indexOf('app/'));
/** remove templateUrl */
delete directive.templateUrl;
/** add template and get from cache */
directive.template = $templateCache.get(key);
}
/** return the delegate */
return $delegate;
}
}
upgradeDirective('moduleName', 'moduleDirectiveName');
Most of the answers given here involve pre-loading the template in some way so as to make it available synchronously to the directive.
If you want to avoid doing this - e.g. if you have a large AngularJS application that contains many templates, and you don't want to download them all up front - you can simply wrap your directive in a synchronously loaded version instead.
E.g., if you have a directive called myDirective, which has an asynchronously loaded templateUrl which you don't want to download up front, you can do this instead:
angular
.module('my-module')
.directive('myDirectiveWrapper', function() {
return {
restrict: 'E',
template: "<my-directive></my-directive>",
}
});
Then your Upgraded Angular directive just needs to supply 'myDirectiveWrapper' instead of 'myDirective' in it's super() call to the extended UpgradeComponent.
A pretty low-tech solution to this issue is to load your templates up in your index.html, and assign them IDs that match the templateUrls the directives are looking for, ie:
<script type="text/ng-template" id="some/file/path.html">
<div>
<p>Here's my template!</p>
</div>
</script>
Angular then automatically puts the template into the $templateCache, which is where UpgradeComponent's compileTemplate is looking for the template to begin with, so without changing the templateUrl in your directive, things will work because the id matches the templateUrl.
If you check the source code of UpgradeComponent (see below), you can see commented out code that deals with fetching the url, so it must be in the works, but for the time being this could be a viable solution and even a scriptable one.
private compileTemplate(directive: angular.IDirective): angular.ILinkFn {
if (this.directive.template !== undefined) {
return this.compileHtml(getOrCall(this.directive.template));
} else if (this.directive.templateUrl) {
const url = getOrCall(this.directive.templateUrl);
const html = this.$templateCache.get(url) as string;
if (html !== undefined) {
return this.compileHtml(html);
} else {
throw new Error('loading directive templates asynchronously is not supported');
// return new Promise((resolve, reject) => {
// this.$httpBackend('GET', url, null, (status: number, response: string) => {
// if (status == 200) {
// resolve(this.compileHtml(this.$templateCache.put(url, response)));
// } else {
// reject(`GET component template from '${url}' returned '${status}: ${response}'`);
// }
// });
// });
}
} else {
throw new Error(`Directive '${this.name}' is not a component, it is missing template.`);
}
}
If you don't want to modify your Webpack configuration, the quick/dirty solution is to use the raw-loader import syntax:
template: require('!raw-loader!./your-template.html')
As a workaround I used $templateCache and $templateRequest to put templates in $templateCache for Angular needed templates, on AngularJS run as follows:
app.run(['$templateCache', '$templateRequest', function($templateCache, $templateRequest) {
var templateUrlList = [
'app/modules/common/header.html',
...
];
templateUrlList.forEach(function (templateUrl) {
if ($templateCache.get(templateUrl) === undefined) {
$templateRequest(templateUrl)
.then(function (templateContent) {
$templateCache.put(templateUrl, templateContent);
});
}
});
}]);
I've created a method utility to solve this issue.
Basically it adds the template url content to angular's templateCache,
using requireJS and "text.js":
initTemplateUrls(templateUrlList) {
app.run(function ($templateCache) {
templateUrlList.forEach(templateUrl => {
if ($templateCache.get(templateUrl) === undefined) {
$templateCache.put(templateUrl, 'temporaryValue');
require(['text!' + templateUrl],
function (templateContent) {
$templateCache.put(templateUrl, templateContent);
}
);
}
});
});
What you should do is put this method utility in appmodule.ts for example, and then create a list of templateUrls that you are about to upgrade from your angular directive, for example:
const templateUrlList = [
'/app/#fingerprint#/common/directives/grid/pGrid.html',
];
I use webpack's require.context for this:
templates-factory.js
import {resolve} from 'path';
/**
* Wrap given context in AngularJS $templateCache
* #param ctx - A context module
* #param dir - module directory
* #returns {function(...*): void} - AngularJS Run function
*/
export const templatesFactory = (ctx, dir, filename) => {
return $templateCache => ctx.keys().forEach(key => {
const templateId = (() => {
switch (typeof filename) {
case 'function':
return resolve(dir, filename(key));
case 'string':
return resolve(dir, filename);
default:
return resolve(dir, key);
}
})();
$templateCache.put(templateId, ctx(key));
});
};
app.html-bundle.js
import {templatesFactory} from './templates-factory';
const ctx = require.context('./', true, /\.html$/);
export const AppHtmlBundle = angular.module('AppHtmlBundle', [])
.run(templatesFactory(ctx, __dirname))
.name;
Don't forget to add html-loader to your webpack.config.js:
[{
test: /\.html$/,
use: {
loader: 'html-loader',
options: {
minimize: false,
root: path.resolve(__dirname, './src')
}
}
}]
Also you may need to convert relative paths to absolute one. I use my self-written babel plugin ng-template-url-absolutify for this purpose:
[{
test: /\.(es6|js)$/,
include: [path.resolve(__dirname, 'src')],
exclude: /node_modules/,
loader: 'babel-loader',
options: {
plugins: [
'#babel/plugin-syntax-dynamic-import',
['ng-template-url-absolutify', {baseDir: path.resolve(__dirname, 'src'), baseUrl: ''}]
],
presets: [['#babel/preset-env', {'modules': false}]]
}
},

How do I wait until an element is visible with Protractor when Angular is not available?

I have a login function that I'm using for a Protractor test, it looks like this:
var config = require("../helpers/config.js");
var login = function() {
browser.driver.get(config.dsp.url);
browser.driver.findElement(by.name("userName")).sendKeys(config.dsp.user);
browser.driver.findElement(by.name("password")).sendKeys(config.dsp.password);
return browser.driver.findElement(by.name("submit")).click().then(function() {
return browser.driver.wait(function() {
return browser.driver.isElementPresent(browser.driver.findElement(by.className("sample-class-name")));
}, 360000);
});
}
module.exports = login;
I can't use any of the protractor specific hooks because Angular is not used on this page, so I have to use the underlying webdriver API. The problem is, I can't seem to figure out how to wait until an element is visible using this wrapped webdriver object. Any help would be appreciated.
Try with the expected conditions from the underlying driver:
var config = require("../helpers/config.js");
var until = require('selenium-webdriver').until;
var login = function() {
var driver = browser.driver;
driver.get(config.dsp.url);
driver.findElement(by.name("userName")).sendKeys(config.dsp.user);
driver.findElement(by.name("password")).sendKeys(config.dsp.password);
driver.findElement(by.name("submit")).click();
return driver.wait(until.elementLocated(by.css(".sample-class-name")), 10000)
.then(e => driver.wait(until.elementIsVisible(e)), 10000);
}
module.exports = login;

Parse.initialize breaking Angular Cordova app in iOS simulation

So I have an app in angular with cordova, that works as expected when served to a local web host, pushing and pulling the login data from parse.com.
But, when I compile and emulate in an IOS emulator, it appears that the command
parse.initialize("453ioejblahgf3oi5j35p","f30903959fblahblah");
is making $state.go fail to work.
:(
Upon closer inspection, it seems as though Parse.initialize("faerfaerfaerf";"aefaerfeafaer"); is putting a stop to all code that comes after it --
$scope.registerNowClick = function (user) {
//Copy the user to $scope.tmpUser
$scope.tmpUser = angular.copy(user);
// PARSE INITILIZATION CALL Parse.initialize("Zmqvefjaeifai34jofewOvRDxfNdoxH", "HlzTePUSi3fja305f0j341");
alert('Register Now was clicked w/ Email:' + $scope.tmpUser.email);
// PARSE ADD A USER
var user = new Parse.User();
user.se blah blah
If I move the alert above the parse.init, it fires. But when it's after the parse.init, it doesn't.
Javascript is interpreted language. Meaning that, any run-time errors wont halt the program, until that particular line is reached.
Looking at your problem, it looks like Object Parse is not defined. Have you included any parse.js files that it may require? Move Parse.initialize() to the end of the script so that it wont halt the rest of the code from executing.
I have put my Parse initialization in a separate UserService.init() that must be resolved at app startup.
.state('tab', {
url: "/tab",
abstract: true,
templateUrl: "templates/tabs.html",
resolve: {
user: function (UserService) {
var value = UserService.init();
// alert(value); // for debugging
return value;
}
}
})
Here is a snippet of the UserService code
init: function () {
var value = null;
var deferred = $q.defer();
// if initialized, then return the activeUser
if (parseInitialized === false) {
Parse.initialize(ParseConfiguration.applicationId, ParseConfiguration.javascriptKey);
parseInitialized = true;
console.log("parse initialized in init function");
}
setTimeout(function () {
var currentUser = Parse.User.current();
if (currentUser) {
deferred.resolve(currentUser);
} else {
deferred.reject({error: "noUser"});
}
}, 100);
return deferred.promise;
},
The complete solution is here ... https://github.com/aaronksaunders/dcww

How to check internet connection in AngularJs

This is how I would check internet connection in vanilla javascript:
setInterval(function(){
if(navigator.onLine){
$("body").html("Connected.");
}else{
$("body").html("Not connected.");
}
},1000);
I have angular controllers and modules in my project. Where should I put the code above? It should be executed in global context and not be assigned to a certain controller. Are there some kind of global controllers maybe?
First of all, I advise you to listen to online/offline events.
You can do it this way in AnguarJS:
var app = module('yourApp', []);
app.run(function($window, $rootScope) {
$rootScope.online = navigator.onLine;
$window.addEventListener("offline", function() {
$rootScope.$apply(function() {
$rootScope.online = false;
});
}, false);
$window.addEventListener("online", function() {
$rootScope.$apply(function() {
$rootScope.online = true;
});
}, false);
});
NOTE: I am wrapping changing of root scope's variable in $apply method to notify Angular that something was changed.
After that you can:
In controlller:
$scope.$watch('online', function(newStatus) { ... });
In HTML markup:
<div ng-show="online">You're online</div>
<div ng-hide="online">You're offline</div>
Here is a working Plunker: http://plnkr.co/edit/Q3LkiI7Cj4RWBNRLEJUA?p=preview
Other solution could be to broadcast online/offline event. But in this case you need to initialize current status upon loading and then subscribe to event.
It's definitely not as nice, but you could just try an AJAX request to your web server; it'll either succeed or time out.
Also, the HubSpot/offline project looks really good.
Your options:
addEventListener on the window, document, or document.body.
setting the .ononline or .onoffline properties on document or
document.body to a JavaScript Function object.
specifying ononline="..." or onoffline="..." attributes on the tag in
the HTML markup
I will demonstrate the easiest.
In you controller
document.body.onoffline = function() {
alert('You are offline now');
$scope.connection = 'offline'
}
document.body.ononline = function() {
alert('You are online again');
$scope.connection = 'online'
}
Check $scope.connection variable before you try to send requests around.
For Angular 2+ you can use ng-speed-test:
Just install:
npm install ng-speed-test --save
Inject into your module:
import { SpeedTestModule } from 'ng-speed-test';
#NgModule({
...
imports: [
SpeedTestModule,
...
],
...
})
export class AppModule {}
Use service to get speed:
import {SpeedTestService} from 'ng-speed-test';
#Injectable()
export class TechCheckService {
constructor(
private speedTestService:SpeedTestService
) {
this.speedTestService.getMbps().subscribe(
(speed) => {
console.log('Your speed is ' + speed);
}
);
}
}

Resources