diff --git a/deploy/ci/travis/run-e2e-tests.sh b/deploy/ci/travis/run-e2e-tests.sh index b14a25cb2c..6a796b996d 100755 --- a/deploy/ci/travis/run-e2e-tests.sh +++ b/deploy/ci/travis/run-e2e-tests.sh @@ -91,7 +91,7 @@ fi # Output backend log if the tests failed if [ "${RUN_TYPE}" == "quick" ]; then if [ $RESULT -ne 0 ]; then - cat outputs/backend.log + cat src/jetstream/backend.log fi fi diff --git a/protractor.conf.js b/protractor.conf.js index 765a3fb99c..e784e474f8 100644 --- a/protractor.conf.js +++ b/protractor.conf.js @@ -35,8 +35,11 @@ try { process.exit(1); } +// This is the maximum amount of time ALL before/after/it's must execute in +const timeout = 40000 + exports.config = { - allScriptsTimeout: 30000, + allScriptsTimeout: timeout, specs: [ './src/test-e2e/**/*-e2e.spec.ts', ], @@ -53,7 +56,7 @@ exports.config = { framework: 'jasmine', jasmineNodeOpts: { showColors: true, - defaultTimeoutInterval: 30000, + defaultTimeoutInterval: timeout, print: function () {} }, params: secrets, diff --git a/src/frontend/app/shared/components/list/list.component.html b/src/frontend/app/shared/components/list/list.component.html index 6472c720b7..efe0070804 100644 --- a/src/frontend/app/shared/components/list/list.component.html +++ b/src/frontend/app/shared/components/list/list.component.html @@ -30,18 +30,18 @@
- + {{column.headerCell()}} + sort + + sort +
diff --git a/src/frontend/app/test-framework/spec-helper.spec.ts b/src/frontend/app/test-framework/spec-helper.spec.ts index 8a17a567db..2142b8eddc 100644 --- a/src/frontend/app/test-framework/spec-helper.spec.ts +++ b/src/frontend/app/test-framework/spec-helper.spec.ts @@ -6,15 +6,15 @@ import { TestBed } from '@angular/core/testing'; * a global beforeEach so we don't have to add it to all the necessary * spec files. */ -beforeEach( () => { +beforeEach(() => { TestBed.configureTestingModule({ - providers: [ { provide: APP_BASE_HREF, useValue: '/' } ] + providers: [{ provide: APP_BASE_HREF, useValue: '/' }] }); }); /** * Bump up the Jasmine timeout from 5 seconds */ -beforeAll( () => { +beforeAll(() => { jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; }); diff --git a/src/jetstream/plugins/cfapppush/deploy.go b/src/jetstream/plugins/cfapppush/deploy.go index 3cbd12a283..96fe1e73e7 100644 --- a/src/jetstream/plugins/cfapppush/deploy.go +++ b/src/jetstream/plugins/cfapppush/deploy.go @@ -72,9 +72,9 @@ type DeployAppMessageSender interface { func (cfAppPush *CFAppPush) deploy(echoContext echo.Context) error { - cnsiGUID := echoContext.Param("cnsiGUID") - orgGUID := echoContext.Param("orgGUID") - spaceGUID := echoContext.Param("spaceGUID") + cnsiGUID := echoContext.Param("cnsiGuid") + orgGUID := echoContext.Param("orgGuid") + spaceGUID := echoContext.Param("spaceGuid") spaceName := echoContext.QueryParam("space") orgName := echoContext.QueryParam("org") diff --git a/src/test-e2e/application/application-delete-e2e.spec.ts b/src/test-e2e/application/application-delete-e2e.spec.ts index ac4edb4bb9..9ccc220d6c 100644 --- a/src/test-e2e/application/application-delete-e2e.spec.ts +++ b/src/test-e2e/application/application-delete-e2e.spec.ts @@ -34,7 +34,8 @@ describe('Application Delete', function () { // Delete tests for a simple app with no routes describe('Simple App', () => { beforeAll(() => { - const endpointName = e2e.secrets.getDefaultCFEndpoint().name; + const defaultCf = e2e.secrets.getDefaultCFEndpoint(); + const endpointName = defaultCf.name; cfGuid = e2e.helper.getEndpointGuid(e2e.info, endpointName); const testTime = (new Date()).toISOString(); testAppName = ApplicationE2eHelper.createApplicationName(testTime); @@ -42,7 +43,8 @@ describe('Application Delete', function () { cfGuid, e2e.secrets.getDefaultCFEndpoint().testOrg, e2e.secrets.getDefaultCFEndpoint().testSpace, - testAppName + testAppName, + defaultCf ).then(appl => app = appl); }); diff --git a/src/test-e2e/application/application-deploy-e2e.spec.ts b/src/test-e2e/application/application-deploy-e2e.spec.ts index 6e45b40d95..cd12f6b290 100644 --- a/src/test-e2e/application/application-deploy-e2e.spec.ts +++ b/src/test-e2e/application/application-deploy-e2e.spec.ts @@ -8,6 +8,7 @@ import { SideNavigation, SideNavMenuItem } from '../po/side-nav.po'; import { ApplicationE2eHelper } from './application-e2e-helpers'; import { ApplicationSummary } from './application-summary.po'; + const until = protractor.ExpectedConditions; let nav: SideNavigation; @@ -45,6 +46,7 @@ describe('Application Deploy', function () { beforeEach(() => nav.goto(SideNavMenuItem.Applications)); it('Should deploy app from GitHub', () => { + const loggingPrefix = 'Application Deploy: Deploy from Github:'; expect(appWall.isActivePage()).toBeTruthy(); // Should be on deploy app modal @@ -52,6 +54,7 @@ describe('Application Deploy', function () { expect(deployApp.header.getTitleText()).toBe('Deploy'); // Check the steps + e2e.log(`${loggingPrefix} Checking Steps`); deployApp.stepper.getStepNames().then(steps => { expect(steps.length).toBe(4); expect(steps[0]).toBe('Cloud Foundry'); @@ -59,6 +62,7 @@ describe('Application Deploy', function () { expect(steps[2]).toBe('Source Config'); expect(steps[3]).toBe('Deploy'); }); + e2e.log(`${loggingPrefix} Cf/Org/Space Step`); expect(deployApp.stepper.getActiveStepName()).toBe('Cloud Foundry'); promise.all([ deployApp.stepper.getStepperForm().getText('cf'), @@ -79,12 +83,15 @@ describe('Application Deploy', function () { expect(deployApp.stepper.canNext()).toBeTruthy(); deployApp.stepper.next(); + e2e.log(`${loggingPrefix} Source Step`); expect(deployApp.stepper.getActiveStepName()).toBe('Source'); expect(deployApp.stepper.canNext()).toBeFalsy(); deployApp.stepper.getStepperForm().fill({ 'projectname': testApp }); deployApp.stepper.waitUntilCanNext('Next'); deployApp.stepper.next(); + + e2e.log(`${loggingPrefix} Source Config Step`); expect(deployApp.stepper.getActiveStepName()).toBe('Source Config'); const commits = deployApp.getCommitList(); @@ -99,6 +106,8 @@ describe('Application Deploy', function () { commits.selectRow(0); expect(deployApp.stepper.canNext()).toBeTruthy(); + e2e.log(`${loggingPrefix} Select a commit (selected)`); + // Turn off waiting for Angular - the web socket connection is kept open which means the tests will timeout // waiting for angular if we don't turn off. browser.waitForAngularEnabled(false); @@ -106,12 +115,15 @@ describe('Application Deploy', function () { // Press next to deploy the app deployApp.stepper.next(); + e2e.log(`${loggingPrefix} Deploying Step (wait)`); // Wait until app summary button can be pressed deployApp.stepper.waitUntilCanNext('Go to App Summary'); + e2e.log(`${loggingPrefix} Deploying Step (after wait)`); // Click next deployApp.stepper.next(); + e2e.log(`${loggingPrefix} Waiting For Application Summary Page`); // Should be app summary browser.wait(ApplicationSummary.detect() .then(appSummary => { @@ -119,7 +131,10 @@ describe('Application Deploy', function () { appSummary.header.waitForTitleText(appName); return appSummary.cfGuid; }) - .then(cfGuid => applicationE2eHelper.deleteApplication(null, { appName }))); + .then(cfGuid => { + e2e.log(`${loggingPrefix} Starting application delete`); + return applicationE2eHelper.deleteApplication(null, { appName }); + })); }); }); diff --git a/src/test-e2e/application/application-e2e-helpers.ts b/src/test-e2e/application/application-e2e-helpers.ts index 82df7b56e2..7f69e3dd78 100644 --- a/src/test-e2e/application/application-e2e-helpers.ts +++ b/src/test-e2e/application/application-e2e-helpers.ts @@ -1,3 +1,4 @@ +import { E2EConfigCloudFoundry } from '../e2e.types'; import { browser, promise } from 'protractor'; import { IApp, IRoute, ISpace } from '../../frontend/app/core/cf-api.types'; @@ -159,12 +160,10 @@ export class ApplicationE2eHelper { .catch(err => fail(`Failed to delete app or associated dependencies: ${err}`)); } - createApp(cfGuid: string, orgName: string, spaceName: string, appName: string) { + createApp(cfGuid: string, orgName: string, spaceName: string, appName: string, endpoint: E2EConfigCloudFoundry) { return browser.driver.wait( - this.cfHelper.addOrgIfMissing(cfGuid, orgName) - .then(org => { - return this.cfHelper.fetchSpace(cfGuid, org.metadata.guid, spaceName); - }) + this.cfHelper.addOrgIfMissingForEndpointUsers(cfGuid, endpoint, orgName) + .then(org => this.cfHelper.addSpaceIfMissingForEndpointUsers(cfGuid, org.metadata.guid, org.entity.name, spaceName, endpoint)) .then(space => { expect(space).not.toBeNull(); return promise.all([ diff --git a/src/test-e2e/cloud-foundry/org-level/cf-org-level-e2e.spec.ts b/src/test-e2e/cloud-foundry/org-level/cf-org-level-e2e.spec.ts index da5c0b414d..98860dd63f 100644 --- a/src/test-e2e/cloud-foundry/org-level/cf-org-level-e2e.spec.ts +++ b/src/test-e2e/cloud-foundry/org-level/cf-org-level-e2e.spec.ts @@ -1,8 +1,8 @@ import { browser } from 'protractor'; -import { ApplicationE2eHelper } from '../../application/application-e2e-helpers'; import { e2e, E2ESetup } from '../../e2e'; import { E2EConfigCloudFoundry } from '../../e2e.types'; +import { CFHelpers } from '../../helpers/cf-helpers'; import { ConsoleUserType } from '../../helpers/e2e-helpers'; import { CfOrgLevelPage } from './cf-org-level-page.po'; @@ -12,7 +12,7 @@ describe('CF - Org Level - ', () => { let orgPage: CfOrgLevelPage; let e2eSetup: E2ESetup; let defaultCf: E2EConfigCloudFoundry; - let applicationE2eHelper: ApplicationE2eHelper; + let cfHelper: CFHelpers; function setup(user: ConsoleUserType) { e2eSetup = e2e.setup(ConsoleUserType.admin) @@ -22,7 +22,7 @@ describe('CF - Org Level - ', () => { .connectAllEndpoints(ConsoleUserType.user) .loginAs(user) .getInfo(); - applicationE2eHelper = new ApplicationE2eHelper(e2eSetup); + cfHelper = new CFHelpers(e2eSetup); } function testBreadcrumb() { @@ -43,7 +43,7 @@ describe('CF - Org Level - ', () => { function navToPage() { defaultCf = e2e.secrets.getDefaultCFEndpoint(); const endpointGuid = e2e.helper.getEndpointGuid(e2e.info, defaultCf.name); - browser.wait(applicationE2eHelper.cfHelper.fetchOrg(endpointGuid, defaultCf.testOrg).then((org => { + browser.wait(cfHelper.fetchOrg(endpointGuid, defaultCf.testOrg).then((org => { orgPage = CfOrgLevelPage.forEndpoint(endpointGuid, org.metadata.guid); orgPage.navigateTo(); orgPage.waitForPageOrChildPage(); diff --git a/src/test-e2e/cloud-foundry/org-level/org-spaces-e2e.spec.ts b/src/test-e2e/cloud-foundry/org-level/org-spaces-e2e.spec.ts new file mode 100644 index 0000000000..39b5b745b9 --- /dev/null +++ b/src/test-e2e/cloud-foundry/org-level/org-spaces-e2e.spec.ts @@ -0,0 +1,282 @@ +import { browser, promise } from 'protractor'; + +import { IOrganization } from '../../../frontend/app/core/cf-api.types'; +import { APIResource } from '../../../frontend/app/store/types/api.types'; +import { e2e, E2ESetup, E2E } from '../../e2e'; +import { E2EConfigCloudFoundry } from '../../e2e.types'; +import { CFHelpers } from '../../helpers/cf-helpers'; +import { ConsoleUserType, E2EHelpers } from '../../helpers/e2e-helpers'; +import { ListComponent } from '../../po/list.po'; +import { CfOrgLevelPage } from './cf-org-level-page.po'; + +const customOrgSpacesLabel = E2EHelpers.e2eItemPrefix + (process.env.CUSTOM_APP_LABEL || process.env.USER) + '-org-spaces-test'; + +describe('Org Spaces List -', () => { + + let cfHelper: CFHelpers; + let defaultCf: E2EConfigCloudFoundry; + let orgPage: CfOrgLevelPage; + const spaceList = new ListComponent(); + let orgGuid: string; + let endpointGuid: string; + + const timeAllowed = 60000; + + function createSpaceNames(count: number): string[] { + const spaceNames = []; + for (let i = 0; i < count; i++) { + spaceNames.push(E2EHelpers.createCustomName(customOrgSpacesLabel + i)); + } + return spaceNames; + } + + function chainCreateSpace(org: APIResource, spaceNames: string[]): promise.Promise { + return spaceNames.reduce((promiseChain, name) => { + return promiseChain.then(() => { + // Ensure there's a gap so that the 'created_at' is different + browser.sleep(1100); + return cfHelper.addSpaceIfMissingForEndpointUsers( + endpointGuid, + org.metadata.guid, + org.entity.name, + name, + defaultCf, + true); + }); + }, promise.fullyResolved('')); + } + + function concurrentCreateSpace(org: APIResource, spaceNames: string[]): promise.Promise { + return promise.all(spaceNames.map(name => cfHelper.addSpaceIfMissingForEndpointUsers( + endpointGuid, + org.metadata.guid, + org.entity.name, + name, + defaultCf, + true))); + } + + function setup(orgName: string, spaceNames: string[], orderImportant: boolean) { + defaultCf = e2e.secrets.getDefaultCFEndpoint(); + endpointGuid = e2e.helper.getEndpointGuid(e2e.info, defaultCf.name); + + browser.wait( + cfHelper.addOrgIfMissingForEndpointUsers(endpointGuid, defaultCf, orgName) + .then((org: APIResource) => { + orgGuid = org.metadata.guid; + if (!spaceNames || !spaceNames.length) { + return promise.fullyResolved(org); + } + // Chain the creation of the spaces to ensure there's a nice sequential 'created_at' value to be used for sort tests + const promises = orderImportant ? + chainCreateSpace(org, spaceNames) : + concurrentCreateSpace(org, spaceNames); + + return promises.then(() => org.metadata.guid); + }) + .then(navToOrgSpaces) + ); + } + + function navToOrgSpaces() { + orgPage = CfOrgLevelPage.forEndpoint(endpointGuid, orgGuid); + orgPage.navigateTo(); + orgPage.waitForPageOrChildPage(); + orgPage.loadingIndicator.waitUntilNotShown(); + orgPage.goToSpacesTab(); + expect(spaceList.isTableView()).toBeFalsy(); + } + + function tearDown(orgName: string) { + expect(orgName).not.toBeNull(); + browser.wait(cfHelper.deleteOrgIfExisting(endpointGuid, orgName)); + } + + beforeAll(() => { + const e2eSetup = e2e.setup(ConsoleUserType.admin) + .clearAllEndpoints() + .registerDefaultCloudFoundry() + .connectAllEndpoints(ConsoleUserType.admin) + .loginAs(ConsoleUserType.admin) + .getInfo(); + cfHelper = new CFHelpers(e2eSetup); + }); + + describe('No Pages -', () => { + const orgName = E2EHelpers.createCustomName(customOrgSpacesLabel) + '-no-pages'; + beforeAll(() => { + setup(orgName, [], false); + }); + + beforeEach(navToOrgSpaces); + + it('Should show no entities message', () => { + expect(spaceList.isDisplayed()).toBeTruthy(); + spaceList.empty.getDefault().waitUntilShown(); + expect(spaceList.empty.getDefault().getComponent().getText()).toBe('There are no spaces'); + expect(spaceList.cards.getCardCount()).toBe(0); + }); + + afterAll(() => tearDown(orgName)); + }); + + describe('Single Page -', () => { + const orgName = E2EHelpers.createCustomName(customOrgSpacesLabel) + '-1-page'; + + let spaceNames; + + function testSortBy(sortFieldName: string) { + const sortFieldForm = spaceList.header.getSortFieldForm(); + sortFieldForm.fill({ 'sort-field': sortFieldName }); + + let expectedTitleOrder: string[]; + spaceList.cards.getCardsMetadata().then(cards => { + const originalTitleOrder = cards.map(card => card.title); + expectedTitleOrder = new Array(originalTitleOrder.length); + for (let i = 0; i < originalTitleOrder.length; i++) { + expectedTitleOrder[originalTitleOrder.length - i - 1] = originalTitleOrder[i]; + } + }); + + spaceList.header.toggleSortOrder(); + + spaceList.cards.getCardsMetadata().then(cards => { + const newTitleOrder = cards.map(card => card.title); + expect(expectedTitleOrder).toEqual(newTitleOrder); + }); + } + + beforeAll(() => { + spaceNames = createSpaceNames(3); + setup(orgName, spaceNames, true); + expect(spaceList.getTotalResults()).toBeLessThanOrEqual(9); + expect(spaceList.pagination.isDisplayed()).toBeFalsy(); + }, timeAllowed); + + afterAll(() => tearDown(orgName), timeAllowed); + + it('sort by name', () => { + testSortBy('Name'); + }); + + it('sort by creation', () => { + testSortBy('Creation'); + }); + + it('text filter by existing', () => { + // Clear and check initial cards + spaceList.header.clearSearchText(); + expect(spaceList.header.getSearchText()).toBeFalsy(); + expect(spaceList.cards.getCardCount()).toBeGreaterThanOrEqual(spaceNames.length); + + // Apply filter + const spaceToFind = spaceNames[2]; + spaceList.header.setSearchText(spaceToFind); + + // Check for single card + expect(spaceList.header.getSearchText()).toEqual(spaceToFind); + expect(spaceList.cards.getCardCount()).toBe(1); + expect(spaceList.cards.findCardByTitle(spaceToFind)).toBeDefined(); + }); + + it('text filter by non-existing', () => { + // Clear and check initial cards + spaceList.header.clearSearchText(); + expect(spaceList.header.getSearchText()).toBeFalsy(); + expect(spaceList.cards.getCardCount()).toBeGreaterThanOrEqual(spaceNames.length); + + // Apply filter + const spaceToNotFind = 'sdfst4654324543224 s5d4x4g5g gdg4fdg 5fdg'; + spaceList.header.setSearchText(spaceToNotFind); + + expect(spaceList.header.getSearchText()).toEqual(spaceToNotFind); + + // Check for zero cards + expect(spaceList.cards.getCardCount()).toBe(0); + + // Check for 'no spaces' message + spaceList.empty.getDefault().waitUntilShown(); + expect(spaceList.empty.getDefault().getComponent().getText()).toBe('There are no spaces'); + }); + + it('single page pagination settings', () => { + expect(spaceList.pagination.isDisplayed()).toBeFalsy(); + }); + + }); + + describe('Multi Page -', () => { + const orgName = E2EHelpers.createCustomName(customOrgSpacesLabel) + '-multi-page'; + + let spaceNames; + + beforeAll(() => { + spaceNames = createSpaceNames(11); + setup(orgName, spaceNames, false); + expect(spaceList.getTotalResults()).toBeGreaterThanOrEqual(spaceNames.length); + }, timeAllowed); + + afterAll(() => tearDown(orgName), timeAllowed); + + function testStartingPosition() { + // General expects for all tests in this section + expect(spaceList.getTotalResults()).toBeLessThan(80); + expect(spaceList.pagination.isPresent()).toBeTruthy(); + + expect(spaceList.cards.getCardCount()).toBe(9); + expect(spaceList.pagination.getPageSize()).toEqual('9'); + expect(spaceList.pagination.getTotalResults()).toBeGreaterThan(9); + expect(spaceList.pagination.getTotalResults()).toBeLessThanOrEqual(18); + + expect(spaceList.pagination.getNavFirstPage().getComponent().isEnabled()).toBeFalsy(); + expect(spaceList.pagination.getNavPreviousPage().getComponent().isEnabled()).toBeFalsy(); + expect(spaceList.pagination.getNavNextPage().getComponent().isEnabled()).toBeTruthy(); + expect(spaceList.pagination.getNavLastPage().getComponent().isEnabled()).toBeTruthy(); + } + + beforeEach(testStartingPosition, timeAllowed); + + afterEach(testStartingPosition, timeAllowed); + + it('Initial Pagination Values', () => { }); + + it('Next and Previous Page', () => { + spaceList.pagination.getNavNextPage().getComponent().click(); + + expect(spaceList.pagination.getNavFirstPage().getComponent().isEnabled()).toBeTruthy(); + expect(spaceList.pagination.getNavPreviousPage().getComponent().isEnabled()).toBeTruthy(); + expect(spaceList.pagination.getNavNextPage().getComponent().isEnabled()).toBeFalsy(); + expect(spaceList.pagination.getNavLastPage().getComponent().isEnabled()).toBeFalsy(); + + spaceList.pagination.getNavPreviousPage().getComponent().click(); + }); + + it('Last and First Page', () => { + spaceList.pagination.getNavLastPage().getComponent().click(); + + expect(spaceList.pagination.getNavFirstPage().getComponent().isEnabled()).toBeTruthy(); + expect(spaceList.pagination.getNavPreviousPage().getComponent().isEnabled()).toBeTruthy(); + expect(spaceList.pagination.getNavNextPage().getComponent().isEnabled()).toBeFalsy(); + expect(spaceList.pagination.getNavLastPage().getComponent().isEnabled()).toBeFalsy(); + + spaceList.pagination.getNavFirstPage().getComponent().click(); + }); + + it('Change Page Size', () => { + + spaceList.pagination.setPageSize('80'); + expect(spaceList.cards.getCardCount()).toBeGreaterThan(9); + + expect(spaceList.pagination.getNavFirstPage().getComponent().isEnabled()).toBeFalsy(); + expect(spaceList.pagination.getNavPreviousPage().getComponent().isEnabled()).toBeFalsy(); + expect(spaceList.pagination.getNavNextPage().getComponent().isEnabled()).toBeFalsy(); + expect(spaceList.pagination.getNavLastPage().getComponent().isEnabled()).toBeFalsy(); + + spaceList.pagination.setPageSize('9'); + expect(spaceList.cards.getCardCount()).toBe(9); + + }); + + }); + +}); diff --git a/src/test-e2e/cloud-foundry/organizations-spaces-e2e.spec.ts b/src/test-e2e/cloud-foundry/organizations-spaces-e2e.spec.ts index 0e6d148374..9fc34eeb6d 100644 --- a/src/test-e2e/cloud-foundry/organizations-spaces-e2e.spec.ts +++ b/src/test-e2e/cloud-foundry/organizations-spaces-e2e.spec.ts @@ -44,11 +44,7 @@ describe('CF - Manage Organizations and Spaces', () => { cloudFoundry.waitForPageOrChildPage(); }); - afterAll(() => { - return cfHelper.deleteSpaceIfExisting(endpointGuid, testSpaceName).then(() => - cfHelper.deleteOrgIfExisting(endpointGuid, testOrgName) - ); - }); + afterAll(() => cfHelper.deleteOrgIfExisting(endpointGuid, testOrgName)); it('Should validate org name', () => { const cardView = cloudFoundry.goToOrgView(); @@ -162,7 +158,7 @@ describe('CF - Manage Organizations and Spaces', () => { space.openActionMenu().then(menu => { menu.clickItem('Delete'); ConfirmDialogComponent.expectDialogAndConfirm('Delete', 'Delete Space'); - cardView.cards.getCardCound().then(c => { + cardView.cards.getCardCount().then(c => { expect(c).toBe(0); }); }); diff --git a/src/test-e2e/helpers/cf-helpers.ts b/src/test-e2e/helpers/cf-helpers.ts index c162f02d58..c93430f292 100644 --- a/src/test-e2e/helpers/cf-helpers.ts +++ b/src/test-e2e/helpers/cf-helpers.ts @@ -1,7 +1,8 @@ import { promise } from 'protractor'; -import { IOrganization, IRoute } from '../../frontend/app/core/cf-api.types'; -import { APIResource } from '../../frontend/app/store/types/api.types'; +import { IOrganization, IRoute, ISpace } from '../../frontend/app/core/cf-api.types'; +import { APIResource, CFResponse } from '../../frontend/app/store/types/api.types'; +import { CfUser } from '../../frontend/app/store/types/user.types'; import { e2e, E2ESetup } from '../e2e'; import { E2EConfigCloudFoundry } from '../e2e.types'; import { CFRequestHelpers } from './cf-request-helpers'; @@ -12,38 +13,51 @@ export class CFHelpers { cachedDefaultCfGuid: string; cachedDefaultOrgGuid: string; cachedDefaultSpaceGuid: string; + cachedAdminGuid: string; + cachedNonAdminGuid: string; constructor(public e2eSetup: E2ESetup) { this.cfRequestHelper = new CFRequestHelpers(e2eSetup); } + private assignAdminAndUserGuids(cnsiGuid: string, endpoint: E2EConfigCloudFoundry): promise.Promise { + if (this.cachedAdminGuid && this.cachedNonAdminGuid) { + return promise.fullyResolved({}); + } + return this.fetchUsers(cnsiGuid).then(users => { + const testUser = this.findUser(users, endpoint.creds.nonAdmin.username); + const testAdminUser = this.findUser(users, endpoint.creds.admin.username); + expect(testUser).toBeDefined(); + expect(testAdminUser).toBeDefined(); + this.cachedNonAdminGuid = testUser.metadata.guid; + this.cachedAdminGuid = testAdminUser.metadata.guid; + }); + } + addOrgIfMissingForEndpointUsers( guid: string, endpoint: E2EConfigCloudFoundry, testOrgName: string ): promise.Promise> { - let testAdminUser, testUser; - return this.fetchUsers(guid).then(users => { - testUser = this.findUser(users, endpoint.creds.nonAdmin.username); - testAdminUser = this.findUser(users, endpoint.creds.admin.username); - expect(testUser).toBeDefined(); - expect(testAdminUser).toBeDefined(); - return this.addOrgIfMissing(guid, testOrgName, testAdminUser.metadata.guid, testUser.metadata.guid); + return this.assignAdminAndUserGuids(guid, endpoint).then(() => { + expect(this.cachedNonAdminGuid).not.toBeNull(); + expect(this.cachedAdminGuid).not.toBeNull(); + return this.addOrgIfMissing(guid, testOrgName, this.cachedAdminGuid, this.cachedNonAdminGuid); }); } - private findUser(users: any, name: string) { + private findUser(users: any, name: string): APIResource { return users.find(user => user && user.entity && user.entity.username === name); } - addOrgIfMissing(cnsiGuid, orgName, adminGuid?: string, userGuid?: string): promise.Promise> { + addOrgIfMissing(cnsiGuid, orgName, adminGuid, userGuid): promise.Promise> { let added; - return this.cfRequestHelper.sendCfGet(cnsiGuid, 'organizations?q=name IN ' + orgName).then(json => { - if (json.total_results === 0) { + return this.fetchOrg(cnsiGuid, orgName).then(org => { + if (!org) { added = true; return this.cfRequestHelper.sendCfPost>(cnsiGuid, 'organizations', { name: orgName }); } - return json.resources[0]; + return org; }).then(newOrg => { if (!added || !adminGuid || !userGuid) { // No need to mess around with permissions, it exists already. @@ -60,27 +74,28 @@ export class CFHelpers { }); } - addSpaceIfMissing(cnsiGuid, orgGuid, orgName, spaceName, adminGuid, userGuid) { - return this.cfRequestHelper.sendCfGet(cnsiGuid, - 'spaces?inline-relations-depth=1&include-relations=organization&q=name IN ' + spaceName) - .then(function (json) { - let add = false; - if (json.total_results === 0) { - add = true; - } else if (json.total_results > 0) { - add = !!json.resources.find(r => { - return r && r.entity && r.entity.organization && r.entity.organization.entity && r.entity.organization.entity.name === orgName; - }); - } - if (add) { - return this.cfRequestHelper.sendCfPost(cnsiGuid, 'pp/v1/proxy/v2/spaces', - { - name: spaceName, - manager_guids: [adminGuid], - developer_guids: [userGuid, adminGuid], - organization_guid: orgGuid - }); - } + addSpaceIfMissingForEndpointUsers( + cnsiGuid, + orgGuid, + orgName, + spaceName, + endpoint: E2EConfigCloudFoundry, + skipExistsCheck = false, + ): promise.Promise> { + return this.assignAdminAndUserGuids(cnsiGuid, endpoint).then(() => { + expect(this.cachedNonAdminGuid).not.toBeNull(); + return skipExistsCheck ? + this.baseAddSpace(cnsiGuid, orgGuid, orgName, spaceName, this.cachedNonAdminGuid) : + this.addSpaceIfMissing(cnsiGuid, orgGuid, orgName, spaceName, this.cachedNonAdminGuid); + + }); + } + + addSpaceIfMissing(cnsiGuid, orgGuid, orgName, spaceName, userGuid): promise.Promise> { + const that = this; + return this.fetchSpace(cnsiGuid, orgGuid, spaceName) + .then(function (space) { + return space ? space : that.baseAddSpace(cnsiGuid, orgGuid, orgName, spaceName, userGuid); }); } @@ -91,23 +106,17 @@ export class CFHelpers { } deleteOrgIfExisting(cnsiGuid: string, orgName: string) { - return this.cfRequestHelper.sendCfGet(cnsiGuid, 'organizations?q=name IN ' + orgName).then(json => { - if (json.total_results > 0) { - const org = json.resources[0]; - if (org) { - return this.cfRequestHelper.sendCfDelete(cnsiGuid, 'organizations/' + org.metadata.guid); - } + return this.fetchOrg(cnsiGuid, orgName).then(org => { + if (org) { + return this.cfRequestHelper.sendCfDelete(cnsiGuid, 'organizations/' + org.metadata.guid + '?recursive=true&async=false'); } }); } - deleteSpaceIfExisting(cnsiGuid: string, spaceName: string) { - return this.cfRequestHelper.sendCfGet(cnsiGuid, 'spaces?q=name IN ' + spaceName).then(json => { - if (json.total_results > 0) { - const space = json.resources[0]; - if (space) { - return this.cfRequestHelper.sendCfDelete(cnsiGuid, 'spaces/' + space.metadata.guid); - } + deleteSpaceIfExisting(cnsiGuid: string, orgGuid: string, spaceName: string) { + return this.fetchSpace(cnsiGuid, orgGuid, spaceName).then(space => { + if (space) { + return this.cfRequestHelper.sendCfDelete(cnsiGuid, 'spaces/' + space.metadata.guid); } }); } @@ -125,9 +134,6 @@ export class CFHelpers { return org; } return null; - }).catch(err => { - e2e.log(`Failed to fetch organisation with name '${orgName}' from endpoint ${cnsiGuid}`); - throw new Error(err); }); } @@ -163,6 +169,17 @@ export class CFHelpers { return this.cfRequestHelper.sendCfDelete(cnsiGuid, 'apps/' + appGuid); } + baseAddSpace(cnsiGuid, orgGuid, orgName, spaceName, userGuid): promise.Promise> { + const cfRequestHelper = this.cfRequestHelper; + return cfRequestHelper.sendCfPost>(cnsiGuid, 'spaces', + { + name: spaceName, + manager_guids: [], + developer_guids: [userGuid], + organization_guid: orgGuid + }); + } + fetchAppRoutes(cnsiGuid: string, appGuid: string): promise.Promise[]> { return this.cfRequestHelper.sendCfGet(cnsiGuid, `apps/${appGuid}/routes`).then(res => res.resources); } diff --git a/src/test-e2e/po/form.po.ts b/src/test-e2e/po/form.po.ts index a1b263511e..dab7232428 100644 --- a/src/test-e2e/po/form.po.ts +++ b/src/test-e2e/po/form.po.ts @@ -24,6 +24,7 @@ export interface FormItem { tag: string; valid: string; error: string; + id: string; } // Page Object for a form field @@ -96,17 +97,20 @@ export class FormComponent extends Component { clear: elm.clear, click: elm.click, tag: elm.getTagName(), + id: elm.getAttribute('id') }; } // Get the form field with the specified name or formcontrolname getField(ctrlName: string): ElementFinder { const fields = this.getFields().filter((elm => { - return elm.getAttribute('name').then(name => { - return elm.getAttribute('formcontrolname').then(formcontrolname => { - const nameAtt = name || formcontrolname; - return nameAtt.toLowerCase() === ctrlName; - }); + return promise.all([ + elm.getAttribute('name'), + elm.getAttribute('formcontrolname'), + elm.getAttribute('id') + ]).then(([name, formcontrolname, id]) => { + const nameAtt = name || formcontrolname || id; + return nameAtt.toLowerCase() === ctrlName; }); })); expect(fields.count()).toBe(1); @@ -151,7 +155,7 @@ export class FormComponent extends Component { return this.getFieldsMapped().then(items => { const form = {}; items.forEach((item: FormItem) => { - const id = item.name || item.formControlName; + const id = item.name || item.formControlName || item.id; form[id.toLowerCase()] = item; }); return form; diff --git a/src/test-e2e/po/list.po.ts b/src/test-e2e/po/list.po.ts index 1f80c29f7b..ffaf384d49 100644 --- a/src/test-e2e/po/list.po.ts +++ b/src/test-e2e/po/list.po.ts @@ -1,8 +1,9 @@ -import { by, element, promise, browser, protractor, Key } from 'protractor'; +import { browser, by, element, Key, promise, protractor } from 'protractor'; import { ElementArrayFinder, ElementFinder } from 'protractor/built'; + import { Component } from './component.po'; +import { FormComponent } from './form.po'; import { MetaCard } from './meta-card.po'; -import { PaginatorComponent } from './paginator.po'; const until = protractor.ExpectedConditions; @@ -73,7 +74,7 @@ export class ListCardComponent extends Component { super(locator); } - getCardCound() { + getCardCount() { const noRows = this.locator.all(by.css('.no-rows')); return noRows.count().then(rows => { return rows === 1 ? 0 : this.getCards().count(); @@ -112,21 +113,17 @@ export class ListCardComponent extends Component { export class ListHeaderComponent extends Component { constructor(locator: ElementFinder) { - super(locator); - } - - getListHeader(): ElementFinder { - return this.locator.element(by.css('.list-component__header')); + super(locator.element(by.css('.list-component__header'))); } getFilterFormField(): ElementArrayFinder { - return this.getListHeader() + return this.locator .element(by.css('.list-component__header__left--multi-filters')) .all(by.tagName('mat-form-field')); } getRightHeaderSection(): ElementFinder { - return this.getListHeader().element(by.css('.list-component__header__right')); + return this.locator.element(by.css('.list-component__header__right')); } getSearchInputField(): ElementFinder { @@ -136,8 +133,16 @@ export class ListHeaderComponent extends Component { setSearchText(text: string): promise.Promise { const searchField = this.getSearchInputField(); searchField.click(); - searchField.sendKeys(text); - return searchField.sendKeys(Key.RETURN); + searchField.clear(); + return searchField.sendKeys(text); + } + + clearSearchText() { + const searchField = this.getSearchInputField(); + searchField.click(); + searchField.clear(); + searchField.sendKeys('a'); + searchField.sendKeys(Key.BACK_SPACE); } getSearchText(): promise.Promise { @@ -169,6 +174,78 @@ export class ListHeaderComponent extends Component { return this.getRightHeaderSection().element(by.css('#list-card-toggle')); } + private findSortSection(): ElementFinder { + return this.locator.element(by.css('.list-component__header__right .sort')); + } + + getSortFieldForm(): FormComponent { + return new FormComponent(this.findSortSection()); + } + + toggleSortOrder() { + this.findSortSection().element(by.css('button')).click(); + } + +} + +export class ListPaginationComponent extends Component { + constructor(listComponent: ElementFinder) { + super(listComponent.element(by.tagName('.list-component__paginator'))); + } + + getTotalResults() { + return this.locator.element(by.css('.mat-paginator-range-label')).getText().then(label => { + const index = label.indexOf('of '); + if (index > 0) { + const value = label.substr(index + 3).trim(); + return parseInt(value, 10); + } + return -1; + }); + } + + private findPageSizeSection(): ElementFinder { + return this.locator.element(by.css('.mat-paginator-page-size')); + } + + getPageSize(): promise.Promise { + return this.getPageSizeForm().getText('mat-select-1'); + } + + setPageSize(pageSize): promise.Promise { + return this.getPageSizeForm().fill({ 'mat-select-1': pageSize }); + } + + getPageSizeForm(): FormComponent { + return new FormComponent(this.findPageSizeSection()); + } + + getNavFirstPage(): Component { + return new Component(this.locator.element(by.css('.mat-paginator-navigation-first'))); + } + + getNavLastPage(): Component { + return new Component(this.locator.element(by.css('.mat-paginator-navigation-last'))); + } + + getNavPreviousPage(): Component { + return new Component(this.locator.element(by.css('.mat-paginator-navigation-previous'))); + } + + getNavNextPage(): Component { + return new Component(this.locator.element(by.css('.mat-paginator-navigation-next'))); + } + +} + +export class ListEmptyComponent extends Component { + constructor(listComponent: ElementFinder) { + super(listComponent.element(by.css('.list-component__no-entries'))); + } + + getDefault(): Component { + return new Component(element(by.css('.list-component__default-no-entries'))); + } } /** * Page Object for the List component @@ -181,11 +258,17 @@ export class ListComponent extends Component { public header: ListHeaderComponent; + public pagination: ListPaginationComponent; + + public empty: ListEmptyComponent; + constructor(locator: ElementFinder = element(by.tagName('app-list'))) { super(locator); this.table = new ListTableComponent(locator); this.cards = new ListCardComponent(locator); this.header = new ListHeaderComponent(locator); + this.pagination = new ListPaginationComponent(locator); + this.empty = new ListEmptyComponent(locator); } isTableView(): promise.Promise { @@ -205,10 +288,10 @@ export class ListComponent extends Component { } getTotalResults() { - const paginator = new PaginatorComponent(); - return paginator.isDisplayed().then(havePaginator => { + // const paginator = new PaginatorComponent(); + return this.pagination.isDisplayed().then(havePaginator => { if (havePaginator) { - return paginator.getTotalResults(); + return this.pagination.getTotalResults(); } return this.isCardsView().then(haveCardsView => { if (haveCardsView) { diff --git a/src/test-e2e/po/paginator.po.ts b/src/test-e2e/po/paginator.po.ts deleted file mode 100644 index 0bc9502cd0..0000000000 --- a/src/test-e2e/po/paginator.po.ts +++ /dev/null @@ -1,26 +0,0 @@ -// -import { protractor, ElementFinder, ElementArrayFinder } from 'protractor/built'; -import { browser, element, by, promise } from 'protractor'; -import { Component } from './component.po'; - -/** - * Page Object for paginator component - */ -export class PaginatorComponent extends Component { - - constructor() { - super(element(by.css('.mat-paginator'))); - } - - getTotalResults() { - return this.locator.element(by.css('.mat-paginator-range-label')).getText().then(label => { - const index = label.indexOf('of '); - if (index > 0) { - const value = label.substr(index + 3).trim(); - return parseInt(value, 10); - } - return -1; - }); - } - -}