feat(e2e testing): testing framework + initial tests

close #604
This commit is contained in:
Jay Traband 2015-12-20 13:17:16 -08:00 committed by Ward Bell
parent 34122f4d92
commit 872064e12e
19 changed files with 950 additions and 45 deletions

1
.gitignore vendored
View File

@ -23,3 +23,4 @@ npm-debug.log.*
plnkr.html
*plnkr.no-link.html
public/docs/*/latest/guide/cheatsheet.json
protractor-results.txt

View File

@ -15,8 +15,13 @@ var fsExtra = require('fs-extra');
var fs = fsExtra;
var exec = require('child_process').exec;
var execPromise = Q.denodeify(exec);
// cross platform version of spawn that also works on windows.
var xSpawn = require('cross-spawn');
var prompt = require('prompt');
var globby = require("globby");
// Ugh... replacement needed to kill processes on any OS
// - because childProcess.kill does not work properly on windows
var treeKill = require("tree-kill");
// TODO:
// 1. Think about using runSequence
@ -56,6 +61,120 @@ var _excludeMatchers = _excludePatterns.map(function(excludePattern){
var _exampleBoilerplateFiles = ['package.json', 'tsconfig.json', 'karma.conf.js', 'karma-test-shim.js' ]
// --filter may be passed in to filter/select _example app subdir names
// i.e. gulp run-e2e-tests --filter=foo ; would select all example apps with
// 'foo' in their folder names.
gulp.task('run-e2e-tests', function() {
var spawnInfo = spawnExt('npm', ['install'], { cwd: EXAMPLES_PATH});
return spawnInfo.promise.then(function() {
copyExampleBoilerplate();
var exePath = path.join(process.cwd(), "./node_modules/.bin/");
spawnInfo = spawnExt('webdriver-manager', ['update'], {cwd: exePath});
return spawnInfo.promise;
}).then(function(x) {
return findAndRunE2eTests(argv.filter);
}).fail(function(e) {
return e;
});
});
// finds all of the *e2e-spec.tests under the _examples folder along
// with the corresponding apps that they should run under. Then run
// each app/spec collection sequentially.
function findAndRunE2eTests(filter) {
// create an output file with header.
var outputFile = path.join(process.cwd(), 'protractor-results.txt');
var header = "Protractor example results for: " + (new Date()).toLocaleString() + "\n\n";
if (filter) {
header += ' Filter: ' + filter.toString() + '\n\n';
}
fs.writeFileSync(outputFile, header);
// create an array of combos where each
// combo consists of { examplePath: ... , protractorConfigFilename: ... }
var exeConfigs = [];
var e2eSpecPaths = getE2eSpecPaths(EXAMPLES_PATH);
var srcConfig = path.join(EXAMPLES_PATH, 'protractor.config.js');
e2eSpecPaths.forEach(function(specPath) {
var destConfig = path.join(specPath, 'protractor.config.js');
fsExtra.copySync(srcConfig, destConfig);
// get all of the examples under each dir where a pcFilename is found
examplePaths = getExamplePaths(specPath, true);
if (filter) {
examplePaths = examplePaths.filter(function (fn) {
return fn.match(filter) != null;
})
}
examplePaths.forEach(function(exPath) {
exeConfigs.push( { examplePath: exPath, protractorConfigFilename: destConfig });
})
});
// run the tests sequentially
return exeConfigs.reduce(function (promise, combo) {
return promise.then(function () {
return runE2eTests(combo.examplePath, combo.protractorConfigFilename, outputFile);
});
}, Q.resolve());
}
// start the example in appDir; then run protractor with the specified
// fileName; then shut down the example. All protractor output is appended
// to the outputFile.
function runE2eTests(appDir, protractorConfigFilename, outputFile ) {
// start the app
var appRunSpawnInfo = spawnExt('npm',['run','http-server', '--', '-s' ], { cwd: appDir });
// start protractor
var pcFilename = path.resolve(protractorConfigFilename); // need to resolve because we are going to be running from a different dir
var exePath = path.join(process.cwd(), "./node_modules/.bin/");
var spawnInfo = spawnExt('protractor',
[ pcFilename, '--params.appDir=' + appDir, '--params.outputFile=' + outputFile], { cwd: exePath });
return spawnInfo.promise.then(function(data) {
// kill the app now that protractor has completed.
treeKill(appRunSpawnInfo.proc.pid);
// Ugh... proc.kill does not work properly on windows with child processes.
// appRun.proc.kill();
return data;
}).fail(function(err) {
// Ugh... proc.kill does not work properly on windows with child processes.
// appRun.proc.kill();
treeKill(appRunSpawnInfo.proc.pid)
return err;
});
}
// returns both a promise and the spawned process so that it can be killed if needed.
function spawnExt(command, args, options) {
var deferred = Q.defer();
var descr = command + " " + args.join(' ');
var proc;
gutil.log('running: ' + descr);
try {
proc = xSpawn.spawn(command, args, options);
} catch(e) {
gutil.log(e);
deferred.reject(e);
return { proc: null, promise: deferred.promise };
}
proc.stdout.on('data', function (data) {
gutil.log(data.toString());
});
proc.stderr.on('data', function (data) {
gutil.log(data.toString());
});
proc.on('close', function (data) {
gutil.log('completed: ' + descr);
deferred.resolve(data);
});
proc.on('error', function (data) {
gutil.log('completed with error:' + descr);
gutil.log(data.toString());
deferred.reject(data);
});
return { proc: proc, promise: deferred.promise };
}
// Public tasks
gulp.task('default', ['help']);
@ -69,7 +188,7 @@ gulp.task('help', taskListing.withFilters(function(taskName) {
}));
// requires admin access
gulp.task('add-example-boilerplate', function() {
gulp.task('add-example-boilerplate', function() {
var realPath = path.join(EXAMPLES_PATH, '/node_modules');
var nodeModulesPaths = getNodeModulesPaths(EXAMPLES_PATH);
@ -77,12 +196,25 @@ gulp.task('add-example-boilerplate', function() {
gutil.log("symlinking " + linkPath + ' -> ' + realPath)
fsUtils.addSymlink(realPath, linkPath);
});
copyExampleBoilerplate();
});
// copies boilerplate files to locations
// where an example app is found
// also copies protractor.config.js file
function copyExampleBoilerplate() {
var sourceFiles = _exampleBoilerplateFiles.map(function(fn) {
return path.join(EXAMPLES_PATH, fn);
});
var examplePaths = getExamplePaths(EXAMPLES_PATH);
return copyFiles(sourceFiles, examplePaths );
});
// copies protractor.config.js from _examples dir to each subdir that
// contains a e2e-spec file.
return copyFiles(sourceFiles, examplePaths).then(function() {
var sourceFiles = [ path.join(EXAMPLES_PATH, 'protractor.config.js') ];
var e2eSpecPaths = getE2eSpecPaths(EXAMPLES_PATH);
return copyFiles(sourceFiles, e2eSpecPaths);
});
}
gulp.task('remove-example-boilerplate', function() {
var nodeModulesPaths = getNodeModulesPaths(EXAMPLES_PATH);
@ -90,7 +222,10 @@ gulp.task('remove-example-boilerplate', function() {
fsUtils.removeSymlink(linkPath);
});
var examplePaths = getExamplePaths(EXAMPLES_PATH);
return deleteFiles(_exampleBoilerplateFiles, examplePaths );
return deleteFiles(_exampleBoilerplateFiles, examplePaths).then(function() {
var e2eSpecPaths = getE2eSpecPaths(EXAMPLES_PATH);
return deleteFiles(['protractor.config.js'], e2eSpecPaths);
})
});
gulp.task('serve-and-sync', ['build-docs'], function (cb) {
@ -264,18 +399,11 @@ function deleteFiles(baseFileNames, destPaths) {
return Q.all(delPromises);
}
function getExamplePaths(basePath) {
var jsonPattern = path.join(basePath, "**/example-config.json");
// ignore (skip) the top level version.
var exceptJsonPattern = "!" + path.join(basePath, "/example-config.json");
var nmPattern = path.join(basePath, "**/node_modules/**");
var fileNames = globby.sync( [ jsonPattern, exceptJsonPattern ], { ignore: [nmPattern] } );
// same as above but perf can differ.
// var fileNames = globby.sync( [jsonPattern, "!" + nmPattern]);
var paths = fileNames.map(function(fileName) {
return path.dirname(fileName);
});
return paths;
// TODO: filter out all paths that are subdirs of another
// path in the result.
function getE2eSpecPaths(basePath) {
var paths = getPaths(basePath, '*e2e-spec.js', true);
return _.uniq(paths);
}
function getNodeModulesPaths(basePath) {
@ -285,6 +413,31 @@ function getNodeModulesPaths(basePath) {
return paths;
}
function getExamplePaths(basePath, includeBase) {
// includeBase defaults to false
return getPaths(basePath, "example-config.json", includeBase)
}
function getPaths(basePath, filename, includeBase) {
var filenames = getFilenames(basePath, filename, includeBase);
var paths = filenames.map(function(fileName) {
return path.dirname(fileName);
});
return paths;
}
function getFilenames(basePath, filename, includeBase) {
// includeBase defaults to false
var includePatterns = [path.join(basePath, "**/" + filename)];
if (!includeBase) {
// ignore (skip) the top level version.
includePatterns.push("!" + path.join(basePath, "/" + filename));
}
var nmPattern = path.join(basePath, "**/node_modules/**");
var filenames = globby.sync(includePatterns, {ignore: [nmPattern]});
return filenames;
}
function watchAndSync(options, cb) {
execCommands(['npm run harp -- server .'], {}, cb);

View File

@ -8,7 +8,9 @@
"test": "echo \"Error: no test specified\" && exit 1",
"harp": "harp",
"live-server": "live-server",
"test-api-builder": "jasmine-node tools/api-builder"
"test-api-builder": "jasmine-node tools/api-builder",
"protractor": "protractor"
},
"repository": {
"type": "git",
@ -28,6 +30,7 @@
"assert-plus": "^0.1.5",
"browser-sync": "^2.9.3",
"canonical-path": "0.0.2",
"cross-spawn": "^2.1.0",
"del": "^1.2.0",
"dgeni": "^0.4.0",
"dgeni-packages": "^0.11.1",
@ -58,7 +61,9 @@
"nodegit": "0.5.0",
"path": "^0.11.14",
"prompt": "^0.2.14",
"protractor": "^3.0.0",
"q": "^1.4.1",
"tree-kill": "^1.0.0",
"typescript": "1.7.3",
"yargs": "^3.23.0"
},

View File

@ -4,4 +4,5 @@ package.json
karma.conf.js
karma-test-shim.js
tsconfig.json
npm-debug*.log
npm-debug*.
**/protractor.config.js

View File

@ -0,0 +1,60 @@
describe('Architecture', function () {
var _title = "Hero List";
beforeAll(function () {
browser.get('');
});
function itReset(name, func) {
it(name, function() {
browser.get('').then(func);
});
}
it('should display correct title: ' + _title, function () {
expect(element(by.css('h2')).getText()).toEqual(_title);
});
it('should display correct detail after selection', function() {
var detailView = element(by.css('hero-detail'));
expect(detailView.isPresent()).toBe(false);
// select the 2nd element
var selectEle = element.all(by.css('hero-list > div')).get(1);
selectEle.click().then(function() {
return selectEle.getText();
}).then(function(selectedHeroName) {
// works but too specific if we change the app
// expect(selectedHeroName).toEqual('Mr. Nice');
expect(detailView.isDisplayed()).toBe(true);
var detailTitleEle = element(by.css('hero-detail > h4'));
expect(detailTitleEle.getText()).toContain(selectedHeroName);
});
})
itReset('should display correct detail after modification', function() {
var detailView = element(by.css('hero-detail'));
expect(detailView.isPresent()).toBe(false);
// select the 2nd element
var selectEle = element.all(by.css('hero-list > div')).get(1);
selectEle.click().then(function () {
return selectEle.getText();
}).then(function (selectedHeroName) {
var detailTitleEle = element(by.css('hero-detail > h4'));
expect(detailTitleEle.getText()).toContain(selectedHeroName);
var heroNameEle = element.all(by.css('hero-detail input')).get(0);
// check that both the initial selected item and the detail title reflect changes
// made to the input box.
heroNameEle.sendKeys('foo');
expect(detailTitleEle.getText()).toContain('foo');
expect(selectEle.getText()).toContain('foo');
// getText on an input element always returns null
// http://stackoverflow.com/questions/20310442/how-to-gettext-on-an-input-in-protractor
// expect(heroNameEle.getText()).toEqual(selectedHeroName);
expect(heroNameEle.getAttribute('value')).toEqual(selectedHeroName + 'foo');
});
})
});

View File

@ -0,0 +1,26 @@
describe('Attribute directives', function () {
var _title = "My First Attribute Directive";
beforeAll(function () {
browser.get('');
});
it('should display correct title: ' + _title, function () {
expect(element(by.css('h1')).getText()).toEqual(_title);
});
it('should be able to select green highlight', function () {
var highlightedEle = element(by.cssContainingText('p', 'Highlight me'));
var lightGreen = "rgba(144, 238, 144, 1)";
expect(highlightedEle.getCssValue('background-color')).not.toEqual(lightGreen);
// var greenRb = element(by.cssContainingText('input', 'Green'));
var greenRb = element.all(by.css('input')).get(0);
greenRb.click().then(function() {
browser.actions().mouseMove(highlightedEle).perform();
expect(highlightedEle.getCssValue('background-color')).toEqual(lightGreen);
});
});
});

View File

@ -0,0 +1,21 @@
describe('Displaying Data Tests', function () {
var _title = "Tour of Heroes";
var _defaultHero = 'Windstorm'
beforeAll(function () {
browser.get('');
});
it('should display correct title: ' + _title, function () {
expect(element(by.css('h1')).getText()).toEqual(_title);
});
it('should have correct default hero: ' + _defaultHero, function () {
expect(element(by.css('h2')).getText()).toContain(_defaultHero);
});
it('should have many heroes', function () {
expect(element(by.css('ul ~ p')).getText()).toContain('There are many heroes!');
});
});

View File

@ -0,0 +1,61 @@
describeIf(browser.appIsTs, 'Forms Tests', function () {
beforeEach(function () {
browser.get('');
});
it('should display correct title', function () {
expect(element.all(by.css('h1')).get(0).getText()).toEqual('Hero Form');
});
it('should not display message before submit', function () {
var ele = element(by.css('h2'));
expect(ele.isDisplayed()).toBe(false);
});
it('should hide form after submit', function () {
var ele = element.all(by.css('h1')).get(0);
expect(ele.isDisplayed()).toBe(true);
var b = element.all(by.css('button[type=submit]')).get(0);
b.click().then(function() {
expect(ele.isDisplayed()).toBe(false);
});
});
it('should display message after submit', function () {
var b = element.all(by.css('button[type=submit]')).get(0);
b.click().then(function() {
expect(element(by.css('h2')).getText()).toContain('You submitted the following');
});
});
it('should hide form after submit', function () {
var alterEgoEle = element.all(by.css('input[ngcontrol=alterEgo]')).get(0);
expect(alterEgoEle.isDisplayed()).toBe(true);
var submitButtonEle = element.all(by.css('button[type=submit]')).get(0);
submitButtonEle.click().then(function() {
expect(alterEgoEle.isDisplayed()).toBe(false);
})
});
it('should reflect submitted data after submit', function () {
var test = 'testing 1 2 3';
var newValue;
var alterEgoEle = element.all(by.css('input[ngcontrol=alterEgo]')).get(0);
alterEgoEle.getAttribute('value').then(function(value) {
alterEgoEle.sendKeys(test);
newValue = value + test;
expect(alterEgoEle.getAttribute('value')).toEqual(newValue);
}).then(function() {
var b = element.all(by.css('button[type=submit]')).get(0);
return b.click();
}).then(function() {
var alterEgoTextEle = element(by.cssContainingText('div', 'Alter Ego'));
expect(alterEgoTextEle.isPresent()).toBe(true, 'cannot locate "Alter Ego" label');
var divEle = element(by.cssContainingText('div', newValue));
expect(divEle.isPresent()).toBe(true, 'cannot locate div with this text: ' + newValue);
});
});
});

View File

@ -0,0 +1,55 @@
describe('Hierarchical dependency injection', function () {
beforeEach(function () {
browser.get('');
});
it('should open with a card view', function () {
expect(element.all(by.cssContainingText('button','edit')).get(0).isDisplayed()).toBe(true,
"edit button should be displayed");
});
it('should have multiple heros listed', function () {
expect(element.all(by.css('heroes-list li')).count()).toBeGreaterThan(1);
});
it('should change to editor view after selection', function () {
var editButtonEle = element.all(by.cssContainingText('button','edit')).get(0);
editButtonEle.click().then(function() {
expect(editButtonEle.isDisplayed()).toBe(false, "edit button should be hidden after selection");
})
});
it('should be able to save editor change', function () {
testEdit(true);
});
it('should be able to cancel editor change', function () {
testEdit(false);
});
function testEdit(shouldSave) {
var inputEle;
// select 2nd ele
var heroEle = element.all(by.css('heroes-list li')).get(1);
// get the 2nd span which is the name of the hero
var heroNameEle = heroEle.all(by.css('hero-card span')).get(1);
var editButtonEle = heroEle.element(by.cssContainingText('button','edit'));
editButtonEle.click().then(function() {
inputEle = heroEle.element(by.css('hero-editor input'));
return inputEle.sendKeys("foo");
}).then(function() {
buttonName = shouldSave ? 'save' : 'cancel';
var buttonEle = heroEle.element(by.cssContainingText('button', buttonName));
return buttonEle.click();
}).then(function() {
if (shouldSave) {
expect(heroNameEle.getText()).toContain('foo');
} else {
expect(heroNameEle.getText()).not.toContain('foo');
}
})
}
});

View File

@ -0,0 +1,154 @@
describe('Lifecycle hooks', function () {
beforeAll(function () {
browser.get('');
});
it('should open correctly', function () {
expect(element.all(by.css('h2')).get(0).getText()).toEqual('Peek-A-Boo');
});
it('should be able to drive peek-a-boo button', function () {
var pabComp = element(by.css('peek-a-boo-parent peek-a-boo'));
expect(pabComp.isPresent()).toBe(false, "should not be able to find the 'peek-a-boo' component");
var pabButton = element.all(by.css('peek-a-boo-parent button')).get(0);
var updateHeroButton = element.all(by.css('peek-a-boo-parent button')).get(1);
expect(pabButton.getText()).toContain('Create Peek');
pabButton.click().then(function () {
expect(pabButton.getText()).toContain('Destroy Peek');
expect(pabComp.isDisplayed()).toBe(true, "should be able to see the 'peek-a-boo' component");
expect(pabComp.getText()).toContain('Windstorm');
expect(pabComp.getText()).not.toContain('Windstorm!');
expect(updateHeroButton.isPresent()).toBe(true, "should be able to see the update hero button");
return updateHeroButton.click();
}).then(function () {
expect(pabComp.getText()).toContain('Windstorm!');
return pabButton.click();
}).then(function () {
expect(pabComp.isPresent()).toBe(false, "should no longer be able to find the 'peek-a-boo' component");
});
});
it('should be able to trigger onChanges', function () {
var onChangesViewEle = element.all(by.css('on-changes-parent my-hero div')).get(0);
var inputEles = element.all(by.css('on-changes-parent input'));
var heroNameInputEle = inputEles.get(0);
var powerInputEle = inputEles.get(1);
var titleEle = onChangesViewEle.element(by.css('p'));
expect(titleEle.getText()).toContain('Windstorm can sing');
var changeLogEles = onChangesViewEle.all(by.css('div'));
expect(changeLogEles.count()).toEqual(3, "should start with 3 messages");
heroNameInputEle.sendKeys('-foo-').then(function () {
expect(titleEle.getText()).toContain('Windstorm-foo- can sing');
expect(changeLogEles.count()).toEqual(3, "should still have 3 messages");
// protractor bug with sendKeys means that line below does not work.
// return powerInputEle.sendKeys('-bar-');
return sendKeys(powerInputEle, '-bar-');
}).then(function () {
expect(titleEle.getText()).toContain('Windstorm-foo- can sing-bar-');
// 8 == 3 previously + length of '-bar-'
expect(changeLogEles.count()).toEqual(8, "should have 8 messages now");
});
});
it('should support after-view hooks', function () {
var inputEle = element(by.css('after-view-parent input'));
var buttonEle = element(by.css('after-view-parent button'));
var logEles = element.all(by.css('after-view-parent h4 ~ div'));
var childViewTextEle = element(by.css('after-view-parent my-child .child'));
expect(childViewTextEle.getText()).toContain('Magneta is my hero');
expect(logEles.count()).toBeGreaterThan(2);
var logCount;
logEles.count().then(function(count) {
logCount = count;
return sendKeys(inputEle, "-test-");
}).then(function() {
expect(childViewTextEle.getText()).toContain('-test-');
return logEles.count();
}).then(function(count) {
expect(logCount + 6).toEqual(count, "6 additional log messages should have been added");
logCount = count;
return buttonEle.click();
}).then(function() {
expect(childViewTextEle.isPresent()).toBe(false,"child view should no longer be part of the DOM");
sendKeys(inputEle, "-foo-");
expect(logEles.count()).toEqual(logCount, "no additional log messages should have been added");
});
});
it('should support after-content hooks', function () {
var inputEle = element(by.css('after-content-parent input'));
var buttonEle = element(by.css('after-content-parent button'));
var logEles = element.all(by.css('after-content-parent h4 ~ div'));
var childViewTextEle = element(by.css('after-content-parent my-child .child'));
expect(childViewTextEle.getText()).toContain('Magneta is my hero');
expect(logEles.count()).toBeGreaterThan(2);
var logCount;
logEles.count().then(function(count) {
logCount = count;
return sendKeys(inputEle, "-test-");
}).then(function() {
expect(childViewTextEle.getText()).toContain('-test-');
return logEles.count();
}).then(function(count) {
expect(logCount + 6).toEqual(count, "6 additional log messages should have been added");
logCount = count;
return buttonEle.click();
}).then(function() {
expect(childViewTextEle.isPresent()).toBe(false,"child view should no longer be part of the DOM");
sendKeys(inputEle, "-foo-");
expect(logEles.count()).toEqual(logCount, "no additional log messages should have been added");
});
});
it('should support "spy" hooks', function () {
var inputEle = element(by.css('spy-parent input'));
var addHeroButtonEle = element(by.cssContainingText('spy-parent button','Add Hero'));
var resetHeroesButtonEle = element(by.cssContainingText('spy-parent button','Reset Heroes'));
var heroEles = element.all(by.css('spy-parent div[my-spy'));
var logEles = element.all(by.css('spy-parent h4 ~ div'));
expect(heroEles.count()).toBe(2, 'should have two heroes displayed');
expect(logEles.count()).toBe(2, 'should have two log entries');
sendKeys(inputEle, "-test-").then(function() {
return addHeroButtonEle.click();
}).then(function() {
expect(heroEles.count()).toBe(3, 'should have added one hero');
expect(heroEles.get(2).getText()).toContain('-test-');
expect(logEles.count()).toBe(3, 'should now have 3 log entries');
return resetHeroesButtonEle.click();
}).then(function() {
expect(heroEles.count()).toBe(0, 'should no longer have any heroes');
expect(logEles.count()).toBe(7, 'should now have 7 log entries - 3 orig + 1 reset + 3 removeall');
})
});
it('should support "spy counter" hooks', function () {
var updateCounterButtonEle = element(by.cssContainingText('counter-parent button','Update'));
var resetCounterButtonEle = element(by.cssContainingText('counter-parent button','Reset'));
var textEle = element(by.css('counter-parent my-counter > div'));
var logEles = element.all(by.css('counter-parent h4 ~ div'));
expect(textEle.getText()).toContain('Counter = 0');
expect(logEles.count()).toBe(2, 'should start with two log entries');
updateCounterButtonEle.click().then(function() {
expect(textEle.getText()).toContain('Counter = 1');
expect(logEles.count()).toBe(3, 'should now have 3 log entries');
return resetCounterButtonEle.click();
}).then(function() {
expect(textEle.getText()).toContain('Counter = 0');
expect(logEles.count()).toBe(7, 'should now have 7 log entries - 3 prev + 1 reset + 2 destroy + 1 init');
})
});
// Hack - because of bug with send keys
function sendKeys(element, str) {
return str.split('').reduce(function (promise, char) {
return promise.then(function () {
return element.sendKeys(char);
});
}, element.getAttribute('value'));
// better to create a resolved promise here but ... don't know how with protractor;
}
});

View File

@ -10,7 +10,8 @@
"live": "live-server",
"start": "concurrent \"npm run tsc:w\" \"npm run lite\" ",
"test": "karma start karma.conf.js",
"build-and-test": "npm run tsc && npm run test"
"build-and-test": "npm run tsc && npm run test",
"http-server": "tsc && http-server"
},
"keywords": [],
"author": "",
@ -27,15 +28,16 @@
},
"devDependencies": {
"concurrently": "^1.0.0",
"lite-server": "^1.3.1",
"live-server": "^0.8.2",
"typescript": "^1.7.3",
"jasmine-core":"~2.1.0",
"http-server": "^0.8.5",
"jasmine-core": "~2.1.0",
"karma": "^0.12.23",
"karma-chrome-launcher": "^0.1.4",
"karma-cli": "^0.0.4",
"karma-jasmine": "^0.3.6",
"rimraf": "^2.4.3"
}
"lite-server": "^1.3.1",
"live-server": "^0.8.2",
"rimraf": "^2.4.3",
"typescript": "^1.7.3"
},
"repository": { }
}

