diff --git a/README.md b/README.md index c36cbea653..42cd060282 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ as described above. An example of this is expressed in `platform/framework`, which follows bundle conventions. -### Regression Testing +### Functional Testing The tests described above are all at the unit-level; an additional test suite using [Protractor](https://angular.github.io/protractor/) @@ -76,9 +76,9 @@ us under development, in the `protractor` folder. To run: * Install protractor following the instructions above. -* `webdriver-manager start` -* `node app.js -p 1984 -x platform/persistence/elastic -i example/persistence -* `protractor protractor/conf.js` +* `cd protractor` +* `npm install` +* `npm run all` ## Build diff --git a/bundles.json b/bundles.json index 0b97f1abab..898ca3d738 100644 --- a/bundles.json +++ b/bundles.json @@ -21,6 +21,7 @@ "platform/persistence/queue", "platform/policy", "platform/entanglement", + "platform/search", "example/imagery", "example/persistence", diff --git a/circle.yml b/circle.yml index b8f367a604..2a79a5ed93 100644 --- a/circle.yml +++ b/circle.yml @@ -4,3 +4,7 @@ deployment: commands: - ./build-docs.sh - git push git@heroku.com:openmctweb-demo.git $CIRCLE_SHA1:refs/heads/master + openmctweb-staging-un: + branch: search + heroku: + appname: openmctweb-staging-un diff --git a/platform/commonUI/general/res/css/theme-espresso.css b/platform/commonUI/general/res/css/theme-espresso.css index e3bbcd7448..26d9f9c142 100644 --- a/platform/commonUI/general/res/css/theme-espresso.css +++ b/platform/commonUI/general/res/css/theme-espresso.css @@ -92,7 +92,7 @@ * this source code distribution or the Licensing information page available * at runtime from the About dialog for additional information. *****************************************************************************/ -/* line 5, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ +/* line 5, ../../../../../../../../.gem/ruby/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, @@ -113,38 +113,38 @@ time, mark, audio, video { font-size: 100%; vertical-align: baseline; } -/* line 22, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ +/* line 22, ../../../../../../../../.gem/ruby/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ html { line-height: 1; } -/* line 24, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ +/* line 24, ../../../../../../../../.gem/ruby/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ ol, ul { list-style: none; } -/* line 26, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ +/* line 26, ../../../../../../../../.gem/ruby/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ table { border-collapse: collapse; border-spacing: 0; } -/* line 28, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ +/* line 28, ../../../../../../../../.gem/ruby/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ caption, th, td { text-align: left; font-weight: normal; vertical-align: middle; } -/* line 30, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ +/* line 30, ../../../../../../../../.gem/ruby/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ q, blockquote { quotes: none; } - /* line 103, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ + /* line 103, ../../../../../../../../.gem/ruby/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ q:before, q:after, blockquote:before, blockquote:after { content: ""; content: none; } -/* line 32, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ +/* line 32, ../../../../../../../../.gem/ruby/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ a img { border: none; } -/* line 116, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ +/* line 116, ../../../../../../../../.gem/ruby/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section, summary { display: block; } diff --git a/platform/persistence/elastic/src/ElasticsearchSearchProvider.js b/platform/persistence/elastic/src/ElasticsearchSearchProvider.js new file mode 100644 index 0000000000..af13628af9 --- /dev/null +++ b/platform/persistence/elastic/src/ElasticsearchSearchProvider.js @@ -0,0 +1,213 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define*/ + +/** + * Module defining ElasticsearchSearchProvider. Created by shale on 07/16/2015. + * This is not currently included in the bundle definition. + */ +define( + [], + function () { + "use strict"; + + // JSLint doesn't like underscore-prefixed properties, + // so hide them here. + var ID = "_id", + SCORE = "_score", + DEFAULT_MAX_RESULTS = 100; + + /** + * A search service which searches through domain objects in + * the filetree using ElasticSearch. + * + * @constructor + * @param $http Angular's $http service, for working with urls. + * @param {ObjectService} objectService the service from which + * domain objects can be gotten. + * @param ROOT the constant ELASTIC_ROOT which allows us to + * interact with ElasticSearch. + */ + function ElasticsearchSearchProvider($http, objectService, ROOT) { + + // Add the fuzziness operator to the search term + function addFuzziness(searchTerm, editDistance) { + if (!editDistance) { + editDistance = ''; + } + + return searchTerm.split(' ').map(function (s) { + // Don't add fuzziness for quoted strings + if (s.indexOf('"') !== -1) { + return s; + } else { + return s + '~' + editDistance; + } + }).join(' '); + } + + // Currently specific to elasticsearch + function processSearchTerm(searchTerm) { + var spaceIndex; + + // Cut out any extra spaces + while (searchTerm.substr(0, 1) === ' ') { + searchTerm = searchTerm.substring(1, searchTerm.length); + } + while (searchTerm.substr(searchTerm.length - 1, 1) === ' ') { + searchTerm = searchTerm.substring(0, searchTerm.length - 1); + } + spaceIndex = searchTerm.indexOf(' '); + while (spaceIndex !== -1) { + searchTerm = searchTerm.substring(0, spaceIndex) + + searchTerm.substring(spaceIndex + 1, searchTerm.length); + spaceIndex = searchTerm.indexOf(' '); + } + + // Add fuzziness for completeness + searchTerm = addFuzziness(searchTerm); + + return searchTerm; + } + + // Processes results from the format that elasticsearch returns to + // a list of searchResult objects, then returns a result object + // (See documentation for query for object descriptions) + function processResults(rawResults, timestamp) { + var results = rawResults.data.hits.hits, + resultsLength = results.length, + ids = [], + scores = {}, + searchResults = [], + i; + + // Get the result objects' IDs + for (i = 0; i < resultsLength; i += 1) { + ids.push(results[i][ID]); + } + + // Get the result objects' scores + for (i = 0; i < resultsLength; i += 1) { + scores[ids[i]] = results[i][SCORE]; + } + + // Get the domain objects from their IDs + return objectService.getObjects(ids).then(function (objects) { + var j, + id; + + for (j = 0; j < resultsLength; j += 1) { + id = ids[j]; + + // Include items we can get models for + if (objects[id].getModel) { + // Format the results as searchResult objects + searchResults.push({ + id: id, + object: objects[id], + score: scores[id] + }); + } + } + + return { + hits: searchResults, + total: rawResults.data.hits.total, + timedOut: rawResults.data.timed_out + }; + }); + } + + // For documentation, see query below. + function query(searchTerm, timestamp, maxResults, timeout) { + var esQuery; + + // Check to see if the user provided a maximum + // number of results to display + if (!maxResults) { + // Else, we provide a default value. + maxResults = DEFAULT_MAX_RESULTS; + } + + // If the user input is empty, we want to have no search results. + if (searchTerm !== '' && searchTerm !== undefined) { + // Process the search term + searchTerm = processSearchTerm(searchTerm); + + // Create the query to elasticsearch + esQuery = ROOT + "/_search/?q=" + searchTerm + + "&size=" + maxResults; + if (timeout) { + esQuery += "&timeout=" + timeout; + } + + // Get the data... + return $http({ + method: "GET", + url: esQuery + }).then(function (rawResults) { + // ...then process the data + return processResults(rawResults, timestamp); + }, function (err) { + // In case of error, return nothing. (To prevent + // infinite loading time.) + return {hits: [], total: 0}; + }); + } else { + return {hits: [], total: 0}; + } + } + + return { + /** + * Searches through the filetree for domain objects using a search + * term. This is done through querying elasticsearch. Returns a + * promise for a result object that has the format + * {hits: searchResult[], total: number, timedOut: boolean} + * where a searchResult has the format + * {id: string, object: domainObject, score: number} + * + * Notes: + * * The order of the results is from highest to lowest score, + * as elsaticsearch determines them to be. + * * Uses the fuzziness operator to get more results. + * * More about this search's behavior at + * https://www.elastic.co/guide/en/elasticsearch/reference/current/search-uri-request.html + * + * @param searchTerm The text input that is the query. + * @param timestamp The time at which this function was called. + * This timestamp is used as a unique identifier for this + * query and the corresponding results. + * @param maxResults (optional) The maximum number of results + * that this function should return. + * @param timeout (optional) The time after which the search should + * stop calculations and return partial results. Elasticsearch + * does not guarentee that this timeout will be strictly followed. + */ + query: query + }; + } + + + return ElasticsearchSearchProvider; + } +); \ No newline at end of file diff --git a/platform/persistence/elastic/test/ElasticsearchSearchProviderSpec.js b/platform/persistence/elastic/test/ElasticsearchSearchProviderSpec.js new file mode 100644 index 0000000000..1202ef77e6 --- /dev/null +++ b/platform/persistence/elastic/test/ElasticsearchSearchProviderSpec.js @@ -0,0 +1,115 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,describe,it,expect,beforeEach,jasmine*/ + +/** + * SearchSpec. Created by shale on 07/31/2015. + */ +define( + ["../src/ElasticsearchSearchProvider"], + function (ElasticsearchSearchProvider) { + "use strict"; + + // JSLint doesn't like underscore-prefixed properties, + // so hide them here. + var ID = "_id", + SCORE = "_score"; + + describe("The ElasticSearch search provider ", function () { + var mockHttp, + mockHttpPromise, + mockObjectPromise, + mockObjectService, + mockDomainObject, + provider, + mockProviderResults; + + beforeEach(function () { + mockHttp = jasmine.createSpy("$http"); + mockHttpPromise = jasmine.createSpyObj( + "promise", + [ "then" ] + ); + mockHttp.andReturn(mockHttpPromise); + // allow chaining of promise.then().catch(); + mockHttpPromise.then.andReturn(mockHttpPromise); + + mockObjectService = jasmine.createSpyObj( + "objectService", + [ "getObjects" ] + ); + mockObjectPromise = jasmine.createSpyObj( + "promise", + [ "then" ] + ); + mockObjectService.getObjects.andReturn(mockObjectPromise); + + mockDomainObject = jasmine.createSpyObj( + "domainObject", + [ "getId", "getModel" ] + ); + + provider = new ElasticsearchSearchProvider(mockHttp, mockObjectService, ""); + provider.query(' test "query" ', 0, undefined, 1000); + }); + + it("sends a query to ElasticSearch", function () { + expect(mockHttp).toHaveBeenCalled(); + }); + + it("gets data from ElasticSearch", function () { + var data = { + hits: { + hits: [ + {}, + {} + ], + total: 0 + }, + timed_out: false + }; + data.hits.hits[0][ID] = 1; + data.hits.hits[0][SCORE] = 1; + data.hits.hits[1][ID] = 2; + data.hits.hits[1][SCORE] = 2; + + mockProviderResults = mockHttpPromise.then.mostRecentCall.args[0]({data: data}); + + expect( + mockObjectPromise.then.mostRecentCall.args[0]({ + 1: mockDomainObject, + 2: mockDomainObject + }).hits.length + ).toEqual(2); + }); + + it("returns nothing for an empty string query", function () { + expect(provider.query("").hits).toEqual([]); + }); + + it("returns something when there is an ElasticSearch error", function () { + mockProviderResults = mockHttpPromise.then.mostRecentCall.args[1](); + expect(mockProviderResults).toBeDefined(); + }); + }); + } +); \ No newline at end of file diff --git a/platform/persistence/elastic/test/suite.json b/platform/persistence/elastic/test/suite.json index cc8dc2ce0c..939244c089 100644 --- a/platform/persistence/elastic/test/suite.json +++ b/platform/persistence/elastic/test/suite.json @@ -1,4 +1,5 @@ [ "ElasticIndicator", - "ElasticPersistenceProvider" + "ElasticPersistenceProvider", + "ElasticsearchSearchProvider" ] diff --git a/platform/search/bundle.json b/platform/search/bundle.json new file mode 100644 index 0000000000..6668022939 --- /dev/null +++ b/platform/search/bundle.json @@ -0,0 +1,33 @@ +{ + "name": "Search", + "description": "Allows the user to search through the file tree.", + "extensions": { + "constants": [ + { + "key": "GENERIC_SEARCH_ROOTS", + "value": [ "ROOT" ], + "priority": "fallback" + } + ], + "components": [ + { + "provides": "searchService", + "type": "provider", + "implementation": "GenericSearchProvider.js", + "depends": [ "$q", "$timeout", "objectService", "workerService", "GENERIC_SEARCH_ROOTS" ] + }, + { + "provides": "searchService", + "type": "aggregator", + "implementation": "SearchAggregator.js", + "depends": [ "$q" ] + } + ], + "workers": [ + { + "key": "genericSearchWorker", + "scriptUrl": "GenericSearchWorker.js" + } + ] + } +} diff --git a/platform/search/src/GenericSearchProvider.js b/platform/search/src/GenericSearchProvider.js new file mode 100644 index 0000000000..dae2cab9a9 --- /dev/null +++ b/platform/search/src/GenericSearchProvider.js @@ -0,0 +1,268 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define*/ + +/** + * Module defining GenericSearchProvider. Created by shale on 07/16/2015. + */ +define( + [], + function () { + "use strict"; + + var DEFAULT_MAX_RESULTS = 100, + DEFAULT_TIMEOUT = 1000, + stopTime; + + /** + * A search service which searches through domain objects in + * the filetree without using external search implementations. + * + * @constructor + * @param $q Angular's $q, for promise consolidation. + * @param $timeout Angular's $timeout, for delayed function execution. + * @param {ObjectService} objectService The service from which + * domain objects can be gotten. + * @param {WorkerService} workerService The service which allows + * more easy creation of web workers. + * @param {GENERIC_SEARCH_ROOTS} ROOTS An array of the root + * domain objects' IDs. + */ + function GenericSearchProvider($q, $timeout, objectService, workerService, ROOTS) { + var worker = workerService.run('genericSearchWorker'), + indexed = {}, + pendingQueries = {}; + // pendingQueries is a dictionary with the key value pairs st + // the key is the timestamp and the value is the promise + + // Tell the web worker to add a domain object's model to its list of items. + function indexItem(domainObject) { + var message; + + // undefined check + if (domainObject && domainObject.getModel) { + // Using model instead of whole domain object because + // it's a JSON object. + message = { + request: 'index', + model: domainObject.getModel(), + id: domainObject.getId() + }; + worker.postMessage(message); + } + } + + // Tell the worker to search for items it has that match this searchInput. + // Takes the searchInput, as well as a max number of results (will return + // less than that if there are fewer matches). + function workerSearch(searchInput, maxResults, timestamp, timeout) { + var message = { + request: 'search', + input: searchInput, + maxNumber: maxResults, + timestamp: timestamp, + timeout: timeout + }; + worker.postMessage(message); + } + + // Handles responses from the web worker. Namely, the results of + // a search request. + function handleResponse(event) { + var ids = [], + id; + + // If we have the results from a search + if (event.data.request === 'search') { + // Convert the ids given from the web worker into domain objects + for (id in event.data.results) { + ids.push(id); + } + objectService.getObjects(ids).then(function (objects) { + var searchResults = [], + id; + + // Create searchResult objects + for (id in objects) { + searchResults.push({ + object: objects[id], + id: id, + score: event.data.results[id] + }); + } + + // Resove the promise corresponding to this + pendingQueries[event.data.timestamp].resolve({ + hits: searchResults, + total: event.data.total, + timedOut: event.data.timedOut + }); + }); + } + } + + worker.onmessage = handleResponse; + + // Helper function for getItems(). Indexes the tree. + function indexItems(nodes) { + nodes.forEach(function (node) { + var id = node && node.getId && node.getId(); + + // If we have already indexed this item, stop here + if (indexed[id]) { + return; + } + + // Index each item with the web worker + indexItem(node); + indexed[id] = true; + + + // If this node has children, index those + if (node && node.hasCapability && node.hasCapability('composition')) { + // Make sure that this is async, so doesn't block up page + $timeout(function () { + // Get the children... + node.useCapability('composition').then(function (children) { + $timeout(function () { + // ... then index the children + if (children.constructor === Array) { + indexItems(children); + } else { + indexItems([children]); + } + }, 0); + }); + }, 0); + } + + // Watch for changes to this item, in case it gets new children + if (node && node.hasCapability && node.hasCapability('mutation')) { + node.getCapability('mutation').listen(function (listener) { + if (listener && listener.composition) { + // If the node was mutated to have children, get the child domain objects + objectService.getObjects(listener.composition).then(function (objectsById) { + var objects = [], + id; + + // Get each of the domain objects in objectsById + for (id in objectsById) { + objects.push(objectsById[id]); + } + + indexItems(objects); + }); + } + }); + } + }); + } + + // Converts the filetree into a list + function getItems() { + // Aquire root objects + objectService.getObjects(ROOTS).then(function (objectsById) { + var objects = [], + id; + + // Get each of the domain objects in objectsById + for (id in objectsById) { + objects.push(objectsById[id]); + } + + // Index all of the roots' descendents + indexItems(objects); + }); + } + + // For documentation, see query below + function query(input, timestamp, maxResults, timeout) { + var terms = [], + searchResults = [], + defer = $q.defer(); + + // If the input is nonempty, do a search + if (input !== '' && input !== undefined) { + + // Allow us to access this promise later to resolve it later + pendingQueries[timestamp] = defer; + + // Check to see if the user provided a maximum + // number of results to display + if (!maxResults) { + // Else, we provide a default value + maxResults = DEFAULT_MAX_RESULTS; + } + // Similarly, check if timeout was provided + if (!timeout) { + timeout = DEFAULT_TIMEOUT; + } + + // Send the query to the worker + workerSearch(input, maxResults, timestamp, timeout); + + return defer.promise; + } else { + // Otherwise return an empty result + return {hits: [], total: 0}; + } + } + + // Index the tree's contents once at the beginning + getItems(); + + return { + /** + * Searches through the filetree for domain objects which match + * the search term. This function is to be used as a fallback + * in the case where other search services are not avaliable. + * Returns a promise for a result object that has the format + * {hits: searchResult[], total: number, timedOut: boolean} + * where a searchResult has the format + * {id: string, object: domainObject, score: number} + * + * Notes: + * * The order of the results is not guarenteed. + * * A domain object qualifies as a match for a search input if + * the object's name property contains any of the search terms + * (which are generated by splitting the input at spaces). + * * Scores are higher for matches that have more of the terms + * as substrings. + * + * @param input The text input that is the query. + * @param timestamp The time at which this function was called. + * This timestamp is used as a unique identifier for this + * query and the corresponding results. + * @param maxResults (optional) The maximum number of results + * that this function should return. + * @param timeout (optional) The time after which the search should + * stop calculations and return partial results. + */ + query: query + + }; + } + + + return GenericSearchProvider; + } +); \ No newline at end of file diff --git a/platform/search/src/GenericSearchWorker.js b/platform/search/src/GenericSearchWorker.js new file mode 100644 index 0000000000..69e4104602 --- /dev/null +++ b/platform/search/src/GenericSearchWorker.js @@ -0,0 +1,185 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global self*/ + +/** + * Module defining GenericSearchWorker. Created by shale on 07/21/2015. + */ +(function () { + "use strict"; + + // An array of objects composed of domain object IDs and models + // {id: domainObject's ID, model: domainObject's model} + var indexedItems = []; + + // Helper function for index() + // Checks whether an item with this ID is already indexed + function conainsItem(id) { + var i; + for (i = 0; i < indexedItems.length; i += 1) { + if (indexedItems[i].id === id) { + return true; + } + } + return false; + } + + /** + * Indexes an item to indexedItems. + * + * @param data An object which contains: + * * model: The model of the domain object + * * id: The ID of the domain object + */ + function index(data) { + var message; + + if (!conainsItem(data.id)) { + indexedItems.push({ + id: data.id, + model: data.model + }); + } + } + + // Helper function for serach() + function convertToTerms(input) { + var terms = input; + // Shave any spaces off of the ends of the input + while (terms.substr(0, 1) === ' ') { + terms = terms.substring(1, terms.length); + } + while (terms.substr(terms.length - 1, 1) === ' ') { + terms = terms.substring(0, terms.length - 1); + } + + // Then split it at spaces and asterisks + terms = terms.split(/ |\*/); + + // Remove any empty strings from the terms + while (terms.indexOf('') !== -1) { + terms.splice(terms.indexOf(''), 1); + } + + return terms; + } + + // Helper function for search() + function scoreItem(item, input, terms) { + var name = item.model.name.toLocaleLowerCase(), + weight = 0.65, + score = 0.0, + i; + + // Make the score really big if the item name and + // the original search input are the same + if (name === input) { + score = 42; + } + + for (i = 0; i < terms.length; i += 1) { + // Increase the score if the term is in the item name + if (name.indexOf(terms[i]) !== -1) { + score += 1; + + // Add extra to the score if the search term exists + // as its own term within the items + if (name.split(' ').indexOf(terms[i]) !== -1) { + score += 0.5; + } + } + } + + return score * weight; + } + + /** + * Gets search results from the indexedItems based on provided search + * input. Returns matching results from indexedItems, as well as the + * timestamp that was passed to it. + * + * @param data An object which contains: + * * input: The original string which we are searching with + * * maxNumber: The maximum number of search results desired + * * timestamp: The time identifier from when the query was made + */ + function search(data) { + // This results dictionary will have domain object ID keys which + // point to the value the domain object's score. + var results = {}, + input = data.input.toLocaleLowerCase(), + terms = convertToTerms(input), + message = { + request: 'search', + results: {}, + total: 0, + timestamp: data.timestamp, + timedOut: false + }, + score, + i, + id; + + // If the user input is empty, we want to have no search results. + if (input !== '') { + for (i = 0; i < indexedItems.length; i += 1) { + // If this is taking too long, then stop + if (Date.now() > data.timestamp + data.timeout) { + message.timedOut = true; + break; + } + + // Score and add items + score = scoreItem(indexedItems[i], input, terms); + if (score > 0) { + results[indexedItems[i].id] = score; + message.total += 1; + } + } + } + + // Truncate results if there are more than maxResults + if (message.total > data.maxResults) { + i = 0; + for (id in results) { + message.results[id] = results[id]; + i += 1; + if (i >= data.maxResults) { + break; + } + } + // TODO: This seems inefficient. + } else { + message.results = results; + } + + return message; + } + + self.onmessage = function (event) { + if (event.data.request === 'index') { + index(event.data); + } else if (event.data.request === 'search') { + self.postMessage(search(event.data)); + } + }; +}()); \ No newline at end of file diff --git a/platform/search/src/SearchAggregator.js b/platform/search/src/SearchAggregator.js new file mode 100644 index 0000000000..da267214bf --- /dev/null +++ b/platform/search/src/SearchAggregator.js @@ -0,0 +1,146 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define*/ + +/** + * Module defining SearchAggregator. Created by shale on 07/16/2015. + */ +define( + [], + function () { + "use strict"; + + var DEFUALT_TIMEOUT = 1000, + DEFAULT_MAX_RESULTS = 100; + + /** + * Allows multiple services which provide search functionality + * to be treated as one. + * + * @constructor + * @param $q Angular's $q, for promise consolidation. + * @param {SearchProvider[]} providers The search providers to be + * aggregated. + */ + function SearchAggregator($q, providers) { + + // Remove duplicate objects that have the same ID. Modifies the passed + // array, and returns the number that were removed. + function filterDuplicates(results, total) { + var ids = {}, + numRemoved = 0, + i; + + for (i = 0; i < results.length; i += 1) { + if (ids[results[i].id]) { + // If this result's ID is already there, remove the object + results.splice(i, 1); + numRemoved += 1; + + // Reduce loop index because we shortened the array + i -= 1; + } else { + // Otherwise add the ID to the list of the ones we have seen + ids[results[i].id] = true; + } + } + + return numRemoved; + } + + // Order the objects from highest to lowest score in the array. + // Modifies the passed array, as well as returns the modified array. + function orderByScore(results) { + results.sort(function (a, b) { + if (a.score > b.score) { + return -1; + } else if (b.score > a.score) { + return 1; + } else { + return 0; + } + }); + return results; + } + + // For documentation, see query below. + function queryAll(inputText, maxResults) { + var i, + timestamp = Date.now(), + resultPromises = []; + + if (!maxResults) { + maxResults = DEFAULT_MAX_RESULTS; + } + + // Send the query to all the providers + for (i = 0; i < providers.length; i += 1) { + resultPromises.push( + providers[i].query(inputText, timestamp, maxResults, DEFUALT_TIMEOUT) + ); + } + + // Get promises for results arrays + return $q.all(resultPromises).then(function (resultObjects) { + var results = [], + totalSum = 0, + i; + + // Merge results + for (i = 0; i < resultObjects.length; i += 1) { + results = results.concat(resultObjects[i].hits); + totalSum += resultObjects[i].total; + } + // Order by score first, so that when removing repeats we keep the higher scored ones + orderByScore(results); + totalSum -= filterDuplicates(results, totalSum); + + return { + hits: results, + total: totalSum, + timedOut: resultObjects.some(function (obj) { + return obj.timedOut; + }) + }; + }); + } + + return { + /** + * Sends a query to each of the providers. Returns a promise for + * a result object that has the format + * {hits: searchResult[], total: number, timedOut: boolean} + * where a searchResult has the format + * {id: string, object: domainObject, score: number} + * + * @param inputText The text input that is the query. + * @param maxResults (optional) The maximum number of results + * that this function should return. If not provided, a + * default of 100 will be used. + */ + query: queryAll + }; + } + + return SearchAggregator; + } +); \ No newline at end of file diff --git a/platform/search/test/GenericSearchProviderSpec.js b/platform/search/test/GenericSearchProviderSpec.js new file mode 100644 index 0000000000..bec02653b8 --- /dev/null +++ b/platform/search/test/GenericSearchProviderSpec.js @@ -0,0 +1,157 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,describe,it,expect,beforeEach,jasmine*/ + +/** + * SearchSpec. Created by shale on 07/31/2015. + */ +define( + ["../src/GenericSearchProvider"], + function (GenericSearchProvider) { + "use strict"; + + describe("The generic search provider ", function () { + var mockQ, + mockTimeout, + mockDeferred, + mockObjectService, + mockObjectPromise, + mockDomainObjects, + mockCapability, + mockCapabilityPromise, + mockWorkerService, + mockWorker, + mockRoots = ['root1', 'root2'], + provider, + mockProviderResults; + + beforeEach(function () { + var i; + + mockQ = jasmine.createSpyObj( + "$q", + [ "defer" ] + ); + mockDeferred = jasmine.createSpyObj( + "deferred", + [ "resolve", "reject"] + ); + mockDeferred.promise = "mock promise"; + mockQ.defer.andReturn(mockDeferred); + + mockTimeout = jasmine.createSpy("$timeout"); + + mockObjectService = jasmine.createSpyObj( + "objectService", + [ "getObjects" ] + ); + mockObjectPromise = jasmine.createSpyObj( + "promise", + [ "then", "catch" ] + ); + mockObjectService.getObjects.andReturn(mockObjectPromise); + + + mockWorkerService = jasmine.createSpyObj( + "workerService", + [ "run" ] + ); + mockWorker = jasmine.createSpyObj( + "worker", + [ "postMessage" ] + ); + mockWorkerService.run.andReturn(mockWorker); + + mockDomainObjects = {}; + for (i = 0; i < 4; i += 1) { + mockDomainObjects[i] = ( + jasmine.createSpyObj( + "domainObject", + [ "getId", "getModel", "hasCapability", "getCapability", "useCapability" ] + ) + ); + mockDomainObjects[i].getId.andReturn(i); + mockDomainObjects[i].getCapability.andReturn(mockCapability); + } + // Give the first object children + mockDomainObjects[0].hasCapability.andReturn(true); + mockCapability = jasmine.createSpyObj( + "capability", + [ "invoke", "listen" ] + ); + mockCapabilityPromise = jasmine.createSpyObj( + "promise", + [ "then", "catch" ] + ); + mockCapability.invoke.andReturn(mockCapabilityPromise); + mockDomainObjects[0].getCapability.andReturn(mockCapability); + + provider = new GenericSearchProvider(mockQ, mockTimeout, mockObjectService, mockWorkerService, mockRoots); + }); + + it("indexes tree on initialization", function () { + expect(mockObjectService.getObjects).toHaveBeenCalled(); + expect(mockObjectPromise.then).toHaveBeenCalled(); + + mockObjectPromise.then.mostRecentCall.args[0](mockDomainObjects); + + //mockCapabilityPromise.then.mostRecentCall.args[0](mockDomainObjects[1]); + + expect(mockWorker.postMessage).toHaveBeenCalled(); + }); + + it("sends search queries to the worker", function () { + var timestamp = Date.now(); + provider.query(' test "query" ', timestamp, 1, 2); + expect(mockWorker.postMessage).toHaveBeenCalledWith({ + request: "search", + input: ' test "query" ', + timestamp: timestamp, + maxNumber: 1, + timeout: 2 + }); + }); + + it("handles responses from the worker", function () { + var timestamp = Date.now(), + event = { + data: { + request: "search", + results: { + 1: 1, + 2: 2 + }, + total: 2, + timedOut: false, + timestamp: timestamp + } + }; + + provider.query(' test "query" ', timestamp); + mockWorker.onmessage(event); + mockObjectPromise.then.mostRecentCall.args[0](mockDomainObjects); + expect(mockDeferred.resolve).toHaveBeenCalled(); + }); + + }); + } +); \ No newline at end of file diff --git a/platform/search/test/GenericSearchWorkerSpec.js b/platform/search/test/GenericSearchWorkerSpec.js new file mode 100644 index 0000000000..2e17858400 --- /dev/null +++ b/platform/search/test/GenericSearchWorkerSpec.js @@ -0,0 +1,132 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,describe,it,expect,runs,waitsFor,beforeEach,jasmine,Worker,require*/ + +/** + * SearchSpec. Created by shale on 07/31/2015. + */ +define( + [], + function () { + "use strict"; + + describe("The generic search worker ", function () { + // If this test fails, make sure this path is correct + var worker = new Worker(require.toUrl('platform/search/src/GenericSearchWorker.js')), + numObjects = 5; + + beforeEach(function () { + var i; + for (i = 0; i < numObjects; i += 1) { + worker.postMessage( + { + request: "index", + id: i, + model: { + name: "object " + i, + id: i, + type: "something" + } + } + ); + } + }); + + it("searches can reach all objects", function () { + var flag = false, + workerOutput, + resultsLength = 0; + + // Search something that should return all objects + runs(function () { + worker.postMessage( + { + request: "search", + input: "object", + maxNumber: 100, + timestamp: Date.now(), + timeout: 1000 + } + ); + }); + + worker.onmessage = function (event) { + var id; + + workerOutput = event.data; + for (id in workerOutput.results) { + resultsLength += 1; + } + flag = true; + }; + + waitsFor(function () { + return flag; + }, "The worker should be searching", 1000); + + runs(function () { + expect(workerOutput).toBeDefined(); + expect(resultsLength).toEqual(numObjects); + }); + }); + + it("searches return only matches", function () { + var flag = false, + workerOutput, + resultsLength = 0; + + // Search something that should return 1 object + runs(function () { + worker.postMessage( + { + request: "search", + input: "2", + maxNumber: 100, + timestamp: Date.now(), + timeout: 1000 + } + ); + }); + + worker.onmessage = function (event) { + var id; + + workerOutput = event.data; + for (id in workerOutput.results) { + resultsLength += 1; + } + flag = true; + }; + + waitsFor(function () { + return flag; + }, "The worker should be searching", 1000); + + runs(function () { + expect(workerOutput).toBeDefined(); + expect(resultsLength).toEqual(1); + expect(workerOutput.results[2]).toBeDefined(); + }); + }); + }); + } +); \ No newline at end of file diff --git a/platform/search/test/SearchAggregatorSpec.js b/platform/search/test/SearchAggregatorSpec.js new file mode 100644 index 0000000000..cf35e2928e --- /dev/null +++ b/platform/search/test/SearchAggregatorSpec.js @@ -0,0 +1,101 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,describe,it,expect,beforeEach,jasmine*/ + +/** + * SearchSpec. Created by shale on 07/31/2015. + */ +define( + ["../src/SearchAggregator"], + function (SearchAggregator) { + "use strict"; + + describe("The search aggregator ", function () { + var mockQ, + mockPromise, + mockProviders = [], + aggregator, + mockProviderResults = [], + mockAggregatorResults, + i; + + beforeEach(function () { + mockQ = jasmine.createSpyObj( + "$q", + [ "all" ] + ); + mockPromise = jasmine.createSpyObj( + "promise", + [ "then" ] + ); + for (i = 0; i < 3; i += 1) { + mockProviders.push( + jasmine.createSpyObj( + "mockProvider" + i, + [ "query" ] + ) + ); + mockProviders[i].query.andReturn(mockPromise); + } + mockQ.all.andReturn(mockPromise); + + aggregator = new SearchAggregator(mockQ, mockProviders); + aggregator.query(); + + for (i = 0; i < mockProviders.length; i += 1) { + mockProviderResults.push({ + hits: [ + { + id: i, + score: 42 - i + }, + { + id: i + 1, + score: 42 - (2 * i) + } + ] + }); + } + mockAggregatorResults = mockPromise.then.mostRecentCall.args[0](mockProviderResults); + }); + + it("sends queries to all providers", function () { + for (i = 0; i < mockProviders.length; i += 1) { + expect(mockProviders[i].query).toHaveBeenCalled(); + } + }); + + it("filters out duplicate objects", function () { + expect(mockAggregatorResults.hits.length).toEqual(mockProviders.length + 1); + expect(mockAggregatorResults.total).not.toBeLessThan(mockAggregatorResults.hits.length); + }); + + it("orders results by score", function () { + for (i = 1; i < mockAggregatorResults.hits.length; i += 1) { + expect(mockAggregatorResults.hits[i].score) + .not.toBeGreaterThan(mockAggregatorResults.hits[i - 1].score); + } + }); + + }); + } +); \ No newline at end of file diff --git a/platform/search/test/suite.json b/platform/search/test/suite.json new file mode 100644 index 0000000000..5097bde635 --- /dev/null +++ b/platform/search/test/suite.json @@ -0,0 +1,5 @@ +[ + "SearchAggregator", + "GenericSearchProvider", + "GenericSearchWorker" +] diff --git a/pom.xml b/pom.xml index 8ca3cd6edb..3acfe38fa1 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ gov.nasa.arc.wtd open-mct-web Open MCT Web - 0.8.0-SNAPSHOT + 0.8.1-SNAPSHOT war diff --git a/protractor/README b/protractor/README new file mode 100644 index 0000000000..5734e5702d --- /dev/null +++ b/protractor/README @@ -0,0 +1,69 @@ +E2e Protractor Tests. + +1. Instructions: + + 1. 3 Control Scripts located in bin/. + run.js : node script used to start tests + start.js: node script used to setup test(starts node,localstorage and webdriver) + stop.js : node script, kills the 3 process started in start.js. + clean.js: node script used to remove the node_module directory.(clean up directory). + + 2. Use npm(Node Package Mangager) to Run Scripts. + a. cd protractor; + b. npm install; + c. To Run: + -npm start : will start processes need by protractor + -npm stop : will stop the processes need by protractor + -npm run-script run : will execute Protractor Script + -npm run-script all : will execute "start", "run", and "stop" script + +2. Directory Hierachy: + + -protractor: base directory + -common: contains prototype javascript functions that all other tests use. + -Buttons: common prototype functions related to enter fullscreen + -CreateItem: common prototype functions related to creating an item + -drag: common functions to test drag and drop. + -editItem: common functions used to test edit functionality. + -Launch: common script used to navigate the specified website. + -RightMenu: common functions for right click menu(remove). + -create + -e2e tests that creates the specified object. + -delete + -e2e tests that removes the specified object + -logs + -ctrl.sh redirects console output of MMAP, webdriver and elastic search and pipes them to log files. + -UI + -Contains tests that test the UI(drag drop, fullscreen, info bubble) + -conf.js: + -protractor config file. Explained below + -stressTest: + Tests that are used to test for memory leaks. You can use the new tab option on WARP and then open the + timeline in the new tab during the browser.sleep(). Once the test is do the browser will pause and you + can look a the timeline results in the new tab. + + NOTE: Cannot open chrome dev tools on same tab as the test are run on. Protractor uses the dev tools to + exectute the tests. + + -StressTest will create and delete folders. + -StressTestBubble.js: creates manny bubbles. + (Delay variable in InfoGesture.js was changed to 0) +3. Conf.js + Conf.js is used by protractor to setup and execute the tests. + -allScriptsTimeout: gives more time for protractor to synchronize with the page. + -jasmineNodeOpts: Protractor uses jasmine for the tests and jasmine has a default time out 30 seconds + per "it" test. Changed to maximume allowed time 360000 ms + -seleniumAddress: Protractor uses a Selenium server as a "proxy" between the test scripts and the browser + driver. A stand a lone version comes with protractor and to run use "webdriver-manager" + default address is: http://localhost:4444/wd/hub. + -specs[]: Is an array of files. Each File should have a "describe, it" test to be executed by protractor. + -capabilities: Tells protractor what browser to use and any browser arguments. + +4. bundle.json + bundle.json is used by npm to determine dependencies and location of script files. + -Dependencies: + "protractor": Contains protractor and webdriver package. + "psnode": Window/Unix Command, used for list/kill process.(ps aux) + "shelljs": Window/Unix Common JS Commands. eg rm,ls,exec + "sleep": Window/Unix Commands used to sleep the script + "string": Window/Unix Commands for string manipulation. \ No newline at end of file diff --git a/protractor/UI/Fullscreen.js b/protractor/UI/Fullscreen.js index 3c1c785228..532ad318d8 100644 --- a/protractor/UI/Fullscreen.js +++ b/protractor/UI/Fullscreen.js @@ -22,7 +22,7 @@ //TODO Add filter for duplications/ var fullScreenFile = require("../common/Buttons"); -describe('Test Fullscreen', function() { +describe('Enable Fullscreen', function() { var fullScreenClass = new fullScreenFile(); beforeEach(require('../common/Launch')); diff --git a/protractor/UI/InfoBubble.js b/protractor/UI/InfoBubble.js index 274b7966b0..c88e9018d0 100644 --- a/protractor/UI/InfoBubble.js +++ b/protractor/UI/InfoBubble.js @@ -25,7 +25,7 @@ var itemEdit = require("../common/EditItem"); var rightMenu = require("../common/RightMenu"); var Drag = require("../common/drag"); -describe('Test Info Bubble', function() { +describe('Info Bubble', function() { var fullScreenClass = new fullScreenFile(); var createClass = new createItem(); var editItemClass = new itemEdit(); diff --git a/protractor/UI/NewWindow.js b/protractor/UI/NewWindow.js index 7a714d79a2..1e98bbc8b4 100644 --- a/protractor/UI/NewWindow.js +++ b/protractor/UI/NewWindow.js @@ -24,7 +24,7 @@ var createClassFile = require("../common/CreateItem") var itemEdit = require("../common/EditItem"); var rightMenu = require("../common/RightMenu.js"); -describe('Test New Window', function() { +describe('New Window', function() { var fullScreenClass = new fullScreenFile(); var createClass = new createClassFile(); var editItemClass = new itemEdit(); diff --git a/protractor/UI/RightClick.js b/protractor/UI/RightClick.js index 0ae4dd0708..5f7d389313 100644 --- a/protractor/UI/RightClick.js +++ b/protractor/UI/RightClick.js @@ -21,28 +21,64 @@ *****************************************************************************/ var right_click = require("../common/RightMenu.js"); var Create = require("../common/CreateItem") -describe('Right Click Interations', function() { +var itemEdit = require("../common/EditItem"); + +describe('The Right Menu', function() { var clickClass = new right_click(); var createClass = new Create(); + var editItemClass = new itemEdit(); var ITEM_NAME = "Folder"; var ITEM_TYPE = "folder"; var ITEM_MENU_GLYPH = 'F\nFolder'; + var ITEM_GRID_SELECT = 'P\nF\nFolder\n0 Items'; beforeEach(require('../common/Launch')); - it('should delete the specified object', function(){ - createClass.createButton().click(); - var folder = createClass.selectNewItem(ITEM_TYPE); - expect(folder.getText()).toEqual([ ITEM_MENU_GLYPH ]); - browser.sleep(1000); - folder.click() - browser.sleep(1000); - browser.wait(function () { - return element.all(by.model('ngModel[field]')).isDisplayed(); + it('should Dissapear After Delete', function(){ + browser.wait(function() { + createClass.createButton().click(); + return true; + }).then(function (){ + var folder = createClass.selectNewItem(ITEM_TYPE); + expect(folder.getText()).toEqual([ ITEM_MENU_GLYPH ]); + browser.sleep(1000); + folder.click() + }).then(function() { + browser.wait(function () { + return element.all(by.model('ngModel[field]')).isDisplayed(); + }) + createClass.fillFolderForum(ITEM_NAME, ITEM_TYPE).click(); + browser.sleep(1000); + }).then(function (){ + var item = editItemClass.SelectItem(ITEM_GRID_SELECT); + expect(item.count()).toBe(1); + browser.sleep(1000); + }).then(function () { + var MyItem = ">\nF\nMy Items" + element.all(by.repeater('child in composition')).filter(function (ele){ + return ele.getText().then(function(text) { + return text === MyItem; + }); + }).all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click(); + var object = element.all(by.repeater('child in composition')).filter(function (ele){ + return ele.getText().then(function(text) { + return text === ">\nF\nFolder"; + }); + }); + browser.sleep(1000) + browser.actions().mouseMove(object.get(0)).perform(); + browser.actions().click(protractor.Button.RIGHT).perform(); + browser.sleep(1000) + var menu = element.all(by.css('.ng-binding')).filter(function (ele){ + return ele.getText().then(function (text) { + return text == "Z\nRemove"; + }) + }) + menu.click(); + browser.sleep(1000) + + expect(menu.isDisplayed()).toBe(false); }) - createClass.fillFolderForum(ITEM_NAME, ITEM_TYPE).click(); - clickClass.delete(ITEM_NAME); - browser.sleep(1000); }); }); diff --git a/protractor/bin/clean.js b/protractor/bin/clean.js new file mode 100755 index 0000000000..82e776901f --- /dev/null +++ b/protractor/bin/clean.js @@ -0,0 +1,15 @@ +#! /usr/bin/env node +var shell = require("shelljs/global"); + +var startdir = process.cwd(); +var command = "npm unlink"; + +console.log("Cleaning Directory") +exec(command, function(code, output) { + if(code != 0){ + console.log('Exit code:', code); + console.log('Program output:', output); + } +}); +console.log("rm -rf node_modules") +rm('-rf', __dirname + "/../node_modules") diff --git a/protractor/bin/ctrl.sh b/protractor/bin/ctrl.sh new file mode 100755 index 0000000000..faf385d2ad --- /dev/null +++ b/protractor/bin/ctrl.sh @@ -0,0 +1,90 @@ +#! /bin/bash +ARGUMENT=$1; + +if [ $# != 1 ]; then + echo "Expected 1 Aurgument. Received " $# 1>&2; + exit 1 +fi +#Start webdrive and http-server +if [ $ARGUMENT == start ]; then + echo "Creating Log Directory ..." + mkdir logs; + + cd .. + node app.js -p 1984 -x platform/persistence/elastic -i example/persistence > protractor/logs/nodeApp.log 2>&1 & + sleep 3; + if grep -iq "Error" protractor/logs/nodeApp.log; then + if grep -iq "minimist" protractor/logs/nodeApp.log; then + echo " Node Failed Because Minimist is not installed" + echo " Installng Minimist ..." + npm install minimist express > protractor/logs/minimist.log 2>&1 & + wait $! + if [ $? != 0 ]; then + echo " Error: minimist" + echo " Check Log file" + echo + else + echo " Started: Minimist" + echo + node app.js -p 1984 -x platform/persistence/elastic -i example/persistence > protractor/logs/nodeApp.log 2>&1 & + if grep -iq "Error" protractor/logs/nodeApp.log; then + echo " Error: node app failed" + echo " Check Log file" + echo + else + echo " Started: node app.js" + echo + fi + fi + else + echo " Error: node app failed" + echo " Check Log file" + echo + fi + else + echo " Started: node app.js" + echo + fi + echo "Starting webdriver ..." + + cd protractor; + webdriver-manager start > logs/webdriver.log 2>&1 & + sleep 3; + if grep -iq "Exception" logs/webdriver.log; then + echo " Error: webdriver-manager" + echo " Check Log file" + echo + else + echo " Started: webdriver-manager" + fi + echo "Starting Elastic Search..." + + elasticsearch > logs/elasticSearch.log 2>&1 & + sleep 3; + if grep -iq "Exception" logs/elasticSearch.log; then + echo " Error: ElasticSearch" + echo " Check Log file" + echo + else + echo " Started: ElasticSearch" + fi +#Runs Protractor tests +elif [ $ARGUMENT == run ]; then + protractor ./conf.js +#Kill Process +elif [ $ARGUMENT == stop ]; then + echo "Removing logs" + rm -rf logs + echo "Stopping Node" + kill $(ps aux | grep "[n]ode app.js"| awk '{print $2}'); + + echo "Stopping webdriver ..." + kill $(ps aux | grep "[p]rotractor" | awk '{print $2}'); + kill $(ps aux | grep "[w]ebdriver-manager" | awk '{print $2}'); + sleep 1; + echo "Stopping Elastic..." + kill $(ps aux | grep "[e]lastic" | awk '{print $2}'); + sleep 1; +else + echo "Unkown: Command" $1; +fi diff --git a/protractor/bin/run.js b/protractor/bin/run.js new file mode 100755 index 0000000000..316caa11d0 --- /dev/null +++ b/protractor/bin/run.js @@ -0,0 +1,12 @@ +#! /usr/bin/env node +var shell = require("shelljs/global"); +var sleep = require('sleep'); + +var command = __dirname + "/../node_modules/protractor/bin/protractor " +__dirname + "/../conf.js"; +console.log("Executing Protractor Test") +exec(command, function(code, output) { + if(code != 0){ + console.log('Exit code:', code); + console.log('Program output:', output); + } +}); \ No newline at end of file diff --git a/protractor/bin/start.js b/protractor/bin/start.js new file mode 100755 index 0000000000..21aacc7efe --- /dev/null +++ b/protractor/bin/start.js @@ -0,0 +1,40 @@ +#! /usr/bin/env node +var shell,sleep; +try { + shell = require("shelljs/global"); + sleep = require('sleep'); +}catch (e){ + console.log("Dependencies Error"); + console.log("Run npm install"); + throw (e); +} +///Users/jsanderf/git/elastic/wtd/protractor/bin +var startdir = process.cwd(); +var command; +mkdir(__dirname + '/../logs'); + +command = __dirname + "/../node_modules/protractor/bin/webdriver-manager update"; +console.log("Installing Webdriver"); +exec(command,{async:false}); +sleep.sleep(1); + +console.log(); +cd(__dirname + '/../../'); +console.log('Installing Dependencies'); +exec("npm install minimist express", {async:false}); +console.log('Starting Node'); +sleep.sleep(1); +exec("node app.js -p 1984 -x example/persistence -x platform/persistence/elastic -i example/localstorage > protractor/logs/nodeApp.log 2>&1 &", {async:false}); +console.log(' Started Node'); + +console.log(); +console.log('Starting Webdriver'); +sleep.sleep(1); +exec("protractor/node_modules/protractor/bin/webdriver-manager start --standalone> protractor/logs/webdriver.log 2>&1 &",{async:false}); +if(error() == null){ + console.log(" Webdriver Started"); +}else{ + console.log(" Error : ", error()); +} +sleep.sleep(1); +cd(startdir); diff --git a/protractor/bin/stop.js b/protractor/bin/stop.js new file mode 100755 index 0000000000..ac2c3b4295 --- /dev/null +++ b/protractor/bin/stop.js @@ -0,0 +1,44 @@ +#! /usr/bin/env node + +var shell = require("shelljs/global"); +var ps = require('psnode'); +var S = require('string'); +var sleep = require('sleep'); + +// A simple pid lookup +ps.list(function(err, results) { + + results.forEach(function( process ){ + //Killing Node + if(S(process.command).contains("node app.js")) { + console.log(); + console.log( 'Killing Node: %s', process.command); + ps.kill(process.pid, function(err, stdout) { + if (err) { + throw new Error(err); + } + console.log(stdout); + }); + }else if(S(process.command).contains("webdriver")) { + console.log(); + console.log( 'Killing WebDriver: %s', process.command); + ps.kill(process.pid, function(err, stdout) { + if (err){ + throw new Error(err); + } + console.log(stdout); + }); + }else if(S(process.command).contains("protractor")) { + console.log(); + console.log( 'Killing Chrome Drive: %s', process.command); + ps.kill(process.pid, function(err, stdout) { + if (err){ + throw new Error(err); + } + console.log(stdout); + }); + } + }); +}); + + diff --git a/protractor/common/Launch.js b/protractor/common/Launch.js index 2b700672cc..fd745f94c3 100644 --- a/protractor/common/Launch.js +++ b/protractor/common/Launch.js @@ -24,6 +24,6 @@ module.exports = function launch() { 'use strict'; browser.ignoreSynchronization = true; - browser.get('http://localhost:1984/'); - browser.sleep(2000); // 20 seconds + browser.get('http://localhost:1984'); + browser.sleep(2000); // 2 seconds }; diff --git a/protractor/common/RightMenu.js b/protractor/common/RightMenu.js index 490d876d87..d1375d0533 100644 --- a/protractor/common/RightMenu.js +++ b/protractor/common/RightMenu.js @@ -24,18 +24,25 @@ var RightMenu = (function () { function RightMenu() { } + function carrotMyItem(){ + var MyItem = ">\nF\nMy Items" + element.all(by.repeater('child in composition')).filter(function (ele){ + return ele.getText().then(function(text) { + return text === MyItem; + }); + }).all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click(); + } //RightMenu Click on Object RightMenu.prototype.delete = function (name, flag) { if(typeof flag === 'undefined'){ flag = true; } if(flag === true){ - var carrot = element.all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).get(0).click(); + carrotMyItem(); } browser.sleep(1000) var object = element.all(by.repeater('child in composition')).filter(function (ele){ return ele.getText().then(function(text) { - //expect(text).toEqual("3"); return text === name; }); }); @@ -43,7 +50,7 @@ var RightMenu = (function () { browser.actions().mouseMove(object.get(0)).perform(); browser.actions().click(protractor.Button.RIGHT).perform(); browser.sleep(1000) - var remove = element.all(by.css('.ng-binding')).filter(function (ele){ + element.all(by.css('.ng-binding')).filter(function (ele){ return ele.getText().then(function (text) { return text == "Z\nRemove"; }) @@ -58,11 +65,10 @@ var RightMenu = (function () { }); }; RightMenu.prototype.reset = function (name) { - var carrot = element.all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click(); + carrotMyItem(); browser.sleep(1000) var object = element.all(by.repeater('child in composition')).filter(function (ele){ return ele.getText().then(function(text) { - //expect(text).toEqual("3"); return text === name; }); }).click(); @@ -75,19 +81,19 @@ var RightMenu = (function () { return text == "r\nRestart at 0"; }) }).click(); + browser.sleep(1000) }; + //click '<', true==yes false==no RightMenu.prototype.select = function(name, flag){ if(typeof flag == "undefined"){ flag = true; } - //click '<', true==yes false==no if(flag == true){ - var carrot = element.all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click(); + carrotMyItem(); } browser.sleep(1000) return element.all(by.repeater('child in composition')).filter(function (ele){ return ele.getText().then(function(text) { - // expect(text).toEqual("3"); return text === name; }); }); @@ -96,7 +102,6 @@ var RightMenu = (function () { RightMenu.prototype.dragDrop = function(name){ var object = element.all(by.repeater('child in composition')).filter(function (ele){ return ele.getText().then(function(text) { - //expect(text).toEqual("3"); return text === name; }); }); diff --git a/protractor/conf.js b/protractor/conf.js index c33e196157..8b828fcbd7 100644 --- a/protractor/conf.js +++ b/protractor/conf.js @@ -24,34 +24,34 @@ // conf.js exports.config = { allScriptsTimeout: 500000, - defaultTimeoutInterval: 60000, + jasmineNodeOpts: {defaultTimeoutInterval: 360000}, seleniumAddress: 'http://localhost:4444/wd/hub', - //specs: ['StressTest.js'], + //specs: ['StressTestCarrot.js'], specs: [ - //'create/CreateActivity.js', - //'delete/DeleteActivity.js', - //'create/CreateActivityMode.js', - //'delete/DeleteActivityMode.js', - //'create/CreateActivityMode.js', - //'create/CreateClock.js', - //'delete/DeleteClock.js', + // 'create/CreateActivity.js', + // 'delete/DeleteActivity.js', + // 'create/CreateActivityMode.js', + // 'delete/DeleteActivityMode.js', + // 'create/CreateClock.js', + // 'delete/DeleteClock.js', 'create/CreateDisplay.js', - //'delete/DeleteDisplay.js', + 'delete/DeleteDisplay.js', 'create/CreateFolder.js', - //'delete/DeleteFolder.js', - 'create/CreateTelemetry.js', - //'delete/DeleteTelemetry.js', - //'create/CreateTimeline.js', - //'delete/DeleteTimeline.js', - //'create/CreateTimer.js', - //'delete/DeleteTimer.js', + 'delete/DeleteFolder.js', + // 'create/CreateTelemetry.js', + // 'delete/DeleteTelemetry.js', + // 'create/CreateTimeline.js', + // 'delete/DeleteTimeline.js', + // 'create/CreateTimer.js', + // 'delete/DeleteTimer.js', 'create/CreateWebPage.js', - //'delete/DeleteWebPage.js', + 'delete/DeleteWebPage.js', 'UI/Fullscreen.js', 'create/CreateButton.js', //"UI/DragDrop.js", - //"UI/NewWindow.js", - 'UI/InfoBubble.js' + "UI/NewWindow.js" + //'UI/InfoBubble.js', + //'UI/RightClick.js' ], capabilities: { 'browserName': 'chrome', // or 'safari' @@ -61,7 +61,7 @@ exports.config = { // Allow specifying binary location as an environment variable, // for cases where Chrome is not installed in a usual location. -if (process.env.PROTRACTOR_CHROME_BINARY) { +if (process.env.CHROME_BIN) { exports.config.capabilities.chromeOptions.binary = - process.env.PROTRACTOR_CHROME_BINARY; + process.env.CHROME_BIN; } diff --git a/protractor/create/CreateActivityMode.js b/protractor/create/CreateActivityMode.js index e9469749aa..17ed700ae6 100644 --- a/protractor/create/CreateActivityMode.js +++ b/protractor/create/CreateActivityMode.js @@ -22,7 +22,7 @@ var itemCreate = require("../common/CreateItem"); var itemEdit = require("../common/EditItem"); -describe('Create Web Page', function() { +describe('Create Activity Mode', function() { var createClass = new itemCreate(); var editItemClass = new itemEdit(); var ITEM_NAME = "Activity Mode"; diff --git a/protractor/create/CreateClock.js b/protractor/create/CreateClock.js index d1dc781b58..940db62af4 100644 --- a/protractor/create/CreateClock.js +++ b/protractor/create/CreateClock.js @@ -57,7 +57,7 @@ describe('Create Clock', function() { }); it('should check clock', function () { - function getTime() { + function getTime(flag) { function addZero(time){ if(time < 10){ return '0' + time; @@ -66,7 +66,6 @@ describe('Create Clock', function() { } var currentdate = new Date(); - var month = currentdate.getMonth() + 1; month = addZero(month); @@ -77,6 +76,9 @@ describe('Create Clock', function() { hour = addZero(hour); var second = currentdate.getSeconds(); + if(flag == true) { + second = second + 1; + } second = addZero(second); var minute = currentdate.getMinutes(); @@ -85,17 +87,23 @@ describe('Create Clock', function() { return ("UTC " + currentdate.getFullYear() + "/" + (month) + "/" + day + " " + (hour) + ":" + minute + ":" + second + " PM"); } - - var current,clock; - rightClickClass.select(ITEM_MENU_GLYPH, true).click().then(function () { - browser.sleep(1000); - current = browser.executeScript(getTime); - }).then(function () { - clock = element(by.css('.l-time-display.l-digital.l-clock.s-clock.ng-scope')); - clock.getText().then(function (time) { - expect(current).toEqual(time); - }) + this.addMatchers({ + toBeIn: function(expected){ + var posibilities = Array.isArray(this.actual) ? this.actual : [this.actual]; + return posibilities.indexOf(expected) > -1; + } }) - + rightClickClass.select(ITEM_MENU_GLYPH, true).click().then(function () { + browser.sleep(1000); + browser.executeScript(getTime, false).then(function(current){ + browser.executeScript(getTime, true).then(function(current1) { + var clock = element(by.css('.l-time-display.l-digital.l-clock.s-clock.ng-scope')); + clock.getText().then(function (ele) { + expect([current,current1]).toBeIn(ele); + }) + }); + }); + + }) }); }); diff --git a/protractor/create/CreateTimer.js b/protractor/create/CreateTimer.js index d8059c1f59..e83fedfedf 100644 --- a/protractor/create/CreateTimer.js +++ b/protractor/create/CreateTimer.js @@ -63,7 +63,11 @@ describe('Create Timer', function() { browser.sleep(1000) var timer = element(by.css('.value.ng-binding.active')) timer.getText().then(function (time) { - expect(time).toEqual("0D 00:00:01") + var timerChecker = false; + if(time == "0D 00:00:01" || time == "0D 00:00:02"){ + timerChecker = true; + } + expect(timerChecker).toEqual(true) }) }); diff --git a/protractor/package.json b/protractor/package.json new file mode 100644 index 0000000000..b43b9b1cdb --- /dev/null +++ b/protractor/package.json @@ -0,0 +1,20 @@ +{ + "name": "ProtractorLauncher", + "version": "1.0.0", + "scripts" : { + "start" : "bin/start.js", + "protractor" : "bin/run.js", + "stop" : "bin/stop.js", + "all" : "bin/start.js; bin/run.js; bin/stop.js;", + "clean" : "bin/clean.js" + }, + "dependencies": { + "protractor": "^2.1.0", + "psnode": "0.0.1", + "shelljs": "^0.5.2", + "sleep": "^3.0.0", + "string": "^3.3.1" + }, + "description": "E2e Protractor Tests.", + "license": "ISC" +} diff --git a/protractor/StressTest.js b/protractor/stressTest/StressTest.js similarity index 74% rename from protractor/StressTest.js rename to protractor/stressTest/StressTest.js index 5e4a8b1507..108e431868 100644 --- a/protractor/StressTest.js +++ b/protractor/stressTest/StressTest.js @@ -19,10 +19,9 @@ * this source code distribution or the Licensing information page available * at runtime from the About dialog for additional information. *****************************************************************************/ -//TODO Add filter for duplications/ -var itemCreate = require("./common/CreateItem"); -var itemEdit = require("./common/EditItem"); -var right_click = require("./common/RightMenu.js"); +var itemCreate = require("../common/CreateItem"); +var itemEdit = require("../common/EditItem"); +var right_click = require("../common/RightMenu.js"); describe('Create Folder', function() { var clickClass = new right_click(); @@ -41,31 +40,35 @@ describe('Create Folder', function() { }); it('should Create new Folder', function(){ browser.sleep(5000); - for(var i=0; i < 50; i++){ + for(var i=0; i < 25; i++){ browser.wait(function() { createClass.createButton().click(); return true; }).then(function (){ var folder = createClass.selectNewItem(ITEM_TYPE); expect(folder.getText()).toEqual([ ITEM_MENU_GLYPH ]); - browser.sleep(1000); + browser.sleep(500); folder.click() }).then(function() { browser.wait(function () { return element.all(by.model('ngModel[field]')).isDisplayed(); }) createClass.fillFolderForum(ITEM_NAME, ITEM_TYPE).click(); - browser.sleep(1000); + browser.sleep(500); }).then(function (){ - browser.sleep(1000); - // if(i === 1){ - clickClass.delete(ITEM_SIDE_SELECT, true); - element.all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click(); - // }else{ - browser.sleep(1000); - + browser.sleep(500); + clickClass.delete(ITEM_SIDE_SELECT, true); + //element.all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click(); + + + var MyItem = ">\nF\nMy Items" + element.all(by.repeater('child in composition')).filter(function (ele){ + return ele.getText().then(function(text) { + //expect(text).toEqual(MyItem); + return text === MyItem; + }); + }).all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click(); // clickClass.delete(ITEM_SIDE_SELECT, false); - // } }); } browser.pause(); diff --git a/protractor/stressTest/StressTestBubble.js b/protractor/stressTest/StressTestBubble.js new file mode 100644 index 0000000000..b06b29c1b9 --- /dev/null +++ b/protractor/stressTest/StressTestBubble.js @@ -0,0 +1,59 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/StressTestBubble.jsStressTestBubble.js +var itemCreate = require("../common/CreateItem"); +var itemEdit = require("../common/EditItem"); +var right_click = require("../common/RightMenu.js"); + +describe('Create Folder', function() { + var clickClass = new right_click(); + var createClass = new itemCreate(); + var editItemClass = new itemEdit(); + var ITEM_NAME = "Folder"; + var ITEM_TYPE = "folder"; + var ITEM_MENU_GLYPH = 'F\nFolder'; + var ITEM_GRID_SELECT = 'P\nF\nFolder\n0 Items'; + var ITEM_SIDE_SELECT = ">\nF\nFolder" + + beforeEach(function() { + browser.ignoreSynchronization = true; + browser.get('http://localhost:1984/warp/'); + browser.sleep(2000); // 20 seconds + }); + it('should Create new Folder', function(){ + browser.sleep(10000); + for(var i=0; i < 1000; i++){ + var object = element.all(by.repeater('child in composition')).filter(function (ele){ + return ele.getText().then(function(text) { + return text === ">\nF\nMy Items"; + }); + }); + //browser.sleep(1000) + browser.actions().mouseMove(object.get(0)).perform(); + //browser.actions().click(protractor.Button.RIGHT).perform(); + + element.all(by.css('.items-holder.grid.abs.ng-scope')).click(); + } + browser.pause(); + + }); + +}); diff --git a/protractor/stressTest/StressTestCreateButton.js b/protractor/stressTest/StressTestCreateButton.js new file mode 100644 index 0000000000..25debf3bba --- /dev/null +++ b/protractor/stressTest/StressTestCreateButton.js @@ -0,0 +1,56 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +var itemCreate = require("../common/CreateItem"); +var itemEdit = require("../common/EditItem"); +var right_click = require("../common/RightMenu.js"); + +describe('Create Folder', function() { + var clickClass = new right_click(); + var createClass = new itemCreate(); + var editItemClass = new itemEdit(); + var ITEM_NAME = "Folder"; + var ITEM_TYPE = "folder"; + var ITEM_MENU_GLYPH = 'F\nFolder'; + var ITEM_GRID_SELECT = 'P\nF\nFolder\n0 Items'; + var ITEM_SIDE_SELECT = ">\nF\nFolder" + + beforeEach(function() { + browser.ignoreSynchronization = true; + browser.get('http://localhost:1984/warp/'); + browser.sleep(2000); // 20 seconds + }); + it('should Create new Folder', function(){ + browser.sleep(10000); + for(var i=0; i < 1000; i++){ + createClass.createButton().click(); + + //browser.sleep(1000) + //browser.actions().mouseMove(object.get(0)).perform(); + //browser.actions().click(protractor.Button.RIGHT).perform(); + + element.all(by.css('.items-holder.grid.abs.ng-scope')).click(); + } + browser.pause(); + + }); + +}); diff --git a/protractor/stressTest/StressTestMenu.js b/protractor/stressTest/StressTestMenu.js new file mode 100644 index 0000000000..d6e30bc5b2 --- /dev/null +++ b/protractor/stressTest/StressTestMenu.js @@ -0,0 +1,55 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +var itemCreate = require("../common/CreateItem"); +var itemEdit = require("../common/EditItem"); +var right_click = require("../common/RightMenu.js"); + +describe('Create Folder', function() { + var clickClass = new right_click(); + var createClass = new itemCreate(); + var editItemClass = new itemEdit(); + var ITEM_NAME = "Folder"; + var ITEM_TYPE = "folder"; + var ITEM_MENU_GLYPH = 'F\nFolder'; + var ITEM_GRID_SELECT = 'P\nF\nFolder\n0 Items'; + var ITEM_SIDE_SELECT = ">\nF\nFolder" + + beforeEach(function() { + browser.ignoreSynchronization = true; + browser.get('http://localhost:1984/warp/'); + browser.sleep(2000); // 20 seconds + }); + it('should Create new Folder', function(){ + browser.sleep(10000); + for(var i=0; i < 1000; i++){ + browser.wait(function() { + createClass.createButton().click(); + return true; + }).then(function (){ + element.all(by.css('.items-holder.grid.abs.ng-scope')).click(); + }) + } + browser.pause(); + + }); + +}); diff --git a/protractor/stressTest/StressTestNewPage.js b/protractor/stressTest/StressTestNewPage.js new file mode 100644 index 0000000000..2b0e82fbb1 --- /dev/null +++ b/protractor/stressTest/StressTestNewPage.js @@ -0,0 +1,61 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +var itemCreate = require("../common/CreateItem"); +var itemEdit = require("../common/EditItem"); +var right_click = require("../common/RightMenu.js"); +var fullScreenFile = require("../common/FullScreen"); + +describe('Create Folder', function() { + var clickClass = new right_click(); + var createClass = new itemCreate(); + var editItemClass = new itemEdit(); + var fullScreenClass = new fullScreenFile(); + + var ITEM_NAME = "Folder"; + var ITEM_TYPE = "folder"; + var ITEM_MENU_GLYPH = 'F\nFolder'; + var ITEM_GRID_SELECT = 'P\nF\nFolder\n0 Items'; + var ITEM_SIDE_SELECT = ">\nF\nFolder" + + beforeEach(function() { + browser.ignoreSynchronization = true; + browser.get('http://localhost:1984/warp/'); + browser.sleep(2000); // 20 seconds + }); + it('should Create new Folder', function(){ + browser.sleep(15000); + for(var i=0; i < 1000; i++){ + fullScreenClass.newWidnow().click(); + + browser.getAllWindowHandles().then(function (handles) { + //browser.driver.switchTo().window(handles[1]); + browser.sleep(1000); + browser.driver.close(); + browser.sleep(1000); + // browser.driver.switchTo().window(handles[0]); + }); + } + browser.pause(); + + }); + +}); diff --git a/protractor/stressTest/StressTestRightClick.js b/protractor/stressTest/StressTestRightClick.js new file mode 100644 index 0000000000..f16f876a90 --- /dev/null +++ b/protractor/stressTest/StressTestRightClick.js @@ -0,0 +1,59 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +var itemCreate = require("../common/CreateItem"); +var itemEdit = require("../common/EditItem"); +var right_click = require("../common/RightMenu.js"); + +describe('Create Folder', function() { + var clickClass = new right_click(); + var createClass = new itemCreate(); + var editItemClass = new itemEdit(); + var ITEM_NAME = "Folder"; + var ITEM_TYPE = "folder"; + var ITEM_MENU_GLYPH = 'F\nFolder'; + var ITEM_GRID_SELECT = 'P\nF\nFolder\n0 Items'; + var ITEM_SIDE_SELECT = ">\nF\nFolder" + + beforeEach(function() { + browser.ignoreSynchronization = true; + browser.get('http://localhost:1984/warp/'); + browser.sleep(2000); // 20 seconds + }); + it('should Create new Folder', function(){ + browser.sleep(8000); + for(var i=0; i < 1000; i++){ + var object = element.all(by.repeater('child in composition')).filter(function (ele){ + return ele.getText().then(function(text) { + return text === ">\nF\nMy Items"; + }); + }); + //browser.sleep(1000) + browser.actions().mouseMove(object.get(0)).perform(); + browser.actions().click(protractor.Button.RIGHT).perform(); + + element.all(by.css('.items-holder.grid.abs.ng-scope')).click(); + } + browser.pause(); + + }); + +});