Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added integration test for "deploy not existing image" user story #420

Merged
merged 1 commit into from
Feb 29, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build/protractor.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ function createConfig() {
config.maxSessions = 1;

} else {
config.capabilities = {'browserName': 'firefox'};
config.capabilities = {'browserName': 'chrome'};
}

return config;
Expand Down
26 changes: 26 additions & 0 deletions src/test/integration/deploy/deploy_po.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.

export default class DeployPageObject {
constructor() {
this.deployButtonQuery = by.buttonText('Deploy');
this.deployButton = element(this.deployButtonQuery);

this.appNameFieldQuery = by.model('ctrl.name');
this.appNameField = element(this.appNameFieldQuery);

this.containerImageFieldQuery = by.model('ctrl.containerImage');
this.containerImageField = element(this.containerImageFieldQuery);
}
}
21 changes: 21 additions & 0 deletions src/test/integration/logs/logs_po.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.

export default class LogsPageObject {
constructor() {
this.logEntriesQuery = by.css('.kd-logs-element');
this.logEntriesTextQuery = by.css('pre');
this.logEntries = element.all(this.logEntriesQuery).all(this.logEntriesTextQuery);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.

export default class DeleteReplicationControllerDialogObject {
constructor() {
this.deleteAppButtonQuery = by.xpath('//md-dialog-actions/button[2]');
this.deleteAppButton = element(this.deleteAppButtonQuery);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.

export default class ReplicationControllerDetailPageObject {
constructor() {
this.mdTabItemsQuery = by.tagName('md-tab-item');

this.eventsTab = element.all(this.mdTabItemsQuery).get(1);
this.podsTab = element.all(this.mdTabItemsQuery).get(0);

this.mdTabsItemQuery = by.css('md-tabs');
this.mdTabsItem = element(this.mdTabsItemQuery);

this.eventsTypeFilterQuery = by.model('ctrl.eventType');
this.eventsTypeFilter = element(this.eventsTypeFilterQuery);

this.eventsTypeWarningQuery = by.css('md-option[value="Warning"]');
this.eventsTypeWarning = element(this.eventsTypeWarningQuery);

this.eventsTableQuery = by.xpath('//md-tab-content[2]//md-content/table');
this.eventsTable = element(this.eventsTableQuery);

this.podLogsLinkQuery = by.css('td[kd-responsive-header="Logs"');
this.podLogsLink = element(this.podLogsLinkQuery).element(by.css('a'));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.

export default class ReplicationControllersPageObject {
constructor() {
this.cardMenuButtonQuery =
`md-menu/button[contains(@class, 'kd-replicationcontroller-card-menu-button')]`;
this.cardErrorIconQuery = `span/md-icon[contains(@class, 'md-warn')]`;
this.cardDetailsPageLinkQuery = `a[@class='kd-replicationcontroller-card-name']`;
this.cardErrorsQuery = `span[contains(@class, 'kd-replicationcontroller-card-error')]`;
this.deleteAppButtonQuery =
by.xpath('//div[@aria-hidden="false"]/md-menu-content/md-menu-item[3]/button');

this.deleteAppButton = element(this.deleteAppButtonQuery);
}

/**
* @param {string} xpathString - xpath string starting from card 'md-card-content' tag
* @param {string} appName - app name of the card we want to get related elements from
* @param {?boolean} isArray - should be true if more than 1 element may fit queryString
* @return {!Element}
*/
getElementByAppName(xpathString, appName, isArray) {
let elemQuery = by.xpath(`//*[text()='${appName}']/ancestor::md-card-content//${xpathString}`);
if (isArray) {
return element.all(elemQuery);
}

return element(elemQuery);
}
}
175 changes: 175 additions & 0 deletions src/test/integration/stories/deploy_not_existing_img_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.

import DeployPageObject from '../deploy/deploy_po';
import DeleteReplicationControllerDialogObject from '../replicationcontrollerdetail/deletereplicationcontroller_po';
import LogsPageObject from '../logs/logs_po';
import ReplicationControllersPageObject from '../replicationcontrollerslist/replicationcontrollers_po';
import ReplicationControllerDetailPageObject from '../replicationcontrollerdetail/replicationcontrollerdetail_po';
import ZeroStatePageObject from '../zerostate/zerostate_po';

/**
* This integration test will check complete user story in given order:
* - [Zerostate Page] - go to deploy page
* - [Deploy Page] - provide data for not existing image and click deploy
* - [Replication Controller List Page] - wait for card status error to appear and go to
* details page
* - [Replication Controller Details Page] - Go to events tab, filter by warnings and check
* results
* - [Replication Controller Details Page] - Go to Pods tab and click on Logs link near the
* existing pod
* - [Logs Page] - Check if pod logs show that pod is in pending state.
* - Clean up and delete created resources
*/
describe('Deploy not existing image story', () => {
/** @type {!ZeroStatePageObject} */
let zeroStatePage;

/** @type {!DeployPageObject} */
let deployPage;

/** @type {!ReplicationControllersPageObject} */
let replicationControllersPage;

/** @type {!DeleteReplicationControllerDialogObject} */
let deleteDialog;

/** @type {!ReplicationControllerDetailPageObject} */
let replicationControllerDetailPage;

/** @type {!LogsPageObject} */
let logsPage;

let appName = 'test';
let containerImage = 'test';

beforeAll(() => {
// For empty cluster this should actually redirect to zerostate page
browser.get('#/replicationcontrollers');

zeroStatePage = new ZeroStatePageObject();
deployPage = new DeployPageObject();
replicationControllersPage = new ReplicationControllersPageObject();
deleteDialog = new DeleteReplicationControllerDialogObject();
replicationControllerDetailPage = new ReplicationControllerDetailPageObject();
logsPage = new LogsPageObject();
});

it('should go to deploy page', () => {
// when
zeroStatePage.deployButton.click();

// then
expect(browser.getCurrentUrl()).toContain('deploy');
});

it('should deploy app and go to replication controllers list page', () => {
// given
deployPage.appNameField.sendKeys(appName);
deployPage.containerImageField.sendKeys(containerImage);

// when
deployPage.deployButton.click();

// then
expect(browser.getCurrentUrl()).toContain('replicationcontrollers');
});

it('should wait for card to be in error state', () => {
// given
let cardErrors = replicationControllersPage.getElementByAppName(
replicationControllersPage.cardErrorsQuery, appName, true);
let cardErrorIcon = replicationControllersPage.getElementByAppName(
replicationControllersPage.cardErrorIconQuery, appName);

// when
browser.driver.wait(() => {
return cardErrorIcon.isPresent().then((result) => {
if (result) {
return true;
}

browser.driver.navigate().refresh();
return false;
});
});

// then
expect(cardErrorIcon.isDisplayed()).toBeTruthy();
cardErrors.then((errors) => { expect(errors.length).not.toBe(0); });
});

it('should go to details page', () => {
// given
let cardDetailsPageLink = replicationControllersPage.getElementByAppName(
replicationControllersPage.cardDetailsPageLinkQuery, appName);

// when
cardDetailsPageLink.click();

// then
expect(browser.getCurrentUrl()).toContain(`replicationcontrollers/default/${appName}`);
});

it('should switch to events tab and check for errors', () => {
// when
// Switch to events tab
replicationControllerDetailPage.eventsTab.click();

// Filter events by warnings
replicationControllerDetailPage.eventsTypeFilter.click().then(
() => { replicationControllerDetailPage.eventsTypeWarning.click(); });

// then
expect(replicationControllerDetailPage.eventsTable.isDisplayed()).toBeTruthy();
});

it('should switch to pods tab and go to pod logs page', () => {
// when
// Switch to pods tab
replicationControllerDetailPage.podsTab.click();

// Click pod log link
replicationControllerDetailPage.podLogsLink.click().then(() => {
// then
// Logs page is opened in new window so we have to switch browser focus to that window.
browser.getAllWindowHandles().then((handles) => {
let logsWindowHandle = handles[1];
browser.switchTo().window(logsWindowHandle).then(() => {
expect(browser.getCurrentUrl()).toContain(`logs/default/${appName}`);
});
});
});
});

it('pod logs should show pending state', () => {
logsPage.logEntries.then((logEntries) => {
expect(logEntries.length).toBe(1);
let logEntryText = logEntries[0].getText();
expect(logEntryText).toContain('State: "Pending"');
});
});

// Clean up and delete created resources
afterAll(() => {
let cardMenuButton = replicationControllersPage.getElementByAppName(
replicationControllersPage.cardMenuButtonQuery, appName);

browser.get('#/replicationcontrollers');

cardMenuButton.click();
replicationControllersPage.deleteAppButton.click().then(
() => { deleteDialog.deleteAppButton.click(); });
});
});
6 changes: 5 additions & 1 deletion src/test/integration/zerostate/zerostate_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ describe('Zero state view', () => {
let page;

beforeEach(() => {
browser.get('#/replicationcontrollers/zerostate');
/**
* This will be valid when cluster is empty as zerostate is child of
* replicationcontrollers state and is shown only where there are no RCs to display.
*/
browser.get('#/replicationcontrollers');
page = new PageObject();
});

Expand Down