View File

@ -0,0 +1,78 @@
describe('Pipes', function () {
beforeAll(function () {
browser.get('');
});
it('should open correctly', function () {
expect(element.all(by.css('h4')).get(0).getText()).toEqual('Hero Birthday v.1');
expect(element(by.css('body > hero-birthday p')).getText()).toEqual("The hero's birthday is Apr 15, 1988");
});
it('should show delayed message', function () {
expect(element.all(by.css('hero-message')).get(0).getText()).toEqual('Message: You are my Hero!');
});
it('should show 4 heroes', function () {
expect(element.all(by.css('hero-list div')).count()).toEqual(4);
});
it('should show 4 heroes in json', function () {
expect(element(by.cssContainingText('hero-list p', 'Heroes as JSON')).getText()).toContain('Bombasto');
});
it('should show alternate birthday formats', function () {
expect(element(by.cssContainingText('my-app > p', "The hero's birthday is Apr 15, 1988")).isDisplayed()).toBe(true);
expect(element(by.cssContainingText('my-app > p', "The hero's birthday is 04/15/88")).isDisplayed()).toBe(true);
});
it('should be able to toggle birthday formats', function () {
var birthDayEle = element(by.css('my-app > hero-birthday > p'));
expect(birthDayEle.getText()).toEqual("The hero's birthday is 4/15/1988");
var buttonEle = element(by.cssContainingText('my-app > hero-birthday > button', "Toggle Format"));
expect(buttonEle.isDisplayed()).toBe(true);
buttonEle.click().then(function() {
expect(birthDayEle.getText()).toEqual("The hero's birthday is Friday, April 15, 1988");
});
});
it('should be able to chain and compose pipes', function () {
var chainedPipeEles = element.all(by.cssContainingText('my-app p', "The chained hero's"));
expect(chainedPipeEles.count()).toBe(3, "should have 3 chained pipe examples");
expect(chainedPipeEles.get(0).getText()).toContain('APR 15, 1988');
expect(chainedPipeEles.get(1).getText()).toContain('FRIDAY, APRIL 15, 1988');
expect(chainedPipeEles.get(2).getText()).toContain('FRIDAY, APRIL 15, 1988');
});
it('should be able to use ExponentialStrengthPipe pipe', function () {
var ele = element(by.css('power-booster p'));
expect(ele.getText()).toContain('Super power boost: 1024');
});
it('should be able to use the exponential calculator', function () {
var eles = element.all(by.css('power-boost-calculator input'));
var baseInputEle = eles.get(0);
var factorInputEle = eles.get(1);
var outputEle = element(by.css('power-boost-calculator p'));
baseInputEle.clear().then(function() {
return sendKeys(baseInputEle, "7");
}).then(function() {
return factorInputEle.clear();
}).then(function() {
return sendKeys(factorInputEle, "3");
}).then(function() {
expect(outputEle.getText()).toContain("343");
});
});
// Hack - because of bug with send keys
function sendKeys(element, str) {
return str.split('').reduce(function (promise, char) {
return promise.then(function () {
return element.sendKeys(char);
});
}, element.getAttribute('value'));
// better to create a resolved promise here but ... don't know how with protractor;
}
});

