gulp-protractor-qa
Advanced tools
Comparing version 0.1.19 to 0.2.0
326
index.js
@@ -0,240 +1,164 @@ | ||
var util = require('util'); | ||
var gutil = require('gulp-util'); | ||
var fs = require('fs'); | ||
var chalk = require('chalk'); | ||
var Gaze = require('gaze').Gaze; | ||
var glob = require("buster-glob"); | ||
var cheerio = require('cheerio'); | ||
var protractorQaUtil = require('./lib/util'); | ||
var async = require('async'); | ||
var EventEmitter = require('events'); | ||
var _ = require('underscore'); | ||
const PLUGIN_NAME = 'gulp-protractor-qa'; | ||
// `GulpProtractorQA` sub-modules dependecies | ||
var storeFileContent = require('./lib/store-file-content'); | ||
var findSelectors = require('./lib/find-selectors'); | ||
var findViewMatches = require('./lib/find-view-matches'); | ||
var consoleOutput = require('./lib/console-output'); | ||
var watchFilesChange = require('./lib/watch-files-change'); | ||
var gulpProtractorQA = { | ||
testFiles : [], | ||
viewFiles : [], | ||
totalNumOfElements : 0, | ||
// Constant | ||
var PLUGIN_NAME = 'gulp-protractor-qa'; | ||
ptorFindElements : { | ||
regex : protractorQaUtil.getRegexList(), | ||
foundList : [] | ||
}, | ||
// Define `GulpProtractorQA` class. | ||
// @class | ||
function GulpProtractorQA() { | ||
this.testFiles = []; | ||
this.viewFiles = []; | ||
this.selectors = []; | ||
this.watchIsRunning = 0; | ||
this.isFirstLoad = 1; | ||
} | ||
findDotByMatches : function( path, contents ){ | ||
var _this = this, | ||
results, | ||
regexList = _this.ptorFindElements.regex; | ||
// Inherit EventEmitter. | ||
util.inherits(GulpProtractorQA, EventEmitter); | ||
// lopp regexlist | ||
for( var i = 0; i<regexList.length; i++ ){ | ||
// verify matches | ||
while ((results = regexList[i].match.exec(contents)) !== null) | ||
{ | ||
var res = { | ||
at : results[0], | ||
val : results[1], | ||
type : regexList[i].type, | ||
attrName : regexList[i].attrName, | ||
fileName : path | ||
}; | ||
// Init application. | ||
// | ||
// @param {Object} options | ||
// @param {string} options.testSrc - Glob pattern string point to test files | ||
// @param {string} options.viewSrc - Glob pattern string point to view files | ||
// @param {boolean} options.runOnce - Flag to decide whether it should watch files changes or not | ||
GulpProtractorQA.prototype.init = function(options) { | ||
this.options = options || {}; | ||
_this.ptorFindElements.foundList.push( res ); | ||
if (!this.options.testSrc) { | ||
throw new gutil.PluginError(PLUGIN_NAME, '`testSrc` required'); | ||
} | ||
if (!this.options.viewSrc) { | ||
throw new gutil.PluginError(PLUGIN_NAME, '`viewSrc` required!'); | ||
} | ||
} | ||
} | ||
readFiles.call(this); | ||
} | ||
}, | ||
elementsCount : function( contents ){ | ||
var _this = this, | ||
results, | ||
elemRegex = /element[\.all\(|\()]/gi; | ||
// Read `testSrc` and `viewSrc` files content. | ||
function readFiles() { | ||
var self = this; | ||
while ((results = elemRegex.exec(contents)) !== null) { | ||
_this.totalNumOfElements = _this.totalNumOfElements + 1; | ||
async.waterfall([ | ||
function(callback) { | ||
storeFileContent.init(self.options.testSrc, callback); | ||
}, | ||
function(data, callback) { | ||
self.testFiles = data; | ||
storeFileContent.init(self.options.viewSrc, callback); | ||
}, | ||
function(data, callback) { | ||
self.viewFiles = data; | ||
callback(null, 'success'); | ||
} | ||
], function(err, data) { | ||
findElementSelectors.call(self); | ||
}); | ||
} | ||
}, | ||
searchProtractorDotByContents : function( updatedTestFiles, beforeViewMatches ){ | ||
var _this = this; | ||
for( var i = 0; i<updatedTestFiles.length; i++ ){ | ||
var obj = updatedTestFiles[i]; | ||
_this.findDotByMatches(obj.path, obj.contents); | ||
_this.elementsCount( obj.contents ); | ||
} | ||
// Loop through test files and find protractor | ||
// selectors (e.g.: by.css('[href="/"]')). | ||
function findElementSelectors() { | ||
var self = this; | ||
// reset selectors | ||
this.selectors = []; | ||
// log number of watched elements | ||
if( typeof beforeViewMatches === 'function'){ | ||
beforeViewMatches(); | ||
} | ||
this.testFiles.forEach(function(item) { | ||
self.selectors = self.selectors.concat(findSelectors.init(item)); | ||
}); | ||
// verify matches | ||
_this.verifyViewMatches( _this.ptorFindElements.foundList ); | ||
}, | ||
checkSelectorViewMatches.call(this); | ||
} | ||
updateFileContents : function( collection, filepath, newContent ){ | ||
function checkSelectorViewMatches() { | ||
var self = this; | ||
var _this = this; | ||
// Set all selectors to not found | ||
this.selectors.forEach(function(item) { | ||
item.found = 0; | ||
}); | ||
for( var i = 0; i < _this[ collection ].length; i++ ){ | ||
var obj = _this[ collection ][i]; | ||
if( typeof obj.path != 'undefined' ){ | ||
if( filepath.indexOf( obj.path.replace("./", "/") ) >= 0 ){ | ||
obj.contents = newContent; | ||
break; | ||
} | ||
} | ||
} | ||
// Check if selectors are findable | ||
this.viewFiles.forEach(function(item) { | ||
findViewMatches(self.selectors, item.content); | ||
}); | ||
// reset foundList and totalNumOfElements | ||
_this.totalNumOfElements = 0; | ||
_this.ptorFindElements.foundList = []; | ||
outputResult.call(this); | ||
} | ||
// map protractor elements | ||
_this.searchProtractorDotByContents( _this.testFiles ); | ||
}, | ||
function outputResult() { | ||
var notFoundItems = _.filter(this.selectors, function(item) { | ||
return (!item.found && !item.disabled); | ||
}); | ||
bindExists : function( bind, contents ){ | ||
var results, | ||
exists = false, | ||
pattern = /{{(.*?)}}/gi; | ||
consoleOutput.printFoundItems(notFoundItems); | ||
while ( (results = pattern.exec(contents)) !== null ){ | ||
if( results[1].indexOf( bind ) >= 0 ){ | ||
exists = true; | ||
} | ||
} | ||
// On first run warn watched selectors. | ||
if (this.isFirstLoad) { | ||
warnWatchedSelectors.call(this); | ||
this.isFirstLoad = 0; | ||
} | ||
return exists; | ||
}, | ||
if (!this.options.runOnce && !this.watchIsRunning) { | ||
startWatchingFiles.call(this); | ||
} | ||
} | ||
verifyViewMatches : function( foundList ){ | ||
function startWatchingFiles() { | ||
this.watchIsRunning = 1; | ||
var _this = this, | ||
allElementsFound = true; | ||
// Init gaze. | ||
watchFilesChange.call(this); | ||
for( var i = 0; i < foundList.length; i++ ){ | ||
// Listen to change event | ||
this.on('change', onFileChange.bind(this)); | ||
} | ||
var found = false; | ||
var foundItem = foundList[i]; | ||
function onFileChange(data) { | ||
if (data.fileType === 'test') { | ||
updateSelectors.call(this, data); | ||
} | ||
for(var c = 0; c < _this.viewFiles.length; c++ ){ | ||
var obj = _this.viewFiles[c]; | ||
checkSelectorViewMatches.call(this); | ||
} | ||
$ = cheerio.load( obj.contents ); | ||
var selector = ''; | ||
function updateSelectors(data) { | ||
var filtered = _.filter(this.selectors, function(selector) { | ||
return !(selector.path === data.path); | ||
}); | ||
var updatedTestFile = this.testFiles[data.index]; | ||
var newSelectors; | ||
if( foundItem.type === "attr" ){ | ||
selector = [ | ||
'[', foundItem.attrName, '="', foundItem.val, '"], ', | ||
'[data-', foundItem.attrName, '="', foundItem.val, '"]' | ||
].join(""); | ||
if( foundItem.attrName === "ng-bind" ){ | ||
// search for {{bindings}} | ||
if( _this.bindExists( foundItem.val, obj.contents ) ){ | ||
found = true; | ||
} | ||
} | ||
} else if( foundItem.type === "cssAttr" ){ | ||
selector = [ '[', foundItem.val, ']' ].join(""); | ||
} | ||
if ($(selector).length) { | ||
found = true; | ||
} | ||
} | ||
if( !found ){ | ||
allElementsFound = false; | ||
gutil.log('[' + chalk.cyan(PLUGIN_NAME) + '] ' + chalk.red(foundItem.at) + ' at ' + chalk.bold(foundItem.fileName) + ' not found within view files!'); | ||
} | ||
} | ||
if( allElementsFound ){ | ||
gutil.log( | ||
'[' + chalk.cyan(PLUGIN_NAME) + '] ' + | ||
chalk.green("all test elements were successfully found!") | ||
); | ||
} | ||
}, | ||
storeFileContents : function( src, collection, _cb ){ | ||
var _this = this; | ||
// watch test files changes | ||
var gaze = new Gaze(src); | ||
gaze.on('all', function(event, filepath) { | ||
fs.readFile(filepath, 'utf8', function(err, data){ | ||
if (err) { throw err; } | ||
_this.updateFileContents(collection, filepath, data); | ||
}); | ||
}); | ||
// async map file contents | ||
var async = function async(arg, callback) { | ||
fs.readFile(arg, 'utf8', function (err, data) { | ||
if (err) { throw err; } | ||
callback(arg, data); | ||
}); | ||
}; | ||
glob.glob(src, function (er, files) { | ||
files.forEach(function(item) { | ||
async(item, function(filePath, data){ | ||
_this[ collection ].push( | ||
{ | ||
path : filePath, | ||
contents : data | ||
} | ||
); | ||
if( _this[ collection ].length == files.length ) { | ||
_cb(); | ||
} | ||
}); | ||
}); | ||
}); | ||
}, | ||
bindStoreFileContents : function( _callback ){ | ||
var _this = this; | ||
_this.storeFileContents( _this.options.testSrc, "testFiles", function(){ | ||
_this.storeFileContents( _this.options.viewSrc, "viewFiles", function(){ | ||
_callback(); | ||
}); | ||
}); | ||
if (updatedTestFile) { | ||
newSelectors = findSelectors.init(updatedTestFile); | ||
this.selectors = filtered.concat(newSelectors); | ||
} | ||
} | ||
}; | ||
function warnWatchedSelectors() { | ||
var total = this.selectors.length; | ||
gulpProtractorQA.init = function( options ){ | ||
var globals = gulpProtractorQA; | ||
globals.options = options || {}; | ||
if (typeof globals.options.testSrc == 'undefined') { | ||
throw new gutil.PluginError(PLUGIN_NAME, '`testSrc` required'); | ||
} | ||
if (typeof globals.options.viewSrc == 'undefined') { | ||
throw new gutil.PluginError(PLUGIN_NAME, '`viewSrc` required!'); | ||
} | ||
globals.bindStoreFileContents(function(){ | ||
globals.searchProtractorDotByContents(globals.testFiles, function beforeViewMatches(){ | ||
gutil.log( '[' + chalk.cyan(PLUGIN_NAME) + '] ' + chalk.gray( "// " + globals.ptorFindElements.foundList.length + " out of " + globals.totalNumOfElements + " element selectors are being watched" ) ); | ||
}); | ||
var filtered = _.filter(this.selectors, function(selector) { | ||
return !selector.disabled; | ||
}); | ||
}; | ||
// Output `X` out `total` are being watched. | ||
consoleOutput.watchedLocators(filtered.length, total); | ||
} | ||
// Exporting the plugin main function | ||
module.exports = gulpProtractorQA; | ||
module.exports = new GulpProtractorQA(); |
{ | ||
"name": "gulp-protractor-qa", | ||
"version": "0.1.19", | ||
"version": "0.2.0", | ||
"description": "Gulp Protractor QA warns you on the fly whether all element() selectors could be found or not within your AngularJS view files.", | ||
@@ -28,3 +28,7 @@ "license": "MIT", | ||
"cheerio": "0.15.0", | ||
"buster-glob": "0.3.3" | ||
"buster-glob": "0.3.3", | ||
"ast-traverse": "^0.1.1", | ||
"esprima": "^2.6.0", | ||
"async": "^1.4.2", | ||
"underscore": "^1.8.3" | ||
}, | ||
@@ -31,0 +35,0 @@ "bugs": { |
# [gulp](http://gulpjs.com)-protractor-qa [![NPM version](https://badge.fury.io/js/gulp-protractor-qa.svg)](http://badge.fury.io/js/gulp-protractor-qa) [![Build Status](https://travis-ci.org/ramonvictor/gulp-protractor-qa.svg?branch=master)](https://travis-ci.org/ramonvictor/gulp-protractor-qa) | ||
<img src="https://raw.githubusercontent.com/ramonvictor/gulp-protractor-qa/master/assets/protractor-qa-logo.png" alt="Protractor Logo" /> | ||
<img src="https://raw.githubusercontent.com/ramonvictor/gulp-protractor-qa/master/assets/protractor-qa-logo.png" alt="Protractor QA Logo" /> | ||
@@ -12,3 +12,3 @@ Keeping end-to-end tests up-to-date can be really painful. Gulp Protractor QA warns you on the fly whether all element() selectors could be found or not within your application view files. | ||
<img src="https://raw.githubusercontent.com/ramonvictor/gulp-protractor-qa/master/assets/gulp-protractor-qa.gif" alt="Video screen demo of gulp-protractor-qa in action!"> | ||
<img src="https://raw.githubusercontent.com/ramonvictor/gulp-protractor-qa/master/assets/gulp-protractor-qa.gif" width="720" height="auto" alt="Video screen demo of gulp-protractor-qa in action!"> | ||
@@ -18,4 +18,6 @@ | ||
<a href="http://bit.ly/1hceBSw" target="_blank"><img src="https://raw.githubusercontent.com/ramonvictor/gulp-protractor-qa/master/assets/screencast-gulp-protractor-qa.jpg" width="719" height="429" alt="How gulp-protractor-qa works?"></a> | ||
<a href="http://bit.ly/1hceBSw" target="_blank"><img src="https://raw.githubusercontent.com/ramonvictor/gulp-protractor-qa/master/assets/screencast-gulp-protractor-qa.jpg" width="675" height="auto" alt="How gulp-protractor-qa works?"></a> | ||
> TL;DR: a couple of new features and fixes have been added since this screencast publication. | ||
## Install | ||
@@ -26,3 +28,2 @@ | ||
``` | ||
[![NPM](https://nodei.co/npm/gulp-protractor-qa.png?downloads=true)](https://nodei.co/npm/gulp-protractor-qa/) | ||
@@ -39,6 +40,7 @@ ## Example | ||
gulp.task('protractor-qa', function() { | ||
protractorQA.init({ | ||
testSrc : 'test/e2e/**/*Spec.js', | ||
viewSrc : [ 'index.html', 'partials/*.html' ] | ||
}); | ||
protractorQA.init({ | ||
runOnce: true, // optional | ||
testSrc: 'test/e2e/**/*Spec.js', | ||
viewSrc: [ 'index.html', 'partials/*.html' ] | ||
}); | ||
}); | ||
@@ -59,2 +61,10 @@ ``` | ||
##### runOnce | ||
Type: `Boolean` | ||
Default: `false` | ||
Optional: Set to `true` when you want to control yourself when to run task. | ||
In other words, it won't watch files changes. | ||
##### testSrc | ||
@@ -76,13 +86,23 @@ | ||
Gulp-protractor-qa is currently watching the following `element()` selectors: | ||
Gulp-protractor-qa is currently watching the following `element()` locators: | ||
- `by.binding()`; | ||
- `by.model()`; | ||
- `by.binding()`; | ||
- `by.css('[attr-name="attr-value"]')`; | ||
- `by.repeater()`. | ||
- `by.repeater()`; | ||
- `by.css()`; | ||
- `by.id()`; | ||
- `by.className()`; | ||
- `by.name()`; | ||
**Help**: I need to extend this list by adding more regex rules ([see file](https://github.com/ramonvictor/gulp-protractor-qa/blob/master/lib/util.js)). | ||
Interested to help? Fork the project and send a pull request! :) | ||
Note: currently it can't find `by.css()` selectors with `:nth-child()`. | ||
## Changelog | ||
- **0.2.0** api completely rewritten, including: | ||
- Introducing `runOnce` feature; | ||
- Ignoring commented out element selectors; | ||
- Handle any form of denormalized directives - except `ng:*` due to `cheerio` limitation; | ||
- Fix a couple of bugs related to `by.css` old regex; | ||
- Add suport for more protractor locators: `by.id()`, `by.className()` and `by.name()`. | ||
- **0.1.19** improve `by.css` regex; | ||
@@ -89,0 +109,0 @@ |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
20891
13
385
126
9
2
1
+ Addedast-traverse@^0.1.1
+ Addedasync@^1.4.2
+ Addedesprima@^2.6.0
+ Addedunderscore@^1.8.3
+ Addedast-traverse@0.1.1(transitive)
+ Addedasync@1.5.2(transitive)
+ Addedesprima@2.7.3(transitive)
+ Addedunderscore@1.13.6(transitive)