diff --git a/Gruntfile.js b/Gruntfile.js index fab8e27e743e..b4d9425bf0cb 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -4,6 +4,7 @@ var files = require('./angularFiles').files; var util = require('./lib/grunt/utils.js'); var versionInfo = require('./lib/versions/version-info'); var path = require('path'); +var e2e = require('./test/e2e/tools'); module.exports = function(grunt) { //grunt plugins @@ -50,6 +51,7 @@ module.exports = function(grunt) { return [ util.conditionalCsp(), util.rewrite(), + e2e.middleware(), connect.favicon('images/favicon.ico'), connect.static(base), connect.directory(base) @@ -76,6 +78,7 @@ module.exports = function(grunt) { next(); }, util.conditionalCsp(), + e2e.middleware(), connect.favicon('images/favicon.ico'), connect.static(base) ]; diff --git a/lib/grunt/plugins.js b/lib/grunt/plugins.js index cbf32cf360c7..208b109eaca5 100644 --- a/lib/grunt/plugins.js +++ b/lib/grunt/plugins.js @@ -57,7 +57,7 @@ module.exports = function(grunt) { util.updateWebdriver.call(util, this.async()); }); - grunt.registerMultiTask('protractor', 'Run Protractor integration tests', function() { + grunt.registerMultiTask('protractor', 'Run Protractor Docs integration tests', function() { util.startProtractor.call(util, this.data, this.async()); }); diff --git a/package.json b/package.json index ecc7d1a57fd9..5298f8c5327e 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,8 @@ "semver": "~4.0.3", "shelljs": "~0.3.0", "sorted-object": "^1.0.0", - "stringmap": "^0.2.2" + "stringmap": "^0.2.2", + "cheerio": "^0.17.0" }, "licenses": [ { diff --git a/protractor-conf.js b/protractor-conf.js index 577d8b2b10ec..8fad56be8828 100644 --- a/protractor-conf.js +++ b/protractor-conf.js @@ -3,6 +3,7 @@ var config = require('./protractor-shared-conf').config; config.specs = [ + 'test/e2e/tests/**/*.js', 'build/docs/ptore2e/**/*.js', 'docs/app/e2e/**/*.scenario.js' ]; diff --git a/protractor-jenkins-conf.js b/protractor-jenkins-conf.js index 64b67a2644ae..d1668e210a74 100644 --- a/protractor-jenkins-conf.js +++ b/protractor-jenkins-conf.js @@ -4,6 +4,7 @@ exports.config = { allScriptsTimeout: 11000, specs: [ + 'test/e2e/tests/**/*.js', 'build/docs/ptore2e/**/*.js', 'docs/app/e2e/*.scenario.js' ], @@ -30,7 +31,7 @@ exports.config = { require('jasmine-reporters'); jasmine.getEnv().addReporter( - new jasmine.JUnitXmlReporter('test_out/e2e-' + exports.config.capabilities.browserName + '-', true, true)); + new jasmine.JUnitXmlReporter('test_out/docs-e2e-' + exports.config.capabilities.browserName + '-', true, true)); }, jasmineNodeOpts: { diff --git a/scripts/travis/build.sh b/scripts/travis/build.sh index 658c146eb072..b4522cf43a38 100755 --- a/scripts/travis/build.sh +++ b/scripts/travis/build.sh @@ -9,12 +9,18 @@ if [ $JOB = "unit" ]; then grunt test:promises-aplus grunt test:unit --browsers SL_Chrome,SL_Safari,SL_Firefox,SL_IE_9,SL_IE_10,SL_IE_11 --reporters dots grunt tests:docs --browsers SL_Chrome,SL_Safari,SL_Firefox,SL_IE_9,SL_IE_10,SL_IE_11 --reporters dots - grunt test:travis-protractor --specs "docs/app/e2e/**/*.scenario.js" + grunt test:travis-protractor-docs --specs "docs/app/e2e/**/*.scenario.js" elif [ $JOB = "e2e" ]; then + if [ $TEST_TARGET = "jquery" ]; then + export USE_JQUERY=1 + fi + export TARGET_SPECS="build/docs/ptore2e/**/default_test.js" if [ $TEST_TARGET = "jquery" ]; then TARGET_SPECS="build/docs/ptore2e/**/jquery_test.js" fi + + export TARGET_SPECS="test/e2e/tests/**/*.js,$TARGET_SPECS" grunt test:travis-protractor --specs "$TARGET_SPECS" else echo "Unknown job type. Please set JOB=unit or JOB=e2e-*." diff --git a/test/e2e/fixtures/.jshintrc b/test/e2e/fixtures/.jshintrc new file mode 100644 index 000000000000..f3f63911e866 --- /dev/null +++ b/test/e2e/fixtures/.jshintrc @@ -0,0 +1,8 @@ +{ + "browser": true, + "globals": { + "angular": false, + "jQuery": false, + "$": false + } +} diff --git a/test/e2e/fixtures/sample/index.html b/test/e2e/fixtures/sample/index.html new file mode 100644 index 000000000000..45e8747fa931 --- /dev/null +++ b/test/e2e/fixtures/sample/index.html @@ -0,0 +1,9 @@ + + +
+

{{text}}

+
+ + + + diff --git a/test/e2e/fixtures/sample/script.js b/test/e2e/fixtures/sample/script.js new file mode 100644 index 000000000000..2d625bb4f19c --- /dev/null +++ b/test/e2e/fixtures/sample/script.js @@ -0,0 +1,4 @@ +angular.module("test", []). + controller("TestCtrl", function($scope) { + $scope.text = "Hello, world!"; + }); diff --git a/test/e2e/templates/test.html b/test/e2e/templates/test.html new file mode 100644 index 000000000000..2eec4e7ce621 --- /dev/null +++ b/test/e2e/templates/test.html @@ -0,0 +1,23 @@ + +{% if eq(test.ngAppTag, "html") %} + +{% else %} + +{% endif %} + + {% if scripts.jQuery %} + + {% endif %} + {% for script in test.scripts %} + + {% endfor %} + {{ test.head }} + + {% if test.ngAppTag === "body" %} + + {% else %} + + {% endif %} + {% test.body %} + + diff --git a/test/e2e/tests/.jshintrc b/test/e2e/tests/.jshintrc new file mode 100644 index 000000000000..9ddc0db3923a --- /dev/null +++ b/test/e2e/tests/.jshintrc @@ -0,0 +1,11 @@ +{ + "node": true, + "globals": { + "describe": false, + "ddescribe": false, + "xdescribe": false, + "it": false, + "xit": false, + "iit": false + } +} diff --git a/test/e2e/tests/helpers/main.js b/test/e2e/tests/helpers/main.js new file mode 100644 index 000000000000..116271f9136f --- /dev/null +++ b/test/e2e/tests/helpers/main.js @@ -0,0 +1,23 @@ +var helper = { + andWaitForAngular: function() { + browser.waitForAngular(); + }, + loadFixture: function(fixture) { + var i = 0; + while (fixture[i] === '/') ++i; + fixture = fixture.slice(i); + if (!/\/(index\.html)?$/.test(fixture)) { + fixture += '/'; + } + + if (process.env.USE_JQUERY) { + fixture += '?jquery'; + } + + browser.get('/e2e/fixtures/' + fixture); + return helper; + } +}; + +global.test = helper; +global.loadFixture = helper.loadFixture; diff --git a/test/e2e/tests/sampleSpec.js b/test/e2e/tests/sampleSpec.js new file mode 100644 index 000000000000..07cdec659e7e --- /dev/null +++ b/test/e2e/tests/sampleSpec.js @@ -0,0 +1,12 @@ +// Sample E2E test: +// +describe('Sample', function() { + beforeEach(function() { + loadFixture("sample").andWaitForAngular(); + }); + + it('should have the interpolated text', function() { + expect(element(by.binding('text')).getText()) + .toBe('Hello, world!'); + }); +}); diff --git a/test/e2e/tools/.jshintrc b/test/e2e/tools/.jshintrc new file mode 100644 index 000000000000..6cf513eebee9 --- /dev/null +++ b/test/e2e/tools/.jshintrc @@ -0,0 +1,3 @@ +{ + "node": true +} \ No newline at end of file diff --git a/test/e2e/tools/fixture.js b/test/e2e/tools/fixture.js new file mode 100644 index 000000000000..26723f032279 --- /dev/null +++ b/test/e2e/tools/fixture.js @@ -0,0 +1,83 @@ +'use strict'; + +var fs = require('fs'); +var path = require('path'); +var $ = require('cheerio'); +var util = require('./util'); + +var root = path.resolve(__dirname, '..'); +var fixtures = path.resolve(root, 'fixtures'); + +var projectRoot = path.resolve(__dirname, '../../..'); +var build = path.resolve(projectRoot, 'build'); + +function rewriteAngularSrc(src, query) { + if (query) { + if (query.build) { + return query.build + '/' + src; + } else if (query.cdn) { + return '//ajax.googleapis.com/ajax/libs/angularjs/' + query.cdn + '/' + src; + } + } + return '/build/' + src; +} + +function generateFixture(test, query) { + var indexFile = path.resolve(fixtures, test, 'index.html'); + var text = fs.readFileSync(indexFile, 'utf8'); + + var $$ = $.load(text); + + var firstScript = null; + var jquery = null; + var angular = null; + $$('script').each(function(i, script) { + var src = $(script).attr('src'); + if (src === 'jquery.js' && jquery === null) jquery = script; + else if (src === 'angular.js' && angular === null) angular = script; + if (firstScript === null) firstScript = script; + if (src) { + var s = util.stat(path.resolve(build, src)); + if (s && s.isFile()) { + $(script).attr('src', rewriteAngularSrc(src, query)); + } else { + $(script).attr('src', util.rewriteTestFile(test, src)); + } + } + }); + + if (jquery && (!('jquery' in query) || (/^(0|no|false|off|n)$/i).test(query.jquery))) { + $(jquery).remove(); + } else if ('jquery' in query) { + if ((/^(0|no|false|off|n)$/i).test(query.jquery)) { + if (jquery) { + $(jquery).remove(); + } + } else { + if (!jquery) { + jquery = $.load('')('script')[0]; + if (firstScript) { + $(firstScript).before(jquery); + } else { + var head = $$('head'); + if (head.length) { + head.prepend(jquery); + } else { + $$.root().first().before(jquery); + } + } + } + if (!/^\d+\.\d+.*$/.test(query.jquery)) { + $(jquery).attr('src', '/bower_components/jquery/dist/jquery.js'); + } else { + $(jquery).attr('src', '//ajax.googleapis.com/ajax/libs/jquery/' + query.jquery + '/jquery.js'); + } + } + } + + return $$.html(); +} + +module.exports = { + generate: generateFixture +}; diff --git a/test/e2e/tools/index.js b/test/e2e/tools/index.js new file mode 100644 index 000000000000..45f3d1284291 --- /dev/null +++ b/test/e2e/tools/index.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + middleware: require('./middleware') +}; diff --git a/test/e2e/tools/middleware.js b/test/e2e/tools/middleware.js new file mode 100644 index 000000000000..39e88a176bab --- /dev/null +++ b/test/e2e/tools/middleware.js @@ -0,0 +1,44 @@ +'use strict'; + +var url = require('url'); +var util = require('./util'); +var fixture = require('./fixture'); + +module.exports = middlewareFactory; + +function middlewareFactory(base) { + base = base || '/e2e'; + while (base.length && base[base.length-1] === '/') base = base.slice(0, base.length-1); + var fixture_regexp = new RegExp('^' + base + '/fixtures/([a-zA-Z0-9_-]+)(/(index.html)?)?$'); + var static_regexp = new RegExp('^' + base + '/fixtures/([a-zA-Z0-9_-]+)(/.*)$'); + + return function(req, res, next) { + var match; + var basicUrl = req.url; + var idx = basicUrl.indexOf('?'); + if (idx >= 0) { + basicUrl = basicUrl.slice(0, idx); + } + if ((match = fixture_regexp.exec(basicUrl))) { + if (util.testExists(match[1])) { + try { + var query = url.parse(req.url, true).query; + res.write(fixture.generate(match[1], query)); + res.end(); + } catch (e) { + return next(e); + } + } else { + return next('Fixture ' + match[1] + ' not found.'); + } + } else if ((match = static_regexp.exec(basicUrl))) { + var rewritten = util.rewriteTestFile(match[1], match[2]); + if (rewritten !== false) { + req.url = rewritten; + } + next(); + } else { + return next(); + } + }; +} diff --git a/test/e2e/tools/util.js b/test/e2e/tools/util.js new file mode 100644 index 000000000000..313e9bda66db --- /dev/null +++ b/test/e2e/tools/util.js @@ -0,0 +1,41 @@ +'use strict'; + +var fs = require('fs'); +var path = require('path'); +var url = require('url'); + +var root = path.resolve(__dirname, '..'); +var tests = path.resolve(root, 'fixtures'); + +function stat(path) { + try { + return fs.statSync(path); + } catch (e) { + // Ignore ENOENT. + if (e.code !== 'ENOENT') { + throw e; + } + } +} + +function testExists(testname) { + var s = stat(path.resolve(tests, testname)); + return s && s.isDirectory(); +} + +function rewriteTestFile(testname, testfile) { + var i = 0; + while (testfile[i] === '/') ++i; + testfile = testfile.slice(i); + var s = stat(path.resolve(tests, testname, testfile)); + if (s && (s.isFile() || s.isDirectory())) { + return ['/test/e2e/fixtures', testname, testfile].join('/'); + } + return false; +} + +module.exports = { + stat: stat, + testExists: testExists, + rewriteTestFile: rewriteTestFile +};