View File

@ -0,0 +1,161 @@
// TO RUN THE TESTS
//
// The first time, run:
// ./node_modules/.bin/webdriver-manager update
// Make sure the test server is running. Then do.
// ./node_modules/.bin/protractor protractor.config.js
var fs = require('fs');
var path = require('canonical-path');
exports.config = {
directConnect: true,
// Capabilities to be passed to the webdriver instance.
capabilities: {
'browserName': 'chrome'
},
// Framework to use. Jasmine is recommended.
framework: 'jasmine',
// Spec patterns are relative to this config file
specs: ['**/*e2e-spec.js' ],
// For angular2 tests
useAllAngular2AppRoots: true,
baseUrl: 'http://localhost:8080',
// doesn't seem to work.
// resultJsonOutputFile: "foo.json",
onPrepare: function() {
// SpecReporter
//var SpecReporter = require('jasmine-spec-reporter');
//// add jasmine spec reporter
//jasmine.getEnv().addReporter(new SpecReporter({displayStacktrace: 'none'}));
// jasmine.getEnv().addReporter(new SpecReporter({displayStacktrace: 'all'}));
// debugging
console.log('browser.params:' + JSON.stringify(browser.params));
var appDir = browser.params.appDir;
if (appDir) {
if (appDir.match('/ts') != null) {
browser.appIsTs = true;
} else if (appDir.match('/js') != null) {
browser.appIsJs = true;
} else if (appDir.match('/dart') != null) {
browser.appIsDart = true;
} else {
browser.appIsUnknown = true;
}
} else {
browser.appIsUnknown = true;
}
jasmine.getEnv().addReporter(new Reporter( browser.params )) ;
global.describeIf = describeIf;
global.itIf = itIf;
},
jasmineNodeOpts: {
// defaultTimeoutInterval: 60000,
defaultTimeoutInterval: 10000,
showTiming: true,
print: function() {}
}
};
function describeIf(cond, name, func) {
if (cond) {
describe(name, func);
} else {
xdescribe('*** Skipped *** - ' + name, func);
}
}
function itIf(cond, name, func) {
if (cond) {
it(name, func);
} else {
xit('*** Skipped *** - ' + name, func);
}
}
function Reporter(options) {
this.defaultOutputFile = path.resolve(process.cwd(), "../../", 'protractor-results.txt');
var _output = [];
var _logDepth = 0;
var __pad = ' ';
var _currentSuite;
options.outputFile = options.outputFile || this.defaultOutputFile;
log('AppDir: ' + options.appDir);
this.suiteStarted = function(suite) {
_logDepth++;
log('Suite: ' + suite.description);
// debugging info
// log('Suite stringify:' + JSON.stringify(suite));
// log('argv stringify:' + JSON.stringify(process.argv));
_currentSuite = suite;
_currentSuite.ok = true;
};
this.suiteDone = function(suite) {
var status = _currentSuite.ok ? 'Suite passed: ' : 'Suite failed: ';
log(status + suite.description);
_logDepth--;
};
this.specStarted = function(spec) {
_logDepth++;
};
this.specDone = function(spec) {
log(spec.status + ": " + spec.description);
logFailedSpec(spec);
_logDepth--;
};
this.jasmineDone = function() {
outputFile = options.outputFile;
// console.log('Completed - appending output to: ' + outputFile);
fs.appendFileSync(outputFile, _output.join('\n')+ '\n\n');
};
function logFailedSpec(spec) {
if (spec.failedExpectations.length == 0) return;
_logDepth++;
spec.failedExpectations.forEach(function(exp) {
log('detail: ' + exp.message);
_currentSuite.ok = false;
});
_logDepth--;
}
function log(msg) {
msg = lpad(msg, 3 * _logDepth);
console.log(msg);
_output.push(msg);
}
function lpad(value, length) {
return __pad.substr(0,length) + value;
}
};
// To be copied into e2e-tests experiencing sendKeys bug.
//// Hack - because of bug with send keys
//function sendKeys(element, str) {
// return str.split('').reduce(function (promise, char) {
// return promise.then(function () {
// return element.sendKeys(char);
// });
// }, element.getAttribute('value'));
// // better to create a resolved promise here but ... don't know how with protractor;
//}

