diff --git a/test/.jshintrc b/test/.jshintrc index 7f7e233194..b31a51a65d 100644 --- a/test/.jshintrc +++ b/test/.jshintrc @@ -27,6 +27,7 @@ "angular": false, "by": false, "before": false, + "beforeAll": false, "beforeEach": false, "browser": false, "describe": false, diff --git a/test/integration/features/user_adds_imagestream_to_project.spec.js b/test/integration/features/user_adds_imagestream_to_project.spec.js new file mode 100644 index 0000000000..45d9bb0516 --- /dev/null +++ b/test/integration/features/user_adds_imagestream_to_project.spec.js @@ -0,0 +1,43 @@ +'use strict'; + +var h = require('../helpers'); +var projectHelpers = require('../helpers/project'); +var OverviewPage = require('../page-objects/overview').OverviewPage; +var CreateProjectPage = require('../page-objects/createProject').CreateProjectPage; +var ImageStreamsPage = require('../page-objects/imageStreams').ImageStreamsPage; +var centosImageStream = require('../fixtures/image-streams-centos7.json'); + +describe('User adds an image stream to a project', function() { + + beforeEach(function() { + h.commonSetup(); + h.login(); + projectHelpers.deleteAllProjects(); + }); + + afterEach(function() { + h.commonTeardown(); + }); + + describe('after creating a new project', function() { + describe('using the "Import YAML/JSON" tab', function() { + it('should process and create the images in the image stream', function() { + var project = projectHelpers.projectDetails(); + var createProjectPage = new CreateProjectPage(project); + createProjectPage.visit(); + createProjectPage.createProject(); + var overviewPage = new OverviewPage(project); + overviewPage.visit(); + var catalogPage = overviewPage.clickAddToProject(); // implicit redirect to catalog page + catalogPage + .processImageStream(JSON.stringify(centosImageStream)) + .then(function() { + // verify we have the nodejs image stream loaded + var imageStreamsPage = new ImageStreamsPage(project); + imageStreamsPage.visit(); + expect(element(by.cssContainingText('td', 'nodejs')).isPresent()).toBe(true); // TODO: use fixture + }); + }); + }); + }); +}); diff --git a/test/integration/features/user_adds_template_to_project.spec.js b/test/integration/features/user_adds_template_to_project.spec.js index 4b04b95ef6..a57eb5b259 100644 --- a/test/integration/features/user_adds_template_to_project.spec.js +++ b/test/integration/features/user_adds_template_to_project.spec.js @@ -1,6 +1,5 @@ 'use strict'; -require('jasmine-beforeall'); var h = require('../helpers'); var projectHelpers = require('../helpers/project'); var OverviewPage = require('../page-objects/overview').OverviewPage; @@ -9,9 +8,6 @@ var DeploymentsPage = require('../page-objects/deployments').DeploymentsPage; var ServicesPage = require('../page-objects/services').ServicesPage; var RoutesPage = require('../page-objects/routes').RoutesPage; var nodeMongoTemplate = require('../fixtures/nodejs-mongodb'); -//var logger = require('../helpers/logger'); -// TODO: use this to alter whitelist in the tests to support the create from url flow -// var env = require('../helpers/env'); describe('User adds a template to a project', function() { @@ -42,24 +38,42 @@ describe('User adds a template to a project', function() { // verify we have the 2 deployments in the template var deploymentsPage = new DeploymentsPage(project); deploymentsPage.visit(); - expect(element(by.cssContainingText('td', 'mongodb')).isPresent()).toBe(true); - expect(element(by.cssContainingText('td', 'nodejs-mongodb-example')).isPresent()).toBe(true); + expect(element(by.cssContainingText('td', 'mongodb')).isPresent()).toBe(true); // TODO: use fixture + expect(element(by.cssContainingText('td', 'nodejs-mongodb-example')).isPresent()).toBe(true); // TODO: use fixture // verify we have the two services in the template var servicesPage = new ServicesPage(project); servicesPage.visit(); - expect(element(by.cssContainingText('td', 'mongodb')).isPresent()).toBe(true); - expect(element(by.cssContainingText('td', 'nodejs-mongodb-example')).isPresent()).toBe(true); + expect(element(by.cssContainingText('td', 'mongodb')).isPresent()).toBe(true); // TODO: use fixture + expect(element(by.cssContainingText('td', 'nodejs-mongodb-example')).isPresent()).toBe(true); // TODO: use fixture // verify we have one route for the mongo app var routesPage = new RoutesPage(project); routesPage.visit(); - expect(element(by.cssContainingText('td', 'nodejs-mongodb-example')).isPresent()).toBe(true); + expect(element(by.cssContainingText('td', 'nodejs-mongodb-example')).isPresent()).toBe(true); // TODO: use fixture }); }); - xit('should save the template in the project catalog', function() { + it('should save the template in the project catalog', function() { // TODO: same flow as the above test, but use: // catalogPage.saveTemplate(tpl) // & assert that the template was added to the catalog in this project + var project = projectHelpers.projectDetails(); + var createProjectPage = new CreateProjectPage(project); + createProjectPage.visit(); + createProjectPage.createProject(); + var overviewPage = new OverviewPage(project); + overviewPage.visit(); + var catalogPage = overviewPage.clickAddToProject(); // implicit redirect to catalog page + catalogPage + .saveTemplate(JSON.stringify(nodeMongoTemplate)) + .then((overview2) => { + var cat2 = overview2.clickAddToProject(); // implicit redirect to catalog page + // once the template processes, we just have to return + // to the catalog and verify the tile exists + cat2.visit(); + cat2.clickCategory('JavaScript'); // TODO: pass in the tile name from the template fixture + cat2.findTileBy('Node.js + MongoDB (Ephemeral)', project.name); // TODO: pass in... + expect(element).toBeTruthy(); + }); }); }); }); diff --git a/test/integration/features/user_creates_from_url.spec.js b/test/integration/features/user_creates_from_url.spec.js new file mode 100644 index 0000000000..8a6a74771b --- /dev/null +++ b/test/integration/features/user_creates_from_url.spec.js @@ -0,0 +1,203 @@ +'use strict'; + +require('jasmine-beforeall'); + +const h = require('../helpers'); +const addExtension = require('../helpers/extensions').addExtension; +const resetExtensions = require('../helpers/extensions').resetExtensions; +const matchersHelpers = require('../helpers/matchers'); +const projectHelpers = require('../helpers/project'); +const inputsHelpers = require('../helpers/inputs'); +const CreateFromURLPage = require('../page-objects/createFromURL').CreateFromURLPage; +const CreateProjectPage = require('../page-objects/createProject').CreateProjectPage; +const CatalogPage = require('../page-objects/catalog').CatalogPage; +const nodeMongoTemplate = require('../fixtures/nodejs-mongodb'); +const centosImageStream = require('../fixtures/image-streams-centos7.json'); + +describe('authenticated e2e-user', function() { + + let project = projectHelpers.projectDetails(); + let namespaceForFixtures = "template-dumpster"; + + let setupEnv = function() { + // add namespace to create whitelist for template and image stream + addExtension(` + (function() { + // Update whilelist: + window.OPENSHIFT_CONSTANTS.CREATE_FROM_URL_WHITELIST = ['openshift', '${namespaceForFixtures}']; + })(); + `); + let fixturesProject = {name: namespaceForFixtures}; + let createProjectPage = new CreateProjectPage(fixturesProject); + createProjectPage.visit(); + createProjectPage.createProject(); + let catalogPage = new CatalogPage(fixturesProject); + // - add an image stream to that namespace + catalogPage.visit(); + catalogPage.processImageStream(JSON.stringify(centosImageStream)); + // - add a template to that namespace + catalogPage.visit(); + catalogPage.saveTemplate(JSON.stringify(nodeMongoTemplate)); + }; + + beforeAll(function() { + h.commonSetup(); + h.login(); + projectHelpers.deleteAllProjects(); + setupEnv(); + }); + + afterAll(function() { + projectHelpers.deleteAllProjects(); + resetExtensions(); + h.afterAllTeardown(); + }); + + describe('create from URL', function() { + + describe('using an image stream and image stream tag supplied as query string params', function() { + + let name = 'nodejs-edited'; + let sourceURI = 'https://github.com/openshift/nodejs-ex.git-edited'; + let sourceRef = 'master-edited'; + let contextDir = '/-edited'; + let qs = `?imageStream=nodejs&imageTag=4&name=${name}&sourceURI=${sourceURI}&sourceRef=${sourceRef}&contextDir=${contextDir}&namespace=${namespaceForFixtures}`; + let uri = `project/${project.name}create/fromimage${qs}`; + let heading = 'Node.js 4'; + let words = project.name.split(' '); + let timestamp = words[words.length - 1]; + + it('should display details about the the image', function() { + let createFromURLPage = new CreateFromURLPage(); + createFromURLPage.visit(qs); + matchersHelpers.expectHeading(heading); + }); + + it('should load the image stream in to a newly created project', function(){ + let createFromURLPage = new CreateFromURLPage(); + createFromURLPage.visit(qs); + createFromURLPage.clickCreateNewProjectTab(); + projectHelpers.createProject(project, 'project/' + project['name'] + 'create/fromimage' + qs); + matchersHelpers.expectHeading(heading); + projectHelpers.deleteProject(project); + }); + + it('should load the image stream in to an existing project and verify the query string params are loaded in to the corresponding form fields', function(){ + let createFromURLPage = new CreateFromURLPage(); + projectHelpers.visitCreatePage(); + projectHelpers.createProject(project); + createFromURLPage.visit(qs); + createFromURLPage.selectExistingProject(timestamp, uri); + matchersHelpers.expectHeading(heading); + let nameInput = element(by.model('name')); + expect(nameInput.getAttribute('value')).toEqual(name); + let sourceURIInput = element(by.model('buildConfig.sourceUrl')); + expect(sourceURIInput.getAttribute('value')).toEqual(sourceURI); + createFromURLPage.clickShowAdvanced(); + let sourceRefInput = element(by.model('buildConfig.gitRef')); + expect(sourceRefInput.getAttribute('value')).toEqual(sourceRef); + let contextDirInput = element(by.model('buildConfig.contextDir')); + expect(contextDirInput.getAttribute('value')).toEqual(contextDir); + projectHelpers.deleteProject(project); + }); + + }); + + describe('using a template supplied as a query string param', function() { + + let sourceURL = "https://github.com/openshift/nodejs-ex.git-edited"; + let qs = '?template=nodejs-mongodb-example&templateParamsMap=%7B"SOURCE_REPOSITORY_URL":"' + sourceURL + '"%7D' + '&namespace=' + namespaceForFixtures; + let uri = 'project/' + project.name + 'create/fromtemplate' + qs; + let heading = 'Node.js + MongoDB (Ephemeral)'; + let words = project.name.split(' '); + let timestamp = words[words.length - 1]; + + it('should display details about the template', function() { + let createFromURLPage = new CreateFromURLPage(); + createFromURLPage.visit(qs); + matchersHelpers.expectHeading(heading); + }); + + it('should load the template in to a newly created project', function() { + let createFromURLPage = new CreateFromURLPage(); + createFromURLPage.visit(qs); + createFromURLPage.clickCreateNewProjectTab(); + projectHelpers.createProject(project, 'project/' + project['name'] + 'create/fromtemplate' + qs); + matchersHelpers.expectHeading(heading); + projectHelpers.deleteProject(project); + }); + + it('should load the template in an existing project and verify the query string param sourceURL is loaded in to a corresponding form field', function(){ + let createFromURLPage = new CreateFromURLPage(); + projectHelpers.visitCreatePage(); + projectHelpers.createProject(project); + createFromURLPage.visit(qs); + createFromURLPage.selectExistingProject(timestamp, uri); + matchersHelpers.expectHeading(heading); + inputsHelpers + .findValueInInputs(element.all(by.model('parameter.value')), sourceURL) + .then(function(found) { + expect(found).toEqual(sourceURL); + projectHelpers.deleteProject(project); + }); + }); + + }); + + describe('using a namespace that is not in the whitelist', function() { + it('should display an error about the namespace', function() { + let createFromURLPage = new CreateFromURLPage(); + createFromURLPage.visit('?namespace=not-whitelisted'); + matchersHelpers.expectAlert('Resources from the namespace "not-whitelisted" are not permitted.'); + }); + }); + + describe('using an unavailable image stream supplied as a query string param', function() { + it('should display an error about the image stream', function() { + let createFromURLPage = new CreateFromURLPage(); + createFromURLPage.visit('?imageStream=unavailable-imageStream'); + matchersHelpers.expectAlert('The requested image stream "unavailable-imageStream" could not be loaded.'); + }); + }); + + describe('using an unavailable image tag supplied as a query string param', function() { + it('should display an error about the image tag', function() { + let createFromURLPage = new CreateFromURLPage(); + createFromURLPage.visit('?imageStream=nodejs&imageTag=unavailable-imageTag' + '&namespace=' + namespaceForFixtures); + matchersHelpers.expectAlert('The requested image stream tag "unavailable-imageTag" could not be loaded.'); + }); + }); + + describe('using an unavailable template supplied as a query string param', function() { + it('should display an error about the template', function() { + let createFromURLPage = new CreateFromURLPage(); + createFromURLPage.visit('?template=unavailable-template'); + matchersHelpers.expectAlert('The requested template "unavailable-template" could not be loaded.'); + }); + }); + + describe('without using an image stream or a template', function() { + it('should display an error about needing a resource', function() { + let createFromURLPage = new CreateFromURLPage(); + createFromURLPage.visit(''); + matchersHelpers.expectAlert('An image stream or template is required.'); + }); + }); + describe('using both an image stream and a template', function() { + it('should display an error about combining resources', function() { + let createFromURLPage = new CreateFromURLPage(); + createFromURLPage.visit('?imageStream=nodejs&template=nodejs-mongodb-example' + '&namespace=' + namespaceForFixtures); + matchersHelpers.expectAlert('Image streams and templates cannot be combined.'); + }); + }); + describe('using an invalid app name as a query string param', function() { + it('should display an error about the app name', function() { + let createFromURLPage = new CreateFromURLPage(); + createFromURLPage.visit('?name=InvalidAppName&imageStream=nodejs' + '&namespace=' + namespaceForFixtures); + matchersHelpers.expectAlert('The app name "InvalidAppName" is not valid. An app name is an alphanumeric (a-z, and 0-9) string with a maximum length of 24 characters, where the first character is a letter (a-z), and the \'-\' character is allowed anywhere except the first or last character.'); + }); + }); + + }); + +}); diff --git a/test/integration/features/user_creates_project.spec.js b/test/integration/features/user_creates_project.spec.js index b251a63318..7a76726b0f 100644 --- a/test/integration/features/user_creates_project.spec.js +++ b/test/integration/features/user_creates_project.spec.js @@ -1,8 +1,8 @@ 'use strict'; /* jshint unused:false */ -require('jasmine-beforeall'); var h = require('../helpers.js'); +var projectHelpers = require('../helpers/project.js'); var goToAddToProjectPage = function(projectName) { var uri = 'project/' + projectName + '/create'; @@ -122,19 +122,11 @@ describe('', function() { it('should be able to show the create project page', goToCreateProjectPage); - var timestamp = (new Date()).getTime(); - var project = { - name: 'console-test-project-' + timestamp, - displayName: 'Console integration test Project ' + timestamp, - description: 'Created by assets/test/integration/rest-api/project.js' - }; + var project = projectHelpers.projectDetails(); it('should successfully create a new project', function() { goToCreateProjectPage(); - for (var key in project) { - h.setInputValue(key, project[key]); - } - h.clickAndGo('Create', 'project/' + project['name'] + '/create'); + projectHelpers.createProject(project, 'project/' + project['name'] + '/create'); h.waitForPresence('.breadcrumb li a', project['displayName']); checkProjectSettings(project['name'], project['displayName'], project['description']); }); @@ -184,14 +176,7 @@ describe('', function() { }); it('should delete a project', function() { - h.goToPage('/'); - var projectTile = element(by.cssContainingText(".project-info", project.displayName)); - projectTile.element(by.css('.fa-trash-o')).click(); - h.setInputValue('confirmName', project.name); - var deleteButton = element(by.cssContainingText(".modal-dialog .btn", "Delete")); - browser.wait(protractor.ExpectedConditions.elementToBeClickable(deleteButton), 2000); - deleteButton.click(); - h.waitForPresence(".alert-success", "marked for deletion"); + projectHelpers.deleteProject(project); }); /* diff --git a/test/integration/fixtures/image-streams-centos7.json b/test/integration/fixtures/image-streams-centos7.json new file mode 100644 index 0000000000..607550c21c --- /dev/null +++ b/test/integration/fixtures/image-streams-centos7.json @@ -0,0 +1,68 @@ +{ + "kind": "ImageStreamList", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "ImageStream", + "apiVersion": "v1", + "metadata": { + "name": "nodejs", + "annotations": { + "openshift.io/display-name": "Node.js" + } + }, + "spec": { + "tags": [ + { + "name": "latest", + "annotations": { + "openshift.io/display-name": "Node.js (Latest)", + "description": "Build and run Node.js applications on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-nodejs-container/blob/master/4/README.md.\n\nWARNING: By selecting this tag, your application will automatically update to use the latest version of Node.js available on OpenShift, including major versions updates.", + "iconClass": "icon-nodejs", + "tags": "builder,nodejs", + "supports":"nodejs", + "sampleRepo": "https://github.com/openshift/nodejs-ex.git" + }, + "from": { + "kind": "ImageStreamTag", + "name": "4" + } + }, + { + "name": "0.10", + "annotations": { + "openshift.io/display-name": "Node.js 0.10", + "description": "DEPRECATED: Build and run Node.js 0.10 applications on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-nodejs-container/blob/master/0.10/README.md.", + "iconClass": "icon-nodejs", + "tags": "hidden,nodejs", + "supports":"nodejs:0.10,nodejs:0.1,nodejs", + "version": "0.10", + "sampleRepo": "https://github.com/openshift/nodejs-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "openshift/nodejs-010-centos7:latest" + } + }, + { + "name": "4", + "annotations": { + "openshift.io/display-name": "Node.js 4", + "description": "Build and run Node.js 4 applications on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-nodejs-container/blob/master/4/README.md.", + "iconClass": "icon-nodejs", + "tags": "builder,nodejs", + "supports":"nodejs:4,nodejs", + "version": "4", + "sampleRepo": "https://github.com/openshift/nodejs-ex.git" + }, + "from": { + "kind": "DockerImage", + "name": "centos/nodejs-4-centos7:latest" + } + } + ] + } + } + ] +} diff --git a/test/integration/fixtures/nodejs-mongodb.json b/test/integration/fixtures/nodejs-mongodb.json index d7e9b6e2d0..6a55f02519 100644 --- a/test/integration/fixtures/nodejs-mongodb.json +++ b/test/integration/fixtures/nodejs-mongodb.json @@ -4,22 +4,37 @@ "metadata": { "name": "nodejs-mongodb-example", "annotations": { - "description": "An example Node.js application with a MongoDB database", - "tags": "quickstart,nodejs,mongodb", + "openshift.io/display-name": "Node.js + MongoDB (Ephemeral)", + "description": "An example Node.js application with a MongoDB database. For more information about using this template, including OpenShift considerations, see https://github.com/openshift/nodejs-ex/blob/master/README.md.\n\nWARNING: Any data stored will be lost upon pod destruction. Only use this template for testing.", + "tags": "quickstart,nodejs", "iconClass": "icon-nodejs" } }, + "message": "The following service(s) have been created in your project: ${NAME}, ${DATABASE_SERVICE_NAME}.\n\nFor more information about using this template, including OpenShift considerations, see https://github.com/openshift/nodejs-ex/blob/master/README.md.", "labels": { "template": "nodejs-mongodb-example" }, "objects": [ + { + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "${NAME}" + }, + "stringData": { + "database-user": "${DATABASE_USER}", + "database-password": "${DATABASE_PASSWORD}", + "database-admin-password" : "${DATABASE_ADMIN_PASSWORD}" + } + }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "${NAME}", "annotations": { - "description": "Exposes and load balances the application pods" + "description": "Exposes and load balances the application pods", + "service.alpha.openshift.io/dependencies": "[{\"name\": \"${DATABASE_SERVICE_NAME}\", \"kind\": \"Service\"}]" } }, "spec": { @@ -83,8 +98,14 @@ "from": { "kind": "ImageStreamTag", "namespace": "${NAMESPACE}", - "name": "nodejs:0.10" - } + "name": "nodejs:4" + }, + "env": [ + { + "name": "NPM_MIRROR", + "value": "${NPM_MIRROR}" + } + ] } }, "output": { @@ -112,7 +133,10 @@ "secret": "${GENERIC_WEBHOOK_SECRET}" } } - ] + ], + "postCommit": { + "script": "npm test" + } } }, { @@ -161,7 +185,7 @@ "containers": [ { "name": "nodejs-mongodb-example", - "image": "nodejs-mongodb-example", + "image": " ", "ports": [ { "containerPort": 8080 @@ -174,11 +198,21 @@ }, { "name": "MONGODB_USER", - "value": "${DATABASE_USER}" + "valueFrom": { + "secretKeyRef" : { + "name" : "${NAME}", + "key" : "database-user" + } + } }, { "name": "MONGODB_PASSWORD", - "value": "${DATABASE_PASSWORD}" + "valueFrom": { + "secretKeyRef" : { + "name" : "${NAME}", + "key" : "database-password" + } + } }, { "name": "MONGODB_DATABASE", @@ -186,7 +220,12 @@ }, { "name": "MONGODB_ADMIN_PASSWORD", - "value": "${DATABASE_ADMIN_PASSWORD}" + "valueFrom": { + "secretKeyRef" : { + "name" : "${NAME}", + "key" : "database-admin-password" + } + } } ], "readinessProbe": { @@ -198,17 +237,17 @@ } }, "livenessProbe": { - "timeoutSeconds": 3, - "initialDelaySeconds": 30, - "httpGet": { - "path": "/pagecount", - "port": 8080 - } + "timeoutSeconds": 3, + "initialDelaySeconds": 30, + "httpGet": { + "path": "/pagecount", + "port": 8080 + } }, "resources": { - "limits": { - "memory": "${MEMORY_LIMIT}" - } + "limits": { + "memory": "${MEMORY_LIMIT}" + } } } ] @@ -255,14 +294,14 @@ { "type": "ImageChange", "imageChangeParams": { - "automatic": false, + "automatic": true, "containerNames": [ "mongodb" ], "from": { "kind": "ImageStreamTag", "namespace": "${NAMESPACE}", - "name": "mongodb:2.6" + "name": "mongodb:3.2" } } }, @@ -282,35 +321,33 @@ } }, "spec": { - "volumes": [ - { - "name": "data", - "emptyDir": {} - } - ], "containers": [ { "name": "mongodb", - "image": "mongodb", + "image": " ", "ports": [ { "containerPort": 27017 } ], - "volumeMounts": [ - { - "name": "data", - "mountPath": "/var/lib/mongodb/data" - } - ], "env": [ { "name": "MONGODB_USER", - "value": "${DATABASE_USER}" + "valueFrom": { + "secretKeyRef" : { + "name" : "${NAME}", + "key" : "database-user" + } + } }, { "name": "MONGODB_PASSWORD", - "value": "${DATABASE_PASSWORD}" + "valueFrom": { + "secretKeyRef" : { + "name" : "${NAME}", + "key" : "database-password" + } + } }, { "name": "MONGODB_DATABASE", @@ -318,14 +355,24 @@ }, { "name": "MONGODB_ADMIN_PASSWORD", - "value": "${DATABASE_ADMIN_PASSWORD}" + "valueFrom": { + "secretKeyRef" : { + "name" : "${NAME}", + "key" : "database-admin-password" + } + } } ], "readinessProbe": { "timeoutSeconds": 1, "initialDelaySeconds": 3, "exec": { - "command": [ "/bin/sh", "-i", "-c", "mongostat --host 127.0.0.1 -u admin -p ${DATABASE_ADMIN_PASSWORD} -n 1 --noheaders"] + "command": [ + "/bin/sh", + "-i", + "-c", + "mongo 127.0.0.1:27017/$MONGODB_DATABASE -u $MONGODB_USER -p $MONGODB_PASSWORD --eval=\"quit()\"" + ] } }, "livenessProbe": { @@ -336,9 +383,23 @@ } }, "resources": { - "limits": { - "memory": "${MEMORY_MONGODB_LIMIT}" - } + "limits": { + "memory": "${MEMORY_MONGODB_LIMIT}" + } + }, + "volumeMounts": [ + { + "name": "${DATABASE_SERVICE_NAME}-data", + "mountPath": "/var/lib/mongodb/data" + } + ] + } + ], + "volumes": [ + { + "name": "${DATABASE_SERVICE_NAME}-data", + "emptyDir": { + "medium": "" } } ] @@ -359,24 +420,28 @@ "name": "NAMESPACE", "displayName": "Namespace", "description": "The OpenShift Namespace where the ImageStream resides.", + "required": true, "value": "openshift" }, { "name": "MEMORY_LIMIT", "displayName": "Memory Limit", "description": "Maximum amount of memory the Node.js container can use.", + "required": true, "value": "512Mi" }, { "name": "MEMORY_MONGODB_LIMIT", "displayName": "Memory Limit (MongoDB)", "description": "Maximum amount of memory the MongoDB container can use.", + "required": true, "value": "512Mi" }, { "name": "SOURCE_REPOSITORY_URL", "displayName": "Git Repository URL", "description": "The URL of the repository with your application source code.", + "required": true, "value": "https://github.com/openshift/nodejs-ex.git" }, { @@ -412,6 +477,7 @@ { "name": "DATABASE_SERVICE_NAME", "displayName": "Database Service Name", + "required": true, "value": "mongodb" }, { @@ -431,6 +497,7 @@ { "name": "DATABASE_NAME", "displayName": "Database Name", + "required": true, "value": "sampledb" }, { @@ -439,6 +506,12 @@ "description": "Password for the database admin user.", "generate": "expression", "from": "[a-zA-Z0-9]{16}" + }, + { + "name": "NPM_MIRROR", + "displayName": "Custom NPM Mirror URL", + "description": "The custom NPM mirror URL", + "value": "" } ] } diff --git a/test/integration/helpers.js b/test/integration/helpers.js index cb7d28b020..9d88753684 100644 --- a/test/integration/helpers.js +++ b/test/integration/helpers.js @@ -47,12 +47,14 @@ exports.clickAndGo = function(buttonText, uri) { }); }; -var waitForUri = function(uri) { - browser.wait(function() { - return browser.getCurrentUrl().then(function(url) { - return url.indexOf(uri) > -1; +var waitForUri = function(expectedUri) { + let actualUrl; + return browser.wait(() => { + return browser.getCurrentUrl().then((url) => { + actualUrl = url; + return actualUrl.indexOf(expectedUri) > -1; }); - }, 5000, "URL hasn't changed to " + uri); + }, 5000, "URL hasn't changed to " + expectedUri + '(is currently: ' + actualUrl + ')'); }; exports.waitForUri = waitForUri; @@ -92,8 +94,8 @@ exports.waitForPresence = function(selector, elementText, timeout, callback) { }; exports.goToPage = function(uri) { - browser.get(uri).then(function() { - waitForUri(uri); + return browser.get(uri).then(() => { + return waitForUri(uri); }); }; diff --git a/test/integration/helpers/env.js b/test/integration/helpers/env.js index 819c278351..23e374ab1f 100644 --- a/test/integration/helpers/env.js +++ b/test/integration/helpers/env.js @@ -6,7 +6,7 @@ // to give access to 'openshift' for installing templates and things initially. // - the browse catalog flow can be run w/o needing this, however exports.addNamespaceToCreateWhitelist = function(namespace) { - return browser.executeScript(function() { + return browser.executeScript(function(namespace) { window.OPENSHIFT_CONSTANTS.CREATE_FROM_URL_WHITELIST.push(namespace); - }); + }, namespace); }; diff --git a/test/integration/helpers/extensions.js b/test/integration/helpers/extensions.js new file mode 100644 index 0000000000..70fa9e9ba2 --- /dev/null +++ b/test/integration/helpers/extensions.js @@ -0,0 +1,69 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +// unfortunately we have to modify the file stored in .tmp/extensions.js +// since our tests run after grunt does its its copy/build step. +let filePath = path.join(__dirname, '../../../.tmp', 'scripts'); +let fileName = path.join(filePath, 'extensions.js'); +// a cache of the first read so we can reset easily +let cacheFileContents; + +let read = (fileName, cb) => { + return fs.readFile(fileName, 'utf-8', cb); +}; + +let write = (fileName, contents, cb) => { + return fs.writeFile(fileName, contents, 'utf-8', cb); +}; + +// creates a cache of the .tmp/extensions.js file. +// NOTE: addExtension() will also cache the first file read if it has never yet been cached. +exports.cacheCurrentExtensions = () => { + read(fileName, (readErr, fileContents) => { + cacheFileContents = fileContents; + if(readErr) { + console.log('cacheCurrentExtensions() read error:', readErr); + return; + } + }); +}; + +// to use: a template literal is probably the easiest way to provide JS to inject into the +// extensions.js file. Please wrap in IIFE as addExtension() may be called multiple times & +// the file contents will be concatenated: +// var jsToWrite = ` +// (function() { +// // createFromURL test file ${testTime} // (can use interpolation)) +// window.OPENSHIFT_CONSTANTS.CREATE_FROM_URL_WHITELIST = ['openshift', 'template-dumpster']; +// })(); +// `; +// adding to the extension.js file: +// addExtension(jsToWrite) +exports.addExtension = (fileContents) => { + read(fileName, (readErr, originalFile) => { + if(!cacheFileContents) { + cacheFileContents = originalFile; + } + if(readErr) { + console.log('addExtension() read error:', readErr); + return; + } + write(fileName, originalFile + fileContents, (writeErr) => { + if(writeErr) { + console.log('addExtension() write error:', writeErr); + return; + } + }); + }); +}; + +// sets the .tmp/extensions.js file either back to the original contents, or wipes it clean. +exports.resetExtensions = () => { + write(fileName, cacheFileContents || '', (writeErr) => { + if(writeErr) { + console.log('resetExtensions() write error:', writeErr); + } + }); +}; diff --git a/test/integration/page-objects/catalog.js b/test/integration/page-objects/catalog.js index 7359f3fb44..d97528f291 100644 --- a/test/integration/page-objects/catalog.js +++ b/test/integration/page-objects/catalog.js @@ -25,9 +25,12 @@ let AddTemplateModal = function(project) { this.save = function() { inputs.uncheck(this.processBox); inputs.check(this.saveBox); - this.cancel.click(); - browser.sleep(500); - return this; + this.continue.click(); + return browser.sleep(500).then(() => { + // lazy require + var OverviewPage = require('./overview').OverviewPage; + return new OverviewPage(this.project); // automatic redirect + }); }; }; @@ -44,6 +47,18 @@ class CatalogPage extends Page { h.waitForElem(tabs); return tabs; } + clickTile(heading) { + element(by.cssContainingText('.tile', heading)).click(); + } + clickCategory(heading) { + return this.clickTile(heading); + } + findTileBy(heading, namespace) { + var tiles = element.all(by.cssContainingText(heading)); + return namespace ? + tiles.all(by.cssContainingText(namespace)).first() : + tiles; + } clickBrowseCatalog() { return this._findTabs() .element(by.cssContainingText('a', 'Browse Catalog')) @@ -69,21 +84,39 @@ class CatalogPage extends Page { return window.ace.edit('add-component-editor').getValue(); }); } - submitImport() { + submitTemplate() { element(by.cssContainingText('.btn-primary','Create')).click(); return browser.sleep(500).then(() => { return new AddTemplateModal(this.project); }); } + submitImageStream() { + element(by.cssContainingText('.btn-primary','Create')).click(); + } processTemplate(templateStr) { this.clickImport(); return this.setImportValue(templateStr).then(() => { - return this.submitImport().then((addTemplateModal) => { + return this.submitTemplate().then((addTemplateModal) => { // implicit nav therefore returns new CreateFromTemplatePage() return addTemplateModal.process(); }); }); } + saveTemplate(templateStr) { + this.clickImport(); + return this.setImportValue(templateStr).then(() => { + return this.submitTemplate().then((addTemplateModal) => { + // implicit nav therefore returns new CreateFromTemplatePage() + return addTemplateModal.save(); + }); + }); + } + processImageStream(imageStreamStr) { + this.clickImport(); + return this.setImportValue(imageStreamStr).then(() => { + return this.submitImageStream(); + }); + } } exports.CatalogPage = CatalogPage; diff --git a/test/integration/page-objects/createFromURL.js b/test/integration/page-objects/createFromURL.js new file mode 100644 index 0000000000..8b0ae26904 --- /dev/null +++ b/test/integration/page-objects/createFromURL.js @@ -0,0 +1,45 @@ +'use strict'; + +const Page = require('./page').Page; +const logger = require('../helpers/logger'); +const h = require('../helpers.js'); + +class CreateFromURLPage extends Page { + constructor(project, menu) { + super(project, menu); + } + getUrl(qs) { + return 'create' + qs; + } + // TODO: for some reason, the Page.visit()'s use of helpers.goToPage() + // does not work here, so we have to override. + visit(qs) { + this.qs = qs; + logger.log('CreateFromURLPage.visit(): url:',this.getUrl(this.qs)); + return browser.get('create' + this.qs); + } + clickCreateNewProjectTab() { + return element(by.css('.nav-tabs')).isPresent() + .then((result) => { + if (result) { + element.all(by.css('.nav-tabs > li > a')).last().click(); + } + }); + } + selectExistingProject(stringFragment, uri) { + return element(by.css('.nav-tabs')).isPresent() + .then((result) => { + if (result) { + // select project from the dropdown and click next + element(by.model('selected.project')).click(); + element(by.css('.ui-select-search')).sendKeys(stringFragment).sendKeys(protractor.Key.ENTER); + h.clickAndGo('Next', uri); + } + }); + } + clickShowAdvanced() { + return element(by.css('[ng-click="advancedOptions = !advancedOptions"]')).click(); + } +} + +exports.CreateFromURLPage = CreateFromURLPage; diff --git a/test/integration/page-objects/createProject.js b/test/integration/page-objects/createProject.js index 46c873f17f..49dc09e65f 100644 --- a/test/integration/page-objects/createProject.js +++ b/test/integration/page-objects/createProject.js @@ -23,6 +23,7 @@ class CreateProjectPage extends Page { button.click(); return new CatalogPage(this.project); } + // TODO: there is an implicit navigation here, this should return a new Overview page for clarity createProject() { this.enterProjectInfo(); return this.submit(); diff --git a/test/integration/page-objects/imageStreams.js b/test/integration/page-objects/imageStreams.js new file mode 100644 index 0000000000..dad79f6a67 --- /dev/null +++ b/test/integration/page-objects/imageStreams.js @@ -0,0 +1,14 @@ +'use strict'; + +const Page = require('./page').Page; + +class ImageStreamsPage extends Page { + constructor(project, menu) { + super(project, menu); + } + getUrl() { + return 'project/' + this.project.name + '/browse/images'; + } +} + +exports.ImageStreamsPage = ImageStreamsPage; diff --git a/test/protractor.conf.js b/test/protractor.conf.js index 2ffa6733d8..fee50937fb 100644 --- a/test/protractor.conf.js +++ b/test/protractor.conf.js @@ -84,15 +84,15 @@ exports.config = { exclude: ['integration/e2e.js'], // We are temporarily excluding the e2e tests while we transition to the split merge queue // Alternatively, suites may be used. When run without a command line - // parameter, all suites will run. If run with --suite=smoke or - // --suite=smoke,full only the patterns matched by the specified suites will - // run. + // parameter, all suites will run. If run with --suite=create-project or + // --suite=create-projct,add-template-to-project, only the patterns matched by + // the specified suites will run. suites: { - // smoke: 'spec/smoketests/*.js', - 'add-to-project': 'integration/features/user_adds_template_to_project.spec.js', - 'create-project': 'integration/features/user_creates_project.spec.js' // This suite of tests should only require a running master api, it should not require a node - //'create-from-url': 'integration/create-from-url/*.js', - //e2e: 'integration/e2e.js' + 'create-project': 'integration/features/user_creates_project.spec.js', // This suite of tests should only require a running master api, it should not require a node + 'add-template-to-project': 'integration/features/user_adds_template_to_project.spec.js', + 'add-imagestream-to-project': 'integration/features/user_adds_imagestream_to_project.spec.js', + 'create-from-url': 'integration/features/user_creates_from_url.spec.js', + // e2e: 'integration/e2e.js' }, // ---------------------------------------------------------------------------