View File

@ -1,27 +1,15 @@
/*global browser, element, by */
describe('QuickStart E2E Tests', function () {
// #docregion shared
var expectedMsg = 'My First Angular 2 App';
// tests shared across languages
function sharedTests(basePath) {
beforeEach(function () {
browser.get(basePath + 'index.html');
});
it('should display: ' + expectedMsg, function () {
expect(element(by.css('h1')).getText()).toEqual(expectedMsg);
});
}
// #enddocregion
beforeEach(function () {
browser.get('');
});
describe('QuickStart in JavaScript', function () {
sharedTests('quickstart/js/');
});
describe('QuickStart in TypeScript', function () {
sharedTests('quickstart/ts/');
});
it('should display: ' + expectedMsg, function () {
expect(element(by.css('h1')).getText()).toEqual(expectedMsg);
});
});

View File

@ -0,0 +1,64 @@
describe('Structural Directives', function () {
// tests interact - so we need beforeEach instead of beforeAll
beforeEach(function () {
browser.get('');
});
it('should be able to use ngFor, ngIf and ngWhen together', function () {
var allDivEles = element.all(by.css('structural-directives > div'));
expect(allDivEles.get(0).getText()).toEqual('Mr. Nice');
expect(allDivEles.get(1).getText()).toEqual('Mr. Nice');
expect(allDivEles.get(4).getText()).toEqual('Ready');
});
it('should be able to toggle ngIf with a button', function () {
var setConditionButtonEle = element.all(by.css('button')).get(0);
var conditionTrueEles = element.all(by.cssContainingText('p', 'condition is true'));
var conditionFalseEles = element.all(by.cssContainingText('p', 'condition is false'));
expect(conditionTrueEles.count()).toBe(2, 'should be two condition true elements');
expect(conditionFalseEles.count()).toBe(0, 'should be no condition false elements');
setConditionButtonEle.click().then(function() {
expect(conditionTrueEles.count()).toBe(0, 'should be no condition true elements');
expect(conditionFalseEles.count()).toBe(2, 'should be two condition false elements');
});
});
it('should be able to compare use of ngIf with changing css visibility', function () {
var setConditionButtonEle = element.all(by.css('button')).get(0);
var ngIfButtonEle = element(by.cssContainingText('button', 'if | !if'));
var ngIfParentEle = ngIfButtonEle.element(by.xpath('..'));
var ngIfSiblingEle = ngIfParentEle.element(by.css('heavy-loader'));
var cssButtonEle = element(by.cssContainingText('button', 'show | hide'));
var cssSiblingEle = cssButtonEle.element(by.xpath('..')).element(by.css('heavy-loader'));
var setConditionText;
setConditionButtonEle.getText().then(function(text) {
setConditionText = text;
expect(ngIfButtonEle.isPresent()).toBe(true, 'should be able to find ngIfButton');
expect(cssButtonEle.isPresent()).toBe(true, 'should be able to find cssButton');
expect(ngIfParentEle.isPresent()).toBe(true, 'should be able to find ngIfButton parent');
expect(ngIfSiblingEle.isPresent()).toBe(true, 'should be able to find ngIfButton sibling');
expect(cssSiblingEle.isPresent()).toBe(true, 'should be able to find cssButton sibling');
return ngIfButtonEle.click();
}).then(function() {
expect(ngIfSiblingEle.isPresent()).toBe(false, 'now should NOT be able to find ngIfButton sibling');
expect(setConditionButtonEle.getText()).not.toEqual(setConditionText);
return cssButtonEle.click();
}).then(function() {
expect(cssSiblingEle.isPresent()).toBe(true, 'now should still be able to find cssButton sibling');
expect(cssSiblingEle.isDisplayed()).toBe(false, 'now cssButton sibling should NOT be visible');
return ngIfButtonEle.click();
}).then(function() {
expect(setConditionButtonEle.getText()).toEqual(setConditionText);
});
});
it('should be able to use *ngIf ', function () {
var setConditionButtonEle = element.all(by.css('button')).get(0);
var displayEles = element.all(by.cssContainingText('p', 'Our heroes are true!'));
expect(displayEles.count()).toBe(2, "should be displaying two ngIf elements");
setConditionButtonEle.click().then(function() {
expect(displayEles.count()).toBe(0, "should nog longer be displaying ngIf elements");
});
});
});

View File

@ -0,0 +1,29 @@
// Not yet complete
describe('Template Syntax', function () {
beforeAll(function () {
browser.get('');
});
it('should be able to use interpolation with a hero', function () {
var heroInterEle = element.all(by.css('h2+p')).get(0);
expect(heroInterEle.getText()).toEqual('My current hero is Hercules');
});
it('should be able to use interpolation with a calculation', function () {
var theSumEles = element.all(by.cssContainingText('h3~p','The sum of'));
expect(theSumEles.count()).toBe(2);
expect(theSumEles.get(0).getText()).toEqual('The sum of 1 + 1 is 2');
expect(theSumEles.get(1).getText()).toEqual('The sum of 1 + 1 is not 4');
});
it('should be able to use class binding syntax', function () {
var specialEle = element(by.cssContainingText('div','Special'));
expect(specialEle.getAttribute('class')).toMatch('special');
});
it('should be able to use style binding syntax', function () {
var specialButtonEle = element(by.cssContainingText('div.special~button', 'button'));
expect(specialButtonEle.getAttribute('style')).toMatch('color: red');
});
});

View File

@ -0,0 +1,46 @@
// Not yet complete
describe('Tutorial', function () {
beforeAll(function () {
browser.get('');
init();
});
var _hrefEles, _tohDashboardHrefEle, _tohHeroesHrefEle;
function init() {
_hrefEles = element.all(by.css('my-app a'));
_tohDashboardHrefEle = _hrefEles.get(0);
_tohHeroesHrefEle = _hrefEles.get(1);
}
it('should be able to see the start screen', function () {
expect(_hrefEles.count()).toEqual(2, 'should be two dashboard choices');
expect(_tohDashboardHrefEle.getText()).toEqual("Dashboard");
expect(_tohHeroesHrefEle.getText()).toEqual("Heroes");
});
it('should be able to see dashboard choices', function () {
var dashboardHeroEles = element.all(by.css('my-app my-dashboard .module.hero'));
expect(dashboardHeroEles.count()).toBe(4, "should be 4 dashboard hero choices");
});
it('should be able to toggle the views', function () {
var dashboardEle = element(by.css('my-app my-dashboard'));
expect(dashboardEle.element(by.css('h3')).getText()).toEqual('Top Heroes');
_tohHeroesHrefEle.click().then(function() {
expect(dashboardEle.isPresent()).toBe(false, 'should no longer see dashboard element');
var heroEles = element.all(by.css('my-app my-heroes li'));
expect(heroEles.count()).toBeGreaterThan(4, "should be more than 4 heroes shown");
return _tohDashboardHrefEle.click();
}).then(function() {
expect(dashboardEle.isPresent()).toBe(true, 'should once again see the dashboard element');
});
